-
Notifications
You must be signed in to change notification settings - Fork 3
fix: driver maintenance #17
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
Changes from 4 commits
d963f41
4592bf9
c47c6bc
0fdb1be
f500dde
d533bdf
6d10e8b
55a5e09
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,64 +1,35 @@ | ||
| use crate::{SignetCallBundle, SignetCallBundleResponse}; | ||
| use crate::{SignetBundleError, SignetCallBundle, SignetCallBundleResponse}; | ||
| use alloy::{consensus::TxEnvelope, primitives::U256}; | ||
| use signet_evm::OrderDetector; | ||
| use signet_types::{MarketContext, MarketError}; | ||
| use std::fmt::Debug; | ||
| use tracing::{debug_span, instrument, Level}; | ||
| use trevm::{ | ||
| revm::{primitives::EVMError, Database, DatabaseCommit}, | ||
| revm::{Database, DatabaseCommit}, | ||
| trevm_bail, trevm_ensure, unwrap_or_trevm_err, BundleDriver, BundleError, | ||
| }; | ||
|
|
||
| /// Errors that can occur when running a bundle on the Signet EVM. | ||
| #[derive(thiserror::Error)] | ||
| pub enum SignetBundleError<Db: Database> { | ||
| /// A primitive [`BundleError`] error ocurred. | ||
| #[error(transparent)] | ||
| BundleError(#[from] BundleError<Db>), | ||
| /// A [`MarketError`] ocurred. | ||
| #[error(transparent)] | ||
| MarketError(#[from] MarketError), | ||
| } | ||
|
|
||
| impl<Db: Database> Debug for SignetBundleError<Db> { | ||
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| match self { | ||
| SignetBundleError::BundleError(e) => write!(f, "BundleError({:?})", e), | ||
| SignetBundleError::MarketError(e) => write!(f, "MarketError({:?})", e), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<Db: Database> From<EVMError<Db::Error>> for SignetBundleError<Db> { | ||
| fn from(e: EVMError<Db::Error>) -> Self { | ||
| SignetBundleError::BundleError(BundleError::EVMError { inner: e }) | ||
| } | ||
| } | ||
|
|
||
| impl<Db: Database> SignetBundleError<Db> { | ||
| /// Instantiate a new [`SignetBundleError`] from a [`Database::Error`]. | ||
| pub const fn evm_db(e: Db::Error) -> Self { | ||
| SignetBundleError::BundleError(BundleError::EVMError { inner: EVMError::Database(e) }) | ||
| } | ||
| } | ||
|
|
||
| /// A bundle driver for the Signet EVM. | ||
| /// | ||
| /// This type allows for the simulation of a [`SignetCallBundle`] and ensuring | ||
| /// that it conforms to market rules as a unit. | ||
| #[derive(Debug)] | ||
| pub struct SignetBundleDriver<'a> { | ||
| /// The bundle to drive. | ||
| bundle: &'a SignetCallBundle, | ||
| /// The accumulated results of the bundle, if applicable. | ||
| response: SignetCallBundleResponse, | ||
| /// The market context. | ||
| context: MarketContext, | ||
| /// The host chain id. | ||
| host_chain_id: u64, | ||
| } | ||
|
|
||
| impl<'a> From<&'a SignetCallBundle> for SignetBundleDriver<'a> { | ||
| fn from(bundle: &'a SignetCallBundle) -> Self { | ||
| Self::new(bundle) | ||
| } | ||
| } | ||
|
|
||
| impl<'a> SignetBundleDriver<'a> { | ||
| /// Create a new bundle driver with the given bundle and response. | ||
| pub fn new(bundle: &'a SignetCallBundle, host_chain_id: u64) -> Self { | ||
| let context = bundle.make_context(host_chain_id); | ||
| Self { bundle, response: Default::default(), context, host_chain_id } | ||
| pub fn new(bundle: &'a SignetCallBundle) -> Self { | ||
| Self { bundle, response: Default::default() } | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -73,50 +44,28 @@ impl SignetBundleDriver<'_> { | |
| &self.response | ||
| } | ||
|
|
||
| /// Get a reference to the market context. | ||
| pub const fn context(&self) -> &MarketContext { | ||
| &self.context | ||
| } | ||
|
|
||
| /// Take the response from the bundle driver. This consumes | ||
| pub fn into_response(self) -> SignetCallBundleResponse { | ||
| self.response | ||
| } | ||
|
|
||
| /// Clear the driver, resetting the response and the market context. This | ||
| /// reset the driver, allowing for resimulation of the same bundle. | ||
| /// | ||
| /// The returned context contains the amount of overfill, i.e. the amount | ||
| /// that was filled, but not required by the orders in the bundle. | ||
| pub fn clear(&mut self) -> (SignetCallBundleResponse, MarketContext) { | ||
| let r = std::mem::take(&mut self.response); | ||
| let context = self.bundle.make_context(self.host_chain_id); | ||
| let c = std::mem::replace(&mut self.context, context); | ||
| (r, c) | ||
| /// resets the driver, allowing for re-simulation of the same bundle. | ||
| pub fn clear(&mut self) -> SignetCallBundleResponse { | ||
| std::mem::take(&mut self.response) | ||
| } | ||
|
|
||
| /// Check the market context, accept the result, accumulate the transaction | ||
| /// details into the response. | ||
| fn check_market_and_accept<'a, Db: Database + DatabaseCommit, I>( | ||
| fn accept_and_accumulate<'a, Db: Database + DatabaseCommit, I>( | ||
| &mut self, | ||
| mut trevm: signet_evm::EvmTransacted<'a, Db, I>, | ||
| trevm: signet_evm::EvmTransacted<'a, Db, I>, | ||
| tx: &TxEnvelope, | ||
| pre_sim_coinbase_balance: &mut U256, | ||
| basefee: U256, | ||
| ) -> signet_evm::DriveBundleResult<'a, Db, Self, I> { | ||
| let coinbase = trevm.inner().block().coinbase; | ||
|
|
||
| // Taking these clears the context for reuse. | ||
| let (aggregate, market_context) = | ||
| trevm.inner_mut_unchecked().context.external.take_aggregate(); | ||
|
|
||
| // We check the market context here, and if it fails, we discard the | ||
| // transaction outcome and push a failure receipt. | ||
| if let Err(err) = self.context.checked_remove_ru_tx_events(&aggregate, &market_context) { | ||
| tracing::debug!(%err, "Discarding transaction outcome due to market error"); | ||
| return Err(trevm.errored(SignetBundleError::MarketError(err))); | ||
| } | ||
|
|
||
| let (execution_result, mut trevm) = trevm.accept(); | ||
|
|
||
| // Get the post simulation coinbase balance | ||
|
|
@@ -147,6 +96,7 @@ impl SignetBundleDriver<'_> { | |
| impl<I> BundleDriver<OrderDetector<I>> for SignetBundleDriver<'_> { | ||
| type Error<Db: Database + DatabaseCommit> = SignetBundleError<Db>; | ||
|
|
||
| #[instrument(skip_all, level = Level::DEBUG)] | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we maybe add a custom target here as well?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it gets the fn name as the target, which seems fine to me |
||
| fn run_bundle<'a, Db: Database + DatabaseCommit>( | ||
| &mut self, | ||
| trevm: signet_evm::EvmNeedsTx<'a, Db, I>, | ||
|
|
@@ -159,10 +109,12 @@ impl<I> BundleDriver<OrderDetector<I>> for SignetBundleDriver<'_> { | |
|
|
||
| // Check if the block we're in is valid for this bundle. Both must match | ||
| trevm_ensure!( | ||
| trevm.inner().block().number.to::<u64>() == bundle.block_number, | ||
| trevm.block_number().to::<u64>() == bundle.block_number, | ||
| trevm, | ||
| BundleError::BlockNumberMismatch.into() | ||
| ); | ||
| // Set the state block number this simulation was based on | ||
| self.response.state_block_number = trevm.block_number().to::<u64>(); | ||
|
|
||
| // Check if the state block number is valid (not 0, and not a tag) | ||
| trevm_ensure!( | ||
|
|
@@ -177,11 +129,10 @@ impl<I> BundleDriver<OrderDetector<I>> for SignetBundleDriver<'_> { | |
|
|
||
| trevm.try_with_block(self.bundle, |mut trevm| { | ||
| // Get the coinbase and basefee from the block | ||
| let coinbase = trevm.inner().block().coinbase; | ||
| let basefee = trevm.inner().block().basefee; | ||
|
|
||
| // Set the state block number this simulation was based on | ||
| self.response.state_block_number = trevm.inner().block().number.to::<u64>(); | ||
| // NB: Do not move these outside the `try_with_block` closure, as | ||
| // they may be rewritten by the bundle | ||
| let coinbase = trevm.block().coinbase; | ||
| let basefee = trevm.block().basefee; | ||
|
|
||
| // Cache the pre simulation coinbase balance, so we can use it to calculate the coinbase diff after every tx simulated. | ||
| let initial_coinbase_balance = unwrap_or_trevm_err!( | ||
|
|
@@ -192,19 +143,22 @@ impl<I> BundleDriver<OrderDetector<I>> for SignetBundleDriver<'_> { | |
| // Stack vars to keep track of the coinbase balance across txs. | ||
| let mut pre_sim_coinbase_balance = initial_coinbase_balance; | ||
|
|
||
| for tx in txs.iter() { | ||
| let span = debug_span!("bundle loop", count = txs.len()).entered(); | ||
| for (idx, tx) in txs.iter().enumerate() { | ||
| let _span = debug_span!("tx loop", tx = %tx.tx_hash(), idx).entered(); | ||
| let run_result = trevm.run_tx(tx); | ||
|
|
||
| let transacted_trevm = run_result.map_err(|e| e.map_err(Into::into))?; | ||
|
|
||
| // Set the trevm instance to the committed one | ||
| trevm = self.check_market_and_accept( | ||
| trevm = self.accept_and_accumulate( | ||
| transacted_trevm, | ||
| tx, | ||
| &mut pre_sim_coinbase_balance, | ||
| basefee, | ||
| )?; | ||
| } | ||
| drop(span); | ||
|
|
||
| // Accumulate the total results | ||
| self.response.coinbase_diff = | ||
|
|
@@ -218,6 +172,11 @@ impl<I> BundleDriver<OrderDetector<I>> for SignetBundleDriver<'_> { | |
| .unwrap_or_default(); | ||
| self.response.bundle_hash = self.bundle.bundle_hash(); | ||
|
|
||
| // Taking these clears the context for reuse. | ||
| let (orders, fills) = trevm.inner_mut_unchecked().context.external.take_aggregate(); | ||
| self.response.orders = orders; | ||
| self.response.fills = fills; | ||
|
|
||
| // return the final state | ||
| Ok(trevm) | ||
| }) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| use signet_types::MarketError; | ||
| use std::fmt::Debug; | ||
| use trevm::{ | ||
| revm::{primitives::EVMError, Database}, | ||
| BundleError, | ||
| }; | ||
|
|
||
| /// Errors that can occur when running a bundle on the Signet EVM. | ||
| #[derive(thiserror::Error)] | ||
| pub enum SignetBundleError<Db: Database> { | ||
| /// A primitive [`BundleError`] error ocurred. | ||
| #[error(transparent)] | ||
| BundleError(#[from] BundleError<Db>), | ||
| /// A [`MarketError`] ocurred. | ||
| #[error(transparent)] | ||
| MarketError(#[from] MarketError), | ||
| } | ||
|
|
||
| impl<Db: Database> Debug for SignetBundleError<Db> { | ||
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
| match self { | ||
| SignetBundleError::BundleError(e) => write!(f, "BundleError({:?})", e), | ||
| SignetBundleError::MarketError(e) => write!(f, "MarketError({:?})", e), | ||
| } | ||
| } | ||
| } | ||
|
|
||
| impl<Db: Database> From<EVMError<Db::Error>> for SignetBundleError<Db> { | ||
| fn from(e: EVMError<Db::Error>) -> Self { | ||
| SignetBundleError::BundleError(BundleError::EVMError { inner: e }) | ||
| } | ||
| } | ||
|
|
||
| impl<Db: Database> SignetBundleError<Db> { | ||
| /// Instantiate a new [`SignetBundleError`] from a [`Database::Error`]. | ||
| pub const fn evm_db(e: Db::Error) -> Self { | ||
| SignetBundleError::BundleError(BundleError::EVMError { inner: EVMError::Database(e) }) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,12 +4,22 @@ use trevm::{revm::primitives::BlockEnv, Block}; | |
|
|
||
| impl Block for SignetCallBundle { | ||
| fn fill_block_env(&self, block_env: &mut BlockEnv) { | ||
| block_env.number = | ||
| self.bundle.state_block_number.as_number().map(U256::from).unwrap_or(block_env.number); | ||
| block_env.timestamp = self.bundle.timestamp.map(U256::from).unwrap_or(block_env.timestamp); | ||
| block_env.gas_limit = self.bundle.gas_limit.map(U256::from).unwrap_or(block_env.gas_limit); | ||
| block_env.difficulty = | ||
| self.bundle.difficulty.map(U256::from).unwrap_or(block_env.difficulty); | ||
| block_env.basefee = self.bundle.base_fee.map(U256::from).unwrap_or(block_env.basefee); | ||
| let BlockEnv { | ||
| number, | ||
| coinbase, | ||
| timestamp, | ||
| gas_limit, | ||
| basefee, | ||
| difficulty, | ||
| prevrandao: _, | ||
| blob_excess_gas_and_price: _, | ||
| } = block_env; | ||
|
|
||
| *number = self.bundle.state_block_number.as_number().map(U256::from).unwrap_or(*number); | ||
| *coinbase = self.bundle.coinbase.unwrap_or(*coinbase); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. bugfix is here, coinbase was previously not set
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. great catch
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ya it's why i recommend destructuring in the implementation guide. easy to forget things |
||
| *timestamp = self.bundle.timestamp.map(U256::from).unwrap_or(*timestamp); | ||
| *gas_limit = self.bundle.gas_limit.map(U256::from).unwrap_or(*gas_limit); | ||
| *difficulty = self.bundle.difficulty.map(U256::from).unwrap_or(*difficulty); | ||
| *basefee = self.bundle.base_fee.map(U256::from).unwrap_or(*basefee); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.