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