Skip to content

Commit

Permalink
Merge branch 'disable-reference'
Browse files Browse the repository at this point in the history
  • Loading branch information
archived-2 committed Jun 4, 2022
2 parents e85026f + e393150 commit 43a7542
Show file tree
Hide file tree
Showing 17 changed files with 321 additions and 10 deletions.
7 changes: 6 additions & 1 deletion src/main/java/org/qortal/block/BlockChain.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ public enum FeatureTrigger {
shareBinFix,
calcChainWeightTimestamp,
transactionV5Timestamp,
transactionV6Timestamp;
transactionV6Timestamp,
disableReferenceTimestamp
}

// Custom transaction fees
Expand Down Expand Up @@ -410,6 +411,10 @@ public long getTransactionV6Timestamp() {
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) {
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/qortal/transaction/ArbitraryTransaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
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;
Expand All @@ -19,6 +20,7 @@
import org.qortal.repository.Repository;
import org.qortal.arbitrary.ArbitraryDataFile;
import org.qortal.transform.TransformationException;
import org.qortal.transform.Transformer;
import org.qortal.transform.transaction.ArbitraryTransactionTransformer;
import org.qortal.transform.transaction.TransactionTransformer;
import org.qortal.utils.ArbitraryTransactionUtils;
Expand Down Expand Up @@ -86,6 +88,14 @@ public ValidationResult isFeeValid() throws DataException {
@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()) {
// Allow any value as long as it is the correct length
return this.arbitraryTransactionData.getReference() != null &&
this.arbitraryTransactionData.getReference().length == Transformer.SIGNATURE_LENGTH;
}

if (this.arbitraryTransactionData.getReference() == null) {
return false;
}
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/org/qortal/transaction/AtTransaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@

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;
import org.qortal.data.transaction.TransactionData;
import org.qortal.repository.DataException;
import org.qortal.repository.Repository;
import org.qortal.transform.TransformationException;
import org.qortal.transform.Transformer;
import org.qortal.transform.transaction.AtTransactionTransformer;
import org.qortal.utils.Amounts;

Expand Down Expand Up @@ -75,6 +77,13 @@ public Account getRecipient() {

@Override
public boolean hasValidReference() throws DataException {
// Disable reference checking after feature trigger timestamp
if (this.atTransactionData.getTimestamp() >= BlockChain.getInstance().getDisableReferenceTimestamp()) {
// Allow any value as long as it is the correct length
return this.atTransactionData.getReference() != null &&
this.atTransactionData.getReference().length == Transformer.SIGNATURE_LENGTH;
}

// Check reference is correct, using AT account, not transaction creator which is null account
Account atAccount = getATAccount();
return Arrays.equals(atAccount.getLastReference(), atTransactionData.getReference());
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/org/qortal/transaction/MessageTransaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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;
Expand All @@ -20,6 +21,7 @@
import org.qortal.repository.GroupRepository;
import org.qortal.repository.Repository;
import org.qortal.transform.TransformationException;
import org.qortal.transform.Transformer;
import org.qortal.transform.transaction.ChatTransactionTransformer;
import org.qortal.transform.transaction.MessageTransactionTransformer;
import org.qortal.transform.transaction.TransactionTransformer;
Expand Down Expand Up @@ -163,6 +165,14 @@ public ValidationResult isFeeValid() throws DataException {
@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()) {
// Allow any value as long as it is the correct length
return this.messageTransactionData.getReference() != null &&
this.messageTransactionData.getReference().length == Transformer.SIGNATURE_LENGTH;
}

if (this.messageTransactionData.getReference() == null)
return false;

Expand Down
8 changes: 8 additions & 0 deletions src/main/java/org/qortal/transaction/Transaction.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.qortal.repository.Repository;
import org.qortal.settings.Settings;
import org.qortal.transform.TransformationException;
import org.qortal.transform.Transformer;
import org.qortal.transform.transaction.TransactionTransformer;
import org.qortal.utils.NTP;

Expand Down Expand Up @@ -905,6 +906,13 @@ protected void onImportAsUnconfirmed() throws DataException {
* @throws DataException
*/
public boolean hasValidReference() throws DataException {
// Disable reference checking after feature trigger timestamp
if (this.transactionData.getTimestamp() >= BlockChain.getInstance().getDisableReferenceTimestamp()) {
// Allow any value as long as it is the correct length
return this.transactionData.getReference() != null &&
this.transactionData.getReference().length == Transformer.SIGNATURE_LENGTH;
}

Account creator = getCreator();

return Arrays.equals(transactionData.getReference(), creator.getLastReference());
Expand Down
3 changes: 2 additions & 1 deletion src/main/resources/blockchain.json
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@
"shareBinFix": 399000,
"calcChainWeightTimestamp": 1620579600000,
"transactionV5Timestamp": 1642176000000,
"transactionV6Timestamp": 9999999999999
"transactionV6Timestamp": 9999999999999,
"disableReferenceTimestamp": 9999999999999
},
"genesisInfo": {
"version": 4,
Expand Down
165 changes: 165 additions & 0 deletions src/test/java/org/qortal/test/TransactionReferenceTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
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);
}
}

@Test
public void testNullReferenceAfterFeatureTrigger() 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 null reference
paymentTransactionData.setReference(null);

Transaction paymentTransaction = Transaction.fromData(repository, paymentTransactionData);

// Transaction should be invalid, as we require a non-null reference
Transaction.ValidationResult validationResult = paymentTransaction.isValidUnconfirmed();
assertEquals(Transaction.ValidationResult.INVALID_REFERENCE, validationResult);
}
}

@Test
public void testShortReferenceAfterFeatureTrigger() 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 a 1-byte reference
byte[] randomByte = new byte[63];
random.nextBytes(randomByte);
paymentTransactionData.setReference(randomByte);

Transaction paymentTransaction = Transaction.fromData(repository, paymentTransactionData);

// Transaction should be invalid, as reference isn't long enough
Transaction.ValidationResult validationResult = paymentTransaction.isValidUnconfirmed();
assertEquals(Transaction.ValidationResult.INVALID_REFERENCE, validationResult);
}
}

@Test
public void testLongReferenceAfterFeatureTrigger() 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 a 1-byte reference
byte[] randomByte = new byte[65];
random.nextBytes(randomByte);
paymentTransactionData.setReference(randomByte);

Transaction paymentTransaction = Transaction.fromData(repository, paymentTransactionData);

// Transaction should be invalid, as reference is too long
Transaction.ValidationResult validationResult = paymentTransaction.isValidUnconfirmed();
assertEquals(Transaction.ValidationResult.INVALID_REFERENCE, validationResult);
}
}

}
84 changes: 84 additions & 0 deletions src/test/resources/test-chain-v2-disable-reference.json
Original file line number Diff line number Diff line change
@@ -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 }
]
}
}
Loading

0 comments on commit 43a7542

Please sign in to comment.