forked from Qortal/qortal
Some serialization
Add Transaction.parse() support. Subclasses are called, switched by transaction type, using ByteBuffer. Correct RECIPIENT_LENGTH in Transaction. Common [de]serialization tasks moved to methods in Serialization class.
This commit is contained in:
parent
015f4fa725
commit
387c18c909
@ -3,6 +3,7 @@ package qora.transaction;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
@ -31,7 +32,7 @@ public class GenesisTransaction extends Transaction {
|
||||
private BigDecimal amount;
|
||||
|
||||
// Property lengths
|
||||
private static final int RECIPIENT_LENGTH = 32;
|
||||
private static final int RECIPIENT_LENGTH = 25; // raw, not Base58-encoded
|
||||
private static final int AMOUNT_LENGTH = 8;
|
||||
// Note that Genesis transactions don't require reference, fee or signature:
|
||||
private static final int TYPELESS_LENGTH = TIMESTAMP_LENGTH + RECIPIENT_LENGTH + AMOUNT_LENGTH;
|
||||
@ -110,9 +111,15 @@ public class GenesisTransaction extends Transaction {
|
||||
|
||||
// Converters
|
||||
|
||||
public static Transaction parse(byte[] data) throws Exception {
|
||||
// TODO
|
||||
return null;
|
||||
protected static Transaction parse(ByteBuffer byteBuffer) throws TransactionParseException {
|
||||
if (byteBuffer.remaining() < TYPELESS_LENGTH)
|
||||
throw new TransactionParseException("Byte data too short for GenesisTransaction");
|
||||
|
||||
long timestamp = byteBuffer.getLong();
|
||||
String recipient = Serialization.deserializeRecipient(byteBuffer);
|
||||
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
|
||||
|
||||
return new GenesisTransaction(recipient, amount, timestamp);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -3,6 +3,7 @@ package qora.transaction;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
@ -30,7 +31,6 @@ public class PaymentTransaction extends Transaction {
|
||||
|
||||
// Property lengths
|
||||
private static final int SENDER_LENGTH = 32;
|
||||
private static final int RECIPIENT_LENGTH = 32;
|
||||
private static final int AMOUNT_LENGTH = 8;
|
||||
private static final int TYPELESS_LENGTH = BASE_TYPELESS_LENGTH + SENDER_LENGTH + RECIPIENT_LENGTH + AMOUNT_LENGTH;
|
||||
|
||||
@ -118,9 +118,22 @@ public class PaymentTransaction extends Transaction {
|
||||
|
||||
// Converters
|
||||
|
||||
public static Transaction parse(byte[] data) throws Exception {
|
||||
protected static Transaction parse(ByteBuffer byteBuffer) throws TransactionParseException {
|
||||
// TODO
|
||||
return null;
|
||||
if (byteBuffer.remaining() < TYPELESS_LENGTH)
|
||||
throw new TransactionParseException("Byte data too short for PaymentTransaction");
|
||||
|
||||
long timestamp = byteBuffer.getLong();
|
||||
byte[] reference = new byte[REFERENCE_LENGTH];
|
||||
byteBuffer.get(reference);
|
||||
PublicKeyAccount sender = Serialization.deserializePublicKey(byteBuffer);
|
||||
String recipient = Serialization.deserializeRecipient(byteBuffer);
|
||||
BigDecimal amount = Serialization.deserializeBigDecimal(byteBuffer);
|
||||
BigDecimal fee = Serialization.deserializeBigDecimal(byteBuffer);
|
||||
byte[] signature = new byte[SIGNATURE_LENGTH];
|
||||
byteBuffer.get(signature);
|
||||
|
||||
return new PaymentTransaction(sender, recipient, amount, fee, timestamp, reference, signature);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -2,6 +2,7 @@ package qora.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.MathContext;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
@ -89,7 +90,8 @@ public abstract class Transaction {
|
||||
protected static final int BASE_TYPELESS_LENGTH = TIMESTAMP_LENGTH + REFERENCE_LENGTH + FEE_LENGTH + SIGNATURE_LENGTH;
|
||||
|
||||
// Other length constants
|
||||
protected static final int CREATOR_LENGTH = 32;
|
||||
public static final int CREATOR_LENGTH = 32;
|
||||
public static final int RECIPIENT_LENGTH = 25;
|
||||
|
||||
// Constructors
|
||||
|
||||
@ -283,6 +285,31 @@ public abstract class Transaction {
|
||||
|
||||
// Converters
|
||||
|
||||
public static Transaction parse(byte[] data) throws TransactionParseException {
|
||||
if (data == null)
|
||||
return null;
|
||||
|
||||
if (data.length < TYPE_LENGTH)
|
||||
throw new TransactionParseException("Byte data too short to determine transaction type");
|
||||
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(data);
|
||||
|
||||
TransactionType type = TransactionType.valueOf(byteBuffer.getInt());
|
||||
if (type == null)
|
||||
return null;
|
||||
|
||||
switch (type) {
|
||||
case GENESIS:
|
||||
return GenesisTransaction.parse(byteBuffer);
|
||||
|
||||
case PAYMENT:
|
||||
return PaymentTransaction.parse(byteBuffer);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public abstract JSONObject toJSON() throws SQLException;
|
||||
|
||||
/**
|
||||
|
10
src/qora/transaction/TransactionParseException.java
Normal file
10
src/qora/transaction/TransactionParseException.java
Normal file
@ -0,0 +1,10 @@
|
||||
package qora.transaction;
|
||||
|
||||
@SuppressWarnings("serial")
|
||||
public class TransactionParseException extends Exception {
|
||||
|
||||
public TransactionParseException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
64
src/test/transactions.java
Normal file
64
src/test/transactions.java
Normal file
@ -0,0 +1,64 @@
|
||||
package test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import database.DB;
|
||||
import qora.block.Block;
|
||||
import qora.block.GenesisBlock;
|
||||
import qora.transaction.GenesisTransaction;
|
||||
import qora.transaction.Transaction;
|
||||
import qora.transaction.TransactionParseException;
|
||||
|
||||
public class transactions extends common {
|
||||
|
||||
@Test
|
||||
public void testGenesisSerialization() throws SQLException, TransactionParseException {
|
||||
GenesisBlock block = GenesisBlock.getInstance();
|
||||
|
||||
GenesisTransaction transaction = (GenesisTransaction) block.getTransactions().get(1);
|
||||
assertNotNull(transaction);
|
||||
System.out.println(transaction.getTimestamp() + ": " + transaction.getRecipient().getAddress() + " received " + transaction.getAmount().toPlainString());
|
||||
|
||||
byte[] bytes = transaction.toBytes();
|
||||
|
||||
GenesisTransaction parsedTransaction = (GenesisTransaction) Transaction.parse(bytes);
|
||||
System.out.println(parsedTransaction.getTimestamp() + ": " + parsedTransaction.getRecipient().getAddress() + " received " + parsedTransaction.getAmount().toPlainString());
|
||||
|
||||
assertTrue(Arrays.equals(transaction.getSignature(), parsedTransaction.getSignature()));
|
||||
}
|
||||
|
||||
public void testGenericSerialization(Transaction transaction) throws SQLException, TransactionParseException {
|
||||
assertNotNull(transaction);
|
||||
|
||||
byte[] bytes = transaction.toBytes();
|
||||
|
||||
Transaction parsedTransaction = Transaction.parse(bytes);
|
||||
|
||||
assertTrue(Arrays.equals(transaction.getSignature(), parsedTransaction.getSignature()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPaymentSerialization() throws SQLException, TransactionParseException {
|
||||
try (final Connection connection = DB.getConnection()) {
|
||||
// Block 949 has lots of varied transactions
|
||||
// Blocks 390 & 754 have only payment transactions
|
||||
Block block = Block.fromHeight(754);
|
||||
assertNotNull("Block 754 is required for this test", block);
|
||||
assertTrue(block.isSignatureValid());
|
||||
|
||||
List<Transaction> transactions = block.getTransactions();
|
||||
assertNotNull(transactions);
|
||||
|
||||
for (Transaction transaction : transactions)
|
||||
testGenericSerialization(transaction);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,11 @@
|
||||
package utils;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.transaction.Transaction;
|
||||
|
||||
public class Serialization {
|
||||
|
||||
@ -17,4 +22,22 @@ public class Serialization {
|
||||
return output;
|
||||
}
|
||||
|
||||
public static BigDecimal deserializeBigDecimal(ByteBuffer byteBuffer) {
|
||||
byte[] bytes = new byte[8];
|
||||
byteBuffer.get(bytes);
|
||||
return new BigDecimal(new BigInteger(bytes), 8);
|
||||
}
|
||||
|
||||
public static String deserializeRecipient(ByteBuffer byteBuffer) {
|
||||
byte[] bytes = new byte[Transaction.RECIPIENT_LENGTH];
|
||||
byteBuffer.get(bytes);
|
||||
return Base58.encode(bytes);
|
||||
}
|
||||
|
||||
public static PublicKeyAccount deserializePublicKey(ByteBuffer byteBuffer) {
|
||||
byte[] bytes = new byte[Transaction.CREATOR_LENGTH];
|
||||
byteBuffer.get(bytes);
|
||||
return new PublicKeyAccount(bytes);
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user