Skip to content

Commit 8b99ad4

Browse files
authored
internal/ethapi: fix codehash lookup in eth_getProof (#28357)
This change fixes #28355, where eth_getProof failed to return the correct codehash under certain conditions. This PR changes the logic to unconditionally look up the codehash, and also adds some more tests.
1 parent a5544d3 commit 8b99ad4

File tree

2 files changed

+72
-70
lines changed

2 files changed

+72
-70
lines changed

ethclient/gethclient/gethclient_test.go

+32-24
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,12 @@ import (
3939
)
4040

4141
var (
42-
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
43-
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
44-
testSlot = common.HexToHash("0xdeadbeef")
45-
testValue = crypto.Keccak256Hash(testSlot[:])
46-
testBalance = big.NewInt(2e15)
42+
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
43+
testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
44+
testContract = common.HexToAddress("0xbeef")
45+
testSlot = common.HexToHash("0xdeadbeef")
46+
testValue = crypto.Keccak256Hash(testSlot[:])
47+
testBalance = big.NewInt(2e15)
4748
)
4849

4950
func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
@@ -78,8 +79,9 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
7879

7980
func generateTestChain() (*core.Genesis, []*types.Block) {
8081
genesis := &core.Genesis{
81-
Config: params.AllEthashProtocolChanges,
82-
Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance, Storage: map[common.Hash]common.Hash{testSlot: testValue}}},
82+
Config: params.AllEthashProtocolChanges,
83+
Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance, Storage: map[common.Hash]common.Hash{testSlot: testValue}},
84+
testContract: {Nonce: 1, Code: []byte{0x13, 0x37}}},
8385
ExtraData: []byte("test genesis"),
8486
Timestamp: 9000,
8587
}
@@ -103,8 +105,11 @@ func TestGethClient(t *testing.T) {
103105
test func(t *testing.T)
104106
}{
105107
{
106-
"TestGetProof",
107-
func(t *testing.T) { testGetProof(t, client) },
108+
"TestGetProof1",
109+
func(t *testing.T) { testGetProof(t, client, testAddr) },
110+
}, {
111+
"TestGetProof2",
112+
func(t *testing.T) { testGetProof(t, client, testContract) },
108113
}, {
109114
"TestGetProofCanonicalizeKeys",
110115
func(t *testing.T) { testGetProofCanonicalizeKeys(t, client) },
@@ -201,38 +206,41 @@ func testAccessList(t *testing.T, client *rpc.Client) {
201206
}
202207
}
203208

204-
func testGetProof(t *testing.T, client *rpc.Client) {
209+
func testGetProof(t *testing.T, client *rpc.Client, addr common.Address) {
205210
ec := New(client)
206211
ethcl := ethclient.NewClient(client)
207-
result, err := ec.GetProof(context.Background(), testAddr, []string{testSlot.String()}, nil)
212+
result, err := ec.GetProof(context.Background(), addr, []string{testSlot.String()}, nil)
208213
if err != nil {
209214
t.Fatal(err)
210215
}
211-
if !bytes.Equal(result.Address[:], testAddr[:]) {
212-
t.Fatalf("unexpected address, want: %v got: %v", testAddr, result.Address)
216+
if result.Address != addr {
217+
t.Fatalf("unexpected address, have: %v want: %v", result.Address, addr)
213218
}
214219
// test nonce
215-
nonce, _ := ethcl.NonceAt(context.Background(), result.Address, nil)
216-
if result.Nonce != nonce {
220+
if nonce, _ := ethcl.NonceAt(context.Background(), addr, nil); result.Nonce != nonce {
217221
t.Fatalf("invalid nonce, want: %v got: %v", nonce, result.Nonce)
218222
}
219223
// test balance
220-
balance, _ := ethcl.BalanceAt(context.Background(), result.Address, nil)
221-
if result.Balance.Cmp(balance) != 0 {
224+
if balance, _ := ethcl.BalanceAt(context.Background(), addr, nil); result.Balance.Cmp(balance) != 0 {
222225
t.Fatalf("invalid balance, want: %v got: %v", balance, result.Balance)
223226
}
224-
225227
// test storage
226228
if len(result.StorageProof) != 1 {
227229
t.Fatalf("invalid storage proof, want 1 proof, got %v proof(s)", len(result.StorageProof))
228230
}
229-
proof := result.StorageProof[0]
230-
slotValue, _ := ethcl.StorageAt(context.Background(), testAddr, testSlot, nil)
231-
if !bytes.Equal(slotValue, proof.Value.Bytes()) {
232-
t.Fatalf("invalid storage proof value, want: %v, got: %v", slotValue, proof.Value.Bytes())
231+
for _, proof := range result.StorageProof {
232+
if proof.Key != testSlot.String() {
233+
t.Fatalf("invalid storage proof key, want: %q, got: %q", testSlot.String(), proof.Key)
234+
}
235+
slotValue, _ := ethcl.StorageAt(context.Background(), addr, common.HexToHash(proof.Key), nil)
236+
if have, want := common.BigToHash(proof.Value), common.BytesToHash(slotValue); have != want {
237+
t.Fatalf("addr %x, invalid storage proof value: have: %v, want: %v", addr, have, want)
238+
}
233239
}
234-
if proof.Key != testSlot.String() {
235-
t.Fatalf("invalid storage proof key, want: %q, got: %q", testSlot.String(), proof.Key)
240+
// test code
241+
code, _ := ethcl.CodeAt(context.Background(), addr, nil)
242+
if have, want := result.CodeHash, crypto.Keccak256Hash(code); have != want {
243+
t.Fatalf("codehash wrong, have %v want %v ", have, want)
236244
}
237245
}
238246

internal/ethapi/api.go

+40-46
Original file line numberDiff line numberDiff line change
@@ -675,10 +675,6 @@ func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, st
675675
keys = make([]common.Hash, len(storageKeys))
676676
keyLengths = make([]int, len(storageKeys))
677677
storageProof = make([]StorageResult, len(storageKeys))
678-
679-
storageTrie state.Trie
680-
storageHash = types.EmptyRootHash
681-
codeHash = types.EmptyCodeHash
682678
)
683679
// Deserialize all keys. This prevents state access on invalid input.
684680
for i, hexKey := range storageKeys {
@@ -688,51 +684,49 @@ func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, st
688684
return nil, err
689685
}
690686
}
691-
state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
692-
if state == nil || err != nil {
687+
statedb, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
688+
if statedb == nil || err != nil {
693689
return nil, err
694690
}
695-
if storageRoot := state.GetStorageRoot(address); storageRoot != types.EmptyRootHash && storageRoot != (common.Hash{}) {
696-
id := trie.StorageTrieID(header.Root, crypto.Keccak256Hash(address.Bytes()), storageRoot)
697-
tr, err := trie.NewStateTrie(id, state.Database().TrieDB())
698-
if err != nil {
699-
return nil, err
700-
}
701-
storageTrie = tr
702-
}
703-
// If we have a storageTrie, the account exists and we must update
704-
// the storage root hash and the code hash.
705-
if storageTrie != nil {
706-
storageHash = storageTrie.Hash()
707-
codeHash = state.GetCodeHash(address)
708-
}
709-
// Create the proofs for the storageKeys.
710-
for i, key := range keys {
711-
// Output key encoding is a bit special: if the input was a 32-byte hash, it is
712-
// returned as such. Otherwise, we apply the QUANTITY encoding mandated by the
713-
// JSON-RPC spec for getProof. This behavior exists to preserve backwards
714-
// compatibility with older client versions.
715-
var outputKey string
716-
if keyLengths[i] != 32 {
717-
outputKey = hexutil.EncodeBig(key.Big())
718-
} else {
719-
outputKey = hexutil.Encode(key[:])
720-
}
691+
codeHash := statedb.GetCodeHash(address)
692+
storageRoot := statedb.GetStorageRoot(address)
721693

722-
if storageTrie == nil {
723-
storageProof[i] = StorageResult{outputKey, &hexutil.Big{}, []string{}}
724-
continue
694+
if len(keys) > 0 {
695+
var storageTrie state.Trie
696+
if storageRoot != types.EmptyRootHash && storageRoot != (common.Hash{}) {
697+
id := trie.StorageTrieID(header.Root, crypto.Keccak256Hash(address.Bytes()), storageRoot)
698+
st, err := trie.NewStateTrie(id, statedb.Database().TrieDB())
699+
if err != nil {
700+
return nil, err
701+
}
702+
storageTrie = st
725703
}
726-
var proof proofList
727-
if err := storageTrie.Prove(crypto.Keccak256(key.Bytes()), &proof); err != nil {
728-
return nil, err
704+
// Create the proofs for the storageKeys.
705+
for i, key := range keys {
706+
// Output key encoding is a bit special: if the input was a 32-byte hash, it is
707+
// returned as such. Otherwise, we apply the QUANTITY encoding mandated by the
708+
// JSON-RPC spec for getProof. This behavior exists to preserve backwards
709+
// compatibility with older client versions.
710+
var outputKey string
711+
if keyLengths[i] != 32 {
712+
outputKey = hexutil.EncodeBig(key.Big())
713+
} else {
714+
outputKey = hexutil.Encode(key[:])
715+
}
716+
if storageTrie == nil {
717+
storageProof[i] = StorageResult{outputKey, &hexutil.Big{}, []string{}}
718+
continue
719+
}
720+
var proof proofList
721+
if err := storageTrie.Prove(crypto.Keccak256(key.Bytes()), &proof); err != nil {
722+
return nil, err
723+
}
724+
value := (*hexutil.Big)(statedb.GetState(address, key).Big())
725+
storageProof[i] = StorageResult{outputKey, value, proof}
729726
}
730-
value := (*hexutil.Big)(state.GetState(address, key).Big())
731-
storageProof[i] = StorageResult{outputKey, value, proof}
732727
}
733-
734728
// Create the accountProof.
735-
tr, err := trie.NewStateTrie(trie.StateTrieID(header.Root), state.Database().TrieDB())
729+
tr, err := trie.NewStateTrie(trie.StateTrieID(header.Root), statedb.Database().TrieDB())
736730
if err != nil {
737731
return nil, err
738732
}
@@ -743,12 +737,12 @@ func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, st
743737
return &AccountResult{
744738
Address: address,
745739
AccountProof: accountProof,
746-
Balance: (*hexutil.Big)(state.GetBalance(address)),
740+
Balance: (*hexutil.Big)(statedb.GetBalance(address)),
747741
CodeHash: codeHash,
748-
Nonce: hexutil.Uint64(state.GetNonce(address)),
749-
StorageHash: storageHash,
742+
Nonce: hexutil.Uint64(statedb.GetNonce(address)),
743+
StorageHash: storageRoot,
750744
StorageProof: storageProof,
751-
}, state.Error()
745+
}, statedb.Error()
752746
}
753747

754748
// decodeHash parses a hex-encoded 32-byte hash. The input may optionally

0 commit comments

Comments
 (0)