eth: add request cancellation checks #26320 (#1314)

This ensures that RPC method handlers will react to a timeout or
cancelled request soon after the event occurs.

Co-authored-by: Sina Mahmoodi <itz.s1na@gmail.com>
This commit is contained in:
Daniel Liu 2025-09-09 10:18:10 +08:00 committed by GitHub
parent dee4c985b1
commit afc20286fb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 30 additions and 11 deletions

View file

@ -404,7 +404,7 @@ func (api *DebugAPI) StorageRangeAt(ctx context.Context, blockHash common.Hash,
if block == nil { if block == nil {
return StorageRangeResult{}, fmt.Errorf("block %#x not found", blockHash) return StorageRangeResult{}, fmt.Errorf("block %#x not found", blockHash)
} }
_, _, statedb, release, err := api.eth.stateAtTransaction(block, txIndex, 0) _, _, statedb, release, err := api.eth.stateAtTransaction(ctx, block, txIndex, 0)
if err != nil { if err != nil {
return StorageRangeResult{}, err return StorageRangeResult{}, err
} }

View file

@ -415,11 +415,11 @@ func (b *EthAPIBackend) Miner() *miner.Miner {
} }
func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) { func (b *EthAPIBackend) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (*state.StateDB, tracers.StateReleaseFunc, error) {
return b.eth.StateAtBlock(block, reexec, base, readOnly, preferDisk) return b.eth.StateAtBlock(ctx, block, reexec, base, readOnly, preferDisk)
} }
func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { func (b *EthAPIBackend) StateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
return b.eth.stateAtTransaction(block, txIndex, reexec) return b.eth.stateAtTransaction(ctx, block, txIndex, reexec)
} }
func (b *EthAPIBackend) GetIPCClient() (bind.ContractBackend, error) { func (b *EthAPIBackend) GetIPCClient() (bind.ContractBackend, error) {

View file

@ -17,6 +17,7 @@
package eth package eth
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"math/big" "math/big"
@ -57,7 +58,7 @@ var noopReleaser = tracers.StateReleaseFunc(func() {})
// - preferDisk: this arg can be used by the caller to signal that even though the 'base' is // - preferDisk: this arg can be used by the caller to signal that even though the 'base' is
// provided, it would be preferable to start from a fresh state, if we have it // provided, it would be preferable to start from a fresh state, if we have it
// on disk. // on disk.
func (eth *Ethereum) StateAtBlock(block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (statedb *state.StateDB, release tracers.StateReleaseFunc, err error) { func (eth *Ethereum) StateAtBlock(ctx context.Context, block *types.Block, reexec uint64, base *state.StateDB, readOnly bool, preferDisk bool) (statedb *state.StateDB, release tracers.StateReleaseFunc, err error) {
var ( var (
current *types.Block current *types.Block
database state.Database database state.Database
@ -112,6 +113,9 @@ func (eth *Ethereum) StateAtBlock(block *types.Block, reexec uint64, base *state
} }
// Database does not have the state for the given block, try to regenerate // Database does not have the state for the given block, try to regenerate
for i := uint64(0); i < reexec; i++ { for i := uint64(0); i < reexec; i++ {
if err := ctx.Err(); err != nil {
return nil, nil, err
}
if current.NumberU64() == 0 { if current.NumberU64() == 0 {
return nil, nil, errors.New("genesis state is missing") return nil, nil, errors.New("genesis state is missing")
} }
@ -143,6 +147,9 @@ func (eth *Ethereum) StateAtBlock(block *types.Block, reexec uint64, base *state
parent common.Hash parent common.Hash
) )
for current.NumberU64() < origin { for current.NumberU64() < origin {
if err := ctx.Err(); err != nil {
return nil, nil, err
}
// Print progress logs if long enough time elapsed // Print progress logs if long enough time elapsed
if time.Since(logged) > 8*time.Second && report { if time.Since(logged) > 8*time.Second && report {
log.Info("Regenerating historical state", "block", current.NumberU64()+1, "target", origin, "remaining", origin-current.NumberU64()-1, "elapsed", time.Since(start)) log.Info("Regenerating historical state", "block", current.NumberU64()+1, "target", origin, "remaining", origin-current.NumberU64()-1, "elapsed", time.Since(start))
@ -183,7 +190,7 @@ func (eth *Ethereum) StateAtBlock(block *types.Block, reexec uint64, base *state
} }
// stateAtTransaction returns the execution environment of a certain transaction. // stateAtTransaction returns the execution environment of a certain transaction.
func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) { func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block, txIndex int, reexec uint64) (core.Message, vm.BlockContext, *state.StateDB, tracers.StateReleaseFunc, error) {
// Short circuit if it's genesis block. // Short circuit if it's genesis block.
if block.NumberU64() == 0 { if block.NumberU64() == 0 {
return nil, vm.BlockContext{}, nil, nil, errors.New("no transaction in genesis") return nil, vm.BlockContext{}, nil, nil, errors.New("no transaction in genesis")
@ -195,7 +202,7 @@ func (eth *Ethereum) stateAtTransaction(block *types.Block, txIndex int, reexec
} }
// Lookup the statedb of parent block from the live database, // Lookup the statedb of parent block from the live database,
// otherwise regenerate it on the flight. // otherwise regenerate it on the flight.
statedb, release, err := eth.StateAtBlock(parent, reexec, nil, true, false) statedb, release, err := eth.StateAtBlock(ctx, parent, reexec, nil, true, false)
if err != nil { if err != nil {
return nil, vm.BlockContext{}, nil, nil, err return nil, vm.BlockContext{}, nil, nil, err
} }

View file

@ -526,6 +526,9 @@ func (api *API) IntermediateRoots(ctx context.Context, hash common.Hash, config
) )
feeCapacity := state.GetTRC21FeeCapacityFromState(statedb) feeCapacity := state.GetTRC21FeeCapacityFromState(statedb)
for i, tx := range block.Transactions() { for i, tx := range block.Transactions() {
if err := ctx.Err(); err != nil {
return nil, err
}
var balance *big.Int var balance *big.Int
if tx.To() != nil { if tx.To() != nil {
// Bypass the validation for trading and lending transactions as their nonce are not incremented // Bypass the validation for trading and lending transactions as their nonce are not incremented
@ -590,14 +593,13 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
signer = types.MakeSigner(api.backend.ChainConfig(), block.Number()) signer = types.MakeSigner(api.backend.ChainConfig(), block.Number())
txs = block.Transactions() txs = block.Transactions()
results = make([]*txTraceResult, len(txs)) results = make([]*txTraceResult, len(txs))
pend sync.WaitGroup
pend = new(sync.WaitGroup)
jobs = make(chan *txTraceTask, len(txs))
) )
threads := runtime.NumCPU() threads := runtime.NumCPU()
if threads > len(txs) { if threads > len(txs) {
threads = len(txs) threads = len(txs)
} }
jobs := make(chan *txTraceTask, threads)
blockHash := block.Hash() blockHash := block.Hash()
for th := 0; th < threads; th++ { for th := 0; th < threads; th++ {
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
@ -629,13 +631,22 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
} }
}() }()
} }
// Feed the transactions into the tracers and return // Feed the transactions into the tracers and return
feeCapacity := state.GetTRC21FeeCapacityFromState(statedb) feeCapacity := state.GetTRC21FeeCapacityFromState(statedb)
var failed error var failed error
blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil) blockCtx := core.NewEVMBlockContext(block.Header(), api.chainContext(ctx), nil)
txloop:
for i, tx := range txs { for i, tx := range txs {
// Send the trace task over for execution // Send the trace task over for execution
jobs <- &txTraceTask{statedb: statedb.Copy(), index: i} task := &txTraceTask{statedb: statedb.Copy(), index: i}
select {
case <-ctx.Done():
failed = ctx.Err()
break txloop
case jobs <- task:
}
var balance *big.Int var balance *big.Int
if tx.To() != nil { if tx.To() != nil {
// Bypass the validation for trading and lending transactions as their nonce are not incremented // Bypass the validation for trading and lending transactions as their nonce are not incremented
@ -654,12 +665,13 @@ func (api *API) traceBlock(ctx context.Context, block *types.Block, config *Trac
owner := common.Address{} owner := common.Address{}
if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), owner); err != nil { if _, err := core.ApplyMessage(vmenv, msg, new(core.GasPool).AddGas(msg.Gas()), owner); err != nil {
failed = err failed = err
break break txloop
} }
// Finalize the state so any modifications are written to the trie // Finalize the state so any modifications are written to the trie
// Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect // Only delete empty objects if EIP158/161 (a.k.a Spurious Dragon) is in effect
statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number())) statedb.Finalise(vmenv.ChainConfig().IsEIP158(block.Number()))
} }
close(jobs) close(jobs)
pend.Wait() pend.Wait()