From 6e0e7aa70f2ef473f64cb050fd661a80d847bfaf Mon Sep 17 00:00:00 2001 From: 0g-wh Date: Thu, 7 Aug 2025 05:44:51 +0000 Subject: [PATCH] build block with gas limit --- core/block_validator.go | 31 ++++++++------ core/blockchain.go | 95 +++++++++++++++++++++++++++++++++++------ core/blockchain_test.go | 4 +- core/stateless.go | 5 ++- core/types.go | 3 +- eth/catalyst/api.go | 15 ++++--- miner/worker.go | 10 +++-- 7 files changed, 119 insertions(+), 44 deletions(-) diff --git a/core/block_validator.go b/core/block_validator.go index 591e472bc1..30e7962aa9 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -119,13 +119,14 @@ 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. -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 { - return errors.New("nil ProcessResult value") + return nil, errors.New("nil ProcessResult value") } header := block.Header() 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. // For valid blocks this should always validate to true. @@ -135,33 +136,35 @@ func (v *BlockValidator) ValidateState(block *types.Block, statedb *state.StateD // everything. rbloom := types.MergeBloom(res.Receipts) 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 // provided through the witness, rather the cross validator needs to return it. if stateless { - return nil + return nil, nil } // The receipt Trie's root (R = (Tr [[H1, R1], ... [Hn, Rn]])) receiptSha := types.DeriveSha(res.Receipts, trie.NewStackTrie(nil)) 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. if header.RequestsHash != nil { reqhash := types.CalcRequestsHash(res.Requests) 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 { - 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 - // 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 nil + // Get the actual state root from the statedb + actualRoot := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)) + + // Create a new header with the actual state root + newHeader := *header + newHeader.Root = actualRoot + + return &newHeader, nil } // CalcGasLimit computes the gas limit of the next block after parent. It aims diff --git a/core/blockchain.go b/core/blockchain.go index 6667f64911..8e4ae0266b 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -1966,12 +1966,16 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s ptime := time.Since(pstart) 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) return nil, err } 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 // that now. Self validation should *never* run in production, it's more of // a tight integration to enable running *all* consensus tests through the @@ -1979,25 +1983,25 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s // various invalid chain states/behaviors being contained in those tests. xvstart := time.Now() if witness := statedb.Witness(); witness != nil && bc.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 - context := block.Header() + context := newBlock.Header() context.Root = 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 crossStateRoot, crossReceiptRoot, err := ExecuteStateless(bc.chainConfig, bc.vmConfig, task, witness) if 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 crossStateRoot != newBlock.Root() { + return nil, fmt.Errorf("stateless self-validation root mismatch (cross: %x local: %x)", crossStateRoot, newBlock.Root()) } - if crossReceiptRoot != block.ReceiptHash() { - return nil, fmt.Errorf("stateless self-validation receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, block.ReceiptHash()) + if crossReceiptRoot != newBlock.ReceiptHash() { + return nil, fmt.Errorf("stateless self-validation receipt root mismatch (cross: %x local: %x)", crossReceiptRoot, newBlock.ReceiptHash()) } } xvtime := time.Since(xvstart) @@ -2028,9 +2032,9 @@ func (bc *BlockChain) processBlock(block *types.Block, statedb *state.StateDB, s ) if !setHead { // Don't set the head, only insert the block - err = bc.writeBlockWithState(block, res.Receipts, statedb) + err = bc.writeBlockWithState(newBlock, res.Receipts, statedb) } 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 { return nil, err @@ -2418,14 +2422,77 @@ 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 // updating. It relies on the additional SetCanonical call to finalize the entire // 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() { - return nil, errChainStopped + return nil, nil, errChainStopped } defer bc.chainmu.Unlock() - witness, _, err := bc.insertChain(types.Blocks{block}, false, makeWitness) - return witness, err + // Process the block to get the new header with actual state root + 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.vmConfig) + if err != nil { + return nil, nil, err + } + + newHeader, err := bc.validator.ValidateState(block, statedb, res, false) + log.Info("New Header", "stateroot", newHeader.Root) + if err != nil { + return nil, nil, err + } + + // Create a new block with the updated header that contains the correct state root + log.Info("Block", "hash", block.Hash(), "number", block.Number()) + updatedBlock := block.WithSeal(newHeader) + // Print the hash of the updated block for debugging purposes + log.Info("Updated Block Hash", "hash", updatedBlock.Hash()) + + // If the block hash has changed, we need to update the hash-to-number mapping + if updatedBlock.Hash() != block.Hash() { + log.Info("Block hash changed, updating hash-to-number mapping", + "old_hash", block.Hash(), "new_hash", updatedBlock.Hash(), "number", updatedBlock.Number()) + + // Write the updated hash-to-number mapping and remove the old one + batch := bc.db.NewBatch() + rawdb.WriteHeaderNumber(batch, updatedBlock.Hash(), updatedBlock.NumberU64()) + rawdb.DeleteHeaderNumber(batch, block.Hash()) + + // Also clean up old receipts and body data if they exist + rawdb.DeleteReceipts(batch, block.Hash(), block.NumberU64()) + rawdb.DeleteBody(batch, block.Hash(), block.NumberU64()) + rawdb.DeleteHeader(batch, block.Hash(), block.NumberU64()) + + if err := batch.Write(); err != nil { + return nil, nil, fmt.Errorf("failed to update hash-to-number mapping: %w", err) + } + } + + // Write the block with state + err = bc.writeBlockWithState(updatedBlock, 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(updatedBlock.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 diff --git a/core/blockchain_test.go b/core/blockchain_test.go index 134deee237..5e2177d7ab 100644 --- a/core/blockchain_test.go +++ b/core/blockchain_test.go @@ -161,7 +161,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error { blockchain.reportBlock(block, res, err) return err } - err = blockchain.validator.ValidateState(block, statedb, res, false) + _, err = blockchain.validator.ValidateState(block, statedb, res, false) if err != nil { blockchain.reportBlock(block, res, err) return err @@ -3425,7 +3425,7 @@ func testSetCanonical(t *testing.T, scheme string) { gen.AddTx(tx) }) for _, block := range side { - _, err := chain.InsertBlockWithoutSetHead(block, false) + _, _, err := chain.InsertBlockWithoutSetHead(block, false) if err != nil { t.Fatalf("Failed to insert into chain: %v", err) } diff --git a/core/stateless.go b/core/stateless.go index d21a62b4a5..f59996769e 100644 --- a/core/stateless.go +++ b/core/stateless.go @@ -70,11 +70,12 @@ func ExecuteStateless(config *params.ChainConfig, vmconfig vm.Config, block *typ if err != nil { 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 } // 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())) + stateRoot := newHeader.Root return stateRoot, receiptRoot, nil } diff --git a/core/types.go b/core/types.go index bed20802ab..2596168425 100644 --- a/core/types.go +++ b/core/types.go @@ -32,7 +32,8 @@ 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 + // 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. diff --git a/eth/catalyst/api.go b/eth/catalyst/api.go index 54a3ef909d..5f6ac5863d 100644 --- a/eth/catalyst/api.go +++ b/eth/catalyst/api.go @@ -312,7 +312,7 @@ func (api *ConsensusAPI) forkchoiceUpdated(update engine.ForkchoiceStateV1, payl api.forkchoiceLock.Lock() defer api.forkchoiceLock.Unlock() - log.Trace("Engine API request received", "method", "ForkchoiceUpdated", "head", update.HeadBlockHash, "finalized", update.FinalizedBlockHash, "safe", update.SafeBlockHash) + log.Info("Engine API request received", "method", "ForkchoiceUpdated", "head", update.HeadBlockHash, "finalized", update.FinalizedBlockHash, "safe", update.SafeBlockHash) if update.HeadBlockHash == (common.Hash{}) { log.Warn("Forkchoice requested update to zero hash") return engine.STATUS_INVALID, nil // TODO(karalabe): Why does someone send us this? @@ -836,7 +836,7 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe api.newPayloadLock.Lock() defer api.newPayloadLock.Unlock() - log.Trace("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash) + log.Info("Engine API request received", "method", "NewPayload", "number", params.Number, "hash", params.BlockHash) block, err := engine.ExecutableDataToBlock(params, versionedHashes, beaconRoot, requests) if err != nil { bgu := "nil" @@ -876,11 +876,11 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe // If we already have the block locally, ignore the entire execution and just // return a fake success. - if block := api.eth.BlockChain().GetBlockByHash(params.BlockHash); block != nil { + /*if block := api.eth.BlockChain().GetBlockByHash(params.BlockHash); block != nil { log.Warn("Ignoring already known beacon payload", "number", params.Number, "hash", params.BlockHash, "age", common.PrettyAge(time.Unix(int64(block.Time()), 0))) hash := block.Hash() return engine.PayloadStatusV1{Status: engine.VALID, LatestValidHash: &hash}, nil - } + }*/ // If this block was rejected previously, keep rejecting it if res := api.checkInvalidAncestor(block.Hash(), block.Hash()); res != nil { return *res, nil @@ -911,8 +911,8 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe log.Warn("State not available, ignoring new payload") return engine.PayloadStatusV1{Status: engine.ACCEPTED}, nil } - log.Trace("Inserting block without sethead", "hash", block.Hash(), "number", block.Number()) - proofs, err := api.eth.BlockChain().InsertBlockWithoutSetHead(block, witness) + log.Info("Inserting block without sethead", "hash", block.Hash(), "number", block.Number()) + newHeader, proofs, err := api.eth.BlockChain().InsertBlockWithoutSetHead(block, witness) if err != nil { log.Warn("NewPayload: inserting block failed", "error", err) @@ -923,7 +923,8 @@ func (api *ConsensusAPI) newPayload(params engine.ExecutableData, versionedHashe return api.invalid(err, parent.Header()), nil } - hash := block.Hash() + hash := newHeader.Hash() + log.Info("NewHeader", "hash", hash, "root", newHeader.Root) // If witness collection was requested, inject that into the result too var ow *hexutil.Bytes diff --git a/miner/worker.go b/miner/worker.go index 11c4976c1e..efd2bc5747 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -36,6 +36,7 @@ import ( "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" ethparams "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" "github.com/holiman/uint256" ) @@ -101,6 +102,8 @@ func (miner *Miner) generateWork(params *generateParams, witness bool) *newPaylo if err != nil { return &newPayloadResult{err: err} } + + // Use gas limit for block creation instead of executing transactions if !params.noTxs { interrupt := new(atomic.Int32) timer := time.AfterFunc(miner.config.Recommit, func() { @@ -164,10 +167,9 @@ func (miner *Miner) generateWork(params *generateParams, witness bool) *newPaylo work.header.RequestsHash = &reqHash } - block, err := miner.engine.FinalizeAndAssemble(miner.chain, work.header, work.state, &body, work.receipts) - if err != nil { - return &newPayloadResult{err: err} - } + // Directly construct the block without calling FinalizeAndAssemble + // This returns a block with the current header, body, and receipts + block := types.NewBlock(work.header, &body, work.receipts, trie.NewStackTrie(nil)) return &newPayloadResult{ block: block, fees: totalFees(block, work.receipts),