From d2af087984c002f17ec525ed17c0ebaad0c420f0 Mon Sep 17 00:00:00 2001 From: Amir Deris Date: Mon, 8 Jun 2026 11:05:19 -0700 Subject: [PATCH 1/3] Added validation for storage key, and the length of keys --- evmrpc/state.go | 10 +++++++++- evmrpc/state_test.go | 24 ++++++++++++++++++++---- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/evmrpc/state.go b/evmrpc/state.go index 96f7591210..d7ea2fbf0c 100644 --- a/evmrpc/state.go +++ b/evmrpc/state.go @@ -28,6 +28,8 @@ import ( var errNoProofCapableQueryableKVStore = errors.New("cannot find a proof-capable queryable KV store") +const MaxStorageKeysPerProof = 1000 + type StateAPI struct { tmClient client.LocalClient keeper *keeper.Keeper @@ -132,9 +134,15 @@ func (a *StateAPI) GetProof(ctx context.Context, address common.Address, storage if err != nil { return nil, err } + if len(storageKeys) > MaxStorageKeysPerProof { + return nil, fmt.Errorf("too many storage keys: got %d, max %d", len(storageKeys), MaxStorageKeysPerProof) + } proofResult := ProofResult{Address: address} for _, key := range storageKeys { - paddedKey := common.BytesToHash([]byte(key)) + paddedKey, _, err := decodeHash(key) + if err != nil { + return nil, fmt.Errorf("invalid storage key %q: %w", key, err) + } formattedKey := append(types.StateKey(address), paddedKey[:]...) qres := queryStore.Query(ctx, abci.RequestQuery{ Path: "/key", diff --git a/evmrpc/state_test.go b/evmrpc/state_test.go index c066522da3..1b2dd9c5ba 100644 --- a/evmrpc/state_test.go +++ b/evmrpc/state_test.go @@ -235,34 +235,50 @@ func TestGetProof(t *testing.T) { watermarks := evmrpc.NewWatermarkManager(client, ctxProvider, nil, testApp.EvmKeeper.ReceiptStore()) stateAPI := evmrpc.NewStateAPI(client, &testApp.EvmKeeper, ctxProvider, evmrpc.ConnectionTypeHTTP, watermarks) require.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000616263", testApp.EvmKeeper.GetState(testApp.GetCheckCtx(), evmAddr, common.BytesToHash(key)).Hex()) + // hex-encode the storage slot as eth_getProof requires + hexKey := common.BytesToHash(key).Hex() tests := []struct { key string blockNr rpc.BlockNumber expectedVal []byte }{ { - key: string(key), + key: hexKey, blockNr: rpc.BlockNumber(-2), expectedVal: val, }, { - key: string(key), + key: hexKey, blockNr: rpc.BlockNumber(8), expectedVal: val, }, { - key: "non existent", + // valid hex slot that has no state set + key: "0x0000000000000000000000000000000000000000000000000000000000000001", blockNr: rpc.BlockNumber(-2), expectedVal: []byte{}, }, } for _, test := range tests { bptr := &rpc.BlockNumberOrHash{BlockNumber: &test.blockNr} - res, err := stateAPI.GetProof(context.Background(), evmAddr, []string{test.key}, *bptr) + res, err := stateAPI.GetProof(t.Context(), evmAddr, []string{test.key}, *bptr) require.Nil(t, err) vals := res.HexValues require.Equal(t, common.BytesToHash(test.expectedVal), common.HexToHash(vals[0])) proofs := res.StorageProof require.Equal(t, "ics23:iavl", proofs[0].Ops[0].Type) } + + // malformed key must be rejected + bptr := &rpc.BlockNumberOrHash{BlockNumber: func() *rpc.BlockNumber { n := rpc.BlockNumber(-2); return &n }()} + _, err := stateAPI.GetProof(t.Context(), evmAddr, []string{"not-hex"}, *bptr) + require.Error(t, err) + + // too many keys must be rejected + tooManyKeys := make([]string, evmrpc.MaxStorageKeysPerProof+1) + for i := range tooManyKeys { + tooManyKeys[i] = hexKey + } + _, err = stateAPI.GetProof(t.Context(), evmAddr, tooManyKeys, *bptr) + require.Error(t, err) } From 4d2df2b5d01c3ec5c6f2cf834bc9cccf9cf0b729 Mon Sep 17 00:00:00 2001 From: Amir Deris Date: Tue, 9 Jun 2026 09:49:29 -0700 Subject: [PATCH 2/3] Address feedback: decode keys first before store query --- evmrpc/state.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/evmrpc/state.go b/evmrpc/state.go index d7ea2fbf0c..2062029354 100644 --- a/evmrpc/state.go +++ b/evmrpc/state.go @@ -137,12 +137,16 @@ func (a *StateAPI) GetProof(ctx context.Context, address common.Address, storage if len(storageKeys) > MaxStorageKeysPerProof { return nil, fmt.Errorf("too many storage keys: got %d, max %d", len(storageKeys), MaxStorageKeysPerProof) } - proofResult := ProofResult{Address: address} - for _, key := range storageKeys { + paddedKeys := make([]common.Hash, len(storageKeys)) + for i, key := range storageKeys { paddedKey, _, err := decodeHash(key) if err != nil { return nil, fmt.Errorf("invalid storage key %q: %w", key, err) } + paddedKeys[i] = paddedKey + } + proofResult := ProofResult{Address: address} + for _, paddedKey := range paddedKeys { formattedKey := append(types.StateKey(address), paddedKey[:]...) qres := queryStore.Query(ctx, abci.RequestQuery{ Path: "/key", From f62403eb275f1c7ad811ba38a4fcf6d89d5169e6 Mon Sep 17 00:00:00 2001 From: Amir Deris Date: Tue, 9 Jun 2026 10:00:02 -0700 Subject: [PATCH 3/3] Increased max storage keys per proof to match geth --- evmrpc/state.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evmrpc/state.go b/evmrpc/state.go index 2062029354..f379d5352c 100644 --- a/evmrpc/state.go +++ b/evmrpc/state.go @@ -28,7 +28,7 @@ import ( var errNoProofCapableQueryableKVStore = errors.New("cannot find a proof-capable queryable KV store") -const MaxStorageKeysPerProof = 1000 +const MaxStorageKeysPerProof = 1024 type StateAPI struct { tmClient client.LocalClient