Skip to main content

forest/rpc/methods/
state.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4mod types;
5use nonzero_ext::nonzero;
6use tokio::sync::Semaphore;
7pub use types::*;
8
9use super::chain::ChainGetTipSetV2;
10use crate::beacon::Beacon as _;
11use crate::blocks::{Tipset, TipsetKey};
12use crate::chain::index::ResolveNullTipset;
13use crate::cid_collections::CidHashSet;
14use crate::eth::EthChainId;
15use crate::interpreter::{MessageCallbackCtx, VMTrace};
16use crate::libp2p::NetworkMessage;
17use crate::lotus_json::{LotusJson, lotus_json_with_self};
18use crate::networks::{ChainConfig, NetworkChain};
19use crate::prelude::*;
20use crate::rpc::eth::types::CallSource;
21use crate::rpc::registry::actors_reg::load_and_serialize_actor_state;
22use crate::shim::actors::market::DealState;
23use crate::shim::actors::market::ext::MarketStateExt as _;
24use crate::shim::actors::miner::ext::DeadlineExt;
25use crate::shim::actors::state_load::*;
26use crate::shim::actors::verifreg::ext::VerifiedRegistryStateExt as _;
27use crate::shim::actors::verifreg::{Allocation, AllocationID, Claim};
28use crate::shim::actors::{init, system};
29use crate::shim::actors::{
30    market, miner,
31    miner::{MinerInfo, MinerPower},
32    power, reward, verifreg,
33};
34use crate::shim::actors::{
35    market::ext::BalanceTableExt as _, miner::ext::MinerStateExt as _,
36    power::ext::PowerStateExt as _,
37};
38use crate::shim::address::Payload;
39use crate::shim::machine::BuiltinActorManifest;
40use crate::shim::message::{Message, MethodNum};
41use crate::shim::sector::{SectorNumber, SectorSize};
42use crate::shim::state_tree::{ActorID, StateTree};
43use crate::shim::{
44    address::Address, clock::ChainEpoch, deal::DealID, econ::TokenAmount, executor::Receipt,
45    state_tree::ActorState, version::NetworkVersion,
46};
47use crate::state_manager::{ExecutedTipset, NO_CALLBACK};
48use crate::state_manager::{MarketBalance, StateManager, utils::structured};
49use crate::utils::db::car_stream::{CarBlock, CarWriter};
50use crate::{
51    beacon::BeaconEntry,
52    rpc::{ApiPaths, Ctx, Permission, RpcMethod, ServerError, types::*},
53};
54use ahash::{HashMap, HashSet};
55use anyhow::Result;
56use enumflags2::{BitFlags, make_bitflags};
57use fil_actor_miner_state::v10::{qa_power_for_weight, qa_power_max};
58use fil_actor_verifreg_state::v13::ClaimID;
59use fil_actors_shared::fvm_ipld_amt::Amt;
60use fil_actors_shared::fvm_ipld_bitfield::BitField;
61use futures::stream::FuturesOrdered;
62use futures::{StreamExt as _, TryStreamExt as _};
63use fvm_ipld_encoding::{CborStore, DAG_CBOR};
64pub use fvm_shared3::sector::StoragePower;
65use ipld_core::ipld::Ipld;
66use jsonrpsee::types::error::ErrorObject;
67use num_bigint::BigInt;
68use num_traits::Euclid;
69use nunny::vec as nonempty;
70use parking_lot::Mutex;
71use schemars::JsonSchema;
72use serde::{Deserialize, Serialize};
73use std::num::NonZeroU32;
74use std::ops::Mul;
75use std::path::PathBuf;
76use std::sync::LazyLock;
77use std::time::Duration;
78use tokio::task::JoinSet;
79use tokio_util::{sync::CancellationToken, task::AbortOnDropHandle};
80
81const INITIAL_PLEDGE_NUM: u64 = 110;
82const INITIAL_PLEDGE_DEN: u64 = 100;
83const WAIT_FOR_MSG_TIMEOUT: Duration = Duration::from_mins(10);
84const SEARCH_FOR_MSG_TIMEOUT: Duration = Duration::from_mins(10);
85
86pub enum StateCall {}
87
88impl StateCall {
89    pub async fn run(
90        state_manager: &StateManager,
91        message: Arc<Message>,
92        tsk: Option<TipsetKey>,
93    ) -> anyhow::Result<ApiInvocResult> {
94        let mut tipset = state_manager
95            .chain_store()
96            .load_required_tipset_or_heaviest(&tsk)?;
97
98        // Parent-state `call` refuses when a migration spans the parent→tipset window; walk back
99        // to the parent tipset and retry. This does not serve U+1 at the requested tipset (unlike
100        // `eth_call`, which uses explicit tipset state).
101        //
102        // See: <https://github.com/filecoin-project/lotus/blob/797feebc63bfbd4fdfb742b674c97bfb7846cccb/node/impl/full/state.go#L147>
103        loop {
104            match state_manager
105                .call(message.shallow_clone(), Some(tipset.shallow_clone()))
106                .await
107            {
108                Err(crate::state_manager::Error::ExpensiveFork { .. }) => {
109                    tipset = state_manager
110                        .chain_index()
111                        .load_required_tipset(tipset.parents())
112                        .map_err(|e| anyhow::anyhow!("getting parent tipset: {e}"))?;
113                }
114                result => return Ok(result?),
115            }
116        }
117    }
118}
119
120impl RpcMethod<2> for StateCall {
121    const NAME: &'static str = "Filecoin.StateCall";
122    const PARAM_NAMES: [&'static str; 2] = ["message", "tipsetKey"];
123    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
124    const PERMISSION: Permission = Permission::Read;
125    const DESCRIPTION: &'static str = "Runs the given message and returns its result without persisting changes. The message is applied to the tipset's parent state.";
126
127    type Params = (Message, ApiTipsetKey);
128    type Ok = ApiInvocResult;
129
130    async fn handle(
131        ctx: Ctx,
132        (message, ApiTipsetKey(tsk)): Self::Params,
133        _: &http::Extensions,
134    ) -> Result<Self::Ok, ServerError> {
135        Ok(Self::run(&ctx.state_manager, message.into(), tsk).await?)
136    }
137}
138
139pub enum StateReplay {}
140impl RpcMethod<2> for StateReplay {
141    const NAME: &'static str = "Filecoin.StateReplay";
142    const PARAM_NAMES: [&'static str; 2] = ["tipsetKey", "messageCid"];
143    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
144    const PERMISSION: Permission = Permission::Read;
145    const DESCRIPTION: &'static str =
146        "Replays a given message, assuming it was included in a block in the specified tipset.";
147
148    type Params = (ApiTipsetKey, Cid);
149    type Ok = ApiInvocResult;
150
151    /// returns the result of executing the indicated message, assuming it was
152    /// executed in the indicated tipset.
153    async fn handle(
154        ctx: Ctx,
155        (ApiTipsetKey(tsk), message_cid): Self::Params,
156        _: &http::Extensions,
157    ) -> Result<Self::Ok, ServerError> {
158        let tipset = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
159        Ok(ctx
160            .state_manager
161            .replay(tipset, message_cid, CallSource::External)
162            .await?)
163    }
164}
165
166pub enum StateNetworkName {}
167impl RpcMethod<0> for StateNetworkName {
168    const NAME: &'static str = "Filecoin.StateNetworkName";
169    const PARAM_NAMES: [&'static str; 0] = [];
170    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
171    const PERMISSION: Permission = Permission::Read;
172    const DESCRIPTION: &'static str = "Returns the name of the network the node is synced to.";
173
174    type Params = ();
175    type Ok = String;
176
177    async fn handle(
178        ctx: Ctx,
179        (): Self::Params,
180        _: &http::Extensions,
181    ) -> Result<Self::Ok, ServerError> {
182        let heaviest_tipset = ctx.chain_store().heaviest_tipset();
183        Ok(ctx
184            .state_manager
185            .get_network_state_name(*heaviest_tipset.parent_state())?
186            .into())
187    }
188}
189
190pub enum StateNetworkVersion {}
191impl RpcMethod<1> for StateNetworkVersion {
192    const NAME: &'static str = "Filecoin.StateNetworkVersion";
193    const PARAM_NAMES: [&'static str; 1] = ["tipsetKey"];
194    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
195    const PERMISSION: Permission = Permission::Read;
196    const DESCRIPTION: &'static str = "Returns the network version at the given tipset.";
197
198    type Params = (ApiTipsetKey,);
199    type Ok = NetworkVersion;
200
201    async fn handle(
202        ctx: Ctx,
203        (ApiTipsetKey(tsk),): Self::Params,
204        _: &http::Extensions,
205    ) -> Result<Self::Ok, ServerError> {
206        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
207        Ok(ctx.state_manager.get_network_version(ts.epoch()))
208    }
209}
210
211/// gets the public key address of the given ID address
212/// See <https://github.com/filecoin-project/lotus/blob/master/documentation/en/api-methods-v0-deprecated.md#StateAccountKey>
213pub enum StateAccountKey {}
214
215impl RpcMethod<2> for StateAccountKey {
216    const NAME: &'static str = "Filecoin.StateAccountKey";
217    const PARAM_NAMES: [&'static str; 2] = ["address", "tipsetKey"];
218    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
219    const PERMISSION: Permission = Permission::Read;
220    const DESCRIPTION: &'static str =
221        "Returns the public key address for the given ID address (secp and bls accounts).";
222
223    type Params = (Address, ApiTipsetKey);
224    type Ok = Address;
225
226    async fn handle(
227        ctx: Ctx,
228        (address, ApiTipsetKey(tsk)): Self::Params,
229        _: &http::Extensions,
230    ) -> Result<Self::Ok, ServerError> {
231        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
232        Ok(ctx
233            .state_manager
234            .resolve_to_deterministic_address(address, &ts)
235            .await?)
236    }
237}
238
239/// retrieves the ID address of the given address
240/// See <https://github.com/filecoin-project/lotus/blob/master/documentation/en/api-methods-v0-deprecated.md#StateLookupID>
241pub enum StateLookupID {}
242
243impl RpcMethod<2> for StateLookupID {
244    const NAME: &'static str = "Filecoin.StateLookupID";
245    const PARAM_NAMES: [&'static str; 2] = ["address", "tipsetKey"];
246    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
247    const PERMISSION: Permission = Permission::Read;
248    const DESCRIPTION: &'static str = "Retrieves the ID address of the given address.";
249
250    type Params = (Address, ApiTipsetKey);
251    type Ok = Address;
252
253    async fn handle(
254        ctx: Ctx,
255        (address, ApiTipsetKey(tsk)): Self::Params,
256        _: &http::Extensions,
257    ) -> Result<Self::Ok, ServerError> {
258        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
259        Ok(ctx.state_manager.lookup_required_id(&address, &ts)?)
260    }
261}
262
263/// `StateVerifiedRegistryRootKey` returns the address of the Verified Registry's root key
264pub enum StateVerifiedRegistryRootKey {}
265
266impl RpcMethod<1> for StateVerifiedRegistryRootKey {
267    const NAME: &'static str = "Filecoin.StateVerifiedRegistryRootKey";
268    const PARAM_NAMES: [&'static str; 1] = ["tipsetKey"];
269    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
270    const PERMISSION: Permission = Permission::Read;
271    const DESCRIPTION: &'static str = "Returns the address of the Verified Registry's root key.";
272
273    type Params = (ApiTipsetKey,);
274    type Ok = Address;
275
276    async fn handle(
277        ctx: Ctx,
278        (ApiTipsetKey(tsk),): Self::Params,
279        _: &http::Extensions,
280    ) -> Result<Self::Ok, ServerError> {
281        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
282        let state: verifreg::State = ctx.state_manager.get_actor_state(&ts)?;
283        Ok(state.root_key())
284    }
285}
286
287// StateVerifiedClientStatus returns the data cap for the given address.
288// Returns zero if there is no entry in the data cap table for the address.
289pub enum StateVerifierStatus {}
290
291impl RpcMethod<2> for StateVerifierStatus {
292    const NAME: &'static str = "Filecoin.StateVerifierStatus";
293    const PARAM_NAMES: [&'static str; 2] = ["address", "tipsetKey"];
294    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
295    const PERMISSION: Permission = Permission::Read;
296    const DESCRIPTION: &'static str = "Returns the data cap for the given address.";
297
298    type Params = (Address, ApiTipsetKey);
299    type Ok = Option<StoragePower>;
300
301    async fn handle(
302        ctx: Ctx,
303        (address, ApiTipsetKey(tsk)): Self::Params,
304        _: &http::Extensions,
305    ) -> Result<Self::Ok, ServerError> {
306        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
307        let aid = ctx.state_manager.lookup_required_id(&address, &ts)?;
308        let verifreg_state: verifreg::State = ctx.state_manager.get_actor_state(&ts)?;
309        Ok(verifreg_state.verifier_data_cap(ctx.db(), aid)?)
310    }
311}
312
313pub enum StateGetActor {}
314
315impl RpcMethod<2> for StateGetActor {
316    const NAME: &'static str = "Filecoin.StateGetActor";
317    const PARAM_NAMES: [&'static str; 2] = ["address", "tipsetKey"];
318    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
319    const PERMISSION: Permission = Permission::Read;
320    const DESCRIPTION: &'static str = "Returns the nonce and balance for the specified actor.";
321
322    type Params = (Address, ApiTipsetKey);
323    type Ok = Option<ActorState>;
324
325    async fn handle(
326        ctx: Ctx,
327        (address, ApiTipsetKey(tsk)): Self::Params,
328        _: &http::Extensions,
329    ) -> Result<Self::Ok, ServerError> {
330        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
331        let state = ctx.state_manager.get_actor(&address, *ts.parent_state())?;
332        Ok(state)
333    }
334}
335
336pub enum StateGetActorV2 {}
337
338impl RpcMethod<2> for StateGetActorV2 {
339    const NAME: &'static str = "Filecoin.StateGetActor";
340    const PARAM_NAMES: [&'static str; 2] = ["address", "tipsetSelector"];
341    const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::{ V2 });
342    const PERMISSION: Permission = Permission::Read;
343    const DESCRIPTION: &'static str = "Returns the nonce and balance for the specified actor.";
344
345    type Params = (Address, TipsetSelector);
346    type Ok = Option<ActorState>;
347
348    async fn handle(
349        ctx: Ctx,
350        (address, selector): Self::Params,
351        _: &http::Extensions,
352    ) -> Result<Self::Ok, ServerError> {
353        let ts = ChainGetTipSetV2::get_tipset(&ctx, &selector).await?;
354        Ok(ctx.state_manager.get_actor(&address, *ts.parent_state())?)
355    }
356}
357
358pub enum StateGetID {}
359
360impl RpcMethod<2> for StateGetID {
361    const NAME: &'static str = "Filecoin.StateGetID";
362    const PARAM_NAMES: [&'static str; 2] = ["address", "tipsetSelector"];
363    const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::{ V2 });
364    const PERMISSION: Permission = Permission::Read;
365    const DESCRIPTION: &'static str =
366        "Retrieves the ID address for the specified address at the selected tipset.";
367
368    type Params = (Address, TipsetSelector);
369    type Ok = Address;
370
371    async fn handle(
372        ctx: Ctx,
373        (address, selector): Self::Params,
374        _: &http::Extensions,
375    ) -> Result<Self::Ok, ServerError> {
376        let ts = ChainGetTipSetV2::get_tipset(&ctx, &selector).await?;
377        Ok(ctx.state_manager.lookup_required_id(&address, &ts)?)
378    }
379}
380
381pub enum StateLookupRobustAddress {}
382
383macro_rules! get_robust_address {
384    ($store:expr, $id_addr_decoded:expr, $state:expr, $make_map_with_root:path, $robust_addr:expr) => {{
385        let map = $make_map_with_root(&$state.address_map, &$store)?;
386        map.for_each(|addr, v| {
387            if *v == $id_addr_decoded {
388                $robust_addr = Address::from_bytes(addr)?;
389                return Ok(());
390            }
391            Ok(())
392        })?;
393        Ok($robust_addr)
394    }};
395}
396
397impl RpcMethod<2> for StateLookupRobustAddress {
398    const NAME: &'static str = "Filecoin.StateLookupRobustAddress";
399    const PARAM_NAMES: [&'static str; 2] = ["address", "tipsetKey"];
400    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
401    const PERMISSION: Permission = Permission::Read;
402    const DESCRIPTION: &'static str =
403        "Returns the public key address for non-account addresses (e.g., multisig, miners).";
404
405    type Params = (Address, ApiTipsetKey);
406    type Ok = Address;
407
408    async fn handle(
409        ctx: Ctx,
410        (addr, ApiTipsetKey(tsk)): Self::Params,
411        _: &http::Extensions,
412    ) -> Result<Self::Ok, ServerError> {
413        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
414        let store = ctx.db();
415        let state_tree = StateTree::new_from_root(ctx.db(), ts.parent_state())?;
416        if let &Payload::ID(id_addr_decoded) = addr.payload() {
417            let init_state: init::State = state_tree.get_actor_state()?;
418            let mut robust_addr = Address::default();
419            match init_state {
420                init::State::V0(_) => Err(ServerError::internal_error(
421                    "StateLookupRobustAddress is not implemented for init state v0",
422                    None,
423                )),
424                init::State::V8(state) => get_robust_address!(
425                    store,
426                    id_addr_decoded,
427                    state,
428                    fil_actors_shared::v8::make_map_with_root::<_, ActorID>,
429                    robust_addr
430                ),
431                init::State::V9(state) => get_robust_address!(
432                    store,
433                    id_addr_decoded,
434                    state,
435                    fil_actors_shared::v9::make_map_with_root::<_, ActorID>,
436                    robust_addr
437                ),
438                init::State::V10(state) => get_robust_address!(
439                    store,
440                    id_addr_decoded,
441                    state,
442                    fil_actors_shared::v10::make_map_with_root::<_, ActorID>,
443                    robust_addr
444                ),
445                init::State::V11(state) => get_robust_address!(
446                    store,
447                    id_addr_decoded,
448                    state,
449                    fil_actors_shared::v11::make_map_with_root::<_, ActorID>,
450                    robust_addr
451                ),
452                init::State::V12(state) => get_robust_address!(
453                    store,
454                    id_addr_decoded,
455                    state,
456                    fil_actors_shared::v12::make_map_with_root::<_, ActorID>,
457                    robust_addr
458                ),
459                init::State::V13(state) => get_robust_address!(
460                    store,
461                    id_addr_decoded,
462                    state,
463                    fil_actors_shared::v13::make_map_with_root::<_, ActorID>,
464                    robust_addr
465                ),
466                init::State::V14(state) => {
467                    let map = fil_actor_init_state::v14::AddressMap::load(
468                        &store,
469                        &state.address_map,
470                        fil_actors_shared::v14::DEFAULT_HAMT_CONFIG,
471                        "address_map",
472                    )
473                    .context("Failed to load address map")?;
474                    map.for_each(|addr, v| {
475                        if *v == id_addr_decoded {
476                            robust_addr = addr.into();
477                            return Ok(());
478                        }
479                        Ok(())
480                    })
481                    .context("Robust address not found")?;
482                    Ok(robust_addr)
483                }
484                init::State::V15(state) => {
485                    let map = fil_actor_init_state::v15::AddressMap::load(
486                        &store,
487                        &state.address_map,
488                        fil_actors_shared::v15::DEFAULT_HAMT_CONFIG,
489                        "address_map",
490                    )
491                    .context("Failed to load address map")?;
492                    map.for_each(|addr, v| {
493                        if *v == id_addr_decoded {
494                            robust_addr = addr.into();
495                            return Ok(());
496                        }
497                        Ok(())
498                    })
499                    .context("Robust address not found")?;
500                    Ok(robust_addr)
501                }
502                init::State::V16(state) => {
503                    let map = fil_actor_init_state::v16::AddressMap::load(
504                        &store,
505                        &state.address_map,
506                        fil_actors_shared::v16::DEFAULT_HAMT_CONFIG,
507                        "address_map",
508                    )
509                    .context("Failed to load address map")?;
510                    map.for_each(|addr, v| {
511                        if *v == id_addr_decoded {
512                            robust_addr = addr.into();
513                            return Ok(());
514                        }
515                        Ok(())
516                    })
517                    .context("Robust address not found")?;
518                    Ok(robust_addr)
519                }
520                init::State::V17(state) => {
521                    let map = fil_actor_init_state::v17::AddressMap::load(
522                        &store,
523                        &state.address_map,
524                        fil_actors_shared::v17::DEFAULT_HAMT_CONFIG,
525                        "address_map",
526                    )
527                    .context("Failed to load address map")?;
528                    map.for_each(|addr, v| {
529                        if *v == id_addr_decoded {
530                            robust_addr = addr.into();
531                            return Ok(());
532                        }
533                        Ok(())
534                    })
535                    .context("Robust address not found")?;
536                    Ok(robust_addr)
537                }
538                init::State::V18(state) => {
539                    let map = fil_actor_init_state::v18::AddressMap::load(
540                        &store,
541                        &state.address_map,
542                        fil_actors_shared::v18::DEFAULT_HAMT_CONFIG,
543                        "address_map",
544                    )
545                    .context("Failed to load address map")?;
546                    map.for_each(|addr, v| {
547                        if *v == id_addr_decoded {
548                            robust_addr = addr.into();
549                            return Ok(());
550                        }
551                        Ok(())
552                    })
553                    .context("Robust address not found")?;
554                    Ok(robust_addr)
555                }
556            }
557        } else {
558            Ok(Address::default())
559        }
560    }
561}
562
563/// looks up the Escrow and Locked balances of the given address in the Storage
564/// Market
565pub enum StateMarketBalance {}
566
567impl RpcMethod<2> for StateMarketBalance {
568    const NAME: &'static str = "Filecoin.StateMarketBalance";
569    const PARAM_NAMES: [&'static str; 2] = ["address", "tipsetKey"];
570    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
571    const PERMISSION: Permission = Permission::Read;
572    const DESCRIPTION: &'static str =
573        "Returns the Escrow and Locked balances of the specified address in the Storage Market.";
574
575    type Params = (Address, ApiTipsetKey);
576    type Ok = MarketBalance;
577
578    async fn handle(
579        ctx: Ctx,
580        (address, ApiTipsetKey(tsk)): Self::Params,
581        _: &http::Extensions,
582    ) -> Result<Self::Ok, ServerError> {
583        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
584        ctx.state_manager
585            .market_balance(&address, &ts)
586            .map_err(From::from)
587    }
588}
589
590pub enum StateMarketDeals {}
591
592impl RpcMethod<1> for StateMarketDeals {
593    const NAME: &'static str = "Filecoin.StateMarketDeals";
594    const PARAM_NAMES: [&'static str; 1] = ["tipsetKey"];
595    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
596    const PERMISSION: Permission = Permission::Read;
597    const DESCRIPTION: &'static str = "Returns information about every deal in the Storage Market.";
598
599    type Params = (ApiTipsetKey,);
600    type Ok = HashMap<String, ApiMarketDeal>;
601
602    async fn handle(
603        ctx: Ctx,
604        (ApiTipsetKey(tsk),): Self::Params,
605        _: &http::Extensions,
606    ) -> Result<Self::Ok, ServerError> {
607        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
608        let market_state: market::State = ctx.state_manager.get_actor_state(&ts)?;
609
610        let da = market_state.proposals(ctx.db())?;
611        let sa = market_state.states(ctx.db())?;
612
613        let mut out = HashMap::new();
614        da.for_each(|deal_id, d| {
615            let s = sa.get(deal_id)?.unwrap_or(market::DealState {
616                sector_start_epoch: -1,
617                last_updated_epoch: -1,
618                slash_epoch: -1,
619                verified_claim: 0,
620                sector_number: 0,
621            });
622            out.insert(
623                deal_id.to_string(),
624                MarketDeal {
625                    proposal: d?,
626                    state: s,
627                }
628                .into(),
629            );
630            Ok(())
631        })?;
632        Ok(out)
633    }
634}
635
636/// looks up the miner info of the given address.
637pub enum StateMinerInfo {}
638
639impl RpcMethod<2> for StateMinerInfo {
640    const NAME: &'static str = "Filecoin.StateMinerInfo";
641    const PARAM_NAMES: [&'static str; 2] = ["minerAddress", "tipsetKey"];
642    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
643    const PERMISSION: Permission = Permission::Read;
644    const DESCRIPTION: &'static str = "Returns information about the specified miner.";
645
646    type Params = (Address, ApiTipsetKey);
647    type Ok = MinerInfo;
648
649    async fn handle(
650        ctx: Ctx,
651        (address, ApiTipsetKey(tsk)): Self::Params,
652        _: &http::Extensions,
653    ) -> Result<Self::Ok, ServerError> {
654        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
655        Ok(ctx.state_manager.miner_info(&address, &ts)?)
656    }
657}
658
659pub enum StateMinerActiveSectors {}
660
661impl RpcMethod<2> for StateMinerActiveSectors {
662    const NAME: &'static str = "Filecoin.StateMinerActiveSectors";
663    const PARAM_NAMES: [&'static str; 2] = ["minerAddress", "tipsetKey"];
664    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
665    const PERMISSION: Permission = Permission::Read;
666    const DESCRIPTION: &'static str =
667        "Returns information about sectors actively proven by a given miner.";
668
669    type Params = (Address, ApiTipsetKey);
670    type Ok = Vec<SectorOnChainInfo>;
671
672    async fn handle(
673        ctx: Ctx,
674        (address, ApiTipsetKey(tsk)): Self::Params,
675        _: &http::Extensions,
676    ) -> Result<Self::Ok, ServerError> {
677        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
678        let policy = &ctx.chain_config().policy;
679        let miner_state: miner::State = ctx
680            .state_manager
681            .get_actor_state_from_address(&ts, &address)?;
682        // Collect active sectors from each partition in each deadline.
683        let mut active_sectors = vec![];
684        miner_state.for_each_deadline(policy, ctx.db(), |_dlidx, deadline| {
685            deadline.for_each(ctx.db(), |_partidx, partition| {
686                active_sectors.push(partition.active_sectors());
687                Ok(())
688            })
689        })?;
690        let sectors =
691            miner_state.load_sectors_ext(ctx.db(), Some(&BitField::union(&active_sectors)))?;
692        Ok(sectors)
693    }
694}
695
696/// Returns a bitfield containing all sector numbers marked as allocated in miner state
697pub enum StateMinerAllocated {}
698
699impl RpcMethod<2> for StateMinerAllocated {
700    const NAME: &'static str = "Filecoin.StateMinerAllocated";
701    const PARAM_NAMES: [&'static str; 2] = ["minerAddress", "tipsetKey"];
702    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
703    const PERMISSION: Permission = Permission::Read;
704    const DESCRIPTION: &'static str = "Returns a bitfield containing all sector numbers marked as allocated to the provided miner ID.";
705
706    type Params = (Address, ApiTipsetKey);
707    type Ok = BitField;
708
709    async fn handle(
710        ctx: Ctx,
711        (address, ApiTipsetKey(tsk)): Self::Params,
712        _: &http::Extensions,
713    ) -> Result<Self::Ok, ServerError> {
714        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
715        let miner_state: miner::State = ctx
716            .state_manager
717            .get_actor_state_from_address(&ts, &address)?;
718        Ok(miner_state.load_allocated_sector_numbers(ctx.db())?)
719    }
720}
721
722/// Return all partitions in the specified deadline
723pub enum StateMinerPartitions {}
724
725impl RpcMethod<3> for StateMinerPartitions {
726    const NAME: &'static str = "Filecoin.StateMinerPartitions";
727    const PARAM_NAMES: [&'static str; 3] = ["minerAddress", "deadlineIndex", "tipsetKey"];
728    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
729    const PERMISSION: Permission = Permission::Read;
730    const DESCRIPTION: &'static str = "Returns all partitions in the specified deadline.";
731
732    type Params = (Address, u64, ApiTipsetKey);
733    type Ok = Vec<MinerPartitions>;
734
735    async fn handle(
736        ctx: Ctx,
737        (address, dl_idx, ApiTipsetKey(tsk)): Self::Params,
738        _: &http::Extensions,
739    ) -> Result<Self::Ok, ServerError> {
740        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
741        let policy = &ctx.chain_config().policy;
742        let miner_state: miner::State = ctx
743            .state_manager
744            .get_actor_state_from_address(&ts, &address)?;
745        let deadline = miner_state.load_deadline(policy, ctx.db(), dl_idx)?;
746        let mut all_partitions = Vec::new();
747        deadline.for_each(ctx.db(), |_partidx, partition| {
748            all_partitions.push(MinerPartitions::new(
749                partition.all_sectors(),
750                partition.faulty_sectors(),
751                partition.recovering_sectors(),
752                partition.live_sectors(),
753                partition.active_sectors(),
754            ));
755            Ok(())
756        })?;
757        Ok(all_partitions)
758    }
759}
760
761pub enum StateMinerSectors {}
762
763impl RpcMethod<3> for StateMinerSectors {
764    const NAME: &'static str = "Filecoin.StateMinerSectors";
765    const PARAM_NAMES: [&'static str; 3] = ["minerAddress", "sectors", "tipsetKey"];
766    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
767    const PERMISSION: Permission = Permission::Read;
768    const DESCRIPTION: &'static str = "Returns information about the given miner's sectors. If no filter is provided, all sectors are included.";
769
770    type Params = (Address, Option<BitField>, ApiTipsetKey);
771    type Ok = Vec<SectorOnChainInfo>;
772
773    async fn handle(
774        ctx: Ctx,
775        (address, sectors, ApiTipsetKey(tsk)): Self::Params,
776        _: &http::Extensions,
777    ) -> Result<Self::Ok, ServerError> {
778        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
779        let miner_state: miner::State = ctx
780            .state_manager
781            .get_actor_state_from_address(&ts, &address)?;
782        Ok(miner_state.load_sectors_ext(ctx.db(), sectors.as_ref())?)
783    }
784}
785
786/// Returns the number of sectors in a miner's sector set and proving set
787pub enum StateMinerSectorCount {}
788
789impl RpcMethod<2> for StateMinerSectorCount {
790    const NAME: &'static str = "Filecoin.StateMinerSectorCount";
791    const PARAM_NAMES: [&'static str; 2] = ["minerAddress", "tipsetKey"];
792    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
793    const PERMISSION: Permission = Permission::Read;
794    const DESCRIPTION: &'static str =
795        "Returns the number of sectors in a miner's sector and proving sets.";
796
797    type Params = (Address, ApiTipsetKey);
798    type Ok = MinerSectors;
799
800    async fn handle(
801        ctx: Ctx,
802        (address, ApiTipsetKey(tsk)): Self::Params,
803        _: &http::Extensions,
804    ) -> Result<Self::Ok, ServerError> {
805        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
806        let policy = &ctx.chain_config().policy;
807        let miner_state: miner::State = ctx
808            .state_manager
809            .get_actor_state_from_address(&ts, &address)?;
810        // Collect live, active and faulty sectors count from each partition in each deadline.
811        let mut live_count = 0;
812        let mut active_count = 0;
813        let mut faulty_count = 0;
814        miner_state.for_each_deadline(policy, ctx.db(), |_dlidx, deadline| {
815            deadline.for_each(ctx.db(), |_partidx, partition| {
816                live_count += partition.live_sectors().len();
817                active_count += partition.active_sectors().len();
818                faulty_count += partition.faulty_sectors().len();
819                Ok(())
820            })
821        })?;
822        Ok(MinerSectors::new(live_count, active_count, faulty_count))
823    }
824}
825
826/// Checks if a sector is allocated
827pub enum StateMinerSectorAllocated {}
828
829impl RpcMethod<3> for StateMinerSectorAllocated {
830    const NAME: &'static str = "Filecoin.StateMinerSectorAllocated";
831    const PARAM_NAMES: [&'static str; 3] = ["minerAddress", "sectorNumber", "tipsetKey"];
832    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
833    const PERMISSION: Permission = Permission::Read;
834    const DESCRIPTION: &'static str = "Checks if a sector number is marked as allocated.";
835
836    type Params = (Address, SectorNumber, ApiTipsetKey);
837    type Ok = bool;
838
839    async fn handle(
840        ctx: Ctx,
841        (miner_address, sector_number, ApiTipsetKey(tsk)): Self::Params,
842        _: &http::Extensions,
843    ) -> Result<Self::Ok, ServerError> {
844        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
845        let miner_state: miner::State = ctx
846            .state_manager
847            .get_actor_state_from_address(&ts, &miner_address)?;
848        let allocated_sector_numbers: BitField =
849            miner_state.load_allocated_sector_numbers(ctx.db())?;
850        Ok(allocated_sector_numbers.get(sector_number))
851    }
852}
853
854/// looks up the miner power of the given address.
855pub enum StateMinerPower {}
856
857impl RpcMethod<2> for StateMinerPower {
858    const NAME: &'static str = "Filecoin.StateMinerPower";
859    const PARAM_NAMES: [&'static str; 2] = ["minerAddress", "tipsetKey"];
860    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
861    const PERMISSION: Permission = Permission::Read;
862    const DESCRIPTION: &'static str = "Returns the power of the specified miner.";
863
864    type Params = (Address, ApiTipsetKey);
865    type Ok = MinerPower;
866
867    async fn handle(
868        ctx: Ctx,
869        (address, ApiTipsetKey(tsk)): Self::Params,
870        _: &http::Extensions,
871    ) -> Result<Self::Ok, ServerError> {
872        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
873        ctx.state_manager
874            .miner_power(&address, &ts)
875            .map_err(From::from)
876    }
877}
878
879pub enum StateMinerDeadlines {}
880
881impl RpcMethod<2> for StateMinerDeadlines {
882    const NAME: &'static str = "Filecoin.StateMinerDeadlines";
883    const PARAM_NAMES: [&'static str; 2] = ["minerAddress", "tipsetKey"];
884    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
885    const PERMISSION: Permission = Permission::Read;
886    const DESCRIPTION: &'static str = "Returns all proving deadlines for the given miner.";
887
888    type Params = (Address, ApiTipsetKey);
889    type Ok = Vec<ApiDeadline>;
890
891    async fn handle(
892        ctx: Ctx,
893        (address, ApiTipsetKey(tsk)): Self::Params,
894        _: &http::Extensions,
895    ) -> Result<Self::Ok, ServerError> {
896        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
897        let policy = &ctx.chain_config().policy;
898        let state: miner::State = ctx
899            .state_manager
900            .get_actor_state_from_address(&ts, &address)?;
901        let mut res = Vec::new();
902        state.for_each_deadline(policy, ctx.db(), |_idx, deadline| {
903            res.push(ApiDeadline {
904                post_submissions: deadline.partitions_posted(),
905                disputable_proof_count: deadline.disputable_proof_count(ctx.db())?,
906                daily_fee: deadline.daily_fee(),
907            });
908            Ok(())
909        })?;
910        Ok(res)
911    }
912}
913
914pub enum StateMinerProvingDeadline {}
915
916impl RpcMethod<2> for StateMinerProvingDeadline {
917    const NAME: &'static str = "Filecoin.StateMinerProvingDeadline";
918    const PARAM_NAMES: [&'static str; 2] = ["minerAddress", "tipsetKey"];
919    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
920    const PERMISSION: Permission = Permission::Read;
921    const DESCRIPTION: &'static str =
922        "Calculates the deadline and related details for a given epoch during a proving period.";
923
924    type Params = (Address, ApiTipsetKey);
925    type Ok = ApiDeadlineInfo;
926
927    async fn handle(
928        ctx: Ctx,
929        (address, ApiTipsetKey(tsk)): Self::Params,
930        _: &http::Extensions,
931    ) -> Result<Self::Ok, ServerError> {
932        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
933        let policy = &ctx.chain_config().policy;
934        let state: miner::State = ctx
935            .state_manager
936            .get_actor_state_from_address(&ts, &address)?;
937        Ok(ApiDeadlineInfo(
938            state
939                .recorded_deadline_info(policy, ts.epoch())
940                .next_not_elapsed(),
941        ))
942    }
943}
944
945/// looks up the miner power of the given address.
946pub enum StateMinerFaults {}
947
948impl RpcMethod<2> for StateMinerFaults {
949    const NAME: &'static str = "Filecoin.StateMinerFaults";
950    const PARAM_NAMES: [&'static str; 2] = ["minerAddress", "tipsetKey"];
951    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
952    const PERMISSION: Permission = Permission::Read;
953    const DESCRIPTION: &'static str =
954        "Returns a bitfield of the faulty sectors for the given miner.";
955
956    type Params = (Address, ApiTipsetKey);
957    type Ok = BitField;
958
959    async fn handle(
960        ctx: Ctx,
961        (address, ApiTipsetKey(tsk)): Self::Params,
962        _: &http::Extensions,
963    ) -> Result<Self::Ok, ServerError> {
964        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
965        ctx.state_manager
966            .miner_faults(&address, &ts)
967            .map_err(From::from)
968    }
969}
970
971pub enum StateMinerRecoveries {}
972
973impl RpcMethod<2> for StateMinerRecoveries {
974    const NAME: &'static str = "Filecoin.StateMinerRecoveries";
975    const PARAM_NAMES: [&'static str; 2] = ["minerAddress", "tipsetKey"];
976    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
977    const PERMISSION: Permission = Permission::Read;
978    const DESCRIPTION: &'static str =
979        "Returns a bitfield of recovering sectors for the given miner.";
980
981    type Params = (Address, ApiTipsetKey);
982    type Ok = BitField;
983
984    async fn handle(
985        ctx: Ctx,
986        (address, ApiTipsetKey(tsk)): Self::Params,
987        _: &http::Extensions,
988    ) -> Result<Self::Ok, ServerError> {
989        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
990        ctx.state_manager
991            .miner_recoveries(&address, &ts)
992            .map_err(From::from)
993    }
994}
995
996pub enum StateMinerAvailableBalance {}
997
998impl RpcMethod<2> for StateMinerAvailableBalance {
999    const NAME: &'static str = "Filecoin.StateMinerAvailableBalance";
1000    const PARAM_NAMES: [&'static str; 2] = ["minerAddress", "tipsetKey"];
1001    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1002    const PERMISSION: Permission = Permission::Read;
1003    const DESCRIPTION: &'static str =
1004        "Returns the portion of a miner's balance available for withdrawal or spending.";
1005
1006    type Params = (Address, ApiTipsetKey);
1007    type Ok = TokenAmount;
1008
1009    async fn handle(
1010        ctx: Ctx,
1011        (address, ApiTipsetKey(tsk)): Self::Params,
1012        _: &http::Extensions,
1013    ) -> Result<Self::Ok, ServerError> {
1014        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
1015        let actor = ctx
1016            .state_manager
1017            .get_required_actor(&address, *ts.parent_state())?;
1018        let state = miner::State::load(ctx.db(), actor.code, actor.state)?;
1019        let actor_balance: TokenAmount = actor.balance.clone().into();
1020        let (vested, available): (TokenAmount, TokenAmount) = match &state {
1021            miner::State::V18(s) => (
1022                s.check_vested_funds(ctx.db(), ts.epoch())?.into(),
1023                s.get_available_balance(&actor_balance.into())?.into(),
1024            ),
1025            miner::State::V17(s) => (
1026                s.check_vested_funds(ctx.db(), ts.epoch())?.into(),
1027                s.get_available_balance(&actor_balance.into())?.into(),
1028            ),
1029            miner::State::V16(s) => (
1030                s.check_vested_funds(ctx.db(), ts.epoch())?.into(),
1031                s.get_available_balance(&actor_balance.into())?.into(),
1032            ),
1033            miner::State::V15(s) => (
1034                s.check_vested_funds(ctx.db(), ts.epoch())?.into(),
1035                s.get_available_balance(&actor_balance.into())?.into(),
1036            ),
1037            miner::State::V14(s) => (
1038                s.check_vested_funds(ctx.db(), ts.epoch())?.into(),
1039                s.get_available_balance(&actor_balance.into())?.into(),
1040            ),
1041            miner::State::V13(s) => (
1042                s.check_vested_funds(ctx.db(), ts.epoch())?.into(),
1043                s.get_available_balance(&actor_balance.into())?.into(),
1044            ),
1045            miner::State::V12(s) => (
1046                s.check_vested_funds(ctx.db(), ts.epoch())?.into(),
1047                s.get_available_balance(&actor_balance.into())?.into(),
1048            ),
1049            miner::State::V11(s) => (
1050                s.check_vested_funds(ctx.db(), ts.epoch())?.into(),
1051                s.get_available_balance(&actor_balance.into())?.into(),
1052            ),
1053            miner::State::V10(s) => (
1054                s.check_vested_funds(ctx.db(), ts.epoch())?.into(),
1055                s.get_available_balance(&actor_balance.into())?.into(),
1056            ),
1057            miner::State::V9(s) => (
1058                s.check_vested_funds(ctx.db(), ts.epoch())?.into(),
1059                s.get_available_balance(&actor_balance.into())?.into(),
1060            ),
1061            miner::State::V8(s) => (
1062                s.check_vested_funds(ctx.db(), ts.epoch())?.into(),
1063                s.get_available_balance(&actor_balance.into())?.into(),
1064            ),
1065        };
1066
1067        Ok(vested + available)
1068    }
1069}
1070
1071/// Must be positive: `qa_power_for_weight` divides by it.
1072fn sector_duration_from_expiration(
1073    expiration: ChainEpoch,
1074    epoch: ChainEpoch,
1075) -> anyhow::Result<ChainEpoch> {
1076    expiration
1077        .checked_sub(epoch)
1078        .filter(|duration| *duration > 0)
1079        .with_context(|| format!("sector expiration {expiration} must be after epoch {epoch}"))
1080}
1081
1082pub enum StateMinerInitialPledgeCollateral {}
1083
1084impl RpcMethod<3> for StateMinerInitialPledgeCollateral {
1085    const NAME: &'static str = "Filecoin.StateMinerInitialPledgeCollateral";
1086    const PARAM_NAMES: [&'static str; 3] = ["minerAddress", "sectorPreCommitInfo", "tipsetKey"];
1087    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1088    const PERMISSION: Permission = Permission::Read;
1089    const DESCRIPTION: &'static str =
1090        "Returns the initial pledge collateral for the specified miner's sector.";
1091
1092    type Params = (Address, SectorPreCommitInfo, ApiTipsetKey);
1093    type Ok = TokenAmount;
1094
1095    async fn handle(
1096        ctx: Ctx,
1097        (address, pci, ApiTipsetKey(tsk)): Self::Params,
1098        _: &http::Extensions,
1099    ) -> Result<Self::Ok, ServerError> {
1100        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
1101
1102        let sector_size = pci
1103            .seal_proof
1104            .sector_size()
1105            .map_err(|e| anyhow::anyhow!("failed to get resolve size: {e}"))?;
1106
1107        let market_state: market::State = ctx.state_manager.get_actor_state(&ts)?;
1108        let (w, vw) = market_state.verify_deals_for_activation(
1109            ctx.db(),
1110            address,
1111            pci.deal_ids,
1112            ts.epoch(),
1113            pci.expiration,
1114        )?;
1115        let duration = sector_duration_from_expiration(pci.expiration, ts.epoch())?;
1116        let sector_weight =
1117            qa_power_for_weight(SectorSize::from(sector_size).into(), duration, &w, &vw);
1118
1119        let initial_pledge = compute_initial_pledge_for_power(&ctx, &ts, &sector_weight)?;
1120
1121        let (q, _) = (initial_pledge * INITIAL_PLEDGE_NUM).div_rem(INITIAL_PLEDGE_DEN);
1122        Ok(q)
1123    }
1124}
1125
1126pub enum StateMinerPreCommitDepositForPower {}
1127
1128impl RpcMethod<3> for StateMinerPreCommitDepositForPower {
1129    const NAME: &'static str = "Filecoin.StateMinerPreCommitDepositForPower";
1130    const PARAM_NAMES: [&'static str; 3] = ["minerAddress", "sectorPreCommitInfo", "tipsetKey"];
1131    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1132    const PERMISSION: Permission = Permission::Read;
1133    const DESCRIPTION: &'static str =
1134        "Returns the sector precommit deposit for the specified miner.";
1135
1136    type Params = (Address, SectorPreCommitInfo, ApiTipsetKey);
1137    type Ok = TokenAmount;
1138
1139    async fn handle(
1140        ctx: Ctx,
1141        (address, pci, ApiTipsetKey(tsk)): Self::Params,
1142        _: &http::Extensions,
1143    ) -> Result<Self::Ok, ServerError> {
1144        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
1145
1146        let sector_size = pci
1147            .seal_proof
1148            .sector_size()
1149            .map_err(|e| anyhow::anyhow!("failed to get resolve size: {e}"))?;
1150
1151        let state_tree = ctx.state_manager.get_state_tree(ts.parent_state())?;
1152
1153        let market_state: market::State = state_tree.get_actor_state()?;
1154        let (w, vw) = market_state.verify_deals_for_activation(
1155            ctx.db(),
1156            address,
1157            pci.deal_ids,
1158            ts.epoch(),
1159            pci.expiration,
1160        )?;
1161        let sector_size = SectorSize::from(sector_size).into();
1162        let sector_weight =
1163            if ctx.state_manager.get_network_version(ts.epoch()) < NetworkVersion::V16 {
1164                let duration = sector_duration_from_expiration(pci.expiration, ts.epoch())?;
1165                qa_power_for_weight(sector_size, duration, &w, &vw)
1166            } else {
1167                qa_power_max(sector_size)
1168            };
1169
1170        let power_state: power::State = state_tree.get_actor_state()?;
1171        let power_smoothed = power_state.total_power_smoothed();
1172
1173        let reward_state: reward::State = state_tree.get_actor_state()?;
1174        let deposit: TokenAmount =
1175            reward_state.pre_commit_deposit_for_power(power_smoothed, sector_weight)?;
1176        let (value, _) = (deposit * INITIAL_PLEDGE_NUM).div_rem(INITIAL_PLEDGE_DEN);
1177        Ok(value)
1178    }
1179}
1180
1181/// returns the message receipt for the given message
1182/// This method times out in [`SEARCH_FOR_MSG_TIMEOUT`]
1183pub enum StateGetReceipt {}
1184
1185impl RpcMethod<2> for StateGetReceipt {
1186    const NAME: &'static str = "Filecoin.StateGetReceipt";
1187    const PARAM_NAMES: [&'static str; 2] = ["cid", "tipsetKey"];
1188    const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V0); // deprecated in V1
1189    const PERMISSION: Permission = Permission::Read;
1190    const DESCRIPTION: &'static str = "Returns the receipt for the message with the given CID at the specified tipset (deprecated in V1).";
1191
1192    type Params = (Cid, ApiTipsetKey);
1193    type Ok = Receipt;
1194
1195    async fn handle(
1196        ctx: Ctx,
1197        (cid, ApiTipsetKey(tsk)): Self::Params,
1198        _: &http::Extensions,
1199    ) -> Result<Self::Ok, ServerError> {
1200        let tipset = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
1201        let sm = ctx.state_manager.shallow_clone();
1202        let cancellation_token = CancellationToken::new();
1203        let _drop_guard = cancellation_token.drop_guard_ref();
1204        Ok(tokio::time::timeout(
1205            SEARCH_FOR_MSG_TIMEOUT,
1206            tokio::task::spawn_blocking({
1207                let cancellation_token = cancellation_token.clone();
1208                move || sm.get_receipt_blocking(tipset, cid, &cancellation_token)
1209            }),
1210        )
1211        .await
1212        .context("timed out")???)
1213    }
1214}
1215
1216/// looks back in the chain for a message. If not found, it blocks until the
1217/// message arrives on chain, and gets to the indicated confidence depth.
1218pub enum StateWaitMsgV0 {}
1219
1220impl RpcMethod<2> for StateWaitMsgV0 {
1221    const NAME: &'static str = "Filecoin.StateWaitMsg";
1222    const PARAM_NAMES: [&'static str; 2] = ["messageCid", "confidence"];
1223    const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V0); // Changed in V1
1224    const PERMISSION: Permission = Permission::Read;
1225    const DESCRIPTION: &'static str = "Searches the chain for the given message and, if not found, blocks until it appears on-chain and reaches the required confidence depth.";
1226
1227    type Params = (Cid, i64);
1228    type Ok = MessageLookup;
1229
1230    async fn handle(
1231        ctx: Ctx,
1232        (message_cid, confidence): Self::Params,
1233        _: &http::Extensions,
1234    ) -> Result<Self::Ok, ServerError> {
1235        let (tipset, receipt) = ctx
1236            .state_manager
1237            .wait_for_message_with_timeout(
1238                message_cid,
1239                confidence,
1240                None,
1241                None,
1242                WAIT_FOR_MSG_TIMEOUT,
1243            )
1244            .await?;
1245        let ipld = receipt.return_data().deserialize().unwrap_or(Ipld::Null);
1246        Ok(MessageLookup {
1247            receipt,
1248            tipset: tipset.key().clone(),
1249            height: tipset.epoch(),
1250            message: message_cid,
1251            return_dec: ipld,
1252        })
1253    }
1254}
1255
1256/// looks back in the chain for a message. If not found, it blocks until the
1257/// message arrives on chain, and gets to the indicated confidence depth.
1258/// This method times out in [`WAIT_FOR_MSG_TIMEOUT`]
1259pub enum StateWaitMsg {}
1260
1261impl RpcMethod<4> for StateWaitMsg {
1262    const NAME: &'static str = "Filecoin.StateWaitMsg";
1263    const PARAM_NAMES: [&'static str; 4] =
1264        ["messageCid", "confidence", "lookbackLimit", "allowReplaced"];
1265    const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V1); // Changed in V1
1266    const PERMISSION: Permission = Permission::Read;
1267    const DESCRIPTION: &'static str = "StateWaitMsg searches up to limit epochs for a message in the chain. If not found, it blocks until the message appears on-chain and reaches the required confidence depth.";
1268
1269    type Params = (Cid, i64, ChainEpoch, bool);
1270    type Ok = MessageLookup;
1271
1272    async fn handle(
1273        ctx: Ctx,
1274        (message_cid, confidence, look_back_limit, allow_replaced): Self::Params,
1275        _: &http::Extensions,
1276    ) -> Result<Self::Ok, ServerError> {
1277        let (tipset, receipt) = ctx
1278            .state_manager
1279            .wait_for_message_with_timeout(
1280                message_cid,
1281                confidence,
1282                Some(look_back_limit),
1283                Some(allow_replaced),
1284                WAIT_FOR_MSG_TIMEOUT,
1285            )
1286            .await?;
1287        let ipld = receipt.return_data().deserialize().unwrap_or(Ipld::Null);
1288        Ok(MessageLookup {
1289            receipt,
1290            tipset: tipset.key().clone(),
1291            height: tipset.epoch(),
1292            message: message_cid,
1293            return_dec: ipld,
1294        })
1295    }
1296}
1297
1298/// Searches for a message in the chain, and returns its receipt and the tipset where it was executed.
1299/// See <https://github.com/filecoin-project/lotus/blob/master/documentation/en/api-methods-v1-stable.md#StateSearchMsg>
1300/// This method times out in [`SEARCH_FOR_MSG_TIMEOUT`]
1301pub enum StateSearchMsg {}
1302
1303impl RpcMethod<4> for StateSearchMsg {
1304    const NAME: &'static str = "Filecoin.StateSearchMsg";
1305    const PARAM_NAMES: [&'static str; 4] =
1306        ["tipsetKey", "messageCid", "lookBackLimit", "allowReplaced"];
1307    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1308    const PERMISSION: Permission = Permission::Read;
1309    const DESCRIPTION: &'static str = "Returns the receipt and tipset the specified message was included in, or null if the message was not found.";
1310
1311    type Params = (ApiTipsetKey, Cid, i64, bool);
1312    type Ok = Option<MessageLookup>;
1313
1314    async fn handle(
1315        ctx: Ctx,
1316        (ApiTipsetKey(tsk), message_cid, look_back_limit, allow_replaced): Self::Params,
1317        _: &http::Extensions,
1318    ) -> Result<Self::Ok, ServerError> {
1319        let cancellation_token = CancellationToken::new();
1320        let _drop_guard = cancellation_token.drop_guard_ref();
1321        let from = tsk
1322            .map(|k| ctx.chain_index().load_required_tipset(&k))
1323            .transpose()?;
1324        let Some((tipset, receipt)) = tokio::time::timeout(
1325            SEARCH_FOR_MSG_TIMEOUT,
1326            ctx.state_manager.search_for_message(
1327                from,
1328                message_cid,
1329                Some(look_back_limit),
1330                Some(allow_replaced),
1331                &cancellation_token,
1332            ),
1333        )
1334        .await
1335        .context("timed out")??
1336        else {
1337            return Ok(None);
1338        };
1339        let ipld = receipt.return_data().deserialize().unwrap_or(Ipld::Null);
1340        Ok(Some(MessageLookup {
1341            receipt,
1342            tipset: tipset.key().clone(),
1343            height: tipset.epoch(),
1344            message: message_cid,
1345            return_dec: ipld,
1346        }))
1347    }
1348}
1349
1350/// See <https://github.com/filecoin-project/lotus/blob/master/documentation/en/api-methods-v0-deprecated.md#StateSearchMsgLimited>
1351/// This method times out in [`SEARCH_FOR_MSG_TIMEOUT`]
1352pub enum StateSearchMsgLimited {}
1353
1354impl RpcMethod<2> for StateSearchMsgLimited {
1355    const NAME: &'static str = "Filecoin.StateSearchMsgLimited";
1356    const PARAM_NAMES: [&'static str; 2] = ["messageCid", "lookBackLimit"];
1357    const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V0); // Not supported in V1
1358    const PERMISSION: Permission = Permission::Read;
1359    const DESCRIPTION: &'static str = "Looks back up to limit epochs in the chain for a message, and returns its receipt and the tipset where it was executed, or null if it was not found.";
1360    type Params = (Cid, i64);
1361    type Ok = Option<MessageLookup>;
1362
1363    async fn handle(
1364        ctx: Ctx,
1365        (message_cid, look_back_limit): Self::Params,
1366        _: &http::Extensions,
1367    ) -> Result<Self::Ok, ServerError> {
1368        let cancellation_token = CancellationToken::new();
1369        let _drop_guard = cancellation_token.drop_guard_ref();
1370        let Some((tipset, receipt)) = tokio::time::timeout(
1371            SEARCH_FOR_MSG_TIMEOUT,
1372            ctx.state_manager.search_for_message(
1373                None,
1374                message_cid,
1375                Some(look_back_limit),
1376                None,
1377                &cancellation_token,
1378            ),
1379        )
1380        .await
1381        .context("timed out")??
1382        else {
1383            return Ok(None);
1384        };
1385        let ipld = receipt.return_data().deserialize().unwrap_or(Ipld::Null);
1386        Ok(Some(MessageLookup {
1387            receipt,
1388            tipset: tipset.key().clone(),
1389            height: tipset.epoch(),
1390            message: message_cid,
1391            return_dec: ipld,
1392        }))
1393    }
1394}
1395
1396// Sample CIDs (useful for testing):
1397//   Mainnet:
1398//     1,594,681 bafy2bzaceaclaz3jvmbjg3piazaq5dcesoyv26cdpoozlkzdiwnsvdvm2qoqm OhSnap upgrade
1399//     1_960_320 bafy2bzacec43okhmihmnwmgqspyrkuivqtxv75rpymsdbulq6lgsdq2vkwkcg Skyr upgrade
1400//     2,833,266 bafy2bzacecaydufxqo5vtouuysmg3tqik6onyuezm6lyviycriohgfnzfslm2
1401//     2,933,266 bafy2bzacebyp6cmbshtzzuogzk7icf24pt6s5veyq5zkkqbn3sbbvswtptuuu
1402//   Calibnet:
1403//     242,150 bafy2bzaceb522vvt3wo7xhleo2dvb7wb7pyydmzlahc4aqd7lmvg3afreejiw
1404//     630,932 bafy2bzacedidwdsd7ds73t3z76hcjfsaisoxrangkxsqlzih67ulqgtxnypqk
1405//
1406/// Traverse an IPLD directed acyclic graph and use libp2p-bitswap to request any missing nodes.
1407/// This function has two primary uses: (1) Downloading specific state-roots when Forest deviates
1408/// from the mainline blockchain, (2) fetching historical state-trees to verify past versions of the
1409/// consensus rules.
1410pub enum StateFetchRoot {}
1411
1412impl RpcMethod<2> for StateFetchRoot {
1413    const NAME: &'static str = "Forest.StateFetchRoot";
1414    const PARAM_NAMES: [&'static str; 2] = ["rootCid", "saveToFile"];
1415    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1416    const PERMISSION: Permission = Permission::Read;
1417    const DESCRIPTION: &'static str = "Traverses the IPLD graph under the given root CID, fetching any missing nodes over the network, and optionally writes them to a CAR file.";
1418
1419    type Params = (Cid, Option<PathBuf>);
1420    type Ok = String;
1421
1422    async fn handle(
1423        ctx: Ctx,
1424        (root_cid, save_to_file): Self::Params,
1425        _: &http::Extensions,
1426    ) -> Result<Self::Ok, ServerError> {
1427        let network_send = ctx.network_send().clone();
1428        let db = ctx.db_owned();
1429
1430        let (car_tx, car_handle) = if let Some(save_to_file) = save_to_file {
1431            let (car_tx, car_rx) = flume::bounded(100);
1432            let roots = nonempty![root_cid];
1433            let file = tokio::fs::File::create(save_to_file).await?;
1434
1435            let car_handle = AbortOnDropHandle::new(tokio::spawn(async move {
1436                car_rx
1437                    .stream()
1438                    .map(Ok)
1439                    .forward(CarWriter::new_carv1(roots, file)?)
1440                    .await
1441            }));
1442
1443            (Some(car_tx), Some(car_handle))
1444        } else {
1445            (None, None)
1446        };
1447
1448        const MAX_CONCURRENT_REQUESTS: usize = 64;
1449        const REQUEST_TIMEOUT: Duration = Duration::from_secs(10);
1450
1451        let mut seen: CidHashSet = CidHashSet::default();
1452        let mut counter: usize = 0;
1453        let mut fetched: usize = 0;
1454        let mut failures: usize = 0;
1455        let mut task_set = JoinSet::new();
1456
1457        fn handle_worker(fetched: &mut usize, failures: &mut usize, ret: anyhow::Result<()>) {
1458            match ret {
1459                Ok(()) => *fetched += 1,
1460                Err(msg) => {
1461                    *failures += 1;
1462                    tracing::debug!("Request failed: {msg}");
1463                }
1464            }
1465        }
1466
1467        // When walking an Ipld graph, we're only interested in the DAG_CBOR encoded nodes.
1468        let mut get_ipld_link = |ipld: &Ipld| match ipld {
1469            &Ipld::Link(cid) if cid.codec() == DAG_CBOR && seen.insert(cid) => Some(cid),
1470            _ => None,
1471        };
1472
1473        // Do a depth-first-search of the IPLD graph (DAG). Nodes that are _not_ present in our database
1474        // are fetched in background tasks. If the number of tasks reaches MAX_CONCURRENT_REQUESTS, the
1475        // depth-first-search pauses until one of the work tasks returns. The memory usage of this
1476        // algorithm is dominated by the set of seen CIDs and the 'dfs' stack is not expected to grow to
1477        // more than 1000 elements (even when walking tens of millions of nodes).
1478        let dfs = Arc::new(Mutex::new(vec![Ipld::Link(root_cid)]));
1479        let mut to_be_fetched = vec![];
1480
1481        // Loop until: No more items in `dfs` AND no running worker tasks.
1482        loop {
1483            while let Some(ipld) = lock_pop(&dfs) {
1484                {
1485                    let mut dfs_guard = dfs.lock();
1486                    // Scan for unseen CIDs. Available IPLD nodes are pushed to the depth-first-search
1487                    // stack, unavailable nodes will be requested in worker tasks.
1488                    for new_cid in ipld.iter().filter_map(&mut get_ipld_link) {
1489                        counter += 1;
1490                        if counter.is_multiple_of(1_000) {
1491                            // set RUST_LOG=forest::rpc::state_api=debug to enable these printouts.
1492                            tracing::debug!(
1493                                "Graph walk: CIDs: {counter}, Fetched: {fetched}, Failures: {failures}, dfs: {}, Concurrent: {}",
1494                                dfs_guard.len(),
1495                                task_set.len()
1496                            );
1497                        }
1498
1499                        if let Some(next_ipld) = db.get_cbor(&new_cid)? {
1500                            dfs_guard.push(next_ipld);
1501                            if let Some(car_tx) = &car_tx {
1502                                car_tx.send(CarBlock {
1503                                    cid: new_cid,
1504                                    data: db
1505                                        .get(&new_cid)?
1506                                        .with_context(|| {
1507                                            format!("Failed to get cid {new_cid} from block store")
1508                                        })?
1509                                        .into(),
1510                                })?;
1511                            }
1512                        } else {
1513                            to_be_fetched.push(new_cid);
1514                        }
1515                    }
1516                }
1517
1518                while let Some(cid) = to_be_fetched.pop() {
1519                    if task_set.len() == MAX_CONCURRENT_REQUESTS
1520                        && let Some(ret) = task_set.join_next().await
1521                    {
1522                        handle_worker(&mut fetched, &mut failures, ret?)
1523                    }
1524                    task_set.spawn_blocking({
1525                        let network_send = network_send.clone();
1526                        let db = db.shallow_clone();
1527                        let dfs_vec = Arc::clone(&dfs);
1528                        let car_tx = car_tx.clone();
1529                        move || {
1530                            let (tx, rx) = flume::bounded(1);
1531                            network_send.send(NetworkMessage::BitswapRequest {
1532                                cid,
1533                                response_channel: tx,
1534                            })?;
1535                            // Bitswap requests do not fail. They are just ignored if no-one has
1536                            // the requested data. Here we arbitrary decide to only wait for
1537                            // REQUEST_TIMEOUT before judging that the data is unavailable.
1538                            let _ignore = rx.recv_timeout(REQUEST_TIMEOUT);
1539
1540                            let new_ipld = db
1541                                .get_cbor::<Ipld>(&cid)?
1542                                .with_context(|| format!("Request failed: {cid}"))?;
1543                            dfs_vec.lock().push(new_ipld);
1544                            if let Some(car_tx) = &car_tx {
1545                                car_tx.send(CarBlock {
1546                                    cid,
1547                                    data: db
1548                                        .get(&cid)?
1549                                        .with_context(|| {
1550                                            format!("Failed to get cid {cid} from block store")
1551                                        })?
1552                                        .into(),
1553                                })?;
1554                            }
1555
1556                            Ok(())
1557                        }
1558                    });
1559                }
1560                tokio::task::yield_now().await;
1561            }
1562            if let Some(ret) = task_set.join_next().await {
1563                handle_worker(&mut fetched, &mut failures, ret?)
1564            } else {
1565                // We are out of work items (dfs) and all worker threads have finished, this means
1566                // the entire graph has been walked and fetched.
1567                break;
1568            }
1569        }
1570
1571        drop(car_tx);
1572        if let Some(car_handle) = car_handle {
1573            car_handle.await??;
1574        }
1575
1576        Ok(format!(
1577            "IPLD graph traversed! CIDs: {counter}, fetched: {fetched}, failures: {failures}."
1578        ))
1579    }
1580}
1581
1582pub enum ForestStateCompute {}
1583
1584impl RpcMethod<3> for ForestStateCompute {
1585    const NAME: &'static str = "Forest.StateCompute";
1586    const N_REQUIRED_PARAMS: usize = 1;
1587    const PARAM_NAMES: [&'static str; 3] = ["epoch", "nEpochs", "forceRecompute"];
1588    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1589    const PERMISSION: Permission = Permission::Read;
1590    const DESCRIPTION: &'static str = "Forest-specific RPC method that recomputes tipset state over an epoch range. It reuses cached executed tipsets only when the cached state root is loadable; otherwise it recomputes. Unlike Filecoin.StateCompute, it does not apply caller-supplied messages or return execution traces.";
1591
1592    type Params = (ChainEpoch, Option<NonZeroU32>, Option<bool>);
1593    type Ok = Vec<ForestComputeStateOutput>;
1594
1595    async fn handle(
1596        ctx: Ctx,
1597        (from_epoch, n_epochs, force_recompute): Self::Params,
1598        _: &http::Extensions,
1599    ) -> Result<Self::Ok, ServerError> {
1600        const STATE_COMPUTE_DEFAULT_MAX_RANGE: NonZeroU32 = nonzero!(2000u32);
1601        static STATE_COMPUTE_MAX_RANGE: LazyLock<NonZeroU32> = LazyLock::new(|| {
1602            std::env::var("FOREST_STATE_COMPUTE_MAX_RANGE")
1603                .ok()
1604                .and_then(|s| s.parse().ok())
1605                .unwrap_or(STATE_COMPUTE_DEFAULT_MAX_RANGE)
1606        });
1607        static STATE_COMPUTE_SEMAPHORE: LazyLock<Arc<Semaphore>> = LazyLock::new(|| {
1608            Arc::new(Semaphore::new(
1609                std::thread::available_parallelism()
1610                    .map(|i| i.get())
1611                    .unwrap_or(2),
1612            ))
1613        });
1614
1615        let force_recompute = force_recompute.unwrap_or_default();
1616        let n_epochs = n_epochs.map(|n| n.get()).unwrap_or(1);
1617        if n_epochs > STATE_COMPUTE_MAX_RANGE.get() {
1618            return Err(anyhow::anyhow!(
1619                "nEpochs cannot be greater than {}, got {n_epochs}.",
1620                STATE_COMPUTE_MAX_RANGE.get()
1621            )
1622            .into());
1623        }
1624        let n_epochs = ChainEpoch::from(n_epochs);
1625        let to_epoch = from_epoch.saturating_add(n_epochs - 1);
1626        let to_ts = ctx
1627            .chain_index()
1628            .load_required_tipset_by_height(
1629                to_epoch,
1630                ctx.chain_store().heaviest_tipset(),
1631                ResolveNullTipset::TakeOlder,
1632            )
1633            .await?;
1634        let from_ts = if from_epoch >= to_ts.epoch() {
1635            // When `from_epoch` is a null epoch or `n_epochs` is 1,
1636            // `to_ts.epoch()` could be less than or equal to `from_epoch`
1637            to_ts.shallow_clone()
1638        } else {
1639            ctx.chain_index()
1640                .load_required_tipset_by_height(
1641                    from_epoch,
1642                    to_ts.shallow_clone(),
1643                    ResolveNullTipset::TakeOlder,
1644                )
1645                .await?
1646        };
1647
1648        let mut futures = FuturesOrdered::new();
1649        for ts in to_ts
1650            .chain(ctx.db())
1651            .take_while(|ts| ts.epoch() >= from_ts.epoch())
1652        {
1653            let chain_store = ctx.chain_store().shallow_clone();
1654            let network_context = ctx.sync_network_context.shallow_clone();
1655            let semaphore = STATE_COMPUTE_SEMAPHORE.clone();
1656            futures.push_front(AbortOnDropHandle::new(tokio::spawn(async move {
1657                let _permit = semaphore
1658                    .acquire()
1659                    .await
1660                    .context("Semaphore unexpectedly closed")?;
1661                if crate::chain_sync::load_full_tipset(&chain_store, ts.key()).is_err() {
1662                    // Backfill full tipset from the network
1663                    const MAX_RETRIES: usize = 5;
1664                    let fts = 'retry_loop: {
1665                        for i in 1..=MAX_RETRIES {
1666                            match network_context.chain_exchange_messages(None, &ts).await {
1667                                Ok(fts) => break 'retry_loop Ok(fts),
1668                                Err(e) if i >= MAX_RETRIES => break 'retry_loop Err(e),
1669                                Err(_) => continue,
1670                            }
1671                        }
1672                        anyhow::bail!("unreachable chain exchange error in ForestStateCompute")
1673                    }
1674                    .with_context(|| format!("failed to download messages@{}", ts.epoch()))?;
1675                    fts.persist(chain_store.db())?;
1676                }
1677                anyhow::Ok(ts)
1678            })));
1679        }
1680
1681        let mut results = Vec::with_capacity(n_epochs as _);
1682        while let Some(ts) = futures.try_next().await? {
1683            let _permit = STATE_COMPUTE_SEMAPHORE
1684                .acquire()
1685                .await
1686                .context("Semaphore unexpectedly closed")?;
1687            let ts = ts?;
1688            let epoch = ts.epoch();
1689            let tipset_key = ts.key().clone();
1690            if !force_recompute {
1691                let ExecutedTipset { state_root, .. } =
1692                    ctx.state_manager.load_executed_tipset(&ts).await?;
1693                // Verify the state tree is loadable as the root CID could present due to some bad or wrong diff snapshot import
1694                if StateTree::new_from_root(ctx.db(), &state_root).is_ok() {
1695                    results.push(ForestComputeStateOutput {
1696                        state_root,
1697                        epoch,
1698                        tipset_key,
1699                    });
1700                    continue;
1701                }
1702            }
1703
1704            let ExecutedTipset { state_root, .. } = ctx
1705                .state_manager
1706                .compute_tipset_state(ts, NO_CALLBACK, VMTrace::NotTraced)
1707                .await?;
1708            // Verify the result state tree
1709            StateTree::new_from_root(ctx.db(), &state_root).with_context(|| format!("failed to load the result state tree, root: {state_root}, epoch: {epoch}, tipset key: {tipset_key}"))?;
1710            results.push(ForestComputeStateOutput {
1711                state_root,
1712                epoch,
1713                tipset_key,
1714            });
1715        }
1716        Ok(results)
1717    }
1718}
1719
1720pub enum StateCompute {}
1721
1722impl RpcMethod<3> for StateCompute {
1723    const NAME: &'static str = "Filecoin.StateCompute";
1724    const PARAM_NAMES: [&'static str; 3] = ["height", "messages", "tipsetKey"];
1725    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1726    const PERMISSION: Permission = Permission::Read;
1727    const DESCRIPTION: &'static str = "Applies the given messages on the given tipset";
1728
1729    type Params = (ChainEpoch, Vec<Message>, ApiTipsetKey);
1730    type Ok = ComputeStateOutput;
1731
1732    async fn handle(
1733        ctx: Ctx,
1734        (height, messages, ApiTipsetKey(tsk)): Self::Params,
1735        _: &http::Extensions,
1736    ) -> Result<Self::Ok, ServerError> {
1737        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
1738        let (tx, rx) = flume::unbounded();
1739        let callback = move |ctx: MessageCallbackCtx<'_>| {
1740            tx.send(ApiInvocResult {
1741                msg_cid: ctx.message.cid(),
1742                msg: ctx.message.message().clone(),
1743                msg_rct: Some(ctx.apply_ret.msg_receipt()),
1744                error: ctx.apply_ret.failure_info().unwrap_or_default(),
1745                duration: ctx.duration.as_nanos().clamp(0, u128::from(u64::MAX)) as u64,
1746                gas_cost: MessageGasCost::new(ctx.message.message(), ctx.apply_ret)?,
1747                execution_trace: structured::parse_events(ctx.apply_ret.exec_trace())
1748                    .unwrap_or_default(),
1749            })?;
1750            Ok(())
1751        };
1752        let ExecutedTipset { state_root, .. } = ctx
1753            .state_manager
1754            .compute_state(height, messages, ts, Some(callback), VMTrace::Traced)
1755            .await?;
1756        let mut trace = vec![];
1757        while let Ok(v) = rx.try_recv() {
1758            trace.push(v);
1759        }
1760        Ok(ComputeStateOutput {
1761            root: state_root,
1762            trace,
1763        })
1764    }
1765}
1766
1767// Convenience function for locking and popping a value out of a vector. If this function is
1768// inlined, the mutex guard isn't dropped early enough.
1769fn lock_pop<T>(mutex: &Mutex<Vec<T>>) -> Option<T> {
1770    mutex.lock().pop()
1771}
1772
1773/// Get randomness from tickets
1774pub enum StateGetRandomnessFromTickets {}
1775
1776impl RpcMethod<4> for StateGetRandomnessFromTickets {
1777    const NAME: &'static str = "Filecoin.StateGetRandomnessFromTickets";
1778    const PARAM_NAMES: [&'static str; 4] = ["personalization", "randEpoch", "entropy", "tipsetKey"];
1779    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1780    const PERMISSION: Permission = Permission::Read;
1781    const DESCRIPTION: &'static str = "Samples the chain for randomness.";
1782
1783    type Params = (i64, ChainEpoch, Vec<u8>, ApiTipsetKey);
1784    type Ok = Vec<u8>;
1785
1786    async fn handle(
1787        ctx: Ctx,
1788        (personalization, rand_epoch, entropy, ApiTipsetKey(tsk)): Self::Params,
1789        _: &http::Extensions,
1790    ) -> Result<Self::Ok, ServerError> {
1791        let tipset = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
1792        let chain_rand = ctx.state_manager.chain_rand(tipset);
1793        let digest = chain_rand.get_chain_randomness(rand_epoch, false).await?;
1794        let value = crate::state_manager::chain_rand::draw_randomness_from_digest(
1795            &digest,
1796            personalization,
1797            rand_epoch,
1798            &entropy,
1799        )?;
1800        Ok(value.to_vec())
1801    }
1802}
1803
1804pub enum StateGetRandomnessDigestFromTickets {}
1805
1806impl RpcMethod<2> for StateGetRandomnessDigestFromTickets {
1807    const NAME: &'static str = "Filecoin.StateGetRandomnessDigestFromTickets";
1808    const PARAM_NAMES: [&'static str; 2] = ["randEpoch", "tipsetKey"];
1809    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1810    const PERMISSION: Permission = Permission::Read;
1811    const DESCRIPTION: &'static str = "Samples the chain for randomness.";
1812
1813    type Params = (ChainEpoch, ApiTipsetKey);
1814    type Ok = Vec<u8>;
1815
1816    async fn handle(
1817        ctx: Ctx,
1818        (rand_epoch, ApiTipsetKey(tsk)): Self::Params,
1819        _: &http::Extensions,
1820    ) -> Result<Self::Ok, ServerError> {
1821        let tipset = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
1822        let chain_rand = ctx.state_manager.chain_rand(tipset);
1823        let digest = chain_rand.get_chain_randomness(rand_epoch, false).await?;
1824        Ok(digest.to_vec())
1825    }
1826}
1827
1828/// Get randomness from beacon
1829pub enum StateGetRandomnessFromBeacon {}
1830
1831impl RpcMethod<4> for StateGetRandomnessFromBeacon {
1832    const NAME: &'static str = "Filecoin.StateGetRandomnessFromBeacon";
1833    const PARAM_NAMES: [&'static str; 4] = ["personalization", "randEpoch", "entropy", "tipsetKey"];
1834    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1835    const PERMISSION: Permission = Permission::Read;
1836    const DESCRIPTION: &'static str = "Returns the beacon entry for the specified Filecoin epoch. If unavailable, the call blocks until it becomes available.";
1837
1838    type Params = (i64, ChainEpoch, Vec<u8>, ApiTipsetKey);
1839    type Ok = Vec<u8>;
1840
1841    async fn handle(
1842        ctx: Ctx,
1843        (personalization, rand_epoch, entropy, ApiTipsetKey(tsk)): Self::Params,
1844        _: &http::Extensions,
1845    ) -> Result<Self::Ok, ServerError> {
1846        let tipset = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
1847        let chain_rand = ctx.state_manager.chain_rand(tipset);
1848        let digest = chain_rand.get_beacon_randomness_v3(rand_epoch).await?;
1849        let value = crate::state_manager::chain_rand::draw_randomness_from_digest(
1850            &digest,
1851            personalization,
1852            rand_epoch,
1853            &entropy,
1854        )?;
1855        Ok(value.to_vec())
1856    }
1857}
1858
1859pub enum StateGetRandomnessDigestFromBeacon {}
1860
1861impl RpcMethod<2> for StateGetRandomnessDigestFromBeacon {
1862    const NAME: &'static str = "Filecoin.StateGetRandomnessDigestFromBeacon";
1863    const PARAM_NAMES: [&'static str; 2] = ["randEpoch", "tipsetKey"];
1864    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1865    const PERMISSION: Permission = Permission::Read;
1866    const DESCRIPTION: &'static str = "Samples the beacon for randomness.";
1867
1868    type Params = (ChainEpoch, ApiTipsetKey);
1869    type Ok = Vec<u8>;
1870
1871    async fn handle(
1872        ctx: Ctx,
1873        (rand_epoch, ApiTipsetKey(tsk)): Self::Params,
1874        _: &http::Extensions,
1875    ) -> Result<Self::Ok, ServerError> {
1876        let tipset = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
1877        let chain_rand = ctx.state_manager.chain_rand(tipset);
1878        let digest = chain_rand.get_beacon_randomness_v3(rand_epoch).await?;
1879        Ok(digest.to_vec())
1880    }
1881}
1882
1883/// Get read state
1884pub enum StateReadState {}
1885
1886impl RpcMethod<2> for StateReadState {
1887    const NAME: &'static str = "Filecoin.StateReadState";
1888    const PARAM_NAMES: [&'static str; 2] = ["address", "tipsetKey"];
1889    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1890    const PERMISSION: Permission = Permission::Read;
1891    const DESCRIPTION: &'static str = "Returns the state of the specified actor.";
1892
1893    type Params = (Address, ApiTipsetKey);
1894    type Ok = ApiActorState;
1895
1896    async fn handle(
1897        ctx: Ctx,
1898        (address, ApiTipsetKey(tsk)): Self::Params,
1899        _: &http::Extensions,
1900    ) -> Result<Self::Ok, ServerError> {
1901        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
1902        let actor = ctx
1903            .state_manager
1904            .get_required_actor(&address, *ts.parent_state())?;
1905        let state_json = load_and_serialize_actor_state(ctx.db(), &actor.code, &actor.state)
1906            .map_err(|e| anyhow::anyhow!("Failed to load actor state: {}", e))?;
1907        Ok(ApiActorState {
1908            balance: actor.balance.clone().into(),
1909            code: actor.code,
1910            state: state_json,
1911        })
1912    }
1913}
1914
1915pub enum StateDecodeParams {}
1916impl RpcMethod<4> for StateDecodeParams {
1917    const NAME: &'static str = "Filecoin.StateDecodeParams";
1918    const PARAM_NAMES: [&'static str; 4] = ["address", "method", "params", "tipsetKey"];
1919    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1920    const PERMISSION: Permission = Permission::Read;
1921    const DESCRIPTION: &'static str = "Decode the provided method params.";
1922
1923    type Params = (Address, MethodNum, Vec<u8>, ApiTipsetKey);
1924    type Ok = serde_json::Value;
1925
1926    async fn handle(
1927        ctx: Ctx,
1928        (address, method, params, ApiTipsetKey(tsk)): Self::Params,
1929        _: &http::Extensions,
1930    ) -> Result<Self::Ok, ServerError> {
1931        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
1932        let actor = ctx
1933            .state_manager
1934            .get_required_actor(&address, *ts.parent_state())?;
1935
1936        let res = crate::rpc::registry::methods_reg::deserialize_params(
1937            &actor.code,
1938            method,
1939            params.as_slice(),
1940        )?;
1941        Ok(res.into())
1942    }
1943}
1944
1945pub enum StateCirculatingSupply {}
1946
1947impl RpcMethod<1> for StateCirculatingSupply {
1948    const NAME: &'static str = "Filecoin.StateCirculatingSupply";
1949    const PARAM_NAMES: [&'static str; 1] = ["tipsetKey"];
1950    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1951    const PERMISSION: Permission = Permission::Read;
1952    const DESCRIPTION: &'static str =
1953        "Returns the exact circulating supply of Filecoin at the given tipset.";
1954
1955    type Params = (ApiTipsetKey,);
1956    type Ok = TokenAmount;
1957
1958    async fn handle(
1959        ctx: Ctx,
1960        (ApiTipsetKey(tsk),): Self::Params,
1961        _: &http::Extensions,
1962    ) -> Result<Self::Ok, ServerError> {
1963        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
1964        let genesis_info = ctx.genesis_info().shallow_clone();
1965        let supply = genesis_info
1966            .get_state_circulating_supply_with_cache(ctx.db_owned(), ts)
1967            .await?;
1968        Ok(supply)
1969    }
1970}
1971
1972pub enum StateVerifiedClientStatus {}
1973
1974impl RpcMethod<2> for StateVerifiedClientStatus {
1975    const NAME: &'static str = "Filecoin.StateVerifiedClientStatus";
1976    const PARAM_NAMES: [&'static str; 2] = ["address", "tipsetKey"];
1977    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
1978    const PERMISSION: Permission = Permission::Read;
1979    const DESCRIPTION: &'static str = "Returns the data cap for the given address. Returns null if no entry exists in the data cap table.";
1980
1981    type Params = (Address, ApiTipsetKey);
1982    type Ok = Option<BigInt>;
1983
1984    async fn handle(
1985        ctx: Ctx,
1986        (address, ApiTipsetKey(tsk)): Self::Params,
1987        _: &http::Extensions,
1988    ) -> Result<Self::Ok, ServerError> {
1989        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
1990        let status = ctx.state_manager.verified_client_status(&address, &ts)?;
1991        Ok(status)
1992    }
1993}
1994
1995pub enum StateVMCirculatingSupplyInternal {}
1996
1997impl RpcMethod<1> for StateVMCirculatingSupplyInternal {
1998    const NAME: &'static str = "Filecoin.StateVMCirculatingSupplyInternal";
1999    const PARAM_NAMES: [&'static str; 1] = ["tipsetKey"];
2000    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2001    const PERMISSION: Permission = Permission::Read;
2002    const DESCRIPTION: &'static str =
2003        "Returns an approximation of Filecoin's circulating supply at the given tipset.";
2004
2005    type Params = (ApiTipsetKey,);
2006    type Ok = CirculatingSupply;
2007
2008    async fn handle(
2009        ctx: Ctx,
2010        (ApiTipsetKey(tsk),): Self::Params,
2011        _: &http::Extensions,
2012    ) -> Result<Self::Ok, ServerError> {
2013        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
2014        Ok(ctx
2015            .state_manager
2016            .genesis_info()
2017            .get_vm_circulating_supply_detailed(ts.epoch(), ctx.db(), ts.parent_state())?)
2018    }
2019}
2020
2021pub enum StateListMiners {}
2022
2023impl RpcMethod<1> for StateListMiners {
2024    const NAME: &'static str = "Filecoin.StateListMiners";
2025    const PARAM_NAMES: [&'static str; 1] = ["tipsetKey"];
2026    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2027    const PERMISSION: Permission = Permission::Read;
2028    const DESCRIPTION: &'static str =
2029        "Returns the addresses of every miner with claimed power in the Power Actor.";
2030
2031    type Params = (ApiTipsetKey,);
2032    type Ok = Vec<Address>;
2033
2034    async fn handle(
2035        ctx: Ctx,
2036        (ApiTipsetKey(tsk),): Self::Params,
2037        _: &http::Extensions,
2038    ) -> Result<Self::Ok, ServerError> {
2039        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
2040        let state: power::State = ctx.state_manager.get_actor_state(&ts)?;
2041        let miners = state.list_all_miners(ctx.db())?;
2042        Ok(miners)
2043    }
2044}
2045
2046pub enum StateListActors {}
2047
2048impl RpcMethod<1> for StateListActors {
2049    const NAME: &'static str = "Filecoin.StateListActors";
2050    const PARAM_NAMES: [&'static str; 1] = ["tipsetKey"];
2051    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2052    const PERMISSION: Permission = Permission::Read;
2053    const DESCRIPTION: &'static str = "Returns the addresses of every actor in the state.";
2054
2055    type Params = (ApiTipsetKey,);
2056    type Ok = Vec<Address>;
2057
2058    async fn handle(
2059        ctx: Ctx,
2060        (ApiTipsetKey(tsk),): Self::Params,
2061        _: &http::Extensions,
2062    ) -> Result<Self::Ok, ServerError> {
2063        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
2064        let state_tree = ctx.state_manager.get_state_tree(ts.parent_state())?;
2065        // `spawn_blocking` as state tree iteration is expensive
2066        let actors = tokio::task::spawn_blocking(move || {
2067            let mut actors = vec![];
2068            state_tree.for_each_cacheless(|addr, _state| {
2069                actors.push(addr);
2070                anyhow::Ok(())
2071            })?;
2072            anyhow::Ok(actors)
2073        })
2074        .await??;
2075        Ok(actors)
2076    }
2077}
2078
2079pub enum StateMarketStorageDeal {}
2080
2081impl RpcMethod<2> for StateMarketStorageDeal {
2082    const NAME: &'static str = "Filecoin.StateMarketStorageDeal";
2083    const PARAM_NAMES: [&'static str; 2] = ["dealId", "tipsetKey"];
2084    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2085    const PERMISSION: Permission = Permission::Read;
2086    const DESCRIPTION: &'static str = "Returns information about the specified deal.";
2087
2088    type Params = (DealID, ApiTipsetKey);
2089    type Ok = ApiMarketDeal;
2090
2091    async fn handle(
2092        ctx: Ctx,
2093        (deal_id, ApiTipsetKey(tsk)): Self::Params,
2094        _: &http::Extensions,
2095    ) -> Result<Self::Ok, ServerError> {
2096        let store = ctx.db();
2097        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
2098        let market_state: market::State = ctx.state_manager.get_actor_state(&ts)?;
2099        let proposals = market_state.proposals(store)?;
2100        let proposal = proposals.get(deal_id)?.ok_or_else(|| anyhow::anyhow!("deal {deal_id} not found - deal may not have completed sealing before deal proposal start epoch, or deal may have been slashed"))?;
2101
2102        let states = market_state.states(store)?;
2103        let state = states.get(deal_id)?.unwrap_or_else(DealState::empty);
2104
2105        Ok(MarketDeal { proposal, state }.into())
2106    }
2107}
2108
2109pub enum StateMarketParticipants {}
2110
2111impl RpcMethod<1> for StateMarketParticipants {
2112    const NAME: &'static str = "Filecoin.StateMarketParticipants";
2113    const PARAM_NAMES: [&'static str; 1] = ["tipsetKey"];
2114    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2115    const PERMISSION: Permission = Permission::Read;
2116    const DESCRIPTION: &'static str =
2117        "Returns the Escrow and Locked balances of all participants in the Storage Market.";
2118
2119    type Params = (ApiTipsetKey,);
2120    type Ok = HashMap<String, MarketBalance>;
2121
2122    async fn handle(
2123        ctx: Ctx,
2124        (ApiTipsetKey(tsk),): Self::Params,
2125        _: &http::Extensions,
2126    ) -> Result<Self::Ok, ServerError> {
2127        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
2128        let market_state = ctx.state_manager.market_state(&ts)?;
2129        let escrow_table = market_state.escrow_table(ctx.db())?;
2130        let locked_table = market_state.locked_table(ctx.db())?;
2131        let mut result = HashMap::new();
2132        escrow_table.for_each(|address, escrow| {
2133            let locked = locked_table.get(address)?;
2134            result.insert(
2135                address.to_string(),
2136                MarketBalance {
2137                    escrow: escrow.clone(),
2138                    locked,
2139                },
2140            );
2141            Ok(())
2142        })?;
2143        Ok(result)
2144    }
2145}
2146
2147pub enum StateDealProviderCollateralBounds {}
2148
2149impl RpcMethod<3> for StateDealProviderCollateralBounds {
2150    const NAME: &'static str = "Filecoin.StateDealProviderCollateralBounds";
2151    const PARAM_NAMES: [&'static str; 3] = ["size", "verified", "tipsetKey"];
2152    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2153    const PERMISSION: Permission = Permission::Read;
2154    const DESCRIPTION: &'static str = "Returns the minimum and maximum collateral a storage provider can issue, based on deal size and verified status.";
2155
2156    type Params = (u64, bool, ApiTipsetKey);
2157    type Ok = DealCollateralBounds;
2158
2159    async fn handle(
2160        ctx: Ctx,
2161        (size, verified, ApiTipsetKey(tsk)): Self::Params,
2162        _: &http::Extensions,
2163    ) -> Result<Self::Ok, ServerError> {
2164        let deal_provider_collateral_num = BigInt::from(110);
2165        let deal_provider_collateral_denom = BigInt::from(100);
2166
2167        // This is more eloquent than giving the whole match pattern a type.
2168        let _: bool = verified;
2169
2170        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
2171
2172        let state_tree = ctx.state_manager.get_state_tree(ts.parent_state())?;
2173
2174        let power_state: power::State = state_tree.get_actor_state()?;
2175        let reward_state: reward::State = state_tree.get_actor_state()?;
2176
2177        let supply = ctx
2178            .genesis_info()
2179            .get_vm_circulating_supply_detailed_with_state_tree(ts.epoch(), &state_tree)?
2180            .fil_circulating;
2181
2182        let power_claim = power_state.total_power();
2183
2184        let policy = &ctx.chain_config().policy;
2185
2186        let baseline_power = reward_state.this_epoch_baseline_power();
2187
2188        let (min, max) = reward_state.deal_provider_collateral_bounds(
2189            policy,
2190            size.into(),
2191            &power_claim.raw_byte_power,
2192            baseline_power,
2193            &supply,
2194        );
2195
2196        let min = min
2197            .atto()
2198            .mul(deal_provider_collateral_num)
2199            .div_euclid(&deal_provider_collateral_denom);
2200
2201        Ok(DealCollateralBounds {
2202            max,
2203            min: TokenAmount::from_atto(min),
2204        })
2205    }
2206}
2207
2208pub enum StateGetBeaconEntry {}
2209
2210impl RpcMethod<1> for StateGetBeaconEntry {
2211    const NAME: &'static str = "Filecoin.StateGetBeaconEntry";
2212    const PARAM_NAMES: [&'static str; 1] = ["epoch"];
2213    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2214    const PERMISSION: Permission = Permission::Read;
2215    const DESCRIPTION: &'static str = "Returns the beacon entries for the specified epoch.";
2216
2217    type Params = (ChainEpoch,);
2218    type Ok = BeaconEntry;
2219
2220    async fn handle(
2221        ctx: Ctx,
2222        (epoch,): Self::Params,
2223        _: &http::Extensions,
2224    ) -> Result<Self::Ok, ServerError> {
2225        let epoch = epoch.max(0); // negative epochs read genesis (Lotus parity)
2226        // Read the entry from on-chain headers (no drand round-trip, like Lotus). Falls
2227        // through to the beacon path when the tipset isn't stored locally (partial snapshot).
2228        let heaviest = ctx.chain_store().heaviest_tipset();
2229        if epoch <= heaviest.epoch() {
2230            let chain_rand = ctx.state_manager.chain_rand(heaviest);
2231            if let Ok(entry) = tokio::task::spawn_blocking(move || {
2232                chain_rand.extract_beacon_entry_for_epoch(epoch)
2233            })
2234            .await?
2235            {
2236                return Ok(entry);
2237            }
2238        }
2239
2240        // Future epoch (or missing locally): wait only until the drand round is produced.
2241        let (_, beacon) = ctx.beacon().beacon_for_epoch(epoch)?;
2242        let network_version = ctx.state_manager.get_network_version(epoch);
2243        let round = beacon.max_beacon_round_for_epoch(network_version, epoch)?;
2244        let entry = beacon.entry_when_available(round).await?;
2245        Ok(entry)
2246    }
2247}
2248
2249pub enum StateSectorPreCommitInfoV0 {}
2250
2251impl RpcMethod<3> for StateSectorPreCommitInfoV0 {
2252    const NAME: &'static str = "Filecoin.StateSectorPreCommitInfo";
2253    const PARAM_NAMES: [&'static str; 3] = ["minerAddress", "sectorNumber", "tipsetKey"];
2254    const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V0); // Changed in V1
2255    const PERMISSION: Permission = Permission::Read;
2256    const DESCRIPTION: &'static str = "Returns the on-chain pre-commit information for the given miner's sector, erroring if the sector is not precommitted.";
2257
2258    type Params = (Address, u64, ApiTipsetKey);
2259    type Ok = SectorPreCommitOnChainInfo;
2260
2261    async fn handle(
2262        ctx: Ctx,
2263        (miner_address, sector_number, ApiTipsetKey(tsk)): Self::Params,
2264        _: &http::Extensions,
2265    ) -> Result<Self::Ok, ServerError> {
2266        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
2267        let state: miner::State = ctx
2268            .state_manager
2269            .get_actor_state_from_address(&ts, &miner_address)?;
2270        Ok(state
2271            .load_precommit_on_chain_info(ctx.db(), sector_number)?
2272            .context("precommit info does not exist")?)
2273    }
2274}
2275
2276pub enum StateSectorPreCommitInfo {}
2277
2278impl RpcMethod<3> for StateSectorPreCommitInfo {
2279    const NAME: &'static str = "Filecoin.StateSectorPreCommitInfo";
2280    const PARAM_NAMES: [&'static str; 3] = ["minerAddress", "sectorNumber", "tipsetKey"];
2281    const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V1); // Changed in V1
2282    const PERMISSION: Permission = Permission::Read;
2283    const DESCRIPTION: &'static str = "Returns the PreCommit information for the specified miner's sector. Returns null if not precommitted.";
2284
2285    type Params = (Address, u64, ApiTipsetKey);
2286    type Ok = Option<SectorPreCommitOnChainInfo>;
2287
2288    async fn handle(
2289        ctx: Ctx,
2290        (miner_address, sector_number, ApiTipsetKey(tsk)): Self::Params,
2291        _: &http::Extensions,
2292    ) -> Result<Self::Ok, ServerError> {
2293        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
2294        let state: miner::State = ctx
2295            .state_manager
2296            .get_actor_state_from_address(&ts, &miner_address)?;
2297        Ok(state.load_precommit_on_chain_info(ctx.db(), sector_number)?)
2298    }
2299}
2300
2301impl StateSectorPreCommitInfo {
2302    pub fn get_sectors(
2303        store: &(impl Blockstore + ShallowClone),
2304        miner_address: &Address,
2305        tipset: &Tipset,
2306    ) -> anyhow::Result<Vec<u64>> {
2307        let mut sectors = vec![];
2308        let state_tree = StateTree::new_from_root(store, tipset.parent_state())?;
2309        let state: miner::State = state_tree.get_actor_state_from_address(miner_address)?;
2310        match &state {
2311            miner::State::V8(s) => {
2312                let precommitted = fil_actors_shared::v8::make_map_with_root::<
2313                    _,
2314                    fil_actor_miner_state::v8::SectorPreCommitOnChainInfo,
2315                >(&s.pre_committed_sectors, store)?;
2316                precommitted
2317                    .for_each(|_k, v| {
2318                        sectors.push(v.info.sector_number);
2319                        Ok(())
2320                    })
2321                    .context("failed to iterate over precommitted sectors")
2322            }
2323            miner::State::V9(s) => {
2324                let precommitted = fil_actors_shared::v9::make_map_with_root::<
2325                    _,
2326                    fil_actor_miner_state::v9::SectorPreCommitOnChainInfo,
2327                >(&s.pre_committed_sectors, store)?;
2328                precommitted
2329                    .for_each(|_k, v| {
2330                        sectors.push(v.info.sector_number);
2331                        Ok(())
2332                    })
2333                    .context("failed to iterate over precommitted sectors")
2334            }
2335            miner::State::V10(s) => {
2336                let precommitted = fil_actors_shared::v10::make_map_with_root::<
2337                    _,
2338                    fil_actor_miner_state::v10::SectorPreCommitOnChainInfo,
2339                >(&s.pre_committed_sectors, store)?;
2340                precommitted
2341                    .for_each(|_k, v| {
2342                        sectors.push(v.info.sector_number);
2343                        Ok(())
2344                    })
2345                    .context("failed to iterate over precommitted sectors")
2346            }
2347            miner::State::V11(s) => {
2348                let precommitted = fil_actors_shared::v11::make_map_with_root::<
2349                    _,
2350                    fil_actor_miner_state::v11::SectorPreCommitOnChainInfo,
2351                >(&s.pre_committed_sectors, store)?;
2352                precommitted
2353                    .for_each(|_k, v| {
2354                        sectors.push(v.info.sector_number);
2355                        Ok(())
2356                    })
2357                    .context("failed to iterate over precommitted sectors")
2358            }
2359            miner::State::V12(s) => {
2360                let precommitted = fil_actors_shared::v12::make_map_with_root::<
2361                    _,
2362                    fil_actor_miner_state::v12::SectorPreCommitOnChainInfo,
2363                >(&s.pre_committed_sectors, store)?;
2364                precommitted
2365                    .for_each(|_k, v| {
2366                        sectors.push(v.info.sector_number);
2367                        Ok(())
2368                    })
2369                    .context("failed to iterate over precommitted sectors")
2370            }
2371            miner::State::V13(s) => {
2372                let precommitted = fil_actors_shared::v13::make_map_with_root::<
2373                    _,
2374                    fil_actor_miner_state::v13::SectorPreCommitOnChainInfo,
2375                >(&s.pre_committed_sectors, store)?;
2376                precommitted
2377                    .for_each(|_k, v| {
2378                        sectors.push(v.info.sector_number);
2379                        Ok(())
2380                    })
2381                    .context("failed to iterate over precommitted sectors")
2382            }
2383            miner::State::V14(s) => {
2384                let precommitted = fil_actor_miner_state::v14::PreCommitMap::load(
2385                    store,
2386                    &s.pre_committed_sectors,
2387                    fil_actor_miner_state::v14::PRECOMMIT_CONFIG,
2388                    "precommits",
2389                )?;
2390                precommitted
2391                    .for_each(|_k, v| {
2392                        sectors.push(v.info.sector_number);
2393                        Ok(())
2394                    })
2395                    .context("failed to iterate over precommitted sectors")
2396            }
2397            miner::State::V15(s) => {
2398                let precommitted = fil_actor_miner_state::v15::PreCommitMap::load(
2399                    store,
2400                    &s.pre_committed_sectors,
2401                    fil_actor_miner_state::v15::PRECOMMIT_CONFIG,
2402                    "precommits",
2403                )?;
2404                precommitted
2405                    .for_each(|_k, v| {
2406                        sectors.push(v.info.sector_number);
2407                        Ok(())
2408                    })
2409                    .context("failed to iterate over precommitted sectors")
2410            }
2411            miner::State::V16(s) => {
2412                let precommitted = fil_actor_miner_state::v16::PreCommitMap::load(
2413                    store,
2414                    &s.pre_committed_sectors,
2415                    fil_actor_miner_state::v16::PRECOMMIT_CONFIG,
2416                    "precommits",
2417                )?;
2418                precommitted
2419                    .for_each(|_k, v| {
2420                        sectors.push(v.info.sector_number);
2421                        Ok(())
2422                    })
2423                    .context("failed to iterate over precommitted sectors")
2424            }
2425            miner::State::V17(s) => {
2426                let precommitted = fil_actor_miner_state::v17::PreCommitMap::load(
2427                    store,
2428                    &s.pre_committed_sectors,
2429                    fil_actor_miner_state::v17::PRECOMMIT_CONFIG,
2430                    "precommits",
2431                )?;
2432                precommitted
2433                    .for_each(|_k, v| {
2434                        sectors.push(v.info.sector_number);
2435                        Ok(())
2436                    })
2437                    .context("failed to iterate over precommitted sectors")
2438            }
2439            miner::State::V18(s) => {
2440                let precommitted = fil_actor_miner_state::v18::PreCommitMap::load(
2441                    store,
2442                    &s.pre_committed_sectors,
2443                    fil_actor_miner_state::v18::PRECOMMIT_CONFIG,
2444                    "precommits",
2445                )?;
2446                precommitted
2447                    .for_each(|_k, v| {
2448                        sectors.push(v.info.sector_number);
2449                        Ok(())
2450                    })
2451                    .context("failed to iterate over precommitted sectors")
2452            }
2453        }?;
2454
2455        Ok(sectors)
2456    }
2457
2458    pub fn get_sector_pre_commit_infos(
2459        store: &(impl Blockstore + ShallowClone),
2460        miner_address: &Address,
2461        tipset: &Tipset,
2462    ) -> anyhow::Result<Vec<SectorPreCommitInfo>> {
2463        let mut infos = vec![];
2464        let state_tree = StateTree::new_from_root(store, tipset.parent_state())?;
2465        let state: miner::State = state_tree.get_actor_state_from_address(miner_address)?;
2466        match &state {
2467            miner::State::V8(s) => {
2468                let precommitted = fil_actors_shared::v8::make_map_with_root::<
2469                    _,
2470                    fil_actor_miner_state::v8::SectorPreCommitOnChainInfo,
2471                >(&s.pre_committed_sectors, store)?;
2472                precommitted
2473                    .for_each(|_k, v| {
2474                        infos.push(v.info.clone().into());
2475                        Ok(())
2476                    })
2477                    .context("failed to iterate over precommitted sectors")
2478            }
2479            miner::State::V9(s) => {
2480                let precommitted = fil_actors_shared::v9::make_map_with_root::<
2481                    _,
2482                    fil_actor_miner_state::v9::SectorPreCommitOnChainInfo,
2483                >(&s.pre_committed_sectors, store)?;
2484                precommitted
2485                    .for_each(|_k, v| {
2486                        infos.push(v.info.clone().into());
2487                        Ok(())
2488                    })
2489                    .context("failed to iterate over precommitted sectors")
2490            }
2491            miner::State::V10(s) => {
2492                let precommitted = fil_actors_shared::v10::make_map_with_root::<
2493                    _,
2494                    fil_actor_miner_state::v10::SectorPreCommitOnChainInfo,
2495                >(&s.pre_committed_sectors, store)?;
2496                precommitted
2497                    .for_each(|_k, v| {
2498                        infos.push(v.info.clone().into());
2499                        Ok(())
2500                    })
2501                    .context("failed to iterate over precommitted sectors")
2502            }
2503            miner::State::V11(s) => {
2504                let precommitted = fil_actors_shared::v11::make_map_with_root::<
2505                    _,
2506                    fil_actor_miner_state::v11::SectorPreCommitOnChainInfo,
2507                >(&s.pre_committed_sectors, store)?;
2508                precommitted
2509                    .for_each(|_k, v| {
2510                        infos.push(v.info.clone().into());
2511                        Ok(())
2512                    })
2513                    .context("failed to iterate over precommitted sectors")
2514            }
2515            miner::State::V12(s) => {
2516                let precommitted = fil_actors_shared::v12::make_map_with_root::<
2517                    _,
2518                    fil_actor_miner_state::v12::SectorPreCommitOnChainInfo,
2519                >(&s.pre_committed_sectors, store)?;
2520                precommitted
2521                    .for_each(|_k, v| {
2522                        infos.push(v.info.clone().into());
2523                        Ok(())
2524                    })
2525                    .context("failed to iterate over precommitted sectors")
2526            }
2527            miner::State::V13(s) => {
2528                let precommitted = fil_actors_shared::v13::make_map_with_root::<
2529                    _,
2530                    fil_actor_miner_state::v13::SectorPreCommitOnChainInfo,
2531                >(&s.pre_committed_sectors, store)?;
2532                precommitted
2533                    .for_each(|_k, v| {
2534                        infos.push(v.info.clone().into());
2535                        Ok(())
2536                    })
2537                    .context("failed to iterate over precommitted sectors")
2538            }
2539            miner::State::V14(s) => {
2540                let precommitted = fil_actor_miner_state::v14::PreCommitMap::load(
2541                    store,
2542                    &s.pre_committed_sectors,
2543                    fil_actor_miner_state::v14::PRECOMMIT_CONFIG,
2544                    "precommits",
2545                )?;
2546                precommitted
2547                    .for_each(|_k, v| {
2548                        infos.push(v.info.clone().into());
2549                        Ok(())
2550                    })
2551                    .context("failed to iterate over precommitted sectors")
2552            }
2553            miner::State::V15(s) => {
2554                let precommitted = fil_actor_miner_state::v15::PreCommitMap::load(
2555                    store,
2556                    &s.pre_committed_sectors,
2557                    fil_actor_miner_state::v15::PRECOMMIT_CONFIG,
2558                    "precommits",
2559                )?;
2560                precommitted
2561                    .for_each(|_k, v| {
2562                        infos.push(v.info.clone().into());
2563                        Ok(())
2564                    })
2565                    .context("failed to iterate over precommitted sectors")
2566            }
2567            miner::State::V16(s) => {
2568                let precommitted = fil_actor_miner_state::v16::PreCommitMap::load(
2569                    store,
2570                    &s.pre_committed_sectors,
2571                    fil_actor_miner_state::v16::PRECOMMIT_CONFIG,
2572                    "precommits",
2573                )?;
2574                precommitted
2575                    .for_each(|_k, v| {
2576                        infos.push(v.info.clone().into());
2577                        Ok(())
2578                    })
2579                    .context("failed to iterate over precommitted sectors")
2580            }
2581            miner::State::V17(s) => {
2582                let precommitted = fil_actor_miner_state::v17::PreCommitMap::load(
2583                    store,
2584                    &s.pre_committed_sectors,
2585                    fil_actor_miner_state::v17::PRECOMMIT_CONFIG,
2586                    "precommits",
2587                )?;
2588                precommitted
2589                    .for_each(|_k, v| {
2590                        infos.push(v.info.clone().into());
2591                        Ok(())
2592                    })
2593                    .context("failed to iterate over precommitted sectors")
2594            }
2595            miner::State::V18(s) => {
2596                let precommitted = fil_actor_miner_state::v18::PreCommitMap::load(
2597                    store,
2598                    &s.pre_committed_sectors,
2599                    fil_actor_miner_state::v18::PRECOMMIT_CONFIG,
2600                    "precommits",
2601                )?;
2602                precommitted
2603                    .for_each(|_k, v| {
2604                        infos.push(v.info.clone().into());
2605                        Ok(())
2606                    })
2607                    .context("failed to iterate over precommitted sectors")
2608            }
2609        }?;
2610
2611        Ok(infos)
2612    }
2613}
2614
2615pub enum StateSectorGetInfo {}
2616
2617impl RpcMethod<3> for StateSectorGetInfo {
2618    const NAME: &'static str = "Filecoin.StateSectorGetInfo";
2619    const PARAM_NAMES: [&'static str; 3] = ["minerAddress", "sectorNumber", "tipsetKey"];
2620    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2621    const PERMISSION: Permission = Permission::Read;
2622    const DESCRIPTION: &'static str = "Returns on-chain information for the specified miner's sector. Returns null if not found. Use StateSectorExpiration for accurate expiration epochs.";
2623
2624    type Params = (Address, u64, ApiTipsetKey);
2625    type Ok = Option<SectorOnChainInfo>;
2626
2627    async fn handle(
2628        ctx: Ctx,
2629        (miner_address, sector_number, ApiTipsetKey(tsk)): Self::Params,
2630        _: &http::Extensions,
2631    ) -> Result<Self::Ok, ServerError> {
2632        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
2633        Ok(ctx
2634            .state_manager
2635            .get_sector_info(&miner_address, sector_number, &ts)?)
2636    }
2637}
2638
2639impl StateSectorGetInfo {
2640    pub fn get_sectors(
2641        store: &(impl Blockstore + ShallowClone),
2642        miner_address: &Address,
2643        tipset: &Tipset,
2644    ) -> anyhow::Result<Vec<u64>> {
2645        let state_tree = StateTree::new_from_root(store, tipset.parent_state())?;
2646        let state: miner::State = state_tree.get_actor_state_from_address(miner_address)?;
2647        Ok(state
2648            .load_sectors(store, None)?
2649            .into_iter()
2650            .map(|s| s.sector_number)
2651            .collect())
2652    }
2653}
2654
2655pub enum StateSectorExpiration {}
2656
2657impl RpcMethod<3> for StateSectorExpiration {
2658    const NAME: &'static str = "Filecoin.StateSectorExpiration";
2659    const PARAM_NAMES: [&'static str; 3] = ["minerAddress", "sectorNumber", "tipsetKey"];
2660    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2661    const PERMISSION: Permission = Permission::Read;
2662    const DESCRIPTION: &'static str =
2663        "Returns the epoch at which the specified sector will expire.";
2664
2665    type Params = (Address, u64, ApiTipsetKey);
2666    type Ok = SectorExpiration;
2667
2668    async fn handle(
2669        ctx: Ctx,
2670        (miner_address, sector_number, ApiTipsetKey(tsk)): Self::Params,
2671        _: &http::Extensions,
2672    ) -> Result<Self::Ok, ServerError> {
2673        let store = ctx.db();
2674        let policy = &ctx.chain_config().policy;
2675        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
2676        let state: miner::State = ctx
2677            .state_manager
2678            .get_actor_state_from_address(&ts, &miner_address)?;
2679        let (deadline_index, partition_index) = state.find_sector(store, sector_number, policy)?;
2680        let deadline = state.load_deadline(policy, store, deadline_index)?;
2681        let partition = deadline.load_partition(store, partition_index)?;
2682        let mut early = 0;
2683        let mut on_time = 0;
2684        if !partition.terminated().get(sector_number) {
2685            let expirations: Amt<fil_actor_miner_state::v16::ExpirationSet, _> =
2686                Amt::load(&partition.expirations_epochs(), store)?;
2687            expirations.for_each(|epoch, expiration| {
2688                if expiration.early_sectors.get(sector_number) {
2689                    early = epoch as _;
2690                }
2691                if expiration.on_time_sectors.get(sector_number) {
2692                    on_time = epoch as _;
2693                }
2694                Ok(())
2695            })?;
2696        }
2697        if early == 0 && on_time == 0 {
2698            Err(anyhow::anyhow!("failed to find sector {sector_number}").into())
2699        } else {
2700            Ok(SectorExpiration { early, on_time })
2701        }
2702    }
2703}
2704
2705pub enum StateSectorPartition {}
2706
2707impl RpcMethod<3> for StateSectorPartition {
2708    const NAME: &'static str = "Filecoin.StateSectorPartition";
2709    const PARAM_NAMES: [&'static str; 3] = ["minerAddress", "sectorNumber", "tipsetKey"];
2710    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2711    const PERMISSION: Permission = Permission::Read;
2712    const DESCRIPTION: &'static str = "Finds the deadline/partition for the specified sector.";
2713
2714    type Params = (Address, u64, ApiTipsetKey);
2715    type Ok = SectorLocation;
2716
2717    async fn handle(
2718        ctx: Ctx,
2719        (miner_address, sector_number, ApiTipsetKey(tsk)): Self::Params,
2720        _: &http::Extensions,
2721    ) -> Result<Self::Ok, ServerError> {
2722        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
2723        let state: miner::State = ctx
2724            .state_manager
2725            .get_actor_state_from_address(&ts, &miner_address)?;
2726        let (deadline, partition) =
2727            state.find_sector(ctx.db(), sector_number, &ctx.chain_config().policy)?;
2728        Ok(SectorLocation {
2729            deadline,
2730            partition,
2731        })
2732    }
2733}
2734
2735/// Looks back and returns all messages with a matching to or from address, stopping at the given height.
2736pub enum StateListMessages {}
2737
2738impl RpcMethod<3> for StateListMessages {
2739    const NAME: &'static str = "Filecoin.StateListMessages";
2740    const PARAM_NAMES: [&'static str; 3] = ["messageFilter", "tipsetKey", "maxHeight"];
2741    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2742    const PERMISSION: Permission = Permission::Read;
2743    const DESCRIPTION: &'static str =
2744        "Returns all messages with a matching to or from address up to the given height.";
2745
2746    type Params = (MessageFilter, ApiTipsetKey, i64);
2747    type Ok = Vec<Cid>;
2748
2749    async fn handle(
2750        ctx: Ctx,
2751        (from_to, ApiTipsetKey(tsk), max_height): Self::Params,
2752        _: &http::Extensions,
2753    ) -> Result<Self::Ok, ServerError> {
2754        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
2755        if from_to.is_empty() {
2756            return Err(ErrorObject::owned(
2757                1,
2758                "must specify at least To or From in message filter",
2759                Some(from_to),
2760            )
2761            .into());
2762        } else if let Some(to) = from_to.to {
2763            // this is following lotus logic, it probably should be `if let` instead of `else if let`
2764            // see <https://github.com/ChainSafe/forest/pull/3827#discussion_r1462691005>
2765            if ctx.state_manager.lookup_id(&to, &ts)?.is_none() {
2766                return Ok(vec![]);
2767            }
2768        } else if let Some(from) = from_to.from
2769            && ctx.state_manager.lookup_id(&from, &ts)?.is_none()
2770        {
2771            return Ok(vec![]);
2772        }
2773
2774        let mut out = Vec::new();
2775        let mut cur_ts = ts.shallow_clone();
2776
2777        while cur_ts.epoch() >= max_height {
2778            let msgs = ctx.chain_store().messages_for_tipset(&cur_ts)?;
2779
2780            for msg in msgs.iter() {
2781                if from_to.matches(msg.message()) {
2782                    out.push(msg.cid());
2783                }
2784            }
2785
2786            if cur_ts.epoch() == 0 {
2787                break;
2788            }
2789
2790            let next = ctx.chain_index().load_required_tipset(cur_ts.parents())?;
2791            cur_ts = next;
2792        }
2793
2794        Ok(out)
2795    }
2796}
2797
2798pub enum StateGetClaim {}
2799
2800impl RpcMethod<3> for StateGetClaim {
2801    const NAME: &'static str = "Filecoin.StateGetClaim";
2802    const PARAM_NAMES: [&'static str; 3] = ["address", "claimId", "tipsetKey"];
2803    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2804    const PERMISSION: Permission = Permission::Read;
2805    const DESCRIPTION: &'static str = "Returns the claim for a given address and claim ID.";
2806
2807    type Params = (Address, ClaimID, ApiTipsetKey);
2808    type Ok = Option<Claim>;
2809
2810    async fn handle(
2811        ctx: Ctx,
2812        (address, claim_id, ApiTipsetKey(tsk)): Self::Params,
2813        _: &http::Extensions,
2814    ) -> Result<Self::Ok, ServerError> {
2815        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
2816        Ok(ctx.state_manager.get_claim(&address, &ts, claim_id)?)
2817    }
2818}
2819
2820pub enum StateGetClaims {}
2821
2822impl RpcMethod<2> for StateGetClaims {
2823    const NAME: &'static str = "Filecoin.StateGetClaims";
2824    const PARAM_NAMES: [&'static str; 2] = ["address", "tipsetKey"];
2825    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2826    const PERMISSION: Permission = Permission::Read;
2827    const DESCRIPTION: &'static str = "Returns all claims for a given provider.";
2828
2829    type Params = (Address, ApiTipsetKey);
2830    type Ok = HashMap<ClaimID, Claim>;
2831
2832    async fn handle(
2833        ctx: Ctx,
2834        (address, ApiTipsetKey(tsk)): Self::Params,
2835        _: &http::Extensions,
2836    ) -> Result<Self::Ok, ServerError> {
2837        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
2838        Ok(Self::get_claims(ctx.db(), &address, &ts)?)
2839    }
2840}
2841
2842impl StateGetClaims {
2843    pub fn get_claims(
2844        store: &(impl Blockstore + ShallowClone),
2845        address: &Address,
2846        tipset: &Tipset,
2847    ) -> anyhow::Result<HashMap<ClaimID, Claim>> {
2848        let state_tree = StateTree::new_from_tipset(store, tipset)?;
2849        let state: verifreg::State = state_tree.get_actor_state()?;
2850        let actor_id = state_tree.lookup_required_id(address)?;
2851        let actor_id_address = Address::new_id(actor_id);
2852        state.get_claims(store, &actor_id_address)
2853    }
2854}
2855
2856pub enum StateGetAllClaims {}
2857
2858impl RpcMethod<1> for StateGetAllClaims {
2859    const NAME: &'static str = "Filecoin.StateGetAllClaims";
2860    const PARAM_NAMES: [&'static str; 1] = ["tipsetKey"];
2861    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2862    const PERMISSION: Permission = Permission::Read;
2863    const DESCRIPTION: &'static str =
2864        "Returns all claims available in the verified registry actor.";
2865
2866    type Params = (ApiTipsetKey,);
2867    type Ok = HashMap<ClaimID, Claim>;
2868
2869    async fn handle(
2870        ctx: Ctx,
2871        (ApiTipsetKey(tsk),): Self::Params,
2872        _: &http::Extensions,
2873    ) -> Result<Self::Ok, ServerError> {
2874        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
2875        Ok(ctx.state_manager.get_all_claims(&ts)?)
2876    }
2877}
2878
2879pub enum StateGetAllocation {}
2880
2881impl RpcMethod<3> for StateGetAllocation {
2882    const NAME: &'static str = "Filecoin.StateGetAllocation";
2883    const PARAM_NAMES: [&'static str; 3] = ["address", "allocationId", "tipsetKey"];
2884    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2885    const PERMISSION: Permission = Permission::Read;
2886    const DESCRIPTION: &'static str =
2887        "Returns the allocation for a given address and allocation ID.";
2888
2889    type Params = (Address, AllocationID, ApiTipsetKey);
2890    type Ok = Option<Allocation>;
2891
2892    async fn handle(
2893        ctx: Ctx,
2894        (address, allocation_id, ApiTipsetKey(tsk)): Self::Params,
2895        _: &http::Extensions,
2896    ) -> Result<Self::Ok, ServerError> {
2897        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
2898        Ok(ctx
2899            .state_manager
2900            .get_allocation(&address, &ts, allocation_id)?)
2901    }
2902}
2903
2904pub enum StateGetAllocations {}
2905
2906impl RpcMethod<2> for StateGetAllocations {
2907    const NAME: &'static str = "Filecoin.StateGetAllocations";
2908    const PARAM_NAMES: [&'static str; 2] = ["address", "tipsetKey"];
2909    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
2910    const PERMISSION: Permission = Permission::Read;
2911    const DESCRIPTION: &'static str = "Returns all allocations for a given client.";
2912
2913    type Params = (Address, ApiTipsetKey);
2914    type Ok = HashMap<AllocationID, Allocation>;
2915
2916    async fn handle(
2917        ctx: Ctx,
2918        (address, ApiTipsetKey(tsk)): Self::Params,
2919        _: &http::Extensions,
2920    ) -> Result<Self::Ok, ServerError> {
2921        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
2922        Ok(Self::get_allocations(ctx.db(), &address, &ts)?)
2923    }
2924}
2925
2926impl StateGetAllocations {
2927    // For testing
2928    pub fn get_valid_actor_addresses<'a>(
2929        store: &'a (impl Blockstore + ShallowClone),
2930        tipset: &'a Tipset,
2931    ) -> anyhow::Result<impl Iterator<Item = Address> + 'a> {
2932        let mut addresses = HashSet::default();
2933        let state_tree = StateTree::new_from_tipset(store, tipset)?;
2934        let verifreg_state: verifreg::State = state_tree.get_actor_state()?;
2935        match verifreg_state {
2936            verifreg::State::V13(s) => {
2937                let map = s.load_allocs(store)?;
2938                map.for_each(|k, _| {
2939                    let actor_id = fil_actors_shared::v13::parse_uint_key(k)?;
2940                    addresses.insert(Address::new_id(actor_id));
2941                    Ok(())
2942                })?;
2943            }
2944            verifreg::State::V12(s) => {
2945                let map = s.load_allocs(store)?;
2946                map.for_each(|k, _| {
2947                    let actor_id = fil_actors_shared::v12::parse_uint_key(k)?;
2948                    addresses.insert(Address::new_id(actor_id));
2949                    Ok(())
2950                })?;
2951            }
2952            _ => (),
2953        };
2954
2955        if addresses.is_empty() {
2956            let init_state: init::State = state_tree.get_actor_state()?;
2957            match init_state {
2958                init::State::V0(_) => {
2959                    anyhow::bail!("StateGetAllocations is not implemented for init state v0");
2960                }
2961                init::State::V8(s) => {
2962                    let map =
2963                        fil_actors_shared::v8::make_map_with_root::<_, u64>(&s.address_map, store)?;
2964                    map.for_each(|_k, v| {
2965                        addresses.insert(Address::new_id(*v));
2966                        Ok(())
2967                    })?;
2968                }
2969                init::State::V9(s) => {
2970                    let map =
2971                        fil_actors_shared::v9::make_map_with_root::<_, u64>(&s.address_map, store)?;
2972                    map.for_each(|_k, v| {
2973                        addresses.insert(Address::new_id(*v));
2974                        Ok(())
2975                    })?;
2976                }
2977                init::State::V10(s) => {
2978                    let map = fil_actors_shared::v10::make_map_with_root::<_, u64>(
2979                        &s.address_map,
2980                        store,
2981                    )?;
2982                    map.for_each(|_k, v| {
2983                        addresses.insert(Address::new_id(*v));
2984                        Ok(())
2985                    })?;
2986                }
2987                init::State::V11(s) => {
2988                    let map = fil_actors_shared::v11::make_map_with_root::<_, u64>(
2989                        &s.address_map,
2990                        store,
2991                    )?;
2992                    map.for_each(|_k, v| {
2993                        addresses.insert(Address::new_id(*v));
2994                        Ok(())
2995                    })?;
2996                }
2997                init::State::V12(s) => {
2998                    let map = fil_actors_shared::v12::make_map_with_root::<_, u64>(
2999                        &s.address_map,
3000                        store,
3001                    )?;
3002                    map.for_each(|_k, v| {
3003                        addresses.insert(Address::new_id(*v));
3004                        Ok(())
3005                    })?;
3006                }
3007                init::State::V13(s) => {
3008                    let map = fil_actors_shared::v13::make_map_with_root::<_, u64>(
3009                        &s.address_map,
3010                        store,
3011                    )?;
3012                    map.for_each(|_k, v| {
3013                        addresses.insert(Address::new_id(*v));
3014                        Ok(())
3015                    })?;
3016                }
3017                init::State::V14(s) => {
3018                    let map = fil_actor_init_state::v14::AddressMap::load(
3019                        store,
3020                        &s.address_map,
3021                        fil_actors_shared::v14::DEFAULT_HAMT_CONFIG,
3022                        "address_map",
3023                    )?;
3024                    map.for_each(|_k, v| {
3025                        addresses.insert(Address::new_id(*v));
3026                        Ok(())
3027                    })?;
3028                }
3029                init::State::V15(s) => {
3030                    let map = fil_actor_init_state::v15::AddressMap::load(
3031                        store,
3032                        &s.address_map,
3033                        fil_actors_shared::v15::DEFAULT_HAMT_CONFIG,
3034                        "address_map",
3035                    )?;
3036                    map.for_each(|_k, v| {
3037                        addresses.insert(Address::new_id(*v));
3038                        Ok(())
3039                    })?;
3040                }
3041                init::State::V16(s) => {
3042                    let map = fil_actor_init_state::v16::AddressMap::load(
3043                        store,
3044                        &s.address_map,
3045                        fil_actors_shared::v16::DEFAULT_HAMT_CONFIG,
3046                        "address_map",
3047                    )?;
3048                    map.for_each(|_k, v| {
3049                        addresses.insert(Address::new_id(*v));
3050                        Ok(())
3051                    })?;
3052                }
3053                init::State::V17(s) => {
3054                    let map = fil_actor_init_state::v17::AddressMap::load(
3055                        store,
3056                        &s.address_map,
3057                        fil_actors_shared::v17::DEFAULT_HAMT_CONFIG,
3058                        "address_map",
3059                    )?;
3060                    map.for_each(|_k, v| {
3061                        addresses.insert(Address::new_id(*v));
3062                        Ok(())
3063                    })?;
3064                }
3065                init::State::V18(s) => {
3066                    let map = fil_actor_init_state::v18::AddressMap::load(
3067                        store,
3068                        &s.address_map,
3069                        fil_actors_shared::v18::DEFAULT_HAMT_CONFIG,
3070                        "address_map",
3071                    )?;
3072                    map.for_each(|_k, v| {
3073                        addresses.insert(Address::new_id(*v));
3074                        Ok(())
3075                    })?;
3076                }
3077            };
3078        }
3079
3080        Ok(addresses
3081            .into_iter()
3082            .filter(|addr| match Self::get_allocations(store, addr, tipset) {
3083                Ok(r) => !r.is_empty(),
3084                _ => false,
3085            }))
3086    }
3087
3088    pub fn get_allocations(
3089        store: &(impl Blockstore + ShallowClone),
3090        address: &Address,
3091        tipset: &Tipset,
3092    ) -> anyhow::Result<HashMap<AllocationID, Allocation>> {
3093        let state_tree = StateTree::new_from_tipset(store, tipset)?;
3094        let state: verifreg::State = state_tree.get_actor_state()?;
3095        state.get_allocations(store, address)
3096    }
3097}
3098
3099pub enum StateGetAllAllocations {}
3100
3101impl RpcMethod<1> for crate::rpc::prelude::StateGetAllAllocations {
3102    const NAME: &'static str = "Filecoin.StateGetAllAllocations";
3103    const PARAM_NAMES: [&'static str; 1] = ["tipsetKey"];
3104    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
3105    const PERMISSION: Permission = Permission::Read;
3106    const DESCRIPTION: &'static str =
3107        "Returns all allocations available in the verified registry actor.";
3108
3109    type Params = (ApiTipsetKey,);
3110    type Ok = HashMap<AllocationID, Allocation>;
3111
3112    async fn handle(
3113        ctx: Ctx,
3114        (ApiTipsetKey(tsk),): Self::Params,
3115        _: &http::Extensions,
3116    ) -> Result<Self::Ok, ServerError> {
3117        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
3118        Ok(ctx.state_manager.get_all_allocations(&ts)?)
3119    }
3120}
3121
3122pub enum StateGetAllocationIdForPendingDeal {}
3123
3124impl RpcMethod<2> for StateGetAllocationIdForPendingDeal {
3125    const NAME: &'static str = "Filecoin.StateGetAllocationIdForPendingDeal";
3126    const PARAM_NAMES: [&'static str; 2] = ["dealId", "tipsetKey"];
3127    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
3128    const PERMISSION: Permission = Permission::Read;
3129    const DESCRIPTION: &'static str = "Returns the allocation ID for the specified pending deal.";
3130
3131    type Params = (DealID, ApiTipsetKey);
3132    type Ok = AllocationID;
3133
3134    async fn handle(
3135        ctx: Ctx,
3136        (deal_id, ApiTipsetKey(tsk)): Self::Params,
3137        _: &http::Extensions,
3138    ) -> Result<Self::Ok, ServerError> {
3139        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
3140        let state_tree = StateTree::new_from_tipset(ctx.db(), &ts)?;
3141        let market_state: market::State = state_tree.get_actor_state()?;
3142        Ok(market_state.get_allocation_id_for_pending_deal(ctx.db(), &deal_id)?)
3143    }
3144}
3145
3146impl StateGetAllocationIdForPendingDeal {
3147    pub fn get_allocations_for_pending_deals(
3148        store: &(impl Blockstore + ShallowClone),
3149        tipset: &Tipset,
3150    ) -> anyhow::Result<HashMap<DealID, AllocationID>> {
3151        let state_tree = StateTree::new_from_tipset(store, tipset)?;
3152        let state: market::State = state_tree.get_actor_state()?;
3153        state.get_allocations_for_pending_deals(store)
3154    }
3155}
3156
3157pub enum StateGetAllocationForPendingDeal {}
3158
3159impl RpcMethod<2> for StateGetAllocationForPendingDeal {
3160    const NAME: &'static str = "Filecoin.StateGetAllocationForPendingDeal";
3161    const PARAM_NAMES: [&'static str; 2] = ["dealId", "tipsetKey"];
3162    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
3163    const PERMISSION: Permission = Permission::Read;
3164    const DESCRIPTION: &'static str = "Returns the allocation for the specified pending deal. Returns null if no pending allocation is found.";
3165
3166    type Params = (DealID, ApiTipsetKey);
3167    type Ok = Option<Allocation>;
3168
3169    async fn handle(
3170        ctx: Ctx,
3171        (deal_id, tsk): Self::Params,
3172        ext: &http::Extensions,
3173    ) -> Result<Self::Ok, ServerError> {
3174        let allocation_id =
3175            StateGetAllocationIdForPendingDeal::handle(ctx.clone(), (deal_id, tsk.clone()), ext)
3176                .await?;
3177        if allocation_id == fil_actor_market_state::v14::NO_ALLOCATION_ID {
3178            return Ok(None);
3179        }
3180        let deal = StateMarketStorageDeal::handle(ctx.clone(), (deal_id, tsk.clone()), ext).await?;
3181        StateGetAllocation::handle(ctx.clone(), (deal.proposal.client, allocation_id, tsk), ext)
3182            .await
3183    }
3184}
3185
3186pub enum StateGetNetworkParams {}
3187
3188impl RpcMethod<0> for StateGetNetworkParams {
3189    const NAME: &'static str = "Filecoin.StateGetNetworkParams";
3190    const PARAM_NAMES: [&'static str; 0] = [];
3191    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
3192    const PERMISSION: Permission = Permission::Read;
3193    const DESCRIPTION: &'static str = "Returns current network parameters.";
3194
3195    type Params = ();
3196    type Ok = NetworkParams;
3197
3198    async fn handle(
3199        ctx: Ctx,
3200        (): Self::Params,
3201        _: &http::Extensions,
3202    ) -> Result<Self::Ok, ServerError> {
3203        let config = ctx.chain_config().as_ref();
3204        let heaviest_tipset = ctx.chain_store().heaviest_tipset();
3205        let network_name = ctx
3206            .state_manager
3207            .get_network_state_name(*heaviest_tipset.parent_state())?
3208            .into();
3209        let policy = &config.policy;
3210
3211        let params = NetworkParams {
3212            network_name,
3213            block_delay_secs: u64::from(config.block_delay_secs),
3214            consensus_miner_min_power: policy.minimum_consensus_power.clone(),
3215            pre_commit_challenge_delay: policy.pre_commit_challenge_delay,
3216            fork_upgrade_params: ForkUpgradeParams::try_from(config)
3217                .context("Failed to get fork upgrade params")?,
3218            eip155_chain_id: config.eth_chain_id,
3219            genesis_timestamp: ctx.chain_store().genesis_block_header().timestamp,
3220        };
3221
3222        Ok(params)
3223    }
3224}
3225
3226#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)]
3227#[serde(rename_all = "PascalCase")]
3228pub struct NetworkParams {
3229    network_name: String,
3230    block_delay_secs: u64,
3231    #[schemars(with = "crate::lotus_json::LotusJson<BigInt>")]
3232    #[serde(with = "crate::lotus_json")]
3233    consensus_miner_min_power: BigInt,
3234    pre_commit_challenge_delay: ChainEpoch,
3235    fork_upgrade_params: ForkUpgradeParams,
3236    #[serde(rename = "Eip155ChainID")]
3237    eip155_chain_id: EthChainId,
3238    // See <https://github.com/filecoin-project/lotus/blob/a0ecb8687f1c60d5e66040b6de364dbc9cc4d253/api/types.go#L163>
3239    genesis_timestamp: u64,
3240}
3241
3242lotus_json_with_self!(NetworkParams);
3243
3244#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)]
3245#[serde(rename_all = "PascalCase")]
3246pub struct ForkUpgradeParams {
3247    upgrade_smoke_height: ChainEpoch,
3248    upgrade_breeze_height: ChainEpoch,
3249    upgrade_ignition_height: ChainEpoch,
3250    upgrade_liftoff_height: ChainEpoch,
3251    upgrade_assembly_height: ChainEpoch,
3252    upgrade_refuel_height: ChainEpoch,
3253    upgrade_tape_height: ChainEpoch,
3254    upgrade_kumquat_height: ChainEpoch,
3255    breeze_gas_tamping_duration: ChainEpoch,
3256    upgrade_calico_height: ChainEpoch,
3257    upgrade_persian_height: ChainEpoch,
3258    upgrade_orange_height: ChainEpoch,
3259    upgrade_claus_height: ChainEpoch,
3260    upgrade_trust_height: ChainEpoch,
3261    upgrade_norwegian_height: ChainEpoch,
3262    upgrade_turbo_height: ChainEpoch,
3263    upgrade_hyperdrive_height: ChainEpoch,
3264    upgrade_chocolate_height: ChainEpoch,
3265    upgrade_oh_snap_height: ChainEpoch,
3266    upgrade_skyr_height: ChainEpoch,
3267    upgrade_shark_height: ChainEpoch,
3268    upgrade_hygge_height: ChainEpoch,
3269    upgrade_lightning_height: ChainEpoch,
3270    upgrade_thunder_height: ChainEpoch,
3271    upgrade_watermelon_height: ChainEpoch,
3272    upgrade_dragon_height: ChainEpoch,
3273    upgrade_phoenix_height: ChainEpoch,
3274    upgrade_waffle_height: ChainEpoch,
3275    upgrade_tuktuk_height: ChainEpoch,
3276    upgrade_teep_height: ChainEpoch,
3277    upgrade_tock_height: ChainEpoch,
3278    upgrade_golden_week_height: ChainEpoch,
3279    upgrade_fire_horse_height: ChainEpoch,
3280    // placeholder for the next network upgrade
3281    upgrade_xx_height: ChainEpoch,
3282}
3283
3284impl TryFrom<&ChainConfig> for ForkUpgradeParams {
3285    type Error = anyhow::Error;
3286    fn try_from(config: &ChainConfig) -> anyhow::Result<Self> {
3287        let height_infos = &config.height_infos;
3288        let get_height = |height| -> anyhow::Result<ChainEpoch> {
3289            let height = height_infos
3290                .get(&height)
3291                .context(format!("Height info for {height} not found"))?
3292                .epoch;
3293            Ok(height)
3294        };
3295
3296        use crate::networks::Height::*;
3297        Ok(ForkUpgradeParams {
3298            upgrade_smoke_height: get_height(Smoke)?,
3299            upgrade_breeze_height: get_height(Breeze)?,
3300            upgrade_ignition_height: get_height(Ignition)?,
3301            upgrade_liftoff_height: get_height(Liftoff)?,
3302            upgrade_assembly_height: get_height(Assembly)?,
3303            upgrade_refuel_height: get_height(Refuel)?,
3304            upgrade_tape_height: get_height(Tape)?,
3305            upgrade_kumquat_height: get_height(Kumquat)?,
3306            breeze_gas_tamping_duration: config.breeze_gas_tamping_duration,
3307            upgrade_calico_height: get_height(Calico)?,
3308            upgrade_persian_height: get_height(Persian)?,
3309            upgrade_orange_height: get_height(Orange)?,
3310            upgrade_claus_height: get_height(Claus)?,
3311            upgrade_trust_height: get_height(Trust)?,
3312            upgrade_norwegian_height: get_height(Norwegian)?,
3313            upgrade_turbo_height: get_height(Turbo)?,
3314            upgrade_hyperdrive_height: get_height(Hyperdrive)?,
3315            upgrade_chocolate_height: get_height(Chocolate)?,
3316            upgrade_oh_snap_height: get_height(OhSnap)?,
3317            upgrade_skyr_height: get_height(Skyr)?,
3318            upgrade_shark_height: get_height(Shark)?,
3319            upgrade_hygge_height: get_height(Hygge)?,
3320            upgrade_lightning_height: get_height(Lightning)?,
3321            upgrade_thunder_height: get_height(Thunder)?,
3322            upgrade_watermelon_height: get_height(Watermelon)?,
3323            upgrade_dragon_height: get_height(Dragon)?,
3324            upgrade_phoenix_height: get_height(Phoenix)?,
3325            upgrade_waffle_height: get_height(Waffle)?,
3326            upgrade_tuktuk_height: get_height(TukTuk)?,
3327            upgrade_teep_height: get_height(Teep)?,
3328            upgrade_tock_height: get_height(Tock)?,
3329            upgrade_golden_week_height: get_height(GoldenWeek)?,
3330            upgrade_fire_horse_height: get_height(FireHorse)?,
3331            upgrade_xx_height: match config.network {
3332                NetworkChain::Mainnet => 9_999_999_999,
3333                _ => 999_999_999_999_999,
3334            },
3335        })
3336    }
3337}
3338
3339pub enum StateMinerInitialPledgeForSector {}
3340impl RpcMethod<4> for StateMinerInitialPledgeForSector {
3341    const NAME: &'static str = "Filecoin.StateMinerInitialPledgeForSector";
3342    const PARAM_NAMES: [&'static str; 4] =
3343        ["sectorDuration", "sectorSize", "verifiedSize", "tipsetKey"];
3344    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
3345    const PERMISSION: Permission = Permission::Read;
3346    const DESCRIPTION: &'static str = "Returns the initial pledge collateral required to commit a sector with the given duration, size, and verified deal size at the specified tipset.";
3347
3348    type Params = (ChainEpoch, SectorSize, u64, ApiTipsetKey);
3349    type Ok = TokenAmount;
3350
3351    async fn handle(
3352        ctx: Ctx,
3353        (sector_duration, sector_size, verified_size, ApiTipsetKey(tsk)): Self::Params,
3354        _: &http::Extensions,
3355    ) -> Result<Self::Ok, ServerError> {
3356        if sector_duration <= 0 {
3357            return Err(anyhow::anyhow!("sector duration must be greater than 0").into());
3358        }
3359        if verified_size > sector_size as u64 {
3360            return Err(
3361                anyhow::anyhow!("verified deal size cannot be larger than sector size").into(),
3362            );
3363        }
3364
3365        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
3366
3367        let deal_weight = BigInt::from(0);
3368        let verified_deal_weight = BigInt::from(verified_size) * sector_duration;
3369        let sector_weight = qa_power_for_weight(
3370            sector_size.into(),
3371            sector_duration,
3372            &deal_weight,
3373            &verified_deal_weight,
3374        );
3375
3376        let initial_pledge = compute_initial_pledge_for_power(&ctx, &ts, &sector_weight)?;
3377
3378        let (value, _) = (initial_pledge * INITIAL_PLEDGE_NUM).div_rem(INITIAL_PLEDGE_DEN);
3379        Ok(value)
3380    }
3381}
3382
3383pub enum StateMinerCreationDeposit {}
3384impl RpcMethod<1> for StateMinerCreationDeposit {
3385    const NAME: &'static str = "Filecoin.StateMinerCreationDeposit";
3386    const PARAM_NAMES: [&'static str; 1] = ["tipsetKey"];
3387    const API_PATHS: BitFlags<ApiPaths> = make_bitflags!(ApiPaths::V1);
3388    const PERMISSION: Permission = Permission::Read;
3389    const DESCRIPTION: &'static str =
3390        "Returns the deposit required to create a new miner actor at the specified tipset.";
3391
3392    type Params = (ApiTipsetKey,);
3393    type Ok = TokenAmount;
3394
3395    async fn handle(
3396        ctx: Ctx,
3397        (ApiTipsetKey(tsk),): Self::Params,
3398        _: &http::Extensions,
3399    ) -> Result<Self::Ok, ServerError> {
3400        let ts = ctx.chain_store().load_required_tipset_or_heaviest(&tsk)?;
3401
3402        if ctx.state_manager.get_network_version(ts.epoch()) < NetworkVersion::V27 {
3403            return Ok(TokenAmount::from_atto(0));
3404        }
3405
3406        // Reference implementation: https://github.com/filecoin-project/builtin-actors/blob/00db828d09c3dfb61fe768ff6a19416a313444bd/actors/miner/src/lib.rs#L5264-L5279
3407        let create_miner_deposit_power: StoragePower =
3408            &ctx.chain_config().policy.minimum_consensus_power / 10;
3409
3410        compute_initial_pledge_for_power(&ctx, &ts, &create_miner_deposit_power)
3411    }
3412}
3413
3414fn compute_initial_pledge_for_power(
3415    ctx: &Ctx,
3416    ts: &Tipset,
3417    qa_power: &StoragePower,
3418) -> Result<TokenAmount, ServerError> {
3419    let state_tree = ctx.state_manager.get_state_tree(ts.parent_state())?;
3420    let power_state: power::State = state_tree.get_actor_state()?;
3421    let reward_state: reward::State = state_tree.get_actor_state()?;
3422
3423    let circ_supply = ctx
3424        .state_manager
3425        .genesis_info()
3426        .get_vm_circulating_supply_detailed_with_state_tree(ts.epoch(), &state_tree)?;
3427
3428    let (epochs_since_start, duration) = if power_state.ramp_start_epoch() > 0 {
3429        (
3430            ts.epoch() - power_state.ramp_start_epoch(),
3431            power_state.ramp_duration_epochs(),
3432        )
3433    } else {
3434        (0, 0)
3435    };
3436
3437    Ok(reward_state.initial_pledge_for_power(
3438        qa_power,
3439        power_state.total_locked(),
3440        power_state.total_power_smoothed(),
3441        &circ_supply.fil_circulating,
3442        epochs_since_start,
3443        duration,
3444    )?)
3445}
3446
3447#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, JsonSchema)]
3448#[serde(rename_all = "camelCase")]
3449pub struct StateActorCodeCidsOutput {
3450    pub network_version: NetworkVersion,
3451    pub network_version_revision: i64,
3452    pub actor_version: String,
3453    #[serde(with = "crate::lotus_json")]
3454    #[schemars(with = "LotusJson<Cid>")]
3455    pub manifest: Cid,
3456    #[serde(with = "crate::lotus_json")]
3457    #[schemars(with = "LotusJson<Cid>")]
3458    pub bundle: Cid,
3459    #[serde(with = "crate::lotus_json")]
3460    #[schemars(with = "LotusJson<HashMap<String, Cid>>")]
3461    pub actor_cids: HashMap<String, Cid>,
3462}
3463lotus_json_with_self!(StateActorCodeCidsOutput);
3464
3465impl std::fmt::Display for StateActorCodeCidsOutput {
3466    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3467        writeln!(f, "Network Version: {}", self.network_version)?;
3468        writeln!(
3469            f,
3470            "Network Version Revision: {}",
3471            self.network_version_revision
3472        )?;
3473        writeln!(f, "Actor Version: {}", self.actor_version)?;
3474        writeln!(f, "Manifest CID: {}", self.manifest)?;
3475        writeln!(f, "Bundle CID: {}", self.bundle)?;
3476        writeln!(f, "Actor CIDs:")?;
3477        let longest_name = self
3478            .actor_cids
3479            .keys()
3480            .map(|name| name.len())
3481            .max()
3482            .unwrap_or(0);
3483        for (name, cid) in &self.actor_cids {
3484            writeln!(f, "  {:width$} : {}", name, cid, width = longest_name)?;
3485        }
3486        Ok(())
3487    }
3488}
3489
3490pub enum StateActorInfo {}
3491
3492impl RpcMethod<0> for StateActorInfo {
3493    const NAME: &'static str = "Forest.StateActorInfo";
3494    const PARAM_NAMES: [&'static str; 0] = [];
3495    const API_PATHS: BitFlags<ApiPaths> = ApiPaths::all();
3496    const PERMISSION: Permission = Permission::Read;
3497    const DESCRIPTION: &'static str =
3498        "Returns the builtin actor information for the current network.";
3499
3500    type Params = ();
3501    type Ok = StateActorCodeCidsOutput;
3502
3503    async fn handle(
3504        ctx: Ctx,
3505        (): Self::Params,
3506        _: &http::Extensions,
3507    ) -> Result<Self::Ok, ServerError> {
3508        let ts = ctx.chain_store().load_required_tipset_or_heaviest(None)?;
3509        let state_tree = StateTree::new_from_tipset(ctx.db(), &ts)?;
3510        let bundle = state_tree.get_actor_bundle_metadata()?;
3511        let system_state: system::State = state_tree.get_actor_state()?;
3512        let actors = system_state.builtin_actors_cid();
3513
3514        let current_manifest = BuiltinActorManifest::load_v1_actor_list(ctx.db(), actors)?;
3515
3516        // Sanity check: the command would normally be used only for diagnostics, so we want to be
3517        // sure the data is consistent.
3518        if current_manifest != bundle.manifest {
3519            return Err(anyhow::anyhow!("Actor bundle manifest does not match the manifest in the state tree. This indicates that the node is misconfigured or is running an unsupported network.")
3520            .into());
3521        }
3522
3523        let network_version = ctx.chain_config().network_version(ts.epoch() - 1);
3524        let network_version_revision = ctx.chain_config().network_version_revision(ts.epoch() - 1);
3525        let result = StateActorCodeCidsOutput {
3526            network_version,
3527            network_version_revision,
3528            actor_version: bundle.version.to_owned(),
3529            manifest: current_manifest.actor_list_cid,
3530            bundle: bundle.bundle_cid,
3531            actor_cids: current_manifest
3532                .builtin_actors()
3533                .map(|(a, c)| (a.name().to_string(), c))
3534                .collect(),
3535        };
3536
3537        Ok(result)
3538    }
3539}
3540
3541#[cfg(test)]
3542mod tests {
3543    use super::*;
3544    use quickcheck_macros::quickcheck;
3545    use rstest::rstest;
3546
3547    #[rstest]
3548    #[case(1_000, Some(600))]
3549    #[case(401, Some(1))]
3550    #[case(400, None)]
3551    #[case(399, None)]
3552    #[case(i64::MIN, None)]
3553    fn sector_duration_from_expiration_requires_positive(
3554        #[case] expiration: ChainEpoch,
3555        #[case] expected: Option<ChainEpoch>,
3556    ) {
3557        assert_eq!(
3558            sector_duration_from_expiration(expiration, 400).ok(),
3559            expected
3560        );
3561    }
3562
3563    #[quickcheck]
3564    fn sector_duration_from_expiration_no_panic(expiration: ChainEpoch, epoch: ChainEpoch) {
3565        let _ = sector_duration_from_expiration(expiration, epoch);
3566    }
3567}