Skip to content

Commit

Permalink
Implement getAccount RPC method (#370)
Browse files Browse the repository at this point in the history
  • Loading branch information
cabrador authored Dec 4, 2024
1 parent 4a2d82b commit 1c1288e
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 1 deletion.
47 changes: 47 additions & 0 deletions ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
41 changes: 40 additions & 1 deletion ethapi/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down
55 changes: 55 additions & 0 deletions tests/get_account_test.go
Original file line number Diff line number Diff line change
@@ -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)
}

0 comments on commit 1c1288e

Please sign in to comment.