fil_actor_power_state/v11/
state.rs1use anyhow::anyhow;
5use cid::Cid;
6use fil_actors_shared::actor_error_v11;
7use fil_actors_shared::v11::runtime::Policy;
8use fil_actors_shared::v11::{
9    ActorDowncast, ActorError, AsActorError, Map, Multimap, make_empty_map, make_map_with_root,
10    make_map_with_root_and_bitwidth,
11};
12use fvm_ipld_blockstore::Blockstore;
13use fvm_ipld_encoding::RawBytes;
14use fvm_ipld_encoding::tuple::*;
15use fvm_ipld_hamt::BytesKey;
16use fvm_shared3::address::Address;
17use fvm_shared3::bigint::bigint_ser;
18use fvm_shared3::clock::ChainEpoch;
19use fvm_shared3::econ::TokenAmount;
20use fvm_shared3::error::ExitCode;
21use fvm_shared3::sector::{RegisteredPoStProof, StoragePower};
22use fvm_shared3::smooth::FilterEstimate;
23use fvm_shared3::{ActorID, HAMT_BIT_WIDTH};
24use integer_encoding::VarInt;
25use lazy_static::lazy_static;
26use num_traits::Signed;
27
28use super::{CONSENSUS_MINER_MIN_MINERS, CRON_QUEUE_AMT_BITWIDTH, CRON_QUEUE_HAMT_BITWIDTH};
29
30lazy_static! {
31    pub static ref INITIAL_QA_POWER_ESTIMATE_POSITION: StoragePower = StoragePower::from(750_000) * (1 << 30);
33    pub static ref INITIAL_QA_POWER_ESTIMATE_VELOCITY: StoragePower = StoragePower::from(3_840) * (1 << 30);
35}
36
37#[derive(Default, Serialize_tuple, Deserialize_tuple, Clone, Debug)]
39pub struct State {
40    #[serde(with = "bigint_ser")]
41    pub total_raw_byte_power: StoragePower,
42    #[serde(with = "bigint_ser")]
43    pub total_bytes_committed: StoragePower,
44    #[serde(with = "bigint_ser")]
45    pub total_quality_adj_power: StoragePower,
46    #[serde(with = "bigint_ser")]
47    pub total_qa_bytes_committed: StoragePower,
48    pub total_pledge_collateral: TokenAmount,
49
50    #[serde(with = "bigint_ser")]
51    pub this_epoch_raw_byte_power: StoragePower,
52    #[serde(with = "bigint_ser")]
53    pub this_epoch_quality_adj_power: StoragePower,
54    pub this_epoch_pledge_collateral: TokenAmount,
55    pub this_epoch_qa_power_smoothed: FilterEstimate,
56
57    pub miner_count: i64,
58    pub miner_above_min_power_count: i64,
60
61    pub cron_event_queue: Cid, pub first_cron_epoch: ChainEpoch,
67
68    pub claims: Cid, pub proof_validation_batch: Option<Cid>,
72}
73
74impl State {
75    pub fn new<BS: Blockstore>(store: &BS) -> anyhow::Result<State> {
76        let empty_map = make_empty_map::<_, ()>(store, HAMT_BIT_WIDTH)
77            .flush()
78            .map_err(|e| anyhow!("Failed to create empty map: {}", e))?;
79
80        let empty_mmap = Multimap::new(store, CRON_QUEUE_HAMT_BITWIDTH, CRON_QUEUE_AMT_BITWIDTH)
81            .root()
82            .map_err(|e| {
83                e.downcast_default(
84                    ExitCode::USR_ILLEGAL_STATE,
85                    "Failed to get empty multimap cid",
86                )
87            })?;
88        Ok(State {
89            cron_event_queue: empty_mmap,
90            claims: empty_map,
91            this_epoch_qa_power_smoothed: FilterEstimate::new(
92                INITIAL_QA_POWER_ESTIMATE_POSITION.clone(),
93                INITIAL_QA_POWER_ESTIMATE_VELOCITY.clone(),
94            ),
95            ..Default::default()
96        })
97    }
98
99    pub fn into_total_locked(self) -> TokenAmount {
100        self.total_pledge_collateral
101    }
102
103    pub fn miner_nominal_power_meets_consensus_minimum<BS: Blockstore>(
105        &self,
106        policy: &Policy,
107        s: &BS,
108        miner: ActorID,
109    ) -> Result<(StoragePower, bool), ActorError> {
110        let claims = make_map_with_root_and_bitwidth(&self.claims, s, HAMT_BIT_WIDTH)
111            .with_context_code(ExitCode::USR_ILLEGAL_STATE, || {
112                format!("failed to load claims for miner: {}", miner)
113            })?;
114
115        let claim = get_claim(&claims, &Address::new_id(miner))
116            .with_context_code(ExitCode::USR_ILLEGAL_STATE, || {
117                format!("failed to get claim for miner: {}", miner)
118            })?
119            .with_context_code(ExitCode::USR_ILLEGAL_ARGUMENT, || {
120                format!("no claim for actor: {}", miner)
121            })?;
122
123        let miner_nominal_power = claim.raw_byte_power.clone();
124        let miner_min_power = consensus_miner_min_power(policy, claim.window_post_proof_type)
125            .context_code(
126                ExitCode::USR_ILLEGAL_STATE,
127                "could not get miner min power from proof type: {}",
128            )?;
129
130        if miner_nominal_power >= miner_min_power {
131            Ok((miner_nominal_power, true))
133        } else if self.miner_above_min_power_count >= CONSENSUS_MINER_MIN_MINERS {
134            Ok((miner_nominal_power, false))
136        } else {
137            Ok((
139                miner_nominal_power.clone(),
140                miner_nominal_power.is_positive(),
141            ))
142        }
143    }
144
145    pub fn miner_power<BS: Blockstore>(
146        &self,
147        s: &BS,
148        miner: &Address,
149    ) -> anyhow::Result<Option<Claim>> {
150        let claims = make_map_with_root(&self.claims, s)?;
151        get_claim(&claims, miner).map(|s| s.cloned())
152    }
153
154    pub fn current_total_power(&self) -> (StoragePower, StoragePower) {
155        if self.miner_above_min_power_count < CONSENSUS_MINER_MIN_MINERS {
156            (
157                self.total_bytes_committed.clone(),
158                self.total_qa_bytes_committed.clone(),
159            )
160        } else {
161            (
162                self.total_raw_byte_power.clone(),
163                self.total_quality_adj_power.clone(),
164            )
165        }
166    }
167
168    pub fn get_claim<BS: Blockstore>(
169        &self,
170        store: &BS,
171        miner: &Address,
172    ) -> anyhow::Result<Option<Claim>> {
173        let claims =
174            make_map_with_root_and_bitwidth::<_, Claim>(&self.claims, store, HAMT_BIT_WIDTH)
175                .map_err(|e| {
176                    e.downcast_default(ExitCode::USR_ILLEGAL_STATE, "failed to load claims")
177                })?;
178
179        let claim = get_claim(&claims, miner)?;
180        Ok(claim.cloned())
181    }
182}
183
184fn get_claim<'m, BS: Blockstore>(
186    claims: &'m Map<BS, Claim>,
187    a: &Address,
188) -> anyhow::Result<Option<&'m Claim>> {
189    claims
190        .get(&a.to_bytes())
191        .map_err(|e| e.downcast_wrap(format!("failed to get claim for address {}", a)))
192}
193
194pub fn set_claim<BS: Blockstore>(
195    claims: &mut Map<BS, Claim>,
196    a: &Address,
197    claim: Claim,
198) -> anyhow::Result<()> {
199    if claim.raw_byte_power.is_negative() {
200        return Err(anyhow!(actor_error_v11!(
201            illegal_state,
202            "negative claim raw power {}",
203            claim.raw_byte_power
204        )));
205    }
206    if claim.quality_adj_power.is_negative() {
207        return Err(anyhow!(actor_error_v11!(
208            illegal_state,
209            "negative claim quality-adjusted power {}",
210            claim.quality_adj_power
211        )));
212    }
213
214    claims
215        .set(a.to_bytes().into(), claim)
216        .map_err(|e| e.downcast_wrap(format!("failed to set claim for address {}", a)))?;
217    Ok(())
218}
219
220pub fn epoch_key(e: ChainEpoch) -> BytesKey {
221    let bz = e.encode_var_vec();
222    bz.into()
223}
224
225#[derive(Debug, Serialize_tuple, Deserialize_tuple, Clone, PartialEq, Eq)]
226pub struct Claim {
227    pub window_post_proof_type: RegisteredPoStProof,
229    #[serde(with = "bigint_ser")]
231    pub raw_byte_power: StoragePower,
232    #[serde(with = "bigint_ser")]
234    pub quality_adj_power: StoragePower,
235}
236
237#[derive(Clone, Debug, Serialize_tuple, Deserialize_tuple)]
238pub struct CronEvent {
239    pub miner_addr: Address,
240    pub callback_payload: RawBytes,
241}
242
243pub fn consensus_miner_min_power(
245    policy: &Policy,
246    p: RegisteredPoStProof,
247) -> anyhow::Result<StoragePower> {
248    use RegisteredPoStProof::*;
249    match p {
250        StackedDRGWinning2KiBV1
251        | StackedDRGWinning8MiBV1
252        | StackedDRGWinning512MiBV1
253        | StackedDRGWinning32GiBV1
254        | StackedDRGWinning64GiBV1
255        | StackedDRGWindow2KiBV1
256        | StackedDRGWindow8MiBV1
257        | StackedDRGWindow512MiBV1
258        | StackedDRGWindow32GiBV1
259        | StackedDRGWindow64GiBV1
260        | StackedDRGWindow2KiBV1P1
261        | StackedDRGWindow8MiBV1P1
262        | StackedDRGWindow512MiBV1P1
263        | StackedDRGWindow32GiBV1P1
264        | StackedDRGWindow64GiBV1P1 => Ok(policy.minimum_consensus_power.clone()),
265        Invalid(i) => Err(anyhow::anyhow!("unsupported proof type: {}", i)),
266    }
267}
268
269#[cfg(test)]
270mod test {
271    use fvm_shared3::clock::ChainEpoch;
272
273    use super::*;
274
275    #[test]
276    fn epoch_key_test() {
277        let e1: ChainEpoch = 101;
278        let e2: ChainEpoch = 102;
279        let e3: ChainEpoch = 103;
280        let e4: ChainEpoch = -1;
281
282        let b1: BytesKey = [0xca, 0x1].to_vec().into();
283        let b2: BytesKey = [0xcc, 0x1].to_vec().into();
284        let b3: BytesKey = [0xce, 0x1].to_vec().into();
285        let b4: BytesKey = [0x1].to_vec().into();
286
287        assert_eq!(b1, epoch_key(e1));
288        assert_eq!(b2, epoch_key(e2));
289        assert_eq!(b3, epoch_key(e3));
290        assert_eq!(b4, epoch_key(e4));
291    }
292}