go-ethereum/miner/worker.go
Jonny Rhea d6f222a081
core, core/vm: precompile result caching (#35388)
Blocks heavy in precompile calls (e.g. Aztec's proof settlement txs)
spend most of their processing time on operations (ECMUL, pairings, KZG
point evaluation, MODEXP) that the state prefetcher has already computed
and thrown away.

This PR adds a precompile result cache shared between the prefetcher and
block processing (and the miner), following the JumpDestCache pattern.
Note that cached precompiles are keyed by address and input, with
entries partitioned by the active precompile set, so a fork that changes
the behavior behind an address can never be served results from before
it.

---------

Co-authored-by: Gary Rong <garyrong0905@gmail.com>
2026-07-28 10:47:33 +08:00

657 lines
24 KiB
Go

// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package miner
import (
"context"
"errors"
"fmt"
"math/big"
"sync/atomic"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/stateless"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/txpool/txorder"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types/bal"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/internal/telemetry"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
)
var (
errBlockInterruptedByNewHead = errors.New("new head arrived while building block")
errBlockInterruptedByRecommit = errors.New("recommit interrupt while building block")
errBlockInterruptedByTimeout = errors.New("timeout while building block")
errStateReadFailure = errors.New("state read failed while building block")
)
// maxBlobsPerBlock returns the maximum number of blobs per block.
// Users can specify the maximum number of blobs per block if necessary.
func (miner *Miner) maxBlobsPerBlock(time uint64) int {
maxBlobs := eip4844.MaxBlobsPerBlock(miner.chainConfig, time)
if configured := miner.config.MaxBlobsPerBlock; configured != 0 && configured < maxBlobs {
maxBlobs = configured
}
return maxBlobs
}
// environment is the worker's current environment and holds all
// information of the sealing block generation.
type environment struct {
signer types.Signer
state *state.StateDB // apply state changes here
tcount int // tx count in cycle
size uint64 // size of the block we are building
gasPool *core.GasPool // available gas used to pack transactions
coinbase common.Address
evm *vm.EVM
header *types.Header
txs []*types.Transaction
receipts []*types.Receipt
sidecars []*types.BlobTxSidecar
blobs int
bal *bal.ConstructionBlockAccessList
witness *stateless.Witness
}
// txFitsSize reports whether the transaction fits into the block size limit.
func (env *environment) txFitsSize(tx *types.Transaction) bool {
return env.size+tx.Size() < params.MaxBlockSize-maxBlockSizeBufferZone
}
// discard terminates the background threads before discarding it.
func (env *environment) discard() {
env.state.StopPrefetcher()
if env.evm != nil {
env.evm.Release()
}
}
const (
commitInterruptNone int32 = iota
commitInterruptNewHead
commitInterruptResubmit
commitInterruptTimeout
)
// Block size is capped by the protocol at params.MaxBlockSize. When producing blocks, we
// try to say below the size including a buffer zone, this is to avoid going over the
// maximum size with auxiliary data added into the block.
const maxBlockSizeBufferZone = 1_000_000
// newPayloadResult is the result of payload generation.
type newPayloadResult struct {
err error
block *types.Block
fees *big.Int // total block fees
sidecars []*types.BlobTxSidecar // collected blobs of blob transactions
stateDB *state.StateDB // StateDB after executing the transactions
receipts []*types.Receipt // Receipts collected during construction
requests [][]byte // Consensus layer requests collected during block construction
witness *stateless.Witness // Witness is an optional stateless proof
}
// generateParams wraps various settings for generating sealing task.
type generateParams struct {
timestamp uint64 // The timestamp for sealing task
forceTime bool // Flag whether the given timestamp is immutable or not
parentHash common.Hash // Parent block hash, empty means the latest chain head
coinbase common.Address // The fee recipient address for including transaction
random common.Hash // The randomness generated by beacon chain, empty before the merge
withdrawals types.Withdrawals // List of withdrawals to include in block (shanghai field)
beaconRoot *common.Hash // The beacon root (cancun field).
slotNum *uint64 // The slot number (amsterdam field).
targetGasLimit *uint64 // The target gas limit requested by the CL (amsterdam field).
noTxs bool // Flag whether an empty block without any transaction is expected
forceOverrides bool // Flag whether we should overwrite extraData and transactions
overrideExtraData []byte
overrideTxs []*types.Transaction
}
// generateWork generates a sealing block based on the given parameters.
func (miner *Miner) generateWork(ctx context.Context, genParam *generateParams, witness bool) (result *newPayloadResult) {
ctx, span, spanEnd := telemetry.StartSpan(ctx, "miner.generateWork")
defer func() {
if result != nil && result.err == nil {
span.SetAttributes(
telemetry.IntAttribute("txs.count", len(result.block.Transactions())),
telemetry.Int64Attribute("gas.used", int64(result.block.GasUsed())),
telemetry.StringAttribute("fees", result.fees.String()),
)
}
if result != nil {
spanEnd(&result.err)
} else {
spanEnd(nil)
}
}()
work, err := miner.prepareWork(ctx, genParam, witness)
if err != nil {
return &newPayloadResult{err: err}
}
defer work.discard()
// Check withdrawals fit max block size.
// Due to the cap on withdrawal count, this can actually never happen, but we still need to
// check to ensure the CL notices there's a problem if the withdrawal cap is ever lifted.
maxBlockSize := params.MaxBlockSize - maxBlockSizeBufferZone
if genParam.withdrawals.Size() > maxBlockSize {
return &newPayloadResult{err: errors.New("withdrawals exceed max block size")}
}
// Also add size of withdrawals to work block size.
work.size += uint64(genParam.withdrawals.Size())
if !genParam.noTxs {
// If forceOverrides is true and overrideTxs is not empty, commit the override transactions
// otherwise, fill the block with the current transactions from the txpool
if genParam.forceOverrides && len(genParam.overrideTxs) > 0 {
for _, tx := range genParam.overrideTxs {
work.state.SetTxContext(tx.Hash(), work.tcount, uint32(work.tcount+1))
if err := miner.commitTransaction(ctx, work, tx); err != nil {
// all passed transactions HAVE to be valid at this point
return &newPayloadResult{err: err}
}
}
} else {
interrupt := new(atomic.Int32)
timer := time.AfterFunc(miner.config.Recommit, func() {
interrupt.Store(commitInterruptTimeout)
})
defer timer.Stop()
err := miner.fillTransactions(ctx, interrupt, work)
if errors.Is(err, errBlockInterruptedByTimeout) {
log.Warn("Block building is interrupted", "allowance", common.PrettyDuration(miner.config.Recommit))
}
}
}
// Construct the block body, the withdrawal list should never be null
// if Shanghai has been activated.
body := types.Body{
Transactions: work.txs,
Withdrawals: genParam.withdrawals,
}
if !miner.chainConfig.IsShanghai(work.header.Number, work.header.Time) {
if body.Withdrawals != nil {
return &newPayloadResult{err: errors.New("unexpected withdrawals before shanghai")}
}
} else {
if body.Withdrawals == nil {
body.Withdrawals = make([]*types.Withdrawal, 0)
}
}
allLogs := make([]*types.Log, 0)
for _, r := range work.receipts {
allLogs = append(allLogs, r.Logs...)
}
// Collect consensus-layer requests if Prague is enabled.
requests, bal, err := core.PostExecution(ctx, miner.chainConfig, work.header.Number, work.header.Time, allLogs, work.evm, uint32(work.tcount+1))
if err != nil {
return &newPayloadResult{err: err}
}
if requests != nil {
reqHash := types.CalcRequestsHash(requests)
work.header.RequestsHash = &reqHash
}
work.bal.Merge(bal)
// Apply the consensus-specific post-transaction changes
miner.engine.Finalize(miner.chain, work.header, work.state, &body, uint32(work.tcount+1), work.bal)
// Assemble the block for delivery.
_, _, assembleSpanEnd := telemetry.StartSpan(ctx, "miner.AssembleBlock")
block := core.AssembleBlock(miner.chain, work.header, work.state, &body, work.receipts, work.bal)
assembleSpanEnd(nil)
// Check for db errors. If anything happened during the mining or IntermediateRoot, such as a
// weird flush or a trie node missing, we need to bail out and not create a block.
if dbErr := work.state.Error(); dbErr != nil {
return &newPayloadResult{err: fmt.Errorf("%w: %v", errStateReadFailure, dbErr)}
}
return &newPayloadResult{
block: block,
fees: totalFees(block, work.receipts),
sidecars: work.sidecars,
stateDB: work.state,
receipts: work.receipts,
requests: requests,
witness: work.witness,
}
}
// prepareWork constructs the sealing task according to the given parameters,
// either based on the last chain head or specified parent. In this function
// the pending transactions are not filled yet, only the empty task returned.
func (miner *Miner) prepareWork(ctx context.Context, genParams *generateParams, witness bool) (result *environment, err error) {
_, _, spanEnd := telemetry.StartSpan(ctx, "miner.prepareWork")
defer spanEnd(&err)
miner.confMu.RLock()
defer miner.confMu.RUnlock()
// Find the parent block for sealing task
parent := miner.chain.CurrentBlock()
if genParams.parentHash != (common.Hash{}) {
block := miner.chain.GetBlockByHash(genParams.parentHash)
if block == nil {
return nil, errors.New("missing parent")
}
parent = block.Header()
}
// Sanity check the timestamp correctness, recap the timestamp
// to parent+1 if the mutation is allowed.
timestamp := genParams.timestamp
if parent.Time >= timestamp {
if genParams.forceTime {
return nil, fmt.Errorf("invalid timestamp, parent %d given %d", parent.Time, timestamp)
}
timestamp = parent.Time + 1
}
// Post-Amsterdam use TargetGasLimit provided by CL
number := new(big.Int).Add(parent.Number, common.Big1)
gasCeil := miner.config.GasCeil
if miner.chainConfig.IsAmsterdam(number, timestamp) && genParams.targetGasLimit != nil {
gasCeil = *genParams.targetGasLimit
}
// Construct the sealing block header.
header := &types.Header{
ParentHash: parent.Hash(),
Number: number,
GasLimit: core.CalcGasLimit(parent.GasLimit, gasCeil),
Time: timestamp,
Coinbase: genParams.coinbase,
}
// Set the extra field.
if len(miner.config.ExtraData) != 0 {
header.Extra = miner.config.ExtraData
}
if genParams.forceOverrides {
header.Extra = genParams.overrideExtraData
}
// Set the randomness field from the beacon chain if it's available.
if genParams.random != (common.Hash{}) {
header.MixDigest = genParams.random
}
// Set baseFee and GasLimit if we are on an EIP-1559 chain
if miner.chainConfig.IsLondon(header.Number) {
header.BaseFee = eip1559.CalcBaseFee(miner.chainConfig, parent)
if !miner.chainConfig.IsLondon(parent.Number) {
parentGasLimit := parent.GasLimit * miner.chainConfig.ElasticityMultiplier()
header.GasLimit = core.CalcGasLimit(parentGasLimit, miner.config.GasCeil)
}
}
// Run the consensus preparation with the default or customized consensus engine.
// Note that the `header.Time` may be changed.
if err := miner.engine.Prepare(miner.chain, header); err != nil {
log.Error("Failed to prepare header for sealing", "err", err)
return nil, err
}
// Apply EIP-4844, EIP-4788.
if miner.chainConfig.IsCancun(header.Number, header.Time) {
var excessBlobGas uint64
if miner.chainConfig.IsCancun(parent.Number, parent.Time) {
excessBlobGas = eip4844.CalcExcessBlobGas(miner.chainConfig, parent, timestamp)
}
header.BlobGasUsed = new(uint64)
header.ExcessBlobGas = &excessBlobGas
header.ParentBeaconRoot = genParams.beaconRoot
}
// Apply EIP-7843.
if miner.chainConfig.IsAmsterdam(header.Number, header.Time) {
if genParams.slotNum == nil {
return nil, errors.New("no slot number set post-amsterdam")
}
header.SlotNumber = genParams.slotNum
}
// Could potentially happen if starting to mine in an odd state.
// Note genParams.coinbase can be different with header.Coinbase
// since clique algorithm can modify the coinbase field in header.
env, err := miner.makeEnv(parent, header, genParams.coinbase, witness)
if err != nil {
log.Error("Failed to create sealing context", "err", err)
return nil, err
}
// Run pre-execution system calls
env.bal.Merge(core.PreExecution(ctx, header.ParentBeaconRoot, parent, miner.chainConfig, env.evm, header.Number, header.Time))
return env, nil
}
// makeEnv creates a new environment for the sealing block.
func (miner *Miner) makeEnv(parent *types.Header, header *types.Header, coinbase common.Address, witness bool) (*environment, error) {
// Retrieve the parent state to execute on top.
state, err := miner.chain.StateAtForkBoundary(parent, header)
if err != nil {
return nil, err
}
var bundle *stateless.Witness
if witness {
bundle, err = stateless.NewWitness(header, miner.chain, false)
if err != nil {
return nil, err
}
}
state.StartPrefetcher("miner", bundle)
evm := vm.NewEVM(core.NewEVMBlockContext(header, miner.chain, &coinbase), state, miner.chainConfig, vm.Config{})
evm.SetJumpDestCache(miner.chain.JumpDestCache())
evm.SetPrecompileCache(miner.chain.PrecompileCache())
// Note the passed coinbase may be different with header.Coinbase.
return &environment{
signer: types.MakeSigner(miner.chainConfig, header.Number, header.Time),
state: state,
size: uint64(header.Size()),
coinbase: coinbase,
gasPool: core.NewGasPool(header.GasLimit),
header: header,
bal: bal.NewConstructionBlockAccessList(),
witness: state.Witness(),
evm: evm,
}, nil
}
func (miner *Miner) commitTransaction(ctx context.Context, env *environment, tx *types.Transaction) (err error) {
_, _, spanEnd := telemetry.StartSpan(ctx, "miner.commitTransaction")
defer spanEnd(&err)
if tx.Type() == types.BlobTxType {
return miner.commitBlobTransaction(env, tx)
}
receipt, bal, err := miner.applyTransaction(env, tx)
if err != nil {
return err
}
env.txs = append(env.txs, tx)
env.receipts = append(env.receipts, receipt)
env.size += tx.Size()
env.tcount++
env.bal.Merge(bal)
return nil
}
func (miner *Miner) commitBlobTransaction(env *environment, tx *types.Transaction) error {
sc := tx.BlobTxSidecar()
if sc == nil {
panic("blob transaction without blobs in miner")
}
// Checking against blob gas limit: It's kind of ugly to perform this check here, but there
// isn't really a better place right now. The blob gas limit is checked at block validation time
// and not during execution. This means core.ApplyTransaction will not return an error if the
// tx has too many blobs. So we have to explicitly check it here.
maxBlobs := miner.maxBlobsPerBlock(env.header.Time)
if env.blobs+len(sc.Blobs) > maxBlobs {
return errors.New("max data blobs reached")
}
receipt, bal, err := miner.applyTransaction(env, tx)
if err != nil {
return err
}
txNoBlob := tx.WithoutBlobTxSidecar()
env.txs = append(env.txs, txNoBlob)
env.receipts = append(env.receipts, receipt)
env.sidecars = append(env.sidecars, sc)
env.blobs += len(sc.Blobs)
env.size += txNoBlob.Size()
*env.header.BlobGasUsed += receipt.BlobGasUsed
env.tcount++
env.bal.Merge(bal)
return nil
}
// applyTransaction runs the transaction. If execution fails, state and gas pool are reverted.
func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (*types.Receipt, *bal.ConstructionBlockAccessList, error) {
var (
snap = env.state.Snapshot()
gp = env.gasPool.Snapshot()
)
receipt, bal, err := core.ApplyTransaction(env.evm, env.gasPool, env.state, env.header, tx)
if err != nil {
env.state.RevertToSnapshot(snap)
env.gasPool.Set(gp)
return nil, nil, err
}
env.header.GasUsed = env.gasPool.Used()
return receipt, bal, nil
}
func (miner *Miner) commitTransactions(ctx context.Context, env *environment, plainTxs, blobTxs *txorder.TransactionsByPriceAndNonce, interrupt *atomic.Int32) error {
ctx, _, spanEnd := telemetry.StartSpan(ctx, "miner.commitTransactions")
defer spanEnd(nil)
isCancun := miner.chainConfig.IsCancun(env.header.Number, env.header.Time)
for {
// Check interruption signal and abort building if it's fired.
if interrupt != nil {
if signal := interrupt.Load(); signal != commitInterruptNone {
return signalToErr(signal)
}
}
// If we don't have enough gas for any further transactions then we're done.
if env.gasPool.Gas() < params.TxGas {
log.Trace("Not enough gas for further transactions", "have", env.gasPool, "want", params.TxGas)
break
}
// If we don't have enough blob space for any further blob transactions,
// skip that list altogether
if !blobTxs.Empty() && env.blobs >= miner.maxBlobsPerBlock(env.header.Time) {
log.Trace("Not enough blob space for further blob transactions")
blobTxs.Clear()
// Fall though to pick up any plain txs
}
// Retrieve the next transaction and abort if all done.
var (
ltx *txpool.LazyTransaction
txs *txorder.TransactionsByPriceAndNonce
)
pltx, ptip := plainTxs.Peek()
bltx, btip := blobTxs.Peek()
switch {
case pltx == nil:
txs, ltx = blobTxs, bltx
case bltx == nil:
txs, ltx = plainTxs, pltx
default:
if ptip.Lt(btip) {
txs, ltx = blobTxs, bltx
} else {
txs, ltx = plainTxs, pltx
}
}
if ltx == nil {
break
}
// If we don't have enough space for the next transaction, skip the account.
if env.gasPool.Gas() < ltx.Gas {
log.Trace("Not enough gas left for transaction", "hash", ltx.Hash, "left", env.gasPool.Gas(), "needed", ltx.Gas)
txs.Pop()
continue
}
// Most of the blob gas logic here is agnostic as to if the chain supports
// blobs or not, however the max check panics when called on a chain without
// a defined schedule, so we need to verify it's safe to call.
if isCancun {
left := miner.maxBlobsPerBlock(env.header.Time) - env.blobs
if left < int(ltx.BlobGas/params.BlobTxBlobGasPerBlob) {
log.Trace("Not enough blob space left for transaction", "hash", ltx.Hash, "left", left, "needed", ltx.BlobGas/params.BlobTxBlobGasPerBlob)
txs.Pop()
continue
}
}
// Transaction seems to fit, pull it up from the pool
tx := ltx.Resolve()
if tx == nil {
log.Trace("Ignoring evicted transaction", "hash", ltx.Hash)
txs.Pop()
continue
}
// if inclusion of the transaction would put the block size over the
// maximum we allow, don't add any more txs to the payload.
if !env.txFitsSize(tx) {
break
}
// Error may be ignored here. The error has already been checked
// during transaction acceptance in the transaction pool.
from, _ := types.Sender(env.signer, tx)
// Check whether the tx is replay protected. If we're not in the EIP155 hf
// phase, start ignoring the sender until we do.
if tx.Protected() && !miner.chainConfig.IsEIP155(env.header.Number) {
log.Trace("Ignoring replay protected transaction", "hash", ltx.Hash, "eip155", miner.chainConfig.EIP155Block)
txs.Pop()
continue
}
// Start executing the transaction
env.state.SetTxContext(tx.Hash(), env.tcount, uint32(env.tcount+1))
err := miner.commitTransaction(ctx, env, tx)
switch {
case errors.Is(err, core.ErrNonceTooLow):
// New head notification data race between the transaction pool and miner, shift
log.Trace("Skipping transaction with low nonce", "hash", ltx.Hash, "sender", from, "nonce", tx.Nonce())
txs.Shift()
case errors.Is(err, nil):
// Everything ok, collect the logs and shift in the next transaction from the same account
txs.Shift()
default:
// Transaction is regarded as invalid, drop all consecutive transactions from
// the same sender because of `nonce-too-high` clause.
log.Debug("Transaction failed, account skipped", "hash", ltx.Hash, "err", err)
txs.Pop()
}
}
return nil
}
// fillTransactions retrieves the pending transactions from the txpool and fills them
// into the given sealing block. The transaction selection and ordering strategy can
// be customized with the plugin in the future.
func (miner *Miner) fillTransactions(ctx context.Context, interrupt *atomic.Int32, env *environment) (err error) {
ctx, span, spanEnd := telemetry.StartSpan(ctx, "miner.fillTransactions")
defer spanEnd(&err)
miner.confMu.RLock()
tip := miner.config.GasPrice
prio := miner.prio
miner.confMu.RUnlock()
// Retrieve the pending transactions pre-filtered by the 1559/4844 dynamic fees
filter := txpool.PendingFilter{
MinTip: uint256.MustFromBig(tip),
}
if env.header.BaseFee != nil {
filter.BaseFee = uint256.MustFromBig(env.header.BaseFee)
}
if env.header.ExcessBlobGas != nil {
filter.BlobFee = uint256.MustFromBig(eip4844.CalcBlobFee(miner.chainConfig, env.header))
}
if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) && !miner.chainConfig.IsAmsterdam(env.header.Number, env.header.Time) {
filter.GasLimitCap = params.MaxTxGas
}
filter.BlobTxs = false
pendingPlainTxs, plainTxCount := miner.txpool.Pending(filter)
filter.BlobTxs = true
if miner.chainConfig.IsOsaka(env.header.Number, env.header.Time) {
filter.BlobVersion = types.BlobSidecarVersion1
} else {
filter.BlobVersion = types.BlobSidecarVersion0
}
pendingBlobTxs, blobTxCount := miner.txpool.Pending(filter)
span.SetAttributes(
telemetry.IntAttribute("pending.plain.count", plainTxCount),
telemetry.IntAttribute("pending.blob.count", blobTxCount),
)
// Split the pending transactions into locals and remotes.
prioPlainTxs, normalPlainTxs := make(map[common.Address][]*txpool.LazyTransaction), pendingPlainTxs
prioBlobTxs, normalBlobTxs := make(map[common.Address][]*txpool.LazyTransaction), pendingBlobTxs
for _, account := range prio {
if txs := normalPlainTxs[account]; len(txs) > 0 {
delete(normalPlainTxs, account)
prioPlainTxs[account] = txs
}
if txs := normalBlobTxs[account]; len(txs) > 0 {
delete(normalBlobTxs, account)
prioBlobTxs[account] = txs
}
}
// Fill the block with all available pending transactions.
if len(prioPlainTxs) > 0 || len(prioBlobTxs) > 0 {
plainTxs := txorder.NewTransactionsByPriceAndNonce(env.signer, prioPlainTxs, env.header.BaseFee)
blobTxs := txorder.NewTransactionsByPriceAndNonce(env.signer, prioBlobTxs, env.header.BaseFee)
if err := miner.commitTransactions(ctx, env, plainTxs, blobTxs, interrupt); err != nil {
return err
}
}
if len(normalPlainTxs) > 0 || len(normalBlobTxs) > 0 {
plainTxs := txorder.NewTransactionsByPriceAndNonce(env.signer, normalPlainTxs, env.header.BaseFee)
blobTxs := txorder.NewTransactionsByPriceAndNonce(env.signer, normalBlobTxs, env.header.BaseFee)
if err := miner.commitTransactions(ctx, env, plainTxs, blobTxs, interrupt); err != nil {
return err
}
}
return nil
}
// totalFees computes total consumed miner fees in Wei. Block transactions and receipts have to have the same order.
func totalFees(block *types.Block, receipts []*types.Receipt) *big.Int {
baseFee := block.BaseFee()
feesWei := new(big.Int)
var gasUsed, product big.Int
for i, tx := range block.Transactions() {
minerFee, _ := tx.EffectiveGasTip(baseFee)
gasUsed.SetUint64(receipts[i].GasUsed)
product.Mul(&gasUsed, minerFee)
feesWei.Add(feesWei, &product)
}
return feesWei
}
// signalToErr converts the interruption signal to a concrete error type for return.
// The given signal must be a valid interruption signal.
func signalToErr(signal int32) error {
switch signal {
case commitInterruptNewHead:
return errBlockInterruptedByNewHead
case commitInterruptResubmit:
return errBlockInterruptedByRecommit
case commitInterruptTimeout:
return errBlockInterruptedByTimeout
default:
panic(fmt.Errorf("undefined signal %d", signal))
}
}