diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 6f1b7ffdf099..3a6de1cdade7 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -153,8 +153,9 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, blockHash = common.Hash{0x13, 0x37} rejectedTxs []*rejectedTx includedTxs types.Transactions - gasUsed = uint64(0) - blobGasUsed = uint64(0) + gasUsed = uint64(0) + blockGasUsed = uint64(0) + blobGasUsed = uint64(0) receipts = make(types.Receipts, 0) ) gaspool.AddGas(pre.Env.GasLimit) @@ -260,7 +261,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, snapshot = statedb.Snapshot() prevGas = gaspool.Gas() ) - receipt, err := core.ApplyTransactionWithEVM(msg, gaspool, statedb, vmContext.BlockNumber, blockHash, pre.Env.Timestamp, tx, &gasUsed, evm) + receipt, txBlockGas, err := core.ApplyTransactionWithEVM(msg, gaspool, statedb, vmContext.BlockNumber, blockHash, pre.Env.Timestamp, tx, &gasUsed, evm) if err != nil { statedb.RevertToSnapshot(snapshot) log.Info("rejected tx", "index", i, "hash", tx.Hash(), "from", msg.From, "error", err) @@ -269,6 +270,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, continue } includedTxs = append(includedTxs, tx) + blockGasUsed += txBlockGas if hashError != nil { return nil, nil, nil, NewError(ErrorMissingBlockhash, hashError) } @@ -349,7 +351,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, Receipts: receipts, Rejected: rejectedTxs, Difficulty: (*math.HexOrDecimal256)(vmContext.Difficulty), - GasUsed: (math.HexOrDecimal64)(gasUsed), + GasUsed: (math.HexOrDecimal64)(blockGasUsed), BaseFee: (*math.HexOrDecimal256)(vmContext.BaseFee), } if pre.Env.Withdrawals != nil { diff --git a/core/block_validator.go b/core/block_validator.go index 3a5379d1a92f..c604238ef895 100644 --- a/core/block_validator.go +++ b/core/block_validator.go @@ -19,6 +19,7 @@ package core import ( "errors" "fmt" + "github.com/ethereum/go-ethereum/consensus" "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" diff --git a/core/chain_makers.go b/core/chain_makers.go index d7b97e65a78e..4eeff64e16de 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -44,11 +44,12 @@ type BlockGen struct { header *types.Header statedb *state.StateDB - gasPool *GasPool - txs []*types.Transaction - receipts []*types.Receipt - uncles []*types.Header - withdrawals []*types.Withdrawal + gasPool *GasPool + txs []*types.Transaction + receipts []*types.Receipt + uncles []*types.Header + withdrawals []*types.Withdrawal + receiptGasUsed uint64 engine consensus.Engine } @@ -117,7 +118,8 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti evm = vm.NewEVM(blockContext, b.statedb, b.cm.config, vmConfig) ) b.statedb.SetTxContext(tx.Hash(), len(b.txs)) - receipt, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx, &b.header.GasUsed) + receipt, blockGasUsed, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx, &b.receiptGasUsed) + b.header.GasUsed += blockGasUsed if err != nil { panic(err) } diff --git a/core/parallel_state_processor.go b/core/parallel_state_processor.go index 6a9c91750f78..6d8b85e76089 100644 --- a/core/parallel_state_processor.go +++ b/core/parallel_state_processor.go @@ -45,7 +45,7 @@ func NewParallelStateProcessor(chain *HeaderChain, vmConfig *vm.Config) Parallel // performs post-tx state transition (system contracts and withdrawals) // and calculates the ProcessResult, returning it to be sent on resCh // by resultHandler -func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, allStateReads *bal.StateAccesses, tExecStart time.Time, postTxState *state.StateDB, receipts types.Receipts) *ProcessResultWithMetrics { +func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, allStateReads *bal.StateAccesses, tExecStart time.Time, postTxState *state.StateDB, receipts types.Receipts, blockGasUsed uint64) *ProcessResultWithMetrics { tExec := time.Since(tExecStart) var requests [][]byte tPostprocessStart := time.Now() @@ -147,7 +147,7 @@ func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, allStateR Receipts: receipts, Requests: requests, Logs: allLogs, - GasUsed: cumulativeGasUsed, + GasUsed: blockGasUsed, }, PostProcessTime: tPostprocess, ExecTime: tExec, @@ -155,9 +155,10 @@ func (p *ParallelStateProcessor) prepareExecResult(block *types.Block, allStateR } type txExecResult struct { - idx int // transaction index - receipt *types.Receipt - err error // non-EVM error which would render the block invalid + idx int // transaction index + receipt *types.Receipt + blockGasUsed uint64 // pre-refund gas for block-level accounting (EIP-7778) + err error // non-EVM error which would render the block invalid stateReads bal.StateAccesses } @@ -168,6 +169,7 @@ func (p *ParallelStateProcessor) resultHandler(block *types.Block, preTxStateRea // 1. if the block has transactions, receive the execution results from all of them and return an error on resCh if any txs err'd // 2. once all txs are executed, compute the post-tx state transition and produce the ProcessResult sending it on resCh (or an error if the post-tx state didn't match what is reported in the BAL) var receipts []*types.Receipt + var blockGasUsed uint64 gp := new(GasPool) gp.SetGas(block.GasLimit()) var execErr error @@ -184,10 +186,12 @@ func (p *ParallelStateProcessor) resultHandler(block *types.Block, preTxStateRea if res.err != nil { execErr = res.err } else { - if err := gp.SubGas(res.receipt.GasUsed); err != nil { + // EIP-7778: use pre-refund gas for block-level gas pool checks + if err := gp.SubGas(res.blockGasUsed); err != nil { execErr = err } else { receipts = append(receipts, res.receipt) + blockGasUsed += res.blockGasUsed allReads.Merge(res.stateReads) } } @@ -205,7 +209,7 @@ func (p *ParallelStateProcessor) resultHandler(block *types.Block, preTxStateRea } } - execResults := p.prepareExecResult(block, &allReads, tExecStart, postTxState, receipts) + execResults := p.prepareExecResult(block, &allReads, tExecStart, postTxState, receipts, blockGasUsed) rootCalcRes := <-stateRootCalcResCh if execResults.ProcessResult.Error != nil { @@ -271,7 +275,7 @@ func (p *ParallelStateProcessor) execTx(block *types.Block, tx *types.Transactio gp.SetGas(block.GasLimit()) db.SetTxContext(tx.Hash(), txIdx) var gasUsed uint64 - receipt, err := ApplyTransactionWithEVM(msg, gp, db, block.Number(), block.Hash(), context.Time, tx, &gasUsed, evm) + receipt, txBlockGas, err := ApplyTransactionWithEVM(msg, gp, db, block.Number(), block.Hash(), context.Time, tx, &gasUsed, evm) if err != nil { err := fmt.Errorf("could not apply tx %d [%v]: %w", txIdx, tx.Hash().Hex(), err) return &txExecResult{err: err} @@ -283,9 +287,10 @@ func (p *ParallelStateProcessor) execTx(block *types.Block, tx *types.Transactio } return &txExecResult{ - idx: txIdx, - receipt: receipt, - stateReads: accesses, + idx: txIdx, + receipt: receipt, + blockGasUsed: txBlockGas, + stateReads: accesses, } } diff --git a/core/state_processor.go b/core/state_processor.go index 510d5fa1ab22..3d94f80a47ff 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -60,15 +60,15 @@ func (p *StateProcessor) chainConfig() *params.ChainConfig { // transactions failed to execute due to insufficient gas it will return an error. func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) { var ( - config = p.chainConfig() - receipts types.Receipts - chargedGas = new(uint64) - usedGas = uint64(0) - header = block.Header() - blockHash = block.Hash() - blockNumber = block.Number() - allLogs []*types.Log - gp = new(GasPool).AddGas(block.GasLimit()) + config = p.chainConfig() + receipts types.Receipts + usedGas = new(uint64) + blockGasUsed = uint64(0) + header = block.Header() + blockHash = block.Hash() + blockNumber = block.Number() + allLogs []*types.Log + gp = new(GasPool).AddGas(block.GasLimit()) ) var tracingStateDB = vm.StateDB(statedb) @@ -104,20 +104,14 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg } statedb.SetTxContext(tx.Hash(), i) - receipt, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, chargedGas, evm) + receipt, txBlockGas, err := ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, usedGas, evm) if err != nil { return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } - usedGas += receipt.GasUsed + // EIP-7778: block gas accounting excludes refunds. + blockGasUsed += txBlockGas receipts = append(receipts, receipt) allLogs = append(allLogs, receipt.Logs...) - - /* - enc, _ := json.MarshalIndent(receipt, "", " ") - fmt.Printf("receipt json %s\n", string(enc)) - encRLP, _ := rlp.EncodeToBytes(receipt) - fmt.Printf("receipt rlp %x\n", encRLP) - */ } // Read requests if Prague is enabled. @@ -149,14 +143,14 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg Receipts: receipts, Requests: requests, Logs: allLogs, - GasUsed: usedGas, + GasUsed: blockGasUsed, }, nil } // ApplyTransactionWithEVM attempts to apply a transaction to the given state database // and uses the input parameters for its environment similar to ApplyTransaction. However, // this method takes an already created EVM instance as input. -func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (receipt *types.Receipt, err error) { +func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas *uint64, evm *vm.EVM) (receipt *types.Receipt, blockGasUsed uint64, err error) { if hooks := evm.Config.Tracer; hooks != nil { if hooks.OnTxStart != nil { hooks.OnTxStart(evm.GetVMContext(), tx, msg.From) @@ -168,7 +162,7 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, // Apply the transaction to the current state (included in the env). result, err := ApplyMessage(evm, msg, gp) if err != nil { - return nil, err + return nil, 0, err } if evm.ChainConfig().IsAmsterdam(blockNumber, blockTime) { // Emit Selfdesctruct logs where accounts with non-empty balances have been deleted @@ -191,12 +185,19 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB, } *usedGas += result.UsedGas + // EIP-7778: block gas accounting excludes refunds. + if evm.ChainConfig().IsAmsterdam(blockNumber, blockTime) { + blockGasUsed = result.MaxUsedGas + } else { + blockGasUsed = result.UsedGas + } + // Merge the tx-local access event into the "block-local" one, in order to collect // all values, so that the witness can be built. if statedb.Database().TrieDB().IsVerkle() { statedb.AccessEvents().Merge(evm.AccessEvents) } - return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, *usedGas, root), nil + return MakeReceipt(evm, result, statedb, blockNumber, blockHash, blockTime, tx, *usedGas, root), blockGasUsed, nil } // MakeReceipt generates the receipt object for a transaction given its execution result. @@ -210,11 +211,7 @@ func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, b receipt.Status = types.ReceiptStatusSuccessful } receipt.TxHash = tx.Hash() - if evm.ChainConfig().IsAmsterdam(blockNumber, blockTime) { - receipt.GasUsed = result.MaxUsedGas - } else { - receipt.GasUsed = result.UsedGas - } + receipt.GasUsed = result.UsedGas if tx.Type() == types.BlobTxType { receipt.BlobGasUsed = uint64(len(tx.BlobHashes()) * params.BlobTxBlobGasPerBlob) @@ -239,14 +236,14 @@ func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, b // and uses the input parameters for its environment. It returns the receipt // for the transaction, gas used and an error if the transaction failed, // indicating the block was invalid. -func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64) (*types.Receipt, error) { +func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *uint64) (*types.Receipt, uint64, error) { msg, err := TransactionToMessage(tx, types.MakeSigner(evm.ChainConfig(), header.Number, header.Time), header.BaseFee) if err != nil { - return nil, err + return nil, 0, err } // Create a new context to be used in the EVM environment - receipts, err := ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, usedGas, evm) - return receipts, err + receipt, blockGasUsed, err := ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, usedGas, evm) + return receipt, blockGasUsed, err } // ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root diff --git a/eth/tracers/api.go b/eth/tracers/api.go index 5f2f16627a2c..93e2147fd251 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -1055,7 +1055,7 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor // Call Prepare to clear out the statedb access list statedb.SetTxContext(txctx.TxHash, txctx.TxIndex) - _, err = core.ApplyTransactionWithEVM(message, new(core.GasPool).AddGas(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, vmctx.Time, tx, &usedGas, evm) + _, _, err = core.ApplyTransactionWithEVM(message, new(core.GasPool).AddGas(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, vmctx.Time, tx, &usedGas, evm) if err != nil { return nil, fmt.Errorf("tracing failed: %w", err) } diff --git a/eth/tracers/internal/tracetest/selfdestruct_state_test.go b/eth/tracers/internal/tracetest/selfdestruct_state_test.go index 2c714b6dcecd..d86ce838b634 100644 --- a/eth/tracers/internal/tracetest/selfdestruct_state_test.go +++ b/eth/tracers/internal/tracetest/selfdestruct_state_test.go @@ -621,7 +621,7 @@ func TestSelfdestructStateTracer(t *testing.T) { context := core.NewEVMBlockContext(block.Header(), blockchain, nil) evm := vm.NewEVM(context, hookedState, tt.genesis.Config, vm.Config{Tracer: tracer.Hooks()}) usedGas := uint64(0) - _, err = core.ApplyTransactionWithEVM(msg, new(core.GasPool).AddGas(tx.Gas()), statedb, block.Number(), block.Hash(), block.Time(), tx, &usedGas, evm) + _, _, err = core.ApplyTransactionWithEVM(msg, new(core.GasPool).AddGas(tx.Gas()), statedb, block.Number(), block.Hash(), block.Time(), tx, &usedGas, evm) if err != nil { t.Fatalf("failed to execute transaction: %v", err) } diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index deaaafadb002..c8d9b51950e8 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -239,7 +239,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, return nil, nil, nil, err } var ( - gasUsed, blobGasUsed uint64 + gasUsed, blockGasUsed, blobGasUsed uint64 txes = make([]*types.Transaction, len(block.Calls)) callResults = make([]simCallResult, len(block.Calls)) receipts = make([]*types.Receipt, len(block.Calls)) @@ -275,7 +275,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, if err := ctx.Err(); err != nil { return nil, nil, nil, err } - if err := sim.sanitizeCall(&call, sim.state, header, blockContext, &gasUsed); err != nil { + if err := sim.sanitizeCall(&call, sim.state, header, blockContext, &blockGasUsed); err != nil { return nil, nil, nil, err } var ( @@ -301,6 +301,12 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, root = sim.state.IntermediateRoot(sim.chainConfig.IsEIP158(blockContext.BlockNumber)).Bytes() } gasUsed += result.UsedGas + // EIP-7778: block gas accounting excludes refunds. + if sim.chainConfig.IsAmsterdam(blockContext.BlockNumber, blockContext.Time) { + blockGasUsed += result.MaxUsedGas + } else { + blockGasUsed += result.UsedGas + } receipts[i] = core.MakeReceipt(evm, result, sim.state, blockContext.BlockNumber, common.Hash{}, blockContext.Time, tx, gasUsed, root) blobGasUsed += receipts[i].BlobGasUsed logs := tracer.Logs() @@ -320,7 +326,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, } callResults[i] = callRes } - header.GasUsed = gasUsed + header.GasUsed = blockGasUsed if sim.chainConfig.IsCancun(header.Number, header.Time) { header.BlobGasUsed = &blobGasUsed } diff --git a/miner/worker.go b/miner/worker.go index 7547fe0d4e99..8461e5e97f87 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -66,11 +66,12 @@ type environment struct { coinbase common.Address evm *vm.EVM - header *types.Header - txs []*types.Transaction - receipts []*types.Receipt - sidecars []*types.BlobTxSidecar - blobs int + header *types.Header + txs []*types.Transaction + receipts []*types.Receipt + sidecars []*types.BlobTxSidecar + blobs int + receiptGasUsed uint64 // cumulative post-refund gas for receipt CumulativeGasUsed witness *stateless.Witness alTracer *core.BlockAccessListTracer @@ -382,10 +383,14 @@ func (miner *Miner) applyTransaction(env *environment, tx *types.Transaction) (* snap = env.state.Snapshot() gp = env.gasPool.Gas() ) - receipt, err := core.ApplyTransaction(env.evm, env.gasPool, env.state, env.header, tx, &env.header.GasUsed) + receipt, blockGasUsed, err := core.ApplyTransaction(env.evm, env.gasPool, env.state, env.header, tx, &env.receiptGasUsed) if err != nil { env.state.RevertToSnapshot(snap) env.gasPool.SetGas(gp) + } else { + // EIP-7778: block header uses pre-refund gas (blockGasUsed), + // while receipts use post-refund gas (accumulated in receiptGasUsed). + env.header.GasUsed += blockGasUsed } return receipt, err }