From adaef96b0dbb8c4f183aba56768726bbd70621a1 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 29 Mar 2024 14:46:06 -0700 Subject: [PATCH 01/45] feature: TxFiller --- crates/provider/src/layers/join_fill.rs | 208 ++++++++++++++++++++++++ crates/provider/src/layers/mod.rs | 5 +- crates/provider/src/layers/nonce.rs | 129 ++++++++------- 3 files changed, 276 insertions(+), 66 deletions(-) create mode 100644 crates/provider/src/layers/join_fill.rs diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs new file mode 100644 index 00000000000..040c7b4016e --- /dev/null +++ b/crates/provider/src/layers/join_fill.rs @@ -0,0 +1,208 @@ +use crate::{PendingTransactionBuilder, Provider, ProviderLayer, RootProvider}; +use alloy_network::{Ethereum, Network}; +use alloy_transport::{Transport, TransportResult}; +use async_trait::async_trait; +use futures::try_join; +use std::{future::Future, marker::PhantomData}; + +/// A layer that can fill in a `TransactionRequest` with additional information. +pub trait TxFiller: Clone + Send + Sync { + /// The properties that this filler retrieves from the RPC. to fill in the + /// TransactionRequest. + type Fillable: Send + Sync + 'static; + + /// Joins this filler with another filler to compose multiple fillers. + fn join_with(self, other: T) -> JoinFill + where + T: TxFiller, + { + JoinFill::new(self, other) + } + + /// Returns true if the filler is ready to fill in the transaction request. + fn ready(&self, tx: &N::TransactionRequest) -> bool; + + /// Returns true if all fillable properties have been filled. + fn finished(&self, tx: &N::TransactionRequest) -> bool; + + /// Requests the fillable properties from the RPC. + fn request( + &self, + provider: &P, + tx: &N::TransactionRequest, + ) -> impl Future> + Send + where + P: Provider, + T: Transport + Clone; + + /// Fills in the transaction request with the fillable properties. + fn fill(&self, fillable: Self::Fillable, tx: &mut N::TransactionRequest); +} + +/// A layer that can fill in a `TransactionRequest` with additional information +/// by joining two [`FillTxLayer`]s. This struct is itself a [`FillTxLayer`], +/// and can be nested to compose any number of fill layers. +#[derive(Debug, Clone)] +pub struct JoinFill { + left: L, + right: R, + _network: PhantomData N>, +} + +impl JoinFill +where + L: TxFiller, + R: TxFiller, + N: Network, +{ + /// Creates a new `JoinFill` with the given layers. + pub fn new(left: L, right: R) -> Self { + Self { left, right, _network: PhantomData } + } + + /// Get a request for the left layer, if the left layer is ready. + fn left_req<'a: 'd, 'b: 'd, 'c: 'd, 'd, P, T>( + &'a self, + provider: &'b P, + tx: &'c N::TransactionRequest, + ) -> impl Future>> + Send + 'd + where + P: Provider, + T: Transport + Clone, + { + async { + if self.left.ready(tx) { + self.left.request(provider, tx).await.map(Some) + } else { + Ok(None) + } + } + } + + fn right_req<'a: 'd, 'b: 'd, 'c: 'd, 'd, P, T>( + &'a self, + provider: &'b P, + tx: &'c N::TransactionRequest, + ) -> impl Future>> + Send + 'd + where + P: Provider, + T: Transport + Clone, + { + async { + if self.right.ready(tx) { + self.right.request(provider, tx).await.map(Some) + } else { + Ok(None) + } + } + } +} + +impl TxFiller for JoinFill +where + L: TxFiller, + R: TxFiller, + N: Network, +{ + type Fillable = (Option, Option); + + fn ready(&self, tx: &N::TransactionRequest) -> bool { + self.left.ready(tx) || self.right.ready(tx) + } + + fn finished(&self, tx: &N::TransactionRequest) -> bool { + self.left.finished(tx) && self.right.finished(tx) + } + + fn request( + &self, + provider: &P, + tx: &N::TransactionRequest, + ) -> impl Future> + Send + where + P: Provider, + T: Transport + Clone, + { + async { try_join!(self.left_req(provider, tx), self.right_req(provider, tx)) } + } + + fn fill(&self, to_fill: Self::Fillable, tx: &mut N::TransactionRequest) { + if let Some(to_fill) = to_fill.0 { + self.left.fill(to_fill, tx); + }; + if let Some(to_fill) = to_fill.1 { + self.right.fill(to_fill, tx); + }; + } +} + +impl ProviderLayer for JoinFill +where + L: TxFiller, + R: TxFiller, + P: Provider, + T: alloy_transport::Transport + Clone, + N: Network, +{ + type Provider = FillProvider, P, T, N>; + fn layer(&self, inner: P) -> Self::Provider { + FillProvider::new(inner, self.clone()) + } +} + +/// A [`Provider`] that joins or more [`FillTxLayer`]s. +/// +/// Fills arbitrary properties in a transaction request by composing multiple +/// fill layers. + +#[derive(Debug, Clone)] +pub struct FillProvider +where + F: TxFiller, + P: Provider, + T: Transport + Clone, + N: Network, +{ + inner: P, + filler: F, + _pd: PhantomData (N, T)>, +} + +impl FillProvider +where + F: TxFiller, + P: Provider, + T: Transport + Clone, + N: Network, +{ + /// Creates a new `FillProvider` with the given filler and inner provider. + pub fn new(inner: P, filler: F) -> Self { + Self { inner, filler, _pd: PhantomData } + } +} + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl Provider for FillProvider +where + F: TxFiller, + P: Provider, + T: Transport + Clone, + N: Network, +{ + fn root(&self) -> &RootProvider { + self.inner.root() + } + + async fn send_transaction( + &self, + mut tx: N::TransactionRequest, + ) -> TransportResult> { + while self.filler.ready(&tx) && !self.filler.finished(&tx) { + let fillable = self.filler.request(self.root(), &tx).await?; + self.filler.fill(fillable, &mut tx); + } + + self.inner.send_transaction(tx).await + } +} diff --git a/crates/provider/src/layers/mod.rs b/crates/provider/src/layers/mod.rs index 5091f232e4f..77d8cf82605 100644 --- a/crates/provider/src/layers/mod.rs +++ b/crates/provider/src/layers/mod.rs @@ -6,7 +6,10 @@ mod signer; pub use signer::{SignerLayer, SignerProvider}; mod nonce; -pub use nonce::{ManagedNonceProvider, NonceManagerLayer}; +pub use nonce::NonceManagerLayer; mod gas; pub use gas::{GasEstimatorLayer, GasEstimatorProvider}; + +mod join_fill; +pub use join_fill::{FillProvider, JoinFill, TxFiller}; diff --git a/crates/provider/src/layers/nonce.rs b/crates/provider/src/layers/nonce.rs index f00a68c3f52..cea8283fe28 100644 --- a/crates/provider/src/layers/nonce.rs +++ b/crates/provider/src/layers/nonce.rs @@ -1,10 +1,12 @@ -use crate::{PendingTransactionBuilder, Provider, ProviderLayer, RootProvider}; -use alloy_network::{Ethereum, Network, TransactionBuilder}; +use crate::{ + layers::{FillProvider, TxFiller}, + Provider, ProviderLayer, +}; +use alloy_network::{Network, TransactionBuilder}; use alloy_primitives::Address; use alloy_transport::{Transport, TransportResult}; -use async_trait::async_trait; use dashmap::DashMap; -use std::{marker::PhantomData, sync::Arc}; +use std::sync::Arc; use tokio::sync::Mutex; /// A layer that fills nonces on transactions. @@ -39,50 +41,75 @@ use tokio::sync::Mutex; #[derive(Debug, Clone, Copy)] pub struct NonceManagerLayer; +// impl ProviderLayer for NonceManagerLayer +// where +// P: Provider, +// N: Network, +// T: Transport + Clone, +// { +// type Provider = FillProvider; + +// fn layer(&self, inner: P) -> Self::Provider { +// NonceFiller::default() +// } +// } + impl ProviderLayer for NonceManagerLayer where P: Provider, - T: Transport + Clone, + T: alloy_transport::Transport + Clone, N: Network, { - type Provider = ManagedNonceProvider; - + type Provider = FillProvider; fn layer(&self, inner: P) -> Self::Provider { - ManagedNonceProvider { inner, nonces: DashMap::default(), _phantom: PhantomData } + FillProvider::new(inner, NonceFiller::default()) } } -/// A provider that manages account nonces. -/// -/// Fills nonces for transaction requests if unset. -/// -/// # Note -/// -/// If the transaction requests do not have a sender set, this provider will not set nonces. -/// -/// You cannot construct this provider directly. Use [`ProviderBuilder`] with a -/// [`NonceManagerLayer`]. -/// -/// [`ProviderBuilder`]: crate::ProviderBuilder -#[derive(Debug, Clone)] -pub struct ManagedNonceProvider -where - T: Transport + Clone, - P: Provider, - N: Network, -{ - inner: P, +#[derive(Debug, Clone, Default)] +pub struct NonceFiller { nonces: DashMap>>>, - _phantom: PhantomData<(T, N)>, } -impl ManagedNonceProvider -where - N: Network, - T: Transport + Clone, - P: Provider, -{ - async fn get_next_nonce(&self, from: Address) -> TransportResult { +impl TxFiller for NonceFiller { + type Fillable = u64; + + fn ready(&self, tx: &N::TransactionRequest) -> bool { + tx.from().is_some() + } + + fn finished(&self, tx: &N::TransactionRequest) -> bool { + tx.nonce().is_some() + } + + fn request( + &self, + provider: &P, + tx: &N::TransactionRequest, + ) -> impl std::future::Future> + Send + where + P: Provider, + T: Transport + Clone, + { + async { + let from = tx.from().expect("checked by 'ready()'"); + self.get_next_nonce(provider, from).await + } + } + + fn fill(&self, nonce: Self::Fillable, tx: &mut N::TransactionRequest) { + tx.set_nonce(nonce); + } +} + +impl NonceFiller { + /// Get the next nonce for the given account. + async fn get_next_nonce(&self, provider: &P, from: Address) -> TransportResult + where + P: Provider, + N: Network, + T: Transport + Clone, + { // locks dashmap internally for a short duration to clone the `Arc` let mutex = Arc::clone(self.nonces.entry(from).or_default().value()); @@ -95,7 +122,7 @@ where } None => { // initialize the nonce if we haven't seen this account before - let initial_nonce = self.inner.get_transaction_count(from, None).await?; + let initial_nonce = provider.get_transaction_count(from, None).await?; *nonce = Some(initial_nonce); Ok(initial_nonce) } @@ -103,34 +130,6 @@ where } } -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl Provider for ManagedNonceProvider -where - T: Transport + Clone, - P: Provider, - N: Network, -{ - #[inline] - fn root(&self) -> &RootProvider { - self.inner.root() - } - - async fn send_transaction( - &self, - mut tx: N::TransactionRequest, - ) -> TransportResult> { - if tx.nonce().is_none() { - if let Some(from) = tx.from() { - tx.set_nonce(self.get_next_nonce(from).await?); - } - } - - self.inner.send_transaction(tx).await - } -} - -#[cfg(feature = "reqwest")] #[cfg(test)] mod tests { use super::*; From ec38e5db3e43ed347e7166f77b403c8e51d3ebfa Mon Sep 17 00:00:00 2001 From: James Date: Fri, 29 Mar 2024 14:53:15 -0700 Subject: [PATCH 02/45] lint: clippy --- crates/provider/src/layers/join_fill.rs | 14 +++++++++++--- crates/provider/src/layers/nonce.rs | 12 +++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs index 040c7b4016e..dcfc0e4e3dc 100644 --- a/crates/provider/src/layers/join_fill.rs +++ b/crates/provider/src/layers/join_fill.rs @@ -114,16 +114,16 @@ where self.left.finished(tx) && self.right.finished(tx) } - fn request( + async fn request( &self, provider: &P, tx: &N::TransactionRequest, - ) -> impl Future> + Send + ) -> TransportResult where P: Provider, T: Transport + Clone, { - async { try_join!(self.left_req(provider, tx), self.right_req(provider, tx)) } + try_join!(self.left_req(provider, tx), self.right_req(provider, tx)) } fn fill(&self, to_fill: Self::Fillable, tx: &mut N::TransactionRequest) { @@ -179,6 +179,14 @@ where pub fn new(inner: P, filler: F) -> Self { Self { inner, filler, _pd: PhantomData } } + + /// Joins a filler to this provider + pub fn join>( + self, + other: Other, + ) -> FillProvider, P, N, T> { + JoinFill::new(self.filler, other).layer(self.inner) + } } #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] diff --git a/crates/provider/src/layers/nonce.rs b/crates/provider/src/layers/nonce.rs index cea8283fe28..08cbdb8ad72 100644 --- a/crates/provider/src/layers/nonce.rs +++ b/crates/provider/src/layers/nonce.rs @@ -82,19 +82,17 @@ impl TxFiller for NonceFiller { tx.nonce().is_some() } - fn request( + async fn request( &self, provider: &P, tx: &N::TransactionRequest, - ) -> impl std::future::Future> + Send + ) -> TransportResult where P: Provider, T: Transport + Clone, { - async { - let from = tx.from().expect("checked by 'ready()'"); - self.get_next_nonce(provider, from).await - } + let from = tx.from().expect("checked by 'ready()'"); + self.get_next_nonce(provider, from).await } fn fill(&self, nonce: Self::Fillable, tx: &mut N::TransactionRequest) { @@ -133,7 +131,7 @@ impl NonceFiller { #[cfg(test)] mod tests { use super::*; - use crate::ProviderBuilder; + use crate::{ProviderBuilder, RootProvider}; use alloy_network::EthereumSigner; use alloy_node_bindings::Anvil; use alloy_primitives::{address, U256}; From 247599c483672c18b2cbb7b8a15dd2a2ef7c0171 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 29 Mar 2024 14:56:08 -0700 Subject: [PATCH 03/45] doc: CONSIDER --- crates/provider/src/layers/join_fill.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs index dcfc0e4e3dc..e0a86dfdd5e 100644 --- a/crates/provider/src/layers/join_fill.rs +++ b/crates/provider/src/layers/join_fill.rs @@ -20,6 +20,9 @@ pub trait TxFiller: Clone + Send + Sync { } /// Returns true if the filler is ready to fill in the transaction request. + + // CONSIDER: should this return Result<(), String> to allow for error + // messages to specify why it's not ready? fn ready(&self, tx: &N::TransactionRequest) -> bool; /// Returns true if all fillable properties have been filled. @@ -181,11 +184,11 @@ where } /// Joins a filler to this provider - pub fn join>( + pub fn join_with>( self, other: Other, ) -> FillProvider, P, N, T> { - JoinFill::new(self.filler, other).layer(self.inner) + self.filler.join_with(other).layer(self.inner) } } @@ -210,6 +213,7 @@ where let fillable = self.filler.request(self.root(), &tx).await?; self.filler.fill(fillable, &mut tx); } + // CONSIDER: should we error if the filler is not finished and also not ready? self.inner.send_transaction(tx).await } From 162184903a763ca9fcf4e9f0a8185861b7c53541 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 29 Mar 2024 14:58:39 -0700 Subject: [PATCH 04/45] doc: more notes --- crates/provider/src/layers/join_fill.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs index e0a86dfdd5e..9e156e41ee0 100644 --- a/crates/provider/src/layers/join_fill.rs +++ b/crates/provider/src/layers/join_fill.rs @@ -6,6 +6,13 @@ use futures::try_join; use std::{future::Future, marker::PhantomData}; /// A layer that can fill in a `TransactionRequest` with additional information. +/// +/// ## Lifecycle Notes +/// +/// - `ready` MUST be called before `request` and `fill`. It is acceptable to panic in `request` and +/// `fill` if `ready` has not been called. +/// - the output of `request` MUST be passed to `fill` before `finished()` +/// is called again. pub trait TxFiller: Clone + Send + Sync { /// The properties that this filler retrieves from the RPC. to fill in the /// TransactionRequest. @@ -211,6 +218,10 @@ where ) -> TransportResult> { while self.filler.ready(&tx) && !self.filler.finished(&tx) { let fillable = self.filler.request(self.root(), &tx).await?; + + // CONSIDER: should we have some sort of break condition or max loops here to account + // for misimplemented fillers that are always ready and never finished? + self.filler.fill(fillable, &mut tx); } // CONSIDER: should we error if the filler is not finished and also not ready? From 9e009caba4307335943df35314ea1193287e574e Mon Sep 17 00:00:00 2001 From: James Date: Fri, 29 Mar 2024 15:00:28 -0700 Subject: [PATCH 05/45] fix: get rid of ugly lifetimes --- crates/provider/src/layers/join_fill.rs | 40 +++++++++++-------------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs index 9e156e41ee0..0aa760b0808 100644 --- a/crates/provider/src/layers/join_fill.rs +++ b/crates/provider/src/layers/join_fill.rs @@ -71,39 +71,35 @@ where } /// Get a request for the left layer, if the left layer is ready. - fn left_req<'a: 'd, 'b: 'd, 'c: 'd, 'd, P, T>( - &'a self, - provider: &'b P, - tx: &'c N::TransactionRequest, - ) -> impl Future>> + Send + 'd + async fn left_req( + &self, + provider: &P, + tx: &N::TransactionRequest, + ) -> TransportResult> where P: Provider, T: Transport + Clone, { - async { - if self.left.ready(tx) { - self.left.request(provider, tx).await.map(Some) - } else { - Ok(None) - } + if self.left.ready(tx) { + self.left.request(provider, tx).await.map(Some) + } else { + Ok(None) } } - fn right_req<'a: 'd, 'b: 'd, 'c: 'd, 'd, P, T>( - &'a self, - provider: &'b P, - tx: &'c N::TransactionRequest, - ) -> impl Future>> + Send + 'd + async fn right_req( + &self, + provider: &P, + tx: &N::TransactionRequest, + ) -> TransportResult> where P: Provider, T: Transport + Clone, { - async { - if self.right.ready(tx) { - self.right.request(provider, tx).await.map(Some) - } else { - Ok(None) - } + if self.right.ready(tx) { + self.right.request(provider, tx).await.map(Some) + } else { + Ok(None) } } } From d1a19c531c9dea4fac17960875ee6d67440a9c03 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 29 Mar 2024 15:04:13 -0700 Subject: [PATCH 06/45] fix: docs and lints --- crates/provider/src/layers/join_fill.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs index 0aa760b0808..99db7e065ad 100644 --- a/crates/provider/src/layers/join_fill.rs +++ b/crates/provider/src/layers/join_fill.rs @@ -50,7 +50,7 @@ pub trait TxFiller: Clone + Send + Sync { } /// A layer that can fill in a `TransactionRequest` with additional information -/// by joining two [`FillTxLayer`]s. This struct is itself a [`FillTxLayer`], +/// by joining two [`TxFiller`]s. This struct is itself a [`TxFiller`], /// and can be nested to compose any number of fill layers. #[derive(Debug, Clone)] pub struct JoinFill { @@ -156,7 +156,7 @@ where } } -/// A [`Provider`] that joins or more [`FillTxLayer`]s. +/// A [`Provider`] that applies one or more [`TxFiller`]s. /// /// Fills arbitrary properties in a transaction request by composing multiple /// fill layers. From 55ef5f3606af6005ac51b0b9a377769d45b52e83 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 29 Mar 2024 15:05:12 -0700 Subject: [PATCH 07/45] fix: remove populate functions --- crates/provider/src/layers/join_fill.rs | 4 ++-- crates/provider/src/provider.rs | 27 +------------------------ 2 files changed, 3 insertions(+), 28 deletions(-) diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs index 99db7e065ad..0b5d26e00c5 100644 --- a/crates/provider/src/layers/join_fill.rs +++ b/crates/provider/src/layers/join_fill.rs @@ -171,7 +171,7 @@ where { inner: P, filler: F, - _pd: PhantomData (N, T)>, + _pd: PhantomData (T, N)>, } impl FillProvider @@ -190,7 +190,7 @@ where pub fn join_with>( self, other: Other, - ) -> FillProvider, P, N, T> { + ) -> FillProvider, P, T, N> { self.filler.join_with(other).layer(self.inner) } } diff --git a/crates/provider/src/provider.rs b/crates/provider/src/provider.rs index 66d4714f767..47d1a8bc353 100644 --- a/crates/provider/src/provider.rs +++ b/crates/provider/src/provider.rs @@ -7,7 +7,7 @@ use crate::{ PendingTransactionBuilder, }; use alloy_json_rpc::{RpcError, RpcParam, RpcReturn}; -use alloy_network::{Ethereum, Network, TransactionBuilder}; +use alloy_network::{Ethereum, Network}; use alloy_primitives::{ hex, Address, BlockHash, BlockNumber, Bytes, StorageKey, StorageValue, TxHash, B256, U128, U256, U64, @@ -595,31 +595,6 @@ pub trait Provider: self.client().request("eth_getBlockByNumber", (number, hydrate)).await } - /// Populates the legacy gas price field of the given transaction request. - async fn populate_gas( - &self, - tx: &mut N::TransactionRequest, - block: Option, - ) -> TransportResult<()> { - let gas = self.estimate_gas(&*tx, block).await; - - gas.map(|gas| tx.set_gas_limit(gas)) - } - - /// Populates the EIP-1559 gas price fields of the given transaction request. - async fn populate_gas_eip1559( - &self, - tx: &mut N::TransactionRequest, - estimator: Option, - ) -> TransportResult<()> { - let gas = self.estimate_eip1559_fees(estimator).await; - - gas.map(|estimate| { - tx.set_max_fee_per_gas(estimate.max_fee_per_gas); - tx.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); - }) - } - /// Broadcasts a transaction to the network. /// /// Returns a type that can be used to configure how and when to await the transaction's From a64b1c4a56c52e4ad41bf7a3eaad793fdbc5b4b9 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 29 Mar 2024 15:20:08 -0700 Subject: [PATCH 08/45] nit: remove commented code --- crates/provider/src/layers/nonce.rs | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/crates/provider/src/layers/nonce.rs b/crates/provider/src/layers/nonce.rs index 08cbdb8ad72..9a79e18c85b 100644 --- a/crates/provider/src/layers/nonce.rs +++ b/crates/provider/src/layers/nonce.rs @@ -41,19 +41,6 @@ use tokio::sync::Mutex; #[derive(Debug, Clone, Copy)] pub struct NonceManagerLayer; -// impl ProviderLayer for NonceManagerLayer -// where -// P: Provider, -// N: Network, -// T: Transport + Clone, -// { -// type Provider = FillProvider; - -// fn layer(&self, inner: P) -> Self::Provider { -// NonceFiller::default() -// } -// } - impl ProviderLayer for NonceManagerLayer where P: Provider, @@ -102,7 +89,7 @@ impl TxFiller for NonceFiller { impl NonceFiller { /// Get the next nonce for the given account. - async fn get_next_nonce(&self, provider: &P, from: Address) -> TransportResult + async fn get_next_nonce(&self, provider: &P, from: Address) -> TransportResult where P: Provider, N: Network, From a76ecafe54544c0c426d2a64be2d888ddec3f4bf Mon Sep 17 00:00:00 2001 From: James Date: Fri, 29 Mar 2024 15:31:00 -0700 Subject: [PATCH 09/45] feature: FillerControlFlow --- crates/provider/src/layers/join_fill.rs | 88 +++++++++++++++++++++---- crates/provider/src/layers/mod.rs | 2 +- crates/provider/src/layers/nonce.rs | 16 +++-- 3 files changed, 85 insertions(+), 21 deletions(-) diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs index 0b5d26e00c5..2a112c36322 100644 --- a/crates/provider/src/layers/join_fill.rs +++ b/crates/provider/src/layers/join_fill.rs @@ -5,6 +5,66 @@ use async_trait::async_trait; use futures::try_join; use std::{future::Future, marker::PhantomData}; +/// The control flow for a filler. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FillerControlFlow { + /// The filler is missing a required property. + Missing(Vec<&'static str>), + /// The filler is ready to fill in the transaction request. + Ready, + /// The filler has filled in all properties that it can fill. + Finished, +} + +impl FillerControlFlow { + /// Absorb the control flow of another filler. + /// + /// # Behavior: + /// - If either is finished, return the unfinished one + /// - If either is ready, return ready. + /// - If both are missing, return missing. + pub fn absorb(self, other: Self) -> Self { + if other.is_finished() { + return self; + } + if other.is_ready() || self.is_ready() { + return Self::Ready; + } + + if let (Self::Missing(mut a), Self::Missing(b)) = (self, other) { + a.extend(b); + return Self::Missing(a); + } + unreachable!() + } + + /// Returns true if the filler is missing a required property. + pub fn as_missing(&self) -> Option<&[&'static str]> { + match self { + Self::Missing(missing) => Some(missing), + _ => None, + } + } + + /// Returns `true` if the filler is missing information required to fill in + /// the transaction request. + pub fn is_missing(&self) -> bool { + matches!(self, Self::Missing(_)) + } + + /// Returns `true` if the filler is ready to fill in the transaction + /// request. + pub fn is_ready(&self) -> bool { + matches!(self, Self::Ready) + } + + /// Returns `true` if the filler is finished filling in the transaction + /// request. + pub fn is_finished(&self) -> bool { + matches!(self, Self::Finished) + } +} + /// A layer that can fill in a `TransactionRequest` with additional information. /// /// ## Lifecycle Notes @@ -26,14 +86,20 @@ pub trait TxFiller: Clone + Send + Sync { JoinFill::new(self, other) } - /// Returns true if the filler is ready to fill in the transaction request. + /// Return a control-flow enum indicating whether the filler is ready to + /// fill in the transaction request, or if it is missing required + /// properties. + fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow; - // CONSIDER: should this return Result<(), String> to allow for error - // messages to specify why it's not ready? - fn ready(&self, tx: &N::TransactionRequest) -> bool; + /// Returns `true` if the filler is ready to fill in the transaction request. + fn ready(&self, tx: &N::TransactionRequest) -> bool { + self.status(tx).is_ready() + } - /// Returns true if all fillable properties have been filled. - fn finished(&self, tx: &N::TransactionRequest) -> bool; + /// Returns `true` if the filler is finished filling in the transaction request. + fn finished(&self, tx: &N::TransactionRequest) -> bool { + self.status(tx).is_finished() + } /// Requests the fillable properties from the RPC. fn request( @@ -112,12 +178,8 @@ where { type Fillable = (Option, Option); - fn ready(&self, tx: &N::TransactionRequest) -> bool { - self.left.ready(tx) || self.right.ready(tx) - } - - fn finished(&self, tx: &N::TransactionRequest) -> bool { - self.left.finished(tx) && self.right.finished(tx) + fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow { + self.left.status(tx).absorb(self.right.status(tx)) } async fn request( @@ -212,7 +274,7 @@ where &self, mut tx: N::TransactionRequest, ) -> TransportResult> { - while self.filler.ready(&tx) && !self.filler.finished(&tx) { + while self.filler.status(&tx).is_ready() { let fillable = self.filler.request(self.root(), &tx).await?; // CONSIDER: should we have some sort of break condition or max loops here to account diff --git a/crates/provider/src/layers/mod.rs b/crates/provider/src/layers/mod.rs index 77d8cf82605..a47b3634a9f 100644 --- a/crates/provider/src/layers/mod.rs +++ b/crates/provider/src/layers/mod.rs @@ -12,4 +12,4 @@ mod gas; pub use gas::{GasEstimatorLayer, GasEstimatorProvider}; mod join_fill; -pub use join_fill::{FillProvider, JoinFill, TxFiller}; +pub use join_fill::{FillProvider, FillerControlFlow, JoinFill, TxFiller}; diff --git a/crates/provider/src/layers/nonce.rs b/crates/provider/src/layers/nonce.rs index 9a79e18c85b..862949d4f67 100644 --- a/crates/provider/src/layers/nonce.rs +++ b/crates/provider/src/layers/nonce.rs @@ -1,5 +1,5 @@ use crate::{ - layers::{FillProvider, TxFiller}, + layers::{FillProvider, FillerControlFlow, TxFiller}, Provider, ProviderLayer, }; use alloy_network::{Network, TransactionBuilder}; @@ -61,12 +61,14 @@ pub struct NonceFiller { impl TxFiller for NonceFiller { type Fillable = u64; - fn ready(&self, tx: &N::TransactionRequest) -> bool { - tx.from().is_some() - } - - fn finished(&self, tx: &N::TransactionRequest) -> bool { - tx.nonce().is_some() + fn status(&self, tx: &::TransactionRequest) -> FillerControlFlow { + if tx.nonce().is_some() { + return FillerControlFlow::Finished; + } + if tx.from().is_none() { + return FillerControlFlow::Missing(vec!["from"]); + } + FillerControlFlow::Ready } async fn request( From d2722904463b59b647b8d776e903295e764a0162 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 29 Mar 2024 15:33:00 -0700 Subject: [PATCH 10/45] doc: lifecycle notes --- crates/provider/src/layers/join_fill.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs index 2a112c36322..1f2443ec314 100644 --- a/crates/provider/src/layers/join_fill.rs +++ b/crates/provider/src/layers/join_fill.rs @@ -69,10 +69,16 @@ impl FillerControlFlow { /// /// ## Lifecycle Notes /// -/// - `ready` MUST be called before `request` and `fill`. It is acceptable to panic in `request` and -/// `fill` if `ready` has not been called. -/// - the output of `request` MUST be passed to `fill` before `finished()` -/// is called again. +/// The [`FillerControlFlow`] determines the lifecycle of a filler. Fillers +/// may be in one of three states: +/// - **Missing**: The filler is missing a required property to fill in the +/// transaction request. [`TxFiller::status`] should return +/// [`FillerControlFlow::Missing`]. +/// with a list of the missing properties. +/// - **Ready**: The filler is ready to fill in the transaction request. +/// [`TxFiller::status`] should return [`FillerControlFlow::Ready`]. +/// - **Finished**: The filler has filled in all properties that it can fill. +/// [`TxFiller::status`] should return [`FillerControlFlow::Finished`]. pub trait TxFiller: Clone + Send + Sync { /// The properties that this filler retrieves from the RPC. to fill in the /// TransactionRequest. From 935fd9bbfcf0d4281929d05a04827a4fd64f433a Mon Sep 17 00:00:00 2001 From: James Date: Fri, 29 Mar 2024 15:46:22 -0700 Subject: [PATCH 11/45] refactor: request -> prepare --- crates/provider/src/layers/join_fill.rs | 21 +++++++++++---------- crates/provider/src/layers/nonce.rs | 2 +- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs index 1f2443ec314..b4704dd0470 100644 --- a/crates/provider/src/layers/join_fill.rs +++ b/crates/provider/src/layers/join_fill.rs @@ -107,8 +107,8 @@ pub trait TxFiller: Clone + Send + Sync { self.status(tx).is_finished() } - /// Requests the fillable properties from the RPC. - fn request( + /// Prepares fillable properties, potentially by making an RPC request. + fn prepare( &self, provider: &P, tx: &N::TransactionRequest, @@ -142,8 +142,8 @@ where Self { left, right, _network: PhantomData } } - /// Get a request for the left layer, if the left layer is ready. - async fn left_req( + /// Get a request for the left filler, if the left filler is ready. + async fn prepare_left( &self, provider: &P, tx: &N::TransactionRequest, @@ -153,13 +153,14 @@ where T: Transport + Clone, { if self.left.ready(tx) { - self.left.request(provider, tx).await.map(Some) + self.left.prepare(provider, tx).await.map(Some) } else { Ok(None) } } - async fn right_req( + /// Get a prepare for the right filler, if the right filler is ready. + async fn prepare_right( &self, provider: &P, tx: &N::TransactionRequest, @@ -169,7 +170,7 @@ where T: Transport + Clone, { if self.right.ready(tx) { - self.right.request(provider, tx).await.map(Some) + self.right.prepare(provider, tx).await.map(Some) } else { Ok(None) } @@ -188,7 +189,7 @@ where self.left.status(tx).absorb(self.right.status(tx)) } - async fn request( + async fn prepare( &self, provider: &P, tx: &N::TransactionRequest, @@ -197,7 +198,7 @@ where P: Provider, T: Transport + Clone, { - try_join!(self.left_req(provider, tx), self.right_req(provider, tx)) + try_join!(self.prepare_left(provider, tx), self.prepare_right(provider, tx)) } fn fill(&self, to_fill: Self::Fillable, tx: &mut N::TransactionRequest) { @@ -281,7 +282,7 @@ where mut tx: N::TransactionRequest, ) -> TransportResult> { while self.filler.status(&tx).is_ready() { - let fillable = self.filler.request(self.root(), &tx).await?; + let fillable = self.filler.prepare(self.root(), &tx).await?; // CONSIDER: should we have some sort of break condition or max loops here to account // for misimplemented fillers that are always ready and never finished? diff --git a/crates/provider/src/layers/nonce.rs b/crates/provider/src/layers/nonce.rs index 862949d4f67..cf483ea21b8 100644 --- a/crates/provider/src/layers/nonce.rs +++ b/crates/provider/src/layers/nonce.rs @@ -71,7 +71,7 @@ impl TxFiller for NonceFiller { FillerControlFlow::Ready } - async fn request( + async fn prepare( &self, provider: &P, tx: &N::TransactionRequest, From 021c1bdd90df262261b688776c685cfeee58e58a Mon Sep 17 00:00:00 2001 From: James Date: Fri, 29 Mar 2024 15:52:52 -0700 Subject: [PATCH 12/45] lint: clippy --- crates/provider/src/layers/join_fill.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs index b4704dd0470..ce2b333bf6a 100644 --- a/crates/provider/src/layers/join_fill.rs +++ b/crates/provider/src/layers/join_fill.rs @@ -48,19 +48,19 @@ impl FillerControlFlow { /// Returns `true` if the filler is missing information required to fill in /// the transaction request. - pub fn is_missing(&self) -> bool { + pub const fn is_missing(&self) -> bool { matches!(self, Self::Missing(_)) } /// Returns `true` if the filler is ready to fill in the transaction /// request. - pub fn is_ready(&self) -> bool { + pub const fn is_ready(&self) -> bool { matches!(self, Self::Ready) } /// Returns `true` if the filler is finished filling in the transaction /// request. - pub fn is_finished(&self) -> bool { + pub const fn is_finished(&self) -> bool { matches!(self, Self::Finished) } } From c19aaf4bd85db6670ec0410b6930ba07c87a178a Mon Sep 17 00:00:00 2001 From: James Date: Fri, 29 Mar 2024 15:59:08 -0700 Subject: [PATCH 13/45] fix: missing block in absorb --- crates/provider/src/layers/join_fill.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs index ce2b333bf6a..9b78de976db 100644 --- a/crates/provider/src/layers/join_fill.rs +++ b/crates/provider/src/layers/join_fill.rs @@ -27,6 +27,11 @@ impl FillerControlFlow { if other.is_finished() { return self; } + + if self.is_finished() { + return other; + } + if other.is_ready() || self.is_ready() { return Self::Ready; } @@ -35,6 +40,7 @@ impl FillerControlFlow { a.extend(b); return Self::Missing(a); } + unreachable!() } From 5f59ec0937f3bc54c1dfd062e057aa5207907b86 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 29 Mar 2024 16:00:43 -0700 Subject: [PATCH 14/45] fix: absorb preserves association --- crates/provider/src/layers/join_fill.rs | 4 ++-- crates/provider/src/layers/nonce.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs index 9b78de976db..6f1ed446c00 100644 --- a/crates/provider/src/layers/join_fill.rs +++ b/crates/provider/src/layers/join_fill.rs @@ -9,7 +9,7 @@ use std::{future::Future, marker::PhantomData}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum FillerControlFlow { /// The filler is missing a required property. - Missing(Vec<&'static str>), + Missing(Vec<&'static [&'static str]>), /// The filler is ready to fill in the transaction request. Ready, /// The filler has filled in all properties that it can fill. @@ -45,7 +45,7 @@ impl FillerControlFlow { } /// Returns true if the filler is missing a required property. - pub fn as_missing(&self) -> Option<&[&'static str]> { + pub fn as_missing(&self) -> Option<&[&'static [&'static str]]> { match self { Self::Missing(missing) => Some(missing), _ => None, diff --git a/crates/provider/src/layers/nonce.rs b/crates/provider/src/layers/nonce.rs index cf483ea21b8..4363517eeb5 100644 --- a/crates/provider/src/layers/nonce.rs +++ b/crates/provider/src/layers/nonce.rs @@ -66,7 +66,7 @@ impl TxFiller for NonceFiller { return FillerControlFlow::Finished; } if tx.from().is_none() { - return FillerControlFlow::Missing(vec!["from"]); + return FillerControlFlow::Missing(vec![&["from"]]); } FillerControlFlow::Ready } From 49cf6a84d1a5f13ac3108f68596c6ca100e723f0 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 29 Mar 2024 16:06:02 -0700 Subject: [PATCH 15/45] refactor: additional info in missing --- crates/provider/src/layers/join_fill.rs | 14 ++++++++++++-- crates/provider/src/layers/nonce.rs | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs index 6f1ed446c00..71038527da8 100644 --- a/crates/provider/src/layers/join_fill.rs +++ b/crates/provider/src/layers/join_fill.rs @@ -9,7 +9,12 @@ use std::{future::Future, marker::PhantomData}; #[derive(Debug, Clone, PartialEq, Eq)] pub enum FillerControlFlow { /// The filler is missing a required property. - Missing(Vec<&'static [&'static str]>), + /// + /// To allow joining fillers while preserving their associated missing + /// lists, this variant contains a list of `(name, missing)` tuples. When + /// absorbing another control flow, if both are missing, the missing lists + /// are combined. + Missing(Vec<(&'static str, &'static [&'static str])>), /// The filler is ready to fill in the transaction request. Ready, /// The filler has filled in all properties that it can fill. @@ -44,8 +49,13 @@ impl FillerControlFlow { unreachable!() } + /// Creates a new `Missing` control flow. + pub fn missing(name: &'static str, missing: &'static [&'static str]) -> Self { + Self::Missing(vec![(name, missing)]) + } + /// Returns true if the filler is missing a required property. - pub fn as_missing(&self) -> Option<&[&'static [&'static str]]> { + pub fn as_missing(&self) -> Option<&[(&'static str, &'static [&'static str])]> { match self { Self::Missing(missing) => Some(missing), _ => None, diff --git a/crates/provider/src/layers/nonce.rs b/crates/provider/src/layers/nonce.rs index 4363517eeb5..e44d666b944 100644 --- a/crates/provider/src/layers/nonce.rs +++ b/crates/provider/src/layers/nonce.rs @@ -66,7 +66,7 @@ impl TxFiller for NonceFiller { return FillerControlFlow::Finished; } if tx.from().is_none() { - return FillerControlFlow::Missing(vec![&["from"]]); + return FillerControlFlow::missing("NonceManager", &["from"]); } FillerControlFlow::Ready } From be9cec6be3e4a3c1275baf9f1d9102af24a443ee Mon Sep 17 00:00:00 2001 From: James Date: Fri, 29 Mar 2024 18:53:48 -0700 Subject: [PATCH 16/45] fix: impl_future macro --- crates/provider/Cargo.toml | 1 + crates/provider/src/layers/join_fill.rs | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/provider/Cargo.toml b/crates/provider/Cargo.toml index f0bff807187..e463af5f9f2 100644 --- a/crates/provider/Cargo.toml +++ b/crates/provider/Cargo.toml @@ -35,6 +35,7 @@ serde_json.workspace = true tokio = { workspace = true, features = ["sync", "macros"] } tracing.workspace = true url = { workspace = true, optional = true } +futures-utils-wasm.workspace = true [dev-dependencies] alloy-consensus = { workspace = true, features = ["std"] } diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs index 71038527da8..1f473e5915f 100644 --- a/crates/provider/src/layers/join_fill.rs +++ b/crates/provider/src/layers/join_fill.rs @@ -3,7 +3,8 @@ use alloy_network::{Ethereum, Network}; use alloy_transport::{Transport, TransportResult}; use async_trait::async_trait; use futures::try_join; -use std::{future::Future, marker::PhantomData}; +use futures_utils_wasm::impl_future; +use std::marker::PhantomData; /// The control flow for a filler. #[derive(Debug, Clone, PartialEq, Eq)] @@ -128,7 +129,7 @@ pub trait TxFiller: Clone + Send + Sync { &self, provider: &P, tx: &N::TransactionRequest, - ) -> impl Future> + Send + ) -> impl_future!(>) where P: Provider, T: Transport + Clone; From e6ae1b936815fe0e168f114e0514706c1910d124 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 1 Apr 2024 09:13:18 -0700 Subject: [PATCH 17/45] fix: resolve considers --- crates/provider/src/layers/join_fill.rs | 14 +++++++++----- crates/provider/src/layers/signer.rs | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs index 1f473e5915f..83aa804a084 100644 --- a/crates/provider/src/layers/join_fill.rs +++ b/crates/provider/src/layers/join_fill.rs @@ -298,16 +298,20 @@ where &self, mut tx: N::TransactionRequest, ) -> TransportResult> { + let mut count = 0; while self.filler.status(&tx).is_ready() { let fillable = self.filler.prepare(self.root(), &tx).await?; - - // CONSIDER: should we have some sort of break condition or max loops here to account - // for misimplemented fillers that are always ready and never finished? - self.filler.fill(fillable, &mut tx); + + count += 1; + if count >= 20 { + panic!( + "Tx filler loop detected. This indicates a bug in some filler implementation. Please file an issue containing your tx filler set." + ); + } } - // CONSIDER: should we error if the filler is not finished and also not ready? + // Errors in tx building happen further down the stack. self.inner.send_transaction(tx).await } } diff --git a/crates/provider/src/layers/signer.rs b/crates/provider/src/layers/signer.rs index 78d0013ca7f..b0bdf9842bc 100644 --- a/crates/provider/src/layers/signer.rs +++ b/crates/provider/src/layers/signer.rs @@ -1,4 +1,4 @@ -use crate::{PendingTransactionBuilder, Provider, ProviderLayer, RootProvider}; +use crate::{layers::TxFiller, PendingTransactionBuilder, Provider, ProviderLayer, RootProvider}; use alloy_network::{eip2718::Encodable2718, Ethereum, Network, NetworkSigner, TransactionBuilder}; use alloy_transport::{Transport, TransportErrorKind, TransportResult}; use async_trait::async_trait; From b4f22a603143cb09f72511f573e7d5919107897e Mon Sep 17 00:00:00 2001 From: James Date: Tue, 2 Apr 2024 10:44:40 -0700 Subject: [PATCH 18/45] refactor: gas layer to gas filler --- crates/network/src/any/builder.rs | 20 +- crates/network/src/ethereum/builder.rs | 32 ++- crates/network/src/transaction/builder.rs | 15 +- crates/provider/src/builder.rs | 41 ++-- crates/provider/src/layers/gas.rs | 279 ++++++++++++---------- crates/provider/src/layers/mod.rs | 4 +- crates/provider/src/layers/nonce.rs | 6 +- crates/provider/src/layers/signer.rs | 2 +- 8 files changed, 229 insertions(+), 170 deletions(-) diff --git a/crates/network/src/any/builder.rs b/crates/network/src/any/builder.rs index 065d114fe00..b939823157a 100644 --- a/crates/network/src/any/builder.rs +++ b/crates/network/src/any/builder.rs @@ -1,7 +1,7 @@ use std::ops::{Deref, DerefMut}; use alloy_consensus::BlobTransactionSidecar; -use alloy_rpc_types::{TransactionRequest, WithOtherFields}; +use alloy_rpc_types::{AccessList, TransactionRequest, WithOtherFields}; use crate::{ any::AnyNetwork, ethereum::build_unsigned, BuilderResult, Network, TransactionBuilder, @@ -96,18 +96,28 @@ impl TransactionBuilder for WithOtherFields { self.deref_mut().set_gas_limit(gas_limit); } - fn build_unsigned(self) -> BuilderResult<::UnsignedTx> { - build_unsigned::(self.inner) + /// Get the EIP-2930 access list for the transaction. + fn access_list(&self) -> Option<&AccessList> { + self.deref().access_list() + } + + /// Sets the EIP-2930 access list. + fn set_access_list(&mut self, access_list: AccessList) { + self.deref_mut().set_access_list(access_list) } - fn get_blob_sidecar(&self) -> Option<&BlobTransactionSidecar> { - self.deref().get_blob_sidecar() + fn blob_sidecar(&self) -> Option<&BlobTransactionSidecar> { + self.deref().blob_sidecar() } fn set_blob_sidecar(&mut self, sidecar: BlobTransactionSidecar) { self.deref_mut().set_blob_sidecar(sidecar) } + fn build_unsigned(self) -> BuilderResult<::UnsignedTx> { + build_unsigned::(self.inner) + } + async fn build>( self, signer: &S, diff --git a/crates/network/src/ethereum/builder.rs b/crates/network/src/ethereum/builder.rs index f33c96d82f0..40fcb6e04fc 100644 --- a/crates/network/src/ethereum/builder.rs +++ b/crates/network/src/ethereum/builder.rs @@ -4,15 +4,15 @@ use crate::{ use alloy_consensus::{ BlobTransactionSidecar, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxLegacy, }; -use alloy_primitives::{Address, TxKind}; -use alloy_rpc_types::request::TransactionRequest; +use alloy_primitives::{Address, TxKind, U256, Bytes, ChainId}; +use alloy_rpc_types::{request::TransactionRequest, AccessList}; -impl TransactionBuilder for alloy_rpc_types::TransactionRequest { - fn chain_id(&self) -> Option { +impl TransactionBuilder for TransactionRequest { + fn chain_id(&self) -> Option { self.chain_id } - fn set_chain_id(&mut self, chain_id: alloy_primitives::ChainId) { + fn set_chain_id(&mut self, chain_id: ChainId) { self.chain_id = Some(chain_id); } @@ -24,11 +24,11 @@ impl TransactionBuilder for alloy_rpc_types::TransactionRequest { self.nonce = Some(nonce); } - fn input(&self) -> Option<&alloy_primitives::Bytes> { + fn input(&self) -> Option<&Bytes> { self.input.input() } - fn set_input(&mut self, input: alloy_primitives::Bytes) { + fn set_input(&mut self, input: Bytes) { self.input.input = Some(input); } @@ -40,22 +40,22 @@ impl TransactionBuilder for alloy_rpc_types::TransactionRequest { self.from = Some(from); } - fn to(&self) -> Option { + fn to(&self) -> Option { self.to.map(TxKind::Call).or(Some(TxKind::Create)) } - fn set_to(&mut self, to: alloy_primitives::TxKind) { + fn set_to(&mut self, to: TxKind) { match to { TxKind::Create => self.to = None, TxKind::Call(to) => self.to = Some(to), } } - fn value(&self) -> Option { + fn value(&self) -> Option { self.value } - fn set_value(&mut self, value: alloy_primitives::U256) { + fn set_value(&mut self, value: U256) { self.value = Some(value) } @@ -103,7 +103,15 @@ impl TransactionBuilder for alloy_rpc_types::TransactionRequest { build_unsigned::(self) } - fn get_blob_sidecar(&self) -> Option<&BlobTransactionSidecar> { + fn access_list(&self) -> Option<&AccessList> { + self.access_list.as_ref() + } + + fn set_access_list(&mut self, access_list: AccessList) { + self.access_list = Some(access_list); + } + + fn blob_sidecar(&self) -> Option<&BlobTransactionSidecar> { self.sidecar.as_ref() } diff --git a/crates/network/src/transaction/builder.rs b/crates/network/src/transaction/builder.rs index 8ac8c566c2f..20e2c83b47b 100644 --- a/crates/network/src/transaction/builder.rs +++ b/crates/network/src/transaction/builder.rs @@ -2,6 +2,7 @@ use super::signer::NetworkSigner; use crate::Network; use alloy_consensus::BlobTransactionSidecar; use alloy_primitives::{Address, Bytes, ChainId, TxKind, U256}; +use alloy_rpc_types::AccessList; use futures_utils_wasm::impl_future; /// Error type for transaction builders. @@ -191,8 +192,20 @@ pub trait TransactionBuilder: Default + Sized + Send + Sync + 'stati self } + /// Get the EIP-2930 access list for the transaction. + fn access_list(&self) -> Option<&AccessList>; + + /// Sets the EIP-2930 access list. + fn set_access_list(&mut self, access_list: AccessList); + + /// Builder-pattern method for setting the access list. + fn with_access_list(mut self, access_list: AccessList) -> Self { + self.set_access_list(access_list); + self + } + /// Gets the EIP-4844 blob sidecar of the transaction. - fn get_blob_sidecar(&self) -> Option<&BlobTransactionSidecar>; + fn blob_sidecar(&self) -> Option<&BlobTransactionSidecar>; /// Sets the EIP-4844 blob sidecar of the transaction. /// diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index d80befc5fc5..4e23686ad54 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -1,7 +1,4 @@ -use crate::{ - layers::{GasEstimatorLayer, NonceManagerLayer, SignerLayer}, - Provider, RootProvider, -}; +use crate::{layers::SignerLayer, Provider, RootProvider}; use alloy_network::{Ethereum, Network}; use alloy_rpc_client::{BuiltInConnectionString, ClientBuilder, RpcClient}; use alloy_transport::{BoxTransport, Transport, TransportError}; @@ -114,26 +111,26 @@ impl ProviderBuilder { self.layer(SignerLayer::new(signer)) } - /// Add gas estimation to the stack being built. - /// - /// See [`GasEstimatorLayer`] - pub fn with_gas_estimation(self) -> ProviderBuilder, N> { - self.layer(GasEstimatorLayer) - } + // /// Add gas estimation to the stack being built. + // /// + // /// See [`GasEstimatorLayer`] + // pub fn with_gas_estimation(self) -> ProviderBuilder, N> { + // self.layer(GasEstimatorLayer) + // } - /// Add nonce management to the stack being built. - /// - /// See [`NonceManagerLayer`] - pub fn with_nonce_management(self) -> ProviderBuilder, N> { - self.layer(NonceManagerLayer) - } + // /// Add nonce management to the stack being built. + // /// + // /// See [`NonceManagerLayer`] + // pub fn with_nonce_management(self) -> ProviderBuilder, N> { + // self.layer(NonceManagerLayer) + // } - /// Add preconfigured set of layers handling gas estimation and nonce management - pub fn with_recommended_layers( - self, - ) -> ProviderBuilder>, N> { - self.with_gas_estimation().with_nonce_management() - } + // /// Add preconfigured set of layers handling gas estimation and nonce management + // pub fn with_recommended_layers( + // self, + // ) -> ProviderBuilder>, N> { + // self.with_gas_estimation().with_nonce_management() + // } /// Change the network. /// diff --git a/crates/provider/src/layers/gas.rs b/crates/provider/src/layers/gas.rs index 0b4d15949d3..8f09052eeb2 100644 --- a/crates/provider/src/layers/gas.rs +++ b/crates/provider/src/layers/gas.rs @@ -1,13 +1,13 @@ use crate::{ - utils::Eip1559Estimation, PendingTransactionBuilder, Provider, ProviderLayer, RootProvider, + layers::{FillProvider, FillerControlFlow, TxFiller}, + utils::Eip1559Estimation, + Provider, ProviderLayer, }; use alloy_json_rpc::RpcError; use alloy_network::{Network, TransactionBuilder}; use alloy_rpc_types::BlockNumberOrTag; -use alloy_transport::{Transport, TransportError, TransportResult}; -use async_trait::async_trait; +use alloy_transport::{Transport, TransportResult}; use futures::FutureExt; -use std::marker::PhantomData; /// A layer that populates gas related fields in transaction requests if unset. /// @@ -40,71 +40,77 @@ use std::marker::PhantomData; /// provider.send_transaction(TransactionRequest::default()).await; /// # } #[derive(Debug, Clone, Copy, Default)] -pub struct GasEstimatorLayer; +pub struct GasFillerConfig; -impl ProviderLayer for GasEstimatorLayer +impl ProviderLayer for GasFillerConfig where P: Provider, + T: alloy_transport::Transport + Clone, N: Network, - T: Transport + Clone, { - type Provider = GasEstimatorProvider; + type Provider = FillProvider; fn layer(&self, inner: P) -> Self::Provider { - GasEstimatorProvider { inner, _phantom: PhantomData } + FillProvider::new(inner, GasFiller) } } -/// A provider that estimates gas for transactions. -/// -/// Note: This provider requires the chain_id to be set in the transaction request if it's a -/// EIP1559. -/// -/// You cannot construct this directly, use [`ProviderBuilder`] with a [`GasEstimatorLayer`]. -/// -/// [`ProviderBuilder`]: crate::ProviderBuilder -#[derive(Debug, Clone)] -pub struct GasEstimatorProvider -where - N: Network, - T: Transport + Clone, - P: Provider, -{ - inner: P, - _phantom: PhantomData<(N, T)>, +/// An enum over the different types of gas fillable. +#[allow(unreachable_pub)] +#[doc(hidden)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum GasFillable { + Legacy { gas_limit: u128, gas_price: u128 }, + Eip1559 { gas_limit: u128, estimate: Eip1559Estimation }, + Eip4844 { gas_limit: u128, estimate: Eip1559Estimation, max_fee_per_blob_gas: u128 }, } -impl GasEstimatorProvider -where - N: Network, - T: Transport + Clone, - P: Provider, -{ - /// Gets the gas_price to be used in legacy txs. - async fn get_gas_price(&self) -> TransportResult { - self.inner.get_gas_price().await - } +/// A [`TxFiller`] that populates gas related fields in transaction requests if +/// unset. +#[derive(Debug, Clone, Copy, Default)] +pub struct GasFiller; - /// Gets the gas_limit to be used in txs. - async fn get_gas_estimate(&self, tx: &N::TransactionRequest) -> TransportResult { - self.inner.estimate_gas(tx, None).await - } +impl GasFiller { + async fn prepare_legacy( + &self, + provider: &P, + tx: &N::TransactionRequest, + ) -> TransportResult + where + P: Provider, + T: Transport + Clone, + N: Network, + { + let gas_price_fut = if let Some(gas_price) = tx.gas_price() { + async move { Ok(gas_price) }.left_future() + } else { + async { provider.get_gas_price().await }.right_future() + }; + + let gas_limit_fut = if let Some(gas_limit) = tx.gas_limit() { + async move { Ok(gas_limit) }.left_future() + } else { + async { provider.estimate_gas(tx, None).await }.right_future() + }; - /// Gets the max_fee_per_gas and max_priority_fee_per_gas to be used in EIP-1559 txs. - async fn get_eip1559_fees_estimate(&self) -> TransportResult { - self.inner.estimate_eip1559_fees(None).await + let (gas_price, gas_limit) = futures::try_join!(gas_price_fut, gas_limit_fut)?; + + Ok(GasFillable::Legacy { gas_limit, gas_price }) } - /// Populates the gas_limit, max_fee_per_gas and max_priority_fee_per_gas fields if unset. - /// Requires the chain_id to be set in the transaction request to be processed as a EIP-1559 tx. - /// If the network does not support EIP-1559, it will process it as a legacy tx. - async fn handle_eip1559_tx( + async fn prepare_1559( &self, - tx: &mut N::TransactionRequest, - ) -> Result<(), TransportError> { - let gas_estimate_fut = if let Some(gas_limit) = tx.gas_limit() { + provider: &P, + tx: &N::TransactionRequest, + ) -> TransportResult + where + P: Provider, + T: Transport + Clone, + N: Network, + { + let gas_limit_fut = if let Some(gas_limit) = tx.gas_limit() { async move { Ok(gas_limit) }.left_future() } else { - async { self.get_gas_estimate(tx).await }.right_future() + async { provider.estimate_gas(tx, None).await }.right_future() }; let eip1559_fees_fut = if let (Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) = @@ -113,100 +119,123 @@ where async move { Ok(Eip1559Estimation { max_fee_per_gas, max_priority_fee_per_gas }) } .left_future() } else { - async { self.get_eip1559_fees_estimate().await }.right_future() + async { provider.estimate_eip1559_fees(None).await }.right_future() }; - match futures::try_join!(gas_estimate_fut, eip1559_fees_fut) { - Ok((gas_limit, eip1559_fees)) => { - tx.set_gas_limit(gas_limit); - tx.set_max_fee_per_gas(eip1559_fees.max_fee_per_gas); - tx.set_max_priority_fee_per_gas(eip1559_fees.max_priority_fee_per_gas); - Ok(()) - } - Err(RpcError::UnsupportedFeature("eip1559")) => self.handle_legacy_tx(tx).await, - Err(e) => Err(e), - } + let (gas_limit, estimate) = futures::try_join!(gas_limit_fut, eip1559_fees_fut)?; + + Ok(GasFillable::Eip1559 { gas_limit, estimate }) } - /// Populates the gas_price and only populates the gas_limit field if unset. - /// This method always assumes that the gas_price is unset. - async fn handle_legacy_tx(&self, tx: &mut N::TransactionRequest) -> Result<(), TransportError> { - let gas_price_fut = self.get_gas_price(); + async fn prepare_4844( + &self, + provider: &P, + tx: &N::TransactionRequest, + ) -> TransportResult + where + P: Provider, + T: Transport + Clone, + N: Network, + { let gas_limit_fut = if let Some(gas_limit) = tx.gas_limit() { async move { Ok(gas_limit) }.left_future() } else { - async { self.get_gas_estimate(tx).await }.right_future() + async { provider.estimate_gas(tx, None).await }.right_future() }; - futures::try_join!(gas_price_fut, gas_limit_fut).map(|(gas_price, gas_limit)| { - tx.set_gas_price(gas_price); - tx.set_gas_limit(gas_limit); - tx - })?; + let eip1559_fees_fut = if let (Some(max_fee_per_gas), Some(max_priority_fee_per_gas)) = + (tx.max_fee_per_gas(), tx.max_priority_fee_per_gas()) + { + async move { Ok(Eip1559Estimation { max_fee_per_gas, max_priority_fee_per_gas }) } + .left_future() + } else { + async { provider.estimate_eip1559_fees(None).await }.right_future() + }; - Ok(()) - } + let max_fee_per_blob_gas_fut = if let Some(max_fee_per_blob_gas) = tx.max_fee_per_blob_gas() + { + async move { Ok(max_fee_per_blob_gas) }.left_future() + } else { + async { + provider + .get_block_by_number(BlockNumberOrTag::Latest, false) + .await? + .ok_or(RpcError::NullResp)? + .header + .next_block_blob_fee() + .ok_or(RpcError::UnsupportedFeature("eip4844")) + } + .right_future() + }; - /// There are a few ways to obtain the blob base fee for an EIP-4844 transaction: - /// - /// * `eth_blobBaseFee`: Returns the fee for the next block directly. - /// * `eth_feeHistory`: Returns the same info as for the EIP-1559 fees. - /// * retrieving it from the "pending" block directly. - /// - /// At the time of this writing support for EIP-4844 fees is lacking, hence we're defaulting to - /// requesting the fee from the "pending" block. - async fn handle_eip4844_tx( - &self, - tx: &mut N::TransactionRequest, - ) -> Result<(), TransportError> { - // TODO this can be optimized together with 1559 dynamic fees once blob fee support on - // eth_feeHistory is more widely supported - if tx.get_blob_sidecar().is_some() && tx.max_fee_per_blob_gas().is_none() { - let next_blob_fee = self - .inner - .get_block_by_number(BlockNumberOrTag::Latest, false) - .await? - .ok_or(RpcError::NullResp)? - .header - .next_block_blob_fee() - .ok_or(RpcError::UnsupportedFeature("eip4844"))?; - tx.set_max_fee_per_blob_gas(next_blob_fee); - } + let (gas_limit, estimate, max_fee_per_blob_gas) = + futures::try_join!(gas_limit_fut, eip1559_fees_fut, max_fee_per_blob_gas_fut)?; - Ok(()) + Ok(GasFillable::Eip4844 { gas_limit, estimate, max_fee_per_blob_gas }) } } -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl Provider for GasEstimatorProvider -where - N: Network, - T: Transport + Clone, - P: Provider, -{ - fn root(&self) -> &RootProvider { - self.inner.root() +impl TxFiller for GasFiller { + type Fillable = GasFillable; + + fn status(&self, tx: &::TransactionRequest) -> FillerControlFlow { + // legacy and eip2930 tx + if tx.gas_price().is_some() && tx.gas_limit().is_some() { + return FillerControlFlow::Finished; + } + // 4844 + if tx.max_fee_per_blob_gas().is_some() + && tx.max_fee_per_gas().is_some() + && tx.max_priority_fee_per_gas().is_some() + { + return FillerControlFlow::Finished; + } + // eip1559 + if tx.blob_sidecar().is_none() + && tx.max_fee_per_gas().is_some() + && tx.max_priority_fee_per_gas().is_some() + { + return FillerControlFlow::Finished; + } + FillerControlFlow::Ready } - async fn send_transaction( + async fn prepare( &self, - mut tx: N::TransactionRequest, - ) -> TransportResult> { - if tx.gas_price().is_none() { - // Assume its a EIP1559 tx - // Populate the following gas_limit, max_fee_per_gas and max_priority_fee_per_gas fields - // if unset. - self.handle_eip1559_tx(&mut tx).await?; - // TODO: this can be done more elegantly once we can set EIP-1559 and EIP-4844 fields - // with a single eth_feeHistory request - self.handle_eip4844_tx(&mut tx).await?; + provider: &P, + tx: &::TransactionRequest, + ) -> TransportResult + where + P: Provider, + T: Transport + Clone, + { + if tx.gas_price().is_some() || tx.access_list().is_some() { + self.prepare_legacy(provider, tx).await + } else if tx.blob_sidecar().is_some() { + self.prepare_4844(provider, tx).await } else { - // Assume its a legacy tx - // Populate only the gas_limit field if unset. - self.handle_legacy_tx(&mut tx).await?; + self.prepare_1559(provider, tx).await + } + } + + fn fill(&self, fillable: Self::Fillable, tx: &mut ::TransactionRequest) { + match fillable { + GasFillable::Legacy { gas_limit, gas_price } => { + tx.set_gas_limit(gas_limit); + tx.set_gas_price(gas_price); + } + GasFillable::Eip1559 { gas_limit, estimate } => { + tx.set_gas_limit(gas_limit); + tx.set_max_fee_per_gas(estimate.max_fee_per_gas); + tx.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); + } + GasFillable::Eip4844 { gas_limit, estimate, max_fee_per_blob_gas } => { + tx.set_gas_limit(gas_limit); + tx.set_max_fee_per_gas(estimate.max_fee_per_gas); + tx.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); + tx.set_max_fee_per_blob_gas(max_fee_per_blob_gas); + } } - self.inner.send_transaction(tx).await } } diff --git a/crates/provider/src/layers/mod.rs b/crates/provider/src/layers/mod.rs index a47b3634a9f..c56e892386c 100644 --- a/crates/provider/src/layers/mod.rs +++ b/crates/provider/src/layers/mod.rs @@ -6,10 +6,10 @@ mod signer; pub use signer::{SignerLayer, SignerProvider}; mod nonce; -pub use nonce::NonceManagerLayer; +pub use nonce::{NonceFiller, NonceFillerConfig}; mod gas; -pub use gas::{GasEstimatorLayer, GasEstimatorProvider}; +pub use gas::{GasFiller, GasFillerConfig}; mod join_fill; pub use join_fill::{FillProvider, FillerControlFlow, JoinFill, TxFiller}; diff --git a/crates/provider/src/layers/nonce.rs b/crates/provider/src/layers/nonce.rs index e44d666b944..1a85b4418a4 100644 --- a/crates/provider/src/layers/nonce.rs +++ b/crates/provider/src/layers/nonce.rs @@ -39,9 +39,9 @@ use tokio::sync::Mutex; /// /// [`SignerLayer`]: crate::layers::SignerLayer #[derive(Debug, Clone, Copy)] -pub struct NonceManagerLayer; +pub struct NonceFillerConfig; -impl ProviderLayer for NonceManagerLayer +impl ProviderLayer for NonceFillerConfig where P: Provider, T: alloy_transport::Transport + Clone, @@ -53,6 +53,8 @@ where } } +/// A [`TxFiller`] that fills the nonce on transactions by keeping a local +/// mapping of account addresses to their next nonce. #[derive(Debug, Clone, Default)] pub struct NonceFiller { nonces: DashMap>>>, diff --git a/crates/provider/src/layers/signer.rs b/crates/provider/src/layers/signer.rs index b0bdf9842bc..78d0013ca7f 100644 --- a/crates/provider/src/layers/signer.rs +++ b/crates/provider/src/layers/signer.rs @@ -1,4 +1,4 @@ -use crate::{layers::TxFiller, PendingTransactionBuilder, Provider, ProviderLayer, RootProvider}; +use crate::{PendingTransactionBuilder, Provider, ProviderLayer, RootProvider}; use alloy_network::{eip2718::Encodable2718, Ethereum, Network, NetworkSigner, TransactionBuilder}; use alloy_transport::{Transport, TransportErrorKind, TransportResult}; use async_trait::async_trait; From 8f732a93f9bd65d712b51852f3ed0c8f60f325bd Mon Sep 17 00:00:00 2001 From: James Date: Tue, 2 Apr 2024 11:17:29 -0700 Subject: [PATCH 19/45] refactor: improve provider builder --- crates/provider/src/builder.rs | 98 ++++++++++++++++++------- crates/provider/src/layers/gas.rs | 6 +- crates/provider/src/layers/join_fill.rs | 12 +-- crates/provider/src/layers/signer.rs | 2 +- 4 files changed, 81 insertions(+), 37 deletions(-) diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index 4e23686ad54..8c7fec3097f 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -1,7 +1,10 @@ -use crate::{layers::SignerLayer, Provider, RootProvider}; +use crate::{ + layers::{FillerControlFlow, GasFiller, JoinFill, NonceFiller, SignerLayer, TxFiller}, + Provider, RootProvider, +}; use alloy_network::{Ethereum, Network}; use alloy_rpc_client::{BuiltInConnectionString, ClientBuilder, RpcClient}; -use alloy_transport::{BoxTransport, Transport, TransportError}; +use alloy_transport::{BoxTransport, Transport, TransportError, TransportResult}; use std::marker::PhantomData; /// A layering abstraction in the vein of [`tower::Layer`] @@ -19,6 +22,29 @@ pub trait ProviderLayer, T: Transport + Clone, N: Network = Et #[derive(Debug, Clone, Copy)] pub struct Identity; +impl TxFiller for Identity +where + N: Network, +{ + type Fillable = (); + + fn status(&self, _tx: &::TransactionRequest) -> FillerControlFlow { + FillerControlFlow::Finished + } + + async fn prepare( + &self, + _provider: &P, + _tx: &N::TransactionRequest, + ) -> TransportResult { + Ok(()) + } + + fn fill(&self, _to_fill: Self::Fillable, _tx: &mut N::TransactionRequest) { + // Do nothing + } +} + impl ProviderLayer for Identity where T: Transport + Clone, @@ -70,25 +96,26 @@ where /// /// [`tower::ServiceBuilder`]: https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html #[derive(Debug)] -pub struct ProviderBuilder { +pub struct ProviderBuilder { layer: L, - network: PhantomData, + filler: F, + network: PhantomData N>, } -impl ProviderBuilder { +impl ProviderBuilder { /// Create a new [`ProviderBuilder`]. pub const fn new() -> Self { - ProviderBuilder { layer: Identity, network: PhantomData } + ProviderBuilder { layer: Identity, filler: Identity, network: PhantomData } } } -impl Default for ProviderBuilder { +impl Default for ProviderBuilder { fn default() -> Self { - ProviderBuilder { layer: Identity, network: PhantomData } + ProviderBuilder { layer: Identity, filler: Identity, network: PhantomData } } } -impl ProviderBuilder { +impl ProviderBuilder { /// Add a layer to the stack being built. This is similar to /// [`tower::ServiceBuilder::layer`]. /// @@ -100,30 +127,45 @@ impl ProviderBuilder { /// /// [`tower::ServiceBuilder::layer`]: https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html#method.layer /// [`tower::ServiceBuilder`]: https://docs.rs/tower/latest/tower/struct.ServiceBuilder.html - pub fn layer(self, layer: Inner) -> ProviderBuilder, N> { - ProviderBuilder { layer: Stack::new(layer, self.layer), network: PhantomData } + pub fn layer(self, layer: Inner) -> ProviderBuilder, F, N> { + ProviderBuilder { + layer: Stack::new(layer, self.layer), + filler: self.filler, + network: PhantomData, + } + } + + /// Add a transaction filler to the stack being built. Transaction fillers + /// are used to fill in missing fields on transactions before they are sent, + /// and are all joined to form the outermost layer of the stack. + pub fn filler(self, filler: F2) -> ProviderBuilder, N> { + ProviderBuilder { + layer: self.layer, + filler: JoinFill::new(self.filler, filler), + network: PhantomData, + } } /// Add a signer layer to the stack being built. /// /// See [`SignerLayer`]. - pub fn signer(self, signer: S) -> ProviderBuilder, L>, N> { + pub fn signer(self, signer: S) -> ProviderBuilder, L>, F, N> { self.layer(SignerLayer::new(signer)) } - // /// Add gas estimation to the stack being built. - // /// - // /// See [`GasEstimatorLayer`] - // pub fn with_gas_estimation(self) -> ProviderBuilder, N> { - // self.layer(GasEstimatorLayer) - // } + /// Add gas estimation to the stack being built. + /// + /// See [`GasFiller`] + pub fn with_gas_estimation(self) -> ProviderBuilder, N> { + self.filler(GasFiller) + } - // /// Add nonce management to the stack being built. - // /// - // /// See [`NonceManagerLayer`] - // pub fn with_nonce_management(self) -> ProviderBuilder, N> { - // self.layer(NonceManagerLayer) - // } + /// Add nonce management to the stack being built. + /// + /// See [`NonceManager`] + pub fn with_nonce_management(self) -> ProviderBuilder, N> { + self.filler(NonceFiller::default()) + } // /// Add preconfigured set of layers handling gas estimation and nonce management // pub fn with_recommended_layers( @@ -140,13 +182,13 @@ impl ProviderBuilder { /// ```ignore /// builder.network::() /// ``` - pub fn network(self) -> ProviderBuilder { - ProviderBuilder { layer: self.layer, network: PhantomData } + pub fn network(self) -> ProviderBuilder { + ProviderBuilder { layer: self.layer, filler: self.filler, network: PhantomData } } /// Finish the layer stack by providing a root [`Provider`], outputting /// the final [`Provider`] type with all stack components. - pub fn provider(self, provider: P) -> L::Provider + pub fn on_provider(self, provider: P) -> L::Provider where L: ProviderLayer, P: Provider, @@ -167,7 +209,7 @@ impl ProviderBuilder { T: Transport + Clone, N: Network, { - self.provider(RootProvider::new(client)) + self.on_provider(RootProvider::new(client)) } /// Finish the layer stack by providing a connection string for a built-in diff --git a/crates/provider/src/layers/gas.rs b/crates/provider/src/layers/gas.rs index 8f09052eeb2..c1ba2302371 100644 --- a/crates/provider/src/layers/gas.rs +++ b/crates/provider/src/layers/gas.rs @@ -264,7 +264,7 @@ mod tests { .with_nonce_management() .with_gas_estimation() .signer(EthereumSigner::from(wallet)) - .provider(RootProvider::new(RpcClient::new(http, true))); + .on_http(url); // GasEstimationLayer requires chain_id to be set to handle EIP-1559 tx let tx = TransactionRequest { @@ -295,7 +295,7 @@ mod tests { .with_nonce_management() .with_gas_estimation() .signer(EthereumSigner::from(wallet)) - .provider(RootProvider::new(RpcClient::new(http, true))); + .on_http(url); let gas_price = provider.get_gas_price().await.unwrap(); let tx = TransactionRequest { @@ -325,7 +325,7 @@ mod tests { .with_nonce_management() .with_gas_estimation() .signer(EthereumSigner::from(wallet)) - .provider(RootProvider::new(RpcClient::new(http, true))); + .on_http(url); let tx = TransactionRequest { from: Some(anvil.addresses()[0]), diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs index 83aa804a084..a2226a6c1cd 100644 --- a/crates/provider/src/layers/join_fill.rs +++ b/crates/provider/src/layers/join_fill.rs @@ -148,17 +148,19 @@ pub struct JoinFill { _network: PhantomData N>, } +impl JoinFill { + /// Creates a new `JoinFill` with the given layers. + pub fn new(left: L, right: R) -> Self { + Self { left, right, _network: PhantomData } + } +} + impl JoinFill where L: TxFiller, R: TxFiller, N: Network, { - /// Creates a new `JoinFill` with the given layers. - pub fn new(left: L, right: R) -> Self { - Self { left, right, _network: PhantomData } - } - /// Get a request for the left filler, if the left filler is ready. async fn prepare_left( &self, diff --git a/crates/provider/src/layers/signer.rs b/crates/provider/src/layers/signer.rs index 78d0013ca7f..63f0d00c6f4 100644 --- a/crates/provider/src/layers/signer.rs +++ b/crates/provider/src/layers/signer.rs @@ -118,7 +118,7 @@ mod tests { let provider = ProviderBuilder::new() .signer(EthereumSigner::from(wallet)) - .provider(RootProvider::new(RpcClient::new(http, true))); + .on_provider(RootProvider::new(RpcClient::new(http, true))); let tx = TransactionRequest { nonce: Some(0), From 5c26fa7a3c7c6b4f3338c53b95fec5fc465117f3 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 2 Apr 2024 11:30:35 -0700 Subject: [PATCH 20/45] refactor: filler is outmost layer --- crates/provider/src/builder.rs | 50 ++++++++++++++++--------- crates/provider/src/layers/join_fill.rs | 32 ++++++++-------- 2 files changed, 47 insertions(+), 35 deletions(-) diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index 8c7fec3097f..094f4bb4465 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -138,7 +138,7 @@ impl ProviderBuilder { /// Add a transaction filler to the stack being built. Transaction fillers /// are used to fill in missing fields on transactions before they are sent, /// and are all joined to form the outermost layer of the stack. - pub fn filler(self, filler: F2) -> ProviderBuilder, N> { + pub fn filler(self, filler: F2) -> ProviderBuilder, N> { ProviderBuilder { layer: self.layer, filler: JoinFill::new(self.filler, filler), @@ -156,23 +156,23 @@ impl ProviderBuilder { /// Add gas estimation to the stack being built. /// /// See [`GasFiller`] - pub fn with_gas_estimation(self) -> ProviderBuilder, N> { + pub fn with_gas_estimation(self) -> ProviderBuilder, N> { self.filler(GasFiller) } /// Add nonce management to the stack being built. /// /// See [`NonceManager`] - pub fn with_nonce_management(self) -> ProviderBuilder, N> { + pub fn with_nonce_management(self) -> ProviderBuilder, N> { self.filler(NonceFiller::default()) } - // /// Add preconfigured set of layers handling gas estimation and nonce management - // pub fn with_recommended_layers( - // self, - // ) -> ProviderBuilder>, N> { - // self.with_gas_estimation().with_nonce_management() - // } + /// Add preconfigured set of layers handling gas estimation and nonce management + pub fn with_recommended_layers( + self, + ) -> ProviderBuilder, NonceFiller>, N> { + self.with_gas_estimation().with_nonce_management() + } /// Change the network. /// @@ -188,14 +188,17 @@ impl ProviderBuilder { /// 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) -> L::Provider + pub fn on_provider(self, provider: P) -> F::Provider where L: ProviderLayer, + F: TxFiller + ProviderLayer, P: Provider, T: Transport + Clone, N: Network, { - self.layer.layer(provider) + let Self { layer, filler, .. } = self; + let stack = Stack::new(layer, filler); + stack.layer(provider) } /// Finish the layer stack by providing a root [`RpcClient`], outputting @@ -203,9 +206,10 @@ impl ProviderBuilder { /// /// This is a convenience function for /// `ProviderBuilder::provider`. - pub fn on_client(self, client: RpcClient) -> L::Provider + pub fn on_client(self, client: RpcClient) -> F::Provider where L: ProviderLayer, T, N>, + F: TxFiller + ProviderLayer, T: Transport + Clone, N: Network, { @@ -215,9 +219,10 @@ impl ProviderBuilder { /// Finish the layer stack by providing a connection string for a built-in /// transport type, outputting the final [`Provider`] type with all stack /// components. - pub async fn on_builtin(self, s: &str) -> Result + pub async fn on_builtin(self, s: &str) -> Result where L: ProviderLayer, BoxTransport, N>, + F: TxFiller + ProviderLayer, N: Network, { let connect: BuiltInConnectionString = s.parse()?; @@ -230,13 +235,14 @@ impl ProviderBuilder { pub async fn on_ws( self, connect: alloy_transport_ws::WsConnect, - ) -> Result + ) -> Result where L: ProviderLayer< RootProvider, alloy_pubsub::PubSubFrontend, N, >, + F: TxFiller + ProviderLayer, N: Network, { let client = ClientBuilder::default().ws(connect).await?; @@ -248,7 +254,7 @@ impl ProviderBuilder { pub async fn on_ipc( self, connect: alloy_transport_ipc::IpcConnect, - ) -> Result + ) -> Result where alloy_transport_ipc::IpcConnect: alloy_pubsub::PubSubConnect, L: ProviderLayer< @@ -256,6 +262,7 @@ impl ProviderBuilder { alloy_pubsub::PubSubFrontend, N, >, + F: TxFiller + ProviderLayer, N: Network, { let client = ClientBuilder::default().ipc(connect).await?; @@ -264,24 +271,31 @@ impl ProviderBuilder { /// Build this provider with an Reqwest HTTP transport. #[cfg(feature = "reqwest")] - pub fn on_http(self, url: url::Url) -> Result + pub fn on_http(self, url: url::Url) -> Result where L: ProviderLayer, alloy_transport_http::Http, N>, + F: TxFiller + ProviderLayer, N>, N: Network, { let client = ClientBuilder::default().http(url); Ok(self.on_client(client)) } - /// Build this provider with an Hyper HTTP transport. + /// Build this provider with an Hyper HTTP qransport. #[cfg(feature = "hyper")] - pub fn on_hyper_http(self, url: url::Url) -> Result + pub fn on_hyper_http(self, url: url::Url) -> Result where L: ProviderLayer< crate::HyperProvider, alloy_transport_http::Http, N, >, + F: TxFiller + + ProviderLayer< + L::Provider, + alloy_transport_http::Http, + N, + >, N: Network, { let client = ClientBuilder::default().hyper_http(url); diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs index a2226a6c1cd..336fb3ed95c 100644 --- a/crates/provider/src/layers/join_fill.rs +++ b/crates/provider/src/layers/join_fill.rs @@ -102,7 +102,7 @@ pub trait TxFiller: Clone + Send + Sync { type Fillable: Send + Sync + 'static; /// Joins this filler with another filler to compose multiple fillers. - fn join_with(self, other: T) -> JoinFill + fn join_with(self, other: T) -> JoinFill where T: TxFiller, { @@ -142,27 +142,21 @@ pub trait TxFiller: Clone + Send + Sync { /// by joining two [`TxFiller`]s. This struct is itself a [`TxFiller`], /// and can be nested to compose any number of fill layers. #[derive(Debug, Clone)] -pub struct JoinFill { +pub struct JoinFill { left: L, right: R, - _network: PhantomData N>, } -impl JoinFill { +impl JoinFill { /// Creates a new `JoinFill` with the given layers. pub fn new(left: L, right: R) -> Self { - Self { left, right, _network: PhantomData } + Self { left, right } } } -impl JoinFill -where - L: TxFiller, - R: TxFiller, - N: Network, -{ +impl JoinFill { /// Get a request for the left filler, if the left filler is ready. - async fn prepare_left( + async fn prepare_left( &self, provider: &P, tx: &N::TransactionRequest, @@ -170,6 +164,8 @@ where where P: Provider, T: Transport + Clone, + L: TxFiller, + N: Network, { if self.left.ready(tx) { self.left.prepare(provider, tx).await.map(Some) @@ -179,7 +175,7 @@ where } /// Get a prepare for the right filler, if the right filler is ready. - async fn prepare_right( + async fn prepare_right( &self, provider: &P, tx: &N::TransactionRequest, @@ -187,6 +183,8 @@ where where P: Provider, T: Transport + Clone, + R: TxFiller, + N: Network, { if self.right.ready(tx) { self.right.prepare(provider, tx).await.map(Some) @@ -196,7 +194,7 @@ where } } -impl TxFiller for JoinFill +impl TxFiller for JoinFill where L: TxFiller, R: TxFiller, @@ -230,7 +228,7 @@ where } } -impl ProviderLayer for JoinFill +impl ProviderLayer for JoinFill where L: TxFiller, R: TxFiller, @@ -238,7 +236,7 @@ where T: alloy_transport::Transport + Clone, N: Network, { - type Provider = FillProvider, P, T, N>; + type Provider = FillProvider, P, T, N>; fn layer(&self, inner: P) -> Self::Provider { FillProvider::new(inner, self.clone()) } @@ -278,7 +276,7 @@ where pub fn join_with>( self, other: Other, - ) -> FillProvider, P, T, N> { + ) -> FillProvider, P, T, N> { self.filler.join_with(other).layer(self.inner) } } From e40b1c8bee9b1739b1f51b2b446bacac4a719811 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 2 Apr 2024 15:32:01 -0700 Subject: [PATCH 21/45] refactor: protect against double-fill and add anvil shortcut --- crates/provider/src/builder.rs | 76 ++++++++++++++++++------- crates/provider/src/layers/gas.rs | 62 +++----------------- crates/provider/src/layers/join_fill.rs | 4 +- crates/provider/src/layers/nonce.rs | 47 +++------------ crates/provider/src/layers/signer.rs | 17 +----- 5 files changed, 75 insertions(+), 131 deletions(-) diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index 094f4bb4465..06205eab995 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -7,6 +7,9 @@ use alloy_rpc_client::{BuiltInConnectionString, ClientBuilder, RpcClient}; use alloy_transport::{BoxTransport, Transport, TransportError, TransportResult}; use std::marker::PhantomData; +/// The recommended filler. +type RecommendFiller = JoinFill, NonceFiller>; + /// A layering abstraction in the vein of [`tower::Layer`] /// /// [`tower::Layer`]: https://docs.rs/tower/latest/tower/trait.Layer.html @@ -115,6 +118,27 @@ impl Default for ProviderBuilder { } } +impl ProviderBuilder { + /// Add preconfigured set of layers handling gas estimation and nonce management + pub fn with_recommended_fillers(self) -> ProviderBuilder { + self.filler(GasFiller).filler(NonceFiller::default()) + } + + /// Add gas estimation to the stack being built. + /// + /// See [`GasFiller`] + pub fn with_gas_estimation(self) -> ProviderBuilder, N> { + self.filler(GasFiller) + } + + /// Add nonce management to the stack being built. + /// + /// See [`NonceManager`] + pub fn with_nonce_management(self) -> ProviderBuilder, N> { + self.filler(NonceFiller::default()) + } +} + impl ProviderBuilder { /// Add a layer to the stack being built. This is similar to /// [`tower::ServiceBuilder::layer`]. @@ -153,27 +177,6 @@ impl ProviderBuilder { self.layer(SignerLayer::new(signer)) } - /// Add gas estimation to the stack being built. - /// - /// See [`GasFiller`] - pub fn with_gas_estimation(self) -> ProviderBuilder, N> { - self.filler(GasFiller) - } - - /// Add nonce management to the stack being built. - /// - /// See [`NonceManager`] - pub fn with_nonce_management(self) -> ProviderBuilder, N> { - self.filler(NonceFiller::default()) - } - - /// Add preconfigured set of layers handling gas estimation and nonce management - pub fn with_recommended_layers( - self, - ) -> ProviderBuilder, NonceFiller>, N> { - self.with_gas_estimation().with_nonce_management() - } - /// Change the network. /// /// By default, the network is `Ethereum`. This method must be called to configure a different @@ -303,6 +306,37 @@ impl ProviderBuilder { } } +#[cfg(all(test, feature = "reqwest"))] +impl ProviderBuilder { + /// Build this provider with anvil, using an Reqwest HTTP transport. This + /// function configures a signer backed by anvil keys, and is intended for + /// use in tests. + pub fn on_anvil_with_signer(self) -> (F::Provider, alloy_node_bindings::AnvilInstance) + where + L: ProviderLayer< + crate::layers::SignerProvider< + alloy_transport_http::Http, + crate::ReqwestProvider, + crate::network::EthereumSigner, + Ethereum, + >, + alloy_transport_http::Http, + Ethereum, + >, + F: TxFiller + + ProviderLayer, Ethereum>, + { + let anvil = alloy_node_bindings::Anvil::new().spawn(); + let url = anvil.endpoint().parse().unwrap(); + + let wallet = alloy_signer_wallet::Wallet::from(anvil.keys()[0].clone()); + + let this = self.signer(crate::network::EthereumSigner::from(wallet)); + + (this.on_reqwest_http(url).unwrap(), anvil) + } +} + // Copyright (c) 2019 Tower Contributors // Permission is hereby granted, free of charge, to any diff --git a/crates/provider/src/layers/gas.rs b/crates/provider/src/layers/gas.rs index c1ba2302371..54f620e7f79 100644 --- a/crates/provider/src/layers/gas.rs +++ b/crates/provider/src/layers/gas.rs @@ -1,7 +1,7 @@ use crate::{ - layers::{FillProvider, FillerControlFlow, TxFiller}, + layers::{FillerControlFlow, TxFiller}, utils::Eip1559Estimation, - Provider, ProviderLayer, + Provider, }; use alloy_json_rpc::RpcError; use alloy_network::{Network, TransactionBuilder}; @@ -42,18 +42,6 @@ use futures::FutureExt; #[derive(Debug, Clone, Copy, Default)] pub struct GasFillerConfig; -impl ProviderLayer for GasFillerConfig -where - P: Provider, - T: alloy_transport::Transport + Clone, - N: Network, -{ - type Provider = FillProvider; - fn layer(&self, inner: P) -> Self::Provider { - FillProvider::new(inner, GasFiller) - } -} - /// An enum over the different types of gas fillable. #[allow(unreachable_pub)] #[doc(hidden)] @@ -244,27 +232,13 @@ impl TxFiller for GasFiller { mod tests { use super::*; use crate::ProviderBuilder; - use alloy_network::EthereumSigner; - use alloy_node_bindings::Anvil; - use alloy_primitives::{address, U256}; - use alloy_rpc_client::RpcClient; + use alloy_primitives::address; use alloy_rpc_types::TransactionRequest; - use alloy_transport_http::Http; - use reqwest::Client; #[tokio::test] async fn no_gas_price_or_limit() { - let anvil = Anvil::new().spawn(); - let url = anvil.endpoint().parse().unwrap(); - let http = Http::::new(url); - - let wallet = alloy_signer_wallet::Wallet::from(anvil.keys()[0].clone()); - - let provider = ProviderBuilder::new() - .with_nonce_management() - .with_gas_estimation() - .signer(EthereumSigner::from(wallet)) - .on_http(url); + let (provider, anvil) = + ProviderBuilder::new().with_recommended_fillers().on_anvil_with_signer(); // GasEstimationLayer requires chain_id to be set to handle EIP-1559 tx let tx = TransactionRequest { @@ -285,17 +259,8 @@ mod tests { #[tokio::test] async fn no_gas_limit() { - let anvil = Anvil::new().spawn(); - let url = anvil.endpoint().parse().unwrap(); - let http = Http::::new(url); - - let wallet = alloy_signer_wallet::Wallet::from(anvil.keys()[0].clone()); - - let provider = ProviderBuilder::new() - .with_nonce_management() - .with_gas_estimation() - .signer(EthereumSigner::from(wallet)) - .on_http(url); + let (provider, anvil) = + ProviderBuilder::new().with_recommended_fillers().on_anvil_with_signer(); let gas_price = provider.get_gas_price().await.unwrap(); let tx = TransactionRequest { @@ -315,17 +280,8 @@ mod tests { #[tokio::test] async fn non_eip1559_network() { - let anvil = Anvil::new().arg("--hardfork").arg("frontier").spawn(); - let url = anvil.endpoint().parse().unwrap(); - let http = Http::::new(url); - - let wallet = alloy_signer_wallet::Wallet::from(anvil.keys()[0].clone()); - - let provider = ProviderBuilder::new() - .with_nonce_management() - .with_gas_estimation() - .signer(EthereumSigner::from(wallet)) - .on_http(url); + let (provider, anvil) = + ProviderBuilder::new().with_recommended_fillers().on_anvil_with_signer(); let tx = TransactionRequest { from: Some(anvil.addresses()[0]), diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/layers/join_fill.rs index 336fb3ed95c..db0be13fbaf 100644 --- a/crates/provider/src/layers/join_fill.rs +++ b/crates/provider/src/layers/join_fill.rs @@ -141,7 +141,7 @@ pub trait TxFiller: Clone + Send + Sync { /// A layer that can fill in a `TransactionRequest` with additional information /// by joining two [`TxFiller`]s. This struct is itself a [`TxFiller`], /// and can be nested to compose any number of fill layers. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub struct JoinFill { left: L, right: R, @@ -149,7 +149,7 @@ pub struct JoinFill { impl JoinFill { /// Creates a new `JoinFill` with the given layers. - pub fn new(left: L, right: R) -> Self { + pub const fn new(left: L, right: R) -> Self { Self { left, right } } } diff --git a/crates/provider/src/layers/nonce.rs b/crates/provider/src/layers/nonce.rs index 1a85b4418a4..6f500348028 100644 --- a/crates/provider/src/layers/nonce.rs +++ b/crates/provider/src/layers/nonce.rs @@ -1,6 +1,6 @@ use crate::{ - layers::{FillProvider, FillerControlFlow, TxFiller}, - Provider, ProviderLayer, + layers::{FillerControlFlow, TxFiller}, + Provider, }; use alloy_network::{Network, TransactionBuilder}; use alloy_primitives::Address; @@ -41,18 +41,6 @@ use tokio::sync::Mutex; #[derive(Debug, Clone, Copy)] pub struct NonceFillerConfig; -impl ProviderLayer for NonceFillerConfig -where - P: Provider, - T: alloy_transport::Transport + Clone, - N: Network, -{ - type Provider = FillProvider; - fn layer(&self, inner: P) -> Self::Provider { - FillProvider::new(inner, NonceFiller::default()) - } -} - /// A [`TxFiller`] that fills the nonce on transactions by keeping a local /// mapping of account addresses to their next nonce. #[derive(Debug, Clone, Default)] @@ -122,27 +110,14 @@ impl NonceFiller { #[cfg(test)] mod tests { use super::*; - use crate::{ProviderBuilder, RootProvider}; - use alloy_network::EthereumSigner; - use alloy_node_bindings::Anvil; + use crate::ProviderBuilder; use alloy_primitives::{address, U256}; - use alloy_rpc_client::RpcClient; use alloy_rpc_types::TransactionRequest; - use alloy_transport_http::Http; - use reqwest::Client; #[tokio::test] async fn no_nonce_if_sender_unset() { - let anvil = Anvil::new().spawn(); - let url = anvil.endpoint().parse().unwrap(); - let http = Http::::new(url); - - let wallet = alloy_signer_wallet::Wallet::from(anvil.keys()[0].clone()); - - let provider = ProviderBuilder::new() - .with_nonce_management() - .signer(EthereumSigner::from(wallet)) - .provider(RootProvider::new(RpcClient::new(http, true))); + let (provider, _anvil) = + ProviderBuilder::new().with_nonce_management().on_anvil_with_signer(); let tx = TransactionRequest { value: Some(U256::from(100)), @@ -158,16 +133,8 @@ mod tests { #[tokio::test] async fn increments_nonce() { - let anvil = Anvil::new().spawn(); - let url = anvil.endpoint().parse().unwrap(); - let http = Http::::new(url); - - let wallet = alloy_signer_wallet::Wallet::from(anvil.keys()[0].clone()); - - let provider = ProviderBuilder::new() - .with_nonce_management() - .signer(EthereumSigner::from(wallet)) - .provider(RootProvider::new(RpcClient::new(http, true))); + let (provider, anvil) = + ProviderBuilder::new().with_nonce_management().on_anvil_with_signer(); let from = anvil.addresses()[0]; let tx = TransactionRequest { diff --git a/crates/provider/src/layers/signer.rs b/crates/provider/src/layers/signer.rs index 63f0d00c6f4..00e14271927 100644 --- a/crates/provider/src/layers/signer.rs +++ b/crates/provider/src/layers/signer.rs @@ -99,26 +99,13 @@ where #[cfg(feature = "reqwest")] #[cfg(test)] mod tests { - use crate::{Provider, ProviderBuilder, RootProvider}; - use alloy_network::EthereumSigner; - use alloy_node_bindings::Anvil; + use crate::{Provider, ProviderBuilder}; use alloy_primitives::{address, b256, U256}; - use alloy_rpc_client::RpcClient; use alloy_rpc_types::TransactionRequest; - use alloy_transport_http::Http; - use reqwest::Client; #[tokio::test] async fn poc() { - let anvil = Anvil::new().spawn(); - let url = anvil.endpoint().parse().unwrap(); - let http = Http::::new(url); - - let wallet = alloy_signer_wallet::Wallet::from(anvil.keys()[0].clone()); - - let provider = ProviderBuilder::new() - .signer(EthereumSigner::from(wallet)) - .on_provider(RootProvider::new(RpcClient::new(http, true))); + let (provider, _anvil) = ProviderBuilder::new().on_anvil_with_signer(); let tx = TransactionRequest { nonce: Some(0), From f663dd86b3256ba9605c6ed66c95505cce85a60e Mon Sep 17 00:00:00 2001 From: James Date: Tue, 2 Apr 2024 15:50:07 -0700 Subject: [PATCH 22/45] doc: improve docstrings for noncemanager and gas filler --- crates/provider/src/builder.rs | 2 +- crates/provider/src/layers/gas.rs | 79 ++++++++++++++++++++++------- crates/provider/src/layers/nonce.rs | 52 +++++++++++++------ 3 files changed, 100 insertions(+), 33 deletions(-) diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index 06205eab995..e44c9fad4a5 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -133,7 +133,7 @@ impl ProviderBuilder { /// Add nonce management to the stack being built. /// - /// See [`NonceManager`] + /// See [`NonceFiller`] pub fn with_nonce_management(self) -> ProviderBuilder, N> { self.filler(NonceFiller::default()) } diff --git a/crates/provider/src/layers/gas.rs b/crates/provider/src/layers/gas.rs index 54f620e7f79..6307cf9a0ba 100644 --- a/crates/provider/src/layers/gas.rs +++ b/crates/provider/src/layers/gas.rs @@ -9,31 +9,37 @@ use alloy_rpc_types::BlockNumberOrTag; use alloy_transport::{Transport, TransportResult}; use futures::FutureExt; -/// A layer that populates gas related fields in transaction requests if unset. +/// A [`TxFiller`] that populates gas related fields in transaction requests if +/// unset. /// -/// Gas related fields are gas_price, gas_limit, max_fee_per_gas and max_priority_fee_per_gas. +/// Gas related fields are gas_price, gas_limit, max_fee_per_gas +/// max_priority_fee_per_gas and max_fee_per_blob_gas. /// -/// The layer fetches the estimations for these via the [`Provider::get_gas_price`], -/// [`Provider::estimate_gas`] and [`Provider::estimate_eip1559_fees`] methods. +/// The layer fetches the estimations for these via the +/// [`Provider::get_gas_price`], [`Provider::estimate_gas`] and +/// [`Provider::estimate_eip1559_fees`] methods. /// -/// If you use layers that redirect the behavior of [`Provider::send_transaction`] (e.g. -/// [`crate::layers::SignerLayer`]), you should add this layer before those. +/// ## Note: /// -/// Note: -/// - If none of the gas related fields are set, the layer first assumes it's a EIP-1559 tx and -/// populates the gas_limit, max_fee_per_gas and max_priority_fee_per_gas fields. -/// - If the network does not support EIP-1559, it will process as a legacy tx and populate the -/// gas_limit and gas_price fields. -/// - If the gas_price is already set by the user, it will process as a legacy tx and populate -/// the gas_limit field if unset. +/// The layer will populate gas fields based on the following logic: +/// - if `gas_price` is set, it will process as a legacy tx and populate the +/// `gas_limit` field if unset. +/// - if `access_list` is set, it will process as a 2930 tx and populate the +/// `gas_limit` and `gas_price` field if unset. +/// - if `blob_sidecar` is set, it will process as a 4844 tx and populate the +/// `gas_limit`, `max_fee_per_gas`, `max_priority_fee_per_gas` and +/// `max_fee_per_blob_gas` fields if unset. +/// - Otherwise, it will process as a EIP-1559 tx and populate the `gas_limit`, +/// `max_fee_per_gas` and `max_priority_fee_per_gas` fields if unset. +/// - If the network does not support EIP-1559, it will fallback to the legacy +/// tx and populate the `gas_limit` and `gas_price` fields if unset. /// /// # Example /// -/// ```rs +/// ``` /// # async fn test>(transport: T, signer: S) { /// let provider = ProviderBuilder::new() -/// .with_nonce_management() -/// .with_gas_estimation() +/// .with_recommended_fillers() /// .signer(EthereumSigner::from(signer)) // note the order! /// .provider(RootProvider::new(transport)); /// @@ -54,6 +60,40 @@ pub enum GasFillable { /// A [`TxFiller`] that populates gas related fields in transaction requests if /// unset. +/// +/// Gas related fields are gas_price, gas_limit, max_fee_per_gas +/// max_priority_fee_per_gas and max_fee_per_blob_gas. +/// +/// The layer fetches the estimations for these via the +/// [`Provider::get_gas_price`], [`Provider::estimate_gas`] and +/// [`Provider::estimate_eip1559_fees`] methods. +/// +/// ## Note: +/// +/// The layer will populate gas fields based on the following logic: +/// - if `gas_price` is set, it will process as a legacy tx and populate the +/// `gas_limit` field if unset. +/// - if `access_list` is set, it will process as a 2930 tx and populate the +/// `gas_limit` and `gas_price` field if unset. +/// - if `blob_sidecar` is set, it will process as a 4844 tx and populate the +/// `gas_limit`, `max_fee_per_gas`, `max_priority_fee_per_gas` and +/// `max_fee_per_blob_gas` fields if unset. +/// - Otherwise, it will process as a EIP-1559 tx and populate the `gas_limit`, +/// `max_fee_per_gas` and `max_priority_fee_per_gas` fields if unset. +/// - If the network does not support EIP-1559, it will fallback to the legacy +/// tx and populate the `gas_limit` and `gas_price` fields if unset. +/// +/// # Example +/// +/// ``` +/// # async fn test>(transport: T, signer: S) { +/// let provider = ProviderBuilder::new() +/// .with_recommended_fillers() +/// .signer(EthereumSigner::from(signer)) // note the order! +/// .provider(RootProvider::new(transport)); +/// +/// provider.send_transaction(TransactionRequest::default()).await; +/// # } #[derive(Debug, Clone, Copy, Default)] pub struct GasFiller; @@ -202,7 +242,12 @@ impl TxFiller for GasFiller { } else if tx.blob_sidecar().is_some() { self.prepare_4844(provider, tx).await } else { - self.prepare_1559(provider, tx).await + match self.prepare_1559(provider, tx).await { + // fallback to legacy + Ok(estimate) => Ok(estimate), + Err(RpcError::UnsupportedFeature(_)) => self.prepare_legacy(provider, tx).await, + Err(e) => Err(e), + } } } diff --git a/crates/provider/src/layers/nonce.rs b/crates/provider/src/layers/nonce.rs index 6f500348028..19abb738a34 100644 --- a/crates/provider/src/layers/nonce.rs +++ b/crates/provider/src/layers/nonce.rs @@ -9,24 +9,24 @@ use dashmap::DashMap; use std::sync::Arc; use tokio::sync::Mutex; -/// A layer that fills nonces on transactions. +/// A [`TxFiller`] that fills nonces on transactions. /// -/// The layer will fetch the transaction count for any new account it sees, store it locally and -/// increment the locally stored nonce as transactions are sent via [`Provider::send_transaction`]. -/// -/// If you use layers that redirect the behavior of [`Provider::send_transaction`] (e.g. -/// [`SignerLayer`]), you should add this layer before those. +/// The filler will fetch the transaction count via +/// [`Provider::get_transaction_count`] for any new account it sees, store it +/// locally and increment the locally stored nonce as transactions are sent via +/// [`Provider::send_transaction`]. /// /// # Note /// -/// - If the transaction request does not have a sender set, this layer will not fill nonces. -/// - Using two providers with their own nonce layer can potentially fill invalid nonces if -/// transactions are sent from the same address, as the next nonce to be used is cached internally -/// in the layer. +/// - If the transaction request does not have a sender set, this layer will +/// not fill nonces. +/// - Using two providers with their own nonce layer can potentially fill +/// invalid nonces if transactions are sent from the same address, as the next +/// nonce to be used is cached internally in the layer. /// /// # Example /// -/// ```rs +/// ``` /// # async fn test>(transport: T, signer: S) { /// let provider = ProviderBuilder::new() /// .with_nonce_management() @@ -36,13 +36,35 @@ use tokio::sync::Mutex; /// provider.send_transaction(TransactionRequest::default()).await; /// # } /// ``` -/// -/// [`SignerLayer`]: crate::layers::SignerLayer #[derive(Debug, Clone, Copy)] pub struct NonceFillerConfig; -/// A [`TxFiller`] that fills the nonce on transactions by keeping a local -/// mapping of account addresses to their next nonce. +/// A [`TxFiller`] that fills nonces on transactions. +/// +/// The filler will fetch the transaction count for any new account it sees, +/// store it locally and increment the locally stored nonce as transactions are +/// sent via [`Provider::send_transaction`]. +/// +/// # Note +/// +/// - If the transaction request does not have a sender set, this layer will +/// not fill nonces. +/// - Using two providers with their own nonce layer can potentially fill +/// invalid nonces if transactions are sent from the same address, as the next +/// nonce to be used is cached internally in the layer. +/// +/// # Example +/// +/// ``` +/// # async fn test>(transport: T, signer: S) { +/// let provider = ProviderBuilder::new() +/// .with_nonce_management() +/// .signer(EthereumSigner::from(signer)) // note the order! +/// .provider(RootProvider::new(transport)); +/// +/// provider.send_transaction(TransactionRequest::default()).await; +/// # } +/// ``` #[derive(Debug, Clone, Default)] pub struct NonceFiller { nonces: DashMap>>>, From ffef2ab7946e0ecfcc6513ba52b6a455b377894b Mon Sep 17 00:00:00 2001 From: James Date: Tue, 2 Apr 2024 15:50:47 -0700 Subject: [PATCH 23/45] fix: delete unused types --- crates/provider/src/layers/gas.rs | 39 ----------------------------- crates/provider/src/layers/mod.rs | 4 +-- crates/provider/src/layers/nonce.rs | 30 ---------------------- 3 files changed, 2 insertions(+), 71 deletions(-) diff --git a/crates/provider/src/layers/gas.rs b/crates/provider/src/layers/gas.rs index 6307cf9a0ba..9aed3c71482 100644 --- a/crates/provider/src/layers/gas.rs +++ b/crates/provider/src/layers/gas.rs @@ -9,45 +9,6 @@ use alloy_rpc_types::BlockNumberOrTag; use alloy_transport::{Transport, TransportResult}; use futures::FutureExt; -/// A [`TxFiller`] that populates gas related fields in transaction requests if -/// unset. -/// -/// Gas related fields are gas_price, gas_limit, max_fee_per_gas -/// max_priority_fee_per_gas and max_fee_per_blob_gas. -/// -/// The layer fetches the estimations for these via the -/// [`Provider::get_gas_price`], [`Provider::estimate_gas`] and -/// [`Provider::estimate_eip1559_fees`] methods. -/// -/// ## Note: -/// -/// The layer will populate gas fields based on the following logic: -/// - if `gas_price` is set, it will process as a legacy tx and populate the -/// `gas_limit` field if unset. -/// - if `access_list` is set, it will process as a 2930 tx and populate the -/// `gas_limit` and `gas_price` field if unset. -/// - if `blob_sidecar` is set, it will process as a 4844 tx and populate the -/// `gas_limit`, `max_fee_per_gas`, `max_priority_fee_per_gas` and -/// `max_fee_per_blob_gas` fields if unset. -/// - Otherwise, it will process as a EIP-1559 tx and populate the `gas_limit`, -/// `max_fee_per_gas` and `max_priority_fee_per_gas` fields if unset. -/// - If the network does not support EIP-1559, it will fallback to the legacy -/// tx and populate the `gas_limit` and `gas_price` fields if unset. -/// -/// # Example -/// -/// ``` -/// # async fn test>(transport: T, signer: S) { -/// let provider = ProviderBuilder::new() -/// .with_recommended_fillers() -/// .signer(EthereumSigner::from(signer)) // note the order! -/// .provider(RootProvider::new(transport)); -/// -/// provider.send_transaction(TransactionRequest::default()).await; -/// # } -#[derive(Debug, Clone, Copy, Default)] -pub struct GasFillerConfig; - /// An enum over the different types of gas fillable. #[allow(unreachable_pub)] #[doc(hidden)] diff --git a/crates/provider/src/layers/mod.rs b/crates/provider/src/layers/mod.rs index c56e892386c..6b35b5fa7d6 100644 --- a/crates/provider/src/layers/mod.rs +++ b/crates/provider/src/layers/mod.rs @@ -6,10 +6,10 @@ mod signer; pub use signer::{SignerLayer, SignerProvider}; mod nonce; -pub use nonce::{NonceFiller, NonceFillerConfig}; +pub use nonce::NonceFiller; mod gas; -pub use gas::{GasFiller, GasFillerConfig}; +pub use gas::GasFiller; mod join_fill; pub use join_fill::{FillProvider, FillerControlFlow, JoinFill, TxFiller}; diff --git a/crates/provider/src/layers/nonce.rs b/crates/provider/src/layers/nonce.rs index 19abb738a34..62be34f7ec6 100644 --- a/crates/provider/src/layers/nonce.rs +++ b/crates/provider/src/layers/nonce.rs @@ -9,36 +9,6 @@ use dashmap::DashMap; use std::sync::Arc; use tokio::sync::Mutex; -/// A [`TxFiller`] that fills nonces on transactions. -/// -/// The filler will fetch the transaction count via -/// [`Provider::get_transaction_count`] for any new account it sees, store it -/// locally and increment the locally stored nonce as transactions are sent via -/// [`Provider::send_transaction`]. -/// -/// # Note -/// -/// - If the transaction request does not have a sender set, this layer will -/// not fill nonces. -/// - Using two providers with their own nonce layer can potentially fill -/// invalid nonces if transactions are sent from the same address, as the next -/// nonce to be used is cached internally in the layer. -/// -/// # Example -/// -/// ``` -/// # async fn test>(transport: T, signer: S) { -/// let provider = ProviderBuilder::new() -/// .with_nonce_management() -/// .signer(EthereumSigner::from(signer)) // note the order! -/// .provider(RootProvider::new(transport)); -/// -/// provider.send_transaction(TransactionRequest::default()).await; -/// # } -/// ``` -#[derive(Debug, Clone, Copy)] -pub struct NonceFillerConfig; - /// A [`TxFiller`] that fills nonces on transactions. /// /// The filler will fetch the transaction count for any new account it sees, From 741eb02b437aef4a4fcd455a7d7c5eadc1e44d67 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 2 Apr 2024 15:51:45 -0700 Subject: [PATCH 24/45] refactor: layers to fillers --- crates/provider/src/builder.rs | 4 ++-- crates/provider/src/{layers => fillers}/gas.rs | 2 +- crates/provider/src/{layers => fillers}/join_fill.rs | 0 crates/provider/src/{layers => fillers}/mod.rs | 0 crates/provider/src/{layers => fillers}/nonce.rs | 2 +- crates/provider/src/{layers => fillers}/signer.rs | 0 crates/provider/src/lib.rs | 2 +- 7 files changed, 5 insertions(+), 5 deletions(-) rename crates/provider/src/{layers => fillers}/gas.rs (99%) rename crates/provider/src/{layers => fillers}/join_fill.rs (100%) rename crates/provider/src/{layers => fillers}/mod.rs (100%) rename crates/provider/src/{layers => fillers}/nonce.rs (99%) rename crates/provider/src/{layers => fillers}/signer.rs (100%) diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index e44c9fad4a5..336dda43a89 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -1,5 +1,5 @@ use crate::{ - layers::{FillerControlFlow, GasFiller, JoinFill, NonceFiller, SignerLayer, TxFiller}, + fillers::{FillerControlFlow, GasFiller, JoinFill, NonceFiller, SignerLayer, TxFiller}, Provider, RootProvider, }; use alloy_network::{Ethereum, Network}; @@ -314,7 +314,7 @@ impl ProviderBuilder { pub fn on_anvil_with_signer(self) -> (F::Provider, alloy_node_bindings::AnvilInstance) where L: ProviderLayer< - crate::layers::SignerProvider< + crate::fillers::SignerProvider< alloy_transport_http::Http, crate::ReqwestProvider, crate::network::EthereumSigner, diff --git a/crates/provider/src/layers/gas.rs b/crates/provider/src/fillers/gas.rs similarity index 99% rename from crates/provider/src/layers/gas.rs rename to crates/provider/src/fillers/gas.rs index 9aed3c71482..015ccba9e3d 100644 --- a/crates/provider/src/layers/gas.rs +++ b/crates/provider/src/fillers/gas.rs @@ -1,5 +1,5 @@ use crate::{ - layers::{FillerControlFlow, TxFiller}, + fillers::{FillerControlFlow, TxFiller}, utils::Eip1559Estimation, Provider, }; diff --git a/crates/provider/src/layers/join_fill.rs b/crates/provider/src/fillers/join_fill.rs similarity index 100% rename from crates/provider/src/layers/join_fill.rs rename to crates/provider/src/fillers/join_fill.rs diff --git a/crates/provider/src/layers/mod.rs b/crates/provider/src/fillers/mod.rs similarity index 100% rename from crates/provider/src/layers/mod.rs rename to crates/provider/src/fillers/mod.rs diff --git a/crates/provider/src/layers/nonce.rs b/crates/provider/src/fillers/nonce.rs similarity index 99% rename from crates/provider/src/layers/nonce.rs rename to crates/provider/src/fillers/nonce.rs index 62be34f7ec6..3d7e7311f46 100644 --- a/crates/provider/src/layers/nonce.rs +++ b/crates/provider/src/fillers/nonce.rs @@ -1,5 +1,5 @@ use crate::{ - layers::{FillerControlFlow, TxFiller}, + fillers::{FillerControlFlow, TxFiller}, Provider, }; use alloy_network::{Network, TransactionBuilder}; diff --git a/crates/provider/src/layers/signer.rs b/crates/provider/src/fillers/signer.rs similarity index 100% rename from crates/provider/src/layers/signer.rs rename to crates/provider/src/fillers/signer.rs diff --git a/crates/provider/src/lib.rs b/crates/provider/src/lib.rs index f4e7a5ab352..7eb241d4fff 100644 --- a/crates/provider/src/lib.rs +++ b/crates/provider/src/lib.rs @@ -39,7 +39,7 @@ extern crate tracing; mod builder; pub use builder::{Identity, ProviderBuilder, ProviderLayer, Stack}; -pub mod layers; +pub mod fillers; mod chain; From cdb9f80cb927b4c88613584e697bfe6194cd59b9 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 2 Apr 2024 16:15:15 -0700 Subject: [PATCH 25/45] feature: chain id filler --- crates/provider/src/builder.rs | 27 +++++++- crates/provider/src/fillers/chain_id.rs | 89 +++++++++++++++++++++++++ crates/provider/src/fillers/mod.rs | 13 +++- 3 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 crates/provider/src/fillers/chain_id.rs diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index 336dda43a89..0143c38eef2 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -1,5 +1,7 @@ use crate::{ - fillers::{FillerControlFlow, GasFiller, JoinFill, NonceFiller, SignerLayer, TxFiller}, + fillers::{ + ChainIdFiller, FillerControlFlow, GasFiller, JoinFill, NonceFiller, SignerLayer, TxFiller, + }, Provider, RootProvider, }; use alloy_network::{Ethereum, Network}; @@ -8,7 +10,8 @@ use alloy_transport::{BoxTransport, Transport, TransportError, TransportResult}; use std::marker::PhantomData; /// The recommended filler. -type RecommendFiller = JoinFill, NonceFiller>; +type RecommendFiller = + JoinFill, NonceFiller>, ChainIdFiller>; /// A layering abstraction in the vein of [`tower::Layer`] /// @@ -121,7 +124,7 @@ impl Default for ProviderBuilder { impl ProviderBuilder { /// Add preconfigured set of layers handling gas estimation and nonce management pub fn with_recommended_fillers(self) -> ProviderBuilder { - self.filler(GasFiller).filler(NonceFiller::default()) + self.filler(GasFiller).filler(NonceFiller::default()).filler(ChainIdFiller::default()) } /// Add gas estimation to the stack being built. @@ -137,6 +140,24 @@ impl ProviderBuilder { pub fn with_nonce_management(self) -> ProviderBuilder, N> { self.filler(NonceFiller::default()) } + + /// Add a chain ID filler to the stack being built. The filler will attempt + /// to fetch the chain ID from the provider using + /// [`Provider::get_chain_id`]. the first time a transaction is prepared, + /// and will cache it for future transactions. + pub fn fetch_chain_id(self) -> ProviderBuilder, N> { + self.filler(ChainIdFiller::default()) + } + + /// Add a specific chain ID to the stack being built. The filler will + /// fill transactions with the provided chain ID, regardless of the chain ID + /// that the provider reports via [`Provider::get_chain_id`]. + pub fn with_chain_id( + self, + chain_id: u64, + ) -> ProviderBuilder, N> { + self.filler(ChainIdFiller::new(Some(chain_id))) + } } impl ProviderBuilder { diff --git a/crates/provider/src/fillers/chain_id.rs b/crates/provider/src/fillers/chain_id.rs new file mode 100644 index 00000000000..ed9efd70092 --- /dev/null +++ b/crates/provider/src/fillers/chain_id.rs @@ -0,0 +1,89 @@ +use std::sync::{Arc, OnceLock}; + +use alloy_network::TransactionBuilder; +use alloy_transport::TransportResult; + +use crate::fillers::{FillerControlFlow, TxFiller}; + +/// A [`TxFiller`] that populates the chain ID of a transaction. +/// +/// If a chain ID is provided, it will be used for filling. If a chain ID +/// is not provided, the filler will attempt to fetch the chain ID from the +/// provider the first time a transaction is prepared, and will cache it for +/// future transactions. +/// +/// Transactions that already have a chain_id set by the user will not be +/// modified. +/// +/// # Example +/// +/// ``` +/// # async fn test>(transport: T, signer: S) { +/// let provider = ProviderBuilder::new() +/// .with_chain_id(1) +/// .signer(EthereumSigner::from(signer)) // note the order! +/// .provider(RootProvider::new(transport)); +/// +/// provider.send_transaction(TransactionRequest::default()).await; +/// # } +#[derive(Debug, Clone, Default, PartialEq, Eq)] +pub struct ChainIdFiller(Arc>); + +impl ChainIdFiller { + /// Create a new [`ChainIdFiller`] with an optional chain ID. + /// + /// If a chain ID is provided, it will be used for filling. If a chain ID + /// is not provided, the filler will attempt to fetch the chain ID from the + /// provider the first time a transaction is prepared. + pub fn new(chain_id: Option) -> Self { + let lock = OnceLock::new(); + if let Some(chain_id) = chain_id { + lock.set(chain_id).expect("brand new"); + } + Self(Arc::new(lock)) + } +} + +impl TxFiller for ChainIdFiller { + type Fillable = u64; + + fn status( + &self, + tx: &::TransactionRequest, + ) -> FillerControlFlow { + if tx.chain_id.is_some() { + FillerControlFlow::Finished + } else { + FillerControlFlow::Ready + } + } + + async fn prepare( + &self, + provider: &P, + _tx: &::TransactionRequest, + ) -> TransportResult + where + P: crate::Provider, + T: alloy_transport::Transport + Clone, + { + match self.0.get().copied() { + Some(chain_id) => Ok(chain_id), + None => { + let chain_id = provider.get_chain_id().await?.as_limbs()[0]; + let chain_id = *self.0.get_or_init(|| chain_id); + Ok(chain_id) + } + } + } + + fn fill( + &self, + fillable: Self::Fillable, + tx: &mut ::TransactionRequest, + ) { + if tx.chain_id().is_none() { + tx.set_chain_id(fillable); + } + } +} diff --git a/crates/provider/src/fillers/mod.rs b/crates/provider/src/fillers/mod.rs index 6b35b5fa7d6..9edef70279f 100644 --- a/crates/provider/src/fillers/mod.rs +++ b/crates/provider/src/fillers/mod.rs @@ -1,7 +1,14 @@ -//! Provider layers. +//! Transaction Fillers //! -//! Layers decorate a `Provider`, transforming various inputs and outputs of the root provider, -//! depending on the layers used. +//! Fillers decorate a [`Provider`], filling transaction details before they +//! are sent to the network. Fillers are used to set the nonce, gas price, gas +//! limit, and other transaction details, and are called before any other layer. +//! +//! [`Provider`]: crate::Provider + +mod chain_id; +pub use chain_id::ChainIdFiller; + mod signer; pub use signer::{SignerLayer, SignerProvider}; From b1358ff5fd54a3e2bd6512f5774bcfd3d0483490 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 2 Apr 2024 20:55:57 -0700 Subject: [PATCH 26/45] refactor: send_transaction_inner and SendableTx --- crates/provider/Cargo.toml | 1 + crates/provider/src/builder.rs | 3 +- crates/provider/src/fillers/chain_id.rs | 31 ++++----- crates/provider/src/fillers/gas.rs | 8 ++- crates/provider/src/fillers/join_fill.rs | 65 +++++++++++++++---- crates/provider/src/fillers/nonce.rs | 7 ++- crates/provider/src/provider.rs | 80 ++++++++++++++++++++++-- 7 files changed, 160 insertions(+), 35 deletions(-) diff --git a/crates/provider/Cargo.toml b/crates/provider/Cargo.toml index e463af5f9f2..3c81c99d658 100644 --- a/crates/provider/Cargo.toml +++ b/crates/provider/Cargo.toml @@ -12,6 +12,7 @@ repository.workspace = true exclude.workspace = true [dependencies] +alloy-eips.workspace = true alloy-json-rpc.workspace = true alloy-network.workspace = true alloy-rpc-client.workspace = true diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index 0143c38eef2..a3f2c3fc926 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -2,6 +2,7 @@ use crate::{ fillers::{ ChainIdFiller, FillerControlFlow, GasFiller, JoinFill, NonceFiller, SignerLayer, TxFiller, }, + provider::SendableTx, Provider, RootProvider, }; use alloy_network::{Ethereum, Network}; @@ -46,7 +47,7 @@ where Ok(()) } - fn fill(&self, _to_fill: Self::Fillable, _tx: &mut N::TransactionRequest) { + fn fill(&self, _to_fill: Self::Fillable, _tx: &mut SendableTx) { // Do nothing } } diff --git a/crates/provider/src/fillers/chain_id.rs b/crates/provider/src/fillers/chain_id.rs index ed9efd70092..3591cf64438 100644 --- a/crates/provider/src/fillers/chain_id.rs +++ b/crates/provider/src/fillers/chain_id.rs @@ -1,9 +1,12 @@ use std::sync::{Arc, OnceLock}; -use alloy_network::TransactionBuilder; +use alloy_network::{Network, TransactionBuilder}; use alloy_transport::TransportResult; -use crate::fillers::{FillerControlFlow, TxFiller}; +use crate::{ + fillers::{FillerControlFlow, TxFiller}, + provider::SendableTx, +}; /// A [`TxFiller`] that populates the chain ID of a transaction. /// @@ -44,14 +47,11 @@ impl ChainIdFiller { } } -impl TxFiller for ChainIdFiller { +impl TxFiller for ChainIdFiller { type Fillable = u64; - fn status( - &self, - tx: &::TransactionRequest, - ) -> FillerControlFlow { - if tx.chain_id.is_some() { + fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow { + if tx.chain_id().is_some() { FillerControlFlow::Finished } else { FillerControlFlow::Ready @@ -61,10 +61,10 @@ impl TxFiller for ChainIdFiller { async fn prepare( &self, provider: &P, - _tx: &::TransactionRequest, + _tx: &N::TransactionRequest, ) -> TransportResult where - P: crate::Provider, + P: crate::Provider, T: alloy_transport::Transport + Clone, { match self.0.get().copied() { @@ -77,11 +77,12 @@ impl TxFiller for ChainIdFiller { } } - fn fill( - &self, - fillable: Self::Fillable, - tx: &mut ::TransactionRequest, - ) { + fn fill(&self, fillable: Self::Fillable, tx: &mut SendableTx) { + let tx = match tx { + SendableTx::Builder(tx) => tx, + _ => return, + }; + if tx.chain_id().is_none() { tx.set_chain_id(fillable); } diff --git a/crates/provider/src/fillers/gas.rs b/crates/provider/src/fillers/gas.rs index 015ccba9e3d..adb8d92288f 100644 --- a/crates/provider/src/fillers/gas.rs +++ b/crates/provider/src/fillers/gas.rs @@ -1,5 +1,6 @@ use crate::{ fillers::{FillerControlFlow, TxFiller}, + provider::SendableTx, utils::Eip1559Estimation, Provider, }; @@ -212,7 +213,12 @@ impl TxFiller for GasFiller { } } - fn fill(&self, fillable: Self::Fillable, tx: &mut ::TransactionRequest) { + fn fill(&self, fillable: Self::Fillable, tx: &mut SendableTx) { + let tx = match tx { + SendableTx::Builder(tx) => tx, + _ => return, + }; + match fillable { GasFillable::Legacy { gas_limit, gas_price } => { tx.set_gas_limit(gas_limit); diff --git a/crates/provider/src/fillers/join_fill.rs b/crates/provider/src/fillers/join_fill.rs index db0be13fbaf..5324d8e4866 100644 --- a/crates/provider/src/fillers/join_fill.rs +++ b/crates/provider/src/fillers/join_fill.rs @@ -1,4 +1,6 @@ -use crate::{PendingTransactionBuilder, Provider, ProviderLayer, RootProvider}; +use crate::{ + provider::SendableTx, PendingTransactionBuilder, Provider, ProviderLayer, RootProvider, +}; use alloy_network::{Ethereum, Network}; use alloy_transport::{Transport, TransportResult}; use async_trait::async_trait; @@ -114,6 +116,11 @@ pub trait TxFiller: Clone + Send + Sync { /// properties. fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow; + /// Returns `true` if the filler is should continnue filling. + fn continue_filling(&self, tx: &SendableTx) -> bool { + tx.as_builder().map(|tx| self.status(tx).is_ready()).unwrap_or_default() + } + /// Returns `true` if the filler is ready to fill in the transaction request. fn ready(&self, tx: &N::TransactionRequest) -> bool { self.status(tx).is_ready() @@ -135,12 +142,37 @@ pub trait TxFiller: Clone + Send + Sync { T: Transport + Clone; /// Fills in the transaction request with the fillable properties. - fn fill(&self, fillable: Self::Fillable, tx: &mut N::TransactionRequest); + fn fill(&self, fillable: Self::Fillable, tx: &mut SendableTx); + + /// Prepares and fills the transaction request with the fillable properties. + fn prepare_and_fill( + &self, + provider: &P, + tx: &mut SendableTx, + ) -> impl_future!(>) + where + P: Provider, + T: Transport + Clone, + { + async move { + if !tx.is_builder() { + return Ok(()); + } + + let fillable = self.prepare(provider, tx.as_builder().unwrap()).await?; + + self.fill(fillable, tx); + + Ok(()) + } + } } -/// A layer that can fill in a `TransactionRequest` with additional information -/// by joining two [`TxFiller`]s. This struct is itself a [`TxFiller`], -/// and can be nested to compose any number of fill layers. +/// A layer that can fill in a [`TransactionRequest`] with additional +/// information by joining two [`TxFiller`]s. This struct is itself a +/// [`TxFiller`], and can be nested to compose any number of fill layers. +/// +/// [`TransactionRequest`]: alloy_rpc_types::TransactionRequest #[derive(Debug, Clone, Copy)] pub struct JoinFill { left: L, @@ -218,7 +250,7 @@ where try_join!(self.prepare_left(provider, tx), self.prepare_right(provider, tx)) } - fn fill(&self, to_fill: Self::Fillable, tx: &mut N::TransactionRequest) { + fn fill(&self, to_fill: Self::Fillable, tx: &mut SendableTx) { if let Some(to_fill) = to_fill.0 { self.left.fill(to_fill, tx); }; @@ -245,7 +277,14 @@ where /// A [`Provider`] that applies one or more [`TxFiller`]s. /// /// Fills arbitrary properties in a transaction request by composing multiple -/// fill layers. +/// fill layers. This struct should always be the outermost layer in a provider +/// stack, and this is enforced when using [`ProviderBuilder::filler`] to +/// construct this layer. +/// +/// Users should NOT use this struct directly. Instead, use +/// [`ProviderBuilder::filler`] to construct and apply it to a stack. +/// +/// [`ProviderBuilder::filler`]: crate::ProviderBuilder::filler #[derive(Debug, Clone)] pub struct FillProvider @@ -294,14 +333,14 @@ where self.inner.root() } - async fn send_transaction( + async fn send_transaction_internal( &self, - mut tx: N::TransactionRequest, + mut tx: SendableTx, ) -> TransportResult> { let mut count = 0; - while self.filler.status(&tx).is_ready() { - let fillable = self.filler.prepare(self.root(), &tx).await?; - self.filler.fill(fillable, &mut tx); + + while self.filler.continue_filling(&tx) { + self.filler.prepare_and_fill(&self.inner, &mut tx).await?; count += 1; if count >= 20 { @@ -312,6 +351,6 @@ where } // Errors in tx building happen further down the stack. - self.inner.send_transaction(tx).await + self.inner.send_transaction_internal(tx).await } } diff --git a/crates/provider/src/fillers/nonce.rs b/crates/provider/src/fillers/nonce.rs index 3d7e7311f46..344a710beda 100644 --- a/crates/provider/src/fillers/nonce.rs +++ b/crates/provider/src/fillers/nonce.rs @@ -1,5 +1,6 @@ use crate::{ fillers::{FillerControlFlow, TxFiller}, + provider::SendableTx, Provider, }; use alloy_network::{Network, TransactionBuilder}; @@ -66,7 +67,11 @@ impl TxFiller for NonceFiller { self.get_next_nonce(provider, from).await } - fn fill(&self, nonce: Self::Fillable, tx: &mut N::TransactionRequest) { + fn fill(&self, nonce: Self::Fillable, tx: &mut SendableTx) { + let tx = match tx { + SendableTx::Builder(tx) => tx, + _ => return, + }; tx.set_nonce(nonce); } } diff --git a/crates/provider/src/provider.rs b/crates/provider/src/provider.rs index 47d1a8bc353..11a86ae929e 100644 --- a/crates/provider/src/provider.rs +++ b/crates/provider/src/provider.rs @@ -6,6 +6,7 @@ use crate::{ utils::{self, Eip1559Estimation, EstimatorFunction}, PendingTransactionBuilder, }; +use alloy_eips::eip2718::Encodable2718; use alloy_json_rpc::{RpcError, RpcParam, RpcReturn}; use alloy_network::{Ethereum, Network}; use alloy_primitives::{ @@ -46,6 +47,51 @@ use alloy_pubsub::{PubSubFrontend, Subscription}; /// See [`PollerBuilder`] for more details. pub type FilterPollerBuilder = PollerBuilder>; +/// A transaction that will be +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum SendableTx { + /// A transaction that is not yet signed. + Builder(N::TransactionRequest), + /// A transaction that is signed and fully constructed. + Envelope(N::TxEnvelope), +} + +impl SendableTx { + /// Fallible cast to an unbuilt transaction request. + pub fn as_mut_builder(&mut self) -> Option<&mut N::TransactionRequest> { + match self { + Self::Builder(tx) => Some(tx), + _ => None, + } + } + + /// Fallible cast to an unbuilt transaction request. + pub fn as_builder(&self) -> Option<&N::TransactionRequest> { + match self { + Self::Builder(tx) => Some(tx), + _ => None, + } + } + + /// Checks if the transaction is a builder. + pub fn is_builder(&self) -> bool { + matches!(self, Self::Builder(_)) + } + + /// Check if the transaction is an envelope. + pub fn is_envelope(&self) -> bool { + matches!(self, Self::Envelope(_)) + } + + /// Fallible cast to a built transaction envelope. + pub fn as_envelope(&self) -> Option<&N::TxEnvelope> { + match self { + Self::Envelope(tx) => Some(tx), + _ => None, + } + } +} + /// The root provider manages the RPC client and the heartbeat. It is at the /// base of every provider stack. pub struct RootProvider { @@ -619,8 +665,34 @@ pub trait Provider: &self, tx: N::TransactionRequest, ) -> TransportResult> { - let tx_hash = self.client().request("eth_sendTransaction", (tx,)).await?; - Ok(PendingTransactionBuilder::new(self.root(), tx_hash)) + self.send_transaction_internal(SendableTx::Builder(tx)).await + } + + /// Backing logic for [`send_transaction`]. + /// + /// This method allows [`ProviderLayer`] and [`TxFiller`] to bulid the + /// transaction and send it to the network without changing user-facing + /// APIs. Generally implementors should NOT override this method. + /// + /// [`send_transaction`]: Self::send_transaction + /// [`ProviderLayer`]: crate::ProviderLayer + /// [`TxFiller`]: crate::TxFiller + #[doc(hidden)] + async fn send_transaction_internal( + &self, + tx: SendableTx, + ) -> TransportResult> { + match tx { + SendableTx::Builder(tx) => { + let tx_hash = self.client().request("eth_sendTransaction", (tx,)).await?; + Ok(PendingTransactionBuilder::new(self.root(), tx_hash)) + } + SendableTx::Envelope(tx) => { + let mut encoded_tx = vec![]; + tx.encode_2718(&mut encoded_tx); + self.send_raw_transaction(&encoded_tx).await + } + } } /// Broadcasts a raw transaction RLP bytes to the network. @@ -628,9 +700,9 @@ pub trait Provider: /// See [`send_transaction`](Self::send_transaction) for more details. async fn send_raw_transaction( &self, - rlp_bytes: &[u8], + encoded_tx: &[u8], ) -> TransportResult> { - let rlp_hex = hex::encode_prefixed(rlp_bytes); + let rlp_hex = hex::encode_prefixed(encoded_tx); let tx_hash = self.client().request("eth_sendRawTransaction", (rlp_hex,)).await?; Ok(PendingTransactionBuilder::new(self.root(), tx_hash)) } From bc475a98551821c98aebe6f68e6dc3e5bf9ffec4 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 2 Apr 2024 21:06:54 -0700 Subject: [PATCH 27/45] wip: sig filler --- crates/json-rpc/src/error.rs | 2 +- crates/provider/src/fillers/signer.rs | 32 ++++++++++++++++++++++++++- crates/provider/src/provider.rs | 9 +++++++- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/crates/json-rpc/src/error.rs b/crates/json-rpc/src/error.rs index 238d6bf5184..4cdd0b154c7 100644 --- a/crates/json-rpc/src/error.rs +++ b/crates/json-rpc/src/error.rs @@ -5,7 +5,7 @@ use serde_json::value::RawValue; #[derive(Debug, thiserror::Error)] pub enum RpcError> { /// Server returned an error response. - #[error("server returned an error response: {0}")] + #[error("Server returned an error response: {0}")] ErrorResp(ErrorPayload), /// Server returned a null response when a non-null response was expected. diff --git a/crates/provider/src/fillers/signer.rs b/crates/provider/src/fillers/signer.rs index 00e14271927..0f2d71061bd 100644 --- a/crates/provider/src/fillers/signer.rs +++ b/crates/provider/src/fillers/signer.rs @@ -4,6 +4,8 @@ use alloy_transport::{Transport, TransportErrorKind, TransportResult}; use async_trait::async_trait; use std::marker::PhantomData; +use super::{FillerControlFlow, TxFiller}; + /// A layer that signs transactions locally. /// /// The layer uses a [`NetworkSigner`] to sign transactions sent using @@ -24,7 +26,7 @@ use std::marker::PhantomData; /// provider.send_transaction(TransactionRequest::default()).await; /// # } /// ``` -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct SignerLayer { signer: S, } @@ -50,6 +52,34 @@ where } } +impl TxFiller for SignerLayer +where + N: Network, + S: NetworkSigner + Clone, +{ + type Fillable = (); + + fn status(&self, _tx: &::TransactionRequest) -> FillerControlFlow { + FillerControlFlow::Ready + } + + async fn prepare( + &self, + provider: &P, + tx: &::TransactionRequest, + ) -> TransportResult + where + P: Provider, + T: Transport + Clone, + { + todo!() + } + + fn fill(&self, fillable: Self::Fillable, tx: &mut crate::provider::SendableTx) { + todo!() + } +} + /// A locally-signing provider. /// /// Signs transactions locally using a [`NetworkSigner`]. diff --git a/crates/provider/src/provider.rs b/crates/provider/src/provider.rs index 11a86ae929e..0c60a9db46a 100644 --- a/crates/provider/src/provider.rs +++ b/crates/provider/src/provider.rs @@ -47,7 +47,14 @@ use alloy_pubsub::{PubSubFrontend, Subscription}; /// See [`PollerBuilder`] for more details. pub type FilterPollerBuilder = PollerBuilder>; -/// A transaction that will be +/// A transaction that can be sent. This is either a builder or an envelope. +/// +/// This type is used to allow for fillers to convert a builder into an envelope +/// without changing the user-facing API. +/// +/// Users should NOT use this type directly. It should only be used as an +/// implementation detail of [`Provider::send_transaction_internal`]. +#[doc(hidden)] #[derive(Debug, Clone, PartialEq, Eq)] pub enum SendableTx { /// A transaction that is not yet signed. From 2a533c541c0f819ba5598fe0ccb349d41831cb35 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 2 Apr 2024 22:23:11 -0700 Subject: [PATCH 28/45] refactor: SignerFiller --- crates/json-rpc/src/error.rs | 12 ++- crates/provider/src/builder.rs | 24 +++--- crates/provider/src/fillers/mod.rs | 2 +- crates/provider/src/fillers/signer.rs | 102 +++++++++----------------- crates/provider/src/provider.rs | 12 +-- 5 files changed, 65 insertions(+), 87 deletions(-) diff --git a/crates/json-rpc/src/error.rs b/crates/json-rpc/src/error.rs index 4cdd0b154c7..c13b4289d98 100644 --- a/crates/json-rpc/src/error.rs +++ b/crates/json-rpc/src/error.rs @@ -57,6 +57,16 @@ where Self::ErrorResp(err) } + /// Instantiate a new `RpcError` from a custom error string. + pub const fn make_err_resp(code: i64, message: String) -> Self { + Self::ErrorResp(ErrorPayload { code, message, data: None }) + } + + /// Instantiate a new `RpcError` from a custom error with data. + pub const fn make_err_resp_with_data(code: i64, message: String, data: ErrResp) -> Self { + Self::ErrorResp(ErrorPayload { code, message, data: Some(data) }) + } + /// Instantiate a new `TransportError` from a [`serde_json::Error`] and the /// text. This should be called when the error occurs during /// deserialization. @@ -76,7 +86,7 @@ where } impl RpcError { - /// Instantiate a new `TransportError` from a [`serde_json::Error`]. This + /// Instantiate a new `RpcError` from a [`serde_json::Error`]. This /// should be called when the error occurs during serialization. pub const fn ser_err(err: serde_json::Error) -> Self { Self::SerError(err) diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index a3f2c3fc926..5b110a2abba 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -1,6 +1,6 @@ use crate::{ fillers::{ - ChainIdFiller, FillerControlFlow, GasFiller, JoinFill, NonceFiller, SignerLayer, TxFiller, + ChainIdFiller, FillerControlFlow, GasFiller, JoinFill, NonceFiller, SignerFiller, TxFiller, }, provider::SendableTx, Provider, RootProvider, @@ -195,8 +195,8 @@ impl ProviderBuilder { /// Add a signer layer to the stack being built. /// /// See [`SignerLayer`]. - pub fn signer(self, signer: S) -> ProviderBuilder, L>, F, N> { - self.layer(SignerLayer::new(signer)) + pub fn signer(self, signer: S) -> ProviderBuilder>, N> { + self.filler(SignerFiller::new(signer)) } /// Change the network. @@ -333,15 +333,19 @@ impl ProviderBuilder { /// Build this provider with anvil, using an Reqwest HTTP transport. This /// function configures a signer backed by anvil keys, and is intended for /// use in tests. - pub fn on_anvil_with_signer(self) -> (F::Provider, alloy_node_bindings::AnvilInstance) + #[allow(clippy::type_complexity)] + pub fn on_anvil_with_signer( + self, + ) -> ( + > as ProviderLayer< + L::Provider, + alloy_transport_http::Http, + >>::Provider, + alloy_node_bindings::AnvilInstance, + ) where L: ProviderLayer< - crate::fillers::SignerProvider< - alloy_transport_http::Http, - crate::ReqwestProvider, - crate::network::EthereumSigner, - Ethereum, - >, + crate::ReqwestProvider, alloy_transport_http::Http, Ethereum, >, diff --git a/crates/provider/src/fillers/mod.rs b/crates/provider/src/fillers/mod.rs index 9edef70279f..6dc5fd3af4b 100644 --- a/crates/provider/src/fillers/mod.rs +++ b/crates/provider/src/fillers/mod.rs @@ -10,7 +10,7 @@ mod chain_id; pub use chain_id::ChainIdFiller; mod signer; -pub use signer::{SignerLayer, SignerProvider}; +pub use signer::SignerFiller; mod nonce; pub use nonce::NonceFiller; diff --git a/crates/provider/src/fillers/signer.rs b/crates/provider/src/fillers/signer.rs index 0f2d71061bd..bb1669a01cf 100644 --- a/crates/provider/src/fillers/signer.rs +++ b/crates/provider/src/fillers/signer.rs @@ -1,8 +1,7 @@ -use crate::{PendingTransactionBuilder, Provider, ProviderLayer, RootProvider}; -use alloy_network::{eip2718::Encodable2718, Ethereum, Network, NetworkSigner, TransactionBuilder}; +use crate::{provider::SendableTx, Provider}; +use alloy_json_rpc::RpcError; +use alloy_network::{Network, NetworkSigner, TransactionBuilder}; use alloy_transport::{Transport, TransportErrorKind, TransportResult}; -use async_trait::async_trait; -use std::marker::PhantomData; use super::{FillerControlFlow, TxFiller}; @@ -27,32 +26,18 @@ use super::{FillerControlFlow, TxFiller}; /// # } /// ``` #[derive(Debug, Clone)] -pub struct SignerLayer { +pub struct SignerFiller { signer: S, } -impl SignerLayer { +impl SignerFiller { /// Creates a new signing layer with the given signer. pub const fn new(signer: S) -> Self { Self { signer } } } -impl ProviderLayer for SignerLayer -where - P: Provider, - T: Transport + Clone, - S: NetworkSigner + Clone, - N: Network, -{ - type Provider = SignerProvider; - - fn layer(&self, inner: P) -> Self::Provider { - SignerProvider { inner, signer: self.signer.clone(), _phantom: PhantomData } - } -} - -impl TxFiller for SignerLayer +impl TxFiller for SignerFiller where N: Network, S: NetworkSigner + Clone, @@ -60,69 +45,48 @@ where type Fillable = (); fn status(&self, _tx: &::TransactionRequest) -> FillerControlFlow { - FillerControlFlow::Ready + todo!("check on ") } async fn prepare( &self, - provider: &P, - tx: &::TransactionRequest, + _provider: &P, + _tx: &::TransactionRequest, ) -> TransportResult where P: Provider, T: Transport + Clone, { - todo!() + panic!("This function should not be called. This is a bug. If you have not manually called SignerLayer::prepare, please file an issue.") } - fn fill(&self, fillable: Self::Fillable, tx: &mut crate::provider::SendableTx) { - todo!() - } -} - -/// A locally-signing provider. -/// -/// Signs transactions locally using a [`NetworkSigner`]. -/// -/// # Note -/// -/// You cannot construct this provider directly. Use [`ProviderBuilder`] with a [`SignerLayer`]. -/// -/// [`ProviderBuilder`]: crate::ProviderBuilder -#[derive(Debug)] -pub struct SignerProvider -where - T: Transport + Clone, - P: Provider, - N: Network, -{ - inner: P, - signer: S, - _phantom: PhantomData<(T, N)>, -} - -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl Provider for SignerProvider -where - T: Transport + Clone, - P: Provider, - S: NetworkSigner, - N: Network, -{ - #[inline] - fn root(&self) -> &RootProvider { - self.inner.root() + fn fill(&self, _fillable: Self::Fillable, _tx: &mut SendableTx) { + panic!("This function should not be called. This is a bug. If you have not manually called SignerLayer::prepare, please file an issue.") } - async fn send_transaction( + async fn prepare_and_fill( &self, - tx: N::TransactionRequest, - ) -> TransportResult> { - let envelope = tx.build(&self.signer).await.map_err(TransportErrorKind::custom)?; - let rlp = envelope.encoded_2718(); + _provider: &P, + tx: &mut SendableTx, + ) -> TransportResult<()> + where + P: Provider, + T: Transport + Clone, + { + let builder = match tx { + SendableTx::Builder(builder) => builder.clone(), + _ => return Ok(()), + }; + + let envelope = builder.build(&self.signer).await.map_err(|e| { + RpcError::::make_err_resp( + -42069, + format!("failed to build transaction: {e}"), + ) + })?; + *tx = SendableTx::Envelope(envelope); - self.inner.send_raw_transaction(&rlp).await + Ok(()) } } diff --git a/crates/provider/src/provider.rs b/crates/provider/src/provider.rs index 0c60a9db46a..e5bd075903c 100644 --- a/crates/provider/src/provider.rs +++ b/crates/provider/src/provider.rs @@ -73,7 +73,7 @@ impl SendableTx { } /// Fallible cast to an unbuilt transaction request. - pub fn as_builder(&self) -> Option<&N::TransactionRequest> { + pub const fn as_builder(&self) -> Option<&N::TransactionRequest> { match self { Self::Builder(tx) => Some(tx), _ => None, @@ -81,17 +81,17 @@ impl SendableTx { } /// Checks if the transaction is a builder. - pub fn is_builder(&self) -> bool { + pub const fn is_builder(&self) -> bool { matches!(self, Self::Builder(_)) } /// Check if the transaction is an envelope. - pub fn is_envelope(&self) -> bool { + pub const fn is_envelope(&self) -> bool { matches!(self, Self::Envelope(_)) } /// Fallible cast to a built transaction envelope. - pub fn as_envelope(&self) -> Option<&N::TxEnvelope> { + pub const fn as_envelope(&self) -> Option<&N::TxEnvelope> { match self { Self::Envelope(tx) => Some(tx), _ => None, @@ -650,8 +650,8 @@ pub trait Provider: /// Broadcasts a transaction to the network. /// - /// Returns a type that can be used to configure how and when to await the transaction's - /// confirmation. + /// Returns a type that can be used to configure how and when to await the + /// transaction's confirmation. /// /// # Examples /// From 6beabc9a4acc5c6bffcdcd62e5ee67d38fad0e46 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 2 Apr 2024 22:40:51 -0700 Subject: [PATCH 29/45] fix: remove clone --- crates/provider/src/fillers/join_fill.rs | 14 +++++++------- crates/provider/src/fillers/signer.rs | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/crates/provider/src/fillers/join_fill.rs b/crates/provider/src/fillers/join_fill.rs index 5324d8e4866..e1dc5136aa1 100644 --- a/crates/provider/src/fillers/join_fill.rs +++ b/crates/provider/src/fillers/join_fill.rs @@ -148,22 +148,22 @@ pub trait TxFiller: Clone + Send + Sync { fn prepare_and_fill( &self, provider: &P, - tx: &mut SendableTx, - ) -> impl_future!(>) + mut tx: SendableTx, + ) -> impl_future!(>>) where P: Provider, T: Transport + Clone, { async move { - if !tx.is_builder() { - return Ok(()); + if tx.is_envelope() { + return Ok(tx); } let fillable = self.prepare(provider, tx.as_builder().unwrap()).await?; - self.fill(fillable, tx); + self.fill(fillable, &mut tx); - Ok(()) + Ok(tx) } } } @@ -340,7 +340,7 @@ where let mut count = 0; while self.filler.continue_filling(&tx) { - self.filler.prepare_and_fill(&self.inner, &mut tx).await?; + tx = self.filler.prepare_and_fill(&self.inner, tx).await?; count += 1; if count >= 20 { diff --git a/crates/provider/src/fillers/signer.rs b/crates/provider/src/fillers/signer.rs index bb1669a01cf..189968f6856 100644 --- a/crates/provider/src/fillers/signer.rs +++ b/crates/provider/src/fillers/signer.rs @@ -67,15 +67,15 @@ where async fn prepare_and_fill( &self, _provider: &P, - tx: &mut SendableTx, - ) -> TransportResult<()> + mut tx: SendableTx, + ) -> TransportResult> where P: Provider, T: Transport + Clone, { let builder = match tx { - SendableTx::Builder(builder) => builder.clone(), - _ => return Ok(()), + SendableTx::Builder(builder) => builder, + _ => return Ok(tx), }; let envelope = builder.build(&self.signer).await.map_err(|e| { @@ -84,9 +84,9 @@ where format!("failed to build transaction: {e}"), ) })?; - *tx = SendableTx::Envelope(envelope); + tx = SendableTx::Envelope(envelope); - Ok(()) + Ok(tx) } } From c9976b2e5d3921d4ab774eecf0bdf5b3e2eb8824 Mon Sep 17 00:00:00 2001 From: James Date: Tue, 2 Apr 2024 22:46:37 -0700 Subject: [PATCH 30/45] docs: fix some --- crates/provider/src/builder.rs | 2 +- crates/provider/src/fillers/signer.rs | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index 5b110a2abba..c62726966da 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -194,7 +194,7 @@ impl ProviderBuilder { /// Add a signer layer to the stack being built. /// - /// See [`SignerLayer`]. + /// See [`SignerFiller`]. pub fn signer(self, signer: S) -> ProviderBuilder>, N> { self.filler(SignerFiller::new(signer)) } diff --git a/crates/provider/src/fillers/signer.rs b/crates/provider/src/fillers/signer.rs index 189968f6856..fb7cfc014a2 100644 --- a/crates/provider/src/fillers/signer.rs +++ b/crates/provider/src/fillers/signer.rs @@ -11,12 +11,9 @@ use super::{FillerControlFlow, TxFiller}; /// [`Provider::send_transaction`] locally before passing them to the node with /// [`Provider::send_raw_transaction`]. /// -/// If you have other layers that depend on [`Provider::send_transaction`] being invoked, add those -/// first. -/// /// # Example /// -/// ```rs +/// ``` /// # async fn test>(transport: T, signer: S) { /// let provider = ProviderBuilder::new() /// .signer(EthereumSigner::from(signer)) From ca3197da4beffc630b7aa8aa49d7b99c546eeb7c Mon Sep 17 00:00:00 2001 From: James Date: Wed, 3 Apr 2024 09:23:46 -0700 Subject: [PATCH 31/45] fix: complete todo --- crates/provider/src/fillers/signer.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/provider/src/fillers/signer.rs b/crates/provider/src/fillers/signer.rs index fb7cfc014a2..37e502a2547 100644 --- a/crates/provider/src/fillers/signer.rs +++ b/crates/provider/src/fillers/signer.rs @@ -42,7 +42,7 @@ where type Fillable = (); fn status(&self, _tx: &::TransactionRequest) -> FillerControlFlow { - todo!("check on ") + todo!("check on if tx is buildable") } async fn prepare( From bc3bc80b4ae306ec972892ed3ca36729df6459eb Mon Sep 17 00:00:00 2001 From: James Date: Wed, 3 Apr 2024 09:31:37 -0700 Subject: [PATCH 32/45] feature: anvil feature for alloy-provider --- crates/alloy/Cargo.toml | 2 +- crates/provider/Cargo.toml | 3 +++ crates/provider/src/builder.rs | 4 +++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/crates/alloy/Cargo.toml b/crates/alloy/Cargo.toml index f7d055d4e4e..5d072e7fc6a 100644 --- a/crates/alloy/Cargo.toml +++ b/crates/alloy/Cargo.toml @@ -94,7 +94,7 @@ contract = ["dep:alloy-contract", "dyn-abi", "json-abi", "json", "sol-types"] eips = ["dep:alloy-eips"] genesis = ["dep:alloy-genesis"] network = ["dep:alloy-network"] -node-bindings = ["dep:alloy-node-bindings"] +node-bindings = ["dep:alloy-node-bindings", "alloy-provider?/anvil"] # providers providers = ["dep:alloy-provider"] diff --git a/crates/provider/Cargo.toml b/crates/provider/Cargo.toml index 3c81c99d658..9dc581f7d74 100644 --- a/crates/provider/Cargo.toml +++ b/crates/provider/Cargo.toml @@ -15,6 +15,8 @@ exclude.workspace = true alloy-eips.workspace = true alloy-json-rpc.workspace = true alloy-network.workspace = true +alloy-node-bindings = { workspace = true, optional = true } +alloy-signer-wallet = { workspace = true, optional = true } alloy-rpc-client.workspace = true alloy-rpc-types-trace.workspace = true alloy-rpc-types.workspace = true @@ -61,3 +63,4 @@ reqwest = [ hyper = ["dep:alloy-transport-http", "dep:url", "alloy-rpc-client/hyper"] ws = ["pubsub", "alloy-rpc-client/ws", "alloy-transport-ws"] ipc = ["pubsub", "alloy-rpc-client/ipc", "alloy-transport-ipc"] +anvil = ["dep:alloy-node-bindings", "dep:alloy-signer-wallet"] \ No newline at end of file diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index c62726966da..0db42401949 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -328,7 +328,9 @@ impl ProviderBuilder { } } -#[cfg(all(test, feature = "reqwest"))] +// Enabled when the `anvil` feature is enabled, or when both in test and the +// `reqwest` feature is enabled. +#[cfg(any(all(test, feature = "reqwest"), feature = "anvil"))] impl ProviderBuilder { /// Build this provider with anvil, using an Reqwest HTTP transport. This /// function configures a signer backed by anvil keys, and is intended for From 8022cc843f6745033e134b03c8e1d6e96a297fe9 Mon Sep 17 00:00:00 2001 From: James Date: Thu, 4 Apr 2024 20:37:53 -0700 Subject: [PATCH 33/45] wip: tests --- crates/eips/src/eip2930.rs | 12 ++++++++ crates/network/src/any/builder.rs | 8 ++++++ crates/network/src/ethereum/builder.rs | 32 ++++++++++++++++++--- crates/network/src/ethereum/signer.rs | 10 +++---- crates/network/src/lib.rs | 4 +-- crates/network/src/transaction/builder.rs | 8 ++++++ crates/network/src/transaction/signer.rs | 13 +++++++-- crates/provider/src/builder.rs | 8 ++++-- crates/provider/src/fillers/chain_id.rs | 20 +++++++------ crates/provider/src/fillers/gas.rs | 24 +++++++++------- crates/provider/src/fillers/join_fill.rs | 34 +++++++++++++++++------ crates/provider/src/fillers/nonce.rs | 17 +++++++----- crates/provider/src/fillers/signer.rs | 30 ++++++++------------ crates/provider/src/provider.rs | 1 - 14 files changed, 152 insertions(+), 69 deletions(-) diff --git a/crates/eips/src/eip2930.rs b/crates/eips/src/eip2930.rs index d653138ddc3..ec655092b49 100644 --- a/crates/eips/src/eip2930.rs +++ b/crates/eips/src/eip2930.rs @@ -58,6 +58,18 @@ pub struct AccessList( pub Vec, ); +impl From> for AccessList { + fn from(list: Vec) -> Self { + Self(list) + } +} + +impl From for Vec { + fn from(this: AccessList) -> Self { + this.0 + } +} + impl AccessList { /// Converts the list into a vec, expected by revm pub fn flattened(&self) -> Vec<(Address, Vec)> { diff --git a/crates/network/src/any/builder.rs b/crates/network/src/any/builder.rs index b939823157a..18a589b1d27 100644 --- a/crates/network/src/any/builder.rs +++ b/crates/network/src/any/builder.rs @@ -114,6 +114,14 @@ impl TransactionBuilder for WithOtherFields { self.deref_mut().set_blob_sidecar(sidecar) } + fn can_build(&self) -> bool { + self.deref().can_build() + } + + fn can_submit(&self) -> bool { + self.deref().can_submit() + } + fn build_unsigned(self) -> BuilderResult<::UnsignedTx> { build_unsigned::(self.inner) } diff --git a/crates/network/src/ethereum/builder.rs b/crates/network/src/ethereum/builder.rs index 40fcb6e04fc..3808200d751 100644 --- a/crates/network/src/ethereum/builder.rs +++ b/crates/network/src/ethereum/builder.rs @@ -3,6 +3,7 @@ use crate::{ }; use alloy_consensus::{ BlobTransactionSidecar, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxLegacy, + TypedTransaction, }; use alloy_primitives::{Address, TxKind, U256, Bytes, ChainId}; use alloy_rpc_types::{request::TransactionRequest, AccessList}; @@ -99,10 +100,6 @@ impl TransactionBuilder for TransactionRequest { self.gas = Some(gas_limit); } - fn build_unsigned(self) -> BuilderResult<::UnsignedTx> { - build_unsigned::(self) - } - fn access_list(&self) -> Option<&AccessList> { self.access_list.as_ref() } @@ -120,6 +117,33 @@ impl TransactionBuilder for TransactionRequest { self.sidecar = Some(sidecar); } + fn can_submit(&self) -> bool { + // value and data may be None. If they are, they will be set to default. + // gas fields and nonce may be None, if they are, they will be populated + // with default values by the RPC server + self.to.is_some() && self.from.is_some() + } + + fn can_build(&self) -> bool { + // value and data may be none. If they are, they will be set to default + // values. + + // chain_id and from may be none. + let common = self.to.is_some() && self.gas.is_some() && self.nonce.is_some(); + + let legacy = self.gas_price.is_some(); + let eip2930 = legacy && self.access_list().is_some(); + + let eip1559 = self.max_fee_per_gas.is_some() && self.max_priority_fee_per_gas.is_some(); + + let eip4844 = eip1559 && self.sidecar.is_some(); + common && (legacy || eip2930 || eip1559 || eip4844) + } + + fn build_unsigned(self) -> BuilderResult { + build_unsigned::(self) + } + async fn build>( self, signer: &S, diff --git a/crates/network/src/ethereum/signer.rs b/crates/network/src/ethereum/signer.rs index 05a818971a7..c330f15e4ce 100644 --- a/crates/network/src/ethereum/signer.rs +++ b/crates/network/src/ethereum/signer.rs @@ -33,7 +33,7 @@ impl EthereumSigner { Self(Arc::new(signer)) } - async fn sign_transaction( + async fn sign_transaction_inner( &self, tx: &mut dyn SignableTransaction, ) -> alloy_signer::Result { @@ -47,19 +47,19 @@ impl NetworkSigner for EthereumSigner { async fn sign_transaction(&self, tx: TypedTransaction) -> alloy_signer::Result { match tx { TypedTransaction::Legacy(mut t) => { - let sig = self.sign_transaction(&mut t).await?; + let sig = self.sign_transaction_inner(&mut t).await?; Ok(t.into_signed(sig).into()) } TypedTransaction::Eip2930(mut t) => { - let sig = self.sign_transaction(&mut t).await?; + let sig = self.sign_transaction_inner(&mut t).await?; Ok(t.into_signed(sig).into()) } TypedTransaction::Eip1559(mut t) => { - let sig = self.sign_transaction(&mut t).await?; + let sig = self.sign_transaction_inner(&mut t).await?; Ok(t.into_signed(sig).into()) } TypedTransaction::Eip4844(mut t) => { - let sig = self.sign_transaction(&mut t).await?; + let sig = self.sign_transaction_inner(&mut t).await?; Ok(t.into_signed(sig).into()) } } diff --git a/crates/network/src/lib.rs b/crates/network/src/lib.rs index adb861f1bf1..1eb0c3024e8 100644 --- a/crates/network/src/lib.rs +++ b/crates/network/src/lib.rs @@ -48,11 +48,11 @@ pub trait ReceiptResponse { /// Networks are only containers for types, so it is recommended to use ZSTs for their definition. // todo: block responses are ethereum only, so we need to include this in here too, or make `Block` // generic over tx/header type -pub trait Network: Clone + Copy + Sized + Send + Sync + 'static { +pub trait Network: std::fmt::Debug + Clone + Copy + Sized + Send + Sync + 'static { // -- Consensus types -- /// The network transaction envelope type. - type TxEnvelope: Eip2718Envelope; + type TxEnvelope: Eip2718Envelope + std::fmt::Debug; /// An enum over the various transaction types. type UnsignedTx; diff --git a/crates/network/src/transaction/builder.rs b/crates/network/src/transaction/builder.rs index 20e2c83b47b..98045eaa5cd 100644 --- a/crates/network/src/transaction/builder.rs +++ b/crates/network/src/transaction/builder.rs @@ -219,6 +219,14 @@ pub trait TransactionBuilder: Default + Sized + Send + Sync + 'stati self } + /// True if the builder contains all necessary information to be submitted + /// to the `eth_sendTransaction` endpoint. + fn can_submit(&self) -> bool; + + /// True if the builder contains all necessary information to be built into + /// a valid transaction. + fn can_build(&self) -> bool; + /// Build an unsigned, but typed, transaction. fn build_unsigned(self) -> BuilderResult; diff --git a/crates/network/src/transaction/signer.rs b/crates/network/src/transaction/signer.rs index 6024008a032..155b530579f 100644 --- a/crates/network/src/transaction/signer.rs +++ b/crates/network/src/transaction/signer.rs @@ -1,4 +1,4 @@ -use crate::Network; +use crate::{Network, TransactionBuilder}; use alloy_consensus::SignableTransaction; use async_trait::async_trait; @@ -9,9 +9,18 @@ use async_trait::async_trait; /// [`TxSigner`] to signify signing capability for specific signature types. #[cfg_attr(target_arch = "wasm32", async_trait(?Send))] #[cfg_attr(not(target_arch = "wasm32"), async_trait)] -pub trait NetworkSigner: Send + Sync { +pub trait NetworkSigner: std::fmt::Debug + Send + Sync { /// Asynchronously sign an unsigned transaction. async fn sign_transaction(&self, tx: N::UnsignedTx) -> alloy_signer::Result; + + /// Asynchronously sign a transaction request. + async fn sign_request( + &self, + request: N::TransactionRequest, + ) -> alloy_signer::Result { + let tx = request.build_unsigned().map_err(alloy_signer::Error::other)?; + self.sign_transaction(tx).await + } } /// Asynchronous transaction signer, capable of signing any [`SignableTransaction`] for the given diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index 0db42401949..87ad591c29f 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -47,8 +47,12 @@ where Ok(()) } - fn fill(&self, _to_fill: Self::Fillable, _tx: &mut SendableTx) { - // Do nothing + async fn fill( + &self, + _to_fill: Self::Fillable, + tx: SendableTx, + ) -> TransportResult> { + Ok(tx) } } diff --git a/crates/provider/src/fillers/chain_id.rs b/crates/provider/src/fillers/chain_id.rs index 3591cf64438..35d2dd08107 100644 --- a/crates/provider/src/fillers/chain_id.rs +++ b/crates/provider/src/fillers/chain_id.rs @@ -77,14 +77,16 @@ impl TxFiller for ChainIdFiller { } } - fn fill(&self, fillable: Self::Fillable, tx: &mut SendableTx) { - let tx = match tx { - SendableTx::Builder(tx) => tx, - _ => return, - }; - - if tx.chain_id().is_none() { - tx.set_chain_id(fillable); - } + async fn fill( + &self, + fillable: Self::Fillable, + mut tx: SendableTx, + ) -> TransportResult> { + tx.as_mut_builder().map(|tx| { + if tx.chain_id().is_none() { + tx.set_chain_id(fillable) + } + }); + Ok(tx) } } diff --git a/crates/provider/src/fillers/gas.rs b/crates/provider/src/fillers/gas.rs index adb8d92288f..714c75a18df 100644 --- a/crates/provider/src/fillers/gas.rs +++ b/crates/provider/src/fillers/gas.rs @@ -173,6 +173,7 @@ impl TxFiller for GasFiller { if tx.gas_price().is_some() && tx.gas_limit().is_some() { return FillerControlFlow::Finished; } + // 4844 if tx.max_fee_per_blob_gas().is_some() && tx.max_fee_per_gas().is_some() @@ -180,6 +181,7 @@ impl TxFiller for GasFiller { { return FillerControlFlow::Finished; } + // eip1559 if tx.blob_sidecar().is_none() && tx.max_fee_per_gas().is_some() @@ -187,6 +189,7 @@ impl TxFiller for GasFiller { { return FillerControlFlow::Finished; } + FillerControlFlow::Ready } @@ -213,13 +216,12 @@ impl TxFiller for GasFiller { } } - fn fill(&self, fillable: Self::Fillable, tx: &mut SendableTx) { - let tx = match tx { - SendableTx::Builder(tx) => tx, - _ => return, - }; - - match fillable { + async fn fill( + &self, + fillable: Self::Fillable, + mut tx: SendableTx, + ) -> TransportResult> { + tx.as_mut_builder().map(|tx| match fillable { GasFillable::Legacy { gas_limit, gas_price } => { tx.set_gas_limit(gas_limit); tx.set_gas_price(gas_price); @@ -235,7 +237,8 @@ impl TxFiller for GasFiller { tx.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); tx.set_max_fee_per_blob_gas(max_fee_per_blob_gas); } - } + }); + Ok(tx) } } @@ -245,7 +248,7 @@ mod tests { use super::*; use crate::ProviderBuilder; use alloy_primitives::address; - use alloy_rpc_types::TransactionRequest; + use alloy_rpc_types::{AccessListItem, TransactionRequest}; #[tokio::test] async fn no_gas_price_or_limit() { @@ -299,7 +302,8 @@ mod tests { from: Some(anvil.addresses()[0]), value: Some(U256::from(100)), to: address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into(), - // chain_id: Some(31337), Not required as this will fallback to legacy_tx + access_list: Some(vec![Default::default()].into()), + // chain_id: Some(31337), Not required as access list causes legacy gassing ..Default::default() }; diff --git a/crates/provider/src/fillers/join_fill.rs b/crates/provider/src/fillers/join_fill.rs index e1dc5136aa1..71527fbeca9 100644 --- a/crates/provider/src/fillers/join_fill.rs +++ b/crates/provider/src/fillers/join_fill.rs @@ -1,6 +1,7 @@ use crate::{ provider::SendableTx, PendingTransactionBuilder, Provider, ProviderLayer, RootProvider, }; +use alloy_json_rpc::RpcError; use alloy_network::{Ethereum, Network}; use alloy_transport::{Transport, TransportResult}; use async_trait::async_trait; @@ -98,7 +99,7 @@ impl FillerControlFlow { /// [`TxFiller::status`] should return [`FillerControlFlow::Ready`]. /// - **Finished**: The filler has filled in all properties that it can fill. /// [`TxFiller::status`] should return [`FillerControlFlow::Finished`]. -pub trait TxFiller: Clone + Send + Sync { +pub trait TxFiller: Clone + Send + Sync + std::fmt::Debug { /// The properties that this filler retrieves from the RPC. to fill in the /// TransactionRequest. type Fillable: Send + Sync + 'static; @@ -142,13 +143,17 @@ pub trait TxFiller: Clone + Send + Sync { T: Transport + Clone; /// Fills in the transaction request with the fillable properties. - fn fill(&self, fillable: Self::Fillable, tx: &mut SendableTx); + fn fill( + &self, + fillable: Self::Fillable, + tx: SendableTx, + ) -> impl_future!(>>); /// Prepares and fills the transaction request with the fillable properties. fn prepare_and_fill( &self, provider: &P, - mut tx: SendableTx, + tx: SendableTx, ) -> impl_future!(>>) where P: Provider, @@ -161,9 +166,7 @@ pub trait TxFiller: Clone + Send + Sync { let fillable = self.prepare(provider, tx.as_builder().unwrap()).await?; - self.fill(fillable, &mut tx); - - Ok(tx) + self.fill(fillable, tx).await } } } @@ -250,13 +253,18 @@ where try_join!(self.prepare_left(provider, tx), self.prepare_right(provider, tx)) } - fn fill(&self, to_fill: Self::Fillable, tx: &mut SendableTx) { + async fn fill( + &self, + to_fill: Self::Fillable, + mut tx: SendableTx, + ) -> TransportResult> { if let Some(to_fill) = to_fill.0 { - self.left.fill(to_fill, tx); + tx = self.left.fill(to_fill, tx).await?; }; if let Some(to_fill) = to_fill.1 { - self.right.fill(to_fill, tx); + tx = self.right.fill(to_fill, tx).await?; }; + Ok(tx) } } @@ -350,6 +358,14 @@ where } } + if let Some(builder) = tx.as_builder() { + if let FillerControlFlow::Missing(missing) = self.filler.status(builder) { + // TODO: improve this. + let message = format!("missing properties: {:?}", missing); + return Err(RpcError::make_err_resp(-42069, message)); + } + } + // Errors in tx building happen further down the stack. self.inner.send_transaction_internal(tx).await } diff --git a/crates/provider/src/fillers/nonce.rs b/crates/provider/src/fillers/nonce.rs index 344a710beda..d27e9161c9c 100644 --- a/crates/provider/src/fillers/nonce.rs +++ b/crates/provider/src/fillers/nonce.rs @@ -67,12 +67,15 @@ impl TxFiller for NonceFiller { self.get_next_nonce(provider, from).await } - fn fill(&self, nonce: Self::Fillable, tx: &mut SendableTx) { - let tx = match tx { - SendableTx::Builder(tx) => tx, - _ => return, - }; - tx.set_nonce(nonce); + async fn fill( + &self, + nonce: Self::Fillable, + mut tx: SendableTx, + ) -> TransportResult> { + tx.as_mut_builder().map(|tx| { + tx.set_nonce(nonce); + }); + Ok(tx) } } @@ -125,7 +128,7 @@ mod tests { }; // errors because signer layer expects nonce to be set, which it is not - assert!(provider.send_transaction(tx.clone()).await.is_err()); + assert!(provider.send_transaction(tx).await.is_err()); } #[tokio::test] diff --git a/crates/provider/src/fillers/signer.rs b/crates/provider/src/fillers/signer.rs index 37e502a2547..4ea250b83b2 100644 --- a/crates/provider/src/fillers/signer.rs +++ b/crates/provider/src/fillers/signer.rs @@ -41,8 +41,12 @@ where { type Fillable = (); - fn status(&self, _tx: &::TransactionRequest) -> FillerControlFlow { - todo!("check on if tx is buildable") + fn status(&self, tx: &::TransactionRequest) -> FillerControlFlow { + if tx.can_build() { + FillerControlFlow::Ready + } else { + FillerControlFlow::Missing(vec![("Signer", &["TODO"])]) + } } async fn prepare( @@ -54,22 +58,14 @@ where P: Provider, T: Transport + Clone, { - panic!("This function should not be called. This is a bug. If you have not manually called SignerLayer::prepare, please file an issue.") + Ok(()) } - fn fill(&self, _fillable: Self::Fillable, _tx: &mut SendableTx) { - panic!("This function should not be called. This is a bug. If you have not manually called SignerLayer::prepare, please file an issue.") - } - - async fn prepare_and_fill( + async fn fill( &self, - _provider: &P, - mut tx: SendableTx, - ) -> TransportResult> - where - P: Provider, - T: Transport + Clone, - { + _fillable: Self::Fillable, + tx: SendableTx, + ) -> TransportResult> { let builder = match tx { SendableTx::Builder(builder) => builder, _ => return Ok(tx), @@ -81,9 +77,7 @@ where format!("failed to build transaction: {e}"), ) })?; - tx = SendableTx::Envelope(envelope); - - Ok(tx) + Ok(SendableTx::Envelope(envelope)) } } diff --git a/crates/provider/src/provider.rs b/crates/provider/src/provider.rs index e5bd075903c..d9e9b065c81 100644 --- a/crates/provider/src/provider.rs +++ b/crates/provider/src/provider.rs @@ -675,7 +675,6 @@ pub trait Provider: self.send_transaction_internal(SendableTx::Builder(tx)).await } - /// Backing logic for [`send_transaction`]. /// /// This method allows [`ProviderLayer`] and [`TxFiller`] to bulid the /// transaction and send it to the network without changing user-facing From 7353f795e4ddbef206aa735f927b29e828c88918 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 5 Apr 2024 08:02:17 -0700 Subject: [PATCH 34/45] fix: apply changes from other PRs --- crates/provider/src/builder.rs | 2 +- crates/provider/src/fillers/chain_id.rs | 10 ++++----- crates/provider/src/fillers/gas.rs | 29 +++++++++++++------------ crates/provider/src/fillers/nonce.rs | 6 ++--- 4 files changed, 24 insertions(+), 23 deletions(-) diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index 87ad591c29f..8709cbf9699 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -365,7 +365,7 @@ impl ProviderBuilder { let this = self.signer(crate::network::EthereumSigner::from(wallet)); - (this.on_reqwest_http(url).unwrap(), anvil) + (this.on_http(url).unwrap(), anvil) } } diff --git a/crates/provider/src/fillers/chain_id.rs b/crates/provider/src/fillers/chain_id.rs index 35d2dd08107..aa3f4b5cb44 100644 --- a/crates/provider/src/fillers/chain_id.rs +++ b/crates/provider/src/fillers/chain_id.rs @@ -70,7 +70,7 @@ impl TxFiller for ChainIdFiller { match self.0.get().copied() { Some(chain_id) => Ok(chain_id), None => { - let chain_id = provider.get_chain_id().await?.as_limbs()[0]; + let chain_id = provider.get_chain_id().await?; let chain_id = *self.0.get_or_init(|| chain_id); Ok(chain_id) } @@ -82,11 +82,11 @@ impl TxFiller for ChainIdFiller { fillable: Self::Fillable, mut tx: SendableTx, ) -> TransportResult> { - tx.as_mut_builder().map(|tx| { - if tx.chain_id().is_none() { - tx.set_chain_id(fillable) + if let Some(builder) = tx.as_mut_builder() { + if builder.chain_id().is_none() { + builder.set_chain_id(fillable) } - }); + }; Ok(tx) } } diff --git a/crates/provider/src/fillers/gas.rs b/crates/provider/src/fillers/gas.rs index 714c75a18df..85286a53b2f 100644 --- a/crates/provider/src/fillers/gas.rs +++ b/crates/provider/src/fillers/gas.rs @@ -221,23 +221,24 @@ impl TxFiller for GasFiller { fillable: Self::Fillable, mut tx: SendableTx, ) -> TransportResult> { - tx.as_mut_builder().map(|tx| match fillable { + if let Some(builder) = tx.as_mut_builder(){ + match fillable { GasFillable::Legacy { gas_limit, gas_price } => { - tx.set_gas_limit(gas_limit); - tx.set_gas_price(gas_price); + builder.set_gas_limit(gas_limit); + builder.set_gas_price(gas_price); } GasFillable::Eip1559 { gas_limit, estimate } => { - tx.set_gas_limit(gas_limit); - tx.set_max_fee_per_gas(estimate.max_fee_per_gas); - tx.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); + builder.set_gas_limit(gas_limit); + builder.set_max_fee_per_gas(estimate.max_fee_per_gas); + builder.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); } GasFillable::Eip4844 { gas_limit, estimate, max_fee_per_blob_gas } => { - tx.set_gas_limit(gas_limit); - tx.set_max_fee_per_gas(estimate.max_fee_per_gas); - tx.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); - tx.set_max_fee_per_blob_gas(max_fee_per_blob_gas); - } - }); + builder.set_gas_limit(gas_limit); + builder.set_max_fee_per_gas(estimate.max_fee_per_gas); + builder.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); + builder.set_max_fee_per_blob_gas(max_fee_per_blob_gas); + }} + }; Ok(tx) } } @@ -247,8 +248,8 @@ impl TxFiller for GasFiller { mod tests { use super::*; use crate::ProviderBuilder; - use alloy_primitives::address; - use alloy_rpc_types::{AccessListItem, TransactionRequest}; + use alloy_primitives::{address, U256}; + use alloy_rpc_types::{TransactionRequest}; #[tokio::test] async fn no_gas_price_or_limit() { diff --git a/crates/provider/src/fillers/nonce.rs b/crates/provider/src/fillers/nonce.rs index d27e9161c9c..0cc623fc086 100644 --- a/crates/provider/src/fillers/nonce.rs +++ b/crates/provider/src/fillers/nonce.rs @@ -72,9 +72,9 @@ impl TxFiller for NonceFiller { nonce: Self::Fillable, mut tx: SendableTx, ) -> TransportResult> { - tx.as_mut_builder().map(|tx| { - tx.set_nonce(nonce); - }); + if let Some(builder) = tx.as_mut_builder(){ + builder.set_nonce(nonce); + } Ok(tx) } } From 1c376d7b52660a1f1a769395d967d2fe82174f2d Mon Sep 17 00:00:00 2001 From: James Date: Fri, 5 Apr 2024 08:17:15 -0700 Subject: [PATCH 35/45] chore: fmt --- crates/network/src/ethereum/builder.rs | 2 +- crates/provider/src/fillers/gas.rs | 33 +++++++++++++------------- crates/provider/src/fillers/nonce.rs | 2 +- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/crates/network/src/ethereum/builder.rs b/crates/network/src/ethereum/builder.rs index 3808200d751..faa221f986c 100644 --- a/crates/network/src/ethereum/builder.rs +++ b/crates/network/src/ethereum/builder.rs @@ -5,7 +5,7 @@ use alloy_consensus::{ BlobTransactionSidecar, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxLegacy, TypedTransaction, }; -use alloy_primitives::{Address, TxKind, U256, Bytes, ChainId}; +use alloy_primitives::{Address, Bytes, ChainId, TxKind, U256}; use alloy_rpc_types::{request::TransactionRequest, AccessList}; impl TransactionBuilder for TransactionRequest { diff --git a/crates/provider/src/fillers/gas.rs b/crates/provider/src/fillers/gas.rs index 85286a53b2f..e81a0869930 100644 --- a/crates/provider/src/fillers/gas.rs +++ b/crates/provider/src/fillers/gas.rs @@ -221,23 +221,24 @@ impl TxFiller for GasFiller { fillable: Self::Fillable, mut tx: SendableTx, ) -> TransportResult> { - if let Some(builder) = tx.as_mut_builder(){ + if let Some(builder) = tx.as_mut_builder() { match fillable { - GasFillable::Legacy { gas_limit, gas_price } => { - builder.set_gas_limit(gas_limit); - builder.set_gas_price(gas_price); + GasFillable::Legacy { gas_limit, gas_price } => { + builder.set_gas_limit(gas_limit); + builder.set_gas_price(gas_price); + } + GasFillable::Eip1559 { gas_limit, estimate } => { + builder.set_gas_limit(gas_limit); + builder.set_max_fee_per_gas(estimate.max_fee_per_gas); + builder.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); + } + GasFillable::Eip4844 { gas_limit, estimate, max_fee_per_blob_gas } => { + builder.set_gas_limit(gas_limit); + builder.set_max_fee_per_gas(estimate.max_fee_per_gas); + builder.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); + builder.set_max_fee_per_blob_gas(max_fee_per_blob_gas); + } } - GasFillable::Eip1559 { gas_limit, estimate } => { - builder.set_gas_limit(gas_limit); - builder.set_max_fee_per_gas(estimate.max_fee_per_gas); - builder.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); - } - GasFillable::Eip4844 { gas_limit, estimate, max_fee_per_blob_gas } => { - builder.set_gas_limit(gas_limit); - builder.set_max_fee_per_gas(estimate.max_fee_per_gas); - builder.set_max_priority_fee_per_gas(estimate.max_priority_fee_per_gas); - builder.set_max_fee_per_blob_gas(max_fee_per_blob_gas); - }} }; Ok(tx) } @@ -249,7 +250,7 @@ mod tests { use super::*; use crate::ProviderBuilder; use alloy_primitives::{address, U256}; - use alloy_rpc_types::{TransactionRequest}; + use alloy_rpc_types::TransactionRequest; #[tokio::test] async fn no_gas_price_or_limit() { diff --git a/crates/provider/src/fillers/nonce.rs b/crates/provider/src/fillers/nonce.rs index 0cc623fc086..9f69563763c 100644 --- a/crates/provider/src/fillers/nonce.rs +++ b/crates/provider/src/fillers/nonce.rs @@ -72,7 +72,7 @@ impl TxFiller for NonceFiller { nonce: Self::Fillable, mut tx: SendableTx, ) -> TransportResult> { - if let Some(builder) = tx.as_mut_builder(){ + if let Some(builder) = tx.as_mut_builder() { builder.set_nonce(nonce); } Ok(tx) From 9401eac38b33ce63c459353d9659019f23175264 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 5 Apr 2024 09:30:20 -0700 Subject: [PATCH 36/45] feature: on_anvil --- crates/provider/src/builder.rs | 17 ++ crates/provider/src/fillers/gas.rs | 11 +- crates/provider/src/fillers/join_fill.rs | 261 +--------------------- crates/provider/src/fillers/mod.rs | 263 ++++++++++++++++++++++- crates/provider/src/fillers/signer.rs | 1 + crates/transport/src/common.rs | 2 - 6 files changed, 292 insertions(+), 263 deletions(-) diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index 8709cbf9699..a1419962652 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -336,6 +336,23 @@ impl ProviderBuilder { // `reqwest` feature is enabled. #[cfg(any(all(test, feature = "reqwest"), feature = "anvil"))] impl ProviderBuilder { + /// Build this provider with anvil, using an Reqwest HTTP transport. + pub fn on_anvil(self) -> (F::Provider, alloy_node_bindings::AnvilInstance) + where + F: TxFiller + + ProviderLayer, Ethereum>, + L: ProviderLayer< + crate::ReqwestProvider, + alloy_transport_http::Http, + Ethereum, + >, + { + let anvil = alloy_node_bindings::Anvil::new().spawn(); + let url = anvil.endpoint().parse().unwrap(); + + (self.on_http(url).unwrap(), anvil) + } + /// Build this provider with anvil, using an Reqwest HTTP transport. This /// function configures a signer backed by anvil keys, and is intended for /// use in tests. diff --git a/crates/provider/src/fillers/gas.rs b/crates/provider/src/fillers/gas.rs index e81a0869930..c1b67ec59e6 100644 --- a/crates/provider/src/fillers/gas.rs +++ b/crates/provider/src/fillers/gas.rs @@ -297,20 +297,25 @@ mod tests { #[tokio::test] async fn non_eip1559_network() { - let (provider, anvil) = - ProviderBuilder::new().with_recommended_fillers().on_anvil_with_signer(); + let (provider, anvil) = ProviderBuilder::new() + .filler(crate::fillers::GasFiller) + .filler(crate::fillers::NonceFiller::default()) + .filler(crate::fillers::ChainIdFiller::default()) + .on_anvil_with_signer(); let tx = TransactionRequest { from: Some(anvil.addresses()[0]), value: Some(U256::from(100)), to: address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into(), access_list: Some(vec![Default::default()].into()), - // chain_id: Some(31337), Not required as access list causes legacy gassing ..Default::default() }; let tx = provider.send_transaction(tx).await.unwrap(); + let _: () = + provider.raw_request(std::borrow::Cow::Borrowed("anvil_mine"), &["0x5"]).await.unwrap(); + let tx = tx.get_receipt().await.unwrap(); assert_eq!(tx.effective_gas_price, 0x6fc23ac0); diff --git a/crates/provider/src/fillers/join_fill.rs b/crates/provider/src/fillers/join_fill.rs index 71527fbeca9..9c4805d7d6e 100644 --- a/crates/provider/src/fillers/join_fill.rs +++ b/crates/provider/src/fillers/join_fill.rs @@ -1,175 +1,11 @@ use crate::{ - provider::SendableTx, PendingTransactionBuilder, Provider, ProviderLayer, RootProvider, + fillers::{FillProvider, FillerControlFlow, TxFiller}, + provider::SendableTx, + Provider, ProviderLayer, }; -use alloy_json_rpc::RpcError; -use alloy_network::{Ethereum, Network}; +use alloy_network::Network; use alloy_transport::{Transport, TransportResult}; -use async_trait::async_trait; use futures::try_join; -use futures_utils_wasm::impl_future; -use std::marker::PhantomData; - -/// The control flow for a filler. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum FillerControlFlow { - /// The filler is missing a required property. - /// - /// To allow joining fillers while preserving their associated missing - /// lists, this variant contains a list of `(name, missing)` tuples. When - /// absorbing another control flow, if both are missing, the missing lists - /// are combined. - Missing(Vec<(&'static str, &'static [&'static str])>), - /// The filler is ready to fill in the transaction request. - Ready, - /// The filler has filled in all properties that it can fill. - Finished, -} - -impl FillerControlFlow { - /// Absorb the control flow of another filler. - /// - /// # Behavior: - /// - If either is finished, return the unfinished one - /// - If either is ready, return ready. - /// - If both are missing, return missing. - pub fn absorb(self, other: Self) -> Self { - if other.is_finished() { - return self; - } - - if self.is_finished() { - return other; - } - - if other.is_ready() || self.is_ready() { - return Self::Ready; - } - - if let (Self::Missing(mut a), Self::Missing(b)) = (self, other) { - a.extend(b); - return Self::Missing(a); - } - - unreachable!() - } - - /// Creates a new `Missing` control flow. - pub fn missing(name: &'static str, missing: &'static [&'static str]) -> Self { - Self::Missing(vec![(name, missing)]) - } - - /// Returns true if the filler is missing a required property. - pub fn as_missing(&self) -> Option<&[(&'static str, &'static [&'static str])]> { - match self { - Self::Missing(missing) => Some(missing), - _ => None, - } - } - - /// Returns `true` if the filler is missing information required to fill in - /// the transaction request. - pub const fn is_missing(&self) -> bool { - matches!(self, Self::Missing(_)) - } - - /// Returns `true` if the filler is ready to fill in the transaction - /// request. - pub const fn is_ready(&self) -> bool { - matches!(self, Self::Ready) - } - - /// Returns `true` if the filler is finished filling in the transaction - /// request. - pub const fn is_finished(&self) -> bool { - matches!(self, Self::Finished) - } -} - -/// A layer that can fill in a `TransactionRequest` with additional information. -/// -/// ## Lifecycle Notes -/// -/// The [`FillerControlFlow`] determines the lifecycle of a filler. Fillers -/// may be in one of three states: -/// - **Missing**: The filler is missing a required property to fill in the -/// transaction request. [`TxFiller::status`] should return -/// [`FillerControlFlow::Missing`]. -/// with a list of the missing properties. -/// - **Ready**: The filler is ready to fill in the transaction request. -/// [`TxFiller::status`] should return [`FillerControlFlow::Ready`]. -/// - **Finished**: The filler has filled in all properties that it can fill. -/// [`TxFiller::status`] should return [`FillerControlFlow::Finished`]. -pub trait TxFiller: Clone + Send + Sync + std::fmt::Debug { - /// The properties that this filler retrieves from the RPC. to fill in the - /// TransactionRequest. - type Fillable: Send + Sync + 'static; - - /// Joins this filler with another filler to compose multiple fillers. - fn join_with(self, other: T) -> JoinFill - where - T: TxFiller, - { - JoinFill::new(self, other) - } - - /// Return a control-flow enum indicating whether the filler is ready to - /// fill in the transaction request, or if it is missing required - /// properties. - fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow; - - /// Returns `true` if the filler is should continnue filling. - fn continue_filling(&self, tx: &SendableTx) -> bool { - tx.as_builder().map(|tx| self.status(tx).is_ready()).unwrap_or_default() - } - - /// Returns `true` if the filler is ready to fill in the transaction request. - fn ready(&self, tx: &N::TransactionRequest) -> bool { - self.status(tx).is_ready() - } - - /// Returns `true` if the filler is finished filling in the transaction request. - fn finished(&self, tx: &N::TransactionRequest) -> bool { - self.status(tx).is_finished() - } - - /// Prepares fillable properties, potentially by making an RPC request. - fn prepare( - &self, - provider: &P, - tx: &N::TransactionRequest, - ) -> impl_future!(>) - where - P: Provider, - T: Transport + Clone; - - /// Fills in the transaction request with the fillable properties. - fn fill( - &self, - fillable: Self::Fillable, - tx: SendableTx, - ) -> impl_future!(>>); - - /// Prepares and fills the transaction request with the fillable properties. - fn prepare_and_fill( - &self, - provider: &P, - tx: SendableTx, - ) -> impl_future!(>>) - where - P: Provider, - T: Transport + Clone, - { - async move { - if tx.is_envelope() { - return Ok(tx); - } - - let fillable = self.prepare(provider, tx.as_builder().unwrap()).await?; - - self.fill(fillable, tx).await - } - } -} /// A layer that can fill in a [`TransactionRequest`] with additional /// information by joining two [`TxFiller`]s. This struct is itself a @@ -281,92 +117,3 @@ where FillProvider::new(inner, self.clone()) } } - -/// A [`Provider`] that applies one or more [`TxFiller`]s. -/// -/// Fills arbitrary properties in a transaction request by composing multiple -/// fill layers. This struct should always be the outermost layer in a provider -/// stack, and this is enforced when using [`ProviderBuilder::filler`] to -/// construct this layer. -/// -/// Users should NOT use this struct directly. Instead, use -/// [`ProviderBuilder::filler`] to construct and apply it to a stack. -/// -/// [`ProviderBuilder::filler`]: crate::ProviderBuilder::filler - -#[derive(Debug, Clone)] -pub struct FillProvider -where - F: TxFiller, - P: Provider, - T: Transport + Clone, - N: Network, -{ - inner: P, - filler: F, - _pd: PhantomData (T, N)>, -} - -impl FillProvider -where - F: TxFiller, - P: Provider, - T: Transport + Clone, - N: Network, -{ - /// Creates a new `FillProvider` with the given filler and inner provider. - pub fn new(inner: P, filler: F) -> Self { - Self { inner, filler, _pd: PhantomData } - } - - /// Joins a filler to this provider - pub fn join_with>( - self, - other: Other, - ) -> FillProvider, P, T, N> { - self.filler.join_with(other).layer(self.inner) - } -} - -#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] -#[cfg_attr(not(target_arch = "wasm32"), async_trait)] -impl Provider for FillProvider -where - F: TxFiller, - P: Provider, - T: Transport + Clone, - N: Network, -{ - fn root(&self) -> &RootProvider { - self.inner.root() - } - - async fn send_transaction_internal( - &self, - mut tx: SendableTx, - ) -> TransportResult> { - let mut count = 0; - - while self.filler.continue_filling(&tx) { - tx = self.filler.prepare_and_fill(&self.inner, tx).await?; - - count += 1; - if count >= 20 { - panic!( - "Tx filler loop detected. This indicates a bug in some filler implementation. Please file an issue containing your tx filler set." - ); - } - } - - if let Some(builder) = tx.as_builder() { - if let FillerControlFlow::Missing(missing) = self.filler.status(builder) { - // TODO: improve this. - let message = format!("missing properties: {:?}", missing); - return Err(RpcError::make_err_resp(-42069, message)); - } - } - - // Errors in tx building happen further down the stack. - self.inner.send_transaction_internal(tx).await - } -} diff --git a/crates/provider/src/fillers/mod.rs b/crates/provider/src/fillers/mod.rs index 6dc5fd3af4b..6b04d3c828c 100644 --- a/crates/provider/src/fillers/mod.rs +++ b/crates/provider/src/fillers/mod.rs @@ -19,4 +19,265 @@ mod gas; pub use gas::GasFiller; mod join_fill; -pub use join_fill::{FillProvider, FillerControlFlow, JoinFill, TxFiller}; +pub use join_fill::JoinFill; + +use crate::{ + provider::SendableTx, PendingTransactionBuilder, Provider, ProviderLayer, RootProvider, +}; +use alloy_json_rpc::RpcError; +use alloy_network::{Ethereum, Network}; +use alloy_transport::{Transport, TransportResult}; +use async_trait::async_trait; +use futures_utils_wasm::impl_future; +use std::marker::PhantomData; + +/// The control flow for a filler. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum FillerControlFlow { + /// The filler is missing a required property. + /// + /// To allow joining fillers while preserving their associated missing + /// lists, this variant contains a list of `(name, missing)` tuples. When + /// absorbing another control flow, if both are missing, the missing lists + /// are combined. + Missing(Vec<(&'static str, &'static [&'static str])>), + /// The filler is ready to fill in the transaction request. + Ready, + /// The filler has filled in all properties that it can fill. + Finished, +} + +impl FillerControlFlow { + /// Absorb the control flow of another filler. + /// + /// # Behavior: + /// - If either is finished, return the unfinished one + /// - If either is ready, return ready. + /// - If both are missing, return missing. + pub fn absorb(self, other: Self) -> Self { + if other.is_finished() { + return self; + } + + if self.is_finished() { + return other; + } + + if other.is_ready() || self.is_ready() { + return Self::Ready; + } + + if let (Self::Missing(mut a), Self::Missing(b)) = (self, other) { + a.extend(b); + return Self::Missing(a); + } + + unreachable!() + } + + /// Creates a new `Missing` control flow. + pub fn missing(name: &'static str, missing: &'static [&'static str]) -> Self { + Self::Missing(vec![(name, missing)]) + } + + /// Returns true if the filler is missing a required property. + pub fn as_missing(&self) -> Option<&[(&'static str, &'static [&'static str])]> { + match self { + Self::Missing(missing) => Some(missing), + _ => None, + } + } + + /// Returns `true` if the filler is missing information required to fill in + /// the transaction request. + pub const fn is_missing(&self) -> bool { + matches!(self, Self::Missing(_)) + } + + /// Returns `true` if the filler is ready to fill in the transaction + /// request. + pub const fn is_ready(&self) -> bool { + matches!(self, Self::Ready) + } + + /// Returns `true` if the filler is finished filling in the transaction + /// request. + pub const fn is_finished(&self) -> bool { + matches!(self, Self::Finished) + } +} + +/// A layer that can fill in a `TransactionRequest` with additional information. +/// +/// ## Lifecycle Notes +/// +/// The [`FillerControlFlow`] determines the lifecycle of a filler. Fillers +/// may be in one of three states: +/// - **Missing**: The filler is missing a required property to fill in the +/// transaction request. [`TxFiller::status`] should return +/// [`FillerControlFlow::Missing`]. +/// with a list of the missing properties. +/// - **Ready**: The filler is ready to fill in the transaction request. +/// [`TxFiller::status`] should return [`FillerControlFlow::Ready`]. +/// - **Finished**: The filler has filled in all properties that it can fill. +/// [`TxFiller::status`] should return [`FillerControlFlow::Finished`]. +pub trait TxFiller: Clone + Send + Sync + std::fmt::Debug { + /// The properties that this filler retrieves from the RPC. to fill in the + /// TransactionRequest. + type Fillable: Send + Sync + 'static; + + /// Joins this filler with another filler to compose multiple fillers. + fn join_with(self, other: T) -> JoinFill + where + T: TxFiller, + { + JoinFill::new(self, other) + } + + /// Return a control-flow enum indicating whether the filler is ready to + /// fill in the transaction request, or if it is missing required + /// properties. + fn status(&self, tx: &N::TransactionRequest) -> FillerControlFlow; + + /// Returns `true` if the filler is should continnue filling. + fn continue_filling(&self, tx: &SendableTx) -> bool { + tx.as_builder().map(|tx| self.status(tx).is_ready()).unwrap_or_default() + } + + /// Returns `true` if the filler is ready to fill in the transaction request. + fn ready(&self, tx: &N::TransactionRequest) -> bool { + self.status(tx).is_ready() + } + + /// Returns `true` if the filler is finished filling in the transaction request. + fn finished(&self, tx: &N::TransactionRequest) -> bool { + self.status(tx).is_finished() + } + + /// Prepares fillable properties, potentially by making an RPC request. + fn prepare( + &self, + provider: &P, + tx: &N::TransactionRequest, + ) -> impl_future!(>) + where + P: Provider, + T: Transport + Clone; + + /// Fills in the transaction request with the fillable properties. + fn fill( + &self, + fillable: Self::Fillable, + tx: SendableTx, + ) -> impl_future!(>>); + + /// Prepares and fills the transaction request with the fillable properties. + fn prepare_and_fill( + &self, + provider: &P, + tx: SendableTx, + ) -> impl_future!(>>) + where + P: Provider, + T: Transport + Clone, + { + async move { + if tx.is_envelope() { + return Ok(tx); + } + + let fillable = self.prepare(provider, tx.as_builder().unwrap()).await?; + + self.fill(fillable, tx).await + } + } +} + +/// A [`Provider`] that applies one or more [`TxFiller`]s. +/// +/// Fills arbitrary properties in a transaction request by composing multiple +/// fill layers. This struct should always be the outermost layer in a provider +/// stack, and this is enforced when using [`ProviderBuilder::filler`] to +/// construct this layer. +/// +/// Users should NOT use this struct directly. Instead, use +/// [`ProviderBuilder::filler`] to construct and apply it to a stack. +/// +/// [`ProviderBuilder::filler`]: crate::ProviderBuilder::filler + +#[derive(Debug, Clone)] +pub struct FillProvider +where + F: TxFiller, + P: Provider, + T: Transport + Clone, + N: Network, +{ + inner: P, + filler: F, + _pd: PhantomData (T, N)>, +} + +impl FillProvider +where + F: TxFiller, + P: Provider, + T: Transport + Clone, + N: Network, +{ + /// Creates a new `FillProvider` with the given filler and inner provider. + pub fn new(inner: P, filler: F) -> Self { + Self { inner, filler, _pd: PhantomData } + } + + /// Joins a filler to this provider + pub fn join_with>( + self, + other: Other, + ) -> FillProvider, P, T, N> { + self.filler.join_with(other).layer(self.inner) + } +} + +#[cfg_attr(target_arch = "wasm32", async_trait(?Send))] +#[cfg_attr(not(target_arch = "wasm32"), async_trait)] +impl Provider for FillProvider +where + F: TxFiller, + P: Provider, + T: Transport + Clone, + N: Network, +{ + fn root(&self) -> &RootProvider { + self.inner.root() + } + + async fn send_transaction_internal( + &self, + mut tx: SendableTx, + ) -> TransportResult> { + let mut count = 0; + + while self.filler.continue_filling(&tx) { + tx = self.filler.prepare_and_fill(&self.inner, tx).await?; + + count += 1; + if count >= 20 { + panic!( + "Tx filler loop detected. This indicates a bug in some filler implementation. Please file an issue containing your tx filler set." + ); + } + } + + if let Some(builder) = tx.as_builder() { + if let FillerControlFlow::Missing(missing) = self.filler.status(builder) { + // TODO: improve this. + let message = format!("missing properties: {:?}", missing); + return Err(RpcError::make_err_resp(-42069, message)); + } + } + + // Errors in tx building happen further down the stack. + self.inner.send_transaction_internal(tx).await + } +} diff --git a/crates/provider/src/fillers/signer.rs b/crates/provider/src/fillers/signer.rs index 4ea250b83b2..9410aef8b89 100644 --- a/crates/provider/src/fillers/signer.rs +++ b/crates/provider/src/fillers/signer.rs @@ -77,6 +77,7 @@ where format!("failed to build transaction: {e}"), ) })?; + Ok(SendableTx::Envelope(envelope)) } } diff --git a/crates/transport/src/common.rs b/crates/transport/src/common.rs index 5018a696a33..9e56a8fe93b 100644 --- a/crates/transport/src/common.rs +++ b/crates/transport/src/common.rs @@ -23,8 +23,6 @@ impl Authorization { return None; } - dbg!(username, password); - (!username.is_empty() || !password.is_empty()).then(|| Self::basic(username, password)) } From 0846652510181a1104d9da825807ee5bd1f11ab3 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 5 Apr 2024 10:55:14 -0700 Subject: [PATCH 37/45] fix: workaround anvil gas est issue --- crates/provider/src/builder.rs | 2 +- crates/provider/src/fillers/gas.rs | 20 +++++++++---------- .../rpc-types/src/eth/transaction/request.rs | 6 ++++++ 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index a1419962652..8e12474eb23 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -340,7 +340,7 @@ impl ProviderBuilder { pub fn on_anvil(self) -> (F::Provider, alloy_node_bindings::AnvilInstance) where F: TxFiller - + ProviderLayer, Ethereum>, + + ProviderLayer, Ethereum>, L: ProviderLayer< crate::ReqwestProvider, alloy_transport_http::Http, diff --git a/crates/provider/src/fillers/gas.rs b/crates/provider/src/fillers/gas.rs index c1b67ec59e6..1f45a0e0a02 100644 --- a/crates/provider/src/fillers/gas.rs +++ b/crates/provider/src/fillers/gas.rs @@ -290,34 +290,34 @@ mod tests { let tx = provider.send_transaction(tx).await.unwrap(); - let tx = tx.get_receipt().await.unwrap(); + let tx_hash = tx.tx_hash(); - assert_eq!(tx.gas_used, Some(0x5208)); + let receipt = provider.get_transaction_receipt(*tx_hash).await.unwrap().unwrap(); + + assert_eq!(receipt.gas_used, Some(0x5208)); } #[tokio::test] async fn non_eip1559_network() { - let (provider, anvil) = ProviderBuilder::new() + let (provider, _anvil) = ProviderBuilder::new() .filler(crate::fillers::GasFiller) .filler(crate::fillers::NonceFiller::default()) .filler(crate::fillers::ChainIdFiller::default()) - .on_anvil_with_signer(); + .on_anvil(); let tx = TransactionRequest { - from: Some(anvil.addresses()[0]), + from: Some(address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266")), value: Some(U256::from(100)), to: address!("d8dA6BF26964aF9D7eEd9e03E53415D37aA96045").into(), + // access list forces legacy gassing access_list: Some(vec![Default::default()].into()), ..Default::default() }; let tx = provider.send_transaction(tx).await.unwrap(); - let _: () = - provider.raw_request(std::borrow::Cow::Borrowed("anvil_mine"), &["0x5"]).await.unwrap(); - - let tx = tx.get_receipt().await.unwrap(); + let receipt = tx.get_receipt().await.unwrap(); - assert_eq!(tx.effective_gas_price, 0x6fc23ac0); + assert_eq!(receipt.effective_gas_price, 2000000000); } } diff --git a/crates/rpc-types/src/eth/transaction/request.rs b/crates/rpc-types/src/eth/transaction/request.rs index 8d3a568407f..fb51393af01 100644 --- a/crates/rpc-types/src/eth/transaction/request.rs +++ b/crates/rpc-types/src/eth/transaction/request.rs @@ -220,6 +220,12 @@ impl TransactionInput { } } +impl From> for TransactionInput { + fn from(input: Vec) -> Self { + Self { input: Some(input.into()), data: None } + } +} + impl From for TransactionInput { fn from(input: Bytes) -> Self { Self { input: Some(input), data: None } From a7295e5e2eab10d62722c113cc1886abf7479b99 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 5 Apr 2024 11:20:18 -0700 Subject: [PATCH 38/45] fix: doctests --- crates/contract/README.md | 2 +- crates/provider/src/fillers/chain_id.rs | 11 ++++++++--- crates/provider/src/fillers/gas.rs | 13 +++++++++---- crates/provider/src/fillers/nonce.rs | 10 +++++++--- crates/provider/src/fillers/signer.rs | 10 +++++++--- 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/crates/contract/README.md b/crates/contract/README.md index 5cd97ea8e51..dd830b68f2f 100644 --- a/crates/contract/README.md +++ b/crates/contract/README.md @@ -32,7 +32,7 @@ sol! { } // Build a provider. -let provider = ProviderBuilder::new().with_recommended_layers().on_builtin("http://localhost:8545").await?; +let provider = ProviderBuilder::new().with_recommended_fillers().on_builtin("http://localhost:8545").await?; // If `#[sol(bytecode = "0x...")]` is provided, the contract can be deployed with `MyContract::deploy`, // and a new instance will be created. diff --git a/crates/provider/src/fillers/chain_id.rs b/crates/provider/src/fillers/chain_id.rs index aa3f4b5cb44..cb66ccc3cf0 100644 --- a/crates/provider/src/fillers/chain_id.rs +++ b/crates/provider/src/fillers/chain_id.rs @@ -21,14 +21,19 @@ use crate::{ /// # Example /// /// ``` -/// # async fn test>(transport: T, signer: S) { +/// # use alloy_network::{NetworkSigner, EthereumSigner, Ethereum}; +/// # use alloy_rpc_types::TransactionRequest; +/// # use alloy_provider::{ProviderBuilder, RootProvider, Provider}; +/// # async fn test + Clone>(url: url::Url, signer: S) -> Result<(), Box> { /// let provider = ProviderBuilder::new() /// .with_chain_id(1) -/// .signer(EthereumSigner::from(signer)) // note the order! -/// .provider(RootProvider::new(transport)); +/// .signer(signer) +/// .on_http(url)?; /// /// provider.send_transaction(TransactionRequest::default()).await; +/// # Ok(()) /// # } +/// ``` #[derive(Debug, Clone, Default, PartialEq, Eq)] pub struct ChainIdFiller(Arc>); diff --git a/crates/provider/src/fillers/gas.rs b/crates/provider/src/fillers/gas.rs index 1f45a0e0a02..f19da3d8b25 100644 --- a/crates/provider/src/fillers/gas.rs +++ b/crates/provider/src/fillers/gas.rs @@ -48,14 +48,19 @@ pub enum GasFillable { /// # Example /// /// ``` -/// # async fn test>(transport: T, signer: S) { +/// # use alloy_network::{NetworkSigner, EthereumSigner, Ethereum}; +/// # use alloy_rpc_types::TransactionRequest; +/// # use alloy_provider::{ProviderBuilder, RootProvider, Provider}; +/// # async fn test + Clone>(url: url::Url, signer: S) -> Result<(), Box> { /// let provider = ProviderBuilder::new() -/// .with_recommended_fillers() -/// .signer(EthereumSigner::from(signer)) // note the order! -/// .provider(RootProvider::new(transport)); +/// .with_gas_estimation() +/// .signer(signer) +/// .on_http(url)?; /// /// provider.send_transaction(TransactionRequest::default()).await; +/// # Ok(()) /// # } +/// ``` #[derive(Debug, Clone, Copy, Default)] pub struct GasFiller; diff --git a/crates/provider/src/fillers/nonce.rs b/crates/provider/src/fillers/nonce.rs index 9f69563763c..ebe174f58c5 100644 --- a/crates/provider/src/fillers/nonce.rs +++ b/crates/provider/src/fillers/nonce.rs @@ -27,13 +27,17 @@ use tokio::sync::Mutex; /// # Example /// /// ``` -/// # async fn test>(transport: T, signer: S) { +/// # use alloy_network::{NetworkSigner, EthereumSigner, Ethereum}; +/// # use alloy_rpc_types::TransactionRequest; +/// # use alloy_provider::{ProviderBuilder, RootProvider, Provider}; +/// # async fn test + Clone>(url: url::Url, signer: S) -> Result<(), Box> { /// let provider = ProviderBuilder::new() /// .with_nonce_management() -/// .signer(EthereumSigner::from(signer)) // note the order! -/// .provider(RootProvider::new(transport)); +/// .signer(signer) +/// .on_http(url)?; /// /// provider.send_transaction(TransactionRequest::default()).await; +/// # Ok(()) /// # } /// ``` #[derive(Debug, Clone, Default)] diff --git a/crates/provider/src/fillers/signer.rs b/crates/provider/src/fillers/signer.rs index 9410aef8b89..ad8a19ddaf7 100644 --- a/crates/provider/src/fillers/signer.rs +++ b/crates/provider/src/fillers/signer.rs @@ -14,12 +14,16 @@ use super::{FillerControlFlow, TxFiller}; /// # Example /// /// ``` -/// # async fn test>(transport: T, signer: S) { +/// # use alloy_network::{NetworkSigner, EthereumSigner, Ethereum}; +/// # use alloy_rpc_types::TransactionRequest; +/// # use alloy_provider::{ProviderBuilder, RootProvider, Provider}; +/// # async fn test + Clone>(url: url::Url, signer: S) -> Result<(), Box> { /// let provider = ProviderBuilder::new() -/// .signer(EthereumSigner::from(signer)) -/// .provider(RootProvider::new(transport)); +/// .signer(signer) +/// .on_http(url)?; /// /// provider.send_transaction(TransactionRequest::default()).await; +/// # Ok(()) /// # } /// ``` #[derive(Debug, Clone)] From 90ba04cdb83c65950fc041934bdd7cb787db5c9c Mon Sep 17 00:00:00 2001 From: James Date: Fri, 5 Apr 2024 11:22:56 -0700 Subject: [PATCH 39/45] fix: anvil => request --- crates/provider/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/provider/Cargo.toml b/crates/provider/Cargo.toml index 9dc581f7d74..4df2701a0f7 100644 --- a/crates/provider/Cargo.toml +++ b/crates/provider/Cargo.toml @@ -63,4 +63,4 @@ reqwest = [ hyper = ["dep:alloy-transport-http", "dep:url", "alloy-rpc-client/hyper"] ws = ["pubsub", "alloy-rpc-client/ws", "alloy-transport-ws"] ipc = ["pubsub", "alloy-rpc-client/ipc", "alloy-transport-ipc"] -anvil = ["dep:alloy-node-bindings", "dep:alloy-signer-wallet"] \ No newline at end of file +anvil = ["reqwest", "dep:alloy-node-bindings", "dep:alloy-signer-wallet"] \ No newline at end of file From fe0aa67e0f119bcb99b239ff7d8d9b9a264ede79 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 5 Apr 2024 11:25:03 -0700 Subject: [PATCH 40/45] fix: test --- crates/provider/src/fillers/gas.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/provider/src/fillers/gas.rs b/crates/provider/src/fillers/gas.rs index f19da3d8b25..95bba241776 100644 --- a/crates/provider/src/fillers/gas.rs +++ b/crates/provider/src/fillers/gas.rs @@ -295,9 +295,7 @@ mod tests { let tx = provider.send_transaction(tx).await.unwrap(); - let tx_hash = tx.tx_hash(); - - let receipt = provider.get_transaction_receipt(*tx_hash).await.unwrap().unwrap(); + let receipt = tx.get_receipt().await.unwrap(); assert_eq!(receipt.gas_used, Some(0x5208)); } From 4ced66c6e03662b7cca122deeebfaaffba31c4bd Mon Sep 17 00:00:00 2001 From: James Date: Fri, 5 Apr 2024 12:46:30 -0700 Subject: [PATCH 41/45] chore: note about blocking on TODO --- crates/provider/src/fillers/signer.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/provider/src/fillers/signer.rs b/crates/provider/src/fillers/signer.rs index ad8a19ddaf7..57d60ed0283 100644 --- a/crates/provider/src/fillers/signer.rs +++ b/crates/provider/src/fillers/signer.rs @@ -49,6 +49,8 @@ where if tx.can_build() { FillerControlFlow::Ready } else { + // Blocked by #431 + // https://github.com/alloy-rs/alloy/pull/431 FillerControlFlow::Missing(vec![("Signer", &["TODO"])]) } } From e21f8fe88249c8b0f7f38bdb85290fe35aee67c1 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 5 Apr 2024 13:09:10 -0700 Subject: [PATCH 42/45] feature: local usage error --- crates/json-rpc/src/error.rs | 33 +++++++++++++++++++-------- crates/provider/src/fillers/mod.rs | 3 ++- crates/provider/src/fillers/signer.rs | 9 ++------ 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/crates/json-rpc/src/error.rs b/crates/json-rpc/src/error.rs index c13b4289d98..e907dd9cc46 100644 --- a/crates/json-rpc/src/error.rs +++ b/crates/json-rpc/src/error.rs @@ -16,6 +16,11 @@ pub enum RpcError> { #[error("unsupported feature: {0}")] UnsupportedFeature(&'static str), + /// Returned when a local pre-processing step fails. This allows custom + /// errors from local signers or request pre-processors. + #[error("local usage error: {0}")] + LocalUsageError(#[source] Box), + /// JSON serialization error. #[error("serialization error: {0}")] SerError( @@ -52,22 +57,22 @@ impl RpcError where ErrResp: RpcReturn, { - /// Instantiate a new `TransportError` from an error response. + /// Instantiate a new `ErrorResp` from an error response. pub const fn err_resp(err: ErrorPayload) -> Self { Self::ErrorResp(err) } - /// Instantiate a new `RpcError` from a custom error string. - pub const fn make_err_resp(code: i64, message: String) -> Self { - Self::ErrorResp(ErrorPayload { code, message, data: None }) + /// Instantiate a new `LocalUsageError` from a custom error. + pub fn local_usage(err: impl std::error::Error + Send + Sync + 'static) -> Self { + Self::LocalUsageError(Box::new(err)) } - /// Instantiate a new `RpcError` from a custom error with data. - pub const fn make_err_resp_with_data(code: i64, message: String, data: ErrResp) -> Self { - Self::ErrorResp(ErrorPayload { code, message, data: Some(data) }) + /// Instantiate a new `LocalUsageError` from a custom error message. + pub fn local_usage_str(err: &str) -> Self { + Self::LocalUsageError(err.into()) } - /// Instantiate a new `TransportError` from a [`serde_json::Error`] and the + /// Instantiate a new `DeserError` from a [`serde_json::Error`] and the /// text. This should be called when the error occurs during /// deserialization. /// @@ -86,7 +91,7 @@ where } impl RpcError { - /// Instantiate a new `RpcError` from a [`serde_json::Error`]. This + /// Instantiate a new `SerError` from a [`serde_json::Error`]. This /// should be called when the error occurs during serialization. pub const fn ser_err(err: serde_json::Error) -> Self { Self::SerError(err) @@ -117,6 +122,16 @@ impl RpcError { matches!(self, Self::NullResp) } + /// Check if the error is an unsupported feature error. + pub const fn is_unsupported_feature(&self) -> bool { + matches!(self, Self::UnsupportedFeature(_)) + } + + /// Check if the error is a local usage error. + pub const fn is_local_usage_error(&self) -> bool { + matches!(self, Self::LocalUsageError(_)) + } + /// Fallible conversion to an error response. pub const fn as_error_resp(&self) -> Option<&ErrorPayload> { match self { diff --git a/crates/provider/src/fillers/mod.rs b/crates/provider/src/fillers/mod.rs index 6b04d3c828c..e5a2221c78a 100644 --- a/crates/provider/src/fillers/mod.rs +++ b/crates/provider/src/fillers/mod.rs @@ -272,8 +272,9 @@ where if let Some(builder) = tx.as_builder() { if let FillerControlFlow::Missing(missing) = self.filler.status(builder) { // TODO: improve this. + // blocked by #431 let message = format!("missing properties: {:?}", missing); - return Err(RpcError::make_err_resp(-42069, message)); + return Err(RpcError::local_usage_str(&message)); } } diff --git a/crates/provider/src/fillers/signer.rs b/crates/provider/src/fillers/signer.rs index 57d60ed0283..e1fb14079f6 100644 --- a/crates/provider/src/fillers/signer.rs +++ b/crates/provider/src/fillers/signer.rs @@ -1,7 +1,7 @@ use crate::{provider::SendableTx, Provider}; use alloy_json_rpc::RpcError; use alloy_network::{Network, NetworkSigner, TransactionBuilder}; -use alloy_transport::{Transport, TransportErrorKind, TransportResult}; +use alloy_transport::{Transport, TransportResult}; use super::{FillerControlFlow, TxFiller}; @@ -77,12 +77,7 @@ where _ => return Ok(tx), }; - let envelope = builder.build(&self.signer).await.map_err(|e| { - RpcError::::make_err_resp( - -42069, - format!("failed to build transaction: {e}"), - ) - })?; + let envelope = builder.build(&self.signer).await.map_err(RpcError::local_usage)?; Ok(SendableTx::Envelope(envelope)) } From fef56d418af7f9bb91a34d71af524636955862b0 Mon Sep 17 00:00:00 2001 From: James Date: Fri, 5 Apr 2024 15:55:26 -0700 Subject: [PATCH 43/45] fix: review nits --- crates/provider/src/builder.rs | 5 +++-- crates/provider/src/fillers/mod.rs | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/provider/src/builder.rs b/crates/provider/src/builder.rs index 8e12474eb23..a5890bae247 100644 --- a/crates/provider/src/builder.rs +++ b/crates/provider/src/builder.rs @@ -127,7 +127,8 @@ impl Default for ProviderBuilder { } impl ProviderBuilder { - /// Add preconfigured set of layers handling gas estimation and nonce management + /// Add preconfigured set of layers handling gas estimation, nonce + /// management, and chain-id fetching. pub fn with_recommended_fillers(self) -> ProviderBuilder { self.filler(GasFiller).filler(NonceFiller::default()).filler(ChainIdFiller::default()) } @@ -310,7 +311,7 @@ impl ProviderBuilder { Ok(self.on_client(client)) } - /// Build this provider with an Hyper HTTP qransport. + /// Build this provider with an Hyper HTTP transport. #[cfg(feature = "hyper")] pub fn on_hyper_http(self, url: url::Url) -> Result where diff --git a/crates/provider/src/fillers/mod.rs b/crates/provider/src/fillers/mod.rs index e5a2221c78a..601e0930735 100644 --- a/crates/provider/src/fillers/mod.rs +++ b/crates/provider/src/fillers/mod.rs @@ -186,7 +186,8 @@ pub trait TxFiller: Clone + Send + Sync + std::fmt::Debug return Ok(tx); } - let fillable = self.prepare(provider, tx.as_builder().unwrap()).await?; + let fillable = + self.prepare(provider, tx.as_builder().expect("checked by is_envelope")).await?; self.fill(fillable, tx).await } From 52ce1dc9de1a5cf44b36da76b71ab1929ad26752 Mon Sep 17 00:00:00 2001 From: James Prestwich Date: Sat, 6 Apr 2024 11:40:21 -0700 Subject: [PATCH 44/45] Update crates/provider/src/fillers/mod.rs Co-authored-by: Oliver Nordbjerg --- crates/provider/src/fillers/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/provider/src/fillers/mod.rs b/crates/provider/src/fillers/mod.rs index 601e0930735..bf4eaa7e333 100644 --- a/crates/provider/src/fillers/mod.rs +++ b/crates/provider/src/fillers/mod.rs @@ -205,7 +205,6 @@ pub trait TxFiller: Clone + Send + Sync + std::fmt::Debug /// [`ProviderBuilder::filler`] to construct and apply it to a stack. /// /// [`ProviderBuilder::filler`]: crate::ProviderBuilder::filler - #[derive(Debug, Clone)] pub struct FillProvider where From b48ff248b85c4cbeb9659e899d19d10e02e04bf3 Mon Sep 17 00:00:00 2001 From: James Date: Sat, 6 Apr 2024 11:41:51 -0700 Subject: [PATCH 45/45] fix: capitalization so @danipopes doesn't hurt me --- crates/json-rpc/src/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/json-rpc/src/error.rs b/crates/json-rpc/src/error.rs index e907dd9cc46..7514a590834 100644 --- a/crates/json-rpc/src/error.rs +++ b/crates/json-rpc/src/error.rs @@ -5,7 +5,7 @@ use serde_json::value::RawValue; #[derive(Debug, thiserror::Error)] pub enum RpcError> { /// Server returned an error response. - #[error("Server returned an error response: {0}")] + #[error("server returned an error response: {0}")] ErrorResp(ErrorPayload), /// Server returned a null response when a non-null response was expected.