Skip to content

Commit ef24d9e

Browse files
authored
New building algorithm. (#214)
## πŸ“ Summary New experimental BlockBuildingAlgorithm added! ## πŸ’‘ Motivation and Context The other algorithm was quite lonely and sad. ## βœ… I have completed the following steps: * [X] Run `make lint` * [X] Run `make test` * [ ] Added tests (if applicable)
1 parent f60a4ec commit ef24d9e

File tree

10 files changed

+1249
-2
lines changed

10 files changed

+1249
-2
lines changed

β€Žconfig-backtest-example.tomlβ€Ž

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,9 @@ sorting = "max-profit"
2828
failed_order_retries = 1
2929
drop_failed_orders = true
3030

31+
[[builders]]
32+
name = "merging"
33+
algo = "merging-builder"
34+
discard_txs = true
35+
num_threads = 5
36+
merge_wait_time_ms = 100

β€Žconfig-live-example.tomlβ€Ž

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,3 +58,10 @@ discard_txs = true
5858
sorting = "max-profit"
5959
failed_order_retries = 1
6060
drop_failed_orders = true
61+
62+
[[builders]]
63+
name = "merging"
64+
algo = "merging-builder"
65+
discard_txs = true
66+
num_threads = 5
67+
merge_wait_time_ms = 100
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
use crate::{
2+
building::{
3+
builders::block_building_helper::{BlockBuildingHelper, BlockBuildingHelperFromDB},
4+
BlockBuildingContext,
5+
},
6+
roothash::RootHashConfig,
7+
};
8+
9+
use crate::utils::check_provider_factory_health;
10+
use reth::{providers::ProviderFactory, tasks::pool::BlockingTaskPool};
11+
use reth_db::database::Database;
12+
use reth_payload_builder::database::CachedReads;
13+
use std::time::Instant;
14+
use time::OffsetDateTime;
15+
use tokio_util::sync::CancellationToken;
16+
use tracing::{info_span, trace};
17+
18+
use super::{GroupOrdering, OrderGroup};
19+
20+
/// CombinatorContext is used for merging the best ordering from groups into final block.
21+
#[derive(Debug)]
22+
pub struct CombinatorContext<DB> {
23+
provider_factory: ProviderFactory<DB>,
24+
root_hash_task_pool: BlockingTaskPool,
25+
ctx: BlockBuildingContext,
26+
groups: Vec<OrderGroup>,
27+
cancellation_token: CancellationToken,
28+
cached_reads: Option<CachedReads>,
29+
discard_txs: bool,
30+
coinbase_payment: bool,
31+
root_hash_config: RootHashConfig,
32+
builder_name: String,
33+
}
34+
35+
impl<DB: Database + Clone + 'static> CombinatorContext<DB> {
36+
#[allow(clippy::too_many_arguments)]
37+
pub fn new(
38+
provider_factory: ProviderFactory<DB>,
39+
root_hash_task_pool: BlockingTaskPool,
40+
ctx: BlockBuildingContext,
41+
groups: Vec<OrderGroup>,
42+
cancellation_token: CancellationToken,
43+
cached_reads: Option<CachedReads>,
44+
discard_txs: bool,
45+
coinbase_payment: bool,
46+
root_hash_config: RootHashConfig,
47+
builder_name: String,
48+
) -> Self {
49+
CombinatorContext {
50+
provider_factory,
51+
root_hash_task_pool,
52+
ctx,
53+
groups,
54+
cancellation_token,
55+
cached_reads,
56+
discard_txs,
57+
coinbase_payment,
58+
root_hash_config,
59+
builder_name,
60+
}
61+
}
62+
63+
pub fn set_groups(&mut self, groups: Vec<OrderGroup>) {
64+
self.groups = groups;
65+
}
66+
67+
/// Checks for simulated bundles that generated kickbacks.
68+
/// orderings MUST be the same size as self.groups
69+
fn contains_kickbacks(&self, orderings: &[GroupOrdering]) -> bool {
70+
orderings.iter().enumerate().any(|(group_idx, ordering)| {
71+
ordering.orders.iter().any(|(order_idx, _)| {
72+
!self.groups[group_idx].orders[*order_idx]
73+
.sim_value
74+
.paid_kickbacks
75+
.is_empty()
76+
})
77+
})
78+
}
79+
80+
pub fn combine_best_groups_mergings(
81+
&mut self,
82+
orders_closed_at: OffsetDateTime,
83+
can_use_suggested_fee_recipient_as_coinbase: bool,
84+
) -> eyre::Result<Box<dyn BlockBuildingHelper>> {
85+
let build_attempt_id: u32 = rand::random();
86+
let span = info_span!("build_run", build_attempt_id);
87+
let _guard = span.enter();
88+
check_provider_factory_health(self.ctx.block(), &self.provider_factory)?;
89+
90+
let build_start = Instant::now();
91+
92+
let mut best_orderings: Vec<GroupOrdering> = self
93+
.groups
94+
.iter()
95+
.map(|g| g.best_ordering.lock().unwrap().clone())
96+
.collect();
97+
98+
let use_suggested_fee_recipient_as_coinbase = self.coinbase_payment
99+
&& !self.contains_kickbacks(&best_orderings)
100+
&& can_use_suggested_fee_recipient_as_coinbase;
101+
// Create a new ctx to remove builder_signer if necessary
102+
let mut ctx = self.ctx.clone();
103+
if use_suggested_fee_recipient_as_coinbase {
104+
ctx.modify_use_suggested_fee_recipient_as_coinbase();
105+
}
106+
107+
let mut block_building_helper = BlockBuildingHelperFromDB::new(
108+
self.provider_factory.clone(),
109+
self.root_hash_task_pool.clone(),
110+
self.root_hash_config.clone(),
111+
ctx,
112+
// @Perf cached reads / cursor caches
113+
None,
114+
self.builder_name.clone(),
115+
self.discard_txs,
116+
None,
117+
self.cancellation_token.clone(),
118+
)?;
119+
block_building_helper.set_trace_orders_closed_at(orders_closed_at);
120+
// loop until we insert all orders into the block
121+
loop {
122+
if self.cancellation_token.is_cancelled() {
123+
break;
124+
}
125+
126+
let sim_order = if let Some((group_idx, order_idx, _)) = best_orderings
127+
.iter()
128+
.enumerate()
129+
.filter_map(|(group_idx, ordering)| {
130+
ordering
131+
.orders
132+
.first()
133+
.map(|(order_idx, order_profit)| (group_idx, *order_idx, *order_profit))
134+
})
135+
.max_by_key(|(_, _, p)| *p)
136+
{
137+
best_orderings[group_idx].orders.remove(0);
138+
&self.groups[group_idx].orders[order_idx]
139+
} else {
140+
// no order left in the groups
141+
break;
142+
};
143+
144+
let start_time = Instant::now();
145+
let commit_result = block_building_helper.commit_order(sim_order)?;
146+
let order_commit_time = start_time.elapsed();
147+
148+
let mut gas_used = 0;
149+
let mut execution_error = None;
150+
let success = commit_result.is_ok();
151+
match commit_result {
152+
Ok(res) => {
153+
gas_used = res.gas_used;
154+
}
155+
Err(err) => execution_error = Some(err),
156+
}
157+
trace!(
158+
order_id = ?sim_order.id(),
159+
success,
160+
order_commit_time_mus = order_commit_time.as_micros(),
161+
gas_used,
162+
?execution_error,
163+
"Executed order"
164+
);
165+
}
166+
block_building_helper.set_trace_fill_time(build_start.elapsed());
167+
self.cached_reads = Some(block_building_helper.clone_cached_reads());
168+
Ok(Box::new(block_building_helper))
169+
}
170+
}

0 commit comments

Comments
Β (0)