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:
parent
2629d412d7
commit
e566e89d03
|
@ -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))
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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]
|
||||
# )
|
|
@ -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
|
||||
continue
|
||||
|
||||
let
|
||||
valueForRetrieval = decodedValue.offerContentToRetrievalContent().encode()
|
||||
contentId = n.portalProtocol.toContentId(contentKey).valueOr:
|
||||
error "Received offered content with invalid content key", contentKey
|
||||
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
|
||||
|
||||
n.portalProtocol.storeContent(contentKey, contentId, valueForRetrieval)
|
||||
info "Received offered content validated successfully", contentKey
|
||||
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 gossipContent(
|
||||
n.portalProtocol, maybeSrcNodeId, contentKey, decodedKey, contentValue,
|
||||
decodedValue,
|
||||
)
|
||||
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"
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue