feat: add ProviderExt trait (#1559)
This commit is contained in:
parent
71933f0d33
commit
3040edf2ad
|
@ -4,6 +4,7 @@ use std::{
|
||||||
convert::{TryFrom, TryInto},
|
convert::{TryFrom, TryInto},
|
||||||
fmt,
|
fmt,
|
||||||
str::FromStr,
|
str::FromStr,
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
use strum::EnumVariantNames;
|
use strum::EnumVariantNames;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
@ -60,6 +61,39 @@ pub enum Chain {
|
||||||
AuroraTestnet = 1313161555,
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
Some(Duration::from_millis(ms))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Display for Chain {
|
impl fmt::Display for Chain {
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let chain = match self {
|
let chain = match self {
|
||||||
|
|
|
@ -8,6 +8,7 @@ use futures_util::future::join_all;
|
||||||
pub use transports::*;
|
pub use transports::*;
|
||||||
|
|
||||||
mod provider;
|
mod provider;
|
||||||
|
pub use provider::{is_local_endpoint, FilterKind, Provider, ProviderError, ProviderExt};
|
||||||
|
|
||||||
// ENS support
|
// ENS support
|
||||||
pub mod ens;
|
pub mod ens;
|
||||||
|
@ -23,7 +24,9 @@ pub use log_query::LogQuery;
|
||||||
|
|
||||||
mod stream;
|
mod stream;
|
||||||
pub use futures_util::StreamExt;
|
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;
|
mod pubsub;
|
||||||
pub use pubsub::{PubsubClient, SubscriptionStream};
|
pub use pubsub::{PubsubClient, SubscriptionStream};
|
||||||
|
@ -38,8 +41,6 @@ use serde::{de::DeserializeOwned, Serialize};
|
||||||
use std::{error::Error, fmt::Debug, future::Future, pin::Pin};
|
use std::{error::Error, fmt::Debug, future::Future, pin::Pin};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
pub use provider::{FilterKind, Provider, ProviderError};
|
|
||||||
|
|
||||||
// feature-enabled support for dev-rpc methods
|
// feature-enabled support for dev-rpc methods
|
||||||
#[cfg(feature = "dev-rpc")]
|
#[cfg(feature = "dev-rpc")]
|
||||||
pub use provider::dev_rpc::DevRpcMiddleware;
|
pub use provider::dev_rpc::DevRpcMiddleware;
|
||||||
|
|
|
@ -2,7 +2,7 @@ use crate::{
|
||||||
call_raw::CallBuilder,
|
call_raw::CallBuilder,
|
||||||
ens, erc, maybe,
|
ens, erc, maybe,
|
||||||
pubsub::{PubsubClient, SubscriptionStream},
|
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,
|
FromErr, Http as HttpProvider, JsonRpcClient, JsonRpcClientWrapper, LogQuery, MockProvider,
|
||||||
PendingTransaction, QuorumProvider, RwClient, SyncingStatus,
|
PendingTransaction, QuorumProvider, RwClient, SyncingStatus,
|
||||||
};
|
};
|
||||||
|
@ -33,6 +33,7 @@ use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use url::{ParseError, Url};
|
use url::{ParseError, Url};
|
||||||
|
|
||||||
|
use ethers_core::types::Chain;
|
||||||
use futures_util::{lock::Mutex, try_join};
|
use futures_util::{lock::Mutex, try_join};
|
||||||
use std::{
|
use std::{
|
||||||
collections::VecDeque, convert::TryFrom, fmt::Debug, str::FromStr, sync::Arc, time::Duration,
|
collections::VecDeque, convert::TryFrom, fmt::Debug, str::FromStr, sync::Arc, time::Duration,
|
||||||
|
@ -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
|
/// A middleware supporting development-specific JSON RPC methods
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
|
|
|
@ -28,6 +28,9 @@ pub fn interval(duration: Duration) -> impl Stream<Item = ()> + Send + Unpin {
|
||||||
/// The default polling interval for filters and pending transactions
|
/// The default polling interval for filters and pending transactions
|
||||||
pub const DEFAULT_POLL_INTERVAL: Duration = Duration::from_millis(7000);
|
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> {
|
enum FilterWatcherState<'a, R> {
|
||||||
WaitForInterval,
|
WaitForInterval,
|
||||||
GetFilterChanges(PinBoxFut<'a, Vec<R>>),
|
GetFilterChanges(PinBoxFut<'a, Vec<R>>),
|
||||||
|
|
Loading…
Reference in New Issue