Skip to content

Commit

Permalink
Implementation of Nimbus eth_getProof RPC Endpoint. (#1960)
Browse files Browse the repository at this point in the history
* Initial implementation of eth_getProof endpoint.

* Implemented generation of account and storage proofs.

* Minor fixes and additional tests.

* Refactor getBranch code into a separate file.

* Improve usage of test data.

* Fix copyright year.

* Return zero hash for codeHash and storageHash if account doesn't exist.

* Update copyright notice and moved trie key hashing to inside getBranch proc.
  • Loading branch information
bhartnett authored Jan 9, 2024
1 parent c0d52ba commit 79c6bdc
Show file tree
Hide file tree
Showing 7 changed files with 387 additions and 14 deletions.
2 changes: 1 addition & 1 deletion nimbus/db/distinct_tries.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2023 Status Research & Development GmbH
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
Expand Down
6 changes: 4 additions & 2 deletions nimbus/db/state_db.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2018-2023 Status Research & Development GmbH
# Copyright (c) 2018-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
Expand Down Expand Up @@ -29,6 +29,8 @@ export
isDeadAccount,
isEmptyAccount,
newAccountStateDB,
rootHash
rootHash,
getAccountProof,
getStorageProof

# End
31 changes: 28 additions & 3 deletions nimbus/db/state_db/base.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2018-2023 Status Research & Development GmbH
# Copyright (c) 2018-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
Expand All @@ -8,10 +8,10 @@
import
std/[sets, strformat],
chronicles,
eth/[common, rlp],
eth/[common, rlp, trie/trie_defs],
../../constants,
../../utils/utils,
".."/[core_db, distinct_tries, storage_types]
".."/[core_db, distinct_tries, storage_types, trie_get_branch]

logScope:
topics = "state_db"
Expand Down Expand Up @@ -55,6 +55,10 @@ type
when aleth_compat:
cleared: HashSet[EthAddress]

MptNodeRlpBytes* = seq[byte]
AccountProof* = seq[MptNodeRlpBytes]
SlotProof* = seq[MptNodeRlpBytes]

proc pruneTrie*(db: AccountStateDB): bool =
db.trie.isPruning

Expand Down Expand Up @@ -249,6 +253,27 @@ proc isDeadAccount*(db: AccountStateDB, address: EthAddress): bool =
else:
result = true

proc removeEmptyRlpNode(branch: var seq[MptNodeRlpBytes]) =
if branch.len() == 1 and branch[0] == emptyRlp:
branch.del(0)

proc getAccountProof*(db: AccountStateDB, address: EthAddress): AccountProof =
var branch = db.trie.phk().getBranch(address)
removeEmptyRlpNode(branch)
branch

proc getStorageProof*(db: AccountStateDB, address: EthAddress, slots: seq[UInt256]): seq[SlotProof] =
var account = db.getAccount(address)
var storageTrie = db.getStorageTrie(account)

var slotProofs = newSeqOfCap[SlotProof](slots.len())
for slot in slots:
var branch = storageTrie.phk().getBranch(createTrieKeyFromSlot(slot))
removeEmptyRlpNode(branch)
slotProofs.add(branch)

slotProofs

# Note: `state_db.getCommittedStorage()` is nowhere used.
#
#proc getCommittedStorage*(db: AccountStateDB, address: EthAddress, slot: UInt256): UInt256 =
Expand Down
4 changes: 3 additions & 1 deletion nimbus/db/state_db/read_only.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2018-2023 Status Research & Development GmbH
# Copyright (c) 2018-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)
# or http://www.apache.org/licenses/LICENSE-2.0)
Expand Down Expand Up @@ -28,6 +28,8 @@ proc hasCodeOrNonce*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
proc accountExists*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
proc isDeadAccount*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
proc isEmptyAccount*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
proc getAccountProof*(db: ReadOnlyStateDB, address: EthAddress): AccountProof {.borrow.}
proc getStorageProof*(db: ReadOnlyStateDB, address: EthAddress, slots: seq[UInt256]): seq[SlotProof] {.borrow.}
#proc getCommittedStorage*(db: ReadOnlyStateDB, address: EthAddress, slot: UInt256): UInt256 {.borrow.}

# End
97 changes: 97 additions & 0 deletions nimbus/db/trie_get_branch.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Nimbus
# Copyright (c) 2023-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.

# This implementation of getBranch on the CoreDbPhkRef type is a temporary solution
# which can be removed once we get an equivient proc defined on the CoreDbPhkRef type
# in the db layer.

{.push raises: [].}

import
eth/[rlp, trie/nibbles],
"."/[core_db]

type
TrieNodeKey = object
hash: KeccakHash
usedBytes: uint8

template len(key: TrieNodeKey): int =
key.usedBytes.int

template asDbKey(k: TrieNodeKey): untyped =
doAssert k.usedBytes == 32
k.hash.data

template extensionNodeKey(r: Rlp): auto =
hexPrefixDecode r.listElem(0).toBytes

proc getLocalBytes(x: TrieNodeKey): seq[byte] =
## This proc should be used on nodes using the optimization
## of short values within the key.
doAssert x.usedBytes < 32
x.hash.data[0..<x.usedBytes]

proc dbGet(db: CoreDbRef, data: openArray[byte]): seq[byte]
{.gcsafe, raises: [].} =
db.kvt.get(data)

template keyToLocalBytes(db: CoreDbRef, k: TrieNodeKey): seq[byte] =
if k.len < 32: k.getLocalBytes
else: dbGet(db, k.asDbKey)

proc expectHash(r: Rlp): seq[byte] {.raises: [RlpError].} =
result = r.toBytes
if result.len != 32:
raise newException(RlpTypeMismatch,
"RLP expected to be a Keccak hash value, but has an incorrect length")

template getNode(db: CoreDbRef, elem: Rlp): untyped =
if elem.isList: @(elem.rawData)
else: dbGet(db, elem.expectHash)

proc getBranchAux(
db: CoreDbRef, node: openArray[byte],
fullPath: NibblesSeq,
pathIndex: int,
output: var seq[seq[byte]]) {.raises: [RlpError].} =
var nodeRlp = rlpFromBytes node
if not nodeRlp.hasData or nodeRlp.isEmpty: return

let path = fullPath.slice(pathIndex)
case nodeRlp.listLen
of 2:
let (isLeaf, k) = nodeRlp.extensionNodeKey
let sharedNibbles = sharedPrefixLen(path, k)
if sharedNibbles == k.len:
let value = nodeRlp.listElem(1)
if not isLeaf:
let nextLookup = getNode(db, value)
output.add nextLookup
getBranchAux(db, nextLookup, fullPath, pathIndex + sharedNibbles, output)
of 17:
if path.len != 0:
var branch = nodeRlp.listElem(path[0].int)
if not branch.isEmpty:
let nextLookup = getNode(db, branch)
output.add nextLookup
getBranchAux(db, nextLookup, fullPath, pathIndex + 1, output)
else:
raise newException(RlpError, "node has an unexpected number of children")

proc getBranch*(
self: CoreDbPhkRef;
key: openArray[byte]): seq[seq[byte]] {.raises: [RlpError].} =
let keyHash = keccakHash(key).data
result = @[]
var node = keyToLocalBytes(self.parent(), TrieNodeKey(
hash: self.rootHash(), usedBytes: self.rootHash().data.len().uint8))
result.add node
getBranchAux(self.parent(), node, initNibbleRange(keyHash), 0, result)
45 changes: 44 additions & 1 deletion nimbus/rpc/p2p.nim
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Nimbus
# Copyright (c) 2018-2023 Status Research & Development GmbH
# Copyright (c) 2018-2024 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
Expand Down Expand Up @@ -503,6 +503,49 @@ proc setupEthRpc*(
)
return logs

server.rpc("eth_getProof") do(data: Web3Address, slots: seq[UInt256], quantityTag: BlockTag) -> ProofResponse:
## Returns information about an account and storage slots (if the account is a contract
## and the slots are requested) along with account and storage proofs which prove the
## existence of the values in the state.
## See spec here: https://eips.ethereum.org/EIPS/eip-1186
##
## data: address of the account.
## slots: integers of the positions in the storage to return with storage proofs.
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
## Returns: the proof response containing the account, account proof and storage proof

let
accDB = stateDBFromTag(quantityTag)
address = data.ethAddr
acc = accDB.getAccount(address)
accExists = accDB.accountExists(address)
accountProof = accDB.getAccountProof(address)
slotProofs = accDB.getStorageProof(address, slots)

var storage = newSeqOfCap[StorageProof](slots.len)

for i, slotKey in slots:
let (slotValue, _) = accDB.getStorage(address, u256(slotKey))
storage.add(StorageProof(
key: u256(slotKey),
value: slotValue,
proof: seq[RlpEncodedBytes](slotProofs[i])))

if accExists:
ProofResponse(
address: w3Addr(address),
accountProof: seq[RlpEncodedBytes](accountProof),
balance: acc.balance,
nonce: w3Qty(acc.nonce),
codeHash: w3Hash(acc.codeHash),
storageHash: w3Hash(acc.storageRoot),
storageProof: storage)
else:
ProofResponse(
address: w3Addr(address),
accountProof: seq[RlpEncodedBytes](accountProof),
storageProof: storage)

#[
server.rpc("eth_newFilter") do(filterOptions: FilterOptions) -> int:
## Creates a filter object, based on filter options, to notify when the state changes (logs).
Expand Down
Loading

0 comments on commit 79c6bdc

Please sign in to comment.