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:
catbref 2018-05-24 21:08:21 +01:00
parent 015f4fa725
commit 387c18c909
6 changed files with 152 additions and 8 deletions

View File

@ -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")

View File

@ -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")

View File

@ -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;
/**

View File

@ -0,0 +1,10 @@
package qora.transaction;
@SuppressWarnings("serial")
public class TransactionParseException extends Exception {
public TransactionParseException(String message) {
super(message);
}
}

View 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);
}
}
}

View File

@ -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);
}
}