Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Joinable transaction fillers #426

Merged
merged 45 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
adaef96
feature: TxFiller
prestwich Mar 29, 2024
ec38e5d
lint: clippy
prestwich Mar 29, 2024
247599c
doc: CONSIDER
prestwich Mar 29, 2024
1621849
doc: more notes
prestwich Mar 29, 2024
9e009ca
fix: get rid of ugly lifetimes
prestwich Mar 29, 2024
d1a19c5
fix: docs and lints
prestwich Mar 29, 2024
55ef5f3
fix: remove populate functions
prestwich Mar 29, 2024
a64b1c4
nit: remove commented code
prestwich Mar 29, 2024
a76ecaf
feature: FillerControlFlow
prestwich Mar 29, 2024
d272290
doc: lifecycle notes
prestwich Mar 29, 2024
935fd9b
refactor: request -> prepare
prestwich Mar 29, 2024
021c1bd
lint: clippy
prestwich Mar 29, 2024
c19aaf4
fix: missing block in absorb
prestwich Mar 29, 2024
5f59ec0
fix: absorb preserves association
prestwich Mar 29, 2024
49cf6a8
refactor: additional info in missing
prestwich Mar 29, 2024
be9cec6
fix: impl_future macro
prestwich Mar 30, 2024
e6ae1b9
fix: resolve considers
prestwich Apr 1, 2024
b4f22a6
refactor: gas layer to gas filler
prestwich Apr 2, 2024
8f732a9
refactor: improve provider builder
prestwich Apr 2, 2024
5c26fa7
refactor: filler is outmost layer
prestwich Apr 2, 2024
e40b1c8
refactor: protect against double-fill and add anvil shortcut
prestwich Apr 2, 2024
f663dd8
doc: improve docstrings for noncemanager and gas filler
prestwich Apr 2, 2024
ffef2ab
fix: delete unused types
prestwich Apr 2, 2024
741eb02
refactor: layers to fillers
prestwich Apr 2, 2024
cdb9f80
feature: chain id filler
prestwich Apr 2, 2024
b1358ff
refactor: send_transaction_inner and SendableTx
prestwich Apr 3, 2024
bc475a9
wip: sig filler
prestwich Apr 3, 2024
2a533c5
refactor: SignerFiller
prestwich Apr 3, 2024
6beabc9
fix: remove clone
prestwich Apr 3, 2024
c9976b2
docs: fix some
prestwich Apr 3, 2024
ca3197d
fix: complete todo
prestwich Apr 3, 2024
bc3bc80
feature: anvil feature for alloy-provider
prestwich Apr 3, 2024
8022cc8
wip: tests
prestwich Apr 5, 2024
7353f79
fix: apply changes from other PRs
prestwich Apr 5, 2024
1c376d7
chore: fmt
prestwich Apr 5, 2024
9401eac
feature: on_anvil
prestwich Apr 5, 2024
0846652
fix: workaround anvil gas est issue
prestwich Apr 5, 2024
a7295e5
fix: doctests
prestwich Apr 5, 2024
90ba04c
fix: anvil => request
prestwich Apr 5, 2024
fe0aa67
fix: test
prestwich Apr 5, 2024
4ced66c
chore: note about blocking on TODO
prestwich Apr 5, 2024
e21f8fe
feature: local usage error
prestwich Apr 5, 2024
fef56d4
fix: review nits
prestwich Apr 5, 2024
52ce1dc
Update crates/provider/src/fillers/mod.rs
prestwich Apr 6, 2024
b48ff24
fix: capitalization so @danipopes doesn't hurt me
prestwich Apr 6, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor: send_transaction_inner and SendableTx
  • Loading branch information
prestwich committed Apr 5, 2024
commit b1358ff5fd54a3e2bd6512f5774bcfd3d0483490
1 change: 1 addition & 0 deletions crates/provider/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion crates/provider/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::{
fillers::{
ChainIdFiller, FillerControlFlow, GasFiller, JoinFill, NonceFiller, SignerLayer, TxFiller,
},
provider::SendableTx,
Provider, RootProvider,
};
use alloy_network::{Ethereum, Network};
Expand Down Expand Up @@ -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<N>) {
// Do nothing
}
}
Expand Down
31 changes: 16 additions & 15 deletions crates/provider/src/fillers/chain_id.rs
Original file line number Diff line number Diff line change
@@ -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.
///
Expand Down Expand Up @@ -44,14 +47,11 @@ impl ChainIdFiller {
}
}

impl TxFiller for ChainIdFiller {
impl<N: Network> TxFiller<N> for ChainIdFiller {
type Fillable = u64;

fn status(
&self,
tx: &<alloy_network::Ethereum as alloy_network::Network>::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
Expand All @@ -61,10 +61,10 @@ impl TxFiller for ChainIdFiller {
async fn prepare<P, T>(
&self,
provider: &P,
_tx: &<alloy_network::Ethereum as alloy_network::Network>::TransactionRequest,
_tx: &N::TransactionRequest,
) -> TransportResult<Self::Fillable>
where
P: crate::Provider<T, alloy_network::Ethereum>,
P: crate::Provider<T, N>,
T: alloy_transport::Transport + Clone,
{
match self.0.get().copied() {
Expand All @@ -77,11 +77,12 @@ impl TxFiller for ChainIdFiller {
}
}

fn fill(
&self,
fillable: Self::Fillable,
tx: &mut <alloy_network::Ethereum as alloy_network::Network>::TransactionRequest,
) {
fn fill(&self, fillable: Self::Fillable, tx: &mut SendableTx<N>) {
let tx = match tx {
SendableTx::Builder(tx) => tx,
_ => return,
};

if tx.chain_id().is_none() {
tx.set_chain_id(fillable);
}
Expand Down
8 changes: 7 additions & 1 deletion crates/provider/src/fillers/gas.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{
fillers::{FillerControlFlow, TxFiller},
provider::SendableTx,
utils::Eip1559Estimation,
Provider,
};
Expand Down Expand Up @@ -212,7 +213,12 @@ impl<N: Network> TxFiller<N> for GasFiller {
}
}

fn fill(&self, fillable: Self::Fillable, tx: &mut <N as Network>::TransactionRequest) {
fn fill(&self, fillable: Self::Fillable, tx: &mut SendableTx<N>) {
let tx = match tx {
SendableTx::Builder(tx) => tx,
_ => return,
};

match fillable {
GasFillable::Legacy { gas_limit, gas_price } => {
tx.set_gas_limit(gas_limit);
Expand Down
65 changes: 52 additions & 13 deletions crates/provider/src/fillers/join_fill.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -114,6 +116,11 @@ pub trait TxFiller<N: Network = Ethereum>: 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<N>) -> 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()
Expand All @@ -135,12 +142,37 @@ pub trait TxFiller<N: Network = Ethereum>: 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<N>);

/// Prepares and fills the transaction request with the fillable properties.
fn prepare_and_fill<P, T>(
&self,
provider: &P,
tx: &mut SendableTx<N>,
) -> impl_future!(<Output = TransportResult<()>>)
where
P: Provider<T, N>,
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<L, R> {
left: L,
Expand Down Expand Up @@ -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<N>) {
if let Some(to_fill) = to_fill.0 {
self.left.fill(to_fill, tx);
};
Expand All @@ -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<F, P, T, N>
Expand Down Expand Up @@ -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<N>,
) -> TransportResult<PendingTransactionBuilder<'_, T, N>> {
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 {
Expand All @@ -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
}
}
7 changes: 6 additions & 1 deletion crates/provider/src/fillers/nonce.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::{
fillers::{FillerControlFlow, TxFiller},
provider::SendableTx,
Provider,
};
use alloy_network::{Network, TransactionBuilder};
Expand Down Expand Up @@ -66,7 +67,11 @@ impl<N: Network> TxFiller<N> 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<N>) {
let tx = match tx {
SendableTx::Builder(tx) => tx,
_ => return,
};
tx.set_nonce(nonce);
}
}
Expand Down
80 changes: 76 additions & 4 deletions crates/provider/src/provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -46,6 +47,51 @@ use alloy_pubsub::{PubSubFrontend, Subscription};
/// See [`PollerBuilder`] for more details.
pub type FilterPollerBuilder<T, R> = PollerBuilder<T, (U256,), Vec<R>>;

/// A transaction that will be
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SendableTx<N: Network> {
/// A transaction that is not yet signed.
Builder(N::TransactionRequest),
/// A transaction that is signed and fully constructed.
Envelope(N::TxEnvelope),
}

impl<N: Network> SendableTx<N> {
/// 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<T, N = Ethereum> {
Expand Down Expand Up @@ -619,18 +665,44 @@ pub trait Provider<T: Transport + Clone = BoxTransport, N: Network = Ethereum>:
&self,
tx: N::TransactionRequest,
) -> TransportResult<PendingTransactionBuilder<'_, T, N>> {
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<N>,
) -> TransportResult<PendingTransactionBuilder<'_, T, N>> {
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
}
}
Comment on lines +691 to +701
Copy link
Member

Choose a reason for hiding this comment

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

found this enum idea + internal very interesting

Copy link
Member Author

Choose a reason for hiding this comment

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

originally i was going to push this enum into the network crate, but it turned out to be a lot easier and cleaner to do it here. additional followup work would be send_envelope

}

/// Broadcasts a raw transaction RLP bytes to the network.
///
/// See [`send_transaction`](Self::send_transaction) for more details.
async fn send_raw_transaction(
&self,
rlp_bytes: &[u8],
encoded_tx: &[u8],
) -> TransportResult<PendingTransactionBuilder<'_, T, N>> {
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))
}
Expand Down