all: simplify witness and integrate it into live geth

This commit is contained in:
Péter Szilágyi 2024-06-19 11:51:27 +03:00
parent d86b650e54
commit e1dd112c48
40 changed files with 748 additions and 897 deletions

View file

@ -86,7 +86,7 @@ func blockTestCmd(ctx *cli.Context) error {
continue continue
} }
test := tests[name] 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 ctx.Bool(DumpFlag.Name) {
if state, _ := chain.State(); state != nil { if state, _ := chain.State(); state != nil {
fmt.Println(string(state.Dump(nil))) fmt.Println(string(state.Dump(nil)))

View file

@ -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
}

View file

@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/state" "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/types"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
@ -35,14 +36,12 @@ import (
type BlockValidator struct { type BlockValidator struct {
config *params.ChainConfig // Chain configuration options config *params.ChainConfig // Chain configuration options
bc *BlockChain // Canonical block chain 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 // 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{ validator := &BlockValidator{
config: config, config: config,
engine: engine,
bc: blockchain, bc: blockchain,
} }
return validator 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 // Header validity is known at this point. Here we verify that uncles, transactions
// and withdrawals given in the block body match the header. // and withdrawals given in the block body match the header.
header := block.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 return err
} }
if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash { 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, // 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. // 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 func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64, stateless bool) error {
// execution.
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64, checkRemoteRoot bool) (root common.Hash, err error) {
header := block.Header() header := block.Header()
if block.GasUsed() != usedGas { 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. // Validate the received block's bloom with the one derived from the generated receipts.
// For valid blocks this should always validate to true. // For valid blocks this should always validate to true.
rbloom := types.CreateBloom(receipts) rbloom := types.CreateBloom(receipts)
if rbloom != header.Bloom { 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]])) // The receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]]))
receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil)) receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil))
if receiptSha != header.ReceiptHash { 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 // Validate the state root against the received state root and throw
// received state root and throw an error if they don't match. // an error if they don't match.
root = statedb.IntermediateRoot(v.config.IsEIP158(header.Number)) if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
if checkRemoteRoot && header.Root != root { return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error())
return root, 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 // CalcGasLimit computes the gas limit of the next block after parent. It aims

View file

@ -37,6 +37,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/state/snapshot" "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/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
@ -302,18 +303,18 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
vmConfig: vmConfig, vmConfig: vmConfig,
logger: vmConfig.Tracer, 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 var err error
bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.insertStopped) bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.insertStopped)
if err != nil { if err != nil {
return nil, err 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) bc.genesisBlock = bc.GetBlockByNumber(0)
if bc.genesisBlock == nil { if bc.genesisBlock == nil {
return nil, ErrNoGenesis 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 // while processing transactions. Before Byzantium the prefetcher is mostly
// useless due to the intermediate root hashing after each transaction. // useless due to the intermediate root hashing after each transaction.
if bc.chainConfig.IsByzantium(block.Number()) { 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 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 // Process block using the parent state as reference point
pstart := time.Now() 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 { if err != nil {
bc.reportBlock(block, receipts, err) bc.reportBlock(block, receipts, err)
return nil, err return nil, err
@ -1924,11 +1932,18 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s
ptime := time.Since(pstart) ptime := time.Since(pstart)
vstart := time.Now() 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) bc.reportBlock(block, receipts, err)
return nil, err return nil, err
} }
vtime := time.Since(vstart) 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 proctime := time.Since(start) // processing + validation
// Update the metrics touched during block processing and validation // Update the metrics touched during block processing and validation

View file

@ -163,13 +163,12 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
if err != nil { if err != nil {
return err 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 { if err != nil {
blockchain.reportBlock(block, receipts, err) blockchain.reportBlock(block, receipts, err)
return err return err
} }
_, err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas, true) if err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas, false); err != nil {
if err != nil {
blockchain.reportBlock(block, receipts, err) blockchain.reportBlock(block, receipts, err)
return err return err
} }

View file

@ -99,7 +99,7 @@ func (b *BlockGen) Difficulty() *big.Int {
func (b *BlockGen) SetParentBeaconRoot(root common.Hash) { func (b *BlockGen) SetParentBeaconRoot(root common.Hash) {
b.header.ParentBeaconRoot = &root b.header.ParentBeaconRoot = &root
var ( 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{}) vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.cm.config, vm.Config{})
) )
ProcessBeaconBlockRoot(root, vmenv, b.statedb) ProcessBeaconBlockRoot(root, vmenv, b.statedb)

View file

@ -19,8 +19,6 @@ package core
import ( import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/consensus/misc/eip4844"
@ -40,9 +38,8 @@ type ChainContext interface {
GetHeader(common.Hash, uint64) *types.Header 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 // NewEVMBlockContext creates a new context for use in the EVM.
// for the BLOCKHASH opcode from the witness. func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, witness *state.Witness) vm.BlockContext {
var ( var (
beneficiary common.Address beneficiary common.Address
baseFee *big.Int baseFee *big.Int
@ -65,18 +62,10 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
if header.Difficulty.Sign() == 0 { if header.Difficulty.Sign() == 0 {
random = &header.MixDigest 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{ return vm.BlockContext{
CanTransfer: CanTransfer, CanTransfer: CanTransfer,
Transfer: Transfer, Transfer: Transfer,
GetHash: getHash, GetHash: GetHashFn(header, chain),
Coinbase: beneficiary, Coinbase: beneficiary,
BlockNumber: new(big.Int).Set(header.Number), BlockNumber: new(big.Int).Set(header.Number),
Time: header.Time, Time: header.Time,

View file

@ -125,9 +125,9 @@ type Trie interface {
// be created with new root and updated trie database for following usage // be created with new root and updated trie database for following usage
Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet) Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet)
// AccessList returns a map of path->blob containing all trie nodes that have // Witness returns a set containing all trie nodes that have been accessed.
// been accessed. // The returned map could be nil if the witness is empty.
AccessList() map[string][]byte Witness() map[string]struct{}
// NodeIterator returns an iterator that returns nodes of the trie. Iteration // 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 // starts at the key after the given start key. And error will be returned

View file

@ -323,7 +323,17 @@ func (s *stateObject) finalise() {
// //
// It assumes all the dirty storage slots have been finalized before. // It assumes all the dirty storage slots have been finalized before.
func (s *stateObject) updateTrie() (Trie, error) { 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() tr := s.getPrefetchedTrie()
if tr != nil { if tr != nil {
// Prefetcher returned a live trie, swap it out for the current one // 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 return nil, err
} }
} }
// Short circuit if nothing changed, don't bother with hashing anything. // 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.
if len(s.uncommittedStorage) == 0 { if len(s.uncommittedStorage) == 0 {
return s.trie, nil 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 // Note, commit may run concurrently across all the state objects. Do not assume
// thread-safe access to the statedb. // thread-safe access to the statedb.
func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, map[string][]byte, error) { func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, error) {
var al map[string][]byte
// commit the account metadata changes // commit the account metadata changes
op := &accountUpdate{ op := &accountUpdate{
address: s.address, address: s.address,
@ -474,18 +478,12 @@ func (s *stateObject) commit() (*accountUpdate, *trienode.NodeSet, map[string][]
if len(op.storages) == 0 { if len(op.storages) == 0 {
// nothing changed, don't bother to commit the trie // nothing changed, don't bother to commit the trie
s.origin = s.data.Copy() s.origin = s.data.Copy()
if s.trie != nil && !s.trie.IsVerkle() { return op, nil, nil
al = s.trie.AccessList()
}
return op, nil, al, nil
} }
root, nodes := s.trie.Commit(false) root, nodes := s.trie.Commit(false)
s.data.Root = root s.data.Root = root
s.origin = s.data.Copy() s.origin = s.data.Copy()
if !s.trie.IsVerkle() { return op, nodes, nil
al = s.trie.AccessList()
}
return op, nodes, al, nil
} }
// AddBalance adds amount to s's balance. // AddBalance adds amount to s's balance.

View file

@ -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
}

View file

@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/snapshot" "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/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
@ -146,6 +147,9 @@ type StateDB struct {
validRevisions []revision validRevisions []revision
nextRevisionId int nextRevisionId int
// State witness if cross validation is needed
witness *stateless.Witness
// Measurements gathered during execution for debugging purposes // Measurements gathered during execution for debugging purposes
AccountReads time.Duration AccountReads time.Duration
AccountHashes time.Duration AccountHashes time.Duration
@ -163,7 +167,6 @@ type StateDB struct {
StorageUpdated atomic.Int64 StorageUpdated atomic.Int64
AccountDeleted int AccountDeleted int
StorageDeleted atomic.Int64 StorageDeleted atomic.Int64
witness *Witness
} }
// New creates a new state from a given trie. // 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 // 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 // state trie concurrently while the state is mutated so that when we reach the
// commit phase, most of the needed data is already hot. // 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 { if s.prefetcher != nil {
s.prefetcher.terminate(false) s.prefetcher.terminate(false)
s.prefetcher.report() s.prefetcher.report()
s.prefetcher = nil s.prefetcher = nil
} }
// Enable witness collection if requested
s.witness = witness
// If snapshots are enabled, start prefethers explicitly
if s.snap != nil { 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 // With the switch to the Proof-of-Stake consensus algorithm, block production
// rewards are now handled at the consensus layer. Consequently, a block may // rewards are now handled at the consensus layer. Consequently, a block may
@ -683,7 +691,7 @@ func (s *StateDB) Copy() *StateDB {
hasher: crypto.NewKeccakState(), hasher: crypto.NewKeccakState(),
originalRoot: s.originalRoot, originalRoot: s.originalRoot,
stateObjects: make(map[common.Address]*stateObject, len(s.stateObjects)), 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)), mutations: make(map[common.Address]*mutation, len(s.mutations)),
dbErr: s.dbErr, dbErr: s.dbErr,
refund: s.refund, refund: s.refund,
@ -710,6 +718,10 @@ func (s *StateDB) Copy() *StateDB {
for addr, obj := range s.stateObjects { for addr, obj := range s.stateObjects {
state.stateObjects[addr] = obj.deepCopy(state) 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. // Deep copy the object state markers.
for addr, op := range s.mutations { for addr, op := range s.mutations {
state.mutations[addr] = op.copy() state.mutations[addr] = op.copy()
@ -758,12 +770,6 @@ func (s *StateDB) RevertToSnapshot(revid int) {
s.validRevisions = s.validRevisions[:idx] 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. // GetRefund returns the current value of the refund counter.
func (s *StateDB) GetRefund() uint64 { func (s *StateDB) GetRefund() uint64 {
return s.refund return s.refund
@ -817,46 +823,6 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) {
s.clearJournalAndRefund() 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. // IntermediateRoot computes the current root hash of the state trie.
// It is called in between transactions to get the root hash that // It is called in between transactions to get the root hash that
// goes into transaction receipts. // 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 s.prefetcher = nil // Pre-byzantium, unset any used up prefetcher
}() }()
} }
// Process all storage updates concurrently. The state object update root // Process all storage updates concurrently. The state object update root
// method will internally call a blocking trie fetch from the prefetcher, // method will internally call a blocking trie fetch from the prefetcher,
// so there's no need to explicitly wait for the prefetchers to finish. // 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 obj := s.stateObjects[addr] // closure for the task runner below
workers.Go(func() error { workers.Go(func() error {
obj.updateRoot() 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 return nil
}) })
} }
// If witness building is enabled, gather all the read-only accesses
if s.witness != nil { if s.witness != nil {
// if witness building is enabled, collect storage access lists from // Pull in anything that has been accessed before destruction
// tries that will not be committed: accounts with non-mutated storage for _, obj := range s.stateObjectsDestruct {
// and self-destructed accounts. // Skip any objects that haven't touched their storage
s.collectNonCommittedTrieAccessLists() 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() workers.Wait()
s.StorageUpdates += time.Since(start) 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 // Track the amount of time wasted on hashing the account trie
defer func(start time.Time) { s.AccountHashes += time.Since(start) }(time.Now()) 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 // 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() buf = crypto.NewKeccakState()
deletes = make(map[common.Hash]*accountDelete) deletes = make(map[common.Hash]*accountDelete)
) )
for addr, obj := range s.stateObjectsDestruct { for addr, prevObj := range s.stateObjectsDestruct {
prev := obj.origin prev := prevObj.origin
// The account was non-existent, and it's marked as destructed in the scope // 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 // of block. It can be either case (a) or (b) and will be interpreted as
// null->null state transition. // null->null state transition.
@ -1225,7 +1228,6 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
root common.Hash root common.Hash
workers errgroup.Group workers errgroup.Group
) )
// Schedule the account trie first since that will be the biggest, so give // Schedule the account trie first since that will be the biggest, so give
// it the most time to crunch. // 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 // Obviously it's not an end of the world issue, just something the original
// code didn't anticipate for. // code didn't anticipate for.
workers.Go(func() error { 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 // Write the account trie changes, measuring the amount of wasted time
if s.witness != nil { newroot, set := s.trie.Commit(true)
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)
}
root = newroot root = newroot
if err := merge(set); err != nil { 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 // Run the storage updates concurrently to one another
workers.Go(func() error { workers.Go(func() error {
// Write any storage changes in the state object to its storage trie // 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 { if err != nil {
return err return err
} }
@ -1290,12 +1275,9 @@ func (s *StateDB) commit(deleteEmptyObjects bool) (*stateUpdate, error) {
return err return err
} }
lock.Lock() lock.Lock()
defer lock.Unlock()
updates[obj.addrHash] = update updates[obj.addrHash] = update
s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime s.StorageCommits = time.Since(start) // overwrite with the longest storage commit runtime
if s.witness != nil && len(al) > 0 { lock.Unlock()
s.witness.addAccessList(obj.addrHash, al)
}
return nil return nil
}) })
} }
@ -1372,12 +1354,6 @@ func (s *StateDB) commitAndFlush(block uint64, deleteEmptyObjects bool) (*stateU
return ret, err 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. // Commit writes the state mutations into the configured data stores.
// //
// Once the state is committed, tries cached in stateDB (including account // 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 { func (s *StateDB) PointCache() *utils.PointCache {
return s.db.PointCache() return s.db.PointCache()
} }
// Witness retrieves the current state witness being collected.
func (s *StateDB) Witness() *stateless.Witness {
return s.witness
}

View file

@ -19,7 +19,6 @@ package core
import ( import (
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
@ -31,16 +30,14 @@ import (
// data from disk before the main block processor start executing. // data from disk before the main block processor start executing.
type statePrefetcher struct { type statePrefetcher struct {
config *params.ChainConfig // Chain configuration options config *params.ChainConfig // Chain configuration options
bc *BlockChain // Canonical block chain chain *HeaderChain // Canonical block chain
engine consensus.Engine // Consensus engine used for block rewards
} }
// newStatePrefetcher initialises a new statePrefetcher. // 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{ return &statePrefetcher{
config: config, config: config,
bc: bc, chain: chain,
engine: engine,
} }
} }
@ -51,7 +48,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
var ( var (
header = block.Header() header = block.Header()
gaspool = new(GasPool).AddGas(block.GasLimit()) 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) evm = vm.NewEVM(blockContext, vm.TxContext{}, statedb, p.config, cfg)
signer = types.MakeSigner(p.config, header.Number, header.Time) signer = types.MakeSigner(p.config, header.Number, header.Time)
) )

View file

@ -22,7 +22,6 @@ import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc" "github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
@ -36,45 +35,18 @@ import (
// //
// StateProcessor implements Processor. // StateProcessor implements Processor.
type StateProcessor struct { type StateProcessor struct {
config *params.ChainConfig // Chain configuration options config *params.ChainConfig // Chain configuration options
bc *BlockChain // Canonical block chain chain *HeaderChain // Canonical header chain
engine consensus.Engine // Consensus engine used for block rewards
statelessChainCtx ChainContext
} }
// NewStateProcessor initialises a new StateProcessor. If the provided // NewStateProcessor initialises a new StateProcessor.
// Blockchain is nil, stateless execution mode is enabled. func NewStateProcessor(config *params.ChainConfig, chain *HeaderChain) *StateProcessor {
func NewStateProcessor(config *params.ChainConfig, bc *BlockChain, engine consensus.Engine) *StateProcessor { return &StateProcessor{
if bc != nil { config: config,
return &StateProcessor{ chain: chain,
config: config,
bc: bc,
engine: engine,
}
} else {
return &StateProcessor{
config: config,
engine: engine,
statelessChainCtx: &statelessChainContext{engine},
bc: &BlockChain{
chainConfig: config,
engine: engine,
},
}
} }
} }
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 // 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 transaction messages using the statedb and applying any rewards to both
// the processor (coinbase) and any included uncles. // 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 // 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 // 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. // 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 ( var (
receipts types.Receipts receipts types.Receipts
usedGas = new(uint64) usedGas = new(uint64)
@ -101,7 +73,7 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
context vm.BlockContext context vm.BlockContext
signer = types.MakeSigner(p.config, header.Number, header.Time) 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) vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, p.config, cfg)
if beaconRoot := block.BeaconRoot(); beaconRoot != nil { if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) 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") return nil, nil, 0, errors.New("withdrawals before shanghai")
} }
// Finalize the block, applying any consensus engine specific extras (e.g. block rewards) // 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 return receipts, allLogs, *usedGas, nil
} }
@ -203,7 +175,7 @@ func ApplyTransaction(config *params.ChainConfig, bc ChainContext, author *commo
return nil, err return nil, err
} }
// Create a new context to be used in the EVM environment // 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) txContext := NewEVMTxContext(msg)
vmenv := vm.NewEVM(blockContext, txContext, statedb, config, cfg) vmenv := vm.NewEVM(blockContext, txContext, statedb, config, cfg)
return ApplyTransactionWithEVM(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv) return ApplyTransactionWithEVM(msg, config, gp, statedb, header.Number, header.Hash(), tx, usedGas, vmenv)

73
core/stateless.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

129
core/stateless/encoding.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -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
}

159
core/stateless/witness.go Normal file
View file

@ -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 <http://www.gnu.org/licenses/>.
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
}

View file

@ -20,8 +20,8 @@ import (
"sync/atomic" "sync/atomic"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/state" "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/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
) )
@ -35,7 +35,10 @@ type Validator interface {
// ValidateState validates the given statedb and optionally the receipts and // ValidateState validates the given statedb and optionally the receipts and
// gas used. // 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. // 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 // 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 transaction messages using the statedb and applying any rewards to both
// the processor (coinbase) and any included uncles. // 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)
} }

View file

@ -174,18 +174,6 @@ type Body struct {
Withdrawals []*Withdrawal `rlp:"optional"` 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. // Block represents an Ethereum block.
// //
// Note the Block type tries to be 'immutable', and contains certain caches that rely // Note the Block type tries to be 'immutable', and contains certain caches that rely

View file

@ -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. // 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. // The contract is a scoped environment for this execution context only.
code := evm.StateDB.GetCode(addr) code := evm.StateDB.GetCode(addr)
codeCopy := make([]byte, len(code))
copy(codeCopy[:], code[:])
if witness := evm.StateDB.Witness(); witness != nil { if witness := evm.StateDB.Witness(); witness != nil {
witness.AddCode(evm.StateDB.GetCodeHash(addr), codeCopy) witness.AddCode(code)
} }
if len(code) == 0 { if len(code) == 0 {
ret, err = nil, nil // gas is unchanged 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. // The contract is a scoped environment for this execution context only.
contract := NewContract(caller, AccountRef(caller.Address()), value, gas) contract := NewContract(caller, AccountRef(caller.Address()), value, gas)
if witness := evm.StateDB.Witness(); witness != nil { 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)) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
ret, err = evm.interpreter.Run(contract, input, false) 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 // Initialise a new contract and make initialise the delegate values
contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate()
if witness := evm.StateDB.Witness(); witness != nil { 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)) contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), evm.StateDB.GetCode(addrCopy))
ret, err = evm.interpreter.Run(contract, input, false) 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. // The contract is a scoped environment for this execution context only.
contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas) contract := NewContract(caller, AccountRef(addrCopy), new(uint256.Int), gas)
if witness := evm.StateDB.Witness(); witness != nil { 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)) 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 // When an error was returned by the EVM or when setting the creation code

View file

@ -342,9 +342,7 @@ func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
slot := scope.Stack.peek() slot := scope.Stack.peek()
address := slot.Bytes20() address := slot.Bytes20()
if witness := interpreter.evm.StateDB.Witness(); witness != nil { if witness := interpreter.evm.StateDB.Witness(); witness != nil {
code := interpreter.evm.StateDB.GetCode(address) witness.AddCode(interpreter.evm.StateDB.GetCode(address))
codeHash := interpreter.evm.StateDB.GetCodeHash(address)
witness.AddCode(codeHash, code)
} }
slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20()))) slot.SetUint64(uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20())))
return nil, nil return nil, nil
@ -384,12 +382,11 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
uint64CodeOffset = math.MaxUint64 uint64CodeOffset = math.MaxUint64
} }
addr := common.Address(a.Bytes20()) addr := common.Address(a.Bytes20())
code := interpreter.evm.StateDB.GetCode(addr)
if witness := interpreter.evm.StateDB.Witness(); witness != nil { if witness := interpreter.evm.StateDB.Witness(); witness != nil {
hash := interpreter.evm.StateDB.GetCodeHash(addr) witness.AddCode(code)
code := interpreter.evm.StateDB.GetCode(addr)
witness.AddCode(hash, 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) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
return nil, nil return nil, nil
@ -427,11 +424,6 @@ func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
if interpreter.evm.StateDB.Empty(address) { if interpreter.evm.StateDB.Empty(address) {
slot.Clear() slot.Clear()
} else { } 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()) slot.SetBytes(interpreter.evm.StateDB.GetCodeHash(address).Bytes())
} }
return nil, nil return nil, nil
@ -461,7 +453,7 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
if num64 >= lower && num64 < upper { if num64 >= lower && num64 < upper {
res := interpreter.evm.Context.GetHash(num64) res := interpreter.evm.Context.GetHash(num64)
if witness := interpreter.evm.StateDB.Witness(); witness != nil { if witness := interpreter.evm.StateDB.Witness(); witness != nil {
witness.AddBlockHash(res, num64) witness.AddBlockHash(num64)
} }
num.SetBytes(res[:]) num.SetBytes(res[:])
} else { } else {

View file

@ -19,9 +19,8 @@ package vm
import ( import (
"math/big" "math/big"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/common" "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/tracing"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
@ -90,7 +89,7 @@ type StateDB interface {
AddLog(*types.Log) AddLog(*types.Log)
AddPreimage(common.Hash, []byte) AddPreimage(common.Hash, []byte)
Witness() *state.Witness Witness() *stateless.Witness
} }
// CallContext provides a basic interface for the EVM calling conventions. The EVM // CallContext provides a basic interface for the EVM calling conventions. The EVM

View file

@ -258,7 +258,7 @@ func (b *EthAPIBackend) GetEVM(ctx context.Context, msg *core.Message, state *st
if blockCtx != nil { if blockCtx != nil {
context = *blockCtx context = *blockCtx
} else { } 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) return vm.NewEVM(context, txContext, state, b.ChainConfig(), *vmConfig)
} }

View file

@ -22,8 +22,6 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/ethereum/go-ethereum/cmd/utils/stateless"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
@ -445,17 +443,3 @@ func (api *DebugAPI) GetTrieFlushInterval() (string, error) {
} }
return api.eth.blockchain.GetTrieFlushInterval().String(), nil 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)
}

View file

@ -218,7 +218,7 @@ func run(ctx context.Context, call *core.Message, opts *Options) (*core.Executio
// Assemble the call and the call context // Assemble the call and the call context
var ( var (
msgContext = core.NewEVMTxContext(call) 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() dirtyState = opts.State.Copy()
evm = vm.NewEVM(evmContext, msgContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true}) evm = vm.NewEVM(evmContext, msgContext, dirtyState, opts.Config, vm.Config{NoBaseFee: true})

View file

@ -146,7 +146,7 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, reexec u
if current = eth.blockchain.GetBlockByNumber(next); current == nil { if current = eth.blockchain.GetBlockByNumber(next); current == nil {
return nil, nil, fmt.Errorf("block #%d not found", next) 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 { if err != nil {
return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err) 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. // Insert parent beacon block root in the state as per EIP-4788.
if beaconRoot := block.BeaconRoot(); beaconRoot != nil { 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{}) vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, eth.blockchain.Config(), vm.Config{})
core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) 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 // Assemble the transaction call message and return if the requested offset
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
txContext := core.NewEVMTxContext(msg) txContext := core.NewEVMTxContext(msg)
context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil, nil) context := core.NewEVMBlockContext(block.Header(), eth.blockchain, nil)
if idx == txIndex { if idx == txIndex {
return tx, context, statedb, release, nil return tx, context, statedb, release, nil
} }

View file

@ -266,7 +266,7 @@ func (api *API) traceChain(start, end *types.Block, config *TraceConfig, closed
for task := range taskCh { for task := range taskCh {
var ( var (
signer = types.MakeSigner(api.backend.ChainConfig(), task.block.Number(), task.block.Time()) 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 // Trace all the transactions contained within
for i, tx := range task.block.Transactions() { 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 // Insert block's parent beacon block root in the state
// as per EIP-4788. // as per EIP-4788.
if beaconRoot := next.BeaconRoot(); beaconRoot != nil { 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{}) vmenv := vm.NewEVM(context, vm.TxContext{}, statedb, api.backend.ChainConfig(), vm.Config{})
core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb) core.ProcessBeaconBlockRoot(*beaconRoot, vmenv, statedb)
} }
@ -527,7 +527,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
roots []common.Hash roots []common.Hash
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
chainConfig = api.backend.ChainConfig() 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()) deleteEmptyObjects = chainConfig.IsEIP158(block.Number())
) )
if beaconRoot := block.BeaconRoot(); beaconRoot != nil { if beaconRoot := block.BeaconRoot(); beaconRoot != nil {
@ -605,7 +605,7 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
var ( var (
txs = block.Transactions() txs = block.Transactions()
blockHash = block.Hash() 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()) signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
results = make([]*txTraceResult, len(txs)) 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 // as the GetHash function of BlockContext is not safe for
// concurrent use. // concurrent use.
// See: https://github.com/ethereum/go-ethereum/issues/29114 // 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) res, err := api.traceTx(ctx, txs[task.index], msg, txctx, blockCtx, task.statedb, config)
if err != nil { if err != nil {
results[task.index] = &txTraceResult{TxHash: txs[task.index].Hash(), Error: err.Error()} 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 // Feed the transactions into the tracers and return
var failed error var failed error
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil, nil) blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
txloop: txloop:
for i, tx := range txs { for i, tx := range txs {
// Send the trace task over for execution // Send the trace task over for execution
@ -755,7 +755,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block
dumps []string dumps []string
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time()) signer = types.MakeSigner(api.backend.ChainConfig(), block.Number(), block.Time())
chainConfig = api.backend.ChainConfig() 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 canon = true
) )
// Check if there are any overrides: the caller may wish to enable a future // 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() 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. // Apply the customization rules if required.
if config != nil { if config != nil {
if err := config.StateOverrides.Apply(statedb); err != nil { if err := config.StateOverrides.Apply(statedb); err != nil {

View file

@ -173,7 +173,7 @@ func (b *testBackend) StateAtTransaction(ctx context.Context, block *types.Block
for idx, tx := range block.Transactions() { for idx, tx := range block.Transactions() {
msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee())
txContext := core.NewEVMTxContext(msg) txContext := core.NewEVMTxContext(msg)
context := core.NewEVMBlockContext(block.Header(), b.chain, nil, nil) context := core.NewEVMBlockContext(block.Header(), b.chain, nil)
if idx == txIndex { if idx == txIndex {
return tx, context, statedb, release, nil return tx, context, statedb, release, nil
} }

View file

@ -1110,7 +1110,7 @@ func doCall(ctx context.Context, b Backend, args TransactionArgs, state *state.S
defer cancel() defer cancel()
// Get a new instance of the EVM. // 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 { if blockOverrides != nil {
blockOverrides.Apply(&blockCtx) blockOverrides.Apply(&blockCtx)
} }

View file

@ -569,7 +569,7 @@ func (b testBackend) GetEVM(ctx context.Context, msg *core.Message, state *state
vmConfig = b.chain.GetVMConfig() vmConfig = b.chain.GetVMConfig()
} }
txContext := core.NewEVMTxContext(msg) txContext := core.NewEVMTxContext(msg)
context := core.NewEVMBlockContext(header, b.chain, nil, nil) context := core.NewEVMBlockContext(header, b.chain, nil)
if blockContext != nil { if blockContext != nil {
context = *blockContext context = *blockContext
} }

View file

@ -196,7 +196,7 @@ func (miner *Miner) prepareWork(genParams *generateParams) (*environment, error)
return nil, err return nil, err
} }
if header.ParentBeaconRoot != nil { 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{}) vmenv := vm.NewEVM(context, vm.TxContext{}, env.state, miner.chainConfig, vm.Config{})
core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state) core.ProcessBeaconBlockRoot(*header.ParentBeaconRoot, vmenv, env.state)
} }

View file

@ -18,101 +18,19 @@ package tests
import ( import (
"math/rand" "math/rand"
"runtime"
"testing" "testing"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb" "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) { func TestBlockchain(t *testing.T) {
bt := new(testMatcher) 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. // We are running most of GeneralStatetests to tests witness support, even
// For now, in hardfork-times (Berlin), we run the tests both as StateTests and // though they are ran as state tests too. Still, the performance tests are
// as blockchain tests, since the latter also covers things like receipt root // less about state andmore about EVM number crunching, so skip those.
bt.skipLoad(`^GeneralStateTests/`) bt.skipLoad(`^GeneralStateTests/VMTests/vmPerformance`)
// Skip random failures due to selfish mining test // Skip random failures due to selfish mining test
bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`) bt.skipLoad(`.*bcForgedTest/bcForkUncle\.json`)
@ -152,33 +70,25 @@ func TestExecutionSpecBlocktests(t *testing.T) {
} }
func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) { func execBlockTest(t *testing.T, bt *testMatcher, test *BlockTest) {
// If -short flag is used, we don't execute all four permutations, only one. // Define all the different flag combinations we should run the tests with,
executionMask := 0xf // 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() { if testing.Short() {
executionMask = (1 << (rand.Int63() & 4)) snapshotConf = []bool{snapshotConf[rand.Int()%2]}
dbschemeConf = []string{dbschemeConf[rand.Int()%2]}
} }
if executionMask&0x1 != 0 { for _, snapshot := range snapshotConf {
if err := bt.checkFailure(t, test.Run(false, rawdb.HashScheme, nil, nil)); err != nil { for _, dbscheme := range dbschemeConf {
t.Errorf("test in hash mode without snapshotter failed: %v", err) if err := bt.checkFailure(t, test.Run(snapshot, dbscheme, true, nil, nil)); err != nil {
return t.Errorf("test with config {snapshotter:%v, scheme:%v} failed: %v", snapshot, dbscheme, 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
}
}
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
} }
} }
} }

View file

@ -26,9 +26,6 @@ import (
"os" "os"
"reflect" "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"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
@ -37,6 +34,7 @@ import (
"github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state" "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/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
@ -112,15 +110,7 @@ type btHeaderMarshaling struct {
ExcessBlobGas *math.HexOrDecimal64 ExcessBlobGas *math.HexOrDecimal64
} }
func (t *BlockTest) Run(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) {
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) {
config, ok := Forks[t.json.Network] config, ok := Forks[t.json.Network]
if !ok { if !ok {
return UnsupportedForkError{t.json.Network} return UnsupportedForkError{t.json.Network}
@ -161,7 +151,8 @@ func (t *BlockTest) run(isStateless bool, snapshotter bool, scheme string, trace
cache.SnapshotWait = true cache.SnapshotWait = true
} }
chain, err := core.NewBlockChain(db, cache, gspec, nil, engine, vm.Config{ chain, err := core.NewBlockChain(db, cache, gspec, nil, engine, vm.Config{
Tracer: tracer, Tracer: tracer,
EnableWitnessCollection: witness,
}, nil, nil) }, nil, nil)
if err != nil { if err != nil {
return err return err
@ -194,29 +185,7 @@ func (t *BlockTest) run(isStateless bool, snapshotter bool, scheme string, trace
return err return err
} }
} }
if err := t.validateImportedHeaders(chain, validBlocks); err != nil { return t.validateImportedHeaders(chain, validBlocks)
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
} }
func (t *BlockTest) genesis(config *params.ChainConfig) *core.Genesis { func (t *BlockTest) genesis(config *params.ChainConfig) *core.Genesis {

View file

@ -302,7 +302,7 @@ func runBenchmark(b *testing.B, t *StateTest) {
// Prepare the EVM. // Prepare the EVM.
txContext := core.NewEVMTxContext(msg) 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.GetHash = vmTestBlockHash
context.BaseFee = baseFee context.BaseFee = baseFee
evm := vm.NewEVM(context, txContext, state.StateDB, config, vmconfig) evm := vm.NewEVM(context, txContext, state.StateDB, config, vmconfig)

View file

@ -278,7 +278,7 @@ func (t *StateTest) RunNoVerify(subtest StateSubtest, vmconfig vm.Config, snapsh
// Prepare the EVM. // Prepare the EVM.
txContext := core.NewEVMTxContext(msg) 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.GetHash = vmTestBlockHash
context.BaseFee = baseFee context.BaseFee = baseFee
context.Random = nil context.Random = nil

View file

@ -214,8 +214,9 @@ func (t *StateTrie) GetKey(shaKey []byte) []byte {
return t.db.Preimage(common.BytesToHash(shaKey)) return t.db.Preimage(common.BytesToHash(shaKey))
} }
func (t *StateTrie) AccessList() map[string][]byte { // Witness returns a set containing all trie nodes that have been accessed.
return t.trie.AccessList() func (t *StateTrie) Witness() map[string]struct{} {
return t.trie.Witness()
} }
// Commit collects all dirty nodes in the trie and replaces them with the // Commit collects all dirty nodes in the trie and replaces them with the

View file

@ -601,12 +601,6 @@ func (t *Trie) Hash() common.Hash {
return common.BytesToHash(hash.(hashNode)) 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 // Commit collects all dirty nodes in the trie and replaces them with the
// corresponding node hash. All collected nodes (including dirty leaves if // corresponding node hash. All collected nodes (including dirty leaves if
// collectLeaf is true) will be encapsulated into a nodeset for return. // collectLeaf is true) will be encapsulated into a nodeset for return.
@ -667,6 +661,18 @@ func (t *Trie) hashRoot() (node, node) {
return hashed, cached 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. // Reset drops the referenced root node and cleans all internal state.
func (t *Trie) Reset() { func (t *Trie) Reset() {
t.root = nil t.root = nil

View file

@ -370,8 +370,7 @@ func (t *VerkleTrie) nodeResolver(path []byte) ([]byte, error) {
return t.reader.node(path, common.Hash{}) return t.reader.node(path, common.Hash{})
} }
// AccessList returns a map of path->blob containing all trie nodes that have // Witness returns a set containing all trie nodes that have been accessed.
// been accessed. func (t *VerkleTrie) Witness() map[string]struct{} {
func (t *VerkleTrie) AccessList() map[string][]byte {
panic("not implemented") panic("not implemented")
} }

View file

@ -46,13 +46,6 @@ var HashDefaults = &Config{
HashDB: hashdb.Defaults, 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 // backend defines the methods needed to access/update trie nodes in different
// state scheme. // state scheme.
type backend interface { type backend interface {