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],
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)
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).
# 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
import ./content/content_keys, ./content/content_values, ./content/nibbles
{.push raises: [].}
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
export content_keys, content_values, nibbles

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,
../wire/[portal_protocol, portal_stream, portal_protocol_config],
./state_content,
./state_validation
./state_validation,
./state_gossip
export results
@ -35,228 +36,6 @@ type StateNetwork* = ref object
func toContentIdHandler(contentKey: ByteList): results.Opt[ContentId] =
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*(
T: type StateNetwork,
baseProtocol: protocol.Protocol,
@ -290,34 +69,161 @@ proc new*(
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.} =
try:
while true:
let (maybeSrcNodeId, contentKeys, contentValues) = await n.contentQueue.popFirst()
for i, contentValue in contentValues:
for i, contentValueBytes in contentValues:
let
contentKey = contentKeys[i]
(decodedKey, decodedValue) = decodeKV(contentKey, contentValue).valueOr:
error "Unable to decode offered Key/Value"
contentKeyBytes = contentKeys[i]
contentKey = decodeKey(contentKeyBytes).valueOr:
error "Unable to decode offered content key", contentKeyBytes
continue
(await n.validateContent(decodedKey, decodedValue)).isOkOr:
error "Received offered content failed validation", contentKey, error
let offerRes =
case contentKey.contentType
of unused:
error "Received content with unused content type"
continue
of accountTrieNode:
let contentValue = AccountTrieNodeOffer.decode(contentValueBytes).valueOr:
error "Unable to decode offered AccountTrieNodeOffer content value"
continue
let
valueForRetrieval = decodedValue.offerContentToRetrievalContent().encode()
contentId = n.portalProtocol.toContentId(contentKey).valueOr:
error "Received offered content with invalid content key", contentKey
continue
n.portalProtocol.storeContent(contentKey, contentId, valueForRetrieval)
info "Received offered content validated successfully", contentKey
await gossipContent(
n.portalProtocol, maybeSrcNodeId, contentKey, decodedKey, contentValue,
decodedValue,
await processOffer(
n, maybeSrcNodeId, contentKeyBytes, contentKey.accountTrieNodeKey,
contentValue,
)
of contractTrieNode:
let contentValue = ContractTrieNodeOffer.decode(contentValueBytes).valueOr:
error "Unable to decode offered ContractTrieNodeOffer content value"
continue
await processOffer(
n, maybeSrcNodeId, contentKeyBytes, contentKey.contractTrieNodeKey,
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:
trace "processContentLoop canceled"

View File

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

View File

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

View File

@ -14,90 +14,6 @@ import
const testVectorDir = "./vendor/portal-spec-tests/tests/mainnet/state/serialization/"
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":
const file = testVectorDir & "account_trie_node_key.yaml"
@ -120,7 +36,7 @@ suite "State Content Keys":
encoded.asSeq() == testCase.content_key.hexToSeqByte()
encoded.toContentId().toBytesBE() == testCase.content_id.hexToSeqByte()
let decoded = encoded.decode()
let decoded = ContentKey.decode(encoded)
check:
decoded.isOk()
decoded.value().contentType == accountTrieNode
@ -151,7 +67,7 @@ suite "State Content Keys":
encoded.asSeq() == testCase.content_key.hexToSeqByte()
encoded.toContentId().toBytesBE() == testCase.content_id.hexToSeqByte()
let decoded = encoded.decode()
let decoded = ContentKey.decode(encoded)
check:
decoded.isOk()
decoded.value().contentType == contractTrieNode
@ -180,7 +96,7 @@ suite "State Content Keys":
encoded.asSeq() == testCase.content_key.hexToSeqByte()
encoded.toContentId().toBytesBE() == testCase.content_id.hexToSeqByte()
let decoded = encoded.decode()
let decoded = ContentKey.decode(encoded)
check:
decoded.isOk()
decoded.value().contentType == contractCode
@ -189,24 +105,24 @@ suite "State Content Keys":
test "Invalid prefix - 0 value":
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":
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":
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":
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)
proof =
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()
decoded = SSZ.decode(encoded, AccountTrieNodeOffer)
decoded = AccountTrieNodeOffer.decode(encoded).get()
check:
encoded == expected
@ -54,9 +54,9 @@ suite "State Content Values":
node = TrieNode.init(testCase.trie_node.hexToSeqByte())
accountTrieNodeRetrieval = AccountTrieNodeRetrieval(node: node)
encoded = SSZ.encode(accountTrieNodeRetrieval)
encoded = accountTrieNodeRetrieval.encode()
expected = testCase.content_value.hexToSeqByte()
decoded = SSZ.decode(encoded, AccountTrieNodeRetrieval)
decoded = AccountTrieNodeRetrieval.decode(encoded).get()
check:
encoded == expected
@ -86,9 +86,9 @@ suite "State Content Values":
blockHash: blockHash, storage_proof: storageProof, account_proof: accountProof
)
encoded = SSZ.encode(contractTrieNodeOffer)
encoded = contractTrieNodeOffer.encode()
expected = testCase.content_value.hexToSeqByte()
decoded = SSZ.decode(encoded, ContractTrieNodeOffer)
decoded = ContractTrieNodeOffer.decode(encoded).get()
check:
encoded == expected
@ -111,9 +111,9 @@ suite "State Content Values":
node = TrieNode.init(testCase.trie_node.hexToSeqByte())
contractTrieNodeRetrieval = ContractTrieNodeRetrieval(node: node)
encoded = SSZ.encode(contractTrieNodeRetrieval)
encoded = contractTrieNodeRetrieval.encode()
expected = testCase.content_value.hexToSeqByte()
decoded = SSZ.decode(encoded, ContractTrieNodeRetrieval)
decoded = ContractTrieNodeRetrieval.decode(encoded).get()
check:
encoded == expected
@ -140,9 +140,9 @@ suite "State Content Values":
contractCodeOffer =
ContractCodeOffer(code: code, blockHash: blockHash, accountProof: accountProof)
encoded = SSZ.encode(contractCodeOffer)
encoded = contractCodeOffer.encode()
expected = testCase.content_value.hexToSeqByte()
decoded = SSZ.decode(encoded, ContractCodeOffer)
decoded = ContractCodeOffer.decode(encoded).get()
check:
encoded == expected
@ -162,9 +162,9 @@ suite "State Content Values":
code = Bytecode.init(testCase.bytecode.hexToSeqByte())
contractCodeRetrieval = ContractCodeRetrieval(code: code)
encoded = SSZ.encode(contractCodeRetrieval)
encoded = contractCodeRetrieval.encode()
expected = testCase.content_value.hexToSeqByte()
decoded = SSZ.decode(encoded, ContractCodeRetrieval)
decoded = ContractCodeRetrieval.decode(encoded).get()
check:
encoded == expected

View File

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

View File

@ -13,7 +13,7 @@ import
eth/p2p/discoveryv5/protocol as discv5_protocol,
../../network/wire/[portal_protocol, portal_stream],
../../network/history/[history_content, history_network],
../../network/state/[state_content, state_network],
../../network/state/[state_content, state_network, state_gossip],
../../database/content_db,
.././test_helpers,
../../eth_data/yaml_utils
@ -74,7 +74,7 @@ procSuite "State Network Gossip":
header: ByteList.init(headerRlp), proof: BlockHeaderProof.init()
)
value = recursiveGossipSteps[0].content_value.hexToSeqByte()
decodedValue = SSZ.decode(value, AccountTrieNodeOffer)
decodedValue = AccountTrieNodeOffer.decode(value).get()
contentKey = history_content.ContentKey
.init(history_content.ContentType.blockHeader, decodedValue.blockHash)
.encode()
@ -89,28 +89,22 @@ procSuite "State Network Gossip":
nextNode = clients[i + 1]
key = ByteList.init(pair.content_key.hexToSeqByte())
decodedKey = state_content.decode(key).valueOr:
decodedKey = state_content.ContentKey.decode(key).valueOr:
raiseAssert "Cannot decode key"
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"
value = pair.content_value.hexToSeqByte()
decodedValue = SSZ.decode(value, AccountTrieNodeOffer)
offerValue =
OfferContentValue(contentType: accountTrieNode, accountTrieNode: decodedValue)
decodedValue = AccountTrieNodeOffer.decode(value).get()
nextValue = recursiveGossipSteps[1].content_value.hexToSeqByte()
nextDecodedValue = SSZ.decode(nextValue, AccountTrieNodeOffer)
nextOfferValue = OfferContentValue(
contentType: accountTrieNode, accountTrieNode: nextDecodedValue
)
nextRetrievalValue = nextOfferValue.offerContentToRetrievalContent().encode()
nextDecodedValue = AccountTrieNodeOffer.decode(nextValue).get()
nextRetrievalValue = nextDecodedValue.toRetrievalValue().encode()
if i == 0:
await currentNode.portalProtocol.gossipContent(
Opt.none(NodeId), key, decodedKey, value, offerValue
await currentNode.portalProtocol.gossipOffer(
Opt.none(NodeId), decodedKey.accountTrieNodeKey, decodedValue
)
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
for testData in testCase:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueRetrieval = SSZ.decode(
testData.content_value_retrieval.hexToSeqByte(), AccountTrieNodeRetrieval
)
let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueRetrieval = AccountTrieNodeRetrieval
.decode(testData.content_value_retrieval.hexToSeqByte())
.get()
check:
validateFetchedAccountTrieNode(
contentKey.accountTrieNodeKey, contentValueRetrieval
)
.isOk()
validateRetrieval(contentKey.accountTrieNodeKey, contentValueRetrieval).isOk()
test "Validate invalid AccountTrieNodeRetrieval nodes":
const file = testVectorDir / "account_trie_node.yaml"
@ -78,16 +76,15 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error
for testData in testCase:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueRetrieval = SSZ.decode(
testData.content_value_retrieval.hexToSeqByte(), AccountTrieNodeRetrieval
)
let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueRetrieval = AccountTrieNodeRetrieval
.decode(testData.content_value_retrieval.hexToSeqByte())
.get()
contentValueRetrieval.node[^1] += 1 # Modify node hash
let res = validateFetchedAccountTrieNode(
contentKey.accountTrieNodeKey, contentValueRetrieval
)
let res = validateRetrieval(contentKey.accountTrieNodeKey, contentValueRetrieval)
check:
res.isErr()
res.error() ==
@ -100,16 +97,14 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error
for testData in testCase:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueRetrieval = SSZ.decode(
testData.content_value_retrieval.hexToSeqByte(), ContractTrieNodeRetrieval
)
let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueRetrieval = ContractTrieNodeRetrieval
.decode(testData.content_value_retrieval.hexToSeqByte())
.get()
check:
validateFetchedContractTrieNode(
contentKey.contractTrieNodeKey, contentValueRetrieval
)
.isOk()
validateRetrieval(contentKey.contractTrieNodeKey, contentValueRetrieval).isOk()
test "Validate invalid ContractTrieNodeRetrieval nodes":
const file = testVectorDir / "contract_storage_trie_node.yaml"
@ -118,16 +113,15 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error
for testData in testCase:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueRetrieval = SSZ.decode(
testData.content_value_retrieval.hexToSeqByte(), ContractTrieNodeRetrieval
)
let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueRetrieval = ContractTrieNodeRetrieval
.decode(testData.content_value_retrieval.hexToSeqByte())
.get()
contentValueRetrieval.node[^1] += 1 # Modify node hash
let res = validateFetchedContractTrieNode(
contentKey.contractTrieNodeKey, contentValueRetrieval
)
let res = validateRetrieval(contentKey.contractTrieNodeKey, contentValueRetrieval)
check:
res.isErr()
res.error() ==
@ -140,14 +134,14 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error
for testData in testCase:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueRetrieval = SSZ.decode(
testData.content_value_retrieval.hexToSeqByte(), ContractCodeRetrieval
)
let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueRetrieval = ContractCodeRetrieval
.decode(testData.content_value_retrieval.hexToSeqByte())
.get()
check:
validateFetchedContractCode(contentKey.contractCodeKey, contentValueRetrieval)
.isOk()
validateRetrieval(contentKey.contractCodeKey, contentValueRetrieval).isOk()
test "Validate invalid ContractCodeRetrieval nodes":
const file = testVectorDir / "contract_bytecode.yaml"
@ -156,15 +150,15 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error
for testData in testCase:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueRetrieval = SSZ.decode(
testData.content_value_retrieval.hexToSeqByte(), ContractCodeRetrieval
)
let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueRetrieval = ContractCodeRetrieval
.decode(testData.content_value_retrieval.hexToSeqByte())
.get()
contentValueRetrieval.code[^1] += 1 # Modify node hash
let res =
validateFetchedContractCode(contentKey.contractCodeKey, contentValueRetrieval)
let res = validateRetrieval(contentKey.contractCodeKey, contentValueRetrieval)
check:
res.isErr()
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
for i, testData in testCase:
var stateRoot = KeccakHash.init(testData.state_root.hexToSeqByte())
var stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), AccountTrieNodeOffer)
AccountTrieNodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
check:
validateOfferedAccountTrieNode(
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer
)
validateOffer(stateRoot, contentKey.accountTrieNodeKey, contentValueOffer)
.isOk()
if i == 1:
continue # second test case only has root node and no recursive gossip
let contentKey =
decode(testData.recursive_gossip.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer = SSZ.decode(
testData.recursive_gossip.content_value_offer.hexToSeqByte(),
AccountTrieNodeOffer,
)
let contentKey = ContentKey
.decode(testData.recursive_gossip.content_key.hexToSeqByte().ByteList)
.get()
let contentValueOffer = AccountTrieNodeOffer
.decode(testData.recursive_gossip.content_value_offer.hexToSeqByte())
.get()
check:
validateOfferedAccountTrieNode(
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer
)
.isOk()
validateOffer(stateRoot, contentKey.accountTrieNodeKey, contentValueOffer).isOk()
test "Validate invalid AccountTrieNodeOffer nodes - bad state roots":
const file = testVectorDir / "account_trie_node.yaml"
@ -219,15 +209,15 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error
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 =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), AccountTrieNodeOffer)
AccountTrieNodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
let res = validateOfferedAccountTrieNode(
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer
)
let res =
validateOffer(stateRoot, contentKey.accountTrieNodeKey, contentValueOffer)
check:
res.isErr()
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
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 =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), AccountTrieNodeOffer)
AccountTrieNodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
contentValueOffer.proof[0][0] += 1.byte
let res = validateOfferedAccountTrieNode(
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer
)
let res =
validateOffer(stateRoot, contentKey.accountTrieNodeKey, contentValueOffer)
check:
res.isErr()
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:
if i == 1:
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 =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), AccountTrieNodeOffer)
AccountTrieNodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
contentValueOffer.proof[^2][^2] += 1.byte
let res = validateOfferedAccountTrieNode(
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer
)
let res =
validateOffer(stateRoot, contentKey.accountTrieNodeKey, contentValueOffer)
check:
res.isErr()
"hash of next node doesn't match the expected" in res.error()
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 =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), AccountTrieNodeOffer)
AccountTrieNodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
contentValueOffer.proof[^1][^1] += 1.byte
let res = validateOfferedAccountTrieNode(
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer
)
let res =
validateOffer(stateRoot, contentKey.accountTrieNodeKey, contentValueOffer)
check:
res.isErr()
@ -296,34 +286,31 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error
for i, testData in testCase:
var stateRoot = KeccakHash.init(testData.state_root.hexToSeqByte())
var stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractTrieNodeOffer)
let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer = ContractTrieNodeOffer
.decode(testData.content_value_offer.hexToSeqByte())
.get()
check:
validateOfferedContractTrieNode(
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
validateOffer(stateRoot, contentKey.contractTrieNodeKey, contentValueOffer)
.isOk()
if i == 1:
continue # second test case has no recursive gossip
let contentKey =
decode(testData.recursive_gossip.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer = SSZ.decode(
testData.recursive_gossip.content_value_offer.hexToSeqByte(),
ContractTrieNodeOffer,
)
let contentKey = ContentKey
.decode(testData.recursive_gossip.content_key.hexToSeqByte().ByteList)
.get()
let contentValueOffer = ContractTrieNodeOffer
.decode(testData.recursive_gossip.content_value_offer.hexToSeqByte())
.get()
check:
validateOfferedContractTrieNode(
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
.isOk()
validateOffer(stateRoot, contentKey.contractTrieNodeKey, contentValueOffer).isOk()
test "Validate invalid ContractTrieNodeOffer nodes - bad state roots":
const file = testVectorDir / "contract_storage_trie_node.yaml"
@ -336,15 +323,15 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error
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 =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractTrieNodeOffer)
ContractTrieNodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
let res = validateOfferedContractTrieNode(
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
let res =
validateOffer(stateRoot, contentKey.contractTrieNodeKey, contentValueOffer)
check:
res.isErr()
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
for i, testData in testCase:
var stateRoot = KeccakHash.init(testData.state_root.hexToSeqByte())
var stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractTrieNodeOffer)
let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer = ContractTrieNodeOffer
.decode(testData.content_value_offer.hexToSeqByte())
.get()
contentValueOffer.accountProof[0][0] += 1.byte
let res = validateOfferedContractTrieNode(
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
let res =
validateOffer(stateRoot, contentKey.contractTrieNodeKey, contentValueOffer)
check:
res.isErr()
res.error() == "hash of proof root node doesn't match the expected root hash"
block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractTrieNodeOffer)
let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer = ContractTrieNodeOffer
.decode(testData.content_value_offer.hexToSeqByte())
.get()
contentValueOffer.storageProof[0][0] += 1.byte
let res = validateOfferedContractTrieNode(
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
let res =
validateOffer(stateRoot, contentKey.contractTrieNodeKey, contentValueOffer)
check:
res.isErr()
res.error() == "hash of proof root node doesn't match the expected root hash"
block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractTrieNodeOffer)
let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer = ContractTrieNodeOffer
.decode(testData.content_value_offer.hexToSeqByte())
.get()
contentValueOffer.accountProof[^1][^1] += 1.byte
check:
validateOfferedContractTrieNode(
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
validateOffer(stateRoot, contentKey.contractTrieNodeKey, contentValueOffer)
.isErr()
block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractTrieNodeOffer)
let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer = ContractTrieNodeOffer
.decode(testData.content_value_offer.hexToSeqByte())
.get()
contentValueOffer.storageProof[^1][^1] += 1.byte
check:
validateOfferedContractTrieNode(
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
validateOffer(stateRoot, contentKey.contractTrieNodeKey, contentValueOffer)
.isErr()
block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractTrieNodeOffer)
let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer = ContractTrieNodeOffer
.decode(testData.content_value_offer.hexToSeqByte())
.get()
contentValueOffer.accountProof[^2][^2] += 1.byte
check:
validateOfferedContractTrieNode(
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
validateOffer(stateRoot, contentKey.contractTrieNodeKey, contentValueOffer)
.isErr()
# Contract bytecode offer validation tests
@ -434,17 +423,15 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error
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 =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractCodeOffer)
ContractCodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
check:
validateOfferedContractCode(
stateRoot, contentKey.contractCodeKey, contentValueOffer
)
.isOk()
validateOffer(stateRoot, contentKey.contractCodeKey, contentValueOffer).isOk()
test "Validate invalid ContractCodeOffer nodes - bad state root":
const file = testVectorDir / "contract_bytecode.yaml"
@ -455,15 +442,14 @@ suite "State Validation":
raiseAssert "Cannot read test vector: " & error
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 =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractCodeOffer)
ContractCodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
let res = validateOfferedContractCode(
stateRoot, contentKey.contractCodeKey, contentValueOffer
)
let res = validateOffer(stateRoot, contentKey.contractCodeKey, contentValueOffer)
check:
res.isErr()
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
for i, testData in testCase:
var stateRoot = KeccakHash.init(testData.state_root.hexToSeqByte())
var stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractCodeOffer)
ContractCodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
contentValueOffer.accountProof[0][0] += 1.byte
let res = validateOfferedContractCode(
stateRoot, contentKey.contractCodeKey, contentValueOffer
)
let res =
validateOffer(stateRoot, contentKey.contractCodeKey, contentValueOffer)
check:
res.isErr()
res.error() == "hash of proof root node doesn't match the expected root hash"
block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractCodeOffer)
ContractCodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
contentValueOffer.code[0] += 1.byte
let res = validateOfferedContractCode(
stateRoot, contentKey.contractCodeKey, contentValueOffer
)
let res =
validateOffer(stateRoot, contentKey.contractCodeKey, contentValueOffer)
check:
res.isErr()
res.error() == "hash of offered bytecode doesn't match the expected code hash"
block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractCodeOffer)
ContractCodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
contentValueOffer.accountProof[^1][^1] += 1.byte
check:
validateOfferedContractCode(
stateRoot, contentKey.contractCodeKey, contentValueOffer
)
.isErr()
validateOffer(stateRoot, contentKey.contractCodeKey, contentValueOffer).isErr()
block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentKey =
ContentKey.decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractCodeOffer)
ContractCodeOffer.decode(testData.content_value_offer.hexToSeqByte()).get()
contentValueOffer.code[^1] += 1.byte
let res = validateOfferedContractCode(
stateRoot, contentKey.contractCodeKey, contentValueOffer
)
let res =
validateOffer(stateRoot, contentKey.contractCodeKey, contentValueOffer)
check:
res.isErr()
res.error() == "hash of offered bytecode doesn't match the expected code hash"
@ -549,17 +533,15 @@ suite "State Validation":
if i == 1:
continue
var stateRoot = KeccakHash.init(stateRoots[i].hexToSeqByte())
var stateRoot = KeccakHash.fromBytes(stateRoots[i].hexToSeqByte())
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 =
SSZ.decode(kv.content_value.hexToSeqByte(), AccountTrieNodeOffer)
AccountTrieNodeOffer.decode(kv.content_value.hexToSeqByte()).get()
check:
validateOfferedAccountTrieNode(
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer
)
validateOffer(stateRoot, contentKey.accountTrieNodeKey, contentValueOffer)
.isOk()
test "Validate valid ContractTrieNodeOffer recursive gossip nodes":
@ -572,15 +554,13 @@ suite "State Validation":
if i != 1:
continue
var stateRoot = KeccakHash.init(testData.state_root.hexToSeqByte())
var stateRoot = KeccakHash.fromBytes(testData.state_root.hexToSeqByte())
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 =
SSZ.decode(kv.content_value.hexToSeqByte(), ContractTrieNodeOffer)
ContractTrieNodeOffer.decode(kv.content_value.hexToSeqByte()).get()
check:
validateOfferedContractTrieNode(
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
validateOffer(stateRoot, contentKey.contractTrieNodeKey, contentValueOffer)
.isOk()

View File

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

View File

@ -16,7 +16,7 @@ import
stint,
nimcrypto/hash,
eth/trie/[hexary, db, trie_defs],
../../network/state/state_validation,
../../network/state/[state_content, state_validation],
./state_test_helpers
proc getKeyBytes(i: int): seq[byte] =
@ -88,8 +88,8 @@ suite "MPT trie proof verification":
check:
res.isOk()
test "Validate proof bytes":
var trie = initHexaryTrie(newMemoryDB(), isPruning = false)
test "Validate proof bytes - 3 keys":
var trie = initHexaryTrie(newMemoryDB())
trie.put("doe".toBytes, "reindeer".toBytes)
trie.put("dog".toBytes, "puppy".toBytes)
@ -101,35 +101,25 @@ suite "MPT trie proof verification":
let
key = "doe".toBytes
proof = trie.getTrieProof(key)
res = validateTrieProof(rootHash, key.asNibbles(), proof)
check:
res.isOk()
check validateTrieProof(rootHash, key.asNibbles(), proof).isOk()
block:
let
key = "dog".toBytes
proof = trie.getTrieProof(key)
res = validateTrieProof(rootHash, key.asNibbles(), proof)
check:
res.isOk()
check validateTrieProof(rootHash, key.asNibbles(), proof).isOk()
block:
let
key = "dogglesworth".toBytes
proof = trie.getTrieProof(key)
res = validateTrieProof(rootHash, key.asNibbles(), proof)
check:
res.isOk()
check validateTrieProof(rootHash, key.asNibbles(), proof).isOk()
block:
let
key = "dogg".toBytes
proof = trie.getTrieProof(key)
res = validateTrieProof(rootHash, key.asNibbles(), proof)
check:
res.isErr()
res.error() == "not enough nibbles to validate node prefix"
@ -139,7 +129,6 @@ suite "MPT trie proof verification":
key = "dogz".toBytes
proof = trie.getTrieProof(key)
res = validateTrieProof(rootHash, key.asNibbles(), proof)
check:
res.isErr()
res.error() == "path contains more nibbles than expected for proof"
@ -149,7 +138,6 @@ suite "MPT trie proof verification":
key = "doe".toBytes
proof = newSeq[seq[byte]]().asTrieProof()
res = validateTrieProof(rootHash, key.asNibbles(), proof)
check:
res.isErr()
res.error() == "proof is empty"
@ -159,7 +147,53 @@ suite "MPT trie proof verification":
key = "doe".toBytes
proof = @["aaa".toBytes, "ccc".toBytes].asTrieProof()
res = validateTrieProof(rootHash, key.asNibbles(), proof)
check:
res.isErr()
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()