diff --git a/crates/rpc/rpc-eth-api/src/helpers/call.rs b/crates/rpc/rpc-eth-api/src/helpers/call.rs index bc2a39e59c..78225dd9b7 100644 --- a/crates/rpc/rpc-eth-api/src/helpers/call.rs +++ b/crates/rpc/rpc-eth-api/src/helpers/call.rs @@ -25,7 +25,7 @@ use reth_evm::{ }; use reth_node_api::BlockBody; use reth_primitives_traits::Recovered; -use reth_revm::{database::StateProviderDatabase, db::State}; +use reth_revm::{cancelled::CancelOnDrop, database::StateProviderDatabase, db::State}; use reth_rpc_convert::{RpcConvert, RpcTxReq}; use reth_rpc_eth_types::{ cache::db::StateProviderTraitObjWrapper, @@ -526,6 +526,11 @@ pub trait Call: } /// Executes the call request at the given [`BlockId`]. + /// + /// This spawns a new task that obtains the state for the given [`BlockId`] and then transacts + /// the call [`Self::transact`]. If the future is dropped before the (blocking) transact + /// call is invoked, then the task is cancelled early, (for example if the request is terminated + /// early client-side). fn transact_call_at( &self, request: RpcTxReq<::Network>, @@ -535,10 +540,23 @@ pub trait Call: where Self: LoadPendingBlock, { - let this = self.clone(); - self.spawn_with_call_at(request, at, overrides, move |db, evm_env, tx_env| { - this.transact(db, evm_env, tx_env) - }) + async move { + let guard = CancelOnDrop::default(); + let cancel = guard.clone(); + let this = self.clone(); + + let res = self + .spawn_with_call_at(request, at, overrides, move |db, evm_env, tx_env| { + if cancel.is_cancelled() { + // callsite dropped the guard + return Err(EthApiError::InternalEthError.into()) + } + this.transact(db, evm_env, tx_env) + }) + .await; + drop(guard); + res + } } /// Executes the closure with the state that corresponds to the given [`BlockId`] on a new task