2023-12-21 09:05:53 +00:00
|
|
|
# Nimbus
|
2024-01-10 08:17:58 +00:00
|
|
|
# Copyright (c) 2023-2024 Status Research & Development GmbH
|
2023-12-21 09:05:53 +00:00
|
|
|
# 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,
|
2024-01-09 15:09:02 +00:00
|
|
|
eth/[common, rlp, trie, trie/trie_defs],
|
|
|
|
../../nimbus/db/[ledger, core_db],
|
|
|
|
../../nimbus/common/chain_config,
|
|
|
|
../../stateless/[witness_from_tree, multi_keys, witness_types],
|
|
|
|
../network/state/experimental/[state_proof_types, state_proof_generation, state_proof_verification],
|
2023-12-21 09:05:53 +00:00
|
|
|
./test_helpers
|
|
|
|
|
|
|
|
proc checkValidProofsForExistingLeafs(
|
2024-01-09 15:09:02 +00:00
|
|
|
genAccounts: GenesisAlloc,
|
|
|
|
accountState: AccountState,
|
|
|
|
storageStates: Table[EthAddress, StorageState]) {.raises: [KeyError, RlpError].} =
|
2023-12-21 09:05:53 +00:00
|
|
|
|
|
|
|
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()
|
2024-01-09 15:09:02 +00:00
|
|
|
|
2023-12-21 09:05:53 +00:00
|
|
|
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(
|
2024-01-09 15:09:02 +00:00
|
|
|
genAccounts: GenesisAlloc,
|
|
|
|
accountState: var AccountState,
|
|
|
|
storageStates: Table[EthAddress, StorageState]) {.raises: [KeyError, RlpError].} =
|
2023-12-21 09:05:53 +00:00
|
|
|
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
|
|
|
|
|
2024-01-09 15:09:02 +00:00
|
|
|
storageState.HexaryTrie.del(keccakHash(toBytesBE(slotKey)).data) # delete the slot from the state
|
2023-12-21 09:05:53 +00:00
|
|
|
dec remainingSlots
|
|
|
|
|
|
|
|
let storageProof = storageState.generateStorageProof(slotKey)
|
|
|
|
let proofResult = verifyContractStorageSlot(acc.storageRoot, slotKey, slotValue, storageProof)
|
2024-01-09 15:09:02 +00:00
|
|
|
check proofResult.isErr()
|
2023-12-21 09:05:53 +00:00
|
|
|
|
2024-01-09 15:09:02 +00:00
|
|
|
accountState.HexaryTrie.del(keccakHash(address).data) # delete the account from the state
|
2023-12-21 09:05:53 +00:00
|
|
|
dec remainingAccounts
|
|
|
|
|
|
|
|
let accountProof = accountState.generateAccountProof(address)
|
|
|
|
let proofResult = verifyAccount(accountState.rootHash(), address, acc, accountProof)
|
2024-01-09 15:09:02 +00:00
|
|
|
check proofResult.isErr()
|
2023-12-21 09:05:53 +00:00
|
|
|
|
|
|
|
proc checkInvalidProofsWithBadStateRoot(
|
2024-01-09 15:09:02 +00:00
|
|
|
genAccounts: GenesisAlloc,
|
|
|
|
accountState: AccountState,
|
|
|
|
storageStates: Table[EthAddress, StorageState]) {.raises: [KeyError, RlpError].} =
|
2023-12-21 09:05:53 +00:00
|
|
|
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)
|
2024-01-09 15:09:02 +00:00
|
|
|
check:
|
2023-12-21 09:05:53 +00:00
|
|
|
proofResult.isErr()
|
|
|
|
proofResult.error() == "missing expected node"
|
|
|
|
|
|
|
|
let accountProof = accountState.generateAccountProof(address)
|
|
|
|
let proofResult = verifyAccount(badHash, address, acc, accountProof)
|
2024-01-09 15:09:02 +00:00
|
|
|
check:
|
2023-12-21 09:05:53 +00:00
|
|
|
proofResult.isErr()
|
2024-01-09 15:09:02 +00:00
|
|
|
proofResult.error() == "missing expected node"
|
2023-12-21 09:05:53 +00:00
|
|
|
|
|
|
|
proc checkInvalidProofsWithBadValue(
|
2024-01-09 15:09:02 +00:00
|
|
|
genAccounts: GenesisAlloc,
|
|
|
|
accountState: AccountState,
|
|
|
|
storageStates: Table[EthAddress, StorageState]) {.raises: [KeyError, RlpError].} =
|
2023-12-21 09:05:53 +00:00
|
|
|
|
|
|
|
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)
|
2024-01-09 15:09:02 +00:00
|
|
|
check:
|
2023-12-21 09:05:53 +00:00
|
|
|
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)
|
2024-01-09 15:09:02 +00:00
|
|
|
check:
|
2023-12-21 09:05:53 +00:00
|
|
|
proofResult.isErr()
|
2024-01-09 15:09:02 +00:00
|
|
|
proofResult.error() == "proof does not contain expected value"
|
|
|
|
|
|
|
|
proc setupStateDB(
|
|
|
|
genAccounts: GenesisAlloc,
|
|
|
|
stateDB: LedgerRef): (Hash256, MultikeysRef) =
|
|
|
|
|
|
|
|
var keys = newSeqOfCap[AccountKey](genAccounts.len)
|
|
|
|
|
|
|
|
for address, genAccount in genAccounts:
|
|
|
|
var storageKeys = newSeqOfCap[StorageSlot](genAccount.storage.len)
|
|
|
|
|
|
|
|
for slotKey, slotValue in genAccount.storage:
|
|
|
|
storageKeys.add(slotKey.toBytesBE)
|
|
|
|
stateDB.setStorage(address, slotKey, slotValue)
|
|
|
|
|
|
|
|
stateDB.setNonce(address, genAccount.nonce)
|
|
|
|
stateDB.setCode(address, genAccount.code)
|
|
|
|
stateDB.setBalance(address, genAccount.balance)
|
|
|
|
|
|
|
|
let sKeys = if storageKeys.len != 0: newMultiKeys(storageKeys) else: MultikeysRef(nil)
|
|
|
|
let codeTouched = genAccount.code.len > 0
|
|
|
|
keys.add(AccountKey(address: address, codeTouched: codeTouched, storageKeys: sKeys))
|
|
|
|
|
|
|
|
stateDB.persist()
|
|
|
|
(stateDB.rootHash, newMultiKeys(keys))
|
|
|
|
|
|
|
|
proc buildWitness(
|
|
|
|
genAccounts: GenesisAlloc): (KeccakHash, BlockWitness) {.raises: [CatchableError].} =
|
2023-12-21 09:05:53 +00:00
|
|
|
|
2024-01-09 15:09:02 +00:00
|
|
|
let
|
|
|
|
coreDb = newCoreDbRef(LegacyDbMemory)
|
|
|
|
accountsCache = AccountsCache.init(coreDb, emptyRlpHash, true)
|
|
|
|
(rootHash, multiKeys) = setupStateDB(genAccounts, accountsCache)
|
|
|
|
|
|
|
|
var wb = initWitnessBuilder(coreDb, rootHash, {wfEIP170})
|
|
|
|
(rootHash, wb.buildWitness(multiKeys))
|
|
|
|
|
|
|
|
proc checkWitnessDataMatchesAccounts(
|
|
|
|
genAccounts: GenesisAlloc,
|
|
|
|
witnessData: TableRef[EthAddress, AccountData]) {.raises: [CatchableError].} =
|
|
|
|
|
|
|
|
for address, genAccount in genAccounts:
|
|
|
|
let accountData = witnessData[address]
|
|
|
|
check genAccount.code == accountData.code
|
|
|
|
check genAccount.storage == accountData.storage
|
|
|
|
check genAccount.balance == accountData.account.balance
|
|
|
|
check genAccount.nonce == accountData.account.nonce
|
2023-12-21 09:05:53 +00:00
|
|
|
|
|
|
|
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])
|
2024-01-09 15:09:02 +00:00
|
|
|
|
2023-12-21 09:05:53 +00:00
|
|
|
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])
|
|
|
|
|
2024-01-09 15:09:02 +00:00
|
|
|
test "Block witness verification with valid state root":
|
|
|
|
for file in genesisFiles:
|
|
|
|
let
|
|
|
|
accounts = getGenesisAlloc("fluffy" / "tests" / "custom_genesis" / file)
|
|
|
|
(stateRoot, witness) = buildWitness(accounts)
|
|
|
|
verifyResult = verifyWitness(stateRoot, witness)
|
|
|
|
|
|
|
|
check verifyResult.isOk()
|
|
|
|
checkWitnessDataMatchesAccounts(accounts, verifyResult.get())
|
|
|
|
|
|
|
|
test "Block witness verification with invalid state root":
|
|
|
|
let badStateRoot = toDigest("2cb1b80b285d09e0570fdbbb808e1d14e4ac53e36dcd95dbc268deec2915b3e7")
|
|
|
|
|
|
|
|
for file in genesisFiles:
|
|
|
|
let
|
|
|
|
accounts = getGenesisAlloc("fluffy" / "tests" / "custom_genesis" / file)
|
|
|
|
(stateRoot, witness) = buildWitness(accounts)
|
|
|
|
verifyResult = verifyWitness(badStateRoot, witness)
|
|
|
|
|
|
|
|
check verifyResult.isErr()
|
|
|
|
check verifyResult.error() == "witness stateRoot doesn't match trustedStateRoot"
|