mirror of
https://github.com/Qortal/qortal.git
synced 2025-07-23 04:36:50 +00:00
Progess on block and transaction processing + tidying up
* Code added for calculating an account's generating balance. (CIYAM AT support yet to be added). * Added associated code in Block for calculating next block's timestamp, generating balance, base target, etc. * ValidationResult enum added to Block, mostly to aid debugging. * Block.isValid() now returns ValidationResult instead of boolean. * Block.isValid() now has added proof-of-stake tests. * Some blockchain-related constants, like feature release heights/timestamps, moved from Block to BlockChain. * Added better Block constructor for use when creating a new block. * Added helpful 'navigation' methods to Block to get to block's parent (or child). * Changed visibility of block's individual signature calculators to protected, in favour of public sign() method. * Added asset existence check to Payment.isValid. * All current transaction objects (qora.transaction.*) now have private subclassed transaction variable to save multiple casts in various methods. * Also added to above: * isInvolved(Account) : boolean * getRecipients() : List<Account> * getAmount(Account) : BigDecimal * Added BlockRepository.getLastBlock() to fetch highest block in blockchain. * Added diagnostics to HSQLDBRepository.close() to alert if there are any uncommitted changes during closure. (Currently under suspicion due to possible HSQLDB bug!) * Old "TransactionTests" renamed to "SerializationTests" as that's what they really are. * New "TransactionTests" added to test processing of transactions. (Currently only a PaymentTransaction). * PaymentTransformer.toBytes() detects and skips null signature. This was causing issues with Transaction.toBytesLessSignature(). Needs rolling out to other transaction types if acceptable.
This commit is contained in:
@@ -12,6 +12,7 @@ import org.junit.Test;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.account.Account;
|
||||
import qora.assets.Asset;
|
||||
import qora.block.Block;
|
||||
import qora.block.GenesisBlock;
|
||||
import qora.transaction.Transaction;
|
||||
import repository.DataException;
|
||||
@@ -46,7 +47,7 @@ public class GenesisTests {
|
||||
assertNotNull(block);
|
||||
assertTrue(block.isSignatureValid());
|
||||
// Note: only true if blockchain is empty
|
||||
assertTrue(block.isValid());
|
||||
assertEquals(Block.ValidationResult.OK, block.isValid());
|
||||
|
||||
List<Transaction> transactions = block.getTransactions();
|
||||
assertNotNull(transactions);
|
||||
|
93
src/test/SerializationTests.java
Normal file
93
src/test/SerializationTests.java
Normal file
@@ -0,0 +1,93 @@
|
||||
package test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import data.block.BlockData;
|
||||
import data.transaction.GenesisTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
import qora.block.Block;
|
||||
import qora.block.GenesisBlock;
|
||||
import qora.transaction.GenesisTransaction;
|
||||
import qora.transaction.Transaction;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
import repository.RepositoryManager;
|
||||
import transform.TransformationException;
|
||||
import transform.transaction.TransactionTransformer;
|
||||
|
||||
public class SerializationTests extends Common {
|
||||
|
||||
@Test
|
||||
public void testGenesisSerialization() throws TransformationException, DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
GenesisBlock block = new GenesisBlock(repository);
|
||||
|
||||
GenesisTransaction transaction = (GenesisTransaction) block.getTransactions().get(1);
|
||||
assertNotNull(transaction);
|
||||
|
||||
GenesisTransactionData genesisTransactionData = (GenesisTransactionData) transaction.getTransactionData();
|
||||
|
||||
System.out.println(genesisTransactionData.getTimestamp() + ": " + genesisTransactionData.getRecipient() + " received "
|
||||
+ genesisTransactionData.getAmount().toPlainString());
|
||||
|
||||
byte[] bytes = TransactionTransformer.toBytes(genesisTransactionData);
|
||||
|
||||
GenesisTransactionData parsedTransactionData = (GenesisTransactionData) TransactionTransformer.fromBytes(bytes);
|
||||
|
||||
System.out.println(parsedTransactionData.getTimestamp() + ": " + parsedTransactionData.getRecipient() + " received "
|
||||
+ parsedTransactionData.getAmount().toPlainString());
|
||||
|
||||
/*
|
||||
* NOTE: parsedTransactionData.getSignature() will be null as no signature is present in serialized bytes and calculating the signature is performed
|
||||
* by GenesisTransaction, not GenesisTransactionData
|
||||
*/
|
||||
// Not applicable: assertTrue(Arrays.equals(genesisTransactionData.getSignature(), parsedTransactionData.getSignature()));
|
||||
|
||||
GenesisTransaction parsedTransaction = new GenesisTransaction(repository, parsedTransactionData);
|
||||
assertTrue(Arrays.equals(genesisTransactionData.getSignature(), parsedTransaction.getTransactionData().getSignature()));
|
||||
}
|
||||
}
|
||||
|
||||
private void testGenericSerialization(TransactionData transactionData) throws TransformationException {
|
||||
assertNotNull(transactionData);
|
||||
|
||||
byte[] bytes = TransactionTransformer.toBytes(transactionData);
|
||||
|
||||
TransactionData parsedTransactionData = TransactionTransformer.fromBytes(bytes);
|
||||
|
||||
assertTrue(Arrays.equals(transactionData.getSignature(), parsedTransactionData.getSignature()));
|
||||
|
||||
assertEquals(TransactionTransformer.getDataLength(transactionData), bytes.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPaymentSerialization() throws TransformationException, DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Block 949 has lots of varied transactions
|
||||
// Blocks 390 & 754 have only payment transactions
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(754);
|
||||
assertNotNull("Block 754 is required for this test", blockData);
|
||||
|
||||
Block block = new Block(repository, blockData);
|
||||
|
||||
List<Transaction> transactions = block.getTransactions();
|
||||
assertNotNull(transactions);
|
||||
|
||||
for (Transaction transaction : transactions)
|
||||
testGenericSerialization(transaction.getTransactionData());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageSerialization() throws SQLException, TransformationException {
|
||||
// Message transactions went live block 99000
|
||||
// Some transactions to be found in block 99001/2/5/6
|
||||
}
|
||||
|
||||
}
|
@@ -53,9 +53,7 @@ public class SignatureTests extends Common {
|
||||
BigDecimal atFees = null;
|
||||
|
||||
Block block = new Block(repository, version, reference, timestamp, generatingBalance, generator, atBytes, atFees);
|
||||
|
||||
block.calcGeneratorSignature();
|
||||
block.calcTransactionsSignature();
|
||||
block.sign();
|
||||
|
||||
assertTrue(block.isSignatureValid());
|
||||
}
|
||||
|
@@ -2,92 +2,128 @@ package test;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import com.google.common.hash.HashCode;
|
||||
|
||||
import data.account.AccountBalanceData;
|
||||
import data.account.AccountData;
|
||||
import data.block.BlockData;
|
||||
import data.transaction.GenesisTransactionData;
|
||||
import data.transaction.TransactionData;
|
||||
import data.transaction.PaymentTransactionData;
|
||||
import qora.account.Account;
|
||||
import qora.account.PrivateKeyAccount;
|
||||
import qora.account.PublicKeyAccount;
|
||||
import qora.assets.Asset;
|
||||
import qora.block.Block;
|
||||
import qora.block.GenesisBlock;
|
||||
import qora.transaction.GenesisTransaction;
|
||||
import qora.block.BlockChain;
|
||||
import qora.transaction.PaymentTransaction;
|
||||
import qora.transaction.Transaction;
|
||||
import qora.transaction.Transaction.ValidationResult;
|
||||
import repository.AccountRepository;
|
||||
import repository.DataException;
|
||||
import repository.Repository;
|
||||
import repository.RepositoryFactory;
|
||||
import repository.RepositoryManager;
|
||||
import transform.TransformationException;
|
||||
import transform.transaction.TransactionTransformer;
|
||||
import repository.hsqldb.HSQLDBRepositoryFactory;
|
||||
import utils.NTP;
|
||||
|
||||
public class TransactionTests extends Common {
|
||||
// Don't extend Common as we want to use an in-memory database
|
||||
public class TransactionTests {
|
||||
|
||||
private static final String connectionUrl = "jdbc:hsqldb:mem:db/test;create=true;close_result=true;sql.strict_exec=true;sql.enforce_names=true;sql.syntax_mys=true";
|
||||
|
||||
private static final byte[] generatorSeed = HashCode.fromString("0123456789abcdeffedcba98765432100123456789abcdeffedcba9876543210").asBytes();
|
||||
private static final byte[] senderSeed = HashCode.fromString("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef").asBytes();
|
||||
private static final byte[] recipientSeed = HashCode.fromString("fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210").asBytes();
|
||||
|
||||
@BeforeClass
|
||||
public static void setRepository() throws DataException {
|
||||
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl);
|
||||
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void closeRepository() throws DataException {
|
||||
RepositoryManager.closeRepositoryFactory();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGenesisSerialization() throws TransformationException, DataException {
|
||||
public void testPaymentTransactions() throws DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
GenesisBlock block = new GenesisBlock(repository);
|
||||
|
||||
GenesisTransaction transaction = (GenesisTransaction) block.getTransactions().get(1);
|
||||
assertNotNull(transaction);
|
||||
|
||||
GenesisTransactionData genesisTransactionData = (GenesisTransactionData) transaction.getTransactionData();
|
||||
|
||||
System.out.println(genesisTransactionData.getTimestamp() + ": " + genesisTransactionData.getRecipient() + " received "
|
||||
+ genesisTransactionData.getAmount().toPlainString());
|
||||
|
||||
byte[] bytes = TransactionTransformer.toBytes(genesisTransactionData);
|
||||
|
||||
GenesisTransactionData parsedTransactionData = (GenesisTransactionData) TransactionTransformer.fromBytes(bytes);
|
||||
|
||||
System.out.println(parsedTransactionData.getTimestamp() + ": " + parsedTransactionData.getRecipient() + " received "
|
||||
+ parsedTransactionData.getAmount().toPlainString());
|
||||
|
||||
/*
|
||||
* NOTE: parsedTransactionData.getSignature() will be null as no signature is present in serialized bytes and calculating the signature is performed
|
||||
* by GenesisTransaction, not GenesisTransactionData
|
||||
*/
|
||||
// Not applicable: assertTrue(Arrays.equals(genesisTransactionData.getSignature(), parsedTransactionData.getSignature()));
|
||||
|
||||
GenesisTransaction parsedTransaction = new GenesisTransaction(repository, parsedTransactionData);
|
||||
assertTrue(Arrays.equals(genesisTransactionData.getSignature(), parsedTransaction.getTransactionData().getSignature()));
|
||||
assertEquals("Blockchain should be empty for this test", 0, repository.getBlockRepository().getBlockchainHeight());
|
||||
}
|
||||
}
|
||||
|
||||
private void testGenericSerialization(TransactionData transactionData) throws TransformationException {
|
||||
assertNotNull(transactionData);
|
||||
// This needs to be called outside of acquiring our own repository or it will deadlock
|
||||
BlockChain.validate();
|
||||
|
||||
byte[] bytes = TransactionTransformer.toBytes(transactionData);
|
||||
|
||||
TransactionData parsedTransactionData = TransactionTransformer.fromBytes(bytes);
|
||||
|
||||
assertTrue(Arrays.equals(transactionData.getSignature(), parsedTransactionData.getSignature()));
|
||||
|
||||
assertEquals(TransactionTransformer.getDataLength(transactionData), bytes.length);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPaymentSerialization() throws TransformationException, DataException {
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
// Block 949 has lots of varied transactions
|
||||
// Blocks 390 & 754 have only payment transactions
|
||||
BlockData blockData = repository.getBlockRepository().fromHeight(754);
|
||||
assertNotNull("Block 754 is required for this test", blockData);
|
||||
// Grab genesis block
|
||||
BlockData genesisBlockData = repository.getBlockRepository().fromHeight(1);
|
||||
|
||||
Block block = new Block(repository, blockData);
|
||||
AccountRepository accountRepository = repository.getAccountRepository();
|
||||
|
||||
List<Transaction> transactions = block.getTransactions();
|
||||
assertNotNull(transactions);
|
||||
// Create test generator account
|
||||
BigDecimal generatorBalance = BigDecimal.valueOf(1_000_000_000L);
|
||||
PrivateKeyAccount generator = new PrivateKeyAccount(repository, generatorSeed);
|
||||
accountRepository.save(new AccountData(generator.getAddress(), generatorSeed));
|
||||
accountRepository.save(new AccountBalanceData(generator.getAddress(), Asset.QORA, generatorBalance));
|
||||
|
||||
for (Transaction transaction : transactions)
|
||||
testGenericSerialization(transaction.getTransactionData());
|
||||
// Create test sender account
|
||||
PrivateKeyAccount sender = new PrivateKeyAccount(repository, senderSeed);
|
||||
|
||||
// Mock account
|
||||
byte[] reference = senderSeed;
|
||||
accountRepository.save(new AccountData(sender.getAddress(), reference));
|
||||
|
||||
// Mock balance
|
||||
BigDecimal initialBalance = BigDecimal.valueOf(1_000_000L);
|
||||
accountRepository.save(new AccountBalanceData(sender.getAddress(), Asset.QORA, initialBalance));
|
||||
|
||||
repository.saveChanges();
|
||||
|
||||
// Make a new payment transaction
|
||||
Account recipient = new PublicKeyAccount(repository, recipientSeed);
|
||||
BigDecimal amount = BigDecimal.valueOf(1_000L);
|
||||
BigDecimal fee = BigDecimal.ONE;
|
||||
long timestamp = genesisBlockData.getTimestamp() + 1_000;
|
||||
PaymentTransactionData paymentTransactionData = new PaymentTransactionData(sender.getPublicKey(), recipient.getAddress(), amount, fee, timestamp,
|
||||
reference);
|
||||
|
||||
Transaction paymentTransaction = new PaymentTransaction(repository, paymentTransactionData);
|
||||
paymentTransaction.calcSignature(sender);
|
||||
assertTrue(paymentTransaction.isSignatureValid());
|
||||
assertEquals(ValidationResult.OK, paymentTransaction.isValid());
|
||||
|
||||
// Forge new block with payment transaction
|
||||
Block block = new Block(repository, genesisBlockData, generator, null, null);
|
||||
block.addTransaction(paymentTransactionData);
|
||||
block.sign();
|
||||
|
||||
assertTrue("Block signatures invalid", block.isSignatureValid());
|
||||
assertEquals("Block is invalid", Block.ValidationResult.OK, block.isValid());
|
||||
|
||||
block.process();
|
||||
repository.saveChanges();
|
||||
|
||||
// Check sender's balance
|
||||
BigDecimal expectedBalance = initialBalance.subtract(amount).subtract(fee);
|
||||
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
||||
assertTrue("Sender's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||
|
||||
// Fee should be in generator's balance
|
||||
expectedBalance = generatorBalance.add(fee);
|
||||
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
||||
assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||
|
||||
// Amount should be in recipient's balance
|
||||
expectedBalance = amount;
|
||||
actualBalance = accountRepository.getBalance(recipient.getAddress(), Asset.QORA).getBalance();
|
||||
assertTrue("Recipient's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMessageSerialization() throws SQLException, TransformationException {
|
||||
// Message transactions went live block 99000
|
||||
// Some transactions to be found in block 99001/2/5/6
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user