1pub(in crate::message_pool) mod events;
5pub(in crate::message_pool) mod metrics;
6pub(in crate::message_pool) mod msg_pool;
7pub(in crate::message_pool) mod msg_set;
8pub(in crate::message_pool) mod pending_store;
9pub(in crate::message_pool) mod provider;
10pub(in crate::message_pool) mod reorg;
11pub(in crate::message_pool) mod republish;
12pub mod selection;
13#[cfg(test)]
14pub mod test_provider;
15pub(in crate::message_pool) mod utils;
16
17pub use events::{MpoolSubscriber, MpoolUpdate};
18
19pub(in crate::message_pool) use utils::recover_sig;
20
21use crate::shim::percent::Percent;
22
23const REPLACE_BY_FEE_RATIO_MIN: Percent = Percent(110);
24const RBF_DENOM: u64 = 100;
25const BASE_FEE_LOWER_BOUND_FACTOR_CONSERVATIVE: i64 = 100;
26const MIN_GAS: u64 = 1298450;
27
28#[cfg(test)]
29pub mod tests {
30 use std::{borrow::BorrowMut, time::Duration};
31
32 use crate::blocks::Tipset;
33 use crate::key_management::{KeyStore, KeyStoreConfig, Wallet};
34 use crate::libp2p::NetworkMessage;
35 use crate::message::{MessageRead as _, SignedMessage};
36 use crate::networks::ChainConfig;
37 use crate::shim::{
38 address::Address,
39 crypto::{Signature, SignatureType},
40 econ::TokenAmount,
41 message::{Message, Message_v3},
42 };
43 use ahash::{HashMap, HashMapExt};
44 use num_traits::Zero;
45 use test_provider::*;
46 use tokio::task::JoinSet;
47
48 use super::*;
49 use crate::message_pool::{
50 Error,
51 msg_chain::{Chains, create_message_chains},
52 msg_pool::MessagePool,
53 provider::Provider,
54 };
55
56 struct TestMpool {
57 mpool: MessagePool<TestApi>,
58 wallet: Wallet,
59 sender: Address,
60 target: Address,
61 services: JoinSet<anyhow::Result<()>>,
62 network_rx: flume::Receiver<NetworkMessage>,
63 }
64
65 fn make_test_mpool(
66 tma: TestApi,
67 ) -> (
68 MessagePool<TestApi>,
69 JoinSet<anyhow::Result<()>>,
70 flume::Receiver<NetworkMessage>,
71 ) {
72 let (tx, rx) = flume::bounded(50);
73 let mut services = JoinSet::new();
74 let mpool = MessagePool::new(
75 tma,
76 tx,
77 Default::default(),
78 Default::default(),
79 &mut services,
80 )
81 .unwrap();
82 (mpool, services, rx)
83 }
84
85 fn make_test_setup() -> TestMpool {
86 let keystore = KeyStore::new(KeyStoreConfig::Memory).unwrap();
87 let mut wallet = Wallet::new(keystore);
88 let sender = wallet.generate_addr(SignatureType::Secp256k1).unwrap();
89 let target = wallet.generate_addr(SignatureType::Secp256k1).unwrap();
90 let tma = TestApi::default();
91 tma.set_state_sequence(&sender, 0);
92 let (mpool, services, network_rx) = make_test_mpool(tma);
93 TestMpool {
94 mpool,
95 wallet,
96 sender,
97 target,
98 services,
99 network_rx,
100 }
101 }
102
103 #[tokio::test]
104 async fn test_per_actor_limit() {
105 let keystore = KeyStore::new(KeyStoreConfig::Memory).unwrap();
106 let mut wallet = Wallet::new(keystore);
107 let sender = wallet.generate_addr(SignatureType::Secp256k1).unwrap();
108 let target = wallet.generate_addr(SignatureType::Secp256k1).unwrap();
109 let tma = TestApi::with_max_actor_pending_messages(200);
110 tma.set_state_sequence(&sender, 0);
111 let (mpool, _services, _rx) = make_test_mpool(tma);
112
113 let mut smsg_vec = Vec::new();
114 for i in 0..(mpool.api.max_actor_pending_messages() + 1) {
115 let msg = create_smsg(&target, &sender, wallet.borrow_mut(), i, 1000000, 1);
116 smsg_vec.push(msg);
117 }
118
119 let (last, body) = smsg_vec.split_last().unwrap();
120 for smsg in body {
121 mpool.add(smsg.clone()).await.unwrap();
122 }
123 assert_eq!(
124 mpool.add(last.clone()).await,
125 Err(Error::TooManyPendingMessages(sender.to_string(), true))
126 );
127 }
128
129 pub fn create_smsg(
130 to: &Address,
131 from: &Address,
132 wallet: &mut Wallet,
133 sequence: u64,
134 gas_limit: i64,
135 gas_price: u64,
136 ) -> SignedMessage {
137 let umsg: Message = Message_v3 {
138 to: to.into(),
139 from: from.into(),
140 sequence,
141 gas_limit: gas_limit as u64,
142 gas_fee_cap: TokenAmount::from_atto(gas_price + 100).into(),
143 gas_premium: TokenAmount::from_atto(gas_price).into(),
144 ..Message_v3::default()
145 }
146 .into();
147 let msg_signing_bytes = umsg.cid().to_bytes();
148 let sig = wallet.sign(from, msg_signing_bytes.as_slice()).unwrap();
149 SignedMessage::new_unchecked(umsg, sig)
150 }
151
152 pub fn create_fake_smsg(
156 pool: &MessagePool<TestApi>,
157 to: &Address,
158 from: &Address,
159 sequence: u64,
160 gas_limit: i64,
161 gas_price: u64,
162 ) -> SignedMessage {
163 let umsg: Message = Message_v3 {
164 to: to.into(),
165 from: from.into(),
166 sequence,
167 gas_limit: gas_limit as u64,
168 gas_fee_cap: TokenAmount::from_atto(gas_price + 100).into(),
169 gas_premium: TokenAmount::from_atto(gas_price).into(),
170 ..Message_v3::default()
171 }
172 .into();
173 let sig = Signature::new_secp256k1(vec![]);
174 let signed = SignedMessage::new_unchecked(umsg, sig);
175 let cid = signed.cid();
176 pool.caches.sig_val.insert(cid.into(), ());
177 signed
178 }
179
180 #[tokio::test]
181 async fn test_message_pool() {
182 let TestMpool {
183 mpool,
184 mut wallet,
185 sender,
186 target,
187 ..
188 } = make_test_setup();
189
190 let mut smsg_vec = Vec::new();
191 for i in 0..2 {
192 let msg = create_smsg(&target, &sender, wallet.borrow_mut(), i, 1000000, 1);
193 smsg_vec.push(msg);
194 }
195
196 mpool.api.inner.lock().set_state_sequence(&sender, 0);
197 assert_eq!(mpool.get_sequence(&sender).await.unwrap(), 0);
198 mpool.add(smsg_vec[0].clone()).await.unwrap();
199 assert_eq!(mpool.get_sequence(&sender).await.unwrap(), 1);
200 mpool.add(smsg_vec[1].clone()).await.unwrap();
201 assert_eq!(mpool.get_sequence(&sender).await.unwrap(), 2);
202
203 let a = mock_block(1, 1);
204
205 mpool.api.inner.lock().set_block_messages(&a, smsg_vec);
206 mpool
207 .apply_head_change(Vec::new(), vec![Tipset::from(a)])
208 .await
209 .unwrap();
210
211 assert_eq!(mpool.get_sequence(&sender).await.unwrap(), 2);
212 }
213
214 #[tokio::test]
215 async fn test_revert_messages() {
216 let tma = TestApi::default();
217 let keystore = KeyStore::new(KeyStoreConfig::Memory).unwrap();
218 let mut wallet = Wallet::new(keystore);
219
220 let a = mock_block(1, 1);
221 let tipset = Tipset::from(&a);
222 let b = mock_block_with_parents(&tipset, 1, 1);
223
224 let sender = wallet.generate_addr(SignatureType::Bls).unwrap();
225 let target = Address::new_id(1001);
226
227 let mut smsg_vec = Vec::new();
228
229 for i in 0..4 {
230 let msg = create_smsg(&target, &sender, wallet.borrow_mut(), i, 1000000, 1);
231 smsg_vec.push(msg);
232 }
233 let (mpool, _services, _rx) = make_test_mpool(tma);
234
235 {
236 let mut api_temp = mpool.api.inner.lock();
237 api_temp.set_block_messages(&a, vec![smsg_vec[0].clone()]);
238 api_temp.set_block_messages(&b.clone(), smsg_vec[1..4].to_vec());
239 api_temp.set_state_sequence(&sender, 0);
240 drop(api_temp);
241 }
242
243 mpool.add(smsg_vec[0].clone()).await.unwrap();
244 mpool.add(smsg_vec[1].clone()).await.unwrap();
245 mpool.add(smsg_vec[2].clone()).await.unwrap();
246 mpool.add(smsg_vec[3].clone()).await.unwrap();
247
248 mpool.api.set_state_sequence(&sender, 0);
249
250 mpool
251 .apply_head_change(Vec::new(), vec![Tipset::from(a)])
252 .await
253 .unwrap();
254
255 assert_eq!(mpool.get_sequence(&sender).await.unwrap(), 4);
256
257 mpool.api.set_state_sequence(&sender, 1);
258
259 mpool
260 .apply_head_change(Vec::new(), vec![Tipset::from(&b)])
261 .await
262 .unwrap();
263
264 assert_eq!(mpool.get_sequence(&sender).await.unwrap(), 4);
265
266 mpool.api.set_state_sequence(&sender, 0);
267
268 mpool
269 .apply_head_change(vec![Tipset::from(b)], Vec::new())
270 .await
271 .unwrap();
272
273 assert_eq!(mpool.get_sequence(&sender).await.unwrap(), 4);
274
275 let (p, _) = mpool.pending();
276 assert_eq!(p.len(), 3);
277 }
278
279 #[tokio::test]
280 async fn test_get_sequence_resolves_id_address() {
281 let tma = TestApi::default();
282 let keystore = KeyStore::new(KeyStoreConfig::Memory).unwrap();
283 let mut wallet = Wallet::new(keystore);
284 let key_addr = wallet.generate_addr(SignatureType::Secp256k1).unwrap();
285 let id_addr = Address::new_id(999);
286
287 tma.set_key_address_mapping(&id_addr, &key_addr);
288 tma.set_state_sequence(&key_addr, 0);
289 let (mpool, _services, _rx) = make_test_mpool(tma);
290
291 let target = Address::new_id(1001);
292 for i in 0..3 {
293 let msg = create_smsg(&target, &key_addr, wallet.borrow_mut(), i, 1000000, 1);
294 mpool.add(msg).await.unwrap();
295 }
296
297 assert_eq!(mpool.get_sequence(&id_addr).await.unwrap(), 3);
299 assert_eq!(mpool.get_sequence(&key_addr).await.unwrap(), 3);
300 }
301
302 #[tokio::test]
303 async fn test_pending_for_resolves_id_address() {
304 let tma = TestApi::default();
305 let keystore = KeyStore::new(KeyStoreConfig::Memory).unwrap();
306 let mut wallet = Wallet::new(keystore);
307 let key_addr = wallet.generate_addr(SignatureType::Secp256k1).unwrap();
308 let id_addr = Address::new_id(888);
309
310 tma.set_key_address_mapping(&id_addr, &key_addr);
311 tma.set_state_sequence(&key_addr, 0);
312 let (mpool, _services, _rx) = make_test_mpool(tma);
313
314 let target = Address::new_id(1001);
315 for i in 0..2 {
316 let msg = create_smsg(&target, &key_addr, wallet.borrow_mut(), i, 1000000, 1);
317 mpool.add(msg).await.unwrap();
318 }
319
320 let msgs = mpool
322 .pending_for(&id_addr)
323 .await
324 .expect("should find pending messages");
325 assert_eq!(msgs.len(), 2);
326
327 let msgs2 = mpool
329 .pending_for(&key_addr)
330 .await
331 .expect("should find pending messages");
332 assert_eq!(msgs2.len(), 2);
333 }
334
335 #[tokio::test]
336 async fn test_add_with_id_from_resolves_to_key_in_pending() {
337 let tma = TestApi::default();
338 let key_addr = Address::new_bls(&[11u8; 48]).unwrap();
339 let id_addr = Address::new_id(777);
340
341 tma.set_key_address_mapping(&id_addr, &key_addr);
342 tma.set_state_sequence(&key_addr, 0);
343 let (mpool, _services, _rx) = make_test_mpool(tma);
344
345 let msg = create_fake_smsg(&mpool, &Address::new_id(1001), &id_addr, 0, 1000000, 1);
347 mpool.add(msg).await.unwrap();
348
349 assert!(
351 mpool.pending.snapshot_for(&key_addr).is_some(),
352 "pending should be keyed by resolved key address"
353 );
354 assert!(
355 mpool.pending.snapshot_for(&id_addr).is_none(),
356 "pending should NOT have entry under raw ID address"
357 );
358 }
359
360 #[tokio::test]
361 async fn test_head_change_removes_via_resolved_address() {
362 let tma = TestApi::default();
363 let keystore = KeyStore::new(KeyStoreConfig::Memory).unwrap();
364 let mut wallet = Wallet::new(keystore);
365 let key_addr = wallet.generate_addr(SignatureType::Bls).unwrap();
366 let id_addr = Address::new_id(555);
367
368 tma.set_key_address_mapping(&id_addr, &key_addr);
369 tma.set_state_sequence(&key_addr, 0);
370
371 let a = mock_block(1, 1);
372 let (mpool, _services, _rx) = make_test_mpool(tma);
373
374 let target = Address::new_id(1001);
375 let msg0 = create_smsg(&target, &key_addr, wallet.borrow_mut(), 0, 1000000, 1);
376 let msg1 = create_smsg(&target, &key_addr, wallet.borrow_mut(), 1, 1000000, 1);
377 mpool.add(msg0.clone()).await.unwrap();
378 mpool.add(msg1).await.unwrap();
379 assert_eq!(mpool.get_sequence(&key_addr).await.unwrap(), 2);
380
381 mpool.api.inner.lock().set_block_messages(&a, vec![msg0]);
384
385 mpool.api.set_state_sequence(&key_addr, 1);
386
387 mpool
388 .apply_head_change(Vec::new(), vec![Tipset::from(a)])
389 .await
390 .unwrap();
391
392 assert_eq!(mpool.get_sequence(&id_addr).await.unwrap(), 2);
394 let msgs = mpool
395 .pending_for(&key_addr)
396 .await
397 .expect("should have remaining msg");
398 assert_eq!(msgs.len(), 1);
399 assert_eq!(msgs[0].sequence(), 1);
400 }
401
402 #[tokio::test]
403 async fn test_async_message_pool() {
404 let TestMpool {
405 mpool,
406 mut wallet,
407 sender,
408 target,
409 services: _services,
410 network_rx: _network_rx,
411 } = make_test_setup();
412
413 let mut smsg_vec = Vec::new();
414 for i in 0..3 {
415 let msg = create_smsg(&target, &sender, wallet.borrow_mut(), i, 1000000, 1);
416 smsg_vec.push(msg);
417 }
418
419 assert_eq!(mpool.get_sequence(&sender).await.unwrap(), 0);
420 mpool.push(smsg_vec[0].clone()).await.unwrap();
421 assert_eq!(mpool.get_sequence(&sender).await.unwrap(), 1);
422 mpool.push(smsg_vec[1].clone()).await.unwrap();
423 assert_eq!(mpool.get_sequence(&sender).await.unwrap(), 2);
424 mpool.push(smsg_vec[2].clone()).await.unwrap();
425 assert_eq!(mpool.get_sequence(&sender).await.unwrap(), 3);
426
427 let header = mock_block(1, 1);
428 let tipset = Tipset::from(&header.clone());
429
430 mpool.api.set_heaviest_tipset(tipset.clone());
431
432 tokio::time::sleep(Duration::new(2, 0)).await;
434
435 let cur_ts = mpool.current_tipset();
436 assert_eq!(cur_ts, tipset);
437 }
438
439 #[tokio::test]
440 async fn test_msg_chains() {
441 let keystore = KeyStore::new(KeyStoreConfig::Memory).unwrap();
442 let mut wallet = Wallet::new(keystore);
443 let a1 = wallet.generate_addr(SignatureType::Secp256k1).unwrap();
444 let a2 = wallet.generate_addr(SignatureType::Secp256k1).unwrap();
445 let tma = TestApi::default();
446 let gas_limit = 6955002;
447
448 let a = mock_block(1, 1);
449 let ts = Tipset::from(a);
450 let chain_config = ChainConfig::default();
451
452 let mut mset = HashMap::new();
456 let mut smsg_vec = Vec::new();
457 for i in 0..10 {
458 let msg = create_smsg(&a2, &a1, wallet.borrow_mut(), i, gas_limit, 1 + i);
459 smsg_vec.push(msg.clone());
460 mset.insert(i, msg);
461 }
462
463 let mut chains = Chains::new();
464 create_message_chains(
465 &tma,
466 &a1,
467 &mset,
468 &TokenAmount::zero(),
469 &ts,
470 &mut chains,
471 &chain_config,
472 )
473 .unwrap();
474 assert_eq!(chains.len(), 1, "expected a single chain");
475 assert_eq!(
476 chains[0].msgs.len(),
477 10,
478 "expected 10 messages in single chain, got: {}",
479 chains[0].msgs.len()
480 );
481 for (i, m) in chains[0].msgs.iter().enumerate() {
482 assert_eq!(
483 m.sequence(),
484 i as u64,
485 "expected sequence {} but got {}",
486 i,
487 m.sequence()
488 );
489 }
490
491 let mut mset = HashMap::new();
494 let mut smsg_vec = Vec::new();
495 for i in 0..10 {
496 let msg = create_smsg(&a2, &a1, wallet.borrow_mut(), i, gas_limit, 10 - i);
497 smsg_vec.push(msg.clone());
498 mset.insert(i, msg);
499 }
500 let mut chains = Chains::new();
501 create_message_chains(
502 &tma,
503 &a1,
504 &mset,
505 &TokenAmount::zero(),
506 &ts,
507 &mut chains,
508 &chain_config,
509 )
510 .unwrap();
511 assert_eq!(chains.len(), 10, "expected 10 chains");
512
513 for i in 0..chains.len() {
514 assert_eq!(
515 chains[i].msgs.len(),
516 1,
517 "expected 1 message in chain {} but got {}",
518 i,
519 chains[i].msgs.len()
520 );
521 }
522
523 for i in 0..chains.len() {
524 let m = &chains[i].msgs[0];
525 assert_eq!(
526 m.sequence(),
527 i as u64,
528 "expected sequence {} but got {}",
529 i,
530 m.sequence()
531 );
532 }
533
534 let mut mset = HashMap::new();
538 let mut smsg_vec = Vec::new();
539 for i in 0..10 {
540 let msg = create_smsg(&a2, &a1, wallet.borrow_mut(), i, gas_limit, 1 + i % 3);
541 smsg_vec.push(msg.clone());
542 mset.insert(i, msg);
543 }
544 let mut chains = Chains::new();
545 create_message_chains(
546 &tma,
547 &a1,
548 &mset,
549 &TokenAmount::zero(),
550 &ts,
551 &mut chains,
552 &chain_config,
553 )
554 .unwrap();
555 assert_eq!(chains.len(), 2, "expected 2 chains");
556 assert_eq!(chains[0].msgs.len(), 9);
557 assert_eq!(chains[1].msgs.len(), 1);
558 let mut next_nonce = 0;
559 for i in 0..chains.len() {
560 for m in chains[i].msgs.iter() {
561 assert_eq!(
562 next_nonce,
563 m.sequence(),
564 "expected nonce {} but got {}",
565 next_nonce,
566 m.sequence()
567 );
568 next_nonce += 1;
569 }
570 }
571
572 let mut mset = HashMap::new();
577 let mut smsg_vec = Vec::new();
578 for i in 0..10 {
579 let bias = (12 - i) / 3;
580 let msg = create_smsg(
581 &a2,
582 &a1,
583 wallet.borrow_mut(),
584 i,
585 gas_limit,
586 1 + i % 3 + bias,
587 );
588 smsg_vec.push(msg.clone());
589 mset.insert(i, msg);
590 }
591
592 let mut chains = Chains::new();
593 create_message_chains(
594 &tma,
595 &a1,
596 &mset,
597 &TokenAmount::zero(),
598 &ts,
599 &mut chains,
600 &chain_config,
601 )
602 .unwrap();
603
604 for i in 0..chains.len() {
605 let expected_len = if i > 2 { 1 } else { 3 };
606 assert_eq!(
607 chains[i].msgs.len(),
608 expected_len,
609 "expected {} message in chain {} but got {}",
610 expected_len,
611 i,
612 chains[i].msgs.len()
613 );
614 }
615
616 let mut next_nonce = 0;
617 for i in 0..chains.len() {
618 for m in chains[i].msgs.iter() {
619 assert_eq!(
620 next_nonce,
621 m.sequence(),
622 "expected nonce {} but got {}",
623 next_nonce,
624 m.sequence()
625 );
626 next_nonce += 1;
627 }
628 }
629
630 let mut mset = HashMap::new();
634 let mut smsg_vec = Vec::new();
635 for i in 0..10 {
636 let msg = create_smsg(&a2, &a1, wallet.borrow_mut(), i * 2, gas_limit, 1 + i);
637 smsg_vec.push(msg.clone());
638 mset.insert(i, msg);
639 }
640
641 let mut chains = Chains::new();
642 create_message_chains(
643 &tma,
644 &a1,
645 &mset,
646 &TokenAmount::zero(),
647 &ts,
648 &mut chains,
649 &chain_config,
650 )
651 .unwrap();
652 assert_eq!(chains.len(), 1, "expected a single chain");
653 for (i, m) in chains[0].msgs.iter().enumerate() {
654 assert_eq!(
655 m.sequence(),
656 i as u64,
657 "expected nonce {} but got {}",
658 i,
659 m.sequence()
660 );
661 }
662
663 let mut mset = HashMap::new();
667 let mut smsg_vec = Vec::new();
668 tma.set_state_balance_raw(&a1, TokenAmount::from_atto(1_000_000_000_000_000_000_u64));
669 for i in 0..10 {
670 let msg = if i != 5 {
671 create_smsg(&a2, &a1, wallet.borrow_mut(), i, gas_limit, 1 + i)
672 } else {
673 create_smsg(&a2, &a1, wallet.borrow_mut(), i, 1, 1 + i)
674 };
675 smsg_vec.push(msg.clone());
676 mset.insert(i, msg);
677 }
678 let mut chains = Chains::new();
679 create_message_chains(
680 &tma,
681 &a1,
682 &mset,
683 &TokenAmount::zero(),
684 &ts,
685 &mut chains,
686 &chain_config,
687 )
688 .unwrap();
689 assert_eq!(chains.len(), 1, "expected a single chain");
690 assert_eq!(chains[0].msgs.len(), 5);
691 for (i, m) in chains[0].msgs.iter().enumerate() {
692 assert_eq!(
693 m.sequence(),
694 i as u64,
695 "expected nonce {} but got {}",
696 i,
697 m.sequence()
698 );
699 }
700
701 let mut mset = HashMap::new();
705 let mut smsg_vec = Vec::new();
706 let max_messages = crate::shim::econ::BLOCK_GAS_LIMIT as i64 / gas_limit;
707 let n_messages = max_messages + 1;
708 for i in 0..n_messages {
709 let msg = create_smsg(
710 &a2,
711 &a1,
712 wallet.borrow_mut(),
713 i as u64,
714 gas_limit,
715 (1 + i) as u64,
716 );
717 smsg_vec.push(msg.clone());
718 mset.insert(i as u64, msg);
719 }
720 let mut chains = Chains::new();
721 create_message_chains(
722 &tma,
723 &a1,
724 &mset,
725 &TokenAmount::zero(),
726 &ts,
727 &mut chains,
728 &chain_config,
729 )
730 .unwrap();
731 assert_eq!(chains.len(), 1, "expected a single chain");
732 assert_eq!(chains[0].msgs.len(), max_messages as usize);
733 for (i, m) in chains[0].msgs.iter().enumerate() {
734 assert_eq!(
735 m.sequence(),
736 i as u64,
737 "expected nonce {} but got {}",
738 i,
739 m.sequence()
740 );
741 }
742
743 tma.set_state_balance_raw(&a1, TokenAmount::from_atto(300 * gas_limit + 1));
745 let mut mset = HashMap::new();
746 let mut smsg_vec = Vec::new();
747 for i in 0..10 {
748 let msg = create_smsg(&a2, &a1, wallet.borrow_mut(), i, gas_limit, 1 + i);
749 smsg_vec.push(msg.clone());
750 mset.insert(i, msg);
751 }
752 let mut chains = Chains::new();
753 create_message_chains(
754 &tma,
755 &a1,
756 &mset,
757 &TokenAmount::zero(),
758 &ts,
759 &mut chains,
760 &chain_config,
761 )
762 .unwrap();
763 assert_eq!(chains.len(), 1, "expected a single chain");
764 assert_eq!(chains[0].msgs.len(), 2);
765 for (i, m) in chains[0].msgs.iter().enumerate() {
766 assert_eq!(
767 m.sequence(),
768 i as u64,
769 "expected nonce {} but got {}",
770 i,
771 m.sequence()
772 );
773 }
774 }
775}