Skip to content

Commit

Permalink
trie, jsonrpc: Fix eth_getProof format (#13741)
Browse files Browse the repository at this point in the history
Should be able to handle inputs less than 32-bytes and output values in
a compact string format to align with geth
  • Loading branch information
somnathb1 authored Feb 10, 2025
1 parent 5417b3c commit 2c6196d
Show file tree
Hide file tree
Showing 7 changed files with 32 additions and 23 deletions.
8 changes: 5 additions & 3 deletions erigon-lib/trie/proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,7 +338,9 @@ func VerifyAccountProofByHash(stateRoot libcommon.Hash, accountKey libcommon.Has
}

func VerifyStorageProof(storageRoot libcommon.Hash, proof accounts.StorProofResult) error {
storageKey := crypto.Keccak256Hash(proof.Key[:])
keyhash := &libcommon.Hash{}
keyhash.SetBytes(hexutility.FromHex(proof.Key))
storageKey := crypto.Keccak256Hash(keyhash[:])
return VerifyStorageProofByHash(storageRoot, storageKey, proof)
}

Expand All @@ -354,13 +356,13 @@ func VerifyStorageProofByHash(storageRoot libcommon.Hash, keyHash libcommon.Hash
// if it corresponds to empty storage tree, having value EmptyRoot above
// then proof should be RLP encoding of empty proof (0x80)
if storageRoot == EmptyRoot {
for i, _ := range proof.Proof {
for i := range proof.Proof {
if len(proof.Proof[i]) != 1 || proof.Proof[i][0] != 0x80 {
return errors.New("empty storage root should have RLP encoding of empty proof")
}
}
} else {
for i, _ := range proof.Proof {
for i := range proof.Proof {
if len(proof.Proof[i]) != 0 {
return errors.New("zero storage root should have empty proof")
}
Expand Down
2 changes: 1 addition & 1 deletion erigon-lib/trie/retain_list.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ func (pr *DefaultProofRetainer) ProofResult() (*accounts.AccProofResult, error)

result.StorageProof = make([]accounts.StorProofResult, len(pr.storageKeys))
for i, sk := range pr.storageKeys {
result.StorageProof[i].Key = sk
result.StorageProof[i].Key = uint256.NewInt(0).SetBytes(sk[:]).Hex()
hexKey := pr.storageHexKeys[i]
if !pr.acc.Initialised || result.StorageHash == EmptyRoot {
// The yellow paper makes it clear that the EmptyRoot is a special case
Expand Down
6 changes: 3 additions & 3 deletions erigon-lib/trie/retain_list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,17 +134,17 @@ func TestProofRetainerConstruction(t *testing.T) {
require.Equal(t, validKeys[7], []byte(accProof.AccountProof[2]))

require.Len(t, accProof.StorageProof, 3)
require.Equal(t, accProof.StorageProof[0].Key, libcommon.Hash{1})
require.Equal(t, libcommon.HexToHash(accProof.StorageProof[0].Key), libcommon.Hash{1})
require.Len(t, accProof.StorageProof[0].Proof, 2)
require.Equal(t, validKeys[6], []byte(accProof.StorageProof[0].Proof[0]))
require.Equal(t, validKeys[5], []byte(accProof.StorageProof[0].Proof[1]))

require.Equal(t, accProof.StorageProof[1].Key, libcommon.Hash{2})
require.Equal(t, libcommon.HexToHash(accProof.StorageProof[1].Key), libcommon.Hash{2})
require.Len(t, accProof.StorageProof[1].Proof, 2)
require.Equal(t, validKeys[4], []byte(accProof.StorageProof[1].Proof[0]))
require.Equal(t, validKeys[3], []byte(accProof.StorageProof[1].Proof[1]))

require.Equal(t, accProof.StorageProof[2].Key, libcommon.Hash{3})
require.Equal(t, libcommon.HexToHash(accProof.StorageProof[2].Key), libcommon.Hash{3})
require.Len(t, accProof.StorageProof[2].Proof, 3)
require.Equal(t, validKeys[2], []byte(accProof.StorageProof[2].Proof[0]))
require.Equal(t, validKeys[1], []byte(accProof.StorageProof[2].Proof[1]))
Expand Down
2 changes: 1 addition & 1 deletion erigon-lib/types/accounts/account_proof.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ type AccProofResult struct {
StorageProof []StorProofResult `json:"storageProof"`
}
type StorProofResult struct {
Key libcommon.Hash `json:"key"`
Key string `json:"key"`
Value *hexutil.Big `json:"value"`
Proof []hexutility.Bytes `json:"proof"`
}
2 changes: 1 addition & 1 deletion turbo/jsonrpc/eth_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ type EthAPI interface {
SendTransaction(_ context.Context, txObject interface{}) (common.Hash, error)
Sign(ctx context.Context, _ common.Address, _ hexutility.Bytes) (hexutility.Bytes, error)
SignTransaction(_ context.Context, txObject interface{}) (common.Hash, error)
GetProof(ctx context.Context, address common.Address, storageKeys []common.Hash, blockNr rpc.BlockNumberOrHash) (*accounts.AccProofResult, error)
GetProof(ctx context.Context, address common.Address, storageKeys []hexutility.Bytes, blockNr rpc.BlockNumberOrHash) (*accounts.AccProofResult, error)
CreateAccessList(ctx context.Context, args ethapi.CallArgs, blockNrOrHash *rpc.BlockNumberOrHash, optimizeGas *bool) (*accessListResult, error)

// Mining related (see ./eth_mining.go)
Expand Down
16 changes: 10 additions & 6 deletions turbo/jsonrpc/eth_call.go
Original file line number Diff line number Diff line change
Expand Up @@ -331,8 +331,8 @@ func (api *APIImpl) EstimateGas(ctx context.Context, argsOrNil *ethapi2.CallArgs
return hexutil.Uint64(hi), nil
}

// GetProof is partially implemented; Proofs are available only with the `latest` block tag.
func (api *APIImpl) GetProof(ctx context.Context, address libcommon.Address, storageKeys []libcommon.Hash, blockNrOrHash rpc.BlockNumberOrHash) (*accounts.AccProofResult, error) {
// GetProof implements eth_getProof partially; Proofs are available only with the `latest` block tag.
func (api *APIImpl) GetProof(ctx context.Context, address libcommon.Address, storageKeys []hexutility.Bytes, blockNrOrHash rpc.BlockNumberOrHash) (*accounts.AccProofResult, error) {
roTx, err := api.db.BeginRo(ctx)
if err != nil {
return nil, err
Expand All @@ -355,7 +355,11 @@ func (api *APIImpl) GetProof(ctx context.Context, address libcommon.Address, sto
return nil, errors.New("proofs are available only for the 'latest' block")
}

return api.getProof(ctx, &roTx, address, storageKeys, rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(latestBlock)), api.db, api.logger)
storageKeysConverted := make([]libcommon.Hash, len(storageKeys))
for i, s := range storageKeys {
storageKeysConverted[i].SetBytes(s)
}
return api.getProof(ctx, &roTx, address, storageKeysConverted, rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(latestBlock)), api.db, api.logger)
}

func (api *APIImpl) getProof(ctx context.Context, roTx *kv.Tx, address libcommon.Address, storageKeys []libcommon.Hash, blockNrOrHash rpc.BlockNumberOrHash, db kv.RoDB, logger log.Logger) (*accounts.AccProofResult, error) {
Expand Down Expand Up @@ -402,7 +406,7 @@ func (api *APIImpl) getProof(ctx context.Context, roTx *kv.Tx, address libcommon
if acc == nil {
for i, k := range storageKeys {
proof.StorageProof[i] = accounts.StorProofResult{
Key: k,
Key: uint256.NewInt(0).SetBytes(k[:]).Hex(),
Value: new(hexutil.Big),
Proof: nil,
}
Expand Down Expand Up @@ -441,7 +445,7 @@ func (api *APIImpl) getProof(ctx context.Context, roTx *kv.Tx, address libcommon

// get storage key proofs
for i, keyHash := range storageKeys {
proof.StorageProof[i].Key = keyHash
proof.StorageProof[i].Key = uint256.NewInt(0).SetBytes(keyHash[:]).Hex()

// if we have simple non contract account just set values directly without requesting any key proof
if proof.StorageHash.Cmp(libcommon.BytesToHash(commitment.EmptyRootHash)) == 0 {
Expand Down Expand Up @@ -487,7 +491,7 @@ func (api *APIImpl) getProof(ctx context.Context, roTx *kv.Tx, address libcommon
for _, storageProof := range proof.StorageProof {
err = trie.VerifyStorageProof(proof.StorageHash, storageProof)
if err != nil {
return nil, fmt.Errorf("internal error: failed to verify storage proof for key=%x , proof=%+v : %w", storageProof.Key.Bytes(), proof, err)
return nil, fmt.Errorf("internal error: failed to verify storage proof for key=%x , proof=%+v : %w", storageProof.Key, proof, err)
}
}

Expand Down
19 changes: 11 additions & 8 deletions turbo/jsonrpc/eth_call_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,17 +113,17 @@ func TestGetProof(t *testing.T) {
m, bankAddr, contractAddr := chainWithDeployedContract(t)
api := NewEthAPI(newBaseApiForTest(m), m.DB, nil, nil, nil, 5000000, ethconfig.Defaults.RPCTxFeeCap, 100_000, false, maxGetProofRewindBlockCount, 128, log.New())

key := func(b byte) libcommon.Hash {
key := func(b byte) hexutility.Bytes {
result := libcommon.Hash{}
result[31] = b
return result
return result.Bytes()
}

tests := []struct {
name string
blockNum uint64
addr libcommon.Address
storageKeys []libcommon.Hash
storageKeys []hexutility.Bytes
stateVal uint64
expectedErr string
}{
Expand All @@ -146,27 +146,27 @@ func TestGetProof(t *testing.T) {
name: "currentBlockWithState",
addr: contractAddr,
blockNum: 3,
storageKeys: []libcommon.Hash{key(0), key(4), key(8), key(10)},
storageKeys: []hexutility.Bytes{key(0), key(4), key(8), key(10)},
stateVal: 2,
},
{
name: "currentBlockWithMissingState",
addr: contractAddr,
storageKeys: []libcommon.Hash{libcommon.HexToHash("0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead")},
storageKeys: []hexutility.Bytes{hexutility.FromHex("0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead")},
blockNum: 3,
stateVal: 0,
},
{
name: "currentBlockEOAMissingState",
addr: bankAddr,
storageKeys: []libcommon.Hash{libcommon.HexToHash("0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead")},
storageKeys: []hexutility.Bytes{hexutility.FromHex("0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead")},
blockNum: 3,
stateVal: 0,
},
{
name: "currentBlockNoAccountMissingState",
addr: libcommon.HexToAddress("0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddead0"),
storageKeys: []libcommon.Hash{libcommon.HexToHash("0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead")},
storageKeys: []hexutility.Bytes{hexutility.FromHex("0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead")},
blockNum: 3,
stateVal: 0,
},
Expand Down Expand Up @@ -215,7 +215,10 @@ func TestGetProof(t *testing.T) {
for _, storageKey := range tt.storageKeys {
found := false
for _, storageProof := range proof.StorageProof {
if storageProof.Key != storageKey {
var proofKeyHash, storageKeyHash libcommon.Hash
proofKeyHash.SetBytes(hexutility.FromHex(storageProof.Key))
storageKeyHash.SetBytes(uint256.NewInt(0).SetBytes(storageKey).Bytes())
if proofKeyHash != storageKeyHash {
continue
}
found = true
Expand Down

0 comments on commit 2c6196d

Please sign in to comment.