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