core: implement eip-7778: block gas accounting without refunds

This commit is contained in:
MariusVanDerWijden 2026-01-13 09:33:05 +01:00
parent ef0a3a2a3b
commit b236537fc4
7 changed files with 42 additions and 25 deletions

View file

@ -260,7 +260,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, _, 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)
@ -268,6 +268,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
gaspool.SetGas(prevGas)
continue
}
gasUsed += receipt.GasUsed
includedTxs = append(includedTxs, tx)
if hashError != nil {
return nil, nil, nil, NewError(ErrorMissingBlockhash, hashError)

View file

@ -117,10 +117,11 @@ 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, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx)
if err != nil {
panic(err)
}
b.header.GasUsed += receipt.GasUsed
// 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 b.statedb.Database().TrieDB().IsVerkle() {

View file

@ -62,12 +62,17 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
var (
config = p.chainConfig()
receipts types.Receipts
usedGas = new(uint64)
header = block.Header()
blockHash = block.Hash()
blockNumber = block.Number()
allLogs []*types.Log
gp = new(GasPool).AddGas(block.GasLimit())
// We are maintaining two counters here:
// one counts all the cumulativeGas which includes refunds
// while the other counts only the usedGas which excludes refunds after Amsterdam
// We need the cumulativeGas for receipts and the usedGas for the block gas limit
cumulativeGas = uint64(0)
usedGas = uint64(0)
)
var tracingStateDB = vm.StateDB(statedb)
@ -103,10 +108,12 @@ 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, usedGas, evm)
var receipt *types.Receipt
receipt, cumulativeGas, err = ApplyTransactionWithEVM(msg, gp, statedb, blockNumber, blockHash, context.Time, tx, cumulativeGas, evm)
if err != nil {
return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err)
}
usedGas += receipt.GasUsed
receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...)
@ -147,14 +154,14 @@ func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg
Receipts: receipts,
Requests: requests,
Logs: allLogs,
GasUsed: *usedGas,
GasUsed: usedGas,
}, 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, cumulativeGas uint64, evm *vm.EVM) (receipt *types.Receipt, cGas uint64, err error) {
if hooks := evm.Config.Tracer; hooks != nil {
if hooks.OnTxStart != nil {
hooks.OnTxStart(evm.GetVMContext(), tx, msg.From)
@ -166,7 +173,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, cumulativeGas, err
}
if evm.ChainConfig().IsAmsterdam(blockNumber, blockTime) {
// Emit Selfdesctruct logs where accounts with non-empty balances have been deleted
@ -187,28 +194,32 @@ func ApplyTransactionWithEVM(msg *Message, gp *GasPool, statedb *state.StateDB,
} else {
root = statedb.IntermediateRoot(evm.ChainConfig().IsEIP158(blockNumber)).Bytes()
}
*usedGas += result.UsedGas
cGas = cumulativeGas + 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, cGas, root), cGas, nil
}
// MakeReceipt generates the receipt object for a transaction given its execution result.
func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, usedGas uint64, root []byte) *types.Receipt {
func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, blockNumber *big.Int, blockHash common.Hash, blockTime uint64, tx *types.Transaction, cumulativeGas uint64, root []byte) *types.Receipt {
// Create a new receipt for the transaction, storing the intermediate root and gas used
// by the tx.
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: usedGas}
receipt := &types.Receipt{Type: tx.Type(), PostState: root, CumulativeGasUsed: cumulativeGas}
if result.Failed() {
receipt.Status = types.ReceiptStatusFailed
} else {
receipt.Status = types.ReceiptStatusSuccessful
}
receipt.TxHash = tx.Hash()
receipt.GasUsed = result.UsedGas
if evm.ChainConfig().IsAmsterdam(blockNumber, blockTime) {
receipt.GasUsed = result.MaxUsedGas
} else {
receipt.GasUsed = result.UsedGas
}
if tx.Type() == types.BlobTxType {
receipt.BlobGasUsed = uint64(len(tx.BlobHashes()) * params.BlobTxBlobGasPerBlob)
@ -231,16 +242,16 @@ func MakeReceipt(evm *vm.EVM, result *ExecutionResult, statedb *state.StateDB, b
// ApplyTransaction attempts to apply a transaction to the given state database
// and uses the input parameters for its environment. It returns the receipt
// for the transaction, gas used and an error if the transaction failed,
// for the transaction 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) (*types.Receipt, error) {
msg, err := TransactionToMessage(tx, types.MakeSigner(evm.ChainConfig(), header.Number, header.Time), header.BaseFee)
if err != nil {
return nil, 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, _, err := ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, header.GasUsed, evm)
return receipt, err
}
// ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root

View file

@ -541,7 +541,14 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
peakGasUsed = floorDataGas
}
}
// Return gas to the user
st.returnGas()
// Return gas to the gas pool
if rules.IsAmsterdam {
st.gp.AddGas(st.initialGas - peakGasUsed)
} else {
st.gp.AddGas(st.gasRemaining)
}
effectiveTip := msg.GasPrice
if rules.IsLondon {
@ -669,10 +676,6 @@ func (st *stateTransition) returnGas() {
if st.evm.Config.Tracer != nil && st.evm.Config.Tracer.OnGasChange != nil && st.gasRemaining > 0 {
st.evm.Config.Tracer.OnGasChange(st.gasRemaining, 0, tracing.GasChangeTxLeftOverReturned)
}
// Also return remaining gas to the block gas counter so it is
// available for the next transaction.
st.gp.AddGas(st.gasRemaining)
}
// gasUsed returns the amount of gas used up by the state transition.

View file

@ -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)
}

View file

@ -620,8 +620,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, uint64(0), evm)
if err != nil {
t.Fatalf("failed to execute transaction: %v", err)
}

View file

@ -382,12 +382,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, err := core.ApplyTransaction(env.evm, env.gasPool, env.state, env.header, tx)
if err != nil {
env.state.RevertToSnapshot(snap)
env.gasPool.SetGas(gp)
return nil, err
}
return receipt, err
env.header.GasUsed += receipt.GasUsed
return receipt, nil
}
func (miner *Miner) commitTransactions(env *environment, plainTxs, blobTxs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error {