From fa21201ee351a2c1c30c36c6b4720f0eb9967f5b Mon Sep 17 00:00:00 2001 From: Daniel Liu <139250065@qq.com> Date: Wed, 25 Mar 2026 19:50:39 +0800 Subject: [PATCH] internal/ethapi: fix access list precompile overrides AccessList previously applied state overrides with a nil precompile set. That meant MovePrecompileTo could not recognize source precompiles in this path, and AccessList generation could diverge from the behavior already used by DoCall and DoEstimateGas. Fix this by constructing a mutable precompile set from the active rules before applying state overrides, then reusing the resulting set for both access-list tracer exclusion and EVM execution. This ensures moved or overridden precompiles are handled consistently throughout access-list generation. Also add regression coverage for CreateAccessList with MovePrecompileTo so the state override path is exercised directly, in addition to the existing EstimateGas coverage. Full go run ./build/ci.go test is currently failing in github.com/ethereum/go-ethereum/tests at TestExecutionSpecTransaction, which was not part of this change and was committed per explicit user instruction. --- internal/ethapi/api.go | 13 ++++++++--- internal/ethapi/api_test.go | 44 +++++++++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index bb0dd042ab..3f3751acf2 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -21,6 +21,7 @@ import ( "encoding/hex" "errors" "fmt" + "maps" gomath "math" "math/big" "strings" @@ -1275,12 +1276,15 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH if db == nil || err != nil { return nil, 0, nil, err } + isPostMerge := header.Difficulty.Sign() == 0 + rules := b.ChainConfig().Rules(header.Number, isPostMerge, header.Time) + precompileSet := maps.Clone(vm.ActivePrecompiledContracts(rules)) // Apply state overrides immediately after StateAndHeaderByNumberOrHash. // If not applied here, there could be cases where user-specified overrides (e.g., nonce) // may conflict with default values from the database, leading to inconsistencies. if stateOverrides != nil { - if err := stateOverrides.Apply(db, nil); err != nil { + if err := stateOverrides.Apply(db, precompileSet); err != nil { return nil, 0, nil, err } } @@ -1304,9 +1308,11 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH } else { to = crypto.CreateAddress(args.from(), uint64(*args.Nonce)) } - isPostMerge := header.Difficulty.Sign() == 0 // Retrieve the precompiles since they don't need to be added to the access list - precompiles := vm.ActivePrecompiles(b.ChainConfig().Rules(header.Number, isPostMerge, header.Time)) + precompiles := make([]common.Address, 0, len(precompileSet)) + for addr := range precompileSet { + precompiles = append(precompiles, addr) + } // addressesToExclude contains sender, receiver, precompiles and valid authorizations addressesToExclude := map[common.Address]struct{}{args.from(): {}, to: {}} @@ -1354,6 +1360,7 @@ func AccessList(ctx context.Context, b Backend, blockNrOrHash rpc.BlockNumberOrH tracer := logger.NewAccessListTracer(accessList, addressesToExclude) config := vm.Config{Tracer: tracer.Hooks(), NoBaseFee: true} evm := b.GetEVM(ctx, statedb, header, &config, nil) + evm.SetPrecompiles(precompileSet) // Lower the basefee to 0 to avoid breaking EVM // invariants (basefee < feecap). diff --git a/internal/ethapi/api_test.go b/internal/ethapi/api_test.go index 62e9979d3d..5f09e5ea2d 100644 --- a/internal/ethapi/api_test.go +++ b/internal/ethapi/api_test.go @@ -1014,7 +1014,7 @@ func TestCall(t *testing.T) { Balance: big.NewInt(params.Ether), Nonce: 1, Storage: map[common.Hash]common.Hash{ - common.Hash{}: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"), + {}: common.HexToHash("0x0000000000000000000000000000000000000000000000000000000000000001"), }, }, }, @@ -3795,7 +3795,7 @@ func TestCreateAccessListWithStateOverrides(t *testing.T) { Balance: (*hexutil.Big)(big.NewInt(1000000000000000000)), Nonce: &nonce, State: map[common.Hash]common.Hash{ - common.Hash{}: common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000002a"), + {}: common.HexToHash("0x000000000000000000000000000000000000000000000000000000000000002a"), }, }, } @@ -3832,6 +3832,46 @@ func TestCreateAccessListWithStateOverrides(t *testing.T) { require.Equal(t, expected, result.Accesslist) } +func TestCreateAccessListWithMovePrecompile(t *testing.T) { + t.Parallel() + // Initialize test accounts + var ( + accounts = newAccounts(2) + genesis = &core.Genesis{ + Config: params.MergedTestChainConfig, + Alloc: types.GenesisAlloc{ + accounts[0].addr: {Balance: big.NewInt(params.Ether)}, + }, + } + ) + backend := newTestBackend(t, 1, genesis, beacon.New(ethash.NewFaker()), func(i int, b *core.BlockGen) { + b.SetPoS() + }) + api := NewBlockChainAPI(backend) + + var ( + sha256Addr = common.BytesToAddress([]byte{0x2}) + newSha256Addr = common.BytesToAddress([]byte{0x10, 0}) + sha256Input = hexutil.Bytes([]byte("hello")) + gas = hexutil.Uint64(100000) + args = TransactionArgs{ + From: &accounts[0].addr, + To: &newSha256Addr, + Data: &sha256Input, + Gas: &gas, + } + overrides = &override.StateOverride{ + sha256Addr: override.OverrideAccount{ + MovePrecompileTo: &newSha256Addr, + }, + } + ) + result, err := api.CreateAccessList(context.Background(), args, nil, overrides) + require.NoError(t, err) + require.NotNil(t, result) + require.NotNil(t, result.Accesslist) +} + func TestEstimateGasWithMovePrecompile(t *testing.T) { t.Parallel() // Initialize test accounts