From 8dbd8c4e65c630edb617269e322818dee65fbdc2 Mon Sep 17 00:00:00 2001 From: catbref Date: Thu, 16 Apr 2020 12:25:01 +0100 Subject: [PATCH] Performance improvement when checking block's online accounts signatures. If the timestamp-pubkey-sig is still 'current' then it'll be in Controller's list of current online accounts, so we can quickly scan that list before falling back to the more expensive Ed25519 verify. Added equals() and hashCode() to OnlineAccountData to support above. --- src/main/java/org/qortal/block/Block.java | 38 +++++++++++-------- .../data/network/OnlineAccountData.java | 34 +++++++++++++++++ 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/src/main/java/org/qortal/block/Block.java b/src/main/java/org/qortal/block/Block.java index 483090bc..680ef3b5 100644 --- a/src/main/java/org/qortal/block/Block.java +++ b/src/main/java/org/qortal/block/Block.java @@ -916,26 +916,34 @@ public class Block { expandedAccounts.add(rewardShareData); } - // Possibly check signatures if block is recent + // If block is past a certain age then we simply assume the signatures were correct long signatureRequirementThreshold = NTP.getTime() - BlockChain.getInstance().getOnlineAccountSignaturesMinLifetime(); - if (this.blockData.getTimestamp() >= signatureRequirementThreshold) { - if (this.blockData.getOnlineAccountsSignatures() == null || this.blockData.getOnlineAccountsSignatures().length == 0) - return ValidationResult.ONLINE_ACCOUNT_SIGNATURES_MISSING; + if (this.blockData.getTimestamp() < signatureRequirementThreshold) + return ValidationResult.OK; - if (this.blockData.getOnlineAccountsSignatures().length != expandedAccounts.size() * Transformer.SIGNATURE_LENGTH) - return ValidationResult.ONLINE_ACCOUNT_SIGNATURES_MALFORMED; + if (this.blockData.getOnlineAccountsSignatures() == null || this.blockData.getOnlineAccountsSignatures().length == 0) + return ValidationResult.ONLINE_ACCOUNT_SIGNATURES_MISSING; - // Check signatures - List onlineAccountsSignatures = BlockTransformer.decodeTimestampSignatures(this.blockData.getOnlineAccountsSignatures()); - byte[] message = Longs.toByteArray(this.blockData.getOnlineAccountsTimestamp()); + if (this.blockData.getOnlineAccountsSignatures().length != expandedAccounts.size() * Transformer.SIGNATURE_LENGTH) + return ValidationResult.ONLINE_ACCOUNT_SIGNATURES_MALFORMED; - for (int i = 0; i < onlineAccountsSignatures.size(); ++i) { - PublicKeyAccount account = new PublicKeyAccount(null, expandedAccounts.get(i).getRewardSharePublicKey()); - byte[] signature = onlineAccountsSignatures.get(i); + // 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 (!account.verify(signature, message)) - return ValidationResult.ONLINE_ACCOUNT_SIGNATURE_INCORRECT; - } + 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 + continue; + + if (!PublicKeyAccount.verify(publicKey, signature, onlineTimestampBytes)) + return ValidationResult.ONLINE_ACCOUNT_SIGNATURE_INCORRECT; } return ValidationResult.OK; diff --git a/src/main/java/org/qortal/data/network/OnlineAccountData.java b/src/main/java/org/qortal/data/network/OnlineAccountData.java index 04ae92f9..15792307 100644 --- a/src/main/java/org/qortal/data/network/OnlineAccountData.java +++ b/src/main/java/org/qortal/data/network/OnlineAccountData.java @@ -1,5 +1,7 @@ package org.qortal.data.network; +import java.util.Arrays; + import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; @@ -44,4 +46,36 @@ public class OnlineAccountData { return new PublicKeyAccount(null, this.publicKey).getAddress(); } + // Comparison + + @Override + public boolean equals(Object other) { + if (other == this) + return true; + + if (!(other instanceof OnlineAccountData)) + return false; + + OnlineAccountData otherOnlineAccountData = (OnlineAccountData) other; + + // Very quick comparison + if (otherOnlineAccountData.timestamp != this.timestamp) + return false; + + // Signature more likely to be unique than public key + if (!Arrays.equals(otherOnlineAccountData.signature, this.signature)) + return false; + + if (!Arrays.equals(otherOnlineAccountData.publicKey, this.publicKey)) + return false; + + return true; + } + + @Override + public int hashCode() { + // Pretty lazy implementation + return (int) this.timestamp; + } + }