From b82119b5a66550eb6cbf6972e3c92d117f23c540 Mon Sep 17 00:00:00 2001 From: Daniel Xifra Date: Thu, 4 Dec 2025 17:26:59 -0300 Subject: [PATCH 1/4] massive share bundle removal --- crates/rbuilder-operator/src/clickhouse.rs | 30 - crates/rbuilder-primitives/src/fmt.rs | 36 +- crates/rbuilder-primitives/src/lib.rs | 348 +---------- .../rbuilder-primitives/src/order_builder.rs | 134 +---- .../src/order_statistics.rs | 2 - crates/rbuilder-primitives/src/serialize.rs | 542 +----------------- .../src/test_data_generator.rs | 34 +- .../src/backtest/backtest_build_range.rs | 1 - .../build_block/backtest_build_block.rs | 5 +- crates/rbuilder/src/backtest/execute.rs | 31 +- .../src/backtest/full_slot_block_data.rs | 3 - crates/rbuilder/src/backtest/mod.rs | 6 +- .../rbuilder/src/backtest/redistribute/mod.rs | 27 +- .../find_landed_orders.rs | 182 +----- crates/rbuilder/src/backtest/store.rs | 25 +- .../rbuilder/src/bin/run-bundle-on-prefix.rs | 1 - .../rbuilder/src/building/block_orders/mod.rs | 4 - .../block_orders/multi_share_bundle_merger.rs | 175 ------ .../block_orders/share_bundle_merger.rs | 506 ---------------- .../src/building/block_orders/test_context.rs | 266 --------- .../src/building/built_block_trace.rs | 5 +- crates/rbuilder/src/building/order_commit.rs | 250 +------- .../src/building/testing/bundle_tests/mod.rs | 489 +--------------- .../building/testing/bundle_tests/setup.rs | 34 +- .../rbuilder/src/live_builder/base_config.rs | 26 +- .../rbuilder/src/live_builder/building/mod.rs | 86 +-- crates/rbuilder/src/live_builder/mod.rs | 4 +- .../live_builder/order_flow_tracing/events.rs | 9 +- .../order_flow_tracing/order_flow_tracer.rs | 12 +- .../order_input/blob_type_order_filter.rs | 8 +- .../order_input/mempool_txs_detector.rs | 8 +- .../src/live_builder/order_input/mod.rs | 9 +- .../order_input/order_replacement_manager.rs | 101 +--- .../src/live_builder/order_input/orderpool.rs | 33 +- .../order_input/replaceable_order_sink.rs | 12 +- .../live_builder/order_input/rpc_server.rs | 57 +- .../live_builder/simulation/simulation_job.rs | 13 +- .../testdata/config_with_relay_override.toml | 1 - .../src/telemetry/metrics/tracing_metrics.rs | 4 +- docs/CONFIG.md | 2 - .../config-live-example.toml | 1 - .../rbuilder/config-backtest-example.toml | 2 - .../config/rbuilder/config-live-example.toml | 1 - 43 files changed, 93 insertions(+), 3432 deletions(-) delete mode 100644 crates/rbuilder/src/building/block_orders/multi_share_bundle_merger.rs delete mode 100644 crates/rbuilder/src/building/block_orders/share_bundle_merger.rs delete mode 100644 crates/rbuilder/src/building/block_orders/test_context.rs diff --git a/crates/rbuilder-operator/src/clickhouse.rs b/crates/rbuilder-operator/src/clickhouse.rs index f9d726881..0272d73ba 100644 --- a/crates/rbuilder-operator/src/clickhouse.rs +++ b/crates/rbuilder-operator/src/clickhouse.rs @@ -65,7 +65,6 @@ pub struct BlockRow { pub used_bundle_hashes: Vec, pub used_bundle_uuids: Vec, - pub used_sbundles_hashes: Vec, pub delayed_payment_sources: Vec, #[serde(with = "vec_u256")] @@ -178,33 +177,6 @@ fn offset_date_to_clickhouse_timestamp(date: OffsetDateTime) -> i64 { (date.unix_timestamp_nanos() / 1000) as i64 } -fn get_used_sbundles_hashes(built_block_trace: &BuiltBlockTrace) -> Vec { - built_block_trace - .included_orders - .iter() - .flat_map(|exec_result| { - if let Order::ShareBundle(sbundle) = &exec_result.order { - // don't like having special cases (merged vs not merged), can we improve this? - if sbundle.is_merged_order() { - exec_result - .original_order_ids - .iter() - .map(|id| id.to_string()) - .collect() - } else if exec_result.tx_infos.is_empty() { - // non merged empty execution sbundle - vec![] - } else { - // non merged non empty execution sbundle - vec![exec_result.order.id().to_string()] - } - } else { - Vec::new() - } - }) - .collect() -} - const MEV_VIRTUAL_BLOCKER_SOURCE: &str = "mev_blocker"; const MEV_VIRTUAL_ADDRESS: Address = Address::ZERO; @@ -280,7 +252,6 @@ impl BidObserver for BuiltBlocksWriter { used_bundle_uuids.push(bundle.uuid.to_string()); } } - let used_sbundles_hashes = get_used_sbundles_hashes(&built_block_trace); let (delayed_payment_sources, delayed_payment_values, delayed_payment_addresses) = get_delayed_payments(&built_block_trace); let delayed_payment_addresses = delayed_payment_addresses @@ -354,7 +325,6 @@ impl BidObserver for BuiltBlocksWriter { block_value: Some(submit_trace.value), used_bundle_hashes, used_bundle_uuids, - used_sbundles_hashes, delayed_payment_sources, delayed_payment_values, delayed_payment_addresses, diff --git a/crates/rbuilder-primitives/src/fmt.rs b/crates/rbuilder-primitives/src/fmt.rs index 0cf1a6d31..939b71461 100644 --- a/crates/rbuilder-primitives/src/fmt.rs +++ b/crates/rbuilder-primitives/src/fmt.rs @@ -1,42 +1,12 @@ use std::fmt::Write; -use super::{Order, ShareBundleBody, ShareBundleInner, ShareBundleTx, SimValue, SimulatedOrder}; +use super::{Order, SimValue, SimulatedOrder}; /// Writes indent spaces pub fn write_indent(indent: usize, buf: &mut Buffer) -> std::fmt::Result { buf.write_str(&format!("{: <1$}", "", indent)) } -pub fn write_share_bundle_tx( - indent: usize, - buf: &mut Buffer, - tx: &ShareBundleTx, -) -> std::fmt::Result { - write_indent(indent, buf)?; - buf.write_str(&format!( - "TX {} Rev {:?} val {}\n", - tx.tx.hash(), - tx.revert_behavior, - tx.tx.value() - )) -} - -pub fn write_share_bundle_inner( - indent: usize, - buf: &mut Buffer, - inner: &ShareBundleInner, -) -> std::fmt::Result { - write_indent(indent, buf)?; - buf.write_str(&format!("Inner can skip {} \n", inner.can_skip))?; - for item in &inner.body { - match item { - ShareBundleBody::Tx(tx) => write_share_bundle_tx(indent + 1, buf, tx)?, - ShareBundleBody::Bundle(sb) => write_share_bundle_inner(indent + 1, buf, sb)?, - } - } - Ok(()) -} - pub fn write_order( indent: usize, buf: &mut Buffer, @@ -50,10 +20,6 @@ pub fn write_order( tx.tx_with_blobs.hash(), tx.tx_with_blobs.value() )), - Order::ShareBundle(sb) => { - buf.write_str(&format!("ShB {:?}\n", sb.hash))?; - write_share_bundle_inner(indent + 1, buf, &sb.inner_bundle) - } } } diff --git a/crates/rbuilder-primitives/src/lib.rs b/crates/rbuilder-primitives/src/lib.rs index d403c7c0c..23aaab494 100644 --- a/crates/rbuilder-primitives/src/lib.rs +++ b/crates/rbuilder-primitives/src/lib.rs @@ -382,155 +382,6 @@ impl TxRevertBehavior { } } -/// Tx as part of a mev share body. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ShareBundleTx { - pub tx: TransactionSignedEcRecoveredWithBlobs, - pub revert_behavior: TxRevertBehavior, -} - -impl ShareBundleTx { - pub fn hash(&self) -> TxHash { - self.tx.hash() - } -} - -/// Body element of a mev share bundle. -/// [`ShareBundleInner::body`] is formed by several of these. -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -#[allow(clippy::large_enum_variant)] -pub enum ShareBundleBody { - Tx(ShareBundleTx), - Bundle(ShareBundleInner), -} - -impl ShareBundleBody { - pub fn refund_config(&self) -> Option> { - match self { - Self::Tx(sbundle_tx) => Some(vec![RefundConfig { - address: sbundle_tx.tx.signer(), - percent: 100, - }]), - Self::Bundle(b) => b.refund_config(), - } - } -} - -/// Mev share contains 2 types of txs: -/// - User txs: simple txs sent to us to be protected and to give kickbacks to the user. -/// - Searcher txs: Txs added by a searcher to extract MEV from the user txs. -/// Refund points to the user txs on the body and has the kickback percentage for it. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct Refund { - /// Index of the ShareBundleInner::body for which this applies. - pub body_idx: usize, - /// Percent of the profit going back to the user as kickback. - pub percent: usize, -} - -/// Users can specify how to get kickbacks and this is propagated by the MEV-Share Node to us. -/// We get this configuration as multiple RefundConfigs, then the refunds are paid to the specified addresses in the indicated percentages. -/// The sum of all RefundConfig::percent on a mev share bundle should be 100%. -/// See [ShareBundleInner::refund_config] for more details. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RefundConfig { - pub address: Address, - pub percent: usize, -} - -/// sub bundle as part of a mev share body -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ShareBundleInner { - pub body: Vec, - pub refund: Vec, - /// Optional RefundConfig for this ShareBundleInner. see [ShareBundleInner::refund_config] for more details. - pub refund_config: Vec, - /// We are allowed to skip this sub bundle (either because of inner reverts or any other reason). - /// Added specifically to allow same user sbundle merging since we stick together many sbundles and allow some of them to fail. - pub can_skip: bool, - /// Patch to track the original orders when performing order merging (see [`ShareBundleMerger`]). - pub original_order_id: Option, -} - -impl ShareBundleInner { - fn list_txs(&self) -> Vec<(&TransactionSignedEcRecoveredWithBlobs, bool)> { - self.body - .iter() - .flat_map(|b| match b { - ShareBundleBody::Tx(sbundle_tx) => { - vec![(&sbundle_tx.tx, sbundle_tx.revert_behavior.can_revert())] - } - ShareBundleBody::Bundle(bundle) => bundle.list_txs(), - }) - .collect() - } - - pub fn list_txs_revert( - &self, - ) -> Vec<(&TransactionSignedEcRecoveredWithBlobs, TxRevertBehavior)> { - self.body - .iter() - .flat_map(|b| match b { - ShareBundleBody::Tx(sbundle_tx) => { - vec![(&sbundle_tx.tx, sbundle_tx.revert_behavior)] - } - ShareBundleBody::Bundle(bundle) => bundle.list_txs_revert(), - }) - .collect() - } - - pub fn list_txs_len(&self) -> usize { - self.body - .iter() - .map(|b| match b { - ShareBundleBody::Tx(_) => 1, - ShareBundleBody::Bundle(bundle) => bundle.list_txs_len(), - }) - .sum() - } - - /// Refunds config for the ShareBundleInner. - /// refund_config not empty -> we use it - /// refund_config empty: - /// - body empty (illegal?) -> None - /// - body not empty -> first child refund_config() - /// Since for ShareBundleBody::Tx we use 100% to the signer of the tx (see [ShareBundleBody::refund_config]) as RefundConfig this basically - /// makes DFS looking for the first ShareBundleInner with explicit RefundConfig or the first Tx. - pub fn refund_config(&self) -> Option> { - if !self.refund_config.is_empty() { - return Some(self.refund_config.clone()); - } - if self.body.is_empty() { - return None; - } - self.body[0].refund_config() - } - - // Recalculate bundle hash. - pub fn hash_slow(&self) -> B256 { - let hashes = self - .body - .iter() - .map(|b| match b { - ShareBundleBody::Tx(sbundle_tx) => sbundle_tx.tx.hash(), - ShareBundleBody::Bundle(inner) => inner.hash_slow(), - }) - .collect::>(); - if hashes.len() == 1 { - hashes[0] - } else { - keccak256( - hashes - .into_iter() - .flat_map(|h| h.0.to_vec()) - .collect::>(), - ) - } - } -} - /// Uniquely identifies a replaceable sbundle or bundle #[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, Serialize, Deserialize)] pub struct ReplacementKey { @@ -540,141 +391,6 @@ pub struct ReplacementKey { pub signer: Option
, } -pub type ShareBundleReplacementData = ReplacementData; - -/// Preprocessed Share bundle originated by mev_sendBundle (https://docs.flashbots.net/flashbots-auction/advanced/rpc-endpoint#eth_sendbundle) -/// Instead of having hashes (as in the original definition) it contains the actual txs. -#[derive(Derivative)] -#[derivative(Debug, Clone, PartialEq, Eq, Hash)] -pub struct ShareBundle { - /// Hash for the ShareBundle (also used in OrderId::ShareBundle). - /// See [ShareBundle::hash_slow] for more details. - pub hash: B256, - pub block: u64, - pub max_block: u64, - inner_bundle: ShareBundleInner, - /// Cached inner_bundle.list_txs_len() - list_txs_len: usize, - pub signer: Option
, - /// data that uniquely identifies this ShareBundle for update or cancellation - pub replacement_data: Option, - /// Only used internally when we build a virtual (not part of the orderflow) ShareBundle from other orders. - pub original_orders: Vec, - - #[derivative(PartialEq = "ignore", Hash = "ignore")] - pub metadata: Metadata, -} - -impl ShareBundle { - pub fn new( - block: u64, - max_block: u64, - inner_bundle: ShareBundleInner, - signer: Option
, - replacement_data: Option, - original_orders: Vec, - metadata: Metadata, - ) -> Self { - let list_txs_len = inner_bundle.list_txs_len(); - let mut sbundle = Self { - hash: B256::default(), - block, - max_block, - inner_bundle, - list_txs_len, - signer, - replacement_data, - original_orders, - metadata, - }; - sbundle.hash_slow(); - sbundle - } - - #[allow(clippy::too_many_arguments)] - pub fn new_with_fake_hash( - hash: B256, - block: u64, - max_block: u64, - inner_bundle: ShareBundleInner, - signer: Option
, - replacement_data: Option, - original_orders: Vec, - metadata: Metadata, - ) -> Self { - let list_txs_len = inner_bundle.list_txs_len(); - Self { - hash, - block, - max_block, - inner_bundle, - list_txs_len, - signer, - replacement_data, - original_orders, - metadata, - } - } - - pub fn with_inner_bundle(self, inner_bundle: ShareBundleInner) -> Self { - Self::new( - self.block, - self.max_block, - inner_bundle, - self.signer, - self.replacement_data, - self.original_orders, - self.metadata, - ) - } - - pub fn inner_bundle(&self) -> &ShareBundleInner { - &self.inner_bundle - } - 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) - } - - pub fn list_txs(&self) -> Vec<(&TransactionSignedEcRecoveredWithBlobs, bool)> { - self.inner_bundle.list_txs() - } - - pub fn list_txs_revert( - &self, - ) -> Vec<(&TransactionSignedEcRecoveredWithBlobs, TxRevertBehavior)> { - self.inner_bundle.list_txs_revert() - } - - /// @Pending: optimize by caching (we need to enforce inner_bundle immutability) - pub fn list_txs_len(&self) -> usize { - self.list_txs_len - } - - /// BundledTxInfo for all the child txs - pub fn nonces(&self) -> Vec { - bundle_nonces(self.inner_bundle.list_txs().into_iter()) - } - - pub fn flatten_txs(&self) -> Vec<(&TransactionSignedEcRecoveredWithBlobs, bool)> { - self.inner_bundle.list_txs() - } - - // Recalculate bundle hash. - fn hash_slow(&mut self) { - self.hash = self.inner_bundle.hash_slow(); - } - - /// Patch to store the original orders for a merged order (see [`ShareBundleMerger`]) - pub fn original_orders(&self) -> Vec<&Order> { - self.original_orders.iter().collect() - } - - /// see [`ShareBundleMerger`] - pub fn is_merged_order(&self) -> bool { - !self.original_orders.is_empty() - } -} - #[derive(Error, Debug, derive_more::From)] pub enum TxWithBlobsCreateError { #[error("Failed to decode transaction, error: {0}")] @@ -1060,23 +776,6 @@ impl InMemorySize for MempoolTx { pub enum Order { Bundle(Bundle), Tx(MempoolTx), - ShareBundle(ShareBundle), -} - -/// Uniquely identifies a replaceable sbundle -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct ShareBundleReplacementKey(ReplacementKey); -impl ShareBundleReplacementKey { - pub fn new(id: Uuid, signer: Address) -> Self { - Self(ReplacementKey { - id, - signer: Some(signer), - }) - } - - pub fn key(&self) -> ReplacementKey { - self.0 - } } /// Uniquely identifies a replaceable bundle @@ -1091,22 +790,12 @@ impl BundleReplacementKey { } } -/// General type for both BundleReplacementKey and ShareBundleReplacementKey -/// Even although BundleReplacementKey and ShareBundleReplacementKey have the same info they are kept -/// as different types to avoid bugs. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub enum OrderReplacementKey { - Bundle(BundleReplacementKey), - ShareBundle(ShareBundleReplacementKey), -} - impl Order { /// 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 { Order::Bundle(bundle) => bundle.can_execute_with_block_base_fee(block_base_fee), Order::Tx(tx) => tx.tx_with_blobs.tx.max_fee_per_gas() >= block_base_fee, - Order::ShareBundle(bundle) => bundle.can_execute_with_block_base_fee(block_base_fee), } } @@ -1118,15 +807,6 @@ impl Order { match self { Order::Bundle(_) => vec![self], Order::Tx(_) => vec![self], - Order::ShareBundle(sb) => { - let res = sb.original_orders(); - if res.is_empty() { - //fallback to this order - vec![self] - } else { - res - } - } } } @@ -1139,7 +819,6 @@ impl Order { address: tx.tx_with_blobs.tx.signer(), optional: false, }], - Order::ShareBundle(bundle) => bundle.nonces(), } } @@ -1147,7 +826,6 @@ impl Order { match self { Order::Bundle(bundle) => OrderId::Bundle(bundle.uuid), Order::Tx(tx) => OrderId::Tx(tx.tx_with_blobs.hash()), - Order::ShareBundle(bundle) => OrderId::ShareBundle(bundle.hash), } } @@ -1167,7 +845,6 @@ impl Order { match self { Order::Bundle(bundle) => bundle.list_txs(), Order::Tx(tx) => vec![(&tx.tx_with_blobs, true)], - Order::ShareBundle(bundle) => bundle.list_txs(), } } @@ -1177,7 +854,6 @@ impl Order { match self { Order::Bundle(bundle) => bundle.list_txs_revert(), Order::Tx(tx) => vec![(&tx.tx_with_blobs, TxRevertBehavior::AllowedIncluded)], - Order::ShareBundle(bundle) => bundle.list_txs_revert(), } } @@ -1186,30 +862,21 @@ impl Order { match self { Order::Bundle(bundle) => bundle.list_txs_len(), Order::Tx(_) => 1, - Order::ShareBundle(bundle) => bundle.list_txs_len(), } } - pub fn replacement_key(&self) -> Option { + pub fn replacement_key(&self) -> Option { self.replacement_key_and_sequence_number() .map(|(key, _)| key) } - pub fn replacement_key_and_sequence_number(&self) -> Option<(OrderReplacementKey, u64)> { + pub fn replacement_key_and_sequence_number(&self) -> Option<(BundleReplacementKey, u64)> { match self { - Order::Bundle(bundle) => bundle.replacement_data.as_ref().map(|r| { - ( - OrderReplacementKey::Bundle(r.clone().key), - r.sequence_number, - ) - }), + Order::Bundle(bundle) => bundle + .replacement_data + .as_ref() + .map(|r| (r.clone().key, r.sequence_number)), Order::Tx(_) => None, - Order::ShareBundle(sbundle) => sbundle.replacement_data.as_ref().map(|r| { - ( - OrderReplacementKey::ShareBundle(r.clone().key), - r.sequence_number, - ) - }), } } @@ -1221,7 +888,6 @@ impl Order { match self { Order::Bundle(bundle) => bundle.block, Order::Tx(_) => None, - Order::ShareBundle(bundle) => Some(bundle.block), } } @@ -1229,7 +895,6 @@ impl Order { pub fn signer(&self) -> Option
{ match self { Order::Bundle(bundle) => bundle.signer, - Order::ShareBundle(bundle) => bundle.signer, Order::Tx(_) => None, } } @@ -1238,7 +903,6 @@ impl Order { match self { Order::Bundle(bundle) => &bundle.metadata, Order::Tx(tx) => &tx.tx_with_blobs.metadata, - Order::ShareBundle(bundle) => &bundle.metadata, } } } diff --git a/crates/rbuilder-primitives/src/order_builder.rs b/crates/rbuilder-primitives/src/order_builder.rs index d43f20e99..9d8efe9a6 100644 --- a/crates/rbuilder-primitives/src/order_builder.rs +++ b/crates/rbuilder-primitives/src/order_builder.rs @@ -1,8 +1,7 @@ use std::mem; use super::{ - Bundle, BundleRefund, BundleReplacementData, MempoolTx, Order, OrderId, Refund, RefundConfig, - ShareBundle, ShareBundleBody, ShareBundleInner, ShareBundleTx, + Bundle, BundleRefund, BundleReplacementData, MempoolTx, Order, TransactionSignedEcRecoveredWithBlobs, TxRevertBehavior, LAST_BUNDLE_VERSION, }; @@ -12,7 +11,6 @@ use super::{ pub enum OrderBuilder { MempoolTx(Option), Bundle(BundleBuilder), - ShareBundle(ShareBundleBuilder), None, } @@ -25,7 +23,6 @@ impl OrderBuilder { Order::Tx(MempoolTx::new(tx)) } OrderBuilder::Bundle(builder) => Order::Bundle(builder.build()), - OrderBuilder::ShareBundle(builder) => Order::ShareBundle(builder.build()), OrderBuilder::None => panic!("Order building was not started"), } } @@ -42,11 +39,6 @@ impl OrderBuilder { *self = OrderBuilder::Bundle(BundleBuilder::new(block)) } - pub fn start_share_bundle_builder(&mut self, block: u64, max_block: u64) { - self.assert_none(); - *self = OrderBuilder::ShareBundle(ShareBundleBuilder::new(block, max_block)); - } - pub fn start_mempool_tx_builder(&mut self) { self.assert_none(); *self = OrderBuilder::MempoolTx(None); @@ -69,9 +61,6 @@ impl OrderBuilder { OrderBuilder::Bundle(builder) => { builder.add_tx(tx_with_blobs, revert_behavior); } - OrderBuilder::ShareBundle(builder) => { - builder.add_tx(tx_with_blobs, revert_behavior); - } OrderBuilder::None => { panic!("Order building was not started"); } @@ -97,34 +86,6 @@ impl OrderBuilder { } } - // nested bundle methods - pub fn start_inner_bundle(&mut self, can_skip: bool) { - match self { - OrderBuilder::ShareBundle(builder) => { - builder.start_inner_bundle(can_skip); - } - _ => panic!("Only ShareBundle can have inner bundle"), - } - } - - pub fn finish_inner_bundle(&mut self) { - match self { - OrderBuilder::ShareBundle(builder) => { - builder.finish_inner_bundle(); - } - _ => panic!("Only ShareBundle can have inner bundle"), - } - } - - pub fn set_inner_bundle_refund(&mut self, refund: Vec) { - match self { - OrderBuilder::ShareBundle(builder) => { - builder.set_inner_bundle_refund(refund); - } - _ => panic!("Only ShareBundle can have refund"), - } - } - pub fn set_bundle_refund(&mut self, refund: BundleRefund) { match self { OrderBuilder::Bundle(builder) => { @@ -133,24 +94,6 @@ impl OrderBuilder { _ => panic!("Only Bundle can have BundleRefund"), } } - - pub fn set_inner_bundle_refund_config(&mut self, refund_config: Vec) { - match self { - OrderBuilder::ShareBundle(builder) => { - builder.set_inner_bundle_refund_config(refund_config); - } - _ => panic!("Only ShareBundle can have refund config"), - } - } - - pub fn set_inner_bundle_original_order_id(&mut self, original_order_id: OrderId) { - match self { - OrderBuilder::ShareBundle(builder) => { - builder.set_inner_bundle_original_order_id(original_order_id); - } - _ => panic!("Only ShareBundle can have refund config"), - } - } } #[derive(Debug)] @@ -233,78 +176,3 @@ impl BundleBuilder { self.txs.push((tx_with_blobs, revert_behavior)); } } - -#[derive(Debug)] -pub struct ShareBundleBuilder { - block: u64, - max_block: u64, - inner_bundle_stack: Vec, -} - -impl ShareBundleBuilder { - fn new(block: u64, max_block: u64) -> Self { - let mut res = Self { - block, - max_block, - inner_bundle_stack: Vec::new(), - }; - res.start_inner_bundle(false); - res - } - - fn build(mut self) -> ShareBundle { - let inner_bundle = self.inner_bundle_stack.pop().unwrap(); - ShareBundle::new( - self.block, - self.max_block, - inner_bundle, - None, - None, - Vec::new(), - Default::default(), - ) - } - - fn start_inner_bundle(&mut self, can_skip: bool) { - self.inner_bundle_stack.push(ShareBundleInner { - body: vec![], - refund: vec![], - refund_config: vec![], - can_skip, - original_order_id: None, - }); - } - - fn set_inner_bundle_refund(&mut self, refund: Vec) { - let last = self.inner_bundle_stack.last_mut().unwrap(); - last.refund = refund; - } - - fn set_inner_bundle_refund_config(&mut self, refund_config: Vec) { - let last = self.inner_bundle_stack.last_mut().unwrap(); - last.refund_config = refund_config; - } - - fn set_inner_bundle_original_order_id(&mut self, original_order_id: OrderId) { - let last = self.inner_bundle_stack.last_mut().unwrap(); - last.original_order_id = Some(original_order_id); - } - - fn finish_inner_bundle(&mut self) { - let inner_bundle = self.inner_bundle_stack.pop().unwrap(); - let last = self.inner_bundle_stack.last_mut().unwrap(); - last.body.push(ShareBundleBody::Bundle(inner_bundle)); - } - - fn add_tx( - &mut self, - tx: TransactionSignedEcRecoveredWithBlobs, - revert_behavior: TxRevertBehavior, - ) { - let last = self.inner_bundle_stack.last_mut().unwrap(); - last.body.push(ShareBundleBody::Tx(ShareBundleTx { - tx, - revert_behavior, - })); - } -} diff --git a/crates/rbuilder-primitives/src/order_statistics.rs b/crates/rbuilder-primitives/src/order_statistics.rs index b091d2ddb..37063f0de 100644 --- a/crates/rbuilder-primitives/src/order_statistics.rs +++ b/crates/rbuilder-primitives/src/order_statistics.rs @@ -18,7 +18,6 @@ impl OrderStatistics { match order { Order::Bundle(_) => self.bundle_count += 1, Order::Tx(_) => self.tx_count += 1, - Order::ShareBundle(_) => self.sbundle_count += 1, } } @@ -26,7 +25,6 @@ impl OrderStatistics { match order { Order::Bundle(_) => self.bundle_count -= 1, Order::Tx(_) => self.tx_count -= 1, - Order::ShareBundle(_) => self.sbundle_count -= 1, } } diff --git a/crates/rbuilder-primitives/src/serialize.rs b/crates/rbuilder-primitives/src/serialize.rs index 474e2a5bc..d1db44191 100644 --- a/crates/rbuilder-primitives/src/serialize.rs +++ b/crates/rbuilder-primitives/src/serialize.rs @@ -1,8 +1,6 @@ use super::{ Bundle, BundleRefund, BundleReplacementData, BundleReplacementKey, BundleVersion, MempoolTx, - Order, RawTransactionDecodable, Refund, RefundConfig, ShareBundle, ShareBundleBody, - ShareBundleInner, ShareBundleReplacementData, ShareBundleReplacementKey, ShareBundleTx, - TransactionSignedEcRecoveredWithBlobs, TxRevertBehavior, TxWithBlobsCreateError, + Order, RawTransactionDecodable, TransactionSignedEcRecoveredWithBlobs, TxWithBlobsCreateError, LAST_BUNDLE_VERSION, }; use alloy_consensus::constants::EIP4844_TX_TYPE_ID; @@ -505,332 +503,13 @@ impl RawTx { } } -/// Struct to de/serialize json Bundles from bundles APIs and from/db. -/// Does not assume a particular format on txs. -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RawShareBundle { - pub version: String, - pub inclusion: RawShareBundleInclusion, - pub body: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub validity: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub metadata: Option, - pub replacement_uuid: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RawShareBundleInclusion { - pub block: U64, - #[serde(skip_serializing_if = "Option::is_none")] - pub max_block: Option, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RawShareBundleBody { - #[serde(skip_serializing_if = "Option::is_none")] - pub tx: Option, - #[serde(default)] - pub can_revert: bool, - pub revert_mode: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub bundle: Option>, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RawShareBundleValidity { - #[serde(default)] - pub refund: Vec, - #[serde(default)] - pub refund_config: Vec, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct RawShareBundleMetadatada { - #[serde(default)] - pub signer: Option
, - /// See [`ShareBundleReplacementData`] sequence_number - pub replacement_nonce: Option, - /// Used for cancelling. When true the only thing we care about is signer,replacement_nonce and RawShareBundle::replacement_uuid - #[serde(default)] - pub cancelled: bool, -} - -#[derive(Error, Debug)] -pub enum RawShareBundleConvertError { - #[error("Failed to decode transaction, idx: {0}, error: {1}")] - FailedToDecodeTransaction(usize, TxWithBlobsCreateError), - #[error("Bundle too deep")] - BundleTooDeep, - #[error("Incorrect version")] - IncorrectVersion, - #[error("Empty body")] - EmptyBody, - #[error("Total refund percent exceeds 100")] - TotalRefundTooBig, - #[error("Refund config does not add to 100")] - RefundConfigIncorrect, - #[error("Found cancel on decode_new_bundle")] - FoundCancelExpectingBundle, - #[error("Unable to parse a Cancel")] - CancelError, -} - -#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] -pub struct CancelShareBundle { - pub block: u64, - pub key: ShareBundleReplacementKey, -} -/// Since we use the same API (mev_sendBundle) to get new bundles and also to cancel them we need this struct -#[allow(clippy::large_enum_variant)] -pub enum RawShareBundleDecodeResult { - NewShareBundle(Box), - CancelShareBundle(CancelShareBundle), -} - -impl RawShareBundle { - /// Same as decode but fails on cancel - pub fn decode_new_bundle( - self, - encoding: TxEncoding, - ) -> Result { - let decode_res = self.decode(encoding)?; - match decode_res { - RawShareBundleDecodeResult::NewShareBundle(b) => Ok(*b), - RawShareBundleDecodeResult::CancelShareBundle(_) => { - Err(RawShareBundleConvertError::FoundCancelExpectingBundle) - } - } - } - - pub fn decode( - self, - encoding: TxEncoding, - ) -> Result { - let (block, max_block) = ( - self.inclusion.block.to(), - self.inclusion - .max_block - .unwrap_or(self.inclusion.block) - .to(), - ); - - let signer = self.metadata.as_ref().and_then(|m| m.signer); - let replacement_nonce = self.metadata.as_ref().and_then(|m| m.replacement_nonce); - let replacement_data = - if let (Some(replacement_uuid), Some(signer), Some(replacement_nonce)) = - (self.replacement_uuid, signer, replacement_nonce) - { - Some(ShareBundleReplacementData { - key: ShareBundleReplacementKey::new(replacement_uuid, signer), - sequence_number: replacement_nonce, - }) - } else { - None - }; - - if self.metadata.as_ref().is_some_and(|r| r.cancelled) { - return Ok(RawShareBundleDecodeResult::CancelShareBundle( - CancelShareBundle { - block, - key: replacement_data - .ok_or(RawShareBundleConvertError::CancelError)? - .key, - }, - )); - } - - let (_, inner_bundle) = extract_inner_bundle(0, 0, self, &encoding)?; - let bundle = ShareBundle::new( - block, - max_block, - inner_bundle, - signer, - replacement_data, - Vec::new(), - Default::default(), - ); - Ok(RawShareBundleDecodeResult::NewShareBundle(Box::new(bundle))) - } - - /// See [TransactionSignedEcRecoveredWithBlobs::envelope_encoded_no_blobs] - pub fn encode_no_blobs(value: ShareBundle) -> Self { - let inclusion = RawShareBundleInclusion { - block: U64::from(value.block), - max_block: (value.block != value.max_block).then_some(U64::from(value.max_block)), - }; - let mut result = inner_bundle_to_raw_bundle_no_blobs(inclusion, value.inner_bundle); - result.metadata = value.signer.map(|signer| RawShareBundleMetadatada { - signer: Some(signer), - replacement_nonce: value.replacement_data.as_ref().map(|r| r.sequence_number), - cancelled: false, - }); - result.replacement_uuid = value.replacement_data.map(|r| r.key.0.id); - result - } -} - -const TX_REVERT_NOT_ALLOWED: &str = "fail"; -const TX_REVERT_ALLOWED_INCLUDED: &str = "allow"; -const TX_REVERT_ALLOWED_EXCLUDED: &str = "drop"; -fn serialize_revert_behavior(revert: TxRevertBehavior) -> String { - match revert { - TxRevertBehavior::NotAllowed => TX_REVERT_NOT_ALLOWED.to_owned(), - TxRevertBehavior::AllowedIncluded => TX_REVERT_ALLOWED_INCLUDED.to_owned(), - TxRevertBehavior::AllowedExcluded => TX_REVERT_ALLOWED_EXCLUDED.to_owned(), - } -} - -fn parse_revert_behavior(can_revert: bool, revert_mode: Option) -> TxRevertBehavior { - if let Some(revert_mode) = revert_mode { - match revert_mode.as_str() { - TX_REVERT_NOT_ALLOWED => TxRevertBehavior::NotAllowed, - TX_REVERT_ALLOWED_INCLUDED => TxRevertBehavior::AllowedIncluded, - TX_REVERT_ALLOWED_EXCLUDED => TxRevertBehavior::AllowedExcluded, - _ => { - error!(?revert_mode, "Illegal revert mode"); - TxRevertBehavior::NotAllowed - } - } - } else { - TxRevertBehavior::from_old_bool(can_revert) - } -} - -fn extract_inner_bundle( - depth: usize, - mut tx_count: usize, - raw: RawShareBundle, - encoding: &TxEncoding, -) -> Result<(usize, ShareBundleInner), RawShareBundleConvertError> { - if depth > 5 { - return Err(RawShareBundleConvertError::BundleTooDeep); - } - if raw.version != "v0.1" && raw.version != "version-1" && raw.version != "beta-1" { - return Err(RawShareBundleConvertError::IncorrectVersion); - } - - let body = raw - .body - .into_iter() - .map( - |body| -> Result { - if let Some(tx) = body.tx { - let tx = encoding.decode(tx).map_err(|e| { - RawShareBundleConvertError::FailedToDecodeTransaction(tx_count, e) - })?; - tx_count += 1; - return Ok(ShareBundleBody::Tx(ShareBundleTx { - tx, - revert_behavior: parse_revert_behavior(body.can_revert, body.revert_mode), - })); - } - - if let Some(bundle) = body.bundle { - // TODO: check that inclusion is correct - - let (new_tx_count, extracted_inner_bundle) = - extract_inner_bundle(depth + 1, tx_count, *bundle, encoding)?; - tx_count = new_tx_count; - return Ok(ShareBundleBody::Bundle(extracted_inner_bundle)); - } - - Err(RawShareBundleConvertError::EmptyBody) - }, - ) - .collect::, _>>()?; - - let (refund, refund_config) = raw - .validity - .map(|v| { - if v.refund.iter().map(|r| r.percent).sum::() > 100 { - return Err(RawShareBundleConvertError::TotalRefundTooBig); - } - - if !v.refund_config.is_empty() - && v.refund_config.iter().map(|r| r.percent).sum::() > 100 - { - return Err(RawShareBundleConvertError::RefundConfigIncorrect); - } - - Ok((v.refund, v.refund_config)) - }) - .unwrap_or_else(|| Ok((Vec::new(), Vec::new())))?; - - Ok(( - tx_count, - ShareBundleInner { - body, - refund, - refund_config, - // mev-share does not allow this yet. - can_skip: false, - original_order_id: None, - }, - )) -} - -/// Txs serialized without blobs data (canonical format) -fn inner_bundle_to_raw_bundle_no_blobs( - inclusion: RawShareBundleInclusion, - inner: ShareBundleInner, -) -> RawShareBundle { - let body = inner - .body - .into_iter() - .map(|b| match b { - ShareBundleBody::Bundle(inner) => RawShareBundleBody { - tx: None, - can_revert: false, - revert_mode: None, - bundle: Some(Box::new(inner_bundle_to_raw_bundle_no_blobs( - inclusion.clone(), - inner, - ))), - }, - ShareBundleBody::Tx(sbundle_tx) => { - // We don't really need this since revert_mode takes priority over can_revert but just in case... - let can_revert = sbundle_tx.revert_behavior.can_revert(); - let revert_mode = Some(serialize_revert_behavior(sbundle_tx.revert_behavior)); - RawShareBundleBody { - tx: Some(sbundle_tx.tx.envelope_encoded_no_blobs()), - can_revert, - revert_mode, - bundle: None, - } - } - }) - .collect(); - - let validity = (!inner.refund.is_empty() || !inner.refund_config.is_empty()).then_some( - RawShareBundleValidity { - refund: inner.refund, - refund_config: inner.refund_config, - }, - ); - - RawShareBundle { - version: String::from("v0.1"), - inclusion, - body, - validity, - metadata: None, - replacement_uuid: None, - } -} - #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[serde(tag = "type")] +#[allow(clippy::large_enum_variant)] pub enum RawOrder { Bundle(RawBundle), Tx(RawTx), - ShareBundle(RawShareBundle), } #[derive(Error, Debug)] @@ -839,8 +518,6 @@ pub enum RawOrderConvertError { FailedToDecodeBundle(RawBundleConvertError), #[error("Failed to decode transaction, error: {0}")] FailedToDecodeTransaction(TxWithBlobsCreateError), - #[error("Failed to decode share bundle`, error: {0}")] - FailedToDecodeShareBundle(RawShareBundleConvertError), #[error("Blobs not supported by RawOrder")] BlobsNotSupported, } @@ -857,12 +534,6 @@ impl RawOrder { tx.decode(encoding) .map_err(RawOrderConvertError::FailedToDecodeTransaction)?, )), - - RawOrder::ShareBundle(bundle) => Ok(Order::ShareBundle( - bundle - .decode_new_bundle(encoding) - .map_err(RawOrderConvertError::FailedToDecodeShareBundle)?, - )), } } } @@ -872,9 +543,6 @@ impl From for RawOrder { match value { Order::Bundle(bundle) => Self::Bundle(RawBundle::encode_no_blobs(bundle)), Order::Tx(tx) => Self::Tx(RawTx::encode_no_blobs(tx)), - Order::ShareBundle(bundle) => { - Self::ShareBundle(RawShareBundle::encode_no_blobs(bundle)) - } } } } @@ -882,10 +550,9 @@ impl From for RawOrder { #[cfg(test)] mod tests { use super::*; - use crate::ReplacementData; use alloy_consensus::Transaction; use alloy_eips::eip2718::Encodable2718; - use alloy_primitives::{address, b256, bytes, fixed_bytes, keccak256, U256}; + use alloy_primitives::{address, b256, bytes, fixed_bytes, U256}; use std::str::FromStr; use uuid::uuid; @@ -1605,209 +1272,6 @@ mod tests { assert_eq!(tx.value(), U256::from(36280797113317316u128)); } - #[test] - fn test_correct_share_bundle_decoding() { - // raw json string - let bundle_json = r#" - { - "version": "v0.1", - "inclusion": { - "block": "0x1", - "maxBlock": "0x11" - }, - "body": [ - { - "bundle": { - "version": "v0.1", - "inclusion": { - "block": "0x1" - }, - "body": [ - { - "tx": "0x02f86b0180843b9aca00852ecc889a0082520894c87037874aed04e51c29f582394217a0a2b89d808080c080a0a463985c616dd8ee17d7ef9112af4e6e06a27b071525b42182fe7b0b5c8b4925a00af5ca177ffef2ff28449292505d41be578bebb77110dfc09361d2fb56998260", - "canRevert": true - }, - { - "tx": "0x02f8730180843b9aca00852ecc889a008288b894c10000000000000000000000000000000000000088016345785d8a000080c001a07c8890151fed9a826f241d5a37c84062ebc55ca7f5caef4683dcda6ac99dbffba069108de72e4051a764f69c51a6b718afeff4299107963a5d84d5207b2d6932a4", - "revertMode": "drop" - } - ], - "validity": { - "refund": [ - { - "bodyIdx": 0, - "percent": 90 - } - ], - "refundConfig": [ - { - "address": "0x3e7dfb3e26a16e3dbf6dfeeff8a5ae7a04f73aad", - "percent": 100 - } - ] - } - } - }, - { - "tx": "0x02f8730101843b9aca00852ecc889a008288b894c10000000000000000000000000000000000000088016345785d8a000080c001a0650c394d77981e46be3d8cf766ecc435ec3706375baed06eb9bef21f9da2828da064965fdf88b91575cd74f20301649c9d011b234cefb6c1761cc5dd579e4750b1" - } - ], - "validity": { - "refund": [ - { - "bodyIdx": 0, - "percent": 80 - } - ] - }, - "metadata": { - "signer": "0x4696595f68034b47BbEc82dB62852B49a8EE7105", - "replacementNonce": 17 - }, - "replacementUuid": "3255ceb4-fdc5-592d-a501-2183727ca3df" - } - "#; - - let bundle_request: RawShareBundle = - serde_json::from_str(bundle_json).expect("failed to decode share bundle"); - - let bundle = bundle_request - .clone() - .decode_new_bundle(TxEncoding::WithBlobData) - .expect("failed to convert share bundle request to share bundle"); - let bundle_clone = bundle.clone(); - - assert_eq!(bundle.block, 0x1); - assert_eq!(bundle.max_block, 0x11); - assert_eq!( - bundle - .flatten_txs() - .into_iter() - .map(|(tx, opt)| (tx.hash(), opt)) - .collect::>(), - vec![ - ( - fixed_bytes!( - "ec5dd7d793a20885a822169df4030d92fbc8d3ac5bd9eaa190b82196ea2858da" - ), - true - ), - ( - fixed_bytes!( - "ba8dd77f4e9cf3c833399dc7f25408bb35fee78787a039e0ce3c80b04c537a71" - ), - true - ), - ( - fixed_bytes!( - "e8953f516797ef26566c705be13c7cc77dd0f557c734b8278fac091f13b0d46a" - ), - false - ), - ] - ); - - let expected_hash = keccak256( - [ - keccak256( - [ - fixed_bytes!( - "ec5dd7d793a20885a822169df4030d92fbc8d3ac5bd9eaa190b82196ea2858da" - ) - .to_vec(), - fixed_bytes!( - "ba8dd77f4e9cf3c833399dc7f25408bb35fee78787a039e0ce3c80b04c537a71" - ) - .to_vec(), - ] - .concat(), - ) - .to_vec(), - fixed_bytes!("e8953f516797ef26566c705be13c7cc77dd0f557c734b8278fac091f13b0d46a") - .to_vec(), - ] - .concat(), - ); - assert_eq!(bundle.hash, expected_hash); - assert_eq!( - bundle.signer, - Some(address!("4696595f68034b47BbEc82dB62852B49a8EE7105")) - ); - - let b = bundle.inner_bundle; - assert_eq!(b.body.len(), 2); - assert!(matches!(b.body[0], ShareBundleBody::Bundle(..))); - assert!(matches!( - b.body[1], - ShareBundleBody::Tx(ShareBundleTx { - revert_behavior: TxRevertBehavior::NotAllowed, - .. - }) - )); - assert_eq!( - b.refund, - vec![Refund { - body_idx: 0, - percent: 80 - }] - ); - assert!(b.refund_config.is_empty()); - - let b = if let ShareBundleBody::Bundle(b) = &b.body[0] { - b.clone() - } else { - unreachable!() - }; - assert_eq!(b.body.len(), 2); - assert!(matches!( - b.body[0], - ShareBundleBody::Tx(ShareBundleTx { - revert_behavior: TxRevertBehavior::AllowedIncluded, - .. - }) - )); - assert!(matches!( - b.body[1], - ShareBundleBody::Tx(ShareBundleTx { - revert_behavior: TxRevertBehavior::AllowedExcluded, - .. - }) - )); - assert_eq!( - b.refund, - vec![Refund { - body_idx: 0, - percent: 90 - }] - ); - assert_eq!( - b.refund_config, - vec![RefundConfig { - address: address!("3e7dfb3e26a16e3dbf6dfeeff8a5ae7a04f73aad"), - percent: 100 - }] - ); - - assert_eq!( - bundle.replacement_data, - Some(ReplacementData { - key: ShareBundleReplacementKey::new( - uuid!("3255ceb4-fdc5-592d-a501-2183727ca3df"), - address!("4696595f68034b47BbEc82dB62852B49a8EE7105") - ), - sequence_number: 17, - }) - ); - - // There are differences in json when decoding and encdoding bundle but we want our internal structures to match - let bundle_roundtrip = RawShareBundle::encode_no_blobs(bundle_clone.clone()); - let bundle_roundtrip = bundle_roundtrip - .clone() - .decode_new_bundle(TxEncoding::WithBlobData) - .expect("failed to convert roundrrip share bundle request to share bundle"); - assert_eq!(bundle_clone, bundle_roundtrip); - } - #[test] fn test_correct_raw_order_decoding() { // raw json string diff --git a/crates/rbuilder-primitives/src/test_data_generator.rs b/crates/rbuilder-primitives/src/test_data_generator.rs index e2a885108..41cc11887 100644 --- a/crates/rbuilder-primitives/src/test_data_generator.rs +++ b/crates/rbuilder-primitives/src/test_data_generator.rs @@ -1,7 +1,6 @@ use super::{ - AccountNonce, Bundle, BundleReplacementData, BundledTxInfo, MempoolTx, Order, ShareBundle, - ShareBundleBody, ShareBundleInner, ShareBundleReplacementData, ShareBundleTx, - TransactionSignedEcRecoveredWithBlobs, TxRevertBehavior, LAST_BUNDLE_VERSION, + AccountNonce, Bundle, BundleReplacementData, BundledTxInfo, MempoolTx, Order, + TransactionSignedEcRecoveredWithBlobs, LAST_BUNDLE_VERSION, }; use alloy_consensus::TxLegacy; use alloy_primitives::{Address, BlockHash, Signature, TxHash, B256, U256}; @@ -107,35 +106,6 @@ impl TestDataGenerator { res } - /// Creates a sbundle with a single TX (non optional) - /// No refunds, only useful to check for identity - pub fn create_sbundle( - &mut self, - block: u64, - sender_nonce: AccountNonce, - replacement_data: Option, - ) -> ShareBundle { - let inner_bundle = ShareBundleInner { - body: vec![ShareBundleBody::Tx(ShareBundleTx { - tx: self.create_tx_with_blobs_nonce(sender_nonce), - revert_behavior: TxRevertBehavior::NotAllowed, - })], - refund: Default::default(), - refund_config: Default::default(), - can_skip: true, - original_order_id: None, - }; - ShareBundle::new( - block, - block, - inner_bundle, - replacement_data.as_ref().and_then(|r| r.key.key().signer), - replacement_data, - Vec::new(), - Default::default(), - ) - } - /// Creates a bundle with a multiple txs pub fn create_bundle_multi_tx( &mut self, diff --git a/crates/rbuilder/src/backtest/backtest_build_range.rs b/crates/rbuilder/src/backtest/backtest_build_range.rs index 9ded65452..cebd9ecc1 100644 --- a/crates/rbuilder/src/backtest/backtest_build_range.rs +++ b/crates/rbuilder/src/backtest/backtest_build_range.rs @@ -180,7 +180,6 @@ where builders_names, &config, blocklist, - &config.base_config().sbundle_mergeable_signers(), ) { Ok(ok) => Some(ok), Err(err) => { diff --git a/crates/rbuilder/src/backtest/build_block/backtest_build_block.rs b/crates/rbuilder/src/backtest/build_block/backtest_build_block.rs index f40a93fd6..228435c22 100644 --- a/crates/rbuilder/src/backtest/build_block/backtest_build_block.rs +++ b/crates/rbuilder/src/backtest/build_block/backtest_build_block.rs @@ -106,7 +106,6 @@ where ctx.clone(), available_orders.clone(), provider_factory.clone(), - &config.base_config().sbundle_mergeable_signers(), )?; if let Some(tx_hash) = build_block_cfg.show_tx_extra_data { @@ -184,7 +183,7 @@ where fn print_order(order: &Order) { println!("{}", order.id()); - if let Order::Bundle(_) | Order::ShareBundle(_) = order { + if let Order::Bundle(_) = order { for (tx, _) in order.list_txs() { println!(" ↳ {:?}", tx.hash()); } @@ -242,7 +241,7 @@ fn print_order_execution_result(order_result: &ExecutionResult) { order_result.space_used.gas, format_ether(order_result.coinbase_profit), ); - if let Order::Bundle(_) | Order::ShareBundle(_) = order_result.order { + if let Order::Bundle(_) = order_result.order { for tx in order_result.tx_infos.iter().map(|info| &info.tx) { println!(" ↳ {:?}", tx.hash()); } diff --git a/crates/rbuilder/src/backtest/execute.rs b/crates/rbuilder/src/backtest/execute.rs index 3b677f94d..e4a63da08 100644 --- a/crates/rbuilder/src/backtest/execute.rs +++ b/crates/rbuilder/src/backtest/execute.rs @@ -1,21 +1,19 @@ use crate::{ backtest::BlockData, building::{ - builders::BacktestSimulateBlockInput, multi_share_bundle_merger::MultiShareBundleMerger, - sim::simulate_all_orders_with_sim_tree, BlockBuildingContext, BundleErr, - NullPartialBlockExecutionTracer, OrderErr, SimulatedOrderSink, SimulatedOrderStore, - TransactionErr, + builders::BacktestSimulateBlockInput, sim::simulate_all_orders_with_sim_tree, + BlockBuildingContext, BundleErr, NullPartialBlockExecutionTracer, OrderErr, TransactionErr, }, live_builder::{block_list_provider::BlockList, cli::LiveBuilderConfig}, provider::StateProviderFactory, utils::{clean_extradata, mevblocker::get_mevblocker_price, Signer}, }; use alloy_eips::BlockNumHash; -use alloy_primitives::{Address, U256}; +use alloy_primitives::U256; use rbuilder_primitives::{OrderId, SimulatedOrder}; use reth_chainspec::ChainSpec; use serde::{Deserialize, Serialize}; -use std::{cell::RefCell, rc::Rc, sync::Arc}; +use std::sync::Arc; use super::OrdersWithTimestamp; @@ -93,7 +91,6 @@ pub fn backtest_prepare_orders_from_building_context

( ctx: BlockBuildingContext, available_orders: Vec, provider: P, - sbundle_mergeable_signers: &[Address], ) -> eyre::Result where P: StateProviderFactory + Clone + 'static, @@ -108,14 +105,6 @@ where let (sim_orders, sim_errors) = simulate_all_orders_with_sim_tree(provider, &ctx, &orders, false)?; - - // Apply bundle merging as in live building. - let order_store = Rc::new(RefCell::new(SimulatedOrderStore::new())); - let mut merger = MultiShareBundleMerger::new(sbundle_mergeable_signers, order_store.clone()); - for sim_order in sim_orders { - merger.insert_order(sim_order); - } - let sim_orders = order_store.borrow().get_orders(); Ok(BacktestBlockInput { sim_orders, sim_errors, @@ -130,7 +119,6 @@ pub fn backtest_simulate_block( builders_names: Vec, config: &ConfigType, blocklist: BlockList, - sbundle_mergeable_signers: &[Address], ) -> eyre::Result where P: StateProviderFactory + Clone + 'static, @@ -145,14 +133,7 @@ where config.base_config().evm_caching_enable, )?; - backtest_simulate_block_with_context( - ctx, - block_data, - provider, - builders_names, - config, - sbundle_mergeable_signers, - ) + backtest_simulate_block_with_context(ctx, block_data, provider, builders_names, config) } pub fn backtest_simulate_block_with_context( @@ -161,7 +142,6 @@ pub fn backtest_simulate_block_with_context( provider: P, builders_names: Vec, config: &ConfigType, - sbundle_mergeabe_signers: &[Address], ) -> eyre::Result where P: StateProviderFactory + Clone + 'static, @@ -174,7 +154,6 @@ where ctx.clone(), block_data.available_orders, provider.clone(), - sbundle_mergeabe_signers, )?; let filtered_orders_blocklist_count = sim_errors diff --git a/crates/rbuilder/src/backtest/full_slot_block_data.rs b/crates/rbuilder/src/backtest/full_slot_block_data.rs index dd18dd69c..a5af10cd9 100644 --- a/crates/rbuilder/src/backtest/full_slot_block_data.rs +++ b/crates/rbuilder/src/backtest/full_slot_block_data.rs @@ -169,9 +169,6 @@ impl FullSlotBlockData { order_id_to_timestamp.insert(order.id(), command_ts.timestamp_ms); order_manager.insert_order(order); } - ReplaceableOrderPoolCommand::CancelShareBundle(cancel_share_bundle) => { - order_manager.remove_sbundle(cancel_share_bundle.key); - } ReplaceableOrderPoolCommand::CancelBundle(replacement_data) => { order_manager.remove_bundle(replacement_data); } diff --git a/crates/rbuilder/src/backtest/mod.rs b/crates/rbuilder/src/backtest/mod.rs index c761de893..a36125e2c 100644 --- a/crates/rbuilder/src/backtest/mod.rs +++ b/crates/rbuilder/src/backtest/mod.rs @@ -19,7 +19,9 @@ use alloy_network_primitives::TransactionResponse; use alloy_primitives::{TxHash, I256}; use alloy_rpc_types::{BlockTransactions, Transaction}; pub use fetch::HistoricalDataFetcher; -use rbuilder_primitives::{serialize::RawOrder, AccountNonce, Order, OrderId, OrderReplacementKey}; +use rbuilder_primitives::{ + serialize::RawOrder, AccountNonce, BundleReplacementKey, Order, OrderId, +}; pub use results_store::{BacktestResultsStorage, StoredBacktestResult}; use serde::{Deserialize, Serialize}; pub use store::HistoricalDataStorage; @@ -141,7 +143,7 @@ impl BlockData { .cmp(&a.timestamp_ms) .then_with(|| a.order.id().cmp(&b.order.id())) }); - let mut replacement_keys_seen: HashSet = HashSet::default(); + let mut replacement_keys_seen: HashSet = HashSet::default(); self.available_orders.retain(|orders| { if let Some(key) = orders.order.replacement_key() { diff --git a/crates/rbuilder/src/backtest/redistribute/mod.rs b/crates/rbuilder/src/backtest/redistribute/mod.rs index 4eb576a2b..82d11103c 100644 --- a/crates/rbuilder/src/backtest/redistribute/mod.rs +++ b/crates/rbuilder/src/backtest/redistribute/mod.rs @@ -312,30 +312,21 @@ where let mut txs = 0; let mut bundles = 0; - let mut share_bundles = 0; for ts_order in &block_data.available_orders { match &ts_order.order { Order::Bundle(_) => bundles += 1, Order::Tx(_) => txs += 1, - Order::ShareBundle(_) => share_bundles += 1, } } - let total = txs + bundles + share_bundles; + let total = txs + bundles; - info!( - total, - txs, bundles, share_bundles, filtered, "Available orders" - ); + info!(total, txs, bundles, filtered, "Available orders"); if txs == 0 { error!("Block has no mempool txs"); } if bundles == 0 { warn!("Block has no bundles"); } - if share_bundles == 0 { - debug!("Block has no share bundles"); - } - let block_profit = if built_block_data.profit.is_positive() { built_block_data.profit.into_sign_and_abs().1 } else { @@ -1149,7 +1140,6 @@ where provider.clone(), base_config.backtest_builders.clone(), config, - &base_config.sbundle_mergeable_signers(), )? .builder_outputs .into_iter() @@ -1220,19 +1210,6 @@ fn order_redistribution_address( let tx = bundle.txs.first()?; Some((tx.signer(), true)) } - Order::ShareBundle(bundle) => { - // if it is a share bundle we take either - // 1. first address from the refund config - // 2. origin of the first tx - - if let Some(first_refund) = bundle.inner_bundle().refund_config.first() { - return Some((first_refund.address, true)); - } - - let txs = bundle.list_txs(); - let (first_tx, _) = txs.first()?; - Some((first_tx.signer(), true)) - } Order::Tx(_) => { unreachable!("Mempool tx order can't have signer"); } diff --git a/crates/rbuilder/src/backtest/restore_landed_orders/find_landed_orders.rs b/crates/rbuilder/src/backtest/restore_landed_orders/find_landed_orders.rs index 2ab1c90ce..25cd536fe 100644 --- a/crates/rbuilder/src/backtest/restore_landed_orders/find_landed_orders.rs +++ b/crates/rbuilder/src/backtest/restore_landed_orders/find_landed_orders.rs @@ -3,7 +3,7 @@ use std::ops::Range; use crate::utils::get_percent; use ahash::HashMap; use alloy_primitives::{B256, I256, U256}; -use rbuilder_primitives::{Order, OrderId, ShareBundleBody, ShareBundleInner, TxRevertBehavior}; +use rbuilder_primitives::{Order, OrderId, TxRevertBehavior}; #[derive(Debug, Clone, Eq, PartialEq)] pub struct OrderTxData { @@ -65,97 +65,10 @@ impl SimplifiedOrder { .collect(); SimplifiedOrder::new(id, txs) } - Order::ShareBundle(bundle) => { - SimplifiedOrder::new(id, order_txs_from_inner_share_bundle(bundle.inner_bundle())) - } } } } -pub fn order_txs_from_inner_share_bundle(inner: &ShareBundleInner) -> Vec { - let total_refund_percent = inner.refund.iter().map(|r| r.percent).sum::(); - - let mut accumulated_txs = Vec::new(); - - let mut prev_element_paid_refund = false; - let mut current_chunk_txs = Vec::new(); - - let release_chunk = |current_chunk_txs: &mut Vec<(B256, TxRevertBehavior)>, - accumulated_txs: &mut Vec, - kickback_percent| { - if !current_chunk_txs.is_empty() { - for (hash, revert) in current_chunk_txs.drain(..) { - accumulated_txs.push(OrderTxData::new(hash, revert, kickback_percent)); - } - } - }; - - for (idx, body) in inner.body.iter().enumerate() { - let current_element_pays_refund = !inner.refund.iter().any(|r| r.body_idx == idx); - - if prev_element_paid_refund != current_element_pays_refund { - let chunk_refund_percent = if prev_element_paid_refund { - total_refund_percent - } else { - 0 - }; - release_chunk( - &mut current_chunk_txs, - &mut accumulated_txs, - chunk_refund_percent, - ); - prev_element_paid_refund = current_element_pays_refund; - } - - match body { - ShareBundleBody::Tx(tx) => { - current_chunk_txs.push((tx.hash(), tx.revert_behavior)); - } - ShareBundleBody::Bundle(inner_bundle) => { - let chunk_refund_percent = if prev_element_paid_refund { - total_refund_percent - } else { - 0 - }; - release_chunk( - &mut current_chunk_txs, - &mut accumulated_txs, - chunk_refund_percent, - ); - - let mut inner_txs = order_txs_from_inner_share_bundle(inner_bundle); - for tx in &mut inner_txs { - if current_element_pays_refund { - tx.kickback_percent = - multiply_inner_refunds(tx.kickback_percent, chunk_refund_percent); - } - } - accumulated_txs.extend(inner_txs); - } - } - } - - let chunk_refund_percent = if prev_element_paid_refund { - total_refund_percent - } else { - 0 - }; - release_chunk( - &mut current_chunk_txs, - &mut accumulated_txs, - chunk_refund_percent, - ); - - accumulated_txs -} - -fn multiply_inner_refunds(a: usize, b: usize) -> usize { - if a > 100 || b > 100 { - return 0; - } - 100 - (100 - a) * (100 - b) / 100 -} - /// ExecutedBlockTx is data from the tx executed in the block #[derive(Debug, Clone)] pub struct ExecutedBlockTx { @@ -439,9 +352,7 @@ fn find_allowed_range( mod tests { use super::*; use crate::utils::test_utils::*; - use rbuilder_primitives::{ - Bundle, BundleRefund, MempoolTx, Refund, ShareBundle, ShareBundleTx, LAST_BUNDLE_VERSION, - }; + use rbuilder_primitives::{Bundle, BundleRefund, MempoolTx, LAST_BUNDLE_VERSION}; #[test] fn test_find_allowed_range() { @@ -1018,93 +929,4 @@ mod tests { let got = SimplifiedOrder::new_from_order(&bundle); assert_eq!(expected, got); } - - #[test] - fn test_simplified_order_conversion_share_bundle() { - let bundle = Order::ShareBundle(ShareBundle::new_with_fake_hash( - hash(0xb1), - 0, - 0, - ShareBundleInner { - body: vec![ - ShareBundleBody::Tx(ShareBundleTx { - tx: tx(0x01), - revert_behavior: TxRevertBehavior::NotAllowed, - }), - ShareBundleBody::Tx(ShareBundleTx { - tx: tx(0x02), - revert_behavior: TxRevertBehavior::AllowedExcluded, - }), - ShareBundleBody::Tx(ShareBundleTx { - tx: tx(0x03), - revert_behavior: TxRevertBehavior::AllowedIncluded, - }), - ShareBundleBody::Bundle(ShareBundleInner { - body: vec![ - ShareBundleBody::Bundle(ShareBundleInner { - body: vec![ShareBundleBody::Tx(ShareBundleTx { - tx: tx(0x11), - revert_behavior: TxRevertBehavior::NotAllowed, - })], - refund: vec![], - refund_config: vec![], - can_skip: false, - original_order_id: None, - }), - ShareBundleBody::Tx(ShareBundleTx { - tx: tx(0x12), - revert_behavior: TxRevertBehavior::NotAllowed, - }), - ], - refund: vec![Refund { - body_idx: 0, - percent: 20, - }], - refund_config: vec![], - can_skip: true, - original_order_id: None, - }), - ShareBundleBody::Tx(ShareBundleTx { - tx: tx(0x04), - revert_behavior: TxRevertBehavior::AllowedIncluded, - }), - ], - refund: vec![ - Refund { - body_idx: 0, - percent: 10, - }, - Refund { - body_idx: 1, - percent: 20, - }, - Refund { - body_idx: 4, - percent: 30, - }, - ], - refund_config: vec![], - can_skip: false, - original_order_id: None, - }, - None, - None, - vec![], - Default::default(), - )); - let expected = SimplifiedOrder::new( - OrderId::ShareBundle(hash(0xb1)), - vec![ - OrderTxData::new(hash(0x01), TxRevertBehavior::NotAllowed, 0), - OrderTxData::new(hash(0x02), TxRevertBehavior::AllowedExcluded, 0), - OrderTxData::new(hash(0x03), TxRevertBehavior::AllowedIncluded, 60), - OrderTxData::new(hash(0x11), TxRevertBehavior::NotAllowed, 60), - OrderTxData::new(hash(0x12), TxRevertBehavior::NotAllowed, 68), - OrderTxData::new(hash(0x04), TxRevertBehavior::AllowedIncluded, 0), - ], - ); - - let got = SimplifiedOrder::new_from_order(&bundle); - assert_eq!(got, expected); - } } diff --git a/crates/rbuilder/src/backtest/store.rs b/crates/rbuilder/src/backtest/store.rs index 90c4cca32..466269157 100644 --- a/crates/rbuilder/src/backtest/store.rs +++ b/crates/rbuilder/src/backtest/store.rs @@ -17,7 +17,7 @@ use alloy_primitives::{ use lz4_flex::{block::DecompressError, compress_prepend_size, decompress_size_prepended}; use rayon::prelude::*; use rbuilder_primitives::{ - serialize::{CancelShareBundle, RawOrder, RawOrderConvertError, TxEncoding}, + serialize::{RawOrder, RawOrderConvertError, TxEncoding}, BundleReplacementData, OrderId, }; use serde::{Deserialize, Serialize}; @@ -246,7 +246,6 @@ impl HistoricalDataStorage { let raw_order: RawReplaceableOrderPoolCommandWithTimestamp = order.clone().into(); let order_id = match &order.command { ReplaceableOrderPoolCommand::Order(order) => Some( order.id().to_string()), - ReplaceableOrderPoolCommand::CancelShareBundle(_) => None, ReplaceableOrderPoolCommand::CancelBundle(_) => None, }; let order_json = compress_data(&serde_json::to_vec(&raw_order)?); @@ -503,9 +502,7 @@ fn order_type(command: &RawReplaceableOrderPoolCommand) -> &'static str { RawReplaceableOrderPoolCommand::Order(raw_order) => match raw_order { RawOrder::Bundle(_) => "bundle", RawOrder::Tx(_) => "tx", - RawOrder::ShareBundle(_) => "sbundle", }, - RawReplaceableOrderPoolCommand::CancelShareBundle(_) => "cancel_sbundle", RawReplaceableOrderPoolCommand::CancelBundle(_) => "cancel_bundle", } } @@ -634,8 +631,6 @@ fn group_rows_into_block_data( pub enum RawReplaceableOrderPoolCommand { /// New or update order Order(RawOrder), - /// Cancellation for sbundle - CancelShareBundle(CancelShareBundle), CancelBundle(BundleReplacementData), } @@ -645,9 +640,6 @@ impl From for RawReplaceableOrderPoolCommand { ReplaceableOrderPoolCommand::Order(order) => { RawReplaceableOrderPoolCommand::Order(order.into()) } - ReplaceableOrderPoolCommand::CancelShareBundle(cancel_share_bundle) => { - RawReplaceableOrderPoolCommand::CancelShareBundle(cancel_share_bundle) - } ReplaceableOrderPoolCommand::CancelBundle(replacement_data) => { RawReplaceableOrderPoolCommand::CancelBundle(replacement_data) } @@ -683,9 +675,6 @@ impl RawReplaceableOrderPoolCommandWithTimestamp { RawReplaceableOrderPoolCommand::Order(raw_order) => { ReplaceableOrderPoolCommand::Order(raw_order.decode(encoding)?) } - RawReplaceableOrderPoolCommand::CancelShareBundle(cancel_share_bundle) => { - ReplaceableOrderPoolCommand::CancelShareBundle(cancel_share_bundle) - } RawReplaceableOrderPoolCommand::CancelBundle(replacement_data) => { ReplaceableOrderPoolCommand::CancelBundle(replacement_data) } @@ -705,7 +694,7 @@ mod test { use alloy_rpc_types::{Block, BlockTransactions, Header, Transaction}; use rbuilder_primitives::{ serialize::{RawBundle, RawBundleMetadata, RawTx}, - BundleReplacementKey, ShareBundleReplacementKey, LAST_BUNDLE_VERSION, + BundleReplacementKey, LAST_BUNDLE_VERSION, }; use reth_primitives::Recovered; use time::OffsetDateTime; @@ -768,16 +757,6 @@ mod test { sequence_number: 876, }), }, - ReplaceableOrderPoolCommandWithTimestamp { - timestamp_ms: 1234, - command: ReplaceableOrderPoolCommand::CancelShareBundle(CancelShareBundle { - key: ShareBundleReplacementKey::new( - uuid!("12345678-1234-1234-1234-123456789abc"), - address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"), - ), - block: 12, - }), - }, ]; let winning_bid_trace = BuilderBlockReceived { diff --git a/crates/rbuilder/src/bin/run-bundle-on-prefix.rs b/crates/rbuilder/src/bin/run-bundle-on-prefix.rs index 01eb99b31..12f229364 100644 --- a/crates/rbuilder/src/bin/run-bundle-on-prefix.rs +++ b/crates/rbuilder/src/bin/run-bundle-on-prefix.rs @@ -115,7 +115,6 @@ impl LandedBlockInfo { self.config .base_config() .create_reth_provider_factory(true)?, - &self.config.base_config().sbundle_mergeable_signers(), )?; Ok(sim_orders) } diff --git a/crates/rbuilder/src/building/block_orders/mod.rs b/crates/rbuilder/src/building/block_orders/mod.rs index fc6967f69..dcc518bd0 100644 --- a/crates/rbuilder/src/building/block_orders/mod.rs +++ b/crates/rbuilder/src/building/block_orders/mod.rs @@ -1,12 +1,8 @@ -pub mod multi_share_bundle_merger; mod prioritized_order_store; -mod share_bundle_merger; #[cfg(test)] mod order_dumper; pub mod order_priority; -#[cfg(test)] -mod test_context; mod test_data_generator; use std::sync::Arc; diff --git a/crates/rbuilder/src/building/block_orders/multi_share_bundle_merger.rs b/crates/rbuilder/src/building/block_orders/multi_share_bundle_merger.rs deleted file mode 100644 index 41ccfa8ed..000000000 --- a/crates/rbuilder/src/building/block_orders/multi_share_bundle_merger.rs +++ /dev/null @@ -1,175 +0,0 @@ -use std::{cell::RefCell, rc::Rc, sync::Arc}; - -use ahash::HashMap; -use alloy_primitives::Address; -use tracing::error; - -use rbuilder_primitives::{OrderId, SimulatedOrder}; - -use super::{share_bundle_merger::ShareBundleMerger, SimulatedOrderSink}; - -/// This struct allows us to have several parallel ShareBundleMerger merging bundles for a particular signer. -/// To do this we create a ShareBundleMerger for each address we have configured and a fallback ShareBundleMerger for any other signer. -/// In the future we may decide to remove this and merge EVERYTHING no matter who is sending. -#[derive(Debug)] -pub struct MultiShareBundleMerger { - /// signer address -> merger - signers_mergers: HashMap>>>, - /// Any signer not in signers_mergers will be merged via fallback_merger - fallback_merger: Rc>>, - /// We must take note of the signer we got on insert_order so we know where to forward on remove_order - /// OrderId-> merger - inserted_orders_signers: HashMap>, -} - -impl MultiShareBundleMerger { - pub fn new(signers: &[Address], order_sink: Rc>) -> Self { - let mut signers_mergers = HashMap::default(); - for signer in signers { - signers_mergers.insert( - *signer, - Rc::new(RefCell::new(ShareBundleMerger::new(order_sink.clone()))), - ); - } - Self { - signers_mergers, - inserted_orders_signers: Default::default(), - fallback_merger: Rc::new(RefCell::new(ShareBundleMerger::new(order_sink.clone()))), - } - } - - fn get_merger( - &mut self, - signer: &Option

, - ) -> &Rc>> { - let merger = if let Some(signer) = signer { - self.signers_mergers - .get(signer) - .unwrap_or(&self.fallback_merger) - } else { - &self.fallback_merger - }; - merger - } - - /// This cloning is tricky since we don't want to clone the Rcs we want to clone the real objects so the sink should be replaced. - pub fn clone_with_sink(&self, order_sink: Rc>) -> Self { - let mut signers_mergers = HashMap::default(); - for (signer, merger) in &self.signers_mergers { - signers_mergers.insert( - *signer, - Rc::new(RefCell::new( - merger.borrow().clone_with_sink(order_sink.clone()), - )), - ); - } - Self { - signers_mergers, - inserted_orders_signers: self.inserted_orders_signers.clone(), - fallback_merger: Rc::new(RefCell::new( - self.fallback_merger - .borrow() - .clone_with_sink(order_sink.clone()), - )), - } - } -} - -impl SimulatedOrderSink for MultiShareBundleMerger { - fn insert_order(&mut self, order: Arc) { - let signer = order.order.signer(); - self.inserted_orders_signers.insert(order.id(), signer); - self.get_merger(&signer).borrow_mut().insert_order(order); - } - - fn remove_order(&mut self, id: rbuilder_primitives::OrderId) -> Option> { - match self.inserted_orders_signers.get(&id).cloned() { - Some(signer) => self.get_merger(&signer).borrow_mut().remove_order(id), - None => { - error!(order_id = ?id, "remove_order for not inserted order"); - None - } - } - } -} - -#[cfg(test)] -mod test { - use crate::building::block_orders::{order_dumper::OrderDumper, test_context::TestContext}; - - use super::MultiShareBundleMerger; - - use alloy_primitives::Address; - use lazy_static::lazy_static; - lazy_static! { - static ref SIGNER_1: Address = Address::random(); - static ref SIGNER_2: Address = Address::random(); - static ref UNKNOWN_SIGNER: Address = Address::random(); - } - - fn new_test_context() -> TestContext> { - let signers = vec![*SIGNER_1, *SIGNER_2]; - TestContext::new(|dumper| MultiShareBundleMerger::new(&signers, dumper)) - } - - #[test] - /// same signer bundles should go to the same megabundle - fn test_same_signer() { - let mut context = new_test_context(); - let (br_hi, br_low) = context.create_hi_low_orders(Some(*SIGNER_1), Some(*SIGNER_1)); - - // first order generates a megabundle with it - context.insert_order(br_hi.clone()); - let generated_order = context.pop_insert(); - context.assert_concatenated_sbundles_ok(&generated_order, &[br_hi.clone()]); - - // for second expect a cancellation and a new megabundle with both - context.insert_order(br_low.clone()); - - assert_eq!(context.pop_remove(), generated_order.id()); - let generated_order = context.pop_insert(); - context.assert_concatenated_sbundles_ok(&generated_order, &[br_hi.clone(), br_low.clone()]); - } - - #[test] - /// dif signer bundles should go to different megabundles - fn test_different_signers() { - let mut context = new_test_context(); - let (br_1, br_2) = context.create_hi_low_orders(Some(*SIGNER_1), Some(*SIGNER_2)); - let (_, br_3) = context.create_hi_low_orders(Some(*SIGNER_1), Some(*UNKNOWN_SIGNER)); - - // first order generates a megabundle with it - context.insert_order(br_1.clone()); - let generated_order = context.pop_insert(); - context.assert_concatenated_sbundles_ok(&generated_order, &[br_1.clone()]); - - // for second expect a new megabundle with it - context.insert_order(br_2.clone()); - let generated_order = context.pop_insert(); - context.assert_concatenated_sbundles_ok(&generated_order, &[br_2.clone()]); - - // for an unknown signer expect a new megabundle with it - context.insert_order(br_3.clone()); - let generated_order = context.pop_insert(); - context.assert_concatenated_sbundles_ok(&generated_order, &[br_3.clone()]); - } - - #[test] - /// unknown signer bundles should go to the same megabundle - fn test_unknown_signers() { - let mut context = new_test_context(); - let (br_hi, br_low) = context.create_hi_low_orders(Some(*UNKNOWN_SIGNER), None); - - // first order generates a megabundle with it - context.insert_order(br_hi.clone()); - let generated_order = context.pop_insert(); - context.assert_concatenated_sbundles_ok(&generated_order, &[br_hi.clone()]); - - // for second expect a cancellation and a new megabundle with both - context.insert_order(br_low.clone()); - - assert_eq!(context.pop_remove(), generated_order.id()); - let generated_order = context.pop_insert(); - context.assert_concatenated_sbundles_ok(&generated_order, &[br_hi.clone(), br_low.clone()]); - } -} diff --git a/crates/rbuilder/src/building/block_orders/share_bundle_merger.rs b/crates/rbuilder/src/building/block_orders/share_bundle_merger.rs deleted file mode 100644 index 2c37e5e9c..000000000 --- a/crates/rbuilder/src/building/block_orders/share_bundle_merger.rs +++ /dev/null @@ -1,506 +0,0 @@ -use std::{ - cell::RefCell, - cmp::Ordering, - collections::{hash_map::Entry, BTreeMap}, - rc::Rc, - sync::Arc, -}; - -use ahash::HashMap; -use alloy_primitives::{TxHash, B256, U256}; -use tracing::{error, warn}; - -use rbuilder_primitives::{ - Order, OrderId, ShareBundle, ShareBundleBody, ShareBundleInner, SimulatedOrder, -}; - -use super::SimulatedOrderSink; - -/// Mergeable mev-share order broken down to it components for easy handling. -/// Contains -#[derive(Debug, Clone)] -struct BrokenDownShareBundle { - /// sim order containing the ShareBundle as received - sim_order: Arc, - /// hash of the user inner bundle. This is the identity of the user txs - user_bundle_hash: B256, - /// extracted from self.sbundle.sim_value.paid_kickbacks - user_kickback: U256, -} - -impl BrokenDownShareBundle { - /// This should always be Some since we only create BrokenDownShareBundles from ShareBundle - pub fn sbundle(&self) -> Option<&ShareBundle> { - if let Order::ShareBundle(sbundle) = &self.sim_order.order { - return Some(sbundle); - } - None - } -} - -#[derive(Debug, Clone, Eq, PartialEq)] -struct BrokenDownShareBundleSortKey { - pub order_id: OrderId, - pub user_kickback: U256, -} - -impl BrokenDownShareBundleSortKey { - pub fn new(order: &BrokenDownShareBundle) -> Self { - Self { - order_id: order.sim_order.id(), - user_kickback: order.user_kickback, - } - } -} - -impl PartialOrd for BrokenDownShareBundleSortKey { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -/// Order by kickback for MultiBackrunManager.sorted_orders -impl Ord for BrokenDownShareBundleSortKey { - fn cmp(&self, other: &Self) -> Ordering { - let res = self.user_kickback.cmp(&other.user_kickback).reverse(); //reverse -> highers first - if let Ordering::Equal = res { - return self.order_id.cmp(&other.order_id); - } - res - } -} - -/// Handles the relation between a user_tx and all the orders that backruns it -#[derive(Debug)] -struct MultiBackrunManager { - /// id of the las that we generated with all sorted_orders - last_multi_order_id: Option, - user_bundle_hash: B256, - /// orders sorted by kickback value - sorted_orders: BTreeMap, - /// OrderId->Kickback to easily check for an order in sorted_orders - order_kickbacks: HashMap, - /// we send here the generated orders - order_sink: Rc>, -} - -impl MultiBackrunManager { - pub fn new(user_bundle_hash: B256, order_sink: Rc>) -> Self { - Self { - user_bundle_hash, - sorted_orders: BTreeMap::default(), - order_kickbacks: HashMap::default(), - last_multi_order_id: None, - order_sink, - } - } - - /// This cloning is tricky since we don't want to clone the Rcs we want to clone the real objects so the sink should be replaced. - pub fn clone_with_sink(&self, order_sink: Rc>) -> Self { - Self { - order_sink, - last_multi_order_id: self.last_multi_order_id, - user_bundle_hash: self.user_bundle_hash, - sorted_orders: self.sorted_orders.clone(), - order_kickbacks: self.order_kickbacks.clone(), - } - } - - /// Merge all orders in a single ShareBundle containing all other ShareBundle as skippable items. - /// All other info for the SimulatedOrder is taken from the first ShareBundle. - fn merge_orders(&self) -> Option> { - if self.sorted_orders.is_empty() { - return None; - } - let highest_payback_order = self.sorted_orders.first_key_value().unwrap().1; - let highest_payback_order_bundle = highest_payback_order.sbundle(); - let highest_payback_order_bundle = highest_payback_order_bundle?; - let mut body = Vec::new(); - let mut original_orders = Vec::new(); - for broken_order in self.sorted_orders.values() { - if let Some(sbundle) = broken_order.sbundle() { - let mut inner_bundle = sbundle.inner_bundle().clone(); - inner_bundle.can_skip = true; - inner_bundle.original_order_id = Some(broken_order.sim_order.id()); - body.push(ShareBundleBody::Bundle(inner_bundle)); - original_orders.push(broken_order.sim_order.order.clone()); - } - } - - let inner_bundle = ShareBundleInner { - body, - refund: Vec::new(), - refund_config: Vec::new(), - can_skip: false, - original_order_id: None, - }; - let sbundle = ShareBundle::new( - highest_payback_order_bundle.block, - highest_payback_order_bundle.max_block, - inner_bundle, - highest_payback_order_bundle.signer, - None, //replacement_data get lost since we merge many sbundles - original_orders, - // We take parent order submission time - highest_payback_order.sim_order.order.metadata().clone(), - ); - Some(Arc::new(SimulatedOrder { - order: Order::ShareBundle(sbundle), - sim_value: highest_payback_order.sim_order.sim_value.clone(), - used_state_trace: highest_payback_order.sim_order.used_state_trace.clone(), - })) - } - - /// On changes calls regenerate_multi_order - pub fn insert_order(&mut self, order: BrokenDownShareBundle) { - // Evaluate if it's valid to get a new simulation for the same bundle? - if self.order_kickbacks.contains_key(&order.sim_order.id()) { - return; - } - self.order_kickbacks - .insert(order.sim_order.id(), order.user_kickback); - self.sorted_orders - .insert(BrokenDownShareBundleSortKey::new(&order), order); - self.regenerate_multi_order(); - } - - /// On changes calls regenerate_multi_order - fn remove_order(&mut self, id: OrderId) -> Option> { - let user_kickback = self.order_kickbacks.remove(&id).or_else(|| { - error!(order_id = ?id, "remove_order for not inserted order"); - None - })?; - let key = BrokenDownShareBundleSortKey { - order_id: id, - user_kickback, - }; - let order = self.sorted_orders.remove(&key); - if let Some(order) = order { - self.regenerate_multi_order(); - return Some(order.sim_order); - } - error!(?key, "sorted order not found"); - None - } - - /// regenerates the virtual multi order and updates downstream - fn regenerate_multi_order(&mut self) { - if let Some(last_multi_order_id) = self.last_multi_order_id { - self.order_sink - .borrow_mut() - .remove_order(last_multi_order_id); - } - if self.sorted_orders.is_empty() { - self.last_multi_order_id = None; - return; - } - let merged_order = self.merge_orders(); - if merged_order.is_none() { - error!(bundle_hash = ?self.user_bundle_hash, "Failed to generate order for user bundle"); - return; - } - let merged_order = merged_order.unwrap(); - self.last_multi_order_id = Some(merged_order.id()); - self.order_sink.borrow_mut().insert_order(merged_order); - } -} - -/// ShareBundleMerger will presume that some of the bundles must be merged. -/// We expect 2 types of sbundles: -/// - UserTxs: It contains only top level tx with no refund since there is no backrun. (see [`ShareBundleMerger::break_down_user_tx_bundle`] for a clearer definition) -/// - Backruns: It contains a sub bundle with the user txs followed by backrunner sub bundles. (see [`ShareBundleMerger::break_down_backrun_bundle`] for a clearer definition) -/// Merging example: SBundle(Bundle(tx)+br1) + SBundle(Bundle(tx)+br2) will become SBundle(Bundle(Bundle(tx)+br1),Bundle(Bundle(tx)+br2)). -/// User Bundle may contain more that one tx (for the case approve + swap). -/// This means what when we insert/remove orders (sbundles) we insert/remove the virtual bundles we generate and not the original orders. -/// We ONLY use this special flow for orders with some particular structure (see [`ShareBundleMerger::break_down_bundle`]). -/// SBundle not complying with that will pass through and use the standard handling. -/// @Pending evaluate if we can avoid to send changes no every order update and "flush" changes all together (since orders are usually processed on batches) -#[derive(Debug)] -pub struct ShareBundleMerger { - /// user_tx -> MultiBackrunManagers - multi_backrun_managers: HashMap>>>, - - /// orders included in some MultiBackrunManager orders for easily deletion - order_id_2_multi_backrun_managers: HashMap>>>, - - /// all active orders are forwarder to order_sink - order_sink: Rc>, -} - -impl ShareBundleMerger { - pub fn new(order_sink: Rc>) -> Self { - Self { - order_sink, - multi_backrun_managers: HashMap::default(), - order_id_2_multi_backrun_managers: HashMap::default(), - } - } - - /// This cloning is tricky since we don't want to clone the Rcs we want to clone the real objects so the sink should be replaced. - /// We also have in order_id_2_multi_backrun_managers references to multi_backrun_managers so we must clone manually :( - pub fn clone_with_sink(&self, order_sink: Rc>) -> Self { - let mut multi_backrun_managers = HashMap::default(); - let mut order_id_2_multi_backrun_managers = HashMap::default(); - for (tx_hash, manager) in &self.multi_backrun_managers { - multi_backrun_managers.insert( - *tx_hash, - Rc::new(RefCell::new( - manager.borrow().clone_with_sink(order_sink.clone()), - )), - ); - manager.borrow().sorted_orders.iter().for_each(|(key, _)| { - order_id_2_multi_backrun_managers.insert(key.order_id, manager.clone()); - }); - } - - Self { - multi_backrun_managers, - order_id_2_multi_backrun_managers, - order_sink, - } - } - - const USER_BUNDLE_INDEX: usize = 0; - /// Tries to analyze if the order is a mergeable sbundle by looking at its structure to check if this is a user txs or a backrun sbundle - /// Only check here is if Signer is in selected_signers - fn break_down_bundle(&self, order: &Arc) -> Option { - let sbundle = if let Order::ShareBundle(sbundle) = &order.order { - sbundle - } else { - return None; - }; - let first_item = sbundle.inner_bundle().body.first().or_else(|| { - error!(?sbundle, "Empty sbundle"); - None - })?; - - match first_item { - ShareBundleBody::Tx(_) => Self::break_down_user_tx_bundle(sbundle, order), - ShareBundleBody::Bundle(_) => Self::break_down_backrun_bundle(sbundle, order), - } - } - - /// This should contain only ShareBundleBody::Tx with no refund (but it can contain refund_info) - fn break_down_user_tx_bundle( - sbundle: &ShareBundle, - sim_order: &Arc, - ) -> Option { - let got_bundles = sbundle - .inner_bundle() - .body - .iter() - .any(|item| matches!(item, ShareBundleBody::Bundle(_))); - if got_bundles { - warn!(hash = ?sbundle.hash, - "sbundle for user txs should not contain bundles" - ); - return None; - } - if !sbundle.inner_bundle().refund.is_empty() { - warn!(hash = ?sbundle.hash, - "sbundle for user txs should not contain refunds" - ); - return None; - } - Some(BrokenDownShareBundle { - sim_order: sim_order.clone(), - user_kickback: U256::from(0), - user_bundle_hash: sbundle.hash, - }) - } - - /// To be a backrun bundle it must comply: - /// - Must contain one refund pointing to the user bundle. - /// - The first (USER_BUNDLE_INDEX) item (the user bundle) should be a sub bundle with no refund. - fn break_down_backrun_bundle( - sbundle: &ShareBundle, - sim_order: &Arc, - ) -> Option { - let user_bundle_hash = Self::check_and_get_user_bundle_hash_from_backrun(sbundle)?; - if !Self::check_refunds_from_backrun_ok(sbundle) { - return None; - } - let mut user_kickback = U256::ZERO; - for (_, kickback) in sim_order.sim_value.paid_kickbacks() { - user_kickback += kickback; - } - Some(BrokenDownShareBundle { - sim_order: sim_order.clone(), - user_kickback, - user_bundle_hash, - }) - } - - /// Checks also that it contains no refund stuff - fn check_and_get_user_bundle_hash_from_backrun(sbundle: &ShareBundle) -> Option { - let user_bundle = sbundle - .inner_bundle() - .body - .get(Self::USER_BUNDLE_INDEX) - .or_else(|| { - warn!( - hash = ?sbundle.hash, - "sbundle should have at least {} items", - Self::USER_BUNDLE_INDEX + 1 - ); - None - })?; - - if let ShareBundleBody::Bundle(inner_bundle) = user_bundle { - if !inner_bundle.refund.is_empty() { - warn!( - hash = ?sbundle.hash, - "sbundle user bundle should not contain refunds" - ); - return None; - } - Some(inner_bundle.hash_slow()) - } else { - warn!( - hash = ?sbundle.hash, - "sbundle user bundle should be a ShareBundleInner" - ); - None - } - } - - /// - Have a single inner_bundle.refund (user tx) with body_idx == 0 - fn check_refunds_from_backrun_ok(sbundle: &ShareBundle) -> bool { - if sbundle.inner_bundle().refund.len() != 1 { - warn!( - hash = ?sbundle.hash, - "sbundle should have a single refund but has {}", - sbundle.inner_bundle().refund.len() - ); - return false; - } - let first_body_idx = sbundle.inner_bundle().refund[0].body_idx; - if first_body_idx != Self::USER_BUNDLE_INDEX { - warn!( - hash = ?sbundle.hash, - "sbundle refund[0].body_idx should be {} but is {}", - Self::USER_BUNDLE_INDEX, - first_body_idx, - ); - return false; - } - true - } -} - -impl SimulatedOrderSink for ShareBundleMerger { - /// if we can manage the SimulatedOrder send it to the MultiBackrunManager - /// if not just forward downstream - fn insert_order(&mut self, order: Arc) { - if let Some(broken_down_sbundle) = self.break_down_bundle(&order) { - let handler = self - .multi_backrun_managers - .entry(broken_down_sbundle.user_bundle_hash) - .or_insert(Rc::new(RefCell::new(MultiBackrunManager::new( - broken_down_sbundle.user_bundle_hash, - self.order_sink.clone(), - )))); - handler.borrow_mut().insert_order(broken_down_sbundle); - self.order_id_2_multi_backrun_managers - .insert(order.id(), handler.clone()); - } else { - self.order_sink.borrow_mut().insert_order(order); - } - } - - /// give to handler or forward downstream - fn remove_order(&mut self, id: OrderId) -> Option> { - match self.order_id_2_multi_backrun_managers.entry(id) { - Entry::Occupied(handler) => handler.get().borrow_mut().remove_order(id), - Entry::Vacant(_) => self.order_sink.borrow_mut().remove_order(id), - } - } -} - -#[cfg(test)] -mod test { - - use crate::building::block_orders::{order_dumper::OrderDumper, test_context::TestContext}; - use rbuilder_primitives::{AccountNonce, Order}; - - use super::ShareBundleMerger; - - fn new_test_context() -> TestContext> { - TestContext::new(ShareBundleMerger::new) - } - - #[test] - /// Txs and bundles should pass as is - fn test_non_sbundle() { - let mut context = new_test_context(); - - // TX - let order = context - .data_gen - .base - .create_tx_order(AccountNonce::default()); - context.assert_passes_as_is(order); - - // Bundle - let bundle = context - .data_gen - .base - .create_bundle(0, AccountNonce::default(), None); - context.assert_passes_as_is(Order::Bundle(bundle)); - } - - #[test] - /// more than a inner_bundle.refund should pass as is - fn test_2_refunds() { - let mut context = new_test_context(); - let sbundle = context.create_sbundle_tx_br(); - let mut new_inner_bundle = sbundle.inner_bundle().clone(); - new_inner_bundle - .refund - .push(sbundle.inner_bundle().refund[0].clone()); - let sbundle = sbundle.with_inner_bundle(new_inner_bundle); - context.assert_passes_as_is(Order::ShareBundle(sbundle)); - } - - #[test] - /// Test tx+br_hi , tx+br_low. adding and removing and checking that we always have the right generated multisbundle - fn test_hi_low() { - let mut context = new_test_context(); - let (br_hi, br_low) = context.create_hi_low_orders(None, None); - - // Insert hi expect an order with only br_hi - context.insert_order(br_hi.clone()); - let generated_order = context.pop_insert(); - context.assert_concatenated_sbundles_ok(&generated_order, &[br_hi.clone()]); - - // Insert low expect a cancellation for prev order and hi+low - context.insert_order(br_low.clone()); - assert_eq!(context.pop_remove(), generated_order.id()); - let generated_order = context.pop_insert(); - context.assert_concatenated_sbundles_ok(&generated_order, &[br_hi.clone(), br_low.clone()]); - - // Remove hi order expect a cancellation for prev order and low - context.remove_order(br_hi.id()); - assert_eq!(context.pop_remove(), generated_order.id()); - let generated_order = context.pop_insert(); - context.assert_concatenated_sbundles_ok(&generated_order, &[br_low.clone()]); - - // Remove low order expect a cancellation for prev order and nothing more (shoudn't insert an empty sbundle!) - context.remove_order(br_low.id()); - assert_eq!(context.pop_remove(), generated_order.id()); - - // We expect an order with only br_low - context.insert_order(br_low.clone()); - let generated_order = context.pop_insert(); - context.assert_concatenated_sbundles_ok(&generated_order, &[br_low.clone()]); - - // Insert hi expect a cancellation for prev order and hi+low - context.insert_order(br_hi.clone()); - assert_eq!(context.pop_remove(), generated_order.id()); - let generated_order = context.pop_insert(); - context.assert_concatenated_sbundles_ok(&generated_order, &[br_hi.clone(), br_low.clone()]); - - // reinsert hi shouldn't do anything - context.insert_order(br_hi.clone()); - } -} diff --git a/crates/rbuilder/src/building/block_orders/test_context.rs b/crates/rbuilder/src/building/block_orders/test_context.rs deleted file mode 100644 index 371a5d1a2..000000000 --- a/crates/rbuilder/src/building/block_orders/test_context.rs +++ /dev/null @@ -1,266 +0,0 @@ -use std::{cell::RefCell, rc::Rc, sync::Arc}; - -use alloy_primitives::{Address, U256}; - -use crate::utils::int_percentage; -use rbuilder_primitives::{ - AccountNonce, Order, OrderId, Refund, RefundConfig, ShareBundle, ShareBundleBody, - ShareBundleInner, ShareBundleTx, SimValue, SimulatedOrder, TxRevertBehavior, -}; - -use super::{order_dumper::OrderDumper, SimulatedOrderSink, TestDataGenerator}; - -/// Helper with a ShareBundleMerger connected to a OrderDumper so we can analyze the results -/// Usage: -/// - Create orders via funcs like create_multiple_sbundle_tx_br -/// - Call insert_order/remove_order -/// - Check expected results (pop_insert/pop_remove) in the expected order. -pub struct TestContext { - pub data_gen: TestDataGenerator, - pub dumper: Rc>, - pub tested_sink: TestedSinkType, -} - -const DEFAULT_REFUND: usize = 90; -const DONT_CARE_BLOCK: u64 = 0; -const DONT_CARE_PROFIT: u64 = 1; -pub const DONT_CARE_GAS_PRICE: u64 = 1; -pub const HI_PROFIT: u64 = 10_000_000; -pub const LOW_PROFIT: u64 = 1_000_000; - -impl TestContext { - pub fn new>) -> TestedSinkType>( - tested_sink_factory: FactoryType, - ) -> Self { - let data_gen = TestDataGenerator::default(); - let dumper = Rc::new(RefCell::new(OrderDumper::default())); - let tested_sink = tested_sink_factory(dumper.clone()); - Self { - data_gen, - dumper, - tested_sink, - } - } - - pub fn insert_order(&mut self, order: Arc) { - self.tested_sink.insert_order(order); - } - - pub fn remove_order(&mut self, id: OrderId) -> Option> { - self.tested_sink.remove_order(id) - } - - pub fn pop_insert(&mut self) -> Arc { - self.dumper.borrow_mut().pop_insert() - } - - pub fn pop_remove(&mut self) -> OrderId { - self.dumper.borrow_mut().pop_remove() - } - - fn default_refund_recipient() -> Address { - Address::default() - } - - /// 100% of the refund goes to Address::default() - fn default_refund_config() -> Vec { - vec![RefundConfig { - address: Self::default_refund_recipient(), - percent: 100, - }] - } - - // DEFAULT_REFUND applies to the tx at index 0 - fn default_tx_br_refund() -> Vec { - vec![Refund { - body_idx: 0, - percent: DEFAULT_REFUND, - }] - } - - fn create_share_bundle_tx(&mut self, revert_behavior: TxRevertBehavior) -> ShareBundleBody { - let tx = self - .data_gen - .base - .create_tx_with_blobs_nonce(AccountNonce::default()); - let stx = ShareBundleTx { - tx, - revert_behavior, - }; - ShareBundleBody::Tx(stx) - } - - /// User tx inside an inner bundle. - fn create_share_bundle_tx_bundle( - &mut self, - revert_behavior: TxRevertBehavior, - ) -> ShareBundleBody { - let inner = ShareBundleInner { - body: vec![self.create_share_bundle_tx(revert_behavior)], - refund: Default::default(), - refund_config: Default::default(), - can_skip: false, - original_order_id: Default::default(), - }; - ShareBundleBody::Bundle(inner) - } - - /// creates the multiple typical tx+backrun with refund to tx - /// tx is the same in all backruns - pub fn create_multiple_sbundle_tx_br(&mut self, sbundle_count: usize) -> Vec { - let tx = self.create_share_bundle_tx_bundle(TxRevertBehavior::AllowedExcluded); - let mut res = Vec::new(); - for _ in 0..sbundle_count { - let body = vec![ - tx.clone(), - self.create_share_bundle_tx(TxRevertBehavior::NotAllowed), - ]; - res.push(self.create_sbundle_from_body( - body, - Self::default_tx_br_refund(), - Self::default_refund_config(), - None, - )); - } - res - } - - /// returns a pair of orders (hi,low) hi having more kickbacks than low - pub fn create_hi_low_orders( - &mut self, - hi_signer: Option
, - low_signer: Option
, - ) -> (Arc, Arc) { - let mut backruns = self.create_multiple_sbundle_tx_br(2); - backruns[0].signer = hi_signer; - backruns[1].signer = low_signer; - let br_hi = self.create_sim_order( - Order::ShareBundle(backruns[0].clone()), - HI_PROFIT, - DONT_CARE_GAS_PRICE, - ); - let br_low = self.create_sim_order( - Order::ShareBundle(backruns[1].clone()), - LOW_PROFIT, - DONT_CARE_GAS_PRICE, - ); - (br_hi, br_low) - } - - /// creates the typical tx+backrun with refund to tx with selected_signer - pub fn create_sbundle_tx_br(&mut self) -> ShareBundle { - self.create_multiple_sbundle_tx_br(1)[0].clone() - } - - /// Creates and order sending default kickbacks - pub fn create_sim_order( - &self, - order: Order, - coinbase_profit: u64, - mev_gas_price: u64, - ) -> Arc { - let sim_value = - SimValue::new_test_no_gas(U256::from(coinbase_profit), U256::from(mev_gas_price)) - .with_kickbacks(vec![( - Self::default_refund_recipient(), - U256::from(int_percentage(coinbase_profit, DEFAULT_REFUND)), - )]); - Arc::new(SimulatedOrder { - order, - sim_value, - used_state_trace: None, - }) - } - - /// creates a basic bundle with body and refund/refund_config. - /// It does NOT check that refund[].body_idx are valid (should be < tx_count) - fn create_sbundle_from_body( - &mut self, - body: Vec, - refund: Vec, - refund_config: Vec, - signer: Option
, - ) -> ShareBundle { - let inner = ShareBundleInner { - body, - refund, - refund_config, - can_skip: false, - original_order_id: None, - }; - ShareBundle::new( - DONT_CARE_BLOCK, - DONT_CARE_BLOCK, - inner, - signer, - None, - Vec::new(), - Default::default(), - ) - } - - fn as_inner(item: &ShareBundleBody) -> &ShareBundleInner { - match item { - ShareBundleBody::Tx(_) => { - panic!("Found ShareBundleBody::Tx, expecting ShareBundleBody::Bundle") - } - ShareBundleBody::Bundle(inner) => inner, - } - } - - fn as_sbundle(item: &Order) -> &ShareBundle { - match item { - Order::Bundle(_) => panic!("Order::Bundle expecting ShareBundle"), - Order::Tx(_) => panic!("Order::Tx expecting ShareBundle"), - Order::ShareBundle(sb) => sb, - } - } - - /// Checks: - /// - concatenated_sbundle is composed of all the ShareBundleInner of the sbundles in that order and made skippable. - /// - the concatenated_sbundle has no refunds. - /// - SimValue of concatenated_order is the same as the first of sbundles (current expected behavior of merging) - /// - /// self is not used but simplifies the call since the static function would need the types specified. - pub fn assert_concatenated_sbundles_ok( - &self, - concatenated_order: &Arc, - sbundles: &[Arc], - ) { - let concatenated_sbundle = Self::as_sbundle(&concatenated_order.order); - assert_eq!( - concatenated_sbundle.inner_bundle().body.len(), - sbundles.len() - ); - assert!(concatenated_sbundle.inner_bundle().refund.is_empty()); - assert!(concatenated_sbundle.inner_bundle().refund_config.is_empty()); - let concatenated_sbundle_inners = concatenated_sbundle - .inner_bundle() - .body - .iter() - .map(Self::as_inner); - let sbundles_inners = sbundles - .iter() - .map(|sb| Self::as_sbundle(&sb.order).inner_bundle()); - concatenated_sbundle_inners - .zip(sbundles_inners) - .for_each(|(conc_sbundle, sbundle)| { - let mut sbundle = sbundle.clone(); - sbundle.can_skip = true; - sbundle.original_order_id = conc_sbundle.original_order_id; - assert_eq!(sbundle, *conc_sbundle); - }); - if !sbundles.is_empty() { - assert_eq!(concatenated_order.sim_value, sbundles[0].sim_value); - } - } - - /// asserts that the given Order when inserted passes as is to the sink. - pub fn assert_passes_as_is(&mut self, order: Order) { - let sim_order = - self.data_gen - .create_sim_order(order, DONT_CARE_PROFIT, DONT_CARE_GAS_PRICE); - self.insert_order(sim_order.clone()); - assert!(self.pop_insert() == sim_order); - } -} diff --git a/crates/rbuilder/src/building/built_block_trace.rs b/crates/rbuilder/src/building/built_block_trace.rs index 6e622d515..b03ca06e7 100644 --- a/crates/rbuilder/src/building/built_block_trace.rs +++ b/crates/rbuilder/src/building/built_block_trace.rs @@ -7,7 +7,7 @@ use super::ExecutionResult; use ahash::{AHasher, HashMap, HashSet}; use alloy_primitives::{Address, TxHash, I256, U256}; use rbuilder_primitives::{ - order_statistics::OrderStatistics, Order, OrderId, OrderReplacementKey, SimulatedOrder, + order_statistics::OrderStatistics, BundleReplacementKey, Order, OrderId, SimulatedOrder, }; use std::{collections::hash_map, hash::Hasher, time::Duration}; use time::OffsetDateTime; @@ -68,7 +68,7 @@ pub struct BuiltBlockTrace { #[derive(thiserror::Error, Debug)] pub enum BuiltBlockTraceError { #[error("More than one order is included with the same replacement data: {0:?}")] - DuplicateReplacementData(OrderReplacementKey), + DuplicateReplacementData(BundleReplacementKey), #[error("Included order had tx from or to blocked address")] BlockedAddress, #[error( @@ -146,7 +146,6 @@ impl BuiltBlockTrace { .fold((0, 0, 0), |acc, order| match order.order { Order::Tx(_) => (acc.0 + 1, acc.1, acc.2), Order::Bundle(_) => (acc.0, acc.1 + 1, acc.2), - Order::ShareBundle(_) => (acc.0, acc.1, acc.2 + 1), }) } diff --git a/crates/rbuilder/src/building/order_commit.rs b/crates/rbuilder/src/building/order_commit.rs index dd9ca2fbf..ab30ba129 100644 --- a/crates/rbuilder/src/building/order_commit.rs +++ b/crates/rbuilder/src/building/order_commit.rs @@ -14,11 +14,9 @@ use alloy_consensus::{constants::KECCAK_EMPTY, Transaction}; use alloy_evm::Database; use alloy_primitives::{Address, B256, I256, U256}; use alloy_rlp::Encodable; -use itertools::Itertools; use rbuilder_primitives::{ evm_inspector::{RBuilderEVMInspector, UsedStateTrace}, - BlockSpace, Bundle, Order, OrderId, RefundConfig, ShareBundle, ShareBundleBody, - ShareBundleInner, SimValue, TransactionSignedEcRecoveredWithBlobs, + BlockSpace, Bundle, Order, OrderId, SimValue, TransactionSignedEcRecoveredWithBlobs, }; use reth::{ consensus_common::validation::MAX_RLP_BLOCK_SIZE, revm::database::StateProviderDatabase, @@ -522,11 +520,6 @@ impl< ) } - /// If current balance < initial balance returns 0. - fn saturating_coinbase_delta(&mut self, initial_balance: U256) -> Result { - Ok(self.coinbase_balance()?.saturating_sub(initial_balance)) - } - /// Helper func that executes f and rollbacks on Ok(Err). /// For CriticalCommitOrderError we don't rollback since it's a critical unrecoverable failure /// Use like this: @@ -988,233 +981,6 @@ impl< Ok(Ok(insert)) } - /// block check + commit_share_bundle_no_rollback + rollback - fn commit_share_bundle( - &mut self, - bundle: &ShareBundle, - space_state: BlockBuildingSpaceState, - allow_tx_skip: bool, - ) -> Result, CriticalCommitOrderError> { - let current_block = self.ctx.block(); - if !(bundle.block <= current_block && current_block <= bundle.max_block) { - return Ok(Err(BundleErr::TargetBlockIncorrect { - block: current_block, - target_block: bundle.block, - target_max_block: bundle.max_block, - })); - } - self.execute_with_rollback(|s| { - s.commit_share_bundle_no_rollback(bundle, space_state, allow_tx_skip) - }) - } - - /// Calls commit_share_bundle_inner to do all the hard work and, if everting goes ok, pays kickbacks - fn commit_share_bundle_no_rollback( - &mut self, - bundle: &ShareBundle, - space_state: BlockBuildingSpaceState, - allow_tx_skip: bool, - ) -> Result, CriticalCommitOrderError> { - let res = - self.commit_share_bundle_inner(bundle.inner_bundle(), space_state, allow_tx_skip)?; - let res = match res { - Ok(r) => r, - Err(e) => { - return Ok(Err(e)); - } - }; - - let mut insert = res.bundle_ok; - - // now pay all kickbacks - for (to, payout) in res.payouts_promissed.into_iter().sorted_by_key(|(a, _)| *a) { - if let Err(err) = self.insert_refund_payout_tx( - payout, - to, - space_state.reserved_block_space, - &mut insert, - )? { - return Ok(Err(err)); - } - } - Ok(Ok(insert)) - } - - /// Only changes the state on Ok(Ok) - fn commit_share_bundle_inner( - &mut self, - bundle: &ShareBundleInner, - space_state: BlockBuildingSpaceState, - allow_tx_skip: bool, - ) -> Result, CriticalCommitOrderError> { - self.execute_with_rollback(|s| { - s.commit_share_bundle_inner_no_rollback(bundle, space_state, allow_tx_skip) - }) - } - - fn commit_share_bundle_inner_no_rollback( - &mut self, - bundle: &ShareBundleInner, - space_state: BlockBuildingSpaceState, - allow_tx_skip: bool, - ) -> Result, CriticalCommitOrderError> { - let mut insert = BundleOk { - space_used: BlockSpace::ZERO, - cumulative_space_used: space_state.space_used(), - tx_infos: Vec::new(), - nonces_updated: Vec::new(), - paid_kickbacks: Vec::new(), - delayed_kickback: None, - original_order_ids: Vec::new(), - }; - let coinbase_balance_before = self.coinbase_balance()?; - let refundable_elements = bundle - .refund - .iter() - .map(|r| (r.body_idx, r.percent)) - .collect::>(); - let mut refundable_profit = U256::from(0); - let mut inner_payouts = HashMap::new(); - for (idx, body) in bundle.body.iter().enumerate() { - match body { - ShareBundleBody::Tx(sbundle_tx) => { - let rollback_point = self.rollback_point(); - let tx = &sbundle_tx.tx; - let result = - self.commit_tx(tx, insert.space_state(space_state.reserved_block_space()))?; - match result { - Ok(res) => { - if !res.tx_info.receipt.success { - match sbundle_tx.revert_behavior { - rbuilder_primitives::TxRevertBehavior::NotAllowed => { - return Ok(Err(BundleErr::TransactionReverted(tx.hash()))); - } - rbuilder_primitives::TxRevertBehavior::AllowedIncluded => {} - rbuilder_primitives::TxRevertBehavior::AllowedExcluded => { - self.rollback(rollback_point); - continue; - } - } - } - if res.tx_info.coinbase_profit.is_positive() - && !refundable_elements.contains_key(&idx) - { - refundable_profit += res.tx_info.coinbase_profit.unsigned_abs(); - } - Self::accumulate_tx_execution(res, &mut insert); - } - Err(err) => { - // if optional transaction, skip - if allow_tx_skip && sbundle_tx.revert_behavior.can_revert() { - continue; - } else { - return Ok(Err(BundleErr::InvalidTransaction(tx.hash(), err))); - } - } - } - } - ShareBundleBody::Bundle(inner_bundle) => { - let inner_res = self.commit_share_bundle_inner( - inner_bundle, - insert.space_state(space_state.reserved_block_space()), - allow_tx_skip, - )?; - match inner_res { - Ok(res) => { - if let Some(original_order_id) = inner_bundle.original_order_id { - if !res.bundle_ok.tx_infos.is_empty() { - // We only consider this order executed if something was so we exclude 100% dropped bundles. - insert.original_order_ids.push(original_order_id); - } - } - if res.coinbase_diff_before_payouts > res.total_payouts_promissed - && !refundable_elements.contains_key(&idx) - { - refundable_profit += - res.coinbase_diff_before_payouts - res.total_payouts_promissed - } - insert - .original_order_ids - .extend(res.bundle_ok.original_order_ids); - insert.space_used += res.bundle_ok.space_used; - insert.cumulative_space_used = res.bundle_ok.cumulative_space_used; - insert.tx_infos.extend(res.bundle_ok.tx_infos); - update_nonce_list_with_updates( - &mut insert.nonces_updated, - res.bundle_ok.nonces_updated, - ); - - for (addr, reserve) in res.payouts_promissed { - inner_payouts - .entry(addr) - .and_modify(|v| { - *v += reserve.total_refundable_value; - }) - .or_insert(reserve.total_refundable_value); - } - } - Err(err) => { - if inner_bundle.can_skip { - continue; - } else { - return Ok(Err(err)); - } - } - } - } - } - } - - for (idx, percent) in refundable_elements { - let refund_config = - if let Some(config) = bundle.body.get(idx).and_then(|b| b.refund_config()) { - config - } else { - return Ok(Err(BundleErr::IncorrectRefundableElement(idx))); - }; - - let total_value = get_percent(refundable_profit, percent); - for RefundConfig { address, percent } in refund_config { - let value = get_percent(total_value, percent); - inner_payouts - .entry(address) - .and_modify(|v| { - *v += value; - }) - .or_insert(value); - } - } - - // calculate gas limits - let mut payouts_promised = HashMap::new(); - for (to, refundable_value) in inner_payouts.drain() { - let payout = match self.estimate_refund_payout_tx( - to, - refundable_value, - insert.cumulative_space_used, - ) { - Ok(payout) => payout, - Err(err) => return Ok(Err(err)), - }; - payouts_promised.insert(to, payout); - } - - let coinbase_diff_before_payouts = self - .saturating_coinbase_delta(coinbase_balance_before) - .unwrap_or_default(); - let total_payouts_promissed = payouts_promised - .values() - .map(|v| v.total_refundable_value) - .sum::(); - - Ok(Ok(ShareBundleCommitResult { - bundle_ok: insert, - coinbase_diff_before_payouts, - total_payouts_promissed, - payouts_promissed: payouts_promised, - })) - } - fn get_used_state_trace(&mut self) -> Option { self.tracer .as_mut() @@ -1277,11 +1043,6 @@ impl< self.commit_bundle(bundle, space_state, allow_tx_skip, combined_refunds)?; self.bundle_to_order_result(res, coinbase_balance_before) } - Order::ShareBundle(bundle) => { - let coinbase_balance_before = self.coinbase_balance()?; - let res = self.commit_share_bundle(bundle, space_state, allow_tx_skip)?; - self.bundle_to_order_result(res, coinbase_balance_before) - } } } @@ -1390,15 +1151,6 @@ fn update_nonce_list(nonces_updated: &mut Vec<(Address, u64)>, new_update: (Addr nonces_updated.push(new_update); } -fn update_nonce_list_with_updates( - nonces_updated: &mut Vec<(Address, u64)>, - new_updates: Vec<(Address, u64)>, -) { - for new_update in new_updates { - update_nonce_list(nonces_updated, new_update); - } -} - /// This method is used to clearly outline inputs and outputs for the EVM interpreter execution /// Mutable parameters: /// * used_state_tracer is filled if set diff --git a/crates/rbuilder/src/building/testing/bundle_tests/mod.rs b/crates/rbuilder/src/building/testing/bundle_tests/mod.rs index 9556c6b3e..d9e35cf3a 100644 --- a/crates/rbuilder/src/building/testing/bundle_tests/mod.rs +++ b/crates/rbuilder/src/building/testing/bundle_tests/mod.rs @@ -1,7 +1,6 @@ pub mod setup; -use alloy_primitives::{Address, Bytes, B256, U256}; -use itertools::Itertools; +use alloy_primitives::{Address, Bytes, U256}; use reth_primitives::Bytecode; use std::collections::{HashMap, HashSet}; @@ -12,9 +11,7 @@ use crate::{ }, utils::{constants::BASE_TX_GAS, int_percentage}, }; -use rbuilder_primitives::{ - Bundle, BundleRefund, Order, OrderId, Refund, RefundConfig, TxRevertBehavior, -}; +use rbuilder_primitives::{Bundle, BundleRefund, Order, TxRevertBehavior}; use self::setup::TestSetup; @@ -120,44 +117,6 @@ fn test_target_block() -> eyre::Result<()> { ); } - { - let mut test_setup = - TestSetup::gen_test_setup(BlockArgs::default().with_number(BUILT_BLOCK_NUMBER))?; - test_setup.begin_share_bundle_order(PREV_BUILT_BLOCK_NUMBER, NEXT_BUILT_BLOCK_NUMBER); - test_setup.add_dummy_tx_0_1_no_rev()?; - test_setup.commit_order_ok(); - - test_setup.begin_share_bundle_order(BUILT_BLOCK_NUMBER, BUILT_BLOCK_NUMBER); - test_setup.add_dummy_tx_0_1_no_rev()?; - test_setup.commit_order_ok(); - - test_setup.begin_share_bundle_order(BUILT_BLOCK_NUMBER, NEXT_BUILT_BLOCK_NUMBER); - test_setup.add_dummy_tx_0_1_no_rev()?; - test_setup.commit_order_ok(); - - test_setup.begin_share_bundle_order(PREV_PREV_BUILT_BLOCK_NUMBER, PREV_BUILT_BLOCK_NUMBER); - test_setup.add_dummy_tx_0_1_no_rev()?; - commit_order_err_matches!( - test_setup, - OrderErr::Bundle(BundleErr::TargetBlockIncorrect { - block: BUILT_BLOCK_NUMBER, - target_block: PREV_PREV_BUILT_BLOCK_NUMBER, - target_max_block: PREV_BUILT_BLOCK_NUMBER - }) - ); - - test_setup.begin_share_bundle_order(NEXT_BUILT_BLOCK_NUMBER, NEXT_NEXT_BUILT_BLOCK_NUMBER); - test_setup.add_dummy_tx_0_1_no_rev()?; - commit_order_err_matches!( - test_setup, - OrderErr::Bundle(BundleErr::TargetBlockIncorrect { - block: BUILT_BLOCK_NUMBER, - target_block: NEXT_BUILT_BLOCK_NUMBER, - target_max_block: NEXT_NEXT_BUILT_BLOCK_NUMBER - }) - ); - } - Ok(()) } @@ -199,40 +158,32 @@ fn test_bundle_timestamp() -> eyre::Result<()> { } Ok(()) } +#[test] +fn bundle_revert_tests() -> eyre::Result<()> { + let target_block = BlockArgs::MIN_BLOCK_NUMBER; + let mut test_setup = TestSetup::gen_test_setup(BlockArgs::default().with_number(target_block))?; -fn bundle_revert_tests( - test_setup: &mut TestSetup, - target_block: u64, - share_bundle: bool, -) -> eyre::Result<()> { let mut current_slot_value = 0; - let begin_bundle = |test_setup: &mut TestSetup| { - if share_bundle { - test_setup.begin_share_bundle_order(target_block, target_block); - } else { - test_setup.begin_bundle_order(target_block); - } - }; // this bundle does not revert - begin_bundle(test_setup); + test_setup.begin_bundle_order(target_block); test_setup.add_mev_test_increment_value_tx_no_rev(CURR_NONCE, current_slot_value)?; current_slot_value += 1; test_setup.commit_order_ok(); // this bundle has incorrect nonce - begin_bundle(test_setup); + test_setup.begin_bundle_order(target_block); test_setup .add_mev_test_increment_value_tx_no_rev(NonceValue::Fixed(1000), current_slot_value)?; test_setup.commit_order_err_check_text("NonceTooHigh"); // this bundle has tx that revert - begin_bundle(test_setup); + test_setup.begin_bundle_order(target_block); test_setup.add_mev_test_increment_value_tx_no_rev(CURR_NONCE, current_slot_value + 1)?; test_setup.commit_order_err_check_text("transaction reverted"); // this bundle has 2 txs one ok other reverts - begin_bundle(test_setup); + test_setup.begin_bundle_order(target_block); test_setup.add_mev_test_increment_value_tx_no_rev(CURR_NONCE, current_slot_value)?; test_setup.add_mev_test_increment_value_tx_no_rev( NonceValue::Relative(1), @@ -241,7 +192,7 @@ fn bundle_revert_tests( test_setup.commit_order_err_check_text("transaction reverted"); // this bundle has 2 txs one ok other reverts but its optional - begin_bundle(test_setup); + test_setup.begin_bundle_order(target_block); test_setup.add_mev_test_increment_value_tx_no_rev(CURR_NONCE, current_slot_value)?; current_slot_value += 1; test_setup.add_mev_test_increment_value_tx( @@ -255,7 +206,7 @@ fn bundle_revert_tests( assert!(!result.tx_infos[1].receipt.success); // this bundle has 2 txs one ok other has incorrect nonce - begin_bundle(test_setup); + test_setup.begin_bundle_order(target_block); test_setup.add_mev_test_increment_value_tx_no_rev(CURR_NONCE, current_slot_value)?; current_slot_value += 1; test_setup.add_mev_test_increment_value_tx( @@ -267,67 +218,8 @@ fn bundle_revert_tests( assert_eq!(result.tx_infos.len(), 1); assert!(result.tx_infos[0].receipt.success); - // for share bundle also try nested bundles - if share_bundle { - // this bundle with 2 ok txs - test_setup.begin_share_bundle_order(target_block, target_block); - test_setup.start_inner_bundle(false); - test_setup.add_mev_test_increment_value_tx_no_rev(CURR_NONCE, current_slot_value)?; - current_slot_value += 1; - test_setup.finish_inner_bundle(); - test_setup - .add_mev_test_increment_value_tx_no_rev(NonceValue::Relative(1), current_slot_value)?; - current_slot_value += 1; - test_setup.commit_order_ok(); - - // this bundle with 1 inner tx that fails - test_setup.begin_share_bundle_order(target_block, target_block); - test_setup.add_mev_test_increment_value_tx_no_rev(CURR_NONCE, current_slot_value)?; - test_setup.start_inner_bundle(false); - test_setup.add_mev_test_increment_value_tx_no_rev( - NonceValue::Relative(1), - current_slot_value + 100, - )?; - - test_setup.finish_inner_bundle(); - test_setup.commit_order_err_check_text("Transaction reverted"); - - // this bundle with 1 optional inner tx that fails - test_setup.begin_share_bundle_order(target_block, target_block); - test_setup.add_mev_test_increment_value_tx_no_rev(CURR_NONCE, current_slot_value)?; - test_setup.start_inner_bundle(false); - - test_setup.add_mev_test_increment_value_tx( - NonceValue::Relative(1), - TxRevertBehavior::AllowedIncluded, - current_slot_value + 100, - )?; - test_setup.finish_inner_bundle(); - test_setup.commit_order_ok(); - } - Ok(()) -} - -#[test] -fn test_bundle_revert() -> eyre::Result<()> { - let target_block = BlockArgs::MIN_BLOCK_NUMBER; - let mut test_setup = TestSetup::gen_test_setup(BlockArgs::default().with_number(target_block))?; - - bundle_revert_tests(&mut test_setup, target_block, false)?; - - Ok(()) -} - -#[test] -fn test_share_bundle_revert() -> eyre::Result<()> { - let target_block = BlockArgs::MIN_BLOCK_NUMBER; - let mut test_setup = TestSetup::gen_test_setup(BlockArgs::default().with_number(target_block))?; - - bundle_revert_tests(&mut test_setup, target_block, true)?; - Ok(()) } - /// Test combined refunds #[test] fn test_bundle_combined_refunds() -> eyre::Result<()> { @@ -544,195 +436,6 @@ fn test_bundle_ok_inner_tx_profits() -> eyre::Result<()> { Ok(()) } -#[test] -fn test_mev_share_ok_refunds() -> eyre::Result<()> { - let target_block = BlockArgs::MIN_BLOCK_NUMBER; - let mut test_setup = TestSetup::gen_test_setup(BlockArgs::default().with_number(target_block))?; - - test_setup.begin_share_bundle_order(target_block, target_block); - test_setup.add_dummy_tx_0_1_no_rev()?; - test_setup.add_send_to_coinbase_tx(NamedAddr::User(1), 100_000)?; - test_setup.set_inner_bundle_refund(vec![Refund { - body_idx: 0, - percent: 90, - }]); - let result = test_setup.commit_order_ok(); - assert_eq!( - result.paid_kickbacks, - vec![( - test_setup.named_address(NamedAddr::User(0))?, - U256::from(90_000 - 21_000) - )] - ); - - test_setup.begin_share_bundle_order(target_block, target_block); - test_setup.start_inner_bundle(false); - test_setup.add_dummy_tx_0_1_no_rev()?; - test_setup.finish_inner_bundle(); - test_setup.add_send_to_coinbase_tx(NamedAddr::User(1), 100_000)?; - test_setup.set_inner_bundle_refund(vec![Refund { - body_idx: 0, - percent: 90, - }]); - let result = test_setup.commit_order_ok(); - assert_eq!( - result.paid_kickbacks, - vec![( - test_setup.named_address(NamedAddr::User(0))?, - U256::from(90_000 - 21_000) - )] - ); - - test_setup.begin_share_bundle_order(target_block, target_block); - test_setup.start_inner_bundle(false); - test_setup.add_dummy_tx_0_1_no_rev()?; - test_setup.set_inner_bundle_refund_config(vec![RefundConfig { - address: test_setup.named_address(NamedAddr::User(3))?, - percent: 100, - }]); - test_setup.finish_inner_bundle(); - test_setup.add_send_to_coinbase_tx(NamedAddr::User(1), 100_000)?; - test_setup.set_inner_bundle_refund(vec![Refund { - body_idx: 0, - percent: 90, - }]); - let result = test_setup.commit_order_ok(); - assert_eq!( - result.paid_kickbacks, - vec![( - test_setup.named_address(NamedAddr::User(3))?, - U256::from(90_000 - 21_000) - )] - ); - - test_setup.begin_share_bundle_order(target_block, target_block); - test_setup.start_inner_bundle(false); - test_setup.add_dummy_tx_0_1_no_rev()?; - test_setup.set_inner_bundle_refund_config(vec![ - RefundConfig { - address: test_setup.named_address(NamedAddr::User(1))?, - percent: 10, - }, - RefundConfig { - address: test_setup.named_address(NamedAddr::User(2))?, - percent: 20, - }, - RefundConfig { - address: test_setup.named_address(NamedAddr::User(3))?, - percent: 70, - }, - ]); - test_setup.finish_inner_bundle(); - test_setup.add_send_to_coinbase_tx(NamedAddr::User(1), 500_000)?; - test_setup.set_inner_bundle_refund(vec![Refund { - body_idx: 0, - percent: 90, - }]); - let result = test_setup.commit_order_ok(); - let got_kickbacks: Vec<_> = result - .paid_kickbacks - .into_iter() - .sorted_by_key(|(a, _)| *a) - .collect(); - let expected_kickbacks: Vec<_> = vec![ - ( - test_setup.named_address(NamedAddr::User(1))?, - U256::from(500_000 * 90 / 100 * 10 / 100 - 21_000), - ), - ( - test_setup.named_address(NamedAddr::User(2))?, - U256::from(500_000 * 90 / 100 * 20 / 100 - 21_000), - ), - ( - test_setup.named_address(NamedAddr::User(3))?, - U256::from(500_000 * 90 / 100 * 70 / 100 - 21_000), - ), - ] - .into_iter() - .sorted_by_key(|(a, _)| *a) - .collect(); - assert_eq!(got_kickbacks, expected_kickbacks); - - // test refund config values above 100 percent - // in this example refund set to 50% but refund config to 200% - // that is equivalent to having refund set to 100% - all profit to the user - test_setup.begin_share_bundle_order(target_block, target_block); - test_setup.start_inner_bundle(false); - test_setup.add_dummy_tx_0_1_no_rev()?; - test_setup.set_inner_bundle_refund_config(vec![RefundConfig { - address: test_setup.named_address(NamedAddr::User(3))?, - percent: 200, - }]); - test_setup.finish_inner_bundle(); - test_setup.add_send_to_coinbase_tx(NamedAddr::User(1), 42_000)?; - test_setup.set_inner_bundle_refund(vec![Refund { - body_idx: 0, - percent: 50, - }]); - let result = test_setup.commit_order_ok(); - assert_eq!( - result.paid_kickbacks, - vec![( - test_setup.named_address(NamedAddr::User(3))?, - U256::from(42_000 - 21_000) - )] - ); - - Ok(()) -} - -#[test] -fn test_mev_share_failed_refunds() -> eyre::Result<()> { - let target_block = BlockArgs::MIN_BLOCK_NUMBER; - let mut test_setup = TestSetup::gen_test_setup(BlockArgs::default().with_number(target_block))?; - - test_setup.begin_share_bundle_order(target_block, target_block); - test_setup.add_dummy_tx_0_1_no_rev()?; - test_setup.add_send_to_coinbase_tx(NamedAddr::User(1), 21_000)?; - test_setup.set_inner_bundle_refund(vec![Refund { - body_idx: 0, - percent: 90, - }]); - test_setup.commit_order_err_check(|err| { - assert!(matches!( - err, - OrderErr::Bundle(BundleErr::NotEnoughRefundForGas { - to: _, - refundable_value: _, - needed_value: _, - }) - )) - }); - - // this bundle tries to go into the builder balance by having really high refund config percent - test_setup.begin_share_bundle_order(target_block, target_block); - test_setup.start_inner_bundle(false); - test_setup.add_dummy_tx_0_1_no_rev()?; - test_setup.set_inner_bundle_refund_config(vec![RefundConfig { - address: test_setup.named_address(NamedAddr::User(3))?, - percent: 201, - }]); - test_setup.finish_inner_bundle(); - test_setup.add_send_to_coinbase_tx(NamedAddr::User(1), 42_000)?; - test_setup.set_inner_bundle_refund(vec![Refund { - body_idx: 0, - percent: 50, - }]); - test_setup.commit_order_err_check(|err| assert!(matches!(err, OrderErr::NegativeProfit(_)))); - - // this bundle tries to go into the builder balance by having high refund percentage - test_setup.begin_share_bundle_order(target_block, target_block); - test_setup.add_dummy_tx_0_1_no_rev()?; - test_setup.add_send_to_coinbase_tx(NamedAddr::User(1), 42_000)?; - test_setup.set_inner_bundle_refund(vec![Refund { - body_idx: 0, - percent: 101, - }]); - test_setup.commit_order_err_check(|err| assert!(matches!(err, OrderErr::NegativeProfit(_)))); - - Ok(()) -} - #[test] fn test_bundle_consistency_check() -> eyre::Result<()> { let block_number = BlockArgs::MIN_BLOCK_NUMBER; @@ -812,61 +515,42 @@ fn test_bundle_consistency_check() -> eyre::Result<()> { #[test] ///Checks TxRevertBehavior::AllowedInclude/AllowedExcluded by checking the consumed gas. -fn test_bundle_revert_modes() -> eyre::Result<()> { - bundle_revert_modes_tests(false)?; - bundle_revert_modes_tests(true)?; - Ok(()) -} - -///Checks TxRevertBehavior::AllowedInclude/AllowedExcluded by checking the consumed gas. -fn bundle_revert_modes_tests(share_bundle: bool) -> eyre::Result<()> { +fn bundle_revert_modes_tests() -> eyre::Result<()> { let target_block = BlockArgs::MIN_BLOCK_NUMBER; // 2 users to avoid caring about nonces let tx_sender0 = NamedAddr::User(0); let tx_sender1 = NamedAddr::User(1); let mut test_setup = TestSetup::gen_test_setup(BlockArgs::default().with_number(target_block))?; - let begin_bundle = |test_setup: &mut TestSetup| { - if share_bundle { - test_setup.begin_share_bundle_order(target_block, target_block); - } else { - test_setup.begin_bundle_order(target_block); - } - }; // Single revert tx AllowedExcluded -> NO GAS - begin_bundle(&mut test_setup); + test_setup.begin_bundle_order(target_block); test_setup.add_revert(tx_sender0, TxRevertBehavior::AllowedExcluded)?; // Bundles behave different to sbundles on empty execution - if share_bundle { - let res = test_setup.commit_order_ok(); - assert_eq!(res.space_used.gas, 0); - } else { - test_setup.commit_order_err_check(|err| { - assert!(matches!(err, OrderErr::Bundle(BundleErr::EmptyBundle))); - }); - } + test_setup.commit_order_err_check(|err| { + assert!(matches!(err, OrderErr::Bundle(BundleErr::EmptyBundle))); + }); // Measure simple tx - begin_bundle(&mut test_setup); + test_setup.begin_bundle_order(target_block); test_setup.add_revert(tx_sender0, TxRevertBehavior::AllowedIncluded)?; let res = test_setup.commit_order_ok(); let reverting_gas = res.space_used.gas; // Measure reverting tx - begin_bundle(&mut test_setup); + test_setup.begin_bundle_order(target_block); test_setup.add_send_to_coinbase_tx(tx_sender0, 0)?; let res = test_setup.commit_order_ok(); let send_gas = res.space_used.gas; // send + rev on AllowedIncluded pay both gases - begin_bundle(&mut test_setup); + test_setup.begin_bundle_order(target_block); test_setup.add_send_to_coinbase_tx(tx_sender1, 0)?; test_setup.add_revert(tx_sender0, TxRevertBehavior::AllowedIncluded)?; let res = test_setup.commit_order_ok(); assert_eq!(res.space_used.gas, send_gas + reverting_gas); // send + rev on AllowedExcluded pay send - begin_bundle(&mut test_setup); + test_setup.begin_bundle_order(target_block); test_setup.add_send_to_coinbase_tx(tx_sender0, 0)?; test_setup.add_revert(tx_sender1, TxRevertBehavior::AllowedExcluded)?; let res = test_setup.commit_order_ok(); @@ -874,134 +558,3 @@ fn bundle_revert_modes_tests(share_bundle: bool) -> eyre::Result<()> { Ok(()) } - -#[test] -/// Checks that failing subbundle with can_skip does not revert the parent bundle. -fn test_subbundle_skip() -> eyre::Result<()> { - let target_block = BlockArgs::MIN_BLOCK_NUMBER; - // 2 users to avoid caring about nonces - let tx_sender0 = NamedAddr::User(0); - let tx_sender1 = NamedAddr::User(1); - let mut test_setup = TestSetup::gen_test_setup(BlockArgs::default().with_number(target_block))?; - - // First bundle not skipable , it fails -> the whole bundle fails - test_setup.begin_share_bundle_order(target_block, target_block); - test_setup.start_inner_bundle(false); - let revert_hash = test_setup.add_revert(tx_sender0, TxRevertBehavior::NotAllowed)?; - test_setup.finish_inner_bundle(); - - test_setup.start_inner_bundle(false); - test_setup.add_send_to_coinbase_tx(tx_sender1, 0)?; - test_setup.finish_inner_bundle(); - - test_setup.commit_order_err_check(|err| { - if let OrderErr::Bundle(BundleErr::TransactionReverted(hash)) = err { - assert_eq!(hash, revert_hash); - } else { - panic!("got {err} while expecting OrderErr::Bundle(BundleErr::TransactionReverted)"); - } - }); - - // First bundle skipable , it fails -> life goes on - test_setup.begin_share_bundle_order(target_block, target_block); - test_setup.start_inner_bundle(true); - test_setup.add_revert(tx_sender0, TxRevertBehavior::NotAllowed)?; - test_setup.finish_inner_bundle(); - - test_setup.start_inner_bundle(false); - test_setup.add_send_to_coinbase_tx(tx_sender1, 0)?; - test_setup.finish_inner_bundle(); - - test_setup.commit_order_ok(); - Ok(()) -} - -#[test] -/// This is a real life example. -/// Some order flow providers (handled in [`ShareBundleMerger`]) sends its user transactions (A in this example) AllowedExcluded so we can chain them [A,Br1]+[A+Br2]+[A+Br3] -/// Usually, when executing normally, the second A will fail by nonce but that's ok, we will get A,Br1,Br2,Br3 and the user will have multiple paybacks! -/// Br1/Br3 execute ok, Br2 fails -fn test_mergeable_multibackrun() -> eyre::Result<()> { - let target_block = BlockArgs::MIN_BLOCK_NUMBER; - let a_sender = NamedAddr::User(0); - let br1_sender = NamedAddr::User(1); - let br2_sender = NamedAddr::User(2); - let br3_sender = NamedAddr::User(3); - let br1_payment = 100_000; - let br3_payment = 200_000; - let kickback_percentage: usize = 90; - let expected_kickback = int_percentage(br1_payment, kickback_percentage) - + int_percentage(br3_payment, kickback_percentage) - - BASE_TX_GAS; - - // Func to add a subbundle [A,Br] - let add_backrun = |test_setup: &mut TestSetup, backrunner: NamedAddr, payment: u64| { - test_setup.start_inner_bundle(true); - test_setup - .add_null_tx(a_sender, TxRevertBehavior::AllowedExcluded) - .unwrap(); - test_setup - .add_send_to_coinbase_tx(backrunner, payment) - .unwrap(); - test_setup.set_inner_bundle_refund(vec![Refund { - body_idx: 0, - percent: kickback_percentage, - }]); - test_setup.finish_inner_bundle(); - }; - - let mut test_setup = TestSetup::gen_test_setup(BlockArgs::default().with_number(target_block))?; - test_setup.begin_share_bundle_order(target_block, target_block); - - //[A,Br1] - add_backrun(&mut test_setup, br1_sender, br1_payment); - - //[A,Br2 (reverts)] - test_setup.start_inner_bundle(true); - test_setup.add_null_tx(a_sender, TxRevertBehavior::AllowedExcluded)?; - test_setup.add_revert(br2_sender, TxRevertBehavior::NotAllowed)?; - test_setup.finish_inner_bundle(); - - //[A,Br3] - add_backrun(&mut test_setup, br3_sender, br3_payment); - - let result = test_setup.commit_order_ok(); - assert_eq!( - result.paid_kickbacks, - vec![( - test_setup.named_address(a_sender)?, - U256::from(expected_kickback) - )] - ); - Ok(()) -} - -#[test] -/// Test the proper propagation of original_order_id on execution -fn test_original_order_id() -> eyre::Result<()> { - let target_block = BlockArgs::MIN_BLOCK_NUMBER; - let mut test_setup = TestSetup::gen_test_setup(BlockArgs::default().with_number(target_block))?; - let original_order_id = OrderId::ShareBundle(B256::with_last_byte(123)); - // Simple good tx with bundle order it should report the order id - test_setup.begin_share_bundle_order(target_block, target_block); - test_setup.start_inner_bundle(true); - test_setup.set_inner_bundle_original_order_id(original_order_id); - test_setup.add_dummy_tx_0_1_no_rev()?; - test_setup.finish_inner_bundle(); - let result = test_setup.commit_order_ok(); - assert_eq!(result.original_order_ids, vec![original_order_id]); - - // Reverting tx will leave an empty execution so order id should not pass - test_setup.begin_share_bundle_order(target_block, target_block); - test_setup.start_inner_bundle(true); - test_setup.set_inner_bundle_original_order_id(original_order_id); - test_setup.add_revert( - NamedAddr::User(0), /*don't care*/ - TxRevertBehavior::AllowedExcluded, - )?; - test_setup.finish_inner_bundle(); - let result = test_setup.commit_order_ok(); - assert_eq!(result.original_order_ids, Vec::new()); - - Ok(()) -} diff --git a/crates/rbuilder/src/building/testing/bundle_tests/setup.rs b/crates/rbuilder/src/building/testing/bundle_tests/setup.rs index 40395227f..69046ac08 100644 --- a/crates/rbuilder/src/building/testing/bundle_tests/setup.rs +++ b/crates/rbuilder/src/building/testing/bundle_tests/setup.rs @@ -11,8 +11,8 @@ use crate::building::{ use alloy_primitives::{Address, TxHash}; use parking_lot::Mutex; use rbuilder_primitives::{ - order_builder::OrderBuilder, BundleRefund, BundleReplacementData, OrderId, Refund, - RefundConfig, SimulatedOrder, TransactionSignedEcRecoveredWithBlobs, TxRevertBehavior, + order_builder::OrderBuilder, BundleRefund, BundleReplacementData, SimulatedOrder, + TransactionSignedEcRecoveredWithBlobs, TxRevertBehavior, }; use reth_provider::StateProvider; use revm::database::states::BundleState; @@ -71,13 +71,7 @@ impl TestSetup { self.order_builder.start_bundle_builder(target_block); } - pub fn begin_share_bundle_order(&mut self, block: u64, max_block: u64) { - self.order_builder - .start_share_bundle_builder(block, max_block); - } - // Bundle methods - pub fn set_bundle_timestamp(&mut self, min_timestamp: Option, max_timestamp: Option) { self.order_builder .set_bundle_timestamp(min_timestamp, max_timestamp); @@ -88,34 +82,10 @@ impl TestSetup { .set_bundle_replacement_data(replacement_data); } - // Share bundle methods - - pub fn start_inner_bundle(&mut self, can_skip: bool) { - self.order_builder.start_inner_bundle(can_skip) - } - - pub fn finish_inner_bundle(&mut self) { - self.order_builder.finish_inner_bundle() - } - - pub fn set_inner_bundle_refund(&mut self, refund: Vec) { - self.order_builder.set_inner_bundle_refund(refund) - } - pub fn set_bundle_refund(&mut self, refund: BundleRefund) { self.order_builder.set_bundle_refund(refund) } - pub fn set_inner_bundle_refund_config(&mut self, refund_config: Vec) { - self.order_builder - .set_inner_bundle_refund_config(refund_config) - } - - pub fn set_inner_bundle_original_order_id(&mut self, original_order_id: OrderId) { - self.order_builder - .set_inner_bundle_original_order_id(original_order_id) - } - /// Adds a tx that does nothing /// Can only fail because of nonce or lack of ETH to paid the gas pub fn add_null_tx( diff --git a/crates/rbuilder/src/live_builder/base_config.rs b/crates/rbuilder/src/live_builder/base_config.rs index a82ebd2fd..34e57ea87 100644 --- a/crates/rbuilder/src/live_builder/base_config.rs +++ b/crates/rbuilder/src/live_builder/base_config.rs @@ -42,7 +42,7 @@ use std::{ time::Duration, }; use tokio::sync::mpsc; -use tracing::{error, warn}; +use tracing::warn; use url::Url; use super::{ @@ -112,12 +112,6 @@ pub struct BaseConfig { #[serde(deserialize_with = "deserialize_extra_data")] pub extra_data: Vec, - /// mev-share bundles coming from this address are treated in a special way(see [`ShareBundleMerger`]) - pub sbundle_mergeable_signers: Option>, - - /// Backwards compatible typo soon to be removed. - pub sbundle_mergeabe_signers: Option>, - /// Number of threads used for incoming order simulation pub simulation_threads: usize, pub simulation_use_random_coinbase: bool, @@ -265,7 +259,6 @@ impl BaseConfig { orderpool_sender, orderpool_receiver, - sbundle_merger_selected_signers: Arc::new(self.sbundle_mergeable_signers()), evm_caching_enable: self.evm_caching_enable, simulation_use_random_coinbase: self.simulation_use_random_coinbase, @@ -278,21 +271,6 @@ impl BaseConfig { chain_value_parser(&self.chain) } - pub fn sbundle_mergeable_signers(&self) -> Vec
{ - if let Some(sbundle_mergeable_signers) = &self.sbundle_mergeable_signers { - if self.sbundle_mergeabe_signers.is_some() { - error!("sbundle_mergeable_signers and sbundle_mergeabe_signers found. Will use bundle_mergeable_signers"); - } - sbundle_mergeable_signers.clone() - } else if let Some(sbundle_mergeable_signers) = &self.sbundle_mergeabe_signers { - warn!("sbundle_mergeable_signers missing but found sbundle_mergeabe_signers. sbundle_mergeabe_signers will be used but this will be deprecated soon"); - sbundle_mergeable_signers.clone() - } else { - warn!("Defaulting sbundle_mergeable_signers to empty. We may not comply with order flow rules."); - Vec::default() - } - } - /// Open reth db and DB should be opened once per process but it can be cloned and moved to different threads. /// skip_root_hash -> will create a mock roothasher. Used on backtesting since reth can't compute roothashes on the past. pub fn create_reth_provider_factory( @@ -528,8 +506,6 @@ impl Default for BaseConfig { live_builders: vec!["mgp-ordering".to_string(), "mp-ordering".to_string()], simulation_threads: 1, simulation_use_random_coinbase: true, - sbundle_mergeable_signers: None, - sbundle_mergeabe_signers: None, require_non_empty_blocklist: Some(DEFAULT_REQUIRE_NON_EMPTY_BLOCKLIST), ipc_provider: None, evm_caching_enable: false, diff --git a/crates/rbuilder/src/live_builder/building/mod.rs b/crates/rbuilder/src/live_builder/building/mod.rs index a650776d1..f78e2f27e 100644 --- a/crates/rbuilder/src/live_builder/building/mod.rs +++ b/crates/rbuilder/src/live_builder/building/mod.rs @@ -3,8 +3,7 @@ pub mod built_block_cache; use crate::{ building::{ builders::{BlockBuildingAlgorithm, BlockBuildingAlgorithmInput, BuiltBlockIdSource}, - multi_share_bundle_merger::MultiShareBundleMerger, - simulated_order_command_to_sink, BlockBuildingContext, SimulatedOrderSink, + BlockBuildingContext, }, live_builder::{ building::built_block_cache::BuiltBlockCache, @@ -14,11 +13,9 @@ use crate::{ }, provider::StateProviderFactory, }; -use alloy_primitives::Address; -use rbuilder_primitives::{OrderId, SimulatedOrder}; use reth_chainspec::EthereumHardforks as _; -use std::{cell::RefCell, rc::Rc, sync::Arc, thread, time::Duration}; -use tokio::sync::{broadcast, mpsc}; +use std::{sync::Arc, thread, time::Duration}; +use tokio::sync::broadcast; use tokio_util::sync::CancellationToken; use tracing::{debug, info, trace, warn}; @@ -31,7 +28,7 @@ use super::{ self, order_replacement_manager::OrderReplacementManager, orderpool::OrdersForBlock, }, payload_events, - simulation::{OrderSimulationPool, SimulatedOrderCommand}, + simulation::OrderSimulationPool, }; /// Struct to connect the pipeline for block building. @@ -43,7 +40,6 @@ pub struct BlockBuildingPool

{ orderpool_subscriber: order_input::OrderPoolSubscriber, order_simulation_pool: OrderSimulationPool

, run_sparse_trie_prefetcher: bool, - sbundle_merger_selected_signers: Arc>, order_flow_tracer_manager: Box, built_block_id_source: Arc, } @@ -60,7 +56,6 @@ where orderpool_subscriber: order_input::OrderPoolSubscriber, order_simulation_pool: OrderSimulationPool

, run_sparse_trie_prefetcher: bool, - sbundle_merger_selected_signers: Arc>, order_flow_tracer_manager: Box, ) -> Self { BlockBuildingPool { @@ -70,7 +65,6 @@ where orderpool_subscriber, order_simulation_pool, run_sparse_trie_prefetcher, - sbundle_merger_selected_signers, order_flow_tracer_manager, built_block_id_source: Arc::new(BuiltBlockIdSource::new()), } @@ -163,7 +157,7 @@ where &mut self, ctx: BlockBuildingContext, slot_data: MevBoostSlotData, - input: SlotOrderSimResults, + mut input: SlotOrderSimResults, cancel: CancellationToken, ) { let built_block_cache = Arc::new(BuiltBlockCache::new()); @@ -210,73 +204,19 @@ where }); } - let sbundle_merger_selected_signers = self.sbundle_merger_selected_signers.clone(); thread::spawn(move || { - merge_and_send( - input.orders, - broadcast_input, - &sbundle_merger_selected_signers, - ) + while let Some(input) = input.orders.blocking_recv() { + // we don't create new subscribers to the broadcast so here we can be sure that err means end of receivers + if broadcast_input.send(input).is_err() { + trace!("Cancelling simulated orders send job, destination stopped"); + return; + } + } + trace!("Cancelling simulated orders send job, source stopped"); }); } } -/// Implements SimulatedOrderSink and sends everything to a broadcast::Sender as SimulatedOrderCommand. -struct SimulatedOrderSinkToChannel { - sender: broadcast::Sender, - sender_returned_error: bool, -} - -impl SimulatedOrderSinkToChannel { - pub fn new(sender: broadcast::Sender) -> Self { - Self { - sender, - sender_returned_error: false, - } - } - - pub fn sender_returned_error(&self) -> bool { - self.sender_returned_error - } -} - -impl SimulatedOrderSink for SimulatedOrderSinkToChannel { - fn insert_order(&mut self, order: Arc) { - self.sender_returned_error |= self - .sender - .send(SimulatedOrderCommand::Simulation(order)) - .is_err() - } - - fn remove_order(&mut self, id: OrderId) -> Option> { - self.sender_returned_error |= self - .sender - .send(SimulatedOrderCommand::Cancellation(id)) - .is_err(); - None - } -} - -/// Merges (see [`MultiShareBundleMerger`]) simulated orders from input and forwards the result to sender. -fn merge_and_send( - mut input: mpsc::Receiver, - sender: broadcast::Sender, - sbundle_merger_selected_signers: &[Address], -) { - let sender = Rc::new(RefCell::new(SimulatedOrderSinkToChannel::new(sender))); - let mut merger = MultiShareBundleMerger::new(sbundle_merger_selected_signers, sender.clone()); - // we don't worry about waiting for input forever because it will be closed by producer job - while let Some(input) = input.blocking_recv() { - simulated_order_command_to_sink(input, &mut merger); - // we don't create new subscribers to the broadcast so here we can be sure that err means end of receivers - if sender.borrow().sender_returned_error() { - trace!("Cancelling merge_and_send job, destination stopped"); - return; - } - } - trace!("Cancelling merge_and_send job, source stopped"); -} - fn run_check_if_parent_block_is_last_block

( provider: P, block_ctx: BlockBuildingContext, diff --git a/crates/rbuilder/src/live_builder/mod.rs b/crates/rbuilder/src/live_builder/mod.rs index 48555cafc..bdcdd38f5 100644 --- a/crates/rbuilder/src/live_builder/mod.rs +++ b/crates/rbuilder/src/live_builder/mod.rs @@ -28,7 +28,7 @@ use crate::{ }, }; use alloy_consensus::Header; -use alloy_primitives::{Address, B256}; +use alloy_primitives::B256; use block_list_provider::BlockListProvider; use block_output::unfinished_block_processing::UnfinishedBuiltBlocksInputFactory; use building::BlockBuildingPool; @@ -128,7 +128,6 @@ where /// Notify rbuilder of new [`ReplaceableOrderPoolCommand`] flow via this channel. pub orderpool_sender: mpsc::Sender, pub orderpool_receiver: mpsc::Receiver, - pub sbundle_merger_selected_signers: Arc>, pub evm_caching_enable: bool, pub faster_finalize: bool, @@ -231,7 +230,6 @@ where orderpool_subscriber, order_simulation_pool, self.run_sparse_trie_prefetcher, - self.sbundle_merger_selected_signers.clone(), self.order_flow_tracer_manager, ); diff --git a/crates/rbuilder/src/live_builder/order_flow_tracing/events.rs b/crates/rbuilder/src/live_builder/order_flow_tracing/events.rs index 76acb0ef4..9659addeb 100644 --- a/crates/rbuilder/src/live_builder/order_flow_tracing/events.rs +++ b/crates/rbuilder/src/live_builder/order_flow_tracing/events.rs @@ -4,9 +4,7 @@ use alloy_primitives::{TxHash, U256}; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; -use rbuilder_primitives::{ - BundleReplacementData, OrderId, OrderReplacementKey, ShareBundleReplacementKey, -}; +use rbuilder_primitives::{BundleReplacementData, BundleReplacementKey, OrderId}; #[derive(Debug, Serialize, Deserialize)] pub struct EventWithTimestamp { @@ -27,7 +25,7 @@ impl EventWithTimestamp { #[derive(Debug, Serialize, Deserialize)] pub struct SimulatedOrderData { pub order_id: OrderId, - pub replacement_key_and_sequence_number: Option<(OrderReplacementKey, u64)>, + pub replacement_key_and_sequence_number: Option<(BundleReplacementKey, u64)>, pub simulation_time: Duration, pub full_profit: U256, pub non_mempool_profit: U256, @@ -46,7 +44,7 @@ pub type SimulationEventWithTimestamp = EventWithTimestamp; #[derive(Debug, Serialize, Deserialize)] pub struct InsertOrderData { pub order_id: OrderId, - pub replacement_key_and_sequence_number: Option<(OrderReplacementKey, u64)>, + pub replacement_key_and_sequence_number: Option<(BundleReplacementKey, u64)>, pub tx_hashes: Vec, } @@ -54,7 +52,6 @@ pub struct InsertOrderData { pub enum ReplaceableOrderEvent { InsertOrder(InsertOrderData), RemoveBundle(BundleReplacementData), - RemoveSBundle(ShareBundleReplacementKey), } pub type ReplaceableOrderEventWithTimestamp = EventWithTimestamp; diff --git a/crates/rbuilder/src/live_builder/order_flow_tracing/order_flow_tracer.rs b/crates/rbuilder/src/live_builder/order_flow_tracing/order_flow_tracer.rs index b8361aab0..77cfdc68d 100644 --- a/crates/rbuilder/src/live_builder/order_flow_tracing/order_flow_tracer.rs +++ b/crates/rbuilder/src/live_builder/order_flow_tracing/order_flow_tracer.rs @@ -14,7 +14,7 @@ use crate::{ simulation::simulation_job_tracer::SimulationJobTracer, }, }; -use rbuilder_primitives::{BundleReplacementData, Order, ShareBundleReplacementKey}; +use rbuilder_primitives::{BundleReplacementData, Order}; /// Struct that stores all the input and simulation orderflow to later dump it. #[derive(Debug)] @@ -68,11 +68,6 @@ impl OrderFlowTracer { )); self.order_input_events.lock().push(event); } - fn remove_sbundle(&self, key: &ShareBundleReplacementKey) { - let event = - ReplaceableOrderEventWithTimestamp::new(ReplaceableOrderEvent::RemoveSBundle(*key)); - self.order_input_events.lock().push(event); - } pub fn into_report(self) -> OrderFlowTracerReport { OrderFlowTracerReport { @@ -141,11 +136,6 @@ impl ReplaceableOrderSink for ReplaceableOrderSniffer { self.sink.remove_bundle(replacement_data) } - fn remove_sbundle(&mut self, key: ShareBundleReplacementKey) -> bool { - self.tracer.remove_sbundle(&key); - self.sink.remove_sbundle(key) - } - fn is_alive(&self) -> bool { self.sink.is_alive() } diff --git a/crates/rbuilder/src/live_builder/order_input/blob_type_order_filter.rs b/crates/rbuilder/src/live_builder/order_input/blob_type_order_filter.rs index a6b217d55..6c69d3d99 100644 --- a/crates/rbuilder/src/live_builder/order_input/blob_type_order_filter.rs +++ b/crates/rbuilder/src/live_builder/order_input/blob_type_order_filter.rs @@ -1,9 +1,7 @@ use alloy_eips::{eip7594::BlobTransactionSidecarVariant, Typed2718}; use crate::live_builder::order_input::replaceable_order_sink::ReplaceableOrderSink; -use rbuilder_primitives::{ - BundleReplacementData, Order, ShareBundleReplacementKey, TransactionSignedEcRecoveredWithBlobs, -}; +use rbuilder_primitives::{BundleReplacementData, Order, TransactionSignedEcRecoveredWithBlobs}; /// Filters out Orders with incorrect blobs (pre/post fusaka). /// Since it's very unlikely what we have many wrong blobs we only filter on insert_order without take note of filtered orders. @@ -75,10 +73,6 @@ impl bool + Send + Syn self.sink.remove_bundle(replacement_data) } - fn remove_sbundle(&mut self, key: ShareBundleReplacementKey) -> bool { - self.sink.remove_sbundle(key) - } - fn is_alive(&self) -> bool { self.sink.is_alive() } diff --git a/crates/rbuilder/src/live_builder/order_input/mempool_txs_detector.rs b/crates/rbuilder/src/live_builder/order_input/mempool_txs_detector.rs index e96c0931f..5f1350912 100644 --- a/crates/rbuilder/src/live_builder/order_input/mempool_txs_detector.rs +++ b/crates/rbuilder/src/live_builder/order_input/mempool_txs_detector.rs @@ -4,9 +4,7 @@ use ahash::RandomState; use alloy_primitives::TxHash; use dashmap::DashSet; -use rbuilder_primitives::{ - BundleReplacementData, Order, ShareBundleReplacementKey, TransactionSignedEcRecoveredWithBlobs, -}; +use rbuilder_primitives::{BundleReplacementData, Order, TransactionSignedEcRecoveredWithBlobs}; use super::replaceable_order_sink::ReplaceableOrderSink; @@ -37,10 +35,6 @@ impl ReplaceableOrderSink for ReplaceableOrderStreamSniffer { self.sink.remove_bundle(replacement_data) } - fn remove_sbundle(&mut self, key: ShareBundleReplacementKey) -> bool { - self.sink.remove_sbundle(key) - } - fn is_alive(&self) -> bool { self.sink.is_alive() } diff --git a/crates/rbuilder/src/live_builder/order_input/mod.rs b/crates/rbuilder/src/live_builder/order_input/mod.rs index a84e1a2a8..3d8ca05fc 100644 --- a/crates/rbuilder/src/live_builder/order_input/mod.rs +++ b/crates/rbuilder/src/live_builder/order_input/mod.rs @@ -22,7 +22,7 @@ use alloy_consensus::Header; use alloy_primitives::Address; use jsonrpsee::RpcModule; use parking_lot::Mutex; -use rbuilder_primitives::{serialize::CancelShareBundle, BundleReplacementData, Order}; +use rbuilder_primitives::{BundleReplacementData, Order}; use std::{ net::Ipv4Addr, path::{Path, PathBuf}, @@ -203,8 +203,6 @@ impl OrderInputConfig { pub enum ReplaceableOrderPoolCommand { /// New or update order Order(Order), - /// Cancellation for sbundle - CancelShareBundle(CancelShareBundle), CancelBundle(BundleReplacementData), } @@ -212,7 +210,6 @@ impl ReplaceableOrderPoolCommand { pub fn target_block(&self) -> Option { match self { ReplaceableOrderPoolCommand::Order(o) => o.target_block(), - ReplaceableOrderPoolCommand::CancelShareBundle(c) => Some(c.block), ReplaceableOrderPoolCommand::CancelBundle(_) => None, } } @@ -305,7 +302,7 @@ where } o.replacement_key().is_some() }, - ReplaceableOrderPoolCommand::CancelShareBundle(_)|ReplaceableOrderPoolCommand::CancelBundle(_) => true + ReplaceableOrderPoolCommand::CancelBundle(_) => true }; !cancellable_order }) @@ -320,7 +317,7 @@ where } o.has_blobs() }, - ReplaceableOrderPoolCommand::CancelShareBundle(_)|ReplaceableOrderPoolCommand::CancelBundle(_) => false + ReplaceableOrderPoolCommand::CancelBundle(_) => false }; !has_blobs }) diff --git a/crates/rbuilder/src/live_builder/order_input/order_replacement_manager.rs b/crates/rbuilder/src/live_builder/order_input/order_replacement_manager.rs index ee8b3408f..8907fb2a1 100644 --- a/crates/rbuilder/src/live_builder/order_input/order_replacement_manager.rs +++ b/crates/rbuilder/src/live_builder/order_input/order_replacement_manager.rs @@ -1,8 +1,6 @@ use ahash::HashMap; -use rbuilder_primitives::{ - BundleReplacementData, Order, OrderId, OrderReplacementKey, ShareBundleReplacementKey, -}; +use rbuilder_primitives::{BundleReplacementData, BundleReplacementKey, Order, OrderId}; use super::{order_sink::OrderSink, replaceable_order_sink::ReplaceableOrderSink}; @@ -16,7 +14,7 @@ use super::{order_sink::OrderSink, replaceable_order_sink::ReplaceableOrderSink} #[derive(Debug)] pub struct OrderReplacementManager { sink: Box, - replacement_states: HashMap, + replacement_states: HashMap, } impl OrderReplacementManager { @@ -28,9 +26,6 @@ impl OrderReplacementManager { } } -// SBundle has no cancellation sequence numbers, cancellations at considered final so we use u64::MAX (ugly? maybe I should make it Option?) -const SBUNDLE_SEQUENCE_NUMBER: u64 = u64::MAX; - impl ReplaceableOrderSink for OrderReplacementManager { fn insert_order(&mut self, order: Order) -> bool { if let Some((rep_key, sequence_number)) = order.replacement_key_and_sequence_number() { @@ -54,10 +49,7 @@ impl ReplaceableOrderSink for OrderReplacementManager { } fn remove_bundle(&mut self, replacement_data: BundleReplacementData) -> bool { - match self - .replacement_states - .entry(OrderReplacementKey::Bundle(replacement_data.key)) - { + match self.replacement_states.entry(replacement_data.key) { std::collections::hash_map::Entry::Occupied(mut e) => e .get_mut() .cancel_order(replacement_data.sequence_number, &mut self.sink), @@ -71,22 +63,6 @@ impl ReplaceableOrderSink for OrderReplacementManager { } } - fn remove_sbundle(&mut self, key: ShareBundleReplacementKey) -> bool { - match self - .replacement_states - .entry(OrderReplacementKey::ShareBundle(key)) - { - std::collections::hash_map::Entry::Occupied(mut e) => e - .get_mut() - .cancel_order(SBUNDLE_SEQUENCE_NUMBER, &mut self.sink), - std::collections::hash_map::Entry::Vacant(e) => { - // New cancelled element (usually out of order notification) - e.insert(BundleReplacementState::Cancelled(SBUNDLE_SEQUENCE_NUMBER)); - true - } - } - } - fn is_alive(&self) -> bool { self.sink.is_alive() } @@ -173,8 +149,7 @@ mod test { order_sink::MockOrderSink, replaceable_order_sink::ReplaceableOrderSink, }; use rbuilder_primitives::{ - AccountNonce, Bundle, BundleReplacementData, BundleReplacementKey, Order, ShareBundle, - ShareBundleReplacementData, ShareBundleReplacementKey, + AccountNonce, Bundle, BundleReplacementData, BundleReplacementKey, Order, }; use super::OrderReplacementManager; @@ -206,17 +181,6 @@ mod test { ) } - fn create_sbundle( - &mut self, - replacement_data: Option, - ) -> ShareBundle { - self.base.create_sbundle( - DONT_CARE_BLOCK, - self.dont_care_nonce.clone(), - replacement_data, - ) - } - fn create_bundle_replacement_data(&mut self) -> BundleReplacementData { BundleReplacementData { key: BundleReplacementKey::new(Uuid::new_v4(), Some(self.base.create_address())), @@ -382,61 +346,4 @@ mod test { manager.insert_order(new_bundle); manager.insert_order(old_bundle); } - - /// bundle uuids and sbundle uuids should be independent (can repeat and everything should work). - #[test] - fn test_bundle_sbundle_mix() { - let mut data_gen = TestDataGenerator::new(); - let bundle_replacement_data = data_gen.create_bundle_replacement_data(); - let sbundle_replacement_data = ShareBundleReplacementData { - key: ShareBundleReplacementKey::new( - bundle_replacement_data.key.key().id, - bundle_replacement_data.key.key().signer.unwrap(), - ), - sequence_number: bundle_replacement_data.sequence_number, - }; - let bundle = Order::Bundle(data_gen.create_bundle(Some(bundle_replacement_data.clone()))); - let sbundle = - Order::ShareBundle(data_gen.create_sbundle(Some(sbundle_replacement_data.clone()))); - - let mut order_sink = MockOrderSink::new(); - // expect bundle added - let bundle_id = bundle.id(); - order_sink - .expect_insert_order() - .times(1) - .withf(move |o| o.id() == bundle_id) - .return_const(true); - // expect sbundle added - let sbundle_id = sbundle.id(); - order_sink - .expect_insert_order() - .times(1) - .withf(move |o| o.id() == sbundle_id) - .return_const(true); - // expect bundle removed - let bundle_id = bundle.id(); - order_sink - .expect_remove_order() - .times(1) - .with(eq(bundle_id)) - .return_const(true); - // expect sbundle removed - let sbundle_id = sbundle.id(); - order_sink - .expect_remove_order() - .times(1) - .with(eq(sbundle_id)) - .return_const(true); - - let mut manager = OrderReplacementManager::new(Box::new(order_sink)); - manager.insert_order(bundle); - manager.insert_order(sbundle); - let cancel_bundle_replacement_data = BundleReplacementData { - key: bundle_replacement_data.key, - sequence_number: bundle_replacement_data.sequence_number + 1, - }; - manager.remove_bundle(cancel_bundle_replacement_data); - manager.remove_sbundle(sbundle_replacement_data.key); - } } diff --git a/crates/rbuilder/src/live_builder/order_input/orderpool.rs b/crates/rbuilder/src/live_builder/order_input/orderpool.rs index 17d540768..485b610ac 100644 --- a/crates/rbuilder/src/live_builder/order_input/orderpool.rs +++ b/crates/rbuilder/src/live_builder/order_input/orderpool.rs @@ -1,8 +1,6 @@ use ahash::HashMap; use lru::LruCache; -use rbuilder_primitives::{ - serialize::CancelShareBundle, BundleReplacementData, Order, OrderId, ShareBundleReplacementKey, -}; +use rbuilder_primitives::{BundleReplacementData, Order, OrderId}; use reth::providers::StateProviderBox; use reth_primitives_traits::InMemorySize; use std::{ @@ -45,7 +43,6 @@ impl OrdersForBlock { struct BundleBlockStore { /// Bundles and SharedBundles bundles: Vec, - cancelled_sbundles: Vec, } #[derive(Debug)] @@ -133,28 +130,11 @@ impl OrderPool { }; (order, target_block) } - Order::ShareBundle(bundle) => { - let target_block = bundle.block; - let bundles_store = self - .bundles_by_target_block - .entry(target_block) - .or_default(); - bundles_store.bundles.push(order.clone()); - (order, Some(target_block)) - } }; self.known_orders .put((order.id(), target_block.unwrap_or_default()), ()); } - fn process_remove_sbundle(&mut self, cancellation: &CancelShareBundle) { - let bundles_store = self - .bundles_by_target_block - .entry(cancellation.block) - .or_default(); - bundles_store.cancelled_sbundles.push(cancellation.key); - } - fn process_remove_bundle(&mut self, key: &BundleReplacementData) { self.bundle_cancellations .push_back((key.clone(), Instant::now())); @@ -163,7 +143,6 @@ impl OrderPool { fn process_command(&mut self, command: ReplaceableOrderPoolCommand) { match &command { ReplaceableOrderPoolCommand::Order(order) => self.process_order(order), - ReplaceableOrderPoolCommand::CancelShareBundle(c) => self.process_remove_sbundle(c), ReplaceableOrderPoolCommand::CancelBundle(key) => self.process_remove_bundle(key), } let target_block = command.target_block(); @@ -174,9 +153,6 @@ impl OrderPool { if target_block.is_none() || target_block == Some(sub.block_number) { let send_ok = match command.clone() { ReplaceableOrderPoolCommand::Order(o) => sub.sink.insert_order(o), - ReplaceableOrderPoolCommand::CancelShareBundle(cancel) => { - sub.sink.remove_sbundle(cancel.key) - } ReplaceableOrderPoolCommand::CancelBundle(replacement_data) => { sub.sink.remove_bundle(replacement_data) } @@ -206,9 +182,6 @@ impl OrderPool { for order in bundle_store.bundles.iter().cloned() { sink.insert_order(order); } - for key in bundle_store.cancelled_sbundles.iter().cloned() { - sink.remove_sbundle(key); - } } for bundle in self.bundles_for_current_block.iter().cloned() { @@ -304,10 +277,6 @@ impl OrderPool { error!("measure_tx called on a bundle"); 0 } - Order::ShareBundle(_) => { - error!("measure_tx called on an sbundle"); - 0 - } } } } diff --git a/crates/rbuilder/src/live_builder/order_input/replaceable_order_sink.rs b/crates/rbuilder/src/live_builder/order_input/replaceable_order_sink.rs index bd5e7cab4..88aa86699 100644 --- a/crates/rbuilder/src/live_builder/order_input/replaceable_order_sink.rs +++ b/crates/rbuilder/src/live_builder/order_input/replaceable_order_sink.rs @@ -1,7 +1,7 @@ use tracing::info; use core::fmt::Debug; -use rbuilder_primitives::{BundleReplacementData, Order, ShareBundleReplacementKey}; +use rbuilder_primitives::{BundleReplacementData, Order}; /// Receiver of order commands in a low level order stream (mempool + RPC calls). /// Orders are assumed to be immutable so there is no update. @@ -13,7 +13,6 @@ use rbuilder_primitives::{BundleReplacementData, Order, ShareBundleReplacementKe pub trait ReplaceableOrderSink: Debug + Send { fn insert_order(&mut self, order: Order) -> bool; fn remove_bundle(&mut self, replacement_data: BundleReplacementData) -> bool; - fn remove_sbundle(&mut self, key: ShareBundleReplacementKey) -> bool; /// @Pending remove this ugly hack to check if we can stop sending data. /// It should be replaced for a better control over object destruction fn is_alive(&self) -> bool; @@ -41,11 +40,6 @@ impl ReplaceableOrderSink for ReplaceableOrderPrinter { fn is_alive(&self) -> bool { true } - - fn remove_sbundle(&mut self, key: ShareBundleReplacementKey) -> bool { - info!(key=?key,"Cancelled SBundle"); - true - } } impl Drop for ReplaceableOrderPrinter { @@ -66,10 +60,6 @@ impl ReplaceableOrderSink for NullReplaceableOrderSink { true } - fn remove_sbundle(&mut self, _key: ShareBundleReplacementKey) -> bool { - true - } - fn is_alive(&self) -> bool { true } diff --git a/crates/rbuilder/src/live_builder/order_input/rpc_server.rs b/crates/rbuilder/src/live_builder/order_input/rpc_server.rs index b58e0aeaa..899177b8b 100644 --- a/crates/rbuilder/src/live_builder/order_input/rpc_server.rs +++ b/crates/rbuilder/src/live_builder/order_input/rpc_server.rs @@ -10,10 +10,7 @@ use jsonrpsee::{ IntoResponse, RpcModule, }; use rbuilder_primitives::{ - serialize::{ - RawBundle, RawBundleDecodeResult, RawShareBundle, RawShareBundleDecodeResult, RawTx, - TxEncoding, - }, + serialize::{RawBundle, RawBundleDecodeResult, RawTx, TxEncoding}, BundleReplacementData, BundleReplacementKey, MempoolTx, Order, OrderId, }; use serde::Deserialize; @@ -33,7 +30,6 @@ use tracing::{info, trace, warn}; use uuid::Uuid; const ETH_SEND_BUNDLE: &str = "eth_sendBundle"; -const MEV_SEND_BUNDLE: &str = "mev_sendBundle"; const ETH_CANCEL_BUNDLE: &str = "eth_cancelBundle"; const ETH_SEND_RAW_TRANSACTION: &str = "eth_sendRawTransaction"; @@ -84,11 +80,6 @@ pub async fn start_server_accepting_bundles( handle_eth_send_bundle(results_clone.clone(), timeout, params) })?; - let results_clone = results.clone(); - register_metered_async_method(&mut module, MEV_SEND_BUNDLE, move |params, _| { - handle_mev_send_bundle(results_clone.clone(), timeout, params) - })?; - let results_clone = results.clone(); register_metered_async_method(&mut module, ETH_CANCEL_BUNDLE, move |params, _| { handle_cancel_bundle(results_clone.clone(), timeout, params) @@ -225,52 +216,6 @@ async fn handle_eth_send_bundle( } } -/// Parses a mev share bundle packet and forwards it to the results. -/// Here we can generate ReplaceableOrderPoolCommand::Order(ShareBundle)) or CancelShareBundle (identified using a "cancel" field (a little ugly)). -async fn handle_mev_send_bundle( - results: mpsc::Sender, - timeout: Duration, - params: jsonrpsee::types::Params<'static>, -) { - let received_at = OffsetDateTime::now_utc(); - let start = Instant::now(); - let raw_bundle: RawShareBundle = match params.one() { - Ok(raw_bundle) => raw_bundle, - Err(err) => { - warn!(?err, "Failed to parse raw share bundle"); - inc_order_input_rpc_errors(MEV_SEND_BUNDLE); - return; - } - }; - let decode_res = match raw_bundle.decode(TxEncoding::WithBlobData) { - Ok(res) => res, - Err(err) => { - warn!(?err, "Failed to decode raw share bundle"); - inc_order_input_rpc_errors(MEV_SEND_BUNDLE); - return; - } - }; - match decode_res { - RawShareBundleDecodeResult::NewShareBundle(bundle) => { - let order = Order::ShareBundle(*bundle); - let parse_duration = start.elapsed(); - let target_block = order.target_block().unwrap_or_default(); - trace!(order = ?order.id(), parse_duration_mus = parse_duration.as_micros(), target_block, "Received share bundle"); - send_order(order, &results, timeout, received_at).await; - } - RawShareBundleDecodeResult::CancelShareBundle(cancel) => { - trace!(cancel = ?cancel, "Received share bundle cancellation"); - send_command( - ReplaceableOrderPoolCommand::CancelShareBundle(cancel), - &results, - timeout, - received_at, - ) - .await; - } - }; -} - async fn send_order( order: Order, channel: &mpsc::Sender, diff --git a/crates/rbuilder/src/live_builder/simulation/simulation_job.rs b/crates/rbuilder/src/live_builder/simulation/simulation_job.rs index b1bff3c57..2fc950272 100644 --- a/crates/rbuilder/src/live_builder/simulation/simulation_job.rs +++ b/crates/rbuilder/src/live_builder/simulation/simulation_job.rs @@ -9,7 +9,7 @@ use crate::{ }; use ahash::HashSet; use alloy_primitives::utils::format_ether; -use rbuilder_primitives::{Order, OrderId, OrderReplacementKey}; +use rbuilder_primitives::{BundleReplacementKey, Order, OrderId}; use tokio::sync::mpsc; use tokio_util::sync::CancellationToken; use tracing::{debug, error, info, trace, warn}; @@ -42,10 +42,10 @@ pub struct SimulationJob { orders_received: OrderCounter, orders_simulated_ok: OrderCounter, - unique_replacement_key_bundles: HashSet, + unique_replacement_key_bundles: HashSet, orders_with_replacement_key: usize, - unique_replacement_key_bundles_sim_ok: HashSet, + unique_replacement_key_bundles_sim_ok: HashSet, orders_with_replacement_key_sim_ok: usize, /// Orders we got via new_order_sub and are still being processed (they could be inside the SimTree or in the sim queue) @@ -309,18 +309,16 @@ impl SimulationJob { struct OrderCounter { mempool_txs: usize, bundles: usize, - share_bundles: usize, } impl fmt::Debug for OrderCounter { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "OrderCounter {{ total: {}, mempool_txs: {}, bundles {}, share_bundles {} }}", + "OrderCounter {{ total: {}, mempool_txs: {}, bundles {} }}", self.total(), self.mempool_txs, self.bundles, - self.share_bundles ) } } @@ -330,10 +328,9 @@ impl OrderCounter { match order { Order::Tx(_) => self.mempool_txs += 1, Order::Bundle(_) => self.bundles += 1, - Order::ShareBundle(_) => self.share_bundles += 1, } } fn total(&self) -> usize { - self.mempool_txs + self.bundles + self.share_bundles + self.mempool_txs + self.bundles } } diff --git a/crates/rbuilder/src/live_builder/testdata/config_with_relay_override.toml b/crates/rbuilder/src/live_builder/testdata/config_with_relay_override.toml index 435b2b0a9..2057d4237 100644 --- a/crates/rbuilder/src/live_builder/testdata/config_with_relay_override.toml +++ b/crates/rbuilder/src/live_builder/testdata/config_with_relay_override.toml @@ -25,7 +25,6 @@ ignore_cancellable_orders = true # genesis_fork_version = "0x00112233" -sbundle_mergeable_signers = [] live_builders = ["mp-ordering", "mgp-ordering", "merging"] enabled_relays = ["playground"] diff --git a/crates/rbuilder/src/telemetry/metrics/tracing_metrics.rs b/crates/rbuilder/src/telemetry/metrics/tracing_metrics.rs index 0f14888fe..ba1bdacbc 100644 --- a/crates/rbuilder/src/telemetry/metrics/tracing_metrics.rs +++ b/crates/rbuilder/src/telemetry/metrics/tracing_metrics.rs @@ -99,11 +99,9 @@ pub fn mark_command_received(command: &ReplaceableOrderPoolCommand, received_at: match order { Order::Bundle(_) => "bundle", Order::Tx(_) => "tx", - Order::ShareBundle(_) => "sbundle", } } - ReplaceableOrderPoolCommand::CancelShareBundle(_) - | ReplaceableOrderPoolCommand::CancelBundle(_) => "cancel", + ReplaceableOrderPoolCommand::CancelBundle(_) => "cancel", }; ORDERPOOL_ORDERS_RECEIVED.with_label_values(&[kind]).inc(); } diff --git a/docs/CONFIG.md b/docs/CONFIG.md index 71ff7efa0..b857c6ba0 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -31,8 +31,6 @@ Every field has a default if omitted. |blocklist_url_max_age_secs|optional int|If the downloaded file get older than this we abort. Used for debugging only|None| |require_non_empty_blocklist|bool|if true will not allow to start without a blocklist or with an empty blocklist.|false| |extra_data|string|Extra data for generated blocks|"extra_data_change_me"| -|sbundle_mergeable_signers|optional vec[string]|mev-share bundles coming from this address are treated in a special way(see [`ShareBundleMerger`])
Example:sbundle_mergeable_signers=["0x1234....","0x334344...."]|None| -|sbundle_mergeabe_signers|optional vec[string]|Alias for sbundle_mergeable_signers.Backwards compatible typo soon to be removed. |None| |simulation_threads|int| Number of threads used for incoming order simulation|1| |simulation_use_random_coinbase|bool| |true| |root_hash_use_sparse_trie|bool| Uses cached sparse trie for root hash (much faster)|false| diff --git a/examples/config/rbuilder-operator/config-live-example.toml b/examples/config/rbuilder-operator/config-live-example.toml index 308ef4d95..35c3836f6 100644 --- a/examples/config/rbuilder-operator/config-live-example.toml +++ b/examples/config/rbuilder-operator/config-live-example.toml @@ -23,7 +23,6 @@ blocks_processor_url = "http://block_processor.internal" key_registration_url = "http://127.0.0.1:8090" ignore_cancellable_orders = true -sbundle_mergeabe_signers = [] live_builders = ["mp-ordering", "mgp-ordering", "parallel"] bidding_service_ipc_path = "/tmp/rpc_bidding_server.sock" diff --git a/examples/config/rbuilder/config-backtest-example.toml b/examples/config/rbuilder/config-backtest-example.toml index 1f7262e8d..9cb531735 100644 --- a/examples/config/rbuilder/config-backtest-example.toml +++ b/examples/config/rbuilder/config-backtest-example.toml @@ -8,8 +8,6 @@ backtest_fetch_eth_rpc_parallel = 400 backtest_fetch_output_file = "~/.rbuilder/backtest/main.sqlite" backtest_fetch_mempool_data_dir = "~/.rbuilder/mempool-data" -sbundle_mergeable_signers = [] - backtest_builders = ["mp-ordering", "mgp-ordering"] [[builders]] diff --git a/examples/config/rbuilder/config-live-example.toml b/examples/config/rbuilder/config-live-example.toml index 790a6af4d..ba5f6770d 100644 --- a/examples/config/rbuilder/config-live-example.toml +++ b/examples/config/rbuilder/config-live-example.toml @@ -29,7 +29,6 @@ ignore_cancellable_orders = true # genesis_fork_version = "0x00112233" -sbundle_mergeable_signers = [] live_builders = ["mp-ordering", "mgp-ordering", "parallel"] enabled_relays = ["flashbots"] From b1088089e977258d5eb7f1fed7cf5b0ec356c576 Mon Sep 17 00:00:00 2001 From: Daniel Xifra Date: Thu, 4 Dec 2025 17:33:25 -0300 Subject: [PATCH 2/4] ReplacementKey->BundleReplacementKey --- crates/rbuilder-primitives/src/lib.rs | 22 +++++++------------ crates/rbuilder-primitives/src/serialize.rs | 18 +++++---------- .../src/test_data_generator.rs | 4 ++-- .../src/building/testing/bundle_tests/mod.rs | 3 --- 4 files changed, 16 insertions(+), 31 deletions(-) diff --git a/crates/rbuilder-primitives/src/lib.rs b/crates/rbuilder-primitives/src/lib.rs index 23aaab494..f55cc786a 100644 --- a/crates/rbuilder-primitives/src/lib.rs +++ b/crates/rbuilder-primitives/src/lib.rs @@ -382,15 +382,21 @@ impl TxRevertBehavior { } } -/// Uniquely identifies a replaceable sbundle or bundle +/// Uniquely identifies a replaceable bundle #[derive(Debug, Clone, PartialEq, Eq, Hash, Copy, Serialize, Deserialize)] -pub struct ReplacementKey { +pub struct BundleReplacementKey { pub id: Uuid, /// None means we don't have signer so the identity will be only by uuid. /// Source not giving signer risk uuid collision but if uuid is properly generated is almost impossible. pub signer: Option

, } +impl BundleReplacementKey { + pub fn new(id: Uuid, signer: Option
) -> Self { + Self { id, signer } + } +} + #[derive(Error, Debug, derive_more::From)] pub enum TxWithBlobsCreateError { #[error("Failed to decode transaction, error: {0}")] @@ -778,18 +784,6 @@ pub enum Order { Tx(MempoolTx), } -/// Uniquely identifies a replaceable bundle -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct BundleReplacementKey(ReplacementKey); -impl BundleReplacementKey { - pub fn new(id: Uuid, signer: Option
) -> Self { - Self(ReplacementKey { id, signer }) - } - pub fn key(&self) -> ReplacementKey { - self.0 - } -} - impl Order { /// 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 { diff --git a/crates/rbuilder-primitives/src/serialize.rs b/crates/rbuilder-primitives/src/serialize.rs index d1db44191..47599876f 100644 --- a/crates/rbuilder-primitives/src/serialize.rs +++ b/crates/rbuilder-primitives/src/serialize.rs @@ -390,14 +390,11 @@ impl RawBundle { /// See [TransactionSignedEcRecoveredWithBlobs::envelope_encoded_no_blobs] pub fn encode_no_blobs(value: Bundle) -> Self { - let replacement_uuid = value.replacement_data.as_ref().map(|r| r.key.key().id); + let replacement_uuid = value.replacement_data.as_ref().map(|r| r.key.id); let replacement_nonce = value.replacement_data.as_ref().map(|r| r.sequence_number); - let signing_address = value.signer.or_else(|| { - value - .replacement_data - .as_ref() - .and_then(|r| r.key.key().signer) - }); + let signing_address = value + .signer + .or_else(|| value.replacement_data.as_ref().and_then(|r| r.key.signer)); Self { txs: value .txs @@ -990,12 +987,9 @@ mod tests { .decode(TxEncoding::WithBlobData) .expect("failed to convert bundle request to RawBundleDecodeResult"); if let RawBundleDecodeResult::CancelBundle(cancel) = bundle { + assert_eq!(cancel.key.id, uuid!("3255ceb4-fdc5-592d-a501-2183727ca3df")); assert_eq!( - cancel.key.key().id, - uuid!("3255ceb4-fdc5-592d-a501-2183727ca3df") - ); - assert_eq!( - cancel.key.key().signer.unwrap(), + cancel.key.signer.unwrap(), address!("0x95222290dd7278aa3ddd389cc1e1d165cc4bafe5") ); assert_eq!(cancel.sequence_number, 49); diff --git a/crates/rbuilder-primitives/src/test_data_generator.rs b/crates/rbuilder-primitives/src/test_data_generator.rs index 41cc11887..c8b3017f7 100644 --- a/crates/rbuilder-primitives/src/test_data_generator.rs +++ b/crates/rbuilder-primitives/src/test_data_generator.rs @@ -94,7 +94,7 @@ impl TestDataGenerator { hash: B256::default(), uuid: Uuid::default(), replacement_data: replacement_data.clone(), - signer: replacement_data.as_ref().and_then(|r| r.key.key().signer), + signer: replacement_data.as_ref().and_then(|r| r.key.signer), refund_identity: None, metadata: Default::default(), dropping_tx_hashes: vec![], @@ -131,7 +131,7 @@ impl TestDataGenerator { hash: B256::default(), uuid: Uuid::default(), replacement_data: replacement_data.clone(), - signer: replacement_data.as_ref().and_then(|r| r.key.key().signer), + signer: replacement_data.as_ref().and_then(|r| r.key.signer), refund_identity: None, metadata: Default::default(), dropping_tx_hashes: Default::default(), diff --git a/crates/rbuilder/src/building/testing/bundle_tests/mod.rs b/crates/rbuilder/src/building/testing/bundle_tests/mod.rs index d9e35cf3a..3078f1c15 100644 --- a/crates/rbuilder/src/building/testing/bundle_tests/mod.rs +++ b/crates/rbuilder/src/building/testing/bundle_tests/mod.rs @@ -84,9 +84,6 @@ fn test_target_block() -> eyre::Result<()> { const BUILT_BLOCK_NUMBER: u64 = BlockArgs::MIN_BLOCK_NUMBER; const NEXT_BUILT_BLOCK_NUMBER: u64 = BUILT_BLOCK_NUMBER + 1; const PREV_BUILT_BLOCK_NUMBER: u64 = BUILT_BLOCK_NUMBER - 1; - const NEXT_NEXT_BUILT_BLOCK_NUMBER: u64 = BUILT_BLOCK_NUMBER + 2; - const PREV_PREV_BUILT_BLOCK_NUMBER: u64 = BUILT_BLOCK_NUMBER - 2; - { let mut test_setup = TestSetup::gen_test_setup(BlockArgs::default().with_number(BUILT_BLOCK_NUMBER))?; From bcdbff3e7260eaec5a519b5f2c214f78d43f037f Mon Sep 17 00:00:00 2001 From: Daniel Xifra Date: Fri, 5 Dec 2025 10:39:28 -0300 Subject: [PATCH 3/4] comments, OrderId, original_order_ids --- crates/rbuilder-primitives/src/lib.rs | 28 ++----------------- .../src/order_statistics.rs | 5 +--- .../rbuilder/src/backtest/redistribute/mod.rs | 1 - .../rbuilder/src/bin/run-bundle-on-prefix.rs | 9 ------ crates/rbuilder/src/building/builders/mod.rs | 3 +- .../src/building/builders/ordering_builder.rs | 6 ++-- .../building/builders/parallel_builder/mod.rs | 3 +- crates/rbuilder/src/building/mod.rs | 8 ++---- crates/rbuilder/src/building/order_commit.rs | 17 +++-------- .../src/building/testing/bundle_tests/mod.rs | 1 - .../live_builder/block_output/relay_submit.rs | 9 ++---- .../order_input/blob_type_order_filter.rs | 2 +- .../order_input/order_replacement_manager.rs | 2 +- docs/CONFIG.md | 6 ++-- 14 files changed, 24 insertions(+), 76 deletions(-) diff --git a/crates/rbuilder-primitives/src/lib.rs b/crates/rbuilder-primitives/src/lib.rs index f55cc786a..73b29bfc9 100644 --- a/crates/rbuilder-primitives/src/lib.rs +++ b/crates/rbuilder-primitives/src/lib.rs @@ -793,17 +793,6 @@ impl Order { } } - /// Patch to allow virtual orders not originated from a source. - /// This patch allows to easily implement sbundle merging see ([`ShareBundleMerger`]) and keep the original - /// orders for post execution work (eg: logs). - /// Non virtual orders should return self - pub fn original_orders(&self) -> Vec<&Order> { - match self { - Order::Bundle(_) => vec![self], - Order::Tx(_) => vec![self], - } - } - /// BundledTxInfo for all the child txs pub fn nonces(&self) -> Vec { match self { @@ -1038,13 +1027,12 @@ impl SimulatedOrder { pub enum OrderId { Tx(B256), Bundle(Uuid), - ShareBundle(B256), } impl OrderId { pub fn fixed_bytes(&self) -> B256 { match self { - Self::Tx(hash) | Self::ShareBundle(hash) => *hash, + Self::Tx(hash) => *hash, Self::Bundle(uuid) => { let mut out = [0u8; 32]; out[0..16].copy_from_slice(uuid.as_bytes()); @@ -1072,9 +1060,6 @@ impl FromStr for OrderId { } else if let Some(id_str) = s.strip_prefix("bundle:") { let uuid = Uuid::from_str(id_str)?; Ok(Self::Bundle(uuid)) - } else if let Some(hash_str) = s.strip_prefix("sbundle:") { - let hash = B256::from_str(hash_str)?; - Ok(Self::ShareBundle(hash)) } else { Err(eyre::eyre!("invalid order id")) } @@ -1087,7 +1072,6 @@ impl Display for OrderId { match self { Self::Tx(hash) => write!(f, "tx:{hash:?}"), Self::Bundle(uuid) => write!(f, "bundle:{uuid:?}"), - Self::ShareBundle(hash) => write!(f, "sbundle:{hash:?}"), } } } @@ -1104,7 +1088,6 @@ impl Ord for OrderId { match id { OrderId::Tx(_) => 1, OrderId::Bundle(_) => 2, - OrderId::ShareBundle(_) => 3, } } @@ -1297,14 +1280,9 @@ mod tests { fixed_bytes!("02e81e3cee67f25203db1178fb11070fcdace65c4eef80daa4037d9b49f011f5") ); + // old sbundle should fail. let id = "sbundle:0x02e81e3cee67f25203db1178fb11070fcdace65c4eef80daa4037d9b49f011f5"; - let parsed = OrderId::from_str(id).unwrap(); - assert_eq!( - parsed, - OrderId::ShareBundle(fixed_bytes!( - "02e81e3cee67f25203db1178fb11070fcdace65c4eef80daa4037d9b49f011f5" - )) - ); + assert!(OrderId::from_str(id).is_err()); let serialized = parsed.to_string(); assert_eq!(serialized, id); let fixed_bytes = parsed.fixed_bytes(); diff --git a/crates/rbuilder-primitives/src/order_statistics.rs b/crates/rbuilder-primitives/src/order_statistics.rs index 37063f0de..e39a4a1d1 100644 --- a/crates/rbuilder-primitives/src/order_statistics.rs +++ b/crates/rbuilder-primitives/src/order_statistics.rs @@ -6,7 +6,6 @@ use std::ops::{Add, Sub}; pub struct OrderStatistics { tx_count: i32, bundle_count: i32, - sbundle_count: i32, } impl OrderStatistics { @@ -29,7 +28,7 @@ impl OrderStatistics { } pub fn total(&self) -> u64 { - self.tx_count as u64 + self.bundle_count as u64 + self.sbundle_count as u64 + self.tx_count as u64 + self.bundle_count as u64 } } @@ -40,7 +39,6 @@ impl Add for OrderStatistics { Self { tx_count: self.tx_count + other.tx_count, bundle_count: self.bundle_count + other.bundle_count, - sbundle_count: self.sbundle_count + other.sbundle_count, } } } @@ -52,7 +50,6 @@ impl Sub for OrderStatistics { Self { tx_count: self.tx_count - other.tx_count, bundle_count: self.bundle_count - other.bundle_count, - sbundle_count: self.sbundle_count - other.sbundle_count, } } } diff --git a/crates/rbuilder/src/backtest/redistribute/mod.rs b/crates/rbuilder/src/backtest/redistribute/mod.rs index 82d11103c..948ae23a3 100644 --- a/crates/rbuilder/src/backtest/redistribute/mod.rs +++ b/crates/rbuilder/src/backtest/redistribute/mod.rs @@ -79,7 +79,6 @@ impl ExtendedOrderId { let hash = bundle_hashes.get(&order_id).cloned().unwrap_or_default(); ExtendedOrderId::Bundle { uuid, hash } } - OrderId::ShareBundle(hash) => ExtendedOrderId::ShareBundle(hash), } } } diff --git a/crates/rbuilder/src/bin/run-bundle-on-prefix.rs b/crates/rbuilder/src/bin/run-bundle-on-prefix.rs index 12f229364..57d783b24 100644 --- a/crates/rbuilder/src/bin/run-bundle-on-prefix.rs +++ b/crates/rbuilder/src/bin/run-bundle-on-prefix.rs @@ -291,15 +291,6 @@ fn execute_sim_orders_on_tob( res.is_ok(), profit ); - println!( - " {:?}", - sim_order - .order - .original_orders() - .iter() - .map(|o| o.id()) - .collect::>() - ); } Ok(()) } diff --git a/crates/rbuilder/src/building/builders/mod.rs b/crates/rbuilder/src/building/builders/mod.rs index 1124fc3ee..fe65d7651 100644 --- a/crates/rbuilder/src/building/builders/mod.rs +++ b/crates/rbuilder/src/building/builders/mod.rs @@ -149,7 +149,7 @@ impl OrderConsumer { &self.new_commands } - // Apply insertions and sbundle cancellations on sink + // Apply insertions and cancellations on sink pub fn apply_new_commands(&mut self, sink: &mut SinkType) { for order_command in self.new_commands.drain(..) { simulated_order_command_to_sink(order_command, sink); @@ -168,7 +168,6 @@ pub struct OrderIntakeConsumer { } impl OrderIntakeConsumer { - /// See [`ShareBundleMerger`] for sbundle_merger_selected_signers pub fn new(nonces: NonceCache, orders: broadcast::Receiver) -> Self { Self { nonces, diff --git a/crates/rbuilder/src/building/builders/ordering_builder.rs b/crates/rbuilder/src/building/builders/ordering_builder.rs index aed6566d2..ffa69eadd 100644 --- a/crates/rbuilder/src/building/builders/ordering_builder.rs +++ b/crates/rbuilder/src/building/builders/ordering_builder.rs @@ -54,9 +54,9 @@ pub fn default_pre_filtered_build_duration_deadline_ms() -> Option { #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] pub struct OrderingBuilderConfig { - /// If a tx inside a bundle or sbundle fails with TransactionErr (don't confuse this with reverting which is TransactionOk with !.receipt.success) - /// and it's configured as allowed to revert (for bundles tx in reverting_tx_hashes or dropping_tx_hashes, for sbundles: TxRevertBehavior != NotAllowed) - /// we continue the execution of the bundle/sbundle. The most typical value is true. + /// If a tx inside a bundle fails with TransactionErr (don't confuse this with reverting which is TransactionOk with !.receipt.success) + /// and it's configured as allowed to revert (for bundles tx in reverting_tx_hashes or dropping_tx_hashes) + /// we continue the execution of the bundle. The most typical value is true. pub discard_txs: bool, pub sorting: Sorting, /// Only when a tx fails because the profit was worst than expected: Number of time an order can fail during a single block building iteration. diff --git a/crates/rbuilder/src/building/builders/parallel_builder/mod.rs b/crates/rbuilder/src/building/builders/parallel_builder/mod.rs index c6d774937..ab11a91ac 100644 --- a/crates/rbuilder/src/building/builders/parallel_builder/mod.rs +++ b/crates/rbuilder/src/building/builders/parallel_builder/mod.rs @@ -51,7 +51,8 @@ pub type ConflictResolutionResultPerGroup = (GroupId, (ResolutionResult, Conflic /// ParallelBuilderConfig configures parallel builder. /// * `num_threads` - number of threads to use for merging. /// * `merge_wait_time_ms` - time to wait for merging to finish before consuming new orders. -/// * `safe_sorting_only` - Will only use sort modes that don't risk breaking the "best refund for user" since we don't megabundle the bundles (only the sbundles). +/// * `safe_sorting_only` - Will only use sort modes that don't risk breaking much the "best refund for user" +/// since random sorting might put the worst kickback first and let a blind backrun win. /// This flag is just to test the algo until we solve every issue. #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] #[serde(deny_unknown_fields)] diff --git a/crates/rbuilder/src/building/mod.rs b/crates/rbuilder/src/building/mod.rs index 88ed842df..b8b09b833 100644 --- a/crates/rbuilder/src/building/mod.rs +++ b/crates/rbuilder/src/building/mod.rs @@ -39,7 +39,7 @@ use evm::EthCachedEvmFactory; use jsonrpsee::core::Serialize; use parking_lot::Mutex; use rbuilder_primitives::{ - mev_boost::BidAdjustmentData, BlockSpace, Order, OrderId, SimValue, SimulatedOrder, + mev_boost::BidAdjustmentData, BlockSpace, Order, SimValue, SimulatedOrder, TransactionSignedEcRecoveredWithBlobs, }; use reth::{ @@ -405,7 +405,7 @@ pub enum Sorting { MevGasPrice, /// Sorts the SimulatedOrders by its absolute profit which is computed as the coinbase balance delta after executing the order MaxProfit, - /// Orders are ordered by their origin (bundle/sbundles then mempool) and then by their absolute profit. + /// Orders are ordered by their origin (bundle then mempool) and then by their absolute profit. TypeMaxProfit, /// Orders are ordered by length 3 (orders length >= 3 first) and then by their absolute profit. LengthThreeMaxProfit, @@ -570,9 +570,6 @@ pub struct ExecutionResult { pub order: Order, /// Landed txs execution info. pub tx_infos: Vec, - /// Patch to get the executed OrderIds for merged sbundles (see: [`BundleOk::original_order_ids`],[`ShareBundleMerger`] ) - /// Fully dropped orders (TxRevertBehavior::AllowedExcluded allows it!) are not included. - pub original_order_ids: Vec, pub nonces_updated: Vec<(Address, u64)>, pub paid_kickbacks: Vec<(Address, U256)>, pub delayed_kickback: Option, @@ -809,7 +806,6 @@ impl, /// The refund amount to accrue per recipient and be paid at the end of the block and tx fee value that is deducted from the profit of the first delayed refund bundle for the recipient in the block pub delayed_kickback: Option, - /// Only for sbundles we accumulate ShareBundleInner::original_order_id that executed ok. - /// Its original use is for only one level or orders with original_order_id but if nesting happens the parent order original_order_id goes before its children (pre-order DFS) - /// Fully dropped orders (TxRevertBehavior::AllowedExcluded allows it!) are not included. - pub original_order_ids: Vec, } impl BundleOk { @@ -329,8 +325,6 @@ pub struct OrderOk { pub space_used: BlockSpace, pub cumulative_space_used: BlockSpace, pub tx_infos: Vec, - /// Patch to get the executed OrderIds for merged sbundles (see: [`BundleOk::original_order_ids`],[`ShareBundleMerger`] ) - pub original_order_ids: Vec, /// nonces_updates has a set of deduplicated final nonces of the txs in the order pub nonces_updated: Vec<(Address, u64)>, pub paid_kickbacks: Vec<(Address, U256)>, @@ -469,9 +463,9 @@ pub enum CriticalCommitOrderError { } /// For all funcs allow_tx_skip means: -/// If a tx inside a bundle or sbundle fails with TransactionErr (don't confuse this with reverting which is TransactionOk with !.receipt.success) -/// and it's configured as allowed to revert (for bundles tx in reverting_tx_hashes, for sbundles: TxRevertBehavior != NotAllowed) we continue the -/// the execution of the bundle/sbundle. +/// If a tx inside a bundle fails with TransactionErr (don't confuse this with reverting which is TransactionOk with !.receipt.success) +/// and it's configured as allowed to revert (for bundles tx in reverting_tx_hashes) we continue the +/// the execution of the bundle. impl< 'a, 'b, @@ -866,7 +860,6 @@ impl< nonces_updated: Vec::new(), paid_kickbacks: Vec::new(), delayed_kickback: None, - original_order_ids: Vec::new(), }; for tx_with_blobs in &bundle.txs { let tx_hash = tx_with_blobs.hash(); @@ -1031,7 +1024,6 @@ impl< paid_kickbacks: Vec::new(), delayed_kickback: None, used_state_trace: self.get_used_state_trace(), - original_order_ids: Vec::new(), })) } Err(err) => Ok(Err(err.into())), @@ -1077,7 +1069,6 @@ impl< paid_kickbacks: ok.paid_kickbacks, delayed_kickback: ok.delayed_kickback, used_state_trace: self.get_used_state_trace(), - original_order_ids: ok.original_order_ids, })) } Err(err) => Ok(Err(err.into())), diff --git a/crates/rbuilder/src/building/testing/bundle_tests/mod.rs b/crates/rbuilder/src/building/testing/bundle_tests/mod.rs index 3078f1c15..33816e546 100644 --- a/crates/rbuilder/src/building/testing/bundle_tests/mod.rs +++ b/crates/rbuilder/src/building/testing/bundle_tests/mod.rs @@ -522,7 +522,6 @@ fn bundle_revert_modes_tests() -> eyre::Result<()> { // Single revert tx AllowedExcluded -> NO GAS test_setup.begin_bundle_order(target_block); test_setup.add_revert(tx_sender0, TxRevertBehavior::AllowedExcluded)?; - // Bundles behave different to sbundles on empty execution test_setup.commit_order_err_check(|err| { assert!(matches!(err, OrderErr::Bundle(BundleErr::EmptyBundle))); }); diff --git a/crates/rbuilder/src/live_builder/block_output/relay_submit.rs b/crates/rbuilder/src/live_builder/block_output/relay_submit.rs index b59173e7b..5c08266a7 100644 --- a/crates/rbuilder/src/live_builder/block_output/relay_submit.rs +++ b/crates/rbuilder/src/live_builder/block_output/relay_submit.rs @@ -186,12 +186,9 @@ async fn run_submit_to_relays_job( if !exec_res.order.is_tx() { bundles += 1; } - - for order in exec_res.order.original_orders() { - order_ids.push(order.id()); - if let Some(bundle_hash) = order.external_bundle_hash() { - bundle_hashes.push(bundle_hash); - } + order_ids.push(exec_res.order.id()); + if let Some(bundle_hash) = exec_res.order.external_bundle_hash() { + bundle_hashes.push(bundle_hash); } } diff --git a/crates/rbuilder/src/live_builder/order_input/blob_type_order_filter.rs b/crates/rbuilder/src/live_builder/order_input/blob_type_order_filter.rs index 6c69d3d99..64982cf5f 100644 --- a/crates/rbuilder/src/live_builder/order_input/blob_type_order_filter.rs +++ b/crates/rbuilder/src/live_builder/order_input/blob_type_order_filter.rs @@ -5,7 +5,7 @@ use rbuilder_primitives::{BundleReplacementData, Order, TransactionSignedEcRecov /// Filters out Orders with incorrect blobs (pre/post fusaka). /// Since it's very unlikely what we have many wrong blobs we only filter on insert_order without take note of filtered orders. -/// If remove_bundle/remove_sbundle is called we just forward the call to the sink so it might try to remove a filtered order. +/// If remove_bundle is called we just forward the call to the sink so it might try to remove a filtered order. pub struct BlobTypeOrderFilter { sink: Box, ///true if it likes the blob sidecar, false if it doesn't (Order gets filtered). diff --git a/crates/rbuilder/src/live_builder/order_input/order_replacement_manager.rs b/crates/rbuilder/src/live_builder/order_input/order_replacement_manager.rs index 8907fb2a1..ad71acd3f 100644 --- a/crates/rbuilder/src/live_builder/order_input/order_replacement_manager.rs +++ b/crates/rbuilder/src/live_builder/order_input/order_replacement_manager.rs @@ -4,7 +4,7 @@ use rbuilder_primitives::{BundleReplacementData, BundleReplacementKey, Order, Or use super::{order_sink::OrderSink, replaceable_order_sink::ReplaceableOrderSink}; -/// Handles all replacement and cancellation for bundles and sbundles by receiving +/// Handles all replacement and cancellation for bundles by receiving /// low level orderflow data via ReplaceableOrderSink and forwarding to an OrderSink. /// The OrderReplacementManager works for a single block. /// IMPORTANT: Due to infra problems we can get notifications our of order, we must always honor the one diff --git a/docs/CONFIG.md b/docs/CONFIG.md index b857c6ba0..427b834df 100644 --- a/docs/CONFIG.md +++ b/docs/CONFIG.md @@ -91,8 +91,8 @@ Each instantiated algorithm starts with: ### Fields for algo="ordering-builder" | Name | Type | Comments | Default | |------|------|-------------|---------| -|discard_txs|mandatory bool| If a tx inside a bundle or sbundle fails with TransactionErr (don't confuse this with reverting which is TransactionOk with !.receipt.success) and it's configured as allowed to revert (for bundles tx in reverting_tx_hashes or dropping_tx_hashes, for sbundles: TxRevertBehavior != NotAllowed) we continue the execution of the bundle/sbundle. The most typical value is true.|| -|sorting|mandatory string|Valid values:
-"mev-gas-price": Sorts the SimulatedOrders by its effective gas price. This not only includes the explicit gas price set in the tx but also the direct coinbase payments so we compute it as (coinbase balance delta after executing the order) / (gas used).
-"max-profit": Sorts the SimulatedOrders by its absolute profit which is computed as the coinbase balance delta after executing the order.
-"type-max-profit": (Experimental) Orders are ordered by their origin (bundle/sbundles then mempool) and then by their absolute profit.
-"length-three-max-profit":(Experimental) Orders are ordered by length 3 (orders length >= 3 first) and then by their absolute profit.
-"length-three-mev-gas-price":(Experimental) Orders are ordered by length 3 (orders length >= 3 first) and then by their mev gas price.|| +|discard_txs|mandatory bool| If a tx inside a bundle fails with TransactionErr (don't confuse this with reverting which is TransactionOk with !.receipt.success) and it's configured as allowed to revert (for bundles tx in reverting_tx_hashes or dropping_tx_hashes) we continue the execution of the bundle. The most typical value is true.|| +|sorting|mandatory string|Valid values:
-"mev-gas-price": Sorts the SimulatedOrders by its effective gas price. This not only includes the explicit gas price set in the tx but also the direct coinbase payments so we compute it as (coinbase balance delta after executing the order) / (gas used).
-"max-profit": Sorts the SimulatedOrders by its absolute profit which is computed as the coinbase balance delta after executing the order.
-"type-max-profit": (Experimental) Orders are ordered by their origin (bundle then mempool) and then by their absolute profit.
-"length-three-max-profit":(Experimental) Orders are ordered by length 3 (orders length >= 3 first) and then by their absolute profit.
-"length-three-mev-gas-price":(Experimental) Orders are ordered by length 3 (orders length >= 3 first) and then by their mev gas price.|| |failed_order_retries|mandatory int | Only when a tx fails because the profit was worst than expected: Number of time an order can fail during a single block building iteration.
When thi happens it gets reinserted in the PrioritizedOrderStore with the new simulated profit (the one that failed).|| |drop_failed_orders|mandatory bool| if a tx fails in a block building iteration it's dropped so next iterations will not use it.|| |build_duration_deadline_ms|optional int| Amount of time allocated for EVM execution while building block. If None it only stops when it tried all orders.| None| @@ -104,7 +104,7 @@ Each instantiated algorithm starts with: | Name | Type | Comments | Default | |------|------|-------------|---------| -|discard_txs|mandatory bool| If a tx inside a bundle or sbundle fails with TransactionErr (don't confuse this with reverting which is TransactionOk with !.receipt.success) and it's configured as allowed to revert (for bundles tx in reverting_tx_hashes or dropping_tx_hashes, for sbundles: TxRevertBehavior != NotAllowed) we continue the execution of the bundle/sbundle. The most typical value is true.|| +|discard_txs|mandatory bool| If a tx inside a bundle fails with TransactionErr (don't confuse this with reverting which is TransactionOk with !.receipt.success) and it's configured as allowed to revert (for bundles tx in reverting_tx_hashes or dropping_tx_hashes) we continue the execution of the bundle. The most typical value is true.|| |num_threads| mandatory int| Number of threads to use for merging.|| ## Bidding fields From 709a3a1003d2f2849df1d3f0ba12e565ef010f1c Mon Sep 17 00:00:00 2001 From: Daniel Xifra Date: Fri, 5 Dec 2025 14:00:08 -0300 Subject: [PATCH 4/4] fixed tests not building --- crates/rbuilder-primitives/src/lib.rs | 16 ---------------- crates/rbuilder/src/backtest/store.rs | 4 +++- crates/rbuilder/src/building/mod.rs | 2 -- .../block_output/best_block_from_algorithms.rs | 1 - 4 files changed, 3 insertions(+), 20 deletions(-) diff --git a/crates/rbuilder-primitives/src/lib.rs b/crates/rbuilder-primitives/src/lib.rs index 73b29bfc9..4a860e50e 100644 --- a/crates/rbuilder-primitives/src/lib.rs +++ b/crates/rbuilder-primitives/src/lib.rs @@ -1237,15 +1237,6 @@ mod tests { serialized, r#"{"Bundle":"5d5bf52c-ac3f-57eb-a3e9-fc01b18ca516"}"# ); - - let id = OrderId::ShareBundle(fixed_bytes!( - "02e81e3cee67f25203db1178fb11070fcdace65c4eef80daa4037d9b49f011f5" - )); - let serialized = serde_json::to_string(&id).unwrap(); - assert_eq!( - serialized, - r#"{"ShareBundle":"0x02e81e3cee67f25203db1178fb11070fcdace65c4eef80daa4037d9b49f011f5"}"# - ); } #[test] @@ -1283,12 +1274,5 @@ mod tests { // old sbundle should fail. let id = "sbundle:0x02e81e3cee67f25203db1178fb11070fcdace65c4eef80daa4037d9b49f011f5"; assert!(OrderId::from_str(id).is_err()); - let serialized = parsed.to_string(); - assert_eq!(serialized, id); - let fixed_bytes = parsed.fixed_bytes(); - assert_eq!( - fixed_bytes, - fixed_bytes!("02e81e3cee67f25203db1178fb11070fcdace65c4eef80daa4037d9b49f011f5") - ); } } diff --git a/crates/rbuilder/src/backtest/store.rs b/crates/rbuilder/src/backtest/store.rs index 466269157..eaec978f9 100644 --- a/crates/rbuilder/src/backtest/store.rs +++ b/crates/rbuilder/src/backtest/store.rs @@ -777,7 +777,9 @@ mod test { }; let onchain_block = create_test_block(); let built_block_data = BuiltBlockData { - included_orders: vec![OrderId::ShareBundle(B256::random())], + included_orders: vec![OrderId::Bundle(uuid!( + "12345678-1234-1234-1234-123456789abc" + ))], orders_closed_at: OffsetDateTime::from_unix_timestamp_nanos(1719845355111000000) .unwrap(), sealed_at: OffsetDateTime::from_unix_timestamp_nanos(1719845355123000000).unwrap(), diff --git a/crates/rbuilder/src/building/mod.rs b/crates/rbuilder/src/building/mod.rs index b8b09b833..7d3ff11b3 100644 --- a/crates/rbuilder/src/building/mod.rs +++ b/crates/rbuilder/src/building/mod.rs @@ -1361,7 +1361,6 @@ mod test { }, ], delayed_kickback: None, - original_order_ids: Default::default(), nonces_updated: Default::default(), paid_kickbacks: Default::default(), used_state_trace: Default::default(), @@ -1402,7 +1401,6 @@ mod test { coinbase_profit: profit, }], delayed_kickback: None, - original_order_ids: Default::default(), nonces_updated: Default::default(), paid_kickbacks: Default::default(), used_state_trace: Default::default(), diff --git a/crates/rbuilder/src/live_builder/block_output/best_block_from_algorithms.rs b/crates/rbuilder/src/live_builder/block_output/best_block_from_algorithms.rs index df080d35c..cebf6888a 100644 --- a/crates/rbuilder/src/live_builder/block_output/best_block_from_algorithms.rs +++ b/crates/rbuilder/src/live_builder/block_output/best_block_from_algorithms.rs @@ -88,7 +88,6 @@ mod tests { space_used: BlockSpace::default(), coinbase_profit: I256::ZERO, }], - original_order_ids: Default::default(), nonces_updated: Default::default(), paid_kickbacks: Default::default(), delayed_kickback: None,