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 <martin@swende.se>
Co-authored-by: Marius van der Wijden <m.vanderwijden@live.de>
This commit is contained in:
James Prestwich 2023-05-30 23:52:27 -07:00 committed by GitHub
parent 8013a494fe
commit 61dcf76230
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -655,43 +655,62 @@ type StorageResult struct {
Proof []string `json:"proof"` 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. // 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) { 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) state, _, err := s.b.StateAndHeaderByNumberOrHash(ctx, blockNrOrHash)
if state == nil || err != nil { if state == nil || err != nil {
return nil, err return nil, err
} }
storageTrie, err := state.StorageTrie(address) if storageTrie, err = state.StorageTrie(address); err != nil {
if err != nil {
return nil, err return nil, err
} }
storageHash := types.EmptyRootHash // if we have a storageTrie, the account exists and we must update
codeHash := state.GetCodeHash(address) // the storage root hash and the code hash.
storageProof := make([]StorageResult, len(storageKeys))
// if we have a storageTrie, (which means the account exists), we can update the storagehash
if storageTrie != nil { if storageTrie != nil {
storageHash = storageTrie.Hash() storageHash = storageTrie.Hash()
} else { codeHash = state.GetCodeHash(address)
// no storageTrie means the account does not exist, so the codeHash is the hash of an empty bytearray.
codeHash = crypto.Keccak256Hash(nil)
} }
// create the proof for the storageKeys // create the proof for the storageKeys
for i, hexKey := range storageKeys { for i, key := range keys {
key, err := decodeHash(hexKey) if storageTrie == nil {
if err != nil { storageProof[i] = StorageResult{storageKeys[i], &hexutil.Big{}, []string{}}
continue
}
var proof proofList
if err := storageTrie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof); err != nil {
return nil, err return nil, err
} }
if storageTrie != nil { storageProof[i] = StorageResult{storageKeys[i],
proof, storageError := state.GetStorageProof(address, key) (*hexutil.Big)(state.GetState(address, key).Big()),
if storageError != nil { proof}
return nil, storageError
}
storageProof[i] = StorageResult{hexKey, (*hexutil.Big)(state.GetState(address, key).Big()), toHexSlice(proof)}
} else {
storageProof[i] = StorageResult{hexKey, &hexutil.Big{}, []string{}}
}
} }
// create the accountProof // create the accountProof