1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
use cfx_math::nth_inv_root;
use cfx_types::U256;
use typenum::{U12, U15, U2, U3, U4, U5};

use crate::transaction::PackingPoolTransaction;
use malloc_size_of_derive::MallocSizeOf;

/// Configuration settings for a [`PackingBatch`][crate::PackingBatch].
#[derive(Default, MallocSizeOf, Clone, Copy)]
pub struct PackingPoolConfig {
    /// The maximum gas limit for a packing batch. If there is only a single
    /// transaction, this limit is not enforced.
    pub(crate) address_gas_limit: U256,
    ///  The maximum number of transactions allowed in a packing batch.
    pub(crate) address_tx_count: usize,
    /// The degree parameter used in the random packing algorithm, with
    /// supported values ranging from 1 to 5.
    pub(crate) loss_ratio_degree: u8,
}

impl PackingPoolConfig {
    pub fn new(
        address_gas_limit: U256, address_tx_count: usize, loss_ratio_degree: u8,
    ) -> Self {
        assert!(loss_ratio_degree > 0 && loss_ratio_degree <= 5);
        Self {
            address_gas_limit,
            address_tx_count,
            loss_ratio_degree,
        }
    }

    #[cfg(test)]
    pub fn new_for_test() -> Self { Self::new(3_000_000.into(), 20, 4) }

    /// Calculates the longest acceptable prefix of transactions in the provided
    /// list.
    ///
    /// This function determines the maximum number of consecutive transactions
    /// starting from the beginning of `txs` that can be accepted based on
    /// their cumulative gas limit and other criteria. It returns the index
    /// of the first transaction that is not acceptable and the total gas limit
    /// of the acceptable portion.
    ///
    /// # Parameters
    /// - `txs`: A reference to a vector of transactions to be evaluated.
    /// - `replaced`: An optional parameter that, if provided, indicates a
    ///   specific transaction and its position in `txs` that should be
    ///   considered as replaced for the purpose of this calculation. This is
    ///   used to simulate the effect of replacing a transaction within `txs`.
    ///
    /// # Returns
    /// Returns a tuple containing:
    /// - The index of the first transaction in `txs` that is not acceptable. If
    ///   all transactions are acceptable, this will be the length of `txs`.
    /// - The total gas limit of the acceptable portion of `txs`.
    #[inline]
    pub(crate) fn check_acceptable_batch<TX: PackingPoolTransaction>(
        &self, txs: &Vec<TX>, replaced: Option<(&TX, usize)>,
    ) -> (usize, U256) {
        let mut total_gas_limit = U256::zero();
        for (idx, tx) in txs.iter().enumerate() {
            if idx >= self.address_tx_count {
                return (idx, total_gas_limit);
            }

            let tx_gas = replaced
                .filter(|(_, i)| idx == *i)
                .map_or(tx.gas_limit(), |(r, _)| r.gas_limit());

            if total_gas_limit + tx_gas > self.address_gas_limit {
                if idx == 0 {
                    // If there is only one tx, it can exceed address_gas_limit
                    return (1, tx_gas);
                } else {
                    return (idx, total_gas_limit);
                }
            }
            total_gas_limit += tx_gas;
        }
        (txs.len(), total_gas_limit)
    }

    /// Calculates the loss ratio for the random packing algorithm.
    ///
    /// The loss ratio is defined as the reciprocal of `gas_price` raised to the
    /// power of the `degree` (a configuration parameter). Since this value
    /// is less than 1, the function represents 1 as 2^128, thereby
    /// preserving 128 binary digits of precision in the result.
    ///
    /// Note that the precision of the returned value is limited:
    /// - For `degree = 5`, the precision is 12 bits.
    /// - For other values of `degree`, the precision is 15 bits.
    /// This limited precision is by design, as the packing pool does not
    /// require high precision and this approach helps in saving
    /// computational resources.
    pub(crate) fn loss_ratio(&self, gas_price: U256) -> U256 {
        if gas_price.is_zero() {
            return U256::one() << 128;
        }

        (match self.loss_ratio_degree {
            1 => U256::MAX / gas_price,
            2 => nth_inv_root::<U2, U15>(gas_price),
            3 => nth_inv_root::<U3, U15>(gas_price),
            4 => nth_inv_root::<U4, U15>(gas_price),
            5 => nth_inv_root::<U5, U12>(gas_price),
            _ => unreachable!(),
        }) >> 128
    }
}