diff --git a/src/data/transaction/IssueAssetTransactionData.java b/src/data/transaction/IssueAssetTransactionData.java index a5f3f0a2..cc5168b0 100644 --- a/src/data/transaction/IssueAssetTransactionData.java +++ b/src/data/transaction/IssueAssetTransactionData.java @@ -36,6 +36,11 @@ public class IssueAssetTransactionData extends TransactionData { 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 public Long getAssetId() { diff --git a/src/qora/block/Block.java b/src/qora/block/Block.java index 9d42c5fc..22d5863b 100644 --- a/src/qora/block/Block.java +++ b/src/qora/block/Block.java @@ -155,9 +155,9 @@ public class Block { * @return 1, 2 or 3 */ public int getNextBlockVersion() { - if (this.blockData.getHeight() < BlockChain.AT_BLOCK_HEIGHT_RELEASE) + if (this.blockData.getHeight() < BlockChain.getATReleaseHeight()) return 1; - else if (this.blockData.getTimestamp() < BlockChain.POWFIX_RELEASE_TIMESTAMP) + else if (this.blockData.getTimestamp() < BlockChain.getPowFixReleaseTimestamp()) return 2; else return 3; diff --git a/src/qora/block/BlockChain.java b/src/qora/block/BlockChain.java index 877b8dec..476a27d1 100644 --- a/src/qora/block/BlockChain.java +++ b/src/qora/block/BlockChain.java @@ -10,6 +10,7 @@ import repository.BlockRepository; import repository.DataException; import repository.Repository; import repository.RepositoryManager; +import settings.Settings; /** * Class representing the blockchain as a whole. @@ -31,11 +32,11 @@ public class BlockChain { public static final long BLOCK_TIMESTAMP_MARGIN = 500L; // Various release timestamps / block heights - public static final int MESSAGE_RELEASE_HEIGHT = 99000; - public static final int AT_BLOCK_HEIGHT_RELEASE = 99000; - public 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 - public static final long VOTING_RELEASE_TIMESTAMP = 1403715600000L; // 2014-06-25T17:00:00+00:00 + private static final int MESSAGE_RELEASE_HEIGHT = 99000; + private static final int AT_RELEASE_HEIGHT = 99000; + private static final long POWFIX_RELEASE_TIMESTAMP = 1456426800000L; // Block Version 3 // 2016-02-25T19:00:00+00:00 + private static final long ASSETS_RELEASE_TIMESTAMP = 0L; // From Qora epoch + private static final long VOTING_RELEASE_TIMESTAMP = 1403715600000L; // 2014-06-25T17:00:00+00:00 /** * Some sort start-up/initialization/checking method. @@ -96,4 +97,39 @@ public class BlockChain { 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; + } + } diff --git a/src/qora/payment/Payment.java b/src/qora/payment/Payment.java index 21837f65..756f0a7e 100644 --- a/src/qora/payment/Payment.java +++ b/src/qora/payment/Payment.java @@ -128,7 +128,7 @@ public class Payment { Account sender = new PublicKeyAccount(this.repository, senderPublicKey); // 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 sender.setLastReference(reference); diff --git a/src/qora/transaction/CreateOrderTransaction.java b/src/qora/transaction/CreateOrderTransaction.java index 1b4680a2..ff9c059c 100644 --- a/src/qora/transaction/CreateOrderTransaction.java +++ b/src/qora/transaction/CreateOrderTransaction.java @@ -115,7 +115,7 @@ public class CreateOrderTransaction extends Transaction { // Check creator has enough funds for fee in QORA // 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) return ValidationResult.NO_BALANCE; } diff --git a/src/qora/transaction/CreatePollTransaction.java b/src/qora/transaction/CreatePollTransaction.java index 78c1c9b9..949bfb97 100644 --- a/src/qora/transaction/CreatePollTransaction.java +++ b/src/qora/transaction/CreatePollTransaction.java @@ -77,7 +77,7 @@ public class CreatePollTransaction extends Transaction { public ValidationResult isValid() throws DataException { // Are CreatePollTransactions even allowed at this point? // 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; // Check owner address is valid diff --git a/src/qora/transaction/IssueAssetTransaction.java b/src/qora/transaction/IssueAssetTransaction.java index d518d385..05127cfc 100644 --- a/src/qora/transaction/IssueAssetTransaction.java +++ b/src/qora/transaction/IssueAssetTransaction.java @@ -79,7 +79,7 @@ public class IssueAssetTransaction extends Transaction { public ValidationResult isValid() throws DataException { // Are IssueAssetTransactions even allowed at this point? // 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; // Check owner address is valid diff --git a/src/qora/transaction/MessageTransaction.java b/src/qora/transaction/MessageTransaction.java index f30b96da..d12f3c8e 100644 --- a/src/qora/transaction/MessageTransaction.java +++ b/src/qora/transaction/MessageTransaction.java @@ -90,7 +90,7 @@ public class MessageTransaction extends Transaction { if (messageTransactionData.getVersion() != MessageTransaction.getVersionByTimestamp(messageTransactionData.getTimestamp())) 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; // Check data length diff --git a/src/qora/transaction/MultiPaymentTransaction.java b/src/qora/transaction/MultiPaymentTransaction.java index bf3d3fb9..1dcd4ed4 100644 --- a/src/qora/transaction/MultiPaymentTransaction.java +++ b/src/qora/transaction/MultiPaymentTransaction.java @@ -90,7 +90,7 @@ public class MultiPaymentTransaction extends Transaction { List payments = multiPaymentTransactionData.getPayments(); // Are MultiPaymentTransactions even allowed at this point? - if (NTP.getTime() < BlockChain.ASSETS_RELEASE_TIMESTAMP) + if (NTP.getTime() < BlockChain.getAssetsReleaseTimestamp()) return ValidationResult.NOT_YET_RELEASED; // Check number of payments @@ -105,7 +105,7 @@ public class MultiPaymentTransaction extends Transaction { // Check sender has enough funds for fee // 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) return ValidationResult.NO_BALANCE; diff --git a/src/qora/transaction/Transaction.java b/src/qora/transaction/Transaction.java index 2c4156b2..7364d362 100644 --- a/src/qora/transaction/Transaction.java +++ b/src/qora/transaction/Transaction.java @@ -206,7 +206,7 @@ public abstract class Transaction { * @return transaction version number, likely 1 or 3 */ public static int getVersionByTimestamp(long timestamp) { - if (timestamp < BlockChain.POWFIX_RELEASE_TIMESTAMP) { + if (timestamp < BlockChain.getPowFixReleaseTimestamp()) { return 1; } else { return 3; diff --git a/src/qora/transaction/TransferAssetTransaction.java b/src/qora/transaction/TransferAssetTransaction.java index 6b410dc3..be100a00 100644 --- a/src/qora/transaction/TransferAssetTransaction.java +++ b/src/qora/transaction/TransferAssetTransaction.java @@ -85,7 +85,7 @@ public class TransferAssetTransaction extends Transaction { TransferAssetTransactionData transferAssetTransactionData = (TransferAssetTransactionData) this.transactionData; // Are IssueAssetTransactions even allowed at this point? - if (NTP.getTime() < BlockChain.ASSETS_RELEASE_TIMESTAMP) + if (NTP.getTime() < BlockChain.getAssetsReleaseTimestamp()) return ValidationResult.NOT_YET_RELEASED; // Check reference is correct diff --git a/src/qora/transaction/VoteOnPollTransaction.java b/src/qora/transaction/VoteOnPollTransaction.java index ac7deb8d..2de138b3 100644 --- a/src/qora/transaction/VoteOnPollTransaction.java +++ b/src/qora/transaction/VoteOnPollTransaction.java @@ -68,7 +68,7 @@ public class VoteOnPollTransaction extends Transaction { public ValidationResult isValid() throws DataException { // Are VoteOnPollTransactions even allowed at this point? // 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; // Check name size bounds diff --git a/src/repository/AssetRepository.java b/src/repository/AssetRepository.java index 66ff6745..977b2644 100644 --- a/src/repository/AssetRepository.java +++ b/src/repository/AssetRepository.java @@ -9,6 +9,8 @@ public interface AssetRepository { public AssetData fromAssetId(long assetId) throws DataException; + public AssetData fromAssetName(String assetName) throws DataException; + public boolean assetExists(long assetId) throws DataException; public boolean assetExists(String assetName) throws DataException; diff --git a/src/repository/hsqldb/HSQLDBAssetRepository.java b/src/repository/hsqldb/HSQLDBAssetRepository.java index 64498a0c..82ff6692 100644 --- a/src/repository/hsqldb/HSQLDBAssetRepository.java +++ b/src/repository/hsqldb/HSQLDBAssetRepository.java @@ -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 { try { return this.repository.exists("Assets", "asset_id = ?", assetId); @@ -73,7 +93,7 @@ public class HSQLDBAssetRepository implements AssetRepository { public void delete(long assetId) throws DataException { try { - this.repository.delete("Assets", "assetId = ?", assetId); + this.repository.delete("Assets", "asset_id = ?", assetId); } catch (SQLException e) { throw new DataException("Unable to delete asset from repository", e); } diff --git a/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java b/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java index faa843d9..90e37f7f 100644 --- a/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java +++ b/src/repository/hsqldb/transaction/HSQLDBIssueAssetTransactionRepository.java @@ -46,9 +46,9 @@ public class HSQLDBIssueAssetTransactionRepository extends HSQLDBTransactionRepo HSQLDBSaver saveHelper = new HSQLDBSaver("IssueAssetTransactions"); saveHelper.bind("signature", issueAssetTransactionData.getSignature()).bind("issuer", issueAssetTransactionData.getIssuerPublicKey()) - .bind("asset_name", issueAssetTransactionData.getAssetName()).bind("description", issueAssetTransactionData.getDescription()) - .bind("quantity", issueAssetTransactionData.getQuantity()).bind("is_divisible", issueAssetTransactionData.getIsDivisible()) - .bind("asset_id", issueAssetTransactionData.getAssetId()); + .bind("owner", issueAssetTransactionData.getOwner()).bind("asset_name", issueAssetTransactionData.getAssetName()) + .bind("description", issueAssetTransactionData.getDescription()).bind("quantity", issueAssetTransactionData.getQuantity()) + .bind("is_divisible", issueAssetTransactionData.getIsDivisible()).bind("asset_id", issueAssetTransactionData.getAssetId()); try { saveHelper.execute(this.repository); diff --git a/src/settings/Settings.java b/src/settings/Settings.java index 8c344aa7..2e64fea0 100644 --- a/src/settings/Settings.java +++ b/src/settings/Settings.java @@ -1,38 +1,118 @@ 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; public class Settings { - private static Settings instance; - // 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 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() { if (instance == null) - instance = new Settings(); + instance = new Settings(SETTINGS_FILENAME); 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() { - return 1024; + return this.maxBytePerFee; } public long getGenesisTimestamp() { - if (this.genesisTimestamp != -1) - return this.genesisTimestamp; - - return GenesisBlock.GENESIS_TIMESTAMP; - } - - public void setGenesisTimestamp(long timestamp) { - this.genesisTimestamp = timestamp; - } - - public void unsetGenesisTimestamp() { - this.genesisTimestamp = -1; + return this.genesisTimestamp; } } diff --git a/src/test/TransactionTests.java b/src/test/TransactionTests.java index 5aad9355..848c53bf 100644 --- a/src/test/TransactionTests.java +++ b/src/test/TransactionTests.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.json.simple.JSONObject; import org.junit.After; import org.junit.Test; @@ -15,15 +16,18 @@ import com.google.common.hash.HashCode; import data.account.AccountBalanceData; import data.account.AccountData; +import data.assets.AssetData; import data.block.BlockData; import data.naming.NameData; import data.transaction.BuyNameTransactionData; import data.transaction.CancelSellNameTransactionData; import data.transaction.CreatePollTransactionData; +import data.transaction.IssueAssetTransactionData; import data.transaction.MessageTransactionData; 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; @@ -38,15 +42,18 @@ import qora.block.BlockChain; import qora.transaction.BuyNameTransaction; import qora.transaction.CancelSellNameTransaction; import qora.transaction.CreatePollTransaction; +import qora.transaction.IssueAssetTransaction; import qora.transaction.MessageTransaction; 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; @@ -63,8 +70,8 @@ public class TransactionTests { private static final byte[] senderSeed = HashCode.fromString("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef").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 senderBalance = BigDecimal.valueOf(1_000_000L); + private static final BigDecimal initialGeneratorBalance = BigDecimal.valueOf(1_000_000_000L); + private static final BigDecimal initialSenderBalance = BigDecimal.valueOf(1_000_000L); private Repository repository; private AccountRepository accountRepository; @@ -73,6 +80,7 @@ public class TransactionTests { private PrivateKeyAccount generator; private byte[] reference; + @SuppressWarnings("unchecked") public void createTestAccounts(Long genesisTimestamp) throws DataException { RepositoryFactory repositoryFactory = new HSQLDBRepositoryFactory(connectionUrl); RepositoryManager.setRepositoryFactory(repositoryFactory); @@ -82,10 +90,11 @@ public class TransactionTests { } // [Un]set genesis timestamp as required by test + JSONObject settingsJSON = new JSONObject(); if (genesisTimestamp != null) - Settings.getInstance().setGenesisTimestamp(genesisTimestamp); - else - Settings.getInstance().unsetGenesisTimestamp(); + settingsJSON.put("testnetstamp", genesisTimestamp); + + Settings.test(settingsJSON); // This needs to be called outside of acquiring our own repository or it will deadlock BlockChain.validate(); @@ -101,7 +110,7 @@ public class TransactionTests { // Create test generator account generator = new PrivateKeyAccount(repository, 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 sender = new PrivateKeyAccount(repository, senderSeed); @@ -111,7 +120,7 @@ public class TransactionTests { accountRepository.save(new AccountData(sender.getAddress(), reference)); // Mock balance - accountRepository.save(new AccountBalanceData(sender.getAddress(), Asset.QORA, senderBalance)); + accountRepository.save(new AccountBalanceData(sender.getAddress(), Asset.QORA, initialSenderBalance)); repository.saveChanges(); } @@ -163,12 +172,12 @@ public class TransactionTests { repository.saveChanges(); // 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(); assertTrue("Sender's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0); // Fee should be in generator's balance - expectedBalance = generatorBalance.add(fee); + expectedBalance = initialGeneratorBalance.add(fee); actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance(); assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0); @@ -176,6 +185,22 @@ public class TransactionTests { expectedBalance = amount; actualBalance = accountRepository.getBalance(recipient.getAddress(), Asset.QORA).getBalance(); 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 @@ -208,12 +233,12 @@ public class TransactionTests { repository.saveChanges(); // Check sender's balance - BigDecimal expectedBalance = senderBalance.subtract(fee); + 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 = generatorBalance.add(fee); + expectedBalance = initialGeneratorBalance.add(fee); actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance(); assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0); @@ -445,7 +470,7 @@ public class TransactionTests { @Test public void testCreatePollTransaction() throws DataException { // 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 String pollName = "test poll"; @@ -479,12 +504,12 @@ public class TransactionTests { repository.saveChanges(); // Check sender's balance - BigDecimal expectedBalance = senderBalance.subtract(fee); + 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 = generatorBalance.add(fee); + expectedBalance = initialGeneratorBalance.add(fee); actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance(); assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0); @@ -575,12 +600,174 @@ public class TransactionTests { @Test 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 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 @@ -600,7 +787,7 @@ public class TransactionTests { @Test public void testMessageTransaction() throws DataException, UnsupportedEncodingException { - createTestAccounts(null); + createTestAccounts(1431861220336L); // timestamp taken from main blockchain block 99000 // Make a new message transaction Account recipient = new PublicKeyAccount(repository, recipientSeed); @@ -632,12 +819,12 @@ public class TransactionTests { repository.saveChanges(); // 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(); assertTrue("Sender's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0); // Fee should be in generator's balance - expectedBalance = generatorBalance.add(fee); + expectedBalance = initialGeneratorBalance.add(fee); actualBalance = accountRepository.getBalance(generator.getAddress(), Asset.QORA).getBalance(); assertTrue("Generator's new balance incorrect", expectedBalance.compareTo(actualBalance) == 0);