diff --git a/fluffy/network/state/state_endpoints.nim b/fluffy/network/state/state_endpoints.nim index ca073642e..ce1be1c85 100644 --- a/fluffy/network/state/state_endpoints.nim +++ b/fluffy/network/state/state_endpoints.nim @@ -136,12 +136,9 @@ proc getStorageProof( Opt.some(proof) proc getAccount( - n: StateNetwork, blockHash: BlockHash, address: EthAddress + n: StateNetwork, stateRoot: KeccakHash, 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) @@ -151,30 +148,27 @@ proc getAccount( Opt.some(account) -# Used by: eth_getBalance, -proc getBalance*( - n: StateNetwork, blockHash: BlockHash, address: EthAddress +proc getBalanceByStateRoot*( + n: StateNetwork, stateRoot: KeccakHash, address: EthAddress ): 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) Opt.some(account.balance) -# Used by: eth_getTransactionCount -proc getTransactionCount*( - n: StateNetwork, blockHash: BlockHash, address: EthAddress +proc getTransactionCountByStateRoot*( + n: StateNetwork, stateRoot: KeccakHash, address: EthAddress ): 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) Opt.some(account.nonce) -# Used by: eth_getStorageAt -proc getStorageAt*( - n: StateNetwork, blockHash: BlockHash, address: EthAddress, slotKey: UInt256 +proc getStorageAtByStateRoot*( + n: StateNetwork, stateRoot: KeccakHash, address: EthAddress, slotKey: UInt256 ): Future[Opt[UInt256]] {.async: (raises: [CancelledError]).} = let - account = (await n.getAccount(blockHash, address)).valueOr: + account = (await n.getAccount(stateRoot, address)).valueOr: return Opt.none(UInt256) storageProof = (await n.getStorageProof(account.storageRoot, address, slotKey)).valueOr: warn "Failed to get storage proof" @@ -185,12 +179,11 @@ proc getStorageAt*( Opt.some(slotValue) -# Used by: eth_getCode -proc getCode*( - n: StateNetwork, blockHash: BlockHash, address: EthAddress +proc getCodeByStateRoot*( + n: StateNetwork, stateRoot: KeccakHash, address: EthAddress ): Future[Opt[Bytecode]] {.async: (raises: [CancelledError]).} = let - account = (await n.getAccount(blockHash, address)).valueOr: + account = (await n.getAccount(stateRoot, address)).valueOr: return Opt.none(Bytecode) contractCodeKey = ContractCodeKey.init(keccakHash(address), account.codeHash) @@ -199,3 +192,43 @@ proc getCode*( return Opt.none(Bytecode) 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) diff --git a/fluffy/rpc/rpc_calls/rpc_debug_calls.nim b/fluffy/rpc/rpc_calls/rpc_debug_calls.nim new file mode 100644 index 000000000..d3caca954 --- /dev/null +++ b/fluffy/rpc/rpc_calls/rpc_debug_calls.nim @@ -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] diff --git a/fluffy/rpc/rpc_eth_api.nim b/fluffy/rpc/rpc_eth_api.nim index 152865faf..d80406d1c 100644 --- a/fluffy/rpc/rpc_eth_api.nim +++ b/fluffy/rpc/rpc_eth_api.nim @@ -15,6 +15,7 @@ import web3/primitives as web3types, eth/common/eth_types, beacon_chain/spec/forks, + ../common/common_utils, ../network/history/[history_network, history_content], ../network/state/[state_network, state_content, state_endpoints], ../network/beacon/beacon_light_client @@ -450,17 +451,82 @@ proc installEthApiHandlers*( raise newException(ValueError, "Unable to get code") return bytecode.asSeq() - # rpcServerWithProxy.rpc("eth_getProof") do( - # address: Address, slots: seq[UInt256], quantityTag: RtBlockIdentifier - # ) -> 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 - # # TODO - # raiseAssert("Not implemented") + # TODO: Should we move these debug methods into a separate debug rpcServer? + rpcServerWithProxy.rpc("debug_getBalanceByStateRoot") do( + data: web3Types.Address, stateRoot: web3types.Hash256 + ) -> UInt256: + ## Returns the balance of the account of given address. + ## + ## data: address to check for balance. + ## stateRoot: the state root used to search the state trie. + ## Returns integer of the current balance in wei. + if stateNetwork.isNone(): + raise newException(ValueError, "State sub-network not enabled") + + let balance = ( + 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() diff --git a/fluffy/tests/state_network_tests/test_state_endpoints_vectors.nim b/fluffy/tests/state_network_tests/test_state_endpoints_vectors.nim index 2b1fdbaa7..ea6044d83 100644 --- a/fluffy/tests/state_network_tests/test_state_endpoints_vectors.nim +++ b/fluffy/tests/state_network_tests/test_state_endpoints_vectors.nim @@ -98,6 +98,21 @@ procSuite "State Endpoints": nonceRes.isOk() 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: # check stateNode2 let @@ -221,11 +236,16 @@ procSuite "State Endpoints": badSlotRes = await stateNode2.stateNetwork.getStorageAt( contentValue.blockHash, address, badSlot ) + slotByStateRootRes = await stateNode2.stateNetwork.getStorageAtByStateRoot( + stateRoot, address, slot + ) check: slotRes.isOk() slotRes.get() == expectedSlot badSlotRes.isNone() + slotByStateRootRes.isOk() + slotByStateRootRes.get() == expectedSlot block: # seed the contract bytecode @@ -264,11 +284,15 @@ procSuite "State Endpoints": codeRes = await stateNode2.stateNetwork.getCode(contentValue.blockHash, address) badCodeRes = await stateNode2.stateNetwork.getCode(contentValue.blockHash, badAddress) + codeByStateRootRes = + await stateNode2.stateNetwork.getCodeByStateRoot(stateRoot, address) check: codeRes.isOk() codeRes.get() == expectedCode badCodeRes.isNone() + codeByStateRootRes.isOk() + codeByStateRootRes.get() == expectedCode await stateNode1.stop() await stateNode2.stop()