Fluffy state debug endpoints (#2578)

* Add debug endpoints that support looking up state by state root.

* Test lookup by state root endpoints.
This commit is contained in:
web3-developer 2024-08-27 20:35:27 +08:00 committed by GitHub
parent 8bf581e72d
commit fa59898388
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 178 additions and 34 deletions

View File

@ -136,12 +136,9 @@ proc getStorageProof(
Opt.some(proof) Opt.some(proof)
proc getAccount( proc getAccount(
n: StateNetwork, blockHash: BlockHash, address: EthAddress n: StateNetwork, stateRoot: KeccakHash, address: EthAddress
): Future[Opt[Account]] {.async: (raises: [CancelledError]).} = ): Future[Opt[Account]] {.async: (raises: [CancelledError]).} =
let 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: accountProof = (await n.getAccountProof(stateRoot, address)).valueOr:
warn "Failed to get account proof" warn "Failed to get account proof"
return Opt.none(Account) return Opt.none(Account)
@ -151,30 +148,27 @@ proc getAccount(
Opt.some(account) Opt.some(account)
# Used by: eth_getBalance, proc getBalanceByStateRoot*(
proc getBalance*( n: StateNetwork, stateRoot: KeccakHash, address: EthAddress
n: StateNetwork, blockHash: BlockHash, address: EthAddress
): Future[Opt[UInt256]] {.async: (raises: [CancelledError]).} = ): Future[Opt[UInt256]] {.async: (raises: [CancelledError]).} =
let account = (await n.getAccount(blockHash, address)).valueOr: let account = (await n.getAccount(stateRoot, address)).valueOr:
return Opt.none(UInt256) return Opt.none(UInt256)
Opt.some(account.balance) Opt.some(account.balance)
# Used by: eth_getTransactionCount proc getTransactionCountByStateRoot*(
proc getTransactionCount*( n: StateNetwork, stateRoot: KeccakHash, address: EthAddress
n: StateNetwork, blockHash: BlockHash, address: EthAddress
): Future[Opt[AccountNonce]] {.async: (raises: [CancelledError]).} = ): Future[Opt[AccountNonce]] {.async: (raises: [CancelledError]).} =
let account = (await n.getAccount(blockHash, address)).valueOr: let account = (await n.getAccount(stateRoot, address)).valueOr:
return Opt.none(AccountNonce) return Opt.none(AccountNonce)
Opt.some(account.nonce) Opt.some(account.nonce)
# Used by: eth_getStorageAt proc getStorageAtByStateRoot*(
proc getStorageAt*( n: StateNetwork, stateRoot: KeccakHash, address: EthAddress, slotKey: UInt256
n: StateNetwork, blockHash: BlockHash, address: EthAddress, slotKey: UInt256
): Future[Opt[UInt256]] {.async: (raises: [CancelledError]).} = ): Future[Opt[UInt256]] {.async: (raises: [CancelledError]).} =
let let
account = (await n.getAccount(blockHash, address)).valueOr: account = (await n.getAccount(stateRoot, address)).valueOr:
return Opt.none(UInt256) return Opt.none(UInt256)
storageProof = (await n.getStorageProof(account.storageRoot, address, slotKey)).valueOr: storageProof = (await n.getStorageProof(account.storageRoot, address, slotKey)).valueOr:
warn "Failed to get storage proof" warn "Failed to get storage proof"
@ -185,12 +179,11 @@ proc getStorageAt*(
Opt.some(slotValue) Opt.some(slotValue)
# Used by: eth_getCode proc getCodeByStateRoot*(
proc getCode*( n: StateNetwork, stateRoot: KeccakHash, address: EthAddress
n: StateNetwork, blockHash: BlockHash, address: EthAddress
): Future[Opt[Bytecode]] {.async: (raises: [CancelledError]).} = ): Future[Opt[Bytecode]] {.async: (raises: [CancelledError]).} =
let let
account = (await n.getAccount(blockHash, address)).valueOr: account = (await n.getAccount(stateRoot, address)).valueOr:
return Opt.none(Bytecode) return Opt.none(Bytecode)
contractCodeKey = ContractCodeKey.init(keccakHash(address), account.codeHash) contractCodeKey = ContractCodeKey.init(keccakHash(address), account.codeHash)
@ -199,3 +192,43 @@ proc getCode*(
return Opt.none(Bytecode) return Opt.none(Bytecode)
Opt.some(contractCodeRetrieval.code) Opt.some(contractCodeRetrieval.code)
# 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)

View File

@ -0,0 +1,21 @@
# 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 stint, json_rpc/[client, jsonmarshal], web3/conversions, web3/eth_api_types
export eth_api_types
createRpcSigsFromNim(RpcClient):
proc debug_getBalanceByStateRoot(data: Address, stateRoot: Hash256): UInt256
proc debug_getTransactionCountByStateRoot(data: Address, stateRoot: Hash256): Quantity
proc debug_getStorageAtByStateRoot(
data: Address, slot: UInt256, stateRoot: Hash256
): FixedBytes[32]
proc debug_getCodeByStateRoot(data: Address, stateRoot: Hash256): seq[byte]

View File

@ -15,6 +15,7 @@ import
web3/primitives as web3types, web3/primitives as web3types,
eth/common/eth_types, eth/common/eth_types,
beacon_chain/spec/forks, beacon_chain/spec/forks,
../common/common_utils,
../network/history/[history_network, history_content], ../network/history/[history_network, history_content],
../network/state/[state_network, state_content, state_endpoints], ../network/state/[state_network, state_content, state_endpoints],
../network/beacon/beacon_light_client ../network/beacon/beacon_light_client
@ -450,17 +451,82 @@ proc installEthApiHandlers*(
raise newException(ValueError, "Unable to get code") raise newException(ValueError, "Unable to get code")
return bytecode.asSeq() return bytecode.asSeq()
# rpcServerWithProxy.rpc("eth_getProof") do( # TODO: Should we move these debug methods into a separate debug rpcServer?
# address: Address, slots: seq[UInt256], quantityTag: RtBlockIdentifier rpcServerWithProxy.rpc("debug_getBalanceByStateRoot") do(
# ) -> ProofResponse: data: web3Types.Address, stateRoot: web3types.Hash256
# ## Returns information about an account and storage slots (if the account is a contract ) -> UInt256:
# ## and the slots are requested) along with account and storage proofs which prove the ## Returns the balance of the account of given address.
# ## existence of the values in the state. ##
# ## See spec here: https://eips.ethereum.org/EIPS/eip-1186 ## data: address to check for balance.
# ## ## stateRoot: the state root used to search the state trie.
# ## data: address of the account. ## Returns integer of the current balance in wei.
# ## slots: integers of the positions in the storage to return with storage proofs. if stateNetwork.isNone():
# ## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter. raise newException(ValueError, "State sub-network not enabled")
# ## Returns: the proof response containing the account, account proof and storage proof
# # TODO let balance = (
# raiseAssert("Not implemented") await stateNetwork.get().getBalanceByStateRoot(
KeccakHash.fromBytes(stateRoot.bytes()), data.EthAddress
)
).valueOr:
raise newException(ValueError, "Unable to get balance")
return balance
rpcServerWithProxy.rpc("debug_getTransactionCountByStateRoot") do(
data: web3Types.Address, stateRoot: web3types.Hash256
) -> Quantity:
## Returns the number of transactions sent from an address.
##
## data: address.
## stateRoot: the state root used to search the state trie.
## Returns integer of the number of transactions send from this address.
if stateNetwork.isNone():
raise newException(ValueError, "State sub-network not enabled")
let nonce = (
await stateNetwork.get().getTransactionCountByStateRoot(
KeccakHash.fromBytes(stateRoot.bytes()), data.EthAddress
)
).valueOr:
raise newException(ValueError, "Unable to get transaction count")
return nonce.Quantity
rpcServerWithProxy.rpc("debug_getStorageAtByStateRoot") do(
data: web3Types.Address, slot: UInt256, stateRoot: web3types.Hash256
) -> FixedBytes[32]:
## Returns the value from a storage position at a given address.
##
## data: address of the storage.
## slot: integer of the position in the storage.
## stateRoot: the state root used to search the state trie.
## Returns: the value at this storage position.
if stateNetwork.isNone():
raise newException(ValueError, "State sub-network not enabled")
let slotValue = (
await stateNetwork.get().getStorageAtByStateRoot(
KeccakHash.fromBytes(stateRoot.bytes()), data.EthAddress, slot
)
).valueOr:
raise newException(ValueError, "Unable to get storage slot")
return FixedBytes[32](slotValue.toBytesBE())
rpcServerWithProxy.rpc("debug_getCodeByStateRoot") do(
data: web3Types.Address, stateRoot: web3types.Hash256
) -> seq[byte]:
## Returns code at a given address.
##
## data: address
## stateRoot: the state root used to search the state trie.
## Returns the code from the given address.
if stateNetwork.isNone():
raise newException(ValueError, "State sub-network not enabled")
let bytecode = (
await stateNetwork.get().getCodeByStateRoot(
KeccakHash.fromBytes(stateRoot.bytes()), data.EthAddress
)
).valueOr:
raise newException(ValueError, "Unable to get code")
return bytecode.asSeq()

View File

@ -98,6 +98,21 @@ procSuite "State Endpoints":
nonceRes.isOk() nonceRes.isOk()
nonceRes.get() == expectedAccount.nonce nonceRes.get() == expectedAccount.nonce
block:
# check stateNode1 by state root
let
balanceRes =
await stateNode1.stateNetwork.getBalanceByStateRoot(stateRoot, address)
nonceRes = await stateNode1.stateNetwork.getTransactionCountByStateRoot(
stateRoot, address
)
check:
balanceRes.isOk()
balanceRes.get() == expectedAccount.balance
nonceRes.isOk()
nonceRes.get() == expectedAccount.nonce
block: block:
# check stateNode2 # check stateNode2
let let
@ -221,11 +236,16 @@ procSuite "State Endpoints":
badSlotRes = await stateNode2.stateNetwork.getStorageAt( badSlotRes = await stateNode2.stateNetwork.getStorageAt(
contentValue.blockHash, address, badSlot contentValue.blockHash, address, badSlot
) )
slotByStateRootRes = await stateNode2.stateNetwork.getStorageAtByStateRoot(
stateRoot, address, slot
)
check: check:
slotRes.isOk() slotRes.isOk()
slotRes.get() == expectedSlot slotRes.get() == expectedSlot
badSlotRes.isNone() badSlotRes.isNone()
slotByStateRootRes.isOk()
slotByStateRootRes.get() == expectedSlot
block: block:
# seed the contract bytecode # seed the contract bytecode
@ -264,11 +284,15 @@ procSuite "State Endpoints":
codeRes = await stateNode2.stateNetwork.getCode(contentValue.blockHash, address) codeRes = await stateNode2.stateNetwork.getCode(contentValue.blockHash, address)
badCodeRes = badCodeRes =
await stateNode2.stateNetwork.getCode(contentValue.blockHash, badAddress) await stateNode2.stateNetwork.getCode(contentValue.blockHash, badAddress)
codeByStateRootRes =
await stateNode2.stateNetwork.getCodeByStateRoot(stateRoot, address)
check: check:
codeRes.isOk() codeRes.isOk()
codeRes.get() == expectedCode codeRes.get() == expectedCode
badCodeRes.isNone() badCodeRes.isNone()
codeByStateRootRes.isOk()
codeByStateRootRes.get() == expectedCode
await stateNode1.stop() await stateNode1.stop()
await stateNode2.stop() await stateNode2.stop()