Skip to main content

forest/message_pool/msgpool/
selection.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4//! Contains routines for message selection APIs.
5//! Whenever a miner is ready to create a block for a tipset, it invokes the
6//! `select_messages` API which selects an appropriate set of messages such that
7//! it optimizes miner reward and chain capacity. See <https://docs.filecoin.io/mine/lotus/message-pool/#message-selection> for more details
8
9use std::cmp::Ordering;
10
11use super::{msg_pool::MessagePool, provider::Provider, utils, utils::recover_sig};
12use crate::blocks::{BLOCK_MESSAGE_LIMIT, Tipset};
13use crate::message::{MessageRead as _, SignedMessage};
14use crate::message_pool::msg_chain::MsgChainNode;
15use crate::message_pool::{
16    Error,
17    msg_chain::{Chains, NodeKey, create_message_chains},
18    msgpool::MIN_GAS,
19};
20use crate::prelude::*;
21use crate::shim::crypto::{Signature, SignatureType};
22use crate::shim::{address::Address, econ::TokenAmount};
23use crate::utils::cache::SizeTrackingCache;
24use ahash::HashMap;
25use anyhow::{bail, ensure};
26use rand::prelude::SliceRandom;
27use tracing::{debug, error, warn};
28
29type Pending = HashMap<Address, HashMap<u64, SignedMessage>>;
30
31// A cap on maximum number of message to include in a block
32const MAX_BLOCK_MSGS: usize = 16000;
33const MAX_BLOCKS: usize = 15;
34const CBOR_GEN_LIMIT: usize = 8192; // Same limits as in
35// [cbor-gen](https://github.com/whyrusleeping/cbor-gen/blob/cba3eeea9ae8ec4db1b7283e3654d8c18979affe/gen.go#L32), which Lotus uses.
36
37/// A structure that holds the selected messages for a block.
38/// It tracks the gas limit and the limits for different signature types
39/// to ensure that the block does not exceed the limits set by the protocol.
40struct SelectedMessages {
41    /// The messages selected for inclusion in the block.
42    msgs: Vec<SignedMessage>,
43    /// The remaining gas limit for the block.
44    gas_limit: u64,
45    /// The remaining limit for `secp256k1` messages in the block.
46    secp_limit: u64,
47    /// The remaining limit for `bls` messages in the block.
48    bls_limit: u64,
49}
50
51impl Default for SelectedMessages {
52    fn default() -> Self {
53        SelectedMessages {
54            msgs: Vec::new(),
55            gas_limit: crate::shim::econ::BLOCK_GAS_LIMIT,
56            secp_limit: CBOR_GEN_LIMIT as u64,
57            bls_limit: CBOR_GEN_LIMIT as u64,
58        }
59    }
60}
61
62impl SelectedMessages {
63    fn new(msgs: Vec<SignedMessage>, gas_limit: u64) -> Self {
64        SelectedMessages {
65            msgs,
66            gas_limit,
67            ..Default::default()
68        }
69    }
70
71    /// Returns the number of messages selected for inclusion in the block.
72    fn len(&self) -> usize {
73        self.msgs.len()
74    }
75
76    /// Truncates the selected messages to the specified length.
77    fn truncate(&mut self, len: usize) {
78        self.msgs.truncate(len);
79    }
80
81    /// Reduces the gas limit by the specified amount. It ensures that the gas limit does not
82    /// go below zero (which would cause a panic).
83    fn reduce_gas_limit(&mut self, gas: u64) {
84        self.gas_limit = self.gas_limit.saturating_sub(gas);
85    }
86
87    /// Reduces the BLS limit by the specified amount. It ensures that the BLS message limit does not
88    /// go below zero (which would cause a panic).
89    fn reduce_bls_limit(&mut self, bls: u64) {
90        self.bls_limit = self.bls_limit.saturating_sub(bls);
91    }
92
93    /// Reduces the `Secp256k1` limit by the specified amount. It ensures that the `Secp256k1` message limit
94    /// does not go below zero (which would cause a panic).
95    fn reduce_secp_limit(&mut self, secp: u64) {
96        self.secp_limit = self.secp_limit.saturating_sub(secp);
97    }
98
99    /// Extends the selected messages with the given messages.
100    fn extend(&mut self, msgs: Vec<SignedMessage>) {
101        self.msgs.extend(msgs);
102    }
103
104    /// Tries to add a message chain to the selected messages. Returns an error if the chain can't
105    /// be added due to block constraints.
106    fn try_to_add(&mut self, message_chain: MsgChainNode) -> anyhow::Result<()> {
107        let msg_chain_len = message_chain.msgs.len();
108        ensure!(
109            BLOCK_MESSAGE_LIMIT >= msg_chain_len + self.len(),
110            "Message chain is too long to fit in the block: {} messages, limit is {BLOCK_MESSAGE_LIMIT}",
111            msg_chain_len + self.len(),
112        );
113        ensure!(
114            self.gas_limit >= message_chain.gas_limit,
115            "Message chain gas limit is too high: {} gas, limit is {}",
116            message_chain.gas_limit,
117            self.gas_limit
118        );
119
120        match message_chain.sig_type {
121            Some(SignatureType::Bls) => {
122                ensure!(
123                    self.bls_limit >= msg_chain_len as u64,
124                    "BLS limit is too low: {msg_chain_len} messages, limit is {}",
125                    self.bls_limit
126                );
127
128                self.extend(message_chain.msgs);
129                self.reduce_bls_limit(msg_chain_len as u64);
130                self.reduce_gas_limit(message_chain.gas_limit);
131            }
132            Some(SignatureType::Secp256k1) | Some(SignatureType::Delegated) => {
133                ensure!(
134                    self.secp_limit >= msg_chain_len as u64,
135                    "Secp256k1 limit is too low: {msg_chain_len} messages, limit is {}",
136                    self.secp_limit
137                );
138
139                self.extend(message_chain.msgs);
140                self.reduce_secp_limit(msg_chain_len as u64);
141                self.reduce_gas_limit(message_chain.gas_limit);
142            }
143            None => {
144                // This is a message with no signature type, which is not allowed in the current
145                // implementation. This _should_ never happen, but we handle it gracefully.
146                warn!("Tried to add a message chain with no signature type");
147            }
148        }
149        Ok(())
150    }
151
152    /// Tries to add a message chain with dependencies to the selected messages.
153    /// It will trim or invalidate if appropriate.
154    fn try_to_add_with_deps(
155        &mut self,
156        idx: usize,
157        chains: &mut Chains,
158        base_fee: &TokenAmount,
159    ) -> anyhow::Result<()> {
160        let message_chain = chains
161            .get_mut_at(idx)
162            .context("Couldn't find required message chain")?;
163        // compute the dependencies that must be merged and the gas limit including deps
164        let mut chain_gas_limit = message_chain.gas_limit;
165        let mut chain_msg_limit = message_chain.msgs.len();
166        let mut dep_gas_limit = 0;
167        let mut dep_message_limit = 0;
168        let selected_messages_limit = match message_chain.sig_type {
169            Some(SignatureType::Bls) => self.bls_limit as usize,
170            Some(SignatureType::Secp256k1) | Some(SignatureType::Delegated) => {
171                self.secp_limit as usize
172            }
173            None => {
174                // This is a message with no signature type, which is not allowed in the current
175                // implementation. This _should_ never happen, but we handle it gracefully.
176                bail!("Tried to add a message chain with no signature type");
177            }
178        };
179        let selected_messages_limit =
180            selected_messages_limit.min(BLOCK_MESSAGE_LIMIT.saturating_sub(self.len()));
181
182        let mut chain_deps = vec![];
183        let mut cur_chain = message_chain.prev;
184        let _ = message_chain; // drop the mutable borrow to avoid conflicts
185        while let Some(cur_chn) = cur_chain {
186            let node = chains.get(cur_chn).unwrap();
187            if !node.merged {
188                chain_deps.push(cur_chn);
189                chain_gas_limit += node.gas_limit;
190                chain_msg_limit += node.msgs.len();
191                dep_gas_limit += node.gas_limit;
192                dep_message_limit += node.msgs.len();
193                cur_chain = node.prev;
194            } else {
195                break;
196            }
197        }
198
199        // the chain doesn't fit as-is, so trim / invalidate it and return false
200        if chain_gas_limit > self.gas_limit || chain_msg_limit > selected_messages_limit {
201            // it doesn't all fit; now we have to take into account the dependent chains before
202            // making a decision about trimming or invalidating.
203            // if the dependencies exceed the block limits, then we must invalidate the chain
204            // as it can never be included.
205            // Otherwise we can just trim and continue
206            if dep_gas_limit > self.gas_limit || dep_message_limit >= selected_messages_limit {
207                chains.invalidate(chains.get_key_at(idx));
208            } else {
209                // dependencies fit, just trim it
210                chains.trim_msgs_at(
211                    idx,
212                    self.gas_limit.saturating_sub(dep_gas_limit),
213                    selected_messages_limit.saturating_sub(dep_message_limit),
214                    base_fee,
215                );
216            }
217
218            bail!("Chain doesn't fit in the block");
219        }
220        for dep in chain_deps.iter().rev() {
221            let cur_chain = chains.get_mut(*dep);
222            if let Some(node) = cur_chain {
223                node.merged = true;
224                self.extend(node.msgs.clone());
225            } else {
226                bail!("Couldn't find required dependent message chain");
227            }
228        }
229
230        let message_chain = chains
231            .get_mut_at(idx)
232            .context("Couldn't find required message chain")?;
233        message_chain.merged = true;
234        self.extend(message_chain.msgs.clone());
235        self.reduce_gas_limit(chain_gas_limit);
236
237        match message_chain.sig_type {
238            Some(SignatureType::Bls) => {
239                self.reduce_bls_limit(chain_msg_limit as u64);
240            }
241            Some(SignatureType::Secp256k1) | Some(SignatureType::Delegated) => {
242                self.reduce_secp_limit(chain_msg_limit as u64);
243            }
244            None => {
245                // This is a message with no signature type, which is not allowed in the current
246                // implementation. This _should_ never happen, but we handle it gracefully.
247                bail!("Tried to add a message chain with no signature type");
248            }
249        }
250
251        Ok(())
252    }
253
254    fn trim_chain_at(&mut self, chains: &mut Chains, idx: usize, base_fee: &TokenAmount) {
255        let message_chain = match chains.get_at(idx) {
256            Some(message_chain) => message_chain,
257            None => {
258                error!("Tried to trim a message chain that doesn't exist");
259                return;
260            }
261        };
262        let msg_limit = BLOCK_MESSAGE_LIMIT.saturating_sub(self.len());
263        let msg_limit = match message_chain.sig_type {
264            Some(SignatureType::Bls) => std::cmp::min(self.bls_limit, msg_limit as u64),
265            Some(SignatureType::Secp256k1) | Some(SignatureType::Delegated) => {
266                std::cmp::min(self.secp_limit, msg_limit as u64)
267            }
268            _ => {
269                // This is a message with no signature type, which is not allowed in the current
270                // implementation. This _should_ never happen, but we handle it gracefully.
271                error!("Tried to trim a message chain with no signature type");
272                return;
273            }
274        };
275
276        if message_chain.gas_limit > self.gas_limit || message_chain.msgs.len() > msg_limit as usize
277        {
278            chains.trim_msgs_at(idx, self.gas_limit, msg_limit as usize, base_fee);
279        }
280    }
281}
282
283impl<T> MessagePool<T>
284where
285    T: Provider,
286{
287    /// Forest employs a sophisticated algorithm for selecting messages
288    /// for inclusion from the pool, given the ticket quality of a miner.
289    /// This method selects messages for including in a block.
290    pub fn select_messages(&self, ts: &Tipset, tq: f64) -> Result<Vec<SignedMessage>, Error> {
291        let cur_ts = self.current_tipset();
292        // if the ticket quality is high enough that the first block has higher
293        // probability than any other block, then we don't bother with optimal
294        // selection because the first block will always have higher effective
295        // performance. Otherwise we select message optimally based on effective
296        // performance of chains.
297        let mut msgs = if tq > 0.84 {
298            self.select_messages_greedy(&cur_ts, ts)
299        } else {
300            self.select_messages_optimal(&cur_ts, ts, tq)
301        }?;
302
303        if msgs.len() > MAX_BLOCK_MSGS {
304            warn!(
305                "Message selection chose too many messages: {} > {MAX_BLOCK_MSGS}",
306                msgs.len(),
307            );
308            msgs.truncate(MAX_BLOCK_MSGS)
309        }
310
311        Ok(msgs.msgs)
312    }
313
314    fn select_messages_greedy(
315        &self,
316        cur_ts: &Tipset,
317        ts: &Tipset,
318    ) -> Result<SelectedMessages, Error> {
319        let base_fee = self.api.chain_compute_base_fee(ts)?;
320
321        // 0. Load messages from the target tipset; if it is the same as the current
322        // tipset in    the mpool, then this is just the pending messages
323        let mut pending = self.get_pending_messages(cur_ts, ts)?;
324
325        if pending.is_empty() {
326            return Ok(SelectedMessages::default());
327        }
328
329        // 0b. Select all priority messages that fit in the block
330        let selected_msgs = self.select_priority_messages(&mut pending, &base_fee, ts)?;
331
332        // check if block has been filled
333        if selected_msgs.gas_limit < MIN_GAS || selected_msgs.len() >= BLOCK_MESSAGE_LIMIT {
334            return Ok(selected_msgs);
335        }
336
337        // 1. Create a list of dependent message chains with maximal gas reward per
338        // limit consumed
339        let mut chains = Chains::new();
340        for (actor, mset) in pending.into_iter() {
341            create_message_chains(
342                self.api.as_ref(),
343                &actor,
344                &mset,
345                &base_fee,
346                ts,
347                &mut chains,
348                &self.chain_config,
349            )?;
350        }
351
352        Ok(merge_and_trim(
353            &mut chains,
354            selected_msgs,
355            &base_fee,
356            MIN_GAS,
357        ))
358    }
359
360    #[allow(clippy::indexing_slicing)]
361    fn select_messages_optimal(
362        &self,
363        cur_ts: &Tipset,
364        target_tipset: &Tipset,
365        ticket_quality: f64,
366    ) -> Result<SelectedMessages, Error> {
367        let base_fee = self.api.chain_compute_base_fee(target_tipset)?;
368
369        // 0. Load messages from the target tipset; if it is the same as the current
370        // tipset in    the mpool, then this is just the pending messages
371        let mut pending = self.get_pending_messages(cur_ts, target_tipset)?;
372
373        if pending.is_empty() {
374            return Ok(SelectedMessages::default());
375        }
376
377        // 0b. Select all priority messages that fit in the block
378        let mut selected_msgs =
379            self.select_priority_messages(&mut pending, &base_fee, target_tipset)?;
380
381        // check if block has been filled
382        if selected_msgs.gas_limit < MIN_GAS || selected_msgs.len() >= BLOCK_MESSAGE_LIMIT {
383            return Ok(selected_msgs);
384        }
385
386        // 1. Create a list of dependent message chains with maximal gas reward per
387        // limit consumed
388        let mut chains = Chains::new();
389        for (actor, mset) in pending.into_iter() {
390            create_message_chains(
391                self.api.as_ref(),
392                &actor,
393                &mset,
394                &base_fee,
395                target_tipset,
396                &mut chains,
397                &self.chain_config,
398            )?;
399        }
400
401        // 2. Sort the chains
402        chains.sort(false);
403
404        if chains.get_at(0).is_some_and(|it| it.gas_perf < 0.0) {
405            tracing::warn!(
406                "all messages in mpool have non-positive gas performance {}",
407                chains[0].gas_perf
408            );
409            return Ok(selected_msgs);
410        }
411
412        // 3. Partition chains into blocks (without trimming)
413        //    we use the full block_gas_limit (as opposed to the residual `gas_limit`
414        //    from the priority message selection) as we have to account for
415        //    what other block providers are doing
416        let mut next_chain = 0;
417        let mut partitions: Vec<Vec<NodeKey>> = vec![vec![]; MAX_BLOCKS];
418        let mut i = 0;
419        while i < MAX_BLOCKS && next_chain < chains.len() {
420            let mut gas_limit = crate::shim::econ::BLOCK_GAS_LIMIT;
421            let mut msg_limit = BLOCK_MESSAGE_LIMIT;
422            while next_chain < chains.len() {
423                let chain_key = chains.key_vec[next_chain];
424                next_chain += 1;
425                partitions[i].push(chain_key);
426                let chain = chains.get(chain_key).unwrap();
427                let chain_gas_limit = chain.gas_limit;
428                if gas_limit < chain_gas_limit {
429                    break;
430                }
431                gas_limit = gas_limit.saturating_sub(chain_gas_limit);
432                msg_limit = msg_limit.saturating_sub(chain.msgs.len());
433                if gas_limit < MIN_GAS || msg_limit == 0 {
434                    break;
435                }
436            }
437            i += 1;
438        }
439
440        // 4. Compute effective performance for each chain, based on the partition they
441        // fall into    The effective performance is the gas_perf of the chain *
442        // block probability
443        let block_prob = crate::message_pool::block_probabilities(ticket_quality);
444        let mut eff_chains = 0;
445        for i in 0..MAX_BLOCKS {
446            for k in &partitions[i] {
447                if let Some(node) = chains.get_mut(*k) {
448                    node.eff_perf = node.gas_perf * block_prob[i];
449                }
450            }
451            eff_chains += partitions[i].len();
452        }
453
454        // nullify the effective performance of chains that don't fit in any partition
455        for i in eff_chains..chains.len() {
456            if let Some(node) = chains.get_mut_at(i) {
457                node.set_null_effective_perf();
458            }
459        }
460
461        // 5. Re-sort the chains based on effective performance
462        chains.sort_effective();
463
464        // 6. Merge the head chains to produce the list of messages selected for
465        //    inclusion subject to the residual block limits
466        //    When a chain is merged in, all its previous dependent chains *must* also
467        //    be merged in or we'll have a broken block
468        let mut last = chains.len();
469        for i in 0..chains.len() {
470            // did we run out of performing chains?
471            if chains[i].gas_perf < 0.0 {
472                break;
473            }
474
475            // has it already been merged?
476            if chains[i].merged {
477                continue;
478            }
479
480            match selected_msgs.try_to_add_with_deps(i, &mut chains, &base_fee) {
481                Ok(_) => {
482                    // adjust the effective performance for all subsequent chains
483                    if let Some(next_key) = chains[i].next {
484                        let next_node = chains.get_mut(next_key).unwrap();
485                        if next_node.eff_perf > 0.0 {
486                            next_node.eff_perf += next_node.parent_offset;
487                            let mut next_next_key = next_node.next;
488                            while let Some(nnk) = next_next_key {
489                                let (nn_node, prev_perfs) = chains.get_mut_with_prev_eff(nnk);
490                                if let Some(nn_node) = nn_node {
491                                    if nn_node.eff_perf > 0.0 {
492                                        nn_node.set_eff_perf(prev_perfs);
493                                        next_next_key = nn_node.next;
494                                    } else {
495                                        break;
496                                    }
497                                } else {
498                                    break;
499                                }
500                            }
501                        }
502                    }
503
504                    // re-sort to account for already merged chains and effective performance
505                    // adjustments the sort *must* be stable or we end up getting
506                    // negative gasPerfs pushed up.
507                    chains.sort_range_effective(i + 1..);
508
509                    continue;
510                }
511                Err(e) => {
512                    debug!("Failed to add message chain with dependencies: {e:#}");
513                }
514            }
515
516            // we can't fit this chain and its dependencies because of block gasLimit -- we
517            // are at the edge
518            last = i;
519            break;
520        }
521
522        // 7. We have reached the edge of what can fit wholesale; if we still hae
523        // available    gasLimit to pack some more chains, then trim the last
524        // chain and push it down.
525        //
526        // Trimming invalidates subsequent dependent chains so that they can't be selected
527        // as their dependency cannot be (fully) included. We do this in a loop because the blocker
528        // might have been inordinately large and we might have to do it
529        // multiple times to satisfy tail packing.
530        'tail_loop: while selected_msgs.gas_limit >= MIN_GAS && last < chains.len() {
531            if !chains[last].valid {
532                // the chain has been invalidated, we can't use it
533                last += 1;
534                continue;
535            }
536
537            // trim if necessary
538            selected_msgs.trim_chain_at(&mut chains, last, &base_fee);
539
540            // push down if it hasn't been invalidated
541            if chains[last].valid {
542                for i in last..chains.len() - 1 {
543                    if chains[i].cmp_effective(&chains[i + 1]) == Ordering::Greater {
544                        break;
545                    }
546                    chains.key_vec.swap(i, i + 1);
547                }
548            }
549
550            // select the next (valid and fitting) chain and its dependencies for inclusion
551            let lst = last; // to make clippy happy, see: https://rust-lang.github.io/rust-clippy/master/index.html#mut_range_bound
552            for i in lst..chains.len() {
553                let chain = &mut chains[i];
554                // has the chain been invalidated
555                if !chain.valid {
556                    continue;
557                }
558
559                // has it already been merged?
560                if chain.merged {
561                    continue;
562                }
563
564                // if gasPerf < 0 we have no more profitable chains
565                if chain.gas_perf < 0.0 {
566                    break 'tail_loop;
567                }
568
569                match selected_msgs.try_to_add_with_deps(i, &mut chains, &base_fee) {
570                    Ok(_) => continue,
571                    Err(e) => debug!("Failed to add message chain with dependencies: {e:#}"),
572                }
573
574                continue 'tail_loop;
575            }
576
577            // the merge loop ended after processing all the chains and we we probably have
578            // still gas to spare; end the loop.
579            break;
580        }
581
582        // if we have room to spare, pick some random (non-negative) chains to fill
583        // the block
584        // we pick randomly so that we minimize the probability of
585        // duplication among all block producers
586        if selected_msgs.gas_limit >= MIN_GAS && selected_msgs.msgs.len() <= BLOCK_MESSAGE_LIMIT {
587            let pre_random_length = selected_msgs.len();
588
589            chains
590                .key_vec
591                .shuffle(&mut crate::utils::rand::forest_rng());
592
593            for i in 0..chains.len() {
594                if selected_msgs.gas_limit < MIN_GAS || selected_msgs.len() >= BLOCK_MESSAGE_LIMIT {
595                    break;
596                }
597
598                // has it been merged or invalidated?
599                if chains[i].merged || !chains[i].valid {
600                    continue;
601                }
602
603                // is it negative?
604                if chains[i].gas_perf < 0.0 {
605                    continue;
606                }
607
608                if selected_msgs
609                    .try_to_add_with_deps(i, &mut chains, &base_fee)
610                    .is_ok()
611                {
612                    // we added it, continue
613                    continue;
614                }
615
616                if chains[i].valid {
617                    // chain got trimmer on the previous call to `try_to_add_with_deps`, so it can
618                    // now be included.
619                    selected_msgs
620                        .try_to_add_with_deps(i, &mut chains, &base_fee)
621                        .context("Failed to add message chain with dependencies")?;
622                    continue;
623                }
624            }
625
626            if selected_msgs.len() != pre_random_length {
627                tracing::warn!(
628                    "optimal selection failed to pack a block; picked {} messages with random selection",
629                    selected_msgs.len() - pre_random_length
630                );
631            }
632        }
633
634        Ok(selected_msgs)
635    }
636
637    fn get_pending_messages(&self, cur_ts: &Tipset, ts: &Tipset) -> Result<Pending, Error> {
638        let snapshot = self.pending.snapshot();
639        let mut result: Pending = HashMap::with_capacity(snapshot.len());
640        for (a, mset) in snapshot {
641            result.insert(a, mset.msgs);
642        }
643
644        if cur_ts.epoch() == ts.epoch() && cur_ts == ts {
645            return Ok(result);
646        }
647
648        // Run head change to do reorg detection
649        run_head_change(
650            self.api.as_ref(),
651            &self.caches.bls_sig,
652            cur_ts.clone(),
653            ts.clone(),
654            &mut result,
655        )?;
656
657        Ok(result)
658    }
659
660    fn select_priority_messages(
661        &self,
662        pending: &mut Pending,
663        base_fee: &TokenAmount,
664        ts: &Tipset,
665    ) -> Result<SelectedMessages, Error> {
666        let result = Vec::with_capacity(self.config.size_limit_low() as usize);
667        let gas_limit = crate::shim::econ::BLOCK_GAS_LIMIT;
668        let min_gas = MIN_GAS;
669
670        // 1. Get priority actor chains
671        let priority = self.config.priority_addrs();
672        let mut chains = Chains::new();
673        for actor in priority.iter() {
674            // remove actor from pending set as we are processing these messages.
675            if let Some(mset) = pending.remove(actor) {
676                // create chains for the priority actor
677                create_message_chains(
678                    self.api.as_ref(),
679                    actor,
680                    &mset,
681                    base_fee,
682                    ts,
683                    &mut chains,
684                    &self.chain_config,
685                )?;
686            }
687        }
688
689        if chains.is_empty() {
690            return Ok(SelectedMessages::new(Vec::new(), gas_limit));
691        }
692
693        Ok(merge_and_trim(
694            &mut chains,
695            SelectedMessages::new(result, gas_limit),
696            base_fee,
697            min_gas,
698        ))
699    }
700}
701
702/// Returns merged and trimmed messages with the gas limit
703#[allow(clippy::indexing_slicing)]
704fn merge_and_trim(
705    chains: &mut Chains,
706    mut selected_msgs: SelectedMessages,
707    base_fee: &TokenAmount,
708    min_gas: u64,
709) -> SelectedMessages {
710    if chains.is_empty() {
711        return selected_msgs;
712    }
713
714    // 2. Sort the chains
715    chains.sort(true);
716
717    let first_chain_gas_perf = chains[0].gas_perf;
718
719    if !chains.is_empty() && first_chain_gas_perf < 0.0 {
720        warn!(
721            "all priority messages in mpool have negative gas performance bestGasPerf: {}",
722            first_chain_gas_perf
723        );
724        return selected_msgs;
725    }
726
727    // 3. Merge chains until the block limit, as long as they have non-negative gas
728    // performance
729    let mut last = chains.len();
730    for i in 0..chains.len() {
731        let node = &chains[i];
732
733        if node.gas_perf < 0.0 {
734            break;
735        }
736
737        if selected_msgs.try_to_add(node.clone()).is_ok() {
738            // there was room, we added the chain, keep going
739            continue;
740        }
741
742        // we can't fit this chain because of block gas limit -- we are at the edge
743        last = i;
744        break;
745    }
746
747    'tail_loop: while selected_msgs.gas_limit >= min_gas && last < chains.len() {
748        // trim, discard negative performing messages
749        selected_msgs.trim_chain_at(chains, last, base_fee);
750
751        // push down if it hasn't been invalidated
752        let node = &chains[last];
753        if node.valid {
754            for i in last..chains.len() - 1 {
755                // slot_chains
756                let cur_node = &chains[i];
757                let next_node = &chains[i + 1];
758                if cur_node.compare(next_node) == Ordering::Greater {
759                    break;
760                }
761
762                chains.key_vec.swap(i, i + 1);
763            }
764        }
765
766        // select the next (valid and fitting) chain for inclusion
767        let lst = last; // to make clippy happy, see: https://rust-lang.github.io/rust-clippy/master/index.html#mut_range_bound
768        for i in lst..chains.len() {
769            let chain = chains[i].clone();
770            if !chain.valid {
771                continue;
772            }
773
774            // if gas_perf < 0 then we have no more profitable chains
775            if chain.gas_perf < 0.0 {
776                break 'tail_loop;
777            }
778
779            // does it fit in the block?
780            if selected_msgs.try_to_add(chain).is_ok() {
781                // there was room, we added the chain, keep going
782                continue;
783            }
784
785            // this chain needs to be trimmed
786            last += i;
787            continue 'tail_loop;
788        }
789
790        break;
791    }
792
793    selected_msgs
794}
795
796/// Like `head_change`, except it simulates a head change call and doesn't change the state of the `MessagePool`.
797// This logic should probably be implemented in the ChainStore. It handles
798// reorgs.
799pub(in crate::message_pool) fn run_head_change<T>(
800    api: &T,
801    bls_sig_cache: &SizeTrackingCache<CidWrapper, Signature>,
802    from: Tipset,
803    to: Tipset,
804    rmsgs: &mut HashMap<Address, HashMap<u64, SignedMessage>>,
805) -> Result<(), Error>
806where
807    T: Provider,
808{
809    let mut left = from;
810    let mut right = to;
811    let mut left_chain = Vec::new();
812    let mut right_chain = Vec::new();
813    while left != right {
814        if left.epoch() > right.epoch() {
815            left_chain.push(left.clone());
816            let par = api.load_tipset(left.parents())?;
817            left = par;
818        } else {
819            right_chain.push(right.clone());
820            let par = api.load_tipset(right.parents())?;
821            right = par;
822        }
823    }
824    for ts in left_chain {
825        let mut msgs: Vec<SignedMessage> = Vec::new();
826        for block in ts.block_headers() {
827            let (umsg, smsgs) = api.messages_for_block(block)?;
828            msgs.extend(smsgs);
829            for msg in umsg {
830                let msg_cid = msg.cid();
831                if let Ok(smsg) = recover_sig(bls_sig_cache, msg) {
832                    msgs.push(smsg);
833                } else {
834                    tracing::debug!("could not recover signature for bls message {msg_cid}");
835                }
836            }
837        }
838        for msg in msgs {
839            utils::add_to_selected_msgs(msg, rmsgs);
840        }
841    }
842
843    for ts in right_chain {
844        for b in ts.block_headers() {
845            let (msgs, smsgs) = api.messages_for_block(b)?;
846
847            for msg in smsgs {
848                utils::remove_from_selected_msgs(&msg.from(), msg.sequence(), rmsgs);
849            }
850            for msg in msgs {
851                utils::remove_from_selected_msgs(&msg.from, msg.sequence, rmsgs);
852            }
853        }
854    }
855    Ok(())
856}
857
858#[cfg(test)]
859mod test_selection {
860    use std::sync::Arc;
861
862    use super::*;
863    use crate::key_management::{KeyStore, KeyStoreConfig, Wallet};
864    use crate::message_pool::msgpool::{
865        test_provider::{TestApi, mock_block},
866        tests::{create_fake_smsg, create_smsg},
867    };
868    use crate::shim::crypto::SignatureType;
869    use crate::shim::econ::BLOCK_GAS_LIMIT;
870    use tokio::task::JoinSet;
871
872    const TEST_GAS_LIMIT: i64 = 6955002;
873
874    fn make_test_mpool(joinset: &mut JoinSet<anyhow::Result<()>>) -> MessagePool<TestApi> {
875        let tma = TestApi::default();
876        let (tx, _rx) = flume::bounded(50);
877        MessagePool::new(tma, tx, Default::default(), Arc::default(), joinset).unwrap()
878    }
879
880    /// Creates a tipset with a mocked block and performs a head change to setup the
881    /// [`MessagePool`] for testing.
882    async fn mock_tipset(mpool: &MessagePool<TestApi>) -> Tipset {
883        let b1 = mock_block(1, 1);
884        let ts = Tipset::from(&b1);
885        mpool
886            .apply_head_change(Vec::new(), vec![Tipset::from(b1)])
887            .await
888            .unwrap();
889        ts
890    }
891
892    #[tokio::test]
893    async fn basic_message_selection() {
894        let mut joinset = JoinSet::new();
895        let mpool = make_test_mpool(&mut joinset);
896
897        let ks1 = KeyStore::new(KeyStoreConfig::Memory).unwrap();
898        let mut w1 = Wallet::new(ks1);
899        let a1 = w1.generate_addr(SignatureType::Secp256k1).unwrap();
900
901        let ks2 = KeyStore::new(KeyStoreConfig::Memory).unwrap();
902        let mut w2 = Wallet::new(ks2);
903        let a2 = w2.generate_addr(SignatureType::Secp256k1).unwrap();
904
905        let ts = mock_tipset(&mpool).await;
906
907        mpool
908            .api
909            .set_state_balance_raw(&a1, TokenAmount::from_whole(1));
910        mpool
911            .api
912            .set_state_balance_raw(&a2, TokenAmount::from_whole(1));
913
914        // we create 10 messages from each actor to another, with the first actor paying
915        // higher gas prices than the second; we expect message selection to
916        // order his messages first
917        for i in 0..10 {
918            let m = create_smsg(&a2, &a1, &mut w1, i, TEST_GAS_LIMIT, 2 * i + 1);
919            mpool.add(m).await.unwrap();
920        }
921        for i in 0..10 {
922            let m = create_smsg(&a1, &a2, &mut w2, i, TEST_GAS_LIMIT, i + 1);
923            mpool.add(m).await.unwrap();
924        }
925
926        let msgs = mpool.select_messages(&ts, 1.0).unwrap();
927
928        assert_eq!(msgs.len(), 20, "Expected 20 messages, got {}", msgs.len());
929
930        let mut next_nonce = 0;
931        for (i, msg) in msgs.iter().enumerate().take(10) {
932            assert_eq!(
933                msg.from(),
934                a1,
935                "first 10 returned messages should be from actor a1 {i}",
936            );
937            assert_eq!(msg.sequence(), next_nonce, "nonce should be in order");
938            next_nonce += 1;
939        }
940
941        next_nonce = 0;
942        for (i, msg) in msgs.iter().enumerate().take(20).skip(10) {
943            assert_eq!(
944                msg.from(),
945                a2,
946                "next 10 returned messages should be from actor a2 {i}",
947            );
948            assert_eq!(msg.sequence(), next_nonce, "nonce should be in order");
949            next_nonce += 1;
950        }
951
952        // now we make a block with all the messages and advance the chain
953        let b2 = mpool.api.next_block();
954        mpool.api.set_block_messages(&b2, msgs);
955        mpool
956            .apply_head_change(Vec::new(), vec![Tipset::from(b2)])
957            .await
958            .unwrap();
959
960        // we should now have no pending messages in the MessagePool
961        let remaining = mpool.pending.snapshot();
962        assert!(
963            remaining.is_empty(),
964            "Expected no pending messages, but got {}",
965            remaining.len()
966        );
967
968        // create a block and advance the chain without applying to the mpool
969        let mut msgs = Vec::with_capacity(20);
970        for i in 10..20 {
971            msgs.push(create_smsg(&a2, &a1, &mut w1, i, TEST_GAS_LIMIT, 2 * i + 1));
972            msgs.push(create_smsg(&a1, &a2, &mut w2, i, TEST_GAS_LIMIT, i + 1));
973        }
974        let b3 = mpool.api.next_block();
975        let ts3 = Tipset::from(&b3);
976        mpool.api.set_block_messages(&b3, msgs);
977
978        // Set state sequence to 20 so that nonces 20..30 don't exceed the nonce gap.
979        mpool.api.set_state_sequence(&a1, 20);
980        mpool.api.set_state_sequence(&a2, 20);
981
982        // now create another set of messages and add them to the mpool
983        for i in 20..30 {
984            mpool
985                .add(create_smsg(
986                    &a2,
987                    &a1,
988                    &mut w1,
989                    i,
990                    TEST_GAS_LIMIT,
991                    2 * i + 200,
992                ))
993                .await
994                .unwrap();
995            mpool
996                .add(create_smsg(&a1, &a2, &mut w2, i, TEST_GAS_LIMIT, i + 1))
997                .await
998                .unwrap();
999        }
1000        // select messages in the last tipset; this should include the missed messages
1001        // as well as the last messages we added, with the first actor's
1002        // messages first first we need to update the nonce on the api
1003        mpool.api.set_state_sequence(&a1, 10);
1004        mpool.api.set_state_sequence(&a2, 10);
1005        let msgs = mpool.select_messages(&ts3, 1.0).unwrap();
1006
1007        assert_eq!(
1008            msgs.len(),
1009            20,
1010            "Expected 20 messages, but got {}",
1011            msgs.len()
1012        );
1013
1014        let mut next_nonce = 20;
1015        for msg in msgs.iter().take(10) {
1016            assert_eq!(
1017                msg.from(),
1018                a1,
1019                "first 10 returned messages should be from actor a1"
1020            );
1021            assert_eq!(msg.sequence(), next_nonce, "nonce should be in order");
1022            next_nonce += 1;
1023        }
1024        next_nonce = 20;
1025        for msg in msgs.iter().take(20).skip(10) {
1026            assert_eq!(
1027                msg.from(),
1028                a2,
1029                "next 10 returned messages should be from actor a2"
1030            );
1031            assert_eq!(msg.sequence(), next_nonce, "nonce should be in order");
1032            next_nonce += 1;
1033        }
1034    }
1035
1036    #[tokio::test]
1037    async fn select_messages_on_non_current_tipset_should_not_update_mpool_state() {
1038        use crate::message_pool::msgpool::msg_pool::TrustPolicy;
1039        use crate::message_pool::msgpool::msg_set::StrictnessPolicy;
1040        use crate::shim::message::Message as ShimMessage;
1041        use tokio::sync::broadcast::error::TryRecvError;
1042
1043        let mut joinset = JoinSet::new();
1044        let mpool = make_test_mpool(&mut joinset);
1045
1046        let id_addr = Address::new_id(1000);
1047        let key_addr = Address::new_bls(&[3u8; 48]).unwrap();
1048        mpool.api.set_key_address_mapping(&id_addr, &key_addr);
1049        mpool
1050            .api
1051            .set_state_balance_raw(&key_addr, TokenAmount::from_whole(1));
1052        mpool.api.set_state_sequence(&key_addr, 0);
1053
1054        // Establish a current head registered with the provider so the
1055        // simulated head change can walk parents back to it.
1056        let b1 = mpool.api.next_block();
1057        mpool.api.set_block_messages(&b1, vec![]);
1058        let head = Tipset::from(&b1);
1059        mpool
1060            .apply_head_change(Vec::new(), vec![head.clone()])
1061            .await
1062            .unwrap();
1063
1064        let pending_msg = SignedMessage::mock_bls_signed_message(ShimMessage {
1065            from: id_addr,
1066            sequence: 0,
1067            gas_limit: TEST_GAS_LIMIT as u64,
1068            gas_fee_cap: TokenAmount::from_atto(200),
1069            gas_premium: TokenAmount::from_atto(100),
1070            ..ShimMessage::default()
1071        });
1072        mpool
1073            .add_to_pool_unchecked(
1074                &head,
1075                pending_msg.clone(),
1076                TrustPolicy::Trusted,
1077                StrictnessPolicy::Relaxed,
1078            )
1079            .await
1080            .unwrap();
1081
1082        let before = mpool.pending.snapshot();
1083        assert!(
1084            before.contains_key(&key_addr),
1085            "precondition: message is pending under the resolved key address"
1086        );
1087
1088        // A *non-current* child tipset whose block applies that very message,
1089        // carrying the `f0` `from` as on-chain messages do.
1090        let b2 = mpool.api.next_block();
1091        let ts2 = Tipset::from(&b2);
1092        mpool.api.set_block_messages(&b2, vec![pending_msg.clone()]);
1093
1094        // Subscribe AFTER the insert so we only observe events emitted by the
1095        // selection call below.
1096        let mut rx = mpool.pending.subscriber().subscribe();
1097
1098        // Select against the non-current tipset.
1099        let _ = mpool.select_messages(&ts2, 1.0).unwrap();
1100
1101        let after = mpool.pending.snapshot();
1102        assert!(
1103            after.contains_key(&key_addr),
1104            "selecting for a non-current tipset must not remove live pending messages"
1105        );
1106        assert_eq!(
1107            before.len(),
1108            after.len(),
1109            "the live pending pool size must be unchanged by selection"
1110        );
1111        assert_eq!(
1112            after
1113                .get(&key_addr)
1114                .and_then(|mset| mset.msgs.get(&0))
1115                .map(|m| m.cid()),
1116            Some(pending_msg.cid()),
1117            "the exact pending message must survive at its nonce"
1118        );
1119        assert!(
1120            matches!(rx.try_recv(), Err(TryRecvError::Empty)),
1121            "a read-only selection simulation must not emit any MpoolUpdate events"
1122        );
1123    }
1124
1125    #[tokio::test]
1126    async fn message_selection_trimming_gas() {
1127        let mut joinset = JoinSet::new();
1128        let mpool = make_test_mpool(&mut joinset);
1129        let ts = mock_tipset(&mpool).await;
1130        let api = mpool.api.clone();
1131
1132        let ks1 = KeyStore::new(KeyStoreConfig::Memory).unwrap();
1133        let mut w1 = Wallet::new(ks1);
1134        let a1 = w1.generate_addr(SignatureType::Secp256k1).unwrap();
1135
1136        let ks2 = KeyStore::new(KeyStoreConfig::Memory).unwrap();
1137        let mut w2 = Wallet::new(ks2);
1138        let a2 = w2.generate_addr(SignatureType::Secp256k1).unwrap();
1139
1140        api.set_state_balance_raw(&a1, TokenAmount::from_whole(1));
1141        api.set_state_balance_raw(&a2, TokenAmount::from_whole(1));
1142
1143        let nmsgs = (crate::shim::econ::BLOCK_GAS_LIMIT as i64 / TEST_GAS_LIMIT) + 1;
1144
1145        // make many small chains for the two actors
1146        for i in 0..nmsgs {
1147            let bias = (nmsgs - i) / 3;
1148            let m = create_fake_smsg(
1149                &mpool,
1150                &a2,
1151                &a1,
1152                i as u64,
1153                TEST_GAS_LIMIT,
1154                (1 + i % 3 + bias) as u64,
1155            );
1156            mpool.add(m).await.unwrap();
1157            let m = create_fake_smsg(
1158                &mpool,
1159                &a1,
1160                &a2,
1161                i as u64,
1162                TEST_GAS_LIMIT,
1163                (1 + i % 3 + bias) as u64,
1164            );
1165            mpool.add(m).await.unwrap();
1166        }
1167
1168        let msgs = mpool.select_messages(&ts, 1.0).unwrap();
1169
1170        let expected = crate::shim::econ::BLOCK_GAS_LIMIT as i64 / TEST_GAS_LIMIT;
1171        assert_eq!(msgs.len(), expected as usize);
1172        let m_gas_limit = msgs.iter().map(|m| m.gas_limit()).sum::<u64>();
1173        assert!(m_gas_limit <= crate::shim::econ::BLOCK_GAS_LIMIT);
1174    }
1175
1176    #[tokio::test]
1177    async fn message_selection_trimming_msgs_basic() {
1178        let mut joinset = JoinSet::new();
1179        let mpool = make_test_mpool(&mut joinset);
1180        let ts = mock_tipset(&mpool).await;
1181        let api = mpool.api.clone();
1182
1183        let keystore = KeyStore::new(KeyStoreConfig::Memory).unwrap();
1184        let mut wallet = Wallet::new(keystore);
1185        let address = wallet.generate_addr(SignatureType::Secp256k1).unwrap();
1186
1187        api.set_state_balance_raw(&address, TokenAmount::from_whole(1));
1188
1189        // create a larger than selectable chain
1190        for i in 0..BLOCK_MESSAGE_LIMIT {
1191            let msg = create_fake_smsg(&mpool, &address, &address, i as u64, 200_000, 100);
1192            mpool.add(msg).await.unwrap();
1193        }
1194
1195        let msgs = mpool.select_messages(&ts, 1.0).unwrap();
1196        assert_eq!(
1197            msgs.len(),
1198            CBOR_GEN_LIMIT,
1199            "Expected {CBOR_GEN_LIMIT} messages, got {}",
1200            msgs.len()
1201        );
1202
1203        // check that the gas limit is not exceeded
1204        let m_gas_limit = msgs.iter().map(|m| m.gas_limit()).sum::<u64>();
1205        assert!(
1206            m_gas_limit <= BLOCK_GAS_LIMIT,
1207            "Selected messages gas limit {m_gas_limit} exceeds block gas limit {BLOCK_GAS_LIMIT}",
1208        );
1209    }
1210
1211    #[tokio::test]
1212    async fn message_selection_trimming_msgs_two_senders() {
1213        let mut joinset = JoinSet::new();
1214        let mpool = make_test_mpool(&mut joinset);
1215        let ts = mock_tipset(&mpool).await;
1216        let api = mpool.api.clone();
1217
1218        let keystore_1 = KeyStore::new(KeyStoreConfig::Memory).unwrap();
1219        let mut wallet_1 = Wallet::new(keystore_1);
1220        let address_1 = wallet_1.generate_addr(SignatureType::Secp256k1).unwrap();
1221
1222        let keystore_2 = KeyStore::new(KeyStoreConfig::Memory).unwrap();
1223        let mut wallet_2 = Wallet::new(keystore_2);
1224        let address_2 = wallet_2.generate_addr(SignatureType::Bls).unwrap();
1225
1226        api.set_state_balance_raw(&address_1, TokenAmount::from_whole(1));
1227        api.set_state_balance_raw(&address_2, TokenAmount::from_whole(1));
1228
1229        // create 2 larger than selectable chains
1230        for i in 0..BLOCK_MESSAGE_LIMIT {
1231            let msg = create_smsg(
1232                &address_2,
1233                &address_1,
1234                &mut wallet_1,
1235                i as u64,
1236                300_000,
1237                100,
1238            );
1239            mpool.add(msg).await.unwrap();
1240            // higher has price, those should be preferred and fill the block up to
1241            // the [`CBOR_GEN_LIMIT`] messages.
1242            let msg = create_smsg(
1243                &address_1,
1244                &address_2,
1245                &mut wallet_2,
1246                i as u64,
1247                300_000,
1248                1000,
1249            );
1250            mpool.add(msg).await.unwrap();
1251        }
1252        let msgs = mpool.select_messages(&ts, 1.0).unwrap();
1253        // check that the gas limit is not exceeded
1254        let m_gas_limit = msgs.iter().map(|m| m.gas_limit()).sum::<u64>();
1255        assert!(
1256            m_gas_limit <= BLOCK_GAS_LIMIT,
1257            "Selected messages gas limit {m_gas_limit} exceeds block gas limit {BLOCK_GAS_LIMIT}",
1258        );
1259        let bls_msgs = msgs.iter().filter(|m| m.is_bls()).count();
1260        assert_eq!(
1261            CBOR_GEN_LIMIT, bls_msgs,
1262            "Expected {CBOR_GEN_LIMIT} bls messages, got {bls_msgs}."
1263        );
1264        assert_eq!(
1265            msgs.len(),
1266            BLOCK_MESSAGE_LIMIT,
1267            "Expected {BLOCK_MESSAGE_LIMIT} messages, got {}",
1268            msgs.len()
1269        );
1270    }
1271
1272    #[tokio::test]
1273    async fn message_selection_trimming_msgs_two_senders_complex() {
1274        let mut joinset = JoinSet::new();
1275        let mpool = make_test_mpool(&mut joinset);
1276        let ts = mock_tipset(&mpool).await;
1277        let api = mpool.api.clone();
1278
1279        let keystore_1 = KeyStore::new(KeyStoreConfig::Memory).unwrap();
1280        let mut wallet_1 = Wallet::new(keystore_1);
1281        let address_1 = wallet_1.generate_addr(SignatureType::Secp256k1).unwrap();
1282
1283        let keystore_2 = KeyStore::new(KeyStoreConfig::Memory).unwrap();
1284        let mut wallet_2 = Wallet::new(keystore_2);
1285        let address_2 = wallet_2.generate_addr(SignatureType::Bls).unwrap();
1286
1287        api.set_state_balance_raw(&address_1, TokenAmount::from_whole(1));
1288        api.set_state_balance_raw(&address_2, TokenAmount::from_whole(1));
1289
1290        // create two almost max-length chains of equal value
1291        let mut counter = 0;
1292        for i in 0..CBOR_GEN_LIMIT {
1293            counter += 1;
1294            let msg = create_smsg(
1295                &address_2,
1296                &address_1,
1297                &mut wallet_1,
1298                i as u64,
1299                300_000,
1300                100,
1301            );
1302            mpool.add(msg).await.unwrap();
1303            // higher has price, those should be preferred and fill the block up to
1304            // the [`CBOR_GEN_LIMIT`] messages.
1305            let msg = create_smsg(
1306                &address_1,
1307                &address_2,
1308                &mut wallet_2,
1309                i as u64,
1310                300_000,
1311                100,
1312            );
1313            mpool.add(msg).await.unwrap();
1314        }
1315
1316        // address_1 8192th message is worth more than address_2 8192th message
1317        let msg = create_smsg(
1318            &address_2,
1319            &address_1,
1320            &mut wallet_1,
1321            counter as u64,
1322            300_000,
1323            1000,
1324        );
1325        mpool.add(msg).await.unwrap();
1326
1327        let msg = create_smsg(
1328            &address_1,
1329            &address_2,
1330            &mut wallet_2,
1331            counter as u64,
1332            300_000,
1333            100,
1334        );
1335        mpool.add(msg).await.unwrap();
1336
1337        counter += 1;
1338
1339        // address 2 (uneselectable) message is worth so much!
1340        let msg = create_smsg(
1341            &address_2,
1342            &address_1,
1343            &mut wallet_1,
1344            counter as u64,
1345            400_000,
1346            1_000_000,
1347        );
1348        mpool.add(msg).await.unwrap();
1349
1350        let msgs = mpool.select_messages(&ts, 1.0).unwrap();
1351        // check that the gas limit is not exceeded
1352        let m_gas_limit = msgs.iter().map(|m| m.gas_limit()).sum::<u64>();
1353        assert!(
1354            m_gas_limit <= BLOCK_GAS_LIMIT,
1355            "Selected messages gas limit {m_gas_limit} exceeds block gas limit {BLOCK_GAS_LIMIT}",
1356        );
1357        // We should have taken the SECP chain from address_1.
1358        let secps_len = msgs.iter().filter(|m| m.is_secp256k1()).count();
1359        assert_eq!(
1360            CBOR_GEN_LIMIT, secps_len,
1361            "Expected {CBOR_GEN_LIMIT} secp messages, got {secps_len}."
1362        );
1363        // The remaining messages should be BLS messages.
1364        assert_eq!(
1365            msgs.len(),
1366            BLOCK_MESSAGE_LIMIT,
1367            "Expected {BLOCK_MESSAGE_LIMIT} messages, got {}",
1368            msgs.len()
1369        );
1370    }
1371
1372    #[tokio::test]
1373    async fn message_selection_priority() {
1374        let ks1 = KeyStore::new(KeyStoreConfig::Memory).unwrap();
1375        let mut w1 = Wallet::new(ks1);
1376        let a1 = w1.generate_addr(SignatureType::Secp256k1).unwrap();
1377
1378        let ks2 = KeyStore::new(KeyStoreConfig::Memory).unwrap();
1379        let mut w2 = Wallet::new(ks2);
1380        let a2 = w2.generate_addr(SignatureType::Secp256k1).unwrap();
1381
1382        let cfg = crate::message_pool::config::MpoolConfig {
1383            priority_addrs: vec![a1],
1384            ..Default::default()
1385        };
1386
1387        let mut joinset = JoinSet::new();
1388        let (tx, _rx) = flume::bounded(50);
1389        let mpool =
1390            MessagePool::new(TestApi::default(), tx, cfg, Arc::default(), &mut joinset).unwrap();
1391        let ts = mock_tipset(&mpool).await;
1392        let api = mpool.api.clone();
1393
1394        // let gas_limit = 6955002;
1395        api.set_state_balance_raw(&a1, TokenAmount::from_whole(1));
1396        api.set_state_balance_raw(&a2, TokenAmount::from_whole(1));
1397
1398        let nmsgs = 10;
1399
1400        // make many small chains for the two actors
1401        for i in 0..nmsgs {
1402            let bias = (nmsgs - i) / 3;
1403            let m = create_smsg(
1404                &a2,
1405                &a1,
1406                &mut w1,
1407                i as u64,
1408                TEST_GAS_LIMIT,
1409                (1 + i % 3 + bias) as u64,
1410            );
1411            mpool.add(m).await.unwrap();
1412            let m = create_smsg(
1413                &a1,
1414                &a2,
1415                &mut w2,
1416                i as u64,
1417                TEST_GAS_LIMIT,
1418                (1 + i % 3 + bias) as u64,
1419            );
1420            mpool.add(m).await.unwrap();
1421        }
1422
1423        let msgs = mpool.select_messages(&ts, 1.0).unwrap();
1424
1425        assert_eq!(msgs.len(), 20);
1426
1427        let mut next_nonce = 0;
1428        for msg in msgs.iter().take(10) {
1429            assert_eq!(
1430                msg.from(),
1431                a1,
1432                "first 10 returned messages should be from actor a1"
1433            );
1434            assert_eq!(msg.sequence(), next_nonce, "nonce should be in order");
1435            next_nonce += 1;
1436        }
1437        next_nonce = 0;
1438        for msg in msgs.iter().take(20).skip(10) {
1439            assert_eq!(
1440                msg.from(),
1441                a2,
1442                "next 10 returned messages should be from actor a2"
1443            );
1444            assert_eq!(msg.sequence(), next_nonce, "nonce should be in order");
1445            next_nonce += 1;
1446        }
1447    }
1448
1449    #[tokio::test]
1450    async fn test_optimal_msg_selection1() {
1451        // this test uses just a single actor sending messages with a low tq
1452        // the chain dependent merging algorithm should pick messages from the actor
1453        // from the start
1454        let mut joinset = JoinSet::new();
1455        let mpool = make_test_mpool(&mut joinset);
1456        let ts = mock_tipset(&mpool).await;
1457        let api = mpool.api.clone();
1458
1459        // create two actors
1460        let mut w1 = Wallet::new(KeyStore::new(KeyStoreConfig::Memory).unwrap());
1461        let a1 = w1.generate_addr(SignatureType::Secp256k1).unwrap();
1462        let mut w2 = Wallet::new(KeyStore::new(KeyStoreConfig::Memory).unwrap());
1463        let a2 = w2.generate_addr(SignatureType::Secp256k1).unwrap();
1464
1465        api.set_state_balance_raw(&a1, TokenAmount::from_whole(1));
1466        api.set_state_balance_raw(&a2, TokenAmount::from_whole(1));
1467
1468        let n_msgs = 10 * crate::shim::econ::BLOCK_GAS_LIMIT as i64 / TEST_GAS_LIMIT;
1469
1470        // we create n_msgs messages from each actor to another, with the first actor paying
1471        // higher gas prices than the second; we expect message selection to
1472        // order his messages first
1473        for i in 0..(n_msgs as usize) {
1474            let bias = (n_msgs as usize - i) / 3;
1475            let m = create_fake_smsg(
1476                &mpool,
1477                &a2,
1478                &a1,
1479                i as u64,
1480                TEST_GAS_LIMIT,
1481                (1 + i % 3 + bias) as u64,
1482            );
1483            mpool.add(m).await.unwrap();
1484        }
1485
1486        let msgs = mpool.select_messages(&ts, 0.25).unwrap();
1487
1488        let expected_msgs = crate::shim::econ::BLOCK_GAS_LIMIT as i64 / TEST_GAS_LIMIT;
1489
1490        assert_eq!(msgs.len(), expected_msgs as usize);
1491
1492        for (next_nonce, m) in msgs.into_iter().enumerate() {
1493            assert_eq!(m.from(), a1, "Expected message from a1");
1494            assert_eq!(
1495                m.message().sequence,
1496                next_nonce as u64,
1497                "expected nonce {} but got {}",
1498                next_nonce,
1499                m.message().sequence
1500            );
1501        }
1502    }
1503
1504    #[tokio::test]
1505    async fn test_optimal_msg_selection2() {
1506        let mut joinset = JoinSet::new();
1507        // this test uses two actors sending messages to each other, with the first
1508        // actor paying (much) higher gas premium than the second.
1509        // We select with a low ticket quality; the chain dependent merging algorithm
1510        // should pick messages from the second actor from the start
1511        let mpool = make_test_mpool(&mut joinset);
1512        let ts = mock_tipset(&mpool).await;
1513        let api = mpool.api.clone();
1514
1515        // create two actors
1516        let mut w1 = Wallet::new(KeyStore::new(KeyStoreConfig::Memory).unwrap());
1517        let a1 = w1.generate_addr(SignatureType::Secp256k1).unwrap();
1518        let mut w2 = Wallet::new(KeyStore::new(KeyStoreConfig::Memory).unwrap());
1519        let a2 = w2.generate_addr(SignatureType::Secp256k1).unwrap();
1520
1521        api.set_state_balance_raw(&a1, TokenAmount::from_whole(1)); // in FIL
1522        api.set_state_balance_raw(&a2, TokenAmount::from_whole(1)); // in FIL
1523
1524        let n_msgs = 5 * crate::shim::econ::BLOCK_GAS_LIMIT as i64 / TEST_GAS_LIMIT;
1525        for i in 0..n_msgs as usize {
1526            let bias = (n_msgs as usize - i) / 3;
1527            let m = create_fake_smsg(
1528                &mpool,
1529                &a2,
1530                &a1,
1531                i as u64,
1532                TEST_GAS_LIMIT,
1533                (200000 + i % 3 + bias) as u64,
1534            );
1535            mpool.add(m).await.unwrap();
1536            let m = create_fake_smsg(
1537                &mpool,
1538                &a1,
1539                &a2,
1540                i as u64,
1541                TEST_GAS_LIMIT,
1542                (190000 + i % 3 + bias) as u64,
1543            );
1544            mpool.add(m).await.unwrap();
1545        }
1546
1547        let msgs = mpool.select_messages(&ts, 0.1).unwrap();
1548
1549        let expected_msgs = crate::shim::econ::BLOCK_GAS_LIMIT as i64 / TEST_GAS_LIMIT;
1550        assert_eq!(
1551            msgs.len(),
1552            expected_msgs as usize,
1553            "Expected {} messages, but got {}",
1554            expected_msgs,
1555            msgs.len()
1556        );
1557
1558        let mut n_from1 = 0;
1559        let mut n_from2 = 0;
1560        let mut next_nonce1 = 0;
1561        let mut next_nonce2 = 0;
1562
1563        for m in msgs {
1564            if m.from() == a1 {
1565                if m.message.sequence != next_nonce1 {
1566                    panic!(
1567                        "Expected nonce {}, but got {}",
1568                        next_nonce1, m.message.sequence
1569                    );
1570                }
1571                next_nonce1 += 1;
1572                n_from1 += 1;
1573            } else {
1574                if m.message.sequence != next_nonce2 {
1575                    panic!(
1576                        "Expected nonce {}, but got {}",
1577                        next_nonce2, m.message.sequence
1578                    );
1579                }
1580                next_nonce2 += 1;
1581                n_from2 += 1;
1582            }
1583        }
1584
1585        if n_from1 > n_from2 {
1586            panic!("Expected more msgs from a2 than a1");
1587        }
1588    }
1589
1590    #[tokio::test]
1591    async fn test_optimal_msg_selection3() {
1592        let mut joinset = JoinSet::new();
1593        // this test uses 10 actors sending a block of messages to each other, with the
1594        // the first actors paying higher gas premium than the subsequent
1595        // actors. We select with a low ticket quality; the chain dependent
1596        // merging algorithm should pick messages from the median actor from the
1597        // start
1598        let mpool = make_test_mpool(&mut joinset);
1599        let ts = mock_tipset(&mpool).await;
1600        let api = mpool.api.clone();
1601
1602        let n_actors = 10;
1603
1604        let mut actors = vec![];
1605        let mut wallets = vec![];
1606
1607        for _ in 0..n_actors {
1608            let mut wallet = Wallet::new(KeyStore::new(KeyStoreConfig::Memory).unwrap());
1609            let actor = wallet.generate_addr(SignatureType::Secp256k1).unwrap();
1610
1611            actors.push(actor);
1612            wallets.push(wallet);
1613        }
1614
1615        for a in &mut actors {
1616            api.set_state_balance_raw(a, TokenAmount::from_whole(1));
1617        }
1618
1619        let n_msgs = 1 + crate::shim::econ::BLOCK_GAS_LIMIT as i64 / TEST_GAS_LIMIT;
1620        for i in 0..n_msgs {
1621            for j in 0..n_actors {
1622                let premium =
1623                    500000 + 10000 * (n_actors - j) + (n_msgs + 2 - i) / (30 * n_actors) + i % 3;
1624                let m = create_fake_smsg(
1625                    &mpool,
1626                    &actors[j as usize],
1627                    &actors[j as usize],
1628                    i as u64,
1629                    TEST_GAS_LIMIT,
1630                    premium as u64,
1631                );
1632                mpool.add(m).await.unwrap();
1633            }
1634        }
1635
1636        let msgs = mpool.select_messages(&ts, 0.1).unwrap();
1637        let expected_msgs = crate::shim::econ::BLOCK_GAS_LIMIT as i64 / TEST_GAS_LIMIT;
1638
1639        assert_eq!(
1640            msgs.len(),
1641            expected_msgs as usize,
1642            "Expected {} messages, but got {}",
1643            expected_msgs,
1644            msgs.len()
1645        );
1646
1647        let who_is = |addr| -> usize {
1648            for (i, a) in actors.iter().enumerate() {
1649                if a == &addr {
1650                    return i;
1651                }
1652            }
1653            // Lotus has -1, but since we don't have -ve indexes, set it some unrealistic
1654            // number
1655            9999999
1656        };
1657
1658        let mut nonces = vec![0; n_actors as usize];
1659        for m in &msgs {
1660            let who = who_is(m.from());
1661            if who < 3 {
1662                panic!("got message from {who}th actor",);
1663            }
1664
1665            let next_nonce: u64 = nonces[who];
1666            if m.message.sequence != next_nonce {
1667                panic!(
1668                    "expected nonce {} but got {}",
1669                    next_nonce, m.message.sequence
1670                );
1671            }
1672            nonces[who] += 1;
1673        }
1674    }
1675}