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)
stateNetwork =
if Network.state in config.networks:
Opt.some(
StateNetwork.new(
d,
db,
streamManager,
bootstrapRecords = bootstrapRecords,
portalConfig = portalConfig,
)
)
else:
Opt.none(StateNetwork)
accumulator =
# Building an accumulator from header epoch files takes > 2m30s and is
# thus not really a viable option at start-up.
@ -234,6 +220,21 @@ proc run(config: PortalConf) {.raises: [CatchableError].} =
else:
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 =
# TODO: Currently disabled by default as it is not sufficiently polished.
# 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
nimcrypto/[hash, sha2, keccak],
stew/results,
results,
stint,
eth/common/eth_types,
ssz_serialization,
@ -208,16 +208,35 @@ func encode*(content: RetrievalContentValue): seq[byte] =
of contractCode:
SSZ.encode(content.contractCode)
func packNibbles*(nibbles: seq[byte]): Nibbles =
doAssert(nibbles.len() <= MAX_UNPACKED_NIBBLES_LEN, "Can't pack more than 64 nibbles")
func init*(T: type Nibbles, packed: openArray[byte], isEven: bool): T =
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)])
let isEvenLength = nibbles.len() mod 2 == 0
let isEvenLength = unpacked.len() mod 2 == 0
var
output = newSeqOfCap[byte](nibbles.len() div 2 + 1)
output = newSeqOfCap[byte](unpacked.len() div 2 + 1)
highNibble = isEvenLength
currentByte: byte = 0
@ -226,7 +245,7 @@ func packNibbles*(nibbles: seq[byte]): Nibbles =
else:
currentByte = 0x10
for i, nibble in nibbles:
for i, nibble in unpacked:
if highNibble:
currentByte = nibble shl 4
else:
@ -236,12 +255,12 @@ func packNibbles*(nibbles: seq[byte]): Nibbles =
Nibbles(output)
func unpackNibbles*(nibbles: Nibbles): seq[byte] =
doAssert(nibbles.len() <= MAX_PACKED_NIBBLES_LEN, "Packed nibbles length is too long")
func unpackNibbles*(packed: Nibbles): seq[byte] =
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:
continue

View File

@ -6,17 +6,20 @@
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import
stew/results,
results,
chronos,
chronicles,
eth/common/eth_hash,
eth/common,
eth/p2p/discoveryv5/[protocol, enr],
../../database/content_db,
../history/history_network,
../wire/[portal_protocol, portal_stream, portal_protocol_config],
./state_content,
./state_validation
export results
logScope:
topics = "portal_state"
@ -27,6 +30,7 @@ type StateNetwork* = ref object
contentDB*: ContentDB
contentQueue*: AsyncQueue[(Opt[NodeId], ContentKeysList, seq[seq[byte]])]
processContentLoop: Future[void]
historyNetwork: Opt[HistoryNetwork]
func toContentIdHandler(contentKey: ByteList): results.Opt[ContentId] =
ok(toContentId(contentKey))
@ -85,20 +89,25 @@ proc validateContent*(
): bool =
doAssert(contentKey.contentType == contentValue.contentType)
case contentKey.contentType
of unused:
warn "Received content with unused content type"
false
of accountTrieNode:
validateFetchedAccountTrieNode(
contentKey.accountTrieNodeKey, contentValue.accountTrieNode
)
of contractTrieNode:
validateFetchedContractTrieNode(
contentKey.contractTrieNodeKey, contentValue.contractTrieNode
)
of contractCode:
validateFetchedContractCode(contentKey.contractCodeKey, contentValue.contractCode)
let res =
case contentKey.contentType
of unused:
Result[void, string].err("Received content with unused content type")
of accountTrieNode:
validateFetchedAccountTrieNode(
contentKey.accountTrieNodeKey, contentValue.accountTrieNode
)
of contractTrieNode:
validateFetchedContractTrieNode(
contentKey.contractTrieNodeKey, contentValue.contractTrieNode
)
of contractCode:
validateFetchedContractCode(contentKey.contractCodeKey, contentValue.contractCode)
res.isOkOr:
warn "Validation of fetched content failed: ", error
res.isOk()
proc getContent*(n: StateNetwork, key: ContentKey): Future[Opt[seq[byte]]] {.async.} =
let
@ -137,34 +146,54 @@ proc getContent*(n: StateNetwork, key: ContentKey): Future[Opt[seq[byte]]] {.asy
# domain types.
return Opt.some(contentResult.content)
proc validateAccountTrieNode(
n: StateNetwork, key: ContentKey, contentValue: OfferContentValue
): bool =
true
proc getStateRootByBlockHash(
n: StateNetwork, hash: BlockHash
): Future[Opt[KeccakHash]] {.async.} =
if n.historyNetwork.isNone():
warn "History network is not available. Unable to get state root by block hash"
return Opt.none(KeccakHash)
proc validateContractTrieNode(
n: StateNetwork, key: ContentKey, contentValue: OfferContentValue
): bool =
true
let header = (await n.historyNetwork.get().getVerifiedBlockHeader(hash)).valueOr:
warn "Failed to get block header by hash", hash
return Opt.none(KeccakHash)
proc validateContractCode(
n: StateNetwork, key: ContentKey, contentValue: OfferContentValue
): bool =
true
Opt.some(header.stateRoot)
proc validateContent*(
n: StateNetwork, contentKey: ContentKey, contentValue: OfferContentValue
): bool =
): Future[Result[void, string]] {.async.} =
doAssert(contentKey.contentType == contentValue.contentType)
case contentKey.contentType
of unused:
warn "Received content with unused content type"
false
Result[void, string].err("Received content with unused content type")
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:
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:
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(
p: PortalProtocol,
@ -235,6 +264,7 @@ proc new*(
streamManager: StreamManager,
bootstrapRecords: openArray[Record] = [],
portalConfig: PortalProtocolConfig = defaultPortalProtocolConfig,
historyNetwork = Opt.none(HistoryNetwork),
): T =
let cq = newAsyncQueue[(Opt[NodeId], ContentKeysList, seq[seq[byte]])](50)
@ -253,8 +283,12 @@ proc new*(
portalProtocol.dbPut =
createStoreHandler(contentDB, portalConfig.radiusConfig, portalProtocol)
return
StateNetwork(portalProtocol: portalProtocol, contentDB: contentDB, contentQueue: cq)
return StateNetwork(
portalProtocol: portalProtocol,
contentDB: contentDB,
contentQueue: cq,
historyNetwork: historyNetwork,
)
proc processContentLoop(n: StateNetwork) {.async.} =
try:
@ -266,22 +300,24 @@ proc processContentLoop(n: StateNetwork) {.async.} =
(decodedKey, decodedValue) = decodeKV(contentKey, contentValue).valueOr:
error "Unable to decode offered Key/Value"
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)
info "Received offered content validated successfully", contentKey
(await n.validateContent(decodedKey, decodedValue)).isOkOr:
error "Received offered content failed validation", contentKey, error
continue
await gossipContent(
n.portalProtocol, maybeSrcNodeId, contentKey, decodedKey, contentValue,
decodedValue,
)
else:
error "Received offered content failed validation", contentKey
let
valueForRetrieval = decodedValue.offerContentToRetrievalContent().encode()
contentId = n.portalProtocol.toContentId(contentKey).valueOr:
error "Received offered content with invalid content key", contentKey
continue
n.portalProtocol.storeContent(contentKey, contentId, valueForRetrieval)
info "Received offered content validated successfully", contentKey
await gossipContent(
n.portalProtocol, maybeSrcNodeId, contentKey, decodedKey, contentValue,
decodedValue,
)
except CancelledError:
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).
# 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*(
trustedAccountTrieNodeKey: AccountTrieNodeKey,
accountTrieNode: AccountTrieNodeRetrieval,
): bool =
let expectedHash = trustedAccountTrieNodeKey.nodeHash
let actualHash = keccakHash(accountTrieNode.node.asSeq())
expectedHash == actualHash
): Result[void, string] =
if accountTrieNode.node.hashEquals(trustedAccountTrieNodeKey.nodeHash):
ok()
else:
err("hash of fetched account trie node doesn't match the expected node hash")
proc validateFetchedContractTrieNode*(
trustedContractTrieNodeKey: ContractTrieNodeKey,
contractTrieNode: ContractTrieNodeRetrieval,
): bool =
let expectedHash = trustedContractTrieNodeKey.nodeHash
let actualHash = keccakHash(contractTrieNode.node.asSeq())
expectedHash == actualHash
): Result[void, string] =
if contractTrieNode.node.hashEquals(trustedContractTrieNodeKey.nodeHash):
ok()
else:
err("hash of fetched contract trie node doesn't match the expected node hash")
proc validateFetchedContractCode*(
trustedContractCodeKey: ContractCodeKey, contractCode: ContractCodeRetrieval
): bool =
let expectedHash = trustedContractCodeKey.codeHash
let actualHash = keccakHash(contractCode.code.asSeq())
): Result[void, string] =
if contractCode.code.hashEquals(trustedContractCodeKey.codeHash):
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
./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_history_network,
./test_content_db,
@ -21,4 +17,5 @@ import
./test_beacon_chain_block_proof_capella,
./test_beacon_chain_historical_roots,
./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.
import
std/[os, json, sequtils, strutils, sugar],
stew/[byteutils, io2],
std/os,
nimcrypto/hash,
testutils/unittests,
chronos,
eth/trie/hexary_proof_verification,
eth/keys,
eth/trie,
eth/common/[eth_types, eth_hash],
eth/p2p/discoveryv5/protocol as discv5_protocol,
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/state/[state_content, state_network],
../../database/content_db,
.././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
../test_helpers,
./state_test_helpers
procSuite "State Network":
let rng = newRng()
asyncTest "Test Share Full State":
let
trie = genesisToTrie("fluffy" / "tests" / "custom_genesis" / "chainid7.json")
node1 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20302))
accounts =
getGenesisAlloc("fluffy" / "tests" / "custom_genesis" / "chainid7.json")
(trie, _) = accounts.toState()
node1 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20312))
sm1 = StreamManager.new(node1)
node2 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20303))
node2 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20313))
sm2 = StreamManager.new(node2)
proto1 =
StateNetwork.new(node1, ContentDB.new("", uint32.high, inMemory = true), sm1)
proto2 =
@ -72,8 +54,14 @@ procSuite "State Network":
contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey
)
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:
var nodeHash: NodeHash
@ -89,13 +77,16 @@ procSuite "State Network":
# Note: GetContent and thus the lookup here is not really needed, as we
# only have to request data to one node.
let foundContent = await proto2.getContent(contentKey)
check foundContent.isSome()
check:
foundContent.isSome()
let accTrieNode = decodeSsz(foundContent.get(), AccountTrieNodeRetrieval)
check accTrieNode.isOk()
let hash = keccakHash(foundContent.get())
let hash = keccakHash(accTrieNode.get().node.asSeq())
check hash.data == key
proto1.stop()
proto2.stop()
await node1.closeWait()
await node2.closeWait()
@ -103,12 +94,14 @@ procSuite "State Network":
# TODO: Improve this test so it actually need to go through several
# findNodes request, to properly test the lookup call.
let
trie = genesisToTrie("fluffy" / "tests" / "custom_genesis" / "chainid7.json")
node1 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20302))
accounts =
getGenesisAlloc("fluffy" / "tests" / "custom_genesis" / "chainid7.json")
(trie, _) = accounts.toState()
node1 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20312))
sm1 = StreamManager.new(node1)
node2 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20303))
node2 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20313))
sm2 = StreamManager.new(node2)
node3 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20304))
node3 = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20314))
sm3 = StreamManager.new(node3)
proto1 =
@ -137,11 +130,19 @@ procSuite "State Network":
contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey
)
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
# 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
var nodeHash: NodeHash
@ -154,14 +155,16 @@ procSuite "State Network":
ContentKey(contentType: accountTrieNode, accountTrieNodeKey: accountTrieNodeKey)
let foundContent = await proto1.getContent(contentKey)
check foundContent.isSome()
check:
foundContent.isSome()
let hash = keccakHash(foundContent.get())
let accTrieNode = decodeSsz(foundContent.get(), AccountTrieNodeRetrieval)
check accTrieNode.isOk()
let hash = keccakHash(accTrieNode.get().node.asSeq())
check hash.data == firstKey
proto1.stop()
proto2.stop()
await node1.closeWait()
await node2.closeWait()
await node3.closeWait()

View File

@ -12,6 +12,7 @@ import
stew/[byteutils, results],
eth/p2p/discoveryv5/protocol as discv5_protocol,
../../network/wire/[portal_protocol, portal_stream],
../../network/history/[history_content, history_network],
../../network/state/[state_content, state_network],
../../database/content_db,
.././test_helpers,
@ -35,7 +36,6 @@ procSuite "State Network Gossip":
let
testCase = YamlRecursiveGossip.loadFromYaml(file).valueOr:
raiseAssert "Cannot read test vector: " & error
recursiveGossipSteps = testCase[0]
numOfClients = recursiveGossipSteps.len() - 1
@ -44,9 +44,10 @@ procSuite "State Network Gossip":
for i in 0 .. numOfClients:
let
node = initDiscoveryNode(rng, PrivateKey.random(rng[]), localAddress(20400 + i))
db = ContentDB.new("", uint32.high, inMemory = true)
sm = StreamManager.new(node)
proto =
StateNetwork.new(node, ContentDB.new("", uint32.high, inMemory = true), sm)
hn = HistoryNetwork.new(node, db, sm, FinishedAccumulator())
proto = StateNetwork.new(node, db, sm, historyNetwork = Opt.some(hn))
proto.start()
clients.add(proto)
@ -54,10 +55,30 @@ procSuite "State Network Gossip":
let
currentNode = clients[i]
nextNode = clients[i + 1]
check:
currentNode.portalProtocol.addNode(nextNode.portalProtocol.localNode) == Added
(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:
let
pair = recursiveGossipSteps[i]
@ -65,11 +86,11 @@ procSuite "State Network Gossip":
nextNode = clients[i + 1]
key = ByteList.init(pair.content_key.hexToSeqByte())
decodedKey = key.decode().valueOr:
decodedKey = state_content.decode(key).valueOr:
raiseAssert "Cannot decode key"
nextKey = ByteList.init(recursiveGossipSteps[1].content_key.hexToSeqByte())
decodedNextKey = nextKey.decode().valueOr:
decodedNextKey = state_content.decode(nextKey).valueOr:
raiseAssert "Cannot decode key"
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.
import
std/os,
std/[os, strutils],
results,
unittest2,
stew/byteutils,
eth/common,
../../network/state/state_content,
../../network/state/state_validation,
../../eth_data/yaml_utils
@ -35,7 +37,15 @@ type YamlContractBytecodeKV = object
type YamlContractBytecodeKVs = seq[YamlContractBytecodeKV]
type YamlRecursiveGossipKV = object
content_key: string
content_value: string
type YamlRecursiveGossipKVs = seq[seq[YamlRecursiveGossipKV]]
suite "State Validation":
# Retrieval validation tests
test "Validate valid AccountTrieNodeRetrieval nodes":
const file = testVectorDir / "account_trie_node.yaml"
@ -52,6 +62,7 @@ suite "State Validation":
validateFetchedAccountTrieNode(
contentKey.accountTrieNodeKey, contentValueRetrieval
)
.isOk()
test "Validate invalid AccountTrieNodeRetrieval nodes":
const file = testVectorDir / "account_trie_node.yaml"
@ -67,10 +78,13 @@ suite "State Validation":
contentValueRetrieval.node[^1] += 1 # Modify node hash
let res = validateFetchedAccountTrieNode(
contentKey.accountTrieNodeKey, contentValueRetrieval
)
check:
not validateFetchedAccountTrieNode(
contentKey.accountTrieNodeKey, contentValueRetrieval
)
res.isErr()
res.error() ==
"hash of fetched account trie node doesn't match the expected node hash"
test "Validate valid ContractTrieNodeRetrieval nodes":
const file = testVectorDir / "contract_storage_trie_node.yaml"
@ -88,6 +102,7 @@ suite "State Validation":
validateFetchedContractTrieNode(
contentKey.contractTrieNodeKey, contentValueRetrieval
)
.isOk()
test "Validate invalid ContractTrieNodeRetrieval nodes":
const file = testVectorDir / "contract_storage_trie_node.yaml"
@ -103,10 +118,13 @@ suite "State Validation":
contentValueRetrieval.node[^1] += 1 # Modify node hash
let res = validateFetchedContractTrieNode(
contentKey.contractTrieNodeKey, contentValueRetrieval
)
check:
not validateFetchedContractTrieNode(
contentKey.contractTrieNodeKey, contentValueRetrieval
)
res.isErr()
res.error() ==
"hash of fetched contract trie node doesn't match the expected node hash"
test "Validate valid ContractCodeRetrieval nodes":
const file = testVectorDir / "contract_bytecode.yaml"
@ -122,6 +140,7 @@ suite "State Validation":
check:
validateFetchedContractCode(contentKey.contractCodeKey, contentValueRetrieval)
.isOk()
test "Validate invalid ContractCodeRetrieval nodes":
const file = testVectorDir / "contract_bytecode.yaml"
@ -137,7 +156,467 @@ suite "State Validation":
contentValueRetrieval.code[^1] += 1 # Modify node hash
let res =
validateFetchedContractCode(contentKey.contractCodeKey, contentValueRetrieval)
check:
not validateFetchedContractCode(
contentKey.contractCodeKey, contentValueRetrieval
res.isErr()
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
std/net,
eth/[common, keys, rlp, trie, trie/db],
eth/[common, keys, rlp],
eth/p2p/discoveryv5/[enr, node, routing_table],
eth/p2p/discoveryv5/protocol as discv5_protocol,
../network/history/[accumulator, history_content],
../network/state/experimental/state_proof_types,
../../nimbus/common/chain_config,
../database/content_db
proc localAddress*(port: int): Address {.raises: [ValueError].} =
@ -113,42 +111,3 @@ func buildHeadersWithProof*(
headersWithProof.add(?buildHeaderWithProof(header, epochAccumulators))
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])