diff --git a/src/main/java/org/qortal/block/BlockChain.java b/src/main/java/org/qortal/block/BlockChain.java index 69779d96..86a00574 100644 --- a/src/main/java/org/qortal/block/BlockChain.java +++ b/src/main/java/org/qortal/block/BlockChain.java @@ -73,9 +73,13 @@ public class BlockChain { } // Custom transaction fees - @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) - private long nameRegistrationUnitFee; - private long nameRegistrationUnitFeeTimestamp; + /** Unit fees by transaction timestamp */ + public static class UnitFeesByTimestamp { + public long timestamp; + @XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class) + public long fee; + } + private List nameRegistrationUnitFees; /** Map of which blockchain features are enabled when (height/timestamp) */ @XmlJavaTypeAdapter(StringLongMapXmlAdapter.class) @@ -306,16 +310,6 @@ public class BlockChain { return this.maxBlockSize; } - // Custom transaction fees - public long getNameRegistrationUnitFee() { - return this.nameRegistrationUnitFee; - } - - public long getNameRegistrationUnitFeeTimestamp() { - // FUTURE: we could use a separate structure to indicate fee adjustments for different transaction types - return this.nameRegistrationUnitFeeTimestamp; - } - /** Returns true if approval-needing transaction types require a txGroupId other than NO_GROUP. */ public boolean getRequireGroupForApproval() { return this.requireGroupForApproval; @@ -430,6 +424,16 @@ public class BlockChain { throw new IllegalStateException(String.format("No block timing info available for height %d", ourHeight)); } + public long getNameRegistrationUnitFeeAtTimestamp(long ourTimestamp) { + // Scan through for reward at our height + for (int i = 0; i < nameRegistrationUnitFees.size(); ++i) + if (ourTimestamp >= nameRegistrationUnitFees.get(i).timestamp) + return nameRegistrationUnitFees.get(i).fee; + + // Default to system-wide unit fee + return this.getUnitFee(); + } + /** Validate blockchain config read from JSON */ private void validateConfig() { if (this.genesisInfo == null) diff --git a/src/main/java/org/qortal/transaction/RegisterNameTransaction.java b/src/main/java/org/qortal/transaction/RegisterNameTransaction.java index 1ababa88..19f1da5a 100644 --- a/src/main/java/org/qortal/transaction/RegisterNameTransaction.java +++ b/src/main/java/org/qortal/transaction/RegisterNameTransaction.java @@ -39,11 +39,7 @@ public class RegisterNameTransaction extends Transaction { @Override public long getUnitFee(Long timestamp) { - // Use a higher unit fee after the fee increase timestamp - if (timestamp > BlockChain.getInstance().getNameRegistrationUnitFeeTimestamp()) { - return BlockChain.getInstance().getNameRegistrationUnitFee(); - } - return BlockChain.getInstance().getUnitFee(); + return BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(timestamp); } // Navigation diff --git a/src/main/resources/blockchain.json b/src/main/resources/blockchain.json index 17858d8d..be62aee4 100644 --- a/src/main/resources/blockchain.json +++ b/src/main/resources/blockchain.json @@ -4,8 +4,9 @@ "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, "unitFee": "0.001", - "nameRegistrationUnitFee": "5", - "nameRegistrationUnitFeeTimestamp": 1645372800000, + "nameRegistrationUnitFees": [ + { "timestamp": 1645372800000, "fee": "5" } + ], "useBrokenMD160ForAddresses": false, "requireGroupForApproval": false, "defaultGroupId": 0, diff --git a/src/test/java/org/qortal/test/naming/MiscTests.java b/src/test/java/org/qortal/test/naming/MiscTests.java index da77446e..8252453c 100644 --- a/src/test/java/org/qortal/test/naming/MiscTests.java +++ b/src/test/java/org/qortal/test/naming/MiscTests.java @@ -2,21 +2,22 @@ package org.qortal.test.naming; import static org.junit.Assert.*; +import java.util.Arrays; import java.util.List; -import java.util.Optional; import org.apache.commons.lang3.reflect.FieldUtils; import org.junit.Before; import org.junit.Test; import org.qortal.account.PrivateKeyAccount; +import org.qortal.api.AmountTypeAdapter; import org.qortal.block.BlockChain; +import org.qortal.block.BlockChain.*; import org.qortal.controller.BlockMinter; import org.qortal.data.transaction.*; import org.qortal.naming.Name; import org.qortal.repository.DataException; import org.qortal.repository.Repository; import org.qortal.repository.RepositoryManager; -import org.qortal.settings.Settings; import org.qortal.test.common.*; import org.qortal.test.common.transaction.TestTransaction; import org.qortal.transaction.RegisterNameTransaction; @@ -325,17 +326,20 @@ public class MiscTests extends Common { // test name registration fee increase @Test - public void testRegisterNameFeeIncrease() throws DataException, IllegalAccessException { + public void testRegisterNameFeeIncrease() throws Exception { try (final Repository repository = RepositoryManager.getRepository()) { // Set nameRegistrationUnitFeeTimestamp to a time far in the future - long futureTimestamp = 9999999999999L; // 20 Nov 2286 - FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFeeTimestamp", futureTimestamp, true); - assertEquals(futureTimestamp, BlockChain.getInstance().getNameRegistrationUnitFeeTimestamp()); + 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); + assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); // Validate unit fees pre and post timestamp assertEquals(10000000, BlockChain.getInstance().getUnitFee()); // 0.1 QORT - assertEquals(500000000, BlockChain.getInstance().getNameRegistrationUnitFee()); // 5 QORT + assertEquals(10000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp - 1)); // 0.1 QORT + assertEquals(500000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); // 5 QORT // Register-name PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); @@ -349,8 +353,11 @@ public class MiscTests extends Common { // Set nameRegistrationUnitFeeTimestamp to a time in the past Long now = NTP.getTime(); - FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFeeTimestamp", now - 1000L, true); - assertEquals(now - 1000L, BlockChain.getInstance().getNameRegistrationUnitFeeTimestamp()); + UnitFeesByTimestamp pastFeeIncrease = new UnitFeesByTimestamp(); + pastFeeIncrease.timestamp = now - 1000L; // 1 second ago + pastFeeIncrease.fee = new AmountTypeAdapter().unmarshal("3"); + FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFees", Arrays.asList(pastFeeIncrease), true); + assertEquals(pastFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(pastFeeIncrease.timestamp)); // Register a different name // First try with the default unit fee @@ -365,7 +372,7 @@ public class MiscTests extends Common { // Now try using correct fee (this is specified by the UI, via the /transaction/unitfee API endpoint) transactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name2, data); transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp())); - assertEquals(500000000L, transactionData.getFee().longValue()); + assertEquals(300000000L, transactionData.getFee().longValue()); transaction = Transaction.fromData(repository, transactionData); transaction.sign(alice); result = transaction.importAsUnconfirmed(); diff --git a/src/test/resources/test-chain-v2-founder-rewards.json b/src/test/resources/test-chain-v2-founder-rewards.json index c2a61503..c0ea8fe5 100644 --- a/src/test/resources/test-chain-v2-founder-rewards.json +++ b/src/test/resources/test-chain-v2-founder-rewards.json @@ -5,7 +5,9 @@ "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, "unitFee": "0.1", - "nameRegistrationUnitFee": "5", + "nameRegistrationUnitFees": [ + { "timestamp": 1645372800000, "fee": "5" } + ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5, "maxRewardSharesPerMintingAccount": 20, diff --git a/src/test/resources/test-chain-v2-leftover-reward.json b/src/test/resources/test-chain-v2-leftover-reward.json index be04d7a2..01505af0 100644 --- a/src/test/resources/test-chain-v2-leftover-reward.json +++ b/src/test/resources/test-chain-v2-leftover-reward.json @@ -5,7 +5,9 @@ "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, "unitFee": "0.1", - "nameRegistrationUnitFee": "5", + "nameRegistrationUnitFees": [ + { "timestamp": 1645372800000, "fee": "5" } + ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5, "maxRewardSharesPerMintingAccount": 20, diff --git a/src/test/resources/test-chain-v2-minting.json b/src/test/resources/test-chain-v2-minting.json index d79c8e98..fcabe4bf 100644 --- a/src/test/resources/test-chain-v2-minting.json +++ b/src/test/resources/test-chain-v2-minting.json @@ -5,7 +5,9 @@ "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, "unitFee": "0.1", - "nameRegistrationUnitFee": "5", + "nameRegistrationUnitFees": [ + { "timestamp": 1645372800000, "fee": "5" } + ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5, "maxRewardSharesPerMintingAccount": 20, 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 08c6fab3..8ec94631 100644 --- a/src/test/resources/test-chain-v2-qora-holder-extremes.json +++ b/src/test/resources/test-chain-v2-qora-holder-extremes.json @@ -5,7 +5,9 @@ "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, "unitFee": "0.1", - "nameRegistrationUnitFee": "5", + "nameRegistrationUnitFees": [ + { "timestamp": 1645372800000, "fee": "5" } + ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5, "maxRewardSharesPerMintingAccount": 20, diff --git a/src/test/resources/test-chain-v2-qora-holder.json b/src/test/resources/test-chain-v2-qora-holder.json index 804087b7..38a563b2 100644 --- a/src/test/resources/test-chain-v2-qora-holder.json +++ b/src/test/resources/test-chain-v2-qora-holder.json @@ -5,7 +5,9 @@ "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, "unitFee": "0.1", - "nameRegistrationUnitFee": "5", + "nameRegistrationUnitFees": [ + { "timestamp": 1645372800000, "fee": "5" } + ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5, "maxRewardSharesPerMintingAccount": 20, diff --git a/src/test/resources/test-chain-v2-reward-levels.json b/src/test/resources/test-chain-v2-reward-levels.json index 2eae612d..ab934d26 100644 --- a/src/test/resources/test-chain-v2-reward-levels.json +++ b/src/test/resources/test-chain-v2-reward-levels.json @@ -5,7 +5,9 @@ "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, "unitFee": "0.1", - "nameRegistrationUnitFee": "5", + "nameRegistrationUnitFees": [ + { "timestamp": 1645372800000, "fee": "5" } + ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5, "maxRewardSharesPerMintingAccount": 20, diff --git a/src/test/resources/test-chain-v2-reward-scaling.json b/src/test/resources/test-chain-v2-reward-scaling.json index 6842a727..b3e358b2 100644 --- a/src/test/resources/test-chain-v2-reward-scaling.json +++ b/src/test/resources/test-chain-v2-reward-scaling.json @@ -5,7 +5,9 @@ "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, "unitFee": "0.1", - "nameRegistrationUnitFee": "5", + "nameRegistrationUnitFees": [ + { "timestamp": 1645372800000, "fee": "5" } + ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5, "maxRewardSharesPerMintingAccount": 20, diff --git a/src/test/resources/test-chain-v2.json b/src/test/resources/test-chain-v2.json index 8fc7f957..20ff391c 100644 --- a/src/test/resources/test-chain-v2.json +++ b/src/test/resources/test-chain-v2.json @@ -5,7 +5,9 @@ "maxBlockSize": 2097152, "maxBytesPerUnitFee": 1024, "unitFee": "0.1", - "nameRegistrationUnitFee": "5", + "nameRegistrationUnitFees": [ + { "timestamp": 1645372800000, "fee": "5" } + ], "requireGroupForApproval": false, "minAccountLevelToRewardShare": 5, "maxRewardSharesPerMintingAccount": 20,