Skip to content

Commit

Permalink
all: simplify witness and integrate it into live geth
Browse files Browse the repository at this point in the history
  • Loading branch information
karalabe committed Jun 25, 2024
1 parent d86b650 commit e1dd112
Show file tree
Hide file tree
Showing 40 changed files with 748 additions and 897 deletions.
2 changes: 1 addition & 1 deletion cmd/evm/blockrunner.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ func blockTestCmd(ctx *cli.Context) error {
continue
}
test := tests[name]
if err := test.Run(false, rawdb.HashScheme, tracer, func(res error, chain *core.BlockChain) {
if err := test.Run(false, rawdb.HashScheme, false, tracer, func(res error, chain *core.BlockChain) {
if ctx.Bool(DumpFlag.Name) {
if state, _ := chain.State(); state != nil {
fmt.Println(string(state.Dump(nil)))
Expand Down
86 changes: 0 additions & 86 deletions cmd/utils/stateless/stateless.go

This file was deleted.

55 changes: 39 additions & 16 deletions core/block_validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie"
Expand All @@ -35,14 +36,12 @@ import (
type BlockValidator struct {
config *params.ChainConfig // Chain configuration options
bc *BlockChain // Canonical block chain
engine consensus.Engine // Consensus engine used for validating
}

// NewBlockValidator returns a new block validator which is safe for re-use
func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engine consensus.Engine) *BlockValidator {
func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain) *BlockValidator {
validator := &BlockValidator{
config: config,
engine: engine,
bc: blockchain,
}
return validator
Expand All @@ -60,7 +59,7 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {
// Header validity is known at this point. Here we verify that uncles, transactions
// and withdrawals given in the block body match the header.
header := block.Header()
if err := v.engine.VerifyUncles(v.bc, block); err != nil {
if err := v.bc.engine.VerifyUncles(v.bc, block); err != nil {
return err
}
if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash {
Expand Down Expand Up @@ -122,31 +121,55 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error {

// ValidateState validates the various changes that happen after a state transition,
// such as amount of used gas, the receipt roots and the state root itself.
// If validateRemoteRoot is false, the provided block header's root is not asserted to be equal to the one computed from
// execution.
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64, checkRemoteRoot bool) (root common.Hash, err error) {
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, receipts types.Receipts, usedGas uint64, stateless bool) error {
header := block.Header()
if block.GasUsed() != usedGas {
return root, fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas)
return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas)
}
// Validate the received block's bloom with the one derived from the generated receipts.
// For valid blocks this should always validate to true.
rbloom := types.CreateBloom(receipts)
if rbloom != header.Bloom {
return root, fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
}
// In stateless mode, return early because the receipt and state root are not
// provided through the witness, rather the cross validator needs to return it.
if stateless {
return nil
}
// The receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]]))
receiptSha := types.DeriveSha(receipts, trie.NewStackTrie(nil))
if receiptSha != header.ReceiptHash {
return root, fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
}
// Compute the state root and if enabled, check it against the
// received state root and throw an error if they don't match.
root = statedb.IntermediateRoot(v.config.IsEIP158(header.Number))
if checkRemoteRoot && header.Root != root {
return root, fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error())
// Validate the state root against the received state root and throw
// an error if they don't match.
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
return fmt.Errorf("invalid merkle root (remote: %x local: %x) dberr: %w", header.Root, root, statedb.Error())
}
return root, nil
return nil
}

// ValidateWitness cross validates a block execution with stateless remote clients.
//
// Normally we'd distribute the block witness to remote cross validators, wait
// for them to respond and then merge the results. For now, however, it's only
// Geth, so do an internal stateless run.
func (v *BlockValidator) ValidateWitness(witness *stateless.Witness, receiptRoot common.Hash, stateRoot common.Hash) error {
// Run the cross client stateless execution
// TODO(karalabe): Self-stateless for now, swap with other clients
crossReceiptRoot, crossStateRoot, err := ExecuteStateless(v.config, witness)
if err != nil {
return fmt.Errorf("stateless execution failed: %v", err)
}
// Stateless cross execution suceeeded, validate the withheld computed fields
if crossReceiptRoot != receiptRoot {
return fmt.Errorf("cross validator receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, receiptRoot)
}
if crossStateRoot != stateRoot {
return fmt.Errorf("cross validator state root mismatch (cross: %x local: %x)", crossStateRoot, stateRoot)
}
return nil
}

// CalcGasLimit computes the gas limit of the next block after parent. It aims
Expand Down
35 changes: 25 additions & 10 deletions core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/state/snapshot"
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
Expand Down Expand Up @@ -302,18 +303,18 @@ func NewBlockChain(db ethdb.Database, cacheConfig *CacheConfig, genesis *Genesis
vmConfig: vmConfig,
logger: vmConfig.Tracer,
}
bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit))
bc.forker = NewForkChoice(bc, shouldPreserve)
bc.stateCache = state.NewDatabaseWithNodeDB(bc.db, bc.triedb)
bc.validator = NewBlockValidator(chainConfig, bc, engine)
bc.prefetcher = newStatePrefetcher(chainConfig, bc, engine)
bc.processor = NewStateProcessor(chainConfig, bc, engine)

var err error
bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.insertStopped)
if err != nil {
return nil, err
}
bc.flushInterval.Store(int64(cacheConfig.TrieTimeLimit))
bc.forker = NewForkChoice(bc, shouldPreserve)
bc.stateCache = state.NewDatabaseWithNodeDB(bc.db, bc.triedb)
bc.validator = NewBlockValidator(chainConfig, bc)
bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc)
bc.processor = NewStateProcessor(chainConfig, bc.hc)

bc.genesisBlock = bc.GetBlockByNumber(0)
if bc.genesisBlock == nil {
return nil, ErrNoGenesis
Expand Down Expand Up @@ -1809,7 +1810,14 @@ func (bc *BlockChain) insertChain(chain types.Blocks, setHead bool) (int, error)
// while processing transactions. Before Byzantium the prefetcher is mostly
// useless due to the intermediate root hashing after each transaction.
if bc.chainConfig.IsByzantium(block.Number()) {
statedb.StartPrefetcher("chain", !bc.vmConfig.EnableWitnessCollection)
var witness *stateless.Witness
if bc.vmConfig.EnableWitnessCollection {
witness, err = stateless.NewWitness(bc, block)
if err != nil {
return it.index, err
}
}
statedb.StartPrefetcher("chain", witness)
}
activeState = statedb

Expand Down Expand Up @@ -1916,19 +1924,26 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s

// Process block using the parent state as reference point
pstart := time.Now()
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig, nil)
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
if err != nil {
bc.reportBlock(block, receipts, err)
return nil, err
}
ptime := time.Since(pstart)

vstart := time.Now()
if _, err := bc.validator.ValidateState(block, statedb, receipts, usedGas, true); err != nil {
if err := bc.validator.ValidateState(block, statedb, receipts, usedGas, false); err != nil {
bc.reportBlock(block, receipts, err)
return nil, err
}
vtime := time.Since(vstart)

if witness := statedb.Witness(); witness != nil {
if err = bc.validator.ValidateWitness(witness, block.ReceiptHash(), block.Root()); err != nil {
bc.reportBlock(block, receipts, err)
return nil, fmt.Errorf("cross verification failed: %v", err)
}
}
proctime := time.Since(start) // processing + validation

// Update the metrics touched during block processing and validation
Expand Down
5 changes: 2 additions & 3 deletions core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,13 +163,12 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
if err != nil {
return err
}
receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{}, nil)
receipts, _, usedGas, err := blockchain.processor.Process(block, statedb, vm.Config{})
if err != nil {
blockchain.reportBlock(block, receipts, err)
return err
}
_, err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas, true)
if err != nil {
if err = blockchain.validator.ValidateState(block, statedb, receipts, usedGas, false); err != nil {
blockchain.reportBlock(block, receipts, err)
return err
}
Expand Down
2 changes: 1 addition & 1 deletion core/chain_makers.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func (b *BlockGen) Difficulty() *big.Int {
func (b *BlockGen) SetParentBeaconRoot(root common.Hash) {
b.header.ParentBeaconRoot = &root
var (
blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase, nil)
blockContext = NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase)
vmenv = vm.NewEVM(blockContext, vm.TxContext{}, b.statedb, b.cm.config, vm.Config{})
)
ProcessBeaconBlockRoot(root, vmenv, b.statedb)
Expand Down
17 changes: 3 additions & 14 deletions core/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ package core
import (
"math/big"

"github.com/ethereum/go-ethereum/core/state"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
Expand All @@ -40,9 +38,8 @@ type ChainContext interface {
GetHeader(common.Hash, uint64) *types.Header
}

// NewEVMBlockContext creates a new context for use in the EVM. If witness is non-nil, the context sources block hashes
// for the BLOCKHASH opcode from the witness.
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address, witness *state.Witness) vm.BlockContext {
// NewEVMBlockContext creates a new context for use in the EVM.
func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common.Address) vm.BlockContext {
var (
beneficiary common.Address
baseFee *big.Int
Expand All @@ -65,18 +62,10 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
if header.Difficulty.Sign() == 0 {
random = &header.MixDigest
}
var getHash vm.GetHashFunc
if witness != nil {
getHash = func(n uint64) common.Hash {
return witness.BlockHash(n)
}
} else {
getHash = GetHashFn(header, chain)
}
return vm.BlockContext{
CanTransfer: CanTransfer,
Transfer: Transfer,
GetHash: getHash,
GetHash: GetHashFn(header, chain),
Coinbase: beneficiary,
BlockNumber: new(big.Int).Set(header.Number),
Time: header.Time,
Expand Down
6 changes: 3 additions & 3 deletions core/state/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ type Trie interface {
// be created with new root and updated trie database for following usage
Commit(collectLeaf bool) (common.Hash, *trienode.NodeSet)

// AccessList returns a map of path->blob containing all trie nodes that have
// been accessed.
AccessList() map[string][]byte
// Witness returns a set containing all trie nodes that have been accessed.
// The returned map could be nil if the witness is empty.
Witness() map[string]struct{}

// NodeIterator returns an iterator that returns nodes of the trie. Iteration
// starts at the key after the given start key. And error will be returned
Expand Down
Loading

0 comments on commit e1dd112

Please sign in to comment.