forest-filecoin 0.36.1

Rust Filecoin implementation.
Documentation
// Copyright 2019-2026 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

use super::utils::structured;
use super::*;
use crate::interpreter::{ExecutionContext, IMPLICIT_MESSAGE_GAS_LIMIT, VM, VMTrace};
use crate::message::{MessageRead as _, MessageReadWrite as _};
use crate::rpc::state::{ApiInvocResult, MessageGasCost};
use crate::shim::executor::ApplyRet;
use crate::shim::message::{METHOD_SEND, Message};
use crate::state_migration::run_state_migrations;
use std::time::Duration;
use tracing::instrument;

impl StateManager {
    /// Blocking version of [`Self::call`], use with caution.
    #[instrument(skip(self))]
    pub fn call_blocking(
        &self,
        msg: &Message,
        tipset: Option<Tipset>,
    ) -> Result<ApiInvocResult, Error> {
        let mut msg = msg.clone();
        let chain_config = self.chain_config();

        let tipset = if let Some(ts) = tipset {
            if ts.epoch() > 0 {
                // The call runs on the parent state, so it lags a tipset: a migration at the
                // parent epoch must be refused too.
                let parent = self
                    .chain_index()
                    .load_required_tipset(ts.parents())
                    .map_err(Error::other)?;
                if let Some(epoch) =
                    chain_config.expensive_fork_between(parent.epoch(), ts.epoch() + 1)
                {
                    return Err(Error::ExpensiveFork { epoch });
                }
            }
            ts
        } else {
            // Search back till we find a height with no fork, or we reach the beginning.
            let mut heaviest_ts = self.heaviest_tipset();
            while heaviest_ts.epoch() > 0 {
                let parent = self
                    .chain_index()
                    .load_required_tipset(heaviest_ts.parents())
                    .map_err(Error::other)?;
                if !chain_config.has_expensive_fork_between(parent.epoch(), heaviest_ts.epoch() + 1)
                {
                    break;
                }
                heaviest_ts = parent;
            }
            heaviest_ts
        };

        let state_cid = *tipset.parent_state();

        // Handle state forks
        let state_cid = match run_state_migrations(
            tipset.epoch(),
            self.chain_config(),
            self.db(),
            &state_cid,
        ) {
            Ok(Some(new_state)) => new_state,
            Ok(None) => state_cid,
            Err(e) => return Err(Error::other(e)),
        };

        let height = tipset.epoch();
        let mut vm = VM::new(
            ExecutionContext {
                heaviest_tipset: tipset.shallow_clone(),
                state_tree_root: state_cid,
                epoch: height,
                rand: Box::new(self.chain_rand(tipset.shallow_clone())),
                base_fee: tipset.block_headers().first().parent_base_fee.clone(),
                circ_supply: self.genesis_info().get_vm_circulating_supply(
                    height,
                    self.db(),
                    &state_cid,
                )?,
                chain_config: self.chain_config().shallow_clone(),
                chain_index: self.chain_index().shallow_clone(),
                timestamp: tipset.min_timestamp(),
            },
            &self.engine,
            VMTrace::Traced,
        )?;

        let tipset_messages = self
            .chain_store()
            .messages_for_tipset(&tipset)
            .map_err(|err| Error::Other(err.to_string()))?;

        let prior_messsages = tipset_messages
            .iter()
            .filter(|ts_msg| ts_msg.message().from() == msg.from());

        for m in prior_messsages {
            vm.apply_message(m)?;
        }

        // We flush to get the VM's view of the state tree after applying the above messages
        // This is needed to get the correct nonce from the actor state to match the VM
        let state_cid = vm.flush()?;

        let state = StateTree::new_from_root(self.db(), &state_cid)?;

        let from_actor = state
            .get_actor(&msg.from())?
            .ok_or_else(|| anyhow::anyhow!("actor not found"))?;
        msg.set_sequence(from_actor.sequence);

        // Implicit messages need to set a special gas limit
        msg.gas_limit = IMPLICIT_MESSAGE_GAS_LIMIT as u64;

        let (apply_ret, duration) = vm.apply_implicit_message(&msg)?;

        let msg_cid = msg.cid();
        Ok(ApiInvocResult {
            msg,
            msg_rct: Some(apply_ret.msg_receipt()),
            msg_cid,
            error: apply_ret.failure_info().unwrap_or_default(),
            duration: duration.as_nanos().clamp(0, u128::from(u64::MAX)) as u64,
            gas_cost: MessageGasCost::default(),
            execution_trace: structured::parse_events(apply_ret.exec_trace()).unwrap_or_default(),
        })
    }

    /// runs the given message and returns its result without any persisted
    /// changes.
    pub async fn call(
        &self,
        message: Arc<Message>,
        tipset: Option<Tipset>,
    ) -> Result<ApiInvocResult, Error> {
        let this = self.shallow_clone();
        tokio::task::spawn_blocking(move || this.call_blocking(&message, tipset)).await?
    }

    pub async fn apply_on_state_with_gas(
        &self,
        tipset: Option<&Tipset>,
        msg: &Message,
        vm_flush: VMFlush,
        vm_trace: VMTrace,
        sender_validation: SenderValidation,
    ) -> anyhow::Result<(ApiInvocResult, Option<Cid>)> {
        let ts = tipset.map_or_else(|| self.heaviest_tipset(), Tipset::shallow_clone);

        let from_protocol = match sender_validation {
            SenderValidation::Skip => msg.from.protocol(),
            SenderValidation::Enforce => self
                .resolve_to_deterministic_address(msg.from, &ts)
                .await
                .context("could not resolve key")?
                .protocol(),
        };
        let chain_msg = ChainMessage::for_gas_estimation(msg.clone(), from_protocol);

        let (apply_ret, duration, state_root) = self
            .call_with_gas(
                chain_msg,
                Default::default(),
                Some(ts),
                vm_flush,
                vm_trace,
                sender_validation,
            )
            .await?;

        let msg_rct = Some(apply_ret.msg_receipt());
        let error = apply_ret.failure_info().unwrap_or_default();
        Ok((
            ApiInvocResult {
                msg_cid: msg.cid(),
                msg: msg.clone(),
                msg_rct,
                error,
                duration: duration.as_nanos().clamp(0, u128::from(u64::MAX)) as u64,
                gas_cost: MessageGasCost::default(),
                execution_trace: structured::parse_events(apply_ret.into_exec_trace())
                    .unwrap_or_default(),
            },
            state_root,
        ))
    }

    /// Computes message on the given [Tipset] state, after applying other
    /// messages and returns the values computed in the VM.
    pub async fn call_with_gas(
        &self,
        mut message: ChainMessage,
        prior_messages: Arc<Vec<ChainMessage>>,
        tipset: Option<Tipset>,
        vm_flush: VMFlush,
        vm_trace: VMTrace,
        sender_validation: SenderValidation,
    ) -> Result<(ApplyRet, Duration, Option<Cid>), Error> {
        let ts = tipset.unwrap_or_else(|| self.heaviest_tipset());
        let TipsetState { state_root, .. } = self
            .load_tipset_state(&ts)
            .await
            .map_err(|e| Error::Other(format!("Could not load tipset state: {e:#}")))?;
        let chain_rand = self.chain_rand(ts.clone());

        // Since we're simulating a future message, pretend we're applying it in the
        // "next" tipset
        let epoch = ts.epoch() + 1;
        let this = self.shallow_clone();
        tokio::task::spawn_blocking(move || {
            // FVM requires a stack size of 64MiB. The alternative is to use `ThreadedExecutor` from
            // FVM, but that introduces some constraints, and possible deadlocks.
            let (ret, duration, state_cid) = stacker::grow(64 << 20, || -> Result<_, Error> {
                let mut vm = VM::new(
                    ExecutionContext {
                        heaviest_tipset: ts.clone(),
                        state_tree_root: state_root,
                        epoch,
                        rand: Box::new(chain_rand),
                        base_fee: ts.block_headers().first().parent_base_fee.clone(),
                        circ_supply: this.genesis_info().get_vm_circulating_supply(
                            epoch,
                            this.chain_index().db(),
                            &state_root,
                        )?,
                        chain_config: this.chain_config().shallow_clone(),
                        chain_index: this.chain_index().shallow_clone(),
                        timestamp: ts.min_timestamp(),
                    },
                    &this.engine,
                    vm_trace,
                )?;

                for msg in prior_messages.iter() {
                    vm.apply_message(msg)?;
                }

                let (from_actor, apply) =
                    sender_for_simulation(&mut vm, message.from(), sender_validation)?;
                message.set_sequence(from_actor.sequence);
                let (ret, duration) = match apply {
                    // An existing non-account sender needs the implicit path, which skips the
                    // account-type, nonce and balance checks, and charges no inclusion cost.
                    SenderApply::Implicit => vm.apply_implicit_message(message.message())?,
                    // A fresh placeholder is a valid sender, so the explicit path keeps gas
                    // accounting, inclusion cost included, matching a real first send.
                    SenderApply::Explicit => vm.apply_message(&message)?,
                };
                let state_root = match vm_flush {
                    VMFlush::Flush => Some(vm.flush()?),
                    VMFlush::Skip => None,
                };
                Ok((ret, duration, state_root))
            })?;

            Ok((ret, duration, state_cid))
        })
        .await?
    }
}

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum SenderApply {
    Explicit,
    Implicit,
}

/// Looks up `from` and decides how to apply the simulated message.
///
/// With [`SenderValidation::Skip`], eth methods accept a missing or non-account (EVM) sender.
/// A missing sender is created as an ephemeral placeholder via an implicit system send, then the
/// user message is applied explicitly so first-send gas still matches a real send. An existing
/// non-account must be applied implicitly to bypass account-type / nonce / balance checks.
fn sender_for_simulation(
    vm: &mut VM,
    from: Address,
    sender_validation: SenderValidation,
) -> Result<(ActorState, SenderApply), Error> {
    match (
        vm.get_actor(&from)
            .map_err(|e| Error::Other(format!("Could not get actor from state: {e:#}")))?,
        sender_validation,
    ) {
        (Some(actor), SenderValidation::Enforce) => Ok((actor, SenderApply::Explicit)),
        (Some(actor), SenderValidation::Skip) => Ok((actor, SenderApply::Implicit)),
        (None, SenderValidation::Enforce) => Err(Error::SenderValidationFailed(format!(
            "sender {from} not found on chain"
        ))),
        (None, SenderValidation::Skip) => {
            let (create_ret, _) = vm.apply_implicit_message(&placeholder_send(from))?;
            let exit_code = create_ret.msg_receipt().exit_code();
            if !exit_code.is_success() {
                return Err(Error::Other(format!(
                    "failed to create ephemeral sender placeholder {from} (exit={exit_code}): {}",
                    create_ret.failure_info().unwrap_or_default()
                )));
            }
            let actor = vm
                .get_actor(&from)
                .map_err(|e| Error::Other(format!("Could not get placeholder actor: {e:#}")))?
                .ok_or_else(|| {
                    Error::Other(format!(
                        "ephemeral sender placeholder {from} missing after creation"
                    ))
                })?;
            Ok((actor, SenderApply::Explicit))
        }
    }
}

fn placeholder_send(to: Address) -> Message {
    Message {
        from: Address::SYSTEM_ACTOR,
        to,
        method_num: METHOD_SEND,
        gas_limit: IMPLICIT_MESSAGE_GAS_LIMIT as u64,
        ..Default::default()
    }
}