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 all commits
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
2 changes: 1 addition & 1 deletion crates/alloy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
2 changes: 1 addition & 1 deletion crates/contract/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?;
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 😅


// If `#[sol(bytecode = "0x...")]` is provided, the contract can be deployed with `MyContract::deploy`,
// and a new instance will be created.
Expand Down
12 changes: 12 additions & 0 deletions crates/eips/src/eip2930.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,18 @@ pub struct AccessList(
pub Vec<AccessListItem>,
);

impl From<Vec<AccessListItem>> for AccessList {
fn from(list: Vec<AccessListItem>) -> Self {
Self(list)
}
}

impl From<AccessList> for Vec<AccessListItem> {
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<U256>)> {
Expand Down
31 changes: 28 additions & 3 deletions crates/json-rpc/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ pub enum RpcError<E, ErrResp = Box<RawValue>> {
#[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<dyn std::error::Error + Send + Sync>),

/// JSON serialization error.
#[error("serialization error: {0}")]
SerError(
Expand Down Expand Up @@ -52,12 +57,22 @@ impl<E, ErrResp> RpcError<E, ErrResp>
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<ErrResp>) -> Self {
Self::ErrorResp(err)
}

/// Instantiate a new `TransportError` from a [`serde_json::Error`] and the
/// 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 `LocalUsageError` from a custom error message.
pub fn local_usage_str(err: &str) -> Self {
Self::LocalUsageError(err.into())
}

/// Instantiate a new `DeserError` from a [`serde_json::Error`] and the
/// text. This should be called when the error occurs during
/// deserialization.
///
Expand All @@ -76,7 +91,7 @@ where
}

impl<E, ErrResp> RpcError<E, ErrResp> {
/// Instantiate a new `TransportError` 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)
Expand Down Expand Up @@ -107,6 +122,16 @@ impl<E, ErrResp> RpcError<E, ErrResp> {
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<ErrResp>> {
match self {
Expand Down
28 changes: 23 additions & 5 deletions crates/network/src/any/builder.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -96,18 +96,36 @@ impl TransactionBuilder<AnyNetwork> for WithOtherFields<TransactionRequest> {
self.deref_mut().set_gas_limit(gas_limit);
}

fn build_unsigned(self) -> BuilderResult<<AnyNetwork as Network>::UnsignedTx> {
build_unsigned::<AnyNetwork>(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> {
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

self.deref().blob_sidecar()
}

fn set_blob_sidecar(&mut self, sidecar: BlobTransactionSidecar) {
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<<AnyNetwork as Network>::UnsignedTx> {
build_unsigned::<AnyNetwork>(self.inner)
}

async fn build<S: crate::NetworkSigner<AnyNetwork>>(
self,
signer: &S,
Expand Down
60 changes: 46 additions & 14 deletions crates/network/src/ethereum/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@ use crate::{
};
use alloy_consensus::{
BlobTransactionSidecar, TxEip1559, TxEip2930, TxEip4844, TxEip4844Variant, TxLegacy,
TypedTransaction,
};
use alloy_primitives::{Address, TxKind};
use alloy_rpc_types::request::TransactionRequest;
use alloy_primitives::{Address, Bytes, ChainId, TxKind, U256};
use alloy_rpc_types::{request::TransactionRequest, AccessList};

impl TransactionBuilder<Ethereum> for alloy_rpc_types::TransactionRequest {
fn chain_id(&self) -> Option<alloy_primitives::ChainId> {
impl TransactionBuilder<Ethereum> for TransactionRequest {
fn chain_id(&self) -> Option<ChainId> {
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);
}

Expand All @@ -24,11 +25,11 @@ impl TransactionBuilder<Ethereum> 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);
}

Expand All @@ -40,22 +41,22 @@ impl TransactionBuilder<Ethereum> for alloy_rpc_types::TransactionRequest {
self.from = Some(from);
}

fn to(&self) -> Option<alloy_primitives::TxKind> {
fn to(&self) -> Option<TxKind> {
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<alloy_primitives::U256> {
fn value(&self) -> Option<U256> {
self.value
}

fn set_value(&mut self, value: alloy_primitives::U256) {
fn set_value(&mut self, value: U256) {
self.value = Some(value)
}

Expand Down Expand Up @@ -99,11 +100,15 @@ impl TransactionBuilder<Ethereum> for alloy_rpc_types::TransactionRequest {
self.gas = Some(gas_limit);
}

fn build_unsigned(self) -> BuilderResult<<Ethereum as Network>::UnsignedTx> {
build_unsigned::<Ethereum>(self)
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 get_blob_sidecar(&self) -> Option<&BlobTransactionSidecar> {
fn blob_sidecar(&self) -> Option<&BlobTransactionSidecar> {
self.sidecar.as_ref()
}

Expand All @@ -112,6 +117,33 @@ impl TransactionBuilder<Ethereum> for alloy_rpc_types::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<TypedTransaction> {
build_unsigned::<Ethereum>(self)
}

async fn build<S: NetworkSigner<Ethereum>>(
self,
signer: &S,
Expand Down
10 changes: 5 additions & 5 deletions crates/network/src/ethereum/signer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ impl EthereumSigner {
Self(Arc::new(signer))
}

async fn sign_transaction(
async fn sign_transaction_inner(
&self,
tx: &mut dyn SignableTransaction<Signature>,
) -> alloy_signer::Result<Signature> {
Expand All @@ -47,19 +47,19 @@ impl NetworkSigner<Ethereum> for EthereumSigner {
async fn sign_transaction(&self, tx: TypedTransaction) -> alloy_signer::Result<TxEnvelope> {
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())
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
23 changes: 22 additions & 1 deletion crates/network/src/transaction/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -191,8 +192,20 @@ pub trait TransactionBuilder<N: Network>: 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.
///
Expand All @@ -206,6 +219,14 @@ pub trait TransactionBuilder<N: Network>: 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;
Comment on lines +222 to +228
Copy link
Member

Choose a reason for hiding this comment

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

like


/// Build an unsigned, but typed, transaction.
fn build_unsigned(self) -> BuilderResult<N::UnsignedTx>;

Expand Down
13 changes: 11 additions & 2 deletions crates/network/src/transaction/signer.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::Network;
use crate::{Network, TransactionBuilder};
use alloy_consensus::SignableTransaction;
use async_trait::async_trait;

Expand All @@ -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<N: Network>: Send + Sync {
pub trait NetworkSigner<N: Network>: std::fmt::Debug + Send + Sync {
/// Asynchronously sign an unsigned transaction.
async fn sign_transaction(&self, tx: N::UnsignedTx) -> alloy_signer::Result<N::TxEnvelope>;

/// Asynchronously sign a transaction request.
async fn sign_request(
&self,
request: N::TransactionRequest,
) -> alloy_signer::Result<N::TxEnvelope> {
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
Expand Down
Loading
Loading