diff --git a/cmd/rpcdaemon/commands/eth_api.go b/cmd/rpcdaemon/commands/eth_api.go index 97edc621420..8d3114d486b 100644 --- a/cmd/rpcdaemon/commands/eth_api.go +++ b/cmd/rpcdaemon/commands/eth_api.go @@ -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" @@ -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) diff --git a/cmd/rpcdaemon/commands/eth_call.go b/cmd/rpcdaemon/commands/eth_call.go index 2e295fc88ac..87abae0421d 100644 --- a/cmd/rpcdaemon/commands/eth_call.go +++ b/cmd/rpcdaemon/commands/eth_call.go @@ -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" @@ -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) @@ -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 { diff --git a/core/types/accounts/account_proof.go b/core/types/accounts/account_proof.go new file mode 100644 index 00000000000..35e9c918680 --- /dev/null +++ b/core/types/accounts/account_proof.go @@ -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"` +} diff --git a/turbo/trie/gen_struct_step.go b/turbo/trie/gen_struct_step.go index 0ae491b0ca2..ff867b9e0ea 100644 --- a/turbo/trie/gen_struct_step.go +++ b/turbo/trie/gen_struct_step.go @@ -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 @@ -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, @@ -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 @@ -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 } @@ -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 } @@ -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, diff --git a/turbo/trie/hashbuilder.go b/turbo/trie/hashbuilder.go index 32659a80ee5..5e1c7456f96 100644 --- a/turbo/trie/hashbuilder.go +++ b/turbo/trie/hashbuilder.go @@ -12,6 +12,7 @@ import ( "golang.org/x/crypto/sha3" "github.com/ledgerwatch/erigon/common" + "github.com/ledgerwatch/erigon/common/hexutil" "github.com/ledgerwatch/erigon/core/types/accounts" "github.com/ledgerwatch/erigon/crypto" "github.com/ledgerwatch/erigon/rlp" @@ -41,6 +42,13 @@ type HashBuilder struct { trace bool // Set to true when HashBuilder is required to print trace information for diagnostics topHashesCopy []byte + + // If an Account proof was requested in trie_root.go, nodes will be written here. + accProofResult *accounts.AccProofResult + // Flag to indicate that the next node should be copied to the stack specified above. + // This was previously done with a "doProof" flag added to the relevant function calls. + // By moving it here the original function signatures do not need to be modified. + collectNode bool } // NewHashBuilder creates a new HashBuilder @@ -61,6 +69,24 @@ func (hb *HashBuilder) Reset() { hb.nodeStack = hb.nodeStack[:0] } hb.topHashesCopy = hb.topHashesCopy[:0] + hb.accProofResult = nil + hb.collectNode = false +} + +func (hb *HashBuilder) SetProofReturn(accProofResult *accounts.AccProofResult) { + accProofResult.AccountProof = make([]string, 0) + accProofResult.StorageProof = make([]accounts.StorProofResult, 0) + hb.accProofResult = accProofResult +} + +// Set the collectNode flag. It will be cleared after an item is added to the proof stack. +func (hb *HashBuilder) collectNextNode() error { + if hb.accProofResult != nil && hb.accProofResult.AccountProof != nil { + hb.collectNode = true + return nil + } else { + return fmt.Errorf("collectNextNode() called with missing accProofResult.AccountProof") + } } func (hb *HashBuilder) leaf(length int, keyHex []byte, val rlphacks.RlpSerializable) error { @@ -148,29 +174,38 @@ func (hb *HashBuilder) completeLeafHash(kp, kl, compactLen int, key []byte, comp writer = hb.sha reader = hb.sha } + // Collect a copy of the hash input if needed for an eth_getProof + var proofBuf bytes.Buffer + mWriter := io.MultiWriter(writer, &proofBuf) - if _, err := writer.Write(hb.lenPrefix[:pt]); err != nil { + if _, err := mWriter.Write(hb.lenPrefix[:pt]); err != nil { return err } - if _, err := writer.Write(hb.keyPrefix[:kp]); err != nil { + if _, err := mWriter.Write(hb.keyPrefix[:kp]); err != nil { return err } hb.b[0] = compact0 - if _, err := writer.Write(hb.b[:]); err != nil { + if _, err := mWriter.Write(hb.b[:]); err != nil { return err } for i := 1; i < compactLen; i++ { hb.b[0] = key[ni]*16 + key[ni+1] - if _, err := writer.Write(hb.b[:]); err != nil { + if _, err := mWriter.Write(hb.b[:]); err != nil { return err } ni += 2 } - if err := val.ToDoubleRLP(writer, hb.prefixBuf[:]); err != nil { + if err := val.ToDoubleRLP(mWriter, hb.prefixBuf[:]); err != nil { return err } + if hb.collectNode { + nodeBytes := hexutil.Bytes(proofBuf.Bytes()) + hb.accProofResult.AccountProof = append([]string{nodeBytes.String()}, hb.accProofResult.AccountProof...) + hb.collectNode = false + } + if reader != nil { hb.hashBuf[0] = 0x80 + length2.Hash if _, err := reader.Read(hb.hashBuf[1:]); err != nil { @@ -363,6 +398,9 @@ func (hb *HashBuilder) extension(key []byte) error { } func (hb *HashBuilder) extensionHash(key []byte) error { + var proofBuf bytes.Buffer + mWriter := io.MultiWriter(hb.sha, &proofBuf) + if hb.trace { fmt.Printf("EXTENSIONHASH %x\n", key) } @@ -399,31 +437,37 @@ func (hb *HashBuilder) extensionHash(key []byte) error { totalLen := kp + kl + 33 pt := rlphacks.GenerateStructLen(hb.lenPrefix[:], totalLen) hb.sha.Reset() - if _, err := hb.sha.Write(hb.lenPrefix[:pt]); err != nil { + if _, err := mWriter.Write(hb.lenPrefix[:pt]); err != nil { return err } - if _, err := hb.sha.Write(hb.keyPrefix[:kp]); err != nil { + if _, err := mWriter.Write(hb.keyPrefix[:kp]); err != nil { return err } hb.b[0] = compact0 - if _, err := hb.sha.Write(hb.b[:]); err != nil { + if _, err := mWriter.Write(hb.b[:]); err != nil { return err } for i := 1; i < compactLen; i++ { hb.b[0] = key[ni]*16 + key[ni+1] - if _, err := hb.sha.Write(hb.b[:]); err != nil { + if _, err := mWriter.Write(hb.b[:]); err != nil { return err } ni += 2 } //capture := common.CopyBytes(branchHash[:length2.Hash+1]) - if _, err := hb.sha.Write(branchHash[:length2.Hash+1]); err != nil { + if _, err := mWriter.Write(branchHash[:length2.Hash+1]); err != nil { return err } // Replace previous hash with the new one if _, err := hb.sha.Read(hb.hashStack[len(hb.hashStack)-length2.Hash:]); err != nil { return err } + + if hb.accProofResult != nil { + nodeBytes := hexutil.Bytes(proofBuf.Bytes()) + hb.accProofResult.AccountProof = append([]string{nodeBytes.String()}, hb.accProofResult.AccountProof...) + } + hb.hashStack[len(hb.hashStack)-hashStackStride] = 0x80 + length2.Hash //fmt.Printf("extensionHash [%x]=>[%x]\nHash [%x]\n", key, capture, hb.hashStack[len(hb.hashStack)-hashStackStride:len(hb.hashStack)]) if _, ok := hb.nodeStack[len(hb.nodeStack)-1].(*fullNode); ok { @@ -472,6 +516,9 @@ func (hb *HashBuilder) branch(set uint16) error { } func (hb *HashBuilder) branchHash(set uint16) error { + var proofBuf bytes.Buffer + mWriter := io.MultiWriter(hb.sha, &proofBuf) + if hb.trace { fmt.Printf("BRANCHHASH (%b)\n", set) } @@ -496,7 +543,7 @@ func (hb *HashBuilder) branchHash(set uint16) error { } hb.sha.Reset() pt := rlphacks.GenerateStructLen(hb.lenPrefix[:], totalSize) - if _, err := hb.sha.Write(hb.lenPrefix[:pt]); err != nil { + if _, err := mWriter.Write(hb.lenPrefix[:pt]); err != nil { return err } // Output hasState hashes or embedded RLPs @@ -506,21 +553,21 @@ func (hb *HashBuilder) branchHash(set uint16) error { for digit := uint(0); digit < 17; digit++ { if ((1 << digit) & set) != 0 { if hashes[hashStackStride*i] == byte(0x80+length2.Hash) { - if _, err := hb.sha.Write(hashes[hashStackStride*i : hashStackStride*i+hashStackStride]); err != nil { + if _, err := mWriter.Write(hashes[hashStackStride*i : hashStackStride*i+hashStackStride]); err != nil { return err } //fmt.Printf("%x: [%x]\n", digit, hashes[hashStackStride*i:hashStackStride*i+hashStackStride]) } else { // Embedded node size := int(hashes[hashStackStride*i]) - rlp.EmptyListCode - if _, err := hb.sha.Write(hashes[hashStackStride*i : hashStackStride*i+size+1]); err != nil { + if _, err := mWriter.Write(hashes[hashStackStride*i : hashStackStride*i+size+1]); err != nil { return err } //fmt.Printf("%x: embedded [%x]\n", digit, hashes[hashStackStride*i:hashStackStride*i+size+1]) } i++ } else { - if _, err := hb.sha.Write(hb.b[:]); err != nil { + if _, err := mWriter.Write(hb.b[:]); err != nil { return err } //fmt.Printf("%x: empty\n", digit) @@ -531,6 +578,13 @@ func (hb *HashBuilder) branchHash(set uint16) error { if _, err := hb.sha.Read(hb.hashStack[len(hb.hashStack)-length2.Hash:]); err != nil { return err } + + if hb.collectNode { + nodeBytes := hexutil.Bytes(proofBuf.Bytes()) + hb.accProofResult.AccountProof = append([]string{nodeBytes.String()}, hb.accProofResult.AccountProof...) + hb.collectNode = false + } + //fmt.Printf("} [%x]\n", hb.hashStack[len(hb.hashStack)-hashStackStride:]) if hashStackStride*len(hb.nodeStack) > len(hb.hashStack) { diff --git a/turbo/trie/trie_root.go b/turbo/trie/trie_root.go index a155031ffdd..91cd8c0a0c9 100644 --- a/turbo/trie/trie_root.go +++ b/turbo/trie/trie_root.go @@ -93,6 +93,9 @@ type FlatDBTrieLoader struct { defaultReceiver *RootHashAggregator hc HashCollector2 shc StorageHashCollector2 + + // Optionally construct an Account Proof for an account key specified in 'rd' + accProofResult *accounts.AccProofResult } // RootHashAggregator - calculates Merkle trie root hash from incoming data stream @@ -125,6 +128,10 @@ type RootHashAggregator struct { a accounts.Account leafData GenStructStepLeafData accData GenStructStepAccountData + + // Used to construct an Account proof while calculating the tree root. + proofMatch RetainDecider + cutoff bool } type StreamReceiver interface { @@ -169,9 +176,16 @@ func (l *FlatDBTrieLoader) Reset(rd RetainDeciderWithMarker, hc HashCollector2, fmt.Printf("----------\n") fmt.Printf("CalcTrieRoot\n") } + l.accProofResult = nil return nil } +func (l *FlatDBTrieLoader) SetProofReturn(accProofResult *accounts.AccProofResult) { + l.accProofResult = accProofResult + l.defaultReceiver.proofMatch = l.rd + l.defaultReceiver.hb.SetProofReturn(accProofResult) +} + func (l *FlatDBTrieLoader) SetStreamReceiver(receiver StreamReceiver) { l.receiver = receiver } @@ -349,6 +363,7 @@ func (r *RootHashAggregator) Reset(hc HashCollector2, shc StorageHashCollector2, r.root = libcommon.Hash{} r.trace = trace r.hb.trace = trace + r.proofMatch = nil } func (r *RootHashAggregator) Receive(itemType StreamItem, @@ -482,6 +497,10 @@ func (r *RootHashAggregator) Receive(itemType StreamItem, r.accData.FieldSet |= AccountFieldStorageOnly } } + + // Used for optional GetProof calculation to trigger inclusion of the top-level node + r.cutoff = true + if r.curr.Len() > 0 { if err := r.genStructAccount(); err != nil { return err @@ -634,14 +653,21 @@ func (r *RootHashAggregator) genStructAccount() error { r.currStorage.Reset() r.succStorage.Reset() var err error - if r.groups, r.hasTree, r.hasHash, err = GenStructStep(r.RetainNothing, r.curr.Bytes(), r.succ.Bytes(), r.hb, func(keyHex []byte, hasState, hasTree, hasHash uint16, hashes, rootHash []byte) error { + + var wantProof func(_ []byte) bool + if r.proofMatch != nil { + wantProof = r.proofMatch.Retain + } + if r.groups, r.hasTree, r.hasHash, err = GenStructStepEx(r.RetainNothing, r.curr.Bytes(), r.succ.Bytes(), r.hb, func(keyHex []byte, hasState, hasTree, hasHash uint16, hashes, rootHash []byte) error { if r.hc == nil { return nil } return r.hc(keyHex, hasState, hasTree, hasHash, hashes, rootHash) }, data, r.groups, r.hasTree, r.hasHash, - false, - //r.trace, + //false, + r.trace, + wantProof, + r.cutoff, ); err != nil { return err }