From 6366bbc19e8dc164c1969e2f63e1091ed8e58091 Mon Sep 17 00:00:00 2001 From: Ravil Ayupov <35985944+RexCloud@users.noreply.github.com> Date: Tue, 21 May 2024 18:19:10 +0000 Subject: [PATCH 1/4] feat: set poll interval based on connected chain --- crates/provider/Cargo.toml | 1 + crates/provider/src/provider/trait.rs | 20 +++++++++++++++++--- crates/rpc-client/src/client.rs | 20 +++++++++++++++----- 3 files changed, 33 insertions(+), 8 deletions(-) diff --git a/crates/provider/Cargo.toml b/crates/provider/Cargo.toml index 0420a3006af..cc00bf0e855 100644 --- a/crates/provider/Cargo.toml +++ b/crates/provider/Cargo.toml @@ -28,6 +28,7 @@ alloy-pubsub = { workspace = true, optional = true } alloy-transport.workspace = true alloy-primitives.workspace = true +alloy-chains = "0.1.18" async-stream = "0.3" async-trait.workspace = true auto_impl.workspace = true diff --git a/crates/provider/src/provider/trait.rs b/crates/provider/src/provider/trait.rs index 5e7df26afdb..038f1a975f2 100644 --- a/crates/provider/src/provider/trait.rs +++ b/crates/provider/src/provider/trait.rs @@ -5,6 +5,7 @@ use crate::{ EthCall, PendingTransaction, PendingTransactionBuilder, PendingTransactionConfig, RootProvider, RpcWithBlock, SendableTx, }; +use alloy_chains::Chain; use alloy_eips::eip2718::Encodable2718; use alloy_json_rpc::{RpcError, RpcParam, RpcReturn}; use alloy_network::{Ethereum, Network}; @@ -537,9 +538,22 @@ pub trait Provider: self.client().request("web3_clientVersion", ()).await } - /// Gets the chain ID. - fn get_chain_id(&self) -> RpcCall { - self.client().request("eth_chainId", ()).map_resp(crate::utils::convert_u64) + /// Gets the chain ID. Sets the client's poll interval (if default) + /// to match the average block time for this chain. + async fn get_chain_id(&self) -> TransportResult { + self.client().request("eth_chainId", ()).map_resp(crate::utils::convert_u64).await.and_then( + |chain_id| { + let client = self.client(); + if !client.is_custom_poll_interval() && !client.is_local() { + let poll_interval = match Chain::from_id(chain_id).average_blocktime_hint() { + Some(avg_block_time) => avg_block_time, + None => client.poll_interval(), + }; + client.set_poll_interval(poll_interval.as_millis() as u64); + } + Ok(chain_id) + }, + ) } /// Gets the network ID. Same as `eth_chainId`. diff --git a/crates/rpc-client/src/client.rs b/crates/rpc-client/src/client.rs index 51297e12d4f..0fee35394a3 100644 --- a/crates/rpc-client/src/client.rs +++ b/crates/rpc-client/src/client.rs @@ -6,7 +6,7 @@ use std::{ borrow::Cow, ops::Deref, sync::{ - atomic::{AtomicU64, Ordering}, + atomic::{AtomicBool, AtomicU64, Ordering}, Arc, Weak, }, time::Duration, @@ -82,7 +82,7 @@ impl RpcClient { &self.0 } - /// Sets the poll interval for the client in milliseconds. + /// Sets custom poll interval for the client in milliseconds. /// /// Note: This will only set the poll interval for the client if it is the only reference to the /// inner client. If the reference is held by many, then it will not update the poll interval. @@ -163,6 +163,8 @@ pub struct RpcClientInner { pub(crate) id: AtomicU64, /// The poll interval for the client in milliseconds. pub(crate) poll_interval: AtomicU64, + /// `true` if the poll interval is custom. + pub(crate) is_custom_poll_interval: AtomicBool, } impl RpcClientInner { @@ -177,17 +179,24 @@ impl RpcClientInner { is_local, id: AtomicU64::new(0), poll_interval: if is_local { AtomicU64::new(250) } else { AtomicU64::new(7000) }, + is_custom_poll_interval: AtomicBool::new(false), } } - /// Returns the default poll interval (milliseconds) for the client. + /// Returns the poll interval (milliseconds) for the client. pub fn poll_interval(&self) -> Duration { Duration::from_millis(self.poll_interval.load(Ordering::Relaxed)) } - /// Set the poll interval for the client in milliseconds. + /// Set custom poll interval for the client in milliseconds. pub fn set_poll_interval(&self, poll_interval: u64) { self.poll_interval.store(poll_interval, Ordering::Relaxed); + self.is_custom_poll_interval.store(true, Ordering::Relaxed); + } + + /// Returns `true` if the poll interval is custom. + pub fn is_custom_poll_interval(&self) -> bool { + self.is_custom_poll_interval.load(Ordering::Relaxed) } /// Returns a reference to the underlying transport. @@ -287,6 +296,7 @@ impl RpcClientInner { is_local: self.is_local, id: self.id, poll_interval: self.poll_interval, + is_custom_poll_interval: self.is_custom_poll_interval, } } } @@ -346,7 +356,7 @@ mod tests { fn test_client_with_poll_interval() { let client = RpcClient::new_http(reqwest::Url::parse("http://localhost").unwrap()) .with_poll_interval(5000); - // let client = client; assert_eq!(client.poll_interval(), Duration::from_millis(5000)); + assert!(client.is_custom_poll_interval()); } } From 3ab4277ff562d8a62ed24f474c2f56f79aec9b89 Mon Sep 17 00:00:00 2001 From: RexCloud Date: Wed, 22 May 2024 16:02:49 +0500 Subject: [PATCH 2/4] Revert changes --- crates/provider/Cargo.toml | 1 - crates/provider/src/provider/trait.rs | 20 +++----------------- crates/rpc-client/src/client.rs | 20 +++++--------------- 3 files changed, 8 insertions(+), 33 deletions(-) diff --git a/crates/provider/Cargo.toml b/crates/provider/Cargo.toml index cc00bf0e855..0420a3006af 100644 --- a/crates/provider/Cargo.toml +++ b/crates/provider/Cargo.toml @@ -28,7 +28,6 @@ alloy-pubsub = { workspace = true, optional = true } alloy-transport.workspace = true alloy-primitives.workspace = true -alloy-chains = "0.1.18" async-stream = "0.3" async-trait.workspace = true auto_impl.workspace = true diff --git a/crates/provider/src/provider/trait.rs b/crates/provider/src/provider/trait.rs index 038f1a975f2..5e7df26afdb 100644 --- a/crates/provider/src/provider/trait.rs +++ b/crates/provider/src/provider/trait.rs @@ -5,7 +5,6 @@ use crate::{ EthCall, PendingTransaction, PendingTransactionBuilder, PendingTransactionConfig, RootProvider, RpcWithBlock, SendableTx, }; -use alloy_chains::Chain; use alloy_eips::eip2718::Encodable2718; use alloy_json_rpc::{RpcError, RpcParam, RpcReturn}; use alloy_network::{Ethereum, Network}; @@ -538,22 +537,9 @@ pub trait Provider: self.client().request("web3_clientVersion", ()).await } - /// Gets the chain ID. Sets the client's poll interval (if default) - /// to match the average block time for this chain. - async fn get_chain_id(&self) -> TransportResult { - self.client().request("eth_chainId", ()).map_resp(crate::utils::convert_u64).await.and_then( - |chain_id| { - let client = self.client(); - if !client.is_custom_poll_interval() && !client.is_local() { - let poll_interval = match Chain::from_id(chain_id).average_blocktime_hint() { - Some(avg_block_time) => avg_block_time, - None => client.poll_interval(), - }; - client.set_poll_interval(poll_interval.as_millis() as u64); - } - Ok(chain_id) - }, - ) + /// Gets the chain ID. + fn get_chain_id(&self) -> RpcCall { + self.client().request("eth_chainId", ()).map_resp(crate::utils::convert_u64) } /// Gets the network ID. Same as `eth_chainId`. diff --git a/crates/rpc-client/src/client.rs b/crates/rpc-client/src/client.rs index 0fee35394a3..51297e12d4f 100644 --- a/crates/rpc-client/src/client.rs +++ b/crates/rpc-client/src/client.rs @@ -6,7 +6,7 @@ use std::{ borrow::Cow, ops::Deref, sync::{ - atomic::{AtomicBool, AtomicU64, Ordering}, + atomic::{AtomicU64, Ordering}, Arc, Weak, }, time::Duration, @@ -82,7 +82,7 @@ impl RpcClient { &self.0 } - /// Sets custom poll interval for the client in milliseconds. + /// Sets the poll interval for the client in milliseconds. /// /// Note: This will only set the poll interval for the client if it is the only reference to the /// inner client. If the reference is held by many, then it will not update the poll interval. @@ -163,8 +163,6 @@ pub struct RpcClientInner { pub(crate) id: AtomicU64, /// The poll interval for the client in milliseconds. pub(crate) poll_interval: AtomicU64, - /// `true` if the poll interval is custom. - pub(crate) is_custom_poll_interval: AtomicBool, } impl RpcClientInner { @@ -179,24 +177,17 @@ impl RpcClientInner { is_local, id: AtomicU64::new(0), poll_interval: if is_local { AtomicU64::new(250) } else { AtomicU64::new(7000) }, - is_custom_poll_interval: AtomicBool::new(false), } } - /// Returns the poll interval (milliseconds) for the client. + /// Returns the default poll interval (milliseconds) for the client. pub fn poll_interval(&self) -> Duration { Duration::from_millis(self.poll_interval.load(Ordering::Relaxed)) } - /// Set custom poll interval for the client in milliseconds. + /// Set the poll interval for the client in milliseconds. pub fn set_poll_interval(&self, poll_interval: u64) { self.poll_interval.store(poll_interval, Ordering::Relaxed); - self.is_custom_poll_interval.store(true, Ordering::Relaxed); - } - - /// Returns `true` if the poll interval is custom. - pub fn is_custom_poll_interval(&self) -> bool { - self.is_custom_poll_interval.load(Ordering::Relaxed) } /// Returns a reference to the underlying transport. @@ -296,7 +287,6 @@ impl RpcClientInner { is_local: self.is_local, id: self.id, poll_interval: self.poll_interval, - is_custom_poll_interval: self.is_custom_poll_interval, } } } @@ -356,7 +346,7 @@ mod tests { fn test_client_with_poll_interval() { let client = RpcClient::new_http(reqwest::Url::parse("http://localhost").unwrap()) .with_poll_interval(5000); + // let client = client; assert_eq!(client.poll_interval(), Duration::from_millis(5000)); - assert!(client.is_custom_poll_interval()); } } From 47077b06cbbdb39d2a38849c3d1a2059a7064dfa Mon Sep 17 00:00:00 2001 From: RexCloud Date: Wed, 22 May 2024 20:03:39 +0500 Subject: [PATCH 3/4] feat: `ChainLayer` to set poll interval based on chain --- Cargo.toml | 2 ++ crates/provider/Cargo.toml | 1 + crates/provider/src/builder.rs | 13 +++++++++ crates/provider/src/layers/chain.rs | 44 +++++++++++++++++++++++++++++ crates/provider/src/layers/mod.rs | 7 +++-- crates/rpc-client/src/client.rs | 15 +++++----- 6 files changed, 73 insertions(+), 9 deletions(-) create mode 100644 crates/provider/src/layers/chain.rs diff --git a/Cargo.toml b/Cargo.toml index c232da5190a..26726c7ae09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,8 @@ alloy-sol-types = { version = "0.7.2", default-features = false } alloy-rlp = { version = "0.3", default-features = false } +alloy-chains = { version = "0.1.18", default-features = false } + # ethereum ethereum_ssz_derive = "0.5" ethereum_ssz = "0.5" diff --git a/crates/provider/Cargo.toml b/crates/provider/Cargo.toml index 0420a3006af..a360bdf97a8 100644 --- a/crates/provider/Cargo.toml +++ b/crates/provider/Cargo.toml @@ -28,6 +28,7 @@ alloy-pubsub = { workspace = true, optional = true } alloy-transport.workspace = true alloy-primitives.workspace = true +alloy-chains.workspace = true async-stream = "0.3" async-trait.workspace = true auto_impl.workspace = true diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index 4d30a19d33a..b6f530d26bd 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -5,6 +5,7 @@ use crate::{ provider::SendableTx, Provider, RootProvider, }; +use alloy_chains::NamedChain; use alloy_network::{Ethereum, Network}; use alloy_rpc_client::{BuiltInConnectionString, ClientBuilder, RpcClient}; use alloy_transport::{BoxTransport, Transport, TransportError, TransportResult}; @@ -216,6 +217,18 @@ impl ProviderBuilder { ProviderBuilder { layer: self.layer, filler: self.filler, network: PhantomData } } + /// Add a chain layer to the stack being built. The layer will set + /// the client's poll interval based on the average block time for this chain. + /// + /// Does nothing to the client with a local transport. + pub fn with_chain( + self, + chain: NamedChain, + ) -> ProviderBuilder, F, N> { + let chain_layer = crate::layers::ChainLayer::from(chain); + self.layer(chain_layer) + } + /// Finish the layer stack by providing a root [`Provider`], outputting /// the final [`Provider`] type with all stack components. pub fn on_provider(self, provider: P) -> F::Provider diff --git a/crates/provider/src/layers/chain.rs b/crates/provider/src/layers/chain.rs new file mode 100644 index 00000000000..28cc6da6e5e --- /dev/null +++ b/crates/provider/src/layers/chain.rs @@ -0,0 +1,44 @@ +use alloy_chains::NamedChain; +use alloy_network::Ethereum; +use alloy_transport::Transport; +use std::time::Duration; + +use crate::{Provider, ProviderLayer}; + +/// A layer that wraps a [`NamedChain`]. The layer will be used to set +/// the client's poll interval based on the average block time for this chain. +/// +/// Does nothing to the client with a local transport. +#[derive(Debug, Clone, Copy)] +pub struct ChainLayer(NamedChain); + +impl ChainLayer { + /// Get the chain's average blocktime, if applicable. + pub fn average_blocktime_hint(&self) -> Option { + self.0.average_blocktime_hint() + } +} + +impl From for ChainLayer { + fn from(chain: NamedChain) -> Self { + Self(chain) + } +} + +impl ProviderLayer for ChainLayer +where + P: Provider, + T: Transport + Clone, +{ + type Provider = P; + + fn layer(&self, inner: P) -> Self::Provider { + if !inner.client().is_local() { + if let Some(avg_block_time) = self.average_blocktime_hint() { + let poll_interval = avg_block_time.mul_f32(0.6); + inner.client().set_poll_interval(poll_interval); + } + } + inner + } +} diff --git a/crates/provider/src/layers/mod.rs b/crates/provider/src/layers/mod.rs index 2ac0795941e..1f93cc06516 100644 --- a/crates/provider/src/layers/mod.rs +++ b/crates/provider/src/layers/mod.rs @@ -1,8 +1,11 @@ //! Useful layer implementations for the provider. Currently this -//! module contains the `AnvilLayer` and `AnvilProvider` types, when the anvil -//! feature is enabled. +//! module contains the `AnvilLayer`, `AnvilProvider` and `ChainLayer` +//! types. #[cfg(any(test, feature = "anvil"))] mod anvil; #[cfg(any(test, feature = "anvil"))] pub use anvil::{AnvilLayer, AnvilProvider}; + +mod chain; +pub use chain::ChainLayer; diff --git a/crates/rpc-client/src/client.rs b/crates/rpc-client/src/client.rs index 51297e12d4f..e62d6ba29fd 100644 --- a/crates/rpc-client/src/client.rs +++ b/crates/rpc-client/src/client.rs @@ -86,7 +86,7 @@ impl RpcClient { /// /// Note: This will only set the poll interval for the client if it is the only reference to the /// inner client. If the reference is held by many, then it will not update the poll interval. - pub fn with_poll_interval(self, poll_interval: u64) -> Self { + pub fn with_poll_interval(self, poll_interval: Duration) -> Self { self.inner().set_poll_interval(poll_interval); self } @@ -185,9 +185,10 @@ impl RpcClientInner { Duration::from_millis(self.poll_interval.load(Ordering::Relaxed)) } - /// Set the poll interval for the client in milliseconds. - pub fn set_poll_interval(&self, poll_interval: u64) { - self.poll_interval.store(poll_interval, Ordering::Relaxed); + /// Set the poll interval for the client in milliseconds. Default: + /// 7s for remote and 250ms for local transports. + pub fn set_poll_interval(&self, poll_interval: Duration) { + self.poll_interval.store(poll_interval.as_millis() as u64, Ordering::Relaxed); } /// Returns a reference to the underlying transport. @@ -344,9 +345,9 @@ mod tests { #[test] fn test_client_with_poll_interval() { + let poll_interval = Duration::from_millis(5_000); let client = RpcClient::new_http(reqwest::Url::parse("http://localhost").unwrap()) - .with_poll_interval(5000); - // let client = client; - assert_eq!(client.poll_interval(), Duration::from_millis(5000)); + .with_poll_interval(poll_interval); + assert_eq!(client.poll_interval(), poll_interval); } } From 803b93c45eff8ae3db31d177712ae0609822f9b3 Mon Sep 17 00:00:00 2001 From: RexCloud Date: Wed, 22 May 2024 21:06:45 +0500 Subject: [PATCH 4/4] use const fn --- crates/provider/src/layers/chain.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/provider/src/layers/chain.rs b/crates/provider/src/layers/chain.rs index 28cc6da6e5e..4f6e854068f 100644 --- a/crates/provider/src/layers/chain.rs +++ b/crates/provider/src/layers/chain.rs @@ -14,7 +14,7 @@ pub struct ChainLayer(NamedChain); impl ChainLayer { /// Get the chain's average blocktime, if applicable. - pub fn average_blocktime_hint(&self) -> Option { + pub const fn average_blocktime_hint(&self) -> Option { self.0.average_blocktime_hint() } }