From 4fb431188766005e72ed3fc4039d1df0d90aeb41 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Tue, 30 May 2023 23:52:27 -0700 Subject: [PATCH] internal/ethapi: prevent unnecessary resource usage in eth_getProof implementation (#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 Co-authored-by: Marius van der Wijden --- internal/ethapi/api.go | 65 +++++++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 080d809d38944..40cf6e131f543 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -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