From 97fb08342d227bbd126516083b0ddaf74e6e8468 Mon Sep 17 00:00:00 2001 From: Simon Jentzsch Date: Thu, 18 Oct 2018 21:41:22 +0200 Subject: [PATCH] EIP-1186 eth_getProof (#17737) * first impl of eth_getProof * fixed docu * added comments and refactored based on comments from holiman * created structs * handle errors correctly * change Value to *hexutil.Big in order to have the same output as parity * use ProofList as return type --- common/bytes.go | 9 ++++++ core/state/statedb.go | 20 +++++++++++++ core/vm/interface.go | 10 +++++++ internal/ethapi/api.go | 66 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 105 insertions(+) diff --git a/common/bytes.go b/common/bytes.go index 0c257a1ee..c82e61624 100644 --- a/common/bytes.go +++ b/common/bytes.go @@ -31,6 +31,15 @@ func ToHex(b []byte) string { return "0x" + hex } +// ToHexArray creates a array of hex-string based on []byte +func ToHexArray(b [][]byte) []string { + r := make([]string, len(b)) + for i := range b { + r[i] = ToHex(b[i]) + } + return r +} + // FromHex returns the bytes represented by the hexadecimal string s. // s may be prefixed with "0x". func FromHex(s string) []byte { diff --git a/core/state/statedb.go b/core/state/statedb.go index 216667ce9..b1bb53ed0 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -18,6 +18,7 @@ package state import ( + "errors" "fmt" "math/big" "sort" @@ -25,6 +26,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/rlp" @@ -256,6 +258,24 @@ func (self *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash return common.Hash{} } +// GetProof returns the MerkleProof for a given Account +func (self *StateDB) GetProof(a common.Address) (vm.ProofList, error) { + var proof vm.ProofList + err := self.trie.Prove(crypto.Keccak256(a.Bytes()), 0, &proof) + return proof, err +} + +// GetProof returns the StorageProof for given key +func (self *StateDB) GetStorageProof(a common.Address, key common.Hash) (vm.ProofList, error) { + var proof vm.ProofList + trie := self.StorageTrie(a) + if trie == nil { + return proof, errors.New("storage trie for requested address does not exist") + } + err := trie.Prove(crypto.Keccak256(key.Bytes()), 0, &proof) + return proof, err +} + // GetCommittedState retrieves a value from the given account's committed storage trie. func (self *StateDB) GetCommittedState(addr common.Address, hash common.Hash) common.Hash { stateObject := self.getStateObject(addr) diff --git a/core/vm/interface.go b/core/vm/interface.go index fc15082f1..1ae98cf7a 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -35,6 +35,8 @@ type StateDB interface { SetNonce(common.Address, uint64) GetCodeHash(common.Address) common.Hash + GetProof(common.Address) (ProofList, error) + GetStorageProof(common.Address, common.Hash) (ProofList, error) GetCode(common.Address) []byte SetCode(common.Address, []byte) GetCodeSize(common.Address) int @@ -78,3 +80,11 @@ type CallContext interface { // Create a new contract Create(env *EVM, me ContractRef, data []byte, gas, value *big.Int) ([]byte, common.Address, error) } + +// MerkleProof +type ProofList [][]byte + +func (n *ProofList) Put(key []byte, value []byte) error { + *n = append(*n, value) + return nil +} diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 196be43e0..0c751c328 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -508,6 +508,72 @@ func (s *PublicBlockChainAPI) GetBalance(ctx context.Context, address common.Add return (*hexutil.Big)(state.GetBalance(address)), state.Error() } +// Result structs for GetProof +type AccountResult struct { + Address common.Address `json:"address"` + AccountProof []string `json:"accountProof"` + Balance *hexutil.Big `json:"balance"` + CodeHash common.Hash `json:"codeHash"` + Nonce hexutil.Uint64 `json:"nonce"` + StorageHash common.Hash `json:"storageHash"` + StorageProof []StorageResult `json:"storageProof"` +} +type StorageResult struct { + Key string `json:"key"` + Value *hexutil.Big `json:"value"` + Proof []string `json:"proof"` +} + +// GetProof returns the Merkle-proof for a given account and optionally some storage keys. +func (s *PublicBlockChainAPI) GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNr rpc.BlockNumber) (*AccountResult, error) { + state, _, err := s.b.StateAndHeaderByNumber(ctx, blockNr) + if state == nil || err != nil { + return nil, err + } + + storageTrie := state.StorageTrie(address) + 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 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) + } + + // create the proof for the storageKeys + for i, key := range storageKeys { + if storageTrie != nil { + proof, storageError := state.GetStorageProof(address, common.HexToHash(key)) + if storageError != nil { + return nil, storageError + } + storageProof[i] = StorageResult{key, (*hexutil.Big)(state.GetState(address, common.HexToHash(key)).Big()), common.ToHexArray(proof)} + } else { + storageProof[i] = StorageResult{key, &hexutil.Big{}, []string{}} + } + } + + // create the accountProof + accountProof, proofErr := state.GetProof(address) + if proofErr != nil { + return nil, proofErr + } + + return &AccountResult{ + Address: address, + AccountProof: common.ToHexArray(accountProof), + Balance: (*hexutil.Big)(state.GetBalance(address)), + CodeHash: codeHash, + Nonce: hexutil.Uint64(state.GetNonce(address)), + StorageHash: storageHash, + StorageProof: storageProof, + }, state.Error() +} + // GetBlockByNumber returns the requested block. When blockNr is -1 the chain head is returned. When fullTx is true all // transactions in the block are returned in full detail, otherwise only the transaction hash is returned. func (s *PublicBlockChainAPI) GetBlockByNumber(ctx context.Context, blockNr rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) {