Skip to main content

forest/message_pool/msgpool/
msg_pool.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4// Contains the implementation of Message Pool component.
5// The Message Pool is the component of forest that handles pending messages for
6// inclusion in the chain. Messages are added either directly for locally
7// published messages or through pubsub propagation.
8
9use crate::blocks::{CachingBlockHeader, Tipset, TipsetKey};
10use crate::chain::{HeadChanges, MINIMUM_BASE_FEE};
11use crate::eth::is_valid_eth_tx_for_sending;
12use crate::libp2p::{NetworkMessage, PUBSUB_MSG_STR, Topic};
13use crate::message::{ChainMessage, MessageRead as _, SignedMessage, valid_for_block_inclusion};
14use crate::message_pool::{
15    config::MpoolConfig,
16    errors::Error,
17    msgpool::{
18        BASE_FEE_LOWER_BOUND_FACTOR_CONSERVATIVE, events::MpoolSubscriber,
19        pending_store::PendingStore, recover_sig, republish::RepublishState,
20    },
21    provider::{Provider, ProviderExt},
22    utils::get_base_fee_lower_bound,
23};
24use crate::networks::{ChainConfig, NEWEST_NETWORK_VERSION};
25use crate::prelude::*;
26use crate::rpc::eth::types::EthAddress;
27use crate::shim::{
28    address::{Address, Protocol},
29    crypto::{Signature, SignatureType},
30    econ::TokenAmount,
31    gas::{Gas, price_list_by_network_version},
32    state_tree::ActorState,
33};
34use crate::state_manager::IdToAddressCache;
35use crate::state_manager::utils::is_valid_for_sending;
36use crate::utils::cache::SizeTrackingCache;
37use ahash::HashSet;
38use futures::StreamExt;
39use fvm_ipld_encoding::to_vec;
40use get_size2::GetSize;
41use itertools::Itertools;
42use nonzero_ext::nonzero;
43use parking_lot::RwLock as SyncRwLock;
44use std::num::NonZeroUsize;
45use std::time::Duration;
46use tokio::{task::JoinSet, time::interval};
47use tracing::warn;
48
49/// Maximum size of a serialized message in bytes. Anti-DoS measure to keep
50/// the pool from ingesting pathologically large messages.
51const MAX_MESSAGE_SIZE: usize = 64 << 10; // 64 KiB
52
53pub(in crate::message_pool) const MAX_ACTOR_PENDING_MESSAGES: u64 = 1000;
54pub(in crate::message_pool) const MAX_UNTRUSTED_ACTOR_PENDING_MESSAGES: u64 = 100;
55
56// Cache sizes have been taken from the lotus implementation
57const BLS_SIG_CACHE_SIZE: NonZeroUsize = nonzero!(40000usize);
58const SIG_VAL_CACHE_SIZE: NonZeroUsize = nonzero!(32000usize);
59const KEY_CACHE_SIZE: NonZeroUsize = nonzero!(1_048_576usize);
60const STATE_NONCE_CACHE_SIZE: NonZeroUsize = nonzero!(32768usize);
61
62#[derive(Clone, Debug, Hash, PartialEq, Eq, GetSize)]
63pub(in crate::message_pool) struct StateNonceCacheKey {
64    tipset_key: TipsetKey,
65    addr: Address,
66}
67
68/// Trust policy for whether a message is from a trusted or untrusted source.
69/// Untrusted sources are subject to stricter limits.
70#[derive(Clone, Copy, Debug, PartialEq, Eq)]
71pub(in crate::message_pool) enum TrustPolicy {
72    Trusted,
73    Untrusted,
74}
75
76pub use super::msg_set::{MsgSetLimits, StrictnessPolicy};
77
78/// Caches owned by [`MessagePool`].
79pub(in crate::message_pool) struct Caches {
80    pub(in crate::message_pool) bls_sig: SizeTrackingCache<CidWrapper, Signature>,
81    pub(in crate::message_pool) sig_val: SizeTrackingCache<CidWrapper, ()>,
82    pub(in crate::message_pool) key: IdToAddressCache,
83    pub(in crate::message_pool) state_nonce: SizeTrackingCache<StateNonceCacheKey, u64>,
84}
85
86impl Caches {
87    pub(in crate::message_pool) fn new() -> Self {
88        Self {
89            bls_sig: SizeTrackingCache::new_with_metrics("bls_sig", BLS_SIG_CACHE_SIZE),
90            sig_val: SizeTrackingCache::new_with_metrics("sig_val", SIG_VAL_CACHE_SIZE),
91            key: SizeTrackingCache::new_with_metrics("mpool_key", KEY_CACHE_SIZE),
92            state_nonce: SizeTrackingCache::new_with_metrics("state_nonce", STATE_NONCE_CACHE_SIZE),
93        }
94    }
95}
96
97impl ShallowClone for Caches {
98    fn shallow_clone(&self) -> Self {
99        Self {
100            bls_sig: self.bls_sig.shallow_clone(),
101            sig_val: self.sig_val.shallow_clone(),
102            key: self.key.shallow_clone(),
103            state_nonce: self.state_nonce.shallow_clone(),
104        }
105    }
106}
107
108/// This contains all necessary information needed for the message pool.
109/// Keeps track of messages to apply, as well as context needed for verifying
110/// transactions.
111pub struct MessagePool<T> {
112    /// Pending messages, keyed by resolved-key address, together with the
113    /// broadcast channel for [`MpoolUpdate`](super::events::MpoolUpdate) events. See [`PendingStore`].
114    pub(in crate::message_pool) pending: PendingStore,
115    pub(in crate::message_pool) caches: Caches,
116    /// Resolved-key senders of locally submitted messages.
117    pub(in crate::message_pool) local_addrs: Arc<SyncRwLock<HashSet<Address>>>,
118    /// The current tipset (a set of blocks)
119    pub(in crate::message_pool) cur_tipset: Arc<SyncRwLock<Tipset>>,
120    /// The underlying provider
121    pub(in crate::message_pool) api: Arc<T>,
122    /// Sender half to send messages to other components
123    pub(in crate::message_pool) network_sender: flume::Sender<NetworkMessage>,
124    /// Republish coordination state
125    pub(in crate::message_pool) republish: Arc<RepublishState>,
126    /// Configurable parameters of the message pool.
127    pub(in crate::message_pool) config: Arc<MpoolConfig>,
128    /// Chain configuration
129    pub(in crate::message_pool) chain_config: Arc<ChainConfig>,
130}
131
132impl<T> ShallowClone for MessagePool<T> {
133    fn shallow_clone(&self) -> Self {
134        Self {
135            pending: self.pending.shallow_clone(),
136            caches: self.caches.shallow_clone(),
137            local_addrs: self.local_addrs.shallow_clone(),
138            cur_tipset: self.cur_tipset.shallow_clone(),
139            api: self.api.shallow_clone(),
140            network_sender: self.network_sender.clone(),
141            republish: self.republish.shallow_clone(),
142            config: self.config.shallow_clone(),
143            chain_config: self.chain_config.shallow_clone(),
144        }
145    }
146}
147
148/// Resolve an address to its key form, checking the cache first.
149/// Non-ID addresses are returned unchanged.
150pub(in crate::message_pool) async fn resolve_to_key<T: Provider + Send + Sync + 'static>(
151    api: &Arc<T>,
152    key_cache: &IdToAddressCache,
153    addr: &Address,
154    cur_ts: &Tipset,
155) -> Result<Address, Error> {
156    let id = addr.id().ok();
157    if let Some(id) = &id
158        && let Some(resolved) = key_cache.get(id)
159    {
160        return Ok(resolved);
161    }
162    let resolved = api
163        .resolve_to_deterministic_address_at_finality_async(*addr, cur_ts.clone())
164        .await?;
165    if let Some(id) = id {
166        key_cache.insert(id, resolved);
167    }
168    Ok(resolved)
169}
170
171impl<T> MessagePool<T>
172where
173    T: Provider,
174{
175    /// Gets the current tipset
176    pub fn current_tipset(&self) -> Tipset {
177        self.cur_tipset.read().clone()
178    }
179
180    pub(in crate::message_pool) async fn resolve_to_key(
181        &self,
182        addr: &Address,
183        cur_ts: &Tipset,
184    ) -> Result<Address, Error>
185    where
186        T: Send + Sync + 'static,
187    {
188        resolve_to_key(&self.api, &self.caches.key, addr, cur_ts).await
189    }
190
191    /// Record the resolved-key sender of a locally-submitted message so the
192    /// republish loop can find it on its next sweep.
193    async fn add_local(&self, m: &SignedMessage) -> Result<(), Error>
194    where
195        T: Send + Sync + 'static,
196    {
197        let cur_ts = self.current_tipset();
198        let resolved = self.resolve_to_key(&m.from(), &cur_ts).await?;
199        self.local_addrs.write().insert(resolved);
200        Ok(())
201    }
202
203    /// Push a signed message to the `MessagePool`. Records the sender as
204    /// local and broadcasts on gossip if validation marks it publishable.
205    async fn push_internal(
206        &self,
207        msg: SignedMessage,
208        trust_policy: TrustPolicy,
209    ) -> Result<Cid, Error>
210    where
211        T: Send + Sync + 'static,
212    {
213        let cid = msg.cid();
214        let publish = self.add_to_pool(msg.clone(), true, trust_policy).await?;
215        self.add_local(&msg).await?;
216        if publish {
217            self.publish_pubsub(&msg).await?;
218        }
219        Ok(cid)
220    }
221
222    /// Broadcast a signed message on the network's `gossipsub` topic.
223    pub(in crate::message_pool) async fn publish_pubsub(
224        &self,
225        msg: &SignedMessage,
226    ) -> Result<(), Error> {
227        let message = to_vec(msg)?;
228        let network_name = self.chain_config.network.genesis_name();
229        self.network_sender
230            .send_async(NetworkMessage::PubsubMessage {
231                topic: Topic::new(format!("{PUBSUB_MSG_STR}/{network_name}")),
232                message,
233            })
234            .await
235            .map_err(|_| Error::Other("Network receiver dropped".to_string()))
236    }
237
238    /// Push a signed message to the `MessagePool` from an trusted source.
239    pub async fn push(&self, msg: SignedMessage) -> Result<Cid, Error>
240    where
241        T: Send + Sync + 'static,
242    {
243        self.push_internal(msg, TrustPolicy::Trusted).await
244    }
245
246    /// Push a signed message to the `MessagePool` from an untrusted source.
247    pub async fn push_untrusted(&self, msg: SignedMessage) -> Result<Cid, Error>
248    where
249        T: Send + Sync + 'static,
250    {
251        self.push_internal(msg, TrustPolicy::Untrusted).await
252    }
253
254    /// Insert a message received via gossip. Runs full validation. Does
255    /// not publish back to the network.
256    pub async fn add(&self, msg: SignedMessage) -> Result<(), Error>
257    where
258        T: Send + Sync + 'static,
259    {
260        self.add_to_pool(msg, false, TrustPolicy::Trusted).await?;
261        Ok(())
262    }
263
264    /// Message validation.
265    ///
266    /// Returns `publish: bool` — `true` when the message should be gossiped
267    /// after insertion; `false` when a local sender's message failed the
268    /// soft base-fee floor (kept locally, not broadcast).
269    pub(in crate::message_pool) async fn validate_for_pool(
270        &self,
271        msg: &SignedMessage,
272        cur_ts: &Tipset,
273        local: bool,
274    ) -> Result<bool, Error>
275    where
276        T: Send + Sync + 'static,
277    {
278        validate_static(msg)?;
279        validate_signature(msg, &self.caches.sig_val, self.chain_config.eth_chain_id)?;
280
281        let expected_sequence = self.get_state_sequence(&msg.from(), cur_ts).await?;
282        let sender_actor = self.api.get_actor_after(&msg.from(), cur_ts)?;
283
284        validate_with_state(
285            msg,
286            &self.chain_config,
287            cur_ts,
288            &sender_actor,
289            expected_sequence,
290            local,
291        )
292    }
293
294    /// Validate `msg` and insert it into the pending pool.
295    ///
296    /// Returns `publish: bool` (see [`Self::validate_for_pool`]).
297    pub(in crate::message_pool) async fn add_to_pool(
298        &self,
299        msg: SignedMessage,
300        local: bool,
301        trust_policy: TrustPolicy,
302    ) -> Result<bool, Error>
303    where
304        T: Send + Sync + 'static,
305    {
306        let cur_ts = self.current_tipset();
307        let publish = self.validate_for_pool(&msg, &cur_ts, local).await?;
308        let strictness = if local {
309            StrictnessPolicy::Relaxed
310        } else {
311            StrictnessPolicy::Strict
312        };
313        self.add_to_pool_unchecked(&cur_ts, msg, trust_policy, strictness)
314            .await?;
315        Ok(publish)
316    }
317
318    /// Insert a message into the pending pool *without* running validation
319    /// (size, sig, base-fee, sender-actor checks). The reorg replay path
320    /// uses this directly to restore reverted messages even when they no
321    /// longer pass the add-time filters.
322    pub(in crate::message_pool) async fn add_to_pool_unchecked(
323        &self,
324        cur_ts: &Tipset,
325        msg: SignedMessage,
326        trust_policy: TrustPolicy,
327        strictness: StrictnessPolicy,
328    ) -> Result<(), Error>
329    where
330        T: Send + Sync + 'static,
331    {
332        if msg.signature().signature_type() == SignatureType::Bls {
333            self.caches
334                .bls_sig
335                .insert(msg.cid().into(), msg.signature().clone());
336        }
337
338        self.api
339            .put_message(&ChainMessage::Signed(msg.clone().into()))?;
340        self.api
341            .put_message(&ChainMessage::Unsigned(msg.message().clone().into()))?;
342
343        let sequence = self.get_state_sequence(&msg.from(), cur_ts).await?;
344        let resolved_from = self.resolve_to_key(&msg.from(), cur_ts).await?;
345        self.pending
346            .insert(resolved_from, msg, sequence, trust_policy, strictness)
347    }
348
349    /// Get the sequence for a given address, return Error if there is a failure
350    /// to retrieve the respective sequence.
351    pub async fn get_sequence(&self, addr: &Address) -> Result<u64, Error>
352    where
353        T: Send + Sync + 'static,
354    {
355        let cur_ts = self.current_tipset();
356
357        let sequence = self.get_state_sequence(addr, &cur_ts).await?;
358
359        let resolved = self.resolve_to_key(addr, &cur_ts).await.ok();
360        let mset = resolved
361            .and_then(|r| self.pending.snapshot_for(&r))
362            .or_else(|| self.pending.snapshot_for(addr));
363        match mset {
364            Some(mset) => {
365                if sequence > mset.next_sequence {
366                    return Ok(sequence);
367                }
368                Ok(mset.next_sequence)
369            }
370            None => Ok(sequence),
371        }
372    }
373
374    /// Get the state nonce for an address in `cur_ts`, accounting for
375    /// messages already included in that tipset. Cached by `(TipsetKey,
376    /// Address)`.
377    pub(in crate::message_pool) async fn get_state_sequence(
378        &self,
379        addr: &Address,
380        cur_ts: &Tipset,
381    ) -> Result<u64, Error>
382    where
383        T: Send + Sync + 'static,
384    {
385        let nk = StateNonceCacheKey {
386            tipset_key: cur_ts.key().clone(),
387            addr: *addr,
388        };
389
390        if let Some(cached) = self.caches.state_nonce.get(&nk) {
391            return Ok(cached);
392        }
393
394        let actor = self.api.get_actor_after(addr, cur_ts)?;
395        let mut next_nonce = actor.sequence;
396
397        let resolved = self
398            .resolve_to_key(addr, cur_ts)
399            .await
400            .inspect_err(|e| tracing::warn!(%addr, "failed to resolve address to key: {e:#}"));
401        let messages = self
402            .api
403            .messages_for_tipset(cur_ts)
404            .inspect_err(|e| tracing::warn!("failed to get messages for tipset: {e:#}"));
405        if let (Ok(resolved), Ok(messages)) = (resolved, messages) {
406            for msg in messages.iter() {
407                if let Ok(from) = self.resolve_to_key(&msg.from(), cur_ts).await.inspect_err(
408                    |e| tracing::warn!(from = %msg.from(), "failed to resolve message sender: {e:#}"),
409                ) && from == resolved
410                {
411                    let n = msg.sequence() + 1;
412                    if n > next_nonce {
413                        next_nonce = n;
414                    }
415                }
416            }
417        }
418
419        self.caches.state_nonce.insert(nk, next_nonce);
420        Ok(next_nonce)
421    }
422
423    /// Return a tuple that contains a vector of all signed messages and the
424    /// current tipset for self.
425    pub fn pending(&self) -> (Vec<SignedMessage>, Tipset) {
426        let snapshot = self.pending.snapshot();
427        let len = snapshot.values().map(|mset| mset.msgs.len()).sum();
428        let mut out = Vec::with_capacity(len);
429
430        for mset in snapshot.into_values() {
431            out.extend(
432                mset.msgs
433                    .into_values()
434                    .sorted_unstable_by_key(|m| m.message().sequence),
435            );
436        }
437
438        let cur_ts = self.current_tipset();
439
440        (out, cur_ts)
441    }
442
443    /// Return a Vector of signed messages for a given from address. This vector
444    /// will be sorted by each `message`'s sequence. If no corresponding
445    /// messages found, return None result type.
446    pub async fn pending_for(&self, a: &Address) -> Option<Vec<SignedMessage>>
447    where
448        T: Send + Sync + 'static,
449    {
450        let cur_ts = self.current_tipset();
451        let resolved = self
452            .resolve_to_key(a, &cur_ts)
453            .await
454            .inspect_err(|e| tracing::debug!(%a, "pending_for: failed to resolve address: {e:#}"))
455            .ok()?;
456        let mset = self.pending.snapshot_for(&resolved)?;
457        if mset.msgs.is_empty() {
458            return None;
459        }
460
461        Some(
462            mset.msgs
463                .into_values()
464                .sorted_by_key(|v| v.message().sequence)
465                .collect(),
466        )
467    }
468
469    /// A subscribe-only handle to the [`MpoolUpdate`](super::events::MpoolUpdate) bus, the single entry
470    /// point for observing insertions into and removals from the pending pool.
471    pub fn subscriber(&self) -> MpoolSubscriber {
472        self.pending.subscriber()
473    }
474
475    /// Return Vector of signed messages given a block header for self.
476    pub fn messages_for_blocks<'a>(
477        &self,
478        blks: impl Iterator<Item = &'a CachingBlockHeader>,
479    ) -> Result<Vec<SignedMessage>, Error> {
480        let mut msg_vec: Vec<SignedMessage> = Vec::new();
481
482        for block in blks {
483            let (umsg, mut smsgs) = self.api.messages_for_block(block)?;
484
485            msg_vec.append(smsgs.as_mut());
486            for msg in umsg {
487                let smsg = recover_sig(&self.caches.bls_sig, msg)?;
488                msg_vec.push(smsg)
489            }
490        }
491        Ok(msg_vec)
492    }
493
494    pub fn gas_limit_overestimation(&self) -> f64 {
495        self.config.gas_limit_overestimation
496    }
497
498    pub fn config(&self) -> MpoolConfig {
499        (*self.config).clone()
500    }
501}
502
503impl<T> MessagePool<T>
504where
505    T: Provider + Send + Sync + 'static,
506{
507    /// Creates a new `MessagePool` instance.
508    pub fn new(
509        api: T,
510        network_sender: flume::Sender<NetworkMessage>,
511        config: MpoolConfig,
512        chain_config: Arc<ChainConfig>,
513        services: &mut JoinSet<anyhow::Result<()>>,
514    ) -> Result<Self, Error>
515    where
516        T: Provider,
517    {
518        // Per-actor limits are constant for the lifetime of this pool; capture
519        // them once here rather than re-reading on every insert.
520        let pending = PendingStore::new(MsgSetLimits::new(
521            api.max_actor_pending_messages(),
522            api.max_untrusted_actor_pending_messages(),
523        ));
524        let cur_tipset = Arc::new(SyncRwLock::new(api.get_heaviest_tipset()));
525        let republish_interval =
526            u64::from(10 * chain_config.block_delay_secs + chain_config.propagation_delay_secs);
527        let (republish, repub_trigger_rx) = RepublishState::new();
528
529        let mp = MessagePool {
530            pending,
531            caches: Caches::new(),
532            local_addrs: Arc::new(SyncRwLock::new(HashSet::default())),
533            republish: Arc::new(republish),
534            cur_tipset,
535            api: Arc::new(api),
536            network_sender,
537            config: Arc::new(config),
538            chain_config,
539        };
540
541        // Reacts to new HeadChanges
542        {
543            let mp = mp.shallow_clone();
544            let head_changes_rx = mp.api.subscribe_head_changes();
545            services.spawn(async move {
546                while let Ok(HeadChanges { reverts, applies }) = head_changes_rx.recv_async().await
547                {
548                    if let Err(e) = mp.apply_head_change(reverts, applies).await {
549                        tracing::warn!("Error changing head: {e}");
550                    }
551                }
552                Ok(())
553            });
554        }
555
556        // Reacts to republishing requests
557        {
558            let mp = mp.shallow_clone();
559            services.spawn(async move {
560                let mut repub_trigger_rx = repub_trigger_rx.stream();
561                let mut interval = interval(Duration::from_secs(republish_interval));
562                loop {
563                    tokio::select! {
564                        _ = interval.tick() => (),
565                        _ = repub_trigger_rx.next() => (),
566                    }
567                    if let Err(e) = mp.run_republish_cycle().await {
568                        warn!("Failed to republish pending messages: {}", e.to_string());
569                    }
570                }
571            });
572        }
573
574        Ok(mp)
575    }
576}
577
578fn validate_static(msg: &SignedMessage) -> Result<(), Error> {
579    if to_vec(msg)?.len() > MAX_MESSAGE_SIZE {
580        return Err(Error::MessageTooBig);
581    }
582    let to = msg.message().to();
583    if to.protocol() == Protocol::Delegated {
584        EthAddress::from_filecoin_address(&to).context(format!(
585            "message recipient {to} is a delegated address but not a valid Eth Address"
586        ))?;
587    }
588    valid_for_block_inclusion(msg.message(), Gas::new(0), NEWEST_NETWORK_VERSION)?;
589    if msg.gas_fee_cap().atto() < &MINIMUM_BASE_FEE.into() {
590        return Err(Error::GasFeeCapTooLow);
591    }
592    Ok(())
593}
594
595fn validate_signature(
596    msg: &SignedMessage,
597    sig_val_cache: &SizeTrackingCache<CidWrapper, ()>,
598    eth_chain_id: u64,
599) -> Result<(), Error> {
600    let cid = msg.cid();
601    if sig_val_cache.get(&cid).is_some() {
602        return Ok(());
603    }
604    msg.verify(eth_chain_id)
605        .map_err(|e| Error::Other(e.to_string()))?;
606    sig_val_cache.insert(cid.into(), ());
607    Ok(())
608}
609
610/// Check the message against the pre-resolved chain state.
611fn validate_with_state(
612    msg: &SignedMessage,
613    chain_config: &ChainConfig,
614    cur_ts: &Tipset,
615    sender_actor: &ActorState,
616    expected_sequence: u64,
617    local: bool,
618) -> Result<bool, Error> {
619    if expected_sequence > msg.message().sequence {
620        return Err(Error::SequenceTooLow);
621    }
622
623    // The message can only be included in the next epoch and beyond, hence the +1.
624    let nv_next = chain_config.network_version(cur_ts.epoch() + 1);
625    if msg.is_delegated() && !is_valid_eth_tx_for_sending(chain_config.eth_chain_id, nv_next, msg) {
626        return Err(Error::Other(
627            "Invalid Ethereum message for the current network version".to_owned(),
628        ));
629    }
630    if !is_valid_for_sending(nv_next, sender_actor) {
631        return Err(Error::Other(
632            "Sender actor is not a valid top-level sender".to_owned(),
633        ));
634    }
635
636    let nv_cur = chain_config.network_version(cur_ts.epoch());
637    let min_gas = price_list_by_network_version(nv_cur).on_chain_message(msg.chain_length()?);
638    valid_for_block_inclusion(msg.message(), min_gas.total(), NEWEST_NETWORK_VERSION)?;
639
640    let publish = check_base_fee_floor(msg, cur_ts, local)?;
641
642    let balance = TokenAmount::from(&sender_actor.balance);
643    let required = msg.required_funds();
644    if balance < required {
645        return Err(Error::NotEnoughFunds { balance, required });
646    }
647
648    Ok(publish)
649}
650
651/// Base-Fee floor check.
652pub(in crate::message_pool) fn check_base_fee_floor(
653    msg: &SignedMessage,
654    cur_ts: &Tipset,
655    local: bool,
656) -> Result<bool, Error> {
657    let base_fee = &cur_ts.block_headers().first().parent_base_fee;
658    let lb = get_base_fee_lower_bound(base_fee, BASE_FEE_LOWER_BOUND_FACTOR_CONSERVATIVE);
659    if msg.gas_fee_cap() >= lb {
660        return Ok(local);
661    }
662    if local {
663        warn!(
664            "local message will not be immediately published because GasFeeCap doesn't meet the lower bound for inclusion in the next 20 blocks (GasFeeCap: {}, baseFeeLowerBound: {})",
665            msg.gas_fee_cap(),
666            lb
667        );
668        return Ok(false);
669    }
670    Err(Error::SoftValidationFailure(format!(
671        "GasFeeCap doesn't meet base fee lower bound for inclusion in the next 20 blocks (GasFeeCap: {}, baseFeeLowerBound:{})",
672        msg.gas_fee_cap(),
673        lb
674    )))
675}
676
677#[cfg(test)]
678mod tests {
679    use crate::blocks::RawBlockHeader;
680    use crate::chain::ChainStore;
681    use crate::db::{DbImpl, MemoryDB};
682    use crate::message_pool::provider::Provider;
683    use crate::message_pool::test_provider::TestApi;
684    use crate::networks::ChainConfig;
685    use crate::shim::econ::TokenAmount;
686    use crate::shim::state_tree::{ActorState, StateTree, StateTreeVersion};
687    use crate::test_utils::dummy_ticket;
688    use crate::utils::db::CborStoreExt as _;
689
690    use super::*;
691    use crate::shim::message::Message as ShimMessage;
692
693    use tokio::task::JoinSet;
694
695    fn make_smsg(from: Address, seq: u64, premium: u64) -> SignedMessage {
696        SignedMessage::mock_bls_signed_message(ShimMessage {
697            from,
698            sequence: seq,
699            gas_premium: TokenAmount::from_atto(premium),
700            gas_limit: 1_000_000,
701            ..ShimMessage::default()
702        })
703    }
704
705    fn make_test_mpool(api: TestApi) -> (MessagePool<TestApi>, JoinSet<anyhow::Result<()>>) {
706        let (tx, _rx) = flume::bounded(50);
707        let mut services = JoinSet::new();
708        let mpool = MessagePool::new(
709            api,
710            tx,
711            Default::default(),
712            Default::default(),
713            &mut services,
714        )
715        .unwrap();
716        (mpool, services)
717    }
718
719    // Regression test for https://github.com/ChainSafe/forest/pull/6118 which fixed a bogus 100M
720    // gas limit. There are no limits on a single message.
721    #[tokio::test]
722    async fn add_to_pool_unchecked_accepts_high_gas_limit() {
723        let api = TestApi::default();
724        let (mpool, _services) = make_test_mpool(api);
725        let cur_ts = mpool.current_tipset();
726        let message = ShimMessage {
727            gas_limit: 666_666_666,
728            ..ShimMessage::default()
729        };
730        let msg = SignedMessage::mock_bls_signed_message(message);
731        let res = mpool
732            .add_to_pool_unchecked(
733                &cur_ts,
734                msg,
735                TrustPolicy::Trusted,
736                StrictnessPolicy::Relaxed,
737            )
738            .await;
739        assert!(res.is_ok());
740    }
741
742    #[tokio::test]
743    async fn test_resolve_to_key_returns_non_id_unchanged() {
744        let api = TestApi::default();
745        let (mpool, _services) = make_test_mpool(api);
746        let ts = mpool.current_tipset();
747
748        let bls_addr = Address::new_bls(&[1u8; 48]).unwrap();
749        let result = mpool.resolve_to_key(&bls_addr, &ts).await.unwrap();
750        assert_eq!(result, bls_addr);
751        assert_eq!(
752            mpool.caches.key.len(),
753            0,
754            "cache should not be populated for non-ID addresses"
755        );
756    }
757
758    #[tokio::test]
759    async fn test_resolve_to_key_resolves_id_and_caches() {
760        let api = TestApi::default();
761        let id_addr = Address::new_id(100);
762        let key_addr = Address::new_bls(&[5u8; 48]).unwrap();
763        api.set_key_address_mapping(&id_addr, &key_addr);
764
765        let (mpool, _services) = make_test_mpool(api);
766        let ts = mpool.current_tipset();
767
768        let result = mpool.resolve_to_key(&id_addr, &ts).await.unwrap();
769        assert_eq!(result, key_addr);
770        assert_eq!(
771            mpool.caches.key.len(),
772            1,
773            "cache should have one entry after resolution"
774        );
775
776        // Second call should hit the cache (no API call needed)
777        let result2 = mpool.resolve_to_key(&id_addr, &ts).await.unwrap();
778        assert_eq!(result2, key_addr);
779    }
780
781    #[tokio::test]
782    async fn test_add_to_pool_unchecked_keys_pending_by_resolved_address() {
783        let api = TestApi::default();
784        let id_addr = Address::new_id(200);
785        let key_addr = Address::new_bls(&[7u8; 48]).unwrap();
786        api.set_key_address_mapping(&id_addr, &key_addr);
787        api.set_state_sequence(&key_addr, 0);
788
789        let (mpool, _services) = make_test_mpool(api);
790        let cur_ts = mpool.current_tipset();
791
792        let message = ShimMessage {
793            from: id_addr,
794            gas_limit: 1_000_000,
795            ..ShimMessage::default()
796        };
797        let msg = SignedMessage::mock_bls_signed_message(message);
798
799        mpool
800            .add_to_pool_unchecked(
801                &cur_ts,
802                msg,
803                TrustPolicy::Trusted,
804                StrictnessPolicy::Relaxed,
805            )
806            .await
807            .unwrap();
808
809        assert!(
810            mpool.pending.snapshot_for(&key_addr).is_some(),
811            "pending should be keyed by the resolved key address"
812        );
813        assert!(
814            mpool.pending.snapshot_for(&id_addr).is_none(),
815            "pending should NOT have an entry under the raw ID address"
816        );
817    }
818
819    #[tokio::test]
820    async fn test_get_sequence_works_with_both_address_forms() {
821        let api = TestApi::default();
822        let id_addr = Address::new_id(300);
823        let key_addr = Address::new_bls(&[9u8; 48]).unwrap();
824        api.set_key_address_mapping(&id_addr, &key_addr);
825        api.set_state_sequence(&key_addr, 0);
826
827        let (mpool, _services) = make_test_mpool(api);
828        let cur_ts = mpool.current_tipset();
829
830        // Add two messages from the ID address
831        for seq in 0..2 {
832            let message = ShimMessage {
833                from: id_addr,
834                sequence: seq,
835                gas_limit: 1_000_000,
836                ..ShimMessage::default()
837            };
838            let msg = SignedMessage::mock_bls_signed_message(message);
839            mpool
840                .add_to_pool_unchecked(
841                    &cur_ts,
842                    msg,
843                    TrustPolicy::Trusted,
844                    StrictnessPolicy::Relaxed,
845                )
846                .await
847                .unwrap();
848        }
849
850        let state_seq = mpool
851            .api
852            .get_actor_after(&id_addr, &cur_ts)
853            .unwrap()
854            .sequence;
855        let resolved_for_id = mpool.resolve_to_key(&id_addr, &cur_ts).await.unwrap();
856        let resolved_for_key = mpool.resolve_to_key(&key_addr, &cur_ts).await.unwrap();
857        assert_eq!(resolved_for_id, resolved_for_key);
858
859        let next_seq = mpool
860            .pending
861            .snapshot_for(&resolved_for_id)
862            .unwrap()
863            .next_sequence;
864        let expected = std::cmp::max(state_seq, next_seq);
865        assert_eq!(expected, 2, "should reflect both pending messages");
866    }
867
868    #[tokio::test]
869    async fn test_get_state_sequence_accounts_for_tipset_messages() {
870        use crate::message_pool::test_provider::mock_block;
871
872        let api = TestApi::default();
873        let sender = Address::new_bls(&[3u8; 48]).unwrap();
874        api.set_state_sequence(&sender, 5);
875
876        let block = mock_block(1, 1);
877        api.inner.lock().set_block_messages(
878            &block,
879            vec![make_smsg(sender, 5, 100), make_smsg(sender, 7, 100)],
880        );
881        let ts = Tipset::from(block);
882
883        let (mpool, _services) = make_test_mpool(api);
884
885        let nonce = mpool.get_state_sequence(&sender, &ts).await.unwrap();
886        assert_eq!(
887            nonce, 8,
888            "should account for non-consecutive tipset message at nonce 7"
889        );
890    }
891
892    #[tokio::test]
893    async fn test_get_state_sequence_ignores_other_addresses() {
894        use crate::message_pool::test_provider::mock_block;
895
896        let api = TestApi::default();
897        let addr_a = Address::new_bls(&[4u8; 48]).unwrap();
898        let addr_b = Address::new_bls(&[5u8; 48]).unwrap();
899        api.set_state_sequence(&addr_a, 0);
900        api.set_state_sequence(&addr_b, 0);
901
902        let block = mock_block(1, 1);
903        api.inner.lock().set_block_messages(
904            &block,
905            vec![
906                make_smsg(addr_b, 0, 100),
907                make_smsg(addr_b, 1, 100),
908                make_smsg(addr_b, 2, 100),
909            ],
910        );
911        let ts = Tipset::from(block);
912
913        let (mpool, _services) = make_test_mpool(api);
914
915        let nonce_a = mpool.get_state_sequence(&addr_a, &ts).await.unwrap();
916        assert_eq!(
917            nonce_a, 0,
918            "addr_a nonce should be unaffected by addr_b's messages"
919        );
920
921        let nonce_b = mpool.get_state_sequence(&addr_b, &ts).await.unwrap();
922        assert_eq!(
923            nonce_b, 3,
924            "addr_b nonce should reflect its tipset messages"
925        );
926    }
927
928    #[tokio::test]
929    async fn test_get_state_sequence_cache_hit() {
930        use crate::message_pool::test_provider::mock_block;
931
932        let api = TestApi::default();
933        let sender = Address::new_bls(&[6u8; 48]).unwrap();
934        api.set_state_sequence(&sender, 5);
935
936        let block = mock_block(1, 1);
937        api.inner
938            .lock()
939            .set_block_messages(&block, vec![make_smsg(sender, 5, 100)]);
940        let ts = Tipset::from(block);
941
942        let (mpool, _services) = make_test_mpool(api);
943
944        let nonce1 = mpool.get_state_sequence(&sender, &ts).await.unwrap();
945        assert_eq!(nonce1, 6);
946
947        // Mutate the underlying state; the cache should still return the old value.
948        mpool.api.set_state_sequence(&sender, 99);
949        let nonce2 = mpool.get_state_sequence(&sender, &ts).await.unwrap();
950        assert_eq!(
951            nonce2, 6,
952            "second call should return the cached value, not re-read state"
953        );
954    }
955
956    #[tokio::test]
957    async fn test_get_state_sequence_cache_miss_on_different_tipset() {
958        use crate::message_pool::test_provider::mock_block;
959
960        let api = TestApi::default();
961        let sender = Address::new_bls(&[7u8; 48]).unwrap();
962        api.set_state_sequence(&sender, 10);
963
964        let (mpool, _services) = make_test_mpool(api);
965
966        let block_a = mock_block(1, 1);
967        let ts_a = Tipset::from(&block_a);
968
969        let nonce_a = mpool.get_state_sequence(&sender, &ts_a).await.unwrap();
970        assert_eq!(nonce_a, 10);
971
972        // Different tipset should be a cache miss and re-read state.
973        mpool.api.set_state_sequence(&sender, 20);
974        let block_b = mock_block(2, 2);
975        let ts_b = Tipset::from(&block_b);
976
977        let nonce_b = mpool.get_state_sequence(&sender, &ts_b).await.unwrap();
978        assert_eq!(
979            nonce_b, 20,
980            "different tipset should miss the cache and read fresh state"
981        );
982    }
983
984    #[test]
985    fn resolve_to_key_uses_finality_lookback() {
986        let db: DbImpl = Arc::new(MemoryDB::default()).into();
987
988        let mut cfg = ChainConfig::default();
989        cfg.policy.chain_finality = 1;
990        let cfg = Arc::new(cfg);
991
992        let bls_a = Address::new_bls(&[8u8; 48]).unwrap();
993        let bls_b = Address::new_bls(&[9u8; 48]).unwrap();
994
995        // root_a: only contains f0300
996        let mut st_a = StateTree::new(&db, StateTreeVersion::V5).unwrap();
997        st_a.set_actor(
998            &Address::new_id(300),
999            ActorState::new_empty(Cid::default(), Some(bls_a)),
1000        )
1001        .unwrap();
1002        let root_a = st_a.flush().unwrap();
1003
1004        // root_b: only contains f0400
1005        let mut st_b = StateTree::new(&db, StateTreeVersion::V5).unwrap();
1006        st_b.set_actor(
1007            &Address::new_id(400),
1008            ActorState::new_empty(Cid::default(), Some(bls_b)),
1009        )
1010        .unwrap();
1011        let root_b = st_b.flush().unwrap();
1012
1013        let genesis = Tipset::from(CachingBlockHeader::new(RawBlockHeader {
1014            ticket: dummy_ticket(0),
1015            state_root: root_a,
1016            ..Default::default()
1017        }));
1018        db.put_cbor_default(genesis.block_headers().first())
1019            .unwrap();
1020
1021        let ts1 = Tipset::from(CachingBlockHeader::new(RawBlockHeader {
1022            parents: genesis.key().clone(),
1023            ticket: dummy_ticket(1),
1024            epoch: 1,
1025            state_root: root_a,
1026            timestamp: 1,
1027            ..Default::default()
1028        }));
1029        db.put_cbor_default(ts1.block_headers().first()).unwrap();
1030
1031        let head = Tipset::from(CachingBlockHeader::new(RawBlockHeader {
1032            parents: ts1.key().clone(),
1033            ticket: dummy_ticket(2),
1034            epoch: 2,
1035            state_root: root_b,
1036            timestamp: 2,
1037            ..Default::default()
1038        }));
1039        db.put_cbor_default(head.block_headers().first()).unwrap();
1040
1041        let cs = ChainStore::new(db, cfg, genesis.block_headers().first().clone()).unwrap();
1042
1043        // f0300 exists in lookback state (root_a) → resolves successfully.
1044        let result = Provider::resolve_to_deterministic_address_at_finality(
1045            &cs,
1046            &Address::new_id(300),
1047            &head,
1048        )
1049        .unwrap();
1050        assert_eq!(result, bls_a);
1051
1052        // f0400 exists only in head state (root_b), not in lookback → fails.
1053        Provider::resolve_to_deterministic_address_at_finality(&cs, &Address::new_id(400), &head)
1054            .expect_err("actor only in head state must not resolve via finality lookback");
1055    }
1056}