From f7dabcaeb00f377396a14f7a790db85999213597 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 1 Apr 2022 11:35:32 +0100 Subject: [PATCH 1/6] Increase ONLINE_ACCOUNTS_MODULUS from 5 to 30 mins at a future undecided timestamp. Note: it's important that this timestamp is set on a 1-hour boundary (such as 16:00:00) to ensure a clean switchover. # Conflicts: # src/main/java/org/qortal/block/BlockChain.java --- src/main/java/org/qortal/block/Block.java | 2 +- .../java/org/qortal/block/BlockChain.java | 9 ++ .../controller/OnlineAccountsManager.java | 18 +++- .../transaction/PresenceTransaction.java | 3 +- src/main/resources/blockchain.json | 1 + .../test/network/OnlineAccountsTests.java | 97 ++++++++++++++++++- .../test-chain-v2-founder-rewards.json | 1 + .../test-chain-v2-leftover-reward.json | 1 + src/test/resources/test-chain-v2-minting.json | 1 + .../test-chain-v2-qora-holder-extremes.json | 1 + .../resources/test-chain-v2-qora-holder.json | 1 + .../test-chain-v2-reward-levels.json | 1 + .../test-chain-v2-reward-scaling.json | 1 + src/test/resources/test-chain-v2.json | 1 + 14 files changed, 127 insertions(+), 11 deletions(-) diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index ea5a6b49..7800f2a1 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -987,7 +987,7 @@ public class Block { byte[] onlineTimestampBytes = Longs.toByteArray(onlineTimestamp); // If this block is much older than current online timestamp, then there's no point checking current online accounts - List currentOnlineAccounts = onlineTimestamp < NTP.getTime() - OnlineAccountsManager.ONLINE_TIMESTAMP_MODULUS + List currentOnlineAccounts = onlineTimestamp < NTP.getTime() - OnlineAccountsManager.getOnlineTimestampModulus() ? null : OnlineAccountsManager.getInstance().getOnlineAccounts(); List latestBlocksOnlineAccounts = OnlineAccountsManager.getInstance().getLatestBlocksOnlineAccounts(); diff --git a/src/main/java/org/qortal/block/BlockChain.java b/src/main/java/org/qortal/block/BlockChain.java index bc06fadf..135708ab 100644 --- a/src/main/java/org/qortal/block/BlockChain.java +++ b/src/main/java/org/qortal/block/BlockChain.java @@ -162,6 +162,10 @@ public class BlockChain { /** Maximum time to retain online account signatures (ms) for block validity checks, to allow for clock variance. */ private long onlineAccountSignaturesMaxLifetime; + /** Feature trigger timestamp for ONLINE_ACCOUNTS_MODULUS time interval increase. Can't use + * featureTriggers because unit tests need to set this value via Reflection. */ + private long onlineAccountsModulusV2Timestamp; + /** Settings relating to CIYAM AT feature. */ public static class CiyamAtSettings { /** Fee per step/op-code executed. */ @@ -310,6 +314,11 @@ public class BlockChain { return this.maxBlockSize; } + // Online accounts + public long getOnlineAccountsModulusV2Timestamp() { + return this.onlineAccountsModulusV2Timestamp; + } + /** Returns true if approval-needing transaction types require a txGroupId other than NO_GROUP. */ public boolean getRequireGroupForApproval() { return this.requireGroupForApproval; diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 70b04e56..a5f86b48 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -50,8 +50,8 @@ public class OnlineAccountsManager extends Thread { // To do with online accounts list private static final long ONLINE_ACCOUNTS_TASKS_INTERVAL = 10 * 1000L; // ms private static final long ONLINE_ACCOUNTS_BROADCAST_INTERVAL = 1 * 60 * 1000L; // ms - public static final long ONLINE_TIMESTAMP_MODULUS = 5 * 60 * 1000L; - private static final long LAST_SEEN_EXPIRY_PERIOD = (ONLINE_TIMESTAMP_MODULUS * 2) + (1 * 60 * 1000L); + public static final long ONLINE_TIMESTAMP_MODULUS_V1 = 5 * 60 * 1000L; + public static final long ONLINE_TIMESTAMP_MODULUS_V2 = 30 * 60 * 1000L; /** How many (latest) blocks' worth of online accounts we cache */ private static final int MAX_BLOCKS_CACHED_ONLINE_ACCOUNTS = 2; private static final long ONLINE_ACCOUNTS_V2_PEER_VERSION = 0x0300020000L; @@ -116,6 +116,13 @@ public class OnlineAccountsManager extends Thread { this.interrupt(); } + public static long getOnlineTimestampModulus() { + if (NTP.getTime() >= BlockChain.getInstance().getOnlineAccountsModulusV2Timestamp()) { + return ONLINE_TIMESTAMP_MODULUS_V2; + } + return ONLINE_TIMESTAMP_MODULUS_V1; + } + // Online accounts import queue @@ -159,7 +166,7 @@ public class OnlineAccountsManager extends Thread { PublicKeyAccount otherAccount = new PublicKeyAccount(repository, onlineAccountData.getPublicKey()); // Check timestamp is 'recent' here - if (Math.abs(onlineAccountData.getTimestamp() - now) > ONLINE_TIMESTAMP_MODULUS * 2) { + if (Math.abs(onlineAccountData.getTimestamp() - now) > getOnlineTimestampModulus() * 2) { LOGGER.trace(() -> String.format("Rejecting online account %s with out of range timestamp %d", otherAccount.getAddress(), onlineAccountData.getTimestamp())); return; } @@ -241,7 +248,8 @@ public class OnlineAccountsManager extends Thread { return; // Expire old entries - final long cutoffThreshold = now - LAST_SEEN_EXPIRY_PERIOD; + final long lastSeenExpiryPeriod = (getOnlineTimestampModulus() * 2) + (1 * 60 * 1000L); + final long cutoffThreshold = now - lastSeenExpiryPeriod; synchronized (this.onlineAccounts) { Iterator iterator = this.onlineAccounts.iterator(); while (iterator.hasNext()) { @@ -372,7 +380,7 @@ public class OnlineAccountsManager extends Thread { } public static long toOnlineAccountTimestamp(long timestamp) { - return (timestamp / ONLINE_TIMESTAMP_MODULUS) * ONLINE_TIMESTAMP_MODULUS; + return (timestamp / getOnlineTimestampModulus()) * getOnlineTimestampModulus(); } /** Returns list of online accounts with timestamp recent enough to be considered currently online. */ diff --git a/src/main/java/org/qortal/transaction/PresenceTransaction.java b/src/main/java/org/qortal/transaction/PresenceTransaction.java index d0f54548..8076997c 100644 --- a/src/main/java/org/qortal/transaction/PresenceTransaction.java +++ b/src/main/java/org/qortal/transaction/PresenceTransaction.java @@ -12,7 +12,6 @@ import java.util.function.Supplier; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.qortal.account.Account; -import org.qortal.controller.Controller; import org.qortal.controller.OnlineAccountsManager; import org.qortal.controller.tradebot.TradeBot; import org.qortal.crosschain.ACCT; @@ -49,7 +48,7 @@ public class PresenceTransaction extends Transaction { REWARD_SHARE(0) { @Override public long getLifetime() { - return OnlineAccountsManager.ONLINE_TIMESTAMP_MODULUS; + return OnlineAccountsManager.getOnlineTimestampModulus(); } }, TRADE_BOT(1) { diff --git a/src/main/resources/blockchain.json b/src/main/resources/blockchain.json index 1f20ccfe..5a59df91 100644 --- a/src/main/resources/blockchain.json +++ b/src/main/resources/blockchain.json @@ -19,6 +19,7 @@ "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 43200000, "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 5.00 }, { "height": 259201, "reward": 4.75 }, diff --git a/src/test/java/org/qortal/test/network/OnlineAccountsTests.java b/src/test/java/org/qortal/test/network/OnlineAccountsTests.java index 4154121c..c804c7db 100644 --- a/src/test/java/org/qortal/test/network/OnlineAccountsTests.java +++ b/src/test/java/org/qortal/test/network/OnlineAccountsTests.java @@ -1,22 +1,36 @@ package org.qortal.test.network; +import org.apache.commons.lang3.reflect.FieldUtils; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider; +import org.junit.Before; import org.junit.Test; +import org.qortal.account.PrivateKeyAccount; +import org.qortal.block.Block; +import org.qortal.block.BlockChain; +import org.qortal.controller.BlockMinter; +import org.qortal.controller.OnlineAccountsManager; import org.qortal.data.network.OnlineAccountData; import org.qortal.network.message.*; +import org.qortal.repository.DataException; +import org.qortal.repository.Repository; +import org.qortal.repository.RepositoryManager; +import org.qortal.settings.Settings; +import org.qortal.test.common.Common; import org.qortal.transform.Transformer; +import org.qortal.utils.Base58; +import org.qortal.utils.NTP; +import java.io.IOException; import java.nio.ByteBuffer; import java.security.Security; import java.util.ArrayList; import java.util.List; import java.util.Random; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; -public class OnlineAccountsTests { +public class OnlineAccountsTests extends Common { private static final Random RANDOM = new Random(); static { @@ -27,6 +41,12 @@ public class OnlineAccountsTests { Security.insertProviderAt(new BouncyCastleJsseProvider(), 1); } + @Before + public void beforeTest() throws DataException, IOException { + Common.useSettingsAndDb(Common.testSettingsFilename, false); + NTP.setFixedOffset(Settings.getInstance().getTestNtpOffset()); + } + @Test public void testGetOnlineAccountsV2() throws MessageException { @@ -111,4 +131,75 @@ public class OnlineAccountsTests { return onlineAccounts; } + @Test + public void testOnlineAccountsModulusV1() throws IllegalAccessException, DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Set feature trigger timestamp to MAX long so that it is inactive + FieldUtils.writeField(BlockChain.getInstance(), "onlineAccountsModulusV2Timestamp", Long.MAX_VALUE, true); + + List onlineAccountSignatures = new ArrayList<>(); + long fakeNTPOffset = 0L; + + // Mint a block and store its timestamp + Block block = BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share")); + long lastBlockTimestamp = block.getBlockData().getTimestamp(); + + // Mint some blocks and keep track of the different online account signatures + for (int i = 0; i < 30; i++) { + block = BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share")); + + // Increase NTP fixed offset by the block time, to simulate time passing + long blockTimeDelta = block.getBlockData().getTimestamp() - lastBlockTimestamp; + lastBlockTimestamp = block.getBlockData().getTimestamp(); + fakeNTPOffset += blockTimeDelta; + NTP.setFixedOffset(fakeNTPOffset); + + String lastOnlineAccountSignatures58 = Base58.encode(block.getBlockData().getOnlineAccountsSignatures()); + if (!onlineAccountSignatures.contains(lastOnlineAccountSignatures58)) { + onlineAccountSignatures.add(lastOnlineAccountSignatures58); + } + } + + // We expect at least 6 unique signatures over 30 blocks (generally 6-8, but could be higher due to block time differences) + System.out.println(String.format("onlineAccountSignatures count: %d", onlineAccountSignatures.size())); + assertTrue(onlineAccountSignatures.size() >= 6); + } + } + + @Test + public void testOnlineAccountsModulusV2() throws IllegalAccessException, DataException { + try (final Repository repository = RepositoryManager.getRepository()) { + + // Set feature trigger timestamp to 0 so that it is active + FieldUtils.writeField(BlockChain.getInstance(), "onlineAccountsModulusV2Timestamp", 0L, true); + + List onlineAccountSignatures = new ArrayList<>(); + long fakeNTPOffset = 0L; + + // Mint a block and store its timestamp + Block block = BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share")); + long lastBlockTimestamp = block.getBlockData().getTimestamp(); + + // Mint some blocks and keep track of the different online account signatures + for (int i = 0; i < 30; i++) { + block = BlockMinter.mintTestingBlock(repository, Common.getTestAccount(repository, "alice-reward-share")); + + // Increase NTP fixed offset by the block time, to simulate time passing + long blockTimeDelta = block.getBlockData().getTimestamp() - lastBlockTimestamp; + lastBlockTimestamp = block.getBlockData().getTimestamp(); + fakeNTPOffset += blockTimeDelta; + NTP.setFixedOffset(fakeNTPOffset); + + String lastOnlineAccountSignatures58 = Base58.encode(block.getBlockData().getOnlineAccountsSignatures()); + if (!onlineAccountSignatures.contains(lastOnlineAccountSignatures58)) { + onlineAccountSignatures.add(lastOnlineAccountSignatures58); + } + } + + // We expect 1-3 unique signatures over 30 blocks + System.out.println(String.format("onlineAccountSignatures count: %d", onlineAccountSignatures.size())); + assertTrue(onlineAccountSignatures.size() >= 1 && onlineAccountSignatures.size() <= 3); + } + } } diff --git a/src/test/resources/test-chain-v2-founder-rewards.json b/src/test/resources/test-chain-v2-founder-rewards.json index c0ea8fe5..b2e4018f 100644 --- a/src/test/resources/test-chain-v2-founder-rewards.json +++ b/src/test/resources/test-chain-v2-founder-rewards.json @@ -14,6 +14,7 @@ "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2-leftover-reward.json b/src/test/resources/test-chain-v2-leftover-reward.json index 01505af0..4fac00b5 100644 --- a/src/test/resources/test-chain-v2-leftover-reward.json +++ b/src/test/resources/test-chain-v2-leftover-reward.json @@ -14,6 +14,7 @@ "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2-minting.json b/src/test/resources/test-chain-v2-minting.json index fcabe4bf..1b85a948 100644 --- a/src/test/resources/test-chain-v2-minting.json +++ b/src/test/resources/test-chain-v2-minting.json @@ -14,6 +14,7 @@ "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2-qora-holder-extremes.json b/src/test/resources/test-chain-v2-qora-holder-extremes.json index 8ec94631..daee01a0 100644 --- a/src/test/resources/test-chain-v2-qora-holder-extremes.json +++ b/src/test/resources/test-chain-v2-qora-holder-extremes.json @@ -14,6 +14,7 @@ "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2-qora-holder.json b/src/test/resources/test-chain-v2-qora-holder.json index 38a563b2..9146d715 100644 --- a/src/test/resources/test-chain-v2-qora-holder.json +++ b/src/test/resources/test-chain-v2-qora-holder.json @@ -14,6 +14,7 @@ "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2-reward-levels.json b/src/test/resources/test-chain-v2-reward-levels.json index ab934d26..dced66c3 100644 --- a/src/test/resources/test-chain-v2-reward-levels.json +++ b/src/test/resources/test-chain-v2-reward-levels.json @@ -14,6 +14,7 @@ "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2-reward-scaling.json b/src/test/resources/test-chain-v2-reward-scaling.json index b3e358b2..1e0f7c6c 100644 --- a/src/test/resources/test-chain-v2-reward-scaling.json +++ b/src/test/resources/test-chain-v2-reward-scaling.json @@ -14,6 +14,7 @@ "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, diff --git a/src/test/resources/test-chain-v2.json b/src/test/resources/test-chain-v2.json index 20ff391c..bf534ce4 100644 --- a/src/test/resources/test-chain-v2.json +++ b/src/test/resources/test-chain-v2.json @@ -14,6 +14,7 @@ "founderEffectiveMintingLevel": 10, "onlineAccountSignaturesMinLifetime": 3600000, "onlineAccountSignaturesMaxLifetime": 86400000, + "onlineAccountsModulusV2Timestamp": 9999999999999, "rewardsByHeight": [ { "height": 1, "reward": 100 }, { "height": 11, "reward": 10 }, From ef51cf5702a639080200dbfca2f61844fbbf9e86 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Thu, 2 Jun 2022 12:46:11 +0100 Subject: [PATCH 2/6] Added defensiveness in getOnlineTimestampModulus(), just in case NTP.getTime() returns null --- src/main/java/org/qortal/controller/OnlineAccountsManager.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index a5f86b48..092cae05 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -117,7 +117,8 @@ public class OnlineAccountsManager extends Thread { } public static long getOnlineTimestampModulus() { - if (NTP.getTime() >= BlockChain.getInstance().getOnlineAccountsModulusV2Timestamp()) { + Long now = NTP.getTime(); + if (now != null && now >= BlockChain.getInstance().getOnlineAccountsModulusV2Timestamp()) { return ONLINE_TIMESTAMP_MODULUS_V2; } return ONLINE_TIMESTAMP_MODULUS_V1; From 80188629dfa9f466195bd642989b799e017fe25a Mon Sep 17 00:00:00 2001 From: CalDescent Date: Fri, 1 Jul 2022 13:13:22 +0100 Subject: [PATCH 3/6] Don't aggregate signatures when running OnlineAccountsTests, as it's too difficult to check how many unique signatures exist over a given period of time. --- .../test/network/OnlineAccountsTests.java | 2 +- .../resources/test-chain-v2-no-sig-agg.json | 86 +++++++++++++++++++ .../test-settings-v2-no-sig-agg.json | 19 ++++ 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 src/test/resources/test-chain-v2-no-sig-agg.json create mode 100644 src/test/resources/test-settings-v2-no-sig-agg.json diff --git a/src/test/java/org/qortal/test/network/OnlineAccountsTests.java b/src/test/java/org/qortal/test/network/OnlineAccountsTests.java index c804c7db..0b554b6a 100644 --- a/src/test/java/org/qortal/test/network/OnlineAccountsTests.java +++ b/src/test/java/org/qortal/test/network/OnlineAccountsTests.java @@ -43,7 +43,7 @@ public class OnlineAccountsTests extends Common { @Before public void beforeTest() throws DataException, IOException { - Common.useSettingsAndDb(Common.testSettingsFilename, false); + Common.useSettingsAndDb("test-settings-v2-no-sig-agg.json", false); NTP.setFixedOffset(Settings.getInstance().getTestNtpOffset()); } diff --git a/src/test/resources/test-chain-v2-no-sig-agg.json b/src/test/resources/test-chain-v2-no-sig-agg.json new file mode 100644 index 00000000..75c5528c --- /dev/null +++ b/src/test/resources/test-chain-v2-no-sig-agg.json @@ -0,0 +1,86 @@ +{ + "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, + "onlineAccountsModulusV2Timestamp": 9999999999999, + "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": 9999999999999, + "aggregateSignatureTimestamp": 9999999999999 + }, + "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 } + ] + } +} diff --git a/src/test/resources/test-settings-v2-no-sig-agg.json b/src/test/resources/test-settings-v2-no-sig-agg.json new file mode 100644 index 00000000..1a55fa65 --- /dev/null +++ b/src/test/resources/test-settings-v2-no-sig-agg.json @@ -0,0 +1,19 @@ +{ + "repositoryPath": "testdb", + "bitcoinNet": "TEST3", + "litecoinNet": "TEST3", + "restrictedApi": false, + "blockchainConfig": "src/test/resources/test-chain-v2-no-sig-agg.json", + "exportPath": "qortal-backup-test", + "bootstrap": false, + "wipeUnconfirmedOnStart": false, + "testNtpOffset": 0, + "minPeers": 0, + "pruneBlockLimit": 100, + "bootstrapFilenamePrefix": "test-", + "dataPath": "data-test", + "tempDataPath": "data-test/_temp", + "listsPath": "lists-test", + "storagePolicy": "FOLLOWED_OR_VIEWED", + "maxStorageCapacity": 104857600 +} From fe9744eec6f43d1edcec6277277e817629420caa Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 30 Jul 2022 14:52:47 +0100 Subject: [PATCH 4/6] Fixed missing feature trigger in testchain config --- src/test/resources/test-chain-v2-no-sig-agg.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/resources/test-chain-v2-no-sig-agg.json b/src/test/resources/test-chain-v2-no-sig-agg.json index 75c5528c..71e1cc3d 100644 --- a/src/test/resources/test-chain-v2-no-sig-agg.json +++ b/src/test/resources/test-chain-v2-no-sig-agg.json @@ -52,6 +52,7 @@ "atFindNextTransactionFix": 0, "newBlockSigHeight": 999999, "shareBinFix": 999999, + "rewardShareLimitTimestamp": 9999999999999, "calcChainWeightTimestamp": 0, "transactionV5Timestamp": 0, "transactionV6Timestamp": 0, From 55f973af3c405dee893e2d31c7db37dc65dc8c63 Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 30 Jul 2022 18:15:16 +0100 Subject: [PATCH 5/6] Ensure all online accounts timestamps are a multiple of the online timestamp modulus. This is a simple way to discard the 5-minute online account timestamps (from out of date nodes) once the switch to 30-minute online account timestamps has taken place. --- .../java/org/qortal/controller/OnlineAccountsManager.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 05fb7f29..6fbeec25 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -217,6 +217,11 @@ public class OnlineAccountsManager { return false; } + // Check timestamp is a multiple of online timestamp modulus + if (onlineAccountTimestamp % getOnlineTimestampModulus() != 0) { + return false; + } + // Verify signature byte[] data = Longs.toByteArray(onlineAccountData.getTimestamp()); boolean isSignatureValid = onlineAccountTimestamp >= BlockChain.getInstance().getAggregateSignatureTimestamp() From c9966337323a003d7d516b76f4293d89ddb6bd0a Mon Sep 17 00:00:00 2001 From: CalDescent Date: Sat, 30 Jul 2022 19:06:04 +0100 Subject: [PATCH 6/6] Added trace level logging. --- src/main/java/org/qortal/controller/OnlineAccountsManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/qortal/controller/OnlineAccountsManager.java b/src/main/java/org/qortal/controller/OnlineAccountsManager.java index 6fbeec25..839620a0 100644 --- a/src/main/java/org/qortal/controller/OnlineAccountsManager.java +++ b/src/main/java/org/qortal/controller/OnlineAccountsManager.java @@ -219,6 +219,7 @@ public class OnlineAccountsManager { // Check timestamp is a multiple of online timestamp modulus if (onlineAccountTimestamp % getOnlineTimestampModulus() != 0) { + LOGGER.trace(() -> String.format("Rejecting online account %s with invalid timestamp %d", Base58.encode(rewardSharePublicKey), onlineAccountTimestamp)); return false; }