forked from Qortal/qortal
Disable last reference validation after feature trigger timestamp (not yet set).
This should prevent the failed transactions that are encountered when issuing two or more in a short space of time. Using a feature trigger (hard fork) to release this, to avoid potential consensus confusion around the time of the update (older versions could consider the main chain invalid until updating).
This commit is contained in:
parent
48b562f71b
commit
f887fcafe3
@ -70,7 +70,8 @@ public class BlockChain {
|
||||
shareBinFix,
|
||||
calcChainWeightTimestamp,
|
||||
transactionV5Timestamp,
|
||||
transactionV6Timestamp;
|
||||
transactionV6Timestamp,
|
||||
disableReferenceTimestamp
|
||||
}
|
||||
|
||||
// Custom transaction fees
|
||||
@ -410,6 +411,10 @@ public class BlockChain {
|
||||
return this.featureTriggers.get(FeatureTrigger.transactionV6Timestamp.name()).longValue();
|
||||
}
|
||||
|
||||
public long getDisableReferenceTimestamp() {
|
||||
return this.featureTriggers.get(FeatureTrigger.disableReferenceTimestamp.name()).longValue();
|
||||
}
|
||||
|
||||
// More complex getters for aspects that change by height or timestamp
|
||||
|
||||
public long getRewardAtHeight(int ourHeight) {
|
||||
|
@ -6,6 +6,7 @@ import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataManager;
|
||||
import org.qortal.controller.arbitrary.ArbitraryDataStorageManager;
|
||||
import org.qortal.crypto.Crypto;
|
||||
@ -86,6 +87,12 @@ public class ArbitraryTransaction extends Transaction {
|
||||
@Override
|
||||
public boolean hasValidReference() throws DataException {
|
||||
// We shouldn't really get this far, but just in case:
|
||||
|
||||
// Disable reference checking after feature trigger timestamp
|
||||
if (this.arbitraryTransactionData.getTimestamp() >= BlockChain.getInstance().getDisableReferenceTimestamp()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.arbitraryTransactionData.getReference() == null) {
|
||||
return false;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import java.util.List;
|
||||
|
||||
import org.qortal.account.Account;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.data.asset.AssetData;
|
||||
import org.qortal.data.transaction.ATTransactionData;
|
||||
@ -75,6 +76,11 @@ public class AtTransaction extends Transaction {
|
||||
|
||||
@Override
|
||||
public boolean hasValidReference() throws DataException {
|
||||
// Disable reference checking after feature trigger timestamp
|
||||
if (this.atTransactionData.getTimestamp() >= BlockChain.getInstance().getDisableReferenceTimestamp()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check reference is correct, using AT account, not transaction creator which is null account
|
||||
Account atAccount = getATAccount();
|
||||
return Arrays.equals(atAccount.getLastReference(), atTransactionData.getReference());
|
||||
|
@ -8,6 +8,7 @@ import org.qortal.account.Account;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.account.PublicKeyAccount;
|
||||
import org.qortal.asset.Asset;
|
||||
import org.qortal.block.BlockChain;
|
||||
import org.qortal.crypto.Crypto;
|
||||
import org.qortal.crypto.MemoryPoW;
|
||||
import org.qortal.data.PaymentData;
|
||||
@ -163,6 +164,12 @@ public class MessageTransaction extends Transaction {
|
||||
@Override
|
||||
public boolean hasValidReference() throws DataException {
|
||||
// We shouldn't really get this far, but just in case:
|
||||
|
||||
// Disable reference checking after feature trigger timestamp
|
||||
if (this.messageTransactionData.getTimestamp() >= BlockChain.getInstance().getDisableReferenceTimestamp()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.messageTransactionData.getReference() == null)
|
||||
return false;
|
||||
|
||||
|
@ -905,6 +905,11 @@ public abstract class Transaction {
|
||||
* @throws DataException
|
||||
*/
|
||||
public boolean hasValidReference() throws DataException {
|
||||
// Disable reference checking after feature trigger timestamp
|
||||
if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getDisableReferenceTimestamp()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Account creator = getCreator();
|
||||
|
||||
return Arrays.equals(transactionData.getReference(), creator.getLastReference());
|
||||
|
@ -59,7 +59,8 @@
|
||||
"shareBinFix": 399000,
|
||||
"calcChainWeightTimestamp": 1620579600000,
|
||||
"transactionV5Timestamp": 1642176000000,
|
||||
"transactionV6Timestamp": 9999999999999
|
||||
"transactionV6Timestamp": 9999999999999,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
83
src/test/java/org/qortal/test/TransactionReferenceTests.java
Normal file
83
src/test/java/org/qortal/test/TransactionReferenceTests.java
Normal file
@ -0,0 +1,83 @@
|
||||
package org.qortal.test;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.qortal.account.PrivateKeyAccount;
|
||||
import org.qortal.data.transaction.PaymentTransactionData;
|
||||
import org.qortal.data.transaction.TransactionData;
|
||||
import org.qortal.repository.DataException;
|
||||
import org.qortal.repository.Repository;
|
||||
import org.qortal.repository.RepositoryManager;
|
||||
import org.qortal.test.common.Common;
|
||||
import org.qortal.test.common.TransactionUtils;
|
||||
import org.qortal.test.common.transaction.TestTransaction;
|
||||
import org.qortal.transaction.Transaction;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class TransactionReferenceTests extends Common {
|
||||
|
||||
@Before
|
||||
public void beforeTest() throws DataException {
|
||||
Common.useDefaultSettings();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidRandomReferenceBeforeFeatureTrigger() throws DataException {
|
||||
Random random = new Random();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
|
||||
byte[] randomPrivateKey = new byte[32];
|
||||
random.nextBytes(randomPrivateKey);
|
||||
PrivateKeyAccount recipient = new PrivateKeyAccount(repository, randomPrivateKey);
|
||||
|
||||
// Create payment transaction data
|
||||
TransactionData paymentTransactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), recipient.getAddress(), 100000L);
|
||||
|
||||
// Set random reference
|
||||
byte[] randomReference = new byte[64];
|
||||
random.nextBytes(randomReference);
|
||||
paymentTransactionData.setReference(randomReference);
|
||||
|
||||
Transaction paymentTransaction = Transaction.fromData(repository, paymentTransactionData);
|
||||
|
||||
// Transaction should be invalid due to random reference
|
||||
Transaction.ValidationResult validationResult = paymentTransaction.isValidUnconfirmed();
|
||||
assertEquals(Transaction.ValidationResult.INVALID_REFERENCE, validationResult);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidRandomReferenceAfterFeatureTrigger() throws DataException {
|
||||
Common.useSettings("test-settings-v2-disable-reference.json");
|
||||
Random random = new Random();
|
||||
|
||||
try (final Repository repository = RepositoryManager.getRepository()) {
|
||||
PrivateKeyAccount alice = Common.getTestAccount(repository, "alice");
|
||||
|
||||
byte[] randomPrivateKey = new byte[32];
|
||||
random.nextBytes(randomPrivateKey);
|
||||
PrivateKeyAccount recipient = new PrivateKeyAccount(repository, randomPrivateKey);
|
||||
|
||||
// Create payment transaction data
|
||||
TransactionData paymentTransactionData = new PaymentTransactionData(TestTransaction.generateBase(alice), recipient.getAddress(), 100000L);
|
||||
|
||||
// Set random reference
|
||||
byte[] randomReference = new byte[64];
|
||||
random.nextBytes(randomReference);
|
||||
paymentTransactionData.setReference(randomReference);
|
||||
|
||||
Transaction paymentTransaction = Transaction.fromData(repository, paymentTransactionData);
|
||||
|
||||
// Transaction should be valid, even with random reference, because reference checking is now disabled
|
||||
Transaction.ValidationResult validationResult = paymentTransaction.isValidUnconfirmed();
|
||||
assertEquals(Transaction.ValidationResult.OK, validationResult);
|
||||
TransactionUtils.signAndImportValid(repository, paymentTransactionData, alice);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
84
src/test/resources/test-chain-v2-disable-reference.json
Normal file
84
src/test/resources/test-chain-v2-disable-reference.json
Normal file
@ -0,0 +1,84 @@
|
||||
{
|
||||
"isTestChain": true,
|
||||
"blockTimestampMargin": 500,
|
||||
"transactionExpiryPeriod": 86400000,
|
||||
"maxBlockSize": 2097152,
|
||||
"maxBytesPerUnitFee": 1024,
|
||||
"unitFee": "0.1",
|
||||
"nameRegistrationUnitFees": [
|
||||
{ "timestamp": 1645372800000, "fee": "5" }
|
||||
],
|
||||
"requireGroupForApproval": false,
|
||||
"minAccountLevelToRewardShare": 5,
|
||||
"maxRewardSharesPerMintingAccount": 20,
|
||||
"founderEffectiveMintingLevel": 10,
|
||||
"onlineAccountSignaturesMinLifetime": 3600000,
|
||||
"onlineAccountSignaturesMaxLifetime": 86400000,
|
||||
"rewardsByHeight": [
|
||||
{ "height": 1, "reward": 100 },
|
||||
{ "height": 11, "reward": 10 },
|
||||
{ "height": 21, "reward": 1 }
|
||||
],
|
||||
"sharesByLevel": [
|
||||
{ "levels": [ 1, 2 ], "share": 0.05 },
|
||||
{ "levels": [ 3, 4 ], "share": 0.10 },
|
||||
{ "levels": [ 5, 6 ], "share": 0.15 },
|
||||
{ "levels": [ 7, 8 ], "share": 0.20 },
|
||||
{ "levels": [ 9, 10 ], "share": 0.25 }
|
||||
],
|
||||
"qoraHoldersShare": 0.20,
|
||||
"qoraPerQortReward": 250,
|
||||
"blocksNeededByLevel": [ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ],
|
||||
"blockTimingsByHeight": [
|
||||
{ "height": 1, "target": 60000, "deviation": 30000, "power": 0.2 }
|
||||
],
|
||||
"ciyamAtSettings": {
|
||||
"feePerStep": "0.0001",
|
||||
"maxStepsPerRound": 500,
|
||||
"stepsPerFunctionCall": 10,
|
||||
"minutesPerBlock": 1
|
||||
},
|
||||
"featureTriggers": {
|
||||
"messageHeight": 0,
|
||||
"atHeight": 0,
|
||||
"assetsTimestamp": 0,
|
||||
"votingTimestamp": 0,
|
||||
"arbitraryTimestamp": 0,
|
||||
"powfixTimestamp": 0,
|
||||
"qortalTimestamp": 0,
|
||||
"newAssetPricingTimestamp": 0,
|
||||
"groupApprovalTimestamp": 0,
|
||||
"atFindNextTransactionFix": 0,
|
||||
"newBlockSigHeight": 999999,
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 0
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
"timestamp": 0,
|
||||
"transactions": [
|
||||
{ "type": "ISSUE_ASSET", "assetName": "QORT", "description": "QORT native coin", "data": "", "quantity": 0, "isDivisible": true, "fee": 0 },
|
||||
{ "type": "ISSUE_ASSET", "assetName": "Legacy-QORA", "description": "Representative legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
|
||||
{ "type": "ISSUE_ASSET", "assetName": "QORT-from-QORA", "description": "QORT gained from holding legacy QORA", "quantity": 0, "isDivisible": true, "data": "{}", "isUnspendable": true },
|
||||
|
||||
{ "type": "GENESIS", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "amount": "1000000000" },
|
||||
{ "type": "GENESIS", "recipient": "QixPbJUwsaHsVEofJdozU9zgVqkK6aYhrK", "amount": "1000000" },
|
||||
{ "type": "GENESIS", "recipient": "QaUpHNhT3Ygx6avRiKobuLdusppR5biXjL", "amount": "1000000" },
|
||||
{ "type": "GENESIS", "recipient": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "amount": "1000000" },
|
||||
|
||||
{ "type": "CREATE_GROUP", "creatorPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "groupName": "dev-group", "description": "developer group", "isOpen": false, "approvalThreshold": "PCT100", "minimumBlockDelay": 0, "maximumBlockDelay": 1440 },
|
||||
|
||||
{ "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "TEST", "description": "test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
|
||||
{ "type": "ISSUE_ASSET", "issuerPublicKey": "C6wuddsBV3HzRrXUtezE7P5MoRXp5m3mEDokRDGZB6ry", "assetName": "OTHER", "description": "other test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
|
||||
{ "type": "ISSUE_ASSET", "issuerPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "assetName": "GOLD", "description": "gold test asset", "data": "", "quantity": "1000000", "isDivisible": true, "fee": 0 },
|
||||
|
||||
{ "type": "ACCOUNT_FLAGS", "target": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "andMask": -1, "orMask": 1, "xorMask": 0 },
|
||||
{ "type": "REWARD_SHARE", "minterPublicKey": "2tiMr5LTpaWCgbRvkPK8TFd7k63DyHJMMFFsz9uBf1ZP", "recipient": "QgV4s3xnzLhVBEJxcYui4u4q11yhUHsd9v", "rewardSharePublicKey": "7PpfnvLSG7y4HPh8hE7KoqAjLCkv7Ui6xw4mKAkbZtox", "sharePercent": "100" },
|
||||
|
||||
{ "type": "ACCOUNT_LEVEL", "target": "Qci5m9k4rcwe4ruKrZZQKka4FzUUMut3er", "level": 5 }
|
||||
]
|
||||
}
|
||||
}
|
@ -53,7 +53,8 @@
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -53,7 +53,8 @@
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -53,7 +53,8 @@
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -53,7 +53,8 @@
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -53,7 +53,8 @@
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -53,7 +53,8 @@
|
||||
"shareBinFix": 6,
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -53,7 +53,8 @@
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
@ -53,7 +53,8 @@
|
||||
"shareBinFix": 999999,
|
||||
"calcChainWeightTimestamp": 0,
|
||||
"transactionV5Timestamp": 0,
|
||||
"transactionV6Timestamp": 0
|
||||
"transactionV6Timestamp": 0,
|
||||
"disableReferenceTimestamp": 9999999999999
|
||||
},
|
||||
"genesisInfo": {
|
||||
"version": 4,
|
||||
|
11
src/test/resources/test-settings-v2-disable-reference.json
Normal file
11
src/test/resources/test-settings-v2-disable-reference.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"repositoryPath": "testdb",
|
||||
"restrictedApi": false,
|
||||
"blockchainConfig": "src/test/resources/test-chain-v2-disable-reference.json",
|
||||
"exportPath": "qortal-backup-test",
|
||||
"bootstrap": false,
|
||||
"wipeUnconfirmedOnStart": false,
|
||||
"testNtpOffset": 0,
|
||||
"minPeers": 0,
|
||||
"pruneBlockLimit": 100
|
||||
}
|
Loading…
Reference in New Issue
Block a user