From 1c1288e4321208db1eca4711f9cc1b8f911ef577 Mon Sep 17 00:00:00 2001 From: cabrador <84449820+cabrador@users.noreply.github.com> Date: Wed, 4 Dec 2024 12:30:52 +0100 Subject: [PATCH] Implement getAccount RPC method (#370) --- ethapi/api.go | 47 +++++++++++++++++++++++++++++++++ ethapi/api_test.go | 41 ++++++++++++++++++++++++++++- tests/get_account_test.go | 55 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 1 deletion(-) create mode 100644 tests/get_account_test.go diff --git a/ethapi/api.go b/ethapi/api.go index 6730c5af0..23742d522 100644 --- a/ethapi/api.go +++ b/ethapi/api.go @@ -708,6 +708,53 @@ func (s *PublicBlockChainAPI) GetBalance(ctx context.Context, address common.Add return (*hexutil.U256)(state.GetBalance(address)), state.Error() } +// GetAccountResult is result struct for GetAccount. +// The result contains: +// 1) CodeHash - hash of the code for the given address +// 2) StorageRoot - storage root for the given address +// 3) Balance - the amount of wei for the given address +// 4) Nonce - the number of transactions for given address +type GetAccountResult struct { + CodeHash common.Hash `json:"codeHash"` + StorageRoot common.Hash `json:"storageRoot"` + Balance *hexutil.U256 `json:"balance"` + Nonce hexutil.Uint64 `json:"nonce"` +} + +// GetAccount returns the information about account with given address in the state of the given block number. +// The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block numbers are also allowed. +func (s *PublicBlockChainAPI) GetAccount(ctx context.Context, address common.Address, blockNrOrHash rpc.BlockNumberOrHash) (*GetAccountResult, error) { + state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) + if err != nil { + return nil, err + } + defer state.Release() + proof, err := state.GetProof(address, nil) + if err != nil { + return nil, err + } + codeHash, _, err := proof.GetCodeHash(cc.Hash(header.Root), cc.Address(address)) + if err != nil { + return nil, err + } + _, storageRoot, _ := proof.GetAccountElements(cc.Hash(header.Root), cc.Address(address)) + balance, _, err := proof.GetBalance(cc.Hash(header.Root), cc.Address(address)) + if err != nil { + return nil, err + } + nonce, _, err := proof.GetNonce(cc.Hash(header.Root), cc.Address(address)) + if err != nil { + return nil, err + } + u256Balance := balance.Uint256() + return &GetAccountResult{ + CodeHash: common.Hash(codeHash), + StorageRoot: common.Hash(storageRoot), + Balance: (*hexutil.U256)(&u256Balance), + Nonce: hexutil.Uint64(nonce.ToUint64()), + }, state.Error() +} + // AccountResult is result struct for GetProof type AccountResult struct { Address common.Address `json:"address"` diff --git a/ethapi/api_test.go b/ethapi/api_test.go index 434c73c82..158bab078 100644 --- a/ethapi/api_test.go +++ b/ethapi/api_test.go @@ -9,6 +9,7 @@ import ( "github.com/Fantom-foundation/go-opera/inter/state" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/stretchr/testify/require" + "go.uber.org/mock/gomock" "math/big" "testing" @@ -18,7 +19,6 @@ import ( "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" - gomock "go.uber.org/mock/gomock" ) func TestGetBlockReceipts(t *testing.T) { @@ -117,6 +117,45 @@ func TestAPI_GetProof(t *testing.T) { require.Equal(t, []StorageResult{storageProof}, accountProof.StorageProof) } +func TestAPI_GetAccount(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + addr := cc.Address{1} + codeHash := cc.Hash{2} + storageRoot := cc.Hash{3} + balance := amount.New(4) + nonce := cc.ToNonce(5) + headerRoot := common.Hash{123} + + mockBackend := NewMockBackend(ctrl) + mockState := state.NewMockStateDB(ctrl) + mockProof := witness.NewMockProof(ctrl) + mockHeader := &evmcore.EvmHeader{Root: headerRoot} + + blkNr := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber) + + mockBackend.EXPECT().StateAndHeaderByNumberOrHash(gomock.Any(), blkNr).Return(mockState, mockHeader, nil) + mockState.EXPECT().GetProof(common.Address(addr), nil).Return(mockProof, nil) + mockProof.EXPECT().GetCodeHash(cc.Hash(headerRoot), addr).Return(codeHash, true, nil) + mockProof.EXPECT().GetAccountElements(cc.Hash(headerRoot), addr).Return(nil, storageRoot, true) + mockProof.EXPECT().GetBalance(cc.Hash(headerRoot), addr).Return(balance, true, nil) + mockProof.EXPECT().GetNonce(cc.Hash(headerRoot), addr).Return(nonce, true, nil) + mockState.EXPECT().Error().Return(nil) + mockState.EXPECT().Release() + + api := NewPublicBlockChainAPI(mockBackend) + + account, err := api.GetAccount(context.Background(), common.Address(addr), blkNr) + require.NoError(t, err, "failed to get account") + + u256Balance := balance.Uint256() + require.Equal(t, common.Hash(codeHash), account.CodeHash) + require.Equal(t, common.Hash(storageRoot), account.StorageRoot) + require.Equal(t, (*hexutil.U256)(&u256Balance), account.Balance) + require.Equal(t, hexutil.Uint64(nonce.ToUint64()), account.Nonce) +} + func testGetBlockReceipts(t *testing.T, blockParam rpc.BlockNumberOrHash) ([]map[string]interface{}, error) { ctrl := gomock.NewController(t) diff --git a/tests/get_account_test.go b/tests/get_account_test.go new file mode 100644 index 000000000..7cb560a34 --- /dev/null +++ b/tests/get_account_test.go @@ -0,0 +1,55 @@ +package tests + +import ( + "github.com/Fantom-foundation/go-opera/ethapi" + "github.com/Fantom-foundation/go-opera/tests/contracts/counter" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" + "github.com/stretchr/testify/require" + "testing" +) + +func TestGetAccount(t *testing.T) { + net, err := StartIntegrationTestNet(t.TempDir()) + require.NoError(t, err, "failed to start the fake network") + defer net.Stop() + + // Deploy the transient storage contract + _, deployReceipt, err := DeployContract(net, counter.DeployCounter) + require.NoError(t, err, "failed to deploy contract") + + addr := deployReceipt.ContractAddress + + c, err := net.GetClient() + require.NoError(t, err, "failed to get client") + defer c.Close() + + rpcClient := c.Client() + defer rpcClient.Close() + + var res ethapi.GetAccountResult + err = rpcClient.Call(&res, "eth_getAccount", addr, rpc.LatestBlockNumber) + require.NoError(t, err, "failed to call get account") + + // Extract proof to find actual StorageHash(Root), Nonce, Balance and CodeHash + var proofRes struct { + StorageHash common.Hash + Nonce hexutil.Uint64 + Balance *hexutil.U256 + CodeHash common.Hash + } + err = rpcClient.Call( + &proofRes, + "eth_getProof", + addr, + nil, + rpc.LatestBlockNumber, + ) + require.NoError(t, err, "failed call to get proof") + + require.Equal(t, proofRes.CodeHash, res.CodeHash) + require.Equal(t, proofRes.StorageHash, res.StorageRoot) + require.Equal(t, proofRes.Balance, res.Balance) + require.Equal(t, proofRes.Nonce, res.Nonce) +}