Update various transaction types at a future unknown timestamp.

- PUBLICIZE transactions are no longer possible.
- ARBITRARY transactions are now only possible using a fee.
- MESSAGE transactions only confirm when they are being sent to an AT. Messages to regular addresses (or no recipient) will expire after 24 hours.
- Difficulty for confirmed MESSAGE transactions increases from 14 to 16.
- Difficulty for unconfirmed MESSAGE transactions decreases from 14 to 12.
This commit is contained in:
CalDescent 2023-08-19 13:57:26 +01:00
parent b0224651c2
commit 7bb61ec564
28 changed files with 341 additions and 111 deletions

View File

@ -84,6 +84,7 @@ public class Block {
TRANSACTION_PROCESSING_FAILED(53), TRANSACTION_PROCESSING_FAILED(53),
TRANSACTION_ALREADY_PROCESSED(54), TRANSACTION_ALREADY_PROCESSED(54),
TRANSACTION_NEEDS_APPROVAL(55), TRANSACTION_NEEDS_APPROVAL(55),
TRANSACTION_NOT_CONFIRMABLE(56),
AT_STATES_MISMATCH(61), AT_STATES_MISMATCH(61),
ONLINE_ACCOUNTS_INVALID(70), ONLINE_ACCOUNTS_INVALID(70),
ONLINE_ACCOUNT_UNKNOWN(71), ONLINE_ACCOUNT_UNKNOWN(71),
@ -1251,6 +1252,13 @@ public class Block {
|| transaction.getDeadline() <= this.blockData.getTimestamp()) || transaction.getDeadline() <= this.blockData.getTimestamp())
return ValidationResult.TRANSACTION_TIMESTAMP_INVALID; return ValidationResult.TRANSACTION_TIMESTAMP_INVALID;
// After feature trigger, check that this transaction is confirmable
if (transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) {
if (!transaction.isConfirmable()) {
return ValidationResult.TRANSACTION_NOT_CONFIRMABLE;
}
}
// Check transaction isn't already included in a block // Check transaction isn't already included in a block
if (this.repository.getTransactionRepository().isConfirmed(transactionData.getSignature())) if (this.repository.getTransactionRepository().isConfirmed(transactionData.getSignature()))
return ValidationResult.TRANSACTION_ALREADY_PROCESSED; return ValidationResult.TRANSACTION_ALREADY_PROCESSED;

View File

@ -209,6 +209,9 @@ public class BlockChain {
/** Snapshot timestamp for self sponsorship algo V1 */ /** Snapshot timestamp for self sponsorship algo V1 */
private long selfSponsorshipAlgoV1SnapshotTimestamp; private long selfSponsorshipAlgoV1SnapshotTimestamp;
/** Feature-trigger timestamp to modify behaviour of various transactions that support mempow */
private long mempowTransactionUpdatesTimestamp;
/** Max reward shares by block height */ /** Max reward shares by block height */
public static class MaxRewardSharesByTimestamp { public static class MaxRewardSharesByTimestamp {
public long timestamp; public long timestamp;
@ -370,6 +373,11 @@ public class BlockChain {
return this.selfSponsorshipAlgoV1SnapshotTimestamp; return this.selfSponsorshipAlgoV1SnapshotTimestamp;
} }
// Feature-trigger timestamp to modify behaviour of various transactions that support mempow
public long getMemPoWTransactionUpdatesTimestamp() {
return this.mempowTransactionUpdatesTimestamp;
}
/** 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;

View File

@ -1,6 +1,5 @@
package org.qortal.transaction; package org.qortal.transaction;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -8,7 +7,6 @@ import java.util.stream.Collectors;
import org.qortal.account.Account; import org.qortal.account.Account;
import org.qortal.block.BlockChain; import org.qortal.block.BlockChain;
import org.qortal.controller.arbitrary.ArbitraryDataManager; import org.qortal.controller.arbitrary.ArbitraryDataManager;
import org.qortal.controller.arbitrary.ArbitraryDataStorageManager;
import org.qortal.controller.repository.NamesDatabaseIntegrityCheck; import org.qortal.controller.repository.NamesDatabaseIntegrityCheck;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.crypto.MemoryPoW; import org.qortal.crypto.MemoryPoW;
@ -88,8 +86,14 @@ public class ArbitraryTransaction extends Transaction {
if (this.transactionData.getFee() < 0) if (this.transactionData.getFee() < 0)
return ValidationResult.NEGATIVE_FEE; return ValidationResult.NEGATIVE_FEE;
// After the feature trigger, we require the fee to be sufficient if it's not 0. // As of the mempow transaction updates timestamp, a nonce is no longer supported, so a valid fee must be included
// If the fee is zero, then the nonce is validated in isSignatureValid() as an alternative to a fee if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) {
// Validate the fee
return super.isFeeValid();
}
// After the earlier "optional fee" feature trigger, we required the fee to be sufficient if it wasn't 0.
// If the fee was zero, then the nonce was validated in isSignatureValid() as an alternative to a fee
if (this.arbitraryTransactionData.getTimestamp() >= BlockChain.getInstance().getArbitraryOptionalFeeTimestamp() && this.arbitraryTransactionData.getFee() != 0L) { if (this.arbitraryTransactionData.getTimestamp() >= BlockChain.getInstance().getArbitraryOptionalFeeTimestamp() && this.arbitraryTransactionData.getFee() != 0L) {
return super.isFeeValid(); return super.isFeeValid();
} }
@ -214,7 +218,13 @@ public class ArbitraryTransaction extends Transaction {
// Clear nonce from transactionBytes // Clear nonce from transactionBytes
ArbitraryTransactionTransformer.clearNonce(transactionBytes); ArbitraryTransactionTransformer.clearNonce(transactionBytes);
// As of feature-trigger timestamp, we only require a nonce when the fee is zero // As of the mempow transaction updates timestamp, a nonce is no longer supported, so a fee must be included
if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) {
// Require that the fee is a positive number. Fee checking itself is performed in isFeeValid()
return (this.arbitraryTransactionData.getFee() > 0L);
}
// As of the earlier "optional fee" feature-trigger timestamp, we only required a nonce when the fee was zero
boolean beforeFeatureTrigger = this.arbitraryTransactionData.getTimestamp() < BlockChain.getInstance().getArbitraryOptionalFeeTimestamp(); boolean beforeFeatureTrigger = this.arbitraryTransactionData.getTimestamp() < BlockChain.getInstance().getArbitraryOptionalFeeTimestamp();
if (beforeFeatureTrigger || this.arbitraryTransactionData.getFee() == 0L) { if (beforeFeatureTrigger || this.arbitraryTransactionData.getFee() == 0L) {
// We only need to check nonce for recent transactions due to PoW verification overhead // We only need to check nonce for recent transactions due to PoW verification overhead

View File

@ -148,6 +148,12 @@ public class ChatTransaction extends Transaction {
// Nothing to do // Nothing to do
} }
@Override
public boolean isConfirmable() {
// CHAT transactions can't go into blocks
return false;
}
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
// Nonce checking is done via isSignatureValid() as that method is only called once per import // Nonce checking is done via isSignatureValid() as that method is only called once per import

View File

@ -33,7 +33,9 @@ public class MessageTransaction extends Transaction {
public static final int MAX_DATA_SIZE = 4000; public static final int MAX_DATA_SIZE = 4000;
public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes public static final int POW_BUFFER_SIZE = 8 * 1024 * 1024; // bytes
public static final int POW_DIFFICULTY = 14; // leading zero bits public static final int POW_DIFFICULTY_V1 = 14; // leading zero bits
public static final int POW_DIFFICULTY_V2_CONFIRMABLE = 16; // leading zero bits
public static final int POW_DIFFICULTY_V2_UNCONFIRMABLE = 12; // leading zero bits
// Properties // Properties
@ -109,7 +111,17 @@ public class MessageTransaction extends Transaction {
MessageTransactionTransformer.clearNonce(transactionBytes); MessageTransactionTransformer.clearNonce(transactionBytes);
// Calculate nonce // Calculate nonce
this.messageTransactionData.setNonce(MemoryPoW.compute2(transactionBytes, POW_BUFFER_SIZE, POW_DIFFICULTY)); this.messageTransactionData.setNonce(MemoryPoW.compute2(transactionBytes, POW_BUFFER_SIZE, getPoWDifficulty()));
}
public int getPoWDifficulty() {
// The difficulty changes at the "mempow transactions updates" timestamp
if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) {
// If this message is confirmable then require a higher difficulty
return this.isConfirmable() ? POW_DIFFICULTY_V2_CONFIRMABLE : POW_DIFFICULTY_V2_UNCONFIRMABLE;
}
// Before feature trigger timestamp, so use existing difficulty value
return POW_DIFFICULTY_V1;
} }
/** /**
@ -183,6 +195,18 @@ public class MessageTransaction extends Transaction {
return super.hasValidReference(); return super.hasValidReference();
} }
@Override
public boolean isConfirmable() {
// After feature trigger timestamp, only messages to an AT address can confirm
if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) {
if (this.messageTransactionData.getRecipient() == null || !this.messageTransactionData.getRecipient().toUpperCase().startsWith("A")) {
// Message isn't to an AT address, so this transaction is unconfirmable
return false;
}
}
return true;
}
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
// Nonce checking is done via isSignatureValid() as that method is only called once per import // Nonce checking is done via isSignatureValid() as that method is only called once per import
@ -235,7 +259,7 @@ public class MessageTransaction extends Transaction {
MessageTransactionTransformer.clearNonce(transactionBytes); MessageTransactionTransformer.clearNonce(transactionBytes);
// Check nonce // Check nonce
return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, POW_DIFFICULTY, nonce); return MemoryPoW.verify2(transactionBytes, POW_BUFFER_SIZE, getPoWDifficulty(), nonce);
} }
@Override @Override
@ -256,6 +280,11 @@ public class MessageTransaction extends Transaction {
@Override @Override
public void process() throws DataException { public void process() throws DataException {
// Only certain MESSAGE transactions are able to confirm
if (!this.isConfirmable()) {
throw new DataException("Unconfirmable MESSAGE transactions should never be processed");
}
// If we have no amount then there's nothing to do // If we have no amount then there's nothing to do
if (this.messageTransactionData.getAmount() == 0L) if (this.messageTransactionData.getAmount() == 0L)
return; return;
@ -280,6 +309,11 @@ public class MessageTransaction extends Transaction {
@Override @Override
public void orphan() throws DataException { public void orphan() throws DataException {
// Only certain MESSAGE transactions are able to confirm
if (!this.isConfirmable()) {
throw new DataException("Unconfirmable MESSAGE transactions should never be orphaned");
}
// If we have no amount then there's nothing to do // If we have no amount then there's nothing to do
if (this.messageTransactionData.getAmount() == 0L) if (this.messageTransactionData.getAmount() == 0L)
return; return;

View File

@ -155,6 +155,12 @@ public class PresenceTransaction extends Transaction {
// Nothing to do // Nothing to do
} }
@Override
public boolean isConfirmable() {
// PRESENCE transactions can't go into blocks
return false;
}
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
// Nonce checking is done via isSignatureValid() as that method is only called once per import // Nonce checking is done via isSignatureValid() as that method is only called once per import

View File

@ -7,6 +7,7 @@ import org.qortal.account.Account;
import org.qortal.account.PublicKeyAccount; import org.qortal.account.PublicKeyAccount;
import org.qortal.api.resource.TransactionsResource.ConfirmationStatus; import org.qortal.api.resource.TransactionsResource.ConfirmationStatus;
import org.qortal.asset.Asset; import org.qortal.asset.Asset;
import org.qortal.block.BlockChain;
import org.qortal.crypto.MemoryPoW; import org.qortal.crypto.MemoryPoW;
import org.qortal.data.transaction.PublicizeTransactionData; import org.qortal.data.transaction.PublicizeTransactionData;
import org.qortal.data.transaction.TransactionData; import org.qortal.data.transaction.TransactionData;
@ -89,6 +90,12 @@ public class PublicizeTransaction extends Transaction {
@Override @Override
public ValidationResult isValid() throws DataException { public ValidationResult isValid() throws DataException {
// Disable completely after feature-trigger timestamp, at the same time that mempow difficulties are being increased.
// It could be enabled again in the future, but preferably with an enforced minimum fee instead of allowing a mempow nonce.
if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getMemPoWTransactionUpdatesTimestamp()) {
return ValidationResult.NOT_SUPPORTED;
}
// There can be only one // There can be only one
List<byte[]> signatures = this.repository.getTransactionRepository().getSignaturesMatchingCriteria( List<byte[]> signatures = this.repository.getTransactionRepository().getSignaturesMatchingCriteria(
TransactionType.PUBLICIZE, TransactionType.PUBLICIZE,

View File

@ -248,7 +248,8 @@ public abstract class Transaction {
GROUP_APPROVAL_REQUIRED(98), GROUP_APPROVAL_REQUIRED(98),
ACCOUNT_NOT_TRANSFERABLE(99), ACCOUNT_NOT_TRANSFERABLE(99),
INVALID_BUT_OK(999), INVALID_BUT_OK(999),
NOT_YET_RELEASED(1000); NOT_YET_RELEASED(1000),
NOT_SUPPORTED(1001);
public final int value; public final int value;
@ -636,7 +637,7 @@ public abstract class Transaction {
} }
/** /**
* Returns sorted, unconfirmed transactions, excluding invalid. * Returns sorted, unconfirmed transactions, excluding invalid and unconfirmable.
* *
* @return sorted, unconfirmed transactions * @return sorted, unconfirmed transactions
* @throws DataException * @throws DataException
@ -654,7 +655,8 @@ public abstract class Transaction {
TransactionData transactionData = unconfirmedTransactionsIterator.next(); TransactionData transactionData = unconfirmedTransactionsIterator.next();
Transaction transaction = Transaction.fromData(repository, transactionData); Transaction transaction = Transaction.fromData(repository, transactionData);
if (transaction.isStillValidUnconfirmed(latestBlockData.getTimestamp()) != ValidationResult.OK) // Must be confirmable and valid
if (!transaction.isConfirmable() || transaction.isStillValidUnconfirmed(latestBlockData.getTimestamp()) != ValidationResult.OK)
unconfirmedTransactionsIterator.remove(); unconfirmedTransactionsIterator.remove();
} }
@ -892,6 +894,17 @@ public abstract class Transaction {
/* To be optionally overridden */ /* To be optionally overridden */
} }
/**
* Returns whether transaction is 'confirmable' - i.e. is of a type that
* can be included in a block. Some transactions are 'unconfirmable'
* and therefore must remain in the mempool until they expire.
* @return
*/
public boolean isConfirmable() {
/* To be optionally overridden */
return true;
}
/** /**
* Returns whether transaction can be added to the blockchain. * Returns whether transaction can be added to the blockchain.
* <p> * <p>

View File

@ -29,6 +29,7 @@
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 1659801600000, "onlineAccountsModulusV2Timestamp": 1659801600000,
"selfSponsorshipAlgoV1SnapshotTimestamp": 1670230000000, "selfSponsorshipAlgoV1SnapshotTimestamp": 1670230000000,
"mempowTransactionUpdatesTimestamp": 9999999999999,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 5.00 }, { "height": 1, "reward": 5.00 },
{ "height": 259201, "reward": 4.75 }, { "height": 259201, "reward": 4.75 },

View File

@ -3,6 +3,8 @@ package org.qortal.test;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.qortal.crypto.MemoryPoW; import org.qortal.crypto.MemoryPoW;
import org.qortal.repository.DataException;
import org.qortal.test.common.Common;
import static org.junit.Assert.*; import static org.junit.Assert.*;
@ -39,13 +41,14 @@ public class MemoryPoWTests {
} }
@Test @Test
public void testMultipleComputes() { public void testMultipleComputes() throws DataException {
Common.useDefaultSettings();
Random random = new Random(); Random random = new Random();
final int sampleSize = 20; final int sampleSize = 10;
final long stddevDivisor = sampleSize * (sampleSize - 1); final long stddevDivisor = sampleSize * (sampleSize - 1);
for (int difficulty = 8; difficulty < 16; difficulty += 2) { for (int difficulty = 8; difficulty <= 16; difficulty++) {
byte[] data = new byte[256]; byte[] data = new byte[256];
long[] times = new long[sampleSize]; long[] times = new long[sampleSize];

View File

@ -1,5 +1,6 @@
package org.qortal.test; package org.qortal.test;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
@ -14,12 +15,9 @@ import org.qortal.group.Group.ApprovalThreshold;
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.test.common.BlockUtils; import org.qortal.test.common.*;
import org.qortal.test.common.Common;
import org.qortal.test.common.GroupUtils;
import org.qortal.test.common.TestAccount;
import org.qortal.test.common.TransactionUtils;
import org.qortal.test.common.transaction.TestTransaction; import org.qortal.test.common.transaction.TestTransaction;
import org.qortal.transaction.DeployAtTransaction;
import org.qortal.transaction.MessageTransaction; import org.qortal.transaction.MessageTransaction;
import org.qortal.transaction.Transaction; import org.qortal.transaction.Transaction;
import org.qortal.transaction.Transaction.TransactionType; import org.qortal.transaction.Transaction.TransactionType;
@ -139,7 +137,7 @@ public class MessageTests extends Common {
} }
@Test @Test
public void withRecipentWithAmount() throws DataException { public void withRecipientWithAmount() throws DataException {
testMessage(Group.NO_GROUP, recipient, 123L, Asset.QORT); testMessage(Group.NO_GROUP, recipient, 123L, Asset.QORT);
} }
@ -153,6 +151,132 @@ public class MessageTests extends Common {
testMessage(1, null, 0L, null); testMessage(1, null, 0L, null);
} }
@Test
public void atRecipientNoFeeWithNonce() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
String atRecipient = deployAt();
MessageTransaction transaction = testFeeNonce(repository, false, true, atRecipient, true);
// Transaction should be confirmable because it's to an AT, and therefore should be present in a block
assertTrue(transaction.isConfirmable());
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
assertTrue(isTransactionConfirmed(repository, transaction));
assertEquals(16, transaction.getPoWDifficulty());
BlockUtils.orphanLastBlock(repository);
}
}
@Test
public void regularRecipientNoFeeWithNonce() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
MessageTransaction transaction = testFeeNonce(repository, false, true, recipient, true);
// Transaction shouldn't be confirmable because it's not to an AT, and therefore shouldn't be present in a block
assertFalse(transaction.isConfirmable());
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
assertFalse(isTransactionConfirmed(repository, transaction));
assertEquals(12, transaction.getPoWDifficulty());
BlockUtils.orphanLastBlock(repository);
}
}
@Test
public void noRecipientNoFeeWithNonce() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
MessageTransaction transaction = testFeeNonce(repository, false, true, null, true);
// Transaction shouldn't be confirmable because it's not to an AT, and therefore shouldn't be present in a block
assertFalse(transaction.isConfirmable());
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
assertFalse(isTransactionConfirmed(repository, transaction));
assertEquals(12, transaction.getPoWDifficulty());
BlockUtils.orphanLastBlock(repository);
}
}
@Test
public void atRecipientWithFeeNoNonce() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
String atRecipient = deployAt();
MessageTransaction transaction = testFeeNonce(repository, true, false, atRecipient, true);
// Transaction should be confirmable because it's to an AT, and therefore should be present in a block
assertTrue(transaction.isConfirmable());
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
assertTrue(isTransactionConfirmed(repository, transaction));
assertEquals(16, transaction.getPoWDifficulty());
BlockUtils.orphanLastBlock(repository);
}
}
@Test
public void regularRecipientWithFeeNoNonce() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
MessageTransaction transaction = testFeeNonce(repository, true, false, recipient, true);
// Transaction shouldn't be confirmable because it's not to an AT, and therefore shouldn't be present in a block
assertFalse(transaction.isConfirmable());
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
assertFalse(isTransactionConfirmed(repository, transaction));
assertEquals(12, transaction.getPoWDifficulty());
BlockUtils.orphanLastBlock(repository);
}
}
@Test
public void atRecipientNoFeeWithNonceLegacyDifficulty() throws DataException, IllegalAccessException {
try (final Repository repository = RepositoryManager.getRepository()) {
// Set mempowTransactionUpdatesTimestamp to a high value, so that it hasn't activated key
FieldUtils.writeField(BlockChain.getInstance(), "mempowTransactionUpdatesTimestamp", Long.MAX_VALUE, true);
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
String atRecipient = deployAt();
MessageTransaction transaction = testFeeNonce(repository, false, true, atRecipient, true);
// Transaction should be confirmable because all MESSAGE transactions confirmed prior to the feature trigger
assertTrue(transaction.isConfirmable());
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
assertTrue(isTransactionConfirmed(repository, transaction));
assertEquals(14, transaction.getPoWDifficulty()); // Legacy difficulty was 14 in all cases
BlockUtils.orphanLastBlock(repository);
}
}
@Test
public void regularRecipientNoFeeWithNonceLegacyDifficulty() throws DataException, IllegalAccessException {
try (final Repository repository = RepositoryManager.getRepository()) {
// Set mempowTransactionUpdatesTimestamp to a high value, so that it hasn't activated key
FieldUtils.writeField(BlockChain.getInstance(), "mempowTransactionUpdatesTimestamp", Long.MAX_VALUE, true);
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
MessageTransaction transaction = testFeeNonce(repository, false, true, recipient, true);
// Transaction should be confirmable because all MESSAGE transactions confirmed prior to the feature trigger
assertTrue(transaction.isConfirmable());
TransactionUtils.signAndMint(repository, transaction.getTransactionData(), alice);
assertTrue(isTransactionConfirmed(repository, transaction)); // All MESSAGE transactions would confirm before feature trigger
assertEquals(14, transaction.getPoWDifficulty()); // Legacy difficulty was 14 in all cases
BlockUtils.orphanLastBlock(repository);
}
}
@Test @Test
public void serializationTests() throws DataException, TransformationException { public void serializationTests() throws DataException, TransformationException {
// with recipient, with amount // with recipient, with amount
@ -165,6 +289,24 @@ public class MessageTests extends Common {
testSerialization(null, 0L, null); testSerialization(null, 0L, null);
} }
private String deployAt() throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount deployer = Common.getTestAccount(repository, "alice");
byte[] creationBytes = AtUtils.buildSimpleAT();
long fundingAmount = 1_00000000L;
DeployAtTransaction deployAtTransaction = AtUtils.doDeployAT(repository, deployer, creationBytes, fundingAmount);
String address = deployAtTransaction.getATAccount().getAddress();
assertNotNull(address);
return address;
}
}
private boolean isTransactionConfirmed(Repository repository, MessageTransaction transaction) throws DataException {
TransactionData queriedTransactionData = repository.getTransactionRepository().fromSignature(transaction.getTransactionData().getSignature());
return queriedTransactionData.getBlockHeight() != null && queriedTransactionData.getBlockHeight() > 0;
}
private boolean isValid(int txGroupId, String recipient, long amount, Long assetId) throws DataException { private boolean isValid(int txGroupId, String recipient, long amount, Long assetId) throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
TestAccount alice = Common.getTestAccount(repository, "alice"); TestAccount alice = Common.getTestAccount(repository, "alice");
@ -195,41 +337,48 @@ public class MessageTests extends Common {
return messageTransaction.hasValidReference(); return messageTransaction.hasValidReference();
} }
private void testFeeNonce(boolean withFee, boolean withNonce, boolean isValid) throws DataException {
private MessageTransaction testFeeNonce(boolean withFee, boolean withNonce, boolean isValid) throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
TestAccount alice = Common.getTestAccount(repository, "alice"); return testFeeNonce(repository, withFee, withNonce, recipient, isValid);
int txGroupId = 0;
int nonce = 0;
long amount = 0;
long assetId = Asset.QORT;
byte[] data = new byte[1];
boolean isText = false;
boolean isEncrypted = false;
MessageTransactionData transactionData = new MessageTransactionData(TestTransaction.generateBase(alice, txGroupId),
version, nonce, recipient, amount, assetId, data, isText, isEncrypted);
MessageTransaction transaction = new MessageTransaction(repository, transactionData);
if (withFee)
transactionData.setFee(transaction.calcRecommendedFee());
else
transactionData.setFee(0L);
if (withNonce) {
transaction.computeNonce();
} else {
transactionData.setNonce(-1);
}
transaction.sign(alice);
assertEquals(isValid, transaction.isSignatureValid());
} }
} }
private void testMessage(int txGroupId, String recipient, long amount, Long assetId) throws DataException { private MessageTransaction testFeeNonce(Repository repository, boolean withFee, boolean withNonce, String recipient, boolean isValid) throws DataException {
TestAccount alice = Common.getTestAccount(repository, "alice");
int txGroupId = 0;
int nonce = 0;
long amount = 0;
long assetId = Asset.QORT;
byte[] data = new byte[1];
boolean isText = false;
boolean isEncrypted = false;
MessageTransactionData transactionData = new MessageTransactionData(TestTransaction.generateBase(alice, txGroupId),
version, nonce, recipient, amount, assetId, data, isText, isEncrypted);
MessageTransaction transaction = new MessageTransaction(repository, transactionData);
if (withFee)
transactionData.setFee(transaction.calcRecommendedFee());
else
transactionData.setFee(0L);
if (withNonce) {
transaction.computeNonce();
} else {
transactionData.setNonce(-1);
}
transaction.sign(alice);
assertEquals(isValid, transaction.isSignatureValid());
return transaction;
}
private MessageTransaction testMessage(int txGroupId, String recipient, long amount, Long assetId) throws DataException {
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
TestAccount alice = Common.getTestAccount(repository, "alice"); TestAccount alice = Common.getTestAccount(repository, "alice");
@ -244,6 +393,8 @@ public class MessageTests extends Common {
TransactionUtils.signAndMint(repository, transactionData, alice); TransactionUtils.signAndMint(repository, transactionData, alice);
BlockUtils.orphanLastBlock(repository); BlockUtils.orphanLastBlock(repository);
return new MessageTransaction(repository, transactionData);
} }
} }

View File

@ -11,6 +11,7 @@ import org.qortal.arbitrary.ArbitraryDataReader;
import org.qortal.arbitrary.exception.MissingDataException; import org.qortal.arbitrary.exception.MissingDataException;
import org.qortal.arbitrary.misc.Category; import org.qortal.arbitrary.misc.Category;
import org.qortal.arbitrary.misc.Service; import org.qortal.arbitrary.misc.Service;
import org.qortal.block.BlockChain;
import org.qortal.controller.arbitrary.ArbitraryDataManager; import org.qortal.controller.arbitrary.ArbitraryDataManager;
import org.qortal.data.arbitrary.ArbitraryResourceMetadata; import org.qortal.data.arbitrary.ArbitraryResourceMetadata;
import org.qortal.data.transaction.ArbitraryTransactionData; import org.qortal.data.transaction.ArbitraryTransactionData;
@ -24,6 +25,7 @@ import org.qortal.test.common.TransactionUtils;
import org.qortal.test.common.transaction.TestTransaction; import org.qortal.test.common.transaction.TestTransaction;
import org.qortal.transaction.RegisterNameTransaction; import org.qortal.transaction.RegisterNameTransaction;
import org.qortal.utils.Base58; import org.qortal.utils.Base58;
import org.qortal.utils.NTP;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
@ -106,8 +108,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
// Create PUT transaction // Create PUT transaction
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength); Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true, identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
title, description, tags, category); title, description, tags, category);
// Check the chunk count is correct // Check the chunk count is correct
@ -157,8 +160,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
// Create PUT transaction // Create PUT transaction
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength); Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true, identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
title, description, tags, category); title, description, tags, category);
// Check the chunk count is correct // Check the chunk count is correct
@ -220,8 +224,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
// Create PUT transaction // Create PUT transaction
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength); Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true, identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
title, description, tags, category); title, description, tags, category);
// Check the chunk count is correct // Check the chunk count is correct
@ -272,8 +277,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
// Create PUT transaction // Create PUT transaction
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength); Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true, identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
title, description, tags, category); title, description, tags, category);
// Check the chunk count is correct // Check the chunk count is correct
@ -316,8 +322,9 @@ public class ArbitraryTransactionMetadataTests extends Common {
// Create PUT transaction // Create PUT transaction
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength); Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true, identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
title, description, tags, category); title, description, tags, category);
// Check the metadata is correct // Check the metadata is correct

View File

@ -10,6 +10,7 @@ import org.qortal.arbitrary.ArbitraryDataTransactionBuilder;
import org.qortal.arbitrary.exception.MissingDataException; import org.qortal.arbitrary.exception.MissingDataException;
import org.qortal.arbitrary.misc.Category; import org.qortal.arbitrary.misc.Category;
import org.qortal.arbitrary.misc.Service; import org.qortal.arbitrary.misc.Service;
import org.qortal.block.BlockChain;
import org.qortal.controller.arbitrary.ArbitraryDataManager; import org.qortal.controller.arbitrary.ArbitraryDataManager;
import org.qortal.crypto.Crypto; import org.qortal.crypto.Crypto;
import org.qortal.data.PaymentData; import org.qortal.data.PaymentData;
@ -50,51 +51,6 @@ public class ArbitraryTransactionTests extends Common {
Common.useDefaultSettings(); Common.useDefaultSettings();
} }
@Test
public void testDifficultyTooLow() throws IllegalAccessException, DataException, IOException {
try (final Repository repository = RepositoryManager.getRepository()) {
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
String publicKey58 = Base58.encode(alice.getPublicKey());
String name = "TEST"; // Can be anything for this test
String identifier = null; // Not used for this test
Service service = Service.ARBITRARY_DATA;
int chunkSize = 100;
int dataLength = 900; // Actual data length will be longer due to encryption
// Register the name to Alice
RegisterNameTransactionData registerNameTransactionData = new RegisterNameTransactionData(TestTransaction.generateBase(alice), name, "");
registerNameTransactionData.setFee(new RegisterNameTransaction(null, null).getUnitFee(registerNameTransactionData.getTimestamp()));
TransactionUtils.signAndMint(repository, registerNameTransactionData, alice);
// Set difficulty to 1
FieldUtils.writeField(ArbitraryDataManager.getInstance(), "powDifficulty", 1, true);
// Create PUT transaction
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength);
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize);
// Check that nonce validation succeeds
byte[] signature = arbitraryDataFile.getSignature();
TransactionData transactionData = repository.getTransactionRepository().fromSignature(signature);
ArbitraryTransaction transaction = new ArbitraryTransaction(repository, transactionData);
assertTrue(transaction.isSignatureValid());
// Increase difficulty to 15
FieldUtils.writeField(ArbitraryDataManager.getInstance(), "powDifficulty", 15, true);
// Make sure the nonce validation fails
// Note: there is a very tiny chance this could succeed due to being extremely lucky
// and finding a high difficulty nonce in the first couple of cycles. It will be rare
// enough that we shouldn't need to account for it.
assertFalse(transaction.isSignatureValid());
// Reduce difficulty back to 1, to double check
FieldUtils.writeField(ArbitraryDataManager.getInstance(), "powDifficulty", 1, true);
assertTrue(transaction.isSignatureValid());
}
}
@Test @Test
public void testNonceAndFee() throws IllegalAccessException, DataException, IOException { public void testNonceAndFee() throws IllegalAccessException, DataException, IOException {
try (final Repository repository = RepositoryManager.getRepository()) { try (final Repository repository = RepositoryManager.getRepository()) {
@ -497,8 +453,9 @@ public class ArbitraryTransactionTests extends Common {
// Create PUT transaction // Create PUT transaction
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true); Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true);
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true, identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
null, null, null, null); null, null, null, null);
byte[] signature = arbitraryDataFile.getSignature(); byte[] signature = arbitraryDataFile.getSignature();
@ -556,8 +513,9 @@ public class ArbitraryTransactionTests extends Common {
// Create PUT transaction // Create PUT transaction
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true); Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true);
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true, identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
title, description, tags, category); title, description, tags, category);
byte[] signature = arbitraryDataFile.getSignature(); byte[] signature = arbitraryDataFile.getSignature();
@ -614,8 +572,9 @@ public class ArbitraryTransactionTests extends Common {
// Create PUT transaction // Create PUT transaction
Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true); Path path1 = ArbitraryUtils.generateRandomDataPath(dataLength, true);
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name, ArbitraryDataFile arbitraryDataFile = ArbitraryUtils.createAndMintTxn(repository, publicKey58, path1, name,
identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, 0L, true, identifier, ArbitraryTransactionData.Method.PUT, service, alice, chunkSize, fee, false,
null, null, null, null); null, null, null, null);
byte[] signature = arbitraryDataFile.getSignature(); byte[] signature = arbitraryDataFile.getSignature();

View File

@ -5,10 +5,12 @@ import org.qortal.arbitrary.ArbitraryDataFile;
import org.qortal.arbitrary.ArbitraryDataTransactionBuilder; import org.qortal.arbitrary.ArbitraryDataTransactionBuilder;
import org.qortal.arbitrary.misc.Category; import org.qortal.arbitrary.misc.Category;
import org.qortal.arbitrary.misc.Service; import org.qortal.arbitrary.misc.Service;
import org.qortal.block.BlockChain;
import org.qortal.data.transaction.ArbitraryTransactionData; import org.qortal.data.transaction.ArbitraryTransactionData;
import org.qortal.repository.DataException; import org.qortal.repository.DataException;
import org.qortal.repository.Repository; import org.qortal.repository.Repository;
import org.qortal.transaction.Transaction; import org.qortal.transaction.Transaction;
import org.qortal.utils.NTP;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.File; import java.io.File;
@ -20,16 +22,15 @@ import java.nio.file.Paths;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
import static org.junit.Assert.assertEquals;
public class ArbitraryUtils { public class ArbitraryUtils {
public static ArbitraryDataFile createAndMintTxn(Repository repository, String publicKey58, Path path, String name, String identifier, public static ArbitraryDataFile createAndMintTxn(Repository repository, String publicKey58, Path path, String name, String identifier,
ArbitraryTransactionData.Method method, Service service, PrivateKeyAccount account, ArbitraryTransactionData.Method method, Service service, PrivateKeyAccount account,
int chunkSize) throws DataException { int chunkSize) throws DataException {
long fee = BlockChain.getInstance().getUnitFeeAtTimestamp(NTP.getTime());
return ArbitraryUtils.createAndMintTxn(repository, publicKey58, path, name, identifier, method, service, return ArbitraryUtils.createAndMintTxn(repository, publicKey58, path, name, identifier, method, service,
account, chunkSize, 0L, true, null, null, null, null); account, chunkSize, fee, false, null, null, null, null);
} }
public static ArbitraryDataFile createAndMintTxn(Repository repository, String publicKey58, Path path, String name, String identifier, public static ArbitraryDataFile createAndMintTxn(Repository repository, String publicKey58, Path path, String name, String identifier,
@ -47,7 +48,9 @@ public class ArbitraryUtils {
} }
ArbitraryTransactionData transactionData = txnBuilder.getArbitraryTransactionData(); ArbitraryTransactionData transactionData = txnBuilder.getArbitraryTransactionData();
Transaction.ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, account); Transaction.ValidationResult result = TransactionUtils.signAndImport(repository, transactionData, account);
assertEquals(Transaction.ValidationResult.OK, result); if (result != Transaction.ValidationResult.OK) {
throw new DataException(String.format("Arbitrary transaction invalid: %s", result.toString()));
}
BlockUtils.mintBlock(repository); BlockUtils.mintBlock(repository);
// We need a new ArbitraryDataFile instance because the files will have been moved to the signature's folder // We need a new ArbitraryDataFile instance because the files will have been moved to the signature's folder

View File

@ -444,6 +444,7 @@ public class MiscTests extends Common {
// Payment // Payment
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice"); PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
PrivateKeyAccount bob = Common.getTestAccount(repository, "bob"); PrivateKeyAccount bob = Common.getTestAccount(repository, "bob");
PrivateKeyAccount chloe = Common.getTestAccount(repository, "chloe");
PaymentTransactionData transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), bob.getAddress(), 100000); PaymentTransactionData transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), bob.getAddress(), 100000);
transactionData.setFee(new PaymentTransaction(null, null).getUnitFee(transactionData.getTimestamp())); transactionData.setFee(new PaymentTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
@ -473,16 +474,16 @@ public class MiscTests extends Common {
Transaction transaction = Transaction.fromData(repository, transactionData); Transaction transaction = Transaction.fromData(repository, transactionData);
transaction.sign(alice); transaction.sign(alice);
ValidationResult result = transaction.importAsUnconfirmed(); ValidationResult result = transaction.importAsUnconfirmed();
assertTrue("Transaction should be valid", ValidationResult.OK == result); assertEquals("Transaction should be valid", ValidationResult.OK, result);
// Now try fetching and setting fee manually // Now try fetching and setting fee manually
transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), bob.getAddress(), 50000); transactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), chloe.getAddress(), 50000);
transactionData.setFee(new PaymentTransaction(null, null).getUnitFee(transactionData.getTimestamp())); transactionData.setFee(new PaymentTransaction(null, null).getUnitFee(transactionData.getTimestamp()));
assertEquals(300000000L, 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();
assertTrue("Transaction should be valid", ValidationResult.OK == result); assertEquals("Transaction should be valid", ValidationResult.OK, result);
} }
} }

View File

@ -18,6 +18,7 @@
"onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMinLifetime": 3600000,
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
"mempowTransactionUpdatesTimestamp": 0,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -22,6 +22,7 @@
"onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMinLifetime": 3600000,
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
"mempowTransactionUpdatesTimestamp": 0,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -23,6 +23,7 @@
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999, "onlineAccountsModulusV2Timestamp": 9999999999999,
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
"mempowTransactionUpdatesTimestamp": 0,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -23,6 +23,7 @@
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999, "onlineAccountsModulusV2Timestamp": 9999999999999,
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
"mempowTransactionUpdatesTimestamp": 0,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -23,6 +23,7 @@
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999, "onlineAccountsModulusV2Timestamp": 9999999999999,
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
"mempowTransactionUpdatesTimestamp": 9999999999999,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -23,6 +23,7 @@
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999, "onlineAccountsModulusV2Timestamp": 9999999999999,
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
"mempowTransactionUpdatesTimestamp": 0,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -23,6 +23,7 @@
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999, "onlineAccountsModulusV2Timestamp": 9999999999999,
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
"mempowTransactionUpdatesTimestamp": 0,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -23,6 +23,7 @@
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999, "onlineAccountsModulusV2Timestamp": 9999999999999,
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
"mempowTransactionUpdatesTimestamp": 0,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -23,6 +23,7 @@
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999, "onlineAccountsModulusV2Timestamp": 9999999999999,
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
"mempowTransactionUpdatesTimestamp": 0,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -23,6 +23,7 @@
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999, "onlineAccountsModulusV2Timestamp": 9999999999999,
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
"mempowTransactionUpdatesTimestamp": 0,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -22,6 +22,7 @@
"onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMinLifetime": 3600000,
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
"mempowTransactionUpdatesTimestamp": 0,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -23,6 +23,7 @@
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999, "onlineAccountsModulusV2Timestamp": 9999999999999,
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
"mempowTransactionUpdatesTimestamp": 0,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },

View File

@ -24,6 +24,7 @@
"onlineAccountSignaturesMaxLifetime": 86400000, "onlineAccountSignaturesMaxLifetime": 86400000,
"onlineAccountsModulusV2Timestamp": 9999999999999, "onlineAccountsModulusV2Timestamp": 9999999999999,
"selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999, "selfSponsorshipAlgoV1SnapshotTimestamp": 9999999999999,
"mempowTransactionUpdatesTimestamp": 0,
"rewardsByHeight": [ "rewardsByHeight": [
{ "height": 1, "reward": 100 }, { "height": 1, "reward": 100 },
{ "height": 11, "reward": 10 }, { "height": 11, "reward": 10 },