This commit is contained in:
0g-wh 2025-07-15 06:22:46 +00:00
parent 12b4131ff7
commit cf3a1c3cd1
7 changed files with 84 additions and 36 deletions

View file

@ -119,13 +119,14 @@ 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.
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, res *ProcessResult, stateless bool) error { // Returns a new header with the actual state root instead of comparing with the original header.
func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateDB, res *ProcessResult, stateless bool) (*types.Header, error) {
if res == nil { if res == nil {
return errors.New("nil ProcessResult value") return nil, errors.New("nil ProcessResult value")
} }
header := block.Header() header := block.Header()
if block.GasUsed() != res.GasUsed { if block.GasUsed() != res.GasUsed {
return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), res.GasUsed) return nil, fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), res.GasUsed)
} }
// 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.
@ -135,33 +136,35 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD
// everything. // everything.
rbloom := types.MergeBloom(res.Receipts) rbloom := types.MergeBloom(res.Receipts)
if rbloom != header.Bloom { if rbloom != header.Bloom {
return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom) return nil, fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
} }
// In stateless mode, return early because the receipt and state root are not // 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. // provided through the witness, rather the cross validator needs to return it.
if stateless { if stateless {
return nil return nil, 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(res.Receipts, trie.NewStackTrie(nil)) receiptSha := types.DeriveSha(res.Receipts, trie.NewStackTrie(nil))
if receiptSha != header.ReceiptHash { if receiptSha != header.ReceiptHash {
return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha) return nil, fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
} }
// Validate the parsed requests match the expected header value. // Validate the parsed requests match the expected header value.
if header.RequestsHash != nil { if header.RequestsHash != nil {
reqhash := types.CalcRequestsHash(res.Requests) reqhash := types.CalcRequestsHash(res.Requests)
if reqhash != *header.RequestsHash { if reqhash != *header.RequestsHash {
return fmt.Errorf("invalid requests hash (remote: %x local: %x)", *header.RequestsHash, reqhash) return nil, fmt.Errorf("invalid requests hash (remote: %x local: %x)", *header.RequestsHash, reqhash)
} }
} else if res.Requests != nil { } else if res.Requests != nil {
return errors.New("block has requests before prague fork") return nil, errors.New("block has requests before prague fork")
} }
// Validate the state root against the received state root and throw // Get the actual state root from the statedb
// an error if they don't match. actualRoot := statedb.IntermediateRoot(v.config.IsEIP158(header.Number))
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()) // Create a new header with the actual state root
} newHeader := *header
return nil newHeader.Root = actualRoot
return &newHeader, 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

@ -210,7 +210,10 @@ func testHeaderVerificationForMerging(t *testing.T, isClique bool) {
t.Fatalf("post-block %d: unexpected result returned: %v", i, result) t.Fatalf("post-block %d: unexpected result returned: %v", i, result)
case <-time.After(25 * time.Millisecond): case <-time.After(25 * time.Millisecond):
} }
chain.InsertBlockWithoutSetHead(postBlocks[i], false) _, _, err := chain.InsertBlockWithoutSetHead(postBlocks[i], false)
if err != nil {
t.Errorf("Failed to insert block: %v", err)
}
} }
// Verify the blocks with pre-merge blocks and post-merge blocks // Verify the blocks with pre-merge blocks and post-merge blocks

View file

@ -2047,12 +2047,16 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
ptime := time.Since(pstart) ptime := time.Since(pstart)
vstart := time.Now() vstart := time.Now()
if err := bc.validator.ValidateState(block, statedb, res, false); err != nil { newHeader, err := bc.validator.ValidateState(block, statedb, res, false)
if err != nil {
bc.reportBlock(block, res, err) bc.reportBlock(block, res, err)
return nil, err return nil, err
} }
vtime := time.Since(vstart) vtime := time.Since(vstart)
// Create a new block with the updated header
newBlock := types.NewBlockWithHeader(newHeader).WithBody(*block.Body())
// If witnesses was generated and stateless self-validation requested, do // If witnesses was generated and stateless self-validation requested, do
// that now. Self validation should *never* run in production, it's more of // that now. Self validation should *never* run in production, it's more of
// a tight integration to enable running *all* consensus tests through the // a tight integration to enable running *all* consensus tests through the
@ -2060,25 +2064,25 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
// various invalid chain states/behaviors being contained in those tests. // various invalid chain states/behaviors being contained in those tests.
xvstart := time.Now() xvstart := time.Now()
if witness := statedb.Witness(); witness != nil && bc.cfg.VmConfig.StatelessSelfValidation { if witness := statedb.Witness(); witness != nil && bc.cfg.VmConfig.StatelessSelfValidation {
log.Warn("Running stateless self-validation", "block", block.Number(), "hash", block.Hash()) log.Warn("Running stateless self-validation", "block", newBlock.Number(), "hash", newBlock.Hash())
// Remove critical computed fields from the block to force true recalculation // Remove critical computed fields from the block to force true recalculation
context := block.Header() context := newBlock.Header()
context.Root = common.Hash{} context.Root = common.Hash{}
context.ReceiptHash = common.Hash{} context.ReceiptHash = common.Hash{}
task := types.NewBlockWithHeader(context).WithBody(*block.Body()) task := types.NewBlockWithHeader(context).WithBody(*newBlock.Body())
// Run the stateless self-cross-validation // Run the stateless self-cross-validation
crossStateRoot, crossReceiptRoot, err := ExecuteStateless(bc.chainConfig, bc.cfg.VmConfig, task, witness) crossStateRoot, crossReceiptRoot, err := ExecuteStateless(bc.chainConfig, bc.cfg.VmConfig, task, witness)
if err != nil { if err != nil {
return nil, fmt.Errorf("stateless self-validation failed: %v", err) return nil, fmt.Errorf("stateless self-validation failed: %v", err)
} }
if crossStateRoot != block.Root() { if crossStateRoot != newBlock.Root() {
return nil, fmt.Errorf("stateless self-validation root mismatch (cross: %x local: %x)", crossStateRoot, block.Root()) return nil, fmt.Errorf("stateless self-validation root mismatch (cross: %x local: %x)", crossStateRoot, newBlock.Root())
} }
if crossReceiptRoot != block.ReceiptHash() { if crossReceiptRoot != newBlock.ReceiptHash() {
return nil, fmt.Errorf("stateless self-validation receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, block.ReceiptHash()) return nil, fmt.Errorf("stateless self-validation receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, newBlock.ReceiptHash())
} }
} }
xvtime := time.Since(xvstart) xvtime := time.Since(xvstart)
@ -2109,9 +2113,9 @@ func (bc *BlockChain) processBlock(parentRoot common.Hash, block *types.Block, s
) )
if !setHead { if !setHead {
// Don't set the head, only insert the block // Don't set the head, only insert the block
err = bc.writeBlockWithState(block, res.Receipts, statedb) err = bc.writeBlockWithState(newBlock, res.Receipts, statedb)
} else { } else {
status, err = bc.writeBlockAndSetHead(block, res.Receipts, res.Logs, statedb, false) status, err = bc.writeBlockAndSetHead(newBlock, res.Receipts, res.Logs, statedb, false)
} }
if err != nil { if err != nil {
return nil, err return nil, err
@ -2509,14 +2513,50 @@ func (bc *BlockChain) reorg(oldHead *types.Header, newHead *types.Header) error
// The key difference between the InsertChain is it won't do the canonical chain // The key difference between the InsertChain is it won't do the canonical chain
// updating. It relies on the additional SetCanonical call to finalize the entire // updating. It relies on the additional SetCanonical call to finalize the entire
// procedure. // procedure.
func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block, makeWitness bool) (*stateless.Witness, error) { // Returns the new header with actual state root and witness.
func (bc *BlockChain) InsertBlockWithoutSetHead(block *types.Block, makeWitness bool) (*types.Header, *stateless.Witness, error) {
if !bc.chainmu.TryLock() { if !bc.chainmu.TryLock() {
return nil, errChainStopped return nil, nil, errChainStopped
} }
defer bc.chainmu.Unlock() defer bc.chainmu.Unlock()
witness, _, err := bc.insertChain(types.Blocks{block}, false, makeWitness) // Process the block to get the new header with actual state root
return witness, err parent := bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
if parent == nil {
return nil, nil, consensus.ErrUnknownAncestor
}
statedb, err := state.New(parent.Root, bc.statedb)
if err != nil {
return nil, nil, err
}
res, err := bc.processor.Process(block, statedb, bc.cfg.VmConfig)
if err != nil {
return nil, nil, err
}
newHeader, err := bc.validator.ValidateState(block, statedb, res, false)
if err != nil {
return nil, nil, err
}
// Write the block with state
err = bc.writeBlockWithState(block, res.Receipts, statedb)
if err != nil {
return nil, nil, err
}
// Generate witness if requested
var witness *stateless.Witness
if makeWitness && bc.chainConfig.IsByzantium(block.Number()) {
witness, err = stateless.NewWitness(block.Header(), bc)
if err != nil {
return nil, nil, err
}
}
return newHeader, witness, nil
} }
// SetCanonical rewinds the chain to set the new head block as the specified // SetCanonical rewinds the chain to set the new head block as the specified

View file

@ -165,7 +165,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
blockchain.reportBlock(block, res, err) blockchain.reportBlock(block, res, err)
return err return err
} }
err = blockchain.validator.ValidateState(block, statedb, res, false) _, err = blockchain.validator.ValidateState(block, statedb, res, false)
if err != nil { if err != nil {
blockchain.reportBlock(block, res, err) blockchain.reportBlock(block, res, err)
return err return err
@ -3456,7 +3456,7 @@ func testSetCanonical(t *testing.T, scheme string) {
gen.AddTx(tx) gen.AddTx(tx)
}) })
for _, block := range side { for _, block := range side {
_, err := chain.InsertBlockWithoutSetHead(block, false) _, _, err := chain.InsertBlockWithoutSetHead(block, false)
if err != nil { if err != nil {
t.Fatalf("Failed to insert into chain: %v", err) t.Fatalf("Failed to insert into chain: %v", err)
} }

View file

@ -70,11 +70,12 @@ func ExecuteStateless(config *params.ChainConfig, vmconfig vm.Config, block *typ
if err != nil { if err != nil {
return common.Hash{}, common.Hash{}, err return common.Hash{}, common.Hash{}, err
} }
if err = validator.ValidateState(block, db, res, true); err != nil { newHeader, err := validator.ValidateState(block, db, res, true)
if err != nil {
return common.Hash{}, common.Hash{}, err return common.Hash{}, common.Hash{}, err
} }
// Almost everything validated, but receipt and state root needs to be returned // Almost everything validated, but receipt and state root needs to be returned
receiptRoot := types.DeriveSha(res.Receipts, trie.NewStackTrie(nil)) receiptRoot := types.DeriveSha(res.Receipts, trie.NewStackTrie(nil))
stateRoot := db.IntermediateRoot(config.IsEIP158(block.Number())) stateRoot := newHeader.Root
return stateRoot, receiptRoot, nil return stateRoot, receiptRoot, nil
} }

View file

@ -32,7 +32,8 @@ type Validator interface {
ValidateBody(block *types.Block) error ValidateBody(block *types.Block) error
// ValidateState validates the given statedb and optionally the process result. // ValidateState validates the given statedb and optionally the process result.
ValidateState(block *types.Block, state *state.StateDB, res *ProcessResult, stateless bool) error // Returns a new header with the actual state root instead of comparing with the original header.
ValidateState(block *types.Block, state *state.StateDB, res *ProcessResult, stateless bool) (*types.Header, error)
} }
// Prefetcher is an interface for pre-caching transaction signatures and state. // Prefetcher is an interface for pre-caching transaction signatures and state.

View file

@ -748,7 +748,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
return engine.PayloadStatusV1{Status: engine.ACCEPTED}, nil return engine.PayloadStatusV1{Status: engine.ACCEPTED}, nil
} }
log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number()) log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number())
proofs, err := api.eth.BlockChain().InsertBlockWithoutSetHead(block, witness) newHeader, proofs, err := api.eth.BlockChain().InsertBlockWithoutSetHead(block, witness)
if err != nil { if err != nil {
log.Warn("NewPayload: inserting block failed", "error", err) log.Warn("NewPayload: inserting block failed", "error", err)
@ -759,7 +759,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe
return api.invalid(err, parent.Header()), nil return api.invalid(err, parent.Header()), nil
} }
hash := block.Hash() hash := newHeader.Hash()
// If witness collection was requested, inject that into the result too // If witness collection was requested, inject that into the result too
var ow *hexutil.Bytes var ow *hexutil.Bytes