forked from Qortal/qortal
Adapted Blockchain.java to use lookup table for name registration fees, to more easily support fee adjustments.
This is currently for name registration transactions only, but can be adapted (or duplicated) for other transaction types when needed. Note: this switches from a greater-than (>) to a greater-than-or-equal (>=) timestamp comparison, as it makes more sense this way. It shouldn't affect the previous transition since there were no REGISTER_NAME transactions at that exact timestamp.
This commit is contained in:
parent
895f02f178
commit
2e5a7cb5a1
@ -73,9 +73,13 @@ public class BlockChain {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Custom transaction fees
|
// Custom transaction fees
|
||||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
/** Unit fees by transaction timestamp */
|
||||||
private long nameRegistrationUnitFee;
|
public static class UnitFeesByTimestamp {
|
||||||
private long nameRegistrationUnitFeeTimestamp;
|
public long timestamp;
|
||||||
|
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||||
|
public long fee;
|
||||||
|
}
|
||||||
|
private List<UnitFeesByTimestamp> nameRegistrationUnitFees;
|
||||||
|
|
||||||
/** Map of which blockchain features are enabled when (height/timestamp) */
|
/** Map of which blockchain features are enabled when (height/timestamp) */
|
||||||
@XmlJavaTypeAdapter(StringLongMapXmlAdapter.class)
|
@XmlJavaTypeAdapter(StringLongMapXmlAdapter.class)
|
||||||
@ -306,16 +310,6 @@ public class BlockChain {
|
|||||||
return this.maxBlockSize;
|
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. */
|
/** Returns true if approval-needing transaction types require a txGroupId other than NO_GROUP. */
|
||||||
public boolean getRequireGroupForApproval() {
|
public boolean getRequireGroupForApproval() {
|
||||||
return this.requireGroupForApproval;
|
return this.requireGroupForApproval;
|
||||||
@ -430,6 +424,16 @@ public class BlockChain {
|
|||||||
throw new IllegalStateException(String.format("No block timing info available for height %d", ourHeight));
|
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 */
|
/** Validate blockchain config read from JSON */
|
||||||
private void validateConfig() {
|
private void validateConfig() {
|
||||||
if (this.genesisInfo == null)
|
if (this.genesisInfo == null)
|
||||||
|
@ -39,11 +39,7 @@ public class RegisterNameTransaction extends Transaction {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getUnitFee(Long timestamp) {
|
public long getUnitFee(Long timestamp) {
|
||||||
// Use a higher unit fee after the fee increase timestamp
|
return BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(timestamp);
|
||||||
if (timestamp > BlockChain.getInstance().getNameRegistrationUnitFeeTimestamp()) {
|
|
||||||
return BlockChain.getInstance().getNameRegistrationUnitFee();
|
|
||||||
}
|
|
||||||
return BlockChain.getInstance().getUnitFee();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigation
|
// Navigation
|
||||||
|
@ -4,8 +4,9 @@
|
|||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.001",
|
"unitFee": "0.001",
|
||||||
"nameRegistrationUnitFee": "5",
|
"nameRegistrationUnitFees": [
|
||||||
"nameRegistrationUnitFeeTimestamp": 1645372800000,
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
|
],
|
||||||
"useBrokenMD160ForAddresses": false,
|
"useBrokenMD160ForAddresses": false,
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"defaultGroupId": 0,
|
"defaultGroupId": 0,
|
||||||
|
@ -2,21 +2,22 @@ package org.qortal.test.naming;
|
|||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.reflect.FieldUtils;
|
import org.apache.commons.lang3.reflect.FieldUtils;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.qortal.account.PrivateKeyAccount;
|
import org.qortal.account.PrivateKeyAccount;
|
||||||
|
import org.qortal.api.AmountTypeAdapter;
|
||||||
import org.qortal.block.BlockChain;
|
import org.qortal.block.BlockChain;
|
||||||
|
import org.qortal.block.BlockChain.*;
|
||||||
import org.qortal.controller.BlockMinter;
|
import org.qortal.controller.BlockMinter;
|
||||||
import org.qortal.data.transaction.*;
|
import org.qortal.data.transaction.*;
|
||||||
import org.qortal.naming.Name;
|
import org.qortal.naming.Name;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
import org.qortal.repository.Repository;
|
import org.qortal.repository.Repository;
|
||||||
import org.qortal.repository.RepositoryManager;
|
import org.qortal.repository.RepositoryManager;
|
||||||
import org.qortal.settings.Settings;
|
|
||||||
import org.qortal.test.common.*;
|
import org.qortal.test.common.*;
|
||||||
import org.qortal.test.common.transaction.TestTransaction;
|
import org.qortal.test.common.transaction.TestTransaction;
|
||||||
import org.qortal.transaction.RegisterNameTransaction;
|
import org.qortal.transaction.RegisterNameTransaction;
|
||||||
@ -325,17 +326,20 @@ public class MiscTests extends Common {
|
|||||||
|
|
||||||
// test name registration fee increase
|
// test name registration fee increase
|
||||||
@Test
|
@Test
|
||||||
public void testRegisterNameFeeIncrease() throws DataException, IllegalAccessException {
|
public void testRegisterNameFeeIncrease() throws Exception {
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
|
|
||||||
// Set nameRegistrationUnitFeeTimestamp to a time far in the future
|
// Set nameRegistrationUnitFeeTimestamp to a time far in the future
|
||||||
long futureTimestamp = 9999999999999L; // 20 Nov 2286
|
UnitFeesByTimestamp futureFeeIncrease = new UnitFeesByTimestamp();
|
||||||
FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFeeTimestamp", futureTimestamp, true);
|
futureFeeIncrease.timestamp = 9999999999999L; // 20 Nov 2286
|
||||||
assertEquals(futureTimestamp, BlockChain.getInstance().getNameRegistrationUnitFeeTimestamp());
|
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
|
// Validate unit fees pre and post timestamp
|
||||||
assertEquals(10000000, BlockChain.getInstance().getUnitFee()); // 0.1 QORT
|
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
|
// Register-name
|
||||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||||
@ -349,8 +353,11 @@ public class MiscTests extends Common {
|
|||||||
|
|
||||||
// Set nameRegistrationUnitFeeTimestamp to a time in the past
|
// Set nameRegistrationUnitFeeTimestamp to a time in the past
|
||||||
Long now = NTP.getTime();
|
Long now = NTP.getTime();
|
||||||
FieldUtils.writeField(BlockChain.getInstance(), "nameRegistrationUnitFeeTimestamp", now - 1000L, true);
|
UnitFeesByTimestamp pastFeeIncrease = new UnitFeesByTimestamp();
|
||||||
assertEquals(now - 1000L, BlockChain.getInstance().getNameRegistrationUnitFeeTimestamp());
|
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
|
// Register a different name
|
||||||
// First try with the default unit fee
|
// 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)
|
// 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 = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name2, data);
|
||||||
transactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
|
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 = Transaction.fromData(repository, transactionData);
|
||||||
transaction.sign(alice);
|
transaction.sign(alice);
|
||||||
result = transaction.importAsUnconfirmed();
|
result = transaction.importAsUnconfirmed();
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFee": "0.1",
|
||||||
"nameRegistrationUnitFee": "5",
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerMintingAccount": 20,
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFee": "0.1",
|
||||||
"nameRegistrationUnitFee": "5",
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerMintingAccount": 20,
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFee": "0.1",
|
||||||
"nameRegistrationUnitFee": "5",
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerMintingAccount": 20,
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFee": "0.1",
|
||||||
"nameRegistrationUnitFee": "5",
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerMintingAccount": 20,
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFee": "0.1",
|
||||||
"nameRegistrationUnitFee": "5",
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerMintingAccount": 20,
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFee": "0.1",
|
||||||
"nameRegistrationUnitFee": "5",
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerMintingAccount": 20,
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFee": "0.1",
|
||||||
"nameRegistrationUnitFee": "5",
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerMintingAccount": 20,
|
||||||
|
@ -5,7 +5,9 @@
|
|||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFee": "0.1",
|
||||||
"nameRegistrationUnitFee": "5",
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
"maxRewardSharesPerMintingAccount": 20,
|
"maxRewardSharesPerMintingAccount": 20,
|
||||||
|
Loading…
Reference in New Issue
Block a user