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