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:
parent
8bf581e72d
commit
fa59898388
|
@ -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)
|
||||||
|
|
|
@ -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]
|
|
@ -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()
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in New Issue