Skip to content
This repository has been archived by the owner on Oct 19, 2024. It is now read-only.

feat: add ProviderExt trait #1559

Merged
merged 1 commit into from
Aug 3, 2022
Merged
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
34 changes: 34 additions & 0 deletions ethers-core/src/types/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::{
convert::{TryFrom, TryInto},
fmt,
str::FromStr,
time::Duration,
};
use strum::EnumVariantNames;
use thiserror::Error;
Expand Down Expand Up @@ -60,6 +61,39 @@ pub enum Chain {
AuroraTestnet = 1313161555,
}

// === impl Chain ===

impl Chain {
/// The blocktime varies from chain to chain
///
/// It can be beneficial to know the average blocktime to adjust the polling of an Http provider
/// for example.
///
/// **Note:** this will not return the accurate average depending on the time but is rather a
/// sensible default derived from blocktime charts like <https://etherscan.com/chart/blocktime>
/// <https://polygonscan.com/chart/blocktime>
pub fn average_blocktime_hint(&self) -> Option<Duration> {
let ms = match self {
Chain::Arbitrum | Chain::ArbitrumTestnet => 1_300,
Chain::Mainnet | Chain::Optimism => 13_000,
Chain::Polygon | Chain::PolygonMumbai => 2_100,
Chain::Moonbeam | Chain::Moonriver => 12_500,
Chain::BinanceSmartChain | Chain::BinanceSmartChainTestnet => 3_000,
Chain::Avalanche | Chain::AvalancheFuji => 2_000,
Chain::Fantom | Chain::FantomTestnet => 1_200,
Chain::Cronos | Chain::CronosTestnet => 5_700,
Chain::Evmos | Chain::EvmosTestnet => 1_900,
Chain::Aurora | Chain::AuroraTestnet => 1_100,
Chain::Oasis => 5_500,
Chain::Emerald => 6_000,
Chain::Dev | Chain::AnvilHardhat => 200,
_ => return None,
Copy link
Contributor

Choose a reason for hiding this comment

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

I think it is better to explicitly catch other variants, otherwise it will be very easy for others contributor that are adding new networks to miss this match!

Copy link
Owner

Choose a reason for hiding this comment

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

What should we do? panic?

Copy link
Contributor

Choose a reason for hiding this comment

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

sorry I wasn't clear. Still return None is fine, but instead of using _ as a pattern, write down all other enum variants.

This way if a new network is added, compiler will help developer noticing that this is something to fill in.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

reasonable, do you want to add this?

Copy link
Contributor

Choose a reason for hiding this comment

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

};

Some(Duration::from_millis(ms))
}
}

impl fmt::Display for Chain {
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
let chain = match self {
Expand Down
7 changes: 4 additions & 3 deletions ethers-providers/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use futures_util::future::join_all;
pub use transports::*;

mod provider;
pub use provider::{is_local_endpoint, FilterKind, Provider, ProviderError, ProviderExt};

// ENS support
pub mod ens;
Expand All @@ -23,7 +24,9 @@ pub use log_query::LogQuery;

mod stream;
pub use futures_util::StreamExt;
pub use stream::{interval, FilterWatcher, TransactionStream, DEFAULT_POLL_INTERVAL};
pub use stream::{
interval, FilterWatcher, TransactionStream, DEFAULT_LOCAL_POLL_INTERVAL, DEFAULT_POLL_INTERVAL,
};

mod pubsub;
pub use pubsub::{PubsubClient, SubscriptionStream};
Expand All @@ -38,8 +41,6 @@ use serde::{de::DeserializeOwned, Serialize};
use std::{error::Error, fmt::Debug, future::Future, pin::Pin};
use url::Url;

pub use provider::{FilterKind, Provider, ProviderError};

// feature-enabled support for dev-rpc methods
#[cfg(feature = "dev-rpc")]
pub use provider::dev_rpc::DevRpcMiddleware;
Expand Down
117 changes: 116 additions & 1 deletion ethers-providers/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use crate::{
call_raw::CallBuilder,
ens, erc, maybe,
pubsub::{PubsubClient, SubscriptionStream},
stream::{FilterWatcher, DEFAULT_POLL_INTERVAL},
stream::{FilterWatcher, DEFAULT_LOCAL_POLL_INTERVAL, DEFAULT_POLL_INTERVAL},
FromErr, Http as HttpProvider, JsonRpcClient, JsonRpcClientWrapper, LogQuery, MockProvider,
PendingTransaction, QuorumProvider, RwClient, SyncingStatus,
};
Expand Down Expand Up @@ -33,6 +33,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
use thiserror::Error;
use url::{ParseError, Url};

use ethers_core::types::Chain;
use futures_util::{lock::Mutex, try_join};
use std::{
collections::VecDeque, convert::TryFrom, fmt::Debug, str::FromStr, sync::Arc, time::Duration,
Expand Down Expand Up @@ -1453,6 +1454,120 @@ impl Provider<RetryClient<HttpProvider>> {
}
}

mod sealed {
use crate::{Http, Provider};
/// private trait to ensure extension trait is not implement outside of this crate
pub trait Sealed {}
impl Sealed for Provider<Http> {}
}

/// Extension trait for `Provider`
///
/// **Note**: this is currently sealed until <https://github.com/gakonst/ethers-rs/pull/1267> is finalized
///
/// # Example
///
/// Automatically configure poll interval via `eth_getChainId`
///
/// Note that this will send an RPC to retrieve the chain id.
///
/// ```
/// # use ethers_providers::{Http, Provider, ProviderExt};
/// # async fn t() {
/// let http_provider = Provider::<Http>::connect("https://eth-mainnet.alchemyapi.io/v2/API_KEY").await;
/// # }
/// ```
///
/// This is essentially short for
///
/// ```
/// use std::convert::TryFrom;
/// use ethers_core::types::Chain;
/// use ethers_providers::{Http, Provider, ProviderExt};
/// let http_provider = Provider::<Http>::try_from("https://eth-mainnet.alchemyapi.io/v2/API_KEY").unwrap().set_chain(Chain::Mainnet);
/// ```
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait ProviderExt: sealed::Sealed {
/// The error type that can occur when creating a provider
type Error: Debug;

/// Creates a new instance connected to the given `url`, exit on error
async fn connect(url: &str) -> Self
where
Self: Sized,
{
Self::try_connect(url).await.unwrap()
}

/// Try to create a new `Provider`
async fn try_connect(url: &str) -> Result<Self, Self::Error>
where
Self: Sized;

/// Customize `Provider` settings for chain.
///
/// E.g. [`Chain::average_blocktime_hint()`] returns the average block time which can be used to
/// tune the polling interval.
///
/// Returns the customized `Provider`
fn for_chain(mut self, chain: impl Into<Chain>) -> Self
where
Self: Sized,
{
self.set_chain(chain);
self
}

/// Customized `Provider` settings for chain
fn set_chain(&mut self, chain: impl Into<Chain>) -> &mut Self;
}

#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl ProviderExt for Provider<HttpProvider> {
type Error = ParseError;

async fn try_connect(url: &str) -> Result<Self, Self::Error>
where
Self: Sized,
{
let mut provider = Provider::try_from(url)?;
if is_local_endpoint(url) {
provider.set_interval(DEFAULT_LOCAL_POLL_INTERVAL);
} else if let Some(chain) =
provider.get_chainid().await.ok().and_then(|id| Chain::try_from(id).ok())
{
provider.set_chain(chain);
}

Ok(provider)
}

fn set_chain(&mut self, chain: impl Into<Chain>) -> &mut Self {
let chain = chain.into();
if let Some(blocktime) = chain.average_blocktime_hint() {
// use half of the block time
self.set_interval(blocktime / 2);
}
self
}
}

/// Returns true if the endpoint is local
///
/// # Example
///
/// ```
/// use ethers_providers::is_local_endpoint;
/// assert!(is_local_endpoint("http://localhost:8545"));
/// assert!(is_local_endpoint("http://127.0.0.1:8545"));
/// ```
#[inline]
pub fn is_local_endpoint(url: &str) -> bool {
url.contains("127.0.0.1") || url.contains("localhost")
}

/// A middleware supporting development-specific JSON RPC methods
///
/// # Example
Expand Down
3 changes: 3 additions & 0 deletions ethers-providers/src/stream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ pub fn interval(duration: Duration) -> impl Stream<Item = ()> + Send + Unpin {
/// The default polling interval for filters and pending transactions
pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_millis(7000);

/// The polling interval to use for local endpoints, See [`crate::is_local_endpoint()`]
pub const DEFAULT_LOCAL_POLL_INTERVAL: Duration = Duration::from_millis(100);

enum FilterWatcherState<'a, R> {
WaitForInterval,
GetFilterChanges(PinBoxFut<'a, Vec<R>>),
Expand Down