Skip to main content

forest/message_pool/msgpool/
utils.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use crate::chain::MINIMUM_BASE_FEE;
5use crate::message::{MessageRead as _, SignedMessage};
6use crate::message_pool::{
7    Error,
8    msgpool::{RBF_DENOM, REPLACE_BY_FEE_RATIO_MIN},
9};
10use crate::shim::address::Address;
11use crate::shim::{crypto::Signature, econ::TokenAmount, message::Message, percent::Percent};
12use crate::utils::cache::SizeTrackingCache;
13use crate::utils::get_size::CidWrapper;
14use ahash::HashMap;
15use num_rational::BigRational;
16use num_traits::ToPrimitive;
17
18pub(in crate::message_pool) fn get_base_fee_lower_bound(
19    base_fee: &TokenAmount,
20    factor: i64,
21) -> TokenAmount {
22    let base_fee_lower_bound = base_fee.div_floor(factor);
23    if base_fee_lower_bound.atto() < &MINIMUM_BASE_FEE.into() {
24        TokenAmount::from_atto(MINIMUM_BASE_FEE)
25    } else {
26        base_fee_lower_bound
27    }
28}
29
30/// Gets the gas reward for the given message.
31pub(in crate::message_pool) fn get_gas_reward(
32    msg: &SignedMessage,
33    base_fee: &TokenAmount,
34) -> TokenAmount {
35    let mut max_prem = msg.gas_fee_cap() - base_fee;
36    if max_prem < msg.gas_premium() {
37        max_prem = msg.gas_premium();
38    }
39    max_prem * msg.gas_limit()
40}
41
42pub(in crate::message_pool) fn get_gas_perf(gas_reward: &TokenAmount, gas_limit: u64) -> f64 {
43    // Guard the hazard directly: `BigRational::new(_, 0)` panics. This is already guaranteed by
44    // upstream message validation, but let's be defensive in case of future changes.
45    if gas_limit == 0 {
46        return 0.0;
47    }
48    let a = BigRational::new(
49        gas_reward.atto() * crate::shim::econ::BLOCK_GAS_LIMIT,
50        gas_limit.into(),
51    );
52    a.to_f64()
53        .expect("gas_limit is nonzero so the ratio converts to f64")
54}
55
56/// Attempt to get a signed message that corresponds to an unsigned message in
57/// `bls_sig_cache`.
58pub(in crate::message_pool) fn recover_sig(
59    bls_sig_cache: &SizeTrackingCache<CidWrapper, Signature>,
60    msg: Message,
61) -> Result<SignedMessage, Error> {
62    let val = bls_sig_cache
63        .get(&msg.cid())
64        .ok_or_else(|| Error::Other("Could not recover sig".to_owned()))?;
65    let smsg = SignedMessage::new_from_parts(msg, val)?;
66    Ok(smsg)
67}
68
69pub(in crate::message_pool) fn add_to_selected_msgs(
70    m: SignedMessage,
71    rmsgs: &mut HashMap<Address, HashMap<u64, SignedMessage>>,
72) {
73    rmsgs.entry(m.from()).or_default().insert(m.sequence(), m);
74}
75
76pub(in crate::message_pool) fn remove_from_selected_msgs(
77    from: &Address,
78    sequence: u64,
79    rmsgs: &mut HashMap<Address, HashMap<u64, SignedMessage>>,
80) {
81    if let Some(set) = rmsgs.get_mut(from) {
82        set.remove(&sequence);
83    }
84}
85
86/// Computes the minimum gas premium required to replace an existing message
87/// using [`REPLACE_BY_FEE_RATIO_MIN`].
88///
89/// See <https://github.com/filecoin-project/lotus/blob/v1.36.0/chain/messagepool/messagepool.go#L210-L213>
90pub(crate) fn compute_rbf_min_premium(premium: &TokenAmount) -> TokenAmount {
91    (premium * *REPLACE_BY_FEE_RATIO_MIN).div_floor(RBF_DENOM) + TokenAmount::from_atto(1u8)
92}
93
94/// Computes the gas premium required to replace an existing message
95/// using provided replace-by-fee ratio.
96///
97/// See <https://github.com/filecoin-project/lotus/blob/v1.36.0/chain/messagepool/messagepool.go#L215-L219>
98pub(crate) fn compute_rbf(premium: &TokenAmount, replace_by_fee_ratio: Percent) -> TokenAmount {
99    (premium * *replace_by_fee_ratio).div_floor(RBF_DENOM) + TokenAmount::from_atto(1u8)
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn test_compute_rbf() {
108        let replace_by_fee_ratio = Percent(125);
109        assert_eq!(
110            super::compute_rbf(&TokenAmount::from_atto(100u64), replace_by_fee_ratio),
111            TokenAmount::from_atto(126u64) // 100 * 125/100 + 1
112        );
113    }
114
115    #[test]
116    fn test_compute_rbf_min_premium() {
117        assert_eq!(
118            super::compute_rbf_min_premium(&TokenAmount::from_atto(100u64)),
119            TokenAmount::from_atto(111u64) // 100 * 110/100 + 1
120        );
121    }
122
123    #[test]
124    fn get_gas_perf_zero_gas_limit_does_not_panic() {
125        // A zero `gas_limit` must not reach `BigRational::new(_, 0)`, which would
126        // panic. This pins the guard at the hazard site regardless of the
127        // upstream message-validation invariants.
128        assert_eq!(
129            super::get_gas_perf(&TokenAmount::from_atto(1_000u64), 0),
130            0.0
131        );
132    }
133}