From 43fb5d9332103575430342d939c25d4005094c5e Mon Sep 17 00:00:00 2001 From: catbref Date: Fri, 7 Aug 2020 13:33:31 +0100 Subject: [PATCH] Cache top 2 blocks' worth of online account data to avoid unnecessary Ed25519 verifications --- src/main/java/org/qortal/block/Block.java | 39 +++++++++++++++++-- .../org/qortal/controller/Controller.java | 34 +++++++++++++++- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index f17a0777..6552fc25 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -215,6 +215,9 @@ public class Block { /** Always use getExpandedAccounts() to access this, as it's lazy-instantiated. */ private List cachedExpandedAccounts = null; + /** Opportunistic cache of this block's valid online accounts. Only created by call to isValid(). */ + private List cachedValidOnlineAccounts = null; + // Other useful constants private static final BigInteger MAX_DISTANCE; @@ -940,24 +943,46 @@ public class Block { return ValidationResult.ONLINE_ACCOUNT_SIGNATURES_MALFORMED; // Check signatures - List onlineAccountsSignatures = BlockTransformer.decodeTimestampSignatures(this.blockData.getOnlineAccountsSignatures()); long onlineTimestamp = this.blockData.getOnlineAccountsTimestamp(); byte[] onlineTimestampBytes = Longs.toByteArray(onlineTimestamp); - List onlineAccounts = Controller.getInstance().getOnlineAccounts(); + + // If this block is much older than current online timestamp, then there's no point checking current online accounts + List currentOnlineAccounts = onlineTimestamp < NTP.getTime() - Controller.ONLINE_TIMESTAMP_MODULUS + ? null + : Controller.getInstance().getOnlineAccounts(); + List latestBlocksOnlineAccounts = Controller.getInstance().getLatestBlocksOnlineAccounts(); + + // Extract online accounts' timestamp signatures from block data + List onlineAccountsSignatures = BlockTransformer.decodeTimestampSignatures(this.blockData.getOnlineAccountsSignatures()); + + // We'll build up a list of online accounts to hand over to Controller if block is added to chain + // and this will become latestBlocksOnlineAccounts (above) to reduce CPU load when we process next block... + List ourOnlineAccounts = new ArrayList<>(); for (int i = 0; i < onlineAccountsSignatures.size(); ++i) { byte[] signature = onlineAccountsSignatures.get(i); byte[] publicKey = expandedAccounts.get(i).getRewardSharePublicKey(); - // If signature is still current then no need to perform Ed25519 verify OnlineAccountData onlineAccountData = new OnlineAccountData(onlineTimestamp, signature, publicKey); - if (onlineAccounts.remove(onlineAccountData)) // remove() is like contains() but also reduces the number to check next time + ourOnlineAccounts.add(onlineAccountData); + + // If signature is still current then no need to perform Ed25519 verify + if (currentOnlineAccounts != null && currentOnlineAccounts.remove(onlineAccountData)) + // remove() returned true, so online account still current + // and one less entry in currentOnlineAccounts to check next time + continue; + + // If signature was okay in latest block then no need to perform Ed25519 verify + if (latestBlocksOnlineAccounts != null && latestBlocksOnlineAccounts.contains(onlineAccountData)) continue; if (!Crypto.verify(publicKey, signature, onlineTimestampBytes)) return ValidationResult.ONLINE_ACCOUNT_SIGNATURE_INCORRECT; } + // All online accounts valid, so save our list of online accounts for potential later use + this.cachedValidOnlineAccounts = ourOnlineAccounts; + return ValidationResult.OK; } @@ -1271,6 +1296,9 @@ public class Block { linkTransactionsToBlock(); postBlockTidy(); + + // Give Controller our cached, valid online accounts data (if any) to help reduce CPU load for next block + Controller.getInstance().pushLatestBlocksOnlineAccounts(this.cachedValidOnlineAccounts); } protected void increaseAccountLevels() throws DataException { @@ -1474,6 +1502,9 @@ public class Block { this.blockData.setHeight(null); postBlockTidy(); + + // Remove any cached, valid online accounts data from Controller + Controller.getInstance().popLatestBlocksOnlineAccounts(); } protected void orphanTransactionsFromBlock() throws DataException { diff --git a/src/main/java/org/qortal/controller/Controller.java b/src/main/java/org/qortal/controller/Controller.java index de569df0..ac4e34e7 100644 --- a/src/main/java/org/qortal/controller/Controller.java +++ b/src/main/java/org/qortal/controller/Controller.java @@ -9,9 +9,11 @@ import java.security.Security; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Deque; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -112,8 +114,10 @@ public class Controller 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 - private static final long ONLINE_TIMESTAMP_MODULUS = 5 * 60 * 1000L; + 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); + /** How many (latest) blocks' worth of online accounts we cache */ + private static final int MAX_BLOCKS_CACHED_ONLINE_ACCOUNTS = 2; private static volatile boolean isStopping = false; private static BlockMinter blockMinter = null; @@ -168,8 +172,10 @@ public class Controller extends Thread { /** Lock for only allowing one blockchain-modifying codepath at a time. e.g. synchronization or newly minted block. */ private final ReentrantLock blockchainLock = new ReentrantLock(); - /** Cache of 'online accounts' */ + /** Cache of current 'online accounts' */ List onlineAccounts = new ArrayList<>(); + /** Cache of latest blocks' online accounts */ + Deque> latestBlocksOnlineAccounts = new ArrayDeque<>(MAX_BLOCKS_CACHED_ONLINE_ACCOUNTS); // Constructors @@ -1465,6 +1471,30 @@ public class Controller extends Thread { } } + /** Returns cached, unmodifiable list of latest block's online accounts. */ + public List getLatestBlocksOnlineAccounts() { + synchronized (this.latestBlocksOnlineAccounts) { + return this.latestBlocksOnlineAccounts.peekFirst(); + } + } + + /** Caches list of latest block's online accounts. Typically called by Block.process() */ + public void pushLatestBlocksOnlineAccounts(List latestBlocksOnlineAccounts) { + synchronized (this.latestBlocksOnlineAccounts) { + if (this.latestBlocksOnlineAccounts.size() == MAX_BLOCKS_CACHED_ONLINE_ACCOUNTS) + this.latestBlocksOnlineAccounts.pollLast(); + + this.latestBlocksOnlineAccounts.addFirst(Collections.unmodifiableList(latestBlocksOnlineAccounts)); + } + } + + /** Reverts list of latest block's online accounts. Typically called by Block.orphan() */ + public void popLatestBlocksOnlineAccounts() { + synchronized (this.latestBlocksOnlineAccounts) { + this.latestBlocksOnlineAccounts.pollFirst(); + } + } + public byte[] fetchArbitraryData(byte[] signature) throws InterruptedException { // Build request Message getArbitraryDataMessage = new GetArbitraryDataMessage(signature);