qortal/src/test/TransactionTests.java
catbref 4a1c3821db 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.
2018-06-15 17:16:44 +01:00

129 lines
5.3 KiB
Java

package test;
import static org.junit.Assert.*;
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.PaymentTransactionData;
import qora.account.Account;
import qora.account.PrivateKeyAccount;
import qora.account.PublicKeyAccount;
import qora.assets.Asset;
import qora.block.Block;
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 repository.hsqldb.HSQLDBRepositoryFactory;
import utils.NTP;
// 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 testPaymentTransactions() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
assertEquals("Blockchain should be empty for this test", 0, repository.getBlockRepository().getBlockchainHeight());
}
// This needs to be called outside of acquiring our own repository or it will deadlock
BlockChain.validate();
try (final Repository repository = RepositoryManager.getRepository()) {
// Grab genesis block
BlockData genesisBlockData = repository.getBlockRepository().fromHeight(1);
AccountRepository accountRepository = repository.getAccountRepository();
// 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));
// 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);
}
}
}