diff --git a/src/config.rs b/src/config.rs index 1296ccc..1f93de4 100644 --- a/src/config.rs +++ b/src/config.rs @@ -163,6 +163,15 @@ pub struct BuilderConfig { )] pub block_query_cutoff_buffer: u64, + /// Number of milliseconds before the end of the slot by which bundle submission to Flashbots must complete. + /// If submission completes after this deadline, a warning is logged. + #[from_env( + var = "SUBMIT_DEADLINE_BUFFER", + desc = "Number of milliseconds before the end of the slot by which bundle submission must complete. Submissions that miss this deadline will be logged as warnings.", + default = 500 + )] + pub submit_deadline_buffer: u64, + /// The slot calculator for the builder. pub slot_calculator: SlotCalculator, diff --git a/src/tasks/submit/flashbots.rs b/src/tasks/submit/flashbots.rs index 6b3d998..ec04dca 100644 --- a/src/tasks/submit/flashbots.rs +++ b/src/tasks/submit/flashbots.rs @@ -13,8 +13,9 @@ use alloy::{ rpc::types::mev::EthSendBundle, }; use init4_bin_base::{deps::metrics::counter, utils::signer::LocalOrAws}; +use std::time::{Duration, Instant}; use tokio::{sync::mpsc, task::JoinHandle}; -use tracing::{Instrument, debug, debug_span, error, instrument}; +use tracing::{Instrument, debug, debug_span, error, info, instrument, warn}; /// Handles preparation and submission of simulated rollup blocks to the /// Flashbots relay as MEV bundles. @@ -166,6 +167,9 @@ impl FlashbotsTask { let span = sim_result.clone_span(); + // Calculate the submission deadline for this block + let deadline = self.calculate_submit_deadline(); + // Don't submit empty blocks if sim_result.block.is_empty() { counter!("signet.builder.flashbots.empty_block").increment(1); @@ -202,18 +206,35 @@ impl FlashbotsTask { let response = flashbots.send_bundle(bundle).with_auth(signer.clone()).into_future().await; - match response { - Ok(resp) => { + // Check if we met the submission deadline + let met_deadline = Instant::now() <= deadline; + + match (response, met_deadline) { + (Ok(resp), true) => { + counter!("signet.builder.flashbots.bundles_submitted").increment(1); + counter!("signet.builder.flashbots.deadline_met").increment(1); + info!( + hash = resp.as_ref().map(|r| r.bundle_hash.to_string()), + "Submitted MEV bundle to Flashbots within deadline" + ); + } + (Ok(resp), false) => { counter!("signet.builder.flashbots.bundles_submitted").increment(1); - debug!( - hash = resp.map(|r| r.bundle_hash.to_string()), - "Submitted MEV bundle to Flashbots, received OK response" + counter!("signet.builder.flashbots.deadline_missed").increment(1); + warn!( + hash = resp.as_ref().map(|r| r.bundle_hash.to_string()), + "Submitted MEV bundle to Flashbots AFTER deadline - submission may be too late" ); } - Err(err) => { + (Err(err), true) => { counter!("signet.builder.flashbots.submission_failures").increment(1); error!(%err, "MEV bundle submission failed - error returned"); } + (Err(err), false) => { + counter!("signet.builder.flashbots.submission_failures").increment(1); + counter!("signet.builder.flashbots.deadline_missed").increment(1); + error!(%err, "MEV bundle submission failed AFTER deadline - error returned"); + } } } .instrument(submit_span.clone()), @@ -221,6 +242,34 @@ impl FlashbotsTask { } } + /// Calculates the deadline for bundle submission. + /// + /// The deadline is calculated as the time remaining in the current slot, + /// minus the configured submit deadline buffer. Submissions completing + /// after this deadline will be logged as warnings. + /// + /// # Returns + /// + /// An `Instant` representing the submission deadline. + fn calculate_submit_deadline(&self) -> Instant { + let slot_calculator = &self.config.slot_calculator; + + // Get the current number of milliseconds into the slot. + let timepoint_ms = + slot_calculator.current_point_within_slot_ms().expect("host chain has started"); + + let slot_duration = slot_calculator.slot_duration() * 1000; // convert to milliseconds + let submit_buffer = self.config.submit_deadline_buffer; + + // To find the remaining slot time, subtract the timepoint from the slot duration. + // Then subtract the submit deadline buffer to give us margin before slot ends. + let remaining = slot_duration.saturating_sub(timepoint_ms).saturating_sub(submit_buffer); + + // The deadline is calculated by adding the remaining time to the current instant. + let deadline = Instant::now() + Duration::from_millis(remaining); + deadline.max(Instant::now()) + } + /// Returns a clone of the host provider for transaction operations. fn host_provider(&self) -> HostProvider { self.zenith.provider().clone() diff --git a/src/test_utils.rs b/src/test_utils.rs index cb9d3e7..51ce082 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -54,6 +54,7 @@ pub fn setup_test_config() -> &'static BuilderConfig { 0, 1, ), block_query_cutoff_buffer: 3000, + submit_deadline_buffer: 500, max_host_gas_coefficient: Some(80), constants: SignetSystemConstants::parmigiana(), }