forest/shim/
executor.rs

1// Copyright 2019-2025 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use super::trace::ExecutionEvent;
5use crate::shim::{
6    econ::TokenAmount, fvm_shared_latest::ActorID, fvm_shared_latest::error::ExitCode,
7};
8use crate::utils::get_size::{GetSize, vec_heap_size_with_fn_helper};
9use cid::Cid;
10use fil_actors_shared::fvm_ipld_amt::Amtv0;
11use fvm_ipld_blockstore::Blockstore;
12use fvm_ipld_encoding::RawBytes;
13use fvm_shared2::receipt::Receipt as Receipt_v2;
14use fvm_shared3::event::ActorEvent as ActorEvent_v3;
15use fvm_shared3::event::Entry as Entry_v3;
16use fvm_shared3::event::StampedEvent as StampedEvent_v3;
17pub use fvm_shared3::receipt::Receipt as Receipt_v3;
18use fvm_shared4::event::ActorEvent as ActorEvent_v4;
19use fvm_shared4::event::Entry as Entry_v4;
20use fvm_shared4::event::StampedEvent as StampedEvent_v4;
21use fvm_shared4::receipt::Receipt as Receipt_v4;
22use fvm2::executor::ApplyRet as ApplyRet_v2;
23use fvm3::executor::ApplyRet as ApplyRet_v3;
24use fvm4::executor::ApplyRet as ApplyRet_v4;
25use serde::Serialize;
26
27#[derive(Clone, Debug)]
28pub enum ApplyRet {
29    V2(Box<ApplyRet_v2>),
30    V3(Box<ApplyRet_v3>),
31    V4(Box<ApplyRet_v4>),
32}
33
34impl From<ApplyRet_v2> for ApplyRet {
35    fn from(other: ApplyRet_v2) -> Self {
36        ApplyRet::V2(Box::new(other))
37    }
38}
39
40impl From<ApplyRet_v3> for ApplyRet {
41    fn from(other: ApplyRet_v3) -> Self {
42        ApplyRet::V3(Box::new(other))
43    }
44}
45
46impl From<ApplyRet_v4> for ApplyRet {
47    fn from(other: ApplyRet_v4) -> Self {
48        ApplyRet::V4(Box::new(other))
49    }
50}
51
52impl ApplyRet {
53    pub fn failure_info(&self) -> Option<String> {
54        match self {
55            ApplyRet::V2(v2) => v2.failure_info.as_ref().map(|failure| failure.to_string()),
56            ApplyRet::V3(v3) => v3.failure_info.as_ref().map(|failure| failure.to_string()),
57            ApplyRet::V4(v4) => v4.failure_info.as_ref().map(|failure| failure.to_string()),
58        }
59        .map(|e| format!("{} (RetCode={})", e, self.msg_receipt().exit_code()))
60    }
61
62    pub fn miner_tip(&self) -> TokenAmount {
63        match self {
64            ApplyRet::V2(v2) => (&v2.miner_tip).into(),
65            ApplyRet::V3(v3) => (&v3.miner_tip).into(),
66            ApplyRet::V4(v4) => (&v4.miner_tip).into(),
67        }
68    }
69
70    pub fn penalty(&self) -> TokenAmount {
71        match self {
72            ApplyRet::V2(v2) => (&v2.penalty).into(),
73            ApplyRet::V3(v3) => (&v3.penalty).into(),
74            ApplyRet::V4(v4) => (&v4.penalty).into(),
75        }
76    }
77
78    pub fn msg_receipt(&self) -> Receipt {
79        match self {
80            ApplyRet::V2(v2) => Receipt::V2(v2.msg_receipt.clone()),
81            ApplyRet::V3(v3) => Receipt::V3(v3.msg_receipt.clone()),
82            ApplyRet::V4(v4) => Receipt::V4(v4.msg_receipt.clone()),
83        }
84    }
85
86    pub fn refund(&self) -> TokenAmount {
87        match self {
88            ApplyRet::V2(v2) => (&v2.refund).into(),
89            ApplyRet::V3(v3) => (&v3.refund).into(),
90            ApplyRet::V4(v4) => (&v4.refund).into(),
91        }
92    }
93
94    pub fn base_fee_burn(&self) -> TokenAmount {
95        match self {
96            ApplyRet::V2(v2) => (&v2.base_fee_burn).into(),
97            ApplyRet::V3(v3) => (&v3.base_fee_burn).into(),
98            ApplyRet::V4(v4) => (&v4.base_fee_burn).into(),
99        }
100    }
101
102    pub fn over_estimation_burn(&self) -> TokenAmount {
103        match self {
104            ApplyRet::V2(v2) => (&v2.over_estimation_burn).into(),
105            ApplyRet::V3(v3) => (&v3.over_estimation_burn).into(),
106            ApplyRet::V4(v4) => (&v4.over_estimation_burn).into(),
107        }
108    }
109
110    pub fn exec_trace(&self) -> Vec<ExecutionEvent> {
111        match self {
112            ApplyRet::V2(v2) => v2.exec_trace.iter().cloned().map(Into::into).collect(),
113            ApplyRet::V3(v3) => v3.exec_trace.iter().cloned().map(Into::into).collect(),
114            ApplyRet::V4(v4) => v4.exec_trace.iter().cloned().map(Into::into).collect(),
115        }
116    }
117
118    pub fn events(&self) -> Vec<StampedEvent> {
119        match self {
120            ApplyRet::V2(_) => Vec::<StampedEvent>::default(),
121            ApplyRet::V3(v3) => v3.events.iter().cloned().map(Into::into).collect(),
122            ApplyRet::V4(v4) => v4.events.iter().cloned().map(Into::into).collect(),
123        }
124    }
125}
126
127// Note: it's impossible to properly derive Deserialize.
128// To deserialize into `Receipt`, refer to `fn get_parent_receipt`
129#[derive(Clone, Debug, Serialize)]
130#[serde(untagged)]
131pub enum Receipt {
132    V2(Receipt_v2),
133    V3(Receipt_v3),
134    V4(Receipt_v4),
135}
136
137impl GetSize for Receipt {
138    fn get_heap_size(&self) -> usize {
139        match self {
140            Self::V2(r) => r.return_data.bytes().get_heap_size(),
141            Self::V3(r) => r.return_data.bytes().get_heap_size(),
142            Self::V4(r) => r.return_data.bytes().get_heap_size(),
143        }
144    }
145}
146
147impl PartialEq for Receipt {
148    fn eq(&self, other: &Self) -> bool {
149        self.exit_code() == other.exit_code()
150            && self.return_data() == other.return_data()
151            && self.gas_used() == other.gas_used()
152            && self.events_root() == other.events_root()
153    }
154}
155
156impl Receipt {
157    pub fn exit_code(&self) -> ExitCode {
158        match self {
159            Receipt::V2(v2) => ExitCode::new(v2.exit_code.value()),
160            Receipt::V3(v3) => ExitCode::new(v3.exit_code.value()),
161            Receipt::V4(v4) => v4.exit_code,
162        }
163    }
164
165    pub fn return_data(&self) -> RawBytes {
166        match self {
167            Receipt::V2(v2) => v2.return_data.clone(),
168            Receipt::V3(v3) => v3.return_data.clone(),
169            Receipt::V4(v4) => v4.return_data.clone(),
170        }
171    }
172
173    pub fn gas_used(&self) -> u64 {
174        match self {
175            Receipt::V2(v2) => v2.gas_used as u64,
176            Receipt::V3(v3) => v3.gas_used,
177            Receipt::V4(v4) => v4.gas_used,
178        }
179    }
180    pub fn events_root(&self) -> Option<Cid> {
181        match self {
182            Receipt::V2(_) => None,
183            Receipt::V3(v3) => v3.events_root,
184            Receipt::V4(v4) => v4.events_root,
185        }
186    }
187
188    pub fn get_receipt(
189        db: &impl Blockstore,
190        receipts: &Cid,
191        i: u64,
192    ) -> anyhow::Result<Option<Self>> {
193        // Try Receipt_v4 first. (Receipt_v4 and Receipt_v3 are identical, use v4 here)
194        if let Ok(amt) = Amtv0::load(receipts, db)
195            && let Ok(receipts) = amt.get(i)
196        {
197            return Ok(receipts.cloned().map(Receipt::V4));
198        }
199
200        // Fallback to Receipt_v2.
201        let amt = Amtv0::load(receipts, db)?;
202        let receipts = amt.get(i)?;
203        Ok(receipts.cloned().map(Receipt::V2))
204    }
205
206    pub fn get_receipts(db: &impl Blockstore, receipts_cid: Cid) -> anyhow::Result<Vec<Receipt>> {
207        let mut receipts = Vec::new();
208
209        // Try Receipt_v4 first. (Receipt_v4 and Receipt_v3 are identical, use v4 here)
210        if let Ok(amt) = Amtv0::<fvm_shared4::receipt::Receipt, _>::load(&receipts_cid, db) {
211            amt.for_each(|_, receipt| {
212                receipts.push(Receipt::V4(receipt.clone()));
213                Ok(())
214            })?;
215        } else {
216            // Fallback to Receipt_v2.
217            let amt = Amtv0::<fvm_shared2::receipt::Receipt, _>::load(&receipts_cid, db)?;
218            amt.for_each(|_, receipt| {
219                receipts.push(Receipt::V2(receipt.clone()));
220                Ok(())
221            })?;
222        }
223
224        Ok(receipts)
225    }
226}
227
228impl From<Receipt_v3> for Receipt {
229    fn from(other: Receipt_v3) -> Self {
230        Receipt::V3(other)
231    }
232}
233
234#[derive(Clone, Debug)]
235pub enum Entry {
236    V3(Entry_v3),
237    V4(Entry_v4),
238}
239
240impl From<Entry_v3> for Entry {
241    fn from(other: Entry_v3) -> Self {
242        Self::V3(other)
243    }
244}
245
246impl From<Entry_v4> for Entry {
247    fn from(other: Entry_v4) -> Self {
248        Self::V4(other)
249    }
250}
251
252impl Entry {
253    #[cfg(test)]
254    pub fn new(
255        flags: crate::shim::fvm_shared_latest::event::Flags,
256        key: String,
257        codec: u64,
258        value: Vec<u8>,
259    ) -> Self {
260        Entry::V4(Entry_v4 {
261            flags,
262            key,
263            codec,
264            value,
265        })
266    }
267
268    pub fn into_parts(self) -> (u64, String, u64, Vec<u8>) {
269        match self {
270            Self::V3(v3) => {
271                let Entry_v3 {
272                    flags,
273                    key,
274                    codec,
275                    value,
276                } = v3;
277                (flags.bits(), key, codec, value)
278            }
279            Self::V4(v4) => {
280                let Entry_v4 {
281                    flags,
282                    key,
283                    codec,
284                    value,
285                } = v4;
286                (flags.bits(), key, codec, value)
287            }
288        }
289    }
290
291    pub fn value(&self) -> &Vec<u8> {
292        match self {
293            Self::V3(v3) => &v3.value,
294            Self::V4(v4) => &v4.value,
295        }
296    }
297
298    pub fn codec(&self) -> u64 {
299        match self {
300            Self::V3(v3) => v3.codec,
301            Self::V4(v4) => v4.codec,
302        }
303    }
304
305    pub fn key(&self) -> &String {
306        match self {
307            Self::V3(v3) => &v3.key,
308            Self::V4(v4) => &v4.key,
309        }
310    }
311}
312
313#[derive(Clone, Debug)]
314pub enum ActorEvent {
315    V3(ActorEvent_v3),
316    V4(ActorEvent_v4),
317}
318
319impl From<ActorEvent_v3> for ActorEvent {
320    fn from(other: ActorEvent_v3) -> Self {
321        ActorEvent::V3(other)
322    }
323}
324
325impl From<ActorEvent_v4> for ActorEvent {
326    fn from(other: ActorEvent_v4) -> Self {
327        ActorEvent::V4(other)
328    }
329}
330
331impl ActorEvent {
332    pub fn entries(&self) -> Vec<Entry> {
333        match self {
334            Self::V3(v3) => v3.entries.clone().into_iter().map(Into::into).collect(),
335            Self::V4(v4) => v4.entries.clone().into_iter().map(Into::into).collect(),
336        }
337    }
338}
339
340/// Event with extra information stamped by the FVM.
341#[derive(Clone, Debug, Serialize)]
342pub enum StampedEvent {
343    V3(StampedEvent_v3),
344    V4(StampedEvent_v4),
345}
346
347impl GetSize for StampedEvent {
348    fn get_heap_size(&self) -> usize {
349        match self {
350            Self::V3(e) => vec_heap_size_with_fn_helper(&e.event.entries, |e| {
351                e.key.get_heap_size() + e.value.get_heap_size()
352            }),
353            Self::V4(e) => vec_heap_size_with_fn_helper(&e.event.entries, |e| {
354                e.key.get_heap_size() + e.value.get_heap_size()
355            }),
356        }
357    }
358}
359
360impl From<StampedEvent_v3> for StampedEvent {
361    fn from(other: StampedEvent_v3) -> Self {
362        StampedEvent::V3(other)
363    }
364}
365
366impl From<StampedEvent_v4> for StampedEvent {
367    fn from(other: StampedEvent_v4) -> Self {
368        StampedEvent::V4(other)
369    }
370}
371
372impl StampedEvent {
373    /// Returns the ID of the actor that emitted this event.
374    pub fn emitter(&self) -> ActorID {
375        match self {
376            Self::V3(v3) => v3.emitter,
377            Self::V4(v4) => v4.emitter,
378        }
379    }
380
381    /// Returns the event as emitted by the actor.
382    pub fn event(&self) -> ActorEvent {
383        match self {
384            Self::V3(v3) => v3.event.clone().into(),
385            Self::V4(v4) => v4.event.clone().into(),
386        }
387    }
388}
389
390#[cfg(test)]
391impl quickcheck::Arbitrary for Receipt {
392    fn arbitrary(g: &mut quickcheck::Gen) -> Self {
393        #[derive(derive_quickcheck_arbitrary::Arbitrary, Clone)]
394        enum Helper {
395            V2 {
396                exit_code: u32,
397                return_data: Vec<u8>,
398                gas_used: i64,
399            },
400            V3 {
401                exit_code: u32,
402                return_data: Vec<u8>,
403                gas_used: u64,
404                events_root: Option<::cid::Cid>,
405            },
406            V4 {
407                exit_code: u32,
408                return_data: Vec<u8>,
409                gas_used: u64,
410                events_root: Option<::cid::Cid>,
411            },
412        }
413        match Helper::arbitrary(g) {
414            Helper::V2 {
415                exit_code,
416                return_data,
417                gas_used,
418            } => Self::V2(Receipt_v2 {
419                exit_code: exit_code.into(),
420                return_data: return_data.into(),
421                gas_used,
422            }),
423            Helper::V3 {
424                exit_code,
425                return_data,
426                gas_used,
427                events_root,
428            } => Self::V3(Receipt_v3 {
429                exit_code: exit_code.into(),
430                return_data: return_data.into(),
431                gas_used,
432                events_root,
433            }),
434            Helper::V4 {
435                exit_code,
436                return_data,
437                gas_used,
438                events_root,
439            } => Self::V4(Receipt_v4 {
440                exit_code: exit_code.into(),
441                return_data: return_data.into(),
442                gas_used,
443                events_root,
444            }),
445        }
446    }
447}
448
449#[cfg(test)]
450mod tests {
451    use super::*;
452    use quickcheck_macros::quickcheck;
453
454    #[quickcheck]
455    fn receipt_cbor_serde_serialize(receipt: Receipt) {
456        let encoded = fvm_ipld_encoding::to_vec(&receipt).unwrap();
457        let encoded2 = match &receipt {
458            Receipt::V2(v) => fvm_ipld_encoding::to_vec(v),
459            Receipt::V3(v) => fvm_ipld_encoding::to_vec(v),
460            Receipt::V4(v) => fvm_ipld_encoding::to_vec(v),
461        }
462        .unwrap();
463        assert_eq!(encoded, encoded2);
464    }
465}