-
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
feat: network abstraction and transaction builder #190
Conversation
/// The TypedTransaction enum represents all Ethereum transaction request types. | ||
/// | ||
/// Its variants correspond to specific allowed transactions: | ||
/// 1. Legacy (pre-EIP2718) [`TxLegacy`] | ||
/// 2. EIP2930 (state access lists) [`TxEip2930`] | ||
/// 3. EIP1559 [`TxEip1559`] | ||
/// 4. EIP4844 [`TxEip4844`] | ||
#[derive(Debug, Clone, PartialEq, Eq, Hash)] | ||
pub enum TypedTransaction { |
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.
Should this impl RLP?
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.
unsure, do we need it? cc @mattsse perhaps
d0a8838
to
3300b0c
Compare
// todo: block responses are ethereum only, so we need to include this in here too, or make `Block` | ||
// generic over tx/header type |
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.
let's do as follow-up IMO when we're adding the 2nd network?
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.
// todo: move | ||
/// A signer capable of signing any transaction for the given network. | ||
#[async_trait] | ||
pub trait NetworkSigner<N: Network> { | ||
/// Asynchronously sign an unsigned transaction. | ||
async fn sign(&self, tx: N::UnsignedTx) -> alloy_signer::Result<N::TxEnvelope>; | ||
} | ||
|
||
// todo: move | ||
/// An async signer capable of signing any [SignableTransaction] for the given [Signature] type. | ||
#[async_trait] | ||
pub trait TxSigner<Signature> { | ||
/// Asynchronously sign an unsigned transaction. | ||
async fn sign_transaction( | ||
&self, | ||
tx: &mut dyn SignableTransaction<Signature>, | ||
) -> alloy_signer::Result<Signature>; |
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.
what's the difference between these 2?
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
is a signer capable of signing any transaction with any signature type for a given network, TxSigner
is a signer capable of signing any transaction regardless of network for a given signature type.
The idea is to have fewer generics for signers
crates/providers/src/lib.rs
Outdated
key: U256, | ||
tag: Option<BlockId>, | ||
) -> TransportResult<StorageValue> { | ||
self.client.prepare("eth_getStorageAt", (address, key, tag.unwrap_or_default())).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.
generally find it weird we're calling this self.client.prepare
when it's sending the request, shouldnt it be self.client.prepare(...).send().await
or self.client.request(...)
?
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 think it's called prepare
because it doesn't do any serialization or really anything at all until await
ed. The send
method in your example is equiv to awaiting it
7ff8f67
to
0ac977d
Compare
e454e82
to
f23137f
Compare
@@ -258,81 +257,6 @@ mod tests { | |||
assert_eq!(recovered2, address); | |||
} | |||
|
|||
#[tokio::test] |
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 test was moved to the network crate
/// | ||
/// Returns `None` if the transaction is not a contract creation (the `to` field is set), or if | ||
/// the `from` or `nonce` fields are not set. | ||
pub fn calculate_create_address(&self) -> Option<Address> { |
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 now part of the Builder
trait, so removed it here. can add it back if we want
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 added it for convenience, it's not strictly necessary but it's nice to have
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.
builder trait should cover us here
crates/network/src/ethereum/mod.rs
Outdated
} | ||
|
||
/// Build a legacy transaction. | ||
fn build_legacy(request: TransactionRequest) -> Result<TxLegacy, TransactionBuilderError> { |
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.
Ideally I'd have these on TransactionRequest
, but that would cause a circular dep (alloy-network
depends on rpc-types
-> rpc-types
would need to depend on alloy-network
for TransactionBuilderError
)
Err(RpcError::Transport(err)) if retries > 0 && err.recoverable() => { | ||
debug!(number, %err, "failed to fetch block, retrying"); | ||
retries -= 1; | ||
continue; | ||
} | ||
Ok(None) if retries > 0 => { |
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 rpc can respond with the block not existing, which can be the case if we are querying the latest block but the node doesn't have it yet. in that case i think we retry imo
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.
agreed
it would be nicer if we had the error, but that would cause cyclical dep
rebased ptal @gakonst @DaniPopes, after merge I will create all the follow up issues |
I realized we're not running doctests in CI because when I ran |
created all the follow up issues, will look into doctest |
@DaniPopes we are, it just showed up later. i ran |
Yes, because I added it in 81c98a5 😄 |
Motivation
We want a transaction builder that can build one of several strongly-typed consensus transactions for signing to implement
send_transaction
. The intended flow is:This is made complicated due to our network abstraction:
Signature
for transactions is not a good abstractionThis flow should also work server side, i.e. if I get a
TransactionRequest
over the wire from a client, I should be able to resolve it to a consensus type transaction. I should also be able to take an already-signed RLP-encoded transaction and decode it into one of the valid transaction types.This PR intends to:
send_transaction
in the provider and thealloy-contract
call builderSolution
TransactionBuilder
trait and implements this forTransactionRequest
Ethereum
network with all types definedalloy-contract
to work with the new providerTransaction
intoTransaction
andSignableTransaction
, since not all transactions are signable (e.g. Optimism deposit tx's)Signature
type inTransaction
into a parameter insteadTxSigner
,TxSignerSync
andNetworkSigner
TxSigner
is essentially ripped out of theSigner
trait and should be implemented for all signers, even if the signer does not care about transactions.Signature
. Signer implementors need to implement this andSigner
.NetworkSigner
instead is implemented by network crate authors. This trait is implemented for a wrapper type and essentially states that the signer can sign any transaction for the given network. (SeeEthereumSigner
)ProviderBuilder
abstraction and add aSignerLayer
to add signing on top of an existing providerCompanion PRs:
TxKind
core#542Things left:
ProviderBuilder
a bitFollow ups
AnyNetwork
that is essentiallyEthereum
+OtherFields
on all types [Feature] AddAnyNetwork
#272Wallet
etc. out ofalloy-signers
to makealloy-network
wasm compatible again, see feat: network abstraction and transaction builder #190 (comment) ([Feature] MoveWallet
/LocalWallet
etc to its own crate #262)RootProvider
([Feature] Make it easier to instantiate aRootProvider
#266)Breaking changes
Anything that uses
alloy-contract
,alloy-provider
, oralloy-consensus
likely broke with this PR as the API changed.PR Checklist