Skip to main content

forest/state_manager/
utils.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use super::MinerActorStateLoad as _;
5use crate::shim::actors::miner;
6use crate::shim::{
7    actors::{is_account_actor, is_ethaccount_actor, is_placeholder_actor},
8    address::{Address, Payload},
9    randomness::Randomness,
10    sector::{ExtendedSectorInfo, RegisteredPoStProof, RegisteredSealProof},
11    state_tree::ActorState,
12    version::NetworkVersion,
13};
14use crate::state_manager::{StateManager, errors::*};
15use crate::utils::encoding::prover_id_from_u64;
16use cid::Cid;
17use fil_actors_shared::filecoin_proofs_api::post;
18use fil_actors_shared::fvm_ipld_bitfield::BitField;
19use fvm_ipld_blockstore::Blockstore;
20use fvm_ipld_encoding::bytes_32;
21
22impl<DB> StateManager<DB>
23where
24    DB: Blockstore,
25{
26    /// Retrieves and generates a vector of sector info for the winning `PoSt`
27    /// verification.
28    pub fn get_sectors_for_winning_post(
29        &self,
30        st: &Cid,
31        nv: NetworkVersion,
32        miner_address: &Address,
33        rand: Randomness,
34    ) -> anyhow::Result<Vec<ExtendedSectorInfo>> {
35        let store = self.blockstore();
36
37        let actor = self
38            .get_actor(miner_address, *st)?
39            .ok_or_else(|| Error::state("Miner actor address could not be resolved"))?;
40        let mas = miner::State::load(self.blockstore(), actor.code, actor.state)?;
41
42        let proving_sectors = {
43            let mut proving_sectors = BitField::new();
44
45            if nv < NetworkVersion::V7 {
46                mas.for_each_deadline(&self.chain_config().policy, store, |_, deadline| {
47                    let mut fault_sectors = BitField::new();
48                    deadline.for_each(store, |_, partition: miner::Partition| {
49                        proving_sectors |= partition.all_sectors();
50                        fault_sectors |= partition.faulty_sectors();
51                        Ok(())
52                    })?;
53
54                    proving_sectors -= &fault_sectors;
55                    Ok(())
56                })?;
57            } else {
58                mas.for_each_deadline(&self.chain_config().policy, store, |_, deadline| {
59                    deadline.for_each(store, |_, partition: miner::Partition| {
60                        proving_sectors |= &partition.active_sectors();
61                        Ok(())
62                    })?;
63                    Ok(())
64                })?;
65            }
66            proving_sectors
67        };
68
69        let num_prov_sect = proving_sectors.len();
70
71        if num_prov_sect == 0 {
72            return Ok(Vec::new());
73        }
74
75        let info = mas.info(store)?;
76        let spt = RegisteredSealProof::from_sector_size(info.sector_size(), nv);
77
78        let wpt = spt.registered_winning_post_proof()?;
79
80        let m_id = miner_address.id()?;
81
82        let ids = generate_winning_post_sector_challenge(wpt.into(), m_id, rand, num_prov_sect)?;
83
84        let mut iter = proving_sectors.iter();
85
86        let mut selected_sectors = BitField::new();
87        for n in ids {
88            let sno = iter.nth(n as usize).ok_or_else(|| {
89                anyhow::anyhow!(
90                    "Error iterating over proving sectors, id {} does not exist",
91                    n
92                )
93            })?;
94            selected_sectors.set(sno);
95        }
96
97        let sectors = mas.load_sectors(store, Some(&selected_sectors))?;
98
99        let out = sectors
100            .into_iter()
101            .map(|s_info| ExtendedSectorInfo {
102                proof: s_info.seal_proof.into(),
103                sector_number: s_info.sector_number,
104                sector_key: s_info.sector_key_cid,
105                sealed_cid: s_info.sealed_cid,
106            })
107            .collect();
108
109        Ok(out)
110    }
111}
112
113pub fn is_valid_for_sending(network_version: NetworkVersion, actor: &ActorState) -> bool {
114    // Comments from Lotus:
115    // Before nv18 (Hygge), we only supported built-in account actors as senders.
116    //
117    // Note: this gate is probably superfluous, since:
118    // 1. Placeholder actors cannot be created before nv18.
119    // 2. EthAccount actors cannot be created before nv18.
120    // 3. Delegated addresses cannot be created before nv18.
121    //
122    // But it's a safeguard.
123    //
124    // Note 2: ad-hoc checks for network versions like this across the codebase
125    // will be problematic with networks with diverging version lineages
126    // (e.g. Hyperspace). We need to revisit this strategy entirely.
127    if network_version < NetworkVersion::V18 {
128        return is_account_actor(&actor.code);
129    }
130
131    // After nv18, we also support other kinds of senders.
132    if is_account_actor(&actor.code) || is_ethaccount_actor(&actor.code) {
133        return true;
134    }
135
136    // Allow placeholder actors with a delegated address and nonce 0 to send a
137    // message. These will be converted to an EthAccount actor on first send.
138    if !is_placeholder_actor(&actor.code)
139        || actor.sequence != 0
140        || actor.delegated_address.is_none()
141    {
142        return false;
143    }
144
145    // Only allow such actors to send if their delegated address is in the EAM's
146    // namespace.
147    if let Payload::Delegated(address) = actor
148        .delegated_address
149        .as_ref()
150        .expect("unfallible")
151        .payload()
152    {
153        address.namespace() == Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR.id().unwrap()
154    } else {
155        false
156    }
157}
158
159/// Generates sector challenge indexes for use in winning PoSt verification.
160fn generate_winning_post_sector_challenge(
161    proof: RegisteredPoStProof,
162    prover_id: u64,
163    mut rand: Randomness,
164    eligible_sector_count: u64,
165) -> anyhow::Result<Vec<u64>> {
166    // Necessary to be valid bls12 381 element.
167    if let Some(b31) = rand.0.get_mut(31) {
168        *b31 &= 0x3f;
169    } else {
170        anyhow::bail!("rand should have at least 32 bytes");
171    }
172
173    post::generate_winning_post_sector_challenge(
174        proof.try_into()?,
175        &bytes_32(&rand.0),
176        eligible_sector_count,
177        prover_id_from_u64(prover_id),
178    )
179}
180
181pub mod state_compute {
182    use crate::{
183        blocks::{FullTipset, Tipset},
184        chain::store::ChainStore,
185        chain_sync::load_full_tipset,
186        db::{
187            MemoryDB,
188            car::{AnyCar, ManyCar},
189        },
190        genesis::read_genesis_header,
191        interpreter::VMTrace,
192        networks::{ChainConfig, NetworkChain},
193        state_manager::{ExecutedTipset, StateManager},
194        utils::net::{DownloadFileOption, download_file_with_cache},
195    };
196    use directories::ProjectDirs;
197    use sonic_rs::JsonValueTrait;
198    use std::{
199        path::{Path, PathBuf},
200        sync::{Arc, LazyLock},
201        time::{Duration, Instant},
202    };
203    use tokio::io::AsyncReadExt;
204    use url::Url;
205
206    const DO_SPACE_ROOT: &str = "https://forest-snapshots.fra1.cdn.digitaloceanspaces.com/";
207
208    #[allow(dead_code)]
209    pub async fn get_state_compute_snapshot(
210        chain: &NetworkChain,
211        epoch: i64,
212    ) -> anyhow::Result<PathBuf> {
213        get_state_snapshot(chain, "state_compute", epoch).await
214    }
215
216    #[allow(dead_code)]
217    async fn get_state_validate_snapshot(
218        chain: &NetworkChain,
219        epoch: i64,
220    ) -> anyhow::Result<PathBuf> {
221        get_state_snapshot(chain, "state_validate", epoch).await
222    }
223
224    #[allow(dead_code)]
225    pub async fn get_state_snapshot(
226        chain: &NetworkChain,
227        bucket: &str,
228        epoch: i64,
229    ) -> anyhow::Result<PathBuf> {
230        let file = format!("{bucket}/{chain}_{epoch}.forest.car.zst");
231        get_state_snapshot_file(&file).await
232    }
233
234    pub async fn get_state_snapshot_file(file: &str) -> anyhow::Result<PathBuf> {
235        static SNAPSHOT_CACHE_DIR: LazyLock<PathBuf> = LazyLock::new(|| {
236            let project_dir = ProjectDirs::from("com", "ChainSafe", "Forest");
237            project_dir
238                .map(|d| d.cache_dir().to_path_buf())
239                .unwrap_or_else(std::env::temp_dir)
240                .join("state_compute_snapshots")
241        });
242
243        let url = Url::parse(&format!("{DO_SPACE_ROOT}{file}"))?;
244        let path = crate::utils::retry(
245            crate::utils::RetryArgs {
246                timeout: Some(Duration::from_secs(30)),
247                max_retries: Some(5),
248                delay: Some(Duration::from_secs(1)),
249            },
250            || {
251                download_file_with_cache(
252                    &url,
253                    &SNAPSHOT_CACHE_DIR,
254                    DownloadFileOption::NonResumable,
255                )
256            },
257        )
258        .await?
259        .path;
260        #[cfg(test)]
261        {
262            // To determine whether a test failure is caused by data corruption
263            println!(
264                "snapshot: {file}, sha256sum: {}",
265                hex::encode(crate::utils::hash::digest_file::<sha2::Sha256>(&path)?)
266            );
267        }
268        Ok(path)
269    }
270
271    pub async fn prepare_state_compute(
272        chain: &NetworkChain,
273        snapshot: &Path,
274    ) -> anyhow::Result<(Arc<StateManager<ManyCar>>, Tipset, Tipset)> {
275        let snap_car = AnyCar::try_from(snapshot)?;
276        let ts_next = snap_car.heaviest_tipset()?;
277        let db = Arc::new(ManyCar::new(MemoryDB::default()).with_read_only(snap_car)?);
278        let ts = Tipset::load_required(&db, ts_next.parents())?;
279        let chain_config = Arc::new(ChainConfig::from_chain(chain));
280        let genesis_header =
281            read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db)
282                .await?;
283        let chain_store = Arc::new(ChainStore::new(
284            db.clone(),
285            db.clone(),
286            db.clone(),
287            chain_config,
288            genesis_header,
289        )?);
290        let state_manager = Arc::new(StateManager::new(chain_store.clone())?);
291        Ok((state_manager, ts, ts_next))
292    }
293
294    pub async fn prepare_state_validate(
295        chain: &NetworkChain,
296        snapshot: &Path,
297    ) -> anyhow::Result<(Arc<StateManager<ManyCar>>, FullTipset)> {
298        let (sm, _, ts) = prepare_state_compute(chain, snapshot).await?;
299        let fts = load_full_tipset(sm.chain_store(), ts.key())?;
300        Ok((sm, fts))
301    }
302
303    pub async fn state_compute(
304        state_manager: &Arc<StateManager<ManyCar>>,
305        ts: Tipset,
306        ts_next: &Tipset,
307    ) -> anyhow::Result<()> {
308        let epoch = ts.epoch();
309        let expected_state_root = *ts_next.parent_state();
310        let expected_receipt_root = *ts_next.parent_message_receipts();
311        let start = Instant::now();
312        let ExecutedTipset {
313            state_root,
314            receipt_root,
315            ..
316        } = state_manager
317            .compute_tipset_state(ts, crate::state_manager::NO_CALLBACK, VMTrace::NotTraced)
318            .await?;
319        tracing::info!(
320            "epoch: {epoch}, state_root: {state_root}, receipt_root: {receipt_root}, took {}.",
321            humantime::format_duration(start.elapsed())
322        );
323        anyhow::ensure!(
324            state_root == expected_state_root,
325            "state root mismatch, state_root: {state_root}, expected_state_root: {expected_state_root}"
326        );
327        anyhow::ensure!(
328            receipt_root == expected_receipt_root,
329            "receipt root mismatch, receipt_root: {receipt_root}, expected_receipt_root: {expected_receipt_root}"
330        );
331        Ok(())
332    }
333
334    pub async fn list_state_snapshot_files() -> anyhow::Result<Vec<String>> {
335        let url = Url::parse(&format!("{DO_SPACE_ROOT}?format=json&prefix=state_"))?;
336        let mut json_str = String::new();
337        crate::utils::net::reader(url.as_str(), DownloadFileOption::NonResumable, None)
338            .await?
339            .read_to_string(&mut json_str)
340            .await?;
341        let obj: sonic_rs::Object = sonic_rs::from_str(&json_str)?;
342        let files = obj
343            .iter()
344            .filter_map(|(k, v)| {
345                if k == "Contents"
346                    && let sonic_rs::ValueRef::Array(arr) = v.as_ref()
347                    && let Some(first) = arr.first()
348                    && let Some(file) = first.as_str()
349                    && file.ends_with(".car.zst")
350                {
351                    Some(file.to_string())
352                } else {
353                    None
354                }
355            })
356            .collect();
357        Ok(files)
358    }
359
360    #[cfg(test)]
361    mod tests {
362        //!
363        //! Test snapshots are generate by `forest-dev state` tool
364        //!
365
366        use super::*;
367        #[cfg(feature = "cargo-test")]
368        use crate::chain_sync::tipset_syncer::validate_tipset;
369
370        #[tokio::test(flavor = "multi_thread")]
371        async fn test_list_state_snapshot_files() {
372            let files = list_state_snapshot_files().await.unwrap();
373            println!("{files:?}");
374            assert!(files.len() > 1);
375            get_state_snapshot_file(&files[0]).await.unwrap();
376        }
377
378        include!(concat!(env!("OUT_DIR"), "/__state_compute_tests_gen.rs"));
379
380        #[allow(dead_code)]
381        async fn state_compute_test_run(chain: NetworkChain, epoch: i64) {
382            let snapshot = get_state_compute_snapshot(&chain, epoch).await.unwrap();
383            let (sm, ts, ts_next) = prepare_state_compute(&chain, &snapshot).await.unwrap();
384            state_compute(&sm, ts, &ts_next).await.unwrap();
385        }
386
387        #[cfg(feature = "cargo-test")]
388        #[tokio::test(flavor = "multi_thread")]
389        #[fickle::fickle]
390        async fn cargo_test_state_validate_mainnet_5688000() {
391            let chain = NetworkChain::Mainnet;
392            let snapshot = get_state_validate_snapshot(&chain, 5688000).await.unwrap();
393            let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
394            validate_tipset(&sm, fts, None).await.unwrap();
395        }
396
397        // Shark state migration
398        #[cfg(feature = "cargo-test")]
399        #[tokio::test(flavor = "multi_thread")]
400        #[fickle::fickle]
401        async fn cargo_test_state_validate_calibnet_16802() {
402            let chain = NetworkChain::Calibnet;
403            let snapshot = get_state_validate_snapshot(&chain, 16802).await.unwrap();
404            let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
405            validate_tipset(&sm, fts, None).await.unwrap();
406        }
407
408        // Hygge state migration
409        #[cfg(feature = "cargo-test")]
410        #[tokio::test(flavor = "multi_thread")]
411        #[fickle::fickle]
412        async fn cargo_test_state_validate_calibnet_322356() {
413            let chain = NetworkChain::Calibnet;
414            let snapshot = get_state_validate_snapshot(&chain, 322356).await.unwrap();
415            let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
416            validate_tipset(&sm, fts, None).await.unwrap();
417        }
418    }
419}
420
421#[cfg(test)]
422mod test {
423    use crate::shim::{address::Address, econ::TokenAmount, state_tree::ActorState};
424    use cid::Cid;
425
426    use super::*;
427
428    #[test]
429    fn is_valid_for_sending_test() {
430        let create_actor = |code: &Cid, sequence: u64, delegated_address: Option<Address>| {
431            ActorState::new(
432                code.to_owned(),
433                // changing this cid will unleash unthinkable horrors upon the world
434                Cid::try_from("bafk2bzaceavfgpiw6whqigmskk74z4blm22nwjfnzxb4unlqz2e4wgcthulhu")
435                    .unwrap(),
436                TokenAmount::default(),
437                sequence,
438                delegated_address,
439            )
440        };
441
442        // calibnet actor version 10
443        let account_actor_cid =
444            Cid::try_from("bafk2bzaceavfgpiw6whqigmskk74z4blm22nwjfnzxb4unlqz2e4wg3c5ujpw")
445                .unwrap();
446        let ethaccount_actor_cid =
447            Cid::try_from("bafk2bzacebiyrhz32xwxi6xql67aaq5nrzeelzas472kuwjqmdmgwotpkj35e")
448                .unwrap();
449        let placeholder_actor_cid =
450            Cid::try_from("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro")
451                .unwrap();
452
453        // happy path for account actor
454        let actor = create_actor(&account_actor_cid, 0, None);
455        assert!(is_valid_for_sending(NetworkVersion::V17, &actor));
456
457        // eth account not allowed before v18, should fail
458        let actor = create_actor(&ethaccount_actor_cid, 0, None);
459        assert!(!is_valid_for_sending(NetworkVersion::V17, &actor));
460
461        // happy path for eth account
462        assert!(is_valid_for_sending(NetworkVersion::V18, &actor));
463
464        // no delegated address for placeholder actor, should fail
465        let actor = create_actor(&placeholder_actor_cid, 0, None);
466        assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
467
468        // happy path for the placeholder actor
469        let delegated_address = Address::new_delegated(
470            Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR.id().unwrap(),
471            &[0; 20],
472        )
473        .ok();
474        let actor = create_actor(&placeholder_actor_cid, 0, delegated_address);
475        assert!(is_valid_for_sending(NetworkVersion::V18, &actor));
476
477        // sequence not 0, should fail
478        let actor = create_actor(&placeholder_actor_cid, 1, delegated_address);
479        assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
480
481        // delegated address not in EAM namespace, should fail
482        let delegated_address =
483            Address::new_delegated(Address::CHAOS_ACTOR.id().unwrap(), &[0; 20]).ok();
484        let actor = create_actor(&placeholder_actor_cid, 0, delegated_address);
485        assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
486    }
487}
488
489/// Parsed tree of [`fvm4::trace::ExecutionEvent`]s
490pub mod structured {
491    use crate::{
492        rpc::state::{ActorTrace, ExecutionTrace, GasTrace, MessageTrace, ReturnTrace},
493        shim::kernel::ErrorNumber,
494    };
495    use std::collections::VecDeque;
496
497    use crate::shim::{
498        address::Address,
499        error::ExitCode,
500        gas::GasCharge,
501        kernel::SyscallError,
502        trace::{Call, CallReturn, ExecutionEvent},
503    };
504    use fvm_ipld_encoding::{RawBytes, ipld_block::IpldBlock};
505    use itertools::Either;
506
507    enum CallTreeReturn {
508        Return(CallReturn),
509        Abort(ExitCode),
510        Error(SyscallError),
511    }
512
513    #[derive(Debug, thiserror::Error)]
514    pub enum BuildExecutionTraceError {
515        #[error(
516            "every ExecutionEvent::Return | ExecutionEvent::CallError should be preceded by an ExecutionEvent::Call, but this one wasn't"
517        )]
518        UnexpectedReturn,
519        #[error(
520            "every ExecutionEvent::Call should have a corresponding ExecutionEvent::Return, but this one didn't"
521        )]
522        NoReturn,
523        #[error("unrecognised ExecutionEvent variant: {0:?}")]
524        UnrecognisedEvent(Box<dyn std::fmt::Debug + Send + Sync + 'static>),
525    }
526
527    /// Construct a single [`ExecutionTrace`]s from a linear array of [`ExecutionEvent`](fvm4::trace::ExecutionEvent)s.
528    ///
529    /// This function is so-called because it similar to the parse step in a traditional compiler:
530    /// ```text
531    /// text --lex-->     tokens     --parse-->   AST
532    ///               ExecutionEvent --parse--> ExecutionTrace
533    /// ```
534    ///
535    /// This function is notable in that [`GasCharge`](fvm4::gas::GasCharge)s which precede a [`ExecutionTrace`] at the root level
536    /// are attributed to that node.
537    ///
538    /// We call this "front loading", and is copied from [this (rather obscure) code in `filecoin-ffi`](https://github.com/filecoin-project/filecoin-ffi/blob/v1.23.0/rust/src/fvm/machine.rs#L209)
539    ///
540    /// ```text
541    /// GasCharge GasCharge Call GasCharge Call CallError CallReturn
542    /// ────┬──── ────┬──── ─┬── ────┬──── ─┬── ───┬───── ────┬─────
543    ///     │         │      │       │      │      │          │
544    ///     │         │      │       │      └─(T)──┘          │
545    ///     │         │      └───────┴───(T)───┴──────────────┘
546    ///     └─────────┴──────────────────►│
547    ///     ("front loaded" GasCharges)   │
548    ///                                  (T)
549    ///
550    /// (T): a ExecutionTrace node
551    /// ```
552    ///
553    /// Multiple call trees and trailing gas will be warned and ignored.
554    /// If no call tree is found, returns [`Ok(None)`]
555    pub fn parse_events(
556        events: Vec<ExecutionEvent>,
557    ) -> anyhow::Result<Option<ExecutionTrace>, BuildExecutionTraceError> {
558        let mut events = VecDeque::from(events);
559        let mut front_load_me = vec![];
560        let mut call_trees = vec![];
561
562        // we don't use a `for` loop so we can pass events them to inner parsers
563        while let Some(event) = events.pop_front() {
564            match event {
565                ExecutionEvent::GasCharge(gc) => front_load_me.push(gc),
566                ExecutionEvent::Call(call) => call_trees.push(ExecutionTrace::parse(call, {
567                    // if ExecutionTrace::parse took impl Iterator<Item = ExecutionEvent>
568                    // the compiler would infinitely recurse trying to resolve
569                    // &mut &mut &mut ..: Iterator
570                    // so use a VecDeque instead
571                    for gc in front_load_me.drain(..).rev() {
572                        events.push_front(ExecutionEvent::GasCharge(gc))
573                    }
574                    &mut events
575                })?),
576                ExecutionEvent::CallReturn(_)
577                | ExecutionEvent::CallAbort(_)
578                | ExecutionEvent::CallError(_) => {
579                    return Err(BuildExecutionTraceError::UnexpectedReturn);
580                }
581                ExecutionEvent::Log(_ignored) => {}
582                ExecutionEvent::InvokeActor(_cid) => {}
583                ExecutionEvent::Ipld { .. } => {}
584                ExecutionEvent::Unknown(u) => {
585                    return Err(BuildExecutionTraceError::UnrecognisedEvent(Box::new(u)));
586                }
587            }
588        }
589
590        if !front_load_me.is_empty() {
591            tracing::warn!(
592                "vm tracing: ignoring {} trailing gas charges",
593                front_load_me.len()
594            );
595        }
596
597        match call_trees.len() {
598            0 => Ok(None),
599            1 => Ok(Some(call_trees.remove(0))),
600            many => {
601                tracing::warn!(
602                    "vm tracing: ignoring {} call trees at the root level",
603                    many - 1
604                );
605                Ok(Some(call_trees.remove(0)))
606            }
607        }
608    }
609
610    impl ExecutionTrace {
611        /// ```text
612        ///    events: GasCharge Call CallError CallReturn ...
613        ///            ────┬──── ─┬── ───┬───── ────┬─────
614        ///                │      │      │          │
615        /// ┌──────┐       │      └─(T)──┘          │
616        /// │ Call ├───────┴───(T)───┴──────────────┘
617        /// └──────┘            |                   ▲
618        ///                     ▼                   │
619        ///              Returned ExecutionTrace    │
620        ///                                     parsing end
621        /// ```
622        fn parse(
623            call: Call,
624            events: &mut VecDeque<ExecutionEvent>,
625        ) -> Result<ExecutionTrace, BuildExecutionTraceError> {
626            let mut gas_charges = vec![];
627            let mut subcalls = vec![];
628            let mut actor_trace = None;
629
630            // we don't use a for loop over `events` so we can pass them to recursive calls
631            while let Some(event) = events.pop_front() {
632                let found_return = match event {
633                    ExecutionEvent::GasCharge(gc) => {
634                        gas_charges.push(to_gas_trace(gc));
635                        None
636                    }
637                    ExecutionEvent::Call(call) => {
638                        subcalls.push(Self::parse(call, events)?);
639                        None
640                    }
641                    ExecutionEvent::CallReturn(ret) => Some(CallTreeReturn::Return(ret)),
642                    ExecutionEvent::CallAbort(ab) => Some(CallTreeReturn::Abort(ab)),
643                    ExecutionEvent::CallError(e) => Some(CallTreeReturn::Error(e)),
644                    ExecutionEvent::Log(_ignored) => None,
645                    ExecutionEvent::InvokeActor(cid) => {
646                        actor_trace = match cid {
647                            Either::Left(_cid) => None,
648                            Either::Right(actor) => Some(ActorTrace {
649                                id: actor.id,
650                                state: actor.state,
651                            }),
652                        };
653                        None
654                    }
655                    ExecutionEvent::Ipld { .. } => None,
656                    // RUST: This should be caught at compile time with #[deny(non_exhaustive_omitted_patterns)]
657                    //       So that BuildExecutionTraceError::UnrecognisedEvent is never constructed
658                    //       But that lint is not yet stabilised: https://github.com/rust-lang/rust/issues/89554
659                    ExecutionEvent::Unknown(u) => {
660                        return Err(BuildExecutionTraceError::UnrecognisedEvent(Box::new(u)));
661                    }
662                };
663
664                // commonise the return branch
665                if let Some(ret) = found_return {
666                    return Ok(ExecutionTrace {
667                        msg: to_message_trace(call),
668                        msg_rct: to_return_trace(ret),
669                        gas_charges,
670                        subcalls,
671                        invoked_actor: actor_trace,
672                    });
673                }
674            }
675
676            Err(BuildExecutionTraceError::NoReturn)
677        }
678    }
679
680    fn to_message_trace(call: Call) -> MessageTrace {
681        let (bytes, codec) = to_bytes_codec(call.params);
682        MessageTrace {
683            from: Address::new_id(call.from),
684            to: call.to,
685            value: call.value,
686            method: call.method_num,
687            params: bytes,
688            params_codec: codec,
689            gas_limit: call.gas_limit,
690            read_only: call.read_only,
691        }
692    }
693
694    fn to_return_trace(ret: CallTreeReturn) -> ReturnTrace {
695        match ret {
696            CallTreeReturn::Return(return_code) => {
697                let exit_code = return_code.exit_code.unwrap_or(0.into());
698                let (bytes, codec) = to_bytes_codec(return_code.data);
699                ReturnTrace {
700                    exit_code,
701                    r#return: bytes,
702                    return_codec: codec,
703                }
704            }
705            CallTreeReturn::Abort(exit_code) => ReturnTrace {
706                exit_code,
707                r#return: RawBytes::default(),
708                return_codec: 0,
709            },
710            CallTreeReturn::Error(syscall_error) => match syscall_error.number {
711                ErrorNumber::InsufficientFunds => ReturnTrace {
712                    exit_code: ExitCode::from(6),
713                    r#return: RawBytes::default(),
714                    return_codec: 0,
715                },
716                _ => ReturnTrace {
717                    exit_code: ExitCode::from(0),
718                    r#return: RawBytes::default(),
719                    return_codec: 0,
720                },
721            },
722        }
723    }
724
725    fn to_bytes_codec(data: Either<RawBytes, Option<IpldBlock>>) -> (RawBytes, u64) {
726        match data {
727            Either::Left(l) => (l, 0),
728            Either::Right(r) => match r {
729                Some(b) => (RawBytes::from(b.data), b.codec),
730                None => (RawBytes::default(), 0),
731            },
732        }
733    }
734
735    fn to_gas_trace(gc: GasCharge) -> GasTrace {
736        GasTrace {
737            name: gc.name().into(),
738            total_gas: gc.total().round_up(),
739            compute_gas: gc.compute_gas().round_up(),
740            storage_gas: gc.other_gas().round_up(),
741            time_taken: gc.elapsed().as_nanos(),
742        }
743    }
744}