From 7fd777cfa9bad98d66031493cb749158eda26234 Mon Sep 17 00:00:00 2001 From: web3-developer <51288821+web3-developer@users.noreply.github.com> Date: Fri, 14 Jun 2024 13:38:24 +0800 Subject: [PATCH] Add push raises to Fluffy state network code. (#2352) * Add push raises to Fluffy state network code. * Refactor handling of rlp errors. --- fluffy/network/state/state_content.nim | 2 + fluffy/network/state/state_endpoints.nim | 67 +++++++++++--------- fluffy/network/state/state_gossip.nim | 35 ++++++----- fluffy/network/state/state_network.nim | 2 + fluffy/network/state/state_utils.nim | 56 +++++++++-------- fluffy/network/state/state_validation.nim | 75 +++++++++++++---------- 6 files changed, 134 insertions(+), 103 deletions(-) diff --git a/fluffy/network/state/state_content.nim b/fluffy/network/state/state_content.nim index b27446a68..fc744315d 100644 --- a/fluffy/network/state/state_content.nim +++ b/fluffy/network/state/state_content.nim @@ -5,6 +5,8 @@ # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # at your option. This file may not be copied, modified, or distributed except according to those terms. +{.push raises: [].} + import ./content/content_keys, ./content/content_values, ./content/nibbles export content_keys, content_values, nibbles diff --git a/fluffy/network/state/state_endpoints.nim b/fluffy/network/state/state_endpoints.nim index 10eeff6cc..b8ef1b50a 100644 --- a/fluffy/network/state/state_endpoints.nim +++ b/fluffy/network/state/state_endpoints.nim @@ -5,6 +5,8 @@ # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # at your option. This file may not be copied, modified, or distributed except according to those terms. +{.push raises: [].} + import results, chronos, @@ -23,46 +25,51 @@ logScope: proc getNextNodeHash( trieNode: TrieNode, nibbles: UnpackedNibbles, nibbleIdx: var int ): Opt[(Nibbles, NodeHash)] = - doAssert(nibbles.len() > 0) - doAssert(nibbleIdx < nibbles.len()) + # the trie node should have already been validated against the lookup hash + # so we expect that no rlp errors should be possible + try: + doAssert(nibbles.len() > 0) + doAssert(nibbleIdx < nibbles.len()) - let trieNodeRlp = rlpFromBytes(trieNode.asSeq()) - # the trie node should have already been validated - doAssert(not trieNodeRlp.isEmpty()) - doAssert(trieNodeRlp.listLen() == 2 or trieNodeRlp.listLen() == 17) + let trieNodeRlp = rlpFromBytes(trieNode.asSeq()) - if trieNodeRlp.listLen() == 17: - let nextNibble = nibbles[nibbleIdx] - doAssert(nextNibble < 16) + doAssert(not trieNodeRlp.isEmpty()) + doAssert(trieNodeRlp.listLen() == 2 or trieNodeRlp.listLen() == 17) - let nextHashBytes = trieNodeRlp.listElem(nextNibble.int) + if trieNodeRlp.listLen() == 17: + let nextNibble = nibbles[nibbleIdx] + doAssert(nextNibble < 16) + + let nextHashBytes = trieNodeRlp.listElem(nextNibble.int) + doAssert(not nextHashBytes.isEmpty()) + + nibbleIdx += 1 + return Opt.some( + ( + nibbles[0 ..< nibbleIdx].packNibbles(), + KeccakHash.fromBytes(nextHashBytes.toBytes()), + ) + ) + + # leaf or extension node + let (_, isLeaf, prefix) = decodePrefix(trieNodeRlp.listElem(0)) + if isLeaf: + return Opt.none((Nibbles, NodeHash)) + + # extension node + nibbleIdx += prefix.unpackNibbles().len() + + let nextHashBytes = trieNodeRlp.listElem(1) doAssert(not nextHashBytes.isEmpty()) - nibbleIdx += 1 - return Opt.some( + Opt.some( ( nibbles[0 ..< nibbleIdx].packNibbles(), KeccakHash.fromBytes(nextHashBytes.toBytes()), ) ) - - # leaf or extension node - let (_, isLeaf, prefix) = decodePrefix(trieNodeRlp.listElem(0)) - if isLeaf: - return Opt.none((Nibbles, NodeHash)) - - # extension node - nibbleIdx += prefix.unpackNibbles().len() - - let nextHashBytes = trieNodeRlp.listElem(1) - doAssert(not nextHashBytes.isEmpty()) - - Opt.some( - ( - nibbles[0 ..< nibbleIdx].packNibbles(), - KeccakHash.fromBytes(nextHashBytes.toBytes()), - ) - ) + except RlpError as e: + raiseAssert(e.msg) proc getAccountProof( n: StateNetwork, stateRoot: KeccakHash, address: Address diff --git a/fluffy/network/state/state_gossip.nim b/fluffy/network/state/state_gossip.nim index 73e08d06d..e6331801b 100644 --- a/fluffy/network/state/state_gossip.nim +++ b/fluffy/network/state/state_gossip.nim @@ -5,6 +5,8 @@ # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # at your option. This file may not be copied, modified, or distributed except according to those terms. +{.push raises: [].} + import results, chronos, @@ -41,26 +43,31 @@ func withKey*( (key: key, offer: offer) func getParent(p: ProofWithPath): ProofWithPath = - doAssert(p.path.len() > 0, "nibbles too short") - doAssert(p.proof.len() > 1, "proof too short") + # this function assumes that the proof contains valid rlp therefore + # if required these proofs should be validated beforehand + try: + doAssert(p.path.len() > 0, "nibbles too short") + doAssert(p.proof.len() > 1, "proof too short") - let - parentProof = TrieProof.init(p.proof[0 ..^ 2]) - parentEndNode = rlpFromBytes(parentProof[^1].asSeq()) + let + parentProof = TrieProof.init(p.proof[0 ..^ 2]) + parentEndNode = rlpFromBytes(parentProof[^1].asSeq()) - # the trie proof should have already been validated when receiving the offer content - doAssert(parentEndNode.listLen() == 2 or parentEndNode.listLen() == 17) + # the trie proof should have already been validated when receiving the offer content + doAssert(parentEndNode.listLen() == 2 or parentEndNode.listLen() == 17) - var unpackedNibbles = p.path.unpackNibbles() + var unpackedNibbles = p.path.unpackNibbles() - if parentEndNode.listLen() == 17: - # branch node so only need to remove a single nibble - return parentProof.withPath(unpackedNibbles.dropN(1).packNibbles()) + if parentEndNode.listLen() == 17: + # branch node so only need to remove a single nibble + return parentProof.withPath(unpackedNibbles.dropN(1).packNibbles()) - # leaf or extension node so we need to remove one or more nibbles - let prefixNibbles = decodePrefix(parentEndNode.listElem(0))[2] + # leaf or extension node so we need to remove one or more nibbles + let (_, _, prefixNibbles) = decodePrefix(parentEndNode.listElem(0)) - parentProof.withPath(unpackedNibbles.dropN(prefixNibbles.len()).packNibbles()) + parentProof.withPath(unpackedNibbles.dropN(prefixNibbles.len()).packNibbles()) + except RlpError as e: + raiseAssert(e.msg) func getParent*(offerWithKey: AccountTrieOfferWithKey): AccountTrieOfferWithKey = let diff --git a/fluffy/network/state/state_network.nim b/fluffy/network/state/state_network.nim index b2e8096fc..a9d99448c 100644 --- a/fluffy/network/state/state_network.nim +++ b/fluffy/network/state/state_network.nim @@ -5,6 +5,8 @@ # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # at your option. This file may not be copied, modified, or distributed except according to those terms. +{.push raises: [].} + import results, chronos, diff --git a/fluffy/network/state/state_utils.nim b/fluffy/network/state/state_utils.nim index d38c61a3c..7c560bc1f 100644 --- a/fluffy/network/state/state_utils.nim +++ b/fluffy/network/state/state_utils.nim @@ -5,11 +5,13 @@ # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # at your option. This file may not be copied, modified, or distributed except according to those terms. +{.push raises: [].} + import results, eth/common, ./state_content export results, common -func decodePrefix*(nodePrefixRlp: Rlp): (byte, bool, Nibbles) = +func decodePrefix*(nodePrefixRlp: Rlp): (byte, bool, Nibbles) {.raises: RlpError.} = doAssert(not nodePrefixRlp.isEmpty()) let @@ -23,36 +25,40 @@ func decodePrefix*(nodePrefixRlp: Rlp): (byte, bool, Nibbles) = (firstNibble.byte, isLeaf, nibbles) func rlpDecodeAccountTrieNode*(accountTrieNode: TrieNode): Result[Account, string] = - let accNodeRlp = rlpFromBytes(accountTrieNode.asSeq()) - if accNodeRlp.isEmpty() or accNodeRlp.listLen() != 2: - return err("invalid account trie node - malformed") + try: + let accNodeRlp = rlpFromBytes(accountTrieNode.asSeq()) + if accNodeRlp.isEmpty() or accNodeRlp.listLen() != 2: + return err("invalid account trie node - malformed") - let accNodePrefixRlp = accNodeRlp.listElem(0) - if accNodePrefixRlp.isEmpty(): - return err("invalid account trie node - empty prefix") + let accNodePrefixRlp = accNodeRlp.listElem(0) + if accNodePrefixRlp.isEmpty(): + return err("invalid account trie node - empty prefix") - let (_, isLeaf, _) = decodePrefix(accNodePrefixRlp) - if not isLeaf: - return err("invalid account trie node - leaf prefix expected") + let (_, isLeaf, _) = decodePrefix(accNodePrefixRlp) + if not isLeaf: + return err("invalid account trie node - leaf prefix expected") - decodeRlp(accNodeRlp.listElem(1).toBytes(), Account) - -# TODO: test the below functions + decodeRlp(accNodeRlp.listElem(1).toBytes(), Account) + except RlpError as e: + err(e.msg) func rlpDecodeContractTrieNode*(contractTrieNode: TrieNode): Result[UInt256, string] = - let storageNodeRlp = rlpFromBytes(contractTrieNode.asSeq()) - if storageNodeRlp.isEmpty() or storageNodeRlp.listLen() != 2: - return err("invalid contract trie node - malformed") + try: + let storageNodeRlp = rlpFromBytes(contractTrieNode.asSeq()) + if storageNodeRlp.isEmpty() or storageNodeRlp.listLen() != 2: + return err("invalid contract trie node - malformed") - let storageNodePrefixRlp = storageNodeRlp.listElem(0) - if storageNodePrefixRlp.isEmpty(): - return err("invalid contract trie node - empty prefix") + let storageNodePrefixRlp = storageNodeRlp.listElem(0) + if storageNodePrefixRlp.isEmpty(): + return err("invalid contract trie node - empty prefix") - let (_, isLeaf, _) = decodePrefix(storageNodePrefixRlp) - if not isLeaf: - return err("invalid contract trie node - leaf prefix expected") + let (_, isLeaf, _) = decodePrefix(storageNodePrefixRlp) + if not isLeaf: + return err("invalid contract trie node - leaf prefix expected") - decodeRlp(storageNodeRlp.listElem(1).toBytes(), UInt256) + decodeRlp(storageNodeRlp.listElem(1).toBytes(), UInt256) + except RlpError as e: + err(e.msg) func toAccount*(accountProof: TrieProof): Result[Account, string] {.inline.} = doAssert(accountProof.len() > 1) @@ -67,8 +73,8 @@ func toSlot*(storageProof: TrieProof): Result[UInt256, string] {.inline.} = func toPath*(hash: KeccakHash): Nibbles {.inline.} = Nibbles.init(hash.data, isEven = true) -func toPath*(address: Address): Nibbles = +func toPath*(address: Address): Nibbles {.inline.} = keccakHash(address).toPath() -func toPath*(slotKey: UInt256): Nibbles = +func toPath*(slotKey: UInt256): Nibbles {.inline.} = keccakHash(toBytesBE(slotKey)).toPath() diff --git a/fluffy/network/state/state_validation.nim b/fluffy/network/state/state_validation.nim index 7767b013f..7ad90bc18 100644 --- a/fluffy/network/state/state_validation.nim +++ b/fluffy/network/state/state_validation.nim @@ -5,6 +5,8 @@ # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # at your option. This file may not be copied, modified, or distributed except according to those terms. +{.push raises: [].} + import results, eth/common, ../../common/common_utils, ./state_content, ./state_utils export results, state_content @@ -12,7 +14,9 @@ export results, state_content proc hashEquals(value: TrieNode | Bytecode, expectedHash: KeccakHash): bool {.inline.} = keccakHash(value.asSeq()) == expectedHash -proc isValidNextNode(thisNodeRlp: Rlp, rlpIdx: int, nextNode: TrieNode): bool = +proc isValidNextNode( + thisNodeRlp: Rlp, rlpIdx: int, nextNode: TrieNode +): bool {.raises: RlpError.} = let hashOrShortRlp = thisNodeRlp.listElem(rlpIdx) if hashOrShortRlp.isEmpty(): return false @@ -63,45 +67,48 @@ proc validateTrieProof*( else: return err("proof has more nodes then expected for given path") - case thisNodeRlp.listLen() - of 2: - let nodePrefixRlp = thisNodeRlp.listElem(0) - if nodePrefixRlp.isEmpty(): - return err("node prefix is empty") + try: + case thisNodeRlp.listLen() + of 2: + let nodePrefixRlp = thisNodeRlp.listElem(0) + if nodePrefixRlp.isEmpty(): + return err("node prefix is empty") - let (prefix, isLeaf, prefixNibbles) = decodePrefix(nodePrefixRlp) - if prefix >= 4: - return err("invalid prefix in node") + let (prefix, isLeaf, prefixNibbles) = decodePrefix(nodePrefixRlp) + if prefix >= 4: + return err("invalid prefix in node") - if not isLastNode or (isLeaf and allowKeyEndInPathForLeafs): - let unpackedPrefix = prefixNibbles.unpackNibbles() - if remainingNibbles < unpackedPrefix.len(): - return err("not enough nibbles to validate node prefix") + if not isLastNode or (isLeaf and allowKeyEndInPathForLeafs): + let unpackedPrefix = prefixNibbles.unpackNibbles() + if remainingNibbles < unpackedPrefix.len(): + return err("not enough nibbles to validate node prefix") - let nibbleEndIdx = nibbleIdx + unpackedPrefix.len() - if nibbles[nibbleIdx ..< nibbleEndIdx] != unpackedPrefix: - return err("nibbles don't match node prefix") - nibbleIdx += unpackedPrefix.len() + let nibbleEndIdx = nibbleIdx + unpackedPrefix.len() + if nibbles[nibbleIdx ..< nibbleEndIdx] != unpackedPrefix: + return err("nibbles don't match node prefix") + nibbleIdx += unpackedPrefix.len() - if not isLastNode: - if isLeaf: - return err("leaf node must be last node in the proof") - else: # is extension node - if not isValidNextNode(thisNodeRlp, 1, proof[proofIdx + 1]): - return - err("hash of next node doesn't match the expected extension node hash") - of 17: - if not isLastNode: - let nextNibble = nibbles[nibbleIdx] - if nextNibble >= 16: - return err("invalid next nibble for branch node") + if not isLastNode: + if isLeaf: + return err("leaf node must be last node in the proof") + else: # is extension node + if not isValidNextNode(thisNodeRlp, 1, proof[proofIdx + 1]): + return + err("hash of next node doesn't match the expected extension node hash") + of 17: + if not isLastNode: + let nextNibble = nibbles[nibbleIdx] + if nextNibble >= 16: + return err("invalid next nibble for branch node") - if not isValidNextNode(thisNodeRlp, nextNibble.int, proof[proofIdx + 1]): - return err("hash of next node doesn't match the expected branch node hash") + if not isValidNextNode(thisNodeRlp, nextNibble.int, proof[proofIdx + 1]): + return err("hash of next node doesn't match the expected branch node hash") - inc nibbleIdx - else: - return err("invalid rlp node, expected 2 or 17 elements") + inc nibbleIdx + else: + return err("invalid rlp node, expected 2 or 17 elements") + except RlpError as e: + return err(e.msg) if nibbleIdx < nibbles.len(): err("path contains more nibbles than expected for proof")