Fluffy state offer validation (#2170)

* Implemented offer validation interface.

* Implement high level offer validation steps.

* Completed state validation tests.

* Update validation to use result type.

* Update state proof verification tests to test offer verification.

* Query history network to get state root by block hash.

* Fix state network test and remove usage of CoreDb.

* Fix state network gossip test and PR comment updates.

* Add trieproof state validation tests and fix for short nodes.
This commit is contained in:
web3-developer 2024-05-16 16:16:16 +08:00 committed by GitHub
parent 8767bbd10a
commit 2891b9aa7d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 31578 additions and 490 deletions

View File

@ -191,20 +191,6 @@ proc run(config: PortalConf) {.raises: [CatchableError].} =
) )
streamManager = StreamManager.new(d) streamManager = StreamManager.new(d)
stateNetwork =
if Network.state in config.networks:
Opt.some(
StateNetwork.new(
d,
db,
streamManager,
bootstrapRecords = bootstrapRecords,
portalConfig = portalConfig,
)
)
else:
Opt.none(StateNetwork)
accumulator = accumulator =
# Building an accumulator from header epoch files takes > 2m30s and is # Building an accumulator from header epoch files takes > 2m30s and is
# thus not really a viable option at start-up. # thus not really a viable option at start-up.
@ -234,6 +220,21 @@ proc run(config: PortalConf) {.raises: [CatchableError].} =
else: else:
Opt.none(HistoryNetwork) Opt.none(HistoryNetwork)
stateNetwork =
if Network.state in config.networks:
Opt.some(
StateNetwork.new(
d,
db,
streamManager,
bootstrapRecords = bootstrapRecords,
portalConfig = portalConfig,
historyNetwork = historyNetwork,
)
)
else:
Opt.none(StateNetwork)
beaconLightClient = beaconLightClient =
# TODO: Currently disabled by default as it is not sufficiently polished. # TODO: Currently disabled by default as it is not sufficiently polished.
# Eventually this should be always-on functionality. # Eventually this should be always-on functionality.

View File

@ -1,22 +0,0 @@
# Nimbus
# 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.
{.push raises: [].}
import stint, eth/[common, trie], ./state_proof_types
proc generateAccountProof*(
state: AccountState, address: EthAddress
): AccountProof {.raises: [RlpError].} =
let key = keccakHash(address).data
state.getBranch(key).AccountProof
proc generateStorageProof*(
state: StorageState, slotKey: UInt256
): StorageProof {.raises: [RlpError].} =
let key = keccakHash(toBytesBE(slotKey)).data
state.getBranch(key).StorageProof

View File

@ -1,30 +0,0 @@
# Nimbus
# 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.
{.push raises: [].}
import eth/[common, trie]
type
AccountState* = distinct HexaryTrie
StorageState* = distinct HexaryTrie
MptProof* = seq[seq[byte]]
AccountProof* = distinct MptProof
StorageProof* = distinct MptProof
proc getBranch*(self: AccountState, key: openArray[byte]): seq[seq[byte]] {.borrow.}
proc rootHash*(self: AccountState): KeccakHash {.borrow.}
proc getBranch*(self: StorageState, key: openArray[byte]): seq[seq[byte]] {.borrow.}
proc rootHash*(self: StorageState): KeccakHash {.borrow.}
proc len*(self: AccountProof): int {.borrow.}
proc len*(self: StorageProof): int {.borrow.}

View File

@ -1,69 +0,0 @@
# Nimbus
# 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.
{.push raises: [].}
import
std/sequtils,
stint,
eth/[common, rlp, trie/hexary_proof_verification],
stew/results,
./state_proof_types
export results
proc verifyAccount*(
trustedStateRoot: KeccakHash,
address: EthAddress,
account: Account,
proof: AccountProof,
): Result[void, string] =
if proof.len() == 0:
return err("proof is empty")
let key = toSeq(keccakHash(address).data)
let value = rlp.encode(account)
let proofResult = verifyMptProof(proof.MptProof, trustedStateRoot, key, value)
case proofResult.kind
of ValidProof:
ok()
of MissingKey:
err("missing key")
of InvalidProof:
err(proofResult.errorMsg)
proc verifyContractStorageSlot*(
trustedStorageRoot: KeccakHash,
slotKey: UInt256,
slotValue: UInt256,
proof: StorageProof,
): Result[void, string] =
if proof.len() == 0:
return err("proof is empty")
let key = toSeq(keccakHash(toBytesBE(slotKey)).data)
let value = rlp.encode(slotValue)
let proofResult = verifyMptProof(proof.MptProof, trustedStorageRoot, key, value)
case proofResult.kind
of ValidProof:
ok()
of MissingKey:
err("missing key")
of InvalidProof:
err(proofResult.errorMsg)
func verifyContractBytecode*(
trustedCodeHash: KeccakHash, bytecode: openArray[byte]
): Result[void, string] =
if trustedCodeHash == keccakHash(bytecode):
ok()
else:
err("hash of bytecode doesn't match the expected code hash")

View File

@ -12,7 +12,7 @@
import import
nimcrypto/[hash, sha2, keccak], nimcrypto/[hash, sha2, keccak],
stew/results, results,
stint, stint,
eth/common/eth_types, eth/common/eth_types,
ssz_serialization, ssz_serialization,
@ -208,16 +208,35 @@ func encode*(content: RetrievalContentValue): seq[byte] =
of contractCode: of contractCode:
SSZ.encode(content.contractCode) SSZ.encode(content.contractCode)
func packNibbles*(nibbles: seq[byte]): Nibbles = func init*(T: type Nibbles, packed: openArray[byte], isEven: bool): T =
doAssert(nibbles.len() <= MAX_UNPACKED_NIBBLES_LEN, "Can't pack more than 64 nibbles") doAssert(packed.len() <= MAX_PACKED_NIBBLES_LEN)
if nibbles.len() == 0: 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)]) return Nibbles(@[byte(0x00)])
let isEvenLength = nibbles.len() mod 2 == 0 let isEvenLength = unpacked.len() mod 2 == 0
var var
output = newSeqOfCap[byte](nibbles.len() div 2 + 1) output = newSeqOfCap[byte](unpacked.len() div 2 + 1)
highNibble = isEvenLength highNibble = isEvenLength
currentByte: byte = 0 currentByte: byte = 0
@ -226,7 +245,7 @@ func packNibbles*(nibbles: seq[byte]): Nibbles =
else: else:
currentByte = 0x10 currentByte = 0x10
for i, nibble in nibbles: for i, nibble in unpacked:
if highNibble: if highNibble:
currentByte = nibble shl 4 currentByte = nibble shl 4
else: else:
@ -236,12 +255,12 @@ func packNibbles*(nibbles: seq[byte]): Nibbles =
Nibbles(output) Nibbles(output)
func unpackNibbles*(nibbles: Nibbles): seq[byte] = func unpackNibbles*(packed: Nibbles): seq[byte] =
doAssert(nibbles.len() <= MAX_PACKED_NIBBLES_LEN, "Packed nibbles length is too long") doAssert(packed.len() <= MAX_PACKED_NIBBLES_LEN, "Packed nibbles length is too long")
var output = newSeqOfCap[byte](nibbles.len() * 2) var output = newSeqOfCap[byte](packed.len() * 2)
for i, pair in nibbles: for i, pair in packed:
if i == 0 and pair == 0x00: if i == 0 and pair == 0x00:
continue continue

View File

@ -6,17 +6,20 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import import
stew/results, results,
chronos, chronos,
chronicles, chronicles,
eth/common/eth_hash, eth/common/eth_hash,
eth/common, eth/common,
eth/p2p/discoveryv5/[protocol, enr], eth/p2p/discoveryv5/[protocol, enr],
../../database/content_db, ../../database/content_db,
../history/history_network,
../wire/[portal_protocol, portal_stream, portal_protocol_config], ../wire/[portal_protocol, portal_stream, portal_protocol_config],
./state_content, ./state_content,
./state_validation ./state_validation
export results
logScope: logScope:
topics = "portal_state" topics = "portal_state"
@ -27,6 +30,7 @@ type StateNetwork* = ref object
contentDB*: ContentDB contentDB*: ContentDB
contentQueue*: AsyncQueue[(Opt[NodeId], ContentKeysList, seq[seq[byte]])] contentQueue*: AsyncQueue[(Opt[NodeId], ContentKeysList, seq[seq[byte]])]
processContentLoop: Future[void] processContentLoop: Future[void]
historyNetwork: Opt[HistoryNetwork]
func toContentIdHandler(contentKey: ByteList): results.Opt[ContentId] = func toContentIdHandler(contentKey: ByteList): results.Opt[ContentId] =
ok(toContentId(contentKey)) ok(toContentId(contentKey))
@ -85,20 +89,25 @@ proc validateContent*(
): bool = ): bool =
doAssert(contentKey.contentType == contentValue.contentType) doAssert(contentKey.contentType == contentValue.contentType)
case contentKey.contentType let res =
of unused: case contentKey.contentType
warn "Received content with unused content type" of unused:
false Result[void, string].err("Received content with unused content type")
of accountTrieNode: of accountTrieNode:
validateFetchedAccountTrieNode( validateFetchedAccountTrieNode(
contentKey.accountTrieNodeKey, contentValue.accountTrieNode contentKey.accountTrieNodeKey, contentValue.accountTrieNode
) )
of contractTrieNode: of contractTrieNode:
validateFetchedContractTrieNode( validateFetchedContractTrieNode(
contentKey.contractTrieNodeKey, contentValue.contractTrieNode contentKey.contractTrieNodeKey, contentValue.contractTrieNode
) )
of contractCode: of contractCode:
validateFetchedContractCode(contentKey.contractCodeKey, contentValue.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.} = proc getContent*(n: StateNetwork, key: ContentKey): Future[Opt[seq[byte]]] {.async.} =
let let
@ -137,34 +146,54 @@ proc getContent*(n: StateNetwork, key: ContentKey): Future[Opt[seq[byte]]] {.asy
# domain types. # domain types.
return Opt.some(contentResult.content) return Opt.some(contentResult.content)
proc validateAccountTrieNode( proc getStateRootByBlockHash(
n: StateNetwork, key: ContentKey, contentValue: OfferContentValue n: StateNetwork, hash: BlockHash
): bool = ): Future[Opt[KeccakHash]] {.async.} =
true if n.historyNetwork.isNone():
warn "History network is not available. Unable to get state root by block hash"
return Opt.none(KeccakHash)
proc validateContractTrieNode( let header = (await n.historyNetwork.get().getVerifiedBlockHeader(hash)).valueOr:
n: StateNetwork, key: ContentKey, contentValue: OfferContentValue warn "Failed to get block header by hash", hash
): bool = return Opt.none(KeccakHash)
true
proc validateContractCode( Opt.some(header.stateRoot)
n: StateNetwork, key: ContentKey, contentValue: OfferContentValue
): bool =
true
proc validateContent*( proc validateContent*(
n: StateNetwork, contentKey: ContentKey, contentValue: OfferContentValue n: StateNetwork, contentKey: ContentKey, contentValue: OfferContentValue
): bool = ): Future[Result[void, string]] {.async.} =
doAssert(contentKey.contentType == contentValue.contentType)
case contentKey.contentType case contentKey.contentType
of unused: of unused:
warn "Received content with unused content type" Result[void, string].err("Received content with unused content type")
false
of accountTrieNode: of accountTrieNode:
validateAccountTrieNode(n, contentKey, contentValue) 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: of contractTrieNode:
validateContractTrieNode(n, contentKey, contentValue) 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: of contractCode:
validateContractCode(n, contentKey, contentValue) 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( proc recursiveGossipAccountTrieNode(
p: PortalProtocol, p: PortalProtocol,
@ -235,6 +264,7 @@ proc new*(
streamManager: StreamManager, streamManager: StreamManager,
bootstrapRecords: openArray[Record] = [], bootstrapRecords: openArray[Record] = [],
portalConfig: PortalProtocolConfig = defaultPortalProtocolConfig, portalConfig: PortalProtocolConfig = defaultPortalProtocolConfig,
historyNetwork = Opt.none(HistoryNetwork),
): T = ): T =
let cq = newAsyncQueue[(Opt[NodeId], ContentKeysList, seq[seq[byte]])](50) let cq = newAsyncQueue[(Opt[NodeId], ContentKeysList, seq[seq[byte]])](50)
@ -253,8 +283,12 @@ proc new*(
portalProtocol.dbPut = portalProtocol.dbPut =
createStoreHandler(contentDB, portalConfig.radiusConfig, portalProtocol) createStoreHandler(contentDB, portalConfig.radiusConfig, portalProtocol)
return return StateNetwork(
StateNetwork(portalProtocol: portalProtocol, contentDB: contentDB, contentQueue: cq) portalProtocol: portalProtocol,
contentDB: contentDB,
contentQueue: cq,
historyNetwork: historyNetwork,
)
proc processContentLoop(n: StateNetwork) {.async.} = proc processContentLoop(n: StateNetwork) {.async.} =
try: try:
@ -266,22 +300,24 @@ proc processContentLoop(n: StateNetwork) {.async.} =
(decodedKey, decodedValue) = decodeKV(contentKey, contentValue).valueOr: (decodedKey, decodedValue) = decodeKV(contentKey, contentValue).valueOr:
error "Unable to decode offered Key/Value" error "Unable to decode offered Key/Value"
continue continue
if validateContent(n, decodedKey, decodedValue):
let
valueForRetrieval = decodedValue.offerContentToRetrievalContent().encode()
contentId = n.portalProtocol.toContentId(contentKey).valueOr:
error "Received offered content with invalid content key", contentKey
continue
n.portalProtocol.storeContent(contentKey, contentId, valueForRetrieval) (await n.validateContent(decodedKey, decodedValue)).isOkOr:
info "Received offered content validated successfully", contentKey error "Received offered content failed validation", contentKey, error
continue
await gossipContent( let
n.portalProtocol, maybeSrcNodeId, contentKey, decodedKey, contentValue, valueForRetrieval = decodedValue.offerContentToRetrievalContent().encode()
decodedValue, contentId = n.portalProtocol.toContentId(contentKey).valueOr:
) error "Received offered content with invalid content key", contentKey
else: continue
error "Received offered content failed validation", contentKey
n.portalProtocol.storeContent(contentKey, contentId, valueForRetrieval)
info "Received offered content validated successfully", contentKey
await gossipContent(
n.portalProtocol, maybeSrcNodeId, contentKey, decodedKey, contentValue,
decodedValue,
)
except CancelledError: except CancelledError:
trace "processContentLoop canceled" trace "processContentLoop canceled"

View File

@ -5,30 +5,209 @@
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import eth/common, ./state_content import
results, stew/arrayops, eth/[common, trie], ../../common/common_types, ./state_content
export results
# private functions
proc hashEquals(value: TrieNode | Bytecode, expectedHash: KeccakHash): bool {.inline.} =
keccakHash(value.asSeq()) == expectedHash
proc isValidNextNode(thisNodeRlp: Rlp, rlpIdx: int, nextNode: TrieNode): bool =
let hashOrShortRlp = thisNodeRlp.listElem(rlpIdx)
if hashOrShortRlp.isEmpty():
return false
let nextHash =
if hashOrShortRlp.isList():
# is a short node
keccakHash(rlp.encode(hashOrShortRlp))
else:
let hash = hashOrShortRlp.toBytes()
if hash.len() != 32:
return false
KeccakHash(data: array[32, byte].initCopyFrom(hash))
nextNode.hashEquals(nextHash)
proc decodePrefix(nodePrefixRlp: Rlp): (byte, bool, Nibbles) =
doAssert(not nodePrefixRlp.isEmpty())
let
rlpBytes = nodePrefixRlp.toBytes()
firstNibble = (rlpBytes[0] and 0xF0) shr 4
isLeaf = firstNibble == 2 or firstNibble == 3
isEven = firstNibble == 0 or firstNibble == 2
startIdx = if isEven: 1 else: 0
nibbles = Nibbles.init(rlpBytes[startIdx .. ^1], isEven)
(firstNibble.byte, isLeaf, nibbles)
proc validateTrieProof*(
expectedRootHash: KeccakHash, path: Nibbles, proof: TrieProof
): Result[void, string] =
if proof.len() == 0:
return err("proof is empty")
if not proof[0].hashEquals(expectedRootHash):
return err("hash of proof root node doesn't match the expected root hash")
let nibbles = path.unpackNibbles()
if nibbles.len() == 0:
if proof.len() == 1:
return ok() # root node case, already validated above
else:
return err("empty path, only one node expected in proof")
var nibbleIdx = 0
for proofIdx, p in proof:
let
thisNodeRlp = rlpFromBytes(p.asSeq())
remainingNibbles = nibbles.len() - nibbleIdx
isLastNode = proofIdx == proof.high
if remainingNibbles == 0:
if isLastNode:
break
else:
return err("empty nibbles but proof has more nodes")
case thisNodeRlp.listLen()
of 2:
let nodePrefixRlp = thisNodeRlp.listElem(0)
if nodePrefixRlp.isEmpty():
return err("node prefix is empty")
let (prefix, isLeaf, prefixNibbles) = decodePrefix(nodePrefixRlp)
if prefix >= 4:
return err("invalid prefix in node")
if not isLastNode or isLeaf:
let unpackedPrefix = prefixNibbles.unpackNibbles()
if remainingNibbles < unpackedPrefix.len():
return err("not enough nibbles to validate node prefix")
let nibbleEndIdx = nibbleIdx + unpackedPrefix.len()
if nibbles[nibbleIdx ..< nibbleEndIdx] != unpackedPrefix:
return err("nibbles don't match node prefix")
nibbleIdx += unpackedPrefix.len()
if not isLastNode:
if isLeaf:
return err("leaf node must be last node in the proof")
else: # is extension node
if not isValidNextNode(thisNodeRlp, 1, proof[proofIdx + 1]):
return
err("hash of next node doesn't match the expected extension node hash")
of 17:
if not isLastNode:
let nextNibble = nibbles[nibbleIdx]
if nextNibble >= 16:
return err("invalid next nibble for branch node")
if not isValidNextNode(thisNodeRlp, nextNibble.int, proof[proofIdx + 1]):
return err("hash of next node doesn't match the expected branch node hash")
inc nibbleIdx
else:
return err("invalid rlp node, expected 2 or 17 elements")
if nibbleIdx < nibbles.len():
err("path contains more nibbles than expected for proof")
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 validateFetchedAccountTrieNode*(
trustedAccountTrieNodeKey: AccountTrieNodeKey, trustedAccountTrieNodeKey: AccountTrieNodeKey,
accountTrieNode: AccountTrieNodeRetrieval, accountTrieNode: AccountTrieNodeRetrieval,
): bool = ): Result[void, string] =
let expectedHash = trustedAccountTrieNodeKey.nodeHash if accountTrieNode.node.hashEquals(trustedAccountTrieNodeKey.nodeHash):
let actualHash = keccakHash(accountTrieNode.node.asSeq()) ok()
else:
expectedHash == actualHash err("hash of fetched account trie node doesn't match the expected node hash")
proc validateFetchedContractTrieNode*( proc validateFetchedContractTrieNode*(
trustedContractTrieNodeKey: ContractTrieNodeKey, trustedContractTrieNodeKey: ContractTrieNodeKey,
contractTrieNode: ContractTrieNodeRetrieval, contractTrieNode: ContractTrieNodeRetrieval,
): bool = ): Result[void, string] =
let expectedHash = trustedContractTrieNodeKey.nodeHash if contractTrieNode.node.hashEquals(trustedContractTrieNodeKey.nodeHash):
let actualHash = keccakHash(contractTrieNode.node.asSeq()) ok()
else:
expectedHash == actualHash err("hash of fetched contract trie node doesn't match the expected node hash")
proc validateFetchedContractCode*( proc validateFetchedContractCode*(
trustedContractCodeKey: ContractCodeKey, contractCode: ContractCodeRetrieval trustedContractCodeKey: ContractCodeKey, contractCode: ContractCodeRetrieval
): bool = ): Result[void, string] =
let expectedHash = trustedContractCodeKey.codeHash if contractCode.code.hashEquals(trustedContractCodeKey.codeHash):
let actualHash = keccakHash(contractCode.code.asSeq()) ok()
else:
err("hash of fetched bytecode doesn't match the expected code hash")
expectedHash == actualHash proc validateOfferedAccountTrieNode*(
trustedStateRoot: KeccakHash,
accountTrieNodeKey: AccountTrieNodeKey,
accountTrieNode: AccountTrieNodeOffer,
): Result[void, string] =
?validateTrieProof(trustedStateRoot, accountTrieNodeKey.path, accountTrieNode.proof)
if accountTrieNode.proof[^1].hashEquals(accountTrieNodeKey.nodeHash):
ok()
else:
err("hash of offered account trie node doesn't match the expected node hash")
proc validateOfferedContractTrieNode*(
trustedStateRoot: KeccakHash,
contractTrieNodeKey: ContractTrieNodeKey,
contractTrieNode: ContractTrieNodeOffer,
): Result[void, string] =
let addressHash = keccakHash(contractTrieNodeKey.address).data
?validateTrieProof(
trustedStateRoot, Nibbles.init(addressHash, true), contractTrieNode.accountProof
)
let account = ?rlpDecodeAccountTrieNode(contractTrieNode.accountProof[^1])
?validateTrieProof(
account.storageRoot, contractTrieNodeKey.path, contractTrieNode.storageProof
)
if contractTrieNode.storageProof[^1].hashEquals(contractTrieNodeKey.nodeHash):
ok()
else:
err("hash of offered contract trie node doesn't match the expected node hash")
proc validateOfferedContractCode*(
trustedStateRoot: KeccakHash,
contractCodeKey: ContractCodeKey,
contractCode: ContractCodeOffer,
): Result[void, string] =
let addressHash = keccakHash(contractCodeKey.address).data
?validateTrieProof(
trustedStateRoot, Nibbles.init(addressHash, true), contractCode.accountProof
)
let account = ?rlpDecodeAccountTrieNode(contractCode.accountProof[^1])
if contractCode.code.hashEquals(account.codeHash):
ok()
else:
err("hash of offered bytecode doesn't match the expected code hash")

View File

@ -9,10 +9,6 @@
import import
./test_portal_wire_protocol, ./test_portal_wire_protocol,
./state_network_tests/test_state_content_keys,
./state_network_tests/test_state_content_values,
./state_network_tests/test_state_network_gossip,
./test_state_proof_verification,
./test_accumulator, ./test_accumulator,
./test_history_network, ./test_history_network,
./test_content_db, ./test_content_db,
@ -21,4 +17,5 @@ import
./test_beacon_chain_block_proof_capella, ./test_beacon_chain_block_proof_capella,
./test_beacon_chain_historical_roots, ./test_beacon_chain_historical_roots,
./test_beacon_chain_historical_summaries, ./test_beacon_chain_historical_summaries,
./beacon_network_tests/all_beacon_network_tests ./beacon_network_tests/all_beacon_network_tests,
./state_network_tests/all_state_network_tests

View File

@ -0,0 +1,838 @@
{
"config": {
"chainId": 123,
"homesteadBlock": 0,
"eip150Block": 0,
"eip150Hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"eip155Block": 0,
"eip158Block": 0,
"byzantiumBlock": 0,
"constantinopleBlock": 0,
"petersburgBlock": 0,
"istanbulBlock": 0,
"berlinBlock": 0,
"londonBlock": 500,
"clique": {
"period": 30,
"epoch": 30000
}
},
"genesis": {
"nonce": "0x0",
"timestamp": "0x60b3877f",
"extraData": "0x00000000000000000000000000000000000000000000000000000000000000005211cea3870c7ba7c6c44b185e62eecdb864cd8c560228ce57d31efbf64c200b2c200aacec78cf17a7148e784fe95a7a750335f8b9572ee28d72e7650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
"gasLimit": "0x47b760",
"difficulty": "0x1",
"mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"coinbase": "0x0000000000000000000000000000000000000000",
"alloc": {
"0000000000000000000000000000000000000000": {
"balance": "0x1"
},
"0000000000000000000000000000000000000001": {
"balance": "0x1"
},
"0000000000000000000000000000000000000002": {
"balance": "0x1"
},
"0000000000000000000000000000000000000003": {
"balance": "0x1"
},
"0000000000000000000000000000000000000004": {
"balance": "0x1"
},
"0000000000000000000000000000000000000005": {
"balance": "0x1"
},
"0000000000000000000000000000000000000006": {
"balance": "0x1"
},
"0000000000000000000000000000000000000007": {
"balance": "0x1"
},
"0000000000000000000000000000000000000008": {
"balance": "0x1"
},
"0000000000000000000000000000000000000009": {
"balance": "0x1"
},
"000000000000000000000000000000000000000a": {
"balance": "0x1"
},
"000000000000000000000000000000000000000b": {
"balance": "0x1"
},
"000000000000000000000000000000000000000c": {
"balance": "0x1"
},
"000000000000000000000000000000000000000d": {
"balance": "0x1"
},
"000000000000000000000000000000000000000e": {
"balance": "0x1"
},
"000000000000000000000000000000000000000f": {
"balance": "0x1"
},
"0000000000000000000000000000000000000010": {
"balance": "0x1"
},
"0000000000000000000000000000000000000011": {
"balance": "0x1"
},
"0000000000000000000000000000000000000012": {
"balance": "0x1"
},
"0000000000000000000000000000000000000013": {
"balance": "0x1"
},
"0000000000000000000000000000000000000014": {
"balance": "0x1"
},
"0000000000000000000000000000000000000015": {
"balance": "0x1"
},
"0000000000000000000000000000000000000016": {
"balance": "0x1"
},
"0000000000000000000000000000000000000017": {
"balance": "0x1"
},
"0000000000000000000000000000000000000018": {
"balance": "0x1"
},
"0000000000000000000000000000000000000019": {
"balance": "0x1"
},
"000000000000000000000000000000000000001a": {
"balance": "0x1"
},
"000000000000000000000000000000000000001b": {
"balance": "0x1"
},
"000000000000000000000000000000000000001c": {
"balance": "0x1"
},
"000000000000000000000000000000000000001d": {
"balance": "0x1"
},
"000000000000000000000000000000000000001e": {
"balance": "0x1"
},
"000000000000000000000000000000000000001f": {
"balance": "0x1"
},
"0000000000000000000000000000000000000020": {
"balance": "0x1"
},
"0000000000000000000000000000000000000021": {
"balance": "0x1"
},
"0000000000000000000000000000000000000022": {
"balance": "0x1"
},
"0000000000000000000000000000000000000023": {
"balance": "0x1"
},
"0000000000000000000000000000000000000024": {
"balance": "0x1"
},
"0000000000000000000000000000000000000025": {
"balance": "0x1"
},
"0000000000000000000000000000000000000026": {
"balance": "0x1"
},
"0000000000000000000000000000000000000027": {
"balance": "0x1"
},
"0000000000000000000000000000000000000028": {
"balance": "0x1"
},
"0000000000000000000000000000000000000029": {
"balance": "0x1"
},
"000000000000000000000000000000000000002a": {
"balance": "0x1"
},
"000000000000000000000000000000000000002b": {
"balance": "0x1"
},
"000000000000000000000000000000000000002c": {
"balance": "0x1"
},
"000000000000000000000000000000000000002d": {
"balance": "0x1"
},
"000000000000000000000000000000000000002e": {
"balance": "0x1"
},
"000000000000000000000000000000000000002f": {
"balance": "0x1"
},
"0000000000000000000000000000000000000030": {
"balance": "0x1"
},
"0000000000000000000000000000000000000031": {
"balance": "0x1"
},
"0000000000000000000000000000000000000032": {
"balance": "0x1"
},
"0000000000000000000000000000000000000033": {
"balance": "0x1"
},
"0000000000000000000000000000000000000034": {
"balance": "0x1"
},
"0000000000000000000000000000000000000035": {
"balance": "0x1"
},
"0000000000000000000000000000000000000036": {
"balance": "0x1"
},
"0000000000000000000000000000000000000037": {
"balance": "0x1"
},
"0000000000000000000000000000000000000038": {
"balance": "0x1"
},
"0000000000000000000000000000000000000039": {
"balance": "0x1"
},
"000000000000000000000000000000000000003a": {
"balance": "0x1"
},
"000000000000000000000000000000000000003b": {
"balance": "0x1"
},
"000000000000000000000000000000000000003c": {
"balance": "0x1"
},
"000000000000000000000000000000000000003d": {
"balance": "0x1"
},
"000000000000000000000000000000000000003e": {
"balance": "0x1"
},
"000000000000000000000000000000000000003f": {
"balance": "0x1"
},
"0000000000000000000000000000000000000040": {
"balance": "0x1"
},
"0000000000000000000000000000000000000041": {
"balance": "0x1"
},
"0000000000000000000000000000000000000042": {
"balance": "0x1"
},
"0000000000000000000000000000000000000043": {
"balance": "0x1"
},
"0000000000000000000000000000000000000044": {
"balance": "0x1"
},
"0000000000000000000000000000000000000045": {
"balance": "0x1"
},
"0000000000000000000000000000000000000046": {
"balance": "0x1"
},
"0000000000000000000000000000000000000047": {
"balance": "0x1"
},
"0000000000000000000000000000000000000048": {
"balance": "0x1"
},
"0000000000000000000000000000000000000049": {
"balance": "0x1"
},
"000000000000000000000000000000000000004a": {
"balance": "0x1"
},
"000000000000000000000000000000000000004b": {
"balance": "0x1"
},
"000000000000000000000000000000000000004c": {
"balance": "0x1"
},
"000000000000000000000000000000000000004d": {
"balance": "0x1"
},
"000000000000000000000000000000000000004e": {
"balance": "0x1"
},
"000000000000000000000000000000000000004f": {
"balance": "0x1"
},
"0000000000000000000000000000000000000050": {
"balance": "0x1"
},
"0000000000000000000000000000000000000051": {
"balance": "0x1"
},
"0000000000000000000000000000000000000052": {
"balance": "0x1"
},
"0000000000000000000000000000000000000053": {
"balance": "0x1"
},
"0000000000000000000000000000000000000054": {
"balance": "0x1"
},
"0000000000000000000000000000000000000055": {
"balance": "0x1"
},
"0000000000000000000000000000000000000056": {
"balance": "0x1"
},
"0000000000000000000000000000000000000057": {
"balance": "0x1"
},
"0000000000000000000000000000000000000058": {
"balance": "0x1"
},
"0000000000000000000000000000000000000059": {
"balance": "0x1"
},
"000000000000000000000000000000000000005a": {
"balance": "0x1"
},
"000000000000000000000000000000000000005b": {
"balance": "0x1"
},
"000000000000000000000000000000000000005c": {
"balance": "0x1"
},
"000000000000000000000000000000000000005d": {
"balance": "0x1"
},
"000000000000000000000000000000000000005e": {
"balance": "0x1"
},
"000000000000000000000000000000000000005f": {
"balance": "0x1"
},
"0000000000000000000000000000000000000060": {
"balance": "0x1"
},
"0000000000000000000000000000000000000061": {
"balance": "0x1"
},
"0000000000000000000000000000000000000062": {
"balance": "0x1"
},
"0000000000000000000000000000000000000063": {
"balance": "0x1"
},
"0000000000000000000000000000000000000064": {
"balance": "0x1"
},
"0000000000000000000000000000000000000065": {
"balance": "0x1"
},
"0000000000000000000000000000000000000066": {
"balance": "0x1"
},
"0000000000000000000000000000000000000067": {
"balance": "0x1"
},
"0000000000000000000000000000000000000068": {
"balance": "0x1"
},
"0000000000000000000000000000000000000069": {
"balance": "0x1"
},
"000000000000000000000000000000000000006a": {
"balance": "0x1"
},
"000000000000000000000000000000000000006b": {
"balance": "0x1"
},
"000000000000000000000000000000000000006c": {
"balance": "0x1"
},
"000000000000000000000000000000000000006d": {
"balance": "0x1"
},
"000000000000000000000000000000000000006e": {
"balance": "0x1"
},
"000000000000000000000000000000000000006f": {
"balance": "0x1"
},
"0000000000000000000000000000000000000070": {
"balance": "0x1"
},
"0000000000000000000000000000000000000071": {
"balance": "0x1"
},
"0000000000000000000000000000000000000072": {
"balance": "0x1"
},
"0000000000000000000000000000000000000073": {
"balance": "0x1"
},
"0000000000000000000000000000000000000074": {
"balance": "0x1"
},
"0000000000000000000000000000000000000075": {
"balance": "0x1"
},
"0000000000000000000000000000000000000076": {
"balance": "0x1"
},
"0000000000000000000000000000000000000077": {
"balance": "0x1"
},
"0000000000000000000000000000000000000078": {
"balance": "0x1"
},
"0000000000000000000000000000000000000079": {
"balance": "0x1"
},
"000000000000000000000000000000000000007a": {
"balance": "0x1"
},
"000000000000000000000000000000000000007b": {
"balance": "0x1"
},
"000000000000000000000000000000000000007c": {
"balance": "0x1"
},
"000000000000000000000000000000000000007d": {
"balance": "0x1"
},
"000000000000000000000000000000000000007e": {
"balance": "0x1"
},
"000000000000000000000000000000000000007f": {
"balance": "0x1"
},
"0000000000000000000000000000000000000080": {
"balance": "0x1"
},
"0000000000000000000000000000000000000081": {
"balance": "0x1"
},
"0000000000000000000000000000000000000082": {
"balance": "0x1"
},
"0000000000000000000000000000000000000083": {
"balance": "0x1"
},
"0000000000000000000000000000000000000084": {
"balance": "0x1"
},
"0000000000000000000000000000000000000085": {
"balance": "0x1"
},
"0000000000000000000000000000000000000086": {
"balance": "0x1"
},
"0000000000000000000000000000000000000087": {
"balance": "0x1"
},
"0000000000000000000000000000000000000088": {
"balance": "0x1"
},
"0000000000000000000000000000000000000089": {
"balance": "0x1"
},
"000000000000000000000000000000000000008a": {
"balance": "0x1"
},
"000000000000000000000000000000000000008b": {
"balance": "0x1"
},
"000000000000000000000000000000000000008c": {
"balance": "0x1"
},
"000000000000000000000000000000000000008d": {
"balance": "0x1"
},
"000000000000000000000000000000000000008e": {
"balance": "0x1"
},
"000000000000000000000000000000000000008f": {
"balance": "0x1"
},
"0000000000000000000000000000000000000090": {
"balance": "0x1"
},
"0000000000000000000000000000000000000091": {
"balance": "0x1"
},
"0000000000000000000000000000000000000092": {
"balance": "0x1"
},
"0000000000000000000000000000000000000093": {
"balance": "0x1"
},
"0000000000000000000000000000000000000094": {
"balance": "0x1"
},
"0000000000000000000000000000000000000095": {
"balance": "0x1"
},
"0000000000000000000000000000000000000096": {
"balance": "0x1"
},
"0000000000000000000000000000000000000097": {
"balance": "0x1"
},
"0000000000000000000000000000000000000098": {
"balance": "0x1"
},
"0000000000000000000000000000000000000099": {
"balance": "0x1"
},
"000000000000000000000000000000000000009a": {
"balance": "0x1"
},
"000000000000000000000000000000000000009b": {
"balance": "0x1"
},
"000000000000000000000000000000000000009c": {
"balance": "0x1"
},
"000000000000000000000000000000000000009d": {
"balance": "0x1"
},
"000000000000000000000000000000000000009e": {
"balance": "0x1"
},
"000000000000000000000000000000000000009f": {
"balance": "0x1"
},
"00000000000000000000000000000000000000a0": {
"balance": "0x1"
},
"00000000000000000000000000000000000000a1": {
"balance": "0x1"
},
"00000000000000000000000000000000000000a2": {
"balance": "0x1"
},
"00000000000000000000000000000000000000a3": {
"balance": "0x1"
},
"00000000000000000000000000000000000000a4": {
"balance": "0x1"
},
"00000000000000000000000000000000000000a5": {
"balance": "0x1"
},
"00000000000000000000000000000000000000a6": {
"balance": "0x1"
},
"00000000000000000000000000000000000000a7": {
"balance": "0x1"
},
"00000000000000000000000000000000000000a8": {
"balance": "0x1"
},
"00000000000000000000000000000000000000a9": {
"balance": "0x1"
},
"00000000000000000000000000000000000000aa": {
"balance": "0x1"
},
"00000000000000000000000000000000000000ab": {
"balance": "0x1"
},
"00000000000000000000000000000000000000ac": {
"balance": "0x1"
},
"00000000000000000000000000000000000000ad": {
"balance": "0x1"
},
"00000000000000000000000000000000000000ae": {
"balance": "0x1"
},
"00000000000000000000000000000000000000af": {
"balance": "0x1"
},
"00000000000000000000000000000000000000b0": {
"balance": "0x1"
},
"00000000000000000000000000000000000000b1": {
"balance": "0x1"
},
"00000000000000000000000000000000000000b2": {
"balance": "0x1"
},
"00000000000000000000000000000000000000b3": {
"balance": "0x1"
},
"00000000000000000000000000000000000000b4": {
"balance": "0x1"
},
"00000000000000000000000000000000000000b5": {
"balance": "0x1"
},
"00000000000000000000000000000000000000b6": {
"balance": "0x1"
},
"00000000000000000000000000000000000000b7": {
"balance": "0x1"
},
"00000000000000000000000000000000000000b8": {
"balance": "0x1"
},
"00000000000000000000000000000000000000b9": {
"balance": "0x1"
},
"00000000000000000000000000000000000000ba": {
"balance": "0x1"
},
"00000000000000000000000000000000000000bb": {
"balance": "0x1"
},
"00000000000000000000000000000000000000bc": {
"balance": "0x1"
},
"00000000000000000000000000000000000000bd": {
"balance": "0x1"
},
"00000000000000000000000000000000000000be": {
"balance": "0x1"
},
"00000000000000000000000000000000000000bf": {
"balance": "0x1"
},
"00000000000000000000000000000000000000c0": {
"balance": "0x1"
},
"00000000000000000000000000000000000000c1": {
"balance": "0x1"
},
"00000000000000000000000000000000000000c2": {
"balance": "0x1"
},
"00000000000000000000000000000000000000c3": {
"balance": "0x1"
},
"00000000000000000000000000000000000000c4": {
"balance": "0x1"
},
"00000000000000000000000000000000000000c5": {
"balance": "0x1"
},
"00000000000000000000000000000000000000c6": {
"balance": "0x1"
},
"00000000000000000000000000000000000000c7": {
"balance": "0x1"
},
"00000000000000000000000000000000000000c8": {
"balance": "0x1"
},
"00000000000000000000000000000000000000c9": {
"balance": "0x1"
},
"00000000000000000000000000000000000000ca": {
"balance": "0x1"
},
"00000000000000000000000000000000000000cb": {
"balance": "0x1"
},
"00000000000000000000000000000000000000cc": {
"balance": "0x1"
},
"00000000000000000000000000000000000000cd": {
"balance": "0x1"
},
"00000000000000000000000000000000000000ce": {
"balance": "0x1"
},
"00000000000000000000000000000000000000cf": {
"balance": "0x1"
},
"00000000000000000000000000000000000000d0": {
"balance": "0x1"
},
"00000000000000000000000000000000000000d1": {
"balance": "0x1"
},
"00000000000000000000000000000000000000d2": {
"balance": "0x1"
},
"00000000000000000000000000000000000000d3": {
"balance": "0x1"
},
"00000000000000000000000000000000000000d4": {
"balance": "0x1"
},
"00000000000000000000000000000000000000d5": {
"balance": "0x1"
},
"00000000000000000000000000000000000000d6": {
"balance": "0x1"
},
"00000000000000000000000000000000000000d7": {
"balance": "0x1"
},
"00000000000000000000000000000000000000d8": {
"balance": "0x1"
},
"00000000000000000000000000000000000000d9": {
"balance": "0x1"
},
"00000000000000000000000000000000000000da": {
"balance": "0x1"
},
"00000000000000000000000000000000000000db": {
"balance": "0x1"
},
"00000000000000000000000000000000000000dc": {
"balance": "0x1"
},
"00000000000000000000000000000000000000dd": {
"balance": "0x1"
},
"00000000000000000000000000000000000000de": {
"balance": "0x1"
},
"00000000000000000000000000000000000000df": {
"balance": "0x1"
},
"00000000000000000000000000000000000000e0": {
"balance": "0x1"
},
"00000000000000000000000000000000000000e1": {
"balance": "0x1"
},
"00000000000000000000000000000000000000e2": {
"balance": "0x1"
},
"00000000000000000000000000000000000000e3": {
"balance": "0x1"
},
"00000000000000000000000000000000000000e4": {
"balance": "0x1"
},
"00000000000000000000000000000000000000e5": {
"balance": "0x1"
},
"00000000000000000000000000000000000000e6": {
"balance": "0x1"
},
"00000000000000000000000000000000000000e7": {
"balance": "0x1"
},
"00000000000000000000000000000000000000e8": {
"balance": "0x1"
},
"00000000000000000000000000000000000000e9": {
"balance": "0x1"
},
"00000000000000000000000000000000000000ea": {
"balance": "0x1"
},
"00000000000000000000000000000000000000eb": {
"balance": "0x1"
},
"00000000000000000000000000000000000000ec": {
"balance": "0x1"
},
"00000000000000000000000000000000000000ed": {
"balance": "0x1"
},
"00000000000000000000000000000000000000ee": {
"balance": "0x1"
},
"00000000000000000000000000000000000000ef": {
"balance": "0x1"
},
"00000000000000000000000000000000000000f0": {
"balance": "0x1"
},
"00000000000000000000000000000000000000f1": {
"balance": "0x1"
},
"00000000000000000000000000000000000000f2": {
"balance": "0x1"
},
"00000000000000000000000000000000000000f3": {
"balance": "0x1"
},
"00000000000000000000000000000000000000f4": {
"balance": "0x1"
},
"00000000000000000000000000000000000000f5": {
"balance": "0x1"
},
"00000000000000000000000000000000000000f6": {
"balance": "0x1"
},
"00000000000000000000000000000000000000f7": {
"balance": "0x1"
},
"00000000000000000000000000000000000000f8": {
"balance": "0x1"
},
"00000000000000000000000000000000000000f9": {
"balance": "0x1"
},
"00000000000000000000000000000000000000fa": {
"balance": "0x1"
},
"00000000000000000000000000000000000000fb": {
"balance": "0x1"
},
"00000000000000000000000000000000000000fc": {
"balance": "0x1"
},
"00000000000000000000000000000000000000fd": {
"balance": "0x1"
},
"00000000000000000000000000000000000000fe": {
"balance": "0x1"
},
"00000000000000000000000000000000000000ff": {
"balance": "0x1"
},
"0e89e2aedb1cfcdb9424d41a1f218f4132738172": {
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
},
"1041afbcb359d5a8dc58c15b2ff51354ff8a217d": {
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
},
"236ff1e97419ae93ad80cafbaa21220c5d78fb7d": {
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
},
"60adc0f89a41af237ce73554ede170d733ec14e0": {
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
},
"799d329e5f583419167cd722962485926e338f4a": {
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
},
"7cf5b79bfe291a67ab02b393e456ccc4c266f753": {
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
},
"8a8eafb1cf62bfbeb1741769dae1a9dd47996192": {
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
},
"8ba1f109551bd432803012645ac136ddd64dba72": {
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
},
"b02a2eda1b317fbd16760128836b0ac59b560e9d": {
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
},
"badc0de9e0794b049b5ea63c3e1e698a3476c172": {
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
},
"f0300bee898ae272eb347e8369ac0c76df42c93f": {
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
},
"fe3b557e8fb62b89f4916b721be55ceb828dbd73": {
"balance": "0x200000000000000000000000000000000000000000000000000000000000000"
}
},
"number": "0x0",
"gasUsed": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000"
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,17 @@
# Nimbus
# Copyright (c) 2022-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
{.warning[UnusedImport]: off.}
import
./test_state_content_keys,
./test_state_content_values,
./test_state_network,
#./test_state_network_gossip,
./test_state_validation,
./test_state_validation_genesis,
./test_state_validation_trieproof

View File

@ -0,0 +1,77 @@
# 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.
{.push raises: [].}
import
std/[sugar, sequtils],
eth/[common, trie, trie/db],
../../nimbus/common/chain_config,
../../network/state/state_content
proc asNibbles*(key: openArray[byte], isEven = true): Nibbles =
Nibbles.init(key, isEven)
proc asTrieProof*(branch: openArray[seq[byte]]): TrieProof =
TrieProof.init(branch.map(node => TrieNode.init(node)))
proc getTrieProof*(
state: HexaryTrie, key: openArray[byte]
): TrieProof {.raises: [RlpError].} =
let branch = state.getBranch(key)
branch.asTrieProof()
proc generateAccountProof*(
state: HexaryTrie, address: EthAddress
): TrieProof {.raises: [RlpError].} =
let key = keccakHash(address).data
state.getTrieProof(key)
proc generateStorageProof*(
state: HexaryTrie, slotKey: UInt256
): TrieProof {.raises: [RlpError].} =
let key = keccakHash(toBytesBE(slotKey)).data
state.getTrieProof(key)
proc getGenesisAlloc*(filePath: string): GenesisAlloc =
var cn: NetworkParams
if not loadNetworkParams(filePath, cn):
quit(1)
cn.genesis.alloc
proc toState*(
alloc: GenesisAlloc
): (HexaryTrie, Table[EthAddress, HexaryTrie]) {.raises: [RlpError].} =
var accountTrie = initHexaryTrie(newMemoryDB())
var storageStates = initTable[EthAddress, HexaryTrie]()
for address, genAccount in alloc:
var storageRoot = EMPTY_ROOT_HASH
var codeHash = EMPTY_CODE_HASH
if genAccount.code.len() > 0:
var storageTrie = initHexaryTrie(newMemoryDB())
for slotKey, slotValue in genAccount.storage:
let key = keccakHash(toBytesBE(slotKey)).data
let value = rlp.encode(slotValue)
storageTrie.put(key, value)
storageStates[address] = storageTrie
storageRoot = storageTrie.rootHash()
codeHash = keccakHash(genAccount.code)
let account = Account(
nonce: genAccount.nonce,
balance: genAccount.balance,
storageRoot: storageRoot,
codeHash: codeHash,
)
let key = keccakHash(address).data
let value = rlp.encode(account)
accountTrie.put(key, value)
(accountTrie, storageStates)

View File

@ -6,51 +6,33 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import import
std/[os, json, sequtils, strutils, sugar], std/os,
stew/[byteutils, io2],
nimcrypto/hash, nimcrypto/hash,
testutils/unittests, testutils/unittests,
chronos, chronos,
eth/trie/hexary_proof_verification,
eth/keys, eth/keys,
eth/trie,
eth/common/[eth_types, eth_hash], eth/common/[eth_types, eth_hash],
eth/p2p/discoveryv5/protocol as discv5_protocol, eth/p2p/discoveryv5/protocol as discv5_protocol,
eth/p2p/discoveryv5/routing_table, eth/p2p/discoveryv5/routing_table,
../../../nimbus/[config, db/core_db, db/state_db],
../../../nimbus/common/[chain_config, genesis],
../../network/wire/[portal_protocol, portal_stream], ../../network/wire/[portal_protocol, portal_stream],
../../network/state/[state_content, state_network], ../../network/state/[state_content, state_network],
../../database/content_db, ../../database/content_db,
.././test_helpers ../test_helpers,
./state_test_helpers
const testVectorDir = "./vendor/portal-spec-tests/tests/mainnet/state/"
proc genesisToTrie(filePath: string): CoreDbMptRef =
# TODO: Doing our best here with API that exists, to be improved.
var cn: NetworkParams
if not loadNetworkParams(filePath, cn):
quit(1)
let sdb = newStateDB(newCoreDbRef LegacyDbMemory, false)
let map = toForkTransitionTable(cn.config)
let fork =
map.toHardFork(forkDeterminationInfo(0.toBlockNumber, cn.genesis.timestamp))
discard toGenesisHeader(cn.genesis, sdb, fork)
sdb.getTrie
procSuite "State Network": procSuite "State Network":
let rng = newRng() let rng = newRng()
asyncTest "Test Share Full State": asyncTest "Test Share Full State":
let let
trie = genesisToTrie("fluffy" / "tests" / "custom_genesis" / "chainid7.json") accounts =
getGenesisAlloc("fluffy" / "tests" / "custom_genesis" / "chainid7.json")
node1 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20302)) (trie, _) = accounts.toState()
node1 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20312))
sm1 = StreamManager.new(node1) sm1 = StreamManager.new(node1)
node2 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20303)) node2 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20313))
sm2 = StreamManager.new(node2) sm2 = StreamManager.new(node2)
proto1 = proto1 =
StateNetwork.new(node1, ContentDB.new("", uint32.high, inMemory = true), sm1) StateNetwork.new(node1, ContentDB.new("", uint32.high, inMemory = true), sm1)
proto2 = proto2 =
@ -72,8 +54,14 @@ procSuite "State Network":
contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey
) )
contentId = toContentId(contentKey) contentId = toContentId(contentKey)
value = RetrievalContentValue(
contentType: accountTrieNode,
accountTrieNode: AccountTrieNodeRetrieval(node: TrieNode.init(v)),
)
discard proto1.contentDB.put(contentId, v, proto1.portalProtocol.localNode.id) discard proto1.contentDB.put(
contentId, value.encode(), proto1.portalProtocol.localNode.id
)
for key in keys: for key in keys:
var nodeHash: NodeHash var nodeHash: NodeHash
@ -89,13 +77,16 @@ procSuite "State Network":
# Note: GetContent and thus the lookup here is not really needed, as we # Note: GetContent and thus the lookup here is not really needed, as we
# only have to request data to one node. # only have to request data to one node.
let foundContent = await proto2.getContent(contentKey) let foundContent = await proto2.getContent(contentKey)
check foundContent.isSome()
check: let accTrieNode = decodeSsz(foundContent.get(), AccountTrieNodeRetrieval)
foundContent.isSome() check accTrieNode.isOk()
let hash = keccakHash(foundContent.get()) let hash = keccakHash(accTrieNode.get().node.asSeq())
check hash.data == key check hash.data == key
proto1.stop()
proto2.stop()
await node1.closeWait() await node1.closeWait()
await node2.closeWait() await node2.closeWait()
@ -103,12 +94,14 @@ procSuite "State Network":
# TODO: Improve this test so it actually need to go through several # TODO: Improve this test so it actually need to go through several
# findNodes request, to properly test the lookup call. # findNodes request, to properly test the lookup call.
let let
trie = genesisToTrie("fluffy" / "tests" / "custom_genesis" / "chainid7.json") accounts =
node1 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20302)) getGenesisAlloc("fluffy" / "tests" / "custom_genesis" / "chainid7.json")
(trie, _) = accounts.toState()
node1 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20312))
sm1 = StreamManager.new(node1) sm1 = StreamManager.new(node1)
node2 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20303)) node2 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20313))
sm2 = StreamManager.new(node2) sm2 = StreamManager.new(node2)
node3 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20304)) node3 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20314))
sm3 = StreamManager.new(node3) sm3 = StreamManager.new(node3)
proto1 = proto1 =
@ -137,11 +130,19 @@ procSuite "State Network":
contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey
) )
contentId = toContentId(contentKey) contentId = toContentId(contentKey)
value = RetrievalContentValue(
contentType: accountTrieNode,
accountTrieNode: AccountTrieNodeRetrieval(node: TrieNode.init(v)),
)
discard proto2.contentDB.put(contentId, v, proto2.portalProtocol.localNode.id) discard proto2.contentDB.put(
contentId, value.encode(), proto2.portalProtocol.localNode.id
)
# Not needed right now as 1 node is enough considering node 1 is connected # Not needed right now as 1 node is enough considering node 1 is connected
# to both. # to both.
discard proto3.contentDB.put(contentId, v, proto3.portalProtocol.localNode.id) discard proto3.contentDB.put(
contentId, value.encode(), proto3.portalProtocol.localNode.id
)
# Get first key # Get first key
var nodeHash: NodeHash var nodeHash: NodeHash
@ -154,14 +155,16 @@ procSuite "State Network":
ContentKey(contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey) ContentKey(contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey)
let foundContent = await proto1.getContent(contentKey) let foundContent = await proto1.getContent(contentKey)
check foundContent.isSome()
check: let accTrieNode = decodeSsz(foundContent.get(), AccountTrieNodeRetrieval)
foundContent.isSome() check accTrieNode.isOk()
let hash = keccakHash(foundContent.get())
let hash = keccakHash(accTrieNode.get().node.asSeq())
check hash.data == firstKey check hash.data == firstKey
proto1.stop()
proto2.stop()
await node1.closeWait() await node1.closeWait()
await node2.closeWait() await node2.closeWait()
await node3.closeWait() await node3.closeWait()

View File

@ -12,6 +12,7 @@ import
stew/[byteutils, results], stew/[byteutils, results],
eth/p2p/discoveryv5/protocol as discv5_protocol, eth/p2p/discoveryv5/protocol as discv5_protocol,
../../network/wire/[portal_protocol, portal_stream], ../../network/wire/[portal_protocol, portal_stream],
../../network/history/[history_content, history_network],
../../network/state/[state_content, state_network], ../../network/state/[state_content, state_network],
../../database/content_db, ../../database/content_db,
.././test_helpers, .././test_helpers,
@ -35,7 +36,6 @@ procSuite "State Network Gossip":
let let
testCase = YamlRecursiveGossip.loadFromYaml(file).valueOr: testCase = YamlRecursiveGossip.loadFromYaml(file).valueOr:
raiseAssert "Cannot read test vector: " & error raiseAssert "Cannot read test vector: " & error
recursiveGossipSteps = testCase[0] recursiveGossipSteps = testCase[0]
numOfClients = recursiveGossipSteps.len() - 1 numOfClients = recursiveGossipSteps.len() - 1
@ -44,9 +44,10 @@ procSuite "State Network Gossip":
for i in 0 .. numOfClients: for i in 0 .. numOfClients:
let let
node = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20400 + i)) node = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20400 + i))
db = ContentDB.new("", uint32.high, inMemory = true)
sm = StreamManager.new(node) sm = StreamManager.new(node)
proto = hn = HistoryNetwork.new(node, db, sm, FinishedAccumulator())
StateNetwork.new(node, ContentDB.new("", uint32.high, inMemory = true), sm) proto = StateNetwork.new(node, db, sm, historyNetwork = Opt.some(hn))
proto.start() proto.start()
clients.add(proto) clients.add(proto)
@ -54,10 +55,30 @@ procSuite "State Network Gossip":
let let
currentNode = clients[i] currentNode = clients[i]
nextNode = clients[i + 1] nextNode = clients[i + 1]
check: check:
currentNode.portalProtocol.addNode(nextNode.portalProtocol.localNode) == Added currentNode.portalProtocol.addNode(nextNode.portalProtocol.localNode) == Added
(await currentNode.portalProtocol.ping(nextNode.portalProtocol.localNode)).isOk() (await currentNode.portalProtocol.ping(nextNode.portalProtocol.localNode)).isOk()
let
blockHeader = BlockHeader(
stateRoot: Hash256.fromHex(
"0x1ad7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61"
)
)
headerRlp = rlp.encode(blockHeader)
blockHeaderWithProof = BlockHeaderWithProof(
header: ByteList.init(headerRlp), proof: BlockHeaderProof.init()
)
value = recursiveGossipSteps[0].content_value.hexToSeqByte()
decodedValue = SSZ.decode(value, AccountTrieNodeOffer)
contentKey = history_content.ContentKey
.init(history_content.ContentType.blockHeader, decodedValue.blockHash)
.encode()
contentId = history_content.toContentId(contentKey)
clients[i].contentDB.put(contentId, SSZ.encode(blockHeaderWithProof))
for i in 0 .. numOfClients - 1: for i in 0 .. numOfClients - 1:
let let
pair = recursiveGossipSteps[i] pair = recursiveGossipSteps[i]
@ -65,11 +86,11 @@ procSuite "State Network Gossip":
nextNode = clients[i + 1] nextNode = clients[i + 1]
key = ByteList.init(pair.content_key.hexToSeqByte()) key = ByteList.init(pair.content_key.hexToSeqByte())
decodedKey = key.decode().valueOr: decodedKey = state_content.decode(key).valueOr:
raiseAssert "Cannot decode key" raiseAssert "Cannot decode key"
nextKey = ByteList.init(recursiveGossipSteps[1].content_key.hexToSeqByte()) nextKey = ByteList.init(recursiveGossipSteps[1].content_key.hexToSeqByte())
decodedNextKey = nextKey.decode().valueOr: decodedNextKey = state_content.decode(nextKey).valueOr:
raiseAssert "Cannot decode key" raiseAssert "Cannot decode key"
value = pair.content_value.hexToSeqByte() value = pair.content_value.hexToSeqByte()

View File

@ -6,9 +6,11 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms. # at your option. This file may not be copied, modified, or distributed except according to those terms.
import import
std/os, std/[os, strutils],
results,
unittest2, unittest2,
stew/byteutils, stew/byteutils,
eth/common,
../../network/state/state_content, ../../network/state/state_content,
../../network/state/state_validation, ../../network/state/state_validation,
../../eth_data/yaml_utils ../../eth_data/yaml_utils
@ -35,7 +37,15 @@ type YamlContractBytecodeKV = object
type YamlContractBytecodeKVs = seq[YamlContractBytecodeKV] type YamlContractBytecodeKVs = seq[YamlContractBytecodeKV]
type YamlRecursiveGossipKV = object
content_key: string
content_value: string
type YamlRecursiveGossipKVs = seq[seq[YamlRecursiveGossipKV]]
suite "State Validation": suite "State Validation":
# Retrieval validation tests
test "Validate valid AccountTrieNodeRetrieval nodes": test "Validate valid AccountTrieNodeRetrieval nodes":
const file = testVectorDir / "account_trie_node.yaml" const file = testVectorDir / "account_trie_node.yaml"
@ -52,6 +62,7 @@ suite "State Validation":
validateFetchedAccountTrieNode( validateFetchedAccountTrieNode(
contentKey.accountTrieNodeKey, contentValueRetrieval contentKey.accountTrieNodeKey, contentValueRetrieval
) )
.isOk()
test "Validate invalid AccountTrieNodeRetrieval nodes": test "Validate invalid AccountTrieNodeRetrieval nodes":
const file = testVectorDir / "account_trie_node.yaml" const file = testVectorDir / "account_trie_node.yaml"
@ -67,10 +78,13 @@ suite "State Validation":
contentValueRetrieval.node[^1] += 1 # Modify node hash contentValueRetrieval.node[^1] += 1 # Modify node hash
let res = validateFetchedAccountTrieNode(
contentKey.accountTrieNodeKey, contentValueRetrieval
)
check: check:
not validateFetchedAccountTrieNode( res.isErr()
contentKey.accountTrieNodeKey, contentValueRetrieval res.error() ==
) "hash of fetched account trie node doesn't match the expected node hash"
test "Validate valid ContractTrieNodeRetrieval nodes": test "Validate valid ContractTrieNodeRetrieval nodes":
const file = testVectorDir / "contract_storage_trie_node.yaml" const file = testVectorDir / "contract_storage_trie_node.yaml"
@ -88,6 +102,7 @@ suite "State Validation":
validateFetchedContractTrieNode( validateFetchedContractTrieNode(
contentKey.contractTrieNodeKey, contentValueRetrieval contentKey.contractTrieNodeKey, contentValueRetrieval
) )
.isOk()
test "Validate invalid ContractTrieNodeRetrieval nodes": test "Validate invalid ContractTrieNodeRetrieval nodes":
const file = testVectorDir / "contract_storage_trie_node.yaml" const file = testVectorDir / "contract_storage_trie_node.yaml"
@ -103,10 +118,13 @@ suite "State Validation":
contentValueRetrieval.node[^1] += 1 # Modify node hash contentValueRetrieval.node[^1] += 1 # Modify node hash
let res = validateFetchedContractTrieNode(
contentKey.contractTrieNodeKey, contentValueRetrieval
)
check: check:
not validateFetchedContractTrieNode( res.isErr()
contentKey.contractTrieNodeKey, contentValueRetrieval res.error() ==
) "hash of fetched contract trie node doesn't match the expected node hash"
test "Validate valid ContractCodeRetrieval nodes": test "Validate valid ContractCodeRetrieval nodes":
const file = testVectorDir / "contract_bytecode.yaml" const file = testVectorDir / "contract_bytecode.yaml"
@ -122,6 +140,7 @@ suite "State Validation":
check: check:
validateFetchedContractCode(contentKey.contractCodeKey, contentValueRetrieval) validateFetchedContractCode(contentKey.contractCodeKey, contentValueRetrieval)
.isOk()
test "Validate invalid ContractCodeRetrieval nodes": test "Validate invalid ContractCodeRetrieval nodes":
const file = testVectorDir / "contract_bytecode.yaml" const file = testVectorDir / "contract_bytecode.yaml"
@ -137,7 +156,467 @@ suite "State Validation":
contentValueRetrieval.code[^1] += 1 # Modify node hash contentValueRetrieval.code[^1] += 1 # Modify node hash
let res =
validateFetchedContractCode(contentKey.contractCodeKey, contentValueRetrieval)
check: check:
not validateFetchedContractCode( res.isErr()
contentKey.contractCodeKey, contentValueRetrieval res.error() == "hash of fetched bytecode doesn't match the expected code hash"
# Account offer validation tests
test "Validate valid AccountTrieNodeOffer nodes":
const file = testVectorDir / "account_trie_node.yaml"
const stateRoots = [
"0x1ad7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte(),
"0x1ad7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte(),
"0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544".hexToSeqByte(),
]
let testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
raiseAssert "Cannot read test vector: " & error
for i, testData in testCase:
var stateRoot: KeccakHash
copyMem(addr stateRoot, unsafeAddr stateRoots[i][0], 32)
block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), AccountTrieNodeOffer)
check:
validateOfferedAccountTrieNode(
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,
)
check:
validateOfferedAccountTrieNode(
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer
) )
.isOk()
test "Validate invalid AccountTrieNodeOffer nodes - bad state roots":
const file = testVectorDir / "account_trie_node.yaml"
const stateRoots = [
"0xBAD7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte(),
"0xBAD7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte(),
"0xBAD8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544".hexToSeqByte(),
]
let testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
raiseAssert "Cannot read test vector: " & error
for i, testData in testCase:
var stateRoot: KeccakHash
copyMem(addr stateRoot, unsafeAddr stateRoots[i][0], 32)
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), AccountTrieNodeOffer)
let res = validateOfferedAccountTrieNode(
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer
)
check:
res.isErr()
res.error() == "hash of proof root node doesn't match the expected root hash"
test "Validate invalid AccountTrieNodeOffer nodes - bad nodes":
const file = testVectorDir / "account_trie_node.yaml"
const stateRoots = [
"0x1ad7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte(),
"0x1ad7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte(),
"0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544".hexToSeqByte(),
]
let testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
raiseAssert "Cannot read test vector: " & error
for i, testData in testCase:
var stateRoot: KeccakHash
copyMem(addr stateRoot, unsafeAddr stateRoots[i][0], 32)
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), AccountTrieNodeOffer)
contentValueOffer.proof[0][0] += 1.byte
let res = validateOfferedAccountTrieNode(
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer
)
check:
res.isErr()
res.error() == "hash of proof root node doesn't match the expected root hash"
for i, testData in testCase:
if i == 1:
continue # second test case only has root node
var stateRoot: KeccakHash
copyMem(addr stateRoot, unsafeAddr stateRoots[i][0], 32)
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), AccountTrieNodeOffer)
contentValueOffer.proof[^2][^2] += 1.byte
let res = validateOfferedAccountTrieNode(
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
copyMem(addr stateRoot, unsafeAddr stateRoots[i][0], 32)
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), AccountTrieNodeOffer)
contentValueOffer.proof[^1][^1] += 1.byte
let res = validateOfferedAccountTrieNode(
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer
)
check:
res.isErr()
# Contract storage offer validation tests
test "Validate valid ContractTrieNodeOffer nodes":
const file = testVectorDir / "contract_storage_trie_node.yaml"
const stateRoots = [
"0x1ad7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte(),
"0x1ad7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte(),
]
let testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
raiseAssert "Cannot read test vector: " & error
for i, testData in testCase:
var stateRoot: KeccakHash
copyMem(addr stateRoot, unsafeAddr stateRoots[i][0], 32)
block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractTrieNodeOffer)
check:
validateOfferedContractTrieNode(
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,
)
check:
validateOfferedContractTrieNode(
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
.isOk()
test "Validate invalid ContractTrieNodeOffer nodes - bad state roots":
const file = testVectorDir / "contract_storage_trie_node.yaml"
const stateRoots = [
"0xBAD7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte(),
"0xBAD7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte(),
]
let testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
raiseAssert "Cannot read test vector: " & error
for i, testData in testCase:
var stateRoot: KeccakHash
copyMem(addr stateRoot, unsafeAddr stateRoots[i][0], 32)
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractTrieNodeOffer)
let res = validateOfferedContractTrieNode(
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
check:
res.isErr()
res.error() == "hash of proof root node doesn't match the expected root hash"
test "Validate invalid ContractTrieNodeOffer nodes - bad nodes":
const file = testVectorDir / "contract_storage_trie_node.yaml"
const stateRoots = [
"0x1ad7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte(),
"0x1ad7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte(),
]
let testCase = YamlTrieNodeKVs.loadFromYaml(file).valueOr:
raiseAssert "Cannot read test vector: " & error
for i, testData in testCase:
var stateRoot: KeccakHash
copyMem(addr stateRoot, unsafeAddr stateRoots[i][0], 32)
block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractTrieNodeOffer)
contentValueOffer.accountProof[0][0] += 1.byte
let res = validateOfferedContractTrieNode(
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)
contentValueOffer.storageProof[0][0] += 1.byte
let res = validateOfferedContractTrieNode(
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)
contentValueOffer.accountProof[^1][^1] += 1.byte
check:
validateOfferedContractTrieNode(
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)
contentValueOffer.storageProof[^1][^1] += 1.byte
check:
validateOfferedContractTrieNode(
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)
contentValueOffer.accountProof[^2][^2] += 1.byte
check:
validateOfferedContractTrieNode(
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
.isErr()
# Contract bytecode offer validation tests
test "Validate valid ContractCodeOffer nodes":
const file = testVectorDir / "contract_bytecode.yaml"
const stateRoots = [
"0x1ad7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte()
]
let testCase = YamlContractBytecodeKVs.loadFromYaml(file).valueOr:
raiseAssert "Cannot read test vector: " & error
for i, testData in testCase:
var stateRoot: KeccakHash
copyMem(addr stateRoot, unsafeAddr stateRoots[i][0], 32)
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractCodeOffer)
check:
validateOfferedContractCode(
stateRoot, contentKey.contractCodeKey, contentValueOffer
)
.isOk()
test "Validate invalid ContractCodeOffer nodes - bad state root":
const file = testVectorDir / "contract_bytecode.yaml"
const stateRoots = [
"0xBAD7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte()
]
let testCase = YamlContractBytecodeKVs.loadFromYaml(file).valueOr:
raiseAssert "Cannot read test vector: " & error
for i, testData in testCase:
var stateRoot: KeccakHash
copyMem(addr stateRoot, unsafeAddr stateRoots[i][0], 32)
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractCodeOffer)
let res = validateOfferedContractCode(
stateRoot, contentKey.contractCodeKey, contentValueOffer
)
check:
res.isErr()
res.error() == "hash of proof root node doesn't match the expected root hash"
test "Validate invalid ContractCodeOffer nodes - bad nodes and bytecode":
const file = testVectorDir / "contract_bytecode.yaml"
const stateRoots = [
"0x1ad7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte()
]
let testCase = YamlContractBytecodeKVs.loadFromYaml(file).valueOr:
raiseAssert "Cannot read test vector: " & error
for i, testData in testCase:
var stateRoot: KeccakHash
copyMem(addr stateRoot, unsafeAddr stateRoots[i][0], 32)
block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractCodeOffer)
contentValueOffer.accountProof[0][0] += 1.byte
let res = validateOfferedContractCode(
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()
var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractCodeOffer)
contentValueOffer.code[0] += 1.byte
let res = validateOfferedContractCode(
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()
var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractCodeOffer)
contentValueOffer.accountProof[^1][^1] += 1.byte
check:
validateOfferedContractCode(
stateRoot, contentKey.contractCodeKey, contentValueOffer
)
.isErr()
block:
let contentKey = decode(testData.content_key.hexToSeqByte().ByteList).get()
var contentValueOffer =
SSZ.decode(testData.content_value_offer.hexToSeqByte(), ContractCodeOffer)
contentValueOffer.code[^1] += 1.byte
let res = validateOfferedContractCode(
stateRoot, contentKey.contractCodeKey, contentValueOffer
)
check:
res.isErr()
res.error() == "hash of offered bytecode doesn't match the expected code hash"
# Recursive gossip offer validation tests
test "Validate valid AccountTrieNodeOffer recursive gossip nodes":
const file = testVectorDir / "recursive_gossip.yaml"
const stateRoots = [
"0x1ad7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte(),
"0x1ad7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte(),
"0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544".hexToSeqByte(),
]
let testCase = YamlRecursiveGossipKVs.loadFromYaml(file).valueOr:
raiseAssert "Cannot read test vector: " & error
for i, testData in testCase:
if i == 1:
continue
var stateRoot: KeccakHash
copyMem(addr stateRoot, unsafeAddr stateRoots[i][0], 32)
for kv in testData:
let contentKey = decode(kv.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer =
SSZ.decode(kv.content_value.hexToSeqByte(), AccountTrieNodeOffer)
check:
validateOfferedAccountTrieNode(
stateRoot, contentKey.accountTrieNodeKey, contentValueOffer
)
.isOk()
test "Validate valid ContractTrieNodeOffer recursive gossip nodes":
const file = testVectorDir / "recursive_gossip.yaml"
const stateRoots = [
"0x1ad7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte(),
"0x1ad7b80af0c28bc1489513346d2706885be90abb07f23ca28e50482adb392d61".hexToSeqByte(),
"0xd7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544".hexToSeqByte(),
]
let testCase = YamlRecursiveGossipKVs.loadFromYaml(file).valueOr:
raiseAssert "Cannot read test vector: " & error
for i, testData in testCase:
if i != 1:
continue
var stateRoot: KeccakHash
copyMem(addr stateRoot, unsafeAddr stateRoots[i][0], 32)
for kv in testData:
let contentKey = decode(kv.content_key.hexToSeqByte().ByteList).get()
let contentValueOffer =
SSZ.decode(kv.content_value.hexToSeqByte(), ContractTrieNodeOffer)
check:
validateOfferedContractTrieNode(
stateRoot, contentKey.contractTrieNodeKey, contentValueOffer
)
.isOk()

View File

@ -0,0 +1,142 @@
# Nimbus
# 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.
{.push raises: [].}
import
std/os,
unittest2,
stew/results,
eth/[common, trie, trie/trie_defs],
../../../nimbus/common/chain_config,
../../network/state/state_content,
../../network/state/state_validation,
./state_test_helpers
template checkValidProofsForExistingLeafs(
genAccounts: GenesisAlloc,
accountState: HexaryTrie,
storageStates: Table[EthAddress, HexaryTrie],
) =
for address, account in genAccounts:
var acc = newAccount(account.nonce, account.balance)
acc.codeHash = keccakHash(account.code)
let
accountProof = accountState.generateAccountProof(address)
accountTrieNodeKey = AccountTrieNodeKey(
path: Nibbles.init(keccakHash(address).data, true),
nodeHash: keccakHash(accountProof[^1].asSeq()),
)
accountTrieOffer = AccountTrieNodeOffer(proof: accountProof)
proofResult = validateOfferedAccountTrieNode(
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
)
check codeResult.isOk()
if account.code.len() > 0:
let storageState = storageStates[address]
acc.storageRoot = storageState.rootHash()
for slotKey, slotValue in account.storage:
let
storageProof = storageState.generateStorageProof(slotKey)
contractTrieNodeKey = ContractTrieNodeKey(
address: address,
path: Nibbles.init(keccakHash(toBytesBE(slotKey)).data, true),
nodeHash: keccakHash(storageProof[^1].asSeq()),
)
contractTrieOffer = ContractTrieNodeOffer(
storageProof: storageProof, accountProof: accountProof
)
proofResult = validateOfferedContractTrieNode(
accountState.rootHash(), contractTrieNodeKey, contractTrieOffer
)
check proofResult.isOk()
template checkInvalidProofsWithBadValue(
genAccounts: GenesisAlloc,
accountState: HexaryTrie,
storageStates: Table[EthAddress, HexaryTrie],
) =
for address, account in genAccounts:
var acc = newAccount(account.nonce, account.balance)
acc.codeHash = keccakHash(account.code)
var
accountProof = accountState.generateAccountProof(address)
accountTrieNodeKey = AccountTrieNodeKey(
path: Nibbles.init(keccakHash(address).data, true),
nodeHash: keccakHash(accountProof[^1].asSeq()),
)
accountProof[^1][^1] += 1 # bad account leaf value
let
accountTrieOffer = AccountTrieNodeOffer(proof: accountProof)
proofResult = validateOfferedAccountTrieNode(
accountState.rootHash(), accountTrieNodeKey, accountTrieOffer
)
check proofResult.isErr()
let
contractCodeKey = ContractCodeKey(address: address, codeHash: acc.codeHash)
contractCode = ContractCodeOffer(
code: Bytecode.init(@[1u8, 2, 3]), # bad code value
accountProof: accountProof,
)
codeResult = validateOfferedContractCode(
accountState.rootHash(), contractCodeKey, contractCode
)
check codeResult.isErr()
if account.code.len() > 0:
let storageState = storageStates[address]
acc.storageRoot = storageState.rootHash()
for slotKey, slotValue in account.storage:
var
storageProof = storageState.generateStorageProof(slotKey)
contractTrieNodeKey = ContractTrieNodeKey(
address: address,
path: Nibbles.init(keccakHash(toBytesBE(slotKey)).data, true),
nodeHash: keccakHash(storageProof[^1].asSeq()),
)
storageProof[^1][^1] += 1 # bad storage leaf value
let
contractTrieOffer = ContractTrieNodeOffer(
storageProof: storageProof, accountProof: accountProof
)
proofResult = validateOfferedContractTrieNode(
accountState.rootHash(), contractTrieNodeKey, contractTrieOffer
)
check proofResult.isErr()
suite "State Proof Verification Tests":
let genesisFiles = [
"berlin2000.json", "calaveras.json", "chainid1.json", "chainid7.json",
"devnet4.json", "devnet5.json", "holesky.json", "mainshadow1.json", "merge.json",
]
test "Valid proofs for existing leafs":
for file in genesisFiles:
let accounts = getGenesisAlloc("fluffy" / "tests" / "custom_genesis" / file)
let state = accounts.toState()
checkValidProofsForExistingLeafs(accounts, state[0], state[1])
test "Invalid proofs with bad value":
for file in genesisFiles:
let accounts = getGenesisAlloc("fluffy" / "tests" / "custom_genesis" / file)
var state = accounts.toState()
checkInvalidProofsWithBadValue(accounts, state[0], state[1])

View File

@ -0,0 +1,165 @@
# Fluffy
# Copyright (c) 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.
{.used.}
{.push raises: [].}
import
std/sequtils,
stew/byteutils,
unittest2,
stint,
nimcrypto/hash,
eth/trie/[hexary, db, trie_defs],
../../network/state/state_validation,
./state_test_helpers
proc getKeyBytes(i: int): seq[byte] =
let hash = keccakHash(u256(i).toBytesBE())
return toSeq(hash.data)
suite "MPT trie proof verification":
test "Validate proof for existing value":
let numValues = 1000
var trie = initHexaryTrie(newMemoryDB())
for i in 1 .. numValues:
let bytes = getKeyBytes(i)
trie.put(bytes, bytes)
let rootHash = trie.rootHash()
for i in 1 .. numValues:
let
kv = getKeyBytes(i)
proof = trie.getTrieProof(kv)
res = validateTrieProof(rootHash, kv.asNibbles(), proof)
check:
res.isOk()
test "Validate proof for non-existing value":
let numValues = 1000
var trie = initHexaryTrie(newMemoryDB())
for i in 1 .. numValues:
let bytes = getKeyBytes(i)
trie.put(bytes, bytes)
let
rootHash = trie.rootHash()
key = getKeyBytes(numValues + 1)
proof = trie.getTrieProof(key)
res = validateTrieProof(rootHash, key.asNibbles(), proof)
check:
res.isErr()
res.error() == "path contains more nibbles than expected for proof"
test "Validate proof for empty trie":
var trie = initHexaryTrie(newMemoryDB())
let
rootHash = trie.rootHash()
key = "not-exist".toBytes
proof = trie.getTrieProof(key)
res = validateTrieProof(rootHash, key.asNibbles(), proof)
check:
res.isErr()
res.error() == "invalid rlp node, expected 2 or 17 elements"
test "Validate proof for one element trie":
var trie = initHexaryTrie(newMemoryDB())
let key = "k".toBytes
trie.put(key, "v".toBytes)
let
rootHash = trie.rootHash
proof = trie.getTrieProof(key)
res = validateTrieProof(rootHash, key.asNibbles(), proof)
check:
res.isOk()
test "Validate proof bytes":
var trie = initHexaryTrie(newMemoryDB(), isPruning = false)
trie.put("doe".toBytes, "reindeer".toBytes)
trie.put("dog".toBytes, "puppy".toBytes)
trie.put("dogglesworth".toBytes, "cat".toBytes)
let rootHash = trie.rootHash
block:
let
key = "doe".toBytes
proof = trie.getTrieProof(key)
res = validateTrieProof(rootHash, key.asNibbles(), proof)
check:
res.isOk()
block:
let
key = "dog".toBytes
proof = trie.getTrieProof(key)
res = validateTrieProof(rootHash, key.asNibbles(), proof)
check:
res.isOk()
block:
let
key = "dogglesworth".toBytes
proof = trie.getTrieProof(key)
res = validateTrieProof(rootHash, key.asNibbles(), proof)
check:
res.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"
block:
let
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"
block:
let
key = "doe".toBytes
proof = newSeq[seq[byte]]().asTrieProof()
res = validateTrieProof(rootHash, key.asNibbles(), proof)
check:
res.isErr()
res.error() == "proof is empty"
block:
let
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"

View File

@ -9,12 +9,10 @@
import import
std/net, std/net,
eth/[common, keys, rlp, trie, trie/db], eth/[common, keys, rlp],
eth/p2p/discoveryv5/[enr, node, routing_table], eth/p2p/discoveryv5/[enr, node, routing_table],
eth/p2p/discoveryv5/protocol as discv5_protocol, eth/p2p/discoveryv5/protocol as discv5_protocol,
../network/history/[accumulator, history_content], ../network/history/[accumulator, history_content],
../network/state/experimental/state_proof_types,
../../nimbus/common/chain_config,
../database/content_db ../database/content_db
proc localAddress*(port: int): Address {.raises: [ValueError].} = proc localAddress*(port: int): Address {.raises: [ValueError].} =
@ -113,42 +111,3 @@ func buildHeadersWithProof*(
headersWithProof.add(?buildHeaderWithProof(header, epochAccumulators)) headersWithProof.add(?buildHeaderWithProof(header, epochAccumulators))
ok(headersWithProof) ok(headersWithProof)
proc getGenesisAlloc*(filePath: string): GenesisAlloc =
var cn: NetworkParams
if not loadNetworkParams(filePath, cn):
quit(1)
cn.genesis.alloc
proc toState*(
alloc: GenesisAlloc
): (AccountState, Table[EthAddress, StorageState]) {.raises: [RlpError].} =
var accountTrie = initHexaryTrie(newMemoryDB())
var storageStates = initTable[EthAddress, StorageState]()
for address, genAccount in alloc:
var storageRoot = EMPTY_ROOT_HASH
var codeHash = EMPTY_CODE_HASH
if genAccount.code.len() > 0:
var storageTrie = initHexaryTrie(newMemoryDB())
for slotKey, slotValue in genAccount.storage:
let key = keccakHash(toBytesBE(slotKey)).data
let value = rlp.encode(slotValue)
storageTrie.put(key, value)
storageStates[address] = storageTrie.StorageState
storageRoot = storageTrie.rootHash()
codeHash = keccakHash(genAccount.code)
let account = Account(
nonce: genAccount.nonce,
balance: genAccount.balance,
storageRoot: storageRoot,
codeHash: codeHash,
)
let key = keccakHash(address).data
let value = rlp.encode(account)
accountTrie.put(key, value)
(accountTrie.AccountState, storageStates)

View File

@ -1,178 +0,0 @@
# Nimbus
# 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.
{.push raises: [].}
import
std/os,
unittest2,
stew/results,
eth/[common, rlp, trie, trie/trie_defs],
../../nimbus/common/chain_config,
../network/state/experimental/
[state_proof_types, state_proof_generation, state_proof_verification],
./test_helpers
proc checkValidProofsForExistingLeafs(
genAccounts: GenesisAlloc,
accountState: AccountState,
storageStates: Table[EthAddress, StorageState],
) {.raises: [KeyError, RlpError].} =
for address, account in genAccounts:
var acc = newAccount(account.nonce, account.balance)
acc.codeHash = keccakHash(account.code)
let codeResult = verifyContractBytecode(acc.codeHash, account.code)
check codeResult.isOk()
if account.code.len() > 0:
let storageState = storageStates[address]
acc.storageRoot = storageState.rootHash()
for slotKey, slotValue in account.storage:
let storageProof = storageState.generateStorageProof(slotKey)
let proofResult =
verifyContractStorageSlot(acc.storageRoot, slotKey, slotValue, storageProof)
check proofResult.isOk()
let accountProof = accountState.generateAccountProof(address)
let proofResult = verifyAccount(accountState.rootHash(), address, acc, accountProof)
check proofResult.isOk()
proc checkValidProofsForMissingLeafs(
genAccounts: GenesisAlloc,
accountState: var AccountState,
storageStates: Table[EthAddress, StorageState],
) {.raises: [KeyError, RlpError].} =
var remainingAccounts = genAccounts.len()
for address, account in genAccounts:
if (remainingAccounts == 1):
break # can't generate proofs from an empty state
var acc = newAccount(account.nonce, account.balance)
acc.codeHash = keccakHash(account.code)
if account.code.len() > 0:
var storageState = storageStates[address]
acc.storageRoot = storageState.rootHash()
var remainingSlots = account.storage.len()
for slotKey, slotValue in account.storage:
if (remainingSlots == 1):
break # can't generate proofs from an empty state
storageState.HexaryTrie.del(keccakHash(toBytesBE(slotKey)).data)
# delete the slot from the state
dec remainingSlots
let storageProof = storageState.generateStorageProof(slotKey)
let proofResult =
verifyContractStorageSlot(acc.storageRoot, slotKey, slotValue, storageProof)
check proofResult.isErr()
accountState.HexaryTrie.del(keccakHash(address).data)
# delete the account from the state
dec remainingAccounts
let accountProof = accountState.generateAccountProof(address)
let proofResult = verifyAccount(accountState.rootHash(), address, acc, accountProof)
check proofResult.isErr()
proc checkInvalidProofsWithBadStateRoot(
genAccounts: GenesisAlloc,
accountState: AccountState,
storageStates: Table[EthAddress, StorageState],
) {.raises: [KeyError, RlpError].} =
let badHash =
toDigest("2cb1b80b285d09e0570fdbbb808e1d14e4ac53e36dcd95dbc268deec2915b3e7")
for address, account in genAccounts:
var acc = newAccount(account.nonce, account.balance)
acc.codeHash = keccakHash(account.code)
let codeResult = verifyContractBytecode(badHash, account.code)
check codeResult.isErr()
if account.code.len() > 0:
var storageState = storageStates[address]
acc.storageRoot = storageState.rootHash()
var remainingSlots = account.storage.len()
for slotKey, slotValue in account.storage:
let storageProof = storageState.generateStorageProof(slotKey)
let proofResult =
verifyContractStorageSlot(badHash, slotKey, slotValue, storageProof)
check:
proofResult.isErr()
proofResult.error() == "missing expected node"
let accountProof = accountState.generateAccountProof(address)
let proofResult = verifyAccount(badHash, address, acc, accountProof)
check:
proofResult.isErr()
proofResult.error() == "missing expected node"
proc checkInvalidProofsWithBadValue(
genAccounts: GenesisAlloc,
accountState: AccountState,
storageStates: Table[EthAddress, StorageState],
) {.raises: [KeyError, RlpError].} =
for address, account in genAccounts:
var acc = newAccount(account.nonce, account.balance)
acc.codeHash = keccakHash(account.code)
let codeResult = verifyContractBytecode(acc.codeHash, @[1u8, 2, 3]) # bad code value
check codeResult.isErr()
if account.code.len() > 0:
var storageState = storageStates[address]
acc.storageRoot = storageState.rootHash()
var remainingSlots = account.storage.len()
for slotKey, slotValue in account.storage:
let storageProof = storageState.generateStorageProof(slotKey)
let badSlotValue = slotValue + 1 # bad slot value
let proofResult = verifyContractStorageSlot(
acc.storageRoot, slotKey, badSlotValue, storageProof
)
check:
proofResult.isErr()
proofResult.error() == "proof does not contain expected value"
let accountProof = accountState.generateAccountProof(address)
inc acc.balance # bad account balance
let proofResult = verifyAccount(accountState.rootHash(), address, acc, accountProof)
check:
proofResult.isErr()
proofResult.error() == "proof does not contain expected value"
suite "State Proof Verification Tests":
let genesisFiles = ["berlin2000.json", "chainid1.json", "chainid7.json", "merge.json"]
test "Valid proofs for existing leafs":
for file in genesisFiles:
let accounts = getGenesisAlloc("fluffy" / "tests" / "custom_genesis" / file)
let state = accounts.toState()
checkValidProofsForExistingLeafs(accounts, state[0], state[1])
test "Valid proofs for missing leafs":
for file in genesisFiles:
let accounts = getGenesisAlloc("fluffy" / "tests" / "custom_genesis" / file)
var state = accounts.toState()
checkValidProofsForMissingLeafs(accounts, state[0], state[1])
test "Invalid proofs with bad state root":
for file in genesisFiles:
let accounts = getGenesisAlloc("fluffy" / "tests" / "custom_genesis" / file)
var state = accounts.toState()
checkInvalidProofsWithBadStateRoot(accounts, state[0], state[1])
test "Invalid proofs with bad value":
for file in genesisFiles:
let accounts = getGenesisAlloc("fluffy" / "tests" / "custom_genesis" / file)
var state = accounts.toState()
checkInvalidProofsWithBadValue(accounts, state[0], state[1])