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 {