mirror of
https://github.com/Qortal/qortal.git
synced 2025-03-26 23:44:34 +00:00
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.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
@ -31,7 +32,7 @@ public class GenesisTransaction extends Transaction {
|
|||||||
private BigDecimal amount;
|
private BigDecimal amount;
|
||||||
|
|
||||||
// Property lengths
|
// 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;
|
private static final int AMOUNT_LENGTH = 8;
|
||||||
// Note that Genesis transactions don't require reference, fee or signature:
|
// Note that Genesis transactions don't require reference, fee or signature:
|
||||||
private static final int TYPELESS_LENGTH = TIMESTAMP_LENGTH + RECIPIENT_LENGTH + AMOUNT_LENGTH;
|
private static final int TYPELESS_LENGTH = TIMESTAMP_LENGTH + RECIPIENT_LENGTH + AMOUNT_LENGTH;
|
||||||
@ -110,9 +111,15 @@ public class GenesisTransaction extends Transaction {
|
|||||||
|
|
||||||
// Converters
|
// Converters
|
||||||
|
|
||||||
public static Transaction parse(byte[] data) throws Exception {
|
protected static Transaction parse(ByteBuffer byteBuffer) throws TransactionParseException {
|
||||||
// TODO
|
if (byteBuffer.remaining() < TYPELESS_LENGTH)
|
||||||
return null;
|
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")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -3,6 +3,7 @@ package qora.transaction;
|
|||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
@ -30,7 +31,6 @@ public class PaymentTransaction extends Transaction {
|
|||||||
|
|
||||||
// Property lengths
|
// Property lengths
|
||||||
private static final int SENDER_LENGTH = 32;
|
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 AMOUNT_LENGTH = 8;
|
||||||
private static final int TYPELESS_LENGTH = BASE_TYPELESS_LENGTH + SENDER_LENGTH + RECIPIENT_LENGTH + AMOUNT_LENGTH;
|
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
|
// Converters
|
||||||
|
|
||||||
public static Transaction parse(byte[] data) throws Exception {
|
protected static Transaction parse(ByteBuffer byteBuffer) throws TransactionParseException {
|
||||||
// TODO
|
// 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")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -2,6 +2,7 @@ package qora.transaction;
|
|||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.MathContext;
|
import java.math.MathContext;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.PreparedStatement;
|
import java.sql.PreparedStatement;
|
||||||
import java.sql.ResultSet;
|
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;
|
protected static final int BASE_TYPELESS_LENGTH = TIMESTAMP_LENGTH + REFERENCE_LENGTH + FEE_LENGTH + SIGNATURE_LENGTH;
|
||||||
|
|
||||||
// Other length constants
|
// 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
|
// Constructors
|
||||||
|
|
||||||
@ -283,6 +285,31 @@ public abstract class Transaction {
|
|||||||
|
|
||||||
// Converters
|
// 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;
|
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;
|
package utils;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.math.BigInteger;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import qora.account.PublicKeyAccount;
|
||||||
|
import qora.transaction.Transaction;
|
||||||
|
|
||||||
public class Serialization {
|
public class Serialization {
|
||||||
|
|
||||||
@ -17,4 +22,22 @@ public class Serialization {
|
|||||||
return output;
|
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…
x
Reference in New Issue
Block a user