diff --git a/crates/bundle/src/lib.rs b/crates/bundle/src/lib.rs index 09a51833..ee267132 100644 --- a/crates/bundle/src/lib.rs +++ b/crates/bundle/src/lib.rs @@ -51,6 +51,6 @@ pub use call::{SignetBundleDriver, SignetCallBundle, SignetCallBundleResponse}; mod send; pub use send::{ - BundleInspector, SignetEthBundle, SignetEthBundleDriver, SignetEthBundleError, - SignetEthBundleInsp, + BundleInspector, BundleRecoverError, RecoverError, RecoveredBundle, SignetEthBundle, + SignetEthBundleDriver, SignetEthBundleError, SignetEthBundleInsp, }; diff --git a/crates/bundle/src/send/bundle.rs b/crates/bundle/src/send/bundle.rs index 7424e67c..66026645 100644 --- a/crates/bundle/src/send/bundle.rs +++ b/crates/bundle/src/send/bundle.rs @@ -1,6 +1,9 @@ //! Signet bundle types. use alloy::{ - consensus::{transaction::SignerRecoverable, TxEnvelope}, + consensus::{ + transaction::{Recovered, SignerRecoverable}, + TxEnvelope, + }, eips::{eip2718::Eip2718Result, Decodable2718}, primitives::{Address, Bytes, TxHash, B256}, rlp::Buf, @@ -13,6 +16,8 @@ use trevm::{ BundleError, }; +use crate::{BundleRecoverError, RecoveredBundle}; + /// The inspector type required by the Signet bundle driver. pub type BundleInspector = Layered; @@ -76,6 +81,61 @@ impl SignetEthBundle { self.host_txs.iter().map(|tx| TxEnvelope::decode_2718(&mut tx.chunk())) } + /// Return an iterator over recovered transactions in this bundle. This + /// iterator may include errors. + pub fn recover_txs( + &self, + ) -> impl Iterator, BundleRecoverError>> + '_ { + self.decode_txs().enumerate().map(|(index, res)| match res { + Ok(tx) => { + tx.try_into_recovered().map_err(|err| BundleRecoverError::new(err, false, index)) + } + Err(err) => Err(BundleRecoverError::new(err, false, index)), + }) + } + + /// Return an iterator over recovered host transactions in this bundle. This + /// iterator may include errors. + pub fn recover_host_txs( + &self, + ) -> impl Iterator, BundleRecoverError>> + '_ { + self.decode_host_txs().enumerate().map(|(index, res)| match res { + Ok(tx) => { + tx.try_into_recovered().map_err(|err| BundleRecoverError::new(err, true, index)) + } + Err(err) => Err(BundleRecoverError::new(err, true, index)), + }) + } + + /// Create a [`RecoveredBundle`] from this bundle by decoding and recovering + /// all transactions, taking ownership of the bundle. + pub fn try_into_recovered(self) -> Result { + let txs = self.recover_txs().collect::, _>>()?; + + let host_txs = self.recover_host_txs().collect::, _>>()?; + + Ok(RecoveredBundle { + txs, + host_txs, + block_number: self.bundle.block_number, + min_timestamp: self.bundle.min_timestamp, + max_timestamp: self.bundle.max_timestamp, + reverting_tx_hashes: self.bundle.reverting_tx_hashes, + replacement_uuid: self.bundle.replacement_uuid, + dropping_tx_hashes: self.bundle.dropping_tx_hashes, + refund_percent: self.bundle.refund_percent, + refund_recipient: self.bundle.refund_recipient, + refund_tx_hashes: self.bundle.refund_tx_hashes, + extra_fields: self.bundle.extra_fields, + }) + } + + /// Create a [`RecoveredBundle`] from this bundle by decoding and recovering + /// all transactions, cloning other fields as necessary. + pub fn try_to_recovered(&self) -> Result { + self.clone().try_into_recovered() + } + /// Return an iterator over the signers of the transactions in this bundle. /// The iterator yields `Option<(TxHash, Address)>` for each transaction, /// where `None` indicates that the signer could not be recovered. @@ -126,9 +186,10 @@ impl SignetEthBundle { /// Checks if the bundle is valid at a given timestamp. pub fn is_valid_at_timestamp(&self, timestamp: u64) -> bool { - let min_timestamp = self.bundle.min_timestamp.unwrap_or(0); - let max_timestamp = self.bundle.max_timestamp.unwrap_or(u64::MAX); - timestamp >= min_timestamp && timestamp <= max_timestamp + let min_timestamp = self.min_timestamp().unwrap_or(0); + let max_timestamp = self.max_timestamp().unwrap_or(u64::MAX); + + (min_timestamp..=max_timestamp).contains(×tamp) } /// Checks if the bundle is valid at a given block number. diff --git a/crates/bundle/src/send/decoded.rs b/crates/bundle/src/send/decoded.rs new file mode 100644 index 00000000..ad0679bb --- /dev/null +++ b/crates/bundle/src/send/decoded.rs @@ -0,0 +1,175 @@ +use alloy::{ + consensus::{transaction::Recovered, TxEnvelope}, + primitives::{Address, TxHash}, + serde::OtherFields, +}; + +/// Version of [`SignetEthBundle`] with decoded transactions. +/// +/// [`SignetEthBundle`]: crate::send::bundle::SignetEthBundle +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct RecoveredBundle { + /// Transactions in this bundle. + pub(crate) txs: Vec>, + + /// Host transactions to be included in the host bundle. + pub(crate) host_txs: Vec>, + + /// Block number for which this bundle is valid + pub(crate) block_number: u64, + + /// unix timestamp when this bundle becomes active + pub(crate) min_timestamp: Option, + + /// unix timestamp how long this bundle stays valid + pub(crate) max_timestamp: Option, + + /// list of hashes of possibly reverting txs + pub(crate) reverting_tx_hashes: Vec, + + /// UUID that can be used to cancel/replace this bundle + pub(crate) replacement_uuid: Option, + + /// A list of tx hashes that are allowed to be discarded + pub(crate) dropping_tx_hashes: Vec, + + /// The percent that should be refunded to refund recipient + pub(crate) refund_percent: Option, + + /// The address that receives the refund + pub(crate) refund_recipient: Option
, + + /// A list of tx hashes used to determine the refund + pub(crate) refund_tx_hashes: Vec, + + /// Additional fields that are specific to the builder + pub(crate) extra_fields: OtherFields, +} + +impl RecoveredBundle { + /// Instantiator. Generally recommend instantiating via conversion from + /// [`SignetEthBundle`] via [`SignetEthBundle::try_into_recovered`] or + /// [`SignetEthBundle::try_to_recovered`]. + /// + /// [`SignetEthBundle`]: crate::send::bundle::SignetEthBundle + /// [`SignetEthBundle::try_into_recovered`]: crate::send::bundle::SignetEthBundle::try_into_recovered + /// [`SignetEthBundle::try_to_recovered`]: crate::send::bundle::SignetEthBundle::try_to_recovered + #[doc(hidden)] + #[allow(clippy::too_many_arguments)] + pub const fn new( + txs: Vec>, + host_txs: Vec>, + block_number: u64, + min_timestamp: Option, + max_timestamp: Option, + reverting_tx_hashes: Vec, + replacement_uuid: Option, + dropping_tx_hashes: Vec, + refund_percent: Option, + refund_recipient: Option
, + refund_tx_hashes: Vec, + extra_fields: OtherFields, + ) -> Self { + Self { + txs, + host_txs, + block_number, + min_timestamp, + max_timestamp, + reverting_tx_hashes, + replacement_uuid, + dropping_tx_hashes, + refund_percent, + refund_recipient, + refund_tx_hashes, + extra_fields, + } + } + + /// Get the transactions. + pub const fn txs(&self) -> &[Recovered] { + self.txs.as_slice() + } + + /// Get the host transactions. + pub const fn host_txs(&self) -> &[Recovered] { + self.host_txs.as_slice() + } + + /// Get an iterator draining the transactions. + pub fn drain_txns(&mut self) -> impl Iterator> + '_ { + self.txs.drain(..) + } + + /// Get an iterator draining the host transactions. + pub fn drain_host_txns(&mut self) -> impl Iterator> + '_ { + self.host_txs.drain(..) + } + + /// Getter for block_number, a standard bundle prop. + pub const fn block_number(&self) -> u64 { + self.block_number + } + + /// Getter for min_timestamp, a standard bundle prop. + pub const fn min_timestamp(&self) -> Option { + self.min_timestamp + } + + /// Getter for max_timestamp, a standard bundle prop. + pub const fn max_timestamp(&self) -> Option { + self.max_timestamp + } + + /// Getter for reverting_tx_hashes, a standard bundle prop. + pub const fn reverting_tx_hashes(&self) -> &[TxHash] { + self.reverting_tx_hashes.as_slice() + } + + /// Getter for replacement_uuid, a standard bundle prop. + pub const fn replacement_uuid(&self) -> Option<&str> { + if let Some(ref uuid) = self.replacement_uuid { + Some(uuid.as_str()) + } else { + None + } + } + + /// Getter for dropping_tx_hashes, a standard bundle prop. + pub const fn dropping_tx_hashes(&self) -> &[TxHash] { + self.dropping_tx_hashes.as_slice() + } + + /// Getter for refund_percent, a standard bundle prop. + pub const fn refund_percent(&self) -> Option { + self.refund_percent + } + + /// Getter for refund_recipient, a standard bundle prop. + pub const fn refund_recipient(&self) -> Option
{ + self.refund_recipient + } + + /// Getter for refund_tx_hashes, a standard bundle prop. + pub const fn refund_tx_hashes(&self) -> &[TxHash] { + self.refund_tx_hashes.as_slice() + } + + /// Getter for extra_fields, a standard bundle prop. + pub const fn extra_fields(&self) -> &OtherFields { + &self.extra_fields + } + + /// Checks if the bundle is valid at a given timestamp. + pub fn is_valid_at_timestamp(&self, timestamp: u64) -> bool { + let min_timestamp = self.min_timestamp.unwrap_or(0); + let max_timestamp = self.max_timestamp.unwrap_or(u64::MAX); + + (min_timestamp..=max_timestamp).contains(×tamp) + } + + /// Checks if the bundle is valid at a given block number. + pub const fn is_valid_at_block_number(&self, block_number: u64) -> bool { + self.block_number == block_number + } +} diff --git a/crates/bundle/src/send/driver.rs b/crates/bundle/src/send/driver.rs index d3562783..b50e5fab 100644 --- a/crates/bundle/src/send/driver.rs +++ b/crates/bundle/src/send/driver.rs @@ -1,7 +1,7 @@ -use crate::send::SignetEthBundle; +use crate::{RecoveredBundle, SignetEthBundleError}; use alloy::{hex, primitives::U256}; use signet_evm::{DriveBundleResult, EvmErrored, EvmNeedsTx, SignetInspector, SignetLayered}; -use signet_types::{AggregateFills, AggregateOrders, MarketError, SignedPermitError}; +use signet_types::{AggregateFills, AggregateOrders}; use std::borrow::Cow; use tracing::{debug, debug_span, enabled, error}; use trevm::{ @@ -71,58 +71,6 @@ where } } -/// Errors while running a [`SignetEthBundle`] on the EVM. -#[derive(thiserror::Error)] -pub enum SignetEthBundleError { - /// Bundle error. - #[error(transparent)] - Bundle(#[from] BundleError), - - /// SignedPermitError. - #[error(transparent)] - SignetPermit(#[from] SignedPermitError), - - /// Contract error. - #[error(transparent)] - Contract(#[from] alloy::contract::Error), - - /// Market error. - #[error(transparent)] - Market(#[from] MarketError), - - /// Host simulation error. - #[error("{0}")] - HostSimulation(&'static str), -} - -impl core::fmt::Debug for SignetEthBundleError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - SignetEthBundleError::Bundle(bundle_error) => { - f.debug_tuple("BundleError").field(bundle_error).finish() - } - SignetEthBundleError::SignetPermit(signed_order_error) => { - f.debug_tuple("SignedPermitError").field(signed_order_error).finish() - } - SignetEthBundleError::Contract(contract_error) => { - f.debug_tuple("ContractError").field(contract_error).finish() - } - SignetEthBundleError::Market(market_error) => { - f.debug_tuple("MarketError").field(market_error).finish() - } - SignetEthBundleError::HostSimulation(msg) => { - f.debug_tuple("HostSimulationError").field(msg).finish() - } - } - } -} - -impl From> for SignetEthBundleError { - fn from(err: EVMError) -> Self { - Self::Bundle(BundleError::from(err)) - } -} - /// Driver for applying a Signet Ethereum bundle to an EVM. #[derive(Debug)] pub struct SignetEthBundleDriver<'a, 'b, Db, Insp> @@ -131,7 +79,7 @@ where Insp: Inspector>, { /// The bundle to apply. - bundle: &'a SignetEthBundle, + bundle: &'a RecoveredBundle, /// Reference to the fill state to check against. pub fill_state: Cow<'b, AggregateFills>, @@ -152,7 +100,7 @@ where /// Creates a new [`SignetEthBundleDriver`] with the given bundle and /// response. pub fn new( - bundle: &'a SignetEthBundle, + bundle: &'a RecoveredBundle, host_evm: signet_evm::EvmNeedsTx, deadline: std::time::Instant, ) -> Self { @@ -164,7 +112,7 @@ where /// /// This is useful for testing, and for combined host-rollup simulation. pub fn new_with_fill_state( - bundle: &'a SignetEthBundle, + bundle: &'a RecoveredBundle, host_evm: signet_evm::EvmNeedsTx, deadline: std::time::Instant, fill_state: Cow<'b, AggregateFills>, @@ -185,7 +133,7 @@ where } /// Get a reference to the bundle. - pub const fn bundle(&self) -> &SignetEthBundle { + pub const fn bundle(&self) -> &RecoveredBundle { self.bundle } @@ -224,15 +172,14 @@ where &mut self, mut trevm: EvmNeedsTx>, ) -> DriveBundleResult> { - let bundle = &self.bundle.bundle; // -- STATELESS CHECKS -- // Ensure that the bundle has transactions - trevm_ensure!(!bundle.txs.is_empty(), trevm, BundleError::BundleEmpty.into()); + trevm_ensure!(!self.bundle.txs.is_empty(), trevm, BundleError::BundleEmpty.into()); // Check if the block we're in is valid for this bundle. Both must match trevm_ensure!( - trevm.block_number().to::() == bundle.block_number, + trevm.block_number().to::() == self.bundle.block_number, trevm, BundleError::BlockNumberMismatch.into() ); @@ -245,10 +192,6 @@ where BundleError::TimestampOutOfRange.into() ); - // Decode and validate the transactions in the bundle - let host_txs = trevm_try!(self.bundle.decode_and_validate_host_txs(), trevm); - let txs = trevm_try!(self.bundle.decode_and_validate_txs(), trevm); - // -- STATEFUL ACTIONS -- // Get the beneficiary address and its initial balance @@ -261,13 +204,13 @@ where // We simply run all host transactions first, accumulating their state // changes into the host_evm's state. If any reverts, we error out the // simulation. - for tx in host_txs.into_iter() { + for tx in self.bundle.host_txs().iter() { self.output.host_evm = Some(trevm_try!( self.output .host_evm .take() .expect("host_evm missing") - .run_tx(&tx) + .run_tx(tx) .and_then(|mut htrevm| { let result = htrevm.result(); if let Some(output) = result.output() { @@ -312,7 +255,7 @@ where } // -- ROLLUP PORTION -- - for tx in txs.into_iter() { + for tx in self.bundle.txs().iter() { let span = debug_span!( "bundle_tx_loop", tx_hash = %tx.hash(), @@ -331,7 +274,7 @@ where // Temporary rebinding of trevm within each loop iteration. // The type of t is `EvmTransacted`, while the type of trevm is // `EvmNeedsTx`. - let mut t = trevm.run_tx(&tx).map_err(EvmErrored::err_into).inspect_err( + let mut t = trevm.run_tx(tx).map_err(EvmErrored::err_into).inspect_err( |err| error!(err = %err.error(), "error while running rollup transaction"), )?; diff --git a/crates/bundle/src/send/error.rs b/crates/bundle/src/send/error.rs new file mode 100644 index 00000000..83b12bb0 --- /dev/null +++ b/crates/bundle/src/send/error.rs @@ -0,0 +1,91 @@ +use alloy::eips::eip2718::Eip2718Error; +use signet_types::{MarketError, SignedPermitError}; +use trevm::{ + revm::{context::result::EVMError, Database}, + BundleError, +}; + +/// Errors that can occur while recovering signatures from transactions in +/// bundles. +#[derive(Debug, thiserror::Error)] +pub enum RecoverError { + /// Error occurred while decoding the transaction. + #[error(transparent)] + Decoding(#[from] Eip2718Error), + /// Error occurred while recovering the signature. + #[error(transparent)] + Recovering(#[from] alloy::consensus::crypto::RecoveryError), +} + +/// Decoding error specifying the an error encountered while decoding +/// transactions in a Signet bundle. +#[derive(Debug, thiserror::Error)] +#[error("Failed to decode transaction. Host: {host}, Index: {index}, Error: {inner}")] +pub struct BundleRecoverError { + /// Error decoding a transaction. + #[source] + pub inner: RecoverError, + /// Whether the transaction was a host transaction. + pub host: bool, + /// Index of the transaction in the bundle. + pub index: usize, +} + +impl BundleRecoverError { + /// Creates a new `BundleRecoverError`. + pub fn new(inner: impl Into, host: bool, index: usize) -> Self { + Self { inner: inner.into(), host, index } + } +} + +/// Errors while running a [`SignetEthBundle`] on the EVM. +#[derive(thiserror::Error)] +pub enum SignetEthBundleError { + /// Bundle error. + #[error(transparent)] + Bundle(#[from] BundleError), + + /// SignetPermit error. + #[error(transparent)] + SignetPermit(#[from] SignedPermitError), + + /// Contract error. + #[error(transparent)] + Contract(#[from] alloy::contract::Error), + + /// Market error. + #[error(transparent)] + Market(#[from] MarketError), + + /// Host simulation error. + #[error("{0}")] + HostSimulation(&'static str), +} + +impl core::fmt::Debug for SignetEthBundleError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + SignetEthBundleError::Bundle(inner) => { + f.debug_tuple("BundleError").field(inner).finish() + } + SignetEthBundleError::SignetPermit(inner) => { + f.debug_tuple("SignedPermitError").field(inner).finish() + } + SignetEthBundleError::Contract(inner) => { + f.debug_tuple("ContractError").field(inner).finish() + } + SignetEthBundleError::Market(inner) => { + f.debug_tuple("MarketError").field(inner).finish() + } + SignetEthBundleError::HostSimulation(msg) => { + f.debug_tuple("HostSimulationError").field(msg).finish() + } + } + } +} + +impl From> for SignetEthBundleError { + fn from(err: EVMError) -> Self { + Self::Bundle(BundleError::from(err)) + } +} diff --git a/crates/bundle/src/send/mod.rs b/crates/bundle/src/send/mod.rs index 196fff56..86939da7 100644 --- a/crates/bundle/src/send/mod.rs +++ b/crates/bundle/src/send/mod.rs @@ -1,5 +1,11 @@ mod bundle; pub use bundle::{BundleInspector, SignetEthBundle}; +mod decoded; +pub use decoded::RecoveredBundle; + mod driver; -pub use driver::{SignetEthBundleDriver, SignetEthBundleError, SignetEthBundleInsp}; +pub use driver::{SignetEthBundleDriver, SignetEthBundleInsp}; + +mod error; +pub use error::{BundleRecoverError, RecoverError, SignetEthBundleError}; diff --git a/crates/sim/src/built.rs b/crates/sim/src/built.rs index 243f4dfa..29bc1383 100644 --- a/crates/sim/src/built.rs +++ b/crates/sim/src/built.rs @@ -1,24 +1,22 @@ use crate::{outcome::SimulatedItem, SimItem}; use alloy::{ - consensus::{SidecarBuilder, SidecarCoder, TxEnvelope}, - eips::Decodable2718, + consensus::{transaction::Recovered, SidecarBuilder, SidecarCoder, TxEnvelope}, primitives::{keccak256, Bytes, B256}, - rlp::Buf, }; use core::fmt; -use signet_bundle::SignetEthBundle; +use signet_bundle::RecoveredBundle; use signet_zenith::{encode_txns, Alloy2718Coder}; use std::sync::OnceLock; -use tracing::{error, trace}; +use tracing::trace; /// A block that has been built by the simulator. #[derive(Clone, Default)] pub struct BuiltBlock { /// The host transactions to be included in a resulting bundle. - pub(crate) host_txns: Vec, + pub(crate) host_txns: Vec>, /// Transactions in the block. - pub(crate) transactions: Vec, + pub(crate) transactions: Vec>, /// The block number for the Signet block. pub(crate) block_number: u64, @@ -89,14 +87,13 @@ impl BuiltBlock { /// Get the current list of transactions included in this block. #[allow(clippy::missing_const_for_fn)] // false positive, const deref - pub fn transactions(&self) -> &[TxEnvelope] { + pub fn transactions(&self) -> &[Recovered] { &self.transactions } /// Get the current list of host transactions included in this block. - #[allow(clippy::missing_const_for_fn)] // false positive, const deref - pub fn host_transactions(&self) -> &[Bytes] { - &self.host_txns + pub const fn host_transactions(&self) -> &[Recovered] { + self.host_txns.as_slice() } /// Unseal the block @@ -108,12 +105,15 @@ impl BuiltBlock { /// Seal the block by encoding the transactions and calculating the hash of /// the block contents. pub(crate) fn seal(&self) { - self.raw_encoding.get_or_init(|| encode_txns::(&self.transactions).into()); + self.raw_encoding.get_or_init(|| { + let iter = self.transactions.iter().map(Recovered::inner); + encode_txns::(iter).into() + }); self.hash.get_or_init(|| keccak256(self.raw_encoding.get().unwrap().as_ref())); } /// Ingest a transaction into the in-progress block. - pub fn ingest_tx(&mut self, tx: TxEnvelope) { + pub fn ingest_tx(&mut self, tx: Recovered) { trace!(hash = %tx.tx_hash(), "ingesting tx"); self.unseal(); self.transactions.push(tx); @@ -121,25 +121,14 @@ impl BuiltBlock { /// Ingest a bundle into the in-progress block. /// Ignores Signed Orders for now. - pub fn ingest_bundle(&mut self, bundle: SignetEthBundle) { + pub fn ingest_bundle(&mut self, mut bundle: RecoveredBundle) { trace!(replacement_uuid = bundle.replacement_uuid(), "adding bundle to block"); - let txs = bundle - .bundle - .txs - .into_iter() - .map(|tx| TxEnvelope::decode_2718(&mut tx.chunk())) - .collect::, _>>(); - - if let Ok(txs) = txs { - self.unseal(); - // extend the transactions with the decoded transactions. - // As this builder does not provide bundles landing "top of block", its fine to just extend. - self.transactions.extend(txs); - self.host_txns.extend(bundle.host_txs); - } else { - error!("failed to decode bundle. dropping"); - } + self.unseal(); + // extend the transactions with the decoded transactions. + // As this builder does not provide bundles landing "top of block", its fine to just extend. + self.transactions.extend(bundle.drain_txns()); + self.host_txns.extend(bundle.drain_host_txns()); } /// Ingest a simulated item, extending the block. @@ -148,8 +137,8 @@ impl BuiltBlock { self.host_gas_used += item.host_gas_used; match item.item { - SimItem::Bundle(bundle) => self.ingest_bundle(bundle), - SimItem::Tx(tx) => self.ingest_tx(tx), + SimItem::Bundle(bundle) => self.ingest_bundle(*bundle), + SimItem::Tx(tx) => self.ingest_tx(*tx), } } diff --git a/crates/sim/src/cache.rs b/crates/sim/src/cache.rs index cace5409..97720b7e 100644 --- a/crates/sim/src/cache.rs +++ b/crates/sim/src/cache.rs @@ -1,8 +1,8 @@ use crate::{item::SimIdentifier, CacheError, SimItem}; -use alloy::consensus::TxEnvelope; +use alloy::consensus::{transaction::Recovered, TxEnvelope}; use core::fmt; use parking_lot::RwLock; -use signet_bundle::SignetEthBundle; +use signet_bundle::{RecoveredBundle, SignetEthBundle}; use std::{ collections::{BTreeMap, HashSet}, sync::Arc, @@ -121,7 +121,7 @@ impl SimCache { pub fn add_bundles(&self, item: I, basefee: u64) where I: IntoIterator, - Item: Into, + Item: Into, { let mut inner = self.inner.write(); @@ -137,7 +137,7 @@ impl SimCache { } /// Add a transaction to the cache. - pub fn add_tx(&self, tx: TxEnvelope, basefee: u64) { + pub fn add_tx(&self, tx: Recovered, basefee: u64) { let item = SimItem::from(tx); let cache_rank = item.calculate_total_fee(basefee); @@ -148,7 +148,7 @@ impl SimCache { /// Add an iterator of transactions to the cache. This locks the cache only once pub fn add_txs(&self, item: I, basefee: u64) where - I: IntoIterator, + I: IntoIterator>, { let mut inner = self.inner.write(); @@ -221,7 +221,8 @@ impl CacheInner { #[cfg(test)] mod test { - use alloy::{eips::Encodable2718, primitives::b256}; + + use alloy::primitives::{b256, Address}; use super::*; @@ -295,42 +296,54 @@ mod test { gas_limit: u64, mpfpg: u128, replacement_uuid: String, - ) -> signet_bundle::SignetEthBundle { + ) -> signet_bundle::RecoveredBundle { let tx = invalid_tx_with_score(gas_limit, mpfpg); - signet_bundle::SignetEthBundle { - bundle: alloy::rpc::types::mev::EthSendBundle { - txs: vec![tx.encoded_2718().into()], - block_number: 1, - min_timestamp: Some(2), - max_timestamp: Some(3), - replacement_uuid: Some(replacement_uuid), - ..Default::default() - }, - host_txs: vec![], - } + signet_bundle::RecoveredBundle::new( + vec![tx], + vec![], + 1, + Some(2), + Some(3), + vec![], + Some(replacement_uuid.clone()), + vec![], + None, + None, + vec![], + Default::default(), + ) } - fn invalid_tx_with_score(gas_limit: u64, mpfpg: u128) -> alloy::consensus::TxEnvelope { + fn invalid_tx_with_score( + gas_limit: u64, + mpfpg: u128, + ) -> Recovered { let tx = build_alloy_tx(gas_limit, mpfpg); - TxEnvelope::Eip1559(alloy::consensus::Signed::new_unhashed( - tx, - alloy::signers::Signature::test_signature(), - )) + Recovered::new_unchecked( + TxEnvelope::Eip1559(alloy::consensus::Signed::new_unhashed( + tx, + alloy::signers::Signature::test_signature(), + )), + Address::with_last_byte(7), + ) } fn invalid_tx_with_score_and_hash( gas_limit: u64, mpfpg: u128, hash: alloy::primitives::B256, - ) -> alloy::consensus::TxEnvelope { + ) -> Recovered { let tx = build_alloy_tx(gas_limit, mpfpg); - TxEnvelope::Eip1559(alloy::consensus::Signed::new_unchecked( - tx, - alloy::signers::Signature::test_signature(), - hash, - )) + Recovered::new_unchecked( + TxEnvelope::Eip1559(alloy::consensus::Signed::new_unchecked( + tx, + alloy::signers::Signature::test_signature(), + hash, + )), + Address::with_last_byte(8), + ) } fn build_alloy_tx(gas_limit: u64, mpfpg: u128) -> alloy::consensus::TxEip1559 { diff --git a/crates/sim/src/env/sim_env.rs b/crates/sim/src/env/sim_env.rs index d30135b9..42a05323 100644 --- a/crates/sim/src/env/sim_env.rs +++ b/crates/sim/src/env/sim_env.rs @@ -1,7 +1,7 @@ use crate::{env::RollupEnv, HostEnv, SimCache, SimDb, SimItem, SimOutcomeWithCache}; use alloy::{consensus::TxEnvelope, hex}; use core::fmt; -use signet_bundle::{SignetEthBundle, SignetEthBundleDriver, SignetEthBundleError}; +use signet_bundle::{RecoveredBundle, SignetEthBundleDriver, SignetEthBundleError}; use signet_evm::SignetInspector; use signet_types::constants::SignetSystemConstants; use std::{borrow::Cow, sync::Arc}; @@ -192,7 +192,7 @@ where fn simulate_bundle( &self, cache_rank: u128, - bundle: &SignetEthBundle, + bundle: &RecoveredBundle, ) -> Result>> where RuInsp: Inspector>> + Default + Sync, diff --git a/crates/sim/src/error.rs b/crates/sim/src/error.rs index 81f08c72..47e1a58a 100644 --- a/crates/sim/src/error.rs +++ b/crates/sim/src/error.rs @@ -1,7 +1,15 @@ /// Possible errors that can occur when using the cache. -#[derive(Debug, Clone, Copy, thiserror::Error)] +#[derive(Debug, thiserror::Error)] pub enum CacheError { /// The bundle does not have a replacement UUID, which is required for caching. #[error("bundle has no replacement UUID")] BundleWithoutReplacementUuid, + + /// Error recovering a transaction. + #[error(transparent)] + TxRecover(#[from] alloy::consensus::crypto::RecoveryError), + + /// Error recovering a bundle. + #[error(transparent)] + BundleRecover(#[from] signet_bundle::BundleRecoverError), } diff --git a/crates/sim/src/item.rs b/crates/sim/src/item.rs index af8caf91..0007bd83 100644 --- a/crates/sim/src/item.rs +++ b/crates/sim/src/item.rs @@ -1,44 +1,63 @@ +use crate::CacheError; use alloy::{ - consensus::{Transaction, TxEnvelope}, - eips::Decodable2718, + consensus::{ + transaction::{Recovered, SignerRecoverable}, + Transaction, TxEnvelope, + }, primitives::TxHash, }; -use signet_bundle::SignetEthBundle; +use signet_bundle::{RecoveredBundle, SignetEthBundle}; use std::{ borrow::{Borrow, Cow}, hash::Hash, }; /// An item that can be simulated. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum SimItem { /// A bundle to be simulated. - Bundle(SignetEthBundle), + Bundle(Box), /// A transaction to be simulated. - Tx(TxEnvelope), + Tx(Box>), } impl TryFrom for SimItem { - type Error = crate::CacheError; + type Error = CacheError; fn try_from(bundle: SignetEthBundle) -> Result { + bundle.try_into_recovered().map_err(CacheError::BundleRecover).and_then(TryInto::try_into) + } +} + +impl TryFrom for SimItem { + type Error = CacheError; + + fn try_from(bundle: RecoveredBundle) -> Result { if bundle.replacement_uuid().is_some() { - Ok(Self::Bundle(bundle)) + Ok(Self::Bundle(bundle.into())) } else { - Err(crate::CacheError::BundleWithoutReplacementUuid) + Err(CacheError::BundleWithoutReplacementUuid) } } } -impl From for SimItem { - fn from(tx: TxEnvelope) -> Self { - Self::Tx(tx) +impl From> for SimItem { + fn from(tx: Recovered) -> Self { + Self::Tx(tx.into()) + } +} + +impl TryFrom for SimItem { + type Error = CacheError; + + fn try_from(tx: TxEnvelope) -> Result { + tx.try_into_recovered().map_err(Into::into).map(Self::from) } } impl SimItem { /// Get the bundle if it is a bundle. - pub const fn as_bundle(&self) -> Option<&SignetEthBundle> { + pub const fn as_bundle(&self) -> Option<&RecoveredBundle> { match self { Self::Bundle(bundle) => Some(bundle), Self::Tx(_) => None, @@ -46,7 +65,7 @@ impl SimItem { } /// Get the transaction if it is a transaction. - pub const fn as_tx(&self) -> Option<&TxEnvelope> { + pub const fn as_tx(&self) -> Option<&Recovered> { match self { Self::Bundle(_) => None, Self::Tx(tx) => Some(tx), @@ -59,10 +78,7 @@ impl SimItem { match self { Self::Bundle(bundle) => { let mut total_tx_fee = 0; - for tx in bundle.bundle.txs.iter() { - let Ok(tx) = TxEnvelope::decode_2718(&mut tx.as_ref()) else { - continue; - }; + for tx in bundle.txs() { total_tx_fee += tx.effective_gas_price(Some(basefee)) * tx.gas_limit() as u128; } total_tx_fee @@ -81,7 +97,7 @@ impl SimItem { Self::Bundle(bundle) => { SimIdentifier::Bundle(Cow::Borrowed(bundle.replacement_uuid().unwrap())) } - Self::Tx(tx) => SimIdentifier::Tx(*tx.hash()), + Self::Tx(tx) => SimIdentifier::Tx(*tx.inner().hash()), } } @@ -91,7 +107,7 @@ impl SimItem { Self::Bundle(bundle) => { SimIdentifier::Bundle(Cow::Owned(bundle.replacement_uuid().unwrap().to_string())) } - Self::Tx(tx) => SimIdentifier::Tx(*tx.hash()), + Self::Tx(tx) => SimIdentifier::Tx(*tx.inner().hash()), } } } diff --git a/crates/test-utils/tests/basic_sim.rs b/crates/test-utils/tests/basic_sim.rs index fb2b83de..426d123b 100644 --- a/crates/test-utils/tests/basic_sim.rs +++ b/crates/test-utils/tests/basic_sim.rs @@ -1,5 +1,7 @@ use alloy::{ - consensus::{constants::GWEI_TO_WEI, Signed, TxEip1559, TxEnvelope}, + consensus::{ + constants::GWEI_TO_WEI, transaction::SignerRecoverable, Signed, TxEip1559, TxEnvelope, + }, network::TxSigner, primitives::{Address, TxKind, U256}, signers::Signature, @@ -17,16 +19,14 @@ pub async fn complex_simulation() { // Set up 10 simple sends with escalating priority fee for (i, sender) in TEST_SIGNERS.iter().enumerate() { - builder.sim_items().add_tx( - signed_send_with_mfpg( - sender, - TEST_USERS[i], - U256::from(1000), - (10 - i) as u128 * GWEI_TO_WEI as u128, - ) - .await, - 0, - ); + let tx = signed_send_with_mfpg( + sender, + TEST_USERS[i], + U256::from(1000), + (10 - i) as u128 * GWEI_TO_WEI as u128, + ) + .await; + builder.sim_items().add_tx(tx.try_into_recovered().unwrap(), 0); } // Set up the simulator diff --git a/crates/test-utils/tests/bundle.rs b/crates/test-utils/tests/bundle.rs index 183774e0..38fcb2b3 100644 --- a/crates/test-utils/tests/bundle.rs +++ b/crates/test-utils/tests/bundle.rs @@ -140,6 +140,7 @@ fn test_bundle_ok() { let trevm = bundle_evm(); let bundle = counter_bundle(false); + let bundle = bundle.try_to_recovered().unwrap(); let mut driver = SignetEthBundleDriver::new(&bundle, host_evm(), Instant::now() + Duration::from_secs(5)); @@ -160,6 +161,7 @@ fn test_bundle_revert() { let trevm = bundle_evm(); let bundle = counter_bundle(true); + let bundle = bundle.try_to_recovered().unwrap(); let mut driver = SignetEthBundleDriver::new(&bundle, host_evm(), Instant::now() + Duration::from_secs(5)); @@ -184,6 +186,7 @@ fn test_bundle_droppable() { let hash = keccak256(&bundle.txs()[1]); bundle.bundle.reverting_tx_hashes.push(hash); + let bundle = bundle.try_to_recovered().unwrap(); let mut driver = SignetEthBundleDriver::new(&bundle, host_evm(), Instant::now() + Duration::from_secs(5)); @@ -207,6 +210,7 @@ fn test_order_bundle() { agg_fills.add_fill(HOST_CHAIN_ID, &host_fills); let bundle = order_bundle(vec![]); + let bundle = bundle.try_to_recovered().unwrap(); let mut driver = SignetEthBundleDriver::new_with_fill_state( &bundle, @@ -238,6 +242,7 @@ fn test_order_bundle_revert() { // This should cause the order to be invalid, as no fill is provided. let bundle = order_bundle(vec![]); + let bundle = bundle.try_to_recovered().unwrap(); let mut driver = SignetEthBundleDriver::new(&bundle, host_evm(), Instant::now() + Duration::from_secs(5)); @@ -267,6 +272,7 @@ fn test_order_bundle_droppable() { bundle.bundle.reverting_tx_hashes.push(hash); dbg!(hash); + let bundle = bundle.try_to_recovered().unwrap(); let mut driver = SignetEthBundleDriver::new(&bundle, host_evm(), Instant::now() + Duration::from_secs(5));