Skip to content

Commit

Permalink
New building algorithm. (#214)
Browse files Browse the repository at this point in the history
## πŸ“ 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)
  • Loading branch information
ZanCorDX authored Oct 9, 2024
1 parent f60a4ec commit ef24d9e
Show file tree
Hide file tree
Showing 10 changed files with 1,249 additions and 2 deletions.
6 changes: 6 additions & 0 deletions config-backtest-example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,9 @@ sorting = "max-profit"
failed_order_retries = 1
drop_failed_orders = true

[[builders]]
name = "merging"
algo = "merging-builder"
discard_txs = true
num_threads = 5
merge_wait_time_ms = 100
7 changes: 7 additions & 0 deletions config-live-example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,10 @@ discard_txs = true
sorting = "max-profit"
failed_order_retries = 1
drop_failed_orders = true

[[builders]]
name = "merging"
algo = "merging-builder"
discard_txs = true
num_threads = 5
merge_wait_time_ms = 100
170 changes: 170 additions & 0 deletions crates/rbuilder/src/building/builders/merging_builder/combinator.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
use crate::{
building::{
builders::block_building_helper::{BlockBuildingHelper, BlockBuildingHelperFromDB},
BlockBuildingContext,
},
roothash::RootHashConfig,
};

use crate::utils::check_provider_factory_health;
use reth::{providers::ProviderFactory, tasks::pool::BlockingTaskPool};
use reth_db::database::Database;
use reth_payload_builder::database::CachedReads;
use std::time::Instant;
use time::OffsetDateTime;
use tokio_util::sync::CancellationToken;
use tracing::{info_span, trace};

use super::{GroupOrdering, OrderGroup};

/// CombinatorContext is used for merging the best ordering from groups into final block.
#[derive(Debug)]
pub struct CombinatorContext<DB> {
provider_factory: ProviderFactory<DB>,
root_hash_task_pool: BlockingTaskPool,
ctx: BlockBuildingContext,
groups: Vec<OrderGroup>,
cancellation_token: CancellationToken,
cached_reads: Option<CachedReads>,
discard_txs: bool,
coinbase_payment: bool,
root_hash_config: RootHashConfig,
builder_name: String,
}

impl<DB: Database + Clone + 'static> CombinatorContext<DB> {
#[allow(clippy::too_many_arguments)]
pub fn new(
provider_factory: ProviderFactory<DB>,
root_hash_task_pool: BlockingTaskPool,
ctx: BlockBuildingContext,
groups: Vec<OrderGroup>,
cancellation_token: CancellationToken,
cached_reads: Option<CachedReads>,
discard_txs: bool,
coinbase_payment: bool,
root_hash_config: RootHashConfig,
builder_name: String,
) -> Self {
CombinatorContext {
provider_factory,
root_hash_task_pool,
ctx,
groups,
cancellation_token,
cached_reads,
discard_txs,
coinbase_payment,
root_hash_config,
builder_name,
}
}

pub fn set_groups(&mut self, groups: Vec<OrderGroup>) {
self.groups = groups;
}

/// Checks for simulated bundles that generated kickbacks.
/// orderings MUST be the same size as self.groups
fn contains_kickbacks(&self, orderings: &[GroupOrdering]) -> bool {
orderings.iter().enumerate().any(|(group_idx, ordering)| {
ordering.orders.iter().any(|(order_idx, _)| {
!self.groups[group_idx].orders[*order_idx]
.sim_value
.paid_kickbacks
.is_empty()
})
})
}

pub fn combine_best_groups_mergings(
&mut self,
orders_closed_at: OffsetDateTime,
can_use_suggested_fee_recipient_as_coinbase: bool,
) -> eyre::Result<Box<dyn BlockBuildingHelper>> {
let build_attempt_id: u32 = rand::random();
let span = info_span!("build_run", build_attempt_id);
let _guard = span.enter();
check_provider_factory_health(self.ctx.block(), &self.provider_factory)?;

let build_start = Instant::now();

let mut best_orderings: Vec<GroupOrdering> = self
.groups
.iter()
.map(|g| g.best_ordering.lock().unwrap().clone())
.collect();

let use_suggested_fee_recipient_as_coinbase = self.coinbase_payment
&& !self.contains_kickbacks(&best_orderings)
&& can_use_suggested_fee_recipient_as_coinbase;
// Create a new ctx to remove builder_signer if necessary
let mut ctx = self.ctx.clone();
if use_suggested_fee_recipient_as_coinbase {
ctx.modify_use_suggested_fee_recipient_as_coinbase();
}

let mut block_building_helper = BlockBuildingHelperFromDB::new(
self.provider_factory.clone(),
self.root_hash_task_pool.clone(),
self.root_hash_config.clone(),
ctx,
// @Perf cached reads / cursor caches
None,
self.builder_name.clone(),
self.discard_txs,
None,
self.cancellation_token.clone(),
)?;
block_building_helper.set_trace_orders_closed_at(orders_closed_at);
// loop until we insert all orders into the block
loop {
if self.cancellation_token.is_cancelled() {
break;
}

let sim_order = if let Some((group_idx, order_idx, _)) = best_orderings
.iter()
.enumerate()
.filter_map(|(group_idx, ordering)| {
ordering
.orders
.first()
.map(|(order_idx, order_profit)| (group_idx, *order_idx, *order_profit))
})
.max_by_key(|(_, _, p)| *p)
{
best_orderings[group_idx].orders.remove(0);
&self.groups[group_idx].orders[order_idx]
} else {
// no order left in the groups
break;
};

let start_time = Instant::now();
let commit_result = block_building_helper.commit_order(sim_order)?;
let order_commit_time = start_time.elapsed();

let mut gas_used = 0;
let mut execution_error = None;
let success = commit_result.is_ok();
match commit_result {
Ok(res) => {
gas_used = res.gas_used;
}
Err(err) => execution_error = Some(err),
}
trace!(
order_id = ?sim_order.id(),
success,
order_commit_time_mus = order_commit_time.as_micros(),
gas_used,
?execution_error,
"Executed order"
);
}
block_building_helper.set_trace_fill_time(build_start.elapsed());
self.cached_reads = Some(block_building_helper.clone_cached_reads());
Ok(Box::new(block_building_helper))
}
}
Loading

0 comments on commit ef24d9e

Please sign in to comment.