-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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
Changes from 14 commits
dabb7f9
6fd5f41
5b9576d
2b00e72
0254a2a
82a9260
4fa89ca
85ba521
5c8adf0
40e2910
7dc3db7
a4cefc5
6e5f3d7
3e09c8a
f566782
b7fd137
9367198
aab37ee
d238126
3b8deba
d1b7f51
f9d2f99
80d27e8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -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::{ | ||||||||||||||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
&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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
hey ty for your review, you means i should directly call the function call_many inside this function ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, at least for now There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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?; | ||||||||||||||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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; | ||||||
|
@@ -51,6 +51,17 @@ where | |||||
EthApiSpec::protocol_version(self).await.to_rpc_result() | ||||||
} | ||||||
|
||||||
/// Handler for ! `eth_MultiCallV1` | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
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"); | ||||||
|
There was a problem hiding this comment.
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