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> {
25 let (_, trace) = self
26 .execution_trace(&ts)
27 .await
28 .map_err(|e| Error::Other(format!("unexpected error during execution : {e}")))?;
29 trace
30 .iter()
31 .find(|r| r.msg_cid == mcid)
32 .map(|r| (**r).clone())
33 .ok_or_else(|| Error::Other("failed to replay".into()))
34 }
35
36 pub async fn replay_for_prestate(
39 &self,
40 ts: Tipset,
41 target_message_cid: Cid,
42 ) -> Result<(Cid, ApiInvocResult, Cid), Error> {
43 let permit = self.replay_permit().await;
44 let this = self.shallow_clone();
45 tokio::task::spawn_blocking(move || {
46 let _permit = permit;
47 this.replay_for_prestate_blocking(ts, target_message_cid)
48 })
49 .await
50 .map_err(|e| Error::Other(format!("{e}")))?
51 }
52
53 fn replay_for_prestate_blocking(
54 &self,
55 ts: Tipset,
56 target_msg_cid: Cid,
57 ) -> Result<(Cid, ApiInvocResult, Cid), Error> {
58 if ts.epoch() == 0 {
59 return Err(Error::Other(
60 "cannot trace messages in the genesis block".into(),
61 ));
62 }
63
64 let genesis_timestamp = self.chain_store().genesis_block_header().timestamp;
65 let exec = TipsetExecutor::new(
66 self.chain_index().shallow_clone(),
67 self.chain_config().shallow_clone(),
68 self.beacon_schedule().shallow_clone(),
69 &self.engine,
70 ts.shallow_clone(),
71 );
72 let mut no_cb = NO_CALLBACK;
73 let (parent_state, epoch, block_messages) =
74 exec.prepare_parent_state_blocking(genesis_timestamp, VMTrace::NotTraced, &mut no_cb)?;
75
76 Ok(stacker::grow(64 << 20, || {
77 let mut vm =
78 exec.create_vm(parent_state, epoch, ts.min_timestamp(), VMTrace::NotTraced)?;
79 let mut processed = ahash::HashSet::default();
80
81 for block in block_messages.iter() {
82 let mut penalty = TokenAmount::zero();
83 let mut gas_reward = TokenAmount::zero();
84
85 for msg in block.messages.iter() {
86 let cid = msg.cid();
87 if processed.contains(&cid) {
88 continue;
89 }
90
91 processed.insert(cid);
92
93 if cid == target_msg_cid {
94 let pre_root = vm.flush()?;
95 let mut traced_vm =
96 exec.create_vm(pre_root, epoch, ts.min_timestamp(), VMTrace::Traced)?;
97 let (ret, duration) = traced_vm.apply_message(msg)?;
98 let post_root = traced_vm.flush()?;
99
100 return Ok((
101 pre_root,
102 ApiInvocResult {
103 msg_cid: cid,
104 msg: msg.message().clone(),
105 msg_rct: Some(ret.msg_receipt()),
106 error: ret.failure_info().unwrap_or_default(),
107 duration: duration.as_nanos().clamp(0, u128::from(u64::MAX)) as u64,
108 gas_cost: MessageGasCost::default(),
109 execution_trace: structured::parse_events(ret.exec_trace())
110 .unwrap_or_default(),
111 },
112 post_root,
113 ));
114 }
115
116 let (ret, _) = vm.apply_message(msg)?;
117 gas_reward += ret.miner_tip();
118 penalty += ret.penalty();
119 }
120
121 if let Some(rew_msg) =
122 vm.reward_message(epoch, block.miner, block.win_count, penalty, gas_reward)?
123 {
124 let (ret, _) = vm.apply_implicit_message(&rew_msg)?;
125 if let Some(err) = ret.failure_info() {
126 bail!(
127 "failed to apply reward message for miner {}: {err}",
128 block.miner
129 );
130 }
131
132 if !ret.msg_receipt().exit_code().is_success() {
134 bail!(
135 "reward application message failed (exit: {:?})",
136 ret.msg_receipt().exit_code()
137 );
138 }
139 }
140 }
141
142 bail!("message {target_msg_cid} not found in tipset")
143 })?)
144 }
145
146 #[tracing::instrument(skip(self))]
168 pub fn validate_range_blocking(&self, epochs: RangeInclusive<i64>) -> anyhow::Result<()> {
169 let heaviest = self.heaviest_tipset();
170 let heaviest_epoch = heaviest.epoch();
171 let end = self.chain_index().load_required_tipset_by_height_blocking(
172 *epochs.end(),
173 heaviest,
174 ResolveNullTipset::TakeOlder,
175 ).with_context(|| {
176 format!(
177 "couldn't get a tipset at height {} behind heaviest tipset at height {heaviest_epoch}",
178 *epochs.end(),
179 )})?;
180
181 let tipsets = end
183 .chain(self.db())
184 .take_while(|ts| ts.epoch() >= *epochs.start());
185
186 self.validate_tipsets_blocking(tipsets)
187 }
188
189 pub fn validate_tipsets_blocking<T>(&self, tipsets: T) -> anyhow::Result<()>
190 where
191 T: Iterator<Item = Tipset> + Send,
192 {
193 validate_tipsets_blocking(
194 self.chain_index(),
195 self.chain_config(),
196 self.beacon_schedule(),
197 &self.engine,
198 tipsets,
199 )
200 }
201
202 pub async fn execution_trace(
203 &self,
204 tipset: &Tipset,
205 ) -> anyhow::Result<(Cid, Vec<Arc<ApiInvocResult>>)> {
206 let key = tipset.key();
207 let (state_root, invoc_trace) = self
208 .trace_cache
209 .get_or_insert_async(key, self.execution_trace_inner(tipset.shallow_clone()))
210 .await?;
211 Ok((state_root.into(), invoc_trace))
212 }
213
214 async fn execution_trace_inner(
215 &self,
216 tipset: Tipset,
217 ) -> anyhow::Result<(CidWrapper, Vec<Arc<ApiInvocResult>>)> {
218 let permit = self.replay_permit().await;
219 let this = self.shallow_clone();
220 tokio::task::spawn_blocking(move || {
221 let _permit = permit;
222 this.execution_trace_inner_blocking(tipset)
223 })
224 .await
225 .context("tokio join error")?
226 }
227
228 fn execution_trace_inner_blocking(
229 &self,
230 tipset: Tipset,
231 ) -> anyhow::Result<(CidWrapper, Vec<Arc<ApiInvocResult>>)> {
232 let mut invoc_trace = vec![];
233
234 let callback = |ctx: MessageCallbackCtx<'_>| {
235 match ctx.at {
236 CalledAt::Applied | CalledAt::Reward => {
237 invoc_trace.push(Arc::new(ApiInvocResult {
238 msg_cid: ctx.message.cid(),
239 msg: ctx.message.message().clone(),
240 msg_rct: Some(ctx.apply_ret.msg_receipt()),
241 error: ctx.apply_ret.failure_info().unwrap_or_default(),
242 duration: ctx.duration.as_nanos().clamp(0, u128::from(u64::MAX)) as u64,
243 gas_cost: MessageGasCost::new(ctx.message.message(), ctx.apply_ret)?,
244 execution_trace: structured::parse_events(ctx.apply_ret.exec_trace())
245 .unwrap_or_default(),
246 }));
247 Ok(())
248 }
249 _ => Ok(()), }
251 };
252
253 let ExecutedTipset { state_root, .. } = apply_block_messages_blocking(
254 self.chain_index().shallow_clone(),
255 self.chain_config().shallow_clone(),
256 self.beacon_schedule().shallow_clone(),
257 &self.engine,
258 tipset,
259 Some(callback),
260 VMTrace::Traced,
261 )?;
262
263 Ok((state_root.into(), invoc_trace))
264 }
265}