1use super::circulating_supply::GenesisInfo;
5use super::*;
6use crate::interpreter::{BlockMessages, ExecutionContext, VM, VMTrace};
7use crate::prelude::*;
8use crate::shim::message::Message;
9use crate::state_migration::run_state_migrations;
10use anyhow::{bail, ensure};
11use fil_actors_shared::fvm_ipld_amt::{Amt, Amtv0};
12use tracing::{error, info, instrument};
13
14enum StateRecomputePolicy {
15 Allowed,
16 Disallowed,
17}
18
19impl StateManager {
20 pub async fn load_tipset_state(&self, ts: &Tipset) -> anyhow::Result<TipsetState> {
22 if let Some(state) = self.cache.get_map(ts.key(), |et| et.into()) {
23 Ok(state)
24 } else {
25 match self.chain_store().load_child_tipset(ts).await? {
26 Some(receipt_ts) => Ok(TipsetState {
27 state_root: *receipt_ts.parent_state(),
28 receipt_root: *receipt_ts.parent_message_receipts(),
29 }),
30 None => Ok(self.load_executed_tipset(ts).await?.into()),
31 }
32 }
33 }
34
35 fn rpc_state_recompute_policy() -> StateRecomputePolicy {
38 crate::def_is_env_truthy!(
39 enable_state_computation,
40 "FOREST_ETH_RPC_COMPUTE_STATE_ON_INDEX_MISS"
41 );
42
43 if enable_state_computation() {
44 StateRecomputePolicy::Allowed
45 } else {
46 StateRecomputePolicy::Disallowed
47 }
48 }
49
50 pub async fn load_executed_tipset_for_rpc(
52 &self,
53 ts: &Tipset,
54 ) -> anyhow::Result<ExecutedTipset> {
55 self.load_executed_tipset_with_cache(ts, Self::rpc_state_recompute_policy())
56 .await
57 }
58
59 pub async fn load_executed_tipset_with_receipt(
63 &self,
64 msg_ts: &Tipset,
65 receipt_ts: &Tipset,
66 ) -> anyhow::Result<ExecutedTipset> {
67 self.cache
68 .get_or_insert_async(msg_ts.key(), async move {
69 self.load_executed_tipset_inner(
70 msg_ts,
71 Some(receipt_ts),
72 Self::rpc_state_recompute_policy(),
73 )
74 .await
75 })
76 .await
77 }
78
79 pub async fn load_executed_tipset(&self, ts: &Tipset) -> anyhow::Result<ExecutedTipset> {
81 self.load_executed_tipset_with_cache(ts, StateRecomputePolicy::Allowed)
82 .await
83 }
84
85 async fn load_executed_tipset_with_cache(
86 &self,
87 ts: &Tipset,
88 policy: StateRecomputePolicy,
89 ) -> anyhow::Result<ExecutedTipset> {
90 if ts.epoch() >= self.heaviest_tipset().epoch()
92 && let Some(cached) = self.cache.get(ts.key())
93 {
94 if StateTree::new_from_root(self.db(), &cached.state_root).is_ok() {
95 return Ok(cached);
96 } else {
97 self.cache.remove(ts.key());
98 }
99 }
100 self.cache
101 .get_or_insert_async(ts.key(), async move {
102 let receipt_ts = self.chain_store().load_child_tipset(ts).await?;
103 self.load_executed_tipset_inner(ts, receipt_ts.as_ref(), policy)
104 .await
105 })
106 .await
107 }
108
109 async fn load_executed_tipset_inner(
110 &self,
111 msg_ts: &Tipset,
112 receipt_ts: Option<&Tipset>,
114 policy: StateRecomputePolicy,
115 ) -> anyhow::Result<ExecutedTipset> {
116 let state_compute_disallow_error = || {
117 format!(
118 "failed to load tipset state output and recomputation is disallowed, epoch={}, key={}",
119 msg_ts.epoch(),
120 msg_ts.key()
121 )
122 };
123
124 if let Some(receipt_ts) = receipt_ts {
125 anyhow::ensure!(
126 msg_ts.key() == receipt_ts.parents(),
127 "message tipset should be the parent of message receipt tipset"
128 );
129 }
130 let allow_state_compute = matches!(policy, StateRecomputePolicy::Allowed);
131 let mut recomputed = false;
132 let (state_root, receipt_root, receipts) = match receipt_ts.and_then(|ts| {
133 let receipt_root = *ts.parent_message_receipts();
134 Receipt::get_receipts(self.cs.db(), receipt_root)
135 .ok()
136 .map(|r| (*ts.parent_state(), receipt_root, r))
137 }) {
138 Some((state_root, receipt_root, receipts)) => (state_root, receipt_root, receipts),
139 None => {
140 if !allow_state_compute {
141 anyhow::bail!(state_compute_disallow_error());
142 }
143 let state_output = self
144 .compute_tipset_state(msg_ts.shallow_clone(), NO_CALLBACK, VMTrace::NotTraced)
145 .await?;
146 recomputed = true;
147 (
148 state_output.state_root,
149 state_output.receipt_root,
150 Receipt::get_receipts(self.cs.db(), state_output.receipt_root)?,
151 )
152 }
153 };
154
155 let messages = self.chain_store().messages_for_tipset(msg_ts)?;
156 anyhow::ensure!(
157 messages.len() == receipts.len(),
158 "mismatching message and receipt counts ({} messages, {} receipts)",
159 messages.len(),
160 receipts.len()
161 );
162 let mut executed_messages = Vec::with_capacity(messages.len());
163 for (message, receipt) in messages.iter().cloned().zip(receipts) {
164 let events = if let Some(events_root) = receipt.events_root() {
165 Some(match StampedEvent::get_events(self.cs.db(), &events_root) {
166 Ok(events) => events,
167 Err(e) if recomputed => return Err(e),
168 Err(_) => {
169 if !allow_state_compute {
170 anyhow::bail!(state_compute_disallow_error());
171 }
172 self.compute_tipset_state(
173 msg_ts.shallow_clone(),
174 NO_CALLBACK,
175 VMTrace::NotTraced,
176 )
177 .await?;
178 recomputed = true;
179 StampedEvent::get_events(self.cs.db(), &events_root)?
180 }
181 })
182 } else {
183 None
184 };
185 executed_messages.push(ExecutedMessage {
186 message,
187 receipt,
188 events,
189 });
190 }
191 Ok(ExecutedTipset {
192 state_root,
193 receipt_root,
194 executed_messages: Arc::new(executed_messages),
195 })
196 }
197
198 pub async fn compute_tipset_state(
220 &self,
221 tipset: Tipset,
222 callback: Option<impl FnMut(MessageCallbackCtx<'_>) -> anyhow::Result<()> + Send + 'static>,
223 enable_tracing: VMTrace,
224 ) -> Result<ExecutedTipset, Error> {
225 let this = self.shallow_clone();
226 tokio::task::spawn_blocking(move || {
227 this.compute_tipset_state_blocking(tipset, callback, enable_tracing)
228 })
229 .await?
230 }
231
232 pub fn compute_tipset_state_blocking(
234 &self,
235 tipset: Tipset,
236 callback: Option<impl FnMut(MessageCallbackCtx<'_>) -> anyhow::Result<()>>,
237 enable_tracing: VMTrace,
238 ) -> Result<ExecutedTipset, Error> {
239 let epoch = tipset.epoch();
240 let has_callback = callback.is_some();
241 info!(
242 "Evaluating tipset: EPOCH={epoch}, blocks={}, tsk={}",
243 tipset.len(),
244 tipset.key(),
245 );
246 Ok(apply_block_messages_blocking(
247 self.chain_index().shallow_clone(),
248 self.chain_config().shallow_clone(),
249 self.beacon_schedule().shallow_clone(),
250 &self.engine,
251 tipset,
252 callback,
253 enable_tracing,
254 )
255 .map_err(|e| {
256 if has_callback {
257 e
258 } else {
259 e.context(format!("Failed to compute tipset state@{epoch}"))
260 }
261 })?)
262 }
263
264 #[instrument(skip_all)]
265 pub async fn compute_state(
266 &self,
267 height: ChainEpoch,
268 messages: Vec<Message>,
269 tipset: Tipset,
270 callback: Option<impl FnMut(MessageCallbackCtx<'_>) -> anyhow::Result<()> + Send + 'static>,
271 enable_tracing: VMTrace,
272 ) -> Result<ExecutedTipset, Error> {
273 let this = self.shallow_clone();
274 tokio::task::spawn_blocking(move || {
275 this.compute_state_blocking(height, messages, tipset, callback, enable_tracing)
276 })
277 .await?
278 }
279
280 #[tracing::instrument(skip_all)]
282 pub fn compute_state_blocking(
283 &self,
284 height: ChainEpoch,
285 messages: Vec<Message>,
286 tipset: Tipset,
287 callback: Option<impl FnMut(MessageCallbackCtx<'_>) -> anyhow::Result<()>>,
288 enable_tracing: VMTrace,
289 ) -> Result<ExecutedTipset, Error> {
290 Ok(compute_state_blocking(
291 height,
292 messages,
293 tipset,
294 self.chain_index().shallow_clone(),
295 self.chain_config().shallow_clone(),
296 self.beacon_schedule().shallow_clone(),
297 &self.engine,
298 callback,
299 enable_tracing,
300 )?)
301 }
302}
303
304pub fn validate_tipsets_blocking<T>(
305 chain_index: &ChainIndex,
306 chain_config: &Arc<ChainConfig>,
307 beacon: &Arc<BeaconSchedule>,
308 engine: &MultiEngine,
309 tipsets: T,
310) -> anyhow::Result<()>
311where
312 T: Iterator<Item = Tipset> + Send,
313{
314 for (child, parent) in tipsets.tuple_windows() {
319 info!(height = parent.epoch(), "compute parent state");
320 let ExecutedTipset {
321 state_root: actual_state,
322 receipt_root: actual_receipt,
323 ..
324 } = apply_block_messages_blocking(
325 chain_index.shallow_clone(),
326 chain_config.shallow_clone(),
327 beacon.shallow_clone(),
328 engine,
329 parent,
330 NO_CALLBACK,
331 VMTrace::NotTraced,
332 )
333 .context("couldn't compute tipset state")?;
334 let expected_receipt = child.min_ticket_block().message_receipts;
335 let expected_state = child.parent_state();
336 if (expected_state, expected_receipt) != (&actual_state, actual_receipt) {
337 error!(
338 height = child.epoch(),
339 ?expected_state,
340 ?expected_receipt,
341 ?actual_state,
342 ?actual_receipt,
343 "state mismatch"
344 );
345 bail!("state mismatch");
346 }
347 }
348 Ok(())
349}
350
351pub(in crate::state_manager) struct TipsetExecutor<'a> {
356 tipset: Tipset,
357 rand: ChainRand,
358 chain_config: Arc<ChainConfig>,
359 chain_index: ChainIndex,
360 genesis_info: GenesisInfo,
361 engine: &'a MultiEngine,
362}
363
364impl<'a> TipsetExecutor<'a> {
365 pub(in crate::state_manager) fn new(
366 chain_index: ChainIndex,
367 chain_config: Arc<ChainConfig>,
368 beacon: Arc<BeaconSchedule>,
369 engine: &'a MultiEngine,
370 tipset: Tipset,
371 ) -> Self {
372 let rand = ChainRand::new(
373 chain_config.shallow_clone(),
374 tipset.shallow_clone(),
375 chain_index.shallow_clone(),
376 beacon,
377 );
378 let genesis_info = GenesisInfo::from_chain_config(chain_config.shallow_clone());
379 Self {
380 tipset,
381 rand,
382 chain_config,
383 chain_index,
384 genesis_info,
385 engine,
386 }
387 }
388
389 pub(in crate::state_manager) fn create_vm(
390 &self,
391 state_root: Cid,
392 epoch: ChainEpoch,
393 timestamp: u64,
394 trace: VMTrace,
395 ) -> anyhow::Result<VM> {
396 let circ_supply = self.genesis_info.get_vm_circulating_supply(
397 epoch,
398 self.chain_index.db(),
399 &state_root,
400 )?;
401 VM::new(
402 ExecutionContext {
403 heaviest_tipset: self.tipset.shallow_clone(),
404 state_tree_root: state_root,
405 epoch,
406 rand: Box::new(self.rand.shallow_clone()),
407 base_fee: self.tipset.min_ticket_block().parent_base_fee.clone(),
408 circ_supply,
409 chain_config: self.chain_config.shallow_clone(),
410 chain_index: self.chain_index.shallow_clone(),
411 timestamp,
412 },
413 self.engine,
414 trace,
415 )
416 }
417
418 pub(in crate::state_manager) fn prepare_parent_state_blocking<F>(
421 &self,
422 genesis_timestamp: u64,
423 null_epoch_trace: VMTrace,
424 cron_callback: &mut Option<F>,
425 ) -> anyhow::Result<(Cid, ChainEpoch, Vec<BlockMessages>)>
426 where
427 F: FnMut(MessageCallbackCtx<'_>) -> anyhow::Result<()>,
428 {
429 use crate::shim::clock::EPOCH_DURATION_SECONDS;
430
431 let mut parent_state = *self.tipset.parent_state();
432 let parent_epoch = self
433 .chain_index
434 .load_required_tipset(self.tipset.parents())?
435 .epoch();
436 let epoch = self.tipset.epoch();
437
438 for epoch_i in parent_epoch..epoch {
439 if epoch_i > parent_epoch {
440 let timestamp = genesis_timestamp + ((EPOCH_DURATION_SECONDS * epoch_i) as u64);
441 parent_state = stacker::grow(64 << 20, || -> anyhow::Result<Cid> {
442 let mut vm =
443 self.create_vm(parent_state, epoch_i, timestamp, null_epoch_trace)?;
444 if let Err(e) = vm.run_cron(epoch_i, cron_callback.as_mut()) {
445 error!("Beginning of epoch cron failed to run: {e:#}");
446 return Err(e);
447 }
448 vm.flush()
449 })?;
450 }
451 if let Some(new_state) = run_state_migrations(
452 epoch_i,
453 &self.chain_config,
454 self.chain_index.db(),
455 &parent_state,
456 )? {
457 parent_state = new_state;
458 }
459 }
460
461 let block_messages = BlockMessages::for_tipset(self.chain_index.db(), &self.tipset)?;
462 Ok((parent_state, epoch, block_messages))
463 }
464}
465
466#[allow(clippy::too_many_arguments)]
543pub fn apply_block_messages_blocking(
544 chain_index: ChainIndex,
545 chain_config: Arc<ChainConfig>,
546 beacon: Arc<BeaconSchedule>,
547 engine: &MultiEngine,
548 tipset: Tipset,
549 mut callback: Option<impl FnMut(MessageCallbackCtx<'_>) -> anyhow::Result<()>>,
550 enable_tracing: VMTrace,
551) -> anyhow::Result<ExecutedTipset> {
552 let genesis_timestamp = chain_index.genesis().min_ticket_block().timestamp;
561 if tipset.epoch() == 0 {
562 let message_receipts = tipset.min_ticket_block().message_receipts;
567 return Ok(ExecutedTipset {
568 state_root: *tipset.parent_state(),
569 receipt_root: message_receipts,
570 executed_messages: vec![].into(),
571 });
572 }
573
574 let exec = TipsetExecutor::new(
575 chain_index.shallow_clone(),
576 chain_config,
577 beacon,
578 engine,
579 tipset.shallow_clone(),
580 );
581
582 let (parent_state, epoch, block_messages) =
585 exec.prepare_parent_state_blocking(genesis_timestamp, enable_tracing, &mut callback)?;
586
587 stacker::grow(64 << 20, || -> anyhow::Result<ExecutedTipset> {
590 let mut vm = exec.create_vm(parent_state, epoch, tipset.min_timestamp(), enable_tracing)?;
591
592 let (receipts, events, events_roots) =
594 vm.apply_block_messages(&block_messages, epoch, callback)?;
595
596 let receipt_root = Amtv0::new_from_iter(chain_index.db(), receipts.iter())?;
598
599 for (events, events_root) in events.iter().zip(events_roots.iter()) {
601 if let Some(events) = events {
602 let event_root =
603 events_root.context("events root should be present when events present")?;
604 let derived_event_root = Amt::new_from_iter_with_bit_width(
606 chain_index.db(),
607 EVENTS_AMT_BITWIDTH,
608 events.iter(),
609 )
610 .map_err(|e| Error::Other(format!("failed to store events AMT: {e}")))?;
611
612 ensure!(
614 derived_event_root == event_root,
615 "Events AMT root mismatch: derived={derived_event_root}, actual={event_root}."
616 );
617 }
618 }
619
620 let state_root = vm.flush()?;
621
622 let messages: Vec<ChainMessage> = block_messages
624 .into_iter()
625 .flat_map(|bm| bm.messages)
626 .collect_vec();
627 anyhow::ensure!(
628 messages.len() == receipts.len() && messages.len() == events.len(),
629 "length of messages, receipts, and events should match",
630 );
631 Ok(ExecutedTipset {
632 state_root,
633 receipt_root,
634 executed_messages: messages
635 .into_iter()
636 .zip(receipts)
637 .zip(events)
638 .map(|((message, receipt), events)| ExecutedMessage {
639 message,
640 receipt,
641 events,
642 })
643 .collect_vec()
644 .into(),
645 })
646 })
647}
648
649#[allow(clippy::too_many_arguments)]
650pub(in crate::state_manager) fn compute_state_blocking(
651 _height: ChainEpoch,
652 messages: Vec<Message>,
653 tipset: Tipset,
654 chain_index: ChainIndex,
655 chain_config: Arc<ChainConfig>,
656 beacon: Arc<BeaconSchedule>,
657 engine: &MultiEngine,
658 callback: Option<impl FnMut(MessageCallbackCtx<'_>) -> anyhow::Result<()>>,
659 enable_tracing: VMTrace,
660) -> anyhow::Result<ExecutedTipset> {
661 if !messages.is_empty() {
662 anyhow::bail!("Applying messages is not yet implemented.");
663 }
664
665 let output = apply_block_messages_blocking(
666 chain_index,
667 chain_config,
668 beacon,
669 engine,
670 tipset,
671 callback,
672 enable_tracing,
673 )?;
674
675 Ok(output)
676}