Skip to content

Commit df23150

Browse files
authored
New sorting modes (flashbots#499)
## 📝 Summary This PR adds 3 new experimental sorting modes for block building: /// Orders are ordered by their origin (bundle/sbundles 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, /// Orders are ordered by length 3 (orders length >= 3 first) and then by their mev gas price. LengthThreeMevGasPrice, ## 💡 Motivation and Context ## ✅ I have completed the following steps: * [X] Run `make lint` * [X] Run `make test` * [ ] Added tests (if applicable)
1 parent db00b4e commit df23150

File tree

12 files changed

+339
-166
lines changed

12 files changed

+339
-166
lines changed

crates/rbuilder/src/backtest/redistribute/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1066,7 +1066,7 @@ fn order_redistribution_address(order: &Order, protect_signers: &[Address]) -> O
10661066
// 1. first address from the refund config
10671067
// 2. origin of the first tx
10681068

1069-
if let Some(first_refund) = bundle.inner_bundle.refund_config.first() {
1069+
if let Some(first_refund) = bundle.inner_bundle().refund_config.first() {
10701070
return Some(first_refund.address);
10711071
}
10721072

crates/rbuilder/src/backtest/restore_landed_orders/find_landed_orders.rs

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ impl SimplifiedOrder {
4545
}
4646
Order::ShareBundle(bundle) => SimplifiedOrder::new(
4747
id,
48-
OrderChunk::chunks_from_inner_share_bundle(&bundle.inner_bundle),
48+
OrderChunk::chunks_from_inner_share_bundle(bundle.inner_bundle()),
4949
),
5050
}
5151
}
@@ -766,11 +766,11 @@ mod tests {
766766

767767
#[test]
768768
fn test_simplified_order_conversion_share_bundle() {
769-
let bundle = Order::ShareBundle(ShareBundle {
770-
hash: hash(0xb1),
771-
block: 0,
772-
max_block: 0,
773-
inner_bundle: ShareBundleInner {
769+
let bundle = Order::ShareBundle(ShareBundle::new_with_fake_hash(
770+
hash(0xb1),
771+
0,
772+
0,
773+
ShareBundleInner {
774774
body: vec![
775775
ShareBundleBody::Tx(ShareBundleTx {
776776
tx: tx(0x01),
@@ -832,11 +832,11 @@ mod tests {
832832
can_skip: false,
833833
original_order_id: None,
834834
},
835-
signer: None,
836-
replacement_data: None,
837-
original_orders: vec![],
838-
metadata: Default::default(),
839-
});
835+
None,
836+
None,
837+
vec![],
838+
Default::default(),
839+
));
840840
let expected = SimplifiedOrder::new(
841841
OrderId::ShareBundle(hash(0xb1)),
842842
vec![

crates/rbuilder/src/building/block_orders/order_priority.rs

Lines changed: 125 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -19,110 +19,152 @@ fn new_sim_value_too_low(original_sim: U256, new_sim: U256) -> bool {
1919
new_sim * U256::from(100) < (original_sim * U256::from(MIN_SIM_RESULT_PERCENTAGE))
2020
}
2121

22-
// @TODO: Make macro!
23-
24-
///////////////////////////////
25-
/// MevGasPrice
26-
///////////////////////////////
27-
28-
#[derive(Debug, Clone)]
29-
pub struct OrderMevGasPricePriority {
30-
order: Arc<SimulatedOrder>,
22+
macro_rules! create_order_priority {
23+
($order_priority:ident($cmp:ident $( , $next_cmp:ident )*)<$new_sim_value_too_low_func:ident>) => {
24+
#[derive(Debug, Clone)]
25+
pub struct $order_priority {
26+
order: Arc<SimulatedOrder>,
27+
}
28+
29+
impl OrderPriority for $order_priority {
30+
fn new(order: Arc<SimulatedOrder>) -> Self {
31+
Self { order }
32+
}
33+
34+
fn simulation_too_low(
35+
original_sim_value: &SimValue,
36+
new_sim_value: &SimValue,
37+
) -> bool {
38+
$new_sim_value_too_low_func(
39+
original_sim_value,
40+
new_sim_value,
41+
)
42+
}
43+
}
44+
45+
impl PartialEq for $order_priority {
46+
fn eq(&self, other: &Self) -> bool {
47+
$cmp::eq(&self.order, &other.order)
48+
}
49+
}
50+
51+
impl Eq for $order_priority {}
52+
53+
impl PartialOrd for $order_priority {
54+
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
55+
Some(self.cmp(other))
56+
}
57+
}
58+
59+
impl Ord for $order_priority {
60+
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
61+
$cmp::cmp(&self.order, &other.order)
62+
$( .then_with(|| $next_cmp::cmp(&self.order, &other.order)) )*
63+
}
64+
}
65+
};
3166
}
3267

33-
impl OrderPriority for OrderMevGasPricePriority {
34-
fn new(order: Arc<SimulatedOrder>) -> Self {
35-
Self { order }
68+
/// MevGasPrice
69+
struct OrderMevGasPricePriorityCmp {}
70+
impl OrderMevGasPricePriorityCmp {
71+
#[inline]
72+
fn eq(a: &SimulatedOrder, b: &SimulatedOrder) -> bool {
73+
a.sim_value.mev_gas_price == b.sim_value.mev_gas_price
3674
}
3775

38-
fn simulation_too_low(original_sim_value: &SimValue, new_sim_value: &SimValue) -> bool {
39-
new_sim_value_too_low(
40-
original_sim_value.mev_gas_price,
41-
new_sim_value.mev_gas_price,
42-
)
76+
#[inline]
77+
fn cmp(a: &SimulatedOrder, b: &SimulatedOrder) -> Ordering {
78+
a.sim_value.mev_gas_price.cmp(&b.sim_value.mev_gas_price)
4379
}
4480
}
45-
46-
#[inline]
47-
fn mev_gas_price_eq(a: &SimulatedOrder, b: &SimulatedOrder) -> bool {
48-
a.sim_value.mev_gas_price == b.sim_value.mev_gas_price
49-
}
50-
5181
#[inline]
52-
fn mev_gas_price_cmp(a: &SimulatedOrder, b: &SimulatedOrder) -> Ordering {
53-
a.sim_value.mev_gas_price.cmp(&b.sim_value.mev_gas_price)
54-
}
55-
56-
impl PartialEq for OrderMevGasPricePriority {
57-
fn eq(&self, other: &Self) -> bool {
58-
mev_gas_price_eq(&self.order, &other.order)
59-
}
60-
}
61-
62-
impl Eq for OrderMevGasPricePriority {}
63-
64-
impl PartialOrd for OrderMevGasPricePriority {
65-
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
66-
Some(self.cmp(other))
67-
}
82+
fn simulation_too_low_gas_price(original_sim_value: &SimValue, new_sim_value: &SimValue) -> bool {
83+
new_sim_value_too_low(
84+
original_sim_value.mev_gas_price,
85+
new_sim_value.mev_gas_price,
86+
)
6887
}
6988

70-
impl Ord for OrderMevGasPricePriority {
71-
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
72-
mev_gas_price_cmp(&self.order, &other.order)
73-
}
74-
}
75-
76-
///////////////////////////////
7789
/// MaxProfit
78-
///////////////////////////////
79-
80-
#[derive(Debug, Clone)]
81-
pub struct OrderMaxProfitPriority {
82-
order: Arc<SimulatedOrder>,
83-
}
84-
85-
impl OrderPriority for OrderMaxProfitPriority {
86-
fn new(order: Arc<SimulatedOrder>) -> Self {
87-
Self { order }
90+
struct OrderMaxProfitPriorityCmp {}
91+
impl OrderMaxProfitPriorityCmp {
92+
#[inline]
93+
fn eq(a: &SimulatedOrder, b: &SimulatedOrder) -> bool {
94+
a.sim_value.coinbase_profit == b.sim_value.coinbase_profit
8895
}
8996

90-
fn simulation_too_low(original_sim_value: &SimValue, new_sim_value: &SimValue) -> bool {
91-
new_sim_value_too_low(
92-
original_sim_value.coinbase_profit,
93-
new_sim_value.coinbase_profit,
94-
)
97+
#[inline]
98+
fn cmp(a: &SimulatedOrder, b: &SimulatedOrder) -> Ordering {
99+
a.sim_value
100+
.coinbase_profit
101+
.cmp(&b.sim_value.coinbase_profit)
95102
}
96103
}
97-
98104
#[inline]
99-
fn max_profit_eq(a: &SimulatedOrder, b: &SimulatedOrder) -> bool {
100-
a.sim_value.coinbase_profit == b.sim_value.coinbase_profit
101-
}
102-
103-
#[inline]
104-
fn max_profit_cmp(a: &SimulatedOrder, b: &SimulatedOrder) -> Ordering {
105-
a.sim_value
106-
.coinbase_profit
107-
.cmp(&b.sim_value.coinbase_profit)
108-
}
105+
fn simulation_too_low_profit(original_sim_value: &SimValue, new_sim_value: &SimValue) -> bool {
106+
new_sim_value_too_low(
107+
original_sim_value.coinbase_profit,
108+
new_sim_value.coinbase_profit,
109+
)
110+
}
111+
112+
/// OrderType
113+
/// Prioritizes Bundles over Mempool
114+
struct OrderTypeCmp {}
115+
impl OrderTypeCmp {
116+
#[inline]
117+
fn eq(a: &SimulatedOrder, b: &SimulatedOrder) -> bool {
118+
a.order.is_tx() == b.order.is_tx()
119+
}
109120

110-
impl PartialEq for OrderMaxProfitPriority {
111-
fn eq(&self, other: &Self) -> bool {
112-
max_profit_eq(&self.order, &other.order)
121+
#[inline]
122+
fn cmp(a: &SimulatedOrder, b: &SimulatedOrder) -> Ordering {
123+
let a_is_tx = a.order.is_tx();
124+
let b_is_tx = b.order.is_tx();
125+
if a_is_tx == b_is_tx {
126+
Ordering::Equal
127+
} else if a_is_tx {
128+
//*a_is_tx && !b_is_tx
129+
Ordering::Less
130+
} else {
131+
//*!a_is_tx && b_is_tx
132+
Ordering::Greater
133+
}
113134
}
114135
}
115136

116-
impl Eq for OrderMaxProfitPriority {}
137+
/// Prioritizes orders with 3 or more txs
138+
struct OrderLengthThreeCmp {}
139+
impl OrderLengthThreeCmp {
140+
#[inline]
141+
fn is_long(a: &SimulatedOrder) -> bool {
142+
a.order.list_txs_len() >= 3
143+
}
117144

118-
impl PartialOrd for OrderMaxProfitPriority {
119-
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
120-
Some(self.cmp(other))
145+
#[inline]
146+
fn eq(a: &SimulatedOrder, b: &SimulatedOrder) -> bool {
147+
Self::is_long(a) == Self::is_long(b)
121148
}
122-
}
123149

124-
impl Ord for OrderMaxProfitPriority {
125-
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
126-
max_profit_cmp(&self.order, &other.order)
150+
#[inline]
151+
fn cmp(a: &SimulatedOrder, b: &SimulatedOrder) -> Ordering {
152+
let a_is_long = Self::is_long(a);
153+
let b_is_long = Self::is_long(b);
154+
if a_is_long == b_is_long {
155+
Ordering::Equal
156+
} else if a_is_long {
157+
//*a_is_long && !b_is_long
158+
Ordering::Greater
159+
} else {
160+
//*!a_is_long && b_is_long
161+
Ordering::Less
162+
}
127163
}
128164
}
165+
166+
create_order_priority!(OrderMevGasPricePriority(OrderMevGasPricePriorityCmp)<simulation_too_low_gas_price>);
167+
create_order_priority!(OrderMaxProfitPriority(OrderMaxProfitPriorityCmp)<simulation_too_low_profit>);
168+
create_order_priority!(OrderTypePriority(OrderTypeCmp,OrderMaxProfitPriorityCmp)<simulation_too_low_profit>);
169+
create_order_priority!(OrderLengthThreeMaxProfitPriority(OrderLengthThreeCmp,OrderMaxProfitPriorityCmp)<simulation_too_low_profit>);
170+
create_order_priority!(OrderLengthThreeMevGasPricePriority(OrderLengthThreeCmp,OrderMevGasPricePriorityCmp)<simulation_too_low_profit>);

crates/rbuilder/src/building/block_orders/share_bundle_merger.rs

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ impl<SinkType: SimulatedOrderSink> MultiBackrunManager<SinkType> {
119119
let mut original_orders = Vec::new();
120120
for broken_order in self.sorted_orders.values() {
121121
if let Some(sbundle) = broken_order.sbundle() {
122-
let mut inner_bundle = sbundle.inner_bundle.clone();
122+
let mut inner_bundle = sbundle.inner_bundle().clone();
123123
inner_bundle.can_skip = true;
124124
inner_bundle.original_order_id = Some(broken_order.sim_order.id());
125125
body.push(ShareBundleBody::Bundle(inner_bundle));
@@ -134,18 +134,16 @@ impl<SinkType: SimulatedOrderSink> MultiBackrunManager<SinkType> {
134134
can_skip: false,
135135
original_order_id: None,
136136
};
137-
let mut sbundle = ShareBundle {
138-
hash: Default::default(),
139-
block: highest_payback_order_bundle.block,
140-
max_block: highest_payback_order_bundle.max_block,
137+
let sbundle = ShareBundle::new(
138+
highest_payback_order_bundle.block,
139+
highest_payback_order_bundle.max_block,
141140
inner_bundle,
142-
signer: highest_payback_order_bundle.signer,
143-
replacement_data: None, //replacement_data get lost since we merge many sbundles
141+
highest_payback_order_bundle.signer,
142+
None, //replacement_data get lost since we merge many sbundles
144143
original_orders,
145144
// We take parent order submission time
146-
metadata: highest_payback_order.sim_order.order.metadata().clone(),
147-
};
148-
sbundle.hash_slow();
145+
highest_payback_order.sim_order.order.metadata().clone(),
146+
);
149147
Some(Arc::new(SimulatedOrder {
150148
order: Order::ShareBundle(sbundle),
151149
sim_value: highest_payback_order.sim_order.sim_value.clone(),
@@ -271,7 +269,7 @@ impl<SinkType: SimulatedOrderSink> ShareBundleMerger<SinkType> {
271269
} else {
272270
return None;
273271
};
274-
let first_item = sbundle.inner_bundle.body.first().or_else(|| {
272+
let first_item = sbundle.inner_bundle().body.first().or_else(|| {
275273
error!(?sbundle, "Empty sbundle");
276274
None
277275
})?;
@@ -288,7 +286,7 @@ impl<SinkType: SimulatedOrderSink> ShareBundleMerger<SinkType> {
288286
sim_order: &Arc<SimulatedOrder>,
289287
) -> Option<BrokenDownShareBundle> {
290288
let got_bundles = sbundle
291-
.inner_bundle
289+
.inner_bundle()
292290
.body
293291
.iter()
294292
.any(|item| matches!(item, ShareBundleBody::Bundle(_)));
@@ -298,7 +296,7 @@ impl<SinkType: SimulatedOrderSink> ShareBundleMerger<SinkType> {
298296
);
299297
return None;
300298
}
301-
if !sbundle.inner_bundle.refund.is_empty() {
299+
if !sbundle.inner_bundle().refund.is_empty() {
302300
warn!(hash = ?sbundle.hash,
303301
"sbundle for user txs should not contain refunds"
304302
);
@@ -336,7 +334,7 @@ impl<SinkType: SimulatedOrderSink> ShareBundleMerger<SinkType> {
336334
/// Checks also that it contains no refund stuff
337335
fn check_and_get_user_bundle_hash_from_backrun(sbundle: &ShareBundle) -> Option<B256> {
338336
let user_bundle = sbundle
339-
.inner_bundle
337+
.inner_bundle()
340338
.body
341339
.get(Self::USER_BUNDLE_INDEX)
342340
.or_else(|| {
@@ -368,15 +366,15 @@ impl<SinkType: SimulatedOrderSink> ShareBundleMerger<SinkType> {
368366

369367
/// - Have a single inner_bundle.refund (user tx) with body_idx == 0
370368
fn check_refunds_from_backrun_ok(sbundle: &ShareBundle) -> bool {
371-
if sbundle.inner_bundle.refund.len() != 1 {
369+
if sbundle.inner_bundle().refund.len() != 1 {
372370
warn!(
373371
hash = ?sbundle.hash,
374372
"sbundle should have a single refund but has {}",
375-
sbundle.inner_bundle.refund.len()
373+
sbundle.inner_bundle().refund.len()
376374
);
377375
return false;
378376
}
379-
let first_body_idx = sbundle.inner_bundle.refund[0].body_idx;
377+
let first_body_idx = sbundle.inner_bundle().refund[0].body_idx;
380378
if first_body_idx != Self::USER_BUNDLE_INDEX {
381379
warn!(
382380
hash = ?sbundle.hash,
@@ -457,11 +455,12 @@ mod test {
457455
/// more than a inner_bundle.refund should pass as is
458456
fn test_2_refunds() {
459457
let mut context = new_test_context();
460-
let mut sbundle = context.create_sbundle_tx_br();
461-
sbundle
462-
.inner_bundle
458+
let sbundle = context.create_sbundle_tx_br();
459+
let mut new_inner_bundle = sbundle.inner_bundle().clone();
460+
new_inner_bundle
463461
.refund
464-
.push(sbundle.inner_bundle.refund[0].clone());
462+
.push(sbundle.inner_bundle().refund[0].clone());
463+
let sbundle = sbundle.with_inner_bundle(new_inner_bundle);
465464
context.assert_passes_as_is(Order::ShareBundle(sbundle));
466465
}
467466

0 commit comments

Comments
 (0)