From 295d9cd5abdea6a71da9905100b611d88832b430 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Fri, 24 Apr 2026 11:37:15 +0800 Subject: [PATCH] core, cmd, internal, eth, miner: introduce blockAccessIndex --- cmd/evm/internal/t8ntool/execution.go | 4 ++-- core/chain_makers.go | 4 ++-- core/state/state_object.go | 2 +- core/state/statedb.go | 27 +++++++++++++++++++-------- core/state/statedb_hooked.go | 4 ++++ core/state/statedb_hooked_test.go | 2 +- core/state_prefetcher.go | 2 +- core/state_processor.go | 24 +++++++++++++----------- core/vm/interface.go | 1 + eth/state_accessor.go | 2 +- eth/tracers/api.go | 8 ++++---- internal/ethapi/simulate.go | 4 ++-- miner/worker.go | 6 +++--- 13 files changed, 54 insertions(+), 36 deletions(-) diff --git a/cmd/evm/internal/t8ntool/execution.go b/cmd/evm/internal/t8ntool/execution.go index 15973e934d..59c43693a2 100644 --- a/cmd/evm/internal/t8ntool/execution.go +++ b/cmd/evm/internal/t8ntool/execution.go @@ -270,7 +270,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, continue } } - statedb.SetTxContext(tx.Hash(), len(receipts)) + statedb.SetTxContext(tx.Hash(), len(receipts), uint16(len(receipts)+1)) var ( snapshot = statedb.Snapshot() gp = gaspool.Snapshot() @@ -336,7 +336,7 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig, for _, receipt := range receipts { allLogs = append(allLogs, receipt.Logs...) } - requests, err := core.PostExecution(context.Background(), chainConfig, vmContext.BlockNumber, vmContext.Time, allLogs, evm) + requests, err := core.PostExecution(context.Background(), chainConfig, vmContext.BlockNumber, vmContext.Time, allLogs, evm, uint16(len(receipts)+1)) if err != nil { return nil, nil, nil, NewError(ErrorEVM, fmt.Errorf("failed to process post-execution: %v", err)) } diff --git a/core/chain_makers.go b/core/chain_makers.go index 7474d892b1..da6881e050 100644 --- a/core/chain_makers.go +++ b/core/chain_makers.go @@ -117,7 +117,7 @@ func (b *BlockGen) addTx(bc *BlockChain, vmConfig vm.Config, tx *types.Transacti blockContext = NewEVMBlockContext(b.header, bc, &b.header.Coinbase) evm = vm.NewEVM(blockContext, b.statedb, b.cm.config, vmConfig) ) - b.statedb.SetTxContext(tx.Hash(), len(b.txs)) + b.statedb.SetTxContext(tx.Hash(), len(b.txs), uint16(len(b.txs)+1)) receipt, err := ApplyTransaction(evm, b.gasPool, b.statedb, b.header, tx) if err != nil { panic(err) @@ -323,7 +323,7 @@ func (b *BlockGen) collectRequests(readonly bool) (requests [][]byte) { blockContext := NewEVMBlockContext(b.header, b.cm, &b.header.Coinbase) evm := vm.NewEVM(blockContext, statedb, b.cm.config, vm.Config{}) - requests, err := PostExecution(context.Background(), b.cm.config, b.header.Number, b.header.Time, blockLogs, evm) + requests, err := PostExecution(context.Background(), b.cm.config, b.header.Number, b.header.Time, blockLogs, evm, uint16(len(b.txs)+1)) if err != nil { panic(fmt.Sprintf("failed to run post-execution: %v", err)) } diff --git a/core/state/state_object.go b/core/state/state_object.go index df0740320c..ce456e7668 100644 --- a/core/state/state_object.go +++ b/core/state/state_object.go @@ -280,7 +280,7 @@ func (s *stateObject) finalise() { // All slots in the dirtyStorage set must have post-transaction // values that differ from their pre-transaction values. if s.db.stateAccessList != nil { - s.db.stateAccessList.StorageWrite(uint16(s.db.txIndex+1), s.address, key, value) + s.db.stateAccessList.StorageWrite(s.db.blockAccessIndex, s.address, key, value) } } if s.db.prefetcher != nil && len(slotsToPrefetch) > 0 && s.data.Root != types.EmptyRootHash { diff --git a/core/state/statedb.go b/core/state/statedb.go index ea5900537f..9866195874 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -131,6 +131,9 @@ type StateDB struct { // Per-transaction state access footprint for EIP-7928 stateAccessList *bal.ConstructionBlockAccessList + // Block access index (0 for pre-execution, 1..n for transactions, n+1 for post-execution) + blockAccessIndex uint16 + // Transient storage transientStorage transientStorage @@ -695,6 +698,7 @@ func (s *StateDB) Copy() *StateDB { refund: s.refund, thash: s.thash, txIndex: s.txIndex, + blockAccessIndex: s.blockAccessIndex, logs: make(map[common.Hash][]*types.Log, len(s.logs)), logSize: s.logSize, preimages: maps.Clone(s.preimages), @@ -742,6 +746,9 @@ func (s *StateDB) Copy() *StateDB { } state.logs[hash] = cpy } + if s.stateAccessList != nil { + state.stateAccessList = s.stateAccessList.Copy() + } return state } @@ -807,9 +814,12 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccess obj, exist := s.stateObjects[addr] if !exist { // RIPEMD160 (0x03) gets an extra dirty marker for a historical - // mainnet consensus exception 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 + // 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 @@ -840,7 +850,7 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccess // clean up here. balance := uint256.NewInt(0) if state.balanceSet && balance.Cmp(state.balance) != 0 { - s.stateAccessList.BalanceChange(uint16(s.txIndex+1), addr, balance) + s.stateAccessList.BalanceChange(s.blockAccessIndex, addr, balance) } } } else { @@ -849,15 +859,15 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccess if s.stateAccessList != nil { balance := obj.Balance() if state.balanceSet && balance.Cmp(state.balance) != 0 { - s.stateAccessList.BalanceChange(uint16(s.txIndex+1), addr, balance) + s.stateAccessList.BalanceChange(s.blockAccessIndex, addr, balance) } nonce := obj.Nonce() if state.nonceSet && nonce != state.nonce { - s.stateAccessList.NonceChange(addr, uint16(s.txIndex+1), nonce) + s.stateAccessList.NonceChange(addr, s.blockAccessIndex, nonce) } if state.codeSet { if code := obj.Code(); !bytes.Equal(code, state.code) { - s.stateAccessList.CodeChange(addr, uint16(s.txIndex+1), code) + s.stateAccessList.CodeChange(addr, s.blockAccessIndex, code) } } } @@ -1090,9 +1100,10 @@ func (s *StateDB) IntermediateRoot(deleteEmptyObjects bool) common.Hash { // SetTxContext sets the current transaction hash and index which are // used when the EVM emits new state logs. It should be invoked before // transaction execution. -func (s *StateDB) SetTxContext(thash common.Hash, ti int) { +func (s *StateDB) SetTxContext(thash common.Hash, ti int, blockAccessIndex uint16) { s.thash = thash s.txIndex = ti + s.blockAccessIndex = blockAccessIndex } func (s *StateDB) clearJournalAndRefund() { diff --git a/core/state/statedb_hooked.go b/core/state/statedb_hooked.go index f32e982b5f..5e1ad5afbd 100644 --- a/core/state/statedb_hooked.go +++ b/core/state/statedb_hooked.go @@ -288,3 +288,7 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlock } return s.inner.Finalise(deleteEmptyObjects) } + +func (s *hookedStateDB) SetTxContext(thash common.Hash, ti int, blockAccessIndex uint16) { + s.inner.SetTxContext(thash, ti, blockAccessIndex) +} diff --git a/core/state/statedb_hooked_test.go b/core/state/statedb_hooked_test.go index 6fe17ec1b4..fad234f848 100644 --- a/core/state/statedb_hooked_test.go +++ b/core/state/statedb_hooked_test.go @@ -82,7 +82,7 @@ func TestBurn(t *testing.T) { // TestHooks is a basic sanity-check of all hooks func TestHooks(t *testing.T) { inner, _ := New(types.EmptyRootHash, NewDatabaseForTesting()) - inner.SetTxContext(common.Hash{0x11}, 100) // For the log + inner.SetTxContext(common.Hash{0x11}, 100, 101) // For the log var result []string var wants = []string{ "0xaa00000000000000000000000000000000000000.balance: 0->100 (Unspecified)", diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index ed292d0beb..11a0f282d2 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -104,7 +104,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c // Disable the nonce check msg.SkipNonceChecks = true - stateCpy.SetTxContext(tx.Hash(), i) + stateCpy.SetTxContext(tx.Hash(), i, uint16(i+1)) // We attempt to apply a transaction. The goal is not to execute // the transaction successfully, rather to warm up touched data slots. diff --git a/core/state_processor.go b/core/state_processor.go index 4bffece7ac..0e57d32410 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -94,7 +94,7 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated if err != nil { return nil, fmt.Errorf("could not apply tx %d [%v]: %w", i, tx.Hash().Hex(), err) } - statedb.SetTxContext(tx.Hash(), i) + statedb.SetTxContext(tx.Hash(), i, uint16(i+1)) _, _, spanEnd := telemetry.StartSpan(ctx, "core.ApplyTransactionWithEVM", telemetry.StringAttribute("tx.hash", tx.Hash().Hex()), telemetry.Int64Attribute("tx.index", int64(i)), @@ -109,8 +109,7 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated allLogs = append(allLogs, receipt.Logs...) spanEnd(nil) } - // Run the post-execution system calls - requests, err := PostExecution(ctx, config, block.Number(), block.Time(), allLogs, evm) + requests, err := PostExecution(ctx, config, block.Number(), block.Time(), allLogs, evm, uint16(len(block.Transactions())+1)) if err != nil { return nil, err } @@ -143,7 +142,7 @@ func PreExecution(ctx context.Context, beaconRoot *common.Hash, parent common.Ha // PostExecution processes post-execution system calls when Prague is enabled. // If Prague is not activated, it returns null requests to differentiate from // empty requests. -func PostExecution(ctx context.Context, config *params.ChainConfig, number *big.Int, time uint64, allLogs []*types.Log, evm *vm.EVM) (requests [][]byte, err error) { +func PostExecution(ctx context.Context, config *params.ChainConfig, number *big.Int, time uint64, allLogs []*types.Log, evm *vm.EVM, blockAccessIndex uint16) (requests [][]byte, err error) { _, _, spanEnd := telemetry.StartSpan(ctx, "core.postExecution") defer spanEnd(&err) @@ -155,11 +154,11 @@ func PostExecution(ctx context.Context, config *params.ChainConfig, number *big. return nil, fmt.Errorf("failed to parse deposit logs: %w", err) } // EIP-7002 - if err := ProcessWithdrawalQueue(&requests, evm); err != nil { + if err := ProcessWithdrawalQueue(&requests, evm, blockAccessIndex); err != nil { return nil, fmt.Errorf("failed to process withdrawal queue: %w", err) } // EIP-7251 - if err := ProcessConsolidationQueue(&requests, evm); err != nil { + if err := ProcessConsolidationQueue(&requests, evm, blockAccessIndex); err != nil { return nil, fmt.Errorf("failed to process consolidation queue: %w", err) } } @@ -268,6 +267,7 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM) { Data: beaconRoot[:], } evm.SetTxContext(NewEVMTxContext(msg)) + evm.StateDB.SetTxContext(common.Hash{}, 0, 0) evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress) _, _, _ = evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560) if evm.StateDB.AccessEvents() != nil { @@ -295,6 +295,7 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) { Data: prevHash.Bytes(), } evm.SetTxContext(NewEVMTxContext(msg)) + evm.StateDB.SetTxContext(common.Hash{}, 0, 0) evm.StateDB.AddAddressToAccessList(params.HistoryStorageAddress) _, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560) if err != nil { @@ -308,17 +309,17 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM) { // ProcessWithdrawalQueue calls the EIP-7002 withdrawal queue contract. // It returns the opaque request data returned by the contract. -func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM) error { - return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress) +func ProcessWithdrawalQueue(requests *[][]byte, evm *vm.EVM, blockAccessIndex uint16) error { + return processRequestsSystemCall(requests, evm, 0x01, params.WithdrawalQueueAddress, blockAccessIndex) } // ProcessConsolidationQueue calls the EIP-7251 consolidation queue contract. // It returns the opaque request data returned by the contract. -func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM) error { - return processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress) +func ProcessConsolidationQueue(requests *[][]byte, evm *vm.EVM, blockAccessIndex uint16) error { + return processRequestsSystemCall(requests, evm, 0x02, params.ConsolidationQueueAddress, blockAccessIndex) } -func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address) error { +func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte, addr common.Address, blockAccessIndex uint16) error { if tracer := evm.Config.Tracer; tracer != nil { onSystemCallStart(tracer, evm.GetVMContext()) if tracer.OnSystemCallEnd != nil { @@ -334,6 +335,7 @@ func processRequestsSystemCall(requests *[][]byte, evm *vm.EVM, requestType byte To: &addr, } evm.SetTxContext(NewEVMTxContext(msg)) + evm.StateDB.SetTxContext(common.Hash{}, 0, blockAccessIndex) evm.StateDB.AddAddressToAccessList(addr) ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560) if evm.StateDB.AccessEvents() != nil { diff --git a/core/vm/interface.go b/core/vm/interface.go index 5619d98c53..6d197d06f6 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -99,4 +99,5 @@ type StateDB interface { // Finalise must be invoked at the end of a transaction Finalise(bool) *bal.ConstructionBlockAccessList + SetTxContext(thash common.Hash, ti int, blockAccessIndex uint16) } diff --git a/eth/state_accessor.go b/eth/state_accessor.go index 53dfb7d458..fd2f879616 100644 --- a/eth/state_accessor.go +++ b/eth/state_accessor.go @@ -265,7 +265,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) // Not yet the searched for transaction, execute on top of the current state - statedb.SetTxContext(tx.Hash(), idx) + statedb.SetTxContext(tx.Hash(), idx, uint16(idx+1)) if _, err := core.ApplyMessage(evm, msg, nil); err != nil { return nil, vm.BlockContext{}, nil, nil, fmt.Errorf("transaction %#x failed: %v", tx.Hash(), err) } diff --git a/eth/tracers/api.go b/eth/tracers/api.go index d9e40f7ec1..822f38ea5b 100644 --- a/eth/tracers/api.go +++ b/eth/tracers/api.go @@ -530,7 +530,7 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config return nil, err } msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) - statedb.SetTxContext(tx.Hash(), i) + statedb.SetTxContext(tx.Hash(), i, uint16(i+1)) if _, err := core.ApplyMessage(evm, msg, nil); err != nil { log.Warn("Tracing intermediate roots did not complete", "txindex", i, "txhash", tx.Hash(), "err", err) // We intentionally don't return the error here: if we do, then the RPC server will not @@ -681,7 +681,7 @@ txloop: // Generate the next state snapshot fast without tracing msg, _ := core.TransactionToMessage(tx, signer, block.BaseFee()) - statedb.SetTxContext(tx.Hash(), i) + statedb.SetTxContext(tx.Hash(), i, uint16(i+1)) if _, err := core.ApplyMessage(evm, msg, nil); err != nil { failed = err break txloop @@ -793,7 +793,7 @@ func (api *API) standardTraceBlockToFile(ctx context.Context, block *types.Block }) ) // Execute the transaction and flush any traces to disk - statedb.SetTxContext(tx.Hash(), i) + statedb.SetTxContext(tx.Hash(), i, uint16(i+1)) if tracer.OnTxStart != nil { tracer.OnTxStart(evm.GetVMContext(), tx, msg.From) } @@ -1016,7 +1016,7 @@ func (api *API) traceTx(ctx context.Context, tx *types.Transaction, message *cor defer cancel() // Call Prepare to clear out the statedb access list - statedb.SetTxContext(txctx.TxHash, txctx.TxIndex) + statedb.SetTxContext(txctx.TxHash, txctx.TxIndex, uint16(txctx.TxIndex)) _, err = core.ApplyTransactionWithEVM(message, core.NewGasPool(message.GasLimit), statedb, vmctx.BlockNumber, txctx.BlockHash, vmctx.Time, tx, evm) if err != nil { diff --git a/internal/ethapi/simulate.go b/internal/ethapi/simulate.go index 170104fbdf..9c3fb6a52e 100644 --- a/internal/ethapi/simulate.go +++ b/internal/ethapi/simulate.go @@ -340,7 +340,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, tracer.reset(txHash, uint(i)) // EoA check is always skipped, even in validation mode. - sim.state.SetTxContext(txHash, i) + sim.state.SetTxContext(txHash, i, uint16(i+1)) msg := call.ToMessage(header.BaseFee, !sim.validate) result, err := applyMessageWithEVM(ctx, evm, msg, timeout, gp) if err != nil { @@ -391,7 +391,7 @@ func (sim *simulator) processBlock(ctx context.Context, block *simBlock, header, } // Run post-execution system calls - requests, err := core.PostExecution(ctx, sim.chainConfig, header.Number, header.Time, allLogs, evm) + requests, err := core.PostExecution(ctx, sim.chainConfig, header.Number, header.Time, allLogs, evm, uint16(len(block.Calls)+1)) if err != nil { return nil, nil, nil, err } diff --git a/miner/worker.go b/miner/worker.go index ccafa20b29..08acdf3339 100644 --- a/miner/worker.go +++ b/miner/worker.go @@ -167,7 +167,7 @@ func (miner *Miner) generateWork(ctx context.Context, genParam *generateParams, // otherwise, fill the block with the current transactions from the txpool if genParam.forceOverrides && len(genParam.overrideTxs) > 0 { for _, tx := range genParam.overrideTxs { - work.state.SetTxContext(tx.Hash(), work.tcount) + work.state.SetTxContext(tx.Hash(), work.tcount, uint16(work.tcount+1)) if err := miner.commitTransaction(ctx, work, tx); err != nil { // all passed transactions HAVE to be valid at this point return &newPayloadResult{err: err} @@ -208,7 +208,7 @@ func (miner *Miner) generateWork(ctx context.Context, genParam *generateParams, } // Collect consensus-layer requests if Prague is enabled. - requests, err := core.PostExecution(ctx, miner.chainConfig, work.header.Number, work.header.Time, allLogs, work.evm) + requests, err := core.PostExecution(ctx, miner.chainConfig, work.header.Number, work.header.Time, allLogs, work.evm, uint16(work.tcount+1)) if err != nil { return &newPayloadResult{err: err} } @@ -502,7 +502,7 @@ func (miner *Miner) commitTransactions(ctx context.Context, env *environment, pl continue } // Start executing the transaction - env.state.SetTxContext(tx.Hash(), env.tcount) + env.state.SetTxContext(tx.Hash(), env.tcount, uint16(env.tcount+1)) err := miner.commitTransaction(ctx, env, tx) switch {