forked from Qortal/qortal
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:
parent
b0224651c2
commit
7bb61ec564
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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,
|
||||||
|
@ -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>
|
||||||
|
@ -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 },
|
||||||
|
@ -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];
|
||||||
|
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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();
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 },
|
||||||
|
@ -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 },
|
||||||
|
@ -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 },
|
||||||
|
@ -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 },
|
||||||
|
@ -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 },
|
||||||
|
@ -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 },
|
||||||
|
@ -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 },
|
||||||
|
@ -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 },
|
||||||
|
@ -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 },
|
||||||
|
@ -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 },
|
||||||
|
@ -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 },
|
||||||
|
@ -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 },
|
||||||
|
@ -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 },
|
||||||
|
Loading…
Reference in New Issue
Block a user