nimbus-eth1/fluffy/network/state/state_endpoints.nim

202 lines
6.5 KiB
Nim

# 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
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)] =
# 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)
let nextHashBytes = trieNodeRlp.listElem(nextNibble.int).toBytes()
if nextHashBytes.len() == 0:
return Opt.none((Nibbles, NodeHash))
nibbleIdx += 1
return Opt.some(
(nibbles[0 ..< nibbleIdx].packNibbles(), KeccakHash.fromBytes(nextHashBytes))
)
# leaf or extension node
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()
if isLeaf:
return Opt.none((Nibbles, NodeHash))
# extension node
let nextHashBytes = trieNodeRlp.listElem(1).toBytes()
if nextHashBytes.len() == 0:
return Opt.none((Nibbles, NodeHash))
Opt.some(
(nibbles[0 ..< nibbleIdx].packNibbles(), KeccakHash.fromBytes(nextHashBytes))
)
except RlpError as e:
raiseAssert(e.msg)
proc getAccountProof(
n: StateNetwork, stateRoot: KeccakHash, address: EthAddress
): Future[Opt[TrieProof]] {.async: (raises: [CancelledError]).} =
let nibbles = address.toPath().unpackNibbles()
var
nibblesIdx = 0
key = AccountTrieNodeKey.init(Nibbles.empty(), stateRoot)
proof = TrieProof.empty()
while nibblesIdx < nibbles.len():
let
accountTrieNode = (await n.getAccountTrieNode(key)).valueOr:
warn "Failed to get account trie node when building account proof"
return Opt.none(TrieProof)
trieNode = accountTrieNode.node
let added = proof.add(trieNode)
doAssert(added)
let (nextPath, nextNodeHash) = trieNode.getNextNodeHash(nibbles, nibblesIdx).valueOr:
break
key = AccountTrieNodeKey.init(nextPath, nextNodeHash)
if nibblesIdx < nibbles.len():
Opt.none(TrieProof)
else:
Opt.some(proof)
proc getStorageProof(
n: StateNetwork, storageRoot: KeccakHash, address: EthAddress, storageKey: UInt256
): Future[Opt[TrieProof]] {.async: (raises: [CancelledError]).} =
let nibbles = storageKey.toPath().unpackNibbles()
var
addressHash = keccakHash(address)
nibblesIdx = 0
key = ContractTrieNodeKey.init(addressHash, Nibbles.empty(), storageRoot)
proof = TrieProof.empty()
while nibblesIdx < nibbles.len():
let
contractTrieNode = (await n.getContractTrieNode(key)).valueOr:
warn "Failed to get contract trie node when building account proof"
return Opt.none(TrieProof)
trieNode = contractTrieNode.node
let added = proof.add(trieNode)
doAssert(added)
let (nextPath, nextNodeHash) = trieNode.getNextNodeHash(nibbles, nibblesIdx).valueOr:
break
key = ContractTrieNodeKey.init(addressHash, nextPath, nextNodeHash)
if nibblesIdx < nibbles.len():
Opt.none(TrieProof)
else:
Opt.some(proof)
proc getAccount(
n: StateNetwork, blockHash: BlockHash, address: EthAddress
): Future[Opt[Account]] {.async: (raises: [CancelledError]).} =
let
stateRoot = (await n.getStateRootByBlockHash(blockHash)).valueOr:
warn "Failed to get state root by block hash"
return Opt.none(Account)
accountProof = (await n.getAccountProof(stateRoot, address)).valueOr:
warn "Failed to get account proof"
return Opt.none(Account)
account = accountProof.toAccount().valueOr:
error "Failed to get account from accountProof"
return Opt.none(Account)
Opt.some(account)
# Used by: eth_getBalance,
proc getBalance*(
n: StateNetwork, blockHash: BlockHash, address: EthAddress
): Future[Opt[UInt256]] {.async: (raises: [CancelledError]).} =
let account = (await n.getAccount(blockHash, address)).valueOr:
return Opt.none(UInt256)
Opt.some(account.balance)
# Used by: eth_getTransactionCount
proc getTransactionCount*(
n: StateNetwork, blockHash: BlockHash, address: EthAddress
): Future[Opt[AccountNonce]] {.async: (raises: [CancelledError]).} =
let account = (await n.getAccount(blockHash, address)).valueOr:
return Opt.none(AccountNonce)
Opt.some(account.nonce)
# Used by: eth_getStorageAt
proc getStorageAt*(
n: StateNetwork, blockHash: BlockHash, address: EthAddress, slotKey: UInt256
): Future[Opt[UInt256]] {.async: (raises: [CancelledError]).} =
let
account = (await n.getAccount(blockHash, address)).valueOr:
return Opt.none(UInt256)
storageProof = (await n.getStorageProof(account.storageRoot, address, slotKey)).valueOr:
warn "Failed to get storage proof"
return Opt.none(UInt256)
slotValue = storageProof.toSlot().valueOr:
error "Failed to get slot from storageProof"
return Opt.none(UInt256)
Opt.some(slotValue)
# Used by: eth_getCode
proc getCode*(
n: StateNetwork, blockHash: BlockHash, address: EthAddress
): Future[Opt[Bytecode]] {.async: (raises: [CancelledError]).} =
let
account = (await n.getAccount(blockHash, address)).valueOr:
return Opt.none(Bytecode)
contractCodeKey = ContractCodeKey.init(keccakHash(address), account.codeHash)
let contractCodeRetrieval = (await n.getContractCode(contractCodeKey)).valueOr:
warn "Failed to get contract code"
return Opt.none(Bytecode)
Opt.some(contractCodeRetrieval.code)