diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c973fa13a4f..fcc4559fb59c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,8 @@ ### Added +- [#3773](https://github.com/ChainSafe/forest/pull/3786) Implement the + `Filecoin.MinerGetBaseInfo` lotus-compatible RPC API. - [#3807](https://github.com/ChainSafe/forest/pull/3807) Add `--run-ignored` flag to `forest-tool api compare`. diff --git a/src/rpc/mod.rs b/src/rpc/mod.rs index 26fa4bd78185..196cae1b87ae 100644 --- a/src/rpc/mod.rs +++ b/src/rpc/mod.rs @@ -129,6 +129,7 @@ where .with_method(STATE_MARKET_BALANCE, state_market_balance::) .with_method(STATE_MARKET_DEALS, state_market_deals::) .with_method(STATE_MINER_INFO, state_miner_info::) + .with_method(MINER_GET_BASE_INFO, miner_get_base_info::) .with_method(STATE_MINER_ACTIVE_SECTORS, state_miner_active_sectors::) .with_method(STATE_MINER_FAULTS, state_miner_faults::) .with_method(STATE_MINER_RECOVERIES, state_miner_recoveries::) diff --git a/src/rpc/state_api.rs b/src/rpc/state_api.rs index 06bf7b1b5c70..9d71a18c0ce4 100644 --- a/src/rpc/state_api.rs +++ b/src/rpc/state_api.rs @@ -8,7 +8,7 @@ use crate::libp2p::NetworkMessage; use crate::lotus_json::LotusJson; use crate::rpc_api::data_types::{ ApiActorState, ApiDeadline, ApiInvocResult, CirculatingSupply, MarketDeal, MessageLookup, - RPCState, SectorOnChainInfo, + MiningBaseInfo, RPCState, SectorOnChainInfo, }; use crate::shim::{ address::Address, clock::ChainEpoch, econ::TokenAmount, executor::Receipt, message::Message, @@ -39,6 +39,20 @@ use tokio::task::JoinSet; type RandomnessParams = (i64, ChainEpoch, Vec, TipsetKeys); +pub(in crate::rpc) async fn miner_get_base_info( + data: Data>, + Params(LotusJson((address, epoch, tsk))): Params>, +) -> anyhow::Result>> { + let ts = data + .state_manager + .chain_store() + .load_required_tipset(&tsk)?; + + data.state_manager + .miner_get_base_info(data.state_manager.beacon_schedule(), ts, address, epoch) + .await + .map(|info| info.into()) +} /// runs the given message and returns its result without any persisted changes. pub(in crate::rpc) async fn state_call( data: Data>, diff --git a/src/rpc_api/data_types.rs b/src/rpc_api/data_types.rs index aa94c9b78861..34aa587565d5 100644 --- a/src/rpc_api/data_types.rs +++ b/src/rpc_api/data_types.rs @@ -4,7 +4,7 @@ use std::str::FromStr; use std::sync::Arc; -use crate::beacon::BeaconSchedule; +use crate::beacon::{BeaconEntry, BeaconSchedule}; use crate::blocks::TipsetKeys; use crate::chain::ChainStore; use crate::chain_sync::{BadBlockCache, SyncState}; @@ -14,6 +14,7 @@ use crate::libp2p::{Multihash, NetworkMessage}; use crate::lotus_json::{lotus_json_with_self, HasLotusJson, LotusJson}; use crate::message::signed_message::SignedMessage; use crate::message_pool::{MessagePool, MpoolRpcProvider}; +use crate::shim::sector::SectorInfo; use crate::shim::{ address::Address, clock::ChainEpoch, @@ -407,6 +408,28 @@ pub struct MinerPowerLotusJson { has_min_power: bool, } +// Note: kept the name in line with Lotus implementation for cross-referencing simplicity. +#[derive(Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "PascalCase")] +pub struct MiningBaseInfo { + #[serde(with = "crate::lotus_json")] + pub miner_power: crate::shim::sector::StoragePower, + #[serde(with = "crate::lotus_json")] + pub network_power: fvm_shared2::sector::StoragePower, + #[serde(with = "crate::lotus_json")] + pub sectors: Vec, + #[serde(with = "crate::lotus_json")] + pub worker_key: Address, + pub sector_size: fvm_shared2::sector::SectorSize, + #[serde(with = "crate::lotus_json")] + pub prev_beacon_entry: BeaconEntry, + #[serde(with = "crate::lotus_json")] + pub beacon_entries: Vec, + pub eligible_for_mining: bool, +} + +lotus_json_with_self!(MiningBaseInfo); + impl HasLotusJson for MinerPower { type LotusJson = MinerPowerLotusJson; fn snapshots() -> Vec<(serde_json::Value, Self)> { diff --git a/src/rpc_api/mod.rs b/src/rpc_api/mod.rs index 36118544d15d..6929845adb09 100644 --- a/src/rpc_api/mod.rs +++ b/src/rpc_api/mod.rs @@ -81,6 +81,7 @@ pub static ACCESS_MAP: Lazy> = Lazy::new(|| { access.insert(state_api::STATE_MARKET_BALANCE, Access::Read); access.insert(state_api::STATE_MARKET_DEALS, Access::Read); access.insert(state_api::STATE_MINER_INFO, Access::Read); + access.insert(state_api::MINER_GET_BASE_INFO, Access::Read); access.insert(state_api::STATE_MINER_ACTIVE_SECTORS, Access::Read); access.insert(state_api::STATE_MINER_FAULTS, Access::Read); access.insert(state_api::STATE_MINER_RECOVERIES, Access::Read); @@ -253,6 +254,7 @@ pub mod state_api { pub const STATE_MARKET_BALANCE: &str = "Filecoin.StateMarketBalance"; pub const STATE_MARKET_DEALS: &str = "Filecoin.StateMarketDeals"; pub const STATE_MINER_INFO: &str = "Filecoin.StateMinerInfo"; + pub const MINER_GET_BASE_INFO: &str = "Filecoin.MinerGetBaseInfo"; pub const STATE_MINER_FAULTS: &str = "Filecoin.StateMinerFaults"; pub const STATE_MINER_RECOVERIES: &str = "Filecoin.StateMinerRecoveries"; pub const STATE_MINER_POWER: &str = "Filecoin.StateMinerPower"; diff --git a/src/rpc_client/state_ops.rs b/src/rpc_client/state_ops.rs index 99159f4e6e0e..16c77f5c3e4b 100644 --- a/src/rpc_client/state_ops.rs +++ b/src/rpc_client/state_ops.rs @@ -3,6 +3,7 @@ use std::path::PathBuf; +use crate::rpc_api::data_types::MiningBaseInfo; use crate::{ blocks::TipsetKeys, rpc_api::{ @@ -65,6 +66,14 @@ impl ApiInfo { RpcRequest::new(STATE_MINER_INFO, (miner, tsk)) } + pub fn miner_get_base_info_req( + miner: Address, + epoch: ChainEpoch, + tsk: TipsetKeys, + ) -> RpcRequest> { + RpcRequest::new(MINER_GET_BASE_INFO, (miner, epoch, tsk)) + } + pub fn state_call_req(message: Message, tsk: TipsetKeys) -> RpcRequest { RpcRequest::new(STATE_CALL, (message, tsk)) } diff --git a/src/shim/sector.rs b/src/shim/sector.rs index f97d3de5bbba..1dedd01a7a2b 100644 --- a/src/shim/sector.rs +++ b/src/shim/sector.rs @@ -18,6 +18,7 @@ use num_derive::FromPrimitive; use std::ops::Deref; pub use fvm_shared3::sector::StoragePower; +use serde::{Deserialize, Serialize}; pub type SectorNumber = fvm_shared3::sector::SectorNumber; @@ -104,7 +105,9 @@ impl quickcheck::Arbitrary for RegisteredSealProof { /// Represents a shim over `SectorInfo` from `fvm_shared` with convenience /// methods to convert to an older version of the type -#[derive(PartialEq, Debug, Clone, derive_more::From, derive_more::Into)] +#[derive( + Eq, PartialEq, Debug, Clone, derive_more::From, derive_more::Into, Serialize, Deserialize, +)] pub struct SectorInfo(SectorInfoV3); #[cfg(test)] diff --git a/src/state_manager/mod.rs b/src/state_manager/mod.rs index cef9eececc21..8e7a4f206016 100644 --- a/src/state_manager/mod.rs +++ b/src/state_manager/mod.rs @@ -14,7 +14,7 @@ use rayon::prelude::ParallelBridge; pub use utils::is_valid_for_sending; pub mod vm_circ_supply; pub use self::errors::*; -use crate::beacon::BeaconSchedule; +use crate::beacon::{BeaconEntry, BeaconSchedule}; use crate::blocks::{Tipset, TipsetKeys}; use crate::chain::{ index::{ChainIndex, ResolveNullTipset}, @@ -26,13 +26,14 @@ use crate::interpreter::{ }; use crate::message::{ChainMessage, Message as MessageTrait}; use crate::networks::ChainConfig; -use crate::rpc_api::data_types::{ApiInvocResult, MessageGasCost}; +use crate::rpc_api::data_types::{ApiInvocResult, MessageGasCost, MiningBaseInfo}; use crate::shim::{ address::{Address, Payload, Protocol, BLS_PUB_LEN}, clock::ChainEpoch, econ::TokenAmount, executor::{ApplyRet, Receipt}, message::Message, + randomness::Randomness, state_tree::{ActorState, StateTree}, version::NetworkVersion, }; @@ -40,14 +41,17 @@ use ahash::{HashMap, HashMapExt}; use chain_rand::ChainRand; use cid::Cid; +use crate::state_manager::chain_rand::draw_randomness; use fil_actor_interface::miner::SectorOnChainInfo; use fil_actor_interface::miner::{MinerInfo, MinerPower, Partition}; use fil_actor_interface::*; use fil_actors_shared::fvm_ipld_amt::Amtv0 as Amt; use fil_actors_shared::fvm_ipld_bitfield::BitField; use fil_actors_shared::v10::runtime::Policy; +use fil_actors_shared::v12::runtime::DomainSeparationTag; use futures::{channel::oneshot, select, FutureExt}; use fvm_ipld_blockstore::Blockstore; +use fvm_ipld_encoding::to_vec; use itertools::Itertools as _; use lru::LruCache; use nonzero_ext::nonzero; @@ -1138,6 +1142,85 @@ where resolve_to_key_addr(&state, self.blockstore(), addr) } + pub async fn miner_get_base_info( + self: &Arc, + beacon_schedule: Arc, + tipset: Arc, + addr: Address, + epoch: ChainEpoch, + ) -> anyhow::Result> { + let prev_beacon = self + .chain_store() + .chain_index + .latest_beacon_entry(&tipset)?; + + let entries: Vec = beacon_schedule + .beacon_entries_for_block( + self.chain_config.network_version(epoch), + epoch, + tipset.epoch(), + &prev_beacon, + ) + .await?; + + let base = entries.last().unwrap_or(&prev_beacon); + + let (lb_tipset, lb_state_root) = ChainStore::get_lookback_tipset_for_round( + self.cs.chain_index.clone(), + self.chain_config.clone(), + tipset.clone(), + epoch, + )?; + + let actor = self + .get_actor(&addr, *tipset.parent_state())? + .context("miner actor does not exist")?; + + let miner_state = miner::State::load(self.blockstore(), actor.code, actor.state)?; + + let addr_buf = to_vec(&addr)?; + let rand = draw_randomness( + base.data(), + DomainSeparationTag::WinningPoStChallengeSeed as i64, + epoch, + &addr_buf, + )?; + + let network_version = self.chain_config.network_version(tipset.epoch()); + let sectors = self.get_sectors_for_winning_post( + &lb_state_root, + network_version, + &addr, + Randomness::new(rand.to_vec()), + )?; + + if sectors.is_empty() { + return Ok(None); + } + + let (miner_power, total_power) = self + .get_power(&lb_state_root, Some(&addr))? + .context("failed to get power")?; + + let info = miner_state.info(self.blockstore())?; + + let worker_key = self + .resolve_to_deterministic_address(info.worker.into(), Some(tipset.clone())) + .await?; + let eligible = self.eligible_to_mine(&addr, &tipset, &lb_tipset)?; + + Ok(Some(MiningBaseInfo { + miner_power: miner_power.quality_adj_power, + network_power: total_power.quality_adj_power, + sectors, + worker_key, + sector_size: info.sector_size, + prev_beacon_entry: prev_beacon, + beacon_entries: entries, + eligible_for_mining: eligible, + })) + } + /// Checks power actor state for if miner meets consensus minimum /// requirements. pub fn miner_has_min_power( diff --git a/src/tool/subcommands/api_cmd.rs b/src/tool/subcommands/api_cmd.rs index 68f287075103..3f1a675ad92b 100644 --- a/src/tool/subcommands/api_cmd.rs +++ b/src/tool/subcommands/api_cmd.rs @@ -500,6 +500,11 @@ fn snapshot_tests(store: &ManyCar, n_tipsets: usize) -> anyhow::Result