diff --git a/cmd/keeper/main.go b/cmd/keeper/main.go index df6881acbf..15fea02243 100644 --- a/cmd/keeper/main.go +++ b/cmd/keeper/main.go @@ -53,17 +53,9 @@ func main() { } vmConfig := vm.Config{} - crossStateRoot, crossReceiptRoot, err := core.ExecuteStateless(context.Background(), chainConfig, vmConfig, payload.Block, payload.Witness) + _, _, err = core.ExecuteStateless(context.Background(), chainConfig, vmConfig, payload.Block, payload.Witness, true) if 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..5c8cfce743 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -53,16 +53,19 @@ 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 + + // Chain-dependent checks: skip in stateless mode (no blockchain available). + if v.bc != nil { + // Check whether the block is already imported. + if v.bc.HasBlockAndState(block.Hash(), block.NumberU64()) { + return ErrKnownBlock + } + 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) @@ -111,12 +114,14 @@ func (v *BlockValidator) ValidateBody(block *types.Block) error { } } - // 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 + // Ancestor block must be known (skip in stateless mode). + if v.bc != nil { + 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 consensus.ErrPrunedAncestor } return nil } diff --git a/core/blockchain.go b/core/blockchain.go index 7b5a910b7a..1df154f8b8 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -2260,7 +2260,7 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot 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) + crossStateRoot, crossReceiptRoot, err := ExecuteStateless(ctx, bc.chainConfig, bc.cfg.VmConfig, task, witness, false) if err != nil { return nil, fmt.Errorf("stateless self-validation failed: %v", err) } diff --git a/core/stateless.go b/core/stateless.go index 86d4dc304b..f9274a19d7 100644 --- a/core/stateless.go +++ b/core/stateless.go @@ -18,6 +18,7 @@ package core import ( "context" + "fmt" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/lru" @@ -27,7 +28,6 @@ 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" @@ -42,15 +42,7 @@ import ( // - 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, validateHeader bool) (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.NewDatabase(triedb.NewDatabase(memdb, triedb.HashDefaults), state.NewCodeDB(memdb))) @@ -67,12 +59,23 @@ func ExecuteStateless(ctx context.Context, config *params.ChainConfig, vmconfig processor := NewStateProcessor(chain) validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block + if validateHeader { + if err := beacon.New(ethash.NewFaker()).VerifyHeader(chain, block.Header()); err != nil { + return common.Hash{}, common.Hash{}, fmt.Errorf("error verifying header in stateless validation: %w", err) + } + + validator := NewBlockValidator(config, nil) + if err := validator.ValidateBody(block); err != nil { + return common.Hash{}, common.Hash{}, fmt.Errorf("error validating body in stateless validation: %w", err) + } + } + // Run the stateless blocks processing and self-validate certain fields res, err := processor.Process(ctx, block, db, vmconfig) if err != nil { return common.Hash{}, common.Hash{}, err } - if err = validator.ValidateState(block, db, res, true); err != nil { + if err = validator.ValidateState(block, db, res, validateHeader); err != nil { return common.Hash{}, common.Hash{}, err } // Almost everything validated, but receipt and state root needs to be returned diff --git a/eth/catalyst/witness.go b/eth/catalyst/witness.go index fe75c66908..b06dd3e6ad 100644 --- a/eth/catalyst/witness.go +++ b/eth/catalyst/witness.go @@ -284,7 +284,7 @@ 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) + stateRoot, receiptRoot, err := core.ExecuteStateless(context.Background(), api.config(), vm.Config{}, block, witness, false) if err != nil { log.Warn("ExecuteStatelessPayload: execution failed", "err", err) errorMsg := err.Error()