From 3880f712f14c05cefd654e87820ece110c061625 Mon Sep 17 00:00:00 2001 From: Kevaundray Wedderburn Date: Fri, 13 Mar 2026 18:19:58 +0000 Subject: [PATCH] initial commit --- cmd/keeper/main.go | 11 +--------- core/block_validator.go | 33 +++------------------------- core/blockchain.go | 22 ++++--------------- core/blockchain_insert.go | 31 ++++++++++++++++++++++----- core/blockchain_test.go | 2 +- core/stateless.go | 45 +++++++++++++++++---------------------- core/types.go | 2 +- eth/catalyst/witness.go | 5 ++--- 8 files changed, 58 insertions(+), 93 deletions(-) diff --git a/cmd/keeper/main.go b/cmd/keeper/main.go index df6881acbf..14316e6659 100644 --- a/cmd/keeper/main.go +++ b/cmd/keeper/main.go @@ -53,17 +53,8 @@ func main() { } vmConfig := vm.Config{} - crossStateRoot, crossReceiptRoot, err := core.ExecuteStateless(context.Background(), chainConfig, vmConfig, payload.Block, payload.Witness) - if err != nil { + if err := core.ExecuteStateless(context.Background(), chainConfig, vmConfig, payload.Block, payload.Witness); err != nil { fmt.Fprintf(os.Stderr, "stateless self-validation failed: %v\n", err) os.Exit(10) } - if crossStateRoot != payload.Block.Root() { - fmt.Fprintf(os.Stderr, "stateless self-validation root mismatch (cross: %x local: %x)\n", crossStateRoot, payload.Block.Root()) - os.Exit(11) - } - if crossReceiptRoot != payload.Block.ReceiptHash() { - fmt.Fprintf(os.Stderr, "stateless self-validation receipt root mismatch (cross: %x local: %x)\n", crossReceiptRoot, payload.Block.ReceiptHash()) - os.Exit(12) - } } diff --git a/core/block_validator.go b/core/block_validator.go index 008444fbbc..0b2d48c2dc 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -20,7 +20,6 @@ import ( "errors" "fmt" - "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -33,16 +32,11 @@ import ( // BlockValidator implements Validator. type BlockValidator struct { config *params.ChainConfig // Chain configuration options - bc *BlockChain // Canonical block chain } // NewBlockValidator returns a new block validator which is safe for re-use -func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain) *BlockValidator { - validator := &BlockValidator{ - config: config, - bc: blockchain, - } - return validator +func NewBlockValidator(config *params.ChainConfig) *BlockValidator { + return &BlockValidator{config: config} } // ValidateBody validates the given block's uncles and verifies the block @@ -53,17 +47,9 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { if v.config.IsOsaka(block.Number(), block.Time()) && block.Size() > params.MaxBlockSize { return ErrBlockOversized } - // Check whether the block is already imported. - if v.bc.HasBlockAndState(block.Hash(), block.NumberU64()) { - return ErrKnownBlock - } - // 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.bc.engine.VerifyUncles(v.bc, block); err != nil { - return err - } if hash := types.CalcUncleHash(block.Uncles()); hash != header.UncleHash { return fmt.Errorf("uncle root hash mismatch (header value %x, calculated %x)", header.UncleHash, hash) } @@ -110,20 +96,12 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { return errors.New("data blobs present in block body") } } - - // Ancestor block must be known. - if !v.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { - if !v.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) { - return consensus.ErrUnknownAncestor - } - return consensus.ErrPrunedAncestor - } return nil } // 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. -func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, res *ProcessResult, stateless bool) error { +func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, res *ProcessResult) error { if res == nil { return errors.New("nil ProcessResult value") } @@ -141,11 +119,6 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD if rbloom != header.Bloom { 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(res.Receipts, trie.NewStackTrie(nil)) if receiptSha != header.ReceiptHash { diff --git a/core/blockchain.go b/core/blockchain.go index 8df2365072..c4ca7fea15 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -423,7 +423,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine, return nil, err } bc.flushInterval.Store(int64(cfg.TrieTimeLimit)) - bc.validator = NewBlockValidator(chainConfig, bc) + bc.validator = NewBlockValidator(chainConfig) bc.prefetcher = newStatePrefetcher(chainConfig, bc.hc) bc.processor = NewStateProcessor(bc.hc) @@ -1908,7 +1908,7 @@ func (bc *BlockChain) insertChain(ctx context.Context, chain types.Blocks, setHe defer close(abort) // Peek the error for the first block to decide the directing import logic - it := newInsertIterator(chain, results, bc.validator) + it := newInsertIterator(chain, results, bc.validator, bc) block, err := it.next() // Left-trim all the known blocks that don't need to build snapshot @@ -2259,7 +2259,7 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, vstart := time.Now() _, _, spanEnd = telemetry.StartSpan(ctx, "bc.validator.ValidateState") - err = bc.validator.ValidateState(block, statedb, res, false) + err = bc.validator.ValidateState(block, statedb, res) spanEnd(&err) if err != nil { bc.reportBadBlock(block, res, err) @@ -2276,24 +2276,10 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash, if witness := statedb.Witness(); witness != nil && config.StatelessSelfValidation { log.Warn("Running stateless self-validation", "block", block.Number(), "hash", block.Hash()) - // Remove critical computed fields from the block to force true recalculation - context := block.Header() - context.Root = common.Hash{} - context.ReceiptHash = common.Hash{} - - task := types.NewBlockWithHeader(context).WithBody(*block.Body()) - // Run the stateless self-cross-validation - crossStateRoot, crossReceiptRoot, err := ExecuteStateless(ctx, bc.chainConfig, bc.cfg.VmConfig, task, witness) - if err != nil { + if err := ExecuteStateless(ctx, bc.chainConfig, bc.cfg.VmConfig, block, witness); err != nil { return nil, fmt.Errorf("stateless self-validation failed: %v", err) } - if crossStateRoot != block.Root() { - return nil, fmt.Errorf("stateless self-validation root mismatch (cross: %x local: %x)", crossStateRoot, block.Root()) - } - if crossReceiptRoot != block.ReceiptHash() { - return nil, fmt.Errorf("stateless self-validation receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, block.ReceiptHash()) - } } var ( diff --git a/core/blockchain_insert.go b/core/blockchain_insert.go index 07a250a1bb..6a981fcfa7 100644 --- a/core/blockchain_insert.go +++ b/core/blockchain_insert.go @@ -21,6 +21,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/mclock" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" ) @@ -95,19 +96,21 @@ type insertIterator struct { results <-chan error // Verification result sink from the consensus engine errors []error // Header verification errors for the blocks - index int // Current offset of the iterator - validator Validator // Validator to run if verification succeeds + index int // Current offset of the iterator + validator Validator // Validator to run if verification succeeds + bc *BlockChain // Blockchain for admission checks } // newInsertIterator creates a new iterator based on the given blocks, which are // assumed to be a contiguous chain. -func newInsertIterator(chain types.Blocks, results <-chan error, validator Validator) *insertIterator { +func newInsertIterator(chain types.Blocks, results <-chan error, validator Validator, bc *BlockChain) *insertIterator { return &insertIterator{ chain: chain, results: results, errors: make([]error, 0, len(chain)), index: -1, validator: validator, + bc: bc, } } @@ -127,8 +130,26 @@ func (it *insertIterator) next() (*types.Block, error) { if it.errors[it.index] != nil { return it.chain[it.index], it.errors[it.index] } - // Block header valid, run body validation and return - return it.chain[it.index], it.validator.ValidateBody(it.chain[it.index]) + + block := it.chain[it.index] + + // Skip blocks we've already imported and fully processed. + if it.bc.HasBlockAndState(block.Hash(), block.NumberU64()) { + return block, ErrKnownBlock + } + // Verify uncle blocks against chain history (pre-mereg only) + if err := it.bc.engine.VerifyUncles(it.bc, block); err != nil { + return block, err + } + // Ensure the parent block is known and its state is available. + if !it.bc.HasBlockAndState(block.ParentHash(), block.NumberU64()-1) { + if !it.bc.HasBlock(block.ParentHash(), block.NumberU64()-1) { + return block, consensus.ErrUnknownAncestor + } + return block, consensus.ErrPrunedAncestor + } + // Validate the block body against header + return block, it.validator.ValidateBody(block) } // previous returns the previous header that was being processed, or nil. diff --git a/core/blockchain_test.go b/core/blockchain_test.go index ce592f0267..0d90c62bcf 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -166,7 +166,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { blockchain.reportBadBlock(block, res, err) return err } - err = blockchain.validator.ValidateState(block, statedb, res, false) + err = blockchain.validator.ValidateState(block, statedb, res) if err != nil { blockchain.reportBadBlock(block, res, err) return err diff --git a/core/stateless.go b/core/stateless.go index 88d8ed8138..0eb236676e 100644 --- a/core/stateless.go +++ b/core/stateless.go @@ -27,56 +27,51 @@ import ( "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/log" "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 state root and receipt root, that -// need the other side to explicitly check. +// ExecuteStateless runs a stateless execution based on a witness, fully +// validating the block including header, body, state root and receipt root. // // 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(ctx context.Context, config *params.ChainConfig, vmconfig vm.Config, block *types.Block, witness *stateless.Witness) (common.Hash, common.Hash, error) { - // Sanity check if the supplied block accidentally contains a set root or - // receipt hash. If so, be very loud, but still continue. - if block.Root() != (common.Hash{}) { - log.Error("stateless runner received state root it's expected to calculate (faulty consensus client)", "block", block.Number()) - } - if block.ReceiptHash() != (common.Hash{}) { - log.Error("stateless runner received receipt root it's expected to calculate (faulty consensus client)", "block", block.Number()) - } +func ExecuteStateless(ctx context.Context, config *params.ChainConfig, vmconfig vm.Config, block *types.Block, witness *stateless.Witness) error { // Create and populate the state database to serve as the stateless backend memdb := witness.MakeHashDB() db, err := state.New(witness.Root(), state.NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), nil)) if err != nil { - return common.Hash{}, common.Hash{}, err + return err } // Create a blockchain that is idle, but can be used to access headers through + engine := beacon.New(ethash.NewFaker()) chain := &HeaderChain{ config: config, chainDb: memdb, headerCache: lru.NewCache[common.Hash, *types.Header](256), - engine: beacon.New(ethash.NewFaker()), + engine: engine, + } + // Verify the block header against the parent header from the witness + if err := engine.VerifyHeader(chain, block.Header()); err != nil { + return err } processor := NewStateProcessor(chain) - validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block + validator := NewBlockValidator(config) - // Run the stateless blocks processing and self-validate certain fields + // Verify the block body (transactions, withdrawals, blob gas) against the header + if err := validator.ValidateBody(block); err != nil { + return err + } + // Run the stateless block processing and self-validate all fields res, err := processor.Process(ctx, block, db, vmconfig) if err != nil { - return common.Hash{}, common.Hash{}, err + return err } - if err = validator.ValidateState(block, db, res, true); err != nil { - return common.Hash{}, common.Hash{}, err + if err = validator.ValidateState(block, db, res); err != nil { + return err } - // Almost everything validated, but receipt and state root needs to be returned - receiptRoot := types.DeriveSha(res.Receipts, trie.NewStackTrie(nil)) - stateRoot := db.IntermediateRoot(config.IsEIP158(block.Number())) - return stateRoot, receiptRoot, nil + return nil } diff --git a/core/types.go b/core/types.go index 87bbfcff58..a7681ce6d2 100644 --- a/core/types.go +++ b/core/types.go @@ -33,7 +33,7 @@ type Validator interface { ValidateBody(block *types.Block) error // ValidateState validates the given statedb and optionally the process result. - ValidateState(block *types.Block, state *state.StateDB, res *ProcessResult, stateless bool) error + ValidateState(block *types.Block, state *state.StateDB, res *ProcessResult) error } // Prefetcher is an interface for pre-caching transaction signatures and state. diff --git a/eth/catalyst/witness.go b/eth/catalyst/witness.go index 14ca29e079..f6a51bfd77 100644 --- a/eth/catalyst/witness.go +++ b/eth/catalyst/witness.go @@ -284,11 +284,10 @@ func (api *ConsensusAPI) executeStatelessPayload(params engine.ExecutableData, v api.lastNewPayloadUpdate.Store(time.Now().Unix()) log.Trace("Executing block statelessly", "number", block.Number(), "hash", params.BlockHash) - stateRoot, receiptRoot, err := core.ExecuteStateless(context.Background(), api.config(), vm.Config{}, block, witness) - if err != nil { + if err := core.ExecuteStateless(context.Background(), api.config(), vm.Config{}, block, witness); err != nil { log.Warn("ExecuteStatelessPayload: execution failed", "err", err) errorMsg := err.Error() return engine.StatelessPayloadStatusV1{Status: engine.INVALID, ValidationError: &errorMsg}, nil } - return engine.StatelessPayloadStatusV1{Status: engine.VALID, StateRoot: stateRoot, ReceiptsRoot: receiptRoot}, nil + return engine.StatelessPayloadStatusV1{Status: engine.VALID, StateRoot: block.Root(), ReceiptsRoot: block.ReceiptHash()}, nil }