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