From cb298d6c5f8a87c340abe077a158782e4b208c03 Mon Sep 17 00:00:00 2001 From: Robert Miller Date: Wed, 24 Jul 2024 13:50:50 -0400 Subject: [PATCH] initial sponsorship code --- crates/rbuilder/src/building/order_commit.rs | 6 +- crates/rbuilder/src/building/payout_tx.rs | 4 +- crates/rbuilder/src/building/sim.rs | 61 +++++++++++++++++++- crates/rbuilder/src/primitives/mod.rs | 29 ++++++++++ 4 files changed, 94 insertions(+), 6 deletions(-) diff --git a/crates/rbuilder/src/building/order_commit.rs b/crates/rbuilder/src/building/order_commit.rs index 48873bca..c012562a 100644 --- a/crates/rbuilder/src/building/order_commit.rs +++ b/crates/rbuilder/src/building/order_commit.rs @@ -174,7 +174,7 @@ pub struct TransactionOk { pub receipt: Receipt, } -#[derive(Error, Debug, Eq, PartialEq)] +#[derive(Clone, Error, Debug, Eq, PartialEq)] pub enum TransactionErr { #[error("Invalid transaction: {0:?}")] InvalidTransaction(InvalidTransaction), @@ -203,7 +203,7 @@ pub struct BundleOk { pub original_order_ids: Vec, } -#[derive(Error, Debug, Eq, PartialEq)] +#[derive(Clone, Error, Debug, Eq, PartialEq)] pub enum BundleErr { #[error("Invalid transaction, hash: {0:?}, err: {1}")] InvalidTransaction(B256, TransactionErr), @@ -264,7 +264,7 @@ pub struct OrderOk { pub used_state_trace: Option, } -#[derive(Error, Debug, Eq, PartialEq)] +#[derive(Clone, Error, Debug, Eq, PartialEq)] pub enum OrderErr { #[error("Transaction error: {0}")] Transaction(#[from] TransactionErr), diff --git a/crates/rbuilder/src/building/payout_tx.rs b/crates/rbuilder/src/building/payout_tx.rs index 8a0ae836..fe1bdfbd 100644 --- a/crates/rbuilder/src/building/payout_tx.rs +++ b/crates/rbuilder/src/building/payout_tx.rs @@ -32,7 +32,7 @@ pub fn create_payout_tx( signer.sign_tx(tx) } -#[derive(Debug, thiserror::Error, Eq, PartialEq)] +#[derive(Clone, Debug, thiserror::Error, Eq, PartialEq)] pub enum PayoutTxErr { #[error("Reth error: {0}")] Reth(#[from] ProviderError), @@ -92,7 +92,7 @@ pub fn insert_test_payout_tx( } } -#[derive(Debug, thiserror::Error, Eq, PartialEq)] +#[derive(Clone, Debug, thiserror::Error, Eq, PartialEq)] pub enum EstimatePayoutGasErr { #[error("Reth error: {0}")] Reth(#[from] ProviderError), diff --git a/crates/rbuilder/src/building/sim.rs b/crates/rbuilder/src/building/sim.rs index ea16b125..d80680f4 100644 --- a/crates/rbuilder/src/building/sim.rs +++ b/crates/rbuilder/src/building/sim.rs @@ -3,7 +3,10 @@ use super::{ OrderErr, PartialBlockFork, }; use crate::{ - building::{BlockBuildingContext, BlockState, CriticalCommitOrderError}, + building::{ + BlockBuildingContext, BlockState, BundleErr, CriticalCommitOrderError, InvalidTransaction, + TransactionErr, + }, primitives::{Order, OrderId, SimValue, SimulatedOrder}, utils::{NonceCache, NonceCacheRef}, }; @@ -404,9 +407,37 @@ pub fn simulate_order( let mut tracer = AccumulatorSimulationTracer::new(); let mut fork = PartialBlockFork::new(state).with_tracer(&mut tracer); let rollback_point = fork.rollback_point(); + let order_copy = order.clone(); let sim_res = simulate_order_using_fork(parent_orders, order, ctx, &mut fork); fork.rollback(rollback_point); let sim_res = sim_res?; + if let OrderSimResult::Failed(err) = &sim_res { + let err_copy = err.clone(); + tracing::trace!("Order simulation failed: {}", err); + match err { + OrderErr::Bundle(BundleErr::InvalidTransaction(_, transaction_err)) + | OrderErr::Transaction(transaction_err) => { // TODO: add share bundle case + if let TransactionErr::InvalidTransaction(invalid_transaction) = transaction_err { + if let InvalidTransaction::LackOfFundForMaxFee { fee, balance } = + invalid_transaction + { + let balance_needed = **fee - **balance; + + tracing::trace!("Order failed due to lack of funds for max fee. Attempting to sponsor. Fee: {}, Balance: {}, Sponsor fee: {}", fee, balance, balance_needed); + + let nonce = state.nonce(ctx.builder_signer.as_ref().unwrap().address)?; + + let signer = get_tx_signer(order_copy, err_copy); + + // TODO: actually sponsor the tx + // I had thought about doing so with create_payout_tx - not sure what the right way to do it is + // We also need an update to OrderSimResult to include sponsorship payment info ("e.g. this tx needs to be sponsored for x amount of ETH") + } + } + } + _ => {} + } + } Ok(OrderSimResultWithGas { result: sim_res, gas_used: tracer.used_gas, @@ -467,3 +498,31 @@ pub fn simulate_order_using_fork( Err(err) => Ok(OrderSimResult::Failed(err)), } } + +fn get_tx_signer(order: Order, order_err: OrderErr) -> Option
{ + match order_err { + OrderErr::Bundle(bundle_err) => { + if let BundleErr::InvalidTransaction(tx_hash, _) = bundle_err { + Some(order.get_tx_signer_by_hash(tx_hash).unwrap()) + } else { + tracing::trace!("error get_tx_signer: expected invalid transaction and found something that wasn't an invalid transaction"); + None + } + } + OrderErr::Transaction(_) => match order.clone() { + Order::Tx(tx) => Some(tx.tx_with_blobs.signer()), + _ => { + tracing::trace!( + "error get_tx_signer: expected tx and found something that wasn't a tx" + ); + None + } + }, + OrderErr::NegativeProfit(_) => { + tracing::trace!( + "error get_tx_signer: expected tx and found something that wasn't a tx" + ); + None + } + } +} diff --git a/crates/rbuilder/src/primitives/mod.rs b/crates/rbuilder/src/primitives/mod.rs index 721d5687..a55dc79c 100644 --- a/crates/rbuilder/src/primitives/mod.rs +++ b/crates/rbuilder/src/primitives/mod.rs @@ -121,6 +121,15 @@ pub struct Bundle { } impl Bundle { + /// Returns the signer of the tx with the given hash. + /// This is used to figure out who to sponsor the tx. + pub fn get_tx_signer_by_hash(&self, hash: B256) -> Option
{ + self.txs + .iter() + .find(|tx| tx.hash() == hash) + .map(|tx| tx.signer()) + } + pub fn can_execute_with_block_base_fee(&self, block_base_fee: u128) -> bool { can_execute_with_block_base_fee(self.list_txs(), block_base_fee) } @@ -591,6 +600,26 @@ pub enum OrderReplacementKey { } impl Order { + /// Returns the signer of the tx with the given hash. + /// This is used to figure out who to sponsor the tx. + pub fn get_tx_signer_by_hash(&self, hash: B256) -> Option
{ + match self { + Order::Bundle(bundle) => bundle.get_tx_signer_by_hash(hash), + Order::Tx(tx) => { + if tx.tx_with_blobs.hash() == hash { + Some(tx.tx_with_blobs.signer()) + } else { + None + } + } + Order::ShareBundle(bundle) => bundle + .flatten_txs() + .into_iter() + .find(|(tx, _)| tx.hash() == hash) + .map(|(tx, _)| tx.signer()), + } + } + /// Partial execution is valid as long as some tx is left. pub fn can_execute_with_block_base_fee(&self, block_base_fee: u128) -> bool { match self {