Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rpc): add eth_multicallV1 #5596

Closed
Closed
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions crates/rpc/rpc-api/src/eth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ use reth_primitives::{
};
use reth_rpc_types::{
state::StateOverride, AccessListWithGasUsed, BlockOverrides, Bundle, CallRequest,
EIP1186AccountProofResponse, EthCallResponse, FeeHistory, Index, RichBlock, StateContext,
SyncStatus, Transaction, TransactionReceipt, TransactionRequest, Work,
EIP1186AccountProofResponse, EthCallResponse, FeeHistory, Index, MulticallBundle, RichBlock,
StateContext, SyncStatus, Transaction, TransactionReceipt, TransactionRequest, Work,
};

/// Eth rpc interface: <https://ethereum.github.io/execution-apis/api-documentation/>
Expand All @@ -18,6 +18,15 @@ pub trait EthApi {
#[method(name = "protocolVersion")]
async fn protocol_version(&self) -> RpcResult<U64>;

/// Executes complex RPC calls to Ethereum nodes
#[method(name = "ethMulticallV1")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jsonrpc method names are case sensitive and eth prefix is already specified on the trait

Suggested change
#[method(name = "ethMulticallV1")]
#[method(name = "multicallV1")]

async fn eth_multicall_v1(
&self,
multicall: MulticallBundle,
state_context: Option<StateContext>,
state_override: Option<StateOverride>,
) -> RpcResult<Vec<EthCallResponse>>;

/// Returns an object with data about the sync status or false.
#[method(name = "syncing")]
fn syncing(&self) -> RpcResult<SyncStatus>;
Expand All @@ -42,7 +51,7 @@ pub trait EthApi {
#[method(name = "getBlockByHash")]
async fn block_by_hash(&self, hash: B256, full: bool) -> RpcResult<Option<RichBlock>>;

/// Returns information about a block by number.
/// Returns information about a block by number
#[method(name = "getBlockByNumber")]
async fn block_by_number(
&self,
Expand Down
9 changes: 9 additions & 0 deletions crates/rpc/rpc-types/src/eth/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ pub struct Bundle {
/// Block overrides to apply
pub block_override: Option<BlockOverrides>,
}
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
#[serde(default, rename_all = "camelCase")]
/// Prototype eth_multicallV1 RPC API endpoint
pub struct MulticallBundle {
/// All transactions to execute
pub transactions: Vec<CallRequest>,
/// Block overrides to apply
pub block_override: Option<BlockOverrides>,
}

/// State context for callMany
#[derive(Debug, Clone, Default, Eq, PartialEq, Serialize, Deserialize)]
Expand Down
4 changes: 3 additions & 1 deletion crates/rpc/rpc-types/src/eth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ mod work;

pub use account::*;
pub use block::*;
pub use call::{Bundle, CallInput, CallInputError, CallRequest, EthCallResponse, StateContext};
pub use call::{
Bundle, CallInput, CallInputError, CallRequest, EthCallResponse, MulticallBundle, StateContext,
};
pub use engine::{ExecutionPayload, ExecutionPayloadV1, ExecutionPayloadV2, PayloadError};
pub use fee::{FeeHistory, TxGasAndReward};
pub use filter::*;
Expand Down
89 changes: 88 additions & 1 deletion crates/rpc/rpc/src/eth/api/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use reth_provider::{
use reth_revm::{access_list::AccessListInspector, database::StateProviderDatabase};
use reth_rpc_types::{
state::StateOverride, AccessListWithGasUsed, BlockError, Bundle, CallRequest, EthCallResponse,
StateContext,
MulticallBundle, StateContext,
};
use reth_transaction_pool::TransactionPool;
use revm::{
Expand All @@ -40,6 +40,93 @@ where
BlockReaderIdExt + ChainSpecProvider + StateProviderFactory + EvmEnvProvider + 'static,
Network: NetworkInfo + Send + Sync + 'static,
{
/// Executes complex RPC calls to Ethereum nodes

pub async fn eth_multicall_v1(
Comment on lines +43 to +45
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Executes complex RPC calls to Ethereum nodes
pub async fn eth_multicall_v1(
/// Executes complex RPC calls to Ethereum nodes.
/// Ref: <https://github.com/ethereum/execution-apis/pull/484>
pub async fn eth_multicall_v1(

&self,
multicall_bundle: MulticallBundle,
state_context: Option<StateContext>,
mut state_override: Option<StateOverride>,
) -> EthResult<Vec<EthCallResponse>> {
DoTheBestToGetTheBest marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is just the same as call_many?

so atm we don't need this function and can reuse call_many instead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is just the same as call_many?

so atm we don't need this function and can reuse call_many instead

hey ty for your review, you means i should directly call the function call_many inside this function ?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, at least for now

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, at least for now

ty so much, very greatful from your part all these review. should be fine now

if multicall_bundle.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(multicall_bundle.transactions.len());
let mut db = CacheDB::new(StateProviderDatabase::new(state));

if replay_block_txs {
let transactions = block.body.into_iter().take(num_txs);

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 block_override = multicall_bundle.block_override.map(Box::new);
let mut transactions = multicall_bundle.transactions.into_iter().peekable();
while let Some(tx) = transactions.next() {
// apply state overrides only once, before the first transaction
let state_overrides = state_override.take();
let overrides = EvmOverrides::new(state_overrides, block_override.clone());

let env = prepare_call_env(
cfg.clone(),
block_env.clone(),
tx,
gas_limit,
&mut db,
overrides,
)?;
let (res, _) = transact(&mut db, env)?;

match ensure_success(res.result) {
Ok(output) => {
results.push(EthCallResponse { value: Some(output), error: None });
}
Err(err) => {
results.push(EthCallResponse { value: 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
}
/// Estimate gas needed for execution of the `request` at the [BlockId].
pub async fn estimate_gas_at(&self, request: CallRequest, at: BlockId) -> EthResult<U256> {
let (cfg, block_env, at) = self.evm_env_at(at).await?;
Expand Down
15 changes: 13 additions & 2 deletions crates/rpc/rpc/src/eth/api/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use reth_provider::{
use reth_rpc_api::EthApiServer;
use reth_rpc_types::{
state::StateOverride, AccessListWithGasUsed, BlockOverrides, Bundle, CallRequest,
EIP1186AccountProofResponse, EthCallResponse, FeeHistory, Index, RichBlock, StateContext,
SyncStatus, TransactionReceipt, TransactionRequest, Work,
EIP1186AccountProofResponse, EthCallResponse, FeeHistory, Index, MulticallBundle, RichBlock,
StateContext, SyncStatus, TransactionReceipt, TransactionRequest, Work,
};
use reth_transaction_pool::TransactionPool;
use serde_json::Value;
Expand All @@ -51,6 +51,17 @@ where
EthApiSpec::protocol_version(self).await.to_rpc_result()
}

/// Handler for ! `eth_MultiCallV1`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Handler for ! `eth_MultiCallV1`
/// Handler for ! `eth_multicallV1`

async fn eth_multicall_v1(
&self,
multi_call: MulticallBundle,
state_context: Option<StateContext>,
state_override: Option<StateOverride>,
) -> Result<Vec<EthCallResponse>> {
trace!(target: "rpc::eth", "Serving eth_multicall_v1 request");
Ok(EthApi::eth_multicall_v1(self, multi_call, state_context, state_override).await?)
}

/// Handler for: `eth_syncing`
fn syncing(&self) -> Result<SyncStatus> {
trace!(target: "rpc::eth", "Serving eth_syncing");
Expand Down
Loading