1use super::*;
5use crate::blocks::TipsetKey;
6use crate::message::MessageRead as _;
7use ahash::HashSet;
8use parking_lot::RwLock;
9use std::sync::OnceLock;
10use std::time::Duration;
11use tokio::sync::broadcast::error::RecvError;
12use tokio_util::sync::CancellationToken;
13use tracing::warn;
14
15impl StateManager {
16 fn tipset_executed_message(
19 &self,
20 tipset: &Tipset,
21 message: &ChainMessage,
22 allow_replaced: bool,
23 ) -> Result<Option<Receipt>, Error> {
24 if tipset.epoch() == 0 {
25 return Ok(None);
26 }
27 let message_from_address = message.from();
28 let message_sequence = message.sequence();
29 let pts = self
31 .chain_index()
32 .load_required_tipset(tipset.parents())
33 .map_err(|err| Error::Other(format!("Failed to load tipset: {err}")))?;
34 let messages = self
35 .cs
36 .messages_for_tipset(&pts)
37 .map_err(|err| Error::Other(format!("Failed to load messages for tipset: {err}")))?;
38 messages
39 .iter()
40 .enumerate()
41 .rev()
43 .filter(|(_, s)| {
44 s.sequence() == message_sequence
45 && s.from() == message_from_address
46 && s.equal_call(message)
47 })
48 .map(|(index, m)| {
49 if !allow_replaced && message.cid() != m.cid(){
53 Err(Error::Other(format!(
54 "found message with equal nonce and call params but different CID. wanted {}, found: {}, nonce: {}, from: {}",
55 message.cid(),
56 m.cid(),
57 message.sequence(),
58 message.from(),
59 )))
60 } else {
61 let block_header = tipset.block_headers().first();
62 crate::chain::get_parent_receipt(
63 self.db(),
64 block_header,
65 index,
66 )
67 .map_err(|err| Error::Other(format!("Failed to get parent receipt (message_receipts={}, index={index}, error={err})", block_header.message_receipts)))
68 }
69 })
70 .next()
71 .unwrap_or(Ok(None))
72 }
73
74 fn check_search_blocking(
75 &self,
76 mut current: Tipset,
77 message: &ChainMessage,
78 lookback_max_epoch: ChainEpoch,
79 allow_replaced: bool,
80 cancellation_token: &CancellationToken,
81 ) -> Result<Option<(Tipset, Receipt)>, Error> {
82 let message_from_address = message.from();
83 let message_sequence = message.sequence();
84 let current_actor_state = self
85 .get_required_actor(&message_from_address, *current.parent_state())
86 .map_err(Error::state)?;
87 if current_actor_state.sequence <= message_sequence {
91 return Ok(None);
92 }
93 let message_from_id = self.lookup_required_id(&message_from_address, ¤t)?;
94
95 while !cancellation_token.is_cancelled() && current.epoch() >= lookback_max_epoch {
96 let parent_tipset = self
97 .chain_index()
98 .load_required_tipset(current.parents())
99 .map_err(|err| {
100 Error::Other(format!(
101 "failed to load tipset during msg wait searchback: {err:}"
102 ))
103 })?;
104
105 let parent_actor_state = self
106 .get_actor(&message_from_id, *parent_tipset.parent_state())
107 .map_err(|e| Error::State(e.to_string()))?;
108
109 match parent_actor_state {
110 Some(state) if state.sequence > message_sequence => current = parent_tipset,
113 _ => {
118 return Ok(self
119 .tipset_executed_message(¤t, message, allow_replaced)?
120 .map(|receipt| (current, receipt)));
121 }
122 }
123 }
124
125 Ok(None)
126 }
127
128 fn search_back_for_message_blocking(
130 &self,
131 current: Tipset,
132 message: &ChainMessage,
133 look_back_limit: Option<ChainEpoch>,
134 allow_replaced: Option<bool>,
135 cancellation_token: &CancellationToken,
136 ) -> Result<Option<(Tipset, Receipt)>, Error> {
137 let current_epoch = current.epoch();
138 let allow_replaced = allow_replaced.unwrap_or(true);
139
140 let Some(max_lookback_epoch_inclusive) =
141 Self::max_lookback_epoch_inclusive(current_epoch, look_back_limit)
142 else {
143 return Ok(None);
144 };
145
146 self.check_search_blocking(
147 current,
148 message,
149 max_lookback_epoch_inclusive,
150 allow_replaced,
151 cancellation_token,
152 )
153 }
154
155 pub fn max_lookback_epoch_inclusive(
157 current_epoch: ChainEpoch,
158 look_back_limit: Option<ChainEpoch>,
159 ) -> Option<ChainEpoch> {
160 match look_back_limit {
161 Some(0) => None,
163 Some(limit) if limit > 0 => Some((current_epoch - limit + 1).max(0)),
167 _ => Some(0),
169 }
170 }
171
172 pub fn get_receipt_blocking(
174 &self,
175 tipset: Tipset,
176 msg: Cid,
177 cancellation_token: &CancellationToken,
178 ) -> Result<Receipt, Error> {
179 let m = crate::chain::get_chain_message(self.db(), &msg)
180 .map_err(|e| Error::Other(e.to_string()))?;
181 let message_receipt = self.tipset_executed_message(&tipset, &m, true)?;
182 if let Some(receipt) = message_receipt {
183 return Ok(receipt);
184 }
185
186 let maybe_tuple =
187 self.search_back_for_message_blocking(tipset, &m, None, None, cancellation_token)?;
188 let message_receipt = maybe_tuple
189 .ok_or_else(|| {
190 Error::Other("Could not get receipt from search back message".to_string())
191 })?
192 .1;
193 Ok(message_receipt)
194 }
195
196 pub async fn wait_for_message_with_timeout(
197 &self,
198 msg_cid: Cid,
199 confidence: i64,
200 look_back_limit: Option<ChainEpoch>,
201 allow_replaced: Option<bool>,
202 timeout: Duration,
203 ) -> Result<(Tipset, Receipt), Error> {
204 let cancellation_token = CancellationToken::new();
205 let _cancellation_token_drop_guard = cancellation_token.drop_guard_ref();
206 tokio::time::timeout(
207 timeout,
208 self.wait_for_message(
209 msg_cid,
210 confidence,
211 look_back_limit,
212 allow_replaced,
213 &cancellation_token,
214 ),
215 )
216 .await
217 .map_err(|_| {
218 Error::other(format!(
219 "wait_for_message timed out after {}",
220 humantime::format_duration(timeout)
221 ))
222 })?
223 }
224
225 pub async fn wait_for_message(
231 &self,
232 msg_cid: Cid,
233 confidence: i64,
234 look_back_limit: Option<ChainEpoch>,
235 allow_replaced: Option<bool>,
236 cancellation_token: &CancellationToken,
237 ) -> Result<(Tipset, Receipt), Error> {
238 let message = Arc::new(
239 crate::chain::get_chain_message(self.db(), &msg_cid)
240 .map_err(|err| Error::Other(format!("failed to load message {err:}")))?,
241 );
242 let current_ts = self.heaviest_tipset();
243 let maybe_message_receipt = self.tipset_executed_message(¤t_ts, &message, true)?;
244 if let Some(receipt) = maybe_message_receipt {
245 return Ok((current_ts, receipt));
246 }
247
248 let (search_back_tx, search_back_rx) = flume::bounded(1);
250 let search_back_candidate: Arc<OnceLock<(Tipset, Receipt)>> = Default::default();
251 let reverted: Arc<RwLock<HashSet<TipsetKey>>> = Arc::new(RwLock::new(HashSet::default()));
252 tokio::task::spawn_blocking({
254 let sm = self.shallow_clone();
255 let message = message.shallow_clone();
256 let search_back_tx = search_back_tx.clone();
258 let search_back_candidate = search_back_candidate.shallow_clone();
259 let reverted = reverted.shallow_clone();
260 let cancellation_token = cancellation_token.clone();
261 move || {
262 if let Ok(Some((ts, receipt))) = sm
263 .search_back_for_message_blocking(
264 current_ts,
265 &message,
266 look_back_limit,
267 allow_replaced,
268 &cancellation_token,
269 )
270 .inspect_err(|e| {
271 tracing::warn!("failed to search back for message: {e}");
272 })
273 && !reverted.read().contains(ts.key())
274 {
275 if sm.heaviest_tipset().epoch() >= ts.epoch() + confidence {
276 _ = search_back_tx.send((ts, receipt)).inspect_err(|e| {
277 tracing::warn!("failed to send to search_back_tx: {e}");
278 });
279 } else {
280 _ = search_back_candidate.set((ts, receipt)).inspect_err(|_| {
281 tracing::warn!("failed to send to set search_back_candidate");
282 });
283 }
284 }
285 }
286 });
287
288 let subscriber_poll = tokio::task::spawn({
290 let cancellation_token = cancellation_token.clone();
291 let search_back_candidate = search_back_candidate.shallow_clone();
292 let reverted = reverted.shallow_clone();
293 let sm = self.shallow_clone();
294 async move {
295 let mut head_changes_rx = sm.cs.subscribe_head_changes();
296 let mut candidate: Option<(Tipset, Receipt)> = None;
297 while !cancellation_token.is_cancelled() {
298 match head_changes_rx.recv().await {
299 Ok(head_changes) => {
300 for reverted_ts in head_changes.reverts {
301 reverted.write().insert(reverted_ts.key().clone());
302
303 if candidate
304 .as_ref()
305 .is_some_and(|(ts, _)| ts.key() == reverted_ts.key())
306 {
307 candidate = None;
308 }
309 }
310 for applied_ts in head_changes.applies {
311 reverted.write().remove(applied_ts.key());
312
313 if let Some((candidate_ts, candidate_receipt)) =
315 search_back_candidate.get()
316 && applied_ts.epoch() >= candidate_ts.epoch() + confidence
317 && !reverted.read().contains(candidate_ts.key())
318 {
319 return Ok((
320 candidate_ts.shallow_clone(),
321 candidate_receipt.clone(),
322 ));
323 }
324
325 if let Some((candidate_ts, _)) = &candidate
327 && applied_ts.epoch() >= candidate_ts.epoch() + confidence
328 && let Some(candidate) = candidate
329 {
330 return Ok(candidate);
331 }
332
333 let maybe_receipt =
334 sm.tipset_executed_message(&applied_ts, &message, true)?;
335 if let Some(receipt) = maybe_receipt {
336 if confidence == 0 {
337 return Ok((applied_ts, receipt));
339 } else {
340 candidate = Some((applied_ts, receipt));
342 }
343 }
344 }
345 }
346 Err(RecvError::Lagged(i)) => {
347 warn!(
348 "wait for message head change subscriber lagged, skipped {} events",
349 i
350 );
351 }
352 Err(RecvError::Closed) => break,
353 }
354 }
355 Err(Error::other("cancelled"))
356 }
357 });
358
359 tokio::select! {
361 res = subscriber_poll => {
362 res.context("tokio join error")?
363 }
364 res = search_back_rx.recv_async() => {
365 Ok(res.context("channel receive error")?)
366 }
367 _ = cancellation_token.cancelled() => {
368 Err(Error::other("cancelled"))
369 }
370 }
371 }
372
373 pub async fn search_for_message(
374 &self,
375 from: Option<Tipset>,
376 msg_cid: Cid,
377 look_back_limit: Option<i64>,
378 allow_replaced: Option<bool>,
379 cancellation_token: &CancellationToken,
380 ) -> Result<Option<(Tipset, Receipt)>, Error> {
381 let from = from.unwrap_or_else(|| self.heaviest_tipset());
382 let message = crate::chain::get_chain_message(self.db(), &msg_cid)
383 .map_err(|err| Error::Other(format!("failed to load message {err}")))?;
384 let maybe_message_receipt =
385 self.tipset_executed_message(&from, &message, allow_replaced.unwrap_or(true))?;
386 if let Some(r) = maybe_message_receipt {
387 Ok(Some((from, r)))
388 } else {
389 tokio::task::spawn_blocking({
390 let this = self.shallow_clone();
391 let cancellation_token = cancellation_token.clone();
392 move || {
393 this.search_back_for_message_blocking(
394 from,
395 &message,
396 look_back_limit,
397 allow_replaced,
398 &cancellation_token,
399 )
400 }
401 })
402 .await?
403 }
404 }
405}
406
407#[cfg(test)]
408mod tests {
409 use super::*;
410 use crate::blocks::{
411 CachingBlockHeader, Chain4U, HeaderBuilder, RawBlockHeader, TxMeta, chain4u,
412 };
413 use crate::chain::ChainStore;
414 use crate::db::MemoryDB;
415 use crate::networks::ChainConfig;
416 use crate::shim::address::Address;
417 use crate::shim::econ::TokenAmount;
418 use crate::shim::message::Message;
419 use crate::shim::state_tree::{ActorState, StateTree, StateTreeVersion};
420 use crate::utils::db::CborStoreExt as _;
421 use fil_actors_shared::fvm_ipld_amt::Amtv0;
422 use fvm_ipld_blockstore::Blockstore;
423
424 const SENDER: Address = Address::new_id(100);
425
426 fn state_root_with_sender_nonce(db: &Arc<MemoryDB>, sequence: u64) -> Cid {
427 let mut state_tree = StateTree::new(db, StateTreeVersion::V5).unwrap();
428 state_tree
429 .set_actor(
430 &SENDER,
431 ActorState::new(
432 Cid::default(),
433 Cid::default(),
434 TokenAmount::default(),
435 sequence,
436 None,
437 ),
438 )
439 .unwrap();
440 state_tree.flush().unwrap()
441 }
442
443 fn tx_meta(db: &impl Blockstore, message: Cid) -> Cid {
444 let bls_message_root = Amtv0::new_from_iter(db, [message]).unwrap();
445 let secp_message_root = Amtv0::new_from_iter(db, std::iter::empty::<Cid>()).unwrap();
446 db.put_cbor_default(&TxMeta {
447 bls_message_root,
448 secp_message_root,
449 })
450 .unwrap()
451 }
452
453 fn receipts_root(db: &impl Blockstore) -> Cid {
454 let receipt = fvm_shared4::receipt::Receipt {
455 exit_code: fvm_shared4::error::ExitCode::OK,
456 return_data: Default::default(),
457 gas_used: 0,
458 events_root: None,
459 };
460 Amtv0::new_from_iter(db, [receipt]).unwrap()
461 }
462
463 fn message_with_nonce(sequence: u64) -> Message {
464 Message {
465 from: SENDER,
466 to: Address::new_id(101),
467 sequence,
468 ..Default::default()
469 }
470 }
471
472 fn state_manager_with_head(
473 db: Arc<MemoryDB>,
474 genesis: &RawBlockHeader,
475 head: &Tipset,
476 ) -> StateManager {
477 let chain_store = ChainStore::new(
478 db,
479 Arc::new(ChainConfig::default()),
480 CachingBlockHeader::new(genesis.clone()),
481 )
482 .unwrap();
483 chain_store.set_heaviest_tipset(head.clone()).unwrap();
484 StateManager::new(chain_store).unwrap()
485 }
486
487 async fn search_pending(
491 actor_nonce: u64,
492 message_nonce: u64,
493 ) -> Result<Option<(Tipset, Receipt)>, Error> {
494 let db = Arc::new(MemoryDB::default());
495 let root = state_root_with_sender_nonce(&db, actor_nonce);
496 let c4u = Chain4U::with_blockstore(db.clone());
497 chain4u! {
498 in c4u;
499 [genesis = HeaderBuilder::new().with_timestamp(7777)]
500 -> [_e1 = HeaderBuilder::new().with_state_root(root)]
501 -> [_e2 = HeaderBuilder::new().with_state_root(root)]
502 -> head @ [_e3 = HeaderBuilder::new().with_state_root(root)]
503 };
504 let state_manager = state_manager_with_head(db.clone(), genesis, head);
505
506 let message = message_with_nonce(message_nonce);
507 let msg_cid = db.put_cbor_default(&message).unwrap();
508
509 state_manager
510 .search_for_message(None, msg_cid, None, Some(true), &CancellationToken::new())
511 .await
512 }
513
514 #[tokio::test]
515 async fn search_returns_none_for_message_with_future_nonce() {
516 let result = search_pending(5, 10).await.unwrap();
517 assert!(result.is_none());
518 }
519
520 #[tokio::test]
521 async fn search_returns_none_for_pending_message_at_current_nonce() {
522 let result = search_pending(5, 5).await.unwrap();
523 assert!(result.is_none());
524 }
525
526 #[tokio::test]
527 async fn search_returns_none_for_pending_message_from_fresh_sender() {
528 let result = search_pending(0, 0).await.unwrap();
529 assert!(result.is_none());
530 }
531
532 #[tokio::test]
536 async fn search_returns_none_for_replaced_message() {
537 let db = Arc::new(MemoryDB::default());
538 let message = message_with_nonce(5);
539 let msg_cid = db.put_cbor_default(&message).unwrap();
540
541 let root_before = state_root_with_sender_nonce(&db, 5);
542 let root_after = state_root_with_sender_nonce(&db, 6);
543 let c4u = Chain4U::with_blockstore(db.clone());
544 chain4u! {
545 in c4u;
546 [genesis = HeaderBuilder::new().with_timestamp(7777)]
547 -> [_e1 = HeaderBuilder::new().with_state_root(root_before)]
548 -> [_e2 = HeaderBuilder::new().with_state_root(root_before)]
549 -> [_e3 = HeaderBuilder::new().with_state_root(root_after)]
550 -> head @ [_e4 = HeaderBuilder::new().with_state_root(root_after)]
551 };
552 let state_manager = state_manager_with_head(db.clone(), genesis, head);
553
554 let result = state_manager
555 .search_for_message(None, msg_cid, None, Some(true), &CancellationToken::new())
556 .await
557 .unwrap();
558 assert!(result.is_none());
559 }
560
561 #[tokio::test]
562 async fn search_finds_executed_message() {
563 let db = Arc::new(MemoryDB::default());
564 let message = message_with_nonce(5);
565 let msg_cid = db.put_cbor_default(&message).unwrap();
566
567 let root_before = state_root_with_sender_nonce(&db, 5);
568 let root_after = state_root_with_sender_nonce(&db, 6);
569 let messages = tx_meta(&db, msg_cid);
570 let receipts = receipts_root(&db);
571 let c4u = Chain4U::with_blockstore(db.clone());
572 chain4u! {
573 in c4u;
574 [genesis = HeaderBuilder::new().with_timestamp(7777)]
575 -> [_e1 = HeaderBuilder::new().with_state_root(root_before)]
576 -> [_e2 = HeaderBuilder::new()
577 .with_state_root(root_before)
578 .with_messages(messages)]
579 -> [_e3 = HeaderBuilder::new()
580 .with_state_root(root_after)
581 .with_message_receipts(receipts)]
582 -> head @ [_e4 = HeaderBuilder::new().with_state_root(root_after)]
583 };
584 let state_manager = state_manager_with_head(db.clone(), genesis, head);
585
586 let (tipset, receipt) = state_manager
587 .search_for_message(None, msg_cid, None, Some(true), &CancellationToken::new())
588 .await
589 .unwrap()
590 .expect("executed message should be found");
591 assert_eq!(tipset.epoch(), 3);
592 assert!(receipt.exit_code().is_success());
593 }
594
595 #[tokio::test]
598 async fn search_from_older_tipset_ignores_later_execution() {
599 let db = Arc::new(MemoryDB::default());
600 let message = message_with_nonce(5);
601 let msg_cid = db.put_cbor_default(&message).unwrap();
602
603 let root_before = state_root_with_sender_nonce(&db, 5);
604 let root_after = state_root_with_sender_nonce(&db, 6);
605 let messages = tx_meta(&db, msg_cid);
606 let receipts = receipts_root(&db);
607 let c4u = Chain4U::with_blockstore(db.clone());
608 chain4u! {
609 in c4u;
610 [genesis = HeaderBuilder::new().with_timestamp(7777)]
611 -> [_e1 = HeaderBuilder::new().with_state_root(root_before)]
612 -> from @ [_e2 = HeaderBuilder::new()
613 .with_state_root(root_before)
614 .with_messages(messages)]
615 -> [_e3 = HeaderBuilder::new()
616 .with_state_root(root_after)
617 .with_message_receipts(receipts)]
618 -> head @ [_e4 = HeaderBuilder::new().with_state_root(root_after)]
619 };
620 let state_manager = state_manager_with_head(db.clone(), genesis, head);
621
622 let result = state_manager
623 .search_for_message(
624 Some(from.clone()),
625 msg_cid,
626 None,
627 Some(true),
628 &CancellationToken::new(),
629 )
630 .await
631 .unwrap();
632 assert!(result.is_none());
633 }
634}