Type
Summary
eth_createAccessList never traces the call. createAccessList runs the tx through DoCall (a plain EthCall gRPC query, no tracer attached) and then builds newTracer from the input access list, so newTracer.Equal(prevTracer) is true on the first iteration and the method returns whatever access list the caller passed in (empty by default). No SLOAD/SSTORE/EXT*/BALANCE/CALL target is ever recorded.
|
for { |
|
accessList := prevTracer.AccessList() |
|
traceArgs.AccessList = &accessList |
|
res, err := b.DoCall(ctx, *traceArgs, blockNum, overrides) |
|
if err != nil { |
|
b.Logger.Error("failed to apply transaction", "error", err) |
|
return nil, 0, nil, fmt.Errorf("failed to apply transaction: %v err: %v", traceArgs.ToTransaction(ethtypes.LegacyTxType).Hash(), err) |
|
} |
|
|
|
// Check if access list has converged (no new addresses/slots accessed) |
|
newTracer := logger.NewAccessListTracer(accessList, addressesToExclude) |
|
if newTracer.Equal(prevTracer) { |
|
b.Logger.Info("access list converged", "accessList", accessList) |
|
var vmErr error |
|
if res.VmError != "" { |
for {
accessList := prevTracer.AccessList()
traceArgs.AccessList = &accessList
res, err := b.DoCall(ctx, *traceArgs, blockNum, overrides) // no tracer involved
...
newTracer := logger.NewAccessListTracer(accessList, addressesToExclude) // built from the input, not from execution
if newTracer.Equal(prevTracer) { // always true
return accessList, res.GasUsed, vmErr, nil
}
In go-ethereum the tracer is passed to the EVM via vm.Config{Tracer: tracer.Hooks()} and tracer.Equal(prevTracer) compares what was actually touched. Here nothing bridges the tracer to ApplyMessageWithConfig.
Reproduction (for bugs)
main (469142d), ./local_node.sh -y, dev account 0xc6fe5d33615a1c52c08018c47e8bc53646a0e101.
- Deploy a contract whose runtime is
PUSH20 0x1111…1111; BALANCE; STOP (touches another account, which geth reports in the access list):
eth_sendTransaction {"from":"0xc6fe…e101","data":"0x6017600c60003960176000f37311111111111111111111111111111111111111113100","gas":"0x30000"}
-> deployed at 0xe2f81b30e1d47dffdbb6ab41ec5f0572705b026d
- Ask for the access list:
eth_createAccessList [{"from":"0xc6fe…e101","to":"0xe2f81b30…026d","data":"0x"},"latest"]
-> {"accessList":[],"gasUsed":"0x5c33"}
geth returns [{"address":"0x1111111111111111111111111111111111111111","storageKeys":[]}] for the same call.
- Pass a bogus list in; it comes back unchanged (and the call is even charged for it):
eth_createAccessList [{"from":…,"to":"0xe2f81b30…026d","data":"0x","accessList":[{"address":"0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddead","storageKeys":["0x…01"]}]},"latest"]
-> {"accessList":[{"address":"0xdeaddeaddeaddeaddeaddeaddeaddeaddeaddead","storageKeys":["0x…01"]}],"gasUsed":"0x6cff"}
The unit test TestCreateAccessList only checks that a result is returned, so it does not catch this.
Impact
The method has been a no-op since it was added (#346). Clients that rely on it to build EIP-2930 access lists (ethers populateTransaction, viem prepareTransactionRequest with accessList, MEV/simulation tooling) silently get an empty or unchanged list, so the "optimized" transactions never get the expected gas savings.
Fixing it needs the tracer to run inside the EVM: either an EthCall/TraceCall variant in x/vm that installs logger.NewAccessListTracer(...).Hooks() as tracingHooks in ApplyMessageWithConfig and returns the list, or deriving the list from a prestateTracer debug_traceCall (accounts + storage keys minus the exclude set). I can send a PR for either approach if you tell me which one you prefer.
Related
Checklist
Type
Summary
eth_createAccessListnever traces the call.createAccessListruns the tx throughDoCall(a plainEthCallgRPC query, no tracer attached) and then buildsnewTracerfrom the input access list, sonewTracer.Equal(prevTracer)is true on the first iteration and the method returns whatever access list the caller passed in (empty by default). No SLOAD/SSTORE/EXT*/BALANCE/CALL target is ever recorded.evm/rpc/backend/tx_info.go
Lines 480 to 494 in 469142d
In go-ethereum the tracer is passed to the EVM via
vm.Config{Tracer: tracer.Hooks()}andtracer.Equal(prevTracer)compares what was actually touched. Here nothing bridges the tracer toApplyMessageWithConfig.Reproduction (for bugs)
main(469142d),./local_node.sh -y, dev account0xc6fe5d33615a1c52c08018c47e8bc53646a0e101.PUSH20 0x1111…1111; BALANCE; STOP(touches another account, which geth reports in the access list):geth returns
[{"address":"0x1111111111111111111111111111111111111111","storageKeys":[]}]for the same call.The unit test
TestCreateAccessListonly checks that a result is returned, so it does not catch this.Impact
The method has been a no-op since it was added (#346). Clients that rely on it to build EIP-2930 access lists (ethers
populateTransaction, viemprepareTransactionRequestwithaccessList, MEV/simulation tooling) silently get an empty or unchanged list, so the "optimized" transactions never get the expected gas savings.Fixing it needs the tracer to run inside the EVM: either an
EthCall/TraceCallvariant inx/vmthat installslogger.NewAccessListTracer(...).Hooks()astracingHooksinApplyMessageWithConfigand returns the list, or deriving the list from aprestateTracerdebug_traceCall(accounts + storage keys minus the exclude set). I can send a PR for either approach if you tell me which one you prefer.Related
Checklist