Fluffy state network refactor (#2200)

* Add additional getTrieProof test.

* Refactor state network: Removed variant objects for values and split state_content into multiple files.
This commit is contained in:
web3-developer 2024-05-22 09:22:07 +08:00 committed by GitHub
parent 2629d412d7
commit e566e89d03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 938 additions and 886 deletions

View File

@ -14,7 +14,7 @@ import
stew/[io2, arrayops], stew/[io2, arrayops],
eth/p2p/discoveryv5/enr eth/p2p/discoveryv5/enr
func init*(T: type KeccakHash, hash: openArray[byte]): T = func fromBytes*(T: type KeccakHash, hash: openArray[byte]): T =
doAssert(hash.len() == 32) doAssert(hash.len() == 32)
KeccakHash(data: array[32, byte].initCopyFrom(hash)) KeccakHash(data: array[32, byte].initCopyFrom(hash))

View File

@ -0,0 +1,112 @@
# Fluffy
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * 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.
# As per spec:
# https://github.com/ethereum/portal-network-specs/blob/master/state-network.md#content-keys-and-content-ids
{.push raises: [].}
import
nimcrypto/[hash, sha2, keccak],
results,
stint,
eth/common/eth_types,
ssz_serialization,
./nibbles
export ssz_serialization, common_types, hash, results
type
NodeHash* = KeccakHash
CodeHash* = KeccakHash
Address* = EthAddress
ContentType* = enum
# Note: Need to add this unused value as a case object with an enum without
# a 0 valueis not allowed: "low(contentType) must be 0 for discriminant".
# For prefix values that are in the enum gap, the deserialization will fail
# at runtime as is wanted.
# In the future it might be possible that this will fail at compile time for
# the SSZ Union type, but currently it is allowed in the implementation, and
# the SSZ spec is not explicit about disallowing this.
unused = 0x00
accountTrieNode = 0x20
contractTrieNode = 0x21
contractCode = 0x22
AccountTrieNodeKey* = object
path*: Nibbles
nodeHash*: NodeHash
ContractTrieNodeKey* = object
address*: Address
path*: Nibbles
nodeHash*: NodeHash
ContractCodeKey* = object
address*: Address
codeHash*: CodeHash
ContentKey* = object
case contentType*: ContentType
of unused:
discard
of accountTrieNode:
accountTrieNodeKey*: AccountTrieNodeKey
of contractTrieNode:
contractTrieNodeKey*: ContractTrieNodeKey
of contractCode:
contractCodeKey*: ContractCodeKey
func init*(T: type AccountTrieNodeKey, path: Nibbles, nodeHash: NodeHash): T =
AccountTrieNodeKey(path: path, nodeHash: nodeHash)
func init*(
T: type ContractTrieNodeKey, address: Address, path: Nibbles, nodeHash: NodeHash
): T =
ContractTrieNodeKey(address: address, path: path, nodeHash: nodeHash)
func init*(T: type ContractCodeKey, address: Address, codeHash: CodeHash): T =
ContractCodeKey(address: address, codeHash: codeHash)
func initAccountTrieNodeKey*(path: Nibbles, nodeHash: NodeHash): ContentKey =
ContentKey(
contentType: accountTrieNode,
accountTrieNodeKey: AccountTrieNodeKey.init(path, nodeHash),
)
func initContractTrieNodeKey*(
address: Address, path: Nibbles, nodeHash: NodeHash
): ContentKey =
ContentKey(
contentType: contractTrieNode,
contractTrieNodeKey: ContractTrieNodeKey.init(address, path, nodeHash),
)
func initContractCodeKey*(address: Address, codeHash: CodeHash): ContentKey =
ContentKey(
contentType: contractCode, contractCodeKey: ContractCodeKey.init(address, codeHash)
)
proc readSszBytes*(data: openArray[byte], val: var ContentKey) {.raises: [SszError].} =
mixin readSszValue
if data.len() > 0 and data[0] == ord(unused):
raise newException(MalformedSszError, "SSZ selector is unused value")
readSszValue(data, val)
func encode*(contentKey: ContentKey): ByteList =
doAssert(contentKey.contentType != unused)
ByteList.init(SSZ.encode(contentKey))
func decode*(T: type ContentKey, contentKey: ByteList): Result[T, string] =
decodeSsz(contentKey.asSeq(), T)
func toContentId*(contentKey: ByteList): ContentId =
# TODO: Should we try to parse the content key here for invalid ones?
let idHash = sha256.digest(contentKey.asSeq())
readUintBE[256](idHash.data)

View File

@ -0,0 +1,97 @@
# Fluffy
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * 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.
# As per spec:
# https://github.com/ethereum/portal-network-specs/blob/master/state-network.md#content-keys-and-content-ids
{.push raises: [].}
import results, eth/common/eth_types, ssz_serialization, ../../../common/common_types
export ssz_serialization, common_types, hash, results
const
MAX_TRIE_NODE_LEN = 1024
MAX_TRIE_PROOF_LEN = 65
MAX_BYTECODE_LEN = 32768
type
TrieNode* = List[byte, MAX_TRIE_NODE_LEN]
TrieProof* = List[TrieNode, MAX_TRIE_PROOF_LEN]
Bytecode* = List[byte, MAX_BYTECODE_LEN]
AccountTrieNodeOffer* = object
proof*: TrieProof
blockHash*: BlockHash
AccountTrieNodeRetrieval* = object
node*: TrieNode
ContractTrieNodeOffer* = object
storageProof*: TrieProof
accountProof*: TrieProof
blockHash*: BlockHash
ContractTrieNodeRetrieval* = object
node*: TrieNode
ContractCodeOffer* = object
code*: Bytecode
accountProof*: TrieProof
blockHash*: BlockHash
ContractCodeRetrieval* = object
code*: Bytecode
ContentValue* =
AccountTrieNodeOffer | ContractTrieNodeOffer | ContractCodeOffer |
AccountTrieNodeRetrieval | ContractTrieNodeRetrieval | ContractCodeRetrieval
func init*(T: type AccountTrieNodeOffer, proof: TrieProof, blockHash: BlockHash): T =
AccountTrieNodeOffer(proof: proof, blockHash: blockHash)
func init*(T: type AccountTrieNodeRetrieval, node: TrieNode): T =
AccountTrieNodeRetrieval(node: node)
func init*(
T: type ContractTrieNodeOffer,
storageProof: TrieProof,
accountProof: TrieProof,
blockHash: BlockHash,
): T =
ContractTrieNodeOffer(
storageProof: storageProof, accountProof: accountProof, blockHash: blockHash
)
func init*(T: type ContractTrieNodeRetrieval, node: TrieNode): T =
ContractTrieNodeRetrieval(node: node)
func init*(
T: type ContractCodeOffer,
code: Bytecode,
accountProof: TrieProof,
blockHash: BlockHash,
): T =
ContractCodeOffer(code: code, accountProof: accountProof, blockHash: blockHash)
func init*(T: type ContractCodeRetrieval, code: Bytecode): T =
ContractCodeRetrieval(code: code)
func toRetrievalValue*(offer: AccountTrieNodeOffer): AccountTrieNodeRetrieval =
AccountTrieNodeRetrieval.init(offer.proof[^1])
func toRetrievalValue*(offer: ContractTrieNodeOffer): ContractTrieNodeRetrieval =
ContractTrieNodeRetrieval.init(offer.storageProof[^1])
func toRetrievalValue*(offer: ContractCodeOffer): ContractCodeRetrieval =
ContractCodeRetrieval.init(offer.code)
func encode*(value: ContentValue): seq[byte] =
SSZ.encode(value)
func decode*(T: type ContentValue, bytes: openArray[byte]): Result[T, string] =
decodeSsz(bytes, T)

View File

@ -0,0 +1,101 @@
# Fluffy
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * 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.
# As per spec:
# https://github.com/ethereum/portal-network-specs/blob/master/state-network.md#content-keys-and-content-ids
{.push raises: [].}
import
nimcrypto/hash,
results,
stint,
eth/common/eth_types,
ssz_serialization,
../../../common/common_types
export ssz_serialization, common_types, hash, results
const
MAX_PACKED_NIBBLES_LEN = 33
MAX_UNPACKED_NIBBLES_LEN = 64
type Nibbles* = List[byte, MAX_PACKED_NIBBLES_LEN]
func init*(T: type Nibbles, packed: openArray[byte], isEven: bool): T =
doAssert(packed.len() <= MAX_PACKED_NIBBLES_LEN)
var output = newSeqOfCap[byte](packed.len() + 1)
if isEven:
output.add(0x00)
else:
doAssert(packed.len() > 0)
# set the first nibble to 1 and copy the second nibble from the input
output.add((packed[0] and 0x0F) or 0x10)
let startIdx = if isEven: 0 else: 1
for i in startIdx ..< packed.len():
output.add(packed[i])
Nibbles(output)
func encode*(nibbles: Nibbles): seq[byte] =
SSZ.encode(nibbles)
func decode*(T: type Nibbles, bytes: openArray[byte]): Result[T, string] =
decodeSsz(bytes, T)
func packNibbles*(unpacked: openArray[byte]): Nibbles =
doAssert(
unpacked.len() <= MAX_UNPACKED_NIBBLES_LEN, "Can't pack more than 64 nibbles"
)
if unpacked.len() == 0:
return Nibbles(@[byte(0x00)])
let isEvenLength = unpacked.len() mod 2 == 0
var
output = newSeqOfCap[byte](unpacked.len() div 2 + 1)
highNibble = isEvenLength
currentByte: byte = 0
if isEvenLength:
output.add(0x00)
else:
currentByte = 0x10
for i, nibble in unpacked:
if highNibble:
currentByte = nibble shl 4
else:
output.add(currentByte or nibble)
currentByte = 0
highNibble = not highNibble
Nibbles(output)
func unpackNibbles*(packed: Nibbles): seq[byte] =
doAssert(packed.len() <= MAX_PACKED_NIBBLES_LEN, "Packed nibbles length is too long")
var output = newSeqOfCap[byte](packed.len() * 2)
for i, pair in packed:
if i == 0 and pair == 0x00:
continue
let
first = (pair and 0xF0) shr 4
second = pair and 0x0F
if i == 0 and first == 0x01:
output.add(second)
else:
output.add(first)
output.add(second)
output

View File

@ -5,273 +5,6 @@
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # * 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. # at your option. This file may not be copied, modified, or distributed except according to those terms.
# As per spec: import ./content/content_keys, ./content/content_values, ./content/nibbles
# https://github.com/ethereum/portal-network-specs/blob/master/state-network.md#content-keys-and-content-ids
{.push raises: [].} export content_keys, content_values, nibbles
import
nimcrypto/[hash, sha2, keccak],
results,
stint,
eth/common/eth_types,
ssz_serialization,
../../common/common_types
export ssz_serialization, common_types, hash, results
const
MAX_PACKED_NIBBLES_LEN = 33
MAX_UNPACKED_NIBBLES_LEN = 64
MAX_TRIE_NODE_LEN = 1024
MAX_TRIE_PROOF_LEN = 65
MAX_BYTECODE_LEN = 32768
type
NodeHash* = KeccakHash
CodeHash* = KeccakHash
Address* = EthAddress
ContentType* = enum
# Note: Need to add this unused value as a case object with an enum without
# a 0 valueis not allowed: "low(contentType) must be 0 for discriminant".
# For prefix values that are in the enum gap, the deserialization will fail
# at runtime as is wanted.
# In the future it might be possible that this will fail at compile time for
# the SSZ Union type, but currently it is allowed in the implementation, and
# the SSZ spec is not explicit about disallowing this.
unused = 0x00
accountTrieNode = 0x20
contractTrieNode = 0x21
contractCode = 0x22
Nibbles* = List[byte, MAX_PACKED_NIBBLES_LEN]
TrieNode* = List[byte, MAX_TRIE_NODE_LEN]
TrieProof* = List[TrieNode, MAX_TRIE_PROOF_LEN]
Bytecode* = List[byte, MAX_BYTECODE_LEN]
AccountTrieNodeKey* = object
path*: Nibbles
nodeHash*: NodeHash
ContractTrieNodeKey* = object
address*: Address
path*: Nibbles
nodeHash*: NodeHash
ContractCodeKey* = object
address*: Address
codeHash*: CodeHash
ContentKey* = object
case contentType*: ContentType
of unused:
discard
of accountTrieNode:
accountTrieNodeKey*: AccountTrieNodeKey
of contractTrieNode:
contractTrieNodeKey*: ContractTrieNodeKey
of contractCode:
contractCodeKey*: ContractCodeKey
AccountTrieNodeOffer* = object
proof*: TrieProof
blockHash*: BlockHash
AccountTrieNodeRetrieval* = object
node*: TrieNode
ContractTrieNodeOffer* = object
storageProof*: TrieProof
accountProof*: TrieProof
blockHash*: BlockHash
ContractTrieNodeRetrieval* = object
node*: TrieNode
ContractCodeOffer* = object
code*: Bytecode
accountProof*: TrieProof
blockHash*: BlockHash
ContractCodeRetrieval* = object
code*: Bytecode
OfferContentValueType* = enum
accountTrieNodeOffer
contractTrieNodeOffer
contractCodeOffer
OfferContentValue* = object
case contentType*: ContentType
of unused:
discard
of accountTrieNode:
accountTrieNode*: AccountTrieNodeOffer
of contractTrieNode:
contractTrieNode*: ContractTrieNodeOffer
of contractCode:
contractCode*: ContractCodeOffer
RetrievalContentValue* = object
case contentType*: ContentType
of unused:
discard
of accountTrieNode:
accountTrieNode*: AccountTrieNodeRetrieval
of contractTrieNode:
contractTrieNode*: ContractTrieNodeRetrieval
of contractCode:
contractCode*: ContractCodeRetrieval
func encode*(contentKey: ContentKey): ByteList =
doAssert(contentKey.contentType != unused)
ByteList.init(SSZ.encode(contentKey))
proc readSszBytes*(data: openArray[byte], val: var ContentKey) {.raises: [SszError].} =
mixin readSszValue
if data.len() > 0 and data[0] == ord(unused):
raise newException(MalformedSszError, "SSZ selector is unused value")
readSszValue(data, val)
func decode*(contentKey: ByteList): Opt[ContentKey] =
try:
Opt.some(SSZ.decode(contentKey.asSeq(), ContentKey))
except SerializationError:
return Opt.none(ContentKey)
func toContentId*(contentKey: ByteList): ContentId =
# TODO: Should we try to parse the content key here for invalid ones?
let idHash = sha2.sha256.digest(contentKey.asSeq())
readUintBE[256](idHash.data)
func toContentId*(contentKey: ContentKey): ContentId =
toContentId(encode(contentKey))
func initAccountTrieNodeKey*(path: Nibbles, nodeHash: NodeHash): ContentKey =
ContentKey(
contentType: accountTrieNode,
accountTrieNodeKey: AccountTrieNodeKey(path: path, nodeHash: nodeHash),
)
func initContractTrieNodeKey*(
address: Address, path: Nibbles, nodeHash: NodeHash
): ContentKey =
ContentKey(
contentType: contractTrieNode,
contractTrieNodeKey:
ContractTrieNodeKey(address: address, path: path, nodeHash: nodeHash),
)
func initContractCodeKey*(address: Address, codeHash: CodeHash): ContentKey =
ContentKey(
contentType: contractCode,
contractCodeKey: ContractCodeKey(address: address, codeHash: codeHash),
)
func offerContentToRetrievalContent*(
offerContent: OfferContentValue
): RetrievalContentValue =
case offerContent.contentType
of unused:
raiseAssert "Converting content with unused content type"
of accountTrieNode:
RetrievalContentValue(
contentType: accountTrieNode,
accountTrieNode:
AccountTrieNodeRetrieval(node: offerContent.accountTrieNode.proof[^1]),
) # TODO implement properly
of contractTrieNode:
RetrievalContentValue(
contentType: contractTrieNode,
contractTrieNode:
ContractTrieNodeRetrieval(node: offerContent.contractTrieNode.storageProof[^1]),
) # TODO implement properly
of contractCode:
RetrievalContentValue(
contentType: contractCode,
contractCode: ContractCodeRetrieval(code: offerContent.contractCode.code),
)
func encode*(content: RetrievalContentValue): seq[byte] =
case content.contentType
of unused:
raiseAssert "Encoding content with unused content type"
of accountTrieNode:
SSZ.encode(content.accountTrieNode)
of contractTrieNode:
SSZ.encode(content.contractTrieNode)
of contractCode:
SSZ.encode(content.contractCode)
func init*(T: type Nibbles, packed: openArray[byte], isEven: bool): T =
doAssert(packed.len() <= MAX_PACKED_NIBBLES_LEN)
var output = newSeqOfCap[byte](packed.len() + 1)
if isEven:
output.add(0x00)
else:
doAssert(packed.len() > 0)
# set the first nibble to 1 and copy the second nibble from the input
output.add((packed[0] and 0x0F) or 0x10)
let startIdx = if isEven: 0 else: 1
for i in startIdx ..< packed.len():
output.add(packed[i])
Nibbles(output)
func packNibbles*(unpacked: openArray[byte]): Nibbles =
doAssert(
unpacked.len() <= MAX_UNPACKED_NIBBLES_LEN, "Can't pack more than 64 nibbles"
)
if unpacked.len() == 0:
return Nibbles(@[byte(0x00)])
let isEvenLength = unpacked.len() mod 2 == 0
var
output = newSeqOfCap[byte](unpacked.len() div 2 + 1)
highNibble = isEvenLength
currentByte: byte = 0
if isEvenLength:
output.add(0x00)
else:
currentByte = 0x10
for i, nibble in unpacked:
if highNibble:
currentByte = nibble shl 4
else:
output.add(currentByte or nibble)
currentByte = 0
highNibble = not highNibble
Nibbles(output)
func unpackNibbles*(packed: Nibbles): seq[byte] =
doAssert(packed.len() <= MAX_PACKED_NIBBLES_LEN, "Packed nibbles length is too long")
var output = newSeqOfCap[byte](packed.len() * 2)
for i, pair in packed:
if i == 0 and pair == 0x00:
continue
let
first = (pair and 0xF0) shr 4
second = pair and 0x0F
if i == 0 and first == 0x01:
output.add(second)
else:
output.add(first)
output.add(second)
output

View File

@ -0,0 +1,97 @@
# Fluffy
# Copyright (c) 2021-2024 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * 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.
import
results,
chronos,
chronicles,
eth/common/eth_hash,
eth/common,
eth/p2p/discoveryv5/[protocol, enr],
../../database/content_db,
../history/history_network,
../wire/[portal_protocol, portal_stream],
./state_content
export results
logScope:
topics = "portal_state"
proc gossipOffer*(
p: PortalProtocol,
maybeSrcNodeId: Opt[NodeId],
decodedKey: AccountTrieNodeKey,
decodedValue: AccountTrieNodeOffer,
): Future[void] {.async.} =
var
nibbles = decodedKey.path.unpackNibbles()
proof = decodedValue.proof
# When nibbles is empty this means the root node was received. Recursive
# gossiping is finished.
if nibbles.len() == 0:
return
# TODO: Review this logic.
# Removing a single nibble will not work for extension nodes with multiple prefix nibbles
discard nibbles.pop()
discard (distinctBase proof).pop()
let
updatedValue = AccountTrieNodeOffer(proof: proof, blockHash: decodedValue.blockHash)
updatedNodeHash = keccakHash(distinctBase proof[^1])
encodedValue = SSZ.encode(updatedValue)
updatedKey =
AccountTrieNodeKey(path: nibbles.packNibbles(), nodeHash: updatedNodeHash)
encodedKey =
ContentKey(accountTrieNodeKey: updatedKey, contentType: accountTrieNode).encode()
await p.neighborhoodGossipDiscardPeers(
maybeSrcNodeId, ContentKeysList.init(@[encodedKey]), @[encodedValue]
)
proc gossipOffer*(
p: PortalProtocol,
maybeSrcNodeId: Opt[NodeId],
decodedKey: ContractTrieNodeKey,
decodedValue: ContractTrieNodeOffer,
): Future[void] {.async.} =
# TODO: Recursive gossiping for contract trie nodes
return
proc gossipOffer*(
p: PortalProtocol,
maybeSrcNodeId: Opt[NodeId],
decodedKey: ContractCodeKey,
decodedValue: ContractCodeOffer,
): Future[void] {.async.} =
# TODO: Recursive gossiping for bytecode?
return
# proc gossipContent*(
# p: PortalProtocol,
# maybeSrcNodeId: Opt[NodeId],
# contentKey: ByteList,
# decodedKey: ContentKey,
# contentValue: seq[byte],
# decodedValue: OfferContentValue,
# ): Future[void] {.async.} =
# case decodedKey.contentType
# of unused:
# raiseAssert "Gossiping content with unused content type"
# of accountTrieNode:
# await recursiveGossipAccountTrieNode(
# p, maybeSrcNodeId, decodedKey, decodedValue.accountTrieNode
# )
# of contractTrieNode:
# await recursiveGossipContractTrieNode(
# p, maybeSrcNodeId, decodedKey, decodedValue.contractTrieNode
# )
# of contractCode:
# await p.neighborhoodGossipDiscardPeers(
# maybeSrcNodeId, ContentKeysList.init(@[contentKey]), @[contentValue]
# )

View File

@ -16,7 +16,8 @@ import
../history/history_network, ../history/history_network,
../wire/[portal_protocol, portal_stream, portal_protocol_config], ../wire/[portal_protocol, portal_stream, portal_protocol_config],
./state_content, ./state_content,
./state_validation ./state_validation,
./state_gossip
export results export results
@ -35,228 +36,6 @@ type StateNetwork* = ref object
func toContentIdHandler(contentKey: ByteList): results.Opt[ContentId] = func toContentIdHandler(contentKey: ByteList): results.Opt[ContentId] =
ok(toContentId(contentKey)) ok(toContentId(contentKey))
func decodeKV*(
contentKey: ByteList, contentValue: seq[byte]
): Opt[(ContentKey, OfferContentValue)] =
const empty = Opt.none((ContentKey, OfferContentValue))
let
key = contentKey.decode().valueOr:
return empty
value =
case key.contentType
of unused:
return empty
of accountTrieNode:
let val = decodeSsz(contentValue, AccountTrieNodeOffer).valueOr:
return empty
OfferContentValue(contentType: accountTrieNode, accountTrieNode: val)
of contractTrieNode:
let val = decodeSsz(contentValue, ContractTrieNodeOffer).valueOr:
return empty
OfferContentValue(contentType: contractTrieNode, contractTrieNode: val)
of contractCode:
let val = decodeSsz(contentValue, ContractCodeOffer).valueOr:
return empty
OfferContentValue(contentType: contractCode, contractCode: val)
Opt.some((key, value))
func decodeValue*(
contentKey: ContentKey, contentValue: seq[byte]
): Opt[RetrievalContentValue] =
const empty = Opt.none(RetrievalContentValue)
let value =
case contentKey.contentType
of unused:
return empty
of accountTrieNode:
let val = decodeSsz(contentValue, AccountTrieNodeRetrieval).valueOr:
return empty
RetrievalContentValue(contentType: accountTrieNode, accountTrieNode: val)
of contractTrieNode:
let val = decodeSsz(contentValue, ContractTrieNodeRetrieval).valueOr:
return empty
RetrievalContentValue(contentType: contractTrieNode, contractTrieNode: val)
of contractCode:
let val = decodeSsz(contentValue, ContractCodeRetrieval).valueOr:
return empty
RetrievalContentValue(contentType: contractCode, contractCode: val)
Opt.some(value)
proc validateContent*(
n: StateNetwork, contentKey: ContentKey, contentValue: RetrievalContentValue
): bool =
doAssert(contentKey.contentType == contentValue.contentType)
let res =
case contentKey.contentType
of unused:
Result[void, string].err("Received content with unused content type")
of accountTrieNode:
validateFetchedAccountTrieNode(
contentKey.accountTrieNodeKey, contentValue.accountTrieNode
)
of contractTrieNode:
validateFetchedContractTrieNode(
contentKey.contractTrieNodeKey, contentValue.contractTrieNode
)
of contractCode:
validateFetchedContractCode(contentKey.contractCodeKey, contentValue.contractCode)
res.isOkOr:
warn "Validation of fetched content failed: ", error
res.isOk()
proc getContent*(n: StateNetwork, key: ContentKey): Future[Opt[seq[byte]]] {.async.} =
let
keyEncoded = encode(key)
contentId = toContentId(key)
contentInRange = n.portalProtocol.inRange(contentId)
# When the content id is in the radius range, try to look it up in the db.
if contentInRange:
let contentFromDB = n.contentDB.get(contentId)
if contentFromDB.isSome():
return contentFromDB
let content = await n.portalProtocol.contentLookup(keyEncoded, contentId)
if content.isNone():
return Opt.none(seq[byte])
let
contentResult = content.get()
decodedValue = decodeValue(key, contentResult.content).valueOr:
error "Unable to decode offered Key/Value"
return Opt.none(seq[byte])
if not validateContent(n, key, decodedValue):
return Opt.none(seq[byte])
# When content is found on the network and is in the radius range, store it.
if content.isSome() and contentInRange:
# TODO Add poke when working on state network
# TODO When working on state network, make it possible to pass different
# distance functions to store content
n.portalProtocol.storeContent(keyEncoded, contentId, contentResult.content)
# TODO: for now returning bytes, ultimately it would be nice to return proper
# domain types.
return Opt.some(contentResult.content)
proc getStateRootByBlockHash(
n: StateNetwork, hash: BlockHash
): Future[Opt[KeccakHash]] {.async.} =
if n.historyNetwork.isNone():
warn "History network is not available. Unable to get state root by block hash"
return Opt.none(KeccakHash)
let header = (await n.historyNetwork.get().getVerifiedBlockHeader(hash)).valueOr:
warn "Failed to get block header by hash", hash
return Opt.none(KeccakHash)
Opt.some(header.stateRoot)
proc validateContent*(
n: StateNetwork, contentKey: ContentKey, contentValue: OfferContentValue
): Future[Result[void, string]] {.async.} =
doAssert(contentKey.contentType == contentValue.contentType)
case contentKey.contentType
of unused:
Result[void, string].err("Received content with unused content type")
of accountTrieNode:
let stateRoot = (
await n.getStateRootByBlockHash(contentValue.accountTrieNode.blockHash)
).valueOr:
return Result[void, string].err("Failed to get state root by block hash")
validateOfferedAccountTrieNode(
stateRoot, contentKey.accountTrieNodeKey, contentValue.accountTrieNode
)
of contractTrieNode:
let stateRoot = (
await n.getStateRootByBlockHash(contentValue.contractTrieNode.blockHash)
).valueOr:
return Result[void, string].err("Failed to get state root by block hash")
validateOfferedContractTrieNode(
stateRoot, contentKey.contractTrieNodeKey, contentValue.contractTrieNode
)
of contractCode:
let stateRoot = (
await n.getStateRootByBlockHash(contentValue.contractCode.blockHash)
).valueOr:
return Result[void, string].err("Failed to get state root by block hash")
validateOfferedContractCode(
stateRoot, contentKey.contractCodeKey, contentValue.contractCode
)
proc recursiveGossipAccountTrieNode(
p: PortalProtocol,
maybeSrcNodeId: Opt[NodeId],
decodedKey: ContentKey,
decodedValue: AccountTrieNodeOffer,
): Future[void] {.async.} =
var
nibbles = decodedKey.accountTrieNodeKey.path.unpackNibbles()
proof = decodedValue.proof
# When nibbles is empty this means the root node was received. Recursive
# gossiping is finished.
if nibbles.len() == 0:
return
discard nibbles.pop()
discard (distinctBase proof).pop()
let
updatedValue = AccountTrieNodeOffer(proof: proof, blockHash: decodedValue.blockHash)
updatedNodeHash = keccakHash(distinctBase proof[^1])
encodedValue = SSZ.encode(updatedValue)
updatedKey =
AccountTrieNodeKey(path: nibbles.packNibbles(), nodeHash: updatedNodeHash)
encodedKey =
ContentKey(accountTrieNodeKey: updatedKey, contentType: accountTrieNode).encode()
await neighborhoodGossipDiscardPeers(
p, maybeSrcNodeId, ContentKeysList.init(@[encodedKey]), @[encodedValue]
)
proc recursiveGossipContractTrieNode(
p: PortalProtocol,
maybeSrcNodeId: Opt[NodeId],
decodedKey: ContentKey,
decodedValue: ContractTrieNodeOffer,
): Future[void] {.async.} =
return
proc gossipContent*(
p: PortalProtocol,
maybeSrcNodeId: Opt[NodeId],
contentKey: ByteList,
decodedKey: ContentKey,
contentValue: seq[byte],
decodedValue: OfferContentValue,
): Future[void] {.async.} =
case decodedKey.contentType
of unused:
raiseAssert "Gossiping content with unused content type"
of accountTrieNode:
await recursiveGossipAccountTrieNode(
p, maybeSrcNodeId, decodedKey, decodedValue.accountTrieNode
)
of contractTrieNode:
await recursiveGossipContractTrieNode(
p, maybeSrcNodeId, decodedKey, decodedValue.contractTrieNode
)
of contractCode:
await p.neighborhoodGossipDiscardPeers(
maybeSrcNodeId, ContentKeysList.init(@[contentKey]), @[contentValue]
)
proc new*( proc new*(
T: type StateNetwork, T: type StateNetwork,
baseProtocol: protocol.Protocol, baseProtocol: protocol.Protocol,
@ -290,34 +69,161 @@ proc new*(
historyNetwork: historyNetwork, historyNetwork: historyNetwork,
) )
# TODO: implement content lookups for each type
proc getContent*(
n: StateNetwork, contentKey: ContentKey
): Future[Opt[seq[byte]]] {.async.} =
let
contentKeyBytes = contentKey.encode()
contentId = contentKeyBytes.toContentId()
contentInRange = n.portalProtocol.inRange(contentId)
# When the content id is in the radius range, try to look it up in the db.
if contentInRange:
let contentFromDB = n.contentDB.get(contentId)
if contentFromDB.isSome():
return contentFromDB
let
contentLookupResult = (
await n.portalProtocol.contentLookup(contentKeyBytes, contentId)
).valueOr:
return Opt.none(seq[byte])
contentValueBytes = contentLookupResult.content
case contentKey.contentType
of unused:
error "Received content with unused content type"
return Opt.none(seq[byte])
of accountTrieNode:
let contentValue = AccountTrieNodeRetrieval.decode(contentValueBytes).valueOr:
error "Unable to decode AccountTrieNodeRetrieval content value"
return Opt.none(seq[byte])
validateRetrieval(contentKey.accountTrieNodeKey, contentValue).isOkOr:
error "Validation of retrieval content failed: ", error
return Opt.none(seq[byte])
of contractTrieNode:
let contentValue = ContractTrieNodeRetrieval.decode(contentValueBytes).valueOr:
error "Unable to decode ContractTrieNodeRetrieval content value"
return Opt.none(seq[byte])
validateRetrieval(contentKey.contractTrieNodeKey, contentValue).isOkOr:
error "Validation of retrieval content failed: ", error
return Opt.none(seq[byte])
of contractCode:
let contentValue = ContractCodeRetrieval.decode(contentValueBytes).valueOr:
error "Unable to decode ContractCodeRetrieval content value"
return Opt.none(seq[byte])
validateRetrieval(contentKey.contractCodeKey, contentValue).isOkOr:
error "Validation of retrieval content failed: ", error
return Opt.none(seq[byte])
# When content is in the radius range, store it.
if contentInRange:
# TODO Add poke when working on state network
# TODO When working on state network, make it possible to pass different
# distance functions to store content
n.portalProtocol.storeContent(contentKeyBytes, contentId, contentValueBytes)
# TODO: for now returning bytes, ultimately it would be nice to return proper
# domain types.
return Opt.some(contentValueBytes)
func decodeKey(contentKey: ByteList): Opt[ContentKey] =
let key = ContentKey.decode(contentKey).valueOr:
return Opt.none(ContentKey)
Opt.some(key)
proc getStateRootByBlockHash(
n: StateNetwork, hash: BlockHash
): Future[Opt[KeccakHash]] {.async.} =
if n.historyNetwork.isNone():
warn "History network is not available. Unable to get state root by block hash"
return Opt.none(KeccakHash)
let header = (await n.historyNetwork.get().getVerifiedBlockHeader(hash)).valueOr:
warn "Failed to get block header by hash", hash
return Opt.none(KeccakHash)
Opt.some(header.stateRoot)
proc processOffer[K, V](
n: StateNetwork,
maybeSrcNodeId: Opt[NodeId],
contentKeyBytes: ByteList,
contentKey: K,
contentValue: V,
): Future[Result[void, string]] {.async.} =
mixin blockHash, validateOffer, toRetrievalValue, gossipOffer
let stateRoot = (await n.getStateRootByBlockHash(contentValue.blockHash)).valueOr:
return err("Failed to get state root by block hash")
let res = validateOffer(stateRoot, contentKey, contentValue)
if res.isErr():
return err("Received offered content failed validation: " & res.error())
let contentId = n.portalProtocol.toContentId(contentKeyBytes).valueOr:
return err("Received offered content with invalid content key")
n.portalProtocol.storeContent(
contentKeyBytes, contentId, contentValue.toRetrievalValue().encode()
)
info "Received offered content validated successfully", contentKeyBytes
await gossipOffer(n.portalProtocol, maybeSrcNodeId, contentKey, contentValue)
proc processContentLoop(n: StateNetwork) {.async.} = proc processContentLoop(n: StateNetwork) {.async.} =
try: try:
while true: while true:
let (maybeSrcNodeId, contentKeys, contentValues) = await n.contentQueue.popFirst() let (maybeSrcNodeId, contentKeys, contentValues) = await n.contentQueue.popFirst()
for i, contentValue in contentValues: for i, contentValueBytes in contentValues:
let let
contentKey = contentKeys[i] contentKeyBytes = contentKeys[i]
(decodedKey, decodedValue) = decodeKV(contentKey, contentValue).valueOr: contentKey = decodeKey(contentKeyBytes).valueOr:
error "Unable to decode offered Key/Value" error "Unable to decode offered content key", contentKeyBytes
continue continue
(await n.validateContent(decodedKey, decodedValue)).isOkOr: let offerRes =
error "Received offered content failed validation", contentKey, error case contentKey.contentType
continue of unused:
error "Received content with unused content type"
let
valueForRetrieval = decodedValue.offerContentToRetrievalContent().encode()
contentId = n.portalProtocol.toContentId(contentKey).valueOr:
error "Received offered content with invalid content key", contentKey
continue continue
of accountTrieNode:
let contentValue = AccountTrieNodeOffer.decode(contentValueBytes).valueOr:
error "Unable to decode offered AccountTrieNodeOffer content value"
continue
n.portalProtocol.storeContent(contentKey, contentId, valueForRetrieval) await processOffer(
info "Received offered content validated successfully", contentKey n, maybeSrcNodeId, contentKeyBytes, contentKey.accountTrieNodeKey,
contentValue,
)
of contractTrieNode:
let contentValue = ContractTrieNodeOffer.decode(contentValueBytes).valueOr:
error "Unable to decode offered ContractTrieNodeOffer content value"
continue
await gossipContent( await processOffer(
n.portalProtocol, maybeSrcNodeId, contentKey, decodedKey, contentValue, n, maybeSrcNodeId, contentKeyBytes, contentKey.contractTrieNodeKey,
decodedValue, contentValue,
) )
of contractCode:
let contentValue = ContractCodeOffer.decode(contentValueBytes).valueOr:
error "Unable to decode offered ContractCodeOffer content value"
continue
await processOffer(
n, maybeSrcNodeId, contentKeyBytes, contentKey.contractCodeKey,
contentValue,
)
if offerRes.isOk():
info "Offered content processed successfully", contentKeyBytes
else:
error "Offered content processing failed",
contentKeyBytes, error = offerRes.error()
except CancelledError: except CancelledError:
trace "processContentLoop canceled" trace "processContentLoop canceled"

View File

@ -11,7 +11,7 @@ import
../../common/[common_types, common_utils], ../../common/[common_types, common_utils],
./state_content ./state_content
export results export results, state_content
# private functions # private functions
@ -31,7 +31,7 @@ proc isValidNextNode(thisNodeRlp: Rlp, rlpIdx: int, nextNode: TrieNode): bool =
let hash = hashOrShortRlp.toBytes() let hash = hashOrShortRlp.toBytes()
if hash.len() != 32: if hash.len() != 32:
return false return false
KeccakHash.init(hash) KeccakHash.fromBytes(hash)
nextNode.hashEquals(nextHash) nextNode.hashEquals(nextHash)
@ -48,6 +48,23 @@ proc decodePrefix(nodePrefixRlp: Rlp): (byte, bool, Nibbles) =
(firstNibble.byte, isLeaf, nibbles) (firstNibble.byte, isLeaf, nibbles)
proc rlpDecodeAccountTrieNode(accountNode: TrieNode): Result[Account, string] =
let accNodeRlp = rlpFromBytes(accountNode.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 (_, isLeaf, _) = decodePrefix(accNodePrefixRlp)
if not isLeaf:
return err("invalid account trie node - leaf prefix expected")
decodeRlp(accNodeRlp.listElem(1).toBytes(), Account)
# public functions
proc validateTrieProof*( proc validateTrieProof*(
expectedRootHash: KeccakHash, path: Nibbles, proof: TrieProof expectedRootHash: KeccakHash, path: Nibbles, proof: TrieProof
): Result[void, string] = ): Result[void, string] =
@ -122,24 +139,7 @@ proc validateTrieProof*(
else: else:
ok() ok()
proc rlpDecodeAccountTrieNode(accountNode: TrieNode): Result[Account, string] = proc validateRetrieval*(
let accNodeRlp = rlpFromBytes(accountNode.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 (_, isLeaf, _) = decodePrefix(accNodePrefixRlp)
if not isLeaf:
return err("invalid account trie node - leaf prefix expected")
decodeRlp(accNodeRlp.listElem(1).toBytes(), Account)
# public functions
proc validateFetchedAccountTrieNode*(
trustedAccountTrieNodeKey: AccountTrieNodeKey, trustedAccountTrieNodeKey: AccountTrieNodeKey,
accountTrieNode: AccountTrieNodeRetrieval, accountTrieNode: AccountTrieNodeRetrieval,
): Result[void, string] = ): Result[void, string] =
@ -148,7 +148,7 @@ proc validateFetchedAccountTrieNode*(
else: else:
err("hash of fetched account trie node doesn't match the expected node hash") err("hash of fetched account trie node doesn't match the expected node hash")
proc validateFetchedContractTrieNode*( proc validateRetrieval*(
trustedContractTrieNodeKey: ContractTrieNodeKey, trustedContractTrieNodeKey: ContractTrieNodeKey,
contractTrieNode: ContractTrieNodeRetrieval, contractTrieNode: ContractTrieNodeRetrieval,
): Result[void, string] = ): Result[void, string] =
@ -157,7 +157,7 @@ proc validateFetchedContractTrieNode*(
else: else:
err("hash of fetched contract trie node doesn't match the expected node hash") err("hash of fetched contract trie node doesn't match the expected node hash")
proc validateFetchedContractCode*( proc validateRetrieval*(
trustedContractCodeKey: ContractCodeKey, contractCode: ContractCodeRetrieval trustedContractCodeKey: ContractCodeKey, contractCode: ContractCodeRetrieval
): Result[void, string] = ): Result[void, string] =
if contractCode.code.hashEquals(trustedContractCodeKey.codeHash): if contractCode.code.hashEquals(trustedContractCodeKey.codeHash):
@ -165,7 +165,7 @@ proc validateFetchedContractCode*(
else: else:
err("hash of fetched bytecode doesn't match the expected code hash") err("hash of fetched bytecode doesn't match the expected code hash")
proc validateOfferedAccountTrieNode*( proc validateOffer*(
trustedStateRoot: KeccakHash, trustedStateRoot: KeccakHash,
accountTrieNodeKey: AccountTrieNodeKey, accountTrieNodeKey: AccountTrieNodeKey,
accountTrieNode: AccountTrieNodeOffer, accountTrieNode: AccountTrieNodeOffer,
@ -177,7 +177,7 @@ proc validateOfferedAccountTrieNode*(
else: else:
err("hash of offered account trie node doesn't match the expected node hash") err("hash of offered account trie node doesn't match the expected node hash")
proc validateOfferedContractTrieNode*( proc validateOffer*(
trustedStateRoot: KeccakHash, trustedStateRoot: KeccakHash,
contractTrieNodeKey: ContractTrieNodeKey, contractTrieNodeKey: ContractTrieNodeKey,
contractTrieNode: ContractTrieNodeOffer, contractTrieNode: ContractTrieNodeOffer,
@ -198,7 +198,7 @@ proc validateOfferedContractTrieNode*(
else: else:
err("hash of offered contract trie node doesn't match the expected node hash") err("hash of offered contract trie node doesn't match the expected node hash")
proc validateOfferedContractCode*( proc validateOffer*(
trustedStateRoot: KeccakHash, trustedStateRoot: KeccakHash,
contractCodeKey: ContractCodeKey, contractCodeKey: ContractCodeKey,
contractCode: ContractCodeOffer, contractCode: ContractCodeOffer,

View File

@ -10,6 +10,7 @@
import import
./test_state_content_keys, ./test_state_content_keys,
./test_state_content_values, ./test_state_content_values,
./test_state_content_nibbles,
./test_state_network, ./test_state_network,
#./test_state_network_gossip, #./test_state_network_gossip,
./test_state_validation, ./test_state_validation,

View File

@ -14,90 +14,6 @@ import
const testVectorDir = "./vendor/portal-spec-tests/tests/mainnet/state/serialization/" const testVectorDir = "./vendor/portal-spec-tests/tests/mainnet/state/serialization/"
suite "State Content Keys": suite "State Content Keys":
test "Encode/decode empty nibbles":
const
expected = "00"
nibbles: seq[byte] = @[]
packedNibbles = packNibbles(nibbles)
unpackedNibbles = unpackNibbles(packedNibbles)
let encoded = SSZ.encode(packedNibbles)
check:
encoded.toHex() == expected
unpackedNibbles == nibbles
test "Encode/decode zero nibble":
const
expected = "10"
nibbles: seq[byte] = @[0]
packedNibbles = packNibbles(nibbles)
unpackedNibbles = unpackNibbles(packedNibbles)
let encoded = SSZ.encode(packedNibbles)
check:
encoded.toHex() == expected
unpackedNibbles == nibbles
test "Encode/decode one nibble":
const
expected = "11"
nibbles: seq[byte] = @[1]
packedNibbles = packNibbles(nibbles)
unpackedNibbles = unpackNibbles(packedNibbles)
let encoded = SSZ.encode(packedNibbles)
check:
encoded.toHex() == expected
unpackedNibbles == nibbles
test "Encode/decode even nibbles":
const
expected = "008679e8ed"
nibbles: seq[byte] = @[8, 6, 7, 9, 14, 8, 14, 13]
packedNibbles = packNibbles(nibbles)
unpackedNibbles = unpackNibbles(packedNibbles)
let encoded = SSZ.encode(packedNibbles)
check:
encoded.toHex() == expected
unpackedNibbles == nibbles
test "Encode/decode odd nibbles":
const
expected = "138679e8ed"
nibbles: seq[byte] = @[3, 8, 6, 7, 9, 14, 8, 14, 13]
packedNibbles = packNibbles(nibbles)
unpackedNibbles = unpackNibbles(packedNibbles)
let encoded = SSZ.encode(packedNibbles)
check:
encoded.toHex() == expected
unpackedNibbles == nibbles
test "Encode/decode max length nibbles":
const
expected = "008679e8eda65bd257638cf8cf09b8238888947cc3c0bea2aa2cc3f1c4ac7a3002"
nibbles: seq[byte] =
@[
8, 6, 7, 9, 0xe, 8, 0xe, 0xd, 0xa, 6, 5, 0xb, 0xd, 2, 5, 7, 6, 3, 8, 0xc, 0xf,
8, 0xc, 0xf, 0, 9, 0xb, 8, 2, 3, 8, 8, 8, 8, 9, 4, 7, 0xc, 0xc, 3, 0xc, 0,
0xb, 0xe, 0xa, 2, 0xa, 0xa, 2, 0xc, 0xc, 3, 0xf, 1, 0xc, 4, 0xa, 0xc, 7, 0xa,
3, 0, 0, 2,
]
packedNibbles = packNibbles(nibbles)
unpackedNibbles = unpackNibbles(packedNibbles)
let encoded = SSZ.encode(packedNibbles)
check:
encoded.toHex() == expected
unpackedNibbles == nibbles
test "Encode/decode AccountTrieNodeKey": test "Encode/decode AccountTrieNodeKey":
const file = testVectorDir & "account_trie_node_key.yaml" const file = testVectorDir & "account_trie_node_key.yaml"
@ -120,7 +36,7 @@ suite "State Content Keys":
encoded.asSeq() == testCase.content_key.hexToSeqByte() encoded.asSeq() == testCase.content_key.hexToSeqByte()
encoded.toContentId().toBytesBE() == testCase.content_id.hexToSeqByte() encoded.toContentId().toBytesBE() == testCase.content_id.hexToSeqByte()
let decoded = encoded.decode() let decoded = ContentKey.decode(encoded)
check: check:
decoded.isOk() decoded.isOk()
decoded.value().contentType == accountTrieNode decoded.value().contentType == accountTrieNode
@ -151,7 +67,7 @@ suite "State Content Keys":
encoded.asSeq() == testCase.content_key.hexToSeqByte() encoded.asSeq() == testCase.content_key.hexToSeqByte()
encoded.toContentId().toBytesBE() == testCase.content_id.hexToSeqByte() encoded.toContentId().toBytesBE() == testCase.content_id.hexToSeqByte()
let decoded = encoded.decode() let decoded = ContentKey.decode(encoded)
check: check:
decoded.isOk() decoded.isOk()
decoded.value().contentType == contractTrieNode decoded.value().contentType == contractTrieNode
@ -180,7 +96,7 @@ suite "State Content Keys":
encoded.asSeq() == testCase.content_key.hexToSeqByte() encoded.asSeq() == testCase.content_key.hexToSeqByte()
encoded.toContentId().toBytesBE() == testCase.content_id.hexToSeqByte() encoded.toContentId().toBytesBE() == testCase.content_id.hexToSeqByte()
let decoded = encoded.decode() let decoded = ContentKey.decode(encoded)
check: check:
decoded.isOk() decoded.isOk()
decoded.value().contentType == contractCode decoded.value().contentType == contractCode
@ -189,24 +105,24 @@ suite "State Content Keys":
test "Invalid prefix - 0 value": test "Invalid prefix - 0 value":
let encoded = ByteList.init(@[byte 0x00]) let encoded = ByteList.init(@[byte 0x00])
let decoded = decode(encoded) let decoded = ContentKey.decode(encoded)
check decoded.isNone() check decoded.isErr()
test "Invalid prefix - before valid range": test "Invalid prefix - before valid range":
let encoded = ByteList.init(@[byte 0x01]) let encoded = ByteList.init(@[byte 0x01])
let decoded = decode(encoded) let decoded = ContentKey.decode(encoded)
check decoded.isNone() check decoded.isErr()
test "Invalid prefix - after valid range": test "Invalid prefix - after valid range":
let encoded = ByteList.init(@[byte 0x25]) let encoded = ByteList.init(@[byte 0x25])
let decoded = decode(encoded) let decoded = ContentKey.decode(encoded)
check decoded.isNone() check decoded.isErr()
test "Invalid key - empty input": test "Invalid key - empty input":
let encoded = ByteList.init(@[]) let encoded = ByteList.init(@[])
let decoded = decode(encoded) let decoded = ContentKey.decode(encoded)
check decoded.isNone() check decoded.isErr()

View File

@ -0,0 +1,93 @@
# Fluffy
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
# * 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.
import unittest2, stew/byteutils, ../../network/state/state_content
suite "State Content Nibbles":
test "Encode/decode empty nibbles":
const
expected = "00"
nibbles: seq[byte] = @[]
packedNibbles = packNibbles(nibbles)
unpackedNibbles = unpackNibbles(packedNibbles)
let encoded = SSZ.encode(packedNibbles)
check:
encoded.toHex() == expected
unpackedNibbles == nibbles
test "Encode/decode zero nibble":
const
expected = "10"
nibbles: seq[byte] = @[0]
packedNibbles = packNibbles(nibbles)
unpackedNibbles = unpackNibbles(packedNibbles)
let encoded = SSZ.encode(packedNibbles)
check:
encoded.toHex() == expected
unpackedNibbles == nibbles
test "Encode/decode one nibble":
const
expected = "11"
nibbles: seq[byte] = @[1]
packedNibbles = packNibbles(nibbles)
unpackedNibbles = unpackNibbles(packedNibbles)
let encoded = SSZ.encode(packedNibbles)
check:
encoded.toHex() == expected
unpackedNibbles == nibbles
test "Encode/decode even nibbles":
const
expected = "008679e8ed"
nibbles: seq[byte] = @[8, 6, 7, 9, 14, 8, 14, 13]
packedNibbles = packNibbles(nibbles)
unpackedNibbles = unpackNibbles(packedNibbles)
let encoded = SSZ.encode(packedNibbles)
check:
encoded.toHex() == expected
unpackedNibbles == nibbles
test "Encode/decode odd nibbles":
const
expected = "138679e8ed"
nibbles: seq[byte] = @[3, 8, 6, 7, 9, 14, 8, 14, 13]
packedNibbles = packNibbles(nibbles)
unpackedNibbles = unpackNibbles(packedNibbles)
let encoded = SSZ.encode(packedNibbles)
check:
encoded.toHex() == expected
unpackedNibbles == nibbles
test "Encode/decode max length nibbles":
const
expected = "008679e8eda65bd257638cf8cf09b8238888947cc3c0bea2aa2cc3f1c4ac7a3002"
nibbles: seq[byte] =
@[
8, 6, 7, 9, 0xe, 8, 0xe, 0xd, 0xa, 6, 5, 0xb, 0xd, 2, 5, 7, 6, 3, 8, 0xc, 0xf,
8, 0xc, 0xf, 0, 9, 0xb, 8, 2, 3, 8, 8, 8, 8, 9, 4, 7, 0xc, 0xc, 3, 0xc, 0,
0xb, 0xe, 0xa, 2, 0xa, 0xa, 2, 0xc, 0xc, 3, 0xf, 1, 0xc, 4, 0xa, 0xc, 7, 0xa,
3, 0, 0, 2,
]
packedNibbles = packNibbles(nibbles)
unpackedNibbles = unpackNibbles(packedNibbles)
let encoded = SSZ.encode(packedNibbles)
check:
encoded.toHex() == expected
unpackedNibbles == nibbles

View File

@ -30,11 +30,11 @@ suite "State Content Values":
blockHash = BlockHash.fromHex(testCase.block_hash) blockHash = BlockHash.fromHex(testCase.block_hash)
proof = proof =
TrieProof.init(testCase.proof.map((hex) => TrieNode.init(hex.hexToSeqByte()))) TrieProof.init(testCase.proof.map((hex) => TrieNode.init(hex.hexToSeqByte())))
accountTrieNodeOffer = AccountTrieNodeOffer(blockHash: blockHash, proof: proof) accountTrieNodeOffer = AccountTrieNodeOffer.init(proof, blockHash)
encoded = SSZ.encode(accountTrieNodeOffer) encoded = accountTrieNodeOffer.encode()
expected = testCase.content_value.hexToSeqByte() expected = testCase.content_value.hexToSeqByte()
decoded = SSZ.decode(encoded, AccountTrieNodeOffer) decoded = AccountTrieNodeOffer.decode(encoded).get()
check: check:
encoded == expected encoded == expected
@ -54,9 +54,9 @@ suite "State Content Values":
node = TrieNode.init(testCase.trie_node.hexToSeqByte()) node = TrieNode.init(testCase.trie_node.hexToSeqByte())
accountTrieNodeRetrieval = AccountTrieNodeRetrieval(node: node) accountTrieNodeRetrieval = AccountTrieNodeRetrieval(node: node)
encoded = SSZ.encode(accountTrieNodeRetrieval) encoded = accountTrieNodeRetrieval.encode()
expected = testCase.content_value.hexToSeqByte() expected = testCase.content_value.hexToSeqByte()
decoded = SSZ.decode(encoded, AccountTrieNodeRetrieval) decoded = AccountTrieNodeRetrieval.decode(encoded).get()
check: check:
encoded == expected encoded == expected
@ -86,9 +86,9 @@ suite "State Content Values":
blockHash: blockHash, storage_proof: storageProof, account_proof: accountProof blockHash: blockHash, storage_proof: storageProof, account_proof: accountProof
) )
encoded = SSZ.encode(contractTrieNodeOffer) encoded = contractTrieNodeOffer.encode()
expected = testCase.content_value.hexToSeqByte() expected = testCase.content_value.hexToSeqByte()
decoded = SSZ.decode(encoded, ContractTrieNodeOffer) decoded = ContractTrieNodeOffer.decode(encoded).get()
check: check:
encoded == expected encoded == expected
@ -111,9 +111,9 @@ suite "State Content Values":
node = TrieNode.init(testCase.trie_node.hexToSeqByte()) node = TrieNode.init(testCase.trie_node.hexToSeqByte())
contractTrieNodeRetrieval = ContractTrieNodeRetrieval(node: node) contractTrieNodeRetrieval = ContractTrieNodeRetrieval(node: node)
encoded = SSZ.encode(contractTrieNodeRetrieval) encoded = contractTrieNodeRetrieval.encode()
expected = testCase.content_value.hexToSeqByte() expected = testCase.content_value.hexToSeqByte()
decoded = SSZ.decode(encoded, ContractTrieNodeRetrieval) decoded = ContractTrieNodeRetrieval.decode(encoded).get()
check: check:
encoded == expected encoded == expected
@ -140,9 +140,9 @@ suite "State Content Values":
contractCodeOffer = contractCodeOffer =
ContractCodeOffer(code: code, blockHash: blockHash, accountProof: accountProof) ContractCodeOffer(code: code, blockHash: blockHash, accountProof: accountProof)
encoded = SSZ.encode(contractCodeOffer) encoded = contractCodeOffer.encode()
expected = testCase.content_value.hexToSeqByte() expected = testCase.content_value.hexToSeqByte()
decoded = SSZ.decode(encoded, ContractCodeOffer) decoded = ContractCodeOffer.decode(encoded).get()
check: check:
encoded == expected encoded == expected
@ -162,9 +162,9 @@ suite "State Content Values":
code = Bytecode.init(testCase.bytecode.hexToSeqByte()) code = Bytecode.init(testCase.bytecode.hexToSeqByte())
contractCodeRetrieval = ContractCodeRetrieval(code: code) contractCodeRetrieval = ContractCodeRetrieval(code: code)
encoded = SSZ.encode(contractCodeRetrieval) encoded = contractCodeRetrieval.encode()
expected = testCase.content_value.hexToSeqByte() expected = testCase.content_value.hexToSeqByte()
decoded = SSZ.decode(encoded, ContractCodeRetrieval) decoded = ContractCodeRetrieval.decode(encoded).get()
check: check:
encoded == expected encoded == expected

View File

@ -53,11 +53,8 @@ procSuite "State Network":
contentKey = ContentKey( contentKey = ContentKey(
contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey
) )
contentId = toContentId(contentKey) contentId = toContentId(contentKey.encode())
value = RetrievalContentValue( value = AccountTrieNodeRetrieval(node: TrieNode.init(v))
contentType: accountTrieNode,
accountTrieNode: AccountTrieNodeRetrieval(node: TrieNode.init(v)),
)
discard proto1.contentDB.put( discard proto1.contentDB.put(
contentId, value.encode(), proto1.portalProtocol.localNode.id contentId, value.encode(), proto1.portalProtocol.localNode.id
@ -72,7 +69,7 @@ procSuite "State Network":
contentKey = ContentKey( contentKey = ContentKey(
contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey
) )
contentId = toContentId(contentKey) contentId = toContentId(contentKey.encode())
# Note: GetContent and thus the lookup here is not really needed, as we # Note: GetContent and thus the lookup here is not really needed, as we
# only have to request data to one node. # only have to request data to one node.
@ -129,11 +126,8 @@ procSuite "State Network":
contentKey = ContentKey( contentKey = ContentKey(
contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey
) )
contentId = toContentId(contentKey) contentId = toContentId(contentKey.encode())
value = RetrievalContentValue( value = AccountTrieNodeRetrieval(node: TrieNode.init(v))
contentType: accountTrieNode,
accountTrieNode: AccountTrieNodeRetrieval(node: TrieNode.init(v)),
)
discard proto2.contentDB.put( discard proto2.contentDB.put(
contentId, value.encode(), proto2.portalProtocol.localNode.id contentId, value.encode(), proto2.portalProtocol.localNode.id

View File

@ -13,7 +13,7 @@ import
eth/p2p/discoveryv5/protocol as discv5_protocol, eth/p2p/discoveryv5/protocol as discv5_protocol,
../../network/wire/[portal_protocol, portal_stream], ../../network/wire/[portal_protocol, portal_stream],
../../network/history/[history_content, history_network], ../../network/history/[history_content, history_network],
../../network/state/[state_content, state_network], ../../network/state/[state_content, state_network, state_gossip],
../../database/content_db, ../../database/content_db,
.././test_helpers, .././test_helpers,
../../eth_data/yaml_utils ../../eth_data/yaml_utils
@ -74,7 +74,7 @@ procSuite "State Network Gossip":
header: ByteList.init(headerRlp), proof: BlockHeaderProof.init() header: ByteList.init(headerRlp), proof: BlockHeaderProof.init()
) )
value = recursiveGossipSteps[0].content_value.hexToSeqByte() value = recursiveGossipSteps[0].content_value.hexToSeqByte()
decodedValue = SSZ.decode(value, AccountTrieNodeOffer) decodedValue = AccountTrieNodeOffer.decode(value).get()
contentKey = history_content.ContentKey contentKey = history_content.ContentKey
.init(history_content.ContentType.blockHeader, decodedValue.blockHash) .init(history_content.ContentType.blockHeader, decodedValue.blockHash)
.encode() .encode()
@ -89,28 +89,22 @@ procSuite "State Network Gossip":
nextNode = clients[i + 1] nextNode = clients[i + 1]
key = ByteList.init(pair.content_key.hexToSeqByte()) key = ByteList.init(pair.content_key.hexToSeqByte())
decodedKey = state_content.decode(key).valueOr: decodedKey = state_content.ContentKey.decode(key).valueOr:
raiseAssert "Cannot decode key" raiseAssert "Cannot decode key"
nextKey = ByteList.init(recursiveGossipSteps[1].content_key.hexToSeqByte()) nextKey = ByteList.init(recursiveGossipSteps[1].content_key.hexToSeqByte())
decodedNextKey = state_content.decode(nextKey).valueOr: decodedNextKey = state_content.ContentKey.decode(nextKey).valueOr:
raiseAssert "Cannot decode key" raiseAssert "Cannot decode key"
value = pair.content_value.hexToSeqByte() value = pair.content_value.hexToSeqByte()
decodedValue = SSZ.decode(value, AccountTrieNodeOffer) decodedValue = AccountTrieNodeOffer.decode(value).get()
offerValue =
OfferContentValue(contentType: accountTrieNode, accountTrieNode: decodedValue)
nextValue = recursiveGossipSteps[1].content_value.hexToSeqByte() nextValue = recursiveGossipSteps[1].content_value.hexToSeqByte()
nextDecodedValue = SSZ.decode(nextValue, AccountTrieNodeOffer) nextDecodedValue = AccountTrieNodeOffer.decode(nextValue).get()
nextOfferValue = OfferContentValue( nextRetrievalValue = nextDecodedValue.toRetrievalValue().encode()
contentType: accountTrieNode, accountTrieNode: nextDecodedValue
)
nextRetrievalValue = nextOfferValue.offerContentToRetrievalContent().encode()
if i == 0: if i == 0:
await currentNode.portalProtocol.gossipContent( await currentNode.portalProtocol.gossipOffer(
Opt.none(NodeId), key, decodedKey, value, offerValue Opt.none(NodeId), decodedKey.accountTrieNodeKey, decodedValue
) )
await sleepAsync(100.milliseconds) #TODO figure out how to get rid of this sleep await sleepAsync(100.milliseconds) #TODO figure out how to get rid of this sleep

View File

@ -60,16 +60,14 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error raiseAssert "Cannot read test vector: " & error
for testData in testCase: for testData in testCase:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
let contentValueRetrieval = SSZ.decode( ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
testData.content_value_retrieval.hexToSeqByte(), AccountTrieNodeRetrieval let contentValueRetrieval = AccountTrieNodeRetrieval
) .decode(testData.content_value_retrieval.hexToSeqByte())
.get()
check: check:
validateFetchedAccountTrieNode( validateRetrieval(contentKey.accountTrieNodeKey, contentValueRetrieval).isOk()
contentKey.accountTrieNodeKey, contentValueRetrieval
)
.isOk()
test "Validate invalid AccountTrieNodeRetrieval nodes": test "Validate invalid AccountTrieNodeRetrieval nodes":
const file = testVectorDir / "account_trie_node.yaml" const file = testVectorDir / "account_trie_node.yaml"
@ -78,16 +76,15 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error raiseAssert "Cannot read test vector: " & error
for testData in testCase: for testData in testCase:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
var contentValueRetrieval = SSZ.decode( ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
testData.content_value_retrieval.hexToSeqByte(), AccountTrieNodeRetrieval var contentValueRetrieval = AccountTrieNodeRetrieval
) .decode(testData.content_value_retrieval.hexToSeqByte())
.get()
contentValueRetrieval.node[^1] += 1 # Modify node hash contentValueRetrieval.node[^1] += 1 # Modify node hash
let res = validateFetchedAccountTrieNode( let res = validateRetrieval(contentKey.accountTrieNodeKey, contentValueRetrieval)
contentKey.accountTrieNodeKey, contentValueRetrieval
)
check: check:
res.isErr() res.isErr()
res.error() == res.error() ==
@ -100,16 +97,14 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error raiseAssert "Cannot read test vector: " & error
for testData in testCase: for testData in testCase:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
let contentValueRetrieval = SSZ.decode( ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
testData.content_value_retrieval.hexToSeqByte(), ContractTrieNodeRetrieval let contentValueRetrieval = ContractTrieNodeRetrieval
) .decode(testData.content_value_retrieval.hexToSeqByte())
.get()
check: check:
validateFetchedContractTrieNode( validateRetrieval(contentKey.contractTrieNodeKey, contentValueRetrieval).isOk()
contentKey.contractTrieNodeKey, contentValueRetrieval
)
.isOk()
test "Validate invalid ContractTrieNodeRetrieval nodes": test "Validate invalid ContractTrieNodeRetrieval nodes":
const file = testVectorDir / "contract_storage_trie_node.yaml" const file = testVectorDir / "contract_storage_trie_node.yaml"
@ -118,16 +113,15 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error raiseAssert "Cannot read test vector: " & error
for testData in testCase: for testData in testCase:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
var contentValueRetrieval = SSZ.decode( ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
testData.content_value_retrieval.hexToSeqByte(), ContractTrieNodeRetrieval var contentValueRetrieval = ContractTrieNodeRetrieval
) .decode(testData.content_value_retrieval.hexToSeqByte())
.get()
contentValueRetrieval.node[^1] += 1 # Modify node hash contentValueRetrieval.node[^1] += 1 # Modify node hash
let res = validateFetchedContractTrieNode( let res = validateRetrieval(contentKey.contractTrieNodeKey, contentValueRetrieval)
contentKey.contractTrieNodeKey, contentValueRetrieval
)
check: check:
res.isErr() res.isErr()
res.error() == res.error() ==
@ -140,14 +134,14 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error raiseAssert "Cannot read test vector: " & error
for testData in testCase: for testData in testCase:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
let contentValueRetrieval = SSZ.decode( ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
testData.content_value_retrieval.hexToSeqByte(), ContractCodeRetrieval let contentValueRetrieval = ContractCodeRetrieval
) .decode(testData.content_value_retrieval.hexToSeqByte())
.get()
check: check:
validateFetchedContractCode(contentKey.contractCodeKey, contentValueRetrieval) validateRetrieval(contentKey.contractCodeKey, contentValueRetrieval).isOk()
.isOk()
test "Validate invalid ContractCodeRetrieval nodes": test "Validate invalid ContractCodeRetrieval nodes":
const file = testVectorDir / "contract_bytecode.yaml" const file = testVectorDir / "contract_bytecode.yaml"
@ -156,15 +150,15 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error raiseAssert "Cannot read test vector: " & error
for testData in testCase: for testData in testCase:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
var contentValueRetrieval = SSZ.decode( ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
testData.content_value_retrieval.hexToSeqByte(), ContractCodeRetrieval var contentValueRetrieval = ContractCodeRetrieval
) .decode(testData.content_value_retrieval.hexToSeqByte())
.get()
contentValueRetrieval.code[^1] += 1 # Modify node hash contentValueRetrieval.code[^1] += 1 # Modify node hash
let res = let res = validateRetrieval(contentKey.contractCodeKey, contentValueRetrieval)
validateFetchedContractCode(contentKey.contractCodeKey, contentValueRetrieval)
check: check:
res.isErr() res.isErr()
res.error() == "hash of fetched bytecode doesn't match the expected code hash" res.error() == "hash of fetched bytecode doesn't match the expected code hash"
@ -178,34 +172,30 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error raiseAssert "Cannot read test vector: " & error
for i, testData in testCase: for i, testData in testCase:
var stateRoot = KeccakHash.init(testData.state_root.hexToSeqByte()) var stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
block: block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer = let contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), AccountTrieNodeOffer) AccountTrieNodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
check: check:
validateOfferedAccountTrieNode( validateOffer(stateRoot, contentKey.accountTrieNodeKey, contentValueOffer)
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer
)
.isOk() .isOk()
if i == 1: if i == 1:
continue # second test case only has root node and no recursive gossip continue # second test case only has root node and no recursive gossip
let contentKey = let contentKey = ContentKey
decode(testData.recursive_gossip.content_key.hexToSeqByte().ByteList).get() .decode(testData.recursive_gossip.content_key.hexToSeqByte().ByteList)
let contentValueOffer = SSZ.decode( .get()
testData.recursive_gossip.content_value_offer.hexToSeqByte(), let contentValueOffer = AccountTrieNodeOffer
AccountTrieNodeOffer, .decode(testData.recursive_gossip.content_value_offer.hexToSeqByte())
) .get()
check: check:
validateOfferedAccountTrieNode( validateOffer(stateRoot, contentKey.accountTrieNodeKey, contentValueOffer).isOk()
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer
)
.isOk()
test "Validate invalid AccountTrieNodeOffer nodes - bad state roots": test "Validate invalid AccountTrieNodeOffer nodes - bad state roots":
const file = testVectorDir / "account_trie_node.yaml" const file = testVectorDir / "account_trie_node.yaml"
@ -219,15 +209,15 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error raiseAssert "Cannot read test vector: " & error
for i, testData in testCase: for i, testData in testCase:
var stateRoot = KeccakHash.init(stateRoots[i].hexToSeqByte()) var stateRoot = KeccakHash.fromBytes(stateRoots[i].hexToSeqByte())
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer = let contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), AccountTrieNodeOffer) AccountTrieNodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
let res = validateOfferedAccountTrieNode( let res =
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer validateOffer(stateRoot, contentKey.accountTrieNodeKey, contentValueOffer)
)
check: check:
res.isErr() res.isErr()
res.error() == "hash of proof root node doesn't match the expected root hash" res.error() == "hash of proof root node doesn't match the expected root hash"
@ -239,17 +229,17 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error raiseAssert "Cannot read test vector: " & error
for i, testData in testCase: for i, testData in testCase:
var stateRoot = KeccakHash.init(testData.state_root.hexToSeqByte()) var stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer = var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), AccountTrieNodeOffer) AccountTrieNodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
contentValueOffer.proof[0][0] += 1.byte contentValueOffer.proof[0][0] += 1.byte
let res = validateOfferedAccountTrieNode( let res =
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer validateOffer(stateRoot, contentKey.accountTrieNodeKey, contentValueOffer)
)
check: check:
res.isErr() res.isErr()
res.error() == "hash of proof root node doesn't match the expected root hash" res.error() == "hash of proof root node doesn't match the expected root hash"
@ -257,33 +247,33 @@ suite "State Validation":
for i, testData in testCase: for i, testData in testCase:
if i == 1: if i == 1:
continue # second test case only has root node continue # second test case only has root node
var stateRoot = KeccakHash.init(testData.state_root.hexToSeqByte()) var stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer = var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), AccountTrieNodeOffer) AccountTrieNodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
contentValueOffer.proof[^2][^2] += 1.byte contentValueOffer.proof[^2][^2] += 1.byte
let res = validateOfferedAccountTrieNode( let res =
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer validateOffer(stateRoot, contentKey.accountTrieNodeKey, contentValueOffer)
)
check: check:
res.isErr() res.isErr()
"hash of next node doesn't match the expected" in res.error() "hash of next node doesn't match the expected" in res.error()
for i, testData in testCase: for i, testData in testCase:
var stateRoot = KeccakHash.init(testData.state_root.hexToSeqByte()) var stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer = var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), AccountTrieNodeOffer) AccountTrieNodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
contentValueOffer.proof[^1][^1] += 1.byte contentValueOffer.proof[^1][^1] += 1.byte
let res = validateOfferedAccountTrieNode( let res =
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer validateOffer(stateRoot, contentKey.accountTrieNodeKey, contentValueOffer)
)
check: check:
res.isErr() res.isErr()
@ -296,34 +286,31 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error raiseAssert "Cannot read test vector: " & error
for i, testData in testCase: for i, testData in testCase:
var stateRoot = KeccakHash.init(testData.state_root.hexToSeqByte()) var stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
block: block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
let contentValueOffer = ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractTrieNodeOffer) let contentValueOffer = ContractTrieNodeOffer
.decode(testData.content_value_offer.hexToSeqByte())
.get()
check: check:
validateOfferedContractTrieNode( validateOffer(stateRoot, contentKey.contractTrieNodeKey, contentValueOffer)
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
.isOk() .isOk()
if i == 1: if i == 1:
continue # second test case has no recursive gossip continue # second test case has no recursive gossip
let contentKey = let contentKey = ContentKey
decode(testData.recursive_gossip.content_key.hexToSeqByte().ByteList).get() .decode(testData.recursive_gossip.content_key.hexToSeqByte().ByteList)
let contentValueOffer = SSZ.decode( .get()
testData.recursive_gossip.content_value_offer.hexToSeqByte(), let contentValueOffer = ContractTrieNodeOffer
ContractTrieNodeOffer, .decode(testData.recursive_gossip.content_value_offer.hexToSeqByte())
) .get()
check: check:
validateOfferedContractTrieNode( validateOffer(stateRoot, contentKey.contractTrieNodeKey, contentValueOffer).isOk()
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
.isOk()
test "Validate invalid ContractTrieNodeOffer nodes - bad state roots": test "Validate invalid ContractTrieNodeOffer nodes - bad state roots":
const file = testVectorDir / "contract_storage_trie_node.yaml" const file = testVectorDir / "contract_storage_trie_node.yaml"
@ -336,15 +323,15 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error raiseAssert "Cannot read test vector: " & error
for i, testData in testCase: for i, testData in testCase:
var stateRoot = KeccakHash.init(stateRoots[i].hexToSeqByte()) var stateRoot = KeccakHash.fromBytes(stateRoots[i].hexToSeqByte())
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer = let contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractTrieNodeOffer) ContractTrieNodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
let res = validateOfferedContractTrieNode( let res =
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer validateOffer(stateRoot, contentKey.contractTrieNodeKey, contentValueOffer)
)
check: check:
res.isErr() res.isErr()
res.error() == "hash of proof root node doesn't match the expected root hash" res.error() == "hash of proof root node doesn't match the expected root hash"
@ -356,73 +343,75 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error raiseAssert "Cannot read test vector: " & error
for i, testData in testCase: for i, testData in testCase:
var stateRoot = KeccakHash.init(testData.state_root.hexToSeqByte()) var stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
block: block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
var contentValueOffer = ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractTrieNodeOffer) var contentValueOffer = ContractTrieNodeOffer
.decode(testData.content_value_offer.hexToSeqByte())
.get()
contentValueOffer.accountProof[0][0] += 1.byte contentValueOffer.accountProof[0][0] += 1.byte
let res = validateOfferedContractTrieNode( let res =
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer validateOffer(stateRoot, contentKey.contractTrieNodeKey, contentValueOffer)
)
check: check:
res.isErr() res.isErr()
res.error() == "hash of proof root node doesn't match the expected root hash" res.error() == "hash of proof root node doesn't match the expected root hash"
block: block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
var contentValueOffer = ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractTrieNodeOffer) var contentValueOffer = ContractTrieNodeOffer
.decode(testData.content_value_offer.hexToSeqByte())
.get()
contentValueOffer.storageProof[0][0] += 1.byte contentValueOffer.storageProof[0][0] += 1.byte
let res = validateOfferedContractTrieNode( let res =
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer validateOffer(stateRoot, contentKey.contractTrieNodeKey, contentValueOffer)
)
check: check:
res.isErr() res.isErr()
res.error() == "hash of proof root node doesn't match the expected root hash" res.error() == "hash of proof root node doesn't match the expected root hash"
block: block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
var contentValueOffer = ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractTrieNodeOffer) var contentValueOffer = ContractTrieNodeOffer
.decode(testData.content_value_offer.hexToSeqByte())
.get()
contentValueOffer.accountProof[^1][^1] += 1.byte contentValueOffer.accountProof[^1][^1] += 1.byte
check: check:
validateOfferedContractTrieNode( validateOffer(stateRoot, contentKey.contractTrieNodeKey, contentValueOffer)
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
.isErr() .isErr()
block: block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
var contentValueOffer = ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractTrieNodeOffer) var contentValueOffer = ContractTrieNodeOffer
.decode(testData.content_value_offer.hexToSeqByte())
.get()
contentValueOffer.storageProof[^1][^1] += 1.byte contentValueOffer.storageProof[^1][^1] += 1.byte
check: check:
validateOfferedContractTrieNode( validateOffer(stateRoot, contentKey.contractTrieNodeKey, contentValueOffer)
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
.isErr() .isErr()
block: block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
var contentValueOffer = ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractTrieNodeOffer) var contentValueOffer = ContractTrieNodeOffer
.decode(testData.content_value_offer.hexToSeqByte())
.get()
contentValueOffer.accountProof[^2][^2] += 1.byte contentValueOffer.accountProof[^2][^2] += 1.byte
check: check:
validateOfferedContractTrieNode( validateOffer(stateRoot, contentKey.contractTrieNodeKey, contentValueOffer)
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
.isErr() .isErr()
# Contract bytecode offer validation tests # Contract bytecode offer validation tests
@ -434,17 +423,15 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error raiseAssert "Cannot read test vector: " & error
for i, testData in testCase: for i, testData in testCase:
var stateRoot = KeccakHash.init(testData.state_root.hexToSeqByte()) var stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer = let contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractCodeOffer) ContractCodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
check: check:
validateOfferedContractCode( validateOffer(stateRoot, contentKey.contractCodeKey, contentValueOffer).isOk()
stateRoot, contentKey.contractCodeKey, contentValueOffer
)
.isOk()
test "Validate invalid ContractCodeOffer nodes - bad state root": test "Validate invalid ContractCodeOffer nodes - bad state root":
const file = testVectorDir / "contract_bytecode.yaml" const file = testVectorDir / "contract_bytecode.yaml"
@ -455,15 +442,14 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error raiseAssert "Cannot read test vector: " & error
for i, testData in testCase: for i, testData in testCase:
var stateRoot = KeccakHash.init(stateRoots[i].hexToSeqByte()) var stateRoot = KeccakHash.fromBytes(stateRoots[i].hexToSeqByte())
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer = let contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractCodeOffer) ContractCodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
let res = validateOfferedContractCode( let res = validateOffer(stateRoot, contentKey.contractCodeKey, contentValueOffer)
stateRoot, contentKey.contractCodeKey, contentValueOffer
)
check: check:
res.isErr() res.isErr()
res.error() == "hash of proof root node doesn't match the expected root hash" res.error() == "hash of proof root node doesn't match the expected root hash"
@ -475,59 +461,57 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error raiseAssert "Cannot read test vector: " & error
for i, testData in testCase: for i, testData in testCase:
var stateRoot = KeccakHash.init(testData.state_root.hexToSeqByte()) var stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
block: block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer = var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractCodeOffer) ContractCodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
contentValueOffer.accountProof[0][0] += 1.byte contentValueOffer.accountProof[0][0] += 1.byte
let res = validateOfferedContractCode( let res =
stateRoot, contentKey.contractCodeKey, contentValueOffer validateOffer(stateRoot, contentKey.contractCodeKey, contentValueOffer)
)
check: check:
res.isErr() res.isErr()
res.error() == "hash of proof root node doesn't match the expected root hash" res.error() == "hash of proof root node doesn't match the expected root hash"
block: block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer = var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractCodeOffer) ContractCodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
contentValueOffer.code[0] += 1.byte contentValueOffer.code[0] += 1.byte
let res = validateOfferedContractCode( let res =
stateRoot, contentKey.contractCodeKey, contentValueOffer validateOffer(stateRoot, contentKey.contractCodeKey, contentValueOffer)
)
check: check:
res.isErr() res.isErr()
res.error() == "hash of offered bytecode doesn't match the expected code hash" res.error() == "hash of offered bytecode doesn't match the expected code hash"
block: block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer = var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractCodeOffer) ContractCodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
contentValueOffer.accountProof[^1][^1] += 1.byte contentValueOffer.accountProof[^1][^1] += 1.byte
check: check:
validateOfferedContractCode( validateOffer(stateRoot, contentKey.contractCodeKey, contentValueOffer).isErr()
stateRoot, contentKey.contractCodeKey, contentValueOffer
)
.isErr()
block: block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get() let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer = var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractCodeOffer) ContractCodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
contentValueOffer.code[^1] += 1.byte contentValueOffer.code[^1] += 1.byte
let res = validateOfferedContractCode( let res =
stateRoot, contentKey.contractCodeKey, contentValueOffer validateOffer(stateRoot, contentKey.contractCodeKey, contentValueOffer)
)
check: check:
res.isErr() res.isErr()
res.error() == "hash of offered bytecode doesn't match the expected code hash" res.error() == "hash of offered bytecode doesn't match the expected code hash"
@ -549,17 +533,15 @@ suite "State Validation":
if i == 1: if i == 1:
continue continue
var stateRoot = KeccakHash.init(stateRoots[i].hexToSeqByte()) var stateRoot = KeccakHash.fromBytes(stateRoots[i].hexToSeqByte())
for kv in testData.recursive_gossip: for kv in testData.recursive_gossip:
let contentKey = decode(kv.content_key.hexToSeqByte().ByteList).get() let contentKey = ContentKey.decode(kv.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer = let contentValueOffer =
SSZ.decode(kv.content_value.hexToSeqByte(), AccountTrieNodeOffer) AccountTrieNodeOffer.decode(kv.content_value.hexToSeqByte()).get()
check: check:
validateOfferedAccountTrieNode( validateOffer(stateRoot, contentKey.accountTrieNodeKey, contentValueOffer)
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer
)
.isOk() .isOk()
test "Validate valid ContractTrieNodeOffer recursive gossip nodes": test "Validate valid ContractTrieNodeOffer recursive gossip nodes":
@ -572,15 +554,13 @@ suite "State Validation":
if i != 1: if i != 1:
continue continue
var stateRoot = KeccakHash.init(testData.state_root.hexToSeqByte()) var stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
for kv in testData.recursive_gossip: for kv in testData.recursive_gossip:
let contentKey = decode(kv.content_key.hexToSeqByte().ByteList).get() let contentKey = ContentKey.decode(kv.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer = let contentValueOffer =
SSZ.decode(kv.content_value.hexToSeqByte(), ContractTrieNodeOffer) ContractTrieNodeOffer.decode(kv.content_value.hexToSeqByte()).get()
check: check:
validateOfferedContractTrieNode( validateOffer(stateRoot, contentKey.contractTrieNodeKey, contentValueOffer)
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
.isOk() .isOk()

View File

@ -33,18 +33,15 @@ template checkValidProofsForExistingLeafs(
nodeHash: keccakHash(accountProof[^1].asSeq()), nodeHash: keccakHash(accountProof[^1].asSeq()),
) )
accountTrieOffer = AccountTrieNodeOffer(proof: accountProof) accountTrieOffer = AccountTrieNodeOffer(proof: accountProof)
proofResult = validateOfferedAccountTrieNode( proofResult =
accountState.rootHash(), accountTrieNodeKey, accountTrieOffer validateOffer(accountState.rootHash(), accountTrieNodeKey, accountTrieOffer)
)
check proofResult.isOk() check proofResult.isOk()
let let
contractCodeKey = ContractCodeKey(address: address, codeHash: acc.codeHash) contractCodeKey = ContractCodeKey(address: address, codeHash: acc.codeHash)
contractCode = contractCode =
ContractCodeOffer(code: Bytecode.init(account.code), accountProof: accountProof) ContractCodeOffer(code: Bytecode.init(account.code), accountProof: accountProof)
codeResult = validateOfferedContractCode( codeResult = validateOffer(accountState.rootHash(), contractCodeKey, contractCode)
accountState.rootHash(), contractCodeKey, contractCode
)
check codeResult.isOk() check codeResult.isOk()
if account.code.len() > 0: if account.code.len() > 0:
@ -62,7 +59,7 @@ template checkValidProofsForExistingLeafs(
contractTrieOffer = ContractTrieNodeOffer( contractTrieOffer = ContractTrieNodeOffer(
storageProof: storageProof, accountProof: accountProof storageProof: storageProof, accountProof: accountProof
) )
proofResult = validateOfferedContractTrieNode( proofResult = validateOffer(
accountState.rootHash(), contractTrieNodeKey, contractTrieOffer accountState.rootHash(), contractTrieNodeKey, contractTrieOffer
) )
check proofResult.isOk() check proofResult.isOk()
@ -85,9 +82,8 @@ template checkInvalidProofsWithBadValue(
accountProof[^1][^1] += 1 # bad account leaf value accountProof[^1][^1] += 1 # bad account leaf value
let let
accountTrieOffer = AccountTrieNodeOffer(proof: accountProof) accountTrieOffer = AccountTrieNodeOffer(proof: accountProof)
proofResult = validateOfferedAccountTrieNode( proofResult =
accountState.rootHash(), accountTrieNodeKey, accountTrieOffer validateOffer(accountState.rootHash(), accountTrieNodeKey, accountTrieOffer)
)
check proofResult.isErr() check proofResult.isErr()
let let
@ -96,9 +92,7 @@ template checkInvalidProofsWithBadValue(
code: Bytecode.init(@[1u8, 2, 3]), # bad code value code: Bytecode.init(@[1u8, 2, 3]), # bad code value
accountProof: accountProof, accountProof: accountProof,
) )
codeResult = validateOfferedContractCode( codeResult = validateOffer(accountState.rootHash(), contractCodeKey, contractCode)
accountState.rootHash(), contractCodeKey, contractCode
)
check codeResult.isErr() check codeResult.isErr()
if account.code.len() > 0: if account.code.len() > 0:
@ -118,7 +112,7 @@ template checkInvalidProofsWithBadValue(
contractTrieOffer = ContractTrieNodeOffer( contractTrieOffer = ContractTrieNodeOffer(
storageProof: storageProof, accountProof: accountProof storageProof: storageProof, accountProof: accountProof
) )
proofResult = validateOfferedContractTrieNode( proofResult = validateOffer(
accountState.rootHash(), contractTrieNodeKey, contractTrieOffer accountState.rootHash(), contractTrieNodeKey, contractTrieOffer
) )
check proofResult.isErr() check proofResult.isErr()

View File

@ -16,7 +16,7 @@ import
stint, stint,
nimcrypto/hash, nimcrypto/hash,
eth/trie/[hexary, db, trie_defs], eth/trie/[hexary, db, trie_defs],
../../network/state/state_validation, ../../network/state/[state_content, state_validation],
./state_test_helpers ./state_test_helpers
proc getKeyBytes(i: int): seq[byte] = proc getKeyBytes(i: int): seq[byte] =
@ -88,8 +88,8 @@ suite "MPT trie proof verification":
check: check:
res.isOk() res.isOk()
test "Validate proof bytes": test "Validate proof bytes - 3 keys":
var trie = initHexaryTrie(newMemoryDB(), isPruning = false) var trie = initHexaryTrie(newMemoryDB())
trie.put("doe".toBytes, "reindeer".toBytes) trie.put("doe".toBytes, "reindeer".toBytes)
trie.put("dog".toBytes, "puppy".toBytes) trie.put("dog".toBytes, "puppy".toBytes)
@ -101,35 +101,25 @@ suite "MPT trie proof verification":
let let
key = "doe".toBytes key = "doe".toBytes
proof = trie.getTrieProof(key) proof = trie.getTrieProof(key)
res = validateTrieProof(rootHash, key.asNibbles(), proof) check validateTrieProof(rootHash, key.asNibbles(), proof).isOk()
check:
res.isOk()
block: block:
let let
key = "dog".toBytes key = "dog".toBytes
proof = trie.getTrieProof(key) proof = trie.getTrieProof(key)
res = validateTrieProof(rootHash, key.asNibbles(), proof) check validateTrieProof(rootHash, key.asNibbles(), proof).isOk()
check:
res.isOk()
block: block:
let let
key = "dogglesworth".toBytes key = "dogglesworth".toBytes
proof = trie.getTrieProof(key) proof = trie.getTrieProof(key)
res = validateTrieProof(rootHash, key.asNibbles(), proof) check validateTrieProof(rootHash, key.asNibbles(), proof).isOk()
check:
res.isOk()
block: block:
let let
key = "dogg".toBytes key = "dogg".toBytes
proof = trie.getTrieProof(key) proof = trie.getTrieProof(key)
res = validateTrieProof(rootHash, key.asNibbles(), proof) res = validateTrieProof(rootHash, key.asNibbles(), proof)
check: check:
res.isErr() res.isErr()
res.error() == "not enough nibbles to validate node prefix" res.error() == "not enough nibbles to validate node prefix"
@ -139,7 +129,6 @@ suite "MPT trie proof verification":
key = "dogz".toBytes key = "dogz".toBytes
proof = trie.getTrieProof(key) proof = trie.getTrieProof(key)
res = validateTrieProof(rootHash, key.asNibbles(), proof) res = validateTrieProof(rootHash, key.asNibbles(), proof)
check: check:
res.isErr() res.isErr()
res.error() == "path contains more nibbles than expected for proof" res.error() == "path contains more nibbles than expected for proof"
@ -149,7 +138,6 @@ suite "MPT trie proof verification":
key = "doe".toBytes key = "doe".toBytes
proof = newSeq[seq[byte]]().asTrieProof() proof = newSeq[seq[byte]]().asTrieProof()
res = validateTrieProof(rootHash, key.asNibbles(), proof) res = validateTrieProof(rootHash, key.asNibbles(), proof)
check: check:
res.isErr() res.isErr()
res.error() == "proof is empty" res.error() == "proof is empty"
@ -159,7 +147,53 @@ suite "MPT trie proof verification":
key = "doe".toBytes key = "doe".toBytes
proof = @["aaa".toBytes, "ccc".toBytes].asTrieProof() proof = @["aaa".toBytes, "ccc".toBytes].asTrieProof()
res = validateTrieProof(rootHash, key.asNibbles(), proof) res = validateTrieProof(rootHash, key.asNibbles(), proof)
check: check:
res.isErr() res.isErr()
res.error() == "hash of proof root node doesn't match the expected root hash" res.error() == "hash of proof root node doesn't match the expected root hash"
test "Validate proof bytes - 4 keys":
var trie = initHexaryTrie(newMemoryDB())
let
# leaf nodes
kv1 = "0xa7113550".hexToSeqByte()
kv2 = "0xa77d3370".hexToSeqByte()
kv3 = "0xa7f93650".hexToSeqByte()
kv4 = "0xa77d3970".hexToSeqByte()
kv5 = "".hexToSeqByte() # root/first extension node
kv6 = "0xa7".hexToSeqByte() # first branch node
# leaf nodes without key ending
kv7 = "0xa77d33".hexToSeqByte()
kv8 = "0xa77d39".hexToSeqByte()
# failure cases
kv9 = "0xa0".hexToSeqByte()
kv10 = "0xa77d".hexToSeqByte()
kv11 = "0xa71135".hexToSeqByte()
kv12 = "0xa711355000".hexToSeqByte()
kv13 = "0xa711".hexToSeqByte()
trie.put(kv1, kv1)
trie.put(kv2, kv2)
trie.put(kv3, kv3)
trie.put(kv4, kv4)
let rootHash = trie.rootHash
check:
validateTrieProof(rootHash, kv1.asNibbles(), trie.getTrieProof(kv1)).isOk()
validateTrieProof(rootHash, kv2.asNibbles(), trie.getTrieProof(kv2)).isOk()
validateTrieProof(rootHash, kv3.asNibbles(), trie.getTrieProof(kv3)).isOk()
validateTrieProof(rootHash, kv4.asNibbles(), trie.getTrieProof(kv4)).isOk()
validateTrieProof(rootHash, kv5.asNibbles(), trie.getTrieProof(kv5)).isOk()
validateTrieProof(rootHash, kv6.asNibbles(), trie.getTrieProof(kv6)).isOk()
validateTrieProof(rootHash, kv7.asNibbles(), trie.getTrieProof(kv7)).isOk()
validateTrieProof(rootHash, kv8.asNibbles(), trie.getTrieProof(kv8)).isOk()
validateTrieProof(rootHash, kv9.asNibbles(), trie.getTrieProof(kv9)).isErr()
validateTrieProof(rootHash, kv10.asNibbles(), trie.getTrieProof(kv10)).isErr()
validateTrieProof(rootHash, kv11.asNibbles(), trie.getTrieProof(kv11)).isErr()
validateTrieProof(rootHash, kv12.asNibbles(), trie.getTrieProof(kv12)).isErr()
validateTrieProof(rootHash, kv13.asNibbles(), trie.getTrieProof(kv13)).isErr()