Skip to main content

forest/state_manager/
message_simulation.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use super::utils::structured;
5use super::*;
6use crate::interpreter::{ExecutionContext, IMPLICIT_MESSAGE_GAS_LIMIT, VM, VMTrace};
7use crate::message::{MessageRead as _, MessageReadWrite as _};
8use crate::rpc::state::{ApiInvocResult, MessageGasCost};
9use crate::shim::executor::ApplyRet;
10use crate::shim::message::{METHOD_SEND, Message};
11use crate::state_migration::run_state_migrations;
12use std::time::Duration;
13use tracing::instrument;
14
15impl StateManager {
16    /// Blocking version of [`Self::call`], use with caution.
17    #[instrument(skip(self))]
18    pub fn call_blocking(
19        &self,
20        msg: &Message,
21        tipset: Option<Tipset>,
22    ) -> Result<ApiInvocResult, Error> {
23        let mut msg = msg.clone();
24        let chain_config = self.chain_config();
25
26        let tipset = if let Some(ts) = tipset {
27            if ts.epoch() > 0 {
28                // The call runs on the parent state, so it lags a tipset: a migration at the
29                // parent epoch must be refused too.
30                let parent = self
31                    .chain_index()
32                    .load_required_tipset(ts.parents())
33                    .map_err(Error::other)?;
34                if let Some(epoch) =
35                    chain_config.expensive_fork_between(parent.epoch(), ts.epoch() + 1)
36                {
37                    return Err(Error::ExpensiveFork { epoch });
38                }
39            }
40            ts
41        } else {
42            // Search back till we find a height with no fork, or we reach the beginning.
43            let mut heaviest_ts = self.heaviest_tipset();
44            while heaviest_ts.epoch() > 0 {
45                let parent = self
46                    .chain_index()
47                    .load_required_tipset(heaviest_ts.parents())
48                    .map_err(Error::other)?;
49                if !chain_config.has_expensive_fork_between(parent.epoch(), heaviest_ts.epoch() + 1)
50                {
51                    break;
52                }
53                heaviest_ts = parent;
54            }
55            heaviest_ts
56        };
57
58        let state_cid = *tipset.parent_state();
59
60        // Handle state forks
61        let state_cid = match run_state_migrations(
62            tipset.epoch(),
63            self.chain_config(),
64            self.db(),
65            &state_cid,
66        ) {
67            Ok(Some(new_state)) => new_state,
68            Ok(None) => state_cid,
69            Err(e) => return Err(Error::other(e)),
70        };
71
72        let height = tipset.epoch();
73        let mut vm = VM::new(
74            ExecutionContext {
75                heaviest_tipset: tipset.shallow_clone(),
76                state_tree_root: state_cid,
77                epoch: height,
78                rand: Box::new(self.chain_rand(tipset.shallow_clone())),
79                base_fee: tipset.block_headers().first().parent_base_fee.clone(),
80                circ_supply: self.genesis_info().get_vm_circulating_supply(
81                    height,
82                    self.db(),
83                    &state_cid,
84                )?,
85                chain_config: self.chain_config().shallow_clone(),
86                chain_index: self.chain_index().shallow_clone(),
87                timestamp: tipset.min_timestamp(),
88            },
89            &self.engine,
90            VMTrace::Traced,
91        )?;
92
93        let tipset_messages = self
94            .chain_store()
95            .messages_for_tipset(&tipset)
96            .map_err(|err| Error::Other(err.to_string()))?;
97
98        let prior_messsages = tipset_messages
99            .iter()
100            .filter(|ts_msg| ts_msg.message().from() == msg.from());
101
102        for m in prior_messsages {
103            vm.apply_message(m)?;
104        }
105
106        // We flush to get the VM's view of the state tree after applying the above messages
107        // This is needed to get the correct nonce from the actor state to match the VM
108        let state_cid = vm.flush()?;
109
110        let state = StateTree::new_from_root(self.db(), &state_cid)?;
111
112        let from_actor = state
113            .get_actor(&msg.from())?
114            .ok_or_else(|| anyhow::anyhow!("actor not found"))?;
115        msg.set_sequence(from_actor.sequence);
116
117        // Implicit messages need to set a special gas limit
118        msg.gas_limit = IMPLICIT_MESSAGE_GAS_LIMIT as u64;
119
120        let (apply_ret, duration) = vm.apply_implicit_message(&msg)?;
121
122        let msg_cid = msg.cid();
123        Ok(ApiInvocResult {
124            msg,
125            msg_rct: Some(apply_ret.msg_receipt()),
126            msg_cid,
127            error: apply_ret.failure_info().unwrap_or_default(),
128            duration: duration.as_nanos().clamp(0, u128::from(u64::MAX)) as u64,
129            gas_cost: MessageGasCost::default(),
130            execution_trace: structured::parse_events(apply_ret.exec_trace()).unwrap_or_default(),
131        })
132    }
133
134    /// runs the given message and returns its result without any persisted
135    /// changes.
136    pub async fn call(
137        &self,
138        message: Arc<Message>,
139        tipset: Option<Tipset>,
140    ) -> Result<ApiInvocResult, Error> {
141        let this = self.shallow_clone();
142        tokio::task::spawn_blocking(move || this.call_blocking(&message, tipset)).await?
143    }
144
145    pub async fn apply_on_state_with_gas(
146        &self,
147        tipset: Option<&Tipset>,
148        msg: &Message,
149        vm_flush: VMFlush,
150        vm_trace: VMTrace,
151        sender_validation: SenderValidation,
152    ) -> anyhow::Result<(ApiInvocResult, Option<Cid>)> {
153        let ts = tipset.map_or_else(|| self.heaviest_tipset(), Tipset::shallow_clone);
154
155        let from_protocol = match sender_validation {
156            SenderValidation::Skip => msg.from.protocol(),
157            SenderValidation::Enforce => self
158                .resolve_to_deterministic_address(msg.from, &ts)
159                .await
160                .context("could not resolve key")?
161                .protocol(),
162        };
163        let chain_msg = ChainMessage::for_gas_estimation(msg.clone(), from_protocol);
164
165        let (apply_ret, duration, state_root) = self
166            .call_with_gas(
167                chain_msg,
168                Default::default(),
169                Some(ts),
170                vm_flush,
171                vm_trace,
172                sender_validation,
173            )
174            .await?;
175
176        let msg_rct = Some(apply_ret.msg_receipt());
177        let error = apply_ret.failure_info().unwrap_or_default();
178        Ok((
179            ApiInvocResult {
180                msg_cid: msg.cid(),
181                msg: msg.clone(),
182                msg_rct,
183                error,
184                duration: duration.as_nanos().clamp(0, u128::from(u64::MAX)) as u64,
185                gas_cost: MessageGasCost::default(),
186                execution_trace: structured::parse_events(apply_ret.into_exec_trace())
187                    .unwrap_or_default(),
188            },
189            state_root,
190        ))
191    }
192
193    /// Computes message on the given [Tipset] state, after applying other
194    /// messages and returns the values computed in the VM.
195    pub async fn call_with_gas(
196        &self,
197        mut message: ChainMessage,
198        prior_messages: Arc<Vec<ChainMessage>>,
199        tipset: Option<Tipset>,
200        vm_flush: VMFlush,
201        vm_trace: VMTrace,
202        sender_validation: SenderValidation,
203    ) -> Result<(ApplyRet, Duration, Option<Cid>), Error> {
204        let ts = tipset.unwrap_or_else(|| self.heaviest_tipset());
205        let TipsetState { state_root, .. } = self
206            .load_tipset_state(&ts)
207            .await
208            .map_err(|e| Error::Other(format!("Could not load tipset state: {e:#}")))?;
209        let chain_rand = self.chain_rand(ts.clone());
210
211        // Since we're simulating a future message, pretend we're applying it in the
212        // "next" tipset
213        let epoch = ts.epoch() + 1;
214        let this = self.shallow_clone();
215        tokio::task::spawn_blocking(move || {
216            // FVM requires a stack size of 64MiB. The alternative is to use `ThreadedExecutor` from
217            // FVM, but that introduces some constraints, and possible deadlocks.
218            let (ret, duration, state_cid) = stacker::grow(64 << 20, || -> Result<_, Error> {
219                let mut vm = VM::new(
220                    ExecutionContext {
221                        heaviest_tipset: ts.clone(),
222                        state_tree_root: state_root,
223                        epoch,
224                        rand: Box::new(chain_rand),
225                        base_fee: ts.block_headers().first().parent_base_fee.clone(),
226                        circ_supply: this.genesis_info().get_vm_circulating_supply(
227                            epoch,
228                            this.chain_index().db(),
229                            &state_root,
230                        )?,
231                        chain_config: this.chain_config().shallow_clone(),
232                        chain_index: this.chain_index().shallow_clone(),
233                        timestamp: ts.min_timestamp(),
234                    },
235                    &this.engine,
236                    vm_trace,
237                )?;
238
239                for msg in prior_messages.iter() {
240                    vm.apply_message(msg)?;
241                }
242
243                let (from_actor, apply) =
244                    sender_for_simulation(&mut vm, message.from(), sender_validation)?;
245                message.set_sequence(from_actor.sequence);
246                let (ret, duration) = match apply {
247                    // An existing non-account sender needs the implicit path, which skips the
248                    // account-type, nonce and balance checks, and charges no inclusion cost.
249                    SenderApply::Implicit => vm.apply_implicit_message(message.message())?,
250                    // A fresh placeholder is a valid sender, so the explicit path keeps gas
251                    // accounting, inclusion cost included, matching a real first send.
252                    SenderApply::Explicit => vm.apply_message(&message)?,
253                };
254                let state_root = match vm_flush {
255                    VMFlush::Flush => Some(vm.flush()?),
256                    VMFlush::Skip => None,
257                };
258                Ok((ret, duration, state_root))
259            })?;
260
261            Ok((ret, duration, state_cid))
262        })
263        .await?
264    }
265}
266
267#[derive(Debug, Copy, Clone, PartialEq, Eq)]
268enum SenderApply {
269    Explicit,
270    Implicit,
271}
272
273/// Looks up `from` and decides how to apply the simulated message.
274///
275/// With [`SenderValidation::Skip`], eth methods accept a missing or non-account (EVM) sender.
276/// A missing sender is created as an ephemeral placeholder via an implicit system send, then the
277/// user message is applied explicitly so first-send gas still matches a real send. An existing
278/// non-account must be applied implicitly to bypass account-type / nonce / balance checks.
279fn sender_for_simulation(
280    vm: &mut VM,
281    from: Address,
282    sender_validation: SenderValidation,
283) -> Result<(ActorState, SenderApply), Error> {
284    match (
285        vm.get_actor(&from)
286            .map_err(|e| Error::Other(format!("Could not get actor from state: {e:#}")))?,
287        sender_validation,
288    ) {
289        (Some(actor), SenderValidation::Enforce) => Ok((actor, SenderApply::Explicit)),
290        (Some(actor), SenderValidation::Skip) => Ok((actor, SenderApply::Implicit)),
291        (None, SenderValidation::Enforce) => Err(Error::SenderValidationFailed(format!(
292            "sender {from} not found on chain"
293        ))),
294        (None, SenderValidation::Skip) => {
295            let (create_ret, _) = vm.apply_implicit_message(&placeholder_send(from))?;
296            let exit_code = create_ret.msg_receipt().exit_code();
297            if !exit_code.is_success() {
298                return Err(Error::Other(format!(
299                    "failed to create ephemeral sender placeholder {from} (exit={exit_code}): {}",
300                    create_ret.failure_info().unwrap_or_default()
301                )));
302            }
303            let actor = vm
304                .get_actor(&from)
305                .map_err(|e| Error::Other(format!("Could not get placeholder actor: {e:#}")))?
306                .ok_or_else(|| {
307                    Error::Other(format!(
308                        "ephemeral sender placeholder {from} missing after creation"
309                    ))
310                })?;
311            Ok((actor, SenderApply::Explicit))
312        }
313    }
314}
315
316fn placeholder_send(to: Address) -> Message {
317    Message {
318        from: Address::SYSTEM_ACTOR,
319        to,
320        method_num: METHOD_SEND,
321        gas_limit: IMPLICIT_MESSAGE_GAS_LIMIT as u64,
322        ..Default::default()
323    }
324}