1use super::txpool::PendingFees;
2use crate::{
3 identifier::{SenderId, TransactionId},
4 pool::size::SizeTracker,
5 traits::BestTransactionsAttributes,
6 PoolTransaction, SubPoolLimit, ValidPoolTransaction,
7};
8use std::{
9 cmp::Ordering,
10 collections::{BTreeMap, BTreeSet},
11 ops::Bound::Unbounded,
12 sync::Arc,
13};
14
15#[derive(Debug, Clone)]
23pub struct BlobTransactions<T: PoolTransaction> {
24 submission_id: u64,
28 by_id: BTreeMap<TransactionId, BlobTransaction<T>>,
30 all: BTreeSet<BlobTransaction<T>>,
32 pending_fees: PendingFees,
34 size_of: SizeTracker,
38}
39
40impl<T: PoolTransaction> BlobTransactions<T> {
43 pub fn add_transaction(&mut self, tx: Arc<ValidPoolTransaction<T>>) {
50 assert!(tx.is_eip4844(), "transaction is not a blob tx");
51 let id = *tx.id();
52 assert!(!self.contains(&id), "transaction already included {:?}", self.get(&id).unwrap());
53 let submission_id = self.next_id();
54
55 self.size_of += tx.size();
57
58 let transaction = BlobTransaction::new(tx, submission_id, &self.pending_fees);
60
61 self.by_id.insert(id, transaction.clone());
62 self.all.insert(transaction);
63 }
64
65 const fn next_id(&mut self) -> u64 {
66 let id = self.submission_id;
67 self.submission_id = self.submission_id.wrapping_add(1);
68 id
69 }
70
71 pub(crate) fn all(&self) -> impl ExactSizeIterator<Item = Arc<ValidPoolTransaction<T>>> + '_ {
73 self.by_id.values().map(|tx| tx.transaction.clone())
74 }
75
76 pub(crate) fn txs_by_sender(
79 &self,
80 sender: SenderId,
81 ) -> impl Iterator<Item = Arc<ValidPoolTransaction<T>>> + '_ {
82 self.by_id
83 .range((sender.start_bound(), Unbounded))
84 .take_while(move |(other, _)| sender == other.sender)
85 .map(|(_, tx)| Arc::clone(&tx.transaction))
86 }
87
88 pub(crate) fn remove_transaction(
90 &mut self,
91 id: &TransactionId,
92 ) -> Option<Arc<ValidPoolTransaction<T>>> {
93 let tx = self.by_id.remove(id)?;
95
96 self.all.remove(&tx);
97
98 self.size_of -= tx.transaction.size();
100
101 Some(tx.transaction)
102 }
103
104 pub(crate) fn satisfy_attributes(
108 &self,
109 best_transactions_attributes: BestTransactionsAttributes,
110 ) -> Vec<Arc<ValidPoolTransaction<T>>> {
111 let mut transactions = Vec::new();
112 {
113 if let Some(blob_fee_to_satisfy) =
115 best_transactions_attributes.blob_fee.map(|fee| fee as u128)
116 {
117 let mut iter = self.by_id.iter().peekable();
118
119 while let Some((id, tx)) = iter.next() {
120 if tx.transaction.max_fee_per_blob_gas().unwrap_or_default() <
121 blob_fee_to_satisfy ||
122 tx.transaction.max_fee_per_gas() <
123 best_transactions_attributes.basefee as u128
124 {
125 'this: while let Some((peek, _)) = iter.peek() {
128 if peek.sender != id.sender {
129 break 'this
130 }
131 iter.next();
132 }
133 } else {
134 transactions.push(tx.transaction.clone());
135 }
136 }
137 }
138 }
139 transactions
140 }
141
142 #[inline]
144 pub(crate) fn exceeds(&self, limit: &SubPoolLimit) -> bool {
145 limit.is_exceeded(self.len(), self.size())
146 }
147
148 pub(crate) fn size(&self) -> usize {
150 self.size_of.into()
151 }
152
153 pub(crate) fn len(&self) -> usize {
155 self.by_id.len()
156 }
157
158 #[cfg(test)]
160 pub(crate) fn is_empty(&self) -> bool {
161 self.by_id.is_empty()
162 }
163
164 fn satisfy_pending_fee_ids(&self, pending_fees: &PendingFees) -> Vec<TransactionId> {
168 let mut transactions = Vec::new();
169 {
170 let mut iter = self.by_id.iter().peekable();
171
172 while let Some((id, tx)) = iter.next() {
173 if tx.transaction.max_fee_per_blob_gas() < Some(pending_fees.blob_fee) ||
174 tx.transaction.max_fee_per_gas() < pending_fees.base_fee as u128
175 {
176 'this: while let Some((peek, _)) = iter.peek() {
178 if peek.sender != id.sender {
179 break 'this
180 }
181 iter.next();
182 }
183 } else {
184 transactions.push(*id);
185 }
186 }
187 }
188 transactions
189 }
190
191 pub(crate) fn reprioritize(&mut self) {
193 self.all = std::mem::take(&mut self.all)
195 .into_iter()
196 .map(|mut tx| {
197 tx.update_priority(&self.pending_fees);
198 tx
199 })
200 .collect();
201
202 for tx in self.by_id.values_mut() {
205 tx.update_priority(&self.pending_fees);
206 }
207 }
208
209 pub(crate) fn enforce_pending_fees(
218 &mut self,
219 pending_fees: &PendingFees,
220 ) -> Vec<Arc<ValidPoolTransaction<T>>> {
221 let removed = self
222 .satisfy_pending_fee_ids(pending_fees)
223 .into_iter()
224 .map(|id| self.remove_transaction(&id).expect("transaction exists"))
225 .collect();
226
227 self.pending_fees = pending_fees.clone();
229 self.reprioritize();
230
231 removed
232 }
233
234 pub fn truncate_pool(&mut self, limit: SubPoolLimit) -> Vec<Arc<ValidPoolTransaction<T>>> {
241 let mut removed = Vec::new();
242
243 while self.exceeds(&limit) {
244 let tx = self.all.last().expect("pool is not empty");
245 let id = *tx.transaction.id();
246 removed.push(self.remove_transaction(&id).expect("transaction exists"));
247 }
248
249 removed
250 }
251
252 pub(crate) fn contains(&self, id: &TransactionId) -> bool {
254 self.by_id.contains_key(id)
255 }
256
257 fn get(&self, id: &TransactionId) -> Option<&BlobTransaction<T>> {
259 self.by_id.get(id)
260 }
261
262 #[cfg(any(test, feature = "test-utils"))]
264 pub(crate) fn assert_invariants(&self) {
265 assert_eq!(self.by_id.len(), self.all.len(), "by_id.len() != all.len()");
266 }
267}
268
269impl<T: PoolTransaction> Default for BlobTransactions<T> {
270 fn default() -> Self {
271 Self {
272 submission_id: 0,
273 by_id: Default::default(),
274 all: Default::default(),
275 size_of: Default::default(),
276 pending_fees: Default::default(),
277 }
278 }
279}
280
281#[derive(Debug)]
283struct BlobTransaction<T: PoolTransaction> {
284 transaction: Arc<ValidPoolTransaction<T>>,
286 ord: BlobOrd,
288}
289
290impl<T: PoolTransaction> BlobTransaction<T> {
291 pub(crate) fn new(
294 transaction: Arc<ValidPoolTransaction<T>>,
295 submission_id: u64,
296 pending_fees: &PendingFees,
297 ) -> Self {
298 let priority = blob_tx_priority(
299 transaction.max_fee_per_blob_gas().unwrap_or_default(),
300 pending_fees.blob_fee,
301 transaction.max_fee_per_gas(),
302 pending_fees.base_fee as u128,
303 );
304 let ord = BlobOrd { priority, submission_id };
305 Self { transaction, ord }
306 }
307
308 pub(crate) fn update_priority(&mut self, pending_fees: &PendingFees) {
310 self.ord.priority = blob_tx_priority(
311 self.transaction.max_fee_per_blob_gas().unwrap_or_default(),
312 pending_fees.blob_fee,
313 self.transaction.max_fee_per_gas(),
314 pending_fees.base_fee as u128,
315 );
316 }
317}
318
319impl<T: PoolTransaction> Clone for BlobTransaction<T> {
320 fn clone(&self) -> Self {
321 Self { transaction: self.transaction.clone(), ord: self.ord.clone() }
322 }
323}
324
325impl<T: PoolTransaction> Eq for BlobTransaction<T> {}
326
327impl<T: PoolTransaction> PartialEq<Self> for BlobTransaction<T> {
328 fn eq(&self, other: &Self) -> bool {
329 self.cmp(other) == Ordering::Equal
330 }
331}
332
333impl<T: PoolTransaction> PartialOrd<Self> for BlobTransaction<T> {
334 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
335 Some(self.cmp(other))
336 }
337}
338
339impl<T: PoolTransaction> Ord for BlobTransaction<T> {
340 fn cmp(&self, other: &Self) -> Ordering {
341 self.ord.cmp(&other.ord)
342 }
343}
344
345const LOG_2_1_125: f64 = 0.16992500144231237;
347
348pub fn fee_delta(max_tx_fee: u128, current_fee: u128) -> i64 {
368 if max_tx_fee == current_fee {
369 return 0
371 }
372
373 let max_tx_fee_jumps = if max_tx_fee == 0 {
374 0f64
376 } else {
377 (max_tx_fee.ilog2() as f64) / LOG_2_1_125
378 };
379
380 let current_fee_jumps = if current_fee == 0 {
381 0f64
383 } else {
384 (current_fee.ilog2() as f64) / LOG_2_1_125
385 };
386
387 let jumps = max_tx_fee_jumps - current_fee_jumps;
389
390 match (jumps as i64).cmp(&0) {
392 Ordering::Equal => {
393 0
395 }
396 Ordering::Greater => (jumps.ceil() as i64).ilog2() as i64,
397 Ordering::Less => -((-jumps.floor() as i64).ilog2() as i64),
398 }
399}
400
401pub fn blob_tx_priority(
403 blob_fee_cap: u128,
404 blob_fee: u128,
405 max_priority_fee: u128,
406 base_fee: u128,
407) -> i64 {
408 let delta_blob_fee = fee_delta(blob_fee_cap, blob_fee);
409 let delta_priority_fee = fee_delta(max_priority_fee, base_fee);
410
411 delta_blob_fee.min(delta_priority_fee).min(0)
421}
422
423#[derive(Debug, Clone)]
429pub struct BlobOrd {
430 pub(crate) submission_id: u64,
432 pub(crate) priority: i64,
435}
436
437impl Eq for BlobOrd {}
438
439impl PartialEq<Self> for BlobOrd {
440 fn eq(&self, other: &Self) -> bool {
441 self.cmp(other) == Ordering::Equal
442 }
443}
444
445impl PartialOrd<Self> for BlobOrd {
446 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
447 Some(self.cmp(other))
448 }
449}
450
451impl Ord for BlobOrd {
452 fn cmp(&self, other: &Self) -> Ordering {
461 other
462 .priority
463 .cmp(&self.priority)
464 .then_with(|| self.submission_id.cmp(&other.submission_id))
465 }
466}
467
468#[cfg(test)]
469mod tests {
470 use super::*;
471 use crate::test_utils::{MockTransaction, MockTransactionFactory};
472
473 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
475 struct TransactionFees {
476 max_blob_fee: u128,
478 max_priority_fee_per_gas: u128,
480 max_fee_per_gas: u128,
482 }
483
484 #[derive(Debug, Clone)]
486 struct TransactionOrdering {
487 fees: Vec<TransactionFees>,
489 network_fees: PendingFees,
491 }
492
493 #[test]
494 fn test_blob_ordering() {
495 let mut factory = MockTransactionFactory::default();
498
499 let vectors = vec![
500 TransactionOrdering {
502 fees: vec![
503 TransactionFees {
504 max_blob_fee: 2,
505 max_priority_fee_per_gas: 0,
506 max_fee_per_gas: 2,
507 },
508 TransactionFees {
509 max_blob_fee: 3,
510 max_priority_fee_per_gas: 1,
511 max_fee_per_gas: 1,
512 },
513 TransactionFees {
514 max_blob_fee: 1,
515 max_priority_fee_per_gas: 2,
516 max_fee_per_gas: 3,
517 },
518 ],
519 network_fees: PendingFees { base_fee: 0, blob_fee: 0 },
520 },
521 TransactionOrdering {
525 fees: vec![
526 TransactionFees {
527 max_blob_fee: 0,
528 max_priority_fee_per_gas: 1,
529 max_fee_per_gas: 2000,
530 },
531 TransactionFees {
532 max_blob_fee: 0,
533 max_priority_fee_per_gas: 2,
534 max_fee_per_gas: 2000,
535 },
536 TransactionFees {
537 max_blob_fee: 0,
538 max_priority_fee_per_gas: 3,
539 max_fee_per_gas: 2000,
540 },
541 TransactionFees {
542 max_blob_fee: 0,
543 max_priority_fee_per_gas: 50,
544 max_fee_per_gas: 1000,
545 },
546 TransactionFees {
547 max_blob_fee: 0,
548 max_priority_fee_per_gas: 100,
549 max_fee_per_gas: 1000,
550 },
551 TransactionFees {
552 max_blob_fee: 0,
553 max_priority_fee_per_gas: 50,
554 max_fee_per_gas: 500,
555 },
556 TransactionFees {
557 max_blob_fee: 0,
558 max_priority_fee_per_gas: 100,
559 max_fee_per_gas: 500,
560 },
561 ],
562 network_fees: PendingFees { base_fee: 1999, blob_fee: 0 },
563 },
564 TransactionOrdering {
568 fees: vec![
569 TransactionFees {
570 max_blob_fee: 2000,
571 max_priority_fee_per_gas: 1,
572 max_fee_per_gas: 0,
573 },
574 TransactionFees {
575 max_blob_fee: 2000,
576 max_priority_fee_per_gas: 2,
577 max_fee_per_gas: 0,
578 },
579 TransactionFees {
580 max_blob_fee: 2000,
581 max_priority_fee_per_gas: 3,
582 max_fee_per_gas: 0,
583 },
584 TransactionFees {
585 max_blob_fee: 1000,
586 max_priority_fee_per_gas: 50,
587 max_fee_per_gas: 0,
588 },
589 TransactionFees {
590 max_blob_fee: 1000,
591 max_priority_fee_per_gas: 100,
592 max_fee_per_gas: 0,
593 },
594 TransactionFees {
595 max_blob_fee: 500,
596 max_priority_fee_per_gas: 50,
597 max_fee_per_gas: 0,
598 },
599 TransactionFees {
600 max_blob_fee: 500,
601 max_priority_fee_per_gas: 100,
602 max_fee_per_gas: 0,
603 },
604 ],
605 network_fees: PendingFees { base_fee: 0, blob_fee: 1999 },
606 },
607 TransactionOrdering {
618 fees: vec![
619 TransactionFees {
620 max_blob_fee: 80,
621 max_priority_fee_per_gas: 4,
622 max_fee_per_gas: 630,
623 },
624 TransactionFees {
625 max_blob_fee: 80,
626 max_priority_fee_per_gas: 1,
627 max_fee_per_gas: 800,
628 },
629 TransactionFees {
630 max_blob_fee: 63,
631 max_priority_fee_per_gas: 3,
632 max_fee_per_gas: 800,
633 },
634 TransactionFees {
635 max_blob_fee: 63,
636 max_priority_fee_per_gas: 2,
637 max_fee_per_gas: 630,
638 },
639 ],
640 network_fees: PendingFees { base_fee: 1000, blob_fee: 100 },
641 },
642 ];
643
644 for ordering in vectors {
645 let mut pool = BlobTransactions::default();
647
648 let txs = ordering
650 .fees
651 .iter()
652 .map(|fees| {
653 MockTransaction::eip4844()
654 .with_blob_fee(fees.max_blob_fee)
655 .with_priority_fee(fees.max_priority_fee_per_gas)
656 .with_max_fee(fees.max_fee_per_gas)
657 })
658 .collect::<Vec<_>>();
659
660 for tx in &txs {
661 pool.add_transaction(factory.validated_arc(tx.clone()));
662 }
663
664 pool.pending_fees = ordering.network_fees.clone();
666 pool.reprioritize();
667
668 let actual_txs = pool
672 .all
673 .iter()
674 .map(|tx| TransactionFees {
675 max_blob_fee: tx.transaction.max_fee_per_blob_gas().unwrap_or_default(),
676 max_priority_fee_per_gas: tx.transaction.priority_fee_or_price(),
677 max_fee_per_gas: tx.transaction.max_fee_per_gas(),
678 })
679 .collect::<Vec<_>>();
680 assert_eq!(
681 ordering.fees, actual_txs,
682 "ordering mismatch, expected: {:#?}, actual: {:#?}",
683 ordering.fees, actual_txs
684 );
685 }
686 }
687
688 #[test]
689 fn priority_tests() {
690 let vectors = vec![
693 (7u128, 10u128, 2i64),
694 (17_200_000_000, 17_200_000_000, 0),
695 (9_853_941_692, 11_085_092_510, 0),
696 (11_544_106_391, 10_356_781_100, 0),
697 (17_200_000_000, 7, -7),
698 (7, 17_200_000_000, 7),
699 ];
700
701 for (base_fee, tx_fee, expected) in vectors {
702 let actual = fee_delta(tx_fee, base_fee);
703 assert_eq!(
704 actual, expected,
705 "fee_delta({tx_fee}, {base_fee}) = {actual}, expected: {expected}"
706 );
707 }
708 }
709
710 #[test]
711 fn test_empty_pool_operations() {
712 let mut pool: BlobTransactions<MockTransaction> = BlobTransactions::default();
713
714 assert!(pool.is_empty());
716 assert_eq!(pool.len(), 0);
717 assert_eq!(pool.size(), 0);
718
719 let non_existent_id = TransactionId::new(0.into(), 0);
721 assert!(pool.remove_transaction(&non_existent_id).is_none());
722
723 assert!(!pool.contains(&non_existent_id));
725 }
726
727 #[test]
728 fn test_transaction_removal() {
729 let mut factory = MockTransactionFactory::default();
730 let mut pool = BlobTransactions::default();
731
732 let tx = factory.validated_arc(MockTransaction::eip4844());
734 let tx_id = *tx.id();
735 pool.add_transaction(tx);
736
737 let removed = pool.remove_transaction(&tx_id);
739 assert!(removed.is_some());
740 assert_eq!(*removed.unwrap().id(), tx_id);
741 assert!(pool.is_empty());
742 }
743
744 #[test]
745 fn test_satisfy_attributes_empty_pool() {
746 let pool: BlobTransactions<MockTransaction> = BlobTransactions::default();
747 let attributes = BestTransactionsAttributes { blob_fee: Some(100), basefee: 100 };
748 let satisfied = pool.satisfy_attributes(attributes);
750 assert!(satisfied.is_empty());
751 }
752
753 #[test]
754 #[should_panic(expected = "transaction is not a blob tx")]
755 fn test_add_non_blob_transaction() {
756 let mut factory = MockTransactionFactory::default();
758 let mut pool = BlobTransactions::default();
759 let tx = factory.validated_arc(MockTransaction::eip1559()); pool.add_transaction(tx);
761 }
762
763 #[test]
764 #[should_panic(expected = "transaction already included")]
765 fn test_add_duplicate_blob_transaction() {
766 let mut factory = MockTransactionFactory::default();
768 let mut pool = BlobTransactions::default();
769 let tx = factory.validated_arc(MockTransaction::eip4844());
770 pool.add_transaction(tx.clone()); pool.add_transaction(tx); }
773
774 #[test]
775 fn test_remove_transactions_until_limit() {
776 let mut factory = MockTransactionFactory::default();
778 let mut pool = BlobTransactions::default();
779 let tx1 = factory.validated_arc(MockTransaction::eip4844().with_size(100));
780 let tx2 = factory.validated_arc(MockTransaction::eip4844().with_size(200));
781 let tx3 = factory.validated_arc(MockTransaction::eip4844().with_size(300));
782
783 pool.add_transaction(tx1);
785 pool.add_transaction(tx2);
786 pool.add_transaction(tx3);
787
788 let limit = SubPoolLimit { max_txs: 2, max_size: 300 };
790 let removed = pool.truncate_pool(limit);
791
792 assert_eq!(removed.len(), 1);
794 assert_eq!(pool.len(), 2);
795 assert!(pool.size() <= limit.max_size);
796 }
797
798 #[test]
799 fn test_empty_pool_invariants() {
800 let pool: BlobTransactions<MockTransaction> = BlobTransactions::default();
802 pool.assert_invariants();
803 assert!(pool.is_empty());
804 assert_eq!(pool.size(), 0);
805 assert_eq!(pool.len(), 0);
806 }
807}