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