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        // FVM@4
380        #[cfg(feature = "cargo-test")]
381        #[tokio::test(flavor = "multi_thread")]
382        async fn cargo_test_state_compute_mainnet_5709604() {
383            let chain = NetworkChain::Mainnet;
384            let snapshot = get_state_compute_snapshot(&chain, 5709604).await.unwrap();
385            let (sm, ts, ts_next) = prepare_state_compute(&chain, &snapshot).await.unwrap();
386            state_compute(&sm, ts, &ts_next).await.unwrap();
387        }
388
389        // FVM@4
390        #[cfg(feature = "cargo-test")]
391        #[tokio::test(flavor = "multi_thread")]
392        async fn cargo_test_state_compute_calibnet_3408952() {
393            let chain = NetworkChain::Calibnet;
394            let snapshot = get_state_compute_snapshot(&chain, 3408952).await.unwrap();
395            let (sm, ts, ts_next) = prepare_state_compute(&chain, &snapshot).await.unwrap();
396            state_compute(&sm, ts, &ts_next).await.unwrap();
397        }
398
399        // Shark state migration with FVM@2
400        #[cfg(feature = "cargo-test")]
401        #[tokio::test(flavor = "multi_thread")]
402        async fn cargo_test_state_compute_calibnet_16801() {
403            let chain = NetworkChain::Calibnet;
404            let snapshot = get_state_compute_snapshot(&chain, 16801).await.unwrap();
405            let (sm, ts, ts_next) = prepare_state_compute(&chain, &snapshot).await.unwrap();
406            state_compute(&sm, ts, &ts_next).await.unwrap();
407        }
408
409        // Hygge state migration with FVM@2
410        #[cfg(feature = "cargo-test")]
411        #[tokio::test(flavor = "multi_thread")]
412        async fn cargo_test_state_compute_calibnet_322355() {
413            let chain = NetworkChain::Calibnet;
414            let snapshot = get_state_compute_snapshot(&chain, 322355).await.unwrap();
415            let (sm, ts, ts_next) = prepare_state_compute(&chain, &snapshot).await.unwrap();
416            state_compute(&sm, ts, &ts_next).await.unwrap();
417        }
418
419        // Lightning state migration with FVM@3
420        #[cfg(feature = "cargo-test")]
421        #[tokio::test(flavor = "multi_thread")]
422        async fn cargo_test_state_compute_calibnet_489095() {
423            let chain = NetworkChain::Calibnet;
424            let snapshot = get_state_compute_snapshot(&chain, 489095).await.unwrap();
425            let (sm, ts, ts_next) = prepare_state_compute(&chain, &snapshot).await.unwrap();
426            state_compute(&sm, ts, &ts_next).await.unwrap();
427        }
428
429        // Watermelon state migration with FVM@3
430        #[cfg(feature = "cargo-test")]
431        #[tokio::test(flavor = "multi_thread")]
432        async fn cargo_test_state_compute_calibnet_1013135() {
433            let chain = NetworkChain::Calibnet;
434            let snapshot = get_state_compute_snapshot(&chain, 1013135).await.unwrap();
435            let (sm, ts, ts_next) = prepare_state_compute(&chain, &snapshot).await.unwrap();
436            state_compute(&sm, ts, &ts_next).await.unwrap();
437        }
438
439        // Dragon state migration with FVM@4
440        #[cfg(feature = "cargo-test")]
441        #[tokio::test(flavor = "multi_thread")]
442        async fn cargo_test_state_compute_calibnet_1427975() {
443            let chain = NetworkChain::Calibnet;
444            let snapshot = get_state_compute_snapshot(&chain, 1427975).await.unwrap();
445            let (sm, ts, ts_next) = prepare_state_compute(&chain, &snapshot).await.unwrap();
446            state_compute(&sm, ts, &ts_next).await.unwrap();
447        }
448
449        // Waffle state migration with FVM@4
450        #[cfg(feature = "cargo-test")]
451        #[tokio::test(flavor = "multi_thread")]
452        async fn cargo_test_state_compute_calibnet_1779095() {
453            let chain = NetworkChain::Calibnet;
454            let snapshot = get_state_compute_snapshot(&chain, 1779095).await.unwrap();
455            let (sm, ts, ts_next) = prepare_state_compute(&chain, &snapshot).await.unwrap();
456            state_compute(&sm, ts, &ts_next).await.unwrap();
457        }
458
459        // TukTuk state migration with FVM@4
460        #[cfg(feature = "cargo-test")]
461        #[tokio::test(flavor = "multi_thread")]
462        async fn cargo_test_state_compute_calibnet_2078795() {
463            let chain = NetworkChain::Calibnet;
464            let snapshot = get_state_compute_snapshot(&chain, 2078795).await.unwrap();
465            let (sm, ts, ts_next) = prepare_state_compute(&chain, &snapshot).await.unwrap();
466            state_compute(&sm, ts, &ts_next).await.unwrap();
467        }
468
469        // Teep state migration with FVM@4
470        #[cfg(feature = "cargo-test")]
471        #[tokio::test(flavor = "multi_thread")]
472        async fn cargo_test_state_compute_calibnet_2523455() {
473            let chain = NetworkChain::Calibnet;
474            let snapshot = get_state_compute_snapshot(&chain, 2523455).await.unwrap();
475            let (sm, ts, ts_next) = prepare_state_compute(&chain, &snapshot).await.unwrap();
476            state_compute(&sm, ts, &ts_next).await.unwrap();
477        }
478
479        // GoldenWeek state migration with FVM@4
480        #[cfg(feature = "cargo-test")]
481        #[tokio::test(flavor = "multi_thread")]
482        async fn cargo_test_state_compute_calibnet_3007295() {
483            let chain = NetworkChain::Calibnet;
484            let snapshot = get_state_compute_snapshot(&chain, 3007295).await.unwrap();
485            let (sm, ts, ts_next) = prepare_state_compute(&chain, &snapshot).await.unwrap();
486            state_compute(&sm, ts, &ts_next).await.unwrap();
487        }
488
489        #[cfg(feature = "cargo-test")]
490        #[tokio::test(flavor = "multi_thread")]
491        async fn cargo_test_state_validate_mainnet_5688000() {
492            let chain = NetworkChain::Mainnet;
493            let snapshot = get_state_validate_snapshot(&chain, 5688000).await.unwrap();
494            let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
495            validate_tipset(&sm, fts, None).await.unwrap();
496        }
497
498        // Shark state migration
499        #[cfg(feature = "cargo-test")]
500        #[tokio::test(flavor = "multi_thread")]
501        async fn cargo_test_state_validate_calibnet_16802() {
502            let chain = NetworkChain::Calibnet;
503            let snapshot = get_state_validate_snapshot(&chain, 16802).await.unwrap();
504            let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
505            validate_tipset(&sm, fts, None).await.unwrap();
506        }
507
508        // Hygge state migration
509        #[cfg(feature = "cargo-test")]
510        #[tokio::test(flavor = "multi_thread")]
511        async fn cargo_test_state_validate_calibnet_322356() {
512            let chain = NetworkChain::Calibnet;
513            let snapshot = get_state_validate_snapshot(&chain, 322356).await.unwrap();
514            let (sm, fts) = prepare_state_validate(&chain, &snapshot).await.unwrap();
515            validate_tipset(&sm, fts, None).await.unwrap();
516        }
517    }
518}
519
520#[cfg(test)]
521mod test {
522    use crate::shim::{address::Address, econ::TokenAmount, state_tree::ActorState};
523    use cid::Cid;
524
525    use super::*;
526
527    #[test]
528    fn is_valid_for_sending_test() {
529        let create_actor = |code: &Cid, sequence: u64, delegated_address: Option<Address>| {
530            ActorState::new(
531                code.to_owned(),
532                // changing this cid will unleash unthinkable horrors upon the world
533                Cid::try_from("bafk2bzaceavfgpiw6whqigmskk74z4blm22nwjfnzxb4unlqz2e4wgcthulhu")
534                    .unwrap(),
535                TokenAmount::default(),
536                sequence,
537                delegated_address,
538            )
539        };
540
541        // calibnet actor version 10
542        let account_actor_cid =
543            Cid::try_from("bafk2bzaceavfgpiw6whqigmskk74z4blm22nwjfnzxb4unlqz2e4wg3c5ujpw")
544                .unwrap();
545        let ethaccount_actor_cid =
546            Cid::try_from("bafk2bzacebiyrhz32xwxi6xql67aaq5nrzeelzas472kuwjqmdmgwotpkj35e")
547                .unwrap();
548        let placeholder_actor_cid =
549            Cid::try_from("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro")
550                .unwrap();
551
552        // happy path for account actor
553        let actor = create_actor(&account_actor_cid, 0, None);
554        assert!(is_valid_for_sending(NetworkVersion::V17, &actor));
555
556        // eth account not allowed before v18, should fail
557        let actor = create_actor(&ethaccount_actor_cid, 0, None);
558        assert!(!is_valid_for_sending(NetworkVersion::V17, &actor));
559
560        // happy path for eth account
561        assert!(is_valid_for_sending(NetworkVersion::V18, &actor));
562
563        // no delegated address for placeholder actor, should fail
564        let actor = create_actor(&placeholder_actor_cid, 0, None);
565        assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
566
567        // happy path for the placeholder actor
568        let delegated_address = Address::new_delegated(
569            Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR.id().unwrap(),
570            &[0; 20],
571        )
572        .ok();
573        let actor = create_actor(&placeholder_actor_cid, 0, delegated_address);
574        assert!(is_valid_for_sending(NetworkVersion::V18, &actor));
575
576        // sequence not 0, should fail
577        let actor = create_actor(&placeholder_actor_cid, 1, delegated_address);
578        assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
579
580        // delegated address not in EAM namespace, should fail
581        let delegated_address =
582            Address::new_delegated(Address::CHAOS_ACTOR.id().unwrap(), &[0; 20]).ok();
583        let actor = create_actor(&placeholder_actor_cid, 0, delegated_address);
584        assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
585    }
586}
587
588/// Parsed tree of [`fvm4::trace::ExecutionEvent`]s
589pub mod structured {
590    use crate::{
591        rpc::state::{ActorTrace, ExecutionTrace, GasTrace, MessageTrace, ReturnTrace},
592        shim::kernel::ErrorNumber,
593    };
594    use std::collections::VecDeque;
595
596    use crate::shim::{
597        address::Address,
598        error::ExitCode,
599        gas::GasCharge,
600        kernel::SyscallError,
601        trace::{Call, CallReturn, ExecutionEvent},
602    };
603    use fvm_ipld_encoding::{RawBytes, ipld_block::IpldBlock};
604    use itertools::Either;
605
606    enum CallTreeReturn {
607        Return(CallReturn),
608        Abort(ExitCode),
609        Error(SyscallError),
610    }
611
612    #[derive(Debug, thiserror::Error)]
613    pub enum BuildExecutionTraceError {
614        #[error(
615            "every ExecutionEvent::Return | ExecutionEvent::CallError should be preceded by an ExecutionEvent::Call, but this one wasn't"
616        )]
617        UnexpectedReturn,
618        #[error(
619            "every ExecutionEvent::Call should have a corresponding ExecutionEvent::Return, but this one didn't"
620        )]
621        NoReturn,
622        #[error("unrecognised ExecutionEvent variant: {0:?}")]
623        UnrecognisedEvent(Box<dyn std::fmt::Debug + Send + Sync + 'static>),
624    }
625
626    /// Construct a single [`ExecutionTrace`]s from a linear array of [`ExecutionEvent`](fvm4::trace::ExecutionEvent)s.
627    ///
628    /// This function is so-called because it similar to the parse step in a traditional compiler:
629    /// ```text
630    /// text --lex-->     tokens     --parse-->   AST
631    ///               ExecutionEvent --parse--> ExecutionTrace
632    /// ```
633    ///
634    /// This function is notable in that [`GasCharge`](fvm4::gas::GasCharge)s which precede a [`ExecutionTrace`] at the root level
635    /// are attributed to that node.
636    ///
637    /// 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)
638    ///
639    /// ```text
640    /// GasCharge GasCharge Call GasCharge Call CallError CallReturn
641    /// ────┬──── ────┬──── ─┬── ────┬──── ─┬── ───┬───── ────┬─────
642    ///     │         │      │       │      │      │          │
643    ///     │         │      │       │      └─(T)──┘          │
644    ///     │         │      └───────┴───(T)───┴──────────────┘
645    ///     └─────────┴──────────────────►│
646    ///     ("front loaded" GasCharges)   │
647    ///                                  (T)
648    ///
649    /// (T): a ExecutionTrace node
650    /// ```
651    ///
652    /// Multiple call trees and trailing gas will be warned and ignored.
653    /// If no call tree is found, returns [`Ok(None)`]
654    pub fn parse_events(
655        events: Vec<ExecutionEvent>,
656    ) -> anyhow::Result<Option<ExecutionTrace>, BuildExecutionTraceError> {
657        let mut events = VecDeque::from(events);
658        let mut front_load_me = vec![];
659        let mut call_trees = vec![];
660
661        // we don't use a `for` loop so we can pass events them to inner parsers
662        while let Some(event) = events.pop_front() {
663            match event {
664                ExecutionEvent::GasCharge(gc) => front_load_me.push(gc),
665                ExecutionEvent::Call(call) => call_trees.push(ExecutionTrace::parse(call, {
666                    // if ExecutionTrace::parse took impl Iterator<Item = ExecutionEvent>
667                    // the compiler would infinitely recurse trying to resolve
668                    // &mut &mut &mut ..: Iterator
669                    // so use a VecDeque instead
670                    for gc in front_load_me.drain(..).rev() {
671                        events.push_front(ExecutionEvent::GasCharge(gc))
672                    }
673                    &mut events
674                })?),
675                ExecutionEvent::CallReturn(_)
676                | ExecutionEvent::CallAbort(_)
677                | ExecutionEvent::CallError(_) => {
678                    return Err(BuildExecutionTraceError::UnexpectedReturn);
679                }
680                ExecutionEvent::Log(_ignored) => {}
681                ExecutionEvent::InvokeActor(_cid) => {}
682                ExecutionEvent::Ipld { .. } => {}
683                ExecutionEvent::Unknown(u) => {
684                    return Err(BuildExecutionTraceError::UnrecognisedEvent(Box::new(u)));
685                }
686            }
687        }
688
689        if !front_load_me.is_empty() {
690            tracing::warn!(
691                "vm tracing: ignoring {} trailing gas charges",
692                front_load_me.len()
693            );
694        }
695
696        match call_trees.len() {
697            0 => Ok(None),
698            1 => Ok(Some(call_trees.remove(0))),
699            many => {
700                tracing::warn!(
701                    "vm tracing: ignoring {} call trees at the root level",
702                    many - 1
703                );
704                Ok(Some(call_trees.remove(0)))
705            }
706        }
707    }
708
709    impl ExecutionTrace {
710        /// ```text
711        ///    events: GasCharge Call CallError CallReturn ...
712        ///            ────┬──── ─┬── ───┬───── ────┬─────
713        ///                │      │      │          │
714        /// ┌──────┐       │      └─(T)──┘          │
715        /// │ Call ├───────┴───(T)───┴──────────────┘
716        /// └──────┘            |                   ▲
717        ///                     ▼                   │
718        ///              Returned ExecutionTrace    │
719        ///                                     parsing end
720        /// ```
721        fn parse(
722            call: Call,
723            events: &mut VecDeque<ExecutionEvent>,
724        ) -> Result<ExecutionTrace, BuildExecutionTraceError> {
725            let mut gas_charges = vec![];
726            let mut subcalls = vec![];
727            let mut actor_trace = None;
728
729            // we don't use a for loop over `events` so we can pass them to recursive calls
730            while let Some(event) = events.pop_front() {
731                let found_return = match event {
732                    ExecutionEvent::GasCharge(gc) => {
733                        gas_charges.push(to_gas_trace(gc));
734                        None
735                    }
736                    ExecutionEvent::Call(call) => {
737                        subcalls.push(Self::parse(call, events)?);
738                        None
739                    }
740                    ExecutionEvent::CallReturn(ret) => Some(CallTreeReturn::Return(ret)),
741                    ExecutionEvent::CallAbort(ab) => Some(CallTreeReturn::Abort(ab)),
742                    ExecutionEvent::CallError(e) => Some(CallTreeReturn::Error(e)),
743                    ExecutionEvent::Log(_ignored) => None,
744                    ExecutionEvent::InvokeActor(cid) => {
745                        actor_trace = match cid {
746                            Either::Left(_cid) => None,
747                            Either::Right(actor) => Some(ActorTrace {
748                                id: actor.id,
749                                state: actor.state,
750                            }),
751                        };
752                        None
753                    }
754                    ExecutionEvent::Ipld { .. } => None,
755                    // RUST: This should be caught at compile time with #[deny(non_exhaustive_omitted_patterns)]
756                    //       So that BuildExecutionTraceError::UnrecognisedEvent is never constructed
757                    //       But that lint is not yet stabilised: https://github.com/rust-lang/rust/issues/89554
758                    ExecutionEvent::Unknown(u) => {
759                        return Err(BuildExecutionTraceError::UnrecognisedEvent(Box::new(u)));
760                    }
761                };
762
763                // commonise the return branch
764                if let Some(ret) = found_return {
765                    return Ok(ExecutionTrace {
766                        msg: to_message_trace(call),
767                        msg_rct: to_return_trace(ret),
768                        gas_charges,
769                        subcalls,
770                        invoked_actor: actor_trace,
771                    });
772                }
773            }
774
775            Err(BuildExecutionTraceError::NoReturn)
776        }
777    }
778
779    fn to_message_trace(call: Call) -> MessageTrace {
780        let (bytes, codec) = to_bytes_codec(call.params);
781        MessageTrace {
782            from: Address::new_id(call.from),
783            to: call.to,
784            value: call.value,
785            method: call.method_num,
786            params: bytes,
787            params_codec: codec,
788            gas_limit: call.gas_limit,
789            read_only: call.read_only,
790        }
791    }
792
793    fn to_return_trace(ret: CallTreeReturn) -> ReturnTrace {
794        match ret {
795            CallTreeReturn::Return(return_code) => {
796                let exit_code = return_code.exit_code.unwrap_or(0.into());
797                let (bytes, codec) = to_bytes_codec(return_code.data);
798                ReturnTrace {
799                    exit_code,
800                    r#return: bytes,
801                    return_codec: codec,
802                }
803            }
804            CallTreeReturn::Abort(exit_code) => ReturnTrace {
805                exit_code,
806                r#return: RawBytes::default(),
807                return_codec: 0,
808            },
809            CallTreeReturn::Error(syscall_error) => match syscall_error.number {
810                ErrorNumber::InsufficientFunds => ReturnTrace {
811                    exit_code: ExitCode::from(6),
812                    r#return: RawBytes::default(),
813                    return_codec: 0,
814                },
815                _ => ReturnTrace {
816                    exit_code: ExitCode::from(0),
817                    r#return: RawBytes::default(),
818                    return_codec: 0,
819                },
820            },
821        }
822    }
823
824    fn to_bytes_codec(data: Either<RawBytes, Option<IpldBlock>>) -> (RawBytes, u64) {
825        match data {
826            Either::Left(l) => (l, 0),
827            Either::Right(r) => match r {
828                Some(b) => (RawBytes::from(b.data), b.codec),
829                None => (RawBytes::default(), 0),
830            },
831        }
832    }
833
834    fn to_gas_trace(gc: GasCharge) -> GasTrace {
835        GasTrace {
836            name: gc.name().into(),
837            total_gas: gc.total().round_up(),
838            compute_gas: gc.compute_gas().round_up(),
839            storage_gas: gc.other_gas().round_up(),
840            time_taken: gc.elapsed().as_nanos(),
841        }
842    }
843}