Skip to content

Commit

Permalink
Add rpc get account method.
Browse files Browse the repository at this point in the history
  • Loading branch information
cabrador committed Dec 2, 2024
1 parent 1933d89 commit c42271c
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 1 deletion.
31 changes: 31 additions & 0 deletions ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -708,6 +708,37 @@ func (s *PublicBlockChainAPI) GetBalance(ctx context.Context, address common.Add
return (*hexutil.U256)(state.GetBalance(address)), state.Error()
}

// GetAccountResult is result struct for GetAccount
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.
// 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
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
}
proof, err := state.GetProof(address, nil)

Check failure on line 731 in ethapi/api.go

View workflow job for this annotation

GitHub Actions / check-build

this value of err is never used (SA4006)
_, storageRoot, _ := proof.GetAccountElements(cc.Hash(header.Root), cc.Address(address))
defer state.Release()
return &GetAccountResult{
CodeHash: state.GetCodeHash(address),
StorageRoot: common.Hash(storageRoot),
Balance: (*hexutil.U256)(state.GetBalance(address)),
Nonce: hexutil.Uint64(state.GetNonce(address)),
}, state.Error()
}

// AccountResult is result struct for GetProof
type AccountResult struct {
Address common.Address `json:"address"`
Expand Down
55 changes: 54 additions & 1 deletion ethapi/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package ethapi

import (
"context"
"github.com/Fantom-foundation/go-opera/inter/state"
"github.com/holiman/uint256"
"go.uber.org/mock/gomock"
"math/big"
"testing"

Expand All @@ -11,7 +14,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 @@ -52,6 +54,57 @@ func TestGetBlockReceipts(t *testing.T) {
}
}

func TestAPI_GetAccount(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()

addr := common.Address{1}
codeHash := common.Hash{2}
storageRoot := common.Hash{3}
balance := uint256.NewInt(4)
nonce := uint64(5)

mockBackend := NewMockBackend(ctrl)
mockState := state.NewMockStateDB(ctrl)

blkNr := rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)

mockBackend.EXPECT().StateAndHeaderByNumberOrHash(gomock.Any(), blkNr).Return(mockState, nil, nil)
mockState.EXPECT().GetCodeHash(addr).Return(codeHash)
mockState.EXPECT().GetStorageRoot(addr).Return(storageRoot)
mockState.EXPECT().GetBalance(addr).Return(balance)
mockState.EXPECT().GetNonce(addr).Return(nonce)
mockState.EXPECT().Error().Return(nil)
mockState.EXPECT().Release()

api := NewPublicBlockChainAPI(mockBackend)

account, err := api.GetAccount(context.Background(), addr, blkNr)
if err != nil {
t.Fatalf("unexpected err: %v", err)
}

if codeHash.Cmp(account.CodeHash) != 0 {
t.Errorf("unexpected code hash, got: %s want %s", account.CodeHash, codeHash)
}

if storageRoot.Cmp(account.StorageRoot) != 0 {
t.Errorf("unexpected storage root, got: %s want %s", account.StorageRoot, storageRoot)
}

if balance.Cmp((*uint256.Int)(account.Balance)) != 0 {
t.Errorf("unexpected balance, got: %s want %s", account.Balance, balance)
}

if balance.Cmp((*uint256.Int)(account.Balance)) != 0 {
t.Errorf("unexpected balance, got: %s want %s", account.Balance, balance)
}

if nonce != uint64(account.Nonce) {
t.Errorf("unexpected nonce, got: %d want %d", account.Nonce, 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/transientstorage"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rpc"
"github.com/holiman/uint256"
"testing"
)

func TestGetAccount(t *testing.T) {
net, err := StartIntegrationTestNet(t.TempDir())
if err != nil {
t.Fatalf("Failed to start the fake network: %v", err)
}
defer net.Stop()

// Deploy the transient storage contract
_, deployReceipt, err := DeployContract(net, transientstorage.DeployTransientstorage)
if err != nil {
t.Fatalf("failed to deploy contract; %v", err)
}

addr := deployReceipt.ContractAddress

c, err := net.GetClient()
if err != nil {
t.Fatalf("failed to get client; %v", err)
}

rpcClient := c.Client()

var res ethapi.GetAccountResult
err = rpcClient.Call(&res, "eth_getAccount", addr, rpc.LatestBlockNumber)
if err != nil {
t.Fatalf("failed to get account; %v", err)
}

if res.CodeHash == (common.Hash{}) {
t.Error("code hash should not be empty")
}

if res.StorageRoot == (common.Hash{}) {
t.Error("storage root should not be empty")
}

if got, want := (*uint256.Int)(res.Balance).Uint64(), uint64(0); got != want {
t.Errorf("balance not as expected, got: %d want: %d", got, want)
}

if res.Nonce < 1 {
t.Errorf("account nonce is expected to by at least 1")
}
}

0 comments on commit c42271c

Please sign in to comment.