reth_payload_util/
traits.rs

1use std::sync::Arc;
2
3use alloy_primitives::{map::HashSet, Address};
4use reth_transaction_pool::{PoolTransaction, ValidPoolTransaction};
5
6/// Iterator that returns transactions for the block building process in the order they should be
7/// included in the block.
8///
9/// Can include transactions from the pool and other sources (alternative pools,
10/// sequencer-originated transactions, etc.).
11pub trait PayloadTransactions {
12    /// The transaction type this iterator yields.
13    type Transaction;
14
15    /// Returns the next transaction to include in the block.
16    fn next(
17        &mut self,
18        // In the future, `ctx` can include access to state for block building purposes.
19        ctx: (),
20    ) -> Option<Self::Transaction>;
21
22    /// Marks the transaction identified by `sender` and `nonce` as invalid for this iterator.
23    ///
24    /// Implementations must ensure that subsequent transactions returned from this iterator do not
25    /// depend on this transaction. For example, they may choose to stop yielding any further
26    /// transactions from this sender in the current iteration.
27    fn mark_invalid(&mut self, sender: Address, nonce: u64);
28}
29
30/// [`PayloadTransactions`] implementation that produces nothing.
31#[derive(Debug, Clone, Copy)]
32pub struct NoopPayloadTransactions<T>(core::marker::PhantomData<T>);
33
34impl<T> Default for NoopPayloadTransactions<T> {
35    fn default() -> Self {
36        Self(Default::default())
37    }
38}
39
40impl<T> PayloadTransactions for NoopPayloadTransactions<T> {
41    type Transaction = T;
42
43    fn next(&mut self, _ctx: ()) -> Option<Self::Transaction> {
44        None
45    }
46
47    fn mark_invalid(&mut self, _sender: Address, _nonce: u64) {}
48}
49
50/// Wrapper struct that allows to convert `BestTransactions` (used in tx pool) to
51/// `PayloadTransactions` (used in block composition).
52///
53/// Note: `mark_invalid` for this type filters out all further transactions from the given sender
54/// in the current iteration, mirroring the semantics of `BestTransactions::mark_invalid`.
55#[derive(Debug)]
56pub struct BestPayloadTransactions<T, I>
57where
58    T: PoolTransaction,
59    I: Iterator<Item = Arc<ValidPoolTransaction<T>>>,
60{
61    invalid: HashSet<Address>,
62    best: I,
63}
64
65impl<T, I> BestPayloadTransactions<T, I>
66where
67    T: PoolTransaction,
68    I: Iterator<Item = Arc<ValidPoolTransaction<T>>>,
69{
70    /// Create a new `BestPayloadTransactions` with the given iterator.
71    pub fn new(best: I) -> Self {
72        Self { invalid: Default::default(), best }
73    }
74}
75
76impl<T, I> PayloadTransactions for BestPayloadTransactions<T, I>
77where
78    T: PoolTransaction,
79    I: Iterator<Item = Arc<ValidPoolTransaction<T>>>,
80{
81    type Transaction = T;
82
83    fn next(&mut self, _ctx: ()) -> Option<Self::Transaction> {
84        loop {
85            let tx = self.best.next()?;
86            if self.invalid.contains(tx.sender_ref()) {
87                continue
88            }
89            return Some(tx.transaction.clone())
90        }
91    }
92
93    fn mark_invalid(&mut self, sender: Address, _nonce: u64) {
94        self.invalid.insert(sender);
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use std::sync::Arc;
101
102    use crate::{
103        BestPayloadTransactions, PayloadTransactions, PayloadTransactionsChain,
104        PayloadTransactionsFixed,
105    };
106    use alloy_primitives::{map::HashSet, Address};
107    use reth_transaction_pool::{
108        pool::{BestTransactionsWithPrioritizedSenders, PendingPool},
109        test_utils::{MockOrdering, MockTransaction, MockTransactionFactory},
110        PoolTransaction,
111    };
112
113    #[test]
114    fn test_best_transactions_chained_iterators() {
115        let mut priority_pool = PendingPool::new(MockOrdering::default());
116        let mut pool = PendingPool::new(MockOrdering::default());
117        let mut f = MockTransactionFactory::default();
118
119        // Block composition
120        // ===
121        // (1) up to 100 gas: custom top-of-block transaction
122        // (2) up to 100 gas: transactions from the priority pool
123        // (3) up to 200 gas: only transactions from address A
124        // (4) up to 200 gas: only transactions from address B
125        // (5) until block gas limit: all transactions from the main pool
126
127        // Notes:
128        // - If prioritized addresses overlap, a single transaction will be prioritized twice and
129        //   therefore use the per-segment gas limit twice.
130        // - Priority pool and main pool must synchronize between each other to make sure there are
131        //   no conflicts for the same nonce. For example, in this scenario, pools can't reject
132        //   transactions with seemingly incorrect nonces, because previous transactions might be in
133        //   the other pool.
134
135        let address_top_of_block = Address::random();
136        let address_in_priority_pool = Address::random();
137        let address_a = Address::random();
138        let address_b = Address::random();
139        let address_regular = Address::random();
140
141        // Add transactions to the main pool
142        {
143            let prioritized_tx_a =
144                MockTransaction::eip1559().with_gas_price(5).with_sender(address_a);
145            // without our custom logic, B would be prioritized over A due to gas price:
146            let prioritized_tx_b =
147                MockTransaction::eip1559().with_gas_price(10).with_sender(address_b);
148            let regular_tx =
149                MockTransaction::eip1559().with_gas_price(15).with_sender(address_regular);
150            pool.add_transaction(Arc::new(f.validated(prioritized_tx_a)), 0);
151            pool.add_transaction(Arc::new(f.validated(prioritized_tx_b)), 0);
152            pool.add_transaction(Arc::new(f.validated(regular_tx)), 0);
153        }
154
155        // Add transactions to the priority pool
156        {
157            let prioritized_tx =
158                MockTransaction::eip1559().with_gas_price(0).with_sender(address_in_priority_pool);
159            let valid_prioritized_tx = f.validated(prioritized_tx);
160            priority_pool.add_transaction(Arc::new(valid_prioritized_tx), 0);
161        }
162
163        let mut block = PayloadTransactionsChain::new(
164            PayloadTransactionsFixed::single(
165                MockTransaction::eip1559().with_sender(address_top_of_block),
166            ),
167            Some(100),
168            PayloadTransactionsChain::new(
169                BestPayloadTransactions::new(priority_pool.best()),
170                Some(100),
171                BestPayloadTransactions::new(BestTransactionsWithPrioritizedSenders::new(
172                    HashSet::from([address_a]),
173                    200,
174                    BestTransactionsWithPrioritizedSenders::new(
175                        HashSet::from([address_b]),
176                        200,
177                        pool.best(),
178                    ),
179                )),
180                None,
181            ),
182            None,
183        );
184
185        assert_eq!(block.next(()).unwrap().sender(), address_top_of_block);
186        assert_eq!(block.next(()).unwrap().sender(), address_in_priority_pool);
187        assert_eq!(block.next(()).unwrap().sender(), address_a);
188        assert_eq!(block.next(()).unwrap().sender(), address_b);
189        assert_eq!(block.next(()).unwrap().sender(), address_regular);
190    }
191}