From 07dd8e052f50420f1fa29878d9c285fbfc5c0dc6 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Mon, 29 Jun 2026 11:04:30 +0800 Subject: [PATCH 01/16] core: implement EIP-2780 (#35212) Implements https://eips.ethereum.org/EIPS/eip-2780 --------- Co-authored-by: Marius van der Wijden --- cmd/evm/internal/t8ntool/transaction.go | 11 +- core/bench_test.go | 2 +- core/bintrie_witness_test.go | 4 +- core/eip2780_test.go | 214 ++++++++++++++++++++++++ core/eip8037_test.go | 10 +- core/error.go | 5 - core/state_processor.go | 2 +- core/state_transition.go | 119 +++++++++++-- core/state_transition_test.go | 62 +++++-- core/txpool/validation.go | 13 +- params/protocol_params.go | 7 + tests/transaction_test_util.go | 9 +- 12 files changed, 407 insertions(+), 51 deletions(-) create mode 100644 core/eip2780_test.go diff --git a/cmd/evm/internal/t8ntool/transaction.go b/cmd/evm/internal/t8ntool/transaction.go index 9eb1bdbf5f..7207cad41d 100644 --- a/cmd/evm/internal/t8ntool/transaction.go +++ b/cmd/evm/internal/t8ntool/transaction.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/tests" + "github.com/holiman/uint256" "github.com/urfave/cli/v2" ) @@ -132,8 +133,14 @@ func Transaction(ctx *cli.Context) error { r.Address = sender } // Check intrinsic gas + value, overflow := uint256.FromBig(tx.Value()) + if overflow { + // A 256-bit overflow is reported by the field validation below; use a + // non-zero placeholder so intrinsic gas is still computed and reported. + value = uint256.NewInt(1) + } rules := chainConfig.Rules(common.Big0, true, 0) - cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte) + cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), r.Address, tx.To(), value, rules, params.CostPerStateByte) if err != nil { r.Error = err results = append(results, r) @@ -147,7 +154,7 @@ func Transaction(ctx *cli.Context) error { } // For Prague txs, validate the floor data gas. if rules.IsPrague { - floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList()) + floorDataGas, err := core.FloorDataGas(rules, r.Address, tx.To(), value, tx.Data(), tx.AccessList()) if err != nil { r.Error = err results = append(results, r) diff --git a/core/bench_test.go b/core/bench_test.go index fe66aeae0d..79584309da 100644 --- a/core/bench_test.go +++ b/core/bench_test.go @@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) { data := make([]byte, nbytes) return func(i int, gen *BlockGen) { toaddr := common.Address{} - cost, _ := IntrinsicGas(data, nil, nil, false, params.Rules{}, params.CostPerStateByte) + cost, _ := IntrinsicGas(data, nil, nil, common.Address{}, &toaddr, nil, params.Rules{}, params.CostPerStateByte) signer := gen.Signer() gasPrice := big.NewInt(0) if gen.header.BaseFee != nil { diff --git a/core/bintrie_witness_test.go b/core/bintrie_witness_test.go index 5d82fc1df8..2cf7972cf8 100644 --- a/core/bintrie_witness_test.go +++ b/core/bintrie_witness_test.go @@ -64,12 +64,12 @@ var ( func TestProcessUBT(t *testing.T) { var ( code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`) - intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0) + intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, common.Address{}, nil, nil, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0) // A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness // will not contain that copied data. // Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985 codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`) - intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0) + intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, common.Address{}, nil, nil, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0) signer = types.LatestSigner(testUBTChainConfig) testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain diff --git a/core/eip2780_test.go b/core/eip2780_test.go new file mode 100644 index 0000000000..d9545923ff --- /dev/null +++ b/core/eip2780_test.go @@ -0,0 +1,214 @@ +// Copyright 2026 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 . + +package core + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" +) + +// TestEIP2780Intrinsic checks the intrinsic-gas decomposition. +func TestEIP2780Intrinsic(t *testing.T) { + var ( + from = common.HexToAddress("0x1111111111111111111111111111111111111111") + to = common.HexToAddress("0x2222222222222222222222222222222222222222") + ) + cases := []struct { + name string + to *common.Address + value *uint256.Int + want vm.GasCosts + }{ + { + name: "self-transfer", + to: &from, + value: uint256.NewInt(1), + want: vm.GasCosts{RegularGas: params.TxBaseCost2780}, // 12,000 + }, + { + name: "self-transfer/zero-value", + to: &from, + value: uint256.NewInt(0), + want: vm.GasCosts{RegularGas: params.TxBaseCost2780}, // 12,000 + }, + { + name: "zero-value call", + to: &to, + value: uint256.NewInt(0), + // TxBaseCost + ColdAccountAccess = 15,000 + want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780}, + }, + { + name: "value transfer to existing EOA", + to: &to, + value: uint256.NewInt(1), + // TxBaseCost + ColdAccountAccess + TxValueCost + TransferLogCost = 21,000 + want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 + + params.TxValueCost2780 + params.TransferLogCost2780}, + }, + { + name: "contract creation, value = 0", + to: nil, + value: uint256.NewInt(0), + // TxBaseCost + CreateAccess = 23,000 regular, plus one account creation in state. + want: vm.GasCosts{ + RegularGas: params.TxBaseCost2780 + params.CreateAccess2780, + StateGas: params.AccountCreationSize * params.CostPerStateByte, + }, + }, + { + name: "contract creation, value > 0", + to: nil, + value: uint256.NewInt(1), + // TxBaseCost + CreateAccess + TransferLogCost = 24,756 regular, plus account creation. + want: vm.GasCosts{ + RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 + params.TransferLogCost2780, + StateGas: params.AccountCreationSize * params.CostPerStateByte, + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + got, err := IntrinsicGas(nil, nil, nil, from, tc.to, tc.value, rules8037, params.CostPerStateByte) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if got != tc.want { + t.Fatalf("gas mismatch: got %+v, want %+v", got, tc.want) + } + }) + } +} + +// TestEIP2780Gas checks every "Transaction reference case" in +// the EIP-2780 specification end-to-end, asserting the two-dimensional charge +// (intrinsic + top-level + execution) recorded in the block gas pool. +func TestEIP2780Gas(t *testing.T) { + const ( + cold = params.ColdAccountAccess2780 + base = params.TxBaseCost2780 + valueCst = params.TxValueCost2780 + params.TransferLogCost2780 + ) + var ( + existingEOA = common.HexToAddress("0xe0a0000000000000000000000000000000000001") + stopContract = common.HexToAddress("0xc0de000000000000000000000000000000000001") + delegated = common.HexToAddress("0xde1e000000000000000000000000000000000001") + emptyTarget = common.HexToAddress("0x7a76000000000000000000000000000000000001") // never allocated + freshEOA = common.HexToAddress("0xbeef000000000000000000000000000000000001") // never allocated + ) + // Shared world: a funded EOA, a STOP contract and an account delegated to a + // non-existent (codeless) target. The delegation target is intentionally + // absent so resolving it executes no code. + base7702 := types.GenesisAlloc{ + existingEOA: {Balance: big.NewInt(1)}, + stopContract: {Code: []byte{0x00}}, // STOP + delegated: {Code: types.AddressToDelegation(emptyTarget)}, + } + // valueCreateTx builds a contract-creation transaction carrying value. + valueCreateTx := func(value int64) *types.Transaction { + return types.MustSignNewTx(senderKey, signer8037, &types.DynamicFeeTx{ + ChainID: cfg8037.ChainID, Nonce: 0, To: nil, Value: big.NewInt(value), + Gas: 300_000, GasFeeCap: big.NewInt(0), GasTipCap: big.NewInt(0), + }) + } + + cases := []struct { + name string + tx *types.Transaction + wantRegular, wantState uint64 + }{ + // case 1: ETH transfer to self. + {"self-transfer", callTx(0, senderAddr, 1, 100_000, nil), base, 0}, + // case 2: no-transfer to an existing EOA. + {"zero-value/eoa", callTx(0, existingEOA, 0, 100_000, nil), base + cold, 0}, + // case 3: no-transfer to a contract. + {"zero-value/contract", callTx(0, stopContract, 0, 100_000, nil), base + cold, 0}, + // case 4: ETH transfer to an existing EOA. + {"value/eoa", callTx(0, existingEOA, 1, 100_000, nil), base + cold + valueCst, 0}, + // case 5: ETH transfer to a contract. + {"value/contract", callTx(0, stopContract, 1, 100_000, nil), base + cold + valueCst, 0}, + // case 6: no-transfer to a 7702-delegated account. + {"zero-value/delegated", callTx(0, delegated, 0, 100_000, nil), base + 2*cold, 0}, + // case 7: ETH transfer to a 7702-delegated account (no new-account charge). + {"value/delegated", callTx(0, delegated, 1, 100_000, nil), base + 2*cold + valueCst, 0}, + // case 8: ETH transfer creating a new account. + {"value/new-account", callTx(0, freshEOA, 1, 300_000, nil), base + cold + valueCst, newAccountState}, + // case 9: contract-creation transaction, value = 0. + {"create/zero-value", createTx(0, 300_000, nil), base + params.CreateAccess2780, newAccountState}, + // case 10: contract-creation transaction, value > 0. + {"create/value", valueCreateTx(1), base + params.CreateAccess2780 + params.TransferLogCost2780, newAccountState}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + res, gp, err := applyMsg(t, mkState(senderAlloc(base7702)), tc.tx) + if err != nil { + t.Fatalf("consensus error: %v", err) + } + if res.Err != nil { + t.Fatalf("execution failed: %v", res.Err) + } + if gp.cumulativeRegular != tc.wantRegular { + t.Errorf("regular gas = %d, want %d", gp.cumulativeRegular, tc.wantRegular) + } + if gp.cumulativeState != tc.wantState { + t.Errorf("state gas = %d, want %d", gp.cumulativeState, tc.wantState) + } + }) + } +} + +// TestEIP2780NewAccountFunded verifies that a value transfer creating a new +// account both materializes and funds the recipient. +func TestEIP2780NewAccountFunded(t *testing.T) { + fresh := common.HexToAddress("0xbeef000000000000000000000000000000000002") + sdb := mkState(senderAlloc(nil)) + if _, _, err := applyMsg(t, sdb, callTx(0, fresh, 1, 300_000, nil)); err != nil { + t.Fatal(err) + } + if !sdb.Exist(fresh) || sdb.GetBalance(fresh).Cmp(uint256.NewInt(1)) != 0 { + t.Fatalf("recipient not funded: exist=%v balance=%v", sdb.Exist(fresh), sdb.GetBalance(fresh)) + } +} + +// TestEIP2780InsufficientGasForCallCharge verifies that a value transfer +// creating a new account, whose gas limit only covers the 21,000 intrinsic base +// and not the additional new-account state gas charged before the call executes, +// halts out of gas. The transaction stays valid (no consensus error) but +// execution fails and the recipient is not created. +func TestEIP2780InsufficientGasForCallCharge(t *testing.T) { + fresh := common.HexToAddress("0xbeef000000000000000000000000000000000003") + sdb := mkState(senderAlloc(nil)) + res, _, err := applyMsg(t, sdb, callTx(0, fresh, 1, 21_000, nil)) + if err != nil { + t.Fatalf("transaction should remain valid: %v", err) + } + if res.Err != vm.ErrOutOfGas { + t.Fatalf("expected out of gas, got %v", res.Err) + } + if res.UsedGas != 21_000 { + t.Fatalf("expected used gas, got %v", res.UsedGas) + } + if sdb.Exist(fresh) { + t.Fatal("recipient should not be created when the call charge cannot be paid") + } +} diff --git a/core/eip8037_test.go b/core/eip8037_test.go index 8626c0b090..6d506cba96 100644 --- a/core/eip8037_test.go +++ b/core/eip8037_test.go @@ -191,7 +191,7 @@ var ( // A creation tx's intrinsic gas pre-charges one account creation as state gas. func TestCreateTxIntrinsicChargesAccountUnconditionally(t *testing.T) { - cost, err := IntrinsicGas(nil, nil, nil, true, rules8037, params.CostPerStateByte) + cost, err := IntrinsicGas(nil, nil, nil, common.Address{}, nil, nil, rules8037, params.CostPerStateByte) if err != nil { t.Fatal(err) } @@ -325,7 +325,7 @@ func clearSlots(addr common.Address, n int) (types.GenesisAlloc, []byte) { // tx_gas_used_before_refund (peak) exceeds the post-refund gas used. func TestGasUsedBeforeRefund(t *testing.T) { c := common.HexToAddress("0xc1ea0") - alloc, _ := clearSlots(c, 1) + alloc, _ := clearSlots(c, 4) res, _, err := applyMsg(t, mkState(senderAlloc(alloc)), callTx(0, c, 0, 1_000_000, nil)) if err != nil { t.Fatal(err) @@ -351,8 +351,8 @@ func TestRefundCappedAt20Percent(t *testing.T) { // The EIP-7623 calldata floor is applied after the refund. func TestRefundCalldataFloorAfterRefund(t *testing.T) { data := make([]byte, 1000) // all-zero calldata: floor dominates a bare call - floor, _ := FloorDataGas(rules8037, data, nil) to := common.HexToAddress("0xeeee") + floor, _ := FloorDataGas(rules8037, senderAddr, &to, new(uint256.Int), data, nil) res, _, err := applyMsg(t, mkState(senderAlloc(nil)), callTx(0, to, 0, 1_000_000, data)) if err != nil { t.Fatal(err) @@ -367,7 +367,7 @@ func TestRefundFloorNegatesRefund(t *testing.T) { c := common.HexToAddress("0xc1ea1") alloc, _ := clearSlots(c, 1) data := make([]byte, 1000) - floor, _ := FloorDataGas(rules8037, data, nil) + floor, _ := FloorDataGas(rules8037, senderAddr, &c, new(uint256.Int), data, nil) res, _, err := applyMsg(t, mkState(senderAlloc(alloc)), callTx(0, c, 0, 1_000_000, data)) if err != nil { t.Fatal(err) @@ -474,7 +474,7 @@ var delegate8037 = common.HexToAddress("0xde1e8a7e") // Intrinsic gas pre-charges the worst-case (account + indicator) per auth. func TestAuthIntrinsicWorstCase(t *testing.T) { - cost, err := IntrinsicGas(nil, nil, []types.SetCodeAuthorization{{}}, false, rules8037, params.CostPerStateByte) + cost, err := IntrinsicGas(nil, nil, []types.SetCodeAuthorization{{}}, common.Address{}, &delegate8037, nil, rules8037, params.CostPerStateByte) if err != nil { t.Fatal(err) } diff --git a/core/error.go b/core/error.go index 7dd5b8a432..26b007f9d9 100644 --- a/core/error.go +++ b/core/error.go @@ -66,11 +66,6 @@ var ( // have enough funds for transfer(topmost call only). ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer") - // ErrInsufficientBalanceWitness is returned if the transaction sender has enough - // funds to cover the transfer, but not enough to pay for witness access/modification - // costs for the transaction - ErrInsufficientBalanceWitness = errors.New("insufficient funds to cover witness access costs for transaction") - // ErrInsufficientFunds is returned if the total cost of executing a transaction // is higher than the balance of the user's account. ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value") diff --git a/core/state_processor.go b/core/state_processor.go index 48a8da4062..5b81abef6f 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -88,10 +88,10 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated blockAccessList = bal.NewConstructionBlockAccessList() ) defer evm.Release() + if jumpDestCache != nil { evm.SetJumpDestCache(jumpDestCache) } - // Run the pre-execution system calls blockAccessList.Merge(PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), config, evm, block.Number(), block.Time())) diff --git a/core/state_transition.go b/core/state_transition.go index bbeb163b16..3b1642e0b9 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -68,16 +68,19 @@ func (result *ExecutionResult) Revert() []byte { } // IntrinsicGas computes the 'intrinsic gas' for a message with the given data. -func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation bool, rules params.Rules, costPerStateByte uint64) (vm.GasCosts, error) { +func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, from common.Address, to *common.Address, value *uint256.Int, rules params.Rules, costPerStateByte uint64) (vm.GasCosts, error) { + isContractCreation := to == nil + // Set the starting gas for the raw transaction var gas vm.GasCosts - if isContractCreation && rules.IsHomestead { - if rules.IsAmsterdam { - gas.RegularGas = params.TxGas + params.CreateGasAmsterdam + if rules.IsAmsterdam { + gas.RegularGas = intrinsicBaseGasEIP2780(from, to, value) + if isContractCreation { + // New-account creation is charged as state gas (EIP-8037). gas.StateGas = params.AccountCreationSize * costPerStateByte - } else { - gas.RegularGas = params.TxGasContractCreation } + } else if isContractCreation && rules.IsHomestead { + gas.RegularGas = params.TxGasContractCreation } else { gas.RegularGas = params.TxGas } @@ -151,8 +154,41 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set return gas, nil } +// intrinsicBaseGasEIP2780 computes the regular-gas portion of the EIP-2780 +// intrinsic base cost: the per-resource decomposition of the legacy flat 21,000. +func intrinsicBaseGasEIP2780(from common.Address, to *common.Address, value *uint256.Int) uint64 { + var ( + isContractCreation = to == nil + isSelfTransfer = to != nil && *to == from + hasValue = value != nil && !value.IsZero() + ) + // tx.sender: signature recovery plus the sender account access and write. + gas := params.TxBaseCost2780 + + // tx.to charge. + switch { + case isSelfTransfer: + // The recipient account is already accessed and written as the sender. + case isContractCreation: + gas += params.CreateAccess2780 + default: + gas += params.ColdAccountAccess2780 + } + + // tx.value charge. + switch { + case !hasValue || isSelfTransfer: + // No transfer log and no recipient balance write. + case isContractCreation: + gas += params.TransferLogCost2780 + default: + gas += params.TransferLogCost2780 + params.TxValueCost2780 + } + return gas +} + // FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623). -func FloorDataGas(rules params.Rules, data []byte, accessList types.AccessList) (uint64, error) { +func FloorDataGas(rules params.Rules, from common.Address, to *common.Address, value *uint256.Int, data []byte, accessList types.AccessList) (uint64, error) { var ( tokens uint64 tokenCost uint64 @@ -198,12 +234,19 @@ func FloorDataGas(rules params.Rules, data []byte, accessList types.AccessList) tokenCost = params.TxCostFloorPerToken } + // The floor is anchored to the transaction base cost. Under EIP-2780 that + // base is the per-resource decomposition (the same one used by the intrinsic + // gas), so the floor never undercuts the transaction's own base. + floorBase := params.TxGas + if rules.IsAmsterdam { + floorBase = intrinsicBaseGasEIP2780(from, to, value) + } // Check for overflow - if (math.MaxUint64-params.TxGas)/tokenCost < tokens { + if (math.MaxUint64-floorBase)/tokenCost < tokens { return 0, ErrGasUintOverflow } // Minimum gas required for a transaction based on its data tokens (EIP-7623). - return params.TxGas + tokens*tokenCost, nil + return floorBase + tokens*tokenCost, nil } // toWordSize returns the ceiled word size required for init code payment calculation. @@ -614,7 +657,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { contractCreation = msg.To == nil floorDataGas uint64 ) - cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules, st.evm.Context.CostPerStateByte) + cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, msg.From, msg.To, msg.Value, rules, st.evm.Context.CostPerStateByte) if err != nil { return nil, err } @@ -629,7 +672,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { // Validate the EIP-7623 calldata floor against the gas limit. The floor inflates // the total gas usage at tx end, so the gas limit must be sufficient to cover that. if rules.IsPrague { - floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList) + floorDataGas, err = FloorDataGas(rules, msg.From, msg.To, msg.Value, msg.Data, msg.AccessList) if err != nil { return nil, err } @@ -705,9 +748,16 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { if addr, ok := types.ParseDelegation(st.state.GetCode(*msg.To)); ok { st.state.AddAddressToAccessList(addr) } - // Execute the transaction's call. - ret, result, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining.ForwardAll(), value) - st.gasRemaining.Absorb(result) + // EIP-2780: charge the transaction's top-level recipient costs. If the + // budget cannot cover the charge, the top frame halts out of gas. + if rules.IsAmsterdam && !st.chargeCallRecipientEIP2780(value) { + vmerr = vm.ErrOutOfGas + st.gasRemaining = st.gasRemaining.ExitHalt() + } else { + // Execute the transaction's call. + ret, result, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining.ForwardAll(), value) + st.gasRemaining.Absorb(result) + } } // Settle down the gas usage and refund the ETH back if any remaining @@ -754,6 +804,47 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { }, nil } +// chargeCallRecipientEIP2780 applies the EIP-2780 transaction top-level gas costs for +// a message-call transaction, charged before any opcode executes: +// +// - if the recipient is EIP-161 non-existent and the transaction carries value, +// charge for account creation. +// +// - if the recipient is an EIP-7702 delegated account, resolving the delegation +// loads the target's code, charged an additional cold account access in +// regular gas. +func (st *stateTransition) chargeCallRecipientEIP2780(value *uint256.Int) bool { + var ( + cost vm.GasCosts + to = *st.msg.To + ) + // This runs in the topmost frame before any bytecode executes, so unlike the + // execution-level checks which must use StateDB.Empty because SELFDESTRUCT can + // leave a transient EIP-161-empty account, no empty account can exist here, and + // !Exist is equivalent to Empty. + if !value.IsZero() && !st.state.Exist(to) { + cost.StateGas += params.AccountCreationSize * st.evm.Context.CostPerStateByte + } + if _, ok := types.ParseDelegation(st.state.GetCode(to)); ok { + // EIP-2780: The tx.sender, tx.to, and (where applicable) delegation-target + // charges above are always at the cold rate. + // + // The delegation-target is already warmed before, no double warming here. + cost.RegularGas += params.ColdAccountAccess2780 + } + if cost == (vm.GasCosts{}) { + return true + } + prior, ok := st.gasRemaining.Charge(cost) + if !ok { + return false + } + if st.evm.Config.Tracer.HasGasHook() { + st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas) + } + return true +} + // settleGas finalizes the per-tx gas accounting after EVM execution: // // - Snapshots the EIP-8037 block-level 2D figures (tx_regular_gas, diff --git a/core/state_transition_test.go b/core/state_transition_test.go index be2de7f511..ace43aa13a 100644 --- a/core/state_transition_test.go +++ b/core/state_transition_test.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" ) func TestFloorDataGas(t *testing.T) { @@ -73,21 +74,22 @@ func TestFloorDataGas(t *testing.T) { { name: "amsterdam/empty", amsterdam: true, - want: params.TxGas, + // EIP-2780 anchors the floor to the reduced base cost. + want: params.TxBaseCost2780, }, { name: "amsterdam/data-only", amsterdam: true, data: bytes.Repeat([]byte{0x00}, 1024), // post-amsterdam: every byte = 4 tokens regardless of value - want: params.TxGas + 1024*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976, + want: params.TxBaseCost2780 + 1024*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976, }, { name: "amsterdam/data-non-zero", amsterdam: true, data: bytes.Repeat([]byte{0xff}, 1024), // same as zero data post-amsterdam - want: params.TxGas + 1024*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976, + want: params.TxBaseCost2780 + 1024*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976, }, { name: "amsterdam/access-list-addresses-only", @@ -97,7 +99,7 @@ func TestFloorDataGas(t *testing.T) { {Address: addr2}, }, // 2 * 20 bytes * 4 tokens/byte * 16 cost/token - want: params.TxGas + 2*common.AddressLength*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976, + want: params.TxBaseCost2780 + 2*common.AddressLength*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976, }, { name: "amsterdam/access-list-with-storage-keys", @@ -106,7 +108,7 @@ func TestFloorDataGas(t *testing.T) { {Address: addr1, StorageKeys: []common.Hash{key1, key2}}, }, // 1 addr * 20 * 4 + 2 keys * 32 * 4 = 80 + 256 = 336 tokens * 16 - want: params.TxGas + (1*common.AddressLength+2*common.HashLength)*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976, + want: params.TxBaseCost2780 + (1*common.AddressLength+2*common.HashLength)*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976, }, { name: "amsterdam/mixed", @@ -117,13 +119,13 @@ func TestFloorDataGas(t *testing.T) { {Address: addr2, StorageKeys: []common.Hash{key1, key2}}, }, // data: 100*4 = 400; addrs: 2*20*4 = 160; keys: 3*32*4 = 384; total = 944 * 16 - want: params.TxGas + (100*params.TxTokenPerNonZeroByte+2*common.AddressLength*params.TxTokenPerNonZeroByte+3*common.HashLength*params.TxTokenPerNonZeroByte)*params.TxCostFloorPerToken7976, + want: params.TxBaseCost2780 + (100*params.TxTokenPerNonZeroByte+2*common.AddressLength*params.TxTokenPerNonZeroByte+3*common.HashLength*params.TxTokenPerNonZeroByte)*params.TxCostFloorPerToken7976, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { rules := params.Rules{IsAmsterdam: tt.amsterdam} - got, err := FloorDataGas(rules, tt.data, tt.accessList) + got, err := FloorDataGas(rules, addr1, &addr1, new(uint256.Int), tt.data, tt.accessList) if err != nil { t.Fatalf("unexpected error: %v", err) } @@ -155,6 +157,7 @@ func TestIntrinsicGas(t *testing.T) { isEIP2028 bool isEIP3860 bool isAmsterdam bool + value *uint256.Int want vm.GasCosts }{ { @@ -237,8 +240,9 @@ func TestIntrinsicGas(t *testing.T) { }, isEIP2028: true, isAmsterdam: true, - // base access-list charge + EIP-7981 extra - want: vm.GasCosts{RegularGas: params.TxGas + + // EIP-2780: zero-value call base is TxBaseCost + ColdAccountAccess + // (15,000). Plus base access-list charge + EIP-7981 extra. + want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 + 2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas + 2*amsterdamAddressCost + 3*amsterdamStorageKeyCost}, }, @@ -259,10 +263,10 @@ func TestIntrinsicGas(t *testing.T) { isHomestead: true, isEIP2028: true, isAmsterdam: true, - // EIP-8037: creation regular gas is TxGas + CreateGasAmsterdam (not TxGasContractCreation), - // and account-creation cost is moved to state gas. + // EIP-2780: creation regular gas is TxBaseCost + CreateAccess (23,000), + // and account-creation cost is charged as state gas. want: vm.GasCosts{ - RegularGas: params.TxGas + params.CreateGasAmsterdam, + RegularGas: params.TxBaseCost2780 + params.CreateAccess2780, StateGas: params.AccountCreationSize * params.CostPerStateByte, }, }, @@ -275,7 +279,7 @@ func TestIntrinsicGas(t *testing.T) { isEIP3860: true, // Shanghai gates init-code word gas isAmsterdam: true, want: vm.GasCosts{ - RegularGas: params.TxGas + params.CreateGasAmsterdam + + RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 + 64*params.TxDataZeroGas + 2*params.InitCodeWordGas, StateGas: params.AccountCreationSize * params.CostPerStateByte, }, @@ -292,7 +296,7 @@ func TestIntrinsicGas(t *testing.T) { isEIP3860: true, isAmsterdam: true, want: vm.GasCosts{ - RegularGas: params.TxGas + params.CreateGasAmsterdam + + RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 + 32*params.TxDataNonZeroGasEIP2028 + 1*params.InitCodeWordGas + 1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas + 1*amsterdamAddressCost + 1*amsterdamStorageKeyCost, @@ -314,7 +318,7 @@ func TestIntrinsicGas(t *testing.T) { // regular: TxAuthTupleRegularGas (7500) per auth // state: (AuthorizationCreationSize + AccountCreationSize) * CostPerStateByte per auth want: vm.GasCosts{ - RegularGas: params.TxGas + + RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 + 100*params.TxDataNonZeroGasEIP2028 + 1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas + 1*amsterdamAddressCost + 1*amsterdamStorageKeyCost + @@ -322,6 +326,28 @@ func TestIntrinsicGas(t *testing.T) { StateGas: 1 * (params.AuthorizationCreationSize + params.AccountCreationSize) * params.CostPerStateByte, }, }, + { + name: "amsterdam/value-transfer-call", + isEIP2028: true, + isAmsterdam: true, + value: uint256.NewInt(1), + // EIP-2780: TxBaseCost + ColdAccountAccess + TransferLogCost + TxValueCost = 21,000. + want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 + + params.TransferLogCost2780 + params.TxValueCost2780}, + }, + { + name: "amsterdam/value-bearing-contract-creation", + creation: true, + isHomestead: true, + isEIP2028: true, + isAmsterdam: true, + value: uint256.NewInt(1), + // EIP-2780: TxBaseCost + CreateAccess + TransferLogCost = 24,756, plus account-creation state gas. + want: vm.GasCosts{ + RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 + params.TransferLogCost2780, + StateGas: params.AccountCreationSize * params.CostPerStateByte, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -331,8 +357,12 @@ func TestIntrinsicGas(t *testing.T) { IsShanghai: tt.isEIP3860, IsAmsterdam: tt.isAmsterdam, } + var to *common.Address + if !tt.creation { + to = &addr1 + } got, err := IntrinsicGas(tt.data, tt.accessList, tt.authList, - tt.creation, rules, params.CostPerStateByte) + common.Address{}, to, tt.value, rules, params.CostPerStateByte) if err != nil { t.Fatalf("unexpected error: %v", err) } diff --git a/core/txpool/validation.go b/core/txpool/validation.go index 4651c06b3e..b53e3ee2bf 100644 --- a/core/txpool/validation.go +++ b/core/txpool/validation.go @@ -29,6 +29,7 @@ import ( "github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" ) var ( @@ -116,16 +117,22 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types return core.ErrTipAboveFeeCap } // Make sure the transaction is signed properly - if _, err := types.Sender(signer, tx); err != nil { + from, err := types.Sender(signer, tx) + if err != nil { return fmt.Errorf("%w: %v", ErrInvalidSender, err) } // Limit nonce to 2^64-1 per EIP-2681 if tx.Nonce()+1 < tx.Nonce() { return core.ErrNonceMax } + // Sanity check for extremely large numbers (supported by RLP or RPC) + value, overflow := uint256.FromBig(tx.Value()) + if overflow { + return core.ErrInsufficientFunds + } // Ensure the transaction has more gas than the bare minimum needed to cover // the transaction metadata - intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte) + intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), from, tx.To(), value, rules, params.CostPerStateByte) if err != nil { return err } @@ -134,7 +141,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types } // Ensure the transaction can cover floor data gas. if rules.IsPrague { - floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList()) + floorDataGas, err := core.FloorDataGas(rules, from, tx.To(), value, tx.Data(), tx.AccessList()) if err != nil { return err } diff --git a/params/protocol_params.go b/params/protocol_params.go index 84ded9df48..7416eb8cff 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -103,6 +103,13 @@ const ( TxAuthTupleGas uint64 = 12500 // Per auth tuple code specified in EIP-7702 TxAuthTupleRegularGas uint64 = 7500 // Per auth tuple regular gas specified in EIP-8037 + // EIP-2780: resource-based intrinsic transaction gas. + TxBaseCost2780 uint64 = 12000 + ColdAccountAccess2780 uint64 = 3000 + CreateAccess2780 uint64 = 11000 + TxValueCost2780 uint64 = 4244 + TransferLogCost2780 uint64 = 1756 + // These have been changed during the course of the chain CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction. CallGasEIP150 uint64 = 700 // Static portion of gas for CALL-derivates after EIP 150 (Tangerine) diff --git a/tests/transaction_test_util.go b/tests/transaction_test_util.go index 91f7d6c3ec..010c31324b 100644 --- a/tests/transaction_test_util.go +++ b/tests/transaction_test_util.go @@ -27,6 +27,7 @@ import ( "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" + "github.com/holiman/uint256" ) // TransactionTest checks RLP decoding and sender derivation of transactions. @@ -81,7 +82,11 @@ func (tt *TransactionTest) Run() error { return } // Intrinsic cost - cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte) + value, overflow := uint256.FromBig(tx.Value()) + if overflow { + return sender, hash, 0, errors.New("value exceeds 256 bits") + } + cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), sender, tx.To(), value, rules, params.CostPerStateByte) if err != nil { return } @@ -92,7 +97,7 @@ func (tt *TransactionTest) Run() error { if rules.IsPrague { var floorDataGas uint64 - floorDataGas, err = core.FloorDataGas(rules, tx.Data(), tx.AccessList()) + floorDataGas, err = core.FloorDataGas(rules, sender, tx.To(), value, tx.Data(), tx.AccessList()) if err != nil { return } From eccd239a297e8477333fcd3e363399e1a5575d37 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Mon, 29 Jun 2026 17:12:34 +0800 Subject: [PATCH 02/16] trie: introduce UpdateBatch (#32448) This feature is an optimization used in the BAL, mostly for experimental purpose. --------- Co-authored-by: jwasinger Co-authored-by: MariusVanDerWijden --- core/state/database.go | 6 +++ trie/bintrie/trie.go | 29 ++++++++++ trie/secure_trie.go | 46 ++++++++++++++++ trie/tracer.go | 19 +++++-- trie/transitiontrie/transition.go | 29 ++++++++++ trie/trie.go | 89 +++++++++++++++++++++++++++++++ trie/trie_test.go | 54 +++++++++++++++++++ 7 files changed, 269 insertions(+), 3 deletions(-) diff --git a/core/state/database.go b/core/state/database.go index 3b1e627f28..1001a4b4cf 100644 --- a/core/state/database.go +++ b/core/state/database.go @@ -107,12 +107,18 @@ type Trie interface { // in the trie with provided address. UpdateAccount(address common.Address, account *types.StateAccount, codeLen int) error + // UpdateAccountBatch attempts to update a list of accounts in the batch manner. + UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, codeLengths []int) error + // UpdateStorage associates key with value in the trie. If value has length zero, // any existing value is deleted from the trie. The value bytes must not be modified // by the caller while they are stored in the trie. If a node was not found in the // database, a trie.MissingNodeError is returned. UpdateStorage(addr common.Address, key, value []byte) error + // UpdateStorageBatch attempts to update a list of storages in the batch manner. + UpdateStorageBatch(_ common.Address, keys [][]byte, values [][]byte) error + // DeleteAccount abstracts an account deletion from the trie. DeleteAccount(address common.Address) error diff --git a/trie/bintrie/trie.go b/trie/bintrie/trie.go index 7b05e68df9..e8b3021284 100644 --- a/trie/bintrie/trie.go +++ b/trie/bintrie/trie.go @@ -474,3 +474,32 @@ func (t *BinaryTrie) PrefetchStorage(addr common.Address, keys [][]byte) error { func (t *BinaryTrie) Witness() map[string][]byte { return t.tracer.Values() } + +// UpdateStorageBatch updates a list of storage slots sequentially. +func (t *BinaryTrie) UpdateStorageBatch(address common.Address, keys [][]byte, values [][]byte) error { + if len(keys) != len(values) { + return fmt.Errorf("keys and values length mismatch: %d != %d", len(keys), len(values)) + } + for i, key := range keys { + if err := t.UpdateStorage(address, key, values[i]); err != nil { + return err + } + } + return nil +} + +// UpdateAccountBatch updates a list of accounts sequentially. +func (t *BinaryTrie) UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, codeLens []int) error { + if len(addresses) != len(accounts) { + return fmt.Errorf("addresses and accounts length mismatch: %d != %d", len(addresses), len(accounts)) + } + if len(addresses) != len(codeLens) { + return fmt.Errorf("addresses and code length mismatch: %d != %d", len(addresses), len(codeLens)) + } + for i, addr := range addresses { + if err := t.UpdateAccount(addr, accounts[i], codeLens[i]); err != nil { + return err + } + } + return nil +} diff --git a/trie/secure_trie.go b/trie/secure_trie.go index 4d03ca45f0..9f6cea9790 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -210,6 +210,29 @@ func (t *StateTrie) UpdateStorage(_ common.Address, key, value []byte) error { return nil } +// UpdateStorageBatch attempts to update a list storages in the batch manner. +func (t *StateTrie) UpdateStorageBatch(_ common.Address, keys [][]byte, values [][]byte) error { + var ( + hkeys = make([][]byte, 0, len(keys)) + evals = make([][]byte, 0, len(values)) + ) + for _, key := range keys { + hk := crypto.Keccak256(key) + if t.preimages != nil { + t.secKeyCache[common.Hash(hk)] = common.CopyBytes(key) + } + hkeys = append(hkeys, hk) + } + for _, val := range values { + data, err := rlp.EncodeToBytes(val) + if err != nil { + return err + } + evals = append(evals, data) + } + return t.trie.UpdateBatch(hkeys, evals) +} + // UpdateAccount will abstract the write of an account to the secure trie. func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccount, _ int) error { hk := crypto.Keccak256(address.Bytes()) @@ -226,6 +249,29 @@ func (t *StateTrie) UpdateAccount(address common.Address, acc *types.StateAccoun return nil } +// UpdateAccountBatch attempts to update a list accounts in the batch manner. +func (t *StateTrie) UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, _ []int) error { + var ( + hkeys = make([][]byte, 0, len(addresses)) + values = make([][]byte, 0, len(accounts)) + ) + for _, addr := range addresses { + hk := crypto.Keccak256(addr.Bytes()) + if t.preimages != nil { + t.secKeyCache[common.Hash(hk)] = addr.Bytes() + } + hkeys = append(hkeys, hk) + } + for _, acc := range accounts { + data, err := rlp.EncodeToBytes(acc) + if err != nil { + return err + } + values = append(values, data) + } + return t.trie.UpdateBatch(hkeys, values) +} + func (t *StateTrie) UpdateContractCode(_ common.Address, _ common.Hash, _ []byte) error { return nil } diff --git a/trie/tracer.go b/trie/tracer.go index 04122d1384..042fa468bf 100644 --- a/trie/tracer.go +++ b/trie/tracer.go @@ -33,12 +33,10 @@ import ( // while the latter is inserted/deleted in order to follow the rule of trie. // This tool can track all of them no matter the node is embedded in its // parent or not, but valueNode is never tracked. -// -// Note opTracer is not thread-safe, callers should be responsible for handling -// the concurrency issues by themselves. type opTracer struct { inserts map[string]struct{} deletes map[string]struct{} + lock sync.RWMutex } // newOpTracer initializes the tracer for capturing trie changes. @@ -53,6 +51,9 @@ func newOpTracer() *opTracer { // in the deletion set (resurrected node), then just wipe it from // the deletion set as it's "untouched". func (t *opTracer) onInsert(path []byte) { + t.lock.Lock() + defer t.lock.Unlock() + if _, present := t.deletes[string(path)]; present { delete(t.deletes, string(path)) return @@ -64,6 +65,9 @@ func (t *opTracer) onInsert(path []byte) { // in the addition set, then just wipe it from the addition set // as it's untouched. func (t *opTracer) onDelete(path []byte) { + t.lock.Lock() + defer t.lock.Unlock() + if _, present := t.inserts[string(path)]; present { delete(t.inserts, string(path)) return @@ -73,12 +77,18 @@ func (t *opTracer) onDelete(path []byte) { // reset clears the content tracked by tracer. func (t *opTracer) reset() { + t.lock.Lock() + defer t.lock.Unlock() + clear(t.inserts) clear(t.deletes) } // copy returns a deep copied tracer instance. func (t *opTracer) copy() *opTracer { + t.lock.RLock() + defer t.lock.RUnlock() + return &opTracer{ inserts: maps.Clone(t.inserts), deletes: maps.Clone(t.deletes), @@ -87,6 +97,9 @@ func (t *opTracer) copy() *opTracer { // deletedList returns a list of node paths which are deleted from the trie. func (t *opTracer) deletedList() [][]byte { + t.lock.RLock() + defer t.lock.RUnlock() + paths := make([][]byte, 0, len(t.deletes)) for path := range t.deletes { paths = append(paths, []byte(path)) diff --git a/trie/transitiontrie/transition.go b/trie/transitiontrie/transition.go index 3e5511be9e..d939e804e3 100644 --- a/trie/transitiontrie/transition.go +++ b/trie/transitiontrie/transition.go @@ -144,6 +144,19 @@ func (t *TransitionTrie) UpdateStorage(address common.Address, key []byte, value return t.overlay.UpdateStorage(address, key, v) } +// UpdateStorageBatch attempts to update a list storages in the batch manner. +func (t *TransitionTrie) UpdateStorageBatch(address common.Address, keys [][]byte, values [][]byte) error { + if len(keys) != len(values) { + return fmt.Errorf("keys and values length mismatch: %d != %d", len(keys), len(values)) + } + for i, key := range keys { + if err := t.UpdateStorage(address, key, values[i]); err != nil { + return err + } + } + return nil +} + // UpdateAccount abstract an account write to the trie. func (t *TransitionTrie) UpdateAccount(addr common.Address, account *types.StateAccount, codeLen int) error { // NOTE: before the rebase, this was saving the state root, so that OpenStorageTrie @@ -152,6 +165,22 @@ func (t *TransitionTrie) UpdateAccount(addr common.Address, account *types.State return t.overlay.UpdateAccount(addr, account, codeLen) } +// UpdateAccountBatch attempts to update a list accounts in the batch manner. +func (t *TransitionTrie) UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, codeLens []int) error { + if len(addresses) != len(accounts) { + return fmt.Errorf("address and accounts length mismatch: %d != %d", len(addresses), len(accounts)) + } + if len(addresses) != len(codeLens) { + return fmt.Errorf("address and code length mismatch: %d != %d", len(addresses), len(codeLens)) + } + for i, addr := range addresses { + if err := t.UpdateAccount(addr, accounts[i], codeLens[i]); err != nil { + return err + } + } + return nil +} + // DeleteStorage removes any existing value for key from the trie. If a node was not // found in the database, a trie.MissingNodeError is returned. func (t *TransitionTrie) DeleteStorage(addr common.Address, key []byte) error { diff --git a/trie/trie.go b/trie/trie.go index 1ef2c2f1a6..0026c6048c 100644 --- a/trie/trie.go +++ b/trie/trie.go @@ -480,6 +480,95 @@ func (t *Trie) insert(n node, prefix, key []byte, value node) (bool, node, error } } +// parallelUpdateThreshold is the minimum number of entries a batch must contain +// before UpdateBatch processes them concurrently; smaller batches are applied +// sequentially to avoid the goroutine overhead. +const parallelUpdateThreshold = 4 + +// UpdateBatch updates a batch of entries concurrently. +func (t *Trie) UpdateBatch(keys [][]byte, values [][]byte) error { + // Short circuit if the trie is already committed and unusable. + if t.committed { + return ErrCommitted + } + if len(keys) != len(values) { + return fmt.Errorf("keys and values length mismatch: %d != %d", len(keys), len(values)) + } + // Insert the entries sequentially if there are not too many + // trie nodes in the trie. + fn, ok := t.root.(*fullNode) + if !ok || len(keys) < parallelUpdateThreshold { + return t.updateSequential(keys, values) + } + // Group the entries by the first nibble of the (hex) key, so that each group + // can be applied to a distinct child of the root independently. + var ( + ikeys = make(map[byte][][]byte) + ivals = make(map[byte][][]byte) + deleted [17]bool // child positions receiving at least one deletion + eg errgroup.Group + ) + for i, key := range keys { + hkey := keybytesToHex(key) + ikeys[hkey[0]] = append(ikeys[hkey[0]], hkey) + ivals[hkey[0]] = append(ivals[hkey[0]], values[i]) + if len(values[i]) == 0 { + deleted[hkey[0]] = true + } + } + // If the root may be collapsed after applying the updates, fallback + // to the sequential mode to eliminate the additional complexity. + survivors := 0 + for i, child := range &fn.Children { + if child != nil && !deleted[i] { + survivors++ + } + } + if survivors < 2 { + return t.updateSequential(keys, values) + } + // Execute the trie updates in concurrent mode + fn.flags = t.newFlag() + for pos, ks := range ikeys { + eg.Go(func() error { + vs := ivals[pos] + prefix := []byte{pos} + for i, k := range ks { + if len(vs[i]) != 0 { + _, n, err := t.insert(fn.Children[pos], prefix, k[1:], valueNode(vs[i])) + if err != nil { + return err + } + fn.Children[pos] = n + } else { + _, n, err := t.delete(fn.Children[pos], prefix, k[1:]) + if err != nil { + return err + } + fn.Children[pos] = n + } + } + return nil + }) + } + if err := eg.Wait(); err != nil { + return err + } + t.unhashed += len(keys) + t.uncommitted += len(keys) + return nil +} + +// updateSequential applies the given entries to the trie one by one. +func (t *Trie) updateSequential(keys [][]byte, values [][]byte) error { + for i, key := range keys { + if err := t.Update(key, values[i]); err != nil { + return err + } + } + return nil +} + // MustDelete is a wrapper of Delete and will omit any encountered error but // just print out an error message. func (t *Trie) MustDelete(key []byte) { diff --git a/trie/trie_test.go b/trie/trie_test.go index 3661933e22..949f381f07 100644 --- a/trie/trie_test.go +++ b/trie/trie_test.go @@ -1580,3 +1580,57 @@ func BenchmarkTrieSeqPrefetch(b *testing.B) { } } } + +func TestUpdateBatch(t *testing.T) { + testUpdateBatch(t, []kv{ + {k: []byte("do"), v: []byte("verb")}, + {k: []byte("ether"), v: []byte("wookiedoo")}, + {k: []byte("horse"), v: []byte("stallion")}, + {k: []byte("shaman"), v: []byte("horse")}, + {k: []byte("doge"), v: []byte("coin")}, + {k: []byte("dog"), v: []byte("puppy")}, + }) + + var entries []kv + for i := 0; i < 256; i++ { + entries = append(entries, kv{k: testrand.Bytes(32), v: testrand.Bytes(32)}) + } + testUpdateBatch(t, entries) +} + +func testUpdateBatch(t *testing.T, entries []kv) { + var ( + base = NewEmpty(nil) + keys [][]byte + vals [][]byte + ) + for _, entry := range entries { + base.Update(entry.k, entry.v) + keys = append(keys, entry.k) + vals = append(vals, entry.v) + } + for i := 0; i < 10; i++ { + k, v := testrand.Bytes(32), testrand.Bytes(32) + base.Update(k, v) + keys = append(keys, k) + vals = append(vals, v) + } + + cmp := NewEmpty(nil) + if err := cmp.UpdateBatch(keys, vals); err != nil { + t.Fatalf("Failed to update batch, %v", err) + } + + // Traverse the original tree, the changes made on the copy one shouldn't + // affect the old one + for _, key := range keys { + v1, _ := base.Get(key) + v2, _ := cmp.Get(key) + if !bytes.Equal(v1, v2) { + t.Errorf("Unexpected data, key: %v, want: %v, got: %v", key, v1, v2) + } + } + if base.Hash() != cmp.Hash() { + t.Errorf("Hash mismatch: want %x, got %x", base.Hash(), cmp.Hash()) + } +} From 11524ba075eea76dab4fd1c11bba79a869350efd Mon Sep 17 00:00:00 2001 From: cui Date: Mon, 29 Jun 2026 20:59:12 +0800 Subject: [PATCH 03/16] core/rawdb: fix freezer metadata log field ordering (#35246) --- core/rawdb/freezer_meta.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/rawdb/freezer_meta.go b/core/rawdb/freezer_meta.go index 3cda9ae45c..03c26f7daf 100644 --- a/core/rawdb/freezer_meta.go +++ b/core/rawdb/freezer_meta.go @@ -112,7 +112,7 @@ func decodeV2(file *os.File) *freezerTableMeta { return nil } if o.Offset > math.MaxInt64 { - log.Error("Invalid flushOffset %d in freezer metadata", o.Offset, "file", file.Name()) + log.Error("Invalid flushOffset %d in freezer metadata", "offset", o.Offset, "file", file.Name()) return nil } return &freezerTableMeta{ From a0568b1dbfdebb2db967283d44cdef3b83f41013 Mon Sep 17 00:00:00 2001 From: rayoo Date: Mon, 29 Jun 2026 06:41:33 -0700 Subject: [PATCH 04/16] cmd/geth: release account iterator in listEIP7610EligibleAccounts (#35250) listEIP7610EligibleAccounts opens an account iterator and never releases it. --- cmd/geth/snapshot.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/geth/snapshot.go b/cmd/geth/snapshot.go index 8fedc21c27..c0258df68c 100644 --- a/cmd/geth/snapshot.go +++ b/cmd/geth/snapshot.go @@ -1054,6 +1054,7 @@ func listEIP7610EligibleAccounts(ctx *cli.Context) error { log.Error("Failed to get account iterator", "err", err) return err } + defer iter.Release() var ( start = time.Now() accounts []common.Address From 9700b5b10eee6ea3e0f71eaa2da3875e25047c7b Mon Sep 17 00:00:00 2001 From: Chase Wright Date: Mon, 29 Jun 2026 08:55:42 -0500 Subject: [PATCH 05/16] cmd/devp2p: support dual-stack discovery listener (#35220) `devp2p discv4 listen` / `discv5 listen` is the supported replacement for the removed bootnode tool, but it bound IPv4-only and `-extaddr` took a single address, so it couldn't run a dual-stack bootnode. This binds the listener dual-stack (falling back to IPv4-only where IPv6 is unavailable) and lets `-extaddr` take a comma-separated IPv4/IPv6 pair. A single node can then advertise both `ip` and `ip6` in its ENR over one UDP port: ``` devp2p discv4 listen --nodekey --addr [::]:30301 \ --extaddr 203.0.113.10:30301,[2001:db8::1]:30301 ``` The fallback IP is only derived from the listener when no `-extaddr` is given, so a v4- or v6-only `-extaddr` no longer leaks a loopback entry. All addresses must share one UDP port (single socket). --- cmd/devp2p/discv4cmd.go | 60 ++++++++++++++++++++++++++++------------- 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/cmd/devp2p/discv4cmd.go b/cmd/devp2p/discv4cmd.go index 84c7ef0c44..1089210c57 100644 --- a/cmd/devp2p/discv4cmd.go +++ b/cmd/devp2p/discv4cmd.go @@ -126,7 +126,7 @@ var ( } extAddrFlag = &cli.StringFlag{ Name: "extaddr", - Usage: "UDP endpoint announced in ENR. You can provide a bare IP address or IP:port as the value of this flag.", + Usage: "UDP endpoint announced in ENR. You can provide a bare IP address or IP:port as the value of this flag. Provide a comma-separated pair to announce both an IPv4 and an IPv6 endpoint.", } crawlTimeoutFlag = &cli.DurationFlag{ Name: "timeout", @@ -344,36 +344,60 @@ func parseExtAddr(spec string) (ip net.IP, port int, ok bool) { func listen(ctx *cli.Context, ln *enode.LocalNode) *net.UDPConn { addr := ctx.String(listenAddrFlag.Name) + extAddr := ctx.String(extAddrFlag.Name) + var ( + socket net.PacketConn + err error + ) if addr == "" { - addr = "0.0.0.0:0" + // Dual-stack socket, falling back to IPv4-only where IPv6 is unavailable. + if socket, err = net.ListenPacket("udp", "[::]:0"); err != nil { + socket, err = net.ListenPacket("udp", "0.0.0.0:0") + } + } else { + socket, err = net.ListenPacket("udp", addr) } - socket, err := net.ListenPacket("udp4", addr) if err != nil { exit(err) } - // Configure UDP endpoint in ENR from listener address. + // Configure the ENR endpoint from the listener address, but only without an + // explicit -extaddr: otherwise we'd announce a fallback IP for an address + // family the user didn't specify (e.g. loopback IPv4 on an IPv6-only node). usocket := socket.(*net.UDPConn) uaddr := socket.LocalAddr().(*net.UDPAddr) - if uaddr.IP.IsUnspecified() { - ln.SetFallbackIP(net.IP{127, 0, 0, 1}) - } else { - ln.SetFallbackIP(uaddr.IP) + if extAddr == "" { + if uaddr.IP.IsUnspecified() { + ln.SetFallbackIP(net.IP{127, 0, 0, 1}) + } else { + ln.SetFallbackIP(uaddr.IP) + } } ln.SetFallbackUDP(uaddr.Port) - // If an ENR endpoint is set explicitly on the command-line, override - // the information from the listening address. Note this is careful not - // to set the UDP port if the external address doesn't have it. - extAddr := ctx.String(extAddrFlag.Name) + // Override with explicit -extaddr address(es). A static IP is set per family, + // and all specs share one UDP port because the node has a single socket. if extAddr != "" { - ip, port, ok := parseExtAddr(extAddr) - if !ok { - exit(fmt.Errorf("-%s: invalid external address %q", extAddrFlag.Name, extAddr)) + var extPort int + for spec := range strings.SplitSeq(extAddr, ",") { + spec = strings.TrimSpace(spec) + if spec == "" { + continue + } + ip, port, ok := parseExtAddr(spec) + if !ok { + exit(fmt.Errorf("-%s: invalid external address %q", extAddrFlag.Name, spec)) + } + ln.SetStaticIP(ip) + if port != 0 { + if extPort != 0 && port != extPort { + exit(fmt.Errorf("-%s: all addresses must announce the same UDP port, got %d and %d", extAddrFlag.Name, extPort, port)) + } + extPort = port + } } - ln.SetStaticIP(ip) - if port != 0 { - ln.SetFallbackUDP(port) + if extPort != 0 { + ln.SetFallbackUDP(extPort) } } From 68671a453079b6fc2b8ec88a567545ddc361a8d7 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Tue, 30 Jun 2026 08:58:43 +0800 Subject: [PATCH 06/16] core: implement EIP-8246 (#35219) EIP: https://eips.ethereum.org/EIPS/eip-8246 Supersedes #35218 --- core/bal_test.go | 56 ++++++++++++ core/eip8246_test.go | 105 +++++++++++++++++++++ core/state/statedb.go | 172 ++++++++++++++++++++--------------- core/state/statedb_hooked.go | 14 +-- core/state_transition.go | 6 -- core/types/log.go | 14 --- core/vm/instructions.go | 19 ++-- core/vm/interface.go | 1 - 8 files changed, 278 insertions(+), 109 deletions(-) create mode 100644 core/eip8246_test.go diff --git a/core/bal_test.go b/core/bal_test.go index 6b79f86c43..84579ca564 100644 --- a/core/bal_test.go +++ b/core/bal_test.go @@ -946,6 +946,62 @@ func TestBALSelfDestructPreExistingContract(t *testing.T) { } } +// TestBALSelfDestructToSelfKeepsBalance: under EIP-8246 a freshly created +// contract that self-destructs to itself keeps its balance (it is not burnt and +// the account is not removed). The surviving balance-only account must therefore +// be recorded in the BAL with its preserved balance. +func TestBALSelfDestructToSelfKeepsBalance(t *testing.T) { + env := newBALTestEnv(nil) + // Init code: ADDRESS SELFDESTRUCT — the contract self-destructs to itself + // during its own creation transaction (satisfying EIP-6780's same-tx rule). + // ADDRESS (0x30) ; SELFDESTRUCT (0xff) + init := []byte{0x30, 0xff} + + b, receipts := env.run(t, func(g *BlockGen) { + g.AddTx(env.tx(0, nil, big.NewInt(100), 1_000_000, 0, init)) + }) + + created := receipts[0].ContractAddress + cc := assertPresent(t, b, created) + // EIP-8246: balance preserved (not burnt), account survives -> the BAL must + // record the created address with its retained balance. + if len(cc.BalanceChanges) != 1 || cc.BalanceChanges[0].PostBalance.Uint64() != 100 { + t.Fatalf("self-destruct-to-self must preserve balance 100 in the BAL: %+v", cc.BalanceChanges) + } +} + +// TestBALSelfDestructToSelfPrefundedUnchanged: a pre-funded address onto which a +// contract is deployed and which self-destructs to itself in the same +// transaction. Under EIP-8246 the account survives with its balance unchanged, +// so the BAL must list it only as an access (no balance/nonce/code change). +func TestBALSelfDestructToSelfPrefundedUnchanged(t *testing.T) { + // The contract address created by the sender's nonce-0 transaction; it is + // pre-funded in genesis (balance only: nonce 0, no code, no storage), which + // EIP-7610 permits as a deployment target. + key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + created := crypto.CreateAddress(crypto.PubkeyToAddress(key.PublicKey), 0) + + env := newBALTestEnv(types.GenesisAlloc{ + created: {Balance: big.NewInt(77)}, + }) + // Init code: ADDRESS SELFDESTRUCT, deployed with zero value so the balance is + // untouched (stays at the pre-funded 77). + init := []byte{0x30, 0xff} + + b, receipts := env.run(t, func(g *BlockGen) { + g.AddTx(env.tx(0, nil, big.NewInt(0), 1_000_000, 0, init)) + }) + + if receipts[0].ContractAddress != created { + t.Fatalf("unexpected created address: have %x want %x", receipts[0].ContractAddress, created) + } + aa := assertPresent(t, b, created) + // EIP-8246: balance preserved and equal to the pre-transaction value, so no + // balance change; nonce and code end where they started (0 / empty). The + // account is only read, with an empty change set. + assertEmpty(t, aa) +} + // ============================== Mid-tx balance round-trip ============================== // TestBALMidTxBalanceRoundTrip: when an address's balance changes during a diff --git a/core/eip8246_test.go b/core/eip8246_test.go new file mode 100644 index 0000000000..2fd39e3d44 --- /dev/null +++ b/core/eip8246_test.go @@ -0,0 +1,105 @@ +// Copyright 2026 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 . + +package core + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/beacon" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/params" +) + +// TestEIP8246SelfdestructNoBurn verifies that, once EIP-8246 is active +// (Amsterdam), a contract that is created and self-destructs to itself within +// the same transaction keeps its balance instead of burning it: the account +// survives as a balance-only account (no code, zero nonce, balance preserved). +// +// https://eips.ethereum.org/EIPS/eip-8246 +func TestEIP8246SelfdestructNoBurn(t *testing.T) { + var ( + key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") + addr1 = crypto.PubkeyToAddress(key1.PublicKey) + config = *params.MergedTestChainConfig + signer = types.LatestSigner(&config) + engine = beacon.New(ethash.NewFaker()) + value = big.NewInt(1_000_000) + // Init code: ADDRESS (0x30) ; SELFDESTRUCT (0xff). The created contract + // self-destructs to itself during its own creation transaction. + initcode = common.FromHex("30ff") + ) + // TODO: drop this hacky Amsterdam config initialization once the final + // Amsterdam config is available (mirrors TestEthTransferLogs). + config.AmsterdamTime = new(uint64) + blobConfig := *config.BlobScheduleConfig + blobConfig.Amsterdam = blobConfig.Osaka + config.BlobScheduleConfig = &blobConfig + + gspec := &Genesis{ + Config: &config, + Alloc: types.GenesisAlloc{ + addr1: {Balance: newGwei(1_000_000_000)}, + }, + } + // The contract created by addr1's first (nonce 0) transaction. + created := crypto.CreateAddress(addr1, 0) + + db, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) { + tx := types.MustSignNewTx(key1, signer, &types.DynamicFeeTx{ + ChainID: gspec.Config.ChainID, + Nonce: 0, + To: nil, // contract creation + Gas: 1_000_000, + GasFeeCap: newGwei(5), + GasTipCap: newGwei(5), + Value: value, + Data: initcode, + }) + b.AddTx(tx) + }) + + chain, err := NewBlockChain(db, gspec, engine, nil) + if err != nil { + t.Fatalf("failed to create chain: %v", err) + } + defer chain.Stop() + // Read the post-state of the generated block directly. InsertChain is avoided + // on purpose: it would additionally verify the EIP-7928 block access list, + // which the chain-generation harness on this branch does not yet populate + // consistently — an orthogonal concern to the EIP-8246 state semantics under + // test here. + state, err := chain.StateAt(blocks[0].Header()) + if err != nil { + t.Fatalf("failed to obtain block state: %v", err) + } + // EIP-8246: the self-destructed, freshly-created contract keeps its balance + // rather than burning it, so the account survives. + if got := state.GetBalance(created); got.ToBig().Cmp(value) != 0 { + t.Errorf("created account balance = %v, want %v (EIP-8246: balance must be preserved, not burned)", got, value) + } + // It survives as a balance-only account: nonce reset to 0 and no code. + if got := state.GetNonce(created); got != 0 { + t.Errorf("created account nonce = %d, want 0", got) + } + if got := state.GetCodeSize(created); got != 0 { + t.Errorf("created account code size = %d, want 0 (code must be cleared)", got) + } +} diff --git a/core/state/statedb.go b/core/state/statedb.go index 1c49d46020..176445a575 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -764,50 +764,15 @@ func (s *StateDB) GetRefund() uint64 { return s.refund } -type removedAccountWithBalance struct { - address common.Address - balance *uint256.Int -} - -// LogsForBurnAccounts returns the eth burn logs for accounts scheduled for -// removal which still have positive balance. The purpose of this function is -// to handle a corner case of EIP-7708 where a self-destructed account might -// still receive funds between sending/burning its previous balance and actual -// removal. In this case the burning of these remaining balances still need to -// be logged. -// Specification EIP-7708: https://eips.ethereum.org/EIPS/eip-7708 -// -// This function should only be invoked at the transaction boundary, specifically -// before the Finalise. -func (s *StateDB) LogsForBurnAccounts() []*types.Log { - var list []removedAccountWithBalance - for addr := range s.journal.mutations { - if obj, exist := s.stateObjects[addr]; exist && obj.selfDestructed && !obj.Balance().IsZero() { - list = append(list, removedAccountWithBalance{ - address: obj.address, - balance: obj.Balance(), - }) - } - } - if list == nil { - return nil - } - sort.Slice(list, func(i, j int) bool { - return list[i].address.Cmp(list[j].address) < 0 - }) - logs := make([]*types.Log, len(list)) - for i, acct := range list { - logs[i] = types.EthBurnLog(acct.address, acct.balance) - } - return logs -} - // Finalise finalises the state by removing the destructed objects and clears // the journal as well as the refunds. Finalise, however, will not push any updates // into the tries just yet. Only IntermediateRoot or Commit will do that. func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccessList { + if s.stateAccessList != nil { + return s.finaliseAmsterdam(deleteEmptyObjects) + } addressesToPrefetch := make([]common.Address, 0, len(s.journal.mutations)) - for addr, state := range s.journal.mutations { + for addr := range s.journal.mutations { obj, exist := s.stateObjects[addr] if !exist { // RIPEMD160 (0x03) gets an extra dirty marker for a historical @@ -831,46 +796,103 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccess if _, ok := s.stateObjectsDestruct[obj.address]; !ok { s.stateObjectsDestruct[obj.address] = obj } - // Aggregate the account mutation into the block-level accessList - // if Amsterdam has been activated. - if s.stateAccessList != nil { - // Notably, if the account is deleted during the transaction, - // its pre-transaction nonce, code, and storage must be empty. - // - // EIP-6780 restricts self-destruct to contracts deployed within - // the same transaction, while EIP-7610 rejects deployments to - // destinations with non-empty storage, non-zero nonce and non-empty - // code. - // - // Therefore, when an account is deleted, its pre-transaction nonce - // code and storage is guaranteed to be empty, leaving nothing to - // clean up here. - balance := uint256.NewInt(0) - if state.balanceSet && balance.Cmp(state.balance) != 0 { - s.stateAccessList.BalanceChange(s.blockAccessIndex, addr, balance) - } - } } else { - // Aggregate the account mutation into the block-level accessList - // if Amsterdam has been activated. - if s.stateAccessList != nil { - balance := obj.Balance() - if state.balanceSet && balance.Cmp(state.balance) != 0 { - s.stateAccessList.BalanceChange(s.blockAccessIndex, addr, balance) - } - nonce := obj.Nonce() - if state.nonceSet && nonce != state.nonce { - s.stateAccessList.NonceChange(addr, s.blockAccessIndex, nonce) - } - if state.codeSet { - if code := obj.Code(); !bytes.Equal(code, state.code) { - s.stateAccessList.CodeChange(addr, s.blockAccessIndex, code) - } - } - } obj.finalise() s.markUpdate(addr) } + addressesToPrefetch = append(addressesToPrefetch, addr) // Copy needed for closure + } + if s.prefetcher != nil && len(addressesToPrefetch) > 0 { + if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, addressesToPrefetch, nil, false); err != nil { + log.Error("Failed to prefetch addresses", "addresses", len(addressesToPrefetch), "err", err) + } + } + // Invalidate journal because reverting across transactions is not allowed. + s.clearJournalAndRefund() + + return nil +} + +func (s *StateDB) recordAccessListChanges(addr common.Address, state *journalMutationState) { + var ( + balance = uint256.NewInt(0) + nonce uint64 + ) + obj := s.stateObjects[addr] // nil when the account was removed + if obj != nil { + balance, nonce = obj.Balance(), obj.Nonce() + } + if state.balanceSet && balance.Cmp(state.balance) != 0 { + s.stateAccessList.BalanceChange(s.blockAccessIndex, addr, balance) + } + if state.nonceSet && nonce != state.nonce { + s.stateAccessList.NonceChange(addr, s.blockAccessIndex, nonce) + } + if state.codeSet { + var code []byte + if obj != nil { + code = obj.Code() + } + if !bytes.Equal(code, state.code) { + s.stateAccessList.CodeChange(addr, s.blockAccessIndex, code) + } + } +} + +// finaliseAmsterdam is the Amsterdam-and-later variant of Finalise. +func (s *StateDB) finaliseAmsterdam(deleteEmptyObjects bool) *bal.ConstructionBlockAccessList { + addressesToPrefetch := make([]common.Address, 0, len(s.journal.mutations)) + for addr, state := range s.journal.mutations { + obj, exist := s.stateObjects[addr] + if !exist { + // RIPEMD160 (0x03) gets an extra dirty marker for a historical + // mainnet consensus exception (at block 1714175, in tx + // 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2) + // around empty-account touch/revert handling. + // + // That marker survives journal revert, so the account may remain in + // s.journal.mutations even though its state object was rolled + // back and no longer exists. In that case there is nothing to + // finalise or delete, so ignore it here. + continue + } + switch { + case obj.selfDestructed: + // EIP-8264: accounts marked for self-destruction, instead of + // being deleted, are modified as follows: + // - nonce is reset to 0, + // - balance is unchanged, + // - code is cleared, + // - all storage is cleared + if !obj.Balance().IsZero() { + o := newObject(s, obj.address, obj.origin) + o.setBalance(new(uint256.Int).Set(obj.Balance())) + s.setStateObject(o) + s.markUpdate(addr) + } else { + delete(s.stateObjects, obj.address) + s.markDelete(addr) + if _, ok := s.stateObjectsDestruct[obj.address]; !ok { + s.stateObjectsDestruct[obj.address] = obj + } + } + + case deleteEmptyObjects && obj.empty(): + // EIP-161: a touched, empty account is removed. + delete(s.stateObjects, obj.address) + s.markDelete(addr) + if _, ok := s.stateObjectsDestruct[obj.address]; !ok { + s.stateObjectsDestruct[obj.address] = obj + } + + default: + obj.finalise() + s.markUpdate(addr) + } + // Aggregate the resulting account metadata change + // into the block-level access list. + s.recordAccessListChanges(addr, state) + // At this point, also ship the address off to the precacher. The precacher // will start loading tries, and when the change is eventually committed, // the commit-phase will be a lot faster diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index 98d01343a4..73e8253311 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -230,10 +230,6 @@ func (s *hookedStateDB) AddLog(log *types.Log) { } } -func (s *hookedStateDB) LogsForBurnAccounts() []*types.Log { - return s.inner.LogsForBurnAccounts() -} - func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccessList { if s.hooks.OnBalanceChange == nil && s.hooks.OnNonceChangeV2 == nil && s.hooks.OnNonceChange == nil && s.hooks.OnCodeChangeV2 == nil && s.hooks.OnCodeChange == nil { // Short circuit if no relevant hooks are set. @@ -256,18 +252,24 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlock return bytes.Compare(selfDestructedAddrs[i][:], selfDestructedAddrs[j][:]) < 0 }) + // EIP-8246 (Amsterdam) removes the SELFDESTRUCT burn: a self-destructed + // account that retains a non-zero balance is preserved as a balance-only + // account rather than removed, so its balance is no longer burnt. + burnsBalance := s.inner.stateAccessList == nil + for _, addr := range selfDestructedAddrs { obj := s.inner.stateObjects[addr] // Bingo: state object was self-destructed, call relevant hooks. - // If ether was sent to account post-selfdestruct, record as burnt. - if s.hooks.OnBalanceChange != nil { + if burnsBalance && s.hooks.OnBalanceChange != nil { if bal := obj.Balance(); bal.Sign() != 0 { s.hooks.OnBalanceChange(addr, bal.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestructBurn) } } // Nonce is set to reset on self-destruct. + // + // TODO(rjl) shall we emit the nonce change if the pre-tx nonce was zero? if s.hooks.OnNonceChangeV2 != nil { s.hooks.OnNonceChangeV2(addr, obj.Nonce(), 0, tracing.NonceChangeSelfdestruct) } else if s.hooks.OnNonceChange != nil { diff --git a/core/state_transition.go b/core/state_transition.go index 3b1642e0b9..2923e110c7 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -790,12 +790,6 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { } } - // EIP-7708: Emit the ETH-burn logs - if rules.IsAmsterdam { - for _, log := range st.evm.StateDB.LogsForBurnAccounts() { - st.evm.StateDB.AddLog(log) - } - } return &ExecutionResult{ UsedGas: gasUsed, MaxUsedGas: peakUsed, diff --git a/core/types/log.go b/core/types/log.go index 487ca57b5a..3ea1e0db7c 100644 --- a/core/types/log.go +++ b/core/types/log.go @@ -79,17 +79,3 @@ func EthTransferLog(from, to common.Address, amount *uint256.Int) *Log { Data: amount32[:], } } - -// EthBurnLog creates an ETH burn log according to EIP-7708. -// Specification: https://eips.ethereum.org/EIPS/eip-7708 -func EthBurnLog(from common.Address, amount *uint256.Int) *Log { - amount32 := amount.Bytes32() - return &Log{ - Address: params.SystemAddress, - Topics: []common.Hash{ - params.EthBurnLogEvent, - common.BytesToHash(from.Bytes()), - }, - Data: amount32[:], - } -} diff --git a/core/vm/instructions.go b/core/vm/instructions.go index b20cce4c0d..328623848e 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -928,8 +928,15 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro if newContract { if this != beneficiary { // Skip no-op transfer when self-destructing to self. evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct) + evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct) + } else if !evm.chainRules.IsAmsterdam { + // Self-destructing to self burns the balance prior to EIP-8246. + // EIP-8246 (Amsterdam) removes this burn: the balance is left + // untouched and the account is preserved as a balance-only account + // at transaction finalization (unless its balance is zero, in which + // case EIP-161 deletes it). + evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct) } - evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct) evm.StateDB.SelfDestruct(this) } @@ -938,12 +945,10 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct) evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct) } - if evm.chainRules.IsAmsterdam && !balance.IsZero() { - if this != beneficiary { - evm.StateDB.AddLog(types.EthTransferLog(this, beneficiary, balance)) - } else if newContract { - evm.StateDB.AddLog(types.EthBurnLog(this, balance)) - } + // EIP-7708: emit a transfer log for the moved balance. EIP-8246 removes the + // SELFDESTRUCT burn entirely, so there is no longer a burn to log. + if evm.chainRules.IsAmsterdam && !balance.IsZero() && this != beneficiary { + evm.StateDB.AddLog(types.EthTransferLog(this, beneficiary, balance)) } if tracer := evm.Config.Tracer; tracer != nil { diff --git a/core/vm/interface.go b/core/vm/interface.go index a9938c2a28..5bba39069c 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -90,7 +90,6 @@ type StateDB interface { Snapshot() int AddLog(*types.Log) - LogsForBurnAccounts() []*types.Log AddPreimage(common.Hash, []byte) Witness() *stateless.Witness From 04bf0453036dffc7710684354efabdbe3970ffe0 Mon Sep 17 00:00:00 2001 From: Ignacio Hagopian Date: Mon, 29 Jun 2026 22:03:12 -0300 Subject: [PATCH 07/16] core/state: fix account prefetching for absent accounts (#35256) --- core/state/statedb.go | 11 ++++++----- core/state/statedb_test.go | 40 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 176445a575..09a896a89a 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -614,16 +614,17 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject { } s.AccountReads += time.Since(start) - // Short circuit if the account is not found - if acct == nil { - return nil - } - // Schedule the resolved account for prefetching if it's enabled. + // Schedule the account path for prefetching if it's enabled. Even if the + // account is absent, the trie path proves its non-existence for witnesses. if s.prefetcher != nil { if err = s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, []common.Address{addr}, nil, true); err != nil { log.Error("Failed to prefetch account", "addr", addr, "err", err) } } + // Short circuit if the account is not found + if acct == nil { + return nil + } // Insert into the live set obj := newObject(s, addr, acct) s.setStateObject(obj) diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go index 0bf9b50e7b..6f4282054e 100644 --- a/core/state/statedb_test.go +++ b/core/state/statedb_test.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/state/snapshot" + "github.com/ethereum/go-ethereum/core/stateless" "github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" @@ -998,6 +999,45 @@ func TestDeleteCreateRevert(t *testing.T) { } } +func TestWitnessIncludesAbsentAccountReads(t *testing.T) { + db := NewDatabaseForTesting() + state, _ := New(types.EmptyRootHash, db) + for i := byte(0); i < 3; i++ { + addr := common.Address{i + 1} + state.SetBalance(addr, uint256.NewInt(uint64(i+1)), tracing.BalanceChangeUnspecified) + } + root, err := state.Commit(0, false, false) + if err != nil { + t.Fatalf("failed to commit initial state: %v", err) + } + state, err = New(root, db) + if err != nil { + t.Fatalf("failed to reopen state: %v", err) + } + + witness := &stateless.Witness{ + Codes: make(map[string]struct{}), + State: make(map[string]struct{}), + } + state.StartPrefetcher("test", witness) + missing := common.HexToAddress("0x017655eac00c837122cabbbc0dd604a196906648") + if balance := state.GetBalance(missing); balance.Sign() != 0 { + t.Fatalf("unexpected balance for absent account: %v", balance) + } + if err := state.Error(); err != nil { + t.Fatalf("unexpected state error after read: %v", err) + } + if got := state.IntermediateRoot(false); got != root { + t.Fatalf("unexpected root after read-only access: have %x want %x", got, root) + } + if err := state.Error(); err != nil { + t.Fatalf("unexpected state error after root calculation: %v", err) + } + if len(witness.State) == 0 { + t.Fatal("missing witness nodes for absent account read") + } +} + // TestMissingTrieNodes tests that if the StateDB fails to load parts of the trie, // the Commit operation fails with an error // If we are missing trie nodes, we should not continue writing to the trie From 32986bf084f70162442544af4f6e6ca9c8298d42 Mon Sep 17 00:00:00 2001 From: cui Date: Tue, 30 Jun 2026 21:12:06 +0800 Subject: [PATCH 08/16] triedb/pathdb: prealloc node map in nodeSet.write (#35232) --- triedb/pathdb/nodes.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/triedb/pathdb/nodes.go b/triedb/pathdb/nodes.go index 62c72c1953..590f7b499d 100644 --- a/triedb/pathdb/nodes.go +++ b/triedb/pathdb/nodes.go @@ -279,7 +279,7 @@ func (s *nodeSet) decode(r *rlp.Stream) error { // write flushes nodes into the provided database batch as a whole. func (s *nodeSet) write(batch ethdb.Batch, clean *fastcache.Cache) int { - nodes := make(map[common.Hash]map[string]*trienode.Node) + nodes := make(map[common.Hash]map[string]*trienode.Node, len(s.storageNodes)+1) if len(s.accountNodes) > 0 { nodes[common.Hash{}] = s.accountNodes } From 409aaefac158c3bd6ca3d9afb90232ba17102551 Mon Sep 17 00:00:00 2001 From: cui Date: Tue, 30 Jun 2026 21:12:47 +0800 Subject: [PATCH 09/16] log: clone terminal handler attrs (#35141) Clone the existing terminal handler attrs before appending new attrs. This avoids a potential attr memory overwrite when append reuses the backing array shared with the parent handler. --- log/handler.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/log/handler.go b/log/handler.go index 0b51120e51..30a0aff49c 100644 --- a/log/handler.go +++ b/log/handler.go @@ -7,6 +7,7 @@ import ( "log/slog" "math/big" "reflect" + "slices" "sync" "time" @@ -97,7 +98,7 @@ func (h *TerminalHandler) WithAttrs(attrs []slog.Attr) slog.Handler { wr: h.wr, lvl: h.lvl, useColor: h.useColor, - attrs: append(h.attrs, attrs...), + attrs: append(slices.Clone(h.attrs), attrs...), fieldPadding: make(map[string]int), } } From 5c253e557e567e7702a46e8b23be0782394e32bf Mon Sep 17 00:00:00 2001 From: Bosul Mun Date: Wed, 1 Jul 2026 03:43:11 +0200 Subject: [PATCH 10/16] core/rawdb: prevent truncateHead from returning an error for empty tables (#35258) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR is related to the recent bug reported in #35210. While trying to reproduce the error, I found that when the head state is missing (e.g. unclean shutdown), we attempt to truncate the head to the most recent block with state across all chain freezer tables. However, for newly added tables such as the bal table, both the head and tail are initialized to the minimum head of the existing chain freezer tables. As a result, the `truncateHead` fails with the “truncate below tail” error. This PR fixes the issue by resetting newly added empty tables with `items` as the tail when `truncateHead(items)` is called on them. --- core/rawdb/freezer_table.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/core/rawdb/freezer_table.go b/core/rawdb/freezer_table.go index c770e89989..d18b75c2f7 100644 --- a/core/rawdb/freezer_table.go +++ b/core/rawdb/freezer_table.go @@ -611,9 +611,18 @@ func (t *freezerTable) truncateHead(items uint64) error { if existing <= items { return nil } - if items < t.itemHidden.Load() { + + hidden := t.itemHidden.Load() + + if items < hidden { + if existing == hidden { + // Empty table means that it is newly added. Its tail would be + // at the head, so we have to align the table down to the new head. + return t.resetTo(items) + } return errors.New("truncation below tail") } + // We need to truncate, save the old size for metrics tracking oldSize, err := t.sizeNolock() if err != nil { From b6e2f13a9a381b1700722f0432dddb785340c726 Mon Sep 17 00:00:00 2001 From: cui Date: Wed, 1 Jul 2026 10:33:45 +0800 Subject: [PATCH 11/16] accounts/keystore: fix negative timezone sign in toISO8601 (#35245) --- accounts/keystore/key.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts/keystore/key.go b/accounts/keystore/key.go index 9b2ac14712..34712cb842 100644 --- a/accounts/keystore/key.go +++ b/accounts/keystore/key.go @@ -230,7 +230,7 @@ func toISO8601(t time.Time) string { if name == "UTC" { tz = "Z" } else { - tz = fmt.Sprintf("%03d00", offset/3600) + tz = fmt.Sprintf("%+03d00", offset/3600) } return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s", t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz) From dd672c6867b4a5f38a90537dbbe237a06d31d048 Mon Sep 17 00:00:00 2001 From: cui Date: Wed, 1 Jul 2026 10:34:08 +0800 Subject: [PATCH 12/16] eth: prealloc seen map in handleTransactions (#35237) --- eth/handler_eth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/handler_eth.go b/eth/handler_eth.go index 8704a86af4..7922687e0c 100644 --- a/eth/handler_eth.go +++ b/eth/handler_eth.go @@ -89,7 +89,7 @@ func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error { // handleTransactions marks all given transactions as known to the peer // and performs basic validations. func handleTransactions(peer *eth.Peer, list []*types.Transaction, directBroadcast bool) error { - seen := make(map[common.Hash]struct{}) + seen := make(map[common.Hash]struct{}, len(list)) for _, tx := range list { if tx.Type() == types.BlobTxType { if directBroadcast { From 769c8c52c56b4faea77bc2aeb34a6abfa24cd9f0 Mon Sep 17 00:00:00 2001 From: cui Date: Wed, 1 Jul 2026 15:40:01 +0800 Subject: [PATCH 13/16] internal/ethapi: skip transfer capture for callcode in log tracer (#35248) --- internal/ethapi/logtracer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/ethapi/logtracer.go b/internal/ethapi/logtracer.go index 54d2d653ea..0b7c27079a 100644 --- a/internal/ethapi/logtracer.go +++ b/internal/ethapi/logtracer.go @@ -80,7 +80,7 @@ func (t *tracer) Hooks() *tracing.Hooks { func (t *tracer) onEnter(depth int, typ byte, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) { t.logs = append(t.logs, make([]*types.Log, 0)) - if vm.OpCode(typ) != vm.DELEGATECALL && value != nil && value.Cmp(common.Big0) > 0 { + if vm.OpCode(typ) != vm.DELEGATECALL && vm.OpCode(typ) != vm.CALLCODE && value != nil && value.Cmp(common.Big0) > 0 { t.captureTransfer(from, to, value) } } From 00a773dad7ad7a6be566872d852500e2e51df2e4 Mon Sep 17 00:00:00 2001 From: m1lestones <91078895+m1lestones@users.noreply.github.com> Date: Wed, 1 Jul 2026 03:40:46 -0400 Subject: [PATCH 14/16] accounts/abi/abigen: add GetABI() method to abigen v2 generated bindings (#35221) --- accounts/abi/abigen/source2.go.tpl | 5 +++ .../abigen/testdata/v2/callbackparam.go.txt | 5 +++ .../abi/abigen/testdata/v2/crowdsale.go.txt | 5 +++ accounts/abi/abigen/testdata/v2/dao.go.txt | 5 +++ .../testdata/v2/deeplynestedarray.go.txt | 5 +++ accounts/abi/abigen/testdata/v2/empty.go.txt | 5 +++ .../abigen/testdata/v2/eventchecker.go.txt | 5 +++ accounts/abi/abigen/testdata/v2/getter.go.txt | 5 +++ .../testdata/v2/identifiercollision.go.txt | 5 +++ .../abigen/testdata/v2/inputchecker.go.txt | 5 +++ .../abi/abigen/testdata/v2/interactor.go.txt | 5 +++ .../abigen/testdata/v2/nameconflict.go.txt | 5 +++ .../testdata/v2/numericmethodname.go.txt | 5 +++ .../abigen/testdata/v2/outputchecker.go.txt | 5 +++ .../abi/abigen/testdata/v2/overload.go.txt | 5 +++ .../abigen/testdata/v2/rangekeyword.go.txt | 5 +++ accounts/abi/abigen/testdata/v2/slicer.go.txt | 5 +++ .../abi/abigen/testdata/v2/structs.go.txt | 5 +++ accounts/abi/abigen/testdata/v2/token.go.txt | 5 +++ accounts/abi/abigen/testdata/v2/tuple.go.txt | 5 +++ accounts/abi/abigen/testdata/v2/tupler.go.txt | 5 +++ .../abi/abigen/testdata/v2/underscorer.go.txt | 5 +++ .../bind/v2/internal/contracts/db/bindings.go | 5 +++ .../v2/internal/contracts/events/bindings.go | 5 +++ .../contracts/nested_libraries/bindings.go | 40 +++++++++++++++++++ .../contracts/solc_errors/bindings.go | 10 +++++ .../contracts/uint256arrayreturn/bindings.go | 5 +++ 27 files changed, 175 insertions(+) diff --git a/accounts/abi/abigen/source2.go.tpl b/accounts/abi/abigen/source2.go.tpl index c517caf6f4..9285497511 100644 --- a/accounts/abi/abigen/source2.go.tpl +++ b/accounts/abi/abigen/source2.go.tpl @@ -59,6 +59,11 @@ var ( abi abi.ABI } + // GetABI returns the ABI associated with this contract binding. + func (c *{{.Type}}) GetABI() abi.ABI { + return c.abi + } + // New{{.Type}} creates a new instance of {{.Type}}. func New{{.Type}}() *{{.Type}} { parsed, err := {{.Type}}MetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/callbackparam.go.txt b/accounts/abi/abigen/testdata/v2/callbackparam.go.txt index d22b978486..a01b125acf 100644 --- a/accounts/abi/abigen/testdata/v2/callbackparam.go.txt +++ b/accounts/abi/abigen/testdata/v2/callbackparam.go.txt @@ -36,6 +36,11 @@ type CallbackParam struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *CallbackParam) GetABI() abi.ABI { + return c.abi +} + // NewCallbackParam creates a new instance of CallbackParam. func NewCallbackParam() *CallbackParam { parsed, err := CallbackParamMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/crowdsale.go.txt b/accounts/abi/abigen/testdata/v2/crowdsale.go.txt index f0bba246ab..66582aa5c6 100644 --- a/accounts/abi/abigen/testdata/v2/crowdsale.go.txt +++ b/accounts/abi/abigen/testdata/v2/crowdsale.go.txt @@ -36,6 +36,11 @@ type Crowdsale struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *Crowdsale) GetABI() abi.ABI { + return c.abi +} + // NewCrowdsale creates a new instance of Crowdsale. func NewCrowdsale() *Crowdsale { parsed, err := CrowdsaleMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/dao.go.txt b/accounts/abi/abigen/testdata/v2/dao.go.txt index 0e9adba31e..a810c65bd9 100644 --- a/accounts/abi/abigen/testdata/v2/dao.go.txt +++ b/accounts/abi/abigen/testdata/v2/dao.go.txt @@ -36,6 +36,11 @@ type DAO struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *DAO) GetABI() abi.ABI { + return c.abi +} + // NewDAO creates a new instance of DAO. func NewDAO() *DAO { parsed, err := DAOMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/deeplynestedarray.go.txt b/accounts/abi/abigen/testdata/v2/deeplynestedarray.go.txt index 302f1d736f..559ea83422 100644 --- a/accounts/abi/abigen/testdata/v2/deeplynestedarray.go.txt +++ b/accounts/abi/abigen/testdata/v2/deeplynestedarray.go.txt @@ -36,6 +36,11 @@ type DeeplyNestedArray struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *DeeplyNestedArray) GetABI() abi.ABI { + return c.abi +} + // NewDeeplyNestedArray creates a new instance of DeeplyNestedArray. func NewDeeplyNestedArray() *DeeplyNestedArray { parsed, err := DeeplyNestedArrayMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/empty.go.txt b/accounts/abi/abigen/testdata/v2/empty.go.txt index 7082e20799..7deea15570 100644 --- a/accounts/abi/abigen/testdata/v2/empty.go.txt +++ b/accounts/abi/abigen/testdata/v2/empty.go.txt @@ -36,6 +36,11 @@ type Empty struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *Empty) GetABI() abi.ABI { + return c.abi +} + // NewEmpty creates a new instance of Empty. func NewEmpty() *Empty { parsed, err := EmptyMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/eventchecker.go.txt b/accounts/abi/abigen/testdata/v2/eventchecker.go.txt index d0600d7c3e..213ab609f5 100644 --- a/accounts/abi/abigen/testdata/v2/eventchecker.go.txt +++ b/accounts/abi/abigen/testdata/v2/eventchecker.go.txt @@ -35,6 +35,11 @@ type EventChecker struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *EventChecker) GetABI() abi.ABI { + return c.abi +} + // NewEventChecker creates a new instance of EventChecker. func NewEventChecker() *EventChecker { parsed, err := EventCheckerMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/getter.go.txt b/accounts/abi/abigen/testdata/v2/getter.go.txt index 69a4fb54c8..69267767c2 100644 --- a/accounts/abi/abigen/testdata/v2/getter.go.txt +++ b/accounts/abi/abigen/testdata/v2/getter.go.txt @@ -36,6 +36,11 @@ type Getter struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *Getter) GetABI() abi.ABI { + return c.abi +} + // NewGetter creates a new instance of Getter. func NewGetter() *Getter { parsed, err := GetterMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/identifiercollision.go.txt b/accounts/abi/abigen/testdata/v2/identifiercollision.go.txt index e7301521f4..1d14a7109d 100644 --- a/accounts/abi/abigen/testdata/v2/identifiercollision.go.txt +++ b/accounts/abi/abigen/testdata/v2/identifiercollision.go.txt @@ -36,6 +36,11 @@ type IdentifierCollision struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *IdentifierCollision) GetABI() abi.ABI { + return c.abi +} + // NewIdentifierCollision creates a new instance of IdentifierCollision. func NewIdentifierCollision() *IdentifierCollision { parsed, err := IdentifierCollisionMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/inputchecker.go.txt b/accounts/abi/abigen/testdata/v2/inputchecker.go.txt index 1743f336db..4522aea52a 100644 --- a/accounts/abi/abigen/testdata/v2/inputchecker.go.txt +++ b/accounts/abi/abigen/testdata/v2/inputchecker.go.txt @@ -35,6 +35,11 @@ type InputChecker struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *InputChecker) GetABI() abi.ABI { + return c.abi +} + // NewInputChecker creates a new instance of InputChecker. func NewInputChecker() *InputChecker { parsed, err := InputCheckerMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/interactor.go.txt b/accounts/abi/abigen/testdata/v2/interactor.go.txt index f33c95b639..3e88b4a521 100644 --- a/accounts/abi/abigen/testdata/v2/interactor.go.txt +++ b/accounts/abi/abigen/testdata/v2/interactor.go.txt @@ -36,6 +36,11 @@ type Interactor struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *Interactor) GetABI() abi.ABI { + return c.abi +} + // NewInteractor creates a new instance of Interactor. func NewInteractor() *Interactor { parsed, err := InteractorMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/nameconflict.go.txt b/accounts/abi/abigen/testdata/v2/nameconflict.go.txt index 5e4a9ecaf0..13f96527ee 100644 --- a/accounts/abi/abigen/testdata/v2/nameconflict.go.txt +++ b/accounts/abi/abigen/testdata/v2/nameconflict.go.txt @@ -42,6 +42,11 @@ type NameConflict struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *NameConflict) GetABI() abi.ABI { + return c.abi +} + // NewNameConflict creates a new instance of NameConflict. func NewNameConflict() *NameConflict { parsed, err := NameConflictMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/numericmethodname.go.txt b/accounts/abi/abigen/testdata/v2/numericmethodname.go.txt index 0af31a1cfb..4f5392f19d 100644 --- a/accounts/abi/abigen/testdata/v2/numericmethodname.go.txt +++ b/accounts/abi/abigen/testdata/v2/numericmethodname.go.txt @@ -36,6 +36,11 @@ type NumericMethodName struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *NumericMethodName) GetABI() abi.ABI { + return c.abi +} + // NewNumericMethodName creates a new instance of NumericMethodName. func NewNumericMethodName() *NumericMethodName { parsed, err := NumericMethodNameMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/outputchecker.go.txt b/accounts/abi/abigen/testdata/v2/outputchecker.go.txt index f1c98f536e..8d3e475e98 100644 --- a/accounts/abi/abigen/testdata/v2/outputchecker.go.txt +++ b/accounts/abi/abigen/testdata/v2/outputchecker.go.txt @@ -35,6 +35,11 @@ type OutputChecker struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *OutputChecker) GetABI() abi.ABI { + return c.abi +} + // NewOutputChecker creates a new instance of OutputChecker. func NewOutputChecker() *OutputChecker { parsed, err := OutputCheckerMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/overload.go.txt b/accounts/abi/abigen/testdata/v2/overload.go.txt index 563edf7842..006a2c7c3c 100644 --- a/accounts/abi/abigen/testdata/v2/overload.go.txt +++ b/accounts/abi/abigen/testdata/v2/overload.go.txt @@ -36,6 +36,11 @@ type Overload struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *Overload) GetABI() abi.ABI { + return c.abi +} + // NewOverload creates a new instance of Overload. func NewOverload() *Overload { parsed, err := OverloadMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/rangekeyword.go.txt b/accounts/abi/abigen/testdata/v2/rangekeyword.go.txt index 296de1fccc..76c5d72d29 100644 --- a/accounts/abi/abigen/testdata/v2/rangekeyword.go.txt +++ b/accounts/abi/abigen/testdata/v2/rangekeyword.go.txt @@ -36,6 +36,11 @@ type RangeKeyword struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *RangeKeyword) GetABI() abi.ABI { + return c.abi +} + // NewRangeKeyword creates a new instance of RangeKeyword. func NewRangeKeyword() *RangeKeyword { parsed, err := RangeKeywordMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/slicer.go.txt b/accounts/abi/abigen/testdata/v2/slicer.go.txt index 379f136453..416a629cb7 100644 --- a/accounts/abi/abigen/testdata/v2/slicer.go.txt +++ b/accounts/abi/abigen/testdata/v2/slicer.go.txt @@ -36,6 +36,11 @@ type Slicer struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *Slicer) GetABI() abi.ABI { + return c.abi +} + // NewSlicer creates a new instance of Slicer. func NewSlicer() *Slicer { parsed, err := SlicerMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/structs.go.txt b/accounts/abi/abigen/testdata/v2/structs.go.txt index 4d8864cf6e..ee55ec5620 100644 --- a/accounts/abi/abigen/testdata/v2/structs.go.txt +++ b/accounts/abi/abigen/testdata/v2/structs.go.txt @@ -41,6 +41,11 @@ type Structs struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *Structs) GetABI() abi.ABI { + return c.abi +} + // NewStructs creates a new instance of Structs. func NewStructs() *Structs { parsed, err := StructsMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/token.go.txt b/accounts/abi/abigen/testdata/v2/token.go.txt index 3bd60a6cdd..1bd364a918 100644 --- a/accounts/abi/abigen/testdata/v2/token.go.txt +++ b/accounts/abi/abigen/testdata/v2/token.go.txt @@ -36,6 +36,11 @@ type Token struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *Token) GetABI() abi.ABI { + return c.abi +} + // NewToken creates a new instance of Token. func NewToken() *Token { parsed, err := TokenMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/tuple.go.txt b/accounts/abi/abigen/testdata/v2/tuple.go.txt index 10b634f3db..6aabdeb0fe 100644 --- a/accounts/abi/abigen/testdata/v2/tuple.go.txt +++ b/accounts/abi/abigen/testdata/v2/tuple.go.txt @@ -61,6 +61,11 @@ type Tuple struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *Tuple) GetABI() abi.ABI { + return c.abi +} + // NewTuple creates a new instance of Tuple. func NewTuple() *Tuple { parsed, err := TupleMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/tupler.go.txt b/accounts/abi/abigen/testdata/v2/tupler.go.txt index 8643487042..5e6eafb07e 100644 --- a/accounts/abi/abigen/testdata/v2/tupler.go.txt +++ b/accounts/abi/abigen/testdata/v2/tupler.go.txt @@ -36,6 +36,11 @@ type Tupler struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *Tupler) GetABI() abi.ABI { + return c.abi +} + // NewTupler creates a new instance of Tupler. func NewTupler() *Tupler { parsed, err := TuplerMetaData.ParseABI() diff --git a/accounts/abi/abigen/testdata/v2/underscorer.go.txt b/accounts/abi/abigen/testdata/v2/underscorer.go.txt index 13ec968508..abe3bdbacb 100644 --- a/accounts/abi/abigen/testdata/v2/underscorer.go.txt +++ b/accounts/abi/abigen/testdata/v2/underscorer.go.txt @@ -36,6 +36,11 @@ type Underscorer struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *Underscorer) GetABI() abi.ABI { + return c.abi +} + // NewUnderscorer creates a new instance of Underscorer. func NewUnderscorer() *Underscorer { parsed, err := UnderscorerMetaData.ParseABI() diff --git a/accounts/abi/bind/v2/internal/contracts/db/bindings.go b/accounts/abi/bind/v2/internal/contracts/db/bindings.go index 2fc57fba6d..b9a7e3164a 100644 --- a/accounts/abi/bind/v2/internal/contracts/db/bindings.go +++ b/accounts/abi/bind/v2/internal/contracts/db/bindings.go @@ -43,6 +43,11 @@ type DB struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *DB) GetABI() abi.ABI { + return c.abi +} + // NewDB creates a new instance of DB. func NewDB() *DB { parsed, err := DBMetaData.ParseABI() diff --git a/accounts/abi/bind/v2/internal/contracts/events/bindings.go b/accounts/abi/bind/v2/internal/contracts/events/bindings.go index 2eb5751f23..7acd312b65 100644 --- a/accounts/abi/bind/v2/internal/contracts/events/bindings.go +++ b/accounts/abi/bind/v2/internal/contracts/events/bindings.go @@ -36,6 +36,11 @@ type C struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *C) GetABI() abi.ABI { + return c.abi +} + // NewC creates a new instance of C. func NewC() *C { parsed, err := CMetaData.ParseABI() diff --git a/accounts/abi/bind/v2/internal/contracts/nested_libraries/bindings.go b/accounts/abi/bind/v2/internal/contracts/nested_libraries/bindings.go index d1cb08116b..406d71192f 100644 --- a/accounts/abi/bind/v2/internal/contracts/nested_libraries/bindings.go +++ b/accounts/abi/bind/v2/internal/contracts/nested_libraries/bindings.go @@ -40,6 +40,11 @@ type C1 struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *C1) GetABI() abi.ABI { + return c.abi +} + // NewC1 creates a new instance of C1. func NewC1() *C1 { parsed, err := C1MetaData.ParseABI() @@ -118,6 +123,11 @@ type C2 struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *C2) GetABI() abi.ABI { + return c.abi +} + // NewC2 creates a new instance of C2. func NewC2() *C2 { parsed, err := C2MetaData.ParseABI() @@ -192,6 +202,11 @@ type L1 struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *L1) GetABI() abi.ABI { + return c.abi +} + // NewL1 creates a new instance of L1. func NewL1() *L1 { parsed, err := L1MetaData.ParseABI() @@ -257,6 +272,11 @@ type L2 struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *L2) GetABI() abi.ABI { + return c.abi +} + // NewL2 creates a new instance of L2. func NewL2() *L2 { parsed, err := L2MetaData.ParseABI() @@ -322,6 +342,11 @@ type L2b struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *L2b) GetABI() abi.ABI { + return c.abi +} + // NewL2b creates a new instance of L2b. func NewL2b() *L2b { parsed, err := L2bMetaData.ParseABI() @@ -384,6 +409,11 @@ type L3 struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *L3) GetABI() abi.ABI { + return c.abi +} + // NewL3 creates a new instance of L3. func NewL3() *L3 { parsed, err := L3MetaData.ParseABI() @@ -450,6 +480,11 @@ type L4 struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *L4) GetABI() abi.ABI { + return c.abi +} + // NewL4 creates a new instance of L4. func NewL4() *L4 { parsed, err := L4MetaData.ParseABI() @@ -515,6 +550,11 @@ type L4b struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *L4b) GetABI() abi.ABI { + return c.abi +} + // NewL4b creates a new instance of L4b. func NewL4b() *L4b { parsed, err := L4bMetaData.ParseABI() diff --git a/accounts/abi/bind/v2/internal/contracts/solc_errors/bindings.go b/accounts/abi/bind/v2/internal/contracts/solc_errors/bindings.go index 627b86f1b9..12d8b7f3e9 100644 --- a/accounts/abi/bind/v2/internal/contracts/solc_errors/bindings.go +++ b/accounts/abi/bind/v2/internal/contracts/solc_errors/bindings.go @@ -36,6 +36,11 @@ type C struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *C) GetABI() abi.ABI { + return c.abi +} + // NewC creates a new instance of C. func NewC() *C { parsed, err := CMetaData.ParseABI() @@ -173,6 +178,11 @@ type C2 struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *C2) GetABI() abi.ABI { + return c.abi +} + // NewC2 creates a new instance of C2. func NewC2() *C2 { parsed, err := C2MetaData.ParseABI() diff --git a/accounts/abi/bind/v2/internal/contracts/uint256arrayreturn/bindings.go b/accounts/abi/bind/v2/internal/contracts/uint256arrayreturn/bindings.go index 19d09bdd6a..f06788633c 100644 --- a/accounts/abi/bind/v2/internal/contracts/uint256arrayreturn/bindings.go +++ b/accounts/abi/bind/v2/internal/contracts/uint256arrayreturn/bindings.go @@ -36,6 +36,11 @@ type MyContract struct { abi abi.ABI } +// GetABI returns the ABI associated with this contract binding. +func (c *MyContract) GetABI() abi.ABI { + return c.abi +} + // NewMyContract creates a new instance of MyContract. func NewMyContract() *MyContract { parsed, err := MyContractMetaData.ParseABI() From ea242145cff75f9beee6f16701dbac494fbbff94 Mon Sep 17 00:00:00 2001 From: rjl493456442 Date: Wed, 1 Jul 2026 16:48:13 +0800 Subject: [PATCH 15/16] core: implement EIP-7928 spec change (#35260) https://github.com/ethereum/EIPs/pull/11838/changes --- core/bal_test.go | 67 +++++++++++++++++++++++++++++++++++++++ core/vm/operations_acl.go | 16 ++++++++++ 2 files changed, 83 insertions(+) diff --git a/core/bal_test.go b/core/bal_test.go index 84579ca564..9409ff32d1 100644 --- a/core/bal_test.go +++ b/core/bal_test.go @@ -518,6 +518,73 @@ func TestBALStaticCallTargetIncluded(t *testing.T) { assertEmpty(t, assertPresent(t, b, target)) } +// makeValueCaller emits a single value-transferring CALL-family op (CALL 0xf1 +// or CALLCODE 0xf2) against `target` with value=1, then STOPs. Used together +// with a zero-balance caller to make the value transfer fail CanTransfer. +func makeValueCaller(op byte, target common.Address) []byte { + code := []byte{ + 0x60, 0x00, // retSize + 0x60, 0x00, // retOff + 0x60, 0x00, // argsSize + 0x60, 0x00, // argsOff + 0x60, 0x01, // value = 1 + 0x73, // PUSH20 target + } + code = append(code, target.Bytes()...) + return append(code, 0x5a, op, 0x50, 0x00) // GAS, op, POP, STOP +} + +// TestBALCallToDelegatedTargetBalanceFail asserts the EIP-7928 rule revised in +// ethereum/EIPs#11838: when a CALL targets an EIP-7702 delegated account and the +// delegated address passes its access_cost gas check, the delegated +// (implementation) address MUST appear in the BAL even when the call then fails +// its sender-balance check, because the delegation is resolved before that +// check. CALL routes through the EIP-8037 gas path. +func TestBALCallToDelegatedTargetBalanceFail(t *testing.T) { + delegated := common.HexToAddress("0xde1e9a7ed") // EOA carrying a 7702 designator + impl := common.HexToAddress("0x111111") // delegation target (implementation) + caller := common.HexToAddress("0xca11") // zero-balance contract issuing the CALL + + env := newBALTestEnv(types.GenesisAlloc{ + caller: {Code: makeValueCaller(0xf1 /* CALL */, delegated), Balance: common.Big0}, + delegated: {Code: types.AddressToDelegation(impl), Balance: common.Big0}, + impl: {Code: []byte{0x00}, Balance: common.Big0}, // STOP + }) + + b, _ := env.run(t, func(g *BlockGen) { + g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil)) + }) + + assertPresent(t, b, caller) + assertPresent(t, b, delegated) + // The call failed its sender-balance check, so the implementation never + // executed: it is recorded with an empty change set, but it MUST be present. + assertEmpty(t, assertPresent(t, b, impl)) +} + +// TestBALCallCodeToDelegatedTargetBalanceFail is the CALLCODE analogue of +// TestBALCallToDelegatedTargetBalanceFail, exercising the EIP-7702 gas path +// (CALLCODE/STATICCALL/DELEGATECALL) rather than the EIP-8037 one. +func TestBALCallCodeToDelegatedTargetBalanceFail(t *testing.T) { + delegated := common.HexToAddress("0xde1e9a7ed") + impl := common.HexToAddress("0x111111") + caller := common.HexToAddress("0xca11") + + env := newBALTestEnv(types.GenesisAlloc{ + caller: {Code: makeValueCaller(0xf2 /* CALLCODE */, delegated), Balance: common.Big0}, + delegated: {Code: types.AddressToDelegation(impl), Balance: common.Big0}, + impl: {Code: []byte{0x00}, Balance: common.Big0}, // STOP + }) + + b, _ := env.run(t, func(g *BlockGen) { + g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil)) + }) + + assertPresent(t, b, caller) + assertPresent(t, b, delegated) + assertEmpty(t, assertPresent(t, b, impl)) +} + // ============================== Revert behaviour ============================== // TestBALRevertedTxStillIncluded: a tx whose top-level call REVERTs still diff --git a/core/vm/operations_acl.go b/core/vm/operations_acl.go index 8b2a627fef..e07eba451f 100644 --- a/core/vm/operations_acl.go +++ b/core/vm/operations_acl.go @@ -257,6 +257,14 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc { return gasFunc } +// recordDelegationAccess records the EIP-7702 delegated target in the block +// access list (EIP-7928). +func recordDelegationAccess(evm *EVM, target common.Address) { + if evm.chainRules.IsAmsterdam { + evm.StateDB.GetCode(target) + } +} + var ( innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCallIntrinsic) gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic) @@ -336,6 +344,10 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc { if !contract.chargeRegular(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { return GasCosts{}, ErrOutOfGas } + // The delegated address has passed its gas check; record it in the + // block access list now, before the call's sender-balance and + // call-stack-depth checks. + recordDelegationAccess(evm, target) } // Calculate the gas budget for the nested call. The costs defined by // EIP-2929 and EIP-7702 have already been applied. @@ -416,6 +428,10 @@ func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stat if !contract.chargeRegular(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { return GasCosts{}, ErrOutOfGas } + // The delegated address has passed its gas check; record it in the + // block access list now, before the call's sender-balance and + // call-stack-depth checks. + recordDelegationAccess(evm, target) } // Compute and charge state gas (new account creation) AFTER regular gas. From 0fbad29b943a90423289fb74fee236b6ea6d4388 Mon Sep 17 00:00:00 2001 From: Marius van der Wijden Date: Wed, 1 Jul 2026 10:51:05 +0200 Subject: [PATCH 16/16] params: remove named hardforks from bpo schedule (#35029) --- consensus/misc/eip4844/eip4844.go | 2 - consensus/misc/eip4844/eip4844_test.go | 3 +- core/bal_test.go | 3 - core/bintrie_witness_test.go | 3 +- core/eth_transfer_logs_test.go | 4 -- core/genesis_test.go | 2 - core/txpool/blobpool/blobpool_test.go | 5 -- core/txpool/blobpool/cache_test.go | 2 +- eth/catalyst/api_benchmark_test.go | 1 - miner/stress/main.go | 5 +- params/config.go | 85 ++++++++++++-------------- tests/init.go | 25 ++------ 12 files changed, 52 insertions(+), 88 deletions(-) diff --git a/consensus/misc/eip4844/eip4844.go b/consensus/misc/eip4844/eip4844.go index 2ebf4f7155..add85092c2 100644 --- a/consensus/misc/eip4844/eip4844.go +++ b/consensus/misc/eip4844/eip4844.go @@ -73,8 +73,6 @@ func latestBlobConfig(cfg *params.ChainConfig, time uint64) (BlobConfig, error) bc = s.BPO2 case cfg.IsBPO1(london, time) && s.BPO1 != nil: bc = s.BPO1 - case cfg.IsOsaka(london, time) && s.Osaka != nil: - bc = s.Osaka case cfg.IsPrague(london, time) && s.Prague != nil: bc = s.Prague case cfg.IsCancun(london, time) && s.Cancun != nil: diff --git a/consensus/misc/eip4844/eip4844_test.go b/consensus/misc/eip4844/eip4844_test.go index 35934370af..4aa49c9cb1 100644 --- a/consensus/misc/eip4844/eip4844_test.go +++ b/consensus/misc/eip4844/eip4844_test.go @@ -120,7 +120,6 @@ func TestCalcBlobFeePostOsaka(t *testing.T) { BlobScheduleConfig: ¶ms.BlobScheduleConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, - Osaka: params.DefaultOsakaBlobConfig, BPO1: ¶ms.BlobConfig{ Target: 9, Max: 14, @@ -191,7 +190,7 @@ func TestFakeExponential(t *testing.T) { func TestCalcExcessBlobGasEIP7918(t *testing.T) { var ( cfg = params.MergedTestChainConfig - targetBlobs = cfg.BlobScheduleConfig.Osaka.Target + targetBlobs = cfg.BlobScheduleConfig.Prague.Target blobGasTarget = uint64(targetBlobs) * params.BlobTxBlobGasPerBlob ) diff --git a/core/bal_test.go b/core/bal_test.go index 9409ff32d1..64c2714b16 100644 --- a/core/bal_test.go +++ b/core/bal_test.go @@ -42,9 +42,6 @@ import ( func balChainConfig() *params.ChainConfig { cfg := *params.MergedTestChainConfig cfg.AmsterdamTime = new(uint64) - blob := *cfg.BlobScheduleConfig - blob.Amsterdam = blob.Osaka - cfg.BlobScheduleConfig = &blob return &cfg } diff --git a/core/bintrie_witness_test.go b/core/bintrie_witness_test.go index 2cf7972cf8..9cbb489c4e 100644 --- a/core/bintrie_witness_test.go +++ b/core/bintrie_witness_test.go @@ -55,8 +55,9 @@ var ( UBTTime: u64(0), TerminalTotalDifficulty: common.Big0, EnableUBTAtGenesis: true, + // UBT inherits its blob schedule; nothing to declare here. BlobScheduleConfig: ¶ms.BlobScheduleConfig{ - UBT: params.DefaultPragueBlobConfig, + Prague: params.DefaultPragueBlobConfig, }, } ) diff --git a/core/eth_transfer_logs_test.go b/core/eth_transfer_logs_test.go index 815b56b588..a5a5f1894a 100644 --- a/core/eth_transfer_logs_test.go +++ b/core/eth_transfer_logs_test.go @@ -78,11 +78,7 @@ func testEthTransferLogs(t *testing.T, value uint64) { engine = beacon.New(ethash.NewFaker()) ) - //TODO remove this hacky config initialization when final Amsterdam config is available config.AmsterdamTime = new(uint64) - blobConfig := *config.BlobScheduleConfig - blobConfig.Amsterdam = blobConfig.Osaka - config.BlobScheduleConfig = &blobConfig gspec := &Genesis{ Config: &config, diff --git a/core/genesis_test.go b/core/genesis_test.go index 67b88fc415..80ee53493e 100644 --- a/core/genesis_test.go +++ b/core/genesis_test.go @@ -293,8 +293,6 @@ func TestBinaryGenesisCommit(t *testing.T) { BlobScheduleConfig: ¶ms.BlobScheduleConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, - Osaka: params.DefaultOsakaBlobConfig, - UBT: params.DefaultPragueBlobConfig, }, } diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go index 0e182947f0..a2ec06bed4 100644 --- a/core/txpool/blobpool/blobpool_test.go +++ b/core/txpool/blobpool/blobpool_test.go @@ -1215,11 +1215,6 @@ func TestBillyMigration(t *testing.T) { Max: maxBlobs, UpdateFraction: params.DefaultCancunBlobConfig.UpdateFraction, }, - Osaka: ¶ms.BlobConfig{ - Target: maxBlobs / 2, - Max: maxBlobs, - UpdateFraction: params.DefaultCancunBlobConfig.UpdateFraction, - }, }, } chain := &testBlockChain{ diff --git a/core/txpool/blobpool/cache_test.go b/core/txpool/blobpool/cache_test.go index da13cda4b2..3a0436ab85 100644 --- a/core/txpool/blobpool/cache_test.go +++ b/core/txpool/blobpool/cache_test.go @@ -94,7 +94,7 @@ func newTestCache(t *testing.T, txConfig []txSpec) *testCache { CancunTime: &cancunTime, OsakaTime: &cancunTime, BlobScheduleConfig: ¶ms.BlobScheduleConfig{ - Osaka: ¶ms.BlobConfig{ + Cancun: ¶ms.BlobConfig{ Target: 1, Max: 1, UpdateFraction: params.DefaultCancunBlobConfig.UpdateFraction, diff --git a/eth/catalyst/api_benchmark_test.go b/eth/catalyst/api_benchmark_test.go index ee0a0a4888..6d6ad59f7f 100644 --- a/eth/catalyst/api_benchmark_test.go +++ b/eth/catalyst/api_benchmark_test.go @@ -197,7 +197,6 @@ func newBenchmarkBlobEnv(b *testing.B, blobCount int, version byte, fork benchFo config.BlobScheduleConfig = ¶ms.BlobScheduleConfig{ Cancun: ¶ms.BlobConfig{Target: 6, Max: 128, UpdateFraction: 3338477}, Prague: ¶ms.BlobConfig{Target: 6, Max: 128, UpdateFraction: 5007716}, - Osaka: ¶ms.BlobConfig{Target: 6, Max: 128, UpdateFraction: 5007716}, } // Configure fork times based on requested fork switch fork { diff --git a/miner/stress/main.go b/miner/stress/main.go index aaf0993c37..7a8436b01e 100644 --- a/miner/stress/main.go +++ b/miner/stress/main.go @@ -138,7 +138,10 @@ func makeGenesis(faucets []*ecdsa.PrivateKey) *core.Genesis { blockZero := uint64(0) config.AmsterdamTime = &blockZero - config.BlobScheduleConfig.Amsterdam = ¶ms.BlobConfig{ + // Amsterdam inherits its blob schedule from the most recent BPO; activate BPO1 + // at the same time with the target/max we want exercised by the stress harness. + config.BPO1Time = &blockZero + config.BlobScheduleConfig.BPO1 = ¶ms.BlobConfig{ Target: 14, Max: 21, UpdateFraction: 13739630, diff --git a/params/config.go b/params/config.go index 1d69335f97..b277fb303c 100644 --- a/params/config.go +++ b/params/config.go @@ -69,7 +69,6 @@ var ( BlobScheduleConfig: &BlobScheduleConfig{ Cancun: DefaultCancunBlobConfig, Prague: DefaultPragueBlobConfig, - Osaka: DefaultOsakaBlobConfig, BPO1: DefaultBPO1BlobConfig, BPO2: DefaultBPO2BlobConfig, }, @@ -105,7 +104,6 @@ var ( BlobScheduleConfig: &BlobScheduleConfig{ Cancun: DefaultCancunBlobConfig, Prague: DefaultPragueBlobConfig, - Osaka: DefaultOsakaBlobConfig, BPO1: DefaultBPO1BlobConfig, BPO2: DefaultBPO2BlobConfig, }, @@ -141,7 +139,6 @@ var ( BlobScheduleConfig: &BlobScheduleConfig{ Cancun: DefaultCancunBlobConfig, Prague: DefaultPragueBlobConfig, - Osaka: DefaultOsakaBlobConfig, BPO1: DefaultBPO1BlobConfig, BPO2: DefaultBPO2BlobConfig, }, @@ -177,7 +174,6 @@ var ( BlobScheduleConfig: &BlobScheduleConfig{ Cancun: DefaultCancunBlobConfig, Prague: DefaultPragueBlobConfig, - Osaka: DefaultOsakaBlobConfig, BPO1: DefaultBPO1BlobConfig, BPO2: DefaultBPO2BlobConfig, }, @@ -235,7 +231,6 @@ var ( BlobScheduleConfig: &BlobScheduleConfig{ Cancun: DefaultCancunBlobConfig, Prague: DefaultPragueBlobConfig, - Osaka: DefaultOsakaBlobConfig, }, } @@ -330,7 +325,6 @@ var ( BlobScheduleConfig: &BlobScheduleConfig{ Cancun: DefaultCancunBlobConfig, Prague: DefaultPragueBlobConfig, - Osaka: DefaultOsakaBlobConfig, }, } @@ -379,12 +373,6 @@ var ( Max: 9, UpdateFraction: 5007716, } - // DefaultOsakaBlobConfig is the default blob configuration for the Osaka fork. - DefaultOsakaBlobConfig = &BlobConfig{ - Target: 6, - Max: 9, - UpdateFraction: 5007716, - } // DefaultBPO1BlobConfig is the default blob configuration for the BPO1 fork. DefaultBPO1BlobConfig = &BlobConfig{ Target: 10, @@ -413,7 +401,6 @@ var ( DefaultBlobSchedule = &BlobScheduleConfig{ Cancun: DefaultCancunBlobConfig, Prague: DefaultPragueBlobConfig, - Osaka: DefaultOsakaBlobConfig, } ) @@ -670,7 +657,7 @@ func (c *ChainConfig) Description() string { banner += fmt.Sprintf(" - Prague: @%-10v blob: (%s)\n", *c.PragueTime, c.BlobScheduleConfig.Prague) } if c.OsakaTime != nil { - banner += fmt.Sprintf(" - Osaka: @%-10v blob: (%s)\n", *c.OsakaTime, c.BlobScheduleConfig.Osaka) + banner += fmt.Sprintf(" - Osaka: @%-10v\n", *c.OsakaTime) } if c.BPO1Time != nil { banner += fmt.Sprintf(" - BPO1: @%-10v blob: (%s)\n", *c.BPO1Time, c.BlobScheduleConfig.BPO1) @@ -688,10 +675,10 @@ func (c *ChainConfig) Description() string { banner += fmt.Sprintf(" - BPO5: @%-10v blob: (%s)\n", *c.BPO5Time, c.BlobScheduleConfig.BPO5) } if c.AmsterdamTime != nil { - banner += fmt.Sprintf(" - Amsterdam: @%-10v blob: (%s)\n", *c.AmsterdamTime, c.BlobScheduleConfig.Amsterdam) + banner += fmt.Sprintf(" - Amsterdam: @%-10v\n", *c.AmsterdamTime) } if c.UBTTime != nil { - banner += fmt.Sprintf(" - UBT: @%-10v blob: (%s)\n", *c.UBTTime, c.BlobScheduleConfig.UBT) + banner += fmt.Sprintf(" - UBT: @%-10v\n", *c.UBTTime) } banner += fmt.Sprintf("\nAll fork specifications can be found at https://ethereum.github.io/execution-specs/src/ethereum/forks/\n") return banner @@ -713,17 +700,18 @@ func (bc *BlobConfig) String() string { } // BlobScheduleConfig determines target and max number of blobs allow per fork. +// +// From Prague onward, the blob schedule is updated only at BPO (Blob Parameter-Only) +// forks. Named forks such as Osaka or Amsterdam inherit the most recently configured +// BPO entry and must not declare their own BlobConfig. type BlobScheduleConfig struct { - Cancun *BlobConfig `json:"cancun,omitempty"` - Prague *BlobConfig `json:"prague,omitempty"` - Osaka *BlobConfig `json:"osaka,omitempty"` - BPO1 *BlobConfig `json:"bpo1,omitempty"` - BPO2 *BlobConfig `json:"bpo2,omitempty"` - BPO3 *BlobConfig `json:"bpo3,omitempty"` - BPO4 *BlobConfig `json:"bpo4,omitempty"` - BPO5 *BlobConfig `json:"bpo5,omitempty"` - Amsterdam *BlobConfig `json:"amsterdam,omitempty"` - UBT *BlobConfig `json:"ubt,omitempty"` + Cancun *BlobConfig `json:"cancun,omitempty"` + Prague *BlobConfig `json:"prague,omitempty"` + BPO1 *BlobConfig `json:"bpo1,omitempty"` + BPO2 *BlobConfig `json:"bpo2,omitempty"` + BPO3 *BlobConfig `json:"bpo3,omitempty"` + BPO4 *BlobConfig `json:"bpo4,omitempty"` + BPO5 *BlobConfig `json:"bpo5,omitempty"` } // IsHomestead returns whether num is either equal to the homestead block or greater. @@ -1000,13 +988,11 @@ func (c *ChainConfig) CheckConfigForkOrder() error { }{ {name: "cancun", timestamp: c.CancunTime, config: bsc.Cancun}, {name: "prague", timestamp: c.PragueTime, config: bsc.Prague}, - {name: "osaka", timestamp: c.OsakaTime, config: bsc.Osaka}, {name: "bpo1", timestamp: c.BPO1Time, config: bsc.BPO1}, {name: "bpo2", timestamp: c.BPO2Time, config: bsc.BPO2}, {name: "bpo3", timestamp: c.BPO3Time, config: bsc.BPO3}, {name: "bpo4", timestamp: c.BPO4Time, config: bsc.BPO4}, {name: "bpo5", timestamp: c.BPO5Time, config: bsc.BPO5}, - {name: "amsterdam", timestamp: c.AmsterdamTime, config: bsc.Amsterdam}, } { if cur.config != nil { if err := cur.config.validate(); err != nil { @@ -1169,28 +1155,33 @@ func (c *ChainConfig) LatestFork(time uint64) forks.Fork { } } -// BlobConfig returns the blob config associated with the provided fork. +// BlobConfig returns the blob config active at the provided fork. Since named +// forks (Osaka, Amsterdam, ...) no longer carry their own blob schedule, the +// lookup walks down from fork through the BPO chain to Prague/Cancun and returns +// the first non-nil entry. func (c *ChainConfig) BlobConfig(fork forks.Fork) *BlobConfig { - switch fork { - case forks.BPO5: - return c.BlobScheduleConfig.BPO5 - case forks.BPO4: - return c.BlobScheduleConfig.BPO4 - case forks.BPO3: - return c.BlobScheduleConfig.BPO3 - case forks.BPO2: - return c.BlobScheduleConfig.BPO2 - case forks.BPO1: - return c.BlobScheduleConfig.BPO1 - case forks.Osaka: - return c.BlobScheduleConfig.Osaka - case forks.Prague: - return c.BlobScheduleConfig.Prague - case forks.Cancun: - return c.BlobScheduleConfig.Cancun - default: + if c.BlobScheduleConfig == nil { return nil } + bsc := c.BlobScheduleConfig + chain := []struct { + at forks.Fork + cfg *BlobConfig + }{ + {forks.BPO5, bsc.BPO5}, + {forks.BPO4, bsc.BPO4}, + {forks.BPO3, bsc.BPO3}, + {forks.BPO2, bsc.BPO2}, + {forks.BPO1, bsc.BPO1}, + {forks.Prague, bsc.Prague}, + {forks.Cancun, bsc.Cancun}, + } + for _, e := range chain { + if e.at <= fork && e.cfg != nil { + return e.cfg + } + } + return nil } // ActiveSystemContracts returns the currently active system contracts at the diff --git a/tests/init.go b/tests/init.go index 2550eb1231..67f199203f 100644 --- a/tests/init.go +++ b/tests/init.go @@ -434,7 +434,6 @@ var Forks = map[string]*params.ChainConfig{ BlobScheduleConfig: ¶ms.BlobScheduleConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, - Osaka: params.DefaultOsakaBlobConfig, }, }, "PragueToOsakaAtTime15k": { @@ -461,7 +460,6 @@ var Forks = map[string]*params.ChainConfig{ BlobScheduleConfig: ¶ms.BlobScheduleConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, - Osaka: params.DefaultOsakaBlobConfig, }, }, "BPO1": { @@ -489,7 +487,6 @@ var Forks = map[string]*params.ChainConfig{ BlobScheduleConfig: ¶ms.BlobScheduleConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, - Osaka: params.DefaultOsakaBlobConfig, BPO1: bpo1BlobConfig, }, }, @@ -518,7 +515,6 @@ var Forks = map[string]*params.ChainConfig{ BlobScheduleConfig: ¶ms.BlobScheduleConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, - Osaka: params.DefaultOsakaBlobConfig, BPO1: bpo1BlobConfig, }, }, @@ -548,7 +544,6 @@ var Forks = map[string]*params.ChainConfig{ BlobScheduleConfig: ¶ms.BlobScheduleConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, - Osaka: params.DefaultOsakaBlobConfig, BPO1: bpo1BlobConfig, BPO2: bpo2BlobConfig, }, @@ -579,7 +574,6 @@ var Forks = map[string]*params.ChainConfig{ BlobScheduleConfig: ¶ms.BlobScheduleConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, - Osaka: params.DefaultOsakaBlobConfig, BPO1: bpo1BlobConfig, BPO2: bpo2BlobConfig, }, @@ -611,7 +605,6 @@ var Forks = map[string]*params.ChainConfig{ BlobScheduleConfig: ¶ms.BlobScheduleConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, - Osaka: params.DefaultOsakaBlobConfig, BPO1: bpo1BlobConfig, BPO2: bpo2BlobConfig, BPO3: params.DefaultBPO3BlobConfig, @@ -644,7 +637,6 @@ var Forks = map[string]*params.ChainConfig{ BlobScheduleConfig: ¶ms.BlobScheduleConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, - Osaka: params.DefaultOsakaBlobConfig, BPO1: bpo1BlobConfig, BPO2: bpo2BlobConfig, BPO3: params.DefaultBPO3BlobConfig, @@ -678,7 +670,6 @@ var Forks = map[string]*params.ChainConfig{ BlobScheduleConfig: ¶ms.BlobScheduleConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, - Osaka: params.DefaultOsakaBlobConfig, BPO1: bpo1BlobConfig, BPO2: bpo2BlobConfig, BPO3: params.DefaultBPO3BlobConfig, @@ -713,7 +704,6 @@ var Forks = map[string]*params.ChainConfig{ BlobScheduleConfig: ¶ms.BlobScheduleConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, - Osaka: params.DefaultOsakaBlobConfig, BPO1: bpo1BlobConfig, BPO2: bpo2BlobConfig, BPO3: params.DefaultBPO3BlobConfig, @@ -747,14 +737,12 @@ var Forks = map[string]*params.ChainConfig{ AmsterdamTime: u64(0), DepositContractAddress: params.MainnetChainConfig.DepositContractAddress, BlobScheduleConfig: ¶ms.BlobScheduleConfig{ - Cancun: params.DefaultCancunBlobConfig, - Prague: params.DefaultPragueBlobConfig, - Osaka: params.DefaultOsakaBlobConfig, - BPO1: bpo1BlobConfig, - BPO2: bpo2BlobConfig, - BPO3: params.DefaultBPO3BlobConfig, - BPO4: params.DefaultBPO4BlobConfig, - Amsterdam: params.DefaultBPO4BlobConfig, // TODO update when defined + Cancun: params.DefaultCancunBlobConfig, + Prague: params.DefaultPragueBlobConfig, + BPO1: bpo1BlobConfig, + BPO2: bpo2BlobConfig, + BPO3: params.DefaultBPO3BlobConfig, + BPO4: params.DefaultBPO4BlobConfig, }, }, "Verkle": { @@ -801,7 +789,6 @@ var Forks = map[string]*params.ChainConfig{ BlobScheduleConfig: ¶ms.BlobScheduleConfig{ Cancun: params.DefaultCancunBlobConfig, Prague: params.DefaultPragueBlobConfig, - Osaka: params.DefaultOsakaBlobConfig, }, }, }