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