1#![allow(clippy::indexing_slicing)]
4use std::{
5 cmp::Ordering,
6 mem,
7 ops::{Index, IndexMut},
8};
9
10use crate::message::{MessageRead as _, SignedMessage};
11use crate::networks::ChainConfig;
12use crate::shim::{
13 address::Address,
14 econ::TokenAmount,
15 gas::{Gas, price_list_by_network_version},
16};
17use crate::{
18 blocks::{BLOCK_MESSAGE_LIMIT, Tipset},
19 shim::crypto::SignatureType,
20};
21use ahash::HashMap;
22use num_traits::Zero;
23use slotmap::{SlotMap, new_key_type};
24use tracing::warn;
25
26use super::errors::Error;
27use crate::message_pool::{
28 provider::Provider,
29 utils::{get_gas_perf, get_gas_reward},
30};
31
32new_key_type! {
33 pub struct NodeKey;
34}
35
36pub(in crate::message_pool) struct Chains {
45 pub map: SlotMap<NodeKey, MsgChainNode>,
46 pub key_vec: Vec<NodeKey>,
47}
48
49impl Chains {
50 pub(in crate::message_pool) fn sort_effective(&mut self) {
52 let mut chains = mem::take(&mut self.key_vec);
53 chains.sort_by(|a, b| {
54 let a = self.map.get(*a).expect("key_vec keys reference live nodes");
55 let b = self.map.get(*b).expect("key_vec keys reference live nodes");
56 a.cmp_effective(b)
57 });
58 let _ = mem::replace(&mut self.key_vec, chains);
59 }
60
61 pub(in crate::message_pool) fn sort_range_effective(
63 &mut self,
64 range: std::ops::RangeFrom<usize>,
65 ) {
66 let mut chains = mem::take(&mut self.key_vec);
67 chains[range].sort_by(|a, b| {
68 self.map
69 .get(*a)
70 .expect("key_vec keys reference live nodes")
71 .cmp_effective(self.map.get(*b).expect("key_vec keys reference live nodes"))
72 });
73 let _ = mem::replace(&mut self.key_vec, chains);
74 }
75
76 pub(in crate::message_pool) fn get_mut_with_prev_eff(
80 &mut self,
81 k: NodeKey,
82 ) -> (Option<&mut MsgChainNode>, Option<(f64, u64)>) {
83 let node = self.map.get(k);
84 let prev = if let Some(node) = node {
85 if let Some(prev_key) = node.prev {
86 let prev_node = self
87 .map
88 .get(prev_key)
89 .expect("prev key references a live node");
90 Some((prev_node.eff_perf, prev_node.gas_limit))
91 } else {
92 None
93 }
94 } else {
95 None
96 };
97
98 let node = self.map.get_mut(k);
99 (node, prev)
100 }
101
102 pub(in crate::message_pool) fn get(&self, k: NodeKey) -> Option<&MsgChainNode> {
104 self.map.get(k)
105 }
106}
107
108impl Chains {
109 pub(in crate::message_pool) fn new() -> Self {
110 Self {
111 map: SlotMap::with_key(),
112 key_vec: vec![],
113 }
114 }
115
116 pub(in crate::message_pool) fn push_with(
119 &mut self,
120 cur_chain: MsgChainNode,
121 node_vec: &mut Vec<NodeKey>,
122 ) {
123 let key = self.map.insert(cur_chain);
124 node_vec.push(key);
125 }
126
127 pub(in crate::message_pool) fn sort(&mut self, rev: bool) {
130 let mut chains = mem::take(&mut self.key_vec);
132 chains.sort_by(|a, b| {
133 let a = self.map.get(*a).expect("key_vec keys reference live nodes");
134 let b = self.map.get(*b).expect("key_vec keys reference live nodes");
135 if rev { b.compare(a) } else { a.compare(b) }
136 });
137 let _ = mem::replace(&mut self.key_vec, chains);
138 }
139
140 pub(in crate::message_pool) fn get_mut(&mut self, k: NodeKey) -> Option<&mut MsgChainNode> {
142 self.map.get_mut(k)
143 }
144
145 pub(in crate::message_pool) fn get_mut_at(&mut self, i: usize) -> Option<&mut MsgChainNode> {
147 let key = self.key_vec.get(i)?;
148 self.get_mut(*key)
149 }
150
151 pub(in crate::message_pool) fn get_from(&self, i: usize, vec: &[NodeKey]) -> &MsgChainNode {
153 #[allow(clippy::indexing_slicing)]
154 self.map
155 .get(vec[i])
156 .expect("node vec keys reference live nodes")
157 }
158
159 pub(in crate::message_pool) fn get_mut_from(
161 &mut self,
162 i: usize,
163 vec: &[NodeKey],
164 ) -> &mut MsgChainNode {
165 #[allow(clippy::indexing_slicing)]
166 self.map
167 .get_mut(vec[i])
168 .expect("node vec keys reference live nodes")
169 }
170
171 pub(in crate::message_pool) fn get_key_at(&self, i: usize) -> Option<NodeKey> {
173 self.key_vec.get(i).copied()
174 }
175
176 pub(in crate::message_pool) fn get_at(&self, i: usize) -> Option<&MsgChainNode> {
178 self.map.get(self.get_key_at(i)?)
179 }
180
181 pub(in crate::message_pool) fn len(&self) -> usize {
183 self.map.len()
184 }
185
186 pub(in crate::message_pool) fn is_empty(&self) -> bool {
189 self.map.is_empty()
190 }
191
192 #[tracing::instrument(skip_all, level = "debug")]
194 pub(in crate::message_pool) fn trim_msgs_at(
195 &mut self,
196 idx: usize,
197 gas_limit: u64,
198 msg_limit: usize,
199 base_fee: &TokenAmount,
200 ) {
201 let prev = match idx {
202 0 => None,
203 _ => self
204 .get_at(idx - 1)
205 .map(|prev| (prev.eff_perf, prev.gas_limit)),
206 };
207 let chain_node = self
208 .get_mut_at(idx)
209 .expect("caller validates idx is a live chain");
210 let mut i = chain_node.msgs.len() as i64 - 1;
211
212 while i >= 0
213 && (chain_node.gas_limit > gas_limit
214 || chain_node.gas_perf < 0.0
215 || i >= msg_limit as i64)
216 {
217 #[allow(clippy::indexing_slicing)]
218 let msg = &chain_node.msgs[i as usize];
219 let gas_reward = get_gas_reward(msg, base_fee);
220 chain_node.gas_reward -= gas_reward;
221 chain_node.gas_limit = chain_node.gas_limit.saturating_sub(msg.gas_limit());
222 if chain_node.gas_limit > 0 {
223 chain_node.gas_perf = get_gas_perf(&chain_node.gas_reward, chain_node.gas_limit);
224 if chain_node.bp != 0.0 {
225 chain_node.set_eff_perf(prev);
226 }
227 } else {
228 chain_node.gas_perf = 0.0;
229 chain_node.eff_perf = 0.0;
230 }
231 i -= 1;
232 }
233
234 if i < 0 {
235 chain_node.msgs.clear();
236 chain_node.valid = false;
237 } else {
238 chain_node.msgs.truncate(i as usize + 1);
239 }
240
241 let next = chain_node.next;
242 if next.is_some() {
243 self.invalidate(next);
244 }
245 }
246
247 pub(in crate::message_pool) fn bubble_down_after_trim(&mut self, from: usize) {
248 let mut j = from;
249 while j < self.key_vec.len().saturating_sub(1) {
250 #[allow(clippy::indexing_slicing)]
251 if self[j].compare(&self[j + 1]) == Ordering::Less {
252 break;
253 }
254 self.key_vec.swap(j, j + 1);
255 j += 1;
256 }
257 }
258
259 pub(in crate::message_pool) fn invalidate(&mut self, mut key: Option<NodeKey>) {
260 let mut next_keys = vec![];
261
262 while let Some(nk) = key {
263 let chain_node = self.map.get(nk).expect("chain keys reference live nodes");
264 next_keys.push(nk);
265 key = chain_node.next;
266 }
267
268 for k in next_keys.iter().rev() {
269 if let Some(node) = self.map.get_mut(*k) {
270 node.valid = false;
271 node.msgs.clear();
272 node.next = None;
273 }
274 }
275 }
276
277 pub(in crate::message_pool) fn drop_invalid(&mut self, key_vec: &mut Vec<NodeKey>) {
279 let mut valid_keys = vec![];
280 for k in key_vec.iter() {
281 if self
282 .map
283 .get(*k)
284 .map(|n| n.valid)
285 .expect("node vec keys reference live nodes")
286 {
287 valid_keys.push(*k);
288 } else {
289 self.map.remove(*k);
290 }
291 }
292
293 *key_vec = valid_keys;
294 }
295}
296
297impl Index<usize> for Chains {
298 type Output = MsgChainNode;
299 fn index(&self, i: usize) -> &Self::Output {
300 self.get_at(i).expect("index out of bounds")
301 }
302}
303
304impl IndexMut<usize> for Chains {
305 fn index_mut(&mut self, i: usize) -> &mut Self::Output {
306 #[allow(clippy::indexing_slicing)]
307 self.map
308 .get_mut(self.key_vec[i])
309 .expect("key_vec keys reference live nodes")
310 }
311}
312
313#[derive(Clone, Debug)]
315pub struct MsgChainNode {
316 pub msgs: Vec<SignedMessage>,
317 pub gas_reward: TokenAmount,
318 pub gas_limit: u64,
319 pub gas_perf: f64,
320 pub eff_perf: f64,
321 pub bp: f64,
322 pub parent_offset: f64,
323 pub valid: bool,
324 pub merged: bool,
325 pub next: Option<NodeKey>,
326 pub prev: Option<NodeKey>,
327 pub sig_type: Option<SignatureType>,
328}
329
330impl MsgChainNode {
331 pub fn compare(&self, other: &Self) -> Ordering {
332 if approx_cmp(self.gas_perf, other.gas_perf) == Ordering::Greater
333 || approx_cmp(self.gas_perf, other.gas_perf) == Ordering::Equal
334 && self.gas_reward.cmp(&other.gas_reward) == Ordering::Greater
335 {
336 return Ordering::Greater;
337 }
338
339 Ordering::Less
340 }
341
342 pub fn set_eff_perf(&mut self, prev: Option<(f64, u64)>) {
343 let mut eff_perf = self.gas_perf * self.bp;
344 if let Some(prev) = prev
345 && eff_perf > 0.0
346 {
347 let prev_eff_perf = prev.0;
348 let prev_gas_limit = prev.1;
349 let eff_perf_with_parent = (eff_perf * self.gas_limit as f64
350 + prev_eff_perf * prev_gas_limit as f64)
351 / (self.gas_limit + prev_gas_limit) as f64;
352 self.parent_offset = eff_perf - eff_perf_with_parent;
353 eff_perf = eff_perf_with_parent;
354 }
355 self.eff_perf = eff_perf;
356 }
357}
358
359impl MsgChainNode {
360 pub(in crate::message_pool) fn cmp_effective(&self, other: &Self) -> Ordering {
361 match (self.merged, other.merged) {
366 (true, false) => return Ordering::Greater,
367 (false, true) => return Ordering::Less,
368 _ => {}
369 }
370
371 if self.gas_perf >= 0.0 && other.gas_perf < 0.0
372 || self.eff_perf > other.eff_perf
373 || (approx_cmp(self.eff_perf, other.eff_perf) == Ordering::Equal
374 && self.gas_perf > other.gas_perf)
375 || (approx_cmp(self.eff_perf, other.eff_perf) == Ordering::Equal
376 && approx_cmp(self.gas_perf, other.gas_perf) == Ordering::Equal
377 && self.gas_reward > other.gas_reward)
378 {
379 return Ordering::Greater;
380 }
381
382 Ordering::Less
383 }
384
385 pub fn set_null_effective_perf(&mut self) {
386 if self.gas_perf < 0.0 {
387 self.eff_perf = self.gas_perf;
388 } else {
389 self.eff_perf = 0.0;
390 }
391 }
392}
393
394impl std::default::Default for MsgChainNode {
395 fn default() -> Self {
396 Self {
397 msgs: vec![],
398 gas_reward: TokenAmount::zero(),
399 gas_limit: 0,
400 gas_perf: 0.0,
401 eff_perf: 0.0,
402 bp: 0.0,
403 parent_offset: 0.0,
404 valid: true,
405 merged: false,
406 next: None,
407 prev: None,
408 sig_type: None,
409 }
410 }
411}
412
413pub(in crate::message_pool) fn create_message_chains<T>(
414 api: &T,
415 actor: &Address,
416 mset: &HashMap<u64, SignedMessage>,
417 base_fee: &TokenAmount,
418 ts: &Tipset,
419 chains: &mut Chains,
420 chain_config: &ChainConfig,
421) -> Result<(), Error>
422where
423 T: Provider,
424{
425 let mut msgs: Vec<SignedMessage> = mset.values().cloned().collect();
427 msgs.sort_by_key(|v| v.sequence());
428
429 let Ok(actor_state) = api.get_actor_after(actor, ts) else {
438 tracing::warn!("failed to load actor state, not building chain for {actor}");
439 return Ok(());
440 };
441 let mut cur_seq = actor_state.sequence;
442 let mut balance: TokenAmount = TokenAmount::from(&actor_state.balance);
443
444 let mut gas_limit = 0;
445 let mut skip = 0;
446 let mut i = 0;
447 let mut rewards = Vec::with_capacity(msgs.len());
448
449 while let Some(m) = msgs.get(i) {
450 if m.sequence() < cur_seq {
451 warn!(
452 "encountered message from actor {} with nonce {} less than the current nonce {}",
453 actor,
454 m.sequence(),
455 cur_seq
456 );
457 skip += 1;
458 i += 1;
459 continue;
460 }
461
462 if m.sequence() != cur_seq {
463 break;
464 }
465 cur_seq += 1;
466
467 let network_version = chain_config.network_version(ts.epoch());
468
469 let min_gas = price_list_by_network_version(network_version)
470 .on_chain_message(m.chain_length()?)
471 .total();
472
473 if Gas::new(m.gas_limit()) < min_gas {
474 break;
475 }
476 gas_limit += m.gas_limit();
477 if gas_limit > crate::shim::econ::BLOCK_GAS_LIMIT {
478 break;
479 }
480
481 let required = m.required_funds();
482 if balance < required {
483 break;
484 }
485
486 balance -= required;
487 let value = m.value();
488 balance -= value;
489
490 let gas_reward = get_gas_reward(m, base_fee);
491 rewards.push(gas_reward);
492 i += 1;
493 }
494
495 let mut msgs = if i > skip {
497 #[allow(clippy::indexing_slicing)]
498 msgs[skip..i].to_vec()
499 } else {
500 return Ok(());
501 };
502
503 if msgs.len() > BLOCK_MESSAGE_LIMIT {
505 warn!(
506 "dropping {} messages from {actor} as they exceed the block message limit of {BLOCK_MESSAGE_LIMIT}",
507 msgs.len() - BLOCK_MESSAGE_LIMIT,
508 );
509 msgs.truncate(BLOCK_MESSAGE_LIMIT);
510 };
511
512 let mut cur_chain = MsgChainNode::default();
513 let mut node_vec = vec![];
514
515 let new_chain = |m: SignedMessage, reward: &TokenAmount| -> MsgChainNode {
516 let gl = m.gas_limit();
517 let sig_type = Some(m.signature().sig_type);
518 MsgChainNode {
519 msgs: vec![m],
520 gas_reward: reward.clone(),
521 gas_limit: gl,
522 gas_perf: get_gas_perf(reward, gl),
523 eff_perf: 0.0,
524 bp: 0.0,
525 parent_offset: 0.0,
526 valid: true,
527 merged: false,
528 prev: None,
529 next: None,
530 sig_type,
531 }
532 };
533
534 for (i, (m, reward)) in msgs.into_iter().zip(rewards.iter()).enumerate() {
537 if i == 0 {
538 cur_chain = new_chain(m, reward);
539 continue;
540 }
541
542 let gas_reward = &cur_chain.gas_reward + reward;
543 let gas_limit = cur_chain.gas_limit + m.gas_limit();
544 let gas_perf = get_gas_perf(&gas_reward, gas_limit);
545
546 if gas_perf < cur_chain.gas_perf {
549 chains.push_with(cur_chain, &mut node_vec);
550 cur_chain = new_chain(m, reward);
551 } else {
552 cur_chain.msgs.push(m);
553 cur_chain.gas_reward = gas_reward;
554 cur_chain.gas_limit = gas_limit;
555 cur_chain.gas_perf = gas_perf;
556 }
557 }
558
559 chains.push_with(cur_chain, &mut node_vec);
560
561 loop {
563 let mut merged = 0;
564 for i in (1..node_vec.len()).rev() {
565 if chains.get_from(i, &node_vec).gas_perf >= chains.get_from(i - 1, &node_vec).gas_perf
566 {
567 let chain_i_msg = chains.get_from(i, &node_vec).msgs.clone();
569 chains
570 .get_mut_from(i - 1, &node_vec)
571 .msgs
572 .extend(chain_i_msg);
573
574 let chain_i_gas_reward = chains.get_from(i, &node_vec).gas_reward.clone();
576 chains.get_mut_from(i - 1, &node_vec).gas_reward += chain_i_gas_reward;
577
578 let chain_i_gas_limit = chains.get_from(i, &node_vec).gas_limit;
580 chains.get_mut_from(i - 1, &node_vec).gas_limit += chain_i_gas_limit;
581
582 let chain_i_gas_perf = get_gas_perf(
584 &chains.get_from(i - 1, &node_vec).gas_reward,
585 chains.get_from(i - 1, &node_vec).gas_limit,
586 );
587 chains.get_mut_from(i - 1, &node_vec).gas_perf = chain_i_gas_perf;
588 chains.get_mut_from(i, &node_vec).valid = false;
590 merged += 1;
591 }
592 }
593
594 if merged == 0 {
595 break;
596 }
597
598 chains.drop_invalid(&mut node_vec);
599 }
600
601 if node_vec.len() > 1 {
602 for (&k1, &k2) in node_vec.iter().zip(node_vec.iter().skip(1)) {
603 let n1 = chains
605 .get_mut(k1)
606 .ok_or_else(|| Error::Other(format!("{k1:?} should present in `chains`")))?;
607 n1.next = Some(k2);
608 let n2 = chains
612 .get_mut(k2)
613 .ok_or_else(|| Error::Other(format!("{k2:?} should present in `chains`")))?;
614 n2.prev = Some(k1);
615 }
617 }
618
619 chains.key_vec.extend(node_vec);
621
622 Ok(())
623}
624
625fn approx_cmp(a: f64, b: f64) -> Ordering {
626 if (a - b).abs() <= (a * f64::EPSILON).abs() {
627 Ordering::Equal
628 } else {
629 a.total_cmp(&b)
630 }
631}
632
633#[cfg(test)]
634mod tests {
635 use super::*;
636
637 #[test]
638 fn approx_cmp_nan_does_not_panic() {
639 for (a, b) in [
640 (f64::NAN, 1.0),
641 (1.0, f64::NAN),
642 (f64::NAN, f64::NAN),
643 (f64::INFINITY, f64::NAN),
644 ] {
645 let _ = approx_cmp(a, b);
646 }
647 }
648}