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