diff --git a/cmd/evm/blockrunner.go b/cmd/evm/blockrunner.go index 0275c019bc61..d5cd8d8e3de2 100644 --- a/cmd/evm/blockrunner.go +++ b/cmd/evm/blockrunner.go @@ -86,7 +86,7 @@ func blockTestCmd(ctx *cli.Context) error { continue } test := tests[name] - if err := test.Run(false, rawdb.HashScheme, tracer, func(res error, chain *core.BlockChain) { + if err := test.Run(false, rawdb.HashScheme, false, tracer, func(res error, chain *core.BlockChain) { if ctx.Bool(DumpFlag.Name) { if state, _ := chain.State(); state != nil { fmt.Println(string(state.Dump(nil))) diff --git a/cmd/utils/stateless/stateless.go b/cmd/utils/stateless/stateless.go deleted file mode 100644 index 27ed0bc19735..000000000000 --- a/cmd/utils/stateless/stateless.go +++ /dev/null @@ -1,86 +0,0 @@ -package stateless - -import ( - "fmt" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus/beacon" - "github.com/ethereum/go-ethereum/consensus/ethash" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/vm" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/triedb" -) - -// StatelessExecute executes the block contained in the Witness returning the post state root or an error -func StatelessExecute(chainCfg *params.ChainConfig, witness *state.Witness) (root common.Hash, err error) { - rawDb := rawdb.NewMemoryDatabase() - if err := witness.PopulateDB(rawDb); err != nil { - return common.Hash{}, err - } - blob := rawdb.ReadAccountTrieNode(rawDb, nil) - prestateRoot := crypto.Keccak256Hash(blob) - - db, err := state.New(prestateRoot, state.NewDatabaseWithConfig(rawDb, triedb.PathDefaults), nil) - if err != nil { - return common.Hash{}, err - } - engine := beacon.New(ethash.NewFaker()) - validator := core.NewBlockValidator(chainCfg, nil, engine) - processor := core.NewStateProcessor(chainCfg, nil, engine) - - receipts, _, usedGas, err := processor.Process(witness.Block, db, vm.Config{}, witness) - if err != nil { - return common.Hash{}, err - } - - // compute the state root. - if root, err = validator.ValidateState(witness.Block, db, receipts, usedGas, false); err != nil { - return common.Hash{}, err - } - return root, nil -} - -// BuildStatelessProof executes a block, collecting the accessed pre-state into -// a Witness. The RLP-encoded witness is returned. -func BuildStatelessProof(blockHash common.Hash, bc *core.BlockChain) ([]byte, error) { - block := bc.GetBlockByHash(blockHash) - if block == nil { - return nil, fmt.Errorf("non-existent block %x", blockHash) - } else if block.NumberU64() == 0 { - return nil, fmt.Errorf("cannot build a stateless proof of the genesis block") - } - parentHash := block.ParentHash() - parent := bc.GetBlockByHash(parentHash) - if parent == nil { - return nil, fmt.Errorf("block %x parent not present", parentHash) - } - - db, err := bc.StateAt(parent.Header().Root) - if err != nil { - return nil, err - } - db.EnableWitnessBuilding() - if bc.Snapshots() != nil { - db.StartPrefetcher("BuildStatelessProof", false) - defer db.StopPrefetcher() - } - stateProcessor := core.NewStateProcessor(bc.Config(), bc, bc.Engine()) - _, _, _, err = stateProcessor.Process(block, db, vm.Config{}, nil) - if err != nil { - return nil, err - } - if _, err = db.Commit(block.NumberU64(), true); err != nil { - return nil, err - } - proof := db.Witness() - proof.Block = block - enc, err := proof.EncodeRLP() - if err != nil { - return nil, err - } - return enc, nil -} diff --git a/core/block_validator.go b/core/block_validator.go index f5cbe5b185fb..75f7f8a94b68 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -23,6 +23,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/trie" @@ -35,14 +36,12 @@ import ( type BlockValidator struct { config *params.ChainConfig // Chain configuration options bc *BlockChain // Canonical block chain - engine consensus.Engine // Consensus engine used for validating } // NewBlockValidator returns a new block validator which is safe for re-use -func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engine consensus.Engine) *BlockValidator { +func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain) *BlockValidator { validator := &BlockValidator{ config: config, - engine: engine, bc: blockchain, } return validator @@ -60,7 +59,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { // Header validity is known at this point. Here we verify that uncles, transactions // and withdrawals given in the block body match the header. header := block.Header() - if err := v.engine.VerifyUncles(v.bc, block); err != nil { + if err := v.bc.engine.VerifyUncles(v.bc, block); err != nil { return err } if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash { @@ -122,31 +121,55 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { // ValidateState validates the various changes that happen after a state transition, // such as amount of used gas, the receipt roots and the state root itself. -// If validateRemoteRoot is false, the provided block header's root is not asserted to be equal to the one computed from -// execution. -func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64, checkRemoteRoot bool) (root common.Hash, err error) { +func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64, stateless bool) error { header := block.Header() if block.GasUsed() != usedGas { - return root, fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas) + return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas) } // Validate the received block's bloom with the one derived from the generated receipts. // For valid blocks this should always validate to true. rbloom := types.CreateBloom(receipts) if rbloom != header.Bloom { - return root, fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom) + return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom) + } + // In stateless mode, return early because the receipt and state root are not + // provided through the witness, rather the cross validator needs to return it. + if stateless { + return nil } // The receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]])) receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil)) if receiptSha != header.ReceiptHash { - return root, fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha) + return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha) } - // Compute the state root and if enabled, check it against the - // received state root and throw an error if they don't match. - root = statedb.IntermediateRoot(v.config.IsEIP158(header.Number)) - if checkRemoteRoot && header.Root != root { - return root, fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error()) + // Validate the state root against the received state root and throw + // an error if they don't match. + if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { + return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error()) } - return root, nil + return nil +} + +// ValidateWitness cross validates a block execution with stateless remote clients. +// +// Normally we'd distribute the block witness to remote cross validators, wait +// for them to respond and then merge the results. For now, however, it's only +// Geth, so do an internal stateless run. +func (v *BlockValidator) ValidateWitness(witness *stateless.Witness, receiptRoot common.Hash, stateRoot common.Hash) error { + // Run the cross client stateless execution + // TODO(karalabe): Self-stateless for now, swap with other clients + crossReceiptRoot, crossStateRoot, err := ExecuteStateless(v.config, witness) + if err != nil { + return fmt.Errorf("stateless execution failed: %v", err) + } + // Stateless cross execution suceeeded, validate the withheld computed fields + if crossReceiptRoot != receiptRoot { + return fmt.Errorf("cross validator receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, receiptRoot) + } + if crossStateRoot != stateRoot { + return fmt.Errorf("cross validator state root mismatch (cross: %x local: %x)", crossStateRoot, stateRoot) + } + return nil } // CalcGasLimit computes the gas limit of the next block after parent. It aims diff --git a/core/blockchain.go b/core/blockchain.go index 1305efbb9ae9..05ebfd18b830 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -37,6 +37,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -302,18 +303,18 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis vmConfig: vmConfig, logger: vmConfig.Tracer, } - bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit)) - bc.forker = NewForkChoice(bc, shouldPreserve) - bc.stateCache = state.NewDatabaseWithNodeDB(bc.db, bc.triedb) - bc.validator = NewBlockValidator(chainConfig, bc, engine) - bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine) - bc.processor = NewStateProcessor(chainConfig, bc, engine) - var err error bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.insertStopped) if err != nil { return nil, err } + bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit)) + bc.forker = NewForkChoice(bc, shouldPreserve) + bc.stateCache = state.NewDatabaseWithNodeDB(bc.db, bc.triedb) + bc.validator = NewBlockValidator(chainConfig, bc) + bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc) + bc.processor = NewStateProcessor(chainConfig, bc.hc) + bc.genesisBlock = bc.GetBlockByNumber(0) if bc.genesisBlock == nil { return nil, ErrNoGenesis @@ -1809,7 +1810,14 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error) // while processing transactions. Before Byzantium the prefetcher is mostly // useless due to the intermediate root hashing after each transaction. if bc.chainConfig.IsByzantium(block.Number()) { - statedb.StartPrefetcher("chain", !bc.vmConfig.EnableWitnessCollection) + var witness *stateless.Witness + if bc.vmConfig.EnableWitnessCollection { + witness, err = stateless.NewWitness(bc, block) + if err != nil { + return it.index, err + } + } + statedb.StartPrefetcher("chain", witness) } activeState = statedb @@ -1916,7 +1924,7 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s // Process block using the parent state as reference point pstart := time.Now() - receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig, nil) + receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig) if err != nil { bc.reportBlock(block, receipts, err) return nil, err @@ -1924,11 +1932,18 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s ptime := time.Since(pstart) vstart := time.Now() - if _, err := bc.validator.ValidateState(block, statedb, receipts, usedGas, true); err != nil { + if err := bc.validator.ValidateState(block, statedb, receipts, usedGas, false); err != nil { bc.reportBlock(block, receipts, err) return nil, err } vtime := time.Since(vstart) + + if witness := statedb.Witness(); witness != nil { + if err = bc.validator.ValidateWitness(witness, block.ReceiptHash(), block.Root()); err != nil { + bc.reportBlock(block, receipts, err) + return nil, fmt.Errorf("cross verification failed: %v", err) + } + } proctime := time.Since(start) // processing + validation // Update the metrics touched during block processing and validation diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 4af5be7515a5..4f28c6f5e681 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -163,13 +163,12 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { if err != nil { return err } - receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{}, nil) + receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{}) if err != nil { blockchain.reportBlock(block, receipts, err) return err } - _, err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas, true) - if err != nil { + if err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas, false); err != nil { blockchain.reportBlock(block, receipts, err) return err } diff --git a/core/chain_makers.go b/core/chain_makers.go index d7996b035079..58985347bb31 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -99,7 +99,7 @@ func (b *BlockGen) Difficulty() *big.Int { func (b *BlockGen) SetParentBeaconRoot(root common.Hash) { b.header.ParentBeaconRoot = &root var ( - blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase, nil) + blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase) vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.cm.config, vm.Config{}) ) ProcessBeaconBlockRoot(root, vmenv, b.statedb) diff --git a/core/evm.go b/core/evm.go index aee619b0470b..5d3c454d7c47 100644 --- a/core/evm.go +++ b/core/evm.go @@ -19,8 +19,6 @@ package core import ( "math/big" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc/eip4844" @@ -40,9 +38,8 @@ type ChainContext interface { GetHeader(common.Hash, uint64) *types.Header } -// NewEVMBlockContext creates a new context for use in the EVM. If witness is non-nil, the context sources block hashes -// for the BLOCKHASH opcode from the witness. -func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, witness *state.Witness) vm.BlockContext { +// NewEVMBlockContext creates a new context for use in the EVM. +func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext { var ( beneficiary common.Address baseFee *big.Int @@ -65,18 +62,10 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common if header.Difficulty.Sign() == 0 { random = &header.MixDigest } - var getHash vm.GetHashFunc - if witness != nil { - getHash = func(n uint64) common.Hash { - return witness.BlockHash(n) - } - } else { - getHash = GetHashFn(header, chain) - } return vm.BlockContext{ CanTransfer: CanTransfer, Transfer: Transfer, - GetHash: getHash, + GetHash: GetHashFn(header, chain), Coinbase: beneficiary, BlockNumber: new(big.Int).Set(header.Number), Time: header.Time, diff --git a/core/state/database.go b/core/state/database.go index eebfb1c7a7cf..d54417d2f91e 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -125,9 +125,9 @@ type Trie interface { // be created with new root and updated trie database for following usage Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) - // AccessList returns a map of path->blob containing all trie nodes that have - // been accessed. - AccessList() map[string][]byte + // Witness returns a set containing all trie nodes that have been accessed. + // The returned map could be nil if the witness is empty. + Witness() map[string]struct{} // NodeIterator returns an iterator that returns nodes of the trie. Iteration // starts at the key after the given start key. And error will be returned diff --git a/core/state/state_object.go b/core/state/state_object.go index 8060d0b86eb6..880b715b4b37 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -323,7 +323,17 @@ func (s *stateObject) finalise() { // // It assumes all the dirty storage slots have been finalized before. func (s *stateObject) updateTrie() (Trie, error) { - // Retrieve a pretecher populated trie, or fall back to the database + // Short circuit if nothing was accessed, don't trigger a prefetcher warning + if len(s.uncommittedStorage) == 0 { + // Nothing was written, so we could stop early. Unless we have both reads + // and witness collection enabled, in which case we need to fetch the trie. + if s.db.witness == nil || len(s.originStorage) == 0 { + return s.trie, nil + } + } + // Retrieve a pretecher populated trie, or fall back to the database. This will + // block until all prefetch tasks are done, which are needed for witnesses even + // for unmodified state objects. tr := s.getPrefetchedTrie() if tr != nil { // Prefetcher returned a live trie, swap it out for the current one @@ -337,11 +347,7 @@ func (s *stateObject) updateTrie() (Trie, error) { return nil, err } } - // Short circuit if nothing changed, don't bother with hashing anything. - // - // We only quit after the prefetched trie is potentially resolved above - // because, when building a stateless witness we will need to collect - //storage access witnesses from the object's trie when we commit it. + // Short circuit if nothing changed, don't bother with hashing anything if len(s.uncommittedStorage) == 0 { return s.trie, nil } @@ -450,9 +456,7 @@ func (s *stateObject) commitStorage(op *accountUpdate) { // // Note, commit may run concurrently across all the state objects. Do not assume // thread-safe access to the statedb. -func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, map[string][]byte, error) { - var al map[string][]byte - +func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) { // commit the account metadata changes op := &accountUpdate{ address: s.address, @@ -474,18 +478,12 @@ func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, map[string][] if len(op.storages) == 0 { // nothing changed, don't bother to commit the trie s.origin = s.data.Copy() - if s.trie != nil && !s.trie.IsVerkle() { - al = s.trie.AccessList() - } - return op, nil, al, nil + return op, nil, nil } root, nodes := s.trie.Commit(false) s.data.Root = root s.origin = s.data.Copy() - if !s.trie.IsVerkle() { - al = s.trie.AccessList() - } - return op, nodes, al, nil + return op, nodes, nil } // AddBalance adds amount to s's balance. diff --git a/core/state/state_witness.go b/core/state/state_witness.go deleted file mode 100644 index 2223952bc13b..000000000000 --- a/core/state/state_witness.go +++ /dev/null @@ -1,374 +0,0 @@ -package state - -import ( - "bytes" - "crypto/sha256" - "errors" - "fmt" - "sort" - "sync" - - "github.com/ethereum/go-ethereum/crypto" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethdb" - "github.com/ethereum/go-ethereum/rlp" -) - -// A Witness encompasses a block and all state necessary to compute the -// post-state root. -type Witness struct { - Block *types.Block - blockHashes map[uint64]common.Hash - codes map[common.Hash][]byte - root common.Hash - tries map[common.Hash]map[string][]byte - triesLock sync.Mutex -} - -// BlockHash returns the block hash corresponding to an ancestor block between 1-256 blocks old. -func (w *Witness) BlockHash(num uint64) common.Hash { - return w.blockHashes[num] -} - -// Root returns the post-state root of the witness if it has been computed or 0x00..0 if not. -func (w *Witness) Root() common.Hash { - return w.root -} - -// rlpWitness is the encoding structure for a Witness -type rlpWitness struct { - EncBlock []byte - Root common.Hash - Owners []common.Hash - TriesPaths [][]string - TriesNodes [][][]byte - BlockNums []uint64 - BlockHashes []common.Hash - Codes [][]byte -} - -func (e *rlpWitness) toWitness() (*Witness, error) { - res := NewWitness(e.Root) - if err := rlp.DecodeBytes(e.EncBlock, &res.Block); err != nil { - return nil, err - } - for _, code := range e.Codes { - codeHash := crypto.Keccak256Hash(code) - if _, ok := res.codes[codeHash]; ok { - return nil, errors.New("duplicate code in witness") - } - res.codes[codeHash] = code - } - for i, owner := range e.Owners { - trieNodes := make(map[string][]byte) - for j := 0; j < len(e.TriesPaths[i]); j++ { - trieNodes[e.TriesPaths[i][j]] = e.TriesNodes[i][j] - } - res.tries[owner] = trieNodes - } - for i, blockNum := range e.BlockNums { - res.blockHashes[blockNum] = e.BlockHashes[i] - } - return res, nil -} - -// DecodeWitnessRLP decodes a byte slice into a witness object. -func DecodeWitnessRLP(b []byte) (*Witness, error) { - var res rlpWitness - if err := rlp.DecodeBytes(b, &res); err != nil { - return nil, err - } - return res.toWitness() -} - -// EncodeRLP encodes a witness object into bytes. The Witness' state root is -// zeroed before the encoding. The encoding is not deterministic (the result -// can differ for the same Witness) -func (w *Witness) EncodeRLP() ([]byte, error) { - var encWit rlpWitness - var encBlock bytes.Buffer - if err := w.Block.EncodeRLPWithZeroRoot(&encBlock); err != nil { - return nil, err - } - encWit.EncBlock = encBlock.Bytes() - - for owner, trie := range w.tries { - encWit.Owners = append(encWit.Owners, owner) - var ownerPaths []string - var ownerNodes [][]byte - - for path, node := range trie { - ownerPaths = append(ownerPaths, path) - ownerNodes = append(ownerNodes, node) - } - encWit.TriesPaths = append(encWit.TriesPaths, ownerPaths) - encWit.TriesNodes = append(encWit.TriesNodes, ownerNodes) - } - - for _, code := range w.codes { - encWit.Codes = append(encWit.Codes, code) - } - - for blockNum, blockHash := range w.blockHashes { - encWit.BlockNums = append(encWit.BlockNums, blockNum) - encWit.BlockHashes = append(encWit.BlockHashes, blockHash) - } - res, err := rlp.EncodeToBytes(&encWit) - if err != nil { - return nil, err - } - return res, nil -} - -// addAccessList associates a map of RLP-encoded trie nodes keyed by path to -// an owner in the witness. the witness takes ownership of the passed map. It -// is safe to call this method concurrently. -func (w *Witness) addAccessList(owner common.Hash, newTrieNodes map[string][]byte) { - var trie map[string][]byte - - if len(newTrieNodes) == 0 { - return - } - w.triesLock.Lock() - defer w.triesLock.Unlock() - - trie, ok := w.tries[owner] - if !ok { - trie = make(map[string][]byte) - w.tries[owner] = trie - } - for path, node := range newTrieNodes { - trie[path] = node - } -} - -// AddBlockHash adds a block hash/number to the witness -func (w *Witness) AddBlockHash(hash common.Hash, num uint64) { - w.blockHashes[num] = hash -} - -// AddCode associates a hash with code in the Witness. -// The Witness takes ownership over the passed code slice. -func (w *Witness) AddCode(hash common.Hash, code []byte) { - if hash == types.EmptyCodeHash || hash == (common.Hash{}) || len(code) == 0 { - return - } - w.codes[hash] = code -} - -// Summary prints a human-readable summary containing the total size of the -// witness and the sizes of the underlying components -func (w *Witness) Summary() string { - b := new(bytes.Buffer) - xx, err := rlp.EncodeToBytes(w.Block) - if err != nil { - panic(err) - } - totBlock := len(xx) - - yy, _ := w.EncodeRLP() - - totWit := len(yy) - totCode := 0 - for _, c := range w.codes { - totCode += len(c) - } - totNodes := 0 - totPaths := 0 - nodePathCount := 0 - for _, ownerPaths := range w.tries { - for path, node := range ownerPaths { - nodePathCount++ - totNodes += len(node) - totPaths += len(path) - } - } - - fmt.Fprintf(b, "%4d hashes: %v\n", len(w.blockHashes), common.StorageSize(len(w.blockHashes)*32)) - fmt.Fprintf(b, "%4d owners: %v\n", len(w.tries), common.StorageSize(len(w.tries)*32)) - fmt.Fprintf(b, "%4d nodes: %v\n", nodePathCount, common.StorageSize(totNodes)) - fmt.Fprintf(b, "%4d paths: %v\n", nodePathCount, common.StorageSize(totPaths)) - fmt.Fprintf(b, "%4d codes: %v\n", len(w.codes), common.StorageSize(totCode)) - fmt.Fprintf(b, "%4d codeHashes: %v\n", len(w.codes), common.StorageSize(len(w.codes)*32)) - fmt.Fprintf(b, "block (%4d txs): %v\n", len(w.Block.Transactions()), common.StorageSize(totBlock)) - fmt.Fprintf(b, "Total size: %v\n ", common.StorageSize(totWit)) - return b.String() -} - -// Copy deep-copies the witness object. Witness.Block isn't deep-copied as it -// is never mutated by Witness -func (w *Witness) Copy() *Witness { - var res Witness - res.Block = w.Block - - for blockNr, blockHash := range w.blockHashes { - res.blockHashes[blockNr] = blockHash - } - for codeHash, code := range w.codes { - cpy := make([]byte, len(code)) - copy(cpy, code) - res.codes[codeHash] = cpy - } - res.root = w.root - for owner, owned := range w.tries { - res.tries[owner] = make(map[string][]byte) - for path, node := range owned { - cpy := make([]byte, len(node)) - copy(cpy, node) - res.tries[owner][path] = cpy - } - } - return &res -} - -// sortedWitness encodes returns an rlpWitness where hash-map items are sorted lexicographically by key -// in the encoder object to ensure that the encoded bytes are always the same for a given witness. -func (w *Witness) sortedWitness() *rlpWitness { - var ( - sortedCodeHashes []common.Hash - owners []common.Hash - ownersPaths [][]string - ownersNodes [][][]byte - blockNrs []uint64 - blockHashes []common.Hash - codeHashes []common.Hash - codes [][]byte - ) - for key := range w.codes { - sortedCodeHashes = append(sortedCodeHashes, key) - } - sort.Slice(sortedCodeHashes, func(i, j int) bool { - return bytes.Compare(sortedCodeHashes[i][:], sortedCodeHashes[j][:]) > 0 - }) - - // sort the list of owners - for owner := range w.tries { - owners = append(owners, owner) - } - sort.Slice(owners, func(i, j int) bool { - return bytes.Compare(owners[i][:], owners[j][:]) > 0 - }) - - // sort the trie nodes of each trie by path - for _, owner := range owners { - nodes := w.tries[owner] - var ownerPaths []string - for path := range nodes { - ownerPaths = append(ownerPaths, path) - } - sort.Strings(ownerPaths) - - var ownerNodes [][]byte - for _, path := range ownerPaths { - ownerNodes = append(ownerNodes, nodes[path]) - } - ownersPaths = append(ownersPaths, ownerPaths) - ownersNodes = append(ownersNodes, ownerNodes) - } - - for blockNr, blockHash := range w.blockHashes { - blockNrs = append(blockNrs, blockNr) - blockHashes = append(blockHashes, blockHash) - } - - for codeHash := range w.codes { - codeHashes = append(codeHashes, codeHash) - } - sort.Slice(codeHashes, func(i, j int) bool { - return bytes.Compare(codeHashes[i][:], codeHashes[j][:]) > 0 - }) - - for _, codeHash := range codeHashes { - codes = append(codes, w.codes[codeHash]) - } - - encBlock, _ := rlp.EncodeToBytes(w.Block) - return &rlpWitness{ - EncBlock: encBlock, - Root: common.Hash{}, - Owners: owners, - TriesPaths: ownersPaths, - TriesNodes: ownersNodes, - BlockNums: blockNrs, - BlockHashes: blockHashes, - Codes: codes, - } -} - -// PrettyPrint displays the contents of a witness object in a human-readable format to standard output. -func (w *Witness) PrettyPrint() string { - sorted := w.sortedWitness() - b := new(bytes.Buffer) - fmt.Fprintf(b, "block: %+v\n", w.Block) - fmt.Fprintf(b, "root: %x\n", sorted.Root) - fmt.Fprint(b, "owners:\n") - for i, owner := range sorted.Owners { - if owner == (common.Hash{}) { - fmt.Fprintf(b, "\troot:\n") - } else { - fmt.Fprintf(b, "\t%x:\n", owner) - } - ownerPaths := sorted.TriesPaths[i] - ownerNodes := sorted.TriesNodes[i] - for j, path := range ownerPaths { - fmt.Fprintf(b, "\t\t%x:%x\n", []byte(path), ownerNodes[j]) - } - } - fmt.Fprintf(b, "block hashes:\n") - for i, blockNum := range sorted.BlockNums { - blockHash := sorted.BlockHashes[i] - fmt.Fprintf(b, "\t%d:%x\n", blockNum, blockHash) - } - fmt.Fprintf(b, "codes:\n") - for _, code := range sorted.Codes { - hash := crypto.Keccak256Hash(code) - fmt.Fprintf(b, "\t%x:%x\n", hash, code) - } - return b.String() -} - -// Hash returns the sha256 hash of a Witness -func (w *Witness) Hash() common.Hash { - res, err := rlp.EncodeToBytes(w.sortedWitness()) - if err != nil { - panic(err) - } - - return sha256.Sum256(res[:]) -} - -// NewWitness returns a new Witness object. -func NewWitness(root common.Hash) *Witness { - return &Witness{ - Block: nil, - blockHashes: make(map[uint64]common.Hash), - codes: make(map[common.Hash][]byte), - root: root, - tries: make(map[common.Hash]map[string][]byte), - } -} - -// PopulateDB imports tries,codes and block hashes from the witness -// into the specified path-based backing db. -func (w *Witness) PopulateDB(db ethdb.Database) error { - batch := db.NewBatch() - for owner, nodes := range w.tries { - for path, node := range nodes { - if owner == (common.Hash{}) { - rawdb.WriteAccountTrieNode(batch, []byte(path), node) - } else { - rawdb.WriteStorageTrieNode(batch, owner, []byte(path), node) - } - } - } - for codeHash, code := range w.codes { - rawdb.WriteCode(batch, codeHash, code) - } - if err := batch.Write(); err != nil { - return err - } - return nil -} diff --git a/core/state/statedb.go b/core/state/statedb.go index dbb5b30b763e..ac82a8e3e3ab 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -31,6 +31,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -146,6 +147,9 @@ type StateDB struct { validRevisions []revision nextRevisionId int + // State witness if cross validation is needed + witness *stateless.Witness + // Measurements gathered during execution for debugging purposes AccountReads time.Duration AccountHashes time.Duration @@ -163,7 +167,6 @@ type StateDB struct { StorageUpdated atomic.Int64 AccountDeleted int StorageDeleted atomic.Int64 - witness *Witness } // New creates a new state from a given trie. @@ -201,14 +204,19 @@ func (s *StateDB) SetLogger(l *tracing.Hooks) { // StartPrefetcher initializes a new trie prefetcher to pull in nodes from the // state trie concurrently while the state is mutated so that when we reach the // commit phase, most of the needed data is already hot. -func (s *StateDB) StartPrefetcher(namespace string, noreads bool) { +func (s *StateDB) StartPrefetcher(namespace string, witness *stateless.Witness) { + // Terminate any previously running prefetcher if s.prefetcher != nil { s.prefetcher.terminate(false) s.prefetcher.report() s.prefetcher = nil } + // Enable witness collection if requested + s.witness = witness + + // If snapshots are enabled, start prefethers explicitly if s.snap != nil { - s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace, noreads) + s.prefetcher = newTriePrefetcher(s.db, s.originalRoot, namespace, witness == nil) // With the switch to the Proof-of-Stake consensus algorithm, block production // rewards are now handled at the consensus layer. Consequently, a block may @@ -683,7 +691,7 @@ func (s *StateDB) Copy() *StateDB { hasher: crypto.NewKeccakState(), originalRoot: s.originalRoot, stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)), - stateObjectsDestruct: maps.Clone(s.stateObjectsDestruct), + stateObjectsDestruct: make(map[common.Address]*stateObject, len(s.stateObjectsDestruct)), mutations: make(map[common.Address]*mutation, len(s.mutations)), dbErr: s.dbErr, refund: s.refund, @@ -710,6 +718,10 @@ func (s *StateDB) Copy() *StateDB { for addr, obj := range s.stateObjects { state.stateObjects[addr] = obj.deepCopy(state) } + // Deep copy destructed state objects. + for addr, obj := range s.stateObjectsDestruct { + state.stateObjectsDestruct[addr] = obj.deepCopy(state) + } // Deep copy the object state markers. for addr, op := range s.mutations { state.mutations[addr] = op.copy() @@ -758,12 +770,6 @@ func (s *StateDB) RevertToSnapshot(revid int) { s.validRevisions = s.validRevisions[:idx] } -// EnableWitnessRecording configures the StateDB to build a stateless block -// witness this must becalled before starting prefetchers or applying state changes -func (s *StateDB) EnableWitnessBuilding() { - s.witness = NewWitness(s.originalRoot) -} - // GetRefund returns the current value of the refund counter. func (s *StateDB) GetRefund() uint64 { return s.refund @@ -817,46 +823,6 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) { s.clearJournalAndRefund() } -// collectNonCommittedTrieAccessLists is called if witness building is enabled. -// It collects witness access lists for accounts that read from storage, and -// whose tries are not going to be hashed/committed (accounts with non-mutated -// storage and self-destructed accounts). -func (s *StateDB) collectNonCommittedTrieAccessLists() { - collectAccount := func(obj *stateObject) { - if obj.Root() == types.EmptyRootHash { - // TODO: unsure if this explicit check is needed - return - } - tr := obj.getPrefetchedTrie() - if tr == nil { - if obj.trie == nil { - // object storage was never read from - return - } - // either the snapshot is not enabled or the object was not present - tr = obj.trie - } - - al := tr.AccessList() - if al == nil { - panic("impossible case: storage trie is non-empty and known to have been read from but a nil access list was returned") - } - if len(al) == 0 { - panic("impossible case: storage trie is non-empty and known to have been read from but a zero-length access list was returned") - } - s.witness.addAccessList(obj.addrHash, al) - } - for _, obj := range s.stateObjectsDestruct { - collectAccount(obj) - } - for _, obj := range s.stateObjects { - if _, ok := s.mutations[obj.address]; ok { - continue - } - collectAccount(obj) - } -} - // IntermediateRoot computes the current root hash of the state trie. // It is called in between transactions to get the root hash that // goes into transaction receipts. @@ -873,7 +839,6 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { s.prefetcher = nil // Pre-byzantium, unset any used up prefetcher }() } - // Process all storage updates concurrently. The state object update root // method will internally call a blocking trie fetch from the prefetcher, // so there's no need to explicitly wait for the prefetchers to finish. @@ -896,14 +861,45 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { obj := s.stateObjects[addr] // closure for the task runner below workers.Go(func() error { obj.updateRoot() + + // If witness building is enabled and the state object has a trie, + // gather the witnesses for its specific storage trie + if s.witness != nil && obj.trie != nil { + s.witness.AddState(obj.trie.Witness()) + } return nil }) } + // If witness building is enabled, gather all the read-only accesses if s.witness != nil { - // if witness building is enabled, collect storage access lists from - // tries that will not be committed: accounts with non-mutated storage - // and self-destructed accounts. - s.collectNonCommittedTrieAccessLists() + // Pull in anything that has been accessed before destruction + for _, obj := range s.stateObjectsDestruct { + // Skip any objects that haven't touched their storage + if len(obj.originStorage) == 0 { + continue + } + if trie := obj.getPrefetchedTrie(); trie != nil { + s.witness.AddState(trie.Witness()) + } else if obj.trie != nil { + s.witness.AddState(obj.trie.Witness()) + } + } + // Pull in only-read and non-destructed trie witnesses + for _, obj := range s.stateObjects { + // Skip any objects that have been updated + if _, ok := s.mutations[obj.address]; ok { + continue + } + // Skip any objects that haven't touched their storage + if len(obj.originStorage) == 0 { + continue + } + if trie := obj.getPrefetchedTrie(); trie != nil { + s.witness.AddState(trie.Witness()) + } else if obj.trie != nil { + s.witness.AddState(obj.trie.Witness()) + } + } } workers.Wait() s.StorageUpdates += time.Since(start) @@ -960,7 +956,13 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // Track the amount of time wasted on hashing the account trie defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now()) - return s.trie.Hash() + hash := s.trie.Hash() + + // If witness building is enabled, gather the account trie witness + if s.witness != nil { + s.witness.AddState(s.trie.Witness()) + } + return hash } // SetTxContext sets the current transaction hash and index which are @@ -1116,8 +1118,9 @@ func (s *StateDB) handleDestruction() (map[common.Hash]*accountDelete, []*trieno buf = crypto.NewKeccakState() deletes = make(map[common.Hash]*accountDelete) ) - for addr, obj := range s.stateObjectsDestruct { - prev := obj.origin + for addr, prevObj := range s.stateObjectsDestruct { + prev := prevObj.origin + // The account was non-existent, and it's marked as destructed in the scope // of block. It can be either case (a) or (b) and will be interpreted as // null->null state transition. @@ -1225,7 +1228,6 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) { root common.Hash workers errgroup.Group ) - // Schedule the account trie first since that will be the biggest, so give // it the most time to crunch. // @@ -1236,25 +1238,8 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) { // Obviously it's not an end of the world issue, just something the original // code didn't anticipate for. workers.Go(func() error { - var ( - newroot common.Hash - set *trienode.NodeSet - al map[string][]byte - ) // Write the account trie changes, measuring the amount of wasted time - if s.witness != nil { - newroot, set = s.trie.Commit(true) - al = s.trie.AccessList() - if al == nil { - panic("this should only happen if starting with completely empty state") - } - if len(al) == 0 { - panic("blocks without state changes not possible") - } - s.witness.addAccessList(common.Hash{}, al) - } else { - newroot, set = s.trie.Commit(true) - } + newroot, set := s.trie.Commit(true) root = newroot if err := merge(set); err != nil { @@ -1282,7 +1267,7 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) { // Run the storage updates concurrently to one another workers.Go(func() error { // Write any storage changes in the state object to its storage trie - update, set, al, err := obj.commit() + update, set, err := obj.commit() if err != nil { return err } @@ -1290,12 +1275,9 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) { return err } lock.Lock() - defer lock.Unlock() updates[obj.addrHash] = update s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime - if s.witness != nil && len(al) > 0 { - s.witness.addAccessList(obj.addrHash, al) - } + lock.Unlock() return nil }) } @@ -1372,12 +1354,6 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool) (*stateU return ret, err } -// Witness returns a block witness object being constructed or nil if the -// StateDB instance is not configured to record stateless witnesses. -func (s *StateDB) Witness() *Witness { - return s.witness -} - // Commit writes the state mutations into the configured data stores. // // Once the state is committed, tries cached in stateDB (including account @@ -1496,3 +1472,8 @@ func (s *StateDB) markUpdate(addr common.Address) { func (s *StateDB) PointCache() *utils.PointCache { return s.db.PointCache() } + +// Witness retrieves the current state witness being collected. +func (s *StateDB) Witness() *stateless.Witness { + return s.witness +} diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 4441d8a56767..31405fa078bb 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -19,7 +19,6 @@ package core import ( "sync/atomic" - "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" @@ -31,16 +30,14 @@ import ( // data from disk before the main block processor start executing. type statePrefetcher struct { config *params.ChainConfig // Chain configuration options - bc *BlockChain // Canonical block chain - engine consensus.Engine // Consensus engine used for block rewards + chain *HeaderChain // Canonical block chain } // newStatePrefetcher initialises a new statePrefetcher. -func newStatePrefetcher(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *statePrefetcher { +func newStatePrefetcher(config *params.ChainConfig, chain *HeaderChain) *statePrefetcher { return &statePrefetcher{ config: config, - bc: bc, - engine: engine, + chain: chain, } } @@ -51,7 +48,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c var ( header = block.Header() gaspool = new(GasPool).AddGas(block.GasLimit()) - blockContext = NewEVMBlockContext(header, p.bc, nil, nil) + blockContext = NewEVMBlockContext(header, p.chain, nil) evm = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg) signer = types.MakeSigner(p.config, header.Number, header.Time) ) diff --git a/core/state_processor.go b/core/state_processor.go index db6937b3a2c4..c21f644f9851 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -22,7 +22,6 @@ import ( "math/big" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" @@ -36,45 +35,18 @@ import ( // // StateProcessor implements Processor. type StateProcessor struct { - config *params.ChainConfig // Chain configuration options - bc *BlockChain // Canonical block chain - engine consensus.Engine // Consensus engine used for block rewards - statelessChainCtx ChainContext + config *params.ChainConfig // Chain configuration options + chain *HeaderChain // Canonical header chain } -// NewStateProcessor initialises a new StateProcessor. If the provided -// Blockchain is nil, stateless execution mode is enabled. -func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *StateProcessor { - if bc != nil { - return &StateProcessor{ - config: config, - bc: bc, - engine: engine, - } - } else { - return &StateProcessor{ - config: config, - engine: engine, - statelessChainCtx: &statelessChainContext{engine}, - bc: &BlockChain{ - chainConfig: config, - engine: engine, - }, - } +// NewStateProcessor initialises a new StateProcessor. +func NewStateProcessor(config *params.ChainConfig, chain *HeaderChain) *StateProcessor { + return &StateProcessor{ + config: config, + chain: chain, } } -type statelessChainContext struct { - engine consensus.Engine -} - -func (s *statelessChainContext) Engine() consensus.Engine { - return s.engine -} -func (s *statelessChainContext) GetHeader(hash common.Hash, number uint64) *types.Header { - panic("not implemented") -} - // Process processes the state changes according to the Ethereum rules by running // the transaction messages using the statedb and applying any rewards to both // the processor (coinbase) and any included uncles. @@ -82,7 +54,7 @@ func (s *statelessChainContext) GetHeader(hash common.Hash, number uint64) *type // Process returns the receipts and logs accumulated during the process and // returns the amount of gas that was used in the process. If any of the // transactions failed to execute due to insufficient gas it will return an error. -func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config, witness *state.Witness) (types.Receipts, []*types.Log, uint64, error) { +func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) { var ( receipts types.Receipts usedGas = new(uint64) @@ -101,7 +73,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg context vm.BlockContext signer = types.MakeSigner(p.config, header.Number, header.Time) ) - context = NewEVMBlockContext(header, p.bc, nil, witness) + context = NewEVMBlockContext(header, p.chain, nil) vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg) if beaconRoot := block.BeaconRoot(); beaconRoot != nil { ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) @@ -127,7 +99,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg return nil, nil, 0, errors.New("withdrawals before shanghai") } // Finalize the block, applying any consensus engine specific extras (e.g. block rewards) - p.engine.Finalize(p.bc, header, statedb, block.Body()) + p.chain.engine.Finalize(p.chain, header, statedb, block.Body()) return receipts, allLogs, *usedGas, nil } @@ -203,7 +175,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo return nil, err } // Create a new context to be used in the EVM environment - blockContext := NewEVMBlockContext(header, bc, author, statedb.Witness()) + blockContext := NewEVMBlockContext(header, bc, author) txContext := NewEVMTxContext(msg) vmenv := vm.NewEVM(blockContext, txContext, statedb, config, cfg) return ApplyTransactionWithEVM(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) diff --git a/core/stateless.go b/core/stateless.go new file mode 100644 index 000000000000..4c7e6f31027f --- /dev/null +++ b/core/stateless.go @@ -0,0 +1,73 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package core + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/lru" + "github.com/ethereum/go-ethereum/consensus/beacon" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/stateless" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" + "github.com/ethereum/go-ethereum/triedb" +) + +// ExecuteStateless runs a stateless execution based on a witness, verifies +// everything it can locally and returns the two computed fields that need the +// other side to explicitly check. +// +// This method is a bit of a sore thumb here, but: +// - It cannot be placed in core/stateless, because state.New prodces a circular dep +// - It cannot be placed outside of core, because it needs to construct a dud headerchain +// +// TODO(karalabe): Would be nice to resolve both issues above somehow and move it. +func ExecuteStateless(config *params.ChainConfig, witness *stateless.Witness) (common.Hash, common.Hash, error) { + // Create and populate the state database to serve as the stateless backend + memdb := witness.MakeHashDB() + + db, err := state.New(witness.Root(), state.NewDatabaseWithConfig(memdb, triedb.HashDefaults), nil) + if err != nil { + return common.Hash{}, common.Hash{}, err + } + // Create a blockchain that is idle, but can be used to access headers through + chain := &HeaderChain{ + config: config, + chainDb: memdb, + headerCache: lru.NewCache[common.Hash, *types.Header](256), + engine: beacon.New(ethash.NewFaker()), + } + processor := NewStateProcessor(config, chain) + validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block + + // Run the stateless blocks processing and self-validate certain fields + receipts, _, usedGas, err := processor.Process(witness.Block, db, vm.Config{}) + if err != nil { + return common.Hash{}, common.Hash{}, err + } + if err = validator.ValidateState(witness.Block, db, receipts, usedGas, true); err != nil { + return common.Hash{}, common.Hash{}, err + } + // Almost everything validated, but receipt and state root needs to be returned + receiptRoot := types.DeriveSha(receipts, trie.NewStackTrie(nil)) + stateRoot := db.IntermediateRoot(config.IsEIP158(witness.Block.Number())) + + return receiptRoot, stateRoot, nil +} diff --git a/core/stateless/database.go b/core/stateless/database.go new file mode 100644 index 000000000000..135da621934c --- /dev/null +++ b/core/stateless/database.go @@ -0,0 +1,60 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package stateless + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethdb" +) + +// MakeHashDB imports tries, codes and block hashes from a witness into a new +// hash-based memory db. We could eventually rewrite this into a pathdb, but +// simple is better for now. +func (w *Witness) MakeHashDB() ethdb.Database { + var ( + memdb = rawdb.NewMemoryDatabase() + hasher = crypto.NewKeccakState() + hash = make([]byte, 32) + ) + // Inject all the "block hashes" (i.e. headers) into the ephemeral database + for _, header := range w.Headers { + rawdb.WriteHeader(memdb, header) + } + // Inject all the bytecodes into the ephemeral database + for code := range w.Codes { + blob := []byte(code) + + hasher.Reset() + hasher.Write(blob) + hasher.Read(hash) + + rawdb.WriteCode(memdb, common.BytesToHash(hash), blob) + } + // Inject all the MPT trie nodes into the ephemeral database + for node := range w.State { + blob := []byte(node) + + hasher.Reset() + hasher.Write(blob) + hasher.Read(hash) + + rawdb.WriteLegacyTrieNode(memdb, common.BytesToHash(hash), blob) + } + return memdb +} diff --git a/core/stateless/encoding.go b/core/stateless/encoding.go new file mode 100644 index 000000000000..2b7245d377e3 --- /dev/null +++ b/core/stateless/encoding.go @@ -0,0 +1,129 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package stateless + +import ( + "bytes" + "errors" + "fmt" + "io" + "slices" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +//go:generate go run github.com/fjl/gencodec -type extWitness -field-override extWitnessMarshalling -out gen_encoding_json.go + +// toExtWitness converts our internal witness representation to the consensus one. +func (w *Witness) toExtWitness() *extWitness { + ext := &extWitness{ + Block: w.Block, + Headers: w.Headers, + } + ext.Codes = make([][]byte, 0, len(w.Codes)) + for code := range w.Codes { + ext.Codes = append(ext.Codes, []byte(code)) + } + slices.SortFunc(ext.Codes, bytes.Compare) + + ext.State = make([][]byte, 0, len(w.State)) + for node := range w.State { + ext.State = append(ext.State, []byte(node)) + } + slices.SortFunc(ext.State, bytes.Compare) + return ext +} + +// fromExtWitness converts the consensus witness format into our internal one. +func (w *Witness) fromExtWitness(ext *extWitness) error { + w.Block, w.Headers = ext.Block, ext.Headers + + w.Codes = make(map[string]struct{}, len(ext.Codes)) + for _, code := range ext.Codes { + w.Codes[string(code)] = struct{}{} + } + w.State = make(map[string]struct{}, len(ext.State)) + for _, node := range ext.State { + w.State[string(node)] = struct{}{} + } + return w.sanitize() +} + +// MarshalJSON marshals a witness as JSON. +func (w *Witness) MarshalJSON() ([]byte, error) { + return w.toExtWitness().MarshalJSON() +} + +// EncodeRLP serializes a witness as RLP. +func (w *Witness) EncodeRLP(wr io.Writer) error { + return rlp.Encode(wr, w.toExtWitness()) +} + +// UnmarshalJSON unmarshals from JSON. +func (w *Witness) UnmarshalJSON(input []byte) error { + var ext extWitness + if err := ext.UnmarshalJSON(input); err != nil { + return err + } + return w.fromExtWitness(&ext) +} + +// DecodeRLP decodes a witness from RLP. +func (w *Witness) DecodeRLP(s *rlp.Stream) error { + var ext extWitness + if err := s.Decode(&ext); err != nil { + return err + } + return w.fromExtWitness(&ext) +} + +// sanitize checks for some mandatory fields in the witness after decoding so +// the rest of the code can assume invariants and doesn't have to deal with +// corrupted data. +func (w *Witness) sanitize() error { + // Verify that the "parent" header (i.e. index 0) is available, and is the + // true parent of the block-to-be executed, since we use that to link the + // current block to the pre-state. + if len(w.Headers) == 0 { + return errors.New("parent header (for pre-root hash) missing") + } + for i, header := range w.Headers { + if header == nil { + return fmt.Errorf("witness header nil at position %d", i) + } + } + if w.Headers[0].Hash() != w.Block.ParentHash() { + return fmt.Errorf("parent hash different: witness %v, block parent %v", w.Headers[0].Hash(), w.Block.ParentHash()) + } + return nil +} + +// extWitness is a witness RLP encoding for transferring across clients. +type extWitness struct { + Block *types.Block `json:"block" gencodec:"required"` + Headers []*types.Header `json:"headers" gencodec:"required"` + Codes [][]byte `json:"codes"` + State [][]byte `json:"state"` +} + +// extWitnessMarshalling defines the hex marshalling types for a witness. +type extWitnessMarshalling struct { + Codes []hexutil.Bytes + State []hexutil.Bytes +} diff --git a/core/stateless/gen_encoding_json.go b/core/stateless/gen_encoding_json.go new file mode 100644 index 000000000000..1d0497976e34 --- /dev/null +++ b/core/stateless/gen_encoding_json.go @@ -0,0 +1,74 @@ +// Code generated by github.com/fjl/gencodec. DO NOT EDIT. + +package stateless + +import ( + "encoding/json" + "errors" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +var _ = (*extWitnessMarshalling)(nil) + +// MarshalJSON marshals as JSON. +func (e extWitness) MarshalJSON() ([]byte, error) { + type extWitness struct { + Block *types.Block `json:"block" gencodec:"required"` + Headers []*types.Header `json:"headers" gencodec:"required"` + Codes []hexutil.Bytes `json:"codes"` + State []hexutil.Bytes `json:"state"` + } + var enc extWitness + enc.Block = e.Block + enc.Headers = e.Headers + if e.Codes != nil { + enc.Codes = make([]hexutil.Bytes, len(e.Codes)) + for k, v := range e.Codes { + enc.Codes[k] = v + } + } + if e.State != nil { + enc.State = make([]hexutil.Bytes, len(e.State)) + for k, v := range e.State { + enc.State[k] = v + } + } + return json.Marshal(&enc) +} + +// UnmarshalJSON unmarshals from JSON. +func (e *extWitness) UnmarshalJSON(input []byte) error { + type extWitness struct { + Block *types.Block `json:"block" gencodec:"required"` + Headers []*types.Header `json:"headers" gencodec:"required"` + Codes []hexutil.Bytes `json:"codes"` + State []hexutil.Bytes `json:"state"` + } + var dec extWitness + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + if dec.Block == nil { + return errors.New("missing required field 'block' for extWitness") + } + e.Block = dec.Block + if dec.Headers == nil { + return errors.New("missing required field 'headers' for extWitness") + } + e.Headers = dec.Headers + if dec.Codes != nil { + e.Codes = make([][]byte, len(dec.Codes)) + for k, v := range dec.Codes { + e.Codes[k] = v + } + } + if dec.State != nil { + e.State = make([][]byte, len(dec.State)) + for k, v := range dec.State { + e.State[k] = v + } + } + return nil +} diff --git a/core/stateless/witness.go b/core/stateless/witness.go new file mode 100644 index 000000000000..7622c5eb610d --- /dev/null +++ b/core/stateless/witness.go @@ -0,0 +1,159 @@ +// Copyright 2024 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package stateless + +import ( + "bytes" + "errors" + "fmt" + "maps" + "slices" + "sync" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +// HeaderReader is an interface to pull in headers in place of block hashes for +// the witness. +type HeaderReader interface { + // GetHeader retrieves a block header from the database by hash and number, + GetHeader(hash common.Hash, number uint64) *types.Header +} + +// Witness encompasses a block, state and any other chain data required to apply +// a set of transactions and derive a post state/receipt root. +type Witness struct { + Block *types.Block // Current block with rootHash and receiptHash zeroed out + Headers []*types.Header // Past headers in reverse order (0=parent, 1=parent's-parent, etc). First *must* be set. + Codes map[string]struct{} // Set of bytecodes ran or accessed + State map[string]struct{} // Set of MPT state trie nodes (account and storage together) + + chain HeaderReader // Chain reader to convert block hash ops to header proofs + lock sync.Mutex // Lock to allow concurrent state insertions +} + +// NewWitness creates an empty witness ready for population. +func NewWitness(chain HeaderReader, block *types.Block) (*Witness, error) { + // Zero out the result fields to avoid accidentally sending them to the verifier + header := block.Header() + header.Root = common.Hash{} + header.ReceiptHash = common.Hash{} + + // Retrieve the parent header, which will *always* be included to act as a + // trustless pre-root hash container + parent := chain.GetHeader(block.ParentHash(), block.NumberU64()-1) + if parent == nil { + return nil, errors.New("failed to retrieve parent header") + } + // Create the wtness with a reconstructed gutted out block + return &Witness{ + Block: types.NewBlockWithHeader(header).WithBody(*block.Body()), + Codes: make(map[string]struct{}), + State: make(map[string]struct{}), + Headers: []*types.Header{parent}, + chain: chain, + }, nil +} + +// AddBlockHash adds a "blockhash" to the witness with the designated offset from +// chain head. Under the hood, this method actually pulls in enough headers from +// the chain to cover the block being added. +func (w *Witness) AddBlockHash(number uint64) { + // Keep pulling in headers until this hash is populated + for int(w.Block.NumberU64()-number) > len(w.Headers) { + tail := w.Block.Header() + if len(w.Headers) > 0 { + tail = w.Headers[len(w.Headers)-1] + } + w.Headers = append(w.Headers, w.chain.GetHeader(tail.ParentHash, tail.Number.Uint64()-1)) + } +} + +// AddCode adds a bytecode blob to the witness. +func (w *Witness) AddCode(code []byte) { + if len(code) == 0 { + return + } + w.Codes[string(code)] = struct{}{} +} + +// AddState inserts a batch of MPT trie nodes into the witness. +func (w *Witness) AddState(nodes map[string]struct{}) { + if len(nodes) == 0 { + return + } + w.lock.Lock() + defer w.lock.Unlock() + + for node := range nodes { + w.State[node] = struct{}{} + } +} + +// Copy deep-copies the witness object. Witness.Block isn't deep-copied as it +// is never mutated by Witness +func (w *Witness) Copy() *Witness { + return &Witness{ + Block: w.Block, + Headers: slices.Clone(w.Headers), + Codes: maps.Clone(w.Codes), + State: maps.Clone(w.State), + } +} + +// String prints a human-readable summary containing the total size of the +// witness and the sizes of the underlying components +func (w *Witness) String() string { + blob, _ := rlp.EncodeToBytes(w) + bytesTotal := len(blob) + + blob, _ = rlp.EncodeToBytes(w.Block) + bytesBlock := len(blob) + + bytesHeaders := 0 + for _, header := range w.Headers { + blob, _ = rlp.EncodeToBytes(header) + bytesHeaders += len(blob) + } + bytesCodes := 0 + for code := range w.Codes { + bytesCodes += len(code) + } + bytesState := 0 + for node := range w.State { + bytesState += len(node) + } + buf := new(bytes.Buffer) + + fmt.Fprintf(buf, "Witness #%d: %v\n", w.Block.Number(), common.StorageSize(bytesTotal)) + fmt.Fprintf(buf, " block (%4d txs): %10v\n", len(w.Block.Transactions()), common.StorageSize(bytesBlock)) + fmt.Fprintf(buf, "%4d headers: %10v\n", len(w.Headers), common.StorageSize(bytesHeaders)) + fmt.Fprintf(buf, "%4d trie nodes: %10v\n", len(w.State), common.StorageSize(bytesState)) + fmt.Fprintf(buf, "%4d codes: %10v\n", len(w.Codes), common.StorageSize(bytesCodes)) + + return buf.String() +} + +// Root returns the pre-state root from the first header. +// +// Note, this method will panic in case of a bad witness (but RLP decoding will +// sanitize it and fail before that). +func (w *Witness) Root() common.Hash { + return w.Headers[0].Root +} diff --git a/core/types.go b/core/types.go index 0eaeb3452970..dc13de52ce2b 100644 --- a/core/types.go +++ b/core/types.go @@ -20,8 +20,8 @@ import ( "sync/atomic" "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" ) @@ -35,7 +35,10 @@ type Validator interface { // ValidateState validates the given statedb and optionally the receipts and // gas used. - ValidateState(block *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64, rootCheck bool) (common.Hash, error) + ValidateState(block *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64, stateless bool) error + + // ValidateWitness cross validates a block execution with stateless remote clients. + ValidateWitness(witness *stateless.Witness, receiptRoot common.Hash, stateRoot common.Hash) error } // Prefetcher is an interface for pre-caching transaction signatures and state. @@ -51,5 +54,5 @@ type Processor interface { // Process processes the state changes according to the Ethereum rules by running // the transaction messages using the statedb and applying any rewards to both // the processor (coinbase) and any included uncles. - Process(block *types.Block, statedb *state.StateDB, cfg vm.Config, witness *state.Witness) (types.Receipts, []*types.Log, uint64, error) + Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) } diff --git a/core/types/block.go b/core/types/block.go index 31931f124d7c..4857cd6e50c8 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -174,18 +174,6 @@ type Body struct { Withdrawals []*Withdrawal `rlp:"optional"` } -// EncodeRLPWithZeroRoot encodes a block (with header state root set to 0x00...0) to RLP -func (b *Block) EncodeRLPWithZeroRoot(w io.Writer) error { - old := b.header.Root - b.header.Root = common.Hash{} - err := b.EncodeRLP(w) - b.header.Root = old - if err != nil { - return err - } - return nil -} - // Block represents an Ethereum block. // // Note the Block type tries to be 'immutable', and contains certain caches that rely diff --git a/core/vm/evm.go b/core/vm/evm.go index ee91d8026ac7..1944189b5da2 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -231,10 +231,8 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. code := evm.StateDB.GetCode(addr) - codeCopy := make([]byte, len(code)) - copy(codeCopy[:], code[:]) if witness := evm.StateDB.Witness(); witness != nil { - witness.AddCode(evm.StateDB.GetCodeHash(addr), codeCopy) + witness.AddCode(code) } if len(code) == 0 { ret, err = nil, nil // gas is unchanged @@ -304,7 +302,7 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(caller.Address()), value, gas) if witness := evm.StateDB.Witness(); witness != nil { - witness.AddCode(evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + witness.AddCode(evm.StateDB.GetCode(addrCopy)) } contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) ret, err = evm.interpreter.Run(contract, input, false) @@ -354,7 +352,7 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by // Initialise a new contract and make initialise the delegate values contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() if witness := evm.StateDB.Witness(); witness != nil { - witness.AddCode(evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + witness.AddCode(evm.StateDB.GetCode(addrCopy)) } contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) ret, err = evm.interpreter.Run(contract, input, false) @@ -412,7 +410,7 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte // The contract is a scoped environment for this execution context only. contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas) if witness := evm.StateDB.Witness(); witness != nil { - witness.AddCode(evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) + witness.AddCode(evm.StateDB.GetCode(addrCopy)) } contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy)) // When an error was returned by the EVM or when setting the creation code diff --git a/core/vm/instructions.go b/core/vm/instructions.go index f1e8222144f0..9ec454464363 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -342,9 +342,7 @@ func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) slot := scope.Stack.peek() address := slot.Bytes20() if witness := interpreter.evm.StateDB.Witness(); witness != nil { - code := interpreter.evm.StateDB.GetCode(address) - codeHash := interpreter.evm.StateDB.GetCodeHash(address) - witness.AddCode(codeHash, code) + witness.AddCode(interpreter.evm.StateDB.GetCode(address)) } slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20()))) return nil, nil @@ -384,12 +382,11 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) uint64CodeOffset = math.MaxUint64 } addr := common.Address(a.Bytes20()) + code := interpreter.evm.StateDB.GetCode(addr) if witness := interpreter.evm.StateDB.Witness(); witness != nil { - hash := interpreter.evm.StateDB.GetCodeHash(addr) - code := interpreter.evm.StateDB.GetCode(addr) - witness.AddCode(hash, code) + witness.AddCode(code) } - codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) + codeCopy := getData(code, uint64CodeOffset, length.Uint64()) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) return nil, nil @@ -427,11 +424,6 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) if interpreter.evm.StateDB.Empty(address) { slot.Clear() } else { - if witness := interpreter.evm.StateDB.Witness(); witness != nil { - hash := interpreter.evm.StateDB.GetCodeHash(address) - code := interpreter.evm.StateDB.GetCode(address) - witness.AddCode(hash, code) - } slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes()) } return nil, nil @@ -461,7 +453,7 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ( if num64 >= lower && num64 < upper { res := interpreter.evm.Context.GetHash(num64) if witness := interpreter.evm.StateDB.Witness(); witness != nil { - witness.AddBlockHash(res, num64) + witness.AddBlockHash(num64) } num.SetBytes(res[:]) } else { diff --git a/core/vm/interface.go b/core/vm/interface.go index 8e840355df32..5f426435650d 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -19,9 +19,8 @@ package vm import ( "math/big" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -90,7 +89,7 @@ type StateDB interface { AddLog(*types.Log) AddPreimage(common.Hash, []byte) - Witness() *state.Witness + Witness() *stateless.Witness } // CallContext provides a basic interface for the EVM calling conventions. The EVM diff --git a/eth/api_backend.go b/eth/api_backend.go index f949d7bb8f90..8a9898b956f3 100644 --- a/eth/api_backend.go +++ b/eth/api_backend.go @@ -258,7 +258,7 @@ func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *st if blockCtx != nil { context = *blockCtx } else { - context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil, nil) + context = core.NewEVMBlockContext(header, b.eth.BlockChain(), nil) } return vm.NewEVM(context, txContext, state, b.ChainConfig(), *vmConfig) } diff --git a/eth/api_debug.go b/eth/api_debug.go index b95c2189264d..d5e4dda1401c 100644 --- a/eth/api_debug.go +++ b/eth/api_debug.go @@ -22,8 +22,6 @@ import ( "fmt" "time" - "github.com/ethereum/go-ethereum/cmd/utils/stateless" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/core/rawdb" @@ -445,17 +443,3 @@ func (api *DebugAPI) GetTrieFlushInterval() (string, error) { } return api.eth.blockchain.GetTrieFlushInterval().String(), nil } - -// BuildStatelessProof executes a block, collecting the accessed pre-state into -// a Witness. The RLP-encoded witness is returned. -func (api *DebugAPI) BuildStatelessProof(numOrHash rpc.BlockNumberOrHash) ([]byte, error) { - var blockHash common.Hash - if numOrHash.BlockNumber != nil { - number := numOrHash.BlockNumber.Int64() - block := api.eth.blockchain.GetBlockByNumber(uint64(number)) - blockHash = block.Hash() - } else { - blockHash = *numOrHash.BlockHash - } - return stateless.BuildStatelessProof(blockHash, api.eth.blockchain) -} diff --git a/eth/gasestimator/gasestimator.go b/eth/gasestimator/gasestimator.go index 6836ef2b3ade..ac3b59e97e8b 100644 --- a/eth/gasestimator/gasestimator.go +++ b/eth/gasestimator/gasestimator.go @@ -218,7 +218,7 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio // Assemble the call and the call context var ( msgContext = core.NewEVMTxContext(call) - evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil, nil) + evmContext = core.NewEVMBlockContext(opts.Header, opts.Chain, nil) dirtyState = opts.State.Copy() evm = vm.NewEVM(evmContext, msgContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true}) diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 4cc9a044c064..372c76f49692 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -146,7 +146,7 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u if current = eth.blockchain.GetBlockByNumber(next); current == nil { return nil, nil, fmt.Errorf("block #%d not found", next) } - _, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{}, nil) + _, _, _, err := eth.blockchain.Processor().Process(current, statedb, vm.Config{}) if err != nil { return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err) } @@ -235,7 +235,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, } // Insert parent beacon block root in the state as per EIP-4788. if beaconRoot := block.BeaconRoot(); beaconRoot != nil { - context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil, nil) + context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil) vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, eth.blockchain.Config(), vm.Config{}) core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) } @@ -248,7 +248,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, // Assemble the transaction call message and return if the requested offset msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil, nil) + context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil) if idx == txIndex { return tx, context, statedb, release, nil } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 336036329965..51b55ffdbb1b 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -266,7 +266,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed for task := range taskCh { var ( signer = types.MakeSigner(api.backend.ChainConfig(), task.block.Number(), task.block.Time()) - blockCtx = core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil, nil) + blockCtx = core.NewEVMBlockContext(task.block.Header(), api.chainContext(ctx), nil) ) // Trace all the transactions contained within for i, tx := range task.block.Transactions() { @@ -378,7 +378,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed // Insert block's parent beacon block root in the state // as per EIP-4788. if beaconRoot := next.BeaconRoot(); beaconRoot != nil { - context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil, nil) + context := core.NewEVMBlockContext(next.Header(), api.chainContext(ctx), nil) vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{}) core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) } @@ -527,7 +527,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config roots []common.Hash signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) chainConfig = api.backend.ChainConfig() - vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil) + vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) deleteEmptyObjects = chainConfig.IsEIP158(block.Number()) ) if beaconRoot := block.BeaconRoot(); beaconRoot != nil { @@ -605,7 +605,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac var ( txs = block.Transactions() blockHash = block.Hash() - blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil) + blockCtx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) results = make([]*txTraceResult, len(txs)) ) @@ -665,7 +665,7 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat // as the GetHash function of BlockContext is not safe for // concurrent use. // See: https://github.com/ethereum/go-ethereum/issues/29114 - blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil) + blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) res, err := api.traceTx(ctx, txs[task.index], msg, txctx, blockCtx, task.statedb, config) if err != nil { results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()} @@ -678,7 +678,7 @@ func (api *API) traceBlockParallel(ctx context.Context, block *types.Block, stat // Feed the transactions into the tracers and return var failed error - blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil) + blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) txloop: for i, tx := range txs { // Send the trace task over for execution @@ -755,7 +755,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block dumps []string signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) chainConfig = api.backend.ChainConfig() - vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil) + vmctx = core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) canon = true ) // Check if there are any overrides: the caller may wish to enable a future @@ -935,7 +935,7 @@ func (api *API) TraceCall(ctx context.Context, args ethapi.TransactionArgs, bloc } defer release() - vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil) + vmctx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) // Apply the customization rules if required. if config != nil { if err := config.StateOverrides.Apply(statedb); err != nil { diff --git a/eth/tracers/api_test.go b/eth/tracers/api_test.go index 319fe5e78671..6fbb50848d63 100644 --- a/eth/tracers/api_test.go +++ b/eth/tracers/api_test.go @@ -173,7 +173,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block for idx, tx := range block.Transactions() { msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(block.Header(), b.chain, nil, nil) + context := core.NewEVMBlockContext(block.Header(), b.chain, nil) if idx == txIndex { return tx, context, statedb, release, nil } diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 9a6b70a52877..0ecedf113038 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1110,7 +1110,7 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S defer cancel() // Get a new instance of the EVM. - blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil, nil) + blockCtx := core.NewEVMBlockContext(header, NewChainContext(ctx, b), nil) if blockOverrides != nil { blockOverrides.Apply(&blockCtx) } diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 56509ee6bdc0..cf5160caf778 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -569,7 +569,7 @@ func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state vmConfig = b.chain.GetVMConfig() } txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(header, b.chain, nil, nil) + context := core.NewEVMBlockContext(header, b.chain, nil) if blockContext != nil { context = *blockContext } diff --git a/miner/worker.go b/miner/worker.go index 2652cea1a4b6..5dc3e2056b81 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -196,7 +196,7 @@ func (miner *Miner) prepareWork(genParams *generateParams) (*environment, error) return nil, err } if header.ParentBeaconRoot != nil { - context := core.NewEVMBlockContext(header, miner.chain, nil, nil) + context := core.NewEVMBlockContext(header, miner.chain, nil) vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, miner.chainConfig, vm.Config{}) core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state) } diff --git a/tests/block_test.go b/tests/block_test.go index bd88d611bc2d..52184eb27432 100644 --- a/tests/block_test.go +++ b/tests/block_test.go @@ -18,101 +18,19 @@ package tests import ( "math/rand" - "runtime" "testing" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" ) -func TestStatelessBlockchain(t *testing.T) { - bt := new(testMatcher) - - // These tests fail as of https://github.com/ethereum/go-ethereum/pull/28666, since we - // no longer delete "leftover storage" when deploying a contract. - bt.skipLoad(`^GeneralStateTests/stSStoreTest/InitCollision\.json`) - bt.skipLoad(`^GeneralStateTests/stRevertTest/RevertInCreateInInit\.json`) - bt.skipLoad(`^GeneralStateTests/stExtCodeHash/dynamicAccountOverwriteEmpty\.json`) - bt.skipLoad(`^GeneralStateTests/stCreate2/create2collisionStorage\.json`) - bt.skipLoad(`^GeneralStateTests/stCreate2/RevertInCreateInInitCreate2\.json`) - - // this test imports a forked chain. The witness builder API receives a block by number - // loading it from the chain. So it fails to properly source the forked chain block, - // erroneously using the one from the main chain (hence the state root mismatch). - bt.skipLoad(`^InvalidBlocks/bcMultiChainTest/UncleFromSideChain\.json`) - // Skip random failures due to selfish mining test - bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`) - // Skip random failures due to selfish mining test - bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`) - - // Slow tests - bt.slow(`.*bcExploitTest/DelegateCallSpam.json`) - bt.slow(`.*bcExploitTest/ShanghaiLove.json`) - bt.slow(`.*bcExploitTest/SuicideIssue.json`) - bt.slow(`.*/bcForkStressTest/`) - bt.slow(`.*/bcGasPricerTest/RPC_API_Test.json`) - bt.slow(`.*/bcWalletTest/`) - - // Very slow test - bt.skipLoad(`.*/stTimeConsuming/.*`) - // test takes a lot for time and goes easily OOM because of sha3 calculation on a huge range, - // using 4.6 TGas - bt.skipLoad(`.*randomStatetest94.json.*`) - - // skip uncle tests for stateless - bt.skipLoad(`.*/UnclePopulation.json`) - // skip this test in stateless because it uses 5000 blocks and the - // historical state of older blocks is unavailable for stateless - // test verification after importing the test set. - bt.skipLoad(`.*/bcWalletTest/walletReorganizeOwners.json`) - - bt.walk(t, blockTestDir, func(t *testing.T, name string, test *BlockTest) { - if runtime.GOARCH == "386" && runtime.GOOS == "windows" && rand.Int63()%2 == 0 { - t.Skip("test (randomly) skipped on 32-bit windows") - } - - config, ok := Forks[test.json.Network] - if !ok { - t.Fatalf("test malformed: doesn't have chain config embedded") - } - isMerged := config.TerminalTotalDifficulty != nil && config.TerminalTotalDifficulty.BitLen() == 0 - if isMerged { - execBlockTestStateless(t, bt, test) - } else { - t.Skip("skipping pre-merge test") - } - }) - // There is also a LegacyTests folder, containing blockchain tests generated - // prior to Istanbul. However, they are all derived from GeneralStateTests, - // which run natively, so there's no reason to run them here. -} - -func execBlockTestStateless(t *testing.T, bt *testMatcher, test *BlockTest) { - if err := bt.checkFailure(t, test.RunStateless(false, rawdb.HashScheme, nil, nil)); err != nil { - t.Errorf("test in hash mode without snapshotter failed: %v", err) - return - } - if err := bt.checkFailure(t, test.RunStateless(true, rawdb.HashScheme, nil, nil)); err != nil { - t.Errorf("test in hash mode with snapshotter failed: %v", err) - return - } - if err := bt.checkFailure(t, test.RunStateless(false, rawdb.PathScheme, nil, nil)); err != nil { - t.Errorf("test in path mode without snapshotter failed: %v", err) - return - } - if err := bt.checkFailure(t, test.RunStateless(true, rawdb.PathScheme, nil, nil)); err != nil { - t.Errorf("test in path mode with snapshotter failed: %v", err) - return - } -} - func TestBlockchain(t *testing.T) { bt := new(testMatcher) - // General state tests are 'exported' as blockchain tests, but we can run them natively. - // For speedier CI-runs, the line below can be uncommented, so those are skipped. - // For now, in hardfork-times (Berlin), we run the tests both as StateTests and - // as blockchain tests, since the latter also covers things like receipt root - bt.skipLoad(`^GeneralStateTests/`) + + // We are running most of GeneralStatetests to tests witness support, even + // though they are ran as state tests too. Still, the performance tests are + // less about state andmore about EVM number crunching, so skip those. + bt.skipLoad(`^GeneralStateTests/VMTests/vmPerformance`) // Skip random failures due to selfish mining test bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`) @@ -152,33 +70,25 @@ func TestExecutionSpecBlocktests(t *testing.T) { } func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) { - // If -short flag is used, we don't execute all four permutations, only one. - executionMask := 0xf + // Define all the different flag combinations we should run the tests with, + // picking only one for short tests. + // + // Note, witness building and self-testing is always enabled as it's a very + // good test to ensure that we don't break it. + var ( + snapshotConf = []bool{false, true} + dbschemeConf = []string{rawdb.HashScheme, rawdb.PathScheme} + ) if testing.Short() { - executionMask = (1 << (rand.Int63() & 4)) - } - if executionMask&0x1 != 0 { - if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil, nil)); err != nil { - t.Errorf("test in hash mode without snapshotter failed: %v", err) - return - } - } - if executionMask&0x2 != 0 { - if err := bt.checkFailure(t, test.Run(true, rawdb.HashScheme, nil, nil)); err != nil { - t.Errorf("test in hash mode with snapshotter failed: %v", err) - return - } - } - if executionMask&0x4 != 0 { - if err := bt.checkFailure(t, test.Run(false, rawdb.PathScheme, nil, nil)); err != nil { - t.Errorf("test in path mode without snapshotter failed: %v", err) - return - } + snapshotConf = []bool{snapshotConf[rand.Int()%2]} + dbschemeConf = []string{dbschemeConf[rand.Int()%2]} } - if executionMask&0x8 != 0 { - if err := bt.checkFailure(t, test.Run(true, rawdb.PathScheme, nil, nil)); err != nil { - t.Errorf("test in path mode with snapshotter failed: %v", err) - return + for _, snapshot := range snapshotConf { + for _, dbscheme := range dbschemeConf { + if err := bt.checkFailure(t, test.Run(snapshot, dbscheme, true, nil, nil)); err != nil { + t.Errorf("test with config {snapshotter:%v, scheme:%v} failed: %v", snapshot, dbscheme, err) + return + } } } } diff --git a/tests/block_test_util.go b/tests/block_test_util.go index 3439a79b7bc7..62aa582c828e 100644 --- a/tests/block_test_util.go +++ b/tests/block_test_util.go @@ -26,9 +26,6 @@ import ( "os" "reflect" - "github.com/ethereum/go-ethereum/cmd/utils/stateless" - "github.com/ethereum/go-ethereum/core/tracing" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/math" @@ -37,6 +34,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/log" @@ -112,15 +110,7 @@ type btHeaderMarshaling struct { ExcessBlobGas *math.HexOrDecimal64 } -func (t *BlockTest) Run(snapshotter bool, scheme string, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) { - return t.run(false, snapshotter, scheme, tracer, postCheck) -} - -func (t *BlockTest) RunStateless(snapshotter bool, scheme string, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) { - return t.run(true, snapshotter, scheme, tracer, postCheck) -} - -func (t *BlockTest) run(isStateless bool, snapshotter bool, scheme string, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) { +func (t *BlockTest) Run(snapshotter bool, scheme string, witness bool, tracer *tracing.Hooks, postCheck func(error, *core.BlockChain)) (result error) { config, ok := Forks[t.json.Network] if !ok { return UnsupportedForkError{t.json.Network} @@ -161,7 +151,8 @@ func (t *BlockTest) run(isStateless bool, snapshotter bool, scheme string, trace cache.SnapshotWait = true } chain, err := core.NewBlockChain(db, cache, gspec, nil, engine, vm.Config{ - Tracer: tracer, + Tracer: tracer, + EnableWitnessCollection: witness, }, nil, nil) if err != nil { return err @@ -194,29 +185,7 @@ func (t *BlockTest) run(isStateless bool, snapshotter bool, scheme string, trace return err } } - if err := t.validateImportedHeaders(chain, validBlocks); err != nil { - return err - } - if isStateless { - for _, blk := range validBlocks { - proof, err := stateless.BuildStatelessProof(blk.BlockHeader.Hash, chain) - if err != nil { - return fmt.Errorf("failed to build proof: %v", err) - } - witness, err := state.DecodeWitnessRLP(proof) - if err != nil { - return fmt.Errorf("failed to decode witness RLP: %v", err) - } - root, err := stateless.StatelessExecute(config, witness) - if err != nil { - return fmt.Errorf("verification execution error: %v", err) - } - if root != blk.BlockHeader.StateRoot { - return fmt.Errorf("state root mismatch (wanted: %x, got: %x)", blk.BlockHeader.StateRoot, root) - } - } - } - return nil + return t.validateImportedHeaders(chain, validBlocks) } func (t *BlockTest) genesis(config *params.ChainConfig) *core.Genesis { diff --git a/tests/state_test.go b/tests/state_test.go index 837decfa86d1..76fec97de0ee 100644 --- a/tests/state_test.go +++ b/tests/state_test.go @@ -302,7 +302,7 @@ func runBenchmark(b *testing.B, t *StateTest) { // Prepare the EVM. txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase, nil) + context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase) context.GetHash = vmTestBlockHash context.BaseFee = baseFee evm := vm.NewEVM(context, txContext, state.StateDB, config, vmconfig) diff --git a/tests/state_test_util.go b/tests/state_test_util.go index 5958bfeecfe7..416bab947264 100644 --- a/tests/state_test_util.go +++ b/tests/state_test_util.go @@ -278,7 +278,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh // Prepare the EVM. txContext := core.NewEVMTxContext(msg) - context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase, nil) + context := core.NewEVMBlockContext(block.Header(), nil, &t.json.Env.Coinbase) context.GetHash = vmTestBlockHash context.BaseFee = baseFee context.Random = nil diff --git a/trie/secure_trie.go b/trie/secure_trie.go index bb0c891e7963..3572117e034d 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -214,8 +214,9 @@ func (t *StateTrie) GetKey(shaKey []byte) []byte { return t.db.Preimage(common.BytesToHash(shaKey)) } -func (t *StateTrie) AccessList() map[string][]byte { - return t.trie.AccessList() +// Witness returns a set containing all trie nodes that have been accessed. +func (t *StateTrie) Witness() map[string]struct{} { + return t.trie.Witness() } // Commit collects all dirty nodes in the trie and replaces them with the diff --git a/trie/trie.go b/trie/trie.go index b50f55639b60..f44e10b918d4 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -601,12 +601,6 @@ func (t *Trie) Hash() common.Hash { return common.BytesToHash(hash.(hashNode)) } -// AccessList returns a map of path->blob containing all trie nodes that have -// been accessed. -func (t *Trie) AccessList() map[string][]byte { - return t.tracer.accessList -} - // Commit collects all dirty nodes in the trie and replaces them with the // corresponding node hash. All collected nodes (including dirty leaves if // collectLeaf is true) will be encapsulated into a nodeset for return. @@ -667,6 +661,18 @@ func (t *Trie) hashRoot() (node, node) { return hashed, cached } +// Witness returns a set containing all trie nodes that have been accessed. +func (t *Trie) Witness() map[string]struct{} { + if len(t.tracer.accessList) == 0 { + return nil + } + witness := make(map[string]struct{}) + for _, node := range t.tracer.accessList { + witness[string(node)] = struct{}{} + } + return witness +} + // Reset drops the referenced root node and cleans all internal state. func (t *Trie) Reset() { t.root = nil diff --git a/trie/verkle.go b/trie/verkle.go index f9d40ac421e3..94a5ca9a2c5f 100644 --- a/trie/verkle.go +++ b/trie/verkle.go @@ -370,8 +370,7 @@ func (t *VerkleTrie) nodeResolver(path []byte) ([]byte, error) { return t.reader.node(path, common.Hash{}) } -// AccessList returns a map of path->blob containing all trie nodes that have -// been accessed. -func (t *VerkleTrie) AccessList() map[string][]byte { +// Witness returns a set containing all trie nodes that have been accessed. +func (t *VerkleTrie) Witness() map[string]struct{} { panic("not implemented") } diff --git a/triedb/database.go b/triedb/database.go index 1c800db1b622..ef757e7f5bc3 100644 --- a/triedb/database.go +++ b/triedb/database.go @@ -46,13 +46,6 @@ var HashDefaults = &Config{ HashDB: hashdb.Defaults, } -// PathDefaults represents a config for using path-based scheme with -// default settings. -var PathDefaults = &Config{ - Preimages: false, - PathDB: pathdb.Defaults, -} - // backend defines the methods needed to access/update trie nodes in different // state scheme. type backend interface {