mirror of
https://github.com/hl-archive-node/nanoreth.git
synced 2025-12-06 10:59:55 +00:00
RFC: Add rpc method eth_callMany (#4070)
Co-authored-by: Matthias Seitz <matthias.seitz@outlook.de>
This commit is contained in:
@ -4,8 +4,9 @@ use reth_primitives::{
|
||||
AccessListWithGasUsed, Address, BlockId, BlockNumberOrTag, Bytes, H256, H64, U256, U64,
|
||||
};
|
||||
use reth_rpc_types::{
|
||||
state::StateOverride, BlockOverrides, CallRequest, EIP1186AccountProofResponse, FeeHistory,
|
||||
Index, RichBlock, SyncStatus, Transaction, TransactionReceipt, TransactionRequest, Work,
|
||||
state::StateOverride, BlockOverrides, Bundle, CallRequest, EIP1186AccountProofResponse,
|
||||
EthCallResponse, FeeHistory, Index, RichBlock, StateContext, SyncStatus, Transaction,
|
||||
TransactionReceipt, TransactionRequest, Work,
|
||||
};
|
||||
|
||||
/// Eth rpc interface: <https://ethereum.github.io/execution-apis/api-documentation/>
|
||||
@ -153,6 +154,16 @@ pub trait EthApi {
|
||||
block_overrides: Option<Box<BlockOverrides>>,
|
||||
) -> RpcResult<Bytes>;
|
||||
|
||||
/// Simulate arbitrary number of transactions at an arbitrary blockchain index, with the
|
||||
/// optionality of state overrides
|
||||
#[method(name = "callMany")]
|
||||
async fn call_many(
|
||||
&self,
|
||||
bundle: Bundle,
|
||||
state_context: Option<StateContext>,
|
||||
state_override: Option<StateOverride>,
|
||||
) -> RpcResult<Vec<EthCallResponse>>;
|
||||
|
||||
/// Generates an access list for a transaction.
|
||||
///
|
||||
/// This method creates an [EIP2930](https://eips.ethereum.org/EIPS/eip-2930) type accessList based on a given Transaction.
|
||||
|
||||
@ -23,6 +23,18 @@ pub struct StateContext {
|
||||
pub transaction_index: Option<TransactionIndex>,
|
||||
}
|
||||
|
||||
/// CallResponse for eth_callMany
|
||||
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(default, rename_all = "camelCase")]
|
||||
pub struct EthCallResponse {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
/// eth_call output (if no error)
|
||||
pub output: Option<Bytes>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
/// eth_call output (if error)
|
||||
pub error: Option<String>,
|
||||
}
|
||||
|
||||
/// Represents a transaction index where -1 means all transactions
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
|
||||
pub enum TransactionIndex {
|
||||
|
||||
@ -19,7 +19,7 @@ mod work;
|
||||
|
||||
pub use account::*;
|
||||
pub use block::*;
|
||||
pub use call::{Bundle, CallInput, CallInputError, CallRequest, StateContext};
|
||||
pub use call::{Bundle, CallInput, CallInputError, CallRequest, EthCallResponse, StateContext};
|
||||
pub use fee::{FeeHistory, TxGasAndReward};
|
||||
pub use filter::*;
|
||||
pub use index::Index;
|
||||
|
||||
@ -5,7 +5,7 @@ use crate::{
|
||||
error::{ensure_success, EthApiError, EthResult, RevertError, RpcInvalidTransactionError},
|
||||
revm_utils::{
|
||||
build_call_evm_env, caller_gas_allowance, cap_tx_gas_limit_with_caller_allowance,
|
||||
get_precompiles, inspect, transact, EvmOverrides,
|
||||
get_precompiles, inspect, prepare_call_env, transact, EvmOverrides,
|
||||
},
|
||||
EthTransactions,
|
||||
},
|
||||
@ -18,12 +18,16 @@ use reth_provider::{BlockReaderIdExt, EvmEnvProvider, StateProvider, StateProvid
|
||||
use reth_revm::{
|
||||
access_list::AccessListInspector,
|
||||
database::{State, SubState},
|
||||
env::tx_env_with_recovered,
|
||||
};
|
||||
use reth_rpc_types::{
|
||||
state::StateOverride, BlockError, Bundle, CallRequest, EthCallResponse, StateContext,
|
||||
};
|
||||
use reth_rpc_types::CallRequest;
|
||||
use reth_transaction_pool::TransactionPool;
|
||||
use revm::{
|
||||
db::{CacheDB, DatabaseRef},
|
||||
primitives::{BlockEnv, CfgEnv, Env, ExecutionResult, Halt, TransactTo},
|
||||
DatabaseCommit,
|
||||
};
|
||||
use tracing::trace;
|
||||
|
||||
@ -62,6 +66,95 @@ where
|
||||
ensure_success(res.result)
|
||||
}
|
||||
|
||||
/// Simulate arbitrary number of transactions at an arbitrary blockchain index, with the
|
||||
/// optionality of state overrides
|
||||
pub async fn call_many(
|
||||
&self,
|
||||
bundle: Bundle,
|
||||
state_context: Option<StateContext>,
|
||||
state_override: Option<StateOverride>,
|
||||
) -> EthResult<Vec<EthCallResponse>> {
|
||||
let Bundle { transactions, block_override } = bundle;
|
||||
if transactions.is_empty() {
|
||||
return Err(EthApiError::InvalidParams(String::from("transactions are empty.")))
|
||||
}
|
||||
|
||||
let StateContext { transaction_index, block_number } = state_context.unwrap_or_default();
|
||||
let transaction_index = transaction_index.unwrap_or_default();
|
||||
|
||||
let target_block = block_number.unwrap_or(BlockId::Number(BlockNumberOrTag::Latest));
|
||||
let ((cfg, block_env, _), block) =
|
||||
futures::try_join!(self.evm_env_at(target_block), self.block_by_id(target_block))?;
|
||||
|
||||
let block = block.ok_or_else(|| EthApiError::UnknownBlockNumber)?;
|
||||
let gas_limit = self.inner.gas_cap;
|
||||
|
||||
// we're essentially replaying the transactions in the block here, hence we need the state
|
||||
// that points to the beginning of the block, which is the state at the parent block
|
||||
let mut at = block.parent_hash;
|
||||
let mut replay_block_txs = true;
|
||||
|
||||
// but if all transactions are to be replayed, we can use the state at the block itself
|
||||
let num_txs = transaction_index.index().unwrap_or(block.body.len());
|
||||
if num_txs == block.body.len() {
|
||||
at = block.hash;
|
||||
replay_block_txs = false;
|
||||
}
|
||||
|
||||
self.spawn_with_state_at_block(at.into(), move |state| {
|
||||
let mut results = Vec::with_capacity(transactions.len());
|
||||
let mut db = SubState::new(State::new(state));
|
||||
|
||||
if replay_block_txs {
|
||||
// only need to replay the transactions in the block if not all transactions are
|
||||
// to be replayed
|
||||
let transactions = block.body.into_iter().take(num_txs);
|
||||
|
||||
// Execute all transactions until index
|
||||
for tx in transactions {
|
||||
let tx = tx.into_ecrecovered().ok_or(BlockError::InvalidSignature)?;
|
||||
let tx = tx_env_with_recovered(&tx);
|
||||
let env = Env { cfg: cfg.clone(), block: block_env.clone(), tx };
|
||||
let (res, _) = transact(&mut db, env)?;
|
||||
db.commit(res.state);
|
||||
}
|
||||
}
|
||||
|
||||
let overrides = EvmOverrides::new(state_override.clone(), block_override.map(Box::new));
|
||||
|
||||
let mut transactions = transactions.into_iter().peekable();
|
||||
while let Some(tx) = transactions.next() {
|
||||
let env = prepare_call_env(
|
||||
cfg.clone(),
|
||||
block_env.clone(),
|
||||
tx,
|
||||
gas_limit,
|
||||
&mut db,
|
||||
overrides.clone(),
|
||||
)?;
|
||||
let (res, _) = transact(&mut db, env)?;
|
||||
|
||||
match ensure_success(res.result) {
|
||||
Ok(output) => {
|
||||
results.push(EthCallResponse { output: Some(output), error: None });
|
||||
}
|
||||
Err(err) => {
|
||||
results
|
||||
.push(EthCallResponse { output: None, error: Some(err.to_string()) });
|
||||
}
|
||||
}
|
||||
|
||||
if transactions.peek().is_some() {
|
||||
// need to apply the state changes of this call before executing the next call
|
||||
db.commit(res.state);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(results)
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Estimates the gas usage of the `request` with the state.
|
||||
///
|
||||
/// This will execute the [CallRequest] and find the best gas limit via binary search
|
||||
|
||||
@ -21,8 +21,9 @@ use reth_provider::{
|
||||
};
|
||||
use reth_rpc_api::EthApiServer;
|
||||
use reth_rpc_types::{
|
||||
state::StateOverride, BlockOverrides, CallRequest, EIP1186AccountProofResponse, FeeHistory,
|
||||
Index, RichBlock, SyncStatus, TransactionReceipt, TransactionRequest, Work,
|
||||
state::StateOverride, BlockOverrides, Bundle, CallRequest, EIP1186AccountProofResponse,
|
||||
EthCallResponse, FeeHistory, Index, RichBlock, StateContext, SyncStatus, TransactionReceipt,
|
||||
TransactionRequest, Work,
|
||||
};
|
||||
use reth_transaction_pool::TransactionPool;
|
||||
use serde_json::Value;
|
||||
@ -245,6 +246,17 @@ where
|
||||
.await?)
|
||||
}
|
||||
|
||||
/// Handler for: `eth_callMany`
|
||||
async fn call_many(
|
||||
&self,
|
||||
bundle: Bundle,
|
||||
state_context: Option<StateContext>,
|
||||
state_override: Option<StateOverride>,
|
||||
) -> Result<Vec<EthCallResponse>> {
|
||||
trace!(target: "rpc::eth", ?bundle, ?state_context, ?state_override, "Serving eth_callMany");
|
||||
Ok(EthApi::call_many(self, bundle, state_context, state_override).await?)
|
||||
}
|
||||
|
||||
/// Handler for: `eth_createAccessList`
|
||||
async fn create_access_list(
|
||||
&self,
|
||||
|
||||
Reference in New Issue
Block a user