Add push raises to Fluffy state network code. (#2352)

* Add push raises to Fluffy state network code.

* Refactor handling of rlp errors.
This commit is contained in:
web3-developer 2024-06-14 13:38:24 +08:00 committed by GitHub
parent 329a8f05bb
commit 7fd777cfa9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 134 additions and 103 deletions

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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()

View File

@ -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")