Skip to content

Commit

Permalink
internal/ethapi: prevent unnecessary resource usage in eth_getProof i…
Browse files Browse the repository at this point in the history
…mplementation (#27310)

Deserialize hex keys early to shortcut on invalid input, and re-use the account storageTrie for each proof for each proof in the account, preventing repeated deep-copying of the trie.

Closes #27308

 --------

Co-authored-by: Martin Holst Swende <martin@swende.se>
Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de>
  • Loading branch information
3 people committed May 31, 2023
1 parent 8013a49 commit 61dcf76
Showing 1 changed file with 42 additions and 23 deletions.
65 changes: 42 additions & 23 deletions internal/ethapi/api.go
Expand Up @@ -655,43 +655,62 @@ type StorageResult struct {
Proof []string `json:"proof"`
}

// proofList implements ethdb.KeyValueWriter and collects the proofs as
// hex-strings for delivery to rpc-caller.
type proofList []string

func (n *proofList) Put(key []byte, value []byte) error {
*n = append(*n, hexutil.Encode(value))
return nil
}

func (n *proofList) Delete(key []byte) error {
panic("not supported")
}

// GetProof returns the Merkle-proof for a given account and optionally some storage keys.
func (s *BlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (*AccountResult, error) {
var (
keys = make([]common.Hash, len(storageKeys))
storageProof = make([]StorageResult, len(storageKeys))
storageTrie state.Trie
storageHash = types.EmptyRootHash
codeHash = types.EmptyCodeHash
)
// Greedily deserialize all keys. This prevents state access on invalid input
for i, hexKey := range storageKeys {
if key, err := decodeHash(hexKey); err != nil {
return nil, err
} else {
keys[i] = key
}
}
state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil {
return nil, err
}
storageTrie, err := state.StorageTrie(address)
if err != nil {
if storageTrie, err = state.StorageTrie(address); err != nil {
return nil, err
}
storageHash := types.EmptyRootHash
codeHash := state.GetCodeHash(address)
storageProof := make([]StorageResult, len(storageKeys))

// if we have a storageTrie, (which means the account exists), we can update the storagehash
// 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()
} else {
// no storageTrie means the account does not exist, so the codeHash is the hash of an empty bytearray.
codeHash = crypto.Keccak256Hash(nil)
codeHash = state.GetCodeHash(address)
}

// create the proof for the storageKeys
for i, hexKey := range storageKeys {
key, err := decodeHash(hexKey)
if err != nil {
return nil, err
for i, key := range keys {
if storageTrie == nil {
storageProof[i] = StorageResult{storageKeys[i], &hexutil.Big{}, []string{}}
continue
}
if storageTrie != nil {
proof, storageError := state.GetStorageProof(address, key)
if storageError != nil {
return nil, storageError
}
storageProof[i] = StorageResult{hexKey, (*hexutil.Big)(state.GetState(address, key).Big()), toHexSlice(proof)}
} else {
storageProof[i] = StorageResult{hexKey, &hexutil.Big{}, []string{}}
var proof proofList
if err := storageTrie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof); err != nil {
return nil, err
}
storageProof[i] = StorageResult{storageKeys[i],
(*hexutil.Big)(state.GetState(address, key).Big()),
proof}
}

// create the accountProof
Expand Down

0 comments on commit 61dcf76

Please sign in to comment.