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.
pull/28423/head
Martin Holst Swende 1 year ago committed by GitHub
parent a5544d35f6
commit 8b99ad4602
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 56
      ethclient/gethclient/gethclient_test.go
  2. 86
      internal/ethapi/api.go

@ -39,11 +39,12 @@ import (
) )
var ( var (
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
testAddr = crypto.PubkeyToAddress(testKey.PublicKey) testAddr = crypto.PubkeyToAddress(testKey.PublicKey)
testSlot = common.HexToHash("0xdeadbeef") testContract = common.HexToAddress("0xbeef")
testValue = crypto.Keccak256Hash(testSlot[:]) testSlot = common.HexToHash("0xdeadbeef")
testBalance = big.NewInt(2e15) testValue = crypto.Keccak256Hash(testSlot[:])
testBalance = big.NewInt(2e15)
) )
func newTestBackend(t *testing.T) (*node.Node, []*types.Block) { func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
@ -78,8 +79,9 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block) {
func generateTestChain() (*core.Genesis, []*types.Block) { func generateTestChain() (*core.Genesis, []*types.Block) {
genesis := &core.Genesis{ genesis := &core.Genesis{
Config: params.AllEthashProtocolChanges, Config: params.AllEthashProtocolChanges,
Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance, Storage: map[common.Hash]common.Hash{testSlot: testValue}}}, Alloc: core.GenesisAlloc{testAddr: {Balance: testBalance, Storage: map[common.Hash]common.Hash{testSlot: testValue}},
testContract: {Nonce: 1, Code: []byte{0x13, 0x37}}},
ExtraData: []byte("test genesis"), ExtraData: []byte("test genesis"),
Timestamp: 9000, Timestamp: 9000,
} }
@ -103,8 +105,11 @@ func TestGethClient(t *testing.T) {
test func(t *testing.T) test func(t *testing.T)
}{ }{
{ {
"TestGetProof", "TestGetProof1",
func(t *testing.T) { testGetProof(t, client) }, func(t *testing.T) { testGetProof(t, client, testAddr) },
}, {
"TestGetProof2",
func(t *testing.T) { testGetProof(t, client, testContract) },
}, { }, {
"TestGetProofCanonicalizeKeys", "TestGetProofCanonicalizeKeys",
func(t *testing.T) { testGetProofCanonicalizeKeys(t, client) }, func(t *testing.T) { testGetProofCanonicalizeKeys(t, client) },
@ -201,38 +206,41 @@ func testAccessList(t *testing.T, client *rpc.Client) {
} }
} }
func testGetProof(t *testing.T, client *rpc.Client) { func testGetProof(t *testing.T, client *rpc.Client, addr common.Address) {
ec := New(client) ec := New(client)
ethcl := ethclient.NewClient(client) ethcl := ethclient.NewClient(client)
result, err := ec.GetProof(context.Background(), testAddr, []string{testSlot.String()}, nil) result, err := ec.GetProof(context.Background(), addr, []string{testSlot.String()}, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
if !bytes.Equal(result.Address[:], testAddr[:]) { if result.Address != addr {
t.Fatalf("unexpected address, want: %v got: %v", testAddr, result.Address) t.Fatalf("unexpected address, have: %v want: %v", result.Address, addr)
} }
// test nonce // test nonce
nonce, _ := ethcl.NonceAt(context.Background(), result.Address, nil) if nonce, _ := ethcl.NonceAt(context.Background(), addr, nil); result.Nonce != nonce {
if result.Nonce != nonce {
t.Fatalf("invalid nonce, want: %v got: %v", nonce, result.Nonce) t.Fatalf("invalid nonce, want: %v got: %v", nonce, result.Nonce)
} }
// test balance // test balance
balance, _ := ethcl.BalanceAt(context.Background(), result.Address, nil) if balance, _ := ethcl.BalanceAt(context.Background(), addr, nil); result.Balance.Cmp(balance) != 0 {
if result.Balance.Cmp(balance) != 0 {
t.Fatalf("invalid balance, want: %v got: %v", balance, result.Balance) t.Fatalf("invalid balance, want: %v got: %v", balance, result.Balance)
} }
// test storage // test storage
if len(result.StorageProof) != 1 { if len(result.StorageProof) != 1 {
t.Fatalf("invalid storage proof, want 1 proof, got %v proof(s)", len(result.StorageProof)) t.Fatalf("invalid storage proof, want 1 proof, got %v proof(s)", len(result.StorageProof))
} }
proof := result.StorageProof[0] for _, proof := range result.StorageProof {
slotValue, _ := ethcl.StorageAt(context.Background(), testAddr, testSlot, nil) if proof.Key != testSlot.String() {
if !bytes.Equal(slotValue, proof.Value.Bytes()) { t.Fatalf("invalid storage proof key, want: %q, got: %q", testSlot.String(), proof.Key)
t.Fatalf("invalid storage proof value, want: %v, got: %v", slotValue, proof.Value.Bytes()) }
slotValue, _ := ethcl.StorageAt(context.Background(), addr, common.HexToHash(proof.Key), nil)
if have, want := common.BigToHash(proof.Value), common.BytesToHash(slotValue); have != want {
t.Fatalf("addr %x, invalid storage proof value: have: %v, want: %v", addr, have, want)
}
} }
if proof.Key != testSlot.String() { // test code
t.Fatalf("invalid storage proof key, want: %q, got: %q", testSlot.String(), proof.Key) code, _ := ethcl.CodeAt(context.Background(), addr, nil)
if have, want := result.CodeHash, crypto.Keccak256Hash(code); have != want {
t.Fatalf("codehash wrong, have %v want %v ", have, want)
} }
} }

@ -675,10 +675,6 @@ func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, st
keys = make([]common.Hash, len(storageKeys)) keys = make([]common.Hash, len(storageKeys))
keyLengths = make([]int, len(storageKeys)) keyLengths = make([]int, len(storageKeys))
storageProof = make([]StorageResult, len(storageKeys)) storageProof = make([]StorageResult, len(storageKeys))
storageTrie state.Trie
storageHash = types.EmptyRootHash
codeHash = types.EmptyCodeHash
) )
// Deserialize all keys. This prevents state access on invalid input. // Deserialize all keys. This prevents state access on invalid input.
for i, hexKey := range storageKeys { for i, hexKey := range storageKeys {
@ -688,51 +684,49 @@ func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, st
return nil, err return nil, err
} }
} }
state, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash) statedb, header, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil { if statedb == nil || err != nil {
return nil, err return nil, err
} }
if storageRoot := state.GetStorageRoot(address); storageRoot != types.EmptyRootHash && storageRoot != (common.Hash{}) { codeHash := statedb.GetCodeHash(address)
id := trie.StorageTrieID(header.Root, crypto.Keccak256Hash(address.Bytes()), storageRoot) storageRoot := statedb.GetStorageRoot(address)
tr, err := trie.NewStateTrie(id, state.Database().TrieDB())
if err != nil {
return nil, err
}
storageTrie = tr
}
// If we have a storageTrie, the account exists and we must update
// the storage root hash and the code hash.
if storageTrie != nil {
storageHash = storageTrie.Hash()
codeHash = state.GetCodeHash(address)
}
// Create the proofs for the storageKeys.
for i, key := range keys {
// Output key encoding is a bit special: if the input was a 32-byte hash, it is
// returned as such. Otherwise, we apply the QUANTITY encoding mandated by the
// JSON-RPC spec for getProof. This behavior exists to preserve backwards
// compatibility with older client versions.
var outputKey string
if keyLengths[i] != 32 {
outputKey = hexutil.EncodeBig(key.Big())
} else {
outputKey = hexutil.Encode(key[:])
}
if storageTrie == nil { if len(keys) > 0 {
storageProof[i] = StorageResult{outputKey, &hexutil.Big{}, []string{}} var storageTrie state.Trie
continue if storageRoot != types.EmptyRootHash && storageRoot != (common.Hash{}) {
id := trie.StorageTrieID(header.Root, crypto.Keccak256Hash(address.Bytes()), storageRoot)
st, err := trie.NewStateTrie(id, statedb.Database().TrieDB())
if err != nil {
return nil, err
}
storageTrie = st
} }
var proof proofList // Create the proofs for the storageKeys.
if err := storageTrie.Prove(crypto.Keccak256(key.Bytes()), &proof); err != nil { for i, key := range keys {
return nil, err // Output key encoding is a bit special: if the input was a 32-byte hash, it is
// returned as such. Otherwise, we apply the QUANTITY encoding mandated by the
// JSON-RPC spec for getProof. This behavior exists to preserve backwards
// compatibility with older client versions.
var outputKey string
if keyLengths[i] != 32 {
outputKey = hexutil.EncodeBig(key.Big())
} else {
outputKey = hexutil.Encode(key[:])
}
if storageTrie == nil {
storageProof[i] = StorageResult{outputKey, &hexutil.Big{}, []string{}}
continue
}
var proof proofList
if err := storageTrie.Prove(crypto.Keccak256(key.Bytes()), &proof); err != nil {
return nil, err
}
value := (*hexutil.Big)(statedb.GetState(address, key).Big())
storageProof[i] = StorageResult{outputKey, value, proof}
} }
value := (*hexutil.Big)(state.GetState(address, key).Big())
storageProof[i] = StorageResult{outputKey, value, proof}
} }
// Create the accountProof. // Create the accountProof.
tr, err := trie.NewStateTrie(trie.StateTrieID(header.Root), state.Database().TrieDB()) tr, err := trie.NewStateTrie(trie.StateTrieID(header.Root), statedb.Database().TrieDB())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -743,12 +737,12 @@ func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, st
return &AccountResult{ return &AccountResult{
Address: address, Address: address,
AccountProof: accountProof, AccountProof: accountProof,
Balance: (*hexutil.Big)(state.GetBalance(address)), Balance: (*hexutil.Big)(statedb.GetBalance(address)),
CodeHash: codeHash, CodeHash: codeHash,
Nonce: hexutil.Uint64(state.GetNonce(address)), Nonce: hexutil.Uint64(statedb.GetNonce(address)),
StorageHash: storageHash, StorageHash: storageRoot,
StorageProof: storageProof, StorageProof: storageProof,
}, state.Error() }, statedb.Error()
} }
// decodeHash parses a hex-encoded 32-byte hash. The input may optionally // decodeHash parses a hex-encoded 32-byte hash. The input may optionally

Loading…
Cancel
Save