1use super::state_computation::{
5 TipsetExecutor, apply_block_messages_blocking, validate_tipsets_blocking,
6};
7use super::utils::structured;
8use super::*;
9use crate::interpreter::{CalledAt, VMTrace};
10use crate::rpc::state::{ApiInvocResult, MessageGasCost};
11use anyhow::{Context as _, bail};
12use num_traits::identities::Zero;
13use std::ops::RangeInclusive;
14
15impl StateManager {
16 pub async fn replay(&self, ts: Tipset, mcid: Cid) -> Result<ApiInvocResult, Error> {
19 let this = self.shallow_clone();
20 tokio::task::spawn_blocking(move || this.replay_blocking(ts, mcid)).await?
21 }
22
23 pub fn replay_blocking(&self, ts: Tipset, mcid: Cid) -> Result<ApiInvocResult, Error> {
25 const REPLAY_HALT: &str = "replay_halt";
26
27 let mut api_invoc_result = None;
28 let callback = |ctx: MessageCallbackCtx<'_>| {
29 match ctx.at {
30 CalledAt::Applied | CalledAt::Reward
31 if api_invoc_result.is_none() && ctx.cid == mcid =>
32 {
33 api_invoc_result = Some(ApiInvocResult {
34 msg_cid: ctx.message.cid(),
35 msg: ctx.message.message().clone(),
36 msg_rct: Some(ctx.apply_ret.msg_receipt()),
37 error: ctx.apply_ret.failure_info().unwrap_or_default(),
38 duration: ctx.duration.as_nanos().clamp(0, u128::from(u64::MAX)) as u64,
39 gas_cost: MessageGasCost::new(ctx.message.message(), ctx.apply_ret)?,
40 execution_trace: structured::parse_events(ctx.apply_ret.exec_trace())
41 .unwrap_or_default(),
42 });
43 anyhow::bail!(REPLAY_HALT);
44 }
45 _ => Ok(()), }
47 };
48 let result = self.compute_tipset_state_blocking(ts, Some(callback), VMTrace::Traced);
49 if let Err(error_message) = result
50 && error_message.to_string() != REPLAY_HALT
51 {
52 return Err(Error::Other(format!(
53 "unexpected error during execution : {error_message:}"
54 )));
55 }
56 api_invoc_result.ok_or_else(|| Error::Other("failed to replay".into()))
57 }
58
59 pub async fn replay_for_prestate(
62 &self,
63 ts: Tipset,
64 target_message_cid: Cid,
65 ) -> Result<(Cid, ApiInvocResult, Cid), Error> {
66 let this = self.shallow_clone();
67 tokio::task::spawn_blocking(move || {
68 this.replay_for_prestate_blocking(ts, target_message_cid)
69 })
70 .await
71 .map_err(|e| Error::Other(format!("{e}")))?
72 }
73
74 fn replay_for_prestate_blocking(
75 &self,
76 ts: Tipset,
77 target_msg_cid: Cid,
78 ) -> Result<(Cid, ApiInvocResult, Cid), Error> {
79 if ts.epoch() == 0 {
80 return Err(Error::Other(
81 "cannot trace messages in the genesis block".into(),
82 ));
83 }
84
85 let genesis_timestamp = self.chain_store().genesis_block_header().timestamp;
86 let exec = TipsetExecutor::new(
87 self.chain_index().shallow_clone(),
88 self.chain_config().shallow_clone(),
89 self.beacon_schedule().shallow_clone(),
90 &self.engine,
91 ts.shallow_clone(),
92 );
93 let mut no_cb = NO_CALLBACK;
94 let (parent_state, epoch, block_messages) =
95 exec.prepare_parent_state_blocking(genesis_timestamp, VMTrace::NotTraced, &mut no_cb)?;
96
97 Ok(stacker::grow(64 << 20, || {
98 let mut vm =
99 exec.create_vm(parent_state, epoch, ts.min_timestamp(), VMTrace::NotTraced)?;
100 let mut processed = ahash::HashSet::default();
101
102 for block in block_messages.iter() {
103 let mut penalty = TokenAmount::zero();
104 let mut gas_reward = TokenAmount::zero();
105
106 for msg in block.messages.iter() {
107 let cid = msg.cid();
108 if processed.contains(&cid) {
109 continue;
110 }
111
112 processed.insert(cid);
113
114 if cid == target_msg_cid {
115 let pre_root = vm.flush()?;
116 let mut traced_vm =
117 exec.create_vm(pre_root, epoch, ts.min_timestamp(), VMTrace::Traced)?;
118 let (ret, duration) = traced_vm.apply_message(msg)?;
119 let post_root = traced_vm.flush()?;
120
121 return Ok((
122 pre_root,
123 ApiInvocResult {
124 msg_cid: cid,
125 msg: msg.message().clone(),
126 msg_rct: Some(ret.msg_receipt()),
127 error: ret.failure_info().unwrap_or_default(),
128 duration: duration.as_nanos().clamp(0, u128::from(u64::MAX)) as u64,
129 gas_cost: MessageGasCost::default(),
130 execution_trace: structured::parse_events(ret.exec_trace())
131 .unwrap_or_default(),
132 },
133 post_root,
134 ));
135 }
136
137 let (ret, _) = vm.apply_message(msg)?;
138 gas_reward += ret.miner_tip();
139 penalty += ret.penalty();
140 }
141
142 if let Some(rew_msg) =
143 vm.reward_message(epoch, block.miner, block.win_count, penalty, gas_reward)?
144 {
145 let (ret, _) = vm.apply_implicit_message(&rew_msg)?;
146 if let Some(err) = ret.failure_info() {
147 bail!(
148 "failed to apply reward message for miner {}: {err}",
149 block.miner
150 );
151 }
152
153 if !ret.msg_receipt().exit_code().is_success() {
155 bail!(
156 "reward application message failed (exit: {:?})",
157 ret.msg_receipt().exit_code()
158 );
159 }
160 }
161 }
162
163 bail!("message {target_msg_cid} not found in tipset")
164 })?)
165 }
166
167 #[tracing::instrument(skip(self))]
189 pub fn validate_range_blocking(&self, epochs: RangeInclusive<i64>) -> anyhow::Result<()> {
190 let heaviest = self.heaviest_tipset();
191 let heaviest_epoch = heaviest.epoch();
192 let end = self.chain_index().load_required_tipset_by_height_blocking(
193 *epochs.end(),
194 heaviest,
195 ResolveNullTipset::TakeOlder,
196 ).with_context(|| {
197 format!(
198 "couldn't get a tipset at height {} behind heaviest tipset at height {heaviest_epoch}",
199 *epochs.end(),
200 )})?;
201
202 let tipsets = end
204 .chain(self.db())
205 .take_while(|ts| ts.epoch() >= *epochs.start());
206
207 self.validate_tipsets_blocking(tipsets)
208 }
209
210 pub fn validate_tipsets_blocking<T>(&self, tipsets: T) -> anyhow::Result<()>
211 where
212 T: Iterator<Item = Tipset> + Send,
213 {
214 validate_tipsets_blocking(
215 self.chain_index(),
216 self.chain_config(),
217 self.beacon_schedule(),
218 &self.engine,
219 tipsets,
220 )
221 }
222
223 pub async fn execution_trace(
224 &self,
225 tipset: &Tipset,
226 ) -> anyhow::Result<(Cid, Vec<Arc<ApiInvocResult>>)> {
227 let key = tipset.key();
228 let (state_root, invoc_trace) = self
229 .trace_cache
230 .get_or_insert_async(key, self.execution_trace_inner(tipset.shallow_clone()))
231 .await?;
232 Ok((state_root.into(), invoc_trace))
233 }
234
235 async fn execution_trace_inner(
236 &self,
237 tipset: Tipset,
238 ) -> anyhow::Result<(CidWrapper, Vec<Arc<ApiInvocResult>>)> {
239 let this = self.shallow_clone();
240 tokio::task::spawn_blocking(move || this.execution_trace_inner_blocking(tipset))
241 .await
242 .context("tokio join error")?
243 }
244
245 fn execution_trace_inner_blocking(
246 &self,
247 tipset: Tipset,
248 ) -> anyhow::Result<(CidWrapper, Vec<Arc<ApiInvocResult>>)> {
249 let mut invoc_trace = vec![];
250
251 let callback = |ctx: MessageCallbackCtx<'_>| {
252 match ctx.at {
253 CalledAt::Applied | CalledAt::Reward => {
254 invoc_trace.push(Arc::new(ApiInvocResult {
255 msg_cid: ctx.message.cid(),
256 msg: ctx.message.message().clone(),
257 msg_rct: Some(ctx.apply_ret.msg_receipt()),
258 error: ctx.apply_ret.failure_info().unwrap_or_default(),
259 duration: ctx.duration.as_nanos().clamp(0, u128::from(u64::MAX)) as u64,
260 gas_cost: MessageGasCost::new(ctx.message.message(), ctx.apply_ret)?,
261 execution_trace: structured::parse_events(ctx.apply_ret.exec_trace())
262 .unwrap_or_default(),
263 }));
264 Ok(())
265 }
266 _ => Ok(()), }
268 };
269
270 let ExecutedTipset { state_root, .. } = apply_block_messages_blocking(
271 self.chain_index().shallow_clone(),
272 self.chain_config().shallow_clone(),
273 self.beacon_schedule().shallow_clone(),
274 &self.engine,
275 tipset,
276 Some(callback),
277 VMTrace::Traced,
278 )?;
279
280 Ok((state_root.into(), invoc_trace))
281 }
282}