Skip to main content

forest/rpc/methods/
state.rs

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