1use std::sync::Arc;
23use alloy_primitives::{map::HashSet, Address};
4use reth_transaction_pool::{PoolTransaction, ValidPoolTransaction};
56/// 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.
13type Transaction;
1415/// Returns the next transaction to include in the block.
16fn next(
17&mut self,
18// In the future, `ctx` can include access to state for block building purposes.
19ctx: (),
20 ) -> Option<Self::Transaction>;
2122/// Exclude descendants of the transaction with given sender and nonce from the iterator,
23 /// because this transaction won't be included in the block.
24fn mark_invalid(&mut self, sender: Address, nonce: u64);
25}
2627/// [`PayloadTransactions`] implementation that produces nothing.
28#[derive(Debug, Clone, Copy)]
29pub struct NoopPayloadTransactions<T>(core::marker::PhantomData<T>);
3031impl<T> Defaultfor NoopPayloadTransactions<T> {
32fn default() -> Self {
33Self(Default::default())
34 }
35}
3637impl<T> PayloadTransactionsfor NoopPayloadTransactions<T> {
38type Transaction = T;
3940fn next(&mut self, _ctx: ()) -> Option<Self::Transaction> {
41None42 }
4344fn mark_invalid(&mut self, _sender: Address, _nonce: u64) {}
45}
4647/// 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
52T: PoolTransaction,
53 I: Iterator<Item = Arc<ValidPoolTransaction<T>>>,
54{
55 invalid: HashSet<Address>,
56 best: I,
57}
5859impl<T, I> BestPayloadTransactions<T, I>
60where
61T: PoolTransaction,
62 I: Iterator<Item = Arc<ValidPoolTransaction<T>>>,
63{
64/// Create a new `BestPayloadTransactions` with the given iterator.
65pub fn new(best: I) -> Self {
66Self { invalid: Default::default(), best }
67 }
68}
6970impl<T, I> PayloadTransactionsfor BestPayloadTransactions<T, I>
71where
72T: PoolTransaction,
73 I: Iterator<Item = Arc<ValidPoolTransaction<T>>>,
74{
75type Transaction = T;
7677fn next(&mut self, _ctx: ()) -> Option<Self::Transaction> {
78loop {
79let tx = self.best.next()?;
80if self.invalid.contains(&tx.sender()) {
81continue
82}
83return Some(tx.transaction.clone())
84 }
85 }
8687fn mark_invalid(&mut self, sender: Address, _nonce: u64) {
88self.invalid.insert(sender);
89 }
90}
9192#[cfg(test)]
93mod tests {
94use std::sync::Arc;
9596use crate::{
97 BestPayloadTransactions, PayloadTransactions, PayloadTransactionsChain,
98 PayloadTransactionsFixed,
99 };
100use alloy_primitives::{map::HashSet, Address};
101use reth_transaction_pool::{
102 pool::{BestTransactionsWithPrioritizedSenders, PendingPool},
103 test_utils::{MockOrdering, MockTransaction, MockTransactionFactory},
104 PoolTransaction,
105 };
106107#[test]
108fn test_best_transactions_chained_iterators() {
109let mut priority_pool = PendingPool::new(MockOrdering::default());
110let mut pool = PendingPool::new(MockOrdering::default());
111let mut f = MockTransactionFactory::default();
112113// 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
120121 // 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.
128129let address_top_of_block = Address::random();
130let address_in_priority_pool = Address::random();
131let address_a = Address::random();
132let address_b = Address::random();
133let address_regular = Address::random();
134135// Add transactions to the main pool
136{
137let 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:
140let prioritized_tx_b =
141 MockTransaction::eip1559().with_gas_price(10).with_sender(address_b);
142let 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 }
148149// Add transactions to the priority pool
150{
151let prioritized_tx =
152 MockTransaction::eip1559().with_gas_price(0).with_sender(address_in_priority_pool);
153let valid_prioritized_tx = f.validated(prioritized_tx);
154 priority_pool.add_transaction(Arc::new(valid_prioritized_tx), 0);
155 }
156157let mut block = PayloadTransactionsChain::new(
158 PayloadTransactionsFixed::single(
159 MockTransaction::eip1559().with_sender(address_top_of_block),
160 ),
161Some(100),
162 PayloadTransactionsChain::new(
163 BestPayloadTransactions::new(priority_pool.best()),
164Some(100),
165 BestPayloadTransactions::new(BestTransactionsWithPrioritizedSenders::new(
166 HashSet::from([address_a]),
167200,
168 BestTransactionsWithPrioritizedSenders::new(
169 HashSet::from([address_b]),
170200,
171 pool.best(),
172 ),
173 )),
174None,
175 ),
176None,
177 );
178179assert_eq!(block.next(()).unwrap().sender(), address_top_of_block);
180assert_eq!(block.next(()).unwrap().sender(), address_in_priority_pool);
181assert_eq!(block.next(()).unwrap().sender(), address_a);
182assert_eq!(block.next(()).unwrap().sender(), address_b);
183assert_eq!(block.next(()).unwrap().sender(), address_regular);
184 }
185}