More tests and fixes resulting from such

Settings class reworked to allow easier testing

Fix to Payment.orphan() where fee was being incorrectly subtracted instead of added

Added AssetRepository.fromAssetName(String): AssetData

Fixed deleting assets from HSQLDB repository due to broken column name in SQL.

Fixed saving IssueAssetTransactions in HSQLDB repository due to missing column binding.

More TransactionTests!
This commit is contained in:
catbref 2018-07-04 12:49:56 +01:00
parent 2bfb0227ae
commit 7536ab37fa
17 changed files with 387 additions and 57 deletions

View File

@ -36,6 +36,11 @@ public class IssueAssetTransactionData extends TransactionData {
this(null, issuerPublicKey, owner, assetName, description, quantity, isDivisible, fee, timestamp, reference, signature); this(null, issuerPublicKey, owner, assetName, description, quantity, isDivisible, fee, timestamp, reference, signature);
} }
public IssueAssetTransactionData(byte[] issuerPublicKey, String owner, String assetName, String description, long quantity, boolean isDivisible,
BigDecimal fee, long timestamp, byte[] reference) {
this(null, issuerPublicKey, owner, assetName, description, quantity, isDivisible, fee, timestamp, reference, null);
}
// Getters/Setters // Getters/Setters
public Long getAssetId() { public Long getAssetId() {

View File

@ -155,9 +155,9 @@ public class Block {
* @return 1, 2 or 3 * @return 1, 2 or 3
*/ */
public int getNextBlockVersion() { public int getNextBlockVersion() {
if (this.blockData.getHeight() < BlockChain.AT_BLOCK_HEIGHT_RELEASE) if (this.blockData.getHeight() < BlockChain.getATReleaseHeight())
return 1; return 1;
else if (this.blockData.getTimestamp() < BlockChain.POWFIX_RELEASE_TIMESTAMP) else if (this.blockData.getTimestamp() < BlockChain.getPowFixReleaseTimestamp())
return 2; return 2;
else else
return 3; return 3;

View File

@ -10,6 +10,7 @@ import repository.BlockRepository;
import repository.DataException; import repository.DataException;
import repository.Repository; import repository.Repository;
import repository.RepositoryManager; import repository.RepositoryManager;
import settings.Settings;
/** /**
* Class representing the blockchain as a whole. * Class representing the blockchain as a whole.
@ -31,11 +32,11 @@ public class BlockChain {
public static final long BLOCK_TIMESTAMP_MARGIN = 500L; public static final long BLOCK_TIMESTAMP_MARGIN = 500L;
// Various release timestamps / block heights // Various release timestamps / block heights
public static final int MESSAGE_RELEASE_HEIGHT = 99000; private static final int MESSAGE_RELEASE_HEIGHT = 99000;
public static final int AT_BLOCK_HEIGHT_RELEASE = 99000; private static final int AT_RELEASE_HEIGHT = 99000;
public static final long POWFIX_RELEASE_TIMESTAMP = 1456426800000L; // Block Version 3 // 2016-02-25T19:00:00+00:00 private static final long POWFIX_RELEASE_TIMESTAMP = 1456426800000L; // Block Version 3 // 2016-02-25T19:00:00+00:00
public static final long ASSETS_RELEASE_TIMESTAMP = 0L; // From Qora epoch private static final long ASSETS_RELEASE_TIMESTAMP = 0L; // From Qora epoch
public static final long VOTING_RELEASE_TIMESTAMP = 1403715600000L; // 2014-06-25T17:00:00+00:00 private static final long VOTING_RELEASE_TIMESTAMP = 1403715600000L; // 2014-06-25T17:00:00+00:00
/** /**
* Some sort start-up/initialization/checking method. * Some sort start-up/initialization/checking method.
@ -96,4 +97,39 @@ public class BlockChain {
return balance; return balance;
} }
public static int getMessageReleaseHeight() {
if (Settings.getInstance().isTestNet())
return 0;
return MESSAGE_RELEASE_HEIGHT;
}
public static int getATReleaseHeight() {
if (Settings.getInstance().isTestNet())
return 0;
return AT_RELEASE_HEIGHT;
}
public static long getPowFixReleaseTimestamp() {
if (Settings.getInstance().isTestNet())
return 0;
return POWFIX_RELEASE_TIMESTAMP;
}
public static long getAssetsReleaseTimestamp() {
if (Settings.getInstance().isTestNet())
return 0;
return ASSETS_RELEASE_TIMESTAMP;
}
public static long getVotingReleaseTimestamp() {
if (Settings.getInstance().isTestNet())
return 0;
return VOTING_RELEASE_TIMESTAMP;
}
} }

View File

@ -128,7 +128,7 @@ public class Payment {
Account sender = new PublicKeyAccount(this.repository, senderPublicKey); Account sender = new PublicKeyAccount(this.repository, senderPublicKey);
// Update sender's balance due to fee // Update sender's balance due to fee
sender.setConfirmedBalance(Asset.QORA, sender.getConfirmedBalance(Asset.QORA).subtract(fee)); sender.setConfirmedBalance(Asset.QORA, sender.getConfirmedBalance(Asset.QORA).add(fee));
// Update sender's reference // Update sender's reference
sender.setLastReference(reference); sender.setLastReference(reference);

View File

@ -115,7 +115,7 @@ public class CreateOrderTransaction extends Transaction {
// Check creator has enough funds for fee in QORA // Check creator has enough funds for fee in QORA
// NOTE: in Gen1 pre-POWFIX-RELEASE transactions didn't have this check // NOTE: in Gen1 pre-POWFIX-RELEASE transactions didn't have this check
if (createOrderTransactionData.getTimestamp() >= BlockChain.POWFIX_RELEASE_TIMESTAMP if (createOrderTransactionData.getTimestamp() >= BlockChain.getPowFixReleaseTimestamp()
&& creator.getConfirmedBalance(Asset.QORA).compareTo(createOrderTransactionData.getFee()) == -1) && creator.getConfirmedBalance(Asset.QORA).compareTo(createOrderTransactionData.getFee()) == -1)
return ValidationResult.NO_BALANCE; return ValidationResult.NO_BALANCE;
} }

View File

@ -77,7 +77,7 @@ public class CreatePollTransaction extends Transaction {
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
// Are CreatePollTransactions even allowed at this point? // Are CreatePollTransactions even allowed at this point?
// XXX In gen1 this used NTP.getTime() but surely the transaction's timestamp should be used? // XXX In gen1 this used NTP.getTime() but surely the transaction's timestamp should be used?
if (this.createPollTransactionData.getTimestamp() < BlockChain.VOTING_RELEASE_TIMESTAMP) if (this.createPollTransactionData.getTimestamp() < BlockChain.getVotingReleaseTimestamp())
return ValidationResult.NOT_YET_RELEASED; return ValidationResult.NOT_YET_RELEASED;
// Check owner address is valid // Check owner address is valid

View File

@ -79,7 +79,7 @@ public class IssueAssetTransaction extends Transaction {
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
// Are IssueAssetTransactions even allowed at this point? // Are IssueAssetTransactions even allowed at this point?
// XXX In gen1 this used NTP.getTime() but surely the transaction's timestamp should be used? // XXX In gen1 this used NTP.getTime() but surely the transaction's timestamp should be used?
if (this.issueAssetTransactionData.getTimestamp() < BlockChain.ASSETS_RELEASE_TIMESTAMP) if (this.issueAssetTransactionData.getTimestamp() < BlockChain.getAssetsReleaseTimestamp())
return ValidationResult.NOT_YET_RELEASED; return ValidationResult.NOT_YET_RELEASED;
// Check owner address is valid // Check owner address is valid

View File

@ -90,7 +90,7 @@ public class MessageTransaction extends Transaction {
if (messageTransactionData.getVersion() != MessageTransaction.getVersionByTimestamp(messageTransactionData.getTimestamp())) if (messageTransactionData.getVersion() != MessageTransaction.getVersionByTimestamp(messageTransactionData.getTimestamp()))
return ValidationResult.NOT_YET_RELEASED; return ValidationResult.NOT_YET_RELEASED;
if (this.repository.getBlockRepository().getBlockchainHeight() < BlockChain.MESSAGE_RELEASE_HEIGHT) if (this.repository.getBlockRepository().getBlockchainHeight() < BlockChain.getMessageReleaseHeight())
return ValidationResult.NOT_YET_RELEASED; return ValidationResult.NOT_YET_RELEASED;
// Check data length // Check data length

View File

@ -90,7 +90,7 @@ public class MultiPaymentTransaction extends Transaction {
List<PaymentData> payments = multiPaymentTransactionData.getPayments(); List<PaymentData> payments = multiPaymentTransactionData.getPayments();
// Are MultiPaymentTransactions even allowed at this point? // Are MultiPaymentTransactions even allowed at this point?
if (NTP.getTime() < BlockChain.ASSETS_RELEASE_TIMESTAMP) if (NTP.getTime() < BlockChain.getAssetsReleaseTimestamp())
return ValidationResult.NOT_YET_RELEASED; return ValidationResult.NOT_YET_RELEASED;
// Check number of payments // Check number of payments
@ -105,7 +105,7 @@ public class MultiPaymentTransaction extends Transaction {
// Check sender has enough funds for fee // Check sender has enough funds for fee
// NOTE: in Gen1 pre-POWFIX-RELEASE transactions didn't have this check // NOTE: in Gen1 pre-POWFIX-RELEASE transactions didn't have this check
if (multiPaymentTransactionData.getTimestamp() >= BlockChain.POWFIX_RELEASE_TIMESTAMP if (multiPaymentTransactionData.getTimestamp() >= BlockChain.getPowFixReleaseTimestamp()
&& sender.getConfirmedBalance(Asset.QORA).compareTo(multiPaymentTransactionData.getFee()) == -1) && sender.getConfirmedBalance(Asset.QORA).compareTo(multiPaymentTransactionData.getFee()) == -1)
return ValidationResult.NO_BALANCE; return ValidationResult.NO_BALANCE;

View File

@ -206,7 +206,7 @@ public abstract class Transaction {
* @return transaction version number, likely 1 or 3 * @return transaction version number, likely 1 or 3
*/ */
public static int getVersionByTimestamp(long timestamp) { public static int getVersionByTimestamp(long timestamp) {
if (timestamp < BlockChain.POWFIX_RELEASE_TIMESTAMP) { if (timestamp < BlockChain.getPowFixReleaseTimestamp()) {
return 1; return 1;
} else { } else {
return 3; return 3;

View File

@ -85,7 +85,7 @@ public class TransferAssetTransaction extends Transaction {
TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) this.transactionData; TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) this.transactionData;
// Are IssueAssetTransactions even allowed at this point? // Are IssueAssetTransactions even allowed at this point?
if (NTP.getTime() < BlockChain.ASSETS_RELEASE_TIMESTAMP) if (NTP.getTime() < BlockChain.getAssetsReleaseTimestamp())
return ValidationResult.NOT_YET_RELEASED; return ValidationResult.NOT_YET_RELEASED;
// Check reference is correct // Check reference is correct

View File

@ -68,7 +68,7 @@ public class VoteOnPollTransaction extends Transaction {
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
// Are VoteOnPollTransactions even allowed at this point? // Are VoteOnPollTransactions even allowed at this point?
// XXX In gen1 this used NTP.getTime() but surely the transaction's timestamp should be used? // XXX In gen1 this used NTP.getTime() but surely the transaction's timestamp should be used?
if (this.voteOnPollTransactionData.getTimestamp() < BlockChain.VOTING_RELEASE_TIMESTAMP) if (this.voteOnPollTransactionData.getTimestamp() < BlockChain.getVotingReleaseTimestamp())
return ValidationResult.NOT_YET_RELEASED; return ValidationResult.NOT_YET_RELEASED;
// Check name size bounds // Check name size bounds

View File

@ -9,6 +9,8 @@ public interface AssetRepository {
public AssetData fromAssetId(long assetId) throws DataException; public AssetData fromAssetId(long assetId) throws DataException;
public AssetData fromAssetName(String assetName) throws DataException;
public boolean assetExists(long assetId) throws DataException; public boolean assetExists(long assetId) throws DataException;
public boolean assetExists(String assetName) throws DataException; public boolean assetExists(String assetName) throws DataException;

View File

@ -39,6 +39,26 @@ public class HSQLDBAssetRepository implements AssetRepository {
} }
} }
public AssetData fromAssetName(String assetName) throws DataException {
try {
ResultSet resultSet = this.repository
.checkedExecute("SELECT owner, asset_id, description, quantity, is_divisible, reference FROM Assets WHERE asset_name = ?", assetName);
if (resultSet == null)
return null;
String owner = resultSet.getString(1);
long assetId = resultSet.getLong(2);
String description = resultSet.getString(3);
long quantity = resultSet.getLong(4);
boolean isDivisible = resultSet.getBoolean(5);
byte[] reference = resultSet.getBytes(6);
return new AssetData(assetId, owner, assetName, description, quantity, isDivisible, reference);
} catch (SQLException e) {
throw new DataException("Unable to fetch asset from repository", e);
}
}
public boolean assetExists(long assetId) throws DataException { public boolean assetExists(long assetId) throws DataException {
try { try {
return this.repository.exists("Assets", "asset_id = ?", assetId); return this.repository.exists("Assets", "asset_id = ?", assetId);
@ -73,7 +93,7 @@ public class HSQLDBAssetRepository implements AssetRepository {
public void delete(long assetId) throws DataException { public void delete(long assetId) throws DataException {
try { try {
this.repository.delete("Assets", "assetId = ?", assetId); this.repository.delete("Assets", "asset_id = ?", assetId);
} catch (SQLException e) { } catch (SQLException e) {
throw new DataException("Unable to delete asset from repository", e); throw new DataException("Unable to delete asset from repository", e);
} }

View File

@ -46,9 +46,9 @@ public class HSQLDBIssueAssetTransactionRepository extends HSQLDBTransactionRepo
HSQLDBSaver saveHelper = new HSQLDBSaver("IssueAssetTransactions"); HSQLDBSaver saveHelper = new HSQLDBSaver("IssueAssetTransactions");
saveHelper.bind("signature", issueAssetTransactionData.getSignature()).bind("issuer", issueAssetTransactionData.getIssuerPublicKey()) saveHelper.bind("signature", issueAssetTransactionData.getSignature()).bind("issuer", issueAssetTransactionData.getIssuerPublicKey())
.bind("asset_name", issueAssetTransactionData.getAssetName()).bind("description", issueAssetTransactionData.getDescription()) .bind("owner", issueAssetTransactionData.getOwner()).bind("asset_name", issueAssetTransactionData.getAssetName())
.bind("quantity", issueAssetTransactionData.getQuantity()).bind("is_divisible", issueAssetTransactionData.getIsDivisible()) .bind("description", issueAssetTransactionData.getDescription()).bind("quantity", issueAssetTransactionData.getQuantity())
.bind("asset_id", issueAssetTransactionData.getAssetId()); .bind("is_divisible", issueAssetTransactionData.getIsDivisible()).bind("asset_id", issueAssetTransactionData.getAssetId());
try { try {
saveHelper.execute(this.repository); saveHelper.execute(this.repository);

View File

@ -1,38 +1,118 @@
package settings; package settings;
import java.io.File;
import java.io.IOException;
import java.util.List;
import org.json.simple.JSONObject;
import org.json.simple.JSONValue;
import com.google.common.base.Charsets;
import com.google.common.io.Files;
import qora.block.GenesisBlock; import qora.block.GenesisBlock;
public class Settings { public class Settings {
private static Settings instance;
// Properties // Properties
private long genesisTimestamp = -1; private static Settings instance;
private long genesisTimestamp = GenesisBlock.GENESIS_TIMESTAMP;
private int maxBytePerFee = 1024;
// Constants
private static final String SETTINGS_FILENAME = "settings.json";
// Constructors
private Settings() {
}
private Settings(String filename) {
// Read from file
String path = "";
try {
do {
File file = new File(path + filename);
if (!file.exists()) {
// log lack of settings file
break;
}
List<String> lines = Files.readLines(file, Charsets.UTF_8);
// Concatenate lines for JSON parsing
String jsonString = "";
for (String line : lines) {
// Escape single backslashes in "userpath" entries, typically Windows-style paths
if (line.contains("userpath"))
line.replace("\\", "\\\\");
jsonString += line;
}
JSONObject settingsJSON = (JSONObject) JSONValue.parse(jsonString);
String userpath = (String) settingsJSON.get("userpath");
if (userpath != null) {
path = userpath;
// Add trailing directory separator if needed
if (!path.endsWith(File.separator))
path += File.separator;
continue;
}
process(settingsJSON);
break;
} while (true);
} catch (IOException | ClassCastException e) {
}
}
// Other methods
public static Settings getInstance() { public static Settings getInstance() {
if (instance == null) if (instance == null)
instance = new Settings(); instance = new Settings(SETTINGS_FILENAME);
return instance; return instance;
} }
public static void test(JSONObject settingsJSON) {
// Discard previous settings
if (instance != null)
instance = null;
instance = new Settings();
getInstance().process(settingsJSON);
}
private void process(JSONObject json) {
if (json.containsKey("testnetstamp")) {
if (json.get("testnetstamp").toString().equals("now") || ((Long) json.get("testnetstamp")).longValue() == 0) {
this.genesisTimestamp = System.currentTimeMillis();
} else {
this.genesisTimestamp = ((Long) json.get("testnetstamp")).longValue();
}
}
}
public boolean isTestNet() {
return this.genesisTimestamp != GenesisBlock.GENESIS_TIMESTAMP;
}
// Getters / setters
public int getMaxBytePerFee() { public int getMaxBytePerFee() {
return 1024; return this.maxBytePerFee;
} }
public long getGenesisTimestamp() { public long getGenesisTimestamp() {
if (this.genesisTimestamp != -1) return this.genesisTimestamp;
return this.genesisTimestamp;
return GenesisBlock.GENESIS_TIMESTAMP;
}
public void setGenesisTimestamp(long timestamp) {
this.genesisTimestamp = timestamp;
}
public void unsetGenesisTimestamp() {
this.genesisTimestamp = -1;
} }
} }

View File

@ -8,6 +8,7 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import org.json.simple.JSONObject;
import org.junit.After; import org.junit.After;
import org.junit.Test; import org.junit.Test;
@ -15,15 +16,18 @@ import com.google.common.hash.HashCode;
import data.account.AccountBalanceData; import data.account.AccountBalanceData;
import data.account.AccountData; import data.account.AccountData;
import data.assets.AssetData;
import data.block.BlockData; import data.block.BlockData;
import data.naming.NameData; import data.naming.NameData;
import data.transaction.BuyNameTransactionData; import data.transaction.BuyNameTransactionData;
import data.transaction.CancelSellNameTransactionData; import data.transaction.CancelSellNameTransactionData;
import data.transaction.CreatePollTransactionData; import data.transaction.CreatePollTransactionData;
import data.transaction.IssueAssetTransactionData;
import data.transaction.MessageTransactionData; import data.transaction.MessageTransactionData;
import data.transaction.PaymentTransactionData; import data.transaction.PaymentTransactionData;
import data.transaction.RegisterNameTransactionData; import data.transaction.RegisterNameTransactionData;
import data.transaction.SellNameTransactionData; import data.transaction.SellNameTransactionData;
import data.transaction.TransferAssetTransactionData;
import data.transaction.UpdateNameTransactionData; import data.transaction.UpdateNameTransactionData;
import data.transaction.VoteOnPollTransactionData; import data.transaction.VoteOnPollTransactionData;
import data.voting.PollData; import data.voting.PollData;
@ -38,15 +42,18 @@ import qora.block.BlockChain;
import qora.transaction.BuyNameTransaction; import qora.transaction.BuyNameTransaction;
import qora.transaction.CancelSellNameTransaction; import qora.transaction.CancelSellNameTransaction;
import qora.transaction.CreatePollTransaction; import qora.transaction.CreatePollTransaction;
import qora.transaction.IssueAssetTransaction;
import qora.transaction.MessageTransaction; import qora.transaction.MessageTransaction;
import qora.transaction.PaymentTransaction; import qora.transaction.PaymentTransaction;
import qora.transaction.RegisterNameTransaction; import qora.transaction.RegisterNameTransaction;
import qora.transaction.SellNameTransaction; import qora.transaction.SellNameTransaction;
import qora.transaction.Transaction; import qora.transaction.Transaction;
import qora.transaction.Transaction.ValidationResult; import qora.transaction.Transaction.ValidationResult;
import qora.transaction.TransferAssetTransaction;
import qora.transaction.UpdateNameTransaction; import qora.transaction.UpdateNameTransaction;
import qora.transaction.VoteOnPollTransaction; import qora.transaction.VoteOnPollTransaction;
import repository.AccountRepository; import repository.AccountRepository;
import repository.AssetRepository;
import repository.DataException; import repository.DataException;
import repository.Repository; import repository.Repository;
import repository.RepositoryFactory; import repository.RepositoryFactory;
@ -63,8 +70,8 @@ public class TransactionTests {
private static final byte[] senderSeed = HashCode.fromString("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef").asBytes(); private static final byte[] senderSeed = HashCode.fromString("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef").asBytes();
private static final byte[] recipientSeed = HashCode.fromString("fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210").asBytes(); private static final byte[] recipientSeed = HashCode.fromString("fedcba9876543210fedcba9876543210fedcba9876543210fedcba9876543210").asBytes();
private static final BigDecimal generatorBalance = BigDecimal.valueOf(1_000_000_000L); private static final BigDecimal initialGeneratorBalance = BigDecimal.valueOf(1_000_000_000L);
private static final BigDecimal senderBalance = BigDecimal.valueOf(1_000_000L); private static final BigDecimal initialSenderBalance = BigDecimal.valueOf(1_000_000L);
private Repository repository; private Repository repository;
private AccountRepository accountRepository; private AccountRepository accountRepository;
@ -73,6 +80,7 @@ public class TransactionTests {
private PrivateKeyAccount generator; private PrivateKeyAccount generator;
private byte[] reference; private byte[] reference;
@SuppressWarnings("unchecked")
public void createTestAccounts(Long genesisTimestamp) throws DataException { public void createTestAccounts(Long genesisTimestamp) throws DataException {
RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl); RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl);
RepositoryManager.setRepositoryFactory(repositoryFactory); RepositoryManager.setRepositoryFactory(repositoryFactory);
@ -82,10 +90,11 @@ public class TransactionTests {
} }
// [Un]set genesis timestamp as required by test // [Un]set genesis timestamp as required by test
JSONObject settingsJSON = new JSONObject();
if (genesisTimestamp != null) if (genesisTimestamp != null)
Settings.getInstance().setGenesisTimestamp(genesisTimestamp); settingsJSON.put("testnetstamp", genesisTimestamp);
else
Settings.getInstance().unsetGenesisTimestamp(); Settings.test(settingsJSON);
// This needs to be called outside of acquiring our own repository or it will deadlock // This needs to be called outside of acquiring our own repository or it will deadlock
BlockChain.validate(); BlockChain.validate();
@ -101,7 +110,7 @@ public class TransactionTests {
// Create test generator account // Create test generator account
generator = new PrivateKeyAccount(repository, generatorSeed); generator = new PrivateKeyAccount(repository, generatorSeed);
accountRepository.save(new AccountData(generator.getAddress(), generatorSeed)); accountRepository.save(new AccountData(generator.getAddress(), generatorSeed));
accountRepository.save(new AccountBalanceData(generator.getAddress(), Asset.QORA, generatorBalance)); accountRepository.save(new AccountBalanceData(generator.getAddress(), Asset.QORA, initialGeneratorBalance));
// Create test sender account // Create test sender account
sender = new PrivateKeyAccount(repository, senderSeed); sender = new PrivateKeyAccount(repository, senderSeed);
@ -111,7 +120,7 @@ public class TransactionTests {
accountRepository.save(new AccountData(sender.getAddress(), reference)); accountRepository.save(new AccountData(sender.getAddress(), reference));
// Mock balance // Mock balance
accountRepository.save(new AccountBalanceData(sender.getAddress(), Asset.QORA, senderBalance)); accountRepository.save(new AccountBalanceData(sender.getAddress(), Asset.QORA, initialSenderBalance));
repository.saveChanges(); repository.saveChanges();
} }
@ -163,12 +172,12 @@ public class TransactionTests {
repository.saveChanges(); repository.saveChanges();
// Check sender's balance // Check sender's balance
BigDecimal expectedBalance = senderBalance.subtract(amount).subtract(fee); BigDecimal expectedBalance = initialSenderBalance.subtract(amount).subtract(fee);
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance(); BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
assertTrue("Sender's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0); assertTrue("Sender's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
// Fee should be in generator's balance // Fee should be in generator's balance
expectedBalance = generatorBalance.add(fee); expectedBalance = initialGeneratorBalance.add(fee);
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance(); actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0); assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
@ -176,6 +185,22 @@ public class TransactionTests {
expectedBalance = amount; expectedBalance = amount;
actualBalance = accountRepository.getBalance(recipient.getAddress(), Asset.QORA).getBalance(); actualBalance = accountRepository.getBalance(recipient.getAddress(), Asset.QORA).getBalance();
assertTrue("Recipient's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0); assertTrue("Recipient's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
// Check recipient's reference
byte[] recipientsReference = recipient.getLastReference();
assertTrue("Recipient's new reference incorrect", Arrays.equals(paymentTransaction.getTransactionData().getSignature(), recipientsReference));
// Orphan block
block.orphan();
repository.saveChanges();
// Check sender's balance
actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
assertTrue("Sender's reverted balance incorrect", initialSenderBalance.compareTo(actualBalance) == 0);
// Check generator's balance
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
assertTrue("Generator's new balance incorrect", initialGeneratorBalance.compareTo(actualBalance) == 0);
} }
@Test @Test
@ -208,12 +233,12 @@ public class TransactionTests {
repository.saveChanges(); repository.saveChanges();
// Check sender's balance // Check sender's balance
BigDecimal expectedBalance = senderBalance.subtract(fee); BigDecimal expectedBalance = initialSenderBalance.subtract(fee);
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance(); BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
assertTrue("Sender's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0); assertTrue("Sender's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
// Fee should be in generator's balance // Fee should be in generator's balance
expectedBalance = generatorBalance.add(fee); expectedBalance = initialGeneratorBalance.add(fee);
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance(); actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0); assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
@ -445,7 +470,7 @@ public class TransactionTests {
@Test @Test
public void testCreatePollTransaction() throws DataException { public void testCreatePollTransaction() throws DataException {
// This test requires GenesisBlock's timestamp is set to something after BlockChain.VOTING_RELEASE_TIMESTAMP // This test requires GenesisBlock's timestamp is set to something after BlockChain.VOTING_RELEASE_TIMESTAMP
createTestAccounts(BlockChain.VOTING_RELEASE_TIMESTAMP + 1_000L); createTestAccounts(BlockChain.getVotingReleaseTimestamp() + 1_000L);
// Make a new create poll transaction // Make a new create poll transaction
String pollName = "test poll"; String pollName = "test poll";
@ -479,12 +504,12 @@ public class TransactionTests {
repository.saveChanges(); repository.saveChanges();
// Check sender's balance // Check sender's balance
BigDecimal expectedBalance = senderBalance.subtract(fee); BigDecimal expectedBalance = initialSenderBalance.subtract(fee);
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance(); BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
assertTrue("Sender's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0); assertTrue("Sender's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
// Fee should be in generator's balance // Fee should be in generator's balance
expectedBalance = generatorBalance.add(fee); expectedBalance = initialGeneratorBalance.add(fee);
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance(); actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0); assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
@ -575,12 +600,174 @@ public class TransactionTests {
@Test @Test
public void testIssueAssetTransaction() throws DataException { public void testIssueAssetTransaction() throws DataException {
// TODO createTestAccounts(null);
// Create new asset
String assetName = "test asset";
String description = "test asset description";
long quantity = 1_000_000L;
boolean isDivisible = false;
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.calcSignature(sender);
assertTrue(issueAssetTransaction.isSignatureValid());
assertEquals(ValidationResult.OK, issueAssetTransaction.isValid());
// Forge new block with payment transaction
Block block = new Block(repository, parentBlockData, generator, null, null);
block.addTransaction(issueAssetTransactionData);
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 = initialSenderBalance.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 = initialGeneratorBalance.add(fee);
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
// 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("Sender's reverted balance incorrect", initialSenderBalance.compareTo(actualBalance) == 0);
// Check generator's balance
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
assertTrue("Generator's reverted balance incorrect", initialGeneratorBalance.compareTo(actualBalance) == 0);
// 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 @Test
public void testTransferAssetTransaction() throws DataException { public void testTransferAssetTransaction() throws DataException {
// TODO // 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.calcSignature(sender);
assertTrue(transferAssetTransaction.isSignatureValid());
assertEquals(ValidationResult.OK, transferAssetTransaction.isValid());
// Forge new block with payment transaction
Block block = new Block(repository, parentBlockData, generator, null, null);
block.addTransaction(transferAssetTransactionData);
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 = originalSenderBalance.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 = originalGeneratorBalance.add(fee);
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
// 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("Sender's reverted balance incorrect", originalSenderBalance.compareTo(actualBalance) == 0);
// Check generator's balance
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
assertTrue("Generator's reverted balance incorrect", originalGeneratorBalance.compareTo(actualBalance) == 0);
// 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 @Test
@ -600,7 +787,7 @@ public class TransactionTests {
@Test @Test
public void testMessageTransaction() throws DataException, UnsupportedEncodingException { public void testMessageTransaction() throws DataException, UnsupportedEncodingException {
createTestAccounts(null); createTestAccounts(1431861220336L); // timestamp taken from main blockchain block 99000
// Make a new message transaction // Make a new message transaction
Account recipient = new PublicKeyAccount(repository, recipientSeed); Account recipient = new PublicKeyAccount(repository, recipientSeed);
@ -632,12 +819,12 @@ public class TransactionTests {
repository.saveChanges(); repository.saveChanges();
// Check sender's balance // Check sender's balance
BigDecimal expectedBalance = senderBalance.subtract(amount).subtract(fee); BigDecimal expectedBalance = initialSenderBalance.subtract(amount).subtract(fee);
BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance(); BigDecimal actualBalance = accountRepository.getBalance(sender.getAddress(), Asset.QORA).getBalance();
assertTrue("Sender's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0); assertTrue("Sender's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);
// Fee should be in generator's balance // Fee should be in generator's balance
expectedBalance = generatorBalance.add(fee); expectedBalance = initialGeneratorBalance.add(fee);
actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance(); actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance();
assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0); assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);