forked from Qortal/qortal
Merge branch 'Qortal:master' into master
This commit is contained in:
commit
fd9d0c4e51
2
pom.xml
2
pom.xml
@ -3,7 +3,7 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>org.qortal</groupId>
|
<groupId>org.qortal</groupId>
|
||||||
<artifactId>qortal</artifactId>
|
<artifactId>qortal</artifactId>
|
||||||
<version>4.2.2</version>
|
<version>4.2.4</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
<properties>
|
<properties>
|
||||||
<skipTests>true</skipTests>
|
<skipTests>true</skipTests>
|
||||||
|
@ -130,6 +130,9 @@ public class Block {
|
|||||||
/** Locally-generated AT fees */
|
/** Locally-generated AT fees */
|
||||||
protected long ourAtFees; // Generated locally
|
protected long ourAtFees; // Generated locally
|
||||||
|
|
||||||
|
/** Cached online accounts validation decision, to avoid revalidating when true */
|
||||||
|
private boolean onlineAccountsAlreadyValid = false;
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
private interface BlockRewardDistributor {
|
private interface BlockRewardDistributor {
|
||||||
long distribute(long amount, Map<String, Long> balanceChanges) throws DataException;
|
long distribute(long amount, Map<String, Long> 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
|
// More information
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1043,6 +1053,10 @@ public class Block {
|
|||||||
if (this.blockData.getHeight() != null && this.blockData.getHeight() == 1)
|
if (this.blockData.getHeight() != null && this.blockData.getHeight() == 1)
|
||||||
return ValidationResult.OK;
|
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
|
// Expand block's online accounts indexes into actual accounts
|
||||||
ConciseSet accountIndexes = BlockTransformer.decodeOnlineAccounts(this.blockData.getEncodedOnlineAccounts());
|
ConciseSet accountIndexes = BlockTransformer.decodeOnlineAccounts(this.blockData.getEncodedOnlineAccounts());
|
||||||
// We use count of online accounts to validate decoded account indexes
|
// 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
|
// All online accounts valid, so save our list of online accounts for potential later use
|
||||||
this.cachedOnlineRewardShares = onlineRewardShares;
|
this.cachedOnlineRewardShares = onlineRewardShares;
|
||||||
|
|
||||||
|
// Remember that the accounts are valid, to speed up subsequent checks
|
||||||
|
this.onlineAccountsAlreadyValid = true;
|
||||||
|
|
||||||
return ValidationResult.OK;
|
return ValidationResult.OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,9 +48,6 @@ public class BlockChain {
|
|||||||
/** Transaction expiry period, starting from transaction's timestamp, in milliseconds. */
|
/** Transaction expiry period, starting from transaction's timestamp, in milliseconds. */
|
||||||
private long transactionExpiryPeriod;
|
private long transactionExpiryPeriod;
|
||||||
|
|
||||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
|
||||||
private long unitFee;
|
|
||||||
|
|
||||||
private int maxBytesPerUnitFee;
|
private int maxBytesPerUnitFee;
|
||||||
|
|
||||||
/** Maximum acceptable timestamp disagreement offset in milliseconds. */
|
/** Maximum acceptable timestamp disagreement offset in milliseconds. */
|
||||||
@ -89,6 +86,7 @@ public class BlockChain {
|
|||||||
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
@XmlJavaTypeAdapter(value = org.qortal.api.AmountTypeAdapter.class)
|
||||||
public long fee;
|
public long fee;
|
||||||
}
|
}
|
||||||
|
private List<UnitFeesByTimestamp> unitFees;
|
||||||
private List<UnitFeesByTimestamp> nameRegistrationUnitFees;
|
private List<UnitFeesByTimestamp> nameRegistrationUnitFees;
|
||||||
|
|
||||||
/** Map of which blockchain features are enabled when (height/timestamp) */
|
/** Map of which blockchain features are enabled when (height/timestamp) */
|
||||||
@ -346,10 +344,6 @@ public class BlockChain {
|
|||||||
return this.isTestChain;
|
return this.isTestChain;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getUnitFee() {
|
|
||||||
return this.unitFee;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMaxBytesPerUnitFee() {
|
public int getMaxBytesPerUnitFee() {
|
||||||
return this.maxBytesPerUnitFee;
|
return this.maxBytesPerUnitFee;
|
||||||
}
|
}
|
||||||
@ -547,13 +541,22 @@ 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 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) {
|
public long getNameRegistrationUnitFeeAtTimestamp(long ourTimestamp) {
|
||||||
for (int i = nameRegistrationUnitFees.size() - 1; i >= 0; --i)
|
for (int i = nameRegistrationUnitFees.size() - 1; i >= 0; --i)
|
||||||
if (nameRegistrationUnitFees.get(i).timestamp <= ourTimestamp)
|
if (nameRegistrationUnitFees.get(i).timestamp <= ourTimestamp)
|
||||||
return nameRegistrationUnitFees.get(i).fee;
|
return nameRegistrationUnitFees.get(i).fee;
|
||||||
|
|
||||||
// Default to system-wide unit fee
|
// Shouldn't happen, but set a sensible default just in case
|
||||||
return this.getUnitFee();
|
return 100000;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMaxRewardSharesAtTimestamp(long ourTimestamp) {
|
public int getMaxRewardSharesAtTimestamp(long ourTimestamp) {
|
||||||
|
@ -562,6 +562,9 @@ public class BlockMinter extends Thread {
|
|||||||
// Sign to create block's signature
|
// Sign to create block's signature
|
||||||
newBlock.sign();
|
newBlock.sign();
|
||||||
|
|
||||||
|
// Ensure online accounts are fully re-validated in this final check
|
||||||
|
newBlock.clearOnlineAccountsValidationCache();
|
||||||
|
|
||||||
// Is newBlock still valid?
|
// Is newBlock still valid?
|
||||||
ValidationResult validationResult = newBlock.isValid();
|
ValidationResult validationResult = newBlock.isValid();
|
||||||
if (validationResult != ValidationResult.OK)
|
if (validationResult != ValidationResult.OK)
|
||||||
|
@ -229,13 +229,6 @@ public class Synchronizer extends Thread {
|
|||||||
peers.removeIf(Controller.hasOldVersion);
|
peers.removeIf(Controller.hasOldVersion);
|
||||||
|
|
||||||
checkRecoveryModeForPeers(peers);
|
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
|
// Check we have enough peers to potentially synchronize
|
||||||
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
|
if (peers.size() < Settings.getInstance().getMinBlockchainPeers())
|
||||||
@ -262,10 +255,7 @@ public class Synchronizer extends Thread {
|
|||||||
peers.removeIf(Controller.hasInferiorChainTip);
|
peers.removeIf(Controller.hasInferiorChainTip);
|
||||||
|
|
||||||
// Remove any peers that are no longer on a recent block since the last check
|
// 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
|
peers.removeIf(Controller.hasNoRecentBlock);
|
||||||
if (!recoveryMode) {
|
|
||||||
peers.removeIf(Controller.hasNoRecentBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
final int peersRemoved = peersBeforeComparison - peers.size();
|
final int peersRemoved = peersBeforeComparison - peers.size();
|
||||||
if (peersRemoved > 0 && peers.size() > 0)
|
if (peersRemoved > 0 && peers.size() > 0)
|
||||||
@ -1340,8 +1330,8 @@ public class Synchronizer extends Thread {
|
|||||||
return SynchronizationResult.INVALID_DATA;
|
return SynchronizationResult.INVALID_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Final check to make sure the peer isn't out of date (except for when we're in recovery mode)
|
// Final check to make sure the peer isn't out of date
|
||||||
if (!recoveryMode && peer.getChainTipData() != null) {
|
if (peer.getChainTipData() != null) {
|
||||||
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
final Long minLatestBlockTimestamp = Controller.getMinimumLatestBlockTimestamp();
|
||||||
final Long peerLastBlockTimestamp = peer.getChainTipData().getTimestamp();
|
final Long peerLastBlockTimestamp = peer.getChainTipData().getTimestamp();
|
||||||
if (peerLastBlockTimestamp == null || peerLastBlockTimestamp < minLatestBlockTimestamp) {
|
if (peerLastBlockTimestamp == null || peerLastBlockTimestamp < minLatestBlockTimestamp) {
|
||||||
|
@ -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. */
|
/** Map of recent invalid unconfirmed transactions. Key is base58 transaction signature, value is do-not-request expiry timestamp. */
|
||||||
private final Map<String, Long> invalidUnconfirmedTransactions = Collections.synchronizedMap(new HashMap<>());
|
private final Map<String, Long> invalidUnconfirmedTransactions = Collections.synchronizedMap(new HashMap<>());
|
||||||
|
|
||||||
|
/** Cached list of unconfirmed transactions, used when counting per creator. This is replaced regularly */
|
||||||
|
public static List<TransactionData> unconfirmedTransactionsCache = null;
|
||||||
|
|
||||||
|
|
||||||
public static synchronized TransactionImporter getInstance() {
|
public static synchronized TransactionImporter getInstance() {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
@ -254,6 +257,12 @@ public class TransactionImporter extends Thread {
|
|||||||
int processedCount = 0;
|
int processedCount = 0;
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
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<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
||||||
|
unconfirmedTransactions.removeIf(t -> t.getType() == Transaction.TransactionType.CHAT);
|
||||||
|
unconfirmedTransactionsCache = unconfirmedTransactions;
|
||||||
|
|
||||||
// Import transactions with valid signatures
|
// Import transactions with valid signatures
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < sigValidTransactions.size(); ++i) {
|
for (int i = 0; i < sigValidTransactions.size(); ++i) {
|
||||||
@ -286,6 +295,11 @@ public class TransactionImporter extends Thread {
|
|||||||
|
|
||||||
case OK: {
|
case OK: {
|
||||||
LOGGER.debug(() -> String.format("Imported %s transaction %s", transactionData.getType().name(), Base58.encode(transactionData.getSignature())));
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,6 +331,9 @@ public class TransactionImporter extends Thread {
|
|||||||
} finally {
|
} finally {
|
||||||
LOGGER.debug("Finished importing {} incoming transaction{}", processedCount, (processedCount == 1 ? "" : "s"));
|
LOGGER.debug("Finished importing {} incoming transaction{}", processedCount, (processedCount == 1 ? "" : "s"));
|
||||||
blockchainLock.unlock();
|
blockchainLock.unlock();
|
||||||
|
|
||||||
|
// Clear the unconfirmed transaction cache so new data can be populated in the next cycle
|
||||||
|
unconfirmedTransactionsCache = null;
|
||||||
}
|
}
|
||||||
} catch (DataException e) {
|
} catch (DataException e) {
|
||||||
LOGGER.error("Repository issue while importing incoming transactions", e);
|
LOGGER.error("Repository issue while importing incoming transactions", e);
|
||||||
|
@ -227,7 +227,7 @@ public class Settings {
|
|||||||
private int maxRetries = 2;
|
private int maxRetries = 2;
|
||||||
|
|
||||||
/** The number of seconds of no activity before recovery mode begins */
|
/** 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 */
|
/** Minimum peer version number required in order to sync with them */
|
||||||
private String minPeerVersion = "4.1.2";
|
private String minPeerVersion = "4.1.2";
|
||||||
|
@ -13,6 +13,7 @@ import org.qortal.account.PublicKeyAccount;
|
|||||||
import org.qortal.asset.Asset;
|
import org.qortal.asset.Asset;
|
||||||
import org.qortal.block.BlockChain;
|
import org.qortal.block.BlockChain;
|
||||||
import org.qortal.controller.Controller;
|
import org.qortal.controller.Controller;
|
||||||
|
import org.qortal.controller.TransactionImporter;
|
||||||
import org.qortal.crypto.Crypto;
|
import org.qortal.crypto.Crypto;
|
||||||
import org.qortal.data.block.BlockData;
|
import org.qortal.data.block.BlockData;
|
||||||
import org.qortal.data.group.GroupApprovalData;
|
import org.qortal.data.group.GroupApprovalData;
|
||||||
@ -377,7 +378,7 @@ public abstract class Transaction {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public long getUnitFee(Long timestamp) {
|
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 {
|
private int countUnconfirmedByCreator(PublicKeyAccount creator) throws DataException {
|
||||||
List<TransactionData> unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
List<TransactionData> unconfirmedTransactions = TransactionImporter.getInstance().unconfirmedTransactionsCache;
|
||||||
|
if (unconfirmedTransactions == null) {
|
||||||
|
unconfirmedTransactions = repository.getTransactionRepository().getUnconfirmedTransactions();
|
||||||
|
}
|
||||||
|
|
||||||
// We exclude CHAT transactions as they never get included into blocks and
|
// We exclude CHAT transactions as they never get included into blocks and
|
||||||
// have spam/DoS prevention by requiring proof of work
|
// have spam/DoS prevention by requiring proof of work
|
||||||
|
@ -3,8 +3,12 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.001",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.001" },
|
||||||
|
{ "timestamp": 1692118800000, "fee": "0.01" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.001" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" },
|
{ "timestamp": 1645372800000, "fee": "5" },
|
||||||
{ "timestamp": 1651420800000, "fee": "1.25" }
|
{ "timestamp": 1651420800000, "fee": "1.25" }
|
||||||
],
|
],
|
||||||
|
@ -85,7 +85,7 @@ public class MessageTests extends Common {
|
|||||||
byte[] randomReference = new byte[64];
|
byte[] randomReference = new byte[64];
|
||||||
random.nextBytes(randomReference);
|
random.nextBytes(randomReference);
|
||||||
|
|
||||||
long minimumFee = BlockChain.getInstance().getUnitFee();
|
long minimumFee = BlockChain.getInstance().getUnitFeeAtTimestamp(System.currentTimeMillis());
|
||||||
|
|
||||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||||
|
@ -7,13 +7,15 @@ import org.qortal.block.BlockChain;
|
|||||||
import org.qortal.data.transaction.BaseTransactionData;
|
import org.qortal.data.transaction.BaseTransactionData;
|
||||||
import org.qortal.group.Group;
|
import org.qortal.group.Group;
|
||||||
import org.qortal.repository.DataException;
|
import org.qortal.repository.DataException;
|
||||||
|
import org.qortal.utils.NTP;
|
||||||
|
|
||||||
public abstract class TestTransaction {
|
public abstract class TestTransaction {
|
||||||
|
|
||||||
protected static final Random random = new Random();
|
protected static final Random random = new Random();
|
||||||
|
|
||||||
public static BaseTransactionData generateBase(PrivateKeyAccount account, int txGroupId) throws DataException {
|
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 {
|
public static BaseTransactionData generateBase(PrivateKeyAccount account) throws DataException {
|
||||||
|
@ -44,7 +44,7 @@ public class BuySellTests extends Common {
|
|||||||
bob = Common.getTestAccount(repository, "bob");
|
bob = Common.getTestAccount(repository, "bob");
|
||||||
|
|
||||||
name = "test name" + " " + random.nextInt(1_000_000);
|
name = "test name" + " " + random.nextInt(1_000_000);
|
||||||
price = random.nextInt(1000) * Amounts.MULTIPLIER;
|
price = (random.nextInt(1000) + 1) * Amounts.MULTIPLIER;
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
|
@ -20,6 +20,7 @@ import org.qortal.repository.Repository;
|
|||||||
import org.qortal.repository.RepositoryManager;
|
import org.qortal.repository.RepositoryManager;
|
||||||
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.PaymentTransaction;
|
||||||
import org.qortal.transaction.RegisterNameTransaction;
|
import org.qortal.transaction.RegisterNameTransaction;
|
||||||
import org.qortal.transaction.Transaction;
|
import org.qortal.transaction.Transaction;
|
||||||
import org.qortal.transaction.Transaction.ValidationResult;
|
import org.qortal.transaction.Transaction.ValidationResult;
|
||||||
@ -329,15 +330,19 @@ public class MiscTests extends Common {
|
|||||||
public void testRegisterNameFeeIncrease() throws Exception {
|
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
|
// 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();
|
UnitFeesByTimestamp futureFeeIncrease = new UnitFeesByTimestamp();
|
||||||
futureFeeIncrease.timestamp = 9999999999999L; // 20 Nov 2286
|
futureFeeIncrease.timestamp = 9999999999999L; // 20 Nov 2286
|
||||||
futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("5");
|
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));
|
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().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp - 1)); // 0.1 QORT
|
assertEquals(10000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp - 1)); // 0.1 QORT
|
||||||
assertEquals(500000000, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.timestamp)); // 5 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.timestamp = now + (60 * 60 * 1000L); // 1 hour in the future
|
||||||
futureFeeIncrease.fee = new AmountTypeAdapter().unmarshal("10");
|
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(pastFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(pastFeeIncrease.timestamp));
|
||||||
assertEquals(futureFeeIncrease.fee, BlockChain.getInstance().getNameRegistrationUnitFeeAtTimestamp(futureFeeIncrease.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
|
@ -4,8 +4,11 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 0,
|
"maxBytesPerUnitFee": 0,
|
||||||
"unitFee": "0.00000001",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.00000001" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.00000001" },
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 1645372800000, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
|
@ -4,9 +4,13 @@
|
|||||||
"transactionExpiryPeriod": 86400000,
|
"transactionExpiryPeriod": 86400000,
|
||||||
"maxBlockSize": 2097152,
|
"maxBlockSize": 2097152,
|
||||||
"maxBytesPerUnitFee": 1024,
|
"maxBytesPerUnitFee": 1024,
|
||||||
"unitFee": "0.1",
|
"unitFees": [
|
||||||
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
|
{ "timestamp": 9999999999999, "fee": "1" }
|
||||||
|
],
|
||||||
"nameRegistrationUnitFees": [
|
"nameRegistrationUnitFees": [
|
||||||
{ "timestamp": 1645372800000, "fee": "5" }
|
{ "timestamp": 0, "fee": "0.1" },
|
||||||
|
{ "timestamp": 9999999999999, "fee": "5" }
|
||||||
],
|
],
|
||||||
"requireGroupForApproval": false,
|
"requireGroupForApproval": false,
|
||||||
"minAccountLevelToRewardShare": 5,
|
"minAccountLevelToRewardShare": 5,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user