2024-06-10 10:47:09 +00:00
|
|
|
# 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.
|
|
|
|
|
2024-06-14 05:38:24 +00:00
|
|
|
{.push raises: [].}
|
|
|
|
|
2024-06-10 10:47:09 +00:00
|
|
|
import
|
|
|
|
results,
|
|
|
|
chronos,
|
|
|
|
chronicles,
|
|
|
|
eth/common/eth_hash,
|
|
|
|
eth/common/eth_types,
|
|
|
|
../../common/common_utils,
|
|
|
|
./state_network,
|
|
|
|
./state_utils
|
|
|
|
|
|
|
|
export results, state_network
|
|
|
|
|
|
|
|
logScope:
|
|
|
|
topics = "portal_state"
|
|
|
|
|
|
|
|
proc getNextNodeHash(
|
|
|
|
trieNode: TrieNode, nibbles: UnpackedNibbles, nibbleIdx: var int
|
|
|
|
): Opt[(Nibbles, NodeHash)] =
|
2024-06-14 05:38:24 +00:00
|
|
|
# the trie node should have already been validated against the lookup hash
|
|
|
|
# so we expect that no rlp errors should be possible
|
|
|
|
try:
|
|
|
|
doAssert(nibbles.len() > 0)
|
|
|
|
doAssert(nibbleIdx < nibbles.len())
|
|
|
|
|
|
|
|
let trieNodeRlp = rlpFromBytes(trieNode.asSeq())
|
|
|
|
|
|
|
|
doAssert(not trieNodeRlp.isEmpty())
|
|
|
|
doAssert(trieNodeRlp.listLen() == 2 or trieNodeRlp.listLen() == 17)
|
|
|
|
|
|
|
|
if trieNodeRlp.listLen() == 17:
|
|
|
|
let nextNibble = nibbles[nibbleIdx]
|
|
|
|
doAssert(nextNibble < 16)
|
|
|
|
|
2024-06-19 16:21:23 +00:00
|
|
|
let nextHashBytes = trieNodeRlp.listElem(nextNibble.int).toBytes()
|
|
|
|
if nextHashBytes.len() == 0:
|
|
|
|
return Opt.none((Nibbles, NodeHash))
|
2024-06-14 05:38:24 +00:00
|
|
|
|
|
|
|
nibbleIdx += 1
|
|
|
|
return Opt.some(
|
2024-06-19 16:21:23 +00:00
|
|
|
(nibbles[0 ..< nibbleIdx].packNibbles(), KeccakHash.fromBytes(nextHashBytes))
|
2024-06-14 05:38:24 +00:00
|
|
|
)
|
2024-06-10 10:47:09 +00:00
|
|
|
|
2024-06-14 05:38:24 +00:00
|
|
|
# leaf or extension node
|
2024-08-23 07:46:23 +00:00
|
|
|
let
|
|
|
|
(_, isLeaf, prefix) = decodePrefix(trieNodeRlp.listElem(0))
|
|
|
|
unpackedPrefix = prefix.unpackNibbles()
|
|
|
|
|
|
|
|
if unpackedPrefix != nibbles[nibbleIdx ..< nibbleIdx + unpackedPrefix.len()]:
|
|
|
|
# The nibbles don't match so we stop the search and don't increment
|
|
|
|
# the nibble index to indicate how many nibbles were consumed
|
|
|
|
return Opt.none((Nibbles, NodeHash))
|
|
|
|
|
|
|
|
nibbleIdx += prefix.unpackNibbles().len()
|
2024-06-14 05:38:24 +00:00
|
|
|
if isLeaf:
|
|
|
|
return Opt.none((Nibbles, NodeHash))
|
2024-06-10 10:47:09 +00:00
|
|
|
|
2024-06-14 05:38:24 +00:00
|
|
|
# extension node
|
2024-06-19 16:21:23 +00:00
|
|
|
let nextHashBytes = trieNodeRlp.listElem(1).toBytes()
|
|
|
|
if nextHashBytes.len() == 0:
|
|
|
|
return Opt.none((Nibbles, NodeHash))
|
2024-06-10 10:47:09 +00:00
|
|
|
|
2024-06-14 05:38:24 +00:00
|
|
|
Opt.some(
|
2024-06-19 16:21:23 +00:00
|
|
|
(nibbles[0 ..< nibbleIdx].packNibbles(), KeccakHash.fromBytes(nextHashBytes))
|
2024-06-10 10:47:09 +00:00
|
|
|
)
|
2024-06-14 05:38:24 +00:00
|
|
|
except RlpError as e:
|
|
|
|
raiseAssert(e.msg)
|
2024-06-10 10:47:09 +00:00
|
|
|
|
|
|
|
proc getAccountProof(
|
2024-08-07 16:01:30 +00:00
|
|
|
n: StateNetwork, stateRoot: KeccakHash, address: EthAddress
|
2024-08-28 08:27:36 +00:00
|
|
|
): Future[Result[Opt[TrieProof], string]] {.async: (raises: [CancelledError]).} =
|
2024-06-10 10:47:09 +00:00
|
|
|
let nibbles = address.toPath().unpackNibbles()
|
|
|
|
|
|
|
|
var
|
|
|
|
nibblesIdx = 0
|
|
|
|
key = AccountTrieNodeKey.init(Nibbles.empty(), stateRoot)
|
|
|
|
proof = TrieProof.empty()
|
|
|
|
|
|
|
|
while nibblesIdx < nibbles.len():
|
2024-08-28 08:27:36 +00:00
|
|
|
let accountTrieNode = (await n.getAccountTrieNode(key)).valueOr:
|
|
|
|
return err("Failed to get account trie node when building account proof")
|
|
|
|
|
2024-06-10 10:47:09 +00:00
|
|
|
let
|
|
|
|
trieNode = accountTrieNode.node
|
2024-08-28 08:27:36 +00:00
|
|
|
added = proof.add(trieNode)
|
2024-06-10 10:47:09 +00:00
|
|
|
doAssert(added)
|
|
|
|
|
|
|
|
let (nextPath, nextNodeHash) = trieNode.getNextNodeHash(nibbles, nibblesIdx).valueOr:
|
|
|
|
break
|
|
|
|
|
|
|
|
key = AccountTrieNodeKey.init(nextPath, nextNodeHash)
|
|
|
|
|
2024-08-28 08:27:36 +00:00
|
|
|
# For now we don't return partial proofs or proofs for non-existing keys
|
|
|
|
# TODO: implement proofs for non-existing keys when needed for eth_getProof RPC
|
2024-08-23 07:46:23 +00:00
|
|
|
if nibblesIdx < nibbles.len():
|
2024-08-28 08:27:36 +00:00
|
|
|
ok(Opt.none(TrieProof))
|
2024-08-23 07:46:23 +00:00
|
|
|
else:
|
2024-08-28 08:27:36 +00:00
|
|
|
ok(Opt.some(proof))
|
2024-06-10 10:47:09 +00:00
|
|
|
|
|
|
|
proc getStorageProof(
|
2024-08-07 16:01:30 +00:00
|
|
|
n: StateNetwork, storageRoot: KeccakHash, address: EthAddress, storageKey: UInt256
|
2024-08-28 08:27:36 +00:00
|
|
|
): Future[Result[Opt[TrieProof], string]] {.async: (raises: [CancelledError]).} =
|
2024-06-10 10:47:09 +00:00
|
|
|
let nibbles = storageKey.toPath().unpackNibbles()
|
|
|
|
|
|
|
|
var
|
2024-08-07 16:01:30 +00:00
|
|
|
addressHash = keccakHash(address)
|
2024-06-10 10:47:09 +00:00
|
|
|
nibblesIdx = 0
|
2024-08-07 16:01:30 +00:00
|
|
|
key = ContractTrieNodeKey.init(addressHash, Nibbles.empty(), storageRoot)
|
2024-06-10 10:47:09 +00:00
|
|
|
proof = TrieProof.empty()
|
|
|
|
|
|
|
|
while nibblesIdx < nibbles.len():
|
2024-08-28 08:27:36 +00:00
|
|
|
let contractTrieNode = (await n.getContractTrieNode(key)).valueOr:
|
|
|
|
return err("Failed to get contract trie node when building account proof")
|
|
|
|
|
2024-06-10 10:47:09 +00:00
|
|
|
let
|
|
|
|
trieNode = contractTrieNode.node
|
2024-08-28 08:27:36 +00:00
|
|
|
added = proof.add(trieNode)
|
2024-06-10 10:47:09 +00:00
|
|
|
doAssert(added)
|
|
|
|
|
|
|
|
let (nextPath, nextNodeHash) = trieNode.getNextNodeHash(nibbles, nibblesIdx).valueOr:
|
|
|
|
break
|
|
|
|
|
2024-08-07 16:01:30 +00:00
|
|
|
key = ContractTrieNodeKey.init(addressHash, nextPath, nextNodeHash)
|
2024-06-10 10:47:09 +00:00
|
|
|
|
2024-08-28 08:27:36 +00:00
|
|
|
# For now we don't return partial proofs or proofs for non-existing keys
|
|
|
|
# TODO: implement proofs for non-existing keys when needed for eth_getProof RPC
|
2024-08-23 07:46:23 +00:00
|
|
|
if nibblesIdx < nibbles.len():
|
2024-08-28 08:27:36 +00:00
|
|
|
ok(Opt.none(TrieProof))
|
2024-08-23 07:46:23 +00:00
|
|
|
else:
|
2024-08-28 08:27:36 +00:00
|
|
|
ok(Opt.some(proof))
|
2024-06-10 10:47:09 +00:00
|
|
|
|
|
|
|
proc getAccount(
|
2024-08-27 12:35:27 +00:00
|
|
|
n: StateNetwork, stateRoot: KeccakHash, address: EthAddress
|
2024-06-14 12:21:30 +00:00
|
|
|
): Future[Opt[Account]] {.async: (raises: [CancelledError]).} =
|
2024-06-10 10:47:09 +00:00
|
|
|
let
|
2024-08-28 08:27:36 +00:00
|
|
|
maybeAccountProof = (await n.getAccountProof(stateRoot, address)).valueOr:
|
|
|
|
warn "Failed to get account proof", error = error
|
2024-06-10 10:47:09 +00:00
|
|
|
return Opt.none(Account)
|
2024-08-28 08:27:36 +00:00
|
|
|
accountProof = maybeAccountProof.valueOr:
|
|
|
|
info "Account doesn't exist, returning default account"
|
|
|
|
# return an empty account if the account doesn't exist
|
|
|
|
return Opt.some(newAccount())
|
2024-06-10 10:47:09 +00:00
|
|
|
account = accountProof.toAccount().valueOr:
|
2024-06-20 15:41:34 +00:00
|
|
|
error "Failed to get account from accountProof"
|
2024-06-10 10:47:09 +00:00
|
|
|
return Opt.none(Account)
|
|
|
|
|
|
|
|
Opt.some(account)
|
|
|
|
|
2024-08-27 12:35:27 +00:00
|
|
|
proc getBalanceByStateRoot*(
|
|
|
|
n: StateNetwork, stateRoot: KeccakHash, address: EthAddress
|
2024-06-14 12:21:30 +00:00
|
|
|
): Future[Opt[UInt256]] {.async: (raises: [CancelledError]).} =
|
2024-08-27 12:35:27 +00:00
|
|
|
let account = (await n.getAccount(stateRoot, address)).valueOr:
|
2024-06-10 10:47:09 +00:00
|
|
|
return Opt.none(UInt256)
|
|
|
|
|
|
|
|
Opt.some(account.balance)
|
|
|
|
|
2024-08-27 12:35:27 +00:00
|
|
|
proc getTransactionCountByStateRoot*(
|
|
|
|
n: StateNetwork, stateRoot: KeccakHash, address: EthAddress
|
2024-06-14 12:21:30 +00:00
|
|
|
): Future[Opt[AccountNonce]] {.async: (raises: [CancelledError]).} =
|
2024-08-27 12:35:27 +00:00
|
|
|
let account = (await n.getAccount(stateRoot, address)).valueOr:
|
2024-06-10 10:47:09 +00:00
|
|
|
return Opt.none(AccountNonce)
|
|
|
|
|
|
|
|
Opt.some(account.nonce)
|
|
|
|
|
2024-08-27 12:35:27 +00:00
|
|
|
proc getStorageAtByStateRoot*(
|
|
|
|
n: StateNetwork, stateRoot: KeccakHash, address: EthAddress, slotKey: UInt256
|
2024-06-14 12:21:30 +00:00
|
|
|
): Future[Opt[UInt256]] {.async: (raises: [CancelledError]).} =
|
2024-08-28 08:27:36 +00:00
|
|
|
let account = (await n.getAccount(stateRoot, address)).valueOr:
|
|
|
|
return Opt.none(UInt256)
|
|
|
|
|
|
|
|
if account.storageRoot == EMPTY_ROOT_HASH:
|
|
|
|
info "Storage doesn't exist, returning default storage value"
|
|
|
|
# return zero if the storage doesn't exist
|
|
|
|
return Opt.some(0.u256)
|
|
|
|
|
2024-06-10 10:47:09 +00:00
|
|
|
let
|
2024-08-28 08:27:36 +00:00
|
|
|
maybeStorageProof = (await n.getStorageProof(account.storageRoot, address, slotKey)).valueOr:
|
|
|
|
warn "Failed to get storage proof", error = error
|
2024-06-10 10:47:09 +00:00
|
|
|
return Opt.none(UInt256)
|
2024-08-28 08:27:36 +00:00
|
|
|
storageProof = maybeStorageProof.valueOr:
|
|
|
|
info "Slot doesn't exist, returning default storage value"
|
|
|
|
# return zero if the slot doesn't exist
|
|
|
|
return Opt.some(0.u256)
|
2024-06-10 10:47:09 +00:00
|
|
|
slotValue = storageProof.toSlot().valueOr:
|
2024-06-20 15:41:34 +00:00
|
|
|
error "Failed to get slot from storageProof"
|
2024-06-10 10:47:09 +00:00
|
|
|
return Opt.none(UInt256)
|
|
|
|
|
|
|
|
Opt.some(slotValue)
|
|
|
|
|
2024-08-27 12:35:27 +00:00
|
|
|
proc getCodeByStateRoot*(
|
|
|
|
n: StateNetwork, stateRoot: KeccakHash, address: EthAddress
|
2024-06-14 12:21:30 +00:00
|
|
|
): Future[Opt[Bytecode]] {.async: (raises: [CancelledError]).} =
|
2024-08-28 08:27:36 +00:00
|
|
|
let account = (await n.getAccount(stateRoot, address)).valueOr:
|
|
|
|
return Opt.none(Bytecode)
|
|
|
|
|
|
|
|
if account.codeHash == EMPTY_CODE_HASH:
|
|
|
|
info "Code doesn't exist, returning default code value"
|
|
|
|
# return empty bytecode if the code doesn't exist
|
|
|
|
return Opt.some(Bytecode.empty())
|
|
|
|
|
2024-06-10 10:47:09 +00:00
|
|
|
let
|
2024-08-07 16:01:30 +00:00
|
|
|
contractCodeKey = ContractCodeKey.init(keccakHash(address), account.codeHash)
|
2024-08-28 08:27:36 +00:00
|
|
|
contractCodeRetrieval = (await n.getContractCode(contractCodeKey)).valueOr:
|
|
|
|
warn "Failed to get contract code"
|
|
|
|
return Opt.none(Bytecode)
|
2024-06-10 10:47:09 +00:00
|
|
|
|
|
|
|
Opt.some(contractCodeRetrieval.code)
|
2024-08-27 12:35:27 +00:00
|
|
|
|
|
|
|
# Used by: eth_getBalance,
|
|
|
|
proc getBalance*(
|
|
|
|
n: StateNetwork, blockHash: BlockHash, address: EthAddress
|
|
|
|
): Future[Opt[UInt256]] {.async: (raises: [CancelledError]).} =
|
|
|
|
let stateRoot = (await n.getStateRootByBlockHash(blockHash)).valueOr:
|
|
|
|
warn "Failed to get state root by block hash"
|
|
|
|
return Opt.none(UInt256)
|
|
|
|
|
|
|
|
await n.getBalanceByStateRoot(stateRoot, address)
|
|
|
|
|
|
|
|
# Used by: eth_getTransactionCount
|
|
|
|
proc getTransactionCount*(
|
|
|
|
n: StateNetwork, blockHash: BlockHash, address: EthAddress
|
|
|
|
): Future[Opt[AccountNonce]] {.async: (raises: [CancelledError]).} =
|
|
|
|
let stateRoot = (await n.getStateRootByBlockHash(blockHash)).valueOr:
|
|
|
|
warn "Failed to get state root by block hash"
|
|
|
|
return Opt.none(AccountNonce)
|
|
|
|
|
|
|
|
await n.getTransactionCountByStateRoot(stateRoot, address)
|
|
|
|
|
|
|
|
# Used by: eth_getStorageAt
|
|
|
|
proc getStorageAt*(
|
|
|
|
n: StateNetwork, blockHash: BlockHash, address: EthAddress, slotKey: UInt256
|
|
|
|
): Future[Opt[UInt256]] {.async: (raises: [CancelledError]).} =
|
|
|
|
let stateRoot = (await n.getStateRootByBlockHash(blockHash)).valueOr:
|
|
|
|
warn "Failed to get state root by block hash"
|
|
|
|
return Opt.none(UInt256)
|
|
|
|
|
|
|
|
await n.getStorageAtByStateRoot(stateRoot, address, slotKey)
|
|
|
|
|
|
|
|
# Used by: eth_getCode
|
|
|
|
proc getCode*(
|
|
|
|
n: StateNetwork, blockHash: BlockHash, address: EthAddress
|
|
|
|
): Future[Opt[Bytecode]] {.async: (raises: [CancelledError]).} =
|
|
|
|
let stateRoot = (await n.getStateRootByBlockHash(blockHash)).valueOr:
|
|
|
|
warn "Failed to get state root by block hash"
|
|
|
|
return Opt.none(Bytecode)
|
|
|
|
|
|
|
|
await n.getCodeByStateRoot(stateRoot, address)
|