1use super::txpool::PendingFees;
2use crate::{
3 identifier::TransactionId, pool::size::SizeTracker, traits::BestTransactionsAttributes,
4 PoolTransaction, SubPoolLimit, ValidPoolTransaction,
5};
6use std::{
7 cmp::Ordering,
8 collections::{BTreeMap, BTreeSet},
9 sync::Arc,
10};
11
12#[derive(Debug, Clone)]
20pub struct BlobTransactions<T: PoolTransaction> {
21 submission_id: u64,
25 by_id: BTreeMap<TransactionId, BlobTransaction<T>>,
27 all: BTreeSet<BlobTransaction<T>>,
29 pending_fees: PendingFees,
31 size_of: SizeTracker,
35}
36
37impl<T: PoolTransaction> BlobTransactions<T> {
40 pub fn add_transaction(&mut self, tx: Arc<ValidPoolTransaction<T>>) {
47 assert!(tx.is_eip4844(), "transaction is not a blob tx");
48 let id = *tx.id();
49 assert!(!self.contains(&id), "transaction already included {:?}", self.get(&id).unwrap());
50 let submission_id = self.next_id();
51
52 self.size_of += tx.size();
54
55 let transaction = BlobTransaction::new(tx, submission_id, &self.pending_fees);
57
58 self.by_id.insert(id, transaction.clone());
59 self.all.insert(transaction);
60 }
61
62 const fn next_id(&mut self) -> u64 {
63 let id = self.submission_id;
64 self.submission_id = self.submission_id.wrapping_add(1);
65 id
66 }
67
68 pub(crate) fn remove_transaction(
70 &mut self,
71 id: &TransactionId,
72 ) -> Option<Arc<ValidPoolTransaction<T>>> {
73 let tx = self.by_id.remove(id)?;
75
76 self.all.remove(&tx);
77
78 self.size_of -= tx.transaction.size();
80
81 Some(tx.transaction)
82 }
83
84 pub(crate) fn satisfy_attributes(
88 &self,
89 best_transactions_attributes: BestTransactionsAttributes,
90 ) -> Vec<Arc<ValidPoolTransaction<T>>> {
91 let mut transactions = Vec::new();
92 {
93 if let Some(blob_fee_to_satisfy) =
95 best_transactions_attributes.blob_fee.map(|fee| fee as u128)
96 {
97 let mut iter = self.by_id.iter().peekable();
98
99 while let Some((id, tx)) = iter.next() {
100 if tx.transaction.max_fee_per_blob_gas().unwrap_or_default() <
101 blob_fee_to_satisfy ||
102 tx.transaction.max_fee_per_gas() <
103 best_transactions_attributes.basefee as u128
104 {
105 'this: while let Some((peek, _)) = iter.peek() {
108 if peek.sender != id.sender {
109 break 'this
110 }
111 iter.next();
112 }
113 } else {
114 transactions.push(tx.transaction.clone());
115 }
116 }
117 }
118 }
119 transactions
120 }
121
122 #[inline]
124 pub(crate) fn exceeds(&self, limit: &SubPoolLimit) -> bool {
125 limit.is_exceeded(self.len(), self.size())
126 }
127
128 pub(crate) fn size(&self) -> usize {
130 self.size_of.into()
131 }
132
133 pub(crate) fn len(&self) -> usize {
135 self.by_id.len()
136 }
137
138 #[cfg(test)]
140 pub(crate) fn is_empty(&self) -> bool {
141 self.by_id.is_empty()
142 }
143
144 fn satisfy_pending_fee_ids(&self, pending_fees: &PendingFees) -> Vec<TransactionId> {
148 let mut transactions = Vec::new();
149 {
150 let mut iter = self.by_id.iter().peekable();
151
152 while let Some((id, tx)) = iter.next() {
153 if tx.transaction.max_fee_per_blob_gas() < Some(pending_fees.blob_fee) ||
154 tx.transaction.max_fee_per_gas() < pending_fees.base_fee as u128
155 {
156 'this: while let Some((peek, _)) = iter.peek() {
158 if peek.sender != id.sender {
159 break 'this
160 }
161 iter.next();
162 }
163 } else {
164 transactions.push(*id);
165 }
166 }
167 }
168 transactions
169 }
170
171 pub(crate) fn reprioritize(&mut self) {
173 self.all = std::mem::take(&mut self.all)
175 .into_iter()
176 .map(|mut tx| {
177 tx.update_priority(&self.pending_fees);
178 tx
179 })
180 .collect();
181
182 for tx in self.by_id.values_mut() {
185 tx.update_priority(&self.pending_fees);
186 }
187 }
188
189 pub(crate) fn enforce_pending_fees(
198 &mut self,
199 pending_fees: &PendingFees,
200 ) -> Vec<Arc<ValidPoolTransaction<T>>> {
201 let removed = self
202 .satisfy_pending_fee_ids(pending_fees)
203 .into_iter()
204 .map(|id| self.remove_transaction(&id).expect("transaction exists"))
205 .collect();
206
207 self.pending_fees = pending_fees.clone();
209 self.reprioritize();
210
211 removed
212 }
213
214 pub fn truncate_pool(&mut self, limit: SubPoolLimit) -> Vec<Arc<ValidPoolTransaction<T>>> {
221 let mut removed = Vec::new();
222
223 while self.exceeds(&limit) {
224 let tx = self.all.last().expect("pool is not empty");
225 let id = *tx.transaction.id();
226 removed.push(self.remove_transaction(&id).expect("transaction exists"));
227 }
228
229 removed
230 }
231
232 pub(crate) fn contains(&self, id: &TransactionId) -> bool {
234 self.by_id.contains_key(id)
235 }
236
237 fn get(&self, id: &TransactionId) -> Option<&BlobTransaction<T>> {
239 self.by_id.get(id)
240 }
241
242 #[cfg(any(test, feature = "test-utils"))]
244 pub(crate) fn assert_invariants(&self) {
245 assert_eq!(self.by_id.len(), self.all.len(), "by_id.len() != all.len()");
246 }
247}
248
249impl<T: PoolTransaction> Default for BlobTransactions<T> {
250 fn default() -> Self {
251 Self {
252 submission_id: 0,
253 by_id: Default::default(),
254 all: Default::default(),
255 size_of: Default::default(),
256 pending_fees: Default::default(),
257 }
258 }
259}
260
261#[derive(Debug)]
263struct BlobTransaction<T: PoolTransaction> {
264 transaction: Arc<ValidPoolTransaction<T>>,
266 ord: BlobOrd,
268}
269
270impl<T: PoolTransaction> BlobTransaction<T> {
271 pub(crate) fn new(
274 transaction: Arc<ValidPoolTransaction<T>>,
275 submission_id: u64,
276 pending_fees: &PendingFees,
277 ) -> Self {
278 let priority = blob_tx_priority(
279 transaction.max_fee_per_blob_gas().unwrap_or_default(),
280 pending_fees.blob_fee,
281 transaction.max_fee_per_gas(),
282 pending_fees.base_fee as u128,
283 );
284 let ord = BlobOrd { priority, submission_id };
285 Self { transaction, ord }
286 }
287
288 pub(crate) fn update_priority(&mut self, pending_fees: &PendingFees) {
290 self.ord.priority = blob_tx_priority(
291 self.transaction.max_fee_per_blob_gas().unwrap_or_default(),
292 pending_fees.blob_fee,
293 self.transaction.max_fee_per_gas(),
294 pending_fees.base_fee as u128,
295 );
296 }
297}
298
299impl<T: PoolTransaction> Clone for BlobTransaction<T> {
300 fn clone(&self) -> Self {
301 Self { transaction: self.transaction.clone(), ord: self.ord.clone() }
302 }
303}
304
305impl<T: PoolTransaction> Eq for BlobTransaction<T> {}
306
307impl<T: PoolTransaction> PartialEq<Self> for BlobTransaction<T> {
308 fn eq(&self, other: &Self) -> bool {
309 self.cmp(other) == Ordering::Equal
310 }
311}
312
313impl<T: PoolTransaction> PartialOrd<Self> for BlobTransaction<T> {
314 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
315 Some(self.cmp(other))
316 }
317}
318
319impl<T: PoolTransaction> Ord for BlobTransaction<T> {
320 fn cmp(&self, other: &Self) -> Ordering {
321 self.ord.cmp(&other.ord)
322 }
323}
324
325const LOG_2_1_125: f64 = 0.16992500144231237;
327
328pub fn fee_delta(max_tx_fee: u128, current_fee: u128) -> i64 {
348 if max_tx_fee == current_fee {
349 return 0
351 }
352
353 let max_tx_fee_jumps = if max_tx_fee == 0 {
354 0f64
356 } else {
357 (max_tx_fee.ilog2() as f64) / LOG_2_1_125
358 };
359
360 let current_fee_jumps = if current_fee == 0 {
361 0f64
363 } else {
364 (current_fee.ilog2() as f64) / LOG_2_1_125
365 };
366
367 let jumps = max_tx_fee_jumps - current_fee_jumps;
369
370 match (jumps as i64).cmp(&0) {
372 Ordering::Equal => {
373 0
375 }
376 Ordering::Greater => (jumps.ceil() as i64).ilog2() as i64,
377 Ordering::Less => -((-jumps.floor() as i64).ilog2() as i64),
378 }
379}
380
381pub fn blob_tx_priority(
383 blob_fee_cap: u128,
384 blob_fee: u128,
385 max_priority_fee: u128,
386 base_fee: u128,
387) -> i64 {
388 let delta_blob_fee = fee_delta(blob_fee_cap, blob_fee);
389 let delta_priority_fee = fee_delta(max_priority_fee, base_fee);
390
391 delta_blob_fee.min(delta_priority_fee).min(0)
401}
402
403#[derive(Debug, Clone)]
409pub struct BlobOrd {
410 pub(crate) submission_id: u64,
412 pub(crate) priority: i64,
415}
416
417impl Eq for BlobOrd {}
418
419impl PartialEq<Self> for BlobOrd {
420 fn eq(&self, other: &Self) -> bool {
421 self.cmp(other) == Ordering::Equal
422 }
423}
424
425impl PartialOrd<Self> for BlobOrd {
426 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
427 Some(self.cmp(other))
428 }
429}
430
431impl Ord for BlobOrd {
432 fn cmp(&self, other: &Self) -> Ordering {
441 other
442 .priority
443 .cmp(&self.priority)
444 .then_with(|| self.submission_id.cmp(&other.submission_id))
445 }
446}
447
448#[cfg(test)]
449mod tests {
450 use super::*;
451 use crate::test_utils::{MockTransaction, MockTransactionFactory};
452
453 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
455 struct TransactionFees {
456 max_blob_fee: u128,
458 max_priority_fee_per_gas: u128,
460 max_fee_per_gas: u128,
462 }
463
464 #[derive(Debug, Clone)]
466 struct TransactionOrdering {
467 fees: Vec<TransactionFees>,
469 network_fees: PendingFees,
471 }
472
473 #[test]
474 fn test_blob_ordering() {
475 let mut factory = MockTransactionFactory::default();
478
479 let vectors = vec![
480 TransactionOrdering {
482 fees: vec![
483 TransactionFees {
484 max_blob_fee: 2,
485 max_priority_fee_per_gas: 0,
486 max_fee_per_gas: 2,
487 },
488 TransactionFees {
489 max_blob_fee: 3,
490 max_priority_fee_per_gas: 1,
491 max_fee_per_gas: 1,
492 },
493 TransactionFees {
494 max_blob_fee: 1,
495 max_priority_fee_per_gas: 2,
496 max_fee_per_gas: 3,
497 },
498 ],
499 network_fees: PendingFees { base_fee: 0, blob_fee: 0 },
500 },
501 TransactionOrdering {
505 fees: vec![
506 TransactionFees {
507 max_blob_fee: 0,
508 max_priority_fee_per_gas: 1,
509 max_fee_per_gas: 2000,
510 },
511 TransactionFees {
512 max_blob_fee: 0,
513 max_priority_fee_per_gas: 2,
514 max_fee_per_gas: 2000,
515 },
516 TransactionFees {
517 max_blob_fee: 0,
518 max_priority_fee_per_gas: 3,
519 max_fee_per_gas: 2000,
520 },
521 TransactionFees {
522 max_blob_fee: 0,
523 max_priority_fee_per_gas: 50,
524 max_fee_per_gas: 1000,
525 },
526 TransactionFees {
527 max_blob_fee: 0,
528 max_priority_fee_per_gas: 100,
529 max_fee_per_gas: 1000,
530 },
531 TransactionFees {
532 max_blob_fee: 0,
533 max_priority_fee_per_gas: 50,
534 max_fee_per_gas: 500,
535 },
536 TransactionFees {
537 max_blob_fee: 0,
538 max_priority_fee_per_gas: 100,
539 max_fee_per_gas: 500,
540 },
541 ],
542 network_fees: PendingFees { base_fee: 1999, blob_fee: 0 },
543 },
544 TransactionOrdering {
548 fees: vec![
549 TransactionFees {
550 max_blob_fee: 2000,
551 max_priority_fee_per_gas: 1,
552 max_fee_per_gas: 0,
553 },
554 TransactionFees {
555 max_blob_fee: 2000,
556 max_priority_fee_per_gas: 2,
557 max_fee_per_gas: 0,
558 },
559 TransactionFees {
560 max_blob_fee: 2000,
561 max_priority_fee_per_gas: 3,
562 max_fee_per_gas: 0,
563 },
564 TransactionFees {
565 max_blob_fee: 1000,
566 max_priority_fee_per_gas: 50,
567 max_fee_per_gas: 0,
568 },
569 TransactionFees {
570 max_blob_fee: 1000,
571 max_priority_fee_per_gas: 100,
572 max_fee_per_gas: 0,
573 },
574 TransactionFees {
575 max_blob_fee: 500,
576 max_priority_fee_per_gas: 50,
577 max_fee_per_gas: 0,
578 },
579 TransactionFees {
580 max_blob_fee: 500,
581 max_priority_fee_per_gas: 100,
582 max_fee_per_gas: 0,
583 },
584 ],
585 network_fees: PendingFees { base_fee: 0, blob_fee: 1999 },
586 },
587 TransactionOrdering {
598 fees: vec![
599 TransactionFees {
600 max_blob_fee: 80,
601 max_priority_fee_per_gas: 4,
602 max_fee_per_gas: 630,
603 },
604 TransactionFees {
605 max_blob_fee: 80,
606 max_priority_fee_per_gas: 1,
607 max_fee_per_gas: 800,
608 },
609 TransactionFees {
610 max_blob_fee: 63,
611 max_priority_fee_per_gas: 3,
612 max_fee_per_gas: 800,
613 },
614 TransactionFees {
615 max_blob_fee: 63,
616 max_priority_fee_per_gas: 2,
617 max_fee_per_gas: 630,
618 },
619 ],
620 network_fees: PendingFees { base_fee: 1000, blob_fee: 100 },
621 },
622 ];
623
624 for ordering in vectors {
625 let mut pool = BlobTransactions::default();
627
628 let txs = ordering
630 .fees
631 .iter()
632 .map(|fees| {
633 MockTransaction::eip4844()
634 .with_blob_fee(fees.max_blob_fee)
635 .with_priority_fee(fees.max_priority_fee_per_gas)
636 .with_max_fee(fees.max_fee_per_gas)
637 })
638 .collect::<Vec<_>>();
639
640 for tx in &txs {
641 pool.add_transaction(factory.validated_arc(tx.clone()));
642 }
643
644 pool.pending_fees = ordering.network_fees.clone();
646 pool.reprioritize();
647
648 let actual_txs = pool
652 .all
653 .iter()
654 .map(|tx| TransactionFees {
655 max_blob_fee: tx.transaction.max_fee_per_blob_gas().unwrap_or_default(),
656 max_priority_fee_per_gas: tx.transaction.priority_fee_or_price(),
657 max_fee_per_gas: tx.transaction.max_fee_per_gas(),
658 })
659 .collect::<Vec<_>>();
660 assert_eq!(
661 ordering.fees, actual_txs,
662 "ordering mismatch, expected: {:#?}, actual: {:#?}",
663 ordering.fees, actual_txs
664 );
665 }
666 }
667
668 #[test]
669 fn priority_tests() {
670 let vectors = vec![
673 (7u128, 10u128, 2i64),
674 (17_200_000_000, 17_200_000_000, 0),
675 (9_853_941_692, 11_085_092_510, 0),
676 (11_544_106_391, 10_356_781_100, 0),
677 (17_200_000_000, 7, -7),
678 (7, 17_200_000_000, 7),
679 ];
680
681 for (base_fee, tx_fee, expected) in vectors {
682 let actual = fee_delta(tx_fee, base_fee);
683 assert_eq!(
684 actual, expected,
685 "fee_delta({tx_fee}, {base_fee}) = {actual}, expected: {expected}"
686 );
687 }
688 }
689
690 #[test]
691 fn test_empty_pool_operations() {
692 let mut pool: BlobTransactions<MockTransaction> = BlobTransactions::default();
693
694 assert!(pool.is_empty());
696 assert_eq!(pool.len(), 0);
697 assert_eq!(pool.size(), 0);
698
699 let non_existent_id = TransactionId::new(0.into(), 0);
701 assert!(pool.remove_transaction(&non_existent_id).is_none());
702
703 assert!(!pool.contains(&non_existent_id));
705 }
706
707 #[test]
708 fn test_transaction_removal() {
709 let mut factory = MockTransactionFactory::default();
710 let mut pool = BlobTransactions::default();
711
712 let tx = factory.validated_arc(MockTransaction::eip4844());
714 let tx_id = *tx.id();
715 pool.add_transaction(tx);
716
717 let removed = pool.remove_transaction(&tx_id);
719 assert!(removed.is_some());
720 assert_eq!(*removed.unwrap().id(), tx_id);
721 assert!(pool.is_empty());
722 }
723
724 #[test]
725 fn test_satisfy_attributes_empty_pool() {
726 let pool: BlobTransactions<MockTransaction> = BlobTransactions::default();
727 let attributes = BestTransactionsAttributes { blob_fee: Some(100), basefee: 100 };
728 let satisfied = pool.satisfy_attributes(attributes);
730 assert!(satisfied.is_empty());
731 }
732
733 #[test]
734 #[should_panic(expected = "transaction is not a blob tx")]
735 fn test_add_non_blob_transaction() {
736 let mut factory = MockTransactionFactory::default();
738 let mut pool = BlobTransactions::default();
739 let tx = factory.validated_arc(MockTransaction::eip1559()); pool.add_transaction(tx);
741 }
742
743 #[test]
744 #[should_panic(expected = "transaction already included")]
745 fn test_add_duplicate_blob_transaction() {
746 let mut factory = MockTransactionFactory::default();
748 let mut pool = BlobTransactions::default();
749 let tx = factory.validated_arc(MockTransaction::eip4844());
750 pool.add_transaction(tx.clone()); pool.add_transaction(tx); }
753
754 #[test]
755 fn test_remove_transactions_until_limit() {
756 let mut factory = MockTransactionFactory::default();
758 let mut pool = BlobTransactions::default();
759 let tx1 = factory.validated_arc(MockTransaction::eip4844().with_size(100));
760 let tx2 = factory.validated_arc(MockTransaction::eip4844().with_size(200));
761 let tx3 = factory.validated_arc(MockTransaction::eip4844().with_size(300));
762
763 pool.add_transaction(tx1);
765 pool.add_transaction(tx2);
766 pool.add_transaction(tx3);
767
768 let limit = SubPoolLimit { max_txs: 2, max_size: 300 };
770 let removed = pool.truncate_pool(limit);
771
772 assert_eq!(removed.len(), 1);
774 assert_eq!(pool.len(), 2);
775 assert!(pool.size() <= limit.max_size);
776 }
777
778 #[test]
779 fn test_empty_pool_invariants() {
780 let pool: BlobTransactions<MockTransaction> = BlobTransactions::default();
782 pool.assert_invariants();
783 assert!(pool.is_empty());
784 assert_eq!(pool.size(), 0);
785 assert_eq!(pool.len(), 0);
786 }
787}