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    ) -> Result<Vec<ExtendedSectorInfo>, anyhow::Error> {
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) -> Result<Vec<u64>, anyhow::Error> {
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::{StateManager, StateOutput},
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            use digest::Digest as _;
264            println!(
265                "snapshot: {file}, sha256sum: {}",
266                hex::encode(sha2::Sha256::digest(std::fs::read(&path)?))
267            );
268        }
269        Ok(path)
270    }
271
272    pub async fn prepare_state_compute(
273        chain: &NetworkChain,
274        snapshot: &Path,
275    ) -> anyhow::Result<(Arc<StateManager<ManyCar>>, Tipset, Tipset)> {
276        let snap_car = AnyCar::try_from(snapshot)?;
277        let ts_next = snap_car.heaviest_tipset()?;
278        let db = Arc::new(ManyCar::new(MemoryDB::default()).with_read_only(snap_car)?);
279        let ts = Tipset::load_required(&db, ts_next.parents())?;
280        let chain_config = Arc::new(ChainConfig::from_chain(chain));
281        let genesis_header =
282            read_genesis_header(None, chain_config.genesis_bytes(&db).await?.as_deref(), &db)
283                .await?;
284        let chain_store = Arc::new(ChainStore::new(
285            db.clone(),
286            db.clone(),
287            db.clone(),
288            chain_config,
289            genesis_header,
290        )?);
291        let state_manager = Arc::new(StateManager::new(chain_store.clone())?);
292        Ok((state_manager, ts, ts_next))
293    }
294
295    pub async fn prepare_state_validate(
296        chain: &NetworkChain,
297        snapshot: &Path,
298    ) -> anyhow::Result<(Arc<StateManager<ManyCar>>, FullTipset)> {
299        let (sm, _, ts) = prepare_state_compute(chain, snapshot).await?;
300        let fts = load_full_tipset(sm.chain_store(), ts.key())?;
301        Ok((sm, fts))
302    }
303
304    pub async fn state_compute(
305        state_manager: &Arc<StateManager<ManyCar>>,
306        ts: Tipset,
307        ts_next: &Tipset,
308    ) -> anyhow::Result<()> {
309        let epoch = ts.epoch();
310        let expected_state_root = *ts_next.parent_state();
311        let expected_receipt_root = *ts_next.parent_message_receipts();
312        let start = Instant::now();
313        let StateOutput {
314            state_root,
315            receipt_root,
316            ..
317        } = state_manager
318            .compute_tipset_state(ts, crate::state_manager::NO_CALLBACK, VMTrace::NotTraced)
319            .await?;
320        println!(
321            "epoch: {epoch}, state_root: {state_root}, receipt_root: {receipt_root}, took {}.",
322            humantime::format_duration(start.elapsed())
323        );
324        anyhow::ensure!(
325            state_root == expected_state_root,
326            "state root mismatch, state_root: {state_root}, expected_state_root: {expected_state_root}"
327        );
328        anyhow::ensure!(
329            receipt_root == expected_receipt_root,
330            "receipt root mismatch, receipt_root: {receipt_root}, expected_receipt_root: {expected_receipt_root}"
331        );
332        Ok(())
333    }
334
335    pub async fn list_state_snapshot_files() -> anyhow::Result<Vec<String>> {
336        let url = Url::parse(&format!("{DO_SPACE_ROOT}?format=json&prefix=state_"))?;
337        let mut json_str = String::new();
338        crate::utils::net::reader(url.as_str(), DownloadFileOption::NonResumable, None)
339            .await?
340            .read_to_string(&mut json_str)
341            .await?;
342        let obj: sonic_rs::Object = sonic_rs::from_str(&json_str)?;
343        let files = obj
344            .iter()
345            .filter_map(|(k, v)| {
346                if k == "Contents"
347                    && let sonic_rs::ValueRef::Array(arr) = v.as_ref()
348                    && let Some(first) = arr.first()
349                    && let Some(file) = first.as_str()
350                    && file.ends_with(".car.zst")
351                {
352                    Some(file.to_string())
353                } else {
354                    None
355                }
356            })
357            .collect();
358        Ok(files)
359    }
360
361    #[cfg(test)]
362    mod tests {
363        //!
364        //! Test snapshots are generate by `forest-dev state` tool
365        //!
366
367        use super::*;
368        #[cfg(feature = "cargo-test")]
369        use crate::chain_sync::tipset_syncer::validate_tipset;
370
371        #[tokio::test(flavor = "multi_thread")]
372        async fn test_list_state_snapshot_files() {
373            let files = list_state_snapshot_files().await.unwrap();
374            println!("{files:?}");
375            assert!(files.len() > 1);
376            get_state_snapshot_file(&files[0]).await.unwrap();
377        }
378
379        include!(concat!(env!("OUT_DIR"), "/__state_compute_tests_gen.rs"));
380
381        #[allow(dead_code)]
382        async fn state_compute_test_run(chain: NetworkChain, epoch: i64) {
383            let snapshot = get_state_compute_snapshot(&chain, epoch).await.unwrap();
384            let (sm, ts, ts_next) = prepare_state_compute(&chain, &snapshot).await.unwrap();
385            state_compute(&sm, ts, &ts_next).await.unwrap();
386        }
387
388        #[cfg(feature = "cargo-test")]
389        #[tokio::test(flavor = "multi_thread")]
390        #[fickle::fickle]
391        async fn cargo_test_state_validate_mainnet_5688000() {
392            let chain = NetworkChain::Mainnet;
393            let snapshot = get_state_validate_snapshot(&chain, 5688000).await.unwrap();
394            let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
395            validate_tipset(&sm, fts, None).await.unwrap();
396        }
397
398        // Shark state migration
399        #[cfg(feature = "cargo-test")]
400        #[tokio::test(flavor = "multi_thread")]
401        #[fickle::fickle]
402        async fn cargo_test_state_validate_calibnet_16802() {
403            let chain = NetworkChain::Calibnet;
404            let snapshot = get_state_validate_snapshot(&chain, 16802).await.unwrap();
405            let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
406            validate_tipset(&sm, fts, None).await.unwrap();
407        }
408
409        // Hygge state migration
410        #[cfg(feature = "cargo-test")]
411        #[tokio::test(flavor = "multi_thread")]
412        #[fickle::fickle]
413        async fn cargo_test_state_validate_calibnet_322356() {
414            let chain = NetworkChain::Calibnet;
415            let snapshot = get_state_validate_snapshot(&chain, 322356).await.unwrap();
416            let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
417            validate_tipset(&sm, fts, None).await.unwrap();
418        }
419    }
420}
421
422#[cfg(test)]
423mod test {
424    use crate::shim::{address::Address, econ::TokenAmount, state_tree::ActorState};
425    use cid::Cid;
426
427    use super::*;
428
429    #[test]
430    fn is_valid_for_sending_test() {
431        let create_actor = |code: &Cid, sequence: u64, delegated_address: Option<Address>| {
432            ActorState::new(
433                code.to_owned(),
434                // changing this cid will unleash unthinkable horrors upon the world
435                Cid::try_from("bafk2bzaceavfgpiw6whqigmskk74z4blm22nwjfnzxb4unlqz2e4wgcthulhu")
436                    .unwrap(),
437                TokenAmount::default(),
438                sequence,
439                delegated_address,
440            )
441        };
442
443        // calibnet actor version 10
444        let account_actor_cid =
445            Cid::try_from("bafk2bzaceavfgpiw6whqigmskk74z4blm22nwjfnzxb4unlqz2e4wg3c5ujpw")
446                .unwrap();
447        let ethaccount_actor_cid =
448            Cid::try_from("bafk2bzacebiyrhz32xwxi6xql67aaq5nrzeelzas472kuwjqmdmgwotpkj35e")
449                .unwrap();
450        let placeholder_actor_cid =
451            Cid::try_from("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro")
452                .unwrap();
453
454        // happy path for account actor
455        let actor = create_actor(&account_actor_cid, 0, None);
456        assert!(is_valid_for_sending(NetworkVersion::V17, &actor));
457
458        // eth account not allowed before v18, should fail
459        let actor = create_actor(&ethaccount_actor_cid, 0, None);
460        assert!(!is_valid_for_sending(NetworkVersion::V17, &actor));
461
462        // happy path for eth account
463        assert!(is_valid_for_sending(NetworkVersion::V18, &actor));
464
465        // no delegated address for placeholder actor, should fail
466        let actor = create_actor(&placeholder_actor_cid, 0, None);
467        assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
468
469        // happy path for the placeholder actor
470        let delegated_address = Address::new_delegated(
471            Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR.id().unwrap(),
472            &[0; 20],
473        )
474        .ok();
475        let actor = create_actor(&placeholder_actor_cid, 0, delegated_address);
476        assert!(is_valid_for_sending(NetworkVersion::V18, &actor));
477
478        // sequence not 0, should fail
479        let actor = create_actor(&placeholder_actor_cid, 1, delegated_address);
480        assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
481
482        // delegated address not in EAM namespace, should fail
483        let delegated_address =
484            Address::new_delegated(Address::CHAOS_ACTOR.id().unwrap(), &[0; 20]).ok();
485        let actor = create_actor(&placeholder_actor_cid, 0, delegated_address);
486        assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
487    }
488}
489
490/// Parsed tree of [`fvm4::trace::ExecutionEvent`]s
491pub mod structured {
492    use crate::{
493        rpc::state::{ActorTrace, ExecutionTrace, GasTrace, MessageTrace, ReturnTrace},
494        shim::kernel::ErrorNumber,
495    };
496    use std::collections::VecDeque;
497
498    use crate::shim::{
499        address::Address,
500        error::ExitCode,
501        gas::GasCharge,
502        kernel::SyscallError,
503        trace::{Call, CallReturn, ExecutionEvent},
504    };
505    use fvm_ipld_encoding::{RawBytes, ipld_block::IpldBlock};
506    use itertools::Either;
507
508    enum CallTreeReturn {
509        Return(CallReturn),
510        Abort(ExitCode),
511        Error(SyscallError),
512    }
513
514    #[derive(Debug, thiserror::Error)]
515    pub enum BuildExecutionTraceError {
516        #[error(
517            "every ExecutionEvent::Return | ExecutionEvent::CallError should be preceded by an ExecutionEvent::Call, but this one wasn't"
518        )]
519        UnexpectedReturn,
520        #[error(
521            "every ExecutionEvent::Call should have a corresponding ExecutionEvent::Return, but this one didn't"
522        )]
523        NoReturn,
524        #[error("unrecognised ExecutionEvent variant: {0:?}")]
525        UnrecognisedEvent(Box<dyn std::fmt::Debug + Send + Sync + 'static>),
526    }
527
528    /// Construct a single [`ExecutionTrace`]s from a linear array of [`ExecutionEvent`](fvm4::trace::ExecutionEvent)s.
529    ///
530    /// This function is so-called because it similar to the parse step in a traditional compiler:
531    /// ```text
532    /// text --lex-->     tokens     --parse-->   AST
533    ///               ExecutionEvent --parse--> ExecutionTrace
534    /// ```
535    ///
536    /// This function is notable in that [`GasCharge`](fvm4::gas::GasCharge)s which precede a [`ExecutionTrace`] at the root level
537    /// are attributed to that node.
538    ///
539    /// 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)
540    ///
541    /// ```text
542    /// GasCharge GasCharge Call GasCharge Call CallError CallReturn
543    /// ────┬──── ────┬──── ─┬── ────┬──── ─┬── ───┬───── ────┬─────
544    ///     │         │      │       │      │      │          │
545    ///     │         │      │       │      └─(T)──┘          │
546    ///     │         │      └───────┴───(T)───┴──────────────┘
547    ///     └─────────┴──────────────────►│
548    ///     ("front loaded" GasCharges)   │
549    ///                                  (T)
550    ///
551    /// (T): a ExecutionTrace node
552    /// ```
553    ///
554    /// Multiple call trees and trailing gas will be warned and ignored.
555    /// If no call tree is found, returns [`Ok(None)`]
556    pub fn parse_events(
557        events: Vec<ExecutionEvent>,
558    ) -> anyhow::Result<Option<ExecutionTrace>, BuildExecutionTraceError> {
559        let mut events = VecDeque::from(events);
560        let mut front_load_me = vec![];
561        let mut call_trees = vec![];
562
563        // we don't use a `for` loop so we can pass events them to inner parsers
564        while let Some(event) = events.pop_front() {
565            match event {
566                ExecutionEvent::GasCharge(gc) => front_load_me.push(gc),
567                ExecutionEvent::Call(call) => call_trees.push(ExecutionTrace::parse(call, {
568                    // if ExecutionTrace::parse took impl Iterator<Item = ExecutionEvent>
569                    // the compiler would infinitely recurse trying to resolve
570                    // &mut &mut &mut ..: Iterator
571                    // so use a VecDeque instead
572                    for gc in front_load_me.drain(..).rev() {
573                        events.push_front(ExecutionEvent::GasCharge(gc))
574                    }
575                    &mut events
576                })?),
577                ExecutionEvent::CallReturn(_)
578                | ExecutionEvent::CallAbort(_)
579                | ExecutionEvent::CallError(_) => {
580                    return Err(BuildExecutionTraceError::UnexpectedReturn);
581                }
582                ExecutionEvent::Log(_ignored) => {}
583                ExecutionEvent::InvokeActor(_cid) => {}
584                ExecutionEvent::Ipld { .. } => {}
585                ExecutionEvent::Unknown(u) => {
586                    return Err(BuildExecutionTraceError::UnrecognisedEvent(Box::new(u)));
587                }
588            }
589        }
590
591        if !front_load_me.is_empty() {
592            tracing::warn!(
593                "vm tracing: ignoring {} trailing gas charges",
594                front_load_me.len()
595            );
596        }
597
598        match call_trees.len() {
599            0 => Ok(None),
600            1 => Ok(Some(call_trees.remove(0))),
601            many => {
602                tracing::warn!(
603                    "vm tracing: ignoring {} call trees at the root level",
604                    many - 1
605                );
606                Ok(Some(call_trees.remove(0)))
607            }
608        }
609    }
610
611    impl ExecutionTrace {
612        /// ```text
613        ///    events: GasCharge Call CallError CallReturn ...
614        ///            ────┬──── ─┬── ───┬───── ────┬─────
615        ///                │      │      │          │
616        /// ┌──────┐       │      └─(T)──┘          │
617        /// │ Call ├───────┴───(T)───┴──────────────┘
618        /// └──────┘            |                   ▲
619        ///                     ▼                   │
620        ///              Returned ExecutionTrace    │
621        ///                                     parsing end
622        /// ```
623        fn parse(
624            call: Call,
625            events: &mut VecDeque<ExecutionEvent>,
626        ) -> Result<ExecutionTrace, BuildExecutionTraceError> {
627            let mut gas_charges = vec![];
628            let mut subcalls = vec![];
629            let mut actor_trace = None;
630
631            // we don't use a for loop over `events` so we can pass them to recursive calls
632            while let Some(event) = events.pop_front() {
633                let found_return = match event {
634                    ExecutionEvent::GasCharge(gc) => {
635                        gas_charges.push(to_gas_trace(gc));
636                        None
637                    }
638                    ExecutionEvent::Call(call) => {
639                        subcalls.push(Self::parse(call, events)?);
640                        None
641                    }
642                    ExecutionEvent::CallReturn(ret) => Some(CallTreeReturn::Return(ret)),
643                    ExecutionEvent::CallAbort(ab) => Some(CallTreeReturn::Abort(ab)),
644                    ExecutionEvent::CallError(e) => Some(CallTreeReturn::Error(e)),
645                    ExecutionEvent::Log(_ignored) => None,
646                    ExecutionEvent::InvokeActor(cid) => {
647                        actor_trace = match cid {
648                            Either::Left(_cid) => None,
649                            Either::Right(actor) => Some(ActorTrace {
650                                id: actor.id,
651                                state: actor.state,
652                            }),
653                        };
654                        None
655                    }
656                    ExecutionEvent::Ipld { .. } => None,
657                    // RUST: This should be caught at compile time with #[deny(non_exhaustive_omitted_patterns)]
658                    //       So that BuildExecutionTraceError::UnrecognisedEvent is never constructed
659                    //       But that lint is not yet stabilised: https://github.com/rust-lang/rust/issues/89554
660                    ExecutionEvent::Unknown(u) => {
661                        return Err(BuildExecutionTraceError::UnrecognisedEvent(Box::new(u)));
662                    }
663                };
664
665                // commonise the return branch
666                if let Some(ret) = found_return {
667                    return Ok(ExecutionTrace {
668                        msg: to_message_trace(call),
669                        msg_rct: to_return_trace(ret),
670                        gas_charges,
671                        subcalls,
672                        invoked_actor: actor_trace,
673                    });
674                }
675            }
676
677            Err(BuildExecutionTraceError::NoReturn)
678        }
679    }
680
681    fn to_message_trace(call: Call) -> MessageTrace {
682        let (bytes, codec) = to_bytes_codec(call.params);
683        MessageTrace {
684            from: Address::new_id(call.from),
685            to: call.to,
686            value: call.value,
687            method: call.method_num,
688            params: bytes,
689            params_codec: codec,
690            gas_limit: call.gas_limit,
691            read_only: call.read_only,
692        }
693    }
694
695    fn to_return_trace(ret: CallTreeReturn) -> ReturnTrace {
696        match ret {
697            CallTreeReturn::Return(return_code) => {
698                let exit_code = return_code.exit_code.unwrap_or(0.into());
699                let (bytes, codec) = to_bytes_codec(return_code.data);
700                ReturnTrace {
701                    exit_code,
702                    r#return: bytes,
703                    return_codec: codec,
704                }
705            }
706            CallTreeReturn::Abort(exit_code) => ReturnTrace {
707                exit_code,
708                r#return: RawBytes::default(),
709                return_codec: 0,
710            },
711            CallTreeReturn::Error(syscall_error) => match syscall_error.number {
712                ErrorNumber::InsufficientFunds => ReturnTrace {
713                    exit_code: ExitCode::from(6),
714                    r#return: RawBytes::default(),
715                    return_codec: 0,
716                },
717                _ => ReturnTrace {
718                    exit_code: ExitCode::from(0),
719                    r#return: RawBytes::default(),
720                    return_codec: 0,
721                },
722            },
723        }
724    }
725
726    fn to_bytes_codec(data: Either<RawBytes, Option<IpldBlock>>) -> (RawBytes, u64) {
727        match data {
728            Either::Left(l) => (l, 0),
729            Either::Right(r) => match r {
730                Some(b) => (RawBytes::from(b.data), b.codec),
731                None => (RawBytes::default(), 0),
732            },
733        }
734    }
735
736    fn to_gas_trace(gc: GasCharge) -> GasTrace {
737        GasTrace {
738            name: gc.name().into(),
739            total_gas: gc.total().round_up(),
740            compute_gas: gc.compute_gas().round_up(),
741            storage_gas: gc.other_gas().round_up(),
742            time_taken: gc.elapsed().as_nanos(),
743        }
744    }
745}