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::msgpool::{RBF_DENOM, REPLACE_BY_FEE_RATIO_MIN};
7use crate::shim::address::Address;
8use crate::shim::{crypto::Signature, econ::TokenAmount, message::Message, percent::Percent};
9use crate::utils::cache::SizeTrackingCache;
10use crate::utils::get_size::CidWrapper;
11use ahash::HashMap;
12use num_rational::BigRational;
13use num_traits::ToPrimitive;
14
15pub(in crate::message_pool) fn get_base_fee_lower_bound(
16    base_fee: &TokenAmount,
17    factor: i64,
18) -> TokenAmount {
19    let base_fee_lower_bound = base_fee.div_floor(factor);
20    if base_fee_lower_bound.atto() < &MINIMUM_BASE_FEE.into() {
21        TokenAmount::from_atto(MINIMUM_BASE_FEE)
22    } else {
23        base_fee_lower_bound
24    }
25}
26
27/// Gets the gas reward for the given message.
28pub(in crate::message_pool) fn get_gas_reward(
29    msg: &SignedMessage,
30    base_fee: &TokenAmount,
31) -> TokenAmount {
32    let mut max_prem = msg.gas_fee_cap() - base_fee;
33    if max_prem < msg.gas_premium() {
34        max_prem = msg.gas_premium();
35    }
36    max_prem * msg.gas_limit()
37}
38
39pub(in crate::message_pool) fn get_gas_perf(gas_reward: &TokenAmount, gas_limit: u64) -> f64 {
40    // Guard the hazard directly: `BigRational::new(_, 0)` panics. This is already guaranteed by
41    // upstream message validation, but let's be defensive in case of future changes.
42    if gas_limit == 0 {
43        return 0.0;
44    }
45    let a = BigRational::new(
46        gas_reward.atto() * crate::shim::econ::BLOCK_GAS_LIMIT,
47        gas_limit.into(),
48    );
49    a.to_f64()
50        .expect("gas_limit is nonzero so the ratio converts to f64")
51}
52
53/// Attempt to get a signed message that corresponds to an unsigned message in
54/// `bls_sig_cache`, logging and returning [`None`] when the signature is not cached.
55/// <https://github.com/filecoin-project/lotus/blob/27abf0f16a7f2a83305910f3c2a1844764d20b75/chain/messagepool/messagepool.go#L1564>
56pub(in crate::message_pool) fn recover_sig(
57    bls_sig_cache: &SizeTrackingCache<CidWrapper, Signature>,
58    msg: Message,
59) -> Option<SignedMessage> {
60    let msg_cid = msg.cid();
61    match bls_sig_cache.get(&msg_cid) {
62        // Every cached signature was verified before insertion
63        Some(sig) => Some(SignedMessage::new_unchecked(msg, sig)),
64        None => {
65            tracing::debug!("could not recover signature for bls message {msg_cid}");
66            None
67        }
68    }
69}
70
71/// Recover the signed forms of a block's unsigned BLS messages,
72/// dropping any whose signature is not cached. See [`recover_sig`].
73pub(in crate::message_pool) fn recovered_bls_messages(
74    bls_sig_cache: &SizeTrackingCache<CidWrapper, Signature>,
75    umsg: Vec<Message>,
76) -> impl Iterator<Item = SignedMessage> + '_ {
77    umsg.into_iter()
78        .filter_map(|msg| recover_sig(bls_sig_cache, msg))
79}
80
81pub(in crate::message_pool) fn add_to_selected_msgs(
82    m: SignedMessage,
83    rmsgs: &mut HashMap<Address, HashMap<u64, SignedMessage>>,
84) {
85    rmsgs.entry(m.from()).or_default().insert(m.sequence(), m);
86}
87
88pub(in crate::message_pool) fn remove_from_selected_msgs(
89    from: &Address,
90    sequence: u64,
91    rmsgs: &mut HashMap<Address, HashMap<u64, SignedMessage>>,
92) {
93    if let Some(set) = rmsgs.get_mut(from) {
94        set.remove(&sequence);
95    }
96}
97
98/// Computes the minimum gas premium required to replace an existing message
99/// using [`REPLACE_BY_FEE_RATIO_MIN`].
100///
101/// See <https://github.com/filecoin-project/lotus/blob/v1.36.0/chain/messagepool/messagepool.go#L210-L213>
102pub(crate) fn compute_rbf_min_premium(premium: &TokenAmount) -> TokenAmount {
103    (premium * *REPLACE_BY_FEE_RATIO_MIN).div_floor(RBF_DENOM) + TokenAmount::from_atto(1u8)
104}
105
106/// Computes the gas premium required to replace an existing message
107/// using provided replace-by-fee ratio.
108///
109/// See <https://github.com/filecoin-project/lotus/blob/v1.36.0/chain/messagepool/messagepool.go#L215-L219>
110pub(crate) fn compute_rbf(premium: &TokenAmount, replace_by_fee_ratio: Percent) -> TokenAmount {
111    (premium * *replace_by_fee_ratio).div_floor(RBF_DENOM) + TokenAmount::from_atto(1u8)
112}
113
114#[cfg(test)]
115mod tests {
116    use super::*;
117
118    #[test]
119    fn test_compute_rbf() {
120        let replace_by_fee_ratio = Percent(125);
121        assert_eq!(
122            super::compute_rbf(&TokenAmount::from_atto(100u64), replace_by_fee_ratio),
123            TokenAmount::from_atto(126u64) // 100 * 125/100 + 1
124        );
125    }
126
127    #[test]
128    fn test_compute_rbf_min_premium() {
129        assert_eq!(
130            super::compute_rbf_min_premium(&TokenAmount::from_atto(100u64)),
131            TokenAmount::from_atto(111u64) // 100 * 110/100 + 1
132        );
133    }
134
135    #[test]
136    fn get_gas_perf_zero_gas_limit_does_not_panic() {
137        // A zero `gas_limit` must not reach `BigRational::new(_, 0)`, which would
138        // panic. This pins the guard at the hazard site regardless of the
139        // upstream message-validation invariants.
140        assert_eq!(
141            super::get_gas_perf(&TokenAmount::from_atto(1_000u64), 0),
142            0.0
143        );
144    }
145}