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