Skip to main content

forest/state_manager/
mining.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use super::chain_rand::draw_randomness;
5use super::*;
6use crate::beacon::BeaconEntry;
7use crate::db::EthMappingsStore;
8use crate::rpc::types::MiningBaseInfo;
9use crate::shim::randomness::Randomness;
10use crate::shim::runtime::Policy;
11use anyhow::Context as _;
12use fil_actors_shared::v12::runtime::DomainSeparationTag;
13use fvm_ipld_encoding::to_vec;
14use num::BigInt;
15use num_traits::identities::Zero;
16impl<DB> StateManager<DB>
17where
18    DB: Blockstore + Send + Sync + 'static,
19{
20    /// Checks the eligibility of the miner. This is used in the validation that
21    /// a block's miner has the requirements to mine a block.
22    pub fn eligible_to_mine(
23        &self,
24        address: &Address,
25        base_tipset: &Tipset,
26        lookback_tipset: &Tipset,
27    ) -> anyhow::Result<bool, Error> {
28        let hmp =
29            self.miner_has_min_power(&self.chain_config().policy, address, lookback_tipset)?;
30        let version = self.get_network_version(base_tipset.epoch());
31
32        if version <= NetworkVersion::V3 {
33            return Ok(hmp);
34        }
35
36        if !hmp {
37            return Ok(false);
38        }
39
40        let actor = self
41            .get_actor(&Address::POWER_ACTOR, *base_tipset.parent_state())?
42            .ok_or_else(|| Error::state("Power actor address could not be resolved"))?;
43
44        let power_state = power::State::load(self.blockstore(), actor.code, actor.state)?;
45
46        let actor = self
47            .get_actor(address, *base_tipset.parent_state())?
48            .ok_or_else(|| Error::state("Miner actor address could not be resolved"))?;
49
50        let miner_state = miner::State::load(self.blockstore(), actor.code, actor.state)?;
51
52        // Non-empty power claim.
53        let claim = power_state
54            .miner_power(self.blockstore(), address)?
55            .ok_or_else(|| Error::Other("Could not get claim".to_string()))?;
56        if claim.quality_adj_power <= BigInt::zero() {
57            return Ok(false);
58        }
59
60        // No fee debt.
61        if !miner_state.fee_debt().is_zero() {
62            return Ok(false);
63        }
64
65        // No active consensus faults.
66        let info = miner_state.info(self.blockstore())?;
67        if base_tipset.epoch() <= info.consensus_fault_elapsed {
68            return Ok(false);
69        }
70
71        Ok(true)
72    }
73
74    pub async fn miner_get_base_info(
75        self: &Arc<Self>,
76        beacon_schedule: &BeaconSchedule,
77        tipset: Tipset,
78        addr: Address,
79        epoch: ChainEpoch,
80    ) -> anyhow::Result<Option<MiningBaseInfo>>
81    where
82        DB: EthMappingsStore,
83    {
84        let prev_beacon = self
85            .chain_store()
86            .chain_index()
87            .latest_beacon_entry(tipset.clone())?;
88
89        let entries: Vec<BeaconEntry> = beacon_schedule
90            .beacon_entries_for_block(
91                self.chain_config().network_version(epoch),
92                epoch,
93                tipset.epoch(),
94                &prev_beacon,
95            )
96            .await?;
97
98        let base = entries.last().unwrap_or(&prev_beacon);
99
100        let (lb_tipset, lb_state_root) = ChainStore::get_lookback_tipset_for_round(
101            self.chain_index(),
102            self.chain_config(),
103            &tipset,
104            epoch,
105        )?;
106
107        // If the miner actor doesn't exist in the current tipset, it is a
108        // user-error and we must return an error message. If the miner exists
109        // in the current tipset but not in the lookback tipset, we may not
110        // error and should instead return None.
111        let actor = self.get_required_actor(&addr, *tipset.parent_state())?;
112        if self.get_actor(&addr, lb_state_root)?.is_none() {
113            return Ok(None);
114        }
115
116        let miner_state = miner::State::load(self.blockstore(), actor.code, actor.state)?;
117
118        let addr_buf = to_vec(&addr)?;
119        let rand = draw_randomness(
120            base.signature(),
121            DomainSeparationTag::WinningPoStChallengeSeed as i64,
122            epoch,
123            &addr_buf,
124        )?;
125
126        let network_version = self.chain_config().network_version(tipset.epoch());
127        let sectors = self.get_sectors_for_winning_post(
128            &lb_state_root,
129            network_version,
130            &addr,
131            Randomness::new(rand.to_vec()),
132        )?;
133
134        if sectors.is_empty() {
135            return Ok(None);
136        }
137
138        let (miner_power, total_power) = self
139            .get_power(&lb_state_root, Some(&addr))?
140            .context("failed to get power")?;
141
142        let info = miner_state.info(self.blockstore())?;
143
144        let worker_key = self
145            .resolve_to_deterministic_address(info.worker, &tipset)
146            .await?;
147        let eligible = self.eligible_to_mine(&addr, &tipset, &lb_tipset)?;
148
149        Ok(Some(MiningBaseInfo {
150            miner_power: miner_power.quality_adj_power,
151            network_power: total_power.quality_adj_power,
152            sectors,
153            worker_key,
154            sector_size: info.sector_size,
155            prev_beacon_entry: prev_beacon,
156            beacon_entries: entries,
157            eligible_for_mining: eligible,
158        }))
159    }
160
161    /// Checks power actor state for if miner meets consensus minimum
162    /// requirements.
163    pub fn miner_has_min_power(
164        &self,
165        policy: &Policy,
166        addr: &Address,
167        ts: &Tipset,
168    ) -> anyhow::Result<bool> {
169        let actor = self
170            .get_actor(&Address::POWER_ACTOR, *ts.parent_state())?
171            .ok_or_else(|| Error::state("Power actor address could not be resolved"))?;
172        let ps = power::State::load(self.blockstore(), actor.code, actor.state)?;
173
174        ps.miner_nominal_power_meets_consensus_minimum(policy, self.blockstore(), addr)
175    }
176}