forest/state_manager/
utils.rs

1// Copyright 2019-2025 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use crate::shim::actors::miner;
5use crate::shim::{
6    actors::{is_account_actor, is_ethaccount_actor, is_placeholder_actor},
7    address::{Address, Payload},
8    randomness::Randomness,
9    sector::{ExtendedSectorInfo, RegisteredPoStProof, RegisteredSealProof},
10    state_tree::ActorState,
11    version::NetworkVersion,
12};
13use crate::utils::encoding::prover_id_from_u64;
14use cid::Cid;
15use fil_actors_shared::filecoin_proofs_api::post;
16use fil_actors_shared::fvm_ipld_bitfield::BitField;
17use fvm_ipld_blockstore::Blockstore;
18use fvm_ipld_encoding::bytes_32;
19
20use crate::state_manager::{StateManager, errors::*};
21
22use super::MinerActorStateLoad as _;
23
24impl<DB> StateManager<DB>
25where
26    DB: Blockstore,
27{
28    /// Retrieves and generates a vector of sector info for the winning `PoSt`
29    /// verification.
30    pub fn get_sectors_for_winning_post(
31        &self,
32        st: &Cid,
33        nv: NetworkVersion,
34        miner_address: &Address,
35        rand: Randomness,
36    ) -> Result<Vec<ExtendedSectorInfo>, anyhow::Error> {
37        let store = self.blockstore();
38
39        let actor = self
40            .get_actor(miner_address, *st)?
41            .ok_or_else(|| Error::state("Miner actor address could not be resolved"))?;
42        let mas = miner::State::load(self.blockstore(), actor.code, actor.state)?;
43
44        let proving_sectors = {
45            let mut proving_sectors = BitField::new();
46
47            if nv < NetworkVersion::V7 {
48                mas.for_each_deadline(&self.chain_config().policy, store, |_, deadline| {
49                    let mut fault_sectors = BitField::new();
50                    deadline.for_each(store, |_, partition: miner::Partition| {
51                        proving_sectors |= partition.all_sectors();
52                        fault_sectors |= partition.faulty_sectors();
53                        Ok(())
54                    })?;
55
56                    proving_sectors -= &fault_sectors;
57                    Ok(())
58                })?;
59            } else {
60                mas.for_each_deadline(&self.chain_config().policy, store, |_, deadline| {
61                    deadline.for_each(store, |_, partition: miner::Partition| {
62                        proving_sectors |= &partition.active_sectors();
63                        Ok(())
64                    })?;
65                    Ok(())
66                })?;
67            }
68            proving_sectors
69        };
70
71        let num_prov_sect = proving_sectors.len();
72
73        if num_prov_sect == 0 {
74            return Ok(Vec::new());
75        }
76
77        let info = mas.info(store)?;
78        let spt = RegisteredSealProof::from_sector_size(info.sector_size().into(), nv);
79
80        let wpt = spt.registered_winning_post_proof()?;
81
82        let m_id = miner_address.id()?;
83
84        let ids = generate_winning_post_sector_challenge(wpt.into(), m_id, rand, num_prov_sect)?;
85
86        let mut iter = proving_sectors.iter();
87
88        let mut selected_sectors = BitField::new();
89        for n in ids {
90            let sno = iter.nth(n as usize).ok_or_else(|| {
91                anyhow::anyhow!(
92                    "Error iterating over proving sectors, id {} does not exist",
93                    n
94                )
95            })?;
96            selected_sectors.set(sno);
97        }
98
99        let sectors = mas.load_sectors(store, Some(&selected_sectors))?;
100
101        let out = sectors
102            .into_iter()
103            .map(|s_info| ExtendedSectorInfo {
104                proof: s_info.seal_proof.into(),
105                sector_number: s_info.sector_number,
106                sector_key: s_info.sector_key_cid,
107                sealed_cid: s_info.sealed_cid,
108            })
109            .collect();
110
111        Ok(out)
112    }
113}
114
115pub fn is_valid_for_sending(network_version: NetworkVersion, actor: &ActorState) -> bool {
116    // Comments from Lotus:
117    // Before nv18 (Hygge), we only supported built-in account actors as senders.
118    //
119    // Note: this gate is probably superfluous, since:
120    // 1. Placeholder actors cannot be created before nv18.
121    // 2. EthAccount actors cannot be created before nv18.
122    // 3. Delegated addresses cannot be created before nv18.
123    //
124    // But it's a safeguard.
125    //
126    // Note 2: ad-hoc checks for network versions like this across the codebase
127    // will be problematic with networks with diverging version lineages
128    // (e.g. Hyperspace). We need to revisit this strategy entirely.
129    if network_version < NetworkVersion::V18 {
130        return is_account_actor(&actor.code);
131    }
132
133    // After nv18, we also support other kinds of senders.
134    if is_account_actor(&actor.code) || is_ethaccount_actor(&actor.code) {
135        return true;
136    }
137
138    // Allow placeholder actors with a delegated address and nonce 0 to send a
139    // message. These will be converted to an EthAccount actor on first send.
140    if !is_placeholder_actor(&actor.code)
141        || actor.sequence != 0
142        || actor.delegated_address.is_none()
143    {
144        return false;
145    }
146
147    // Only allow such actors to send if their delegated address is in the EAM's
148    // namespace.
149    if let Payload::Delegated(address) = actor
150        .delegated_address
151        .as_ref()
152        .expect("unfallible")
153        .payload()
154    {
155        address.namespace() == Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR.id().unwrap()
156    } else {
157        false
158    }
159}
160
161/// Generates sector challenge indexes for use in winning PoSt verification.
162fn generate_winning_post_sector_challenge(
163    proof: RegisteredPoStProof,
164    prover_id: u64,
165    mut rand: Randomness,
166    eligible_sector_count: u64,
167) -> Result<Vec<u64>, anyhow::Error> {
168    // Necessary to be valid bls12 381 element.
169    if let Some(b31) = rand.0.get_mut(31) {
170        *b31 &= 0x3f;
171    } else {
172        anyhow::bail!("rand should have at least 32 bytes");
173    }
174
175    post::generate_winning_post_sector_challenge(
176        proof.try_into()?,
177        &bytes_32(&rand.0),
178        eligible_sector_count,
179        prover_id_from_u64(prover_id),
180    )
181}
182
183#[cfg(test)]
184mod test {
185    use crate::shim::{address::Address, econ::TokenAmount, state_tree::ActorState};
186    use cid::Cid;
187
188    use super::*;
189
190    #[test]
191    fn is_valid_for_sending_test() {
192        let create_actor = |code: &Cid, sequence: u64, delegated_address: Option<Address>| {
193            ActorState::new(
194                code.to_owned(),
195                // changing this cid will unleash unthinkable horrors upon the world
196                Cid::try_from("bafk2bzaceavfgpiw6whqigmskk74z4blm22nwjfnzxb4unlqz2e4wgcthulhu")
197                    .unwrap(),
198                TokenAmount::default(),
199                sequence,
200                delegated_address,
201            )
202        };
203
204        // calibnet actor version 10
205        let account_actor_cid =
206            Cid::try_from("bafk2bzaceavfgpiw6whqigmskk74z4blm22nwjfnzxb4unlqz2e4wg3c5ujpw")
207                .unwrap();
208        let ethaccount_actor_cid =
209            Cid::try_from("bafk2bzacebiyrhz32xwxi6xql67aaq5nrzeelzas472kuwjqmdmgwotpkj35e")
210                .unwrap();
211        let placeholder_actor_cid =
212            Cid::try_from("bafk2bzacedfvut2myeleyq67fljcrw4kkmn5pb5dpyozovj7jpoez5irnc3ro")
213                .unwrap();
214
215        // happy path for account actor
216        let actor = create_actor(&account_actor_cid, 0, None);
217        assert!(is_valid_for_sending(NetworkVersion::V17, &actor));
218
219        // eth account not allowed before v18, should fail
220        let actor = create_actor(&ethaccount_actor_cid, 0, None);
221        assert!(!is_valid_for_sending(NetworkVersion::V17, &actor));
222
223        // happy path for eth account
224        assert!(is_valid_for_sending(NetworkVersion::V18, &actor));
225
226        // no delegated address for placeholder actor, should fail
227        let actor = create_actor(&placeholder_actor_cid, 0, None);
228        assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
229
230        // happy path for the placeholder actor
231        let delegated_address = Address::new_delegated(
232            Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR.id().unwrap(),
233            &[0; 20],
234        )
235        .ok();
236        let actor = create_actor(&placeholder_actor_cid, 0, delegated_address);
237        assert!(is_valid_for_sending(NetworkVersion::V18, &actor));
238
239        // sequence not 0, should fail
240        let actor = create_actor(&placeholder_actor_cid, 1, delegated_address);
241        assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
242
243        // delegated address not in EAM namespace, should fail
244        let delegated_address =
245            Address::new_delegated(Address::CHAOS_ACTOR.id().unwrap(), &[0; 20]).ok();
246        let actor = create_actor(&placeholder_actor_cid, 0, delegated_address);
247        assert!(!is_valid_for_sending(NetworkVersion::V18, &actor));
248    }
249}
250
251/// Parsed tree of [`fvm4::trace::ExecutionEvent`]s
252pub mod structured {
253    use crate::{
254        rpc::state::{ActorTrace, ExecutionTrace, GasTrace, MessageTrace, ReturnTrace},
255        shim::kernel::ErrorNumber,
256    };
257    use std::collections::VecDeque;
258
259    use crate::shim::{
260        address::Address,
261        error::ExitCode,
262        gas::GasCharge,
263        kernel::SyscallError,
264        trace::{Call, CallReturn, ExecutionEvent},
265    };
266    use fvm_ipld_encoding::{RawBytes, ipld_block::IpldBlock};
267    use itertools::Either;
268
269    enum CallTreeReturn {
270        Return(CallReturn),
271        Abort(ExitCode),
272        Error(SyscallError),
273    }
274
275    #[derive(Debug, thiserror::Error)]
276    pub enum BuildExecutionTraceError {
277        #[error(
278            "every ExecutionEvent::Return | ExecutionEvent::CallError should be preceded by an ExecutionEvent::Call, but this one wasn't"
279        )]
280        UnexpectedReturn,
281        #[error(
282            "every ExecutionEvent::Call should have a corresponding ExecutionEvent::Return, but this one didn't"
283        )]
284        NoReturn,
285        #[error("unrecognised ExecutionEvent variant: {0:?}")]
286        UnrecognisedEvent(Box<dyn std::fmt::Debug + Send + Sync + 'static>),
287    }
288
289    /// Construct a single [`ExecutionTrace`]s from a linear array of [`ExecutionEvent`](fvm4::trace::ExecutionEvent)s.
290    ///
291    /// This function is so-called because it similar to the parse step in a traditional compiler:
292    /// ```text
293    /// text --lex-->     tokens     --parse-->   AST
294    ///               ExecutionEvent --parse--> ExecutionTrace
295    /// ```
296    ///
297    /// This function is notable in that [`GasCharge`](fvm4::gas::GasCharge)s which precede a [`ExecutionTrace`] at the root level
298    /// are attributed to that node.
299    ///
300    /// 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)
301    ///
302    /// ```text
303    /// GasCharge GasCharge Call GasCharge Call CallError CallReturn
304    /// ────┬──── ────┬──── ─┬── ────┬──── ─┬── ───┬───── ────┬─────
305    ///     │         │      │       │      │      │          │
306    ///     │         │      │       │      └─(T)──┘          │
307    ///     │         │      └───────┴───(T)───┴──────────────┘
308    ///     └─────────┴──────────────────►│
309    ///     ("front loaded" GasCharges)   │
310    ///                                  (T)
311    ///
312    /// (T): a ExecutionTrace node
313    /// ```
314    ///
315    /// Multiple call trees and trailing gas will be warned and ignored.
316    /// If no call tree is found, returns [`Ok(None)`]
317    pub fn parse_events(
318        events: Vec<ExecutionEvent>,
319    ) -> anyhow::Result<Option<ExecutionTrace>, BuildExecutionTraceError> {
320        let mut events = VecDeque::from(events);
321        let mut front_load_me = vec![];
322        let mut call_trees = vec![];
323
324        // we don't use a `for` loop so we can pass events them to inner parsers
325        while let Some(event) = events.pop_front() {
326            match event {
327                ExecutionEvent::GasCharge(gc) => front_load_me.push(gc),
328                ExecutionEvent::Call(call) => call_trees.push(ExecutionTrace::parse(call, {
329                    // if ExecutionTrace::parse took impl Iterator<Item = ExecutionEvent>
330                    // the compiler would infinitely recurse trying to resolve
331                    // &mut &mut &mut ..: Iterator
332                    // so use a VecDeque instead
333                    for gc in front_load_me.drain(..).rev() {
334                        events.push_front(ExecutionEvent::GasCharge(gc))
335                    }
336                    &mut events
337                })?),
338                ExecutionEvent::CallReturn(_)
339                | ExecutionEvent::CallAbort(_)
340                | ExecutionEvent::CallError(_) => {
341                    return Err(BuildExecutionTraceError::UnexpectedReturn);
342                }
343                ExecutionEvent::Log(_ignored) => {}
344                ExecutionEvent::InvokeActor(_cid) => {}
345                ExecutionEvent::Ipld { .. } => {}
346                ExecutionEvent::Unknown(u) => {
347                    return Err(BuildExecutionTraceError::UnrecognisedEvent(Box::new(u)));
348                }
349            }
350        }
351
352        if !front_load_me.is_empty() {
353            tracing::warn!(
354                "vm tracing: ignoring {} trailing gas charges",
355                front_load_me.len()
356            );
357        }
358
359        match call_trees.len() {
360            0 => Ok(None),
361            1 => Ok(Some(call_trees.remove(0))),
362            many => {
363                tracing::warn!(
364                    "vm tracing: ignoring {} call trees at the root level",
365                    many - 1
366                );
367                Ok(Some(call_trees.remove(0)))
368            }
369        }
370    }
371
372    impl ExecutionTrace {
373        /// ```text
374        ///    events: GasCharge Call CallError CallReturn ...
375        ///            ────┬──── ─┬── ───┬───── ────┬─────
376        ///                │      │      │          │
377        /// ┌──────┐       │      └─(T)──┘          │
378        /// │ Call ├───────┴───(T)───┴──────────────┘
379        /// └──────┘            |                   ▲
380        ///                     ▼                   │
381        ///              Returned ExecutionTrace    │
382        ///                                     parsing end
383        /// ```
384        fn parse(
385            call: Call,
386            events: &mut VecDeque<ExecutionEvent>,
387        ) -> Result<ExecutionTrace, BuildExecutionTraceError> {
388            let mut gas_charges = vec![];
389            let mut subcalls = vec![];
390            let mut actor_trace = None;
391
392            // we don't use a for loop over `events` so we can pass them to recursive calls
393            while let Some(event) = events.pop_front() {
394                let found_return = match event {
395                    ExecutionEvent::GasCharge(gc) => {
396                        gas_charges.push(to_gas_trace(gc));
397                        None
398                    }
399                    ExecutionEvent::Call(call) => {
400                        subcalls.push(Self::parse(call, events)?);
401                        None
402                    }
403                    ExecutionEvent::CallReturn(ret) => Some(CallTreeReturn::Return(ret)),
404                    ExecutionEvent::CallAbort(ab) => Some(CallTreeReturn::Abort(ab)),
405                    ExecutionEvent::CallError(e) => Some(CallTreeReturn::Error(e)),
406                    ExecutionEvent::Log(_ignored) => None,
407                    ExecutionEvent::InvokeActor(cid) => {
408                        actor_trace = match cid {
409                            Either::Left(_cid) => None,
410                            Either::Right(actor) => Some(ActorTrace {
411                                id: actor.id,
412                                state: actor.state,
413                            }),
414                        };
415                        None
416                    }
417                    ExecutionEvent::Ipld { .. } => None,
418                    // RUST: This should be caught at compile time with #[deny(non_exhaustive_omitted_patterns)]
419                    //       So that BuildExecutionTraceError::UnrecognisedEvent is never constructed
420                    //       But that lint is not yet stabilised: https://github.com/rust-lang/rust/issues/89554
421                    ExecutionEvent::Unknown(u) => {
422                        return Err(BuildExecutionTraceError::UnrecognisedEvent(Box::new(u)));
423                    }
424                };
425
426                // commonise the return branch
427                if let Some(ret) = found_return {
428                    return Ok(ExecutionTrace {
429                        msg: to_message_trace(call),
430                        msg_rct: to_return_trace(ret),
431                        gas_charges,
432                        subcalls,
433                        invoked_actor: actor_trace,
434                    });
435                }
436            }
437
438            Err(BuildExecutionTraceError::NoReturn)
439        }
440    }
441
442    fn to_message_trace(call: Call) -> MessageTrace {
443        let (bytes, codec) = to_bytes_codec(call.params);
444        MessageTrace {
445            from: Address::new_id(call.from),
446            to: call.to,
447            value: call.value,
448            method: call.method_num,
449            params: bytes,
450            params_codec: codec,
451            gas_limit: call.gas_limit,
452            read_only: call.read_only,
453        }
454    }
455
456    fn to_return_trace(ret: CallTreeReturn) -> ReturnTrace {
457        match ret {
458            CallTreeReturn::Return(return_code) => {
459                let exit_code = return_code.exit_code.unwrap_or(0.into());
460                let (bytes, codec) = to_bytes_codec(return_code.data);
461                ReturnTrace {
462                    exit_code,
463                    r#return: bytes,
464                    return_codec: codec,
465                }
466            }
467            CallTreeReturn::Abort(exit_code) => ReturnTrace {
468                exit_code,
469                r#return: RawBytes::default(),
470                return_codec: 0,
471            },
472            CallTreeReturn::Error(syscall_error) => match syscall_error.number {
473                ErrorNumber::InsufficientFunds => ReturnTrace {
474                    exit_code: ExitCode::from(6),
475                    r#return: RawBytes::default(),
476                    return_codec: 0,
477                },
478                _ => ReturnTrace {
479                    exit_code: ExitCode::from(0),
480                    r#return: RawBytes::default(),
481                    return_codec: 0,
482                },
483            },
484        }
485    }
486
487    fn to_bytes_codec(data: Either<RawBytes, Option<IpldBlock>>) -> (RawBytes, u64) {
488        match data {
489            Either::Left(l) => (l, 0),
490            Either::Right(r) => match r {
491                Some(b) => (RawBytes::from(b.data), b.codec),
492                None => (RawBytes::default(), 0),
493            },
494        }
495    }
496
497    fn to_gas_trace(gc: GasCharge) -> GasTrace {
498        GasTrace {
499            name: gc.name().into(),
500            total_gas: gc.total().round_up(),
501            compute_gas: gc.compute_gas().round_up(),
502            storage_gas: gc.other_gas().round_up(),
503            time_taken: gc.elapsed().as_nanos(),
504        }
505    }
506}