Skip to content

Commit

Permalink
Partial EIP1186 eth_getProof implementation (erigontech#6560)
Browse files Browse the repository at this point in the history
This is a partial implementation of eth_getProof (see issue erigontech#1349),
supporting only a request for the latest block and an empty list of
storage keys (i.e. Account proof only). I don't know if there's a better
way of implementing this, but this was what I could come up with.
Posting it here in case it's useful.

Example output:
```
> eth.getProof("0x67b1d87101671b127f5f8714789C7192f7ad340e", [], 'latest')
{
  accountProof: ["0xf90131a0252c9d4ed347b4cf3fdccaea3ccef0a507e6bd4dbe4dcd98609b7195347c4062a0ab8cdb808c8303bb61fb48e276217be9770fa83ecf3f90f2234d558885f5abf18080a01a697e814758281972fcd13bc9707dbcd2f195986b05463d7b78426508445a04a0b5d7a91be5ee273cce27e2ad9a160d2faadd5a6ba518d384019b68728a4f62f4a0c2c799b60a0cd6acd42c1015512872e86c186bcf196e85061e76842f3b7cf86080a0e73919d9f472eec11f6da95518503f5527a98b9428f7a02c4f55bf51854214e480a06301b39b2ea8a44df8b0356120db64b788e71f52e1d7a6309d0d2e5b86fee7cb8080a01b7779e149cadf24d4ffb77ca7e11314b8db7097e4d70b2a173493153ca2e5a0a066a7662811491b3d352e969506b420d269e8b51a224f574b3b38b3463f43f0098080", "0xf8518080808080a0a00135c9ec2655cb6a47ab7ad27d6fc150d9cba8b3d4a702e879179116a68a60808080808080a02fb46956347985b9870156b5747712899d213b1636ad4fe553c63e33521d567a80808080", "0xf873a02056274a27dd7524955417c11ecd917251cc7c4c8310f4c7e4bd3c304d3d9a79b850f84e808a021e19e0c9bab2400000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470"],
  address: "0x67b1d87101671b127f5f8714789c7192f7ad340e",
  balance: "0x21e19e0c9bab2400000",
  codeHash: "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470",
  nonce: "0x0",
  storageHash: "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
  storageProof: []
}
> eth.getBlock('latest').stateRoot
"0x6a0673c691edfa4c4528323986bb43c579316f436ff6f8b4ac70854bbd95340b"
```
  • Loading branch information
mmontour1306 authored and calmbeing committed Mar 16, 2023
1 parent 7df7263 commit 7ac41d6
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 25 deletions.
3 changes: 2 additions & 1 deletion cmd/rpcdaemon/commands/eth_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"github.com/ledgerwatch/erigon/consensus/misc"
"github.com/ledgerwatch/erigon/core/rawdb"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/core/types/accounts"
ethFilters "github.com/ledgerwatch/erigon/eth/filters"
"github.com/ledgerwatch/erigon/ethdb/prune"
"github.com/ledgerwatch/erigon/rpc"
Expand Down Expand Up @@ -92,7 +93,7 @@ type EthAPI interface {
SendTransaction(_ context.Context, txObject interface{}) (common.Hash, error)
Sign(ctx context.Context, _ common.Address, _ hexutil.Bytes) (hexutil.Bytes, error)
SignTransaction(_ context.Context, txObject interface{}) (common.Hash, error)
GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNr rpc.BlockNumber) (*interface{}, error)
GetProof(ctx context.Context, address common.Address, storageKeys []string, blockNr rpc.BlockNumberOrHash) (*accounts.AccProofResult, error)
CreateAccessList(ctx context.Context, args ethapi2.CallArgs, blockNrOrHash *rpc.BlockNumberOrHash, optimizeGas *bool) (*accessListResult, error)

// Mining related (see ./eth_mining.go)
Expand Down
70 changes: 66 additions & 4 deletions cmd/rpcdaemon/commands/eth_call.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ import (
"github.com/ledgerwatch/log/v3"
"google.golang.org/grpc"

"github.com/ledgerwatch/erigon/common"
"github.com/ledgerwatch/erigon/common/hexutil"
"github.com/ledgerwatch/erigon/core"
"github.com/ledgerwatch/erigon/core/state"
"github.com/ledgerwatch/erigon/core/types"
"github.com/ledgerwatch/erigon/core/types/accounts"
"github.com/ledgerwatch/erigon/core/vm"
"github.com/ledgerwatch/erigon/crypto"
"github.com/ledgerwatch/erigon/eth/tracers/logger"
Expand All @@ -27,6 +29,7 @@ import (
ethapi2 "github.com/ledgerwatch/erigon/turbo/adapter/ethapi"
"github.com/ledgerwatch/erigon/turbo/rpchelper"
"github.com/ledgerwatch/erigon/turbo/transactions"
"github.com/ledgerwatch/erigon/turbo/trie"
)

var latestNumOrHash = rpc.BlockNumberOrHashWithNumber(rpc.LatestBlockNumber)
Expand Down Expand Up @@ -300,10 +303,69 @@ func (api *APIImpl) EstimateGas(ctx context.Context, argsOrNil *ethapi2.CallArgs
return hexutil.Uint64(hi), nil
}

// GetProof not implemented
func (api *APIImpl) GetProof(ctx context.Context, address libcommon.Address, storageKeys []string, blockNr rpc.BlockNumber) (*interface{}, error) {
var stub interface{}
return &stub, fmt.Errorf(NotImplemented, "eth_getProof")
// GetProof is partially implemented; no Storage proofs; only for the latest block
func (api *APIImpl) GetProof(ctx context.Context, address libcommon.Address, storageKeys []string, blockNrOrHash rpc.BlockNumberOrHash) (*accounts.AccProofResult, error) {

tx, err := api.db.BeginRo(ctx)
if err != nil {
return nil, err
}
defer tx.Rollback()

blockNr, _, _, err := rpchelper.GetBlockNumber(blockNrOrHash, tx, api.filters)
if err != nil {
return nil, err
}

latestBlock, err := rpchelper.GetLatestBlockNumber(tx)
if err != nil {
return nil, err
} else if blockNr != latestBlock {
return nil, fmt.Errorf(NotImplemented, "eth_getProof for block != latest")
} else if len(storageKeys) != 0 {
return nil, fmt.Errorf(NotImplemented, "eth_getProof with storageKeys")
} else {
addrHash, err := common.HashData(address[:])
if err != nil {
return nil, err
}

rl := trie.NewRetainList(0)
rl.AddKey(addrHash[:])

loader := trie.NewFlatDBTrieLoader("getProof")
trace := true
if err := loader.Reset(rl, nil, nil, trace); err != nil {
return nil, err
}

var accProof accounts.AccProofResult
accProof.Address = address

// Fill in the Account fields here to reduce the code changes
// needed in turbo/trie/hashbuilder.go
reader, err := rpchelper.CreateStateReader(ctx, tx, blockNrOrHash, 0, api.filters, api.stateCache, api.historyV3(tx), "")
if err != nil {
return nil, err
}
a, err := reader.ReadAccountData(address)
if err != nil {
return nil, err
}
if a != nil {
accProof.Balance = (*hexutil.Big)(a.Balance.ToBig())
accProof.CodeHash = a.CodeHash
accProof.Nonce = hexutil.Uint64(a.Nonce)
accProof.StorageHash = a.Root
}

loader.SetProofReturn(&accProof)
_, err = loader.CalcTrieRoot(tx, nil, nil)
if err != nil {
return nil, err
}
return &accProof, nil
}
}

func (api *APIImpl) tryBlockFromLru(hash libcommon.Hash) *types.Block {
Expand Down
22 changes: 22 additions & 0 deletions core/types/accounts/account_proof.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package accounts

import (
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon/common/hexutil"
)

// Result structs for GetProof
type AccProofResult struct {
Address libcommon.Address `json:"address"`
AccountProof []string `json:"accountProof"`
Balance *hexutil.Big `json:"balance"`
CodeHash libcommon.Hash `json:"codeHash"`
Nonce hexutil.Uint64 `json:"nonce"`
StorageHash libcommon.Hash `json:"storageHash"`
StorageProof []StorProofResult `json:"storageProof"`
}
type StorProofResult struct {
Key string `json:"key"`
Value *hexutil.Big `json:"value"`
Proof []string `json:"proof"`
}
37 changes: 34 additions & 3 deletions turbo/trie/gen_struct_step.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type structInfoReceiver interface {
topHash() []byte
topHashes(prefix []byte, branches, children uint16) []byte
printTopHashes(prefix []byte, branches, children uint16)
collectNextNode() error
}

// hashCollector gets called whenever there might be a need to create intermediate hash record
Expand Down Expand Up @@ -99,7 +100,10 @@ func (GenStructStepHashData) GenStructStepData() {}
// Whenever a `BRANCH` or `BRANCHHASH` opcode is emitted, the set of digits is taken from the corresponding `groups` item, which is
// then removed from the slice. This signifies the usage of the number of the stack items by the `BRANCH` or `BRANCHHASH` opcode.
// DESCRIBED: docs/programmers_guide/guide.md#separation-of-keys-and-the-structure
func GenStructStep(

// GenStructStepEx is extended to support optional generation of an Account Proof during trie_root.go CalcTrieRoot().
// The wrapper below calls it with nil/false defaults so that other callers do not need to be modified.
func GenStructStepEx(
retain func(prefix []byte) bool,
curr, succ []byte,
e structInfoReceiver,
Expand All @@ -109,6 +113,8 @@ func GenStructStep(
hasTree []uint16,
hasHash []uint16,
trace bool,
wantProof func(prefix []byte) bool,
cutoff bool,
) ([]uint16, []uint16, []uint16, error) {
for precLen, buildExtensions := calcPrecLen(groups), false; precLen >= 0; precLen, buildExtensions = calcPrecLen(groups), true {
var precExists = len(groups) > 0
Expand Down Expand Up @@ -164,7 +170,8 @@ func GenStructStep(
}
buildExtensions = true
case *GenStructStepAccountData:
if retain(curr[:maxLen]) {
if retain(curr[:maxLen]) || (wantProof != nil && wantProof(curr[:len(curr)-1])) {
e.collectNextNode()
if err := e.accountLeaf(remainderLen, curr, &v.Balance, v.Nonce, v.Incarnation, v.FieldSet, codeSizeUncached); err != nil {
return nil, nil, nil, err
}
Expand Down Expand Up @@ -267,10 +274,21 @@ func GenStructStep(
}
}

var doProof bool
if wantProof != nil {
if maxLen > 0 && wantProof(curr[:maxLen]) {
doProof = true
}
if len(succ) == 0 && maxLen == 0 && cutoff {
doProof = true
}
}

if trace {
e.printTopHashes(curr[:maxLen], 0, groups[maxLen])
}
if retain(curr[:maxLen]) {
if retain(curr[:maxLen]) || doProof {
e.collectNextNode()
if err := e.branch(groups[maxLen]); err != nil {
return nil, nil, nil, err
}
Expand Down Expand Up @@ -305,6 +323,19 @@ func GenStructStep(
}
return nil, nil, nil, nil
}
func GenStructStep(
retain func(prefix []byte) bool,
curr, succ []byte,
e structInfoReceiver,
h HashCollector2,
data GenStructStepData,
groups []uint16,
hasTree []uint16,
hasHash []uint16,
trace bool,
) ([]uint16, []uint16, []uint16, error) {
return GenStructStepEx(retain, curr, succ, e, h, data, groups, hasTree, hasHash, trace, nil, false)
}

func GenStructStepOld(
retain func(prefix []byte) bool,
Expand Down
Loading

0 comments on commit 7ac41d6

Please sign in to comment.