diff --git a/pom.xml b/pom.xml index 30ad1e04..5ba103e6 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.qortal qortal - 4.2.2 + 4.2.4 jar true diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index e030e6a6..cf227d4a 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -130,6 +130,9 @@ public class Block { /** Locally-generated AT fees */ protected long ourAtFees; // Generated locally + /** Cached online accounts validation decision, to avoid revalidating when true */ + private boolean onlineAccountsAlreadyValid = false; + @FunctionalInterface private interface BlockRewardDistributor { long distribute(long amount, Map balanceChanges) throws DataException; @@ -563,6 +566,13 @@ public class Block { } + /** + * Force online accounts to be revalidated, e.g. at final stage of block minting. + */ + public void clearOnlineAccountsValidationCache() { + this.onlineAccountsAlreadyValid = false; + } + // More information /** @@ -1043,6 +1053,10 @@ public class Block { if (this.blockData.getHeight() != null && this.blockData.getHeight() == 1) return ValidationResult.OK; + // Don't bother revalidating if accounts have already been validated in this block + if (this.onlineAccountsAlreadyValid) + return ValidationResult.OK; + // Expand block's online accounts indexes into actual accounts ConciseSet accountIndexes = BlockTransformer.decodeOnlineAccounts(this.blockData.getEncodedOnlineAccounts()); // We use count of online accounts to validate decoded account indexes @@ -1130,6 +1144,9 @@ public class Block { // All online accounts valid, so save our list of online accounts for potential later use this.cachedOnlineRewardShares = onlineRewardShares; + // Remember that the accounts are valid, to speed up subsequent checks + this.onlineAccountsAlreadyValid = true; + return ValidationResult.OK; } diff --git a/src/main/java/org/qortal/block/BlockChain.java b/src/main/java/org/qortal/block/BlockChain.java index 218fb14d..d79203da 100644 --- a/src/main/java/org/qortal/block/BlockChain.java +++ b/src/main/java/org/qortal/block/BlockChain.java @@ -48,9 +48,6 @@ public class BlockChain { /** Transaction expiry period, starting from transaction's timestamp, in milliseconds. */ private long transactionExpiryPeriod; - @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) - private long unitFee; - private int maxBytesPerUnitFee; /** Maximum acceptable timestamp disagreement offset in milliseconds. */ @@ -89,6 +86,7 @@ public class BlockChain { @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) public long fee; } + private List unitFees; private List nameRegistrationUnitFees; /** Map of which blockchain features are enabled when (height/timestamp) */ @@ -346,10 +344,6 @@ public class BlockChain { return this.isTestChain; } - public long getUnitFee() { - return this.unitFee; - } - public int getMaxBytesPerUnitFee() { return this.maxBytesPerUnitFee; } @@ -547,13 +541,22 @@ public class BlockChain { throw new IllegalStateException(String.format("No block timing info available for height %d", ourHeight)); } + public long getUnitFeeAtTimestamp(long ourTimestamp) { + for (int i = unitFees.size() - 1; i >= 0; --i) + if (unitFees.get(i).timestamp <= ourTimestamp) + return unitFees.get(i).fee; + + // Shouldn't happen, but set a sensible default just in case + return 100000; + } + public long getNameRegistrationUnitFeeAtTimestamp(long ourTimestamp) { for (int i = nameRegistrationUnitFees.size() - 1; i >= 0; --i) if (nameRegistrationUnitFees.get(i).timestamp <= ourTimestamp) return nameRegistrationUnitFees.get(i).fee; - // Default to system-wide unit fee - return this.getUnitFee(); + // Shouldn't happen, but set a sensible default just in case + return 100000; } public int getMaxRewardSharesAtTimestamp(long ourTimestamp) { diff --git a/src/main/java/org/qortal/controller/BlockMinter.java b/src/main/java/org/qortal/controller/BlockMinter.java index b1ed7e3c..35c89778 100644 --- a/src/main/java/org/qortal/controller/BlockMinter.java +++ b/src/main/java/org/qortal/controller/BlockMinter.java @@ -562,6 +562,9 @@ public class BlockMinter extends Thread { // Sign to create block's signature newBlock.sign(); + // Ensure online accounts are fully re-validated in this final check + newBlock.clearOnlineAccountsValidationCache(); + // Is newBlock still valid? ValidationResult validationResult = newBlock.isValid(); if (validationResult != ValidationResult.OK) diff --git a/src/main/java/org/qortal/controller/Synchronizer.java b/src/main/java/org/qortal/controller/Synchronizer.java index 2dad62e7..804bacbb 100644 --- a/src/main/java/org/qortal/controller/Synchronizer.java +++ b/src/main/java/org/qortal/controller/Synchronizer.java @@ -229,13 +229,6 @@ public class Synchronizer extends Thread { peers.removeIf(Controller.hasOldVersion); checkRecoveryModeForPeers(peers); - if (recoveryMode) { - // Needs a mutable copy of the unmodifiableList - peers = new ArrayList<>(Network.getInstance().getImmutableHandshakedPeers()); - peers.removeIf(Controller.hasOnlyGenesisBlock); - peers.removeIf(Controller.hasMisbehaved); - peers.removeIf(Controller.hasOldVersion); - } // Check we have enough peers to potentially synchronize if (peers.size() < Settings.getInstance().getMinBlockchainPeers()) @@ -262,10 +255,7 @@ public class Synchronizer extends Thread { peers.removeIf(Controller.hasInferiorChainTip); // Remove any peers that are no longer on a recent block since the last check - // Except for times when we're in recovery mode, in which case we need to keep them - if (!recoveryMode) { - peers.removeIf(Controller.hasNoRecentBlock); - } + peers.removeIf(Controller.hasNoRecentBlock); final int peersRemoved = peersBeforeComparison - peers.size(); if (peersRemoved > 0 && peers.size() > 0) @@ -1340,8 +1330,8 @@ public class Synchronizer extends Thread { return SynchronizationResult.INVALID_DATA; } - // Final check to make sure the peer isn't out of date (except for when we're in recovery mode) - if (!recoveryMode && peer.getChainTipData() != null) { + // Final check to make sure the peer isn't out of date + if (peer.getChainTipData() != null) { final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp(); final Long peerLastBlockTimestamp = peer.getChainTipData().getTimestamp(); if (peerLastBlockTimestamp == null || peerLastBlockTimestamp < minLatestBlockTimestamp) { diff --git a/src/main/java/org/qortal/controller/TransactionImporter.java b/src/main/java/org/qortal/controller/TransactionImporter.java index 5c70f369..fa35cbbe 100644 --- a/src/main/java/org/qortal/controller/TransactionImporter.java +++ b/src/main/java/org/qortal/controller/TransactionImporter.java @@ -47,6 +47,9 @@ public class TransactionImporter extends Thread { /** Map of recent invalid unconfirmed transactions. Key is base58 transaction signature, value is do-not-request expiry timestamp. */ private final Map invalidUnconfirmedTransactions = Collections.synchronizedMap(new HashMap<>()); + /** Cached list of unconfirmed transactions, used when counting per creator. This is replaced regularly */ + public static List unconfirmedTransactionsCache = null; + public static synchronized TransactionImporter getInstance() { if (instance == null) { @@ -254,6 +257,12 @@ public class TransactionImporter extends Thread { int processedCount = 0; try (final Repository repository = RepositoryManager.getRepository()) { + // Use a single copy of the unconfirmed transactions list for each cycle, to speed up constant lookups + // when counting unconfirmed transactions by creator. + List unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions(); + unconfirmedTransactions.removeIf(t -> t.getType() == Transaction.TransactionType.CHAT); + unconfirmedTransactionsCache = unconfirmedTransactions; + // Import transactions with valid signatures try { for (int i = 0; i < sigValidTransactions.size(); ++i) { @@ -286,6 +295,11 @@ public class TransactionImporter extends Thread { case OK: { LOGGER.debug(() -> String.format("Imported %s transaction %s", transactionData.getType().name(), Base58.encode(transactionData.getSignature()))); + + // Add to the unconfirmed transactions cache + if (transactionData.getType() != Transaction.TransactionType.CHAT && unconfirmedTransactionsCache != null) { + unconfirmedTransactionsCache.add(transactionData); + } break; } @@ -317,6 +331,9 @@ public class TransactionImporter extends Thread { } finally { LOGGER.debug("Finished importing {} incoming transaction{}", processedCount, (processedCount == 1 ? "" : "s")); blockchainLock.unlock(); + + // Clear the unconfirmed transaction cache so new data can be populated in the next cycle + unconfirmedTransactionsCache = null; } } catch (DataException e) { LOGGER.error("Repository issue while importing incoming transactions", e); diff --git a/src/main/java/org/qortal/settings/Settings.java b/src/main/java/org/qortal/settings/Settings.java index ac9b8857..8d4eab56 100644 --- a/src/main/java/org/qortal/settings/Settings.java +++ b/src/main/java/org/qortal/settings/Settings.java @@ -227,7 +227,7 @@ public class Settings { private int maxRetries = 2; /** The number of seconds of no activity before recovery mode begins */ - public long recoveryModeTimeout = 24 * 60 * 60 * 1000L; + public long recoveryModeTimeout = 9999999999999L; /** Minimum peer version number required in order to sync with them */ private String minPeerVersion = "4.1.2"; diff --git a/src/main/java/org/qortal/transaction/Transaction.java b/src/main/java/org/qortal/transaction/Transaction.java index bd91f25a..f750aff5 100644 --- a/src/main/java/org/qortal/transaction/Transaction.java +++ b/src/main/java/org/qortal/transaction/Transaction.java @@ -13,6 +13,7 @@ import org.qortal.account.PublicKeyAccount; import org.qortal.asset.Asset; import org.qortal.block.BlockChain; import org.qortal.controller.Controller; +import org.qortal.controller.TransactionImporter; import org.qortal.crypto.Crypto; import org.qortal.data.block.BlockData; import org.qortal.data.group.GroupApprovalData; @@ -377,7 +378,7 @@ public abstract class Transaction { * @return */ public long getUnitFee(Long timestamp) { - return BlockChain.getInstance().getUnitFee(); + return BlockChain.getInstance().getUnitFeeAtTimestamp(timestamp); } /** @@ -617,7 +618,10 @@ public abstract class Transaction { } private int countUnconfirmedByCreator(PublicKeyAccount creator) throws DataException { - List unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions(); + List unconfirmedTransactions = TransactionImporter.getInstance().unconfirmedTransactionsCache; + if (unconfirmedTransactions == null) { + unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions(); + } // We exclude CHAT transactions as they never get included into blocks and // have spam/DoS prevention by requiring proof of work diff --git a/src/main/resources/blockchain.json b/src/main/resources/blockchain.json index c6151204..1cf4f010 100644 --- a/src/main/resources/blockchain.json +++ b/src/main/resources/blockchain.json @@ -3,8 +3,12 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.001", + "unitFees": [ + { "timestamp": 0, "fee": "0.001" }, + { "timestamp": 1692118800000, "fee": "0.01" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.001" }, { "timestamp": 1645372800000, "fee": "5" }, { "timestamp": 1651420800000, "fee": "1.25" } ], diff --git a/src/test/java/org/qortal/test/MessageTests.java b/src/test/java/org/qortal/test/MessageTests.java index f08c7b2f..4d0ecfcc 100644 --- a/src/test/java/org/qortal/test/MessageTests.java +++ b/src/test/java/org/qortal/test/MessageTests.java @@ -85,7 +85,7 @@ public class MessageTests extends Common { byte[] randomReference = new byte[64]; random.nextBytes(randomReference); - long minimumFee = BlockChain.getInstance().getUnitFee(); + long minimumFee = BlockChain.getInstance().getUnitFeeAtTimestamp(System.currentTimeMillis()); try (final Repository repository = RepositoryManager.getRepository()) { PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); diff --git a/src/test/java/org/qortal/test/common/transaction/TestTransaction.java b/src/test/java/org/qortal/test/common/transaction/TestTransaction.java index 11fdf58e..b580ecd3 100644 --- a/src/test/java/org/qortal/test/common/transaction/TestTransaction.java +++ b/src/test/java/org/qortal/test/common/transaction/TestTransaction.java @@ -7,13 +7,15 @@ import org.qortal.block.BlockChain; import org.qortal.data.transaction.BaseTransactionData; import org.qortal.group.Group; import org.qortal.repository.DataException; +import org.qortal.utils.NTP; public abstract class TestTransaction { protected static final Random random = new Random(); public static BaseTransactionData generateBase(PrivateKeyAccount account, int txGroupId) throws DataException { - return new BaseTransactionData(System.currentTimeMillis(), txGroupId, account.getLastReference(), account.getPublicKey(), BlockChain.getInstance().getUnitFee(), null); + long timestamp = System.currentTimeMillis(); + return new BaseTransactionData(timestamp, txGroupId, account.getLastReference(), account.getPublicKey(), BlockChain.getInstance().getUnitFeeAtTimestamp(timestamp), null); } public static BaseTransactionData generateBase(PrivateKeyAccount account) throws DataException { diff --git a/src/test/java/org/qortal/test/naming/BuySellTests.java b/src/test/java/org/qortal/test/naming/BuySellTests.java index 4530820e..f0e97b94 100644 --- a/src/test/java/org/qortal/test/naming/BuySellTests.java +++ b/src/test/java/org/qortal/test/naming/BuySellTests.java @@ -44,7 +44,7 @@ public class BuySellTests extends Common { bob = Common.getTestAccount(repository, "bob"); name = "test name" + " " + random.nextInt(1_000_000); - price = random.nextInt(1000) * Amounts.MULTIPLIER; + price = (random.nextInt(1000) + 1) * Amounts.MULTIPLIER; } @After diff --git a/src/test/java/org/qortal/test/naming/MiscTests.java b/src/test/java/org/qortal/test/naming/MiscTests.java index 2bcd098d..91eb3dc9 100644 --- a/src/test/java/org/qortal/test/naming/MiscTests.java +++ b/src/test/java/org/qortal/test/naming/MiscTests.java @@ -20,6 +20,7 @@ import org.qortal.repository.Repository; import org.qortal.repository.RepositoryManager; import org.qortal.test.common.*; import org.qortal.test.common.transaction.TestTransaction; +import org.qortal.transaction.PaymentTransaction; import org.qortal.transaction.RegisterNameTransaction; import org.qortal.transaction.Transaction; import org.qortal.transaction.Transaction.ValidationResult; @@ -329,15 +330,19 @@ public class MiscTests extends Common { public void testRegisterNameFeeIncrease() throws Exception { try (final Repository repository = RepositoryManager.getRepository()) { - // Set nameRegistrationUnitFeeTimestamp to a time far in the future + // Add original fee to nameRegistrationUnitFees + UnitFeesByTimestamp originalFee = new UnitFeesByTimestamp(); + originalFee.timestamp = 0L; + originalFee.fee = new AmountTypeAdapter().unmarshal("0.1"); + + // Add a time far in the future to nameRegistrationUnitFees UnitFeesByTimestamp futureFeeIncrease = new UnitFeesByTimestamp(); futureFeeIncrease.timestamp = 9999999999999L; // 20 Nov 2286 futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("5"); - FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(futureFeeIncrease), true); + FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(originalFee, futureFeeIncrease), true); assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); // Validate unit fees pre and post timestamp - assertEquals(10000000, BlockChain.getInstance().getUnitFee()); // 0.1 QORT assertEquals(10000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp - 1)); // 0.1 QORT assertEquals(500000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); // 5 QORT @@ -362,7 +367,7 @@ public class MiscTests extends Common { futureFeeIncrease.timestamp = now + (60 * 60 * 1000L); // 1 hour in the future futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("10"); - FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(pastFeeIncrease, futureFeeIncrease), true); + FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(originalFee, pastFeeIncrease, futureFeeIncrease), true); assertEquals(pastFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(pastFeeIncrease.timestamp)); assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); @@ -387,4 +392,123 @@ public class MiscTests extends Common { } } + // test reading the name registration fee schedule from blockchain.json / test-chain-v2.json + @Test + public void testRegisterNameFeeScheduleInTestchainData() throws Exception { + try (final Repository repository = RepositoryManager.getRepository()) { + + final long expectedFutureFeeIncreaseTimestamp = 9999999999999L; // 20 Nov 2286, as per test-chain-v2.json + final long expectedFutureFeeIncreaseValue = new AmountTypeAdapter().unmarshal("5"); + + assertEquals(expectedFutureFeeIncreaseValue, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp)); + + // Validate unit fees pre and post timestamp + assertEquals(10000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp - 1)); // 0.1 QORT + assertEquals(500000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp)); // 5 QORT + + // Register-name + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + String name = "test-name"; + String data = "{\"age\":30}"; + + RegisterNameTransactionData transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, data); + transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp())); + assertEquals(10000000L, transactionData.getFee().longValue()); + TransactionUtils.signAndMint(repository, transactionData, alice); + } + } + + + + // test general unit fee increase + @Test + public void testUnitFeeIncrease() throws Exception { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Add original fee to unitFees + UnitFeesByTimestamp originalFee = new UnitFeesByTimestamp(); + originalFee.timestamp = 0L; + originalFee.fee = new AmountTypeAdapter().unmarshal("0.1"); + + // Add a time far in the future to unitFees + UnitFeesByTimestamp futureFeeIncrease = new UnitFeesByTimestamp(); + futureFeeIncrease.timestamp = 9999999999999L; // 20 Nov 2286 + futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("1"); + FieldUtils.writeField(BlockChain.getInstance(), "unitFees", Arrays.asList(originalFee, futureFeeIncrease), true); + assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); + + // Validate unit fees pre and post timestamp + assertEquals(10000000, BlockChain.getInstance().getUnitFeeAtTimestamp(futureFeeIncrease.timestamp - 1)); // 0.1 QORT + assertEquals(100000000, BlockChain.getInstance().getUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); // 1 QORT + + // Payment + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); + + PaymentTransactionData transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), bob.getAddress(), 100000); + transactionData.setFee(new PaymentTransaction(null, null).getUnitFee(transactionData.getTimestamp())); + assertEquals(10000000L, transactionData.getFee().longValue()); + TransactionUtils.signAndMint(repository, transactionData, alice); + + // Set fee increase to a time in the past + Long now = NTP.getTime(); + UnitFeesByTimestamp pastFeeIncrease = new UnitFeesByTimestamp(); + pastFeeIncrease.timestamp = now - 1000L; // 1 second ago + pastFeeIncrease.fee = new AmountTypeAdapter().unmarshal("3"); + + // Set another increase in the future + futureFeeIncrease = new UnitFeesByTimestamp(); + futureFeeIncrease.timestamp = now + (60 * 60 * 1000L); // 1 hour in the future + futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("10"); + + FieldUtils.writeField(BlockChain.getInstance(), "unitFees", Arrays.asList(originalFee, pastFeeIncrease, futureFeeIncrease), true); + assertEquals(originalFee.fee, BlockChain.getInstance().getUnitFeeAtTimestamp(originalFee.timestamp)); + assertEquals(pastFeeIncrease.fee, BlockChain.getInstance().getUnitFeeAtTimestamp(pastFeeIncrease.timestamp)); + assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); + + // Send another payment transaction + // Fee should be determined automatically + transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), bob.getAddress(), 50000); + assertEquals(300000000L, transactionData.getFee().longValue()); + Transaction transaction = Transaction.fromData(repository, transactionData); + transaction.sign(alice); + ValidationResult result = transaction.importAsUnconfirmed(); + assertTrue("Transaction should be valid", ValidationResult.OK == result); + + // Now try fetching and setting fee manually + transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), bob.getAddress(), 50000); + transactionData.setFee(new PaymentTransaction(null, null).getUnitFee(transactionData.getTimestamp())); + assertEquals(300000000L, transactionData.getFee().longValue()); + transaction = Transaction.fromData(repository, transactionData); + transaction.sign(alice); + result = transaction.importAsUnconfirmed(); + assertTrue("Transaction should be valid", ValidationResult.OK == result); + } + } + + // test reading the fee schedule from blockchain.json / test-chain-v2.json + @Test + public void testFeeScheduleInTestchainData() throws Exception { + try (final Repository repository = RepositoryManager.getRepository()) { + + final long expectedFutureFeeIncreaseTimestamp = 9999999999999L; // 20 Nov 2286, as per test-chain-v2.json + final long expectedFutureFeeIncreaseValue = new AmountTypeAdapter().unmarshal("1"); + + assertEquals(expectedFutureFeeIncreaseValue, BlockChain.getInstance().getUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp)); + + // Validate unit fees pre and post timestamp + assertEquals(10000000, BlockChain.getInstance().getUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp - 1)); // 0.1 QORT + assertEquals(100000000, BlockChain.getInstance().getUnitFeeAtTimestamp(expectedFutureFeeIncreaseTimestamp)); // 1 QORT + + // Payment + PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); + PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); + + PaymentTransactionData transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), bob.getAddress(), 100000); + transactionData.setFee(new PaymentTransaction(null, null).getUnitFee(transactionData.getTimestamp())); + assertEquals(10000000L, transactionData.getFee().longValue()); + TransactionUtils.signAndMint(repository, transactionData, alice); + } + } + } diff --git a/src/test/resources/test-chain-v2-block-timestamps.json b/src/test/resources/test-chain-v2-block-timestamps.json index 3b4de702..25915e9a 100644 --- a/src/test/resources/test-chain-v2-block-timestamps.json +++ b/src/test/resources/test-chain-v2-block-timestamps.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, diff --git a/src/test/resources/test-chain-v2-disable-reference.json b/src/test/resources/test-chain-v2-disable-reference.json index c93fbb78..ae499491 100644 --- a/src/test/resources/test-chain-v2-disable-reference.json +++ b/src/test/resources/test-chain-v2-disable-reference.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, diff --git a/src/test/resources/test-chain-v2-founder-rewards.json b/src/test/resources/test-chain-v2-founder-rewards.json index 1b068932..e6b327b1 100644 --- a/src/test/resources/test-chain-v2-founder-rewards.json +++ b/src/test/resources/test-chain-v2-founder-rewards.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, diff --git a/src/test/resources/test-chain-v2-leftover-reward.json b/src/test/resources/test-chain-v2-leftover-reward.json index aef76cc2..9fda9b1f 100644 --- a/src/test/resources/test-chain-v2-leftover-reward.json +++ b/src/test/resources/test-chain-v2-leftover-reward.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, diff --git a/src/test/resources/test-chain-v2-minting.json b/src/test/resources/test-chain-v2-minting.json index db6d8a0b..4ff3ec3c 100644 --- a/src/test/resources/test-chain-v2-minting.json +++ b/src/test/resources/test-chain-v2-minting.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, diff --git a/src/test/resources/test-chain-v2-qora-holder-extremes.json b/src/test/resources/test-chain-v2-qora-holder-extremes.json index 2452d4d2..306fd9a3 100644 --- a/src/test/resources/test-chain-v2-qora-holder-extremes.json +++ b/src/test/resources/test-chain-v2-qora-holder-extremes.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, diff --git a/src/test/resources/test-chain-v2-qora-holder-reduction.json b/src/test/resources/test-chain-v2-qora-holder-reduction.json index 23193729..6128baa7 100644 --- a/src/test/resources/test-chain-v2-qora-holder-reduction.json +++ b/src/test/resources/test-chain-v2-qora-holder-reduction.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, diff --git a/src/test/resources/test-chain-v2-qora-holder.json b/src/test/resources/test-chain-v2-qora-holder.json index 9d81632b..9b6bccda 100644 --- a/src/test/resources/test-chain-v2-qora-holder.json +++ b/src/test/resources/test-chain-v2-qora-holder.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, diff --git a/src/test/resources/test-chain-v2-reward-levels.json b/src/test/resources/test-chain-v2-reward-levels.json index 81609595..1ffa8baf 100644 --- a/src/test/resources/test-chain-v2-reward-levels.json +++ b/src/test/resources/test-chain-v2-reward-levels.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, diff --git a/src/test/resources/test-chain-v2-reward-scaling.json b/src/test/resources/test-chain-v2-reward-scaling.json index 21a5b7a7..6c3b3276 100644 --- a/src/test/resources/test-chain-v2-reward-scaling.json +++ b/src/test/resources/test-chain-v2-reward-scaling.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, diff --git a/src/test/resources/test-chain-v2-reward-shares.json b/src/test/resources/test-chain-v2-reward-shares.json index 6119ac48..09627e1f 100644 --- a/src/test/resources/test-chain-v2-reward-shares.json +++ b/src/test/resources/test-chain-v2-reward-shares.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.1" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, diff --git a/src/test/resources/test-chain-v2-self-sponsorship-algo.json b/src/test/resources/test-chain-v2-self-sponsorship-algo.json index dc5f3961..81cda1e8 100644 --- a/src/test/resources/test-chain-v2-self-sponsorship-algo.json +++ b/src/test/resources/test-chain-v2-self-sponsorship-algo.json @@ -4,8 +4,11 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 0, - "unitFee": "0.00000001", + "unitFees": [ + { "timestamp": 0, "fee": "0.00000001" } + ], "nameRegistrationUnitFees": [ + { "timestamp": 0, "fee": "0.00000001" }, { "timestamp": 1645372800000, "fee": "5" } ], "requireGroupForApproval": false, diff --git a/src/test/resources/test-chain-v2.json b/src/test/resources/test-chain-v2.json index d0c460df..2e96e911 100644 --- a/src/test/resources/test-chain-v2.json +++ b/src/test/resources/test-chain-v2.json @@ -4,9 +4,13 @@ "transactionExpiryPeriod": 86400000, "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, - "unitFee": "0.1", + "unitFees": [ + { "timestamp": 0, "fee": "0.1" }, + { "timestamp": 9999999999999, "fee": "1" } + ], "nameRegistrationUnitFees": [ - { "timestamp": 1645372800000, "fee": "5" } + { "timestamp": 0, "fee": "0.1" }, + { "timestamp": 9999999999999, "fee": "5" } ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5,