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.
This commit is contained in:
Daniel Liu 2026-03-25 19:50:39 +08:00
parent 5d0e18f775
commit fa21201ee3
2 changed files with 52 additions and 5 deletions

View file

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

View file

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