diff --git a/src/steps/ordering/destination.rs b/src/steps/ordering/destination.rs new file mode 100644 index 0000000..ce14753 --- /dev/null +++ b/src/steps/ordering/destination.rs @@ -0,0 +1,56 @@ +use { + super::{OrderBy, OrderScore}, + crate::{ + alloy::{consensus::Transaction, primitives::Address}, + prelude::*, + }, + core::{convert::Infallible, marker::PhantomData}, +}; + +#[derive(Debug, Clone)] +pub struct DestinationAndPriorityFeeScore { + destination_address: Address, + _phantom: PhantomData

, +} + +impl Default for DestinationAndPriorityFeeScore

{ + fn default() -> Self { + Self { + destination_address: Address::ZERO, + _phantom: PhantomData, + } + } +} + +impl DestinationAndPriorityFeeScore

{ + pub fn new(destination_address: Address) -> Self { + Self { + destination_address, + _phantom: PhantomData, + } + } +} + +impl OrderScore

for DestinationAndPriorityFeeScore

{ + type Error = Infallible; + type Score = (u8, u128); + + // Since this implementation of score() returns a tuple, this creates a + // two-tier priority system which we want: Transactions going to the + // destination address are always prioritized over transactions that aren't. + // Within each tier, they're ordered by effective tip. + fn score( + &self, + checkpoint: &Checkpoint

, + ) -> Result { + let all_destination = checkpoint + .transactions() + .iter() + .all(|tx| tx.to() == Some(self.destination_address)); + let tip_sum = CheckpointExt::effective_tip_per_gas(checkpoint); + Ok((u8::from(all_destination), tip_sum)) + } +} + +pub type OrderByDestinationAndPriorityFee

= + OrderBy>; diff --git a/src/steps/ordering/mod.rs b/src/steps/ordering/mod.rs index 7603a9a..27410cc 100644 --- a/src/steps/ordering/mod.rs +++ b/src/steps/ordering/mod.rs @@ -11,10 +11,18 @@ use { }, }; +mod destination; mod profit; mod tip; -pub use {profit::OrderByCoinbaseProfit, tip::OrderByPriorityFee}; +pub use { + destination::{ + DestinationAndPriorityFeeScore, + OrderByDestinationAndPriorityFee, + }, + profit::OrderByCoinbaseProfit, + tip::OrderByPriorityFee, +}; /// A trait that implements logic for assigning a score to an order. /// Different implementations of this trait provide different ordering @@ -25,7 +33,10 @@ pub trait OrderScore: type Score: Clone + Ord + Eq + core::hash::Hash; type Error: core::error::Error + Send + Sync + 'static; - fn score(_: &Checkpoint

) -> Result; + fn score( + &self, + checkpoint: &Checkpoint

, + ) -> Result; } /// A generic implementation of a step that will order checkpoints based on a @@ -36,8 +47,29 @@ pub trait OrderScore: /// Sorting happens only for the mutable part of the payload, i.e. after /// the last barrier checkpoint. Anything prior to the last barrier /// checkpoint is considered immutable and will not be reordered. -#[derive(Debug, Clone, Default)] -pub struct OrderBy>(PhantomData<(P, S)>); +#[derive(Debug, Clone)] +pub struct OrderBy> { + scorer: S, + _phantom: PhantomData

, +} + +impl> Default for OrderBy { + fn default() -> Self { + Self { + scorer: S::default(), + _phantom: PhantomData, + } + } +} + +impl> OrderBy { + pub fn new(scorer: S) -> Self { + Self { + scorer, + _phantom: PhantomData, + } + } +} impl> Step

for OrderBy { async fn step( self: Arc, @@ -50,7 +82,8 @@ impl> Step

for OrderBy { let history = payload.history_staging(); // Find the correct order of orders in the payload. - let ordered = match SortedOrders::::try_from(&history) { + let ordered = match SortedOrders::::try_from((&history, &self.scorer)) + { Ok(ordered) => ordered.into_iter(), // when the step started running, the payload had no nonce conflicts and // all orders were able to construct valid checkpoints. After reordering, @@ -115,12 +148,14 @@ struct SortedOrders<'a, P: Platform, S: OrderScore

> { _scoring: PhantomData, } -impl<'a, P: Platform, S: OrderScore

> TryFrom<&'a Span

> +impl<'a, P: Platform, S: OrderScore

> TryFrom<(&'a Span

, &'a S)> for SortedOrders<'a, P, S> { type Error = S::Error; - fn try_from(span: &'a Span

) -> Result { + fn try_from( + (span, scorer): (&'a Span

, &'a S), + ) -> Result { let executables = span.iter().filter(|checkpoint| !checkpoint.is_barrier()); let all_orders: HashMap> = executables .map(|checkpoint| (checkpoint.hash().unwrap(), checkpoint)) @@ -141,7 +176,7 @@ impl<'a, P: Platform, S: OrderScore

> TryFrom<&'a Span

> let mut by_score = BTreeMap::<_, BTreeSet<_>>::default(); for (hash, checkpoint) in &all_orders { - let score = S::score(checkpoint)?; + let score = scorer.score(checkpoint)?; by_score.entry(score).or_default().insert(*hash); } diff --git a/src/steps/ordering/profit.rs b/src/steps/ordering/profit.rs index 1ecef75..a0bbf0f 100644 --- a/src/steps/ordering/profit.rs +++ b/src/steps/ordering/profit.rs @@ -13,7 +13,10 @@ impl OrderScore

for CoinbaseProfitScore

{ type Error = ProviderError; type Score = U256; - fn score(checkpoint: &Checkpoint

) -> Result { + fn score( + &self, + checkpoint: &Checkpoint

, + ) -> Result { let fee_recipient = checkpoint.block().coinbase(); let current_balance = checkpoint.balance_of(fee_recipient)?; diff --git a/src/steps/ordering/tip.rs b/src/steps/ordering/tip.rs index 9d89110..f32507e 100644 --- a/src/steps/ordering/tip.rs +++ b/src/steps/ordering/tip.rs @@ -11,7 +11,10 @@ impl OrderScore

for PriorityFeeScore

{ type Error = Infallible; type Score = u128; - fn score(checkpoint: &Checkpoint

) -> Result { + fn score( + &self, + checkpoint: &Checkpoint

, + ) -> Result { Ok(checkpoint.effective_tip_per_gas()) } }