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

Feature: Joinable transaction fillers #426

merged 45 commits into from
Apr 8, 2024

Conversation

prestwich
Copy link
Member

@prestwich prestwich commented Mar 29, 2024

WIP PR for discussion

Motivation

Solution

  • create a TxFiller<N> trait
  • create a JoinFill<L, R, N> to compose TxFiller<N>
  • Create a FillProvider<F, P, N, T> so that TxFiller<N> all share a common provider layer

Remaining:

  • See CONSIDER comments for discussion
  • Convert gas estimation and nonce and chain id to use this pattern
  • Modify ProviderBuilder to add .with_filler() for fillers
  • Add send_transaction_internal to abstract over builder and envelope
  • Change behavior of <RootProvider as Provider>::send_transaction to always prefer raw transaction
  • finish porting signer
  • tests passing

PR Checklist

  • Added Tests
  • Added Documentation
  • Breaking changes

@prestwich prestwich added enhancement New feature or request debt Tech debt which needs to be addressed labels Mar 29, 2024
@prestwich prestwich self-assigned this Mar 29, 2024
@prestwich prestwich changed the title Feature: Joinable transaction fille Feature: Joinable transaction fillers Mar 29, 2024
crates/provider/src/layers/join_fill.rs Outdated Show resolved Hide resolved
crates/provider/src/layers/join_fill.rs Outdated Show resolved Hide resolved
@prestwich prestwich marked this pull request as ready for review April 5, 2024 18:26
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?;
Copy link
Member

Choose a reason for hiding this comment

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

nit, both layers + fillers in example?

and still thinking about the on_builtin naming separately, did you not like connect_?

Copy link
Member Author

Choose a reason for hiding this comment

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

there are currently no layers because they were all converted to fillers 😅

Comment on lines +222 to +228
/// 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;
Copy link
Member

Choose a reason for hiding this comment

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

like

alloy-json-rpc.workspace = true
alloy-network.workspace = true
alloy-node-bindings = { workspace = true, optional = true }
alloy-signer-wallet = { workspace = true, optional = true }
Copy link
Member

Choose a reason for hiding this comment

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

i am guessing this is fine / no risk of circular dep in the future

Copy link
Member Author

Choose a reason for hiding this comment

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

it'd be a major break from the abstraction stack if there were 😅

@@ -22,6 +29,33 @@ pub trait ProviderLayer<P: Provider<T, N>, T: Transport + Clone, N: Network = Et
#[derive(Debug, Clone, Copy)]
pub struct Identity;

impl<N> TxFiller<N> for Identity
Copy link
Member

Choose a reason for hiding this comment

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

👍

Copy link
Member

@gakonst gakonst left a comment

Choose a reason for hiding this comment

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

Great stuff. Mainly nits and questions.

Comment on lines 149 to 165
/// 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<L, JoinFill<Identity, ChainIdFiller>, 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<L, JoinFill<Identity, ChainIdFiller>, N> {
self.filler(ChainIdFiller::new(Some(chain_id)))
}
Copy link
Member

Choose a reason for hiding this comment

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

this is cool

crates/provider/src/builder.rs Outdated Show resolved Hide resolved
Comment on lines 130 to 133
/// Add preconfigured set of layers handling gas estimation and nonce management
pub fn with_recommended_fillers(self) -> ProviderBuilder<L, RecommendFiller, N> {
self.filler(GasFiller).filler(NonceFiller::default()).filler(ChainIdFiller::default())
}
Copy link
Member

Choose a reason for hiding this comment

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

Am I allowed to do with_recommended_fillers().with_chain_id(1)? I guess yes?

Copy link
Member Author

Choose a reason for hiding this comment

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

the answer is no*. no because with_chain_id is implemented only when the builder has the Identity nop filler.

  • you could do with_recommended_fillers().filler(ChainIdFiller::new(Some(1))) but then you would have multiple ChainId fillers

crates/provider/src/builder.rs Outdated Show resolved Hide resolved
L: ProviderLayer<
crate::ReqwestProvider<Ethereum>,
alloy_transport_http::Http<reqwest::Client>,
Ethereum,
Copy link
Member

Choose a reason for hiding this comment

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

Maybe AnyNetwork? Believe we have some non-Ethereum code in Anvil for RPC responses in some places, or when forking?

Copy link
Member Author

Choose a reason for hiding this comment

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

kinda reluctant here, as it adds complexity to writing tests to cover behavior that the tests don't really need

Comment on lines +691 to +701
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
}
}
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

Comment on lines +52 to +56
/// 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`].
Copy link
Member

Choose a reason for hiding this comment

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

👍

Comment on lines +70 to +83
async fn fill(
&self,
_fillable: Self::Fillable,
tx: SendableTx<N>,
) -> TransportResult<SendableTx<N>> {
let builder = match tx {
SendableTx::Builder(builder) => builder,
_ => return Ok(tx),
};

let envelope = builder.build(&self.signer).await.map_err(RpcError::local_usage)?;

Ok(SendableTx::Envelope(envelope))
}
Copy link
Member

Choose a reason for hiding this comment

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

OK I see the need for the enum now.

Are there other cases beyond signers where we expect the builder -> envelope conversion?

Copy link
Member Author

Choose a reason for hiding this comment

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

technically signing is the only way to make this conversion, as the builder will never have a signature, and the envelope must have a signature

N: Network,
S: NetworkSigner<N> + Clone,
{
type Fillable = ();
Copy link
Member

Choose a reason for hiding this comment

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

Is this the signature type of the NetworkSigner? But I guess is unused.

Copy link
Member Author

Choose a reason for hiding this comment

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

NetworkSigner never outputs a signature type, to allow for abstraction over future signature types. This is the only implementation reason why fill is async. I would have liked to keep it sync, but that's incompatible with signature abstraction unless we introduce an additional abstraction like SignatureFor<N> which I think we don't want to do 😮‍💨

impl<S, N> TxFiller<N> for SignerFiller<S>
where
N: Network,
S: NetworkSigner<N> + Clone,
Copy link
Member

Choose a reason for hiding this comment

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

Given this, how should users think about NetworkSigner vs Signer vs ...?

Copy link
Member Author

@prestwich prestwich Apr 5, 2024

Choose a reason for hiding this comment

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

The average user should:

  • Always use NetworkSigner
  • Never use Signer
  • Never use TxSigner

Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

I like that we now have a clear and configurable processing for:
a) filling transactions
b) building them

for most use cases the builtin fillers are sufficient but they can still be customized, only then the user has to deal with the filler trait
and the behaviour is now properly documented and should be easy to follow

crates/json-rpc/src/error.rs Outdated Show resolved Hide resolved
}

fn get_blob_sidecar(&self) -> Option<&BlobTransactionSidecar> {
self.deref().get_blob_sidecar()
fn blob_sidecar(&self) -> Option<&BlobTransactionSidecar> {
Copy link
Member

Choose a reason for hiding this comment

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

dropping the get_ prefix is good

Copy link
Member Author

Choose a reason for hiding this comment

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

matches the style of the rest of all the other methods

Comment on lines +364 to +377
<JoinFill<F, SignerFiller<alloy_network::EthereumSigner>> as ProviderLayer<
L::Provider,
alloy_transport_http::Http<reqwest::Client>,
>>::Provider,
alloy_node_bindings::AnvilInstance,
)
where
L: ProviderLayer<
crate::ReqwestProvider<Ethereum>,
alloy_transport_http::Http<reqwest::Client>,
Ethereum,
>,
F: TxFiller<Ethereum>
+ ProviderLayer<L::Provider, alloy_transport_http::Http<reqwest::Client>, Ethereum>,
Copy link
Member

Choose a reason for hiding this comment

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

hehe, fine with this since all of this is internal complexity

Copy link
Member Author

Choose a reason for hiding this comment

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

just don't look at this impl block lol

Copy link
Member

@onbjerg onbjerg left a comment

Choose a reason for hiding this comment

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

smol nit, t first pass this seems ok

crates/provider/src/fillers/mod.rs Outdated Show resolved Hide resolved
@prestwich prestwich merged commit da41ddb into main Apr 8, 2024
18 checks passed
@prestwich prestwich deleted the prestwich/join-fill branch April 8, 2024 16:32
ben186 pushed a commit to ben186/alloy that referenced this pull request Jul 27, 2024
* feature: TxFiller

* lint: clippy

* doc: CONSIDER

* doc: more notes

* fix: get rid of ugly lifetimes

* fix: docs and lints

* fix: remove populate functions

* nit: remove commented code

* feature: FillerControlFlow

* doc: lifecycle notes

* refactor: request -> prepare

* lint: clippy

* fix: missing block in absorb

* fix: absorb preserves association

* refactor: additional info in missing

* fix: impl_future macro

* fix: resolve considers

* refactor: gas layer to gas filler

* refactor: improve provider builder

* refactor: filler is outmost layer

* refactor: protect against double-fill and add anvil shortcut

* doc: improve docstrings for noncemanager and gas filler

* fix: delete unused types

* refactor: layers to fillers

* feature: chain id filler

* refactor: send_transaction_inner and SendableTx

* wip: sig filler

* refactor: SignerFiller

* fix: remove clone

* docs: fix some

* fix: complete todo

* feature: anvil feature for alloy-provider

* wip: tests

* fix: apply changes from other PRs

* chore: fmt

* feature: on_anvil

* fix: workaround anvil gas est issue

* fix: doctests

* fix: anvil => request

* fix: test

* chore: note about blocking on TODO

* feature: local usage error

* fix: review nits

* Update crates/provider/src/fillers/mod.rs

Co-authored-by: Oliver Nordbjerg <[email protected]>

* fix: capitalization so @DaniPopes doesn't hurt me

---------

Co-authored-by: Oliver Nordbjerg <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
debt Tech debt which needs to be addressed enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug] Ordering problem in interaction between NonceManagerLayer and SignerLayer
4 participants