mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-14 14:24:32 +00:00
833719a866
These create only confusion as if they are actual different types and it is within their usage already clear what they are about because of the name of the variable or the function. They are also nowhere aliased like this in any of the Portal specification.
316 lines
11 KiB
Nim
316 lines
11 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/rlp,
|
|
eth/common/[hashes, accounts, addresses],
|
|
./state_network,
|
|
./state_utils
|
|
|
|
export results, state_network
|
|
|
|
logScope:
|
|
topics = "portal_state"
|
|
|
|
proc getNextNodeHash(
|
|
trieNode: TrieNode, nibbles: UnpackedNibbles, nibbleIdx: var int
|
|
): Opt[(Nibbles, Hash32)] =
|
|
# 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, Hash32))
|
|
|
|
nibbleIdx += 1
|
|
return Opt.some(
|
|
(nibbles[0 ..< nibbleIdx].packNibbles(), Hash32.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, Hash32))
|
|
|
|
nibbleIdx += prefix.unpackNibbles().len()
|
|
if isLeaf:
|
|
return Opt.none((Nibbles, Hash32))
|
|
|
|
# extension node
|
|
let nextHashBytes = trieNodeRlp.listElem(1).toBytes()
|
|
if nextHashBytes.len() == 0:
|
|
return Opt.none((Nibbles, Hash32))
|
|
|
|
Opt.some((nibbles[0 ..< nibbleIdx].packNibbles(), Hash32.fromBytes(nextHashBytes)))
|
|
except RlpError as e:
|
|
raiseAssert(e.msg)
|
|
|
|
proc getAccountProof(
|
|
n: StateNetwork, stateRoot: Hash32, address: Address
|
|
): Future[Result[(TrieProof, bool), string]] {.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:
|
|
return err("Failed to get account trie node when building account proof")
|
|
|
|
let
|
|
trieNode = accountTrieNode.node
|
|
added = proof.add(trieNode)
|
|
doAssert(added)
|
|
|
|
let (nextPath, nextNodeHash) = trieNode.getNextNodeHash(nibbles, nibblesIdx).valueOr:
|
|
break
|
|
|
|
key = AccountTrieNodeKey.init(nextPath, nextNodeHash)
|
|
|
|
doAssert(nibblesIdx <= nibbles.len())
|
|
ok((proof, nibblesIdx == nibbles.len()))
|
|
|
|
proc getStorageProof(
|
|
n: StateNetwork, storageRoot: Hash32, address: Address, slotKey: UInt256
|
|
): Future[Result[(TrieProof, bool), string]] {.async: (raises: [CancelledError]).} =
|
|
let nibbles = slotKey.toPath().unpackNibbles()
|
|
|
|
var
|
|
addressHash = keccak256(address.data)
|
|
nibblesIdx = 0
|
|
key = ContractTrieNodeKey.init(addressHash, Nibbles.empty(), storageRoot)
|
|
proof = TrieProof.empty()
|
|
|
|
while nibblesIdx < nibbles.len():
|
|
let contractTrieNode = (await n.getContractTrieNode(key)).valueOr:
|
|
return err("Failed to get contract trie node when building account proof")
|
|
|
|
let
|
|
trieNode = contractTrieNode.node
|
|
added = proof.add(trieNode)
|
|
doAssert(added)
|
|
|
|
let (nextPath, nextNodeHash) = trieNode.getNextNodeHash(nibbles, nibblesIdx).valueOr:
|
|
break
|
|
|
|
key = ContractTrieNodeKey.init(addressHash, nextPath, nextNodeHash)
|
|
|
|
doAssert(nibblesIdx <= nibbles.len())
|
|
ok((proof, nibblesIdx == nibbles.len()))
|
|
|
|
proc getAccount(
|
|
n: StateNetwork, stateRoot: Hash32, address: Address
|
|
): Future[Opt[Account]] {.async: (raises: [CancelledError]).} =
|
|
let (accountProof, exists) = (await n.getAccountProof(stateRoot, address)).valueOr:
|
|
warn "Failed to get account proof", error = error
|
|
return Opt.none(Account)
|
|
|
|
if not exists:
|
|
info "Account doesn't exist, returning default account"
|
|
# return an empty account if the account doesn't exist
|
|
return Opt.some(EMPTY_ACCOUNT)
|
|
|
|
let account = accountProof.toAccount().valueOr:
|
|
error "Failed to get account from accountProof"
|
|
return Opt.none(Account)
|
|
|
|
Opt.some(account)
|
|
|
|
proc getBalanceByStateRoot*(
|
|
n: StateNetwork, stateRoot: Hash32, address: Address
|
|
): Future[Opt[UInt256]] {.async: (raises: [CancelledError]).} =
|
|
let account = (await n.getAccount(stateRoot, address)).valueOr:
|
|
return Opt.none(UInt256)
|
|
|
|
Opt.some(account.balance)
|
|
|
|
proc getTransactionCountByStateRoot*(
|
|
n: StateNetwork, stateRoot: Hash32, address: Address
|
|
): Future[Opt[AccountNonce]] {.async: (raises: [CancelledError]).} =
|
|
let account = (await n.getAccount(stateRoot, address)).valueOr:
|
|
return Opt.none(AccountNonce)
|
|
|
|
Opt.some(account.nonce)
|
|
|
|
proc getStorageAtByStateRoot*(
|
|
n: StateNetwork, stateRoot: Hash32, address: Address, slotKey: UInt256
|
|
): Future[Opt[UInt256]] {.async: (raises: [CancelledError]).} =
|
|
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)
|
|
|
|
let (storageProof, exists) = (
|
|
await n.getStorageProof(account.storageRoot, address, slotKey)
|
|
).valueOr:
|
|
warn "Failed to get storage proof", error = error
|
|
return Opt.none(UInt256)
|
|
|
|
if not exists:
|
|
info "Slot doesn't exist, returning default storage value"
|
|
# return zero if the slot doesn't exist
|
|
return Opt.some(0.u256)
|
|
|
|
let slotValue = storageProof.toSlot().valueOr:
|
|
error "Failed to get slot from storageProof"
|
|
return Opt.none(UInt256)
|
|
|
|
Opt.some(slotValue)
|
|
|
|
proc getCodeByStateRoot*(
|
|
n: StateNetwork, stateRoot: Hash32, address: Address
|
|
): Future[Opt[Bytecode]] {.async: (raises: [CancelledError]).} =
|
|
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())
|
|
|
|
let
|
|
contractCodeKey = ContractCodeKey.init(keccak256(address.data), account.codeHash)
|
|
contractCodeRetrieval = (await n.getContractCode(contractCodeKey)).valueOr:
|
|
warn "Failed to get contract code"
|
|
return Opt.none(Bytecode)
|
|
|
|
Opt.some(contractCodeRetrieval.code)
|
|
|
|
type Proofs* = ref object
|
|
account*: Account
|
|
accountProof*: TrieProof
|
|
slots*: seq[(UInt256, UInt256)]
|
|
slotProofs*: seq[TrieProof]
|
|
|
|
proc getProofsByStateRoot*(
|
|
n: StateNetwork, stateRoot: Hash32, address: Address, slotKeys: seq[UInt256]
|
|
): Future[Opt[Proofs]] {.async: (raises: [CancelledError]).} =
|
|
let
|
|
(accountProof, accountExists) = (await n.getAccountProof(stateRoot, address)).valueOr:
|
|
warn "Failed to get account proof", error = error
|
|
return Opt.none(Proofs)
|
|
account =
|
|
if accountExists:
|
|
accountProof.toAccount().valueOr:
|
|
error "Failed to get account from accountProof"
|
|
return Opt.none(Proofs)
|
|
else:
|
|
EMPTY_ACCOUNT
|
|
storageExists = account.storageRoot != EMPTY_ROOT_HASH
|
|
|
|
var
|
|
slots = newSeqOfCap[(UInt256, UInt256)](slotKeys.len)
|
|
slotProofs = newSeqOfCap[TrieProof](slotKeys.len)
|
|
|
|
for slotKey in slotKeys:
|
|
if not storageExists:
|
|
slots.add((slotKey, 0.u256))
|
|
slotProofs.add(TrieProof.empty())
|
|
continue
|
|
|
|
let
|
|
(storageProof, slotExists) = (
|
|
await n.getStorageProof(account.storageRoot, address, slotKey)
|
|
).valueOr:
|
|
warn "Failed to get storage proof", error = error
|
|
return Opt.none(Proofs)
|
|
slotValue =
|
|
if slotExists:
|
|
storageProof.toSlot().valueOr:
|
|
error "Failed to get slot from storageProof"
|
|
return Opt.none(Proofs)
|
|
else:
|
|
0.u256
|
|
slots.add((slotKey, slotValue))
|
|
slotProofs.add(storageProof)
|
|
|
|
return Opt.some(
|
|
Proofs(
|
|
account: account, accountProof: accountProof, slots: slots, slotProofs: slotProofs
|
|
)
|
|
)
|
|
|
|
# Used by: eth_getBalance,
|
|
proc getBalance*(
|
|
n: StateNetwork, blockNumOrHash: uint64 | Hash32, address: Address
|
|
): Future[Opt[UInt256]] {.async: (raises: [CancelledError]).} =
|
|
let stateRoot = (await n.getStateRootByBlockNumOrHash(blockNumOrHash)).valueOr:
|
|
warn "Failed to get state root by block number or hash", blockNumOrHash
|
|
return Opt.none(UInt256)
|
|
|
|
await n.getBalanceByStateRoot(stateRoot, address)
|
|
|
|
# Used by: eth_getTransactionCount
|
|
proc getTransactionCount*(
|
|
n: StateNetwork, blockNumOrHash: uint64 | Hash32, address: Address
|
|
): Future[Opt[AccountNonce]] {.async: (raises: [CancelledError]).} =
|
|
let stateRoot = (await n.getStateRootByBlockNumOrHash(blockNumOrHash)).valueOr:
|
|
warn "Failed to get state root by block number or hash", blockNumOrHash
|
|
return Opt.none(AccountNonce)
|
|
|
|
await n.getTransactionCountByStateRoot(stateRoot, address)
|
|
|
|
# Used by: eth_getStorageAt
|
|
proc getStorageAt*(
|
|
n: StateNetwork, blockNumOrHash: uint64 | Hash32, address: Address, slotKey: UInt256
|
|
): Future[Opt[UInt256]] {.async: (raises: [CancelledError]).} =
|
|
let stateRoot = (await n.getStateRootByBlockNumOrHash(blockNumOrHash)).valueOr:
|
|
warn "Failed to get state root by block number or hash", blockNumOrHash
|
|
return Opt.none(UInt256)
|
|
|
|
await n.getStorageAtByStateRoot(stateRoot, address, slotKey)
|
|
|
|
# Used by: eth_getCode
|
|
proc getCode*(
|
|
n: StateNetwork, blockNumOrHash: uint64 | Hash32, address: Address
|
|
): Future[Opt[Bytecode]] {.async: (raises: [CancelledError]).} =
|
|
let stateRoot = (await n.getStateRootByBlockNumOrHash(blockNumOrHash)).valueOr:
|
|
warn "Failed to get state root by block number or hash", blockNumOrHash
|
|
return Opt.none(Bytecode)
|
|
|
|
await n.getCodeByStateRoot(stateRoot, address)
|
|
|
|
# Used by: eth_getProof
|
|
proc getProofs*(
|
|
n: StateNetwork,
|
|
blockNumOrHash: uint64 | Hash32,
|
|
address: Address,
|
|
slotKeys: seq[UInt256],
|
|
): Future[Opt[Proofs]] {.async: (raises: [CancelledError]).} =
|
|
let stateRoot = (await n.getStateRootByBlockNumOrHash(blockNumOrHash)).valueOr:
|
|
warn "Failed to get state root by block number or hash", blockNumOrHash
|
|
return Opt.none(Proofs)
|
|
|
|
await n.getProofsByStateRoot(stateRoot, address, slotKeys)
|