forked from Qortal/qortal
cfd8b53fc1
Added more global parameters to /admin/unused API endpoint (formally /admin/dud) and also managed to remove /admin/unused from API documentation UI. Added results slicing to /assets/all Added /assets/orderbook API call that returns open asset orders Added /assets/trades that returns successful asset trades Added POST /assets/issue stub Unified HSQLDB connectionUrl to public variable inside Controller class. Can't deploy v1 ATs with isFinished=true flag as that prevents later transactions sending messages (during import of v1 chain). Some future hard-fork code will need to set all v1 ATs to "finished". Changed DB's "TransactionRecipients" to "TransactionParticipants" to properly support API call to find all transactions 'involving' a specific address. Support code needed in Block and Transaction with some transaction-specific overrides for Genesis and AT transactions. Removed old, deprecated calls from Transaction/TransactionRepository Moved HSQLDB database properties from connection URL to explicit SQL statements in HSQLDBDatabaseUpdates. They didn't work in connection URL during DB creation anyway. Retrofitted HSQLDB Accounts table with public_key column instead of rebuilding it later. Fixed incorrect comments in IssueAssetTransactionTransformer regarding v1 serialization for signing. Re-imported v1 chain to test latest changes.
1188 lines
48 KiB
Java
1188 lines
48 KiB
Java
package test;
|
|
|
|
import org.junit.jupiter.api.Test;
|
|
import static org.junit.jupiter.api.Assertions.*;
|
|
import org.junit.jupiter.api.AfterEach;
|
|
|
|
import java.io.UnsupportedEncodingException;
|
|
import java.math.BigDecimal;
|
|
import java.math.RoundingMode;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
|
|
import org.json.simple.JSONObject;
|
|
|
|
import com.google.common.hash.HashCode;
|
|
|
|
import data.PaymentData;
|
|
import data.account.AccountBalanceData;
|
|
import data.account.AccountData;
|
|
import data.assets.AssetData;
|
|
import data.assets.OrderData;
|
|
import data.assets.TradeData;
|
|
import data.block.BlockData;
|
|
import data.naming.NameData;
|
|
import data.transaction.BuyNameTransactionData;
|
|
import data.transaction.CancelOrderTransactionData;
|
|
import data.transaction.CancelSellNameTransactionData;
|
|
import data.transaction.CreateOrderTransactionData;
|
|
import data.transaction.CreatePollTransactionData;
|
|
import data.transaction.IssueAssetTransactionData;
|
|
import data.transaction.MessageTransactionData;
|
|
import data.transaction.MultiPaymentTransactionData;
|
|
import data.transaction.PaymentTransactionData;
|
|
import data.transaction.RegisterNameTransactionData;
|
|
import data.transaction.SellNameTransactionData;
|
|
import data.transaction.TransferAssetTransactionData;
|
|
import data.transaction.UpdateNameTransactionData;
|
|
import data.transaction.VoteOnPollTransactionData;
|
|
import data.voting.PollData;
|
|
import data.voting.PollOptionData;
|
|
import data.voting.VoteOnPollData;
|
|
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.BuyNameTransaction;
|
|
import qora.transaction.CancelOrderTransaction;
|
|
import qora.transaction.CancelSellNameTransaction;
|
|
import qora.transaction.CreateOrderTransaction;
|
|
import qora.transaction.CreatePollTransaction;
|
|
import qora.transaction.IssueAssetTransaction;
|
|
import qora.transaction.MessageTransaction;
|
|
import qora.transaction.MultiPaymentTransaction;
|
|
import qora.transaction.PaymentTransaction;
|
|
import qora.transaction.RegisterNameTransaction;
|
|
import qora.transaction.SellNameTransaction;
|
|
import qora.transaction.Transaction;
|
|
import qora.transaction.Transaction.ValidationResult;
|
|
import qora.transaction.TransferAssetTransaction;
|
|
import qora.transaction.UpdateNameTransaction;
|
|
import qora.transaction.VoteOnPollTransaction;
|
|
import repository.AccountRepository;
|
|
import repository.AssetRepository;
|
|
import repository.DataException;
|
|
import repository.Repository;
|
|
import repository.RepositoryFactory;
|
|
import repository.RepositoryManager;
|
|
import repository.hsqldb.HSQLDBRepositoryFactory;
|
|
import settings.Settings;
|
|
|
|
// 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/blockchain;create=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();
|
|
|
|
private static final BigDecimal initialGeneratorBalance = BigDecimal.valueOf(1_000_000_000L).setScale(8);
|
|
private static final BigDecimal initialSenderBalance = BigDecimal.valueOf(1_000_000L).setScale(8);
|
|
private static final BigDecimal genericPaymentAmount = BigDecimal.valueOf(1_000L).setScale(8);
|
|
|
|
private Repository repository;
|
|
private AccountRepository accountRepository;
|
|
private BlockData parentBlockData;
|
|
private PrivateKeyAccount sender;
|
|
private PrivateKeyAccount generator;
|
|
private byte[] reference;
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public void createTestAccounts(Long genesisTimestamp) throws DataException {
|
|
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl);
|
|
RepositoryManager.setRepositoryFactory(repositoryFactory);
|
|
|
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
|
assertEquals(0, repository.getBlockRepository().getBlockchainHeight(), "Blockchain should be empty for this test");
|
|
}
|
|
|
|
// [Un]set genesis timestamp as required by test
|
|
JSONObject settingsJSON = new JSONObject();
|
|
if (genesisTimestamp != null)
|
|
settingsJSON.put("testnetstamp", genesisTimestamp);
|
|
|
|
Settings.test(settingsJSON);
|
|
|
|
// This needs to be called outside of acquiring our own repository or it will deadlock
|
|
BlockChain.validate();
|
|
|
|
// Grab repository for further use, including during test itself
|
|
repository = RepositoryManager.getRepository();
|
|
|
|
// Grab genesis block
|
|
parentBlockData = repository.getBlockRepository().fromHeight(1);
|
|
|
|
accountRepository = repository.getAccountRepository();
|
|
|
|
// Create test generator account
|
|
generator = new PrivateKeyAccount(repository, generatorSeed);
|
|
accountRepository.save(new AccountData(generator.getAddress(), generatorSeed, generator.getPublicKey()));
|
|
accountRepository.save(new AccountBalanceData(generator.getAddress(), Asset.QORA, initialGeneratorBalance));
|
|
|
|
// Create test sender account
|
|
sender = new PrivateKeyAccount(repository, senderSeed);
|
|
|
|
// Mock account
|
|
reference = senderSeed;
|
|
accountRepository.save(new AccountData(sender.getAddress(), reference, sender.getPublicKey()));
|
|
|
|
// Mock balance
|
|
accountRepository.save(new AccountBalanceData(sender.getAddress(), Asset.QORA, initialSenderBalance));
|
|
|
|
repository.saveChanges();
|
|
}
|
|
|
|
@AfterEach
|
|
public void closeRepository() throws DataException {
|
|
RepositoryManager.closeRepositoryFactory();
|
|
}
|
|
|
|
private Transaction createPayment(PrivateKeyAccount sender, String recipient) throws DataException {
|
|
// Make a new payment transaction
|
|
BigDecimal amount = genericPaymentAmount;
|
|
BigDecimal fee = BigDecimal.ONE;
|
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
|
PaymentTransactionData paymentTransactionData = new PaymentTransactionData(sender.getPublicKey(), recipient, amount, fee, timestamp, reference);
|
|
|
|
Transaction paymentTransaction = new PaymentTransaction(repository, paymentTransactionData);
|
|
paymentTransaction.sign(sender);
|
|
|
|
return paymentTransaction;
|
|
}
|
|
|
|
@Test
|
|
public void testPaymentTransaction() throws DataException {
|
|
createTestAccounts(null);
|
|
|
|
// Make a new payment transaction
|
|
Account recipient = new PublicKeyAccount(repository, recipientSeed);
|
|
BigDecimal amount = BigDecimal.valueOf(1_000L);
|
|
BigDecimal fee = BigDecimal.ONE;
|
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
|
PaymentTransactionData paymentTransactionData = new PaymentTransactionData(sender.getPublicKey(), recipient.getAddress(), amount, fee, timestamp,
|
|
reference);
|
|
|
|
Transaction paymentTransaction = new PaymentTransaction(repository, paymentTransactionData);
|
|
paymentTransaction.sign(sender);
|
|
assertTrue(paymentTransaction.isSignatureValid());
|
|
assertEquals(ValidationResult.OK, paymentTransaction.isValid());
|
|
|
|
// Forge new block with transaction
|
|
Block block = new Block(repository, parentBlockData, generator);
|
|
block.addTransaction(paymentTransactionData);
|
|
block.sign();
|
|
|
|
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
|
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
|
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Check sender's balance
|
|
BigDecimal expectedBalance = initialSenderBalance.subtract(amount).subtract(fee);
|
|
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Sender's new balance incorrect");
|
|
|
|
// Fee should be in generator's balance
|
|
expectedBalance = initialGeneratorBalance.add(fee);
|
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Generator's new balance incorrect");
|
|
|
|
// Amount should be in recipient's balance
|
|
expectedBalance = amount;
|
|
actualBalance = accountRepository.getBalance(recipient.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Recipient's new balance incorrect");
|
|
|
|
// Check recipient's reference
|
|
byte[] recipientsReference = recipient.getLastReference();
|
|
assertTrue(Arrays.equals(paymentTransaction.getTransactionData().getSignature(), recipientsReference), "Recipient's new reference incorrect");
|
|
|
|
// Orphan block
|
|
block.orphan();
|
|
repository.saveChanges();
|
|
|
|
// Check sender's balance
|
|
actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(initialSenderBalance.compareTo(actualBalance) == 0, "Sender's reverted balance incorrect");
|
|
|
|
// Check generator's balance
|
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(initialGeneratorBalance.compareTo(actualBalance) == 0, "Generator's new balance incorrect");
|
|
}
|
|
|
|
@Test
|
|
public void testRegisterNameTransaction() throws DataException {
|
|
createTestAccounts(null);
|
|
|
|
// Make a new register name transaction
|
|
String name = "test name";
|
|
String data = "{\"key\":\"value\"}";
|
|
|
|
BigDecimal fee = BigDecimal.ONE;
|
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
|
RegisterNameTransactionData registerNameTransactionData = new RegisterNameTransactionData(sender.getPublicKey(), sender.getAddress(), name, data, fee,
|
|
timestamp, reference);
|
|
|
|
Transaction registerNameTransaction = new RegisterNameTransaction(repository, registerNameTransactionData);
|
|
registerNameTransaction.sign(sender);
|
|
assertTrue(registerNameTransaction.isSignatureValid());
|
|
assertEquals(ValidationResult.OK, registerNameTransaction.isValid());
|
|
|
|
// Forge new block with transaction
|
|
Block block = new Block(repository, parentBlockData, generator);
|
|
block.addTransaction(registerNameTransactionData);
|
|
block.sign();
|
|
|
|
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
|
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
|
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Check sender's balance
|
|
BigDecimal expectedBalance = initialSenderBalance.subtract(fee);
|
|
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Sender's new balance incorrect");
|
|
|
|
// Fee should be in generator's balance
|
|
expectedBalance = initialGeneratorBalance.add(fee);
|
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Generator's new balance incorrect");
|
|
|
|
// Check name was registered
|
|
NameData actualNameData = this.repository.getNameRepository().fromName(name);
|
|
assertNotNull(actualNameData);
|
|
|
|
// Check sender's reference
|
|
assertTrue(Arrays.equals(registerNameTransactionData.getSignature(), sender.getLastReference()), "Sender's new reference incorrect");
|
|
|
|
// Update variables for use by other tests
|
|
reference = sender.getLastReference();
|
|
parentBlockData = block.getBlockData();
|
|
}
|
|
|
|
@Test
|
|
public void testUpdateNameTransaction() throws DataException {
|
|
// Register name using another test
|
|
testRegisterNameTransaction();
|
|
|
|
String name = "test name";
|
|
NameData originalNameData = this.repository.getNameRepository().fromName(name);
|
|
|
|
// Update name's owner and data
|
|
Account newOwner = new PublicKeyAccount(repository, recipientSeed);
|
|
String newData = "{\"newKey\":\"newValue\"}";
|
|
byte[] nameReference = reference;
|
|
|
|
BigDecimal fee = BigDecimal.ONE;
|
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
|
UpdateNameTransactionData updateNameTransactionData = new UpdateNameTransactionData(sender.getPublicKey(), newOwner.getAddress(), name, newData,
|
|
nameReference, fee, timestamp, reference);
|
|
|
|
Transaction updateNameTransaction = new UpdateNameTransaction(repository, updateNameTransactionData);
|
|
updateNameTransaction.sign(sender);
|
|
assertTrue(updateNameTransaction.isSignatureValid());
|
|
assertEquals(ValidationResult.OK, updateNameTransaction.isValid());
|
|
|
|
// Forge new block with transaction
|
|
Block block = new Block(repository, parentBlockData, generator);
|
|
block.addTransaction(updateNameTransactionData);
|
|
block.sign();
|
|
|
|
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
|
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
|
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Check name was updated
|
|
NameData actualNameData = this.repository.getNameRepository().fromName(name);
|
|
assertEquals(newOwner.getAddress(), actualNameData.getOwner());
|
|
assertEquals(newData, actualNameData.getData());
|
|
|
|
// Now orphan block
|
|
block.orphan();
|
|
repository.saveChanges();
|
|
|
|
// Check name has been reverted correctly
|
|
actualNameData = this.repository.getNameRepository().fromName(name);
|
|
assertEquals(originalNameData.getOwner(), actualNameData.getOwner());
|
|
assertEquals(originalNameData.getData(), actualNameData.getData());
|
|
}
|
|
|
|
@Test
|
|
public void testSellNameTransaction() throws DataException {
|
|
// Register name using another test
|
|
testRegisterNameTransaction();
|
|
|
|
String name = "test name";
|
|
|
|
// Sale price
|
|
BigDecimal amount = BigDecimal.valueOf(1234L).setScale(8);
|
|
|
|
BigDecimal fee = BigDecimal.ONE;
|
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
|
SellNameTransactionData sellNameTransactionData = new SellNameTransactionData(sender.getPublicKey(), name, amount, fee, timestamp, reference);
|
|
|
|
Transaction sellNameTransaction = new SellNameTransaction(repository, sellNameTransactionData);
|
|
sellNameTransaction.sign(sender);
|
|
assertTrue(sellNameTransaction.isSignatureValid());
|
|
assertEquals(ValidationResult.OK, sellNameTransaction.isValid());
|
|
|
|
// Forge new block with transaction
|
|
Block block = new Block(repository, parentBlockData, generator);
|
|
block.addTransaction(sellNameTransactionData);
|
|
block.sign();
|
|
|
|
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
|
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
|
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Check name was updated
|
|
NameData actualNameData = this.repository.getNameRepository().fromName(name);
|
|
assertTrue(actualNameData.getIsForSale());
|
|
assertEquals(amount, actualNameData.getSalePrice());
|
|
|
|
// Now orphan block
|
|
block.orphan();
|
|
repository.saveChanges();
|
|
|
|
// Check name has been reverted correctly
|
|
actualNameData = this.repository.getNameRepository().fromName(name);
|
|
assertFalse(actualNameData.getIsForSale());
|
|
assertNull(actualNameData.getSalePrice());
|
|
|
|
// Re-process block for use by other tests
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Update variables for use by other tests
|
|
reference = sender.getLastReference();
|
|
parentBlockData = block.getBlockData();
|
|
}
|
|
|
|
@Test
|
|
public void testCancelSellNameTransaction() throws DataException {
|
|
// Register and sell name using another test
|
|
testSellNameTransaction();
|
|
|
|
String name = "test name";
|
|
NameData originalNameData = this.repository.getNameRepository().fromName(name);
|
|
|
|
BigDecimal fee = BigDecimal.ONE;
|
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
|
CancelSellNameTransactionData cancelSellNameTransactionData = new CancelSellNameTransactionData(sender.getPublicKey(), name, fee, timestamp, reference);
|
|
|
|
Transaction cancelSellNameTransaction = new CancelSellNameTransaction(repository, cancelSellNameTransactionData);
|
|
cancelSellNameTransaction.sign(sender);
|
|
assertTrue(cancelSellNameTransaction.isSignatureValid());
|
|
assertEquals(ValidationResult.OK, cancelSellNameTransaction.isValid());
|
|
|
|
// Forge new block with transaction
|
|
Block block = new Block(repository, parentBlockData, generator);
|
|
block.addTransaction(cancelSellNameTransactionData);
|
|
block.sign();
|
|
|
|
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
|
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
|
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Check name was updated
|
|
NameData actualNameData = this.repository.getNameRepository().fromName(name);
|
|
assertFalse(actualNameData.getIsForSale());
|
|
assertEquals(originalNameData.getSalePrice(), actualNameData.getSalePrice());
|
|
|
|
// Now orphan block
|
|
block.orphan();
|
|
repository.saveChanges();
|
|
|
|
// Check name has been reverted correctly
|
|
actualNameData = this.repository.getNameRepository().fromName(name);
|
|
assertTrue(actualNameData.getIsForSale());
|
|
assertEquals(originalNameData.getSalePrice(), actualNameData.getSalePrice());
|
|
|
|
// Update variables for use by other tests
|
|
reference = sender.getLastReference();
|
|
parentBlockData = block.getBlockData();
|
|
}
|
|
|
|
@Test
|
|
public void testBuyNameTransaction() throws DataException {
|
|
// Register and sell name using another test
|
|
testSellNameTransaction();
|
|
|
|
String name = "test name";
|
|
NameData originalNameData = this.repository.getNameRepository().fromName(name);
|
|
String seller = originalNameData.getOwner();
|
|
|
|
// Buyer
|
|
PrivateKeyAccount buyer = new PrivateKeyAccount(repository, recipientSeed);
|
|
byte[] nameReference = reference;
|
|
|
|
// Send buyer some funds so they have a reference
|
|
Transaction somePaymentTransaction = createPayment(sender, buyer.getAddress());
|
|
byte[] buyersReference = somePaymentTransaction.getTransactionData().getSignature();
|
|
|
|
// Forge new block with transaction
|
|
Block block = new Block(repository, parentBlockData, generator);
|
|
block.addTransaction(somePaymentTransaction.getTransactionData());
|
|
block.sign();
|
|
|
|
block.process();
|
|
repository.saveChanges();
|
|
parentBlockData = block.getBlockData();
|
|
|
|
BigDecimal fee = BigDecimal.ONE;
|
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
|
BuyNameTransactionData buyNameTransactionData = new BuyNameTransactionData(buyer.getPublicKey(), name, originalNameData.getSalePrice(), seller,
|
|
nameReference, fee, timestamp, buyersReference);
|
|
|
|
Transaction buyNameTransaction = new BuyNameTransaction(repository, buyNameTransactionData);
|
|
buyNameTransaction.sign(buyer);
|
|
assertTrue(buyNameTransaction.isSignatureValid());
|
|
assertEquals(ValidationResult.OK, buyNameTransaction.isValid());
|
|
|
|
// Forge new block with transaction
|
|
block = new Block(repository, parentBlockData, generator);
|
|
block.addTransaction(buyNameTransactionData);
|
|
block.sign();
|
|
|
|
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
|
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
|
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Check name was updated
|
|
NameData actualNameData = this.repository.getNameRepository().fromName(name);
|
|
assertFalse(actualNameData.getIsForSale());
|
|
assertEquals(originalNameData.getSalePrice(), actualNameData.getSalePrice());
|
|
assertEquals(buyer.getAddress(), actualNameData.getOwner());
|
|
|
|
// Now orphan block
|
|
block.orphan();
|
|
repository.saveChanges();
|
|
|
|
// Check name has been reverted correctly
|
|
actualNameData = this.repository.getNameRepository().fromName(name);
|
|
assertTrue(actualNameData.getIsForSale());
|
|
assertEquals(originalNameData.getSalePrice(), actualNameData.getSalePrice());
|
|
assertEquals(originalNameData.getOwner(), actualNameData.getOwner());
|
|
}
|
|
|
|
@Test
|
|
public void testCreatePollTransaction() throws DataException {
|
|
// This test requires GenesisBlock's timestamp is set to something after BlockChain.VOTING_RELEASE_TIMESTAMP
|
|
createTestAccounts(BlockChain.getVotingReleaseTimestamp() + 1_000L);
|
|
|
|
// Make a new create poll transaction
|
|
String pollName = "test poll";
|
|
String description = "test poll description";
|
|
|
|
List<PollOptionData> pollOptions = new ArrayList<PollOptionData>();
|
|
pollOptions.add(new PollOptionData("abort"));
|
|
pollOptions.add(new PollOptionData("retry"));
|
|
pollOptions.add(new PollOptionData("fail"));
|
|
|
|
Account recipient = new PublicKeyAccount(repository, recipientSeed);
|
|
BigDecimal fee = BigDecimal.ONE;
|
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
|
CreatePollTransactionData createPollTransactionData = new CreatePollTransactionData(sender.getPublicKey(), recipient.getAddress(), pollName,
|
|
description, pollOptions, fee, timestamp, reference);
|
|
|
|
Transaction createPollTransaction = new CreatePollTransaction(repository, createPollTransactionData);
|
|
createPollTransaction.sign(sender);
|
|
assertTrue(createPollTransaction.isSignatureValid());
|
|
assertEquals(ValidationResult.OK, createPollTransaction.isValid());
|
|
|
|
// Forge new block with transaction
|
|
Block block = new Block(repository, parentBlockData, generator);
|
|
block.addTransaction(createPollTransactionData);
|
|
block.sign();
|
|
|
|
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
|
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
|
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Check sender's balance
|
|
BigDecimal expectedBalance = initialSenderBalance.subtract(fee);
|
|
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Sender's new balance incorrect");
|
|
|
|
// Fee should be in generator's balance
|
|
expectedBalance = initialGeneratorBalance.add(fee);
|
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Generator's new balance incorrect");
|
|
|
|
// Check poll was created
|
|
PollData actualPollData = this.repository.getVotingRepository().fromPollName(pollName);
|
|
assertNotNull(actualPollData);
|
|
|
|
// Check sender's reference
|
|
assertTrue(Arrays.equals(createPollTransactionData.getSignature(), sender.getLastReference()), "Sender's new reference incorrect");
|
|
|
|
// Update variables for use by other tests
|
|
reference = sender.getLastReference();
|
|
parentBlockData = block.getBlockData();
|
|
}
|
|
|
|
@Test
|
|
public void testVoteOnPollTransaction() throws DataException {
|
|
// Create poll using another test
|
|
testCreatePollTransaction();
|
|
|
|
// Try all options, plus invalid optionIndex (note use of <= for this)
|
|
String pollName = "test poll";
|
|
int pollOptionsSize = 3;
|
|
BigDecimal fee = BigDecimal.ONE;
|
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
|
|
|
for (int optionIndex = 0; optionIndex <= pollOptionsSize; ++optionIndex) {
|
|
// Make a vote-on-poll transaction
|
|
VoteOnPollTransactionData voteOnPollTransactionData = new VoteOnPollTransactionData(sender.getPublicKey(), pollName, optionIndex, fee, timestamp,
|
|
reference);
|
|
|
|
Transaction voteOnPollTransaction = new VoteOnPollTransaction(repository, voteOnPollTransactionData);
|
|
voteOnPollTransaction.sign(sender);
|
|
assertTrue(voteOnPollTransaction.isSignatureValid());
|
|
|
|
if (optionIndex == pollOptionsSize) {
|
|
assertEquals(ValidationResult.POLL_OPTION_DOES_NOT_EXIST, voteOnPollTransaction.isValid());
|
|
break;
|
|
}
|
|
assertEquals(ValidationResult.OK, voteOnPollTransaction.isValid());
|
|
|
|
// Forge new block with transaction
|
|
Block block = new Block(repository, parentBlockData, generator);
|
|
block.addTransaction(voteOnPollTransactionData);
|
|
block.sign();
|
|
|
|
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
|
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
|
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Check vote was registered properly
|
|
VoteOnPollData actualVoteOnPollData = repository.getVotingRepository().getVote(pollName, sender.getPublicKey());
|
|
assertNotNull(actualVoteOnPollData);
|
|
assertEquals(optionIndex, actualVoteOnPollData.getOptionIndex());
|
|
|
|
// update variables for next round
|
|
parentBlockData = block.getBlockData();
|
|
timestamp += 1_000;
|
|
reference = voteOnPollTransaction.getTransactionData().getSignature();
|
|
}
|
|
|
|
// Check poll's votes
|
|
List<VoteOnPollData> votes = repository.getVotingRepository().getVotes(pollName);
|
|
assertNotNull(votes);
|
|
|
|
assertEquals(1, votes.size(), "Only one vote expected");
|
|
|
|
assertEquals(pollOptionsSize - 1, votes.get(0).getOptionIndex(), "Wrong vote option index");
|
|
assertTrue(Arrays.equals(sender.getPublicKey(), votes.get(0).getVoterPublicKey()), "Wrong voter public key");
|
|
|
|
// Orphan last block
|
|
BlockData lastBlockData = repository.getBlockRepository().getLastBlock();
|
|
Block lastBlock = new Block(repository, lastBlockData);
|
|
lastBlock.orphan();
|
|
repository.saveChanges();
|
|
|
|
// Re-check poll's votes
|
|
votes = repository.getVotingRepository().getVotes(pollName);
|
|
assertNotNull(votes);
|
|
|
|
assertEquals(1, votes.size(), "Only one vote expected");
|
|
|
|
assertEquals(pollOptionsSize - 1 - 1, votes.get(0).getOptionIndex(), "Wrong vote option index");
|
|
assertTrue(Arrays.equals(sender.getPublicKey(), votes.get(0).getVoterPublicKey()), "Wrong voter public key");
|
|
}
|
|
|
|
@Test
|
|
public void testIssueAssetTransaction() throws DataException {
|
|
createTestAccounts(null);
|
|
|
|
// Create new asset
|
|
String assetName = "test asset";
|
|
String description = "test asset description";
|
|
long quantity = 1_000_000L;
|
|
boolean isDivisible = true;
|
|
BigDecimal fee = BigDecimal.ONE;
|
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
|
|
|
IssueAssetTransactionData issueAssetTransactionData = new IssueAssetTransactionData(sender.getPublicKey(), sender.getAddress(), assetName, description,
|
|
quantity, isDivisible, fee, timestamp, reference);
|
|
|
|
Transaction issueAssetTransaction = new IssueAssetTransaction(repository, issueAssetTransactionData);
|
|
issueAssetTransaction.sign(sender);
|
|
assertTrue(issueAssetTransaction.isSignatureValid());
|
|
assertEquals(ValidationResult.OK, issueAssetTransaction.isValid());
|
|
|
|
// Forge new block with transaction
|
|
Block block = new Block(repository, parentBlockData, generator);
|
|
block.addTransaction(issueAssetTransactionData);
|
|
block.sign();
|
|
|
|
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
|
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
|
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Check sender's balance
|
|
BigDecimal expectedBalance = initialSenderBalance.subtract(fee);
|
|
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Sender's new balance incorrect");
|
|
|
|
// Fee should be in generator's balance
|
|
expectedBalance = initialGeneratorBalance.add(fee);
|
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Generator's new balance incorrect");
|
|
|
|
// Check we now have an assetId
|
|
Long assetId = issueAssetTransactionData.getAssetId();
|
|
assertNotNull(assetId);
|
|
// Should NOT collide with Asset.QORA
|
|
assertFalse(assetId == Asset.QORA);
|
|
|
|
// Check asset now exists
|
|
AssetRepository assetRepo = this.repository.getAssetRepository();
|
|
assertTrue(assetRepo.assetExists(assetId));
|
|
assertTrue(assetRepo.assetExists(assetName));
|
|
// Check asset data
|
|
AssetData assetData = assetRepo.fromAssetId(assetId);
|
|
assertNotNull(assetData);
|
|
assertEquals(assetName, assetData.getName());
|
|
assertEquals(description, assetData.getDescription());
|
|
|
|
// Orphan block
|
|
block.orphan();
|
|
repository.saveChanges();
|
|
|
|
// Check sender's balance
|
|
actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(initialSenderBalance.compareTo(actualBalance) == 0, "Sender's reverted balance incorrect");
|
|
|
|
// Check generator's balance
|
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(initialGeneratorBalance.compareTo(actualBalance) == 0, "Generator's reverted balance incorrect");
|
|
|
|
// Check asset no longer exists
|
|
assertFalse(assetRepo.assetExists(assetId));
|
|
assertFalse(assetRepo.assetExists(assetName));
|
|
assetData = assetRepo.fromAssetId(assetId);
|
|
assertNull(assetData);
|
|
|
|
// Re-process block for use by other tests
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Update variables for use by other tests
|
|
reference = sender.getLastReference();
|
|
parentBlockData = block.getBlockData();
|
|
}
|
|
|
|
@Test
|
|
public void testTransferAssetTransaction() throws DataException {
|
|
// Issue asset using another test
|
|
testIssueAssetTransaction();
|
|
|
|
String assetName = "test asset";
|
|
AssetRepository assetRepo = this.repository.getAssetRepository();
|
|
AssetData originalAssetData = assetRepo.fromAssetName(assetName);
|
|
long assetId = originalAssetData.getAssetId();
|
|
BigDecimal originalSenderBalance = sender.getConfirmedBalance(Asset.QORA);
|
|
BigDecimal originalGeneratorBalance = generator.getConfirmedBalance(Asset.QORA);
|
|
|
|
// Transfer asset to new recipient
|
|
Account recipient = new PublicKeyAccount(repository, recipientSeed);
|
|
BigDecimal amount = BigDecimal.valueOf(1_000L).setScale(8);
|
|
BigDecimal fee = BigDecimal.ONE;
|
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
|
|
|
TransferAssetTransactionData transferAssetTransactionData = new TransferAssetTransactionData(sender.getPublicKey(), recipient.getAddress(), amount,
|
|
assetId, fee, timestamp, reference);
|
|
|
|
Transaction transferAssetTransaction = new TransferAssetTransaction(repository, transferAssetTransactionData);
|
|
transferAssetTransaction.sign(sender);
|
|
assertTrue(transferAssetTransaction.isSignatureValid());
|
|
assertEquals(ValidationResult.OK, transferAssetTransaction.isValid());
|
|
|
|
// Forge new block with transaction
|
|
Block block = new Block(repository, parentBlockData, generator);
|
|
block.addTransaction(transferAssetTransactionData);
|
|
block.sign();
|
|
|
|
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
|
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
|
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Check sender's balance
|
|
BigDecimal expectedBalance = originalSenderBalance.subtract(fee);
|
|
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Sender's new balance incorrect");
|
|
|
|
// Fee should be in generator's balance
|
|
expectedBalance = originalGeneratorBalance.add(fee);
|
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Generator's new balance incorrect");
|
|
|
|
// Check asset balances
|
|
BigDecimal actualSenderAssetBalance = sender.getConfirmedBalance(assetId);
|
|
assertNotNull(actualSenderAssetBalance);
|
|
BigDecimal expectedSenderAssetBalance = BigDecimal.valueOf(originalAssetData.getQuantity()).setScale(8).subtract(amount);
|
|
assertEquals(expectedSenderAssetBalance, actualSenderAssetBalance);
|
|
|
|
BigDecimal actualRecipientAssetBalance = recipient.getConfirmedBalance(assetId);
|
|
assertNotNull(actualRecipientAssetBalance);
|
|
assertEquals(amount, actualRecipientAssetBalance);
|
|
|
|
// Orphan block
|
|
block.orphan();
|
|
repository.saveChanges();
|
|
|
|
// Check sender's balance
|
|
actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(originalSenderBalance.compareTo(actualBalance) == 0, "Sender's reverted balance incorrect");
|
|
|
|
// Check generator's balance
|
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(originalGeneratorBalance.compareTo(actualBalance) == 0, "Generator's reverted balance incorrect");
|
|
|
|
// Check asset balances
|
|
actualSenderAssetBalance = sender.getConfirmedBalance(assetId);
|
|
assertNotNull(actualSenderAssetBalance);
|
|
expectedSenderAssetBalance = BigDecimal.valueOf(originalAssetData.getQuantity()).setScale(8);
|
|
assertEquals(expectedSenderAssetBalance, actualSenderAssetBalance);
|
|
|
|
actualRecipientAssetBalance = recipient.getConfirmedBalance(assetId);
|
|
if (actualRecipientAssetBalance != null)
|
|
assertEquals(BigDecimal.ZERO.setScale(8), actualRecipientAssetBalance);
|
|
|
|
// Re-process block for use by other tests
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Update variables for use by other tests
|
|
reference = sender.getLastReference();
|
|
parentBlockData = block.getBlockData();
|
|
}
|
|
|
|
@Test
|
|
public void testCreateAssetOrderTransaction() throws DataException {
|
|
// Issue asset using another test
|
|
testIssueAssetTransaction();
|
|
|
|
// Asset info
|
|
String assetName = "test asset";
|
|
AssetRepository assetRepo = this.repository.getAssetRepository();
|
|
AssetData originalAssetData = assetRepo.fromAssetName(assetName);
|
|
long assetId = originalAssetData.getAssetId();
|
|
|
|
// Buyer
|
|
PrivateKeyAccount buyer = new PrivateKeyAccount(repository, recipientSeed);
|
|
|
|
// Send buyer some funds so they have a reference
|
|
Transaction somePaymentTransaction = createPayment(sender, buyer.getAddress());
|
|
byte[] buyersReference = somePaymentTransaction.getTransactionData().getSignature();
|
|
|
|
// Forge new block with transaction
|
|
Block block = new Block(repository, parentBlockData, generator);
|
|
block.addTransaction(somePaymentTransaction.getTransactionData());
|
|
block.sign();
|
|
|
|
block.process();
|
|
repository.saveChanges();
|
|
parentBlockData = block.getBlockData();
|
|
|
|
// Order: buyer has 10 QORA and wants to buy "test asset" at a price of 50 "test asset" per QORA.
|
|
long haveAssetId = Asset.QORA;
|
|
BigDecimal amount = BigDecimal.valueOf(10).setScale(8);
|
|
long wantAssetId = assetId;
|
|
BigDecimal price = BigDecimal.valueOf(50).setScale(8);
|
|
BigDecimal fee = BigDecimal.ONE;
|
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
|
|
|
CreateOrderTransactionData createOrderTransactionData = new CreateOrderTransactionData(buyer.getPublicKey(), haveAssetId, wantAssetId, amount, price,
|
|
fee, timestamp, buyersReference);
|
|
Transaction createOrderTransaction = new CreateOrderTransaction(this.repository, createOrderTransactionData);
|
|
createOrderTransaction.sign(buyer);
|
|
assertTrue(createOrderTransaction.isSignatureValid());
|
|
assertEquals(ValidationResult.OK, createOrderTransaction.isValid());
|
|
|
|
// Forge new block with transaction
|
|
block = new Block(repository, parentBlockData, generator);
|
|
block.addTransaction(createOrderTransactionData);
|
|
block.sign();
|
|
|
|
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
|
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
|
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Check order was created
|
|
byte[] orderId = createOrderTransactionData.getSignature();
|
|
OrderData orderData = assetRepo.fromOrderId(orderId);
|
|
assertNotNull(orderData);
|
|
|
|
// Check buyer's balance reduced
|
|
BigDecimal expectedBalance = genericPaymentAmount.subtract(amount).subtract(fee);
|
|
BigDecimal actualBalance = buyer.getConfirmedBalance(haveAssetId);
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0);
|
|
|
|
// Orphan transaction
|
|
block.orphan();
|
|
repository.saveChanges();
|
|
|
|
// Check order no longer exists
|
|
orderData = assetRepo.fromOrderId(orderId);
|
|
assertNull(orderData);
|
|
|
|
// Check buyer's balance restored
|
|
expectedBalance = genericPaymentAmount;
|
|
actualBalance = buyer.getConfirmedBalance(haveAssetId);
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0);
|
|
|
|
// Re-process to allow use by other tests
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Update variables for use by other tests
|
|
reference = sender.getLastReference();
|
|
parentBlockData = block.getBlockData();
|
|
}
|
|
|
|
@Test
|
|
public void testCancelAssetOrderTransaction() throws DataException {
|
|
// Issue asset and create order using another test
|
|
testCreateAssetOrderTransaction();
|
|
|
|
// Asset info
|
|
String assetName = "test asset";
|
|
AssetRepository assetRepo = this.repository.getAssetRepository();
|
|
AssetData originalAssetData = assetRepo.fromAssetName(assetName);
|
|
long assetId = originalAssetData.getAssetId();
|
|
|
|
// Buyer
|
|
PrivateKeyAccount buyer = new PrivateKeyAccount(repository, recipientSeed);
|
|
|
|
// Fetch orders
|
|
long haveAssetId = Asset.QORA;
|
|
long wantAssetId = assetId;
|
|
List<OrderData> orders = assetRepo.getOpenOrders(haveAssetId, wantAssetId);
|
|
|
|
assertNotNull(orders);
|
|
assertEquals(1, orders.size());
|
|
|
|
OrderData originalOrderData = orders.get(0);
|
|
assertNotNull(originalOrderData);
|
|
assertFalse(originalOrderData.getIsClosed());
|
|
|
|
// Create cancel order transaction
|
|
byte[] orderId = originalOrderData.getOrderId();
|
|
BigDecimal fee = BigDecimal.ONE;
|
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
|
byte[] buyersReference = buyer.getLastReference();
|
|
CancelOrderTransactionData cancelOrderTransactionData = new CancelOrderTransactionData(buyer.getPublicKey(), orderId, fee, timestamp, buyersReference);
|
|
|
|
Transaction cancelOrderTransaction = new CancelOrderTransaction(this.repository, cancelOrderTransactionData);
|
|
cancelOrderTransaction.sign(buyer);
|
|
assertTrue(cancelOrderTransaction.isSignatureValid());
|
|
assertEquals(ValidationResult.OK, cancelOrderTransaction.isValid());
|
|
|
|
// Forge new block with transaction
|
|
Block block = new Block(repository, parentBlockData, generator);
|
|
block.addTransaction(cancelOrderTransactionData);
|
|
block.sign();
|
|
|
|
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
|
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
|
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Check order is marked as cancelled
|
|
OrderData cancelledOrderData = assetRepo.fromOrderId(orderId);
|
|
assertNotNull(cancelledOrderData);
|
|
assertTrue(cancelledOrderData.getIsClosed());
|
|
|
|
// Orphan
|
|
block.orphan();
|
|
repository.saveChanges();
|
|
|
|
// Check order is no longer marked as cancelled
|
|
OrderData uncancelledOrderData = assetRepo.fromOrderId(orderId);
|
|
assertNotNull(uncancelledOrderData);
|
|
assertFalse(uncancelledOrderData.getIsClosed());
|
|
|
|
}
|
|
|
|
@Test
|
|
public void testMatchingCreateAssetOrderTransaction() throws DataException {
|
|
// Issue asset and create order using another test
|
|
testCreateAssetOrderTransaction();
|
|
|
|
// Asset info
|
|
String assetName = "test asset";
|
|
AssetRepository assetRepo = this.repository.getAssetRepository();
|
|
AssetData originalAssetData = assetRepo.fromAssetName(assetName);
|
|
long assetId = originalAssetData.getAssetId();
|
|
|
|
// Buyer
|
|
PrivateKeyAccount buyer = new PrivateKeyAccount(repository, recipientSeed);
|
|
|
|
// Fetch orders
|
|
long originalHaveAssetId = Asset.QORA;
|
|
long originalWantAssetId = assetId;
|
|
List<OrderData> orders = assetRepo.getOpenOrders(originalHaveAssetId, originalWantAssetId);
|
|
|
|
assertNotNull(orders);
|
|
assertEquals(1, orders.size());
|
|
|
|
OrderData originalOrderData = orders.get(0);
|
|
assertNotNull(originalOrderData);
|
|
assertFalse(originalOrderData.getIsClosed());
|
|
|
|
// Unfulfilled order: "buyer" has 10 QORA and wants to buy "test asset" at a price of 50 "test asset" per QORA.
|
|
// buyer's order: have=QORA, amount=10, want=test-asset, price=50 (test-asset per QORA, so max return is 500 test-asset)
|
|
|
|
// Original asset owner (sender) will sell asset to "buyer"
|
|
|
|
// Order: seller has 40 "test asset" and wants to buy QORA at a price of 1/60 QORA per "test asset".
|
|
// This order should be a partial match for original order, and at a better price than asked
|
|
long haveAssetId = assetId;
|
|
BigDecimal amount = BigDecimal.valueOf(40).setScale(8);
|
|
long wantAssetId = Asset.QORA;
|
|
BigDecimal price = BigDecimal.ONE.setScale(8).divide(BigDecimal.valueOf(60).setScale(8), RoundingMode.DOWN);
|
|
BigDecimal fee = BigDecimal.ONE;
|
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
|
BigDecimal senderPreTradeWantBalance = sender.getConfirmedBalance(wantAssetId);
|
|
|
|
CreateOrderTransactionData createOrderTransactionData = new CreateOrderTransactionData(sender.getPublicKey(), haveAssetId, wantAssetId, amount, price,
|
|
fee, timestamp, reference);
|
|
Transaction createOrderTransaction = new CreateOrderTransaction(this.repository, createOrderTransactionData);
|
|
createOrderTransaction.sign(sender);
|
|
assertTrue(createOrderTransaction.isSignatureValid());
|
|
assertEquals(ValidationResult.OK, createOrderTransaction.isValid());
|
|
|
|
// Forge new block with transaction
|
|
Block block = new Block(repository, parentBlockData, generator);
|
|
block.addTransaction(createOrderTransactionData);
|
|
block.sign();
|
|
|
|
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
|
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
|
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Check order was created
|
|
byte[] orderId = createOrderTransactionData.getSignature();
|
|
OrderData orderData = assetRepo.fromOrderId(orderId);
|
|
assertNotNull(orderData);
|
|
|
|
// Check order has trades
|
|
List<TradeData> trades = assetRepo.getOrdersTrades(orderId);
|
|
assertNotNull(trades);
|
|
assertEquals(1, trades.size(), "Trade didn't happen");
|
|
TradeData tradeData = trades.get(0);
|
|
|
|
// Check trade has correct values
|
|
BigDecimal expectedAmount = amount.divide(originalOrderData.getPrice()).setScale(8);
|
|
BigDecimal actualAmount = tradeData.getAmount();
|
|
assertTrue(expectedAmount.compareTo(actualAmount) == 0);
|
|
|
|
BigDecimal expectedPrice = amount;
|
|
BigDecimal actualPrice = tradeData.getPrice();
|
|
assertTrue(expectedPrice.compareTo(actualPrice) == 0);
|
|
|
|
// Check seller's "test asset" balance
|
|
BigDecimal expectedBalance = BigDecimal.valueOf(1_000_000L).setScale(8).subtract(amount);
|
|
BigDecimal actualBalance = sender.getConfirmedBalance(haveAssetId);
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0);
|
|
|
|
// Check buyer's "test asset" balance
|
|
expectedBalance = amount;
|
|
actualBalance = buyer.getConfirmedBalance(haveAssetId);
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0);
|
|
|
|
// Check seller's QORA balance
|
|
expectedBalance = senderPreTradeWantBalance.subtract(BigDecimal.ONE).add(expectedAmount);
|
|
actualBalance = sender.getConfirmedBalance(wantAssetId);
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0);
|
|
|
|
// Check seller's order is correctly fulfilled
|
|
assertTrue(orderData.getIsFulfilled());
|
|
|
|
// Check buyer's order is still not fulfilled
|
|
OrderData buyersOrderData = assetRepo.fromOrderId(originalOrderData.getOrderId());
|
|
assertFalse(buyersOrderData.getIsFulfilled());
|
|
|
|
// Orphan transaction
|
|
block.orphan();
|
|
repository.saveChanges();
|
|
|
|
// Check order no longer exists
|
|
orderData = assetRepo.fromOrderId(orderId);
|
|
assertNull(orderData);
|
|
|
|
// Check trades no longer exist
|
|
trades = assetRepo.getOrdersTrades(orderId);
|
|
assertNotNull(trades);
|
|
assertEquals(0, trades.size());
|
|
|
|
// Check seller's "test asset" balance restored
|
|
expectedBalance = BigDecimal.valueOf(1_000_000L).setScale(8);
|
|
actualBalance = sender.getConfirmedBalance(haveAssetId);
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0);
|
|
|
|
// Check buyer's "test asset" balance restored
|
|
expectedBalance = BigDecimal.ZERO.setScale(8);
|
|
actualBalance = buyer.getConfirmedBalance(haveAssetId);
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0);
|
|
}
|
|
|
|
@Test
|
|
public void testMultiPaymentTransaction() throws DataException {
|
|
createTestAccounts(null);
|
|
|
|
// Make a new multi-payment transaction
|
|
BigDecimal fee = BigDecimal.ONE;
|
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
|
|
|
// Payments
|
|
BigDecimal expectedSenderBalance = initialSenderBalance.subtract(fee);
|
|
List<PaymentData> payments = new ArrayList<PaymentData>();
|
|
for (int i = 0; i < 5; ++i) {
|
|
byte[] seed = recipientSeed.clone();
|
|
seed[0] += i;
|
|
Account recipient = new PublicKeyAccount(repository, seed);
|
|
long assetId = Asset.QORA;
|
|
|
|
BigDecimal amount = BigDecimal.valueOf(1_000L + i).setScale(8);
|
|
expectedSenderBalance = expectedSenderBalance.subtract(amount);
|
|
|
|
PaymentData paymentData = new PaymentData(recipient.getAddress(), assetId, amount);
|
|
|
|
payments.add(paymentData);
|
|
}
|
|
|
|
MultiPaymentTransactionData multiPaymentTransactionData = new MultiPaymentTransactionData(sender.getPublicKey(), payments, fee, timestamp, reference);
|
|
|
|
Transaction multiPaymentTransaction = new MultiPaymentTransaction(repository, multiPaymentTransactionData);
|
|
multiPaymentTransaction.sign(sender);
|
|
assertTrue(multiPaymentTransaction.isSignatureValid());
|
|
assertEquals(ValidationResult.OK, multiPaymentTransaction.isValid());
|
|
|
|
// Forge new block with payment transaction
|
|
Block block = new Block(repository, parentBlockData, generator);
|
|
block.addTransaction(multiPaymentTransactionData);
|
|
block.sign();
|
|
|
|
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
|
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
|
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Check sender's balance
|
|
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(expectedSenderBalance.compareTo(actualBalance) == 0, "Sender's new balance incorrect");
|
|
|
|
// Fee should be in generator's balance
|
|
BigDecimal expectedBalance = initialGeneratorBalance.add(fee);
|
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Generator's new balance incorrect");
|
|
|
|
// Check recipients
|
|
for (int i = 0; i < payments.size(); ++i) {
|
|
PaymentData paymentData = payments.get(i);
|
|
Account recipient = new Account(this.repository, paymentData.getRecipient());
|
|
|
|
byte[] recipientsReference = recipient.getLastReference();
|
|
assertTrue(Arrays.equals(multiPaymentTransaction.getTransactionData().getSignature(), recipientsReference), "Recipient's new reference incorrect");
|
|
|
|
// Amount should be in recipient's balance
|
|
expectedBalance = paymentData.getAmount();
|
|
actualBalance = accountRepository.getBalance(recipient.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Recipient's new balance incorrect");
|
|
|
|
}
|
|
|
|
// Orphan block
|
|
block.orphan();
|
|
repository.saveChanges();
|
|
|
|
// Check sender's balance
|
|
actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(initialSenderBalance.compareTo(actualBalance) == 0, "Sender's reverted balance incorrect");
|
|
|
|
// Check generator's balance
|
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(initialGeneratorBalance.compareTo(actualBalance) == 0, "Generator's new balance incorrect");
|
|
}
|
|
|
|
@Test
|
|
public void testMessageTransaction() throws DataException, UnsupportedEncodingException {
|
|
createTestAccounts(1431861220336L); // timestamp taken from main blockchain block 99000
|
|
|
|
// Make a new message transaction
|
|
Account recipient = new PublicKeyAccount(repository, recipientSeed);
|
|
BigDecimal amount = BigDecimal.valueOf(1_000L);
|
|
BigDecimal fee = BigDecimal.ONE;
|
|
long timestamp = parentBlockData.getTimestamp() + 1_000;
|
|
int version = Transaction.getVersionByTimestamp(timestamp);
|
|
byte[] data = "test".getBytes("UTF-8");
|
|
boolean isText = true;
|
|
boolean isEncrypted = false;
|
|
|
|
MessageTransactionData messageTransactionData = new MessageTransactionData(version, sender.getPublicKey(), recipient.getAddress(), Asset.QORA, amount,
|
|
data, isText, isEncrypted, fee, timestamp, reference);
|
|
|
|
Transaction messageTransaction = new MessageTransaction(repository, messageTransactionData);
|
|
messageTransaction.sign(sender);
|
|
assertTrue(messageTransaction.isSignatureValid());
|
|
assertEquals(ValidationResult.OK, messageTransaction.isValid());
|
|
|
|
// Forge new block with message transaction
|
|
Block block = new Block(repository, parentBlockData, generator);
|
|
block.addTransaction(messageTransactionData);
|
|
block.sign();
|
|
|
|
assertTrue(block.isSignatureValid(), "Block signatures invalid");
|
|
assertEquals(Block.ValidationResult.OK, block.isValid(), "Block is invalid");
|
|
|
|
block.process();
|
|
repository.saveChanges();
|
|
|
|
// Check sender's balance
|
|
BigDecimal expectedBalance = initialSenderBalance.subtract(amount).subtract(fee);
|
|
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Sender's new balance incorrect");
|
|
|
|
// Fee should be in generator's balance
|
|
expectedBalance = initialGeneratorBalance.add(fee);
|
|
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Generator's new balance incorrect");
|
|
|
|
// Amount should be in recipient's balance
|
|
expectedBalance = amount;
|
|
actualBalance = accountRepository.getBalance(recipient.getAddress(), Asset.QORA).getBalance();
|
|
assertTrue(expectedBalance.compareTo(actualBalance) == 0, "Recipient's new balance incorrect");
|
|
}
|
|
|
|
} |