-
Notifications
You must be signed in to change notification settings - Fork 245
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
Conversation
1aebabf
to
dd3da7a
Compare
5be7b58
to
0d29fb8
Compare
243d7e4
to
7ea802d
Compare
0d1f468
to
7353f79
Compare
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?; |
There was a problem hiding this comment.
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_?
There was a problem hiding this comment.
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 😅
/// 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; |
There was a problem hiding this comment.
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 } |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this 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.
crates/provider/src/builder.rs
Outdated
/// 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))) | ||
} |
There was a problem hiding this comment.
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
/// 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()) | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 multipleChainId
fillers
L: ProviderLayer< | ||
crate::ReqwestProvider<Ethereum>, | ||
alloy_transport_http::Http<reqwest::Client>, | ||
Ethereum, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
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 | ||
} | ||
} |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
/// 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`]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
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)) | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 = (); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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 ...?
There was a problem hiding this comment.
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
There was a problem hiding this 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
} | ||
|
||
fn get_blob_sidecar(&self) -> Option<&BlobTransactionSidecar> { | ||
self.deref().get_blob_sidecar() | ||
fn blob_sidecar(&self) -> Option<&BlobTransactionSidecar> { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
<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>, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this 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
Co-authored-by: Oliver Nordbjerg <[email protected]>
* 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]>
WIP PR for discussion
Motivation
NonceManagerLayer
andSignerLayer
#406ready
andfinished
and calling in a loop until all are either finished or unreadySolution
TxFiller<N>
traitJoinFill<L, R, N>
to composeTxFiller<N>
FillProvider<F, P, N, T>
so thatTxFiller<N>
all share a common provider layerRemaining:
CONSIDER
comments for discussionProviderBuilder
to add.with_filler()
for fillerssend_transaction_internal
to abstract over builder and envelope<RootProvider as Provider>::send_transaction
to always prefer raw transactionPR Checklist