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

support for --relaxed-rpc in anvil #5490

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions crates/anvil/src/cmd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ impl NodeArgs {
.fork_retry_backoff(self.evm_opts.fork_retry_backoff.map(Duration::from_millis))
.fork_compute_units_per_second(compute_units_per_second)
.with_eth_rpc_url(self.evm_opts.fork_url.map(|fork| fork.url))
.with_relaxed_rpc(self.evm_opts.relaxed_rpc)
.with_base_fee(self.evm_opts.block_base_fee_per_gas)
.with_storage_caching(self.evm_opts.no_storage_caching)
.with_server_config(self.server_config)
Expand Down Expand Up @@ -411,6 +412,13 @@ pub struct AnvilEvmArgs {
#[clap(long, requires = "fork_url", help_heading = "Fork config")]
pub no_storage_caching: bool,

/// Specify whether to be strict in json rpc handling of the upstream provider.
/// For now this amounts to whether the json response dict can contain a "method"
///
/// You still must pass both `--fork-url` and `--relaxed-rpc`
#[clap(long, requires = "fork_url", help_heading = "Fork config")]
pub relaxed_rpc: bool,

/// The block gas limit.
#[clap(long, alias = "block-gas-limit", help_heading = "Environment config")]
pub gas_limit: Option<u64>,
Expand Down Expand Up @@ -578,6 +586,9 @@ impl StateFile {
}
}

// TODO: Maybe ForkUrl should be used wherever pub fork_url:'s exist, and it should be called
// ForkConnSpec, to include various details of the transport (like relaxed_rpc)

/// Represents the input URL for a fork with an optional trailing block number:
/// `http://localhost:8545@1000000`
#[derive(Debug, Clone, PartialEq, Eq)]
Expand Down
13 changes: 13 additions & 0 deletions crates/anvil/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ pub struct NodeConfig {
pub fork_block_number: Option<u64>,
/// specifies chain id for cache to skip fetching from remote in offline-start mode
pub fork_chain_id: Option<U256>,
/// relaxed rpc handling
pub relaxed_rpc: bool,
/// The generator used to generate the dev accounts
pub account_generator: Option<AccountGenerator>,
/// whether to enable tracing
Expand Down Expand Up @@ -381,6 +383,7 @@ impl Default for NodeConfig {
silent: false,
eth_rpc_url: None,
fork_block_number: None,
relaxed_rpc: false,
account_generator: None,
base_fee: None,
enable_tracing: true,
Expand Down Expand Up @@ -698,6 +701,14 @@ impl NodeConfig {
self
}

/// Sets whether to process rpc in a relaxed way (for now this just means if method: appears in
/// the response, ignore it)
#[must_use]
pub fn with_relaxed_rpc(mut self, relaxed_rpc: bool) -> Self {
self.relaxed_rpc = relaxed_rpc;
self
}

/// Sets whether to enable tracing
#[must_use]
pub fn with_tracing(mut self, enable_tracing: bool) -> Self {
Expand Down Expand Up @@ -812,6 +823,7 @@ impl NodeConfig {
.timeout_retry(self.fork_request_retries)
.initial_backoff(self.fork_retry_backoff.as_millis() as u64)
.compute_units_per_second(self.compute_units_per_second)
.relaxed_rpc(self.relaxed_rpc)
.max_retry(10)
.initial_backoff(1000)
.build()
Expand Down Expand Up @@ -964,6 +976,7 @@ latest block number: {latest_block}"
let fork = ClientFork::new(
ClientForkConfig {
eth_rpc_url,
relaxed_rpc: self.relaxed_rpc,
block_number: fork_block_number,
block_hash,
provider,
Expand Down
5 changes: 5 additions & 0 deletions crates/anvil/src/eth/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ pub struct EthApi {
transaction_order: Arc<RwLock<TransactionOrder>>,
/// Whether we're listening for RPC calls
net_listening: bool,
/// Whether to relax rpc handling
relaxed_rpc: bool,
}

// === impl Eth RPC API ===
Expand All @@ -123,6 +125,7 @@ impl EthApi {
logger: LoggingManager,
filters: Filters,
transactions_order: TransactionOrder,
relaxed_rpc: bool,
) -> Self {
Self {
pool,
Expand All @@ -136,6 +139,7 @@ impl EthApi {
filters,
net_listening: true,
transaction_order: Arc::new(RwLock::new(transactions_order)),
relaxed_rpc,
}
}

Expand Down Expand Up @@ -1826,6 +1830,7 @@ impl EthApi {
ProviderBuilder::new(&url)
.max_retry(10)
.initial_backoff(1000)
.relaxed_rpc(self.relaxed_rpc)
.build()
.map_err(|_| {
ProviderError::CustomError(format!("Failed to parse invalid url {url}"))
Expand Down
2 changes: 2 additions & 0 deletions crates/anvil/src/eth/backend/fork.rs
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@ impl ClientFork {
#[derive(Debug, Clone)]
pub struct ClientForkConfig {
pub eth_rpc_url: String,
pub relaxed_rpc: bool,
pub block_number: u64,
pub block_hash: H256,
// TODO make provider agnostic
Expand Down Expand Up @@ -560,6 +561,7 @@ impl ClientForkConfig {
let interval = self.provider.get_interval();
self.provider = Arc::new(
ProviderBuilder::new(url.as_str())
.relaxed_rpc(self.relaxed_rpc)
.timeout(self.timeout)
.timeout_retry(self.retries)
.max_retry(10)
Expand Down
2 changes: 2 additions & 0 deletions crates/anvil/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ pub async fn spawn(mut config: NodeConfig) -> (EthApi, NodeHandle) {
no_mining,
transaction_order,
genesis,
relaxed_rpc,
..
} = config.clone();

Expand Down Expand Up @@ -160,6 +161,7 @@ pub async fn spawn(mut config: NodeConfig) -> (EthApi, NodeHandle) {
logger,
filters.clone(),
transaction_order,
relaxed_rpc,
);

// spawn the node service
Expand Down
11 changes: 4 additions & 7 deletions crates/cast/bin/cmd/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,11 @@ use foundry_cli::{
opts::{EthereumOpts, TransactionOpts},
utils::{self, handle_traces, parse_ether_value, TraceResult},
};
use foundry_common::RetryProvider;
use foundry_config::{find_project_root_path, Config};
use foundry_evm::{executor::opts::EvmOpts, trace::TracingExecutor};
use std::str::FromStr;

type Provider =
ethers::providers::Provider<ethers::providers::RetryClient<ethers::providers::Http>>;

/// CLI arguments for `cast call`.
#[derive(Debug, Parser)]
pub struct CallArgs {
Expand Down Expand Up @@ -129,8 +127,7 @@ impl CallArgs {
let chain = utils::get_chain(config.chain_id, &provider).await?;
let sender = eth.wallet.sender().await;

let mut builder: TxBuilder<'_, Provider> =
TxBuilder::new(&provider, sender, to, chain, tx.legacy).await?;
let mut builder = TxBuilder::new(&provider, sender, to, chain, tx.legacy).await?;

builder
.gas(tx.gas_limit)
Expand Down Expand Up @@ -218,7 +215,7 @@ impl CallArgs {

/// fills the builder from create arg
async fn fill_create(
builder: &mut TxBuilder<'_, Provider>,
builder: &mut TxBuilder<'_, RetryProvider>,
value: Option<U256>,
code: String,
sig: Option<String>,
Expand All @@ -240,7 +237,7 @@ async fn fill_create(

/// fills the builder from args
async fn fill_tx(
builder: &mut TxBuilder<'_, Provider>,
builder: &mut TxBuilder<'_, RetryProvider>,
value: Option<U256>,
sig: Option<String>,
args: Vec<String>,
Expand Down
4 changes: 3 additions & 1 deletion crates/cli/src/utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ pub fn get_provider(config: &Config) -> Result<foundry_common::RetryProvider> {
pub fn get_provider_builder(config: &Config) -> Result<foundry_common::ProviderBuilder> {
let url = config.get_rpc_url_or_localhost_http()?;
let chain = config.chain_id.unwrap_or_default();
let mut builder = foundry_common::ProviderBuilder::new(url.as_ref()).chain(chain);
let mut builder = foundry_common::ProviderBuilder::new(url.as_ref())
.chain(chain)
.relaxed_rpc(config.relaxed_rpc);

let jwt = config.get_rpc_jwt_secret()?;
if let Some(jwt) = jwt {
Expand Down
1 change: 1 addition & 0 deletions crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ repository.workspace = true
# foundry internal
foundry-config.workspace = true
foundry-macros.workspace = true
async-trait = "0.1"

# eth
ethers-core.workspace = true
Expand Down
57 changes: 52 additions & 5 deletions crates/common/src/provider.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,52 @@
//! Commonly used helpers to construct `Provider`s

use crate::{ALCHEMY_FREE_TIER_CUPS, REQUEST_TIMEOUT};
use async_trait::async_trait;
use core::fmt::Debug;
use ethers_core::types::{Chain, U256};
use ethers_middleware::gas_oracle::{GasCategory, GasOracle, Polygon};
use ethers_providers::{
is_local_endpoint, Authorization, Http, HttpRateLimitRetryPolicy, JwtAuth, JwtKey, Middleware,
Provider, RetryClient, RetryClientBuilder, DEFAULT_LOCAL_POLL_INTERVAL,
is_local_endpoint, Authorization, Http, HttpRateLimitRetryPolicy, JsonRpcClient, JwtAuth,
JwtKey, Middleware, Provider, RelaxedHttp, RetryClient, RetryClientBuilder,
DEFAULT_LOCAL_POLL_INTERVAL,
};
use eyre::WrapErr;
use reqwest::{header::HeaderValue, IntoUrl, Url};
use serde::{de::DeserializeOwned, Serialize};
use std::{borrow::Cow, time::Duration};

/// Wraps strict or loose rpc handling in the Http provider
#[derive(Debug)]
pub enum ProviderKinds {
///strict
Http(Http),
///loose
RelaxedHttp(RelaxedHttp),
}

#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl JsonRpcClient for ProviderKinds {
type Error = <Http as JsonRpcClient>::Error;

/// Sends a request with the provided JSON-RPC and parameters serialized as JSON
async fn request<T, R>(&self, method: &str, params: T) -> Result<R, Self::Error>
where
T: Debug + Serialize + Send + Sync,
R: DeserializeOwned + Send,
{
match self {
ProviderKinds::Http(i) => i.request(method, params).await,
ProviderKinds::RelaxedHttp(i) => i.request(method, params).await,
}
}
}
//.await.map_err(Self::Error::into)?,

/// Helper type alias for a retry provider
pub type RetryProvider = Provider<RetryClient<Http>>;
pub type RetryProvider = Provider<RetryClient<ProviderKinds>>;

// TODO: Should this be a ForkUrl/ForkConnInfo?

/// Helper type alias for a rpc url
pub type RpcUrl = String;
Expand Down Expand Up @@ -53,6 +87,7 @@ pub struct ProviderBuilder {
timeout: Duration,
/// available CUPS
compute_units_per_second: u64,
relaxed_rpc: bool,
/// JWT Secret
jwt: Option<String>,
}
Expand All @@ -78,6 +113,7 @@ impl ProviderBuilder {
timeout: REQUEST_TIMEOUT,
// alchemy max cpus <https://github.com/alchemyplatform/alchemy-docs/blob/master/documentation/compute-units.md#rate-limits-cups>
compute_units_per_second: ALCHEMY_FREE_TIER_CUPS,
relaxed_rpc: false,
jwt: None,
}
}
Expand Down Expand Up @@ -144,6 +180,12 @@ impl ProviderBuilder {
self.max_retry(100).initial_backoff(100)
}

/// sets relaxed_rpc mode
pub fn relaxed_rpc(mut self, relaxed_rpc: bool) -> Self {
self.relaxed_rpc = relaxed_rpc;
self
}

/// Sets the JWT secret
pub fn jwt(mut self, jwt: impl Into<String>) -> Self {
self.jwt = Some(jwt.into());
Expand Down Expand Up @@ -172,6 +214,7 @@ impl ProviderBuilder {
initial_backoff,
timeout,
compute_units_per_second,
relaxed_rpc,
jwt,
} = self;
let url = url?;
Expand Down Expand Up @@ -201,7 +244,11 @@ impl ProviderBuilder {
let client = client_builder.build()?;
let is_local = is_local_endpoint(url.as_str());

let provider = Http::new_with_client(url, client);
let json_client = if relaxed_rpc {
ProviderKinds::RelaxedHttp(RelaxedHttp::new_with_client(url, client))
} else {
ProviderKinds::Http(Http::new_with_client(url, client))
};

#[allow(clippy::box_default)]
let mut provider = Provider::new(
Expand All @@ -210,7 +257,7 @@ impl ProviderBuilder {
.rate_limit_retries(max_retry)
.timeout_retries(timeout_retry)
.compute_units_per_second(compute_units_per_second)
.build(provider, Box::new(HttpRateLimitRetryPolicy)),
.build(json_client, Box::new(HttpRateLimitRetryPolicy)),
);

if is_local {
Expand Down
3 changes: 3 additions & 0 deletions crates/config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,8 @@ pub struct Config {
pub model_checker: Option<ModelCheckerSettings>,
/// verbosity to use
pub verbosity: u8,
/// relaxed rpc handling
pub relaxed_rpc: bool, // TODO: propagate paying attention to this everywhere...
/// url of the rpc server that should be used for any rpc calls
pub eth_rpc_url: Option<String>,
/// JWT secret that should be used for any rpc calls
Expand Down Expand Up @@ -1794,6 +1796,7 @@ impl Default for Config {
block_prevrandao: Default::default(),
block_gas_limit: None,
memory_limit: 2u64.pow(25),
relaxed_rpc: false,
eth_rpc_url: None,
eth_rpc_jwt: None,
etherscan_api_key: None,
Expand Down
9 changes: 6 additions & 3 deletions crates/evm/src/executor/fork/multi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ use crate::{
};
use ethers::{
abi::{AbiDecode, AbiEncode, AbiError},
providers::{Http, Provider, RetryClient},
providers::{Provider, RetryClient},
types::{BlockId, BlockNumber},
};
use foundry_common::ProviderBuilder;
use foundry_common::{ProviderBuilder, ProviderKinds};
use foundry_config::Config;
use futures::{
channel::mpsc::{channel, Receiver, Sender},
Expand Down Expand Up @@ -168,7 +168,7 @@ impl MultiFork {
}
}

type Handler = BackendHandler<Arc<Provider<RetryClient<Http>>>>;
type Handler = BackendHandler<Arc<Provider<RetryClient<ProviderKinds>>>>;

type CreateFuture = Pin<Box<dyn Future<Output = eyre::Result<(CreatedFork, Handler)>> + Send>>;
type CreateSender = OneshotSender<eyre::Result<(ForkId, SharedBackend, Env)>>;
Expand Down Expand Up @@ -476,6 +476,9 @@ async fn create_fork(
retries: u32,
backoff: u64,
) -> eyre::Result<(CreatedFork, Handler)> {
// TODO: There seems to be a fundamental design flaw in MultiFork etc ... where are the
// configurations relevant to each Provider that may be created via create_fork() passed?

let provider = Arc::new(
ProviderBuilder::new(fork.url.as_str())
.max_retry(retries)
Expand Down
4 changes: 4 additions & 0 deletions crates/evm/src/executor/inspector/cheatcodes/fork.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,8 @@ fn eth_getlogs<DB: DatabaseExt>(data: &EVMData<DB>, inner: &EthGetLogsCall) -> R
return Err(fmt_err!("Topics array must be less than 4 elements"))
}

//TODO: Where do we get other relevant provider options from? Seems like there needs to be
// context information!
let provider = ProviderBuilder::new(url).build()?;
let mut filter = Filter::new()
.address(b160_to_h160(inner.2.into()))
Expand Down Expand Up @@ -337,6 +339,8 @@ fn eth_getlogs<DB: DatabaseExt>(data: &EVMData<DB>, inner: &EthGetLogsCall) -> R

fn rpc<DB: DatabaseExt>(data: &EVMData<DB>, inner: &RpcCall) -> Result {
let url = data.db.active_fork_url().ok_or(fmt_err!("No active fork url found"))?;
//TODO: Where do we get other relevant provider options from? Seems like there needs to be
// context information!
let provider = ProviderBuilder::new(url).build()?;

let method = inner.0.as_str();
Expand Down
Loading
Loading