From 32e1f94d78d8a222afae4e651343fd66b2ffbb3f Mon Sep 17 00:00:00 2001 From: web3-developer <51288821+web3-developer@users.noreply.github.com> Date: Thu, 21 Dec 2023 17:05:53 +0800 Subject: [PATCH] Portal State Proof Verification with Tests (#1951) * Initial implementation of account and storage proof verification for the portal state network. * Completed initial state proof verification loop test. * Completed proof verification tests. * Minor updates based on PR feedback and comments. * Add state proof verification test to test runner. --- .../experimental/state_proof_generation.nim | 30 +++ .../experimental/state_proof_verification.nim | 69 +++++++ fluffy/tests/all_fluffy_tests.nim | 1 + fluffy/tests/custom_genesis/berlin2000.json | 56 ++++++ fluffy/tests/custom_genesis/chainid1.json | 46 +++++ fluffy/tests/custom_genesis/merge.json | 66 +++++++ fluffy/tests/test_helpers.nim | 45 ++++- .../tests/test_state_proof_verification.nim | 172 ++++++++++++++++++ 8 files changed, 483 insertions(+), 2 deletions(-) create mode 100644 fluffy/network/state/experimental/state_proof_generation.nim create mode 100644 fluffy/network/state/experimental/state_proof_verification.nim create mode 100644 fluffy/tests/custom_genesis/berlin2000.json create mode 100644 fluffy/tests/custom_genesis/chainid1.json create mode 100644 fluffy/tests/custom_genesis/merge.json create mode 100644 fluffy/tests/test_state_proof_verification.nim diff --git a/fluffy/network/state/experimental/state_proof_generation.nim b/fluffy/network/state/experimental/state_proof_generation.nim new file mode 100644 index 000000000..0e7ea3fa9 --- /dev/null +++ b/fluffy/network/state/experimental/state_proof_generation.nim @@ -0,0 +1,30 @@ +# Nimbus +# Copyright (c) 2023 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_verification + +type + AccountState* = HexaryTrie + StorageState* = HexaryTrie + +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 + diff --git a/fluffy/network/state/experimental/state_proof_verification.nim b/fluffy/network/state/experimental/state_proof_verification.nim new file mode 100644 index 000000000..7ccea0549 --- /dev/null +++ b/fluffy/network/state/experimental/state_proof_verification.nim @@ -0,0 +1,69 @@ +# Nimbus +# Copyright (c) 2023 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 + +export results + +type + MptProof = seq[seq[byte]] + AccountProof* = distinct MptProof + StorageProof* = distinct MptProof + +proc verifyAccount*( + trustedStateRoot: KeccakHash, + address: EthAddress, + account: Account, + proof: AccountProof): Result[void, string] = + + 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: + # For an account that doesn't exist yet, which is fine. + ok() + of InvalidProof: + err(proofResult.errorMsg) + +proc verifyContractStorageSlot*( + trustedStorageRoot: KeccakHash, + slotKey: UInt256, + slotValue: UInt256, + proof: StorageProof): Result[void, string] = + + 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: + # This is for a slot that doesn't have anything stored at it, but that's fine. + ok() + 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") diff --git a/fluffy/tests/all_fluffy_tests.nim b/fluffy/tests/all_fluffy_tests.nim index 077ba10c4..1fe057503 100644 --- a/fluffy/tests/all_fluffy_tests.nim +++ b/fluffy/tests/all_fluffy_tests.nim @@ -11,6 +11,7 @@ import ./test_portal_wire_protocol, ./test_state_distance, ./test_state_network, + ./test_state_proof_verification, ./test_accumulator, ./test_history_network, ./test_content_db, diff --git a/fluffy/tests/custom_genesis/berlin2000.json b/fluffy/tests/custom_genesis/berlin2000.json new file mode 100644 index 000000000..28d6b7fe5 --- /dev/null +++ b/fluffy/tests/custom_genesis/berlin2000.json @@ -0,0 +1,56 @@ +{ + "config": { + "chainId": 1, + "homesteadBlock": 0, + "daoForkSupport": true, + "eip150Block": 0, + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "berlinBlock": 2000 + }, + "genesis": { + "nonce": "0x0", + "timestamp": "0x0", + "extraData": "0x00", + "gasLimit": "0x5f5e100", + "difficulty": "0x20000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "coinbase": "0x2adc25665018aa1fe0e6bc666dac8fc2697ff9ba", + "alloc": { + "0000000000000000000000000000000000000100": { + "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0160005500", + "balance": "0xba1a9ce0ba1a9ce" + }, + "0000000000000000000000000000000000000101": { + "code": "0x60047fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0160005500", + "balance": "0xba1a9ce0ba1a9ce" + }, + "0000000000000000000000000000000000000102": { + "code": "0x60017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0160005500", + "balance": "0xba1a9ce0ba1a9ce" + }, + "0000000000000000000000000000000000000103": { + "code": "0x600060000160005500", + "balance": "0xba1a9ce0ba1a9ce" + }, + "0000000000000000000000000000000000000104": { + "code": "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff60010160005500", + "balance": "0xba1a9ce0ba1a9ce" + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b": { + "balance": "0xba1a9ce0ba1a9ce" + }, + "cccccccccccccccccccccccccccccccccccccccc": { + "code": "0x600060006000600060006004356101000162fffffff100", + "balance": "0xba1a9ce0ba1a9ce" + } + }, + "number": "0x0", + "gasUsed": "0x0", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } +} diff --git a/fluffy/tests/custom_genesis/chainid1.json b/fluffy/tests/custom_genesis/chainid1.json new file mode 100644 index 000000000..de383d444 --- /dev/null +++ b/fluffy/tests/custom_genesis/chainid1.json @@ -0,0 +1,46 @@ +{ + "config": { + "chainId": 1, + "homesteadBlock": 0, + "eip150Block": 0, + "eip158Block": 0, + "londonBlock": 1337 + }, + "genesis": { + "coinbase": "0x0000000000000000000000000000000000000000", + "difficulty": "0x20000", + "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000658bdf435d810c91414ec09147daa6db624063790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x2fefd8", + "nonce": "0x0000000000000000", + "timestamp": "0x1234", + "alloc": { + "cf49fda3be353c69b41ed96333cd24302da4556f": { + "balance": "0x123450000000000000000" + }, + "0161e041aad467a890839d5b08b138c1e6373072": { + "balance": "0x123450000000000000000" + }, + "87da6a8c6e9eff15d703fc2773e32f6af8dbe301": { + "balance": "0x123450000000000000000" + }, + "b97de4b8c857e4f6bc354f226dc3249aaee49209": { + "balance": "0x123450000000000000000" + }, + "c5065c9eeebe6df2c2284d046bfc906501846c51": { + "balance": "0x123450000000000000000" + }, + "0000000000000000000000000000000000000314": { + "balance": "0x0", + "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a223e05d1461006a578063abd1a0cf1461008d578063abfced1d146100d4578063e05c914a14610110578063e6768b451461014c575b610000565b346100005761007761019d565b6040518082815260200191505060405180910390f35b34610000576100be600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506101a3565b6040518082815260200191505060405180910390f35b346100005761010e600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101ed565b005b346100005761014a600480803590602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610236565b005b346100005761017960048080359060200190919080359060200190919080359060200190919050506103c4565b60405180848152602001838152602001828152602001935050505060405180910390f35b60005481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b919050565b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b5050565b7f6031a8d62d7c95988fa262657cd92107d90ed96e08d8f867d32f26edfe85502260405180905060405180910390a17f47e2689743f14e97f7dcfa5eec10ba1dff02f83b3d1d4b9c07b206cbbda66450826040518082815260200191505060405180910390a1817fa48a6b249a5084126c3da369fbc9b16827ead8cb5cdc094b717d3f1dcd995e2960405180905060405180910390a27f7890603b316f3509577afd111710f9ebeefa15e12f72347d9dffd0d65ae3bade81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18073ffffffffffffffffffffffffffffffffffffffff167f7efef9ea3f60ddc038e50cccec621f86a0195894dc0520482abf8b5c6b659e4160405180905060405180910390a28181604051808381526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a05b5050565b6000600060008585859250925092505b935093509390505600a165627a7a72305820aaf842d0d0c35c45622c5263cbb54813d2974d3999c8c38551d7c613ea2bc1170029", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x1234", + "0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9": "0x01" + } + }, + "0000000000000000000000000000000000000315": { + "balance": "0x9999999999999999999999999999999", + "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef2769ca1461003e575b610000565b3461000057610078600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061007a565b005b8173ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051809050600060405180830381858888f1935050505015610106578173ffffffffffffffffffffffffffffffffffffffff167f30a3c50752f2552dcc2b93f5b96866280816a986c0c0408cb6778b9fa198288f826040518082815260200191505060405180910390a25b5b50505600a165627a7a72305820637991fabcc8abad4294bf2bb615db78fbec4edff1635a2647d3894e2daf6a610029" + } + } + } +} \ No newline at end of file diff --git a/fluffy/tests/custom_genesis/merge.json b/fluffy/tests/custom_genesis/merge.json new file mode 100644 index 000000000..07f82c2ef --- /dev/null +++ b/fluffy/tests/custom_genesis/merge.json @@ -0,0 +1,66 @@ +{ + "config": { + "chainId": 7, + "homesteadBlock": 0, + "eip150Block": 0, + "eip150Hash": "0x5de1ee4135274003348e80b788e5afa4b18b18d320a5622218d5c493fedf5689", + "eip155Block": 0, + "eip158Block": 0, + "byzantiumBlock": 0, + "constantinopleBlock": 0, + "petersburgBlock": 0, + "istanbulBlock": 0, + "muirGlacierBlock": 0, + "berlinBlock": 0, + "londonBlock": 0, + "clique": { + "epoch": 3000, + "period": 1 + } + }, + "genesis": { + "coinbase": "0x0000000000000000000000000000000000000000", + "difficulty": "0x30000", + "extraData": "0x0000000000000000000000000000000000000000000000000000000000000000658bdf435d810c91414ec09147daa6db624063790000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "gasLimit": "0x2fefd8", + "nonce": "0x0000000000000000", + "timestamp": "0x1234", + "alloc": { + "cf49fda3be353c69b41ed96333cd24302da4556f": { + "balance": "0x123450000000000000000" + }, + "0161e041aad467a890839d5b08b138c1e6373072": { + "balance": "0x123450000000000000000" + }, + "87da6a8c6e9eff15d703fc2773e32f6af8dbe301": { + "balance": "0x123450000000000000000" + }, + "b97de4b8c857e4f6bc354f226dc3249aaee49209": { + "balance": "0x123450000000000000000" + }, + "c5065c9eeebe6df2c2284d046bfc906501846c51": { + "balance": "0x123450000000000000000" + }, + "0000000000000000000000000000000000000314": { + "balance": "0x0", + "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063a223e05d1461006a578063abd1a0cf1461008d578063abfced1d146100d4578063e05c914a14610110578063e6768b451461014c575b610000565b346100005761007761019d565b6040518082815260200191505060405180910390f35b34610000576100be600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919050506101a3565b6040518082815260200191505060405180910390f35b346100005761010e600480803573ffffffffffffffffffffffffffffffffffffffff169060200190919080359060200190919050506101ed565b005b346100005761014a600480803590602001909190803573ffffffffffffffffffffffffffffffffffffffff16906020019091905050610236565b005b346100005761017960048080359060200190919080359060200190919080359060200190919050506103c4565b60405180848152602001838152602001828152602001935050505060405180910390f35b60005481565b6000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205490505b919050565b80600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055505b5050565b7f6031a8d62d7c95988fa262657cd92107d90ed96e08d8f867d32f26edfe85502260405180905060405180910390a17f47e2689743f14e97f7dcfa5eec10ba1dff02f83b3d1d4b9c07b206cbbda66450826040518082815260200191505060405180910390a1817fa48a6b249a5084126c3da369fbc9b16827ead8cb5cdc094b717d3f1dcd995e2960405180905060405180910390a27f7890603b316f3509577afd111710f9ebeefa15e12f72347d9dffd0d65ae3bade81604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390a18073ffffffffffffffffffffffffffffffffffffffff167f7efef9ea3f60ddc038e50cccec621f86a0195894dc0520482abf8b5c6b659e4160405180905060405180910390a28181604051808381526020018273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019250505060405180910390a05b5050565b6000600060008585859250925092505b935093509390505600a165627a7a72305820aaf842d0d0c35c45622c5263cbb54813d2974d3999c8c38551d7c613ea2bc1170029", + "storage": { + "0x0000000000000000000000000000000000000000000000000000000000000000": "0x1234", + "0x6661e9d6d8b923d5bbaab1b96e1dd51ff6ea2a93520fdc9eb75d059238b8c5e9": "0x01" + } + }, + "0000000000000000000000000000000000000315": { + "balance": "0x9999999999999999999999999999999", + "code": "0x60606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff168063ef2769ca1461003e575b610000565b3461000057610078600480803573ffffffffffffffffffffffffffffffffffffffff1690602001909190803590602001909190505061007a565b005b8173ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051809050600060405180830381858888f1935050505015610106578173ffffffffffffffffffffffffffffffffffffffff167f30a3c50752f2552dcc2b93f5b96866280816a986c0c0408cb6778b9fa198288f826040518082815260200191505060405180910390a25b5b50505600a165627a7a72305820637991fabcc8abad4294bf2bb615db78fbec4edff1635a2647d3894e2daf6a610029" + }, + "0000000000000000000000000000000000000316": { + "balance": "0x0", + "code": "0x444355" + }, + "0000000000000000000000000000000000000317": { + "balance": "0x0", + "code": "0x600160003555" + } + } + } +} diff --git a/fluffy/tests/test_helpers.nim b/fluffy/tests/test_helpers.nim index 65f50418c..adf714069 100644 --- a/fluffy/tests/test_helpers.nim +++ b/fluffy/tests/test_helpers.nim @@ -7,10 +7,14 @@ import stew/shims/net, - eth/[keys, rlp], + eth/[common, keys, rlp, trie, trie/db], eth/p2p/discoveryv5/[enr, node, routing_table], eth/p2p/discoveryv5/protocol as discv5_protocol, - ../network/history/[accumulator, history_content] + ../network/history/[accumulator, history_content], + ../network/state/experimental/state_proof_generation, + ../../nimbus/db/core_db, + ../../nimbus/common/[chain_config], + ../database/content_db proc localAddress*(port: int): Address = Address(ip: parseIpAddress("127.0.0.1"), port: Port(port)) @@ -108,3 +112,40 @@ func buildHeadersWithProof*( ? 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) diff --git a/fluffy/tests/test_state_proof_verification.nim b/fluffy/tests/test_state_proof_verification.nim new file mode 100644 index 000000000..b7d244d33 --- /dev/null +++ b/fluffy/tests/test_state_proof_verification.nim @@ -0,0 +1,172 @@ +# Nimbus +# Copyright (c) 2023 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/trie, + ../../nimbus/db/core_db, + ../../nimbus/common/[chain_config], + ../network/state/experimental/[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.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.isOk() + + accountState.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.isOk() + +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]) +