fil_actor_miner_state/v13/
state.rs

1// Copyright 2019-2022 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use std::borrow::Borrow;
5use std::cmp;
6use std::ops::Neg;
7
8use anyhow::{Error, anyhow};
9use cid::Cid;
10use fil_actors_shared::actor_error_v13;
11use fil_actors_shared::v13::runtime::Policy;
12use fil_actors_shared::v13::{
13    ActorDowncast, ActorError, Array, AsActorError, make_empty_map,
14    make_map_with_root_and_bitwidth, u64_key,
15};
16use fvm_ipld_amt::Error as AmtError;
17use fvm_ipld_bitfield::BitField;
18use fvm_ipld_blockstore::Blockstore;
19use fvm_ipld_encoding::tuple::*;
20use fvm_ipld_encoding::{BytesDe, CborStore, strict_bytes};
21use fvm_ipld_hamt::Error as HamtError;
22use fvm_shared4::address::Address;
23use multihash_codetable::Code;
24
25use fvm_shared3::clock::QuantSpec;
26use fvm_shared3::sector::MAX_SECTOR_NUMBER;
27use fvm_shared4::clock::{ChainEpoch, EPOCH_UNDEFINED};
28use fvm_shared4::econ::TokenAmount;
29use fvm_shared4::error::ExitCode;
30use fvm_shared4::sector::{RegisteredPoStProof, SectorNumber, SectorSize};
31use fvm_shared4::{ActorID, HAMT_BIT_WIDTH};
32use itertools::Itertools;
33use num_traits::Zero;
34
35use super::beneficiary::*;
36use super::deadlines::new_deadline_info;
37use super::policy::*;
38use super::types::*;
39use super::{
40    BitFieldQueue, Deadline, DeadlineInfo, DeadlineSectorMap, Deadlines, PowerPair, Sectors,
41    TerminationResult, VestingFunds, assign_deadlines, deadline_is_mutable,
42    new_deadline_info_from_offset_and_epoch, quant_spec_for_deadline,
43};
44
45const PRECOMMIT_EXPIRY_AMT_BITWIDTH: u32 = 6;
46pub const SECTORS_AMT_BITWIDTH: u32 = 5;
47
48/// Balance of Miner Actor should be greater than or equal to
49/// the sum of PreCommitDeposits and LockedFunds.
50/// It is possible for balance to fall below the sum of PCD, LF and
51/// InitialPledgeRequirements, and this is a bad state (IP Debt)
52/// that limits a miner actor's behavior (i.e. no balance withdrawals)
53/// Excess balance as computed by st.GetAvailableBalance will be
54/// withdrawable or usable for pre-commit deposit or pledge lock-up.
55#[derive(Serialize_tuple, Deserialize_tuple, Clone, Debug)]
56pub struct State {
57    /// Contains static info about this miner
58    pub info: Cid,
59
60    /// Total funds locked as pre_commit_deposit
61    pub pre_commit_deposits: TokenAmount,
62
63    /// Total rewards and added funds locked in vesting table
64    pub locked_funds: TokenAmount,
65
66    /// VestingFunds (Vesting Funds schedule for the miner).
67    pub vesting_funds: Cid,
68
69    /// Absolute value of debt this miner owes from unpaid fees.
70    pub fee_debt: TokenAmount,
71
72    /// Sum of initial pledge requirements of all active sectors.
73    pub initial_pledge: TokenAmount,
74
75    /// Sectors that have been pre-committed but not yet proven.
76    /// Map, HAMT<SectorNumber, SectorPreCommitOnChainInfo>
77    pub pre_committed_sectors: Cid,
78
79    // PreCommittedSectorsCleanUp maintains the state required to cleanup expired PreCommittedSectors.
80    pub pre_committed_sectors_cleanup: Cid, // BitFieldQueue (AMT[Epoch]*BitField)
81
82    /// Allocated sector IDs. Sector IDs can never be reused once allocated.
83    pub allocated_sectors: Cid, // BitField
84
85    /// Information for all proven and not-yet-garbage-collected sectors.
86    ///
87    /// Sectors are removed from this AMT when the partition to which the
88    /// sector belongs is compacted.
89    pub sectors: Cid, // Array, AMT[SectorNumber]SectorOnChainInfo (sparse)
90
91    /// The first epoch in this miner's current proving period. This is the first epoch in which a PoSt for a
92    /// partition at the miner's first deadline may arrive. Alternatively, it is after the last epoch at which
93    /// a PoSt for the previous window is valid.
94    /// Always greater than zero, this may be greater than the current epoch for genesis miners in the first
95    /// WPoStProvingPeriod epochs of the chain; the epochs before the first proving period starts are exempt from Window
96    /// PoSt requirements.
97    /// Updated at the end of every period by a cron callback.
98    pub proving_period_start: ChainEpoch,
99
100    /// Index of the deadline within the proving period beginning at ProvingPeriodStart that has not yet been
101    /// finalized.
102    /// Updated at the end of each deadline window by a cron callback.
103    pub current_deadline: u64,
104
105    /// The sector numbers due for PoSt at each deadline in the current proving period, frozen at period start.
106    /// New sectors are added and expired ones removed at proving period boundary.
107    /// Faults are not subtracted from this in state, but on the fly.
108    pub deadlines: Cid,
109
110    /// Deadlines with outstanding fees for early sector termination.
111    pub early_terminations: BitField,
112
113    // True when miner cron is active, false otherwise
114    pub deadline_cron_active: bool,
115}
116
117#[derive(PartialEq, Eq)]
118pub enum CollisionPolicy {
119    AllowCollisions,
120    DenyCollisions,
121}
122
123impl State {
124    #[allow(clippy::too_many_arguments)]
125    pub fn new<BS: Blockstore>(
126        policy: &Policy,
127        store: &BS,
128        info_cid: Cid,
129        period_start: ChainEpoch,
130        deadline_idx: u64,
131    ) -> anyhow::Result<Self> {
132        let empty_precommit_map = make_empty_map::<_, ()>(store, HAMT_BIT_WIDTH)
133            .flush()
134            .map_err(|e| {
135                e.downcast_default(
136                    ExitCode::USR_ILLEGAL_STATE,
137                    "failed to construct empty precommit map",
138                )
139            })?;
140        let empty_precommits_cleanup_array =
141            Array::<BitField, BS>::new_with_bit_width(store, PRECOMMIT_EXPIRY_AMT_BITWIDTH)
142                .flush()
143                .map_err(|e| {
144                    e.downcast_default(
145                        ExitCode::USR_ILLEGAL_STATE,
146                        "failed to construct empty precommits array",
147                    )
148                })?;
149        let empty_sectors_array =
150            Array::<SectorOnChainInfo, BS>::new_with_bit_width(store, SECTORS_AMT_BITWIDTH)
151                .flush()
152                .map_err(|e| {
153                    e.downcast_default(
154                        ExitCode::USR_ILLEGAL_STATE,
155                        "failed to construct sectors array",
156                    )
157                })?;
158        let empty_bitfield = store
159            .put_cbor(&BitField::new(), Code::Blake2b256)
160            .map_err(|e| {
161                e.downcast_default(
162                    ExitCode::USR_ILLEGAL_STATE,
163                    "failed to construct empty bitfield",
164                )
165            })?;
166        let deadline = Deadline::new(store)?;
167        let empty_deadline = store.put_cbor(&deadline, Code::Blake2b256).map_err(|e| {
168            e.downcast_default(
169                ExitCode::USR_ILLEGAL_STATE,
170                "failed to construct illegal state",
171            )
172        })?;
173
174        let empty_deadlines = store
175            .put_cbor(&Deadlines::new(policy, empty_deadline), Code::Blake2b256)
176            .map_err(|e| {
177                e.downcast_default(
178                    ExitCode::USR_ILLEGAL_STATE,
179                    "failed to construct illegal state",
180                )
181            })?;
182
183        let empty_vesting_funds_cid = store
184            .put_cbor(&VestingFunds::new(), Code::Blake2b256)
185            .map_err(|e| {
186                e.downcast_default(
187                    ExitCode::USR_ILLEGAL_STATE,
188                    "failed to construct illegal state",
189                )
190            })?;
191
192        Ok(Self {
193            info: info_cid,
194
195            pre_commit_deposits: TokenAmount::default(),
196            locked_funds: TokenAmount::default(),
197
198            vesting_funds: empty_vesting_funds_cid,
199
200            initial_pledge: TokenAmount::default(),
201            fee_debt: TokenAmount::default(),
202
203            pre_committed_sectors: empty_precommit_map,
204            allocated_sectors: empty_bitfield,
205            sectors: empty_sectors_array,
206            proving_period_start: period_start,
207            current_deadline: deadline_idx,
208            deadlines: empty_deadlines,
209            early_terminations: BitField::new(),
210            deadline_cron_active: false,
211            pre_committed_sectors_cleanup: empty_precommits_cleanup_array,
212        })
213    }
214
215    pub fn get_info<BS: Blockstore>(&self, store: &BS) -> anyhow::Result<MinerInfo> {
216        match store.get_cbor(&self.info) {
217            Ok(Some(info)) => Ok(info),
218            Ok(None) => Err(actor_error_v13!(not_found, "failed to get miner info").into()),
219            Err(e) => Err(e.downcast_wrap("failed to get miner info")),
220        }
221    }
222
223    pub fn save_info<BS: Blockstore>(
224        &mut self,
225        store: &BS,
226        info: &MinerInfo,
227    ) -> anyhow::Result<()> {
228        let cid = store.put_cbor(&info, Code::Blake2b256)?;
229        self.info = cid;
230        Ok(())
231    }
232
233    /// Returns deadline calculations for the current (according to state) proving period.
234    pub fn deadline_info(&self, policy: &Policy, current_epoch: ChainEpoch) -> DeadlineInfo {
235        new_deadline_info_from_offset_and_epoch(policy, self.proving_period_start, current_epoch)
236    }
237    // Returns deadline calculations for the state recorded proving period and deadline.
238    // This is out of date if the a miner does not have an active miner cron
239    pub fn recorded_deadline_info(
240        &self,
241        policy: &Policy,
242        current_epoch: ChainEpoch,
243    ) -> DeadlineInfo {
244        new_deadline_info(
245            policy,
246            self.proving_period_start,
247            self.current_deadline,
248            current_epoch,
249        )
250    }
251
252    // Returns current proving period start for the current epoch according to the current epoch and constant state offset
253    pub fn current_proving_period_start(
254        &self,
255        policy: &Policy,
256        current_epoch: ChainEpoch,
257    ) -> ChainEpoch {
258        let dl_info = self.deadline_info(policy, current_epoch);
259        dl_info.period_start
260    }
261
262    /// Returns deadline calculations for the current (according to state) proving period.
263    pub fn quant_spec_for_deadline(&self, policy: &Policy, deadline_idx: u64) -> QuantSpec {
264        new_deadline_info(policy, self.proving_period_start, deadline_idx, 0).quant_spec()
265    }
266
267    /// Marks a set of sector numbers as having been allocated.
268    /// If policy is `DenyCollisions`, fails if the set intersects with the sector numbers already allocated.
269    pub fn allocate_sector_numbers<BS: Blockstore>(
270        &mut self,
271        store: &BS,
272        sector_numbers: &BitField,
273        policy: CollisionPolicy,
274    ) -> Result<(), ActorError> {
275        let prior_allocation = store
276            .get_cbor(&self.allocated_sectors)
277            .map_err(|e| {
278                e.downcast_default(
279                    ExitCode::USR_ILLEGAL_STATE,
280                    "failed to load allocated sectors bitfield",
281                )
282            })?
283            .ok_or_else(|| {
284                actor_error_v13!(illegal_state, "allocated sectors bitfield not found")
285            })?;
286
287        if policy != CollisionPolicy::AllowCollisions {
288            // NOTE: A fancy merge algorithm could extract this intersection while merging, below, saving
289            // one iteration of the runs
290            let collisions = &prior_allocation & sector_numbers;
291            if !collisions.is_empty() {
292                return Err(actor_error_v13!(
293                    illegal_argument,
294                    "sector numbers {:?} already allocated",
295                    collisions
296                ));
297            }
298        }
299        let new_allocation = &prior_allocation | sector_numbers;
300        self.allocated_sectors =
301            store
302                .put_cbor(&new_allocation, Code::Blake2b256)
303                .map_err(|e| {
304                    e.downcast_default(
305                        ExitCode::USR_ILLEGAL_ARGUMENT,
306                        format!(
307                            "failed to store allocated sectors bitfield after adding {:?}",
308                            sector_numbers,
309                        ),
310                    )
311                })?;
312        Ok(())
313    }
314
315    /// Stores a pre-committed sector info, failing if the sector number is already present.
316    pub fn put_precommitted_sectors<BS: Blockstore>(
317        &mut self,
318        store: &BS,
319        precommits: Vec<SectorPreCommitOnChainInfo>,
320    ) -> anyhow::Result<()> {
321        let mut precommitted =
322            make_map_with_root_and_bitwidth(&self.pre_committed_sectors, store, HAMT_BIT_WIDTH)?;
323        for precommit in precommits.into_iter() {
324            let sector_no = precommit.info.sector_number;
325            let modified = precommitted
326                .set_if_absent(u64_key(precommit.info.sector_number), precommit)
327                .map_err(|e| {
328                    e.downcast_wrap(format!("failed to store precommitment for {:?}", sector_no,))
329                })?;
330            if !modified {
331                return Err(anyhow!("sector {} already pre-commited", sector_no));
332            }
333        }
334
335        self.pre_committed_sectors = precommitted.flush()?;
336        Ok(())
337    }
338
339    pub fn get_precommitted_sector<BS: Blockstore>(
340        &self,
341        store: &BS,
342        sector_num: SectorNumber,
343    ) -> Result<Option<SectorPreCommitOnChainInfo>, HamtError> {
344        let precommitted =
345            make_map_with_root_and_bitwidth(&self.pre_committed_sectors, store, HAMT_BIT_WIDTH)?;
346        Ok(precommitted.get(&u64_key(sector_num))?.cloned())
347    }
348
349    /// Gets and returns the requested pre-committed sectors, skipping missing sectors.
350    pub fn find_precommitted_sectors<BS: Blockstore>(
351        &self,
352        store: &BS,
353        sector_numbers: &[SectorNumber],
354    ) -> anyhow::Result<Vec<SectorPreCommitOnChainInfo>> {
355        let precommitted = make_map_with_root_and_bitwidth::<_, SectorPreCommitOnChainInfo>(
356            &self.pre_committed_sectors,
357            store,
358            HAMT_BIT_WIDTH,
359        )?;
360        let mut result = Vec::with_capacity(sector_numbers.len());
361
362        for &sector_number in sector_numbers {
363            let info = match precommitted.get(&u64_key(sector_number)).map_err(|e| {
364                e.downcast_wrap(format!(
365                    "failed to load precommitment for {}",
366                    sector_number
367                ))
368            })? {
369                Some(info) => info.clone(),
370                None => continue,
371            };
372
373            result.push(info);
374        }
375
376        Ok(result)
377    }
378
379    pub fn delete_precommitted_sectors<BS: Blockstore>(
380        &mut self,
381        store: &BS,
382        sector_nums: &[SectorNumber],
383    ) -> Result<(), HamtError> {
384        let mut precommitted = make_map_with_root_and_bitwidth::<_, SectorPreCommitOnChainInfo>(
385            &self.pre_committed_sectors,
386            store,
387            HAMT_BIT_WIDTH,
388        )?;
389
390        for &sector_num in sector_nums {
391            let prev_entry = precommitted.delete(&u64_key(sector_num))?;
392            if prev_entry.is_none() {
393                return Err(format!("sector {} doesn't exist", sector_num).into());
394            }
395        }
396
397        self.pre_committed_sectors = precommitted.flush()?;
398        Ok(())
399    }
400
401    pub fn has_sector_number<BS: Blockstore>(
402        &self,
403        store: &BS,
404        sector_num: SectorNumber,
405    ) -> anyhow::Result<bool> {
406        let sectors = Sectors::load(store, &self.sectors)?;
407        Ok(sectors.get(sector_num)?.is_some())
408    }
409
410    pub fn put_sectors<BS: Blockstore>(
411        &mut self,
412        store: &BS,
413        new_sectors: Vec<SectorOnChainInfo>,
414    ) -> anyhow::Result<()> {
415        let mut sectors = Sectors::load(store, &self.sectors)
416            .map_err(|e| e.downcast_wrap("failed to load sectors"))?;
417
418        sectors.store(new_sectors)?;
419
420        self.sectors = sectors
421            .amt
422            .flush()
423            .map_err(|e| e.downcast_wrap("failed to persist sectors"))?;
424
425        Ok(())
426    }
427
428    pub fn get_sector<BS: Blockstore>(
429        &self,
430        store: &BS,
431        sector_num: SectorNumber,
432    ) -> Result<Option<SectorOnChainInfo>, ActorError> {
433        let sectors = Sectors::load(store, &self.sectors)
434            .context_code(ExitCode::USR_ILLEGAL_STATE, "loading sectors")?;
435        sectors.get(sector_num)
436    }
437
438    pub fn delete_sectors<BS: Blockstore>(
439        &mut self,
440        store: &BS,
441        sector_nos: &BitField,
442    ) -> Result<(), AmtError> {
443        let mut sectors = Sectors::load(store, &self.sectors)?;
444
445        for sector_num in sector_nos.iter() {
446            let deleted_sector = sectors
447                .amt
448                .delete(sector_num)
449                .map_err(|e| e.downcast_wrap("could not delete sector number"))?;
450            if deleted_sector.is_none() {
451                return Err(AmtError::Dynamic(Error::msg(format!(
452                    "sector {} doesn't exist, failed to delete",
453                    sector_num
454                ))));
455            }
456        }
457
458        self.sectors = sectors.amt.flush()?;
459        Ok(())
460    }
461
462    pub fn for_each_sector<BS: Blockstore, F>(&self, store: &BS, mut f: F) -> anyhow::Result<()>
463    where
464        F: FnMut(&SectorOnChainInfo) -> anyhow::Result<()>,
465    {
466        let sectors = Sectors::load(store, &self.sectors)?;
467        sectors.amt.for_each(|_, v| f(v))?;
468        Ok(())
469    }
470
471    /// Returns the deadline and partition index for a sector number.
472    pub fn find_sector<BS: Blockstore>(
473        &self,
474        store: &BS,
475        sector_number: SectorNumber,
476    ) -> anyhow::Result<(u64, u64)> {
477        let deadlines = self.load_deadlines(store)?;
478        deadlines.find_sector(store, sector_number)
479    }
480
481    /// Schedules each sector to expire at its next deadline end. If it can't find
482    /// any given sector, it skips it.
483    ///
484    /// This method assumes that each sector's power has not changed, despite the rescheduling.
485    ///
486    /// Note: this method is used to "upgrade" sectors, rescheduling the now-replaced
487    /// sectors to expire at the end of the next deadline. Given the expense of
488    /// sealing a sector, this function skips missing/faulty/terminated "upgraded"
489    /// sectors instead of failing. That way, the new sectors can still be proved.
490    pub fn reschedule_sector_expirations<BS: Blockstore>(
491        &mut self,
492        policy: &Policy,
493        store: &BS,
494        current_epoch: ChainEpoch,
495        sector_size: SectorSize,
496        mut deadline_sectors: DeadlineSectorMap,
497    ) -> anyhow::Result<Vec<SectorOnChainInfo>> {
498        let mut deadlines = self.load_deadlines(store)?;
499        let sectors = Sectors::load(store, &self.sectors)?;
500
501        let mut all_replaced = Vec::new();
502        for (deadline_idx, partition_sectors) in deadline_sectors.iter() {
503            let deadline_info = new_deadline_info(
504                policy,
505                self.current_proving_period_start(policy, current_epoch),
506                deadline_idx,
507                current_epoch,
508            )
509            .next_not_elapsed();
510            let new_expiration = deadline_info.last();
511            let mut deadline = deadlines.load_deadline(store, deadline_idx)?;
512
513            let replaced = deadline.reschedule_sector_expirations(
514                store,
515                &sectors,
516                new_expiration,
517                partition_sectors,
518                sector_size,
519                deadline_info.quant_spec(),
520            )?;
521            all_replaced.extend(replaced);
522
523            deadlines.update_deadline(policy, store, deadline_idx, &deadline)?;
524        }
525
526        self.save_deadlines(store, deadlines)?;
527
528        Ok(all_replaced)
529    }
530
531    /// Assign new sectors to deadlines.
532    pub fn assign_sectors_to_deadlines<BS: Blockstore>(
533        &mut self,
534        policy: &Policy,
535        store: &BS,
536        current_epoch: ChainEpoch,
537        mut sectors: Vec<SectorOnChainInfo>,
538        partition_size: u64,
539        sector_size: SectorSize,
540    ) -> anyhow::Result<()> {
541        let mut deadlines = self.load_deadlines(store)?;
542
543        // Sort sectors by number to get better runs in partition bitfields.
544        sectors.sort_by_key(|info| info.sector_number);
545
546        let mut deadline_vec: Vec<Option<Deadline>> =
547            (0..policy.wpost_period_deadlines).map(|_| None).collect();
548
549        deadlines.for_each(store, |deadline_idx, deadline| {
550            // Skip deadlines that aren't currently mutable.
551            if deadline_is_mutable(
552                policy,
553                self.current_proving_period_start(policy, current_epoch),
554                deadline_idx,
555                current_epoch,
556            ) {
557                deadline_vec[deadline_idx as usize] = Some(deadline);
558            }
559
560            Ok(())
561        })?;
562
563        let deadline_to_sectors = assign_deadlines(
564            policy,
565            policy.max_partitions_per_deadline,
566            partition_size,
567            &deadline_vec,
568            sectors,
569        )?;
570
571        for (deadline_idx, deadline_sectors) in deadline_to_sectors.into_iter().enumerate() {
572            if deadline_sectors.is_empty() {
573                continue;
574            }
575
576            let quant = self.quant_spec_for_deadline(policy, deadline_idx as u64);
577            let deadline = deadline_vec[deadline_idx].as_mut().unwrap();
578
579            // The power returned from AddSectors is ignored because it's not activated (proven) yet.
580            let proven = false;
581            deadline.add_sectors(
582                store,
583                partition_size,
584                proven,
585                &deadline_sectors,
586                sector_size,
587                quant,
588            )?;
589
590            deadlines.update_deadline(policy, store, deadline_idx as u64, deadline)?;
591        }
592
593        self.save_deadlines(store, deadlines)?;
594
595        Ok(())
596    }
597
598    /// Pops up to `max_sectors` early terminated sectors from all deadlines.
599    ///
600    /// Returns `true` if we still have more early terminations to process.
601    pub fn pop_early_terminations<BS: Blockstore>(
602        &mut self,
603        policy: &Policy,
604        store: &BS,
605        max_partitions: u64,
606        max_sectors: u64,
607    ) -> anyhow::Result<(TerminationResult, /* has more */ bool)> {
608        // Anything to do? This lets us avoid loading the deadlines if there's nothing to do.
609        if self.early_terminations.is_empty() {
610            return Ok((Default::default(), false));
611        }
612
613        // Load deadlines
614        let mut deadlines = self.load_deadlines(store)?;
615
616        let mut result = TerminationResult::new();
617        let mut to_unset = Vec::new();
618
619        // Process early terminations.
620        for i in self.early_terminations.iter() {
621            let deadline_idx = i;
622
623            // Load deadline + partitions.
624            let mut deadline = deadlines.load_deadline(store, deadline_idx)?;
625
626            let (deadline_result, more) = deadline
627                .pop_early_terminations(
628                    store,
629                    max_partitions - result.partitions_processed,
630                    max_sectors - result.sectors_processed,
631                )
632                .map_err(|e| {
633                    e.downcast_wrap(format!(
634                        "failed to pop early terminations for deadline {}",
635                        deadline_idx
636                    ))
637                })?;
638
639            result += deadline_result;
640
641            if !more {
642                to_unset.push(i);
643            }
644
645            // Save the deadline
646            deadlines.update_deadline(policy, store, deadline_idx, &deadline)?;
647
648            if !result.below_limit(max_partitions, max_sectors) {
649                break;
650            }
651        }
652
653        for deadline_idx in to_unset {
654            self.early_terminations.unset(deadline_idx);
655        }
656
657        // Save back the deadlines.
658        self.save_deadlines(store, deadlines)?;
659
660        // Ok, check to see if we've handled all early terminations.
661        let no_early_terminations = self.early_terminations.is_empty();
662
663        Ok((result, !no_early_terminations))
664    }
665
666    /// Returns an error if the target sector cannot be found, or some other bad state is reached.
667    /// Returns Ok(false) if the target sector is faulty, terminated, or unproven
668    /// Returns Ok(true) otherwise
669    pub fn check_sector_active<BS: Blockstore>(
670        &self,
671        store: &BS,
672        deadline_idx: u64,
673        partition_idx: u64,
674        sector_number: SectorNumber,
675        require_proven: bool,
676    ) -> Result<bool, ActorError> {
677        let dls = self.load_deadlines(store)?;
678        let dl = dls.load_deadline(store, deadline_idx)?;
679        let partition = dl.load_partition(store, partition_idx)?;
680
681        let exists = partition.sectors.get(sector_number);
682        if !exists {
683            return Err(actor_error_v13!(
684                not_found;
685                "sector {} not a member of partition {}, deadline {}",
686                sector_number, partition_idx, deadline_idx
687            ));
688        }
689
690        let faulty = partition.faults.get(sector_number);
691        if faulty {
692            return Ok(false);
693        }
694
695        let terminated = partition.terminated.get(sector_number);
696        if terminated {
697            return Ok(false);
698        }
699
700        let unproven = partition.unproven.get(sector_number);
701        if unproven && require_proven {
702            return Ok(false);
703        }
704
705        Ok(true)
706    }
707
708    /// Returns an error if the target sector cannot be found and/or is faulty/terminated.
709    pub fn check_sector_health<BS: Blockstore>(
710        &self,
711        store: &BS,
712        deadline_idx: u64,
713        partition_idx: u64,
714        sector_number: SectorNumber,
715    ) -> anyhow::Result<()> {
716        let deadlines = self.load_deadlines(store)?;
717        let deadline = deadlines.load_deadline(store, deadline_idx)?;
718        let partition = deadline.load_partition(store, partition_idx)?;
719
720        if !partition.sectors.get(sector_number) {
721            return Err(actor_error_v13!(
722                not_found;
723                "sector {} not a member of partition {}, deadline {}",
724                sector_number, partition_idx, deadline_idx
725            )
726            .into());
727        }
728
729        if partition.faults.get(sector_number) {
730            return Err(actor_error_v13!(
731                forbidden;
732                "sector {} not a member of partition {}, deadline {}",
733                sector_number, partition_idx, deadline_idx
734            )
735            .into());
736        }
737
738        if partition.terminated.get(sector_number) {
739            return Err(actor_error_v13!(
740                not_found;
741                "sector {} not of partition {}, deadline {} is terminated",
742                sector_number, partition_idx, deadline_idx
743            )
744            .into());
745        }
746
747        Ok(())
748    }
749
750    /// Loads sector info for a sequence of sectors.
751    pub fn load_sector_infos<BS: Blockstore>(
752        &self,
753        store: &BS,
754        sectors: &BitField,
755    ) -> anyhow::Result<Vec<SectorOnChainInfo>> {
756        Ok(Sectors::load(store, &self.sectors)?.load_sector(sectors)?)
757    }
758
759    pub fn load_deadlines<BS: Blockstore>(&self, store: &BS) -> Result<Deadlines, ActorError> {
760        store
761            .get_cbor::<Deadlines>(&self.deadlines)
762            .map_err(|e| {
763                e.downcast_default(ExitCode::USR_ILLEGAL_STATE, "failed to load deadlines")
764            })?
765            .ok_or_else(
766                || actor_error_v13!(illegal_state; "failed to load deadlines {}", self.deadlines),
767            )
768    }
769
770    pub fn save_deadlines<BS: Blockstore>(
771        &mut self,
772        store: &BS,
773        deadlines: Deadlines,
774    ) -> anyhow::Result<()> {
775        self.deadlines = store.put_cbor(&deadlines, Code::Blake2b256)?;
776        Ok(())
777    }
778
779    /// Loads the vesting funds table from the store.
780    pub fn load_vesting_funds<BS: Blockstore>(&self, store: &BS) -> anyhow::Result<VestingFunds> {
781        Ok(store
782            .get_cbor(&self.vesting_funds)
783            .map_err(|e| {
784                e.downcast_wrap(format!("failed to load vesting funds {}", self.vesting_funds))
785            })?
786            .ok_or_else(
787                || actor_error_v13!(not_found; "failed to load vesting funds {:?}", self.vesting_funds),
788            )?)
789    }
790
791    /// Saves the vesting table to the store.
792    pub fn save_vesting_funds<BS: Blockstore>(
793        &mut self,
794        store: &BS,
795        funds: &VestingFunds,
796    ) -> anyhow::Result<()> {
797        self.vesting_funds = store.put_cbor(funds, Code::Blake2b256)?;
798        Ok(())
799    }
800
801    // Return true when the miner actor needs to continue scheduling deadline crons
802    pub fn continue_deadline_cron(&self) -> bool {
803        !self.pre_commit_deposits.is_zero()
804            || !self.initial_pledge.is_zero()
805            || !self.locked_funds.is_zero()
806    }
807
808    //
809    // Funds and vesting
810    //
811
812    pub fn add_pre_commit_deposit(&mut self, amount: &TokenAmount) -> anyhow::Result<()> {
813        let new_total = &self.pre_commit_deposits + amount;
814        if new_total.is_negative() {
815            return Err(anyhow!(
816                "negative pre-commit deposit {} after adding {} to prior {}",
817                new_total,
818                amount,
819                self.pre_commit_deposits
820            ));
821        }
822        self.pre_commit_deposits = new_total;
823        Ok(())
824    }
825
826    pub fn add_initial_pledge(&mut self, amount: &TokenAmount) -> anyhow::Result<()> {
827        let new_total = &self.initial_pledge + amount;
828        if new_total.is_negative() {
829            return Err(anyhow!(
830                "negative initial pledge requirement {} after adding {} to prior {}",
831                new_total,
832                amount,
833                self.initial_pledge
834            ));
835        }
836        self.initial_pledge = new_total;
837        Ok(())
838    }
839
840    pub fn apply_penalty(&mut self, penalty: &TokenAmount) -> anyhow::Result<()> {
841        if penalty.is_negative() {
842            Err(anyhow!("applying negative penalty {} not allowed", penalty))
843        } else {
844            self.fee_debt += penalty;
845            Ok(())
846        }
847    }
848
849    /// First vests and unlocks the vested funds AND then locks the given funds in the vesting table.
850    pub fn add_locked_funds<BS: Blockstore>(
851        &mut self,
852        store: &BS,
853        current_epoch: ChainEpoch,
854        vesting_sum: &TokenAmount,
855        spec: &VestSpec,
856    ) -> anyhow::Result<TokenAmount> {
857        if vesting_sum.is_negative() {
858            return Err(anyhow!("negative vesting sum {}", vesting_sum));
859        }
860
861        let mut vesting_funds = self.load_vesting_funds(store)?;
862
863        // unlock vested funds first
864        let amount_unlocked = vesting_funds.unlock_vested_funds(current_epoch);
865        self.locked_funds -= &amount_unlocked;
866        if self.locked_funds.is_negative() {
867            return Err(anyhow!(
868                "negative locked funds {} after unlocking {}",
869                self.locked_funds,
870                amount_unlocked
871            ));
872        }
873        // add locked funds now
874        vesting_funds.add_locked_funds(current_epoch, vesting_sum, self.proving_period_start, spec);
875        self.locked_funds += vesting_sum;
876
877        // save the updated vesting table state
878        self.save_vesting_funds(store, &vesting_funds)?;
879
880        Ok(amount_unlocked)
881    }
882
883    /// Draws from vesting table and unlocked funds to repay up to the fee debt.
884    /// Returns the amount unlocked from the vesting table and the amount taken from
885    /// current balance. If the fee debt exceeds the total amount available for repayment
886    /// the fee debt field is updated to track the remaining debt.  Otherwise it is set to zero.
887    pub fn repay_partial_debt_in_priority_order<BS: Blockstore>(
888        &mut self,
889        store: &BS,
890        current_epoch: ChainEpoch,
891        curr_balance: &TokenAmount,
892    ) -> Result<
893        (
894            TokenAmount, // from vesting
895            TokenAmount, // from balance
896        ),
897        anyhow::Error,
898    > {
899        let unlocked_balance = self.get_unlocked_balance(curr_balance)?;
900
901        let fee_debt = self.fee_debt.clone();
902        let from_vesting = self.unlock_unvested_funds(store, current_epoch, &fee_debt)?;
903
904        if from_vesting > self.fee_debt {
905            return Err(anyhow!(
906                "should never unlock more than the debt we need to repay"
907            ));
908        }
909        self.fee_debt -= &from_vesting;
910
911        let from_balance = cmp::min(&unlocked_balance, &self.fee_debt).clone();
912        self.fee_debt -= &from_balance;
913
914        Ok((from_vesting, from_balance))
915    }
916
917    /// Repays the full miner actor fee debt.  Returns the amount that must be
918    /// burnt and an error if there are not sufficient funds to cover repayment.
919    /// Miner state repays from unlocked funds and fails if unlocked funds are insufficient to cover fee debt.
920    /// FeeDebt will be zero after a successful call.
921    pub fn repay_debts(&mut self, curr_balance: &TokenAmount) -> anyhow::Result<TokenAmount> {
922        let unlocked_balance = self.get_unlocked_balance(curr_balance)?;
923        if unlocked_balance < self.fee_debt {
924            return Err(actor_error_v13!(
925                insufficient_funds,
926                "unlocked balance can not repay fee debt ({} < {})",
927                unlocked_balance,
928                self.fee_debt
929            )
930            .into());
931        }
932
933        Ok(std::mem::take(&mut self.fee_debt))
934    }
935    /// Unlocks an amount of funds that have *not yet vested*, if possible.
936    /// The soonest-vesting entries are unlocked first.
937    /// Returns the amount actually unlocked.
938    pub fn unlock_unvested_funds<BS: Blockstore>(
939        &mut self,
940        store: &BS,
941        current_epoch: ChainEpoch,
942        target: &TokenAmount,
943    ) -> anyhow::Result<TokenAmount> {
944        if target.is_zero() || self.locked_funds.is_zero() {
945            return Ok(TokenAmount::zero());
946        }
947
948        let mut vesting_funds = self.load_vesting_funds(store)?;
949        let amount_unlocked = vesting_funds.unlock_unvested_funds(current_epoch, target);
950        self.locked_funds -= &amount_unlocked;
951        if self.locked_funds.is_negative() {
952            return Err(anyhow!(
953                "negative locked funds {} after unlocking {}",
954                self.locked_funds,
955                amount_unlocked
956            ));
957        }
958
959        self.save_vesting_funds(store, &vesting_funds)?;
960        Ok(amount_unlocked)
961    }
962
963    /// Unlocks all vesting funds that have vested before the provided epoch.
964    /// Returns the amount unlocked.
965    pub fn unlock_vested_funds<BS: Blockstore>(
966        &mut self,
967        store: &BS,
968        current_epoch: ChainEpoch,
969    ) -> anyhow::Result<TokenAmount> {
970        if self.locked_funds.is_zero() {
971            return Ok(TokenAmount::zero());
972        }
973
974        let mut vesting_funds = self.load_vesting_funds(store)?;
975        let amount_unlocked = vesting_funds.unlock_vested_funds(current_epoch);
976        self.locked_funds -= &amount_unlocked;
977        if self.locked_funds.is_negative() {
978            return Err(anyhow!(
979                "vesting cause locked funds to become negative: {}",
980                self.locked_funds,
981            ));
982        }
983
984        self.save_vesting_funds(store, &vesting_funds)?;
985        Ok(amount_unlocked)
986    }
987
988    /// CheckVestedFunds returns the amount of vested funds that have vested before the provided epoch.
989    pub fn check_vested_funds<BS: Blockstore>(
990        &self,
991        store: &BS,
992        current_epoch: ChainEpoch,
993    ) -> anyhow::Result<TokenAmount> {
994        let vesting_funds = self.load_vesting_funds(store)?;
995        Ok(vesting_funds
996            .funds
997            .iter()
998            .take_while(|fund| fund.epoch < current_epoch)
999            .fold(TokenAmount::zero(), |acc, fund| acc + &fund.amount))
1000    }
1001
1002    /// Unclaimed funds that are not locked -- includes funds used to cover initial pledge requirement.
1003    pub fn get_unlocked_balance(&self, actor_balance: &TokenAmount) -> anyhow::Result<TokenAmount> {
1004        let unlocked_balance =
1005            actor_balance - &self.locked_funds - &self.pre_commit_deposits - &self.initial_pledge;
1006        if unlocked_balance.is_negative() {
1007            return Err(anyhow!("negative unlocked balance {}", unlocked_balance));
1008        }
1009        Ok(unlocked_balance)
1010    }
1011
1012    /// Unclaimed funds. Actor balance - (locked funds, precommit deposit, ip requirement)
1013    /// Can go negative if the miner is in IP debt.
1014    pub fn get_available_balance(
1015        &self,
1016        actor_balance: &TokenAmount,
1017    ) -> anyhow::Result<TokenAmount> {
1018        // (actor_balance - &self.locked_funds) - &self.pre_commit_deposit - &self.initial_pledge
1019        Ok(self.get_unlocked_balance(actor_balance)? - &self.fee_debt)
1020    }
1021
1022    pub fn check_balance_invariants(&self, balance: &TokenAmount) -> anyhow::Result<()> {
1023        if self.pre_commit_deposits.is_negative() {
1024            return Err(anyhow!(
1025                "pre-commit deposit is negative: {}",
1026                self.pre_commit_deposits
1027            ));
1028        }
1029        if self.locked_funds.is_negative() {
1030            return Err(anyhow!("locked funds is negative: {}", self.locked_funds));
1031        }
1032        if self.initial_pledge.is_negative() {
1033            return Err(anyhow!(
1034                "initial pledge is negative: {}",
1035                self.initial_pledge
1036            ));
1037        }
1038        if self.fee_debt.is_negative() {
1039            return Err(anyhow!("fee debt is negative: {}", self.fee_debt));
1040        }
1041
1042        let min_balance = &self.pre_commit_deposits + &self.locked_funds + &self.initial_pledge;
1043        if balance < &min_balance {
1044            return Err(anyhow!("fee debt is negative: {}", self.fee_debt));
1045        }
1046
1047        Ok(())
1048    }
1049
1050    /// pre-commit expiry
1051    pub fn quant_spec_every_deadline(&self, policy: &Policy) -> QuantSpec {
1052        QuantSpec {
1053            unit: policy.wpost_challenge_window,
1054            offset: self.proving_period_start,
1055        }
1056    }
1057
1058    pub fn add_pre_commit_clean_ups<BS: Blockstore>(
1059        &mut self,
1060        policy: &Policy,
1061        store: &BS,
1062        cleanup_events: Vec<(ChainEpoch, u64)>,
1063    ) -> anyhow::Result<()> {
1064        // Load BitField Queue for sector expiry
1065        let quant = self.quant_spec_every_deadline(policy);
1066        let mut queue =
1067            super::BitFieldQueue::new(store, &self.pre_committed_sectors_cleanup, quant)
1068                .map_err(|e| e.downcast_wrap("failed to load pre-commit clean up queue"))?;
1069
1070        queue.add_many_to_queue_values(cleanup_events.into_iter())?;
1071        self.pre_committed_sectors_cleanup = queue.amt.flush()?;
1072        Ok(())
1073    }
1074
1075    pub fn cleanup_expired_pre_commits<BS: Blockstore>(
1076        &mut self,
1077        policy: &Policy,
1078        store: &BS,
1079        current_epoch: ChainEpoch,
1080    ) -> anyhow::Result<TokenAmount> {
1081        let mut deposit_to_burn = TokenAmount::zero();
1082
1083        // cleanup expired pre-committed sectors
1084        let mut cleanup_queue = BitFieldQueue::new(
1085            store,
1086            &self.pre_committed_sectors_cleanup,
1087            self.quant_spec_every_deadline(policy),
1088        )?;
1089
1090        let (sectors, modified) = cleanup_queue.pop_until(current_epoch)?;
1091
1092        if modified {
1093            self.pre_committed_sectors_cleanup = cleanup_queue.amt.flush()?;
1094        }
1095
1096        let mut precommits_to_delete = Vec::new();
1097        let precommitted =
1098            make_map_with_root_and_bitwidth(&self.pre_committed_sectors, store, HAMT_BIT_WIDTH)?;
1099
1100        for i in sectors.iter() {
1101            let sector_number = i as SectorNumber;
1102
1103            let sector: SectorPreCommitOnChainInfo =
1104                match precommitted.get(&u64_key(sector_number))?.cloned() {
1105                    Some(sector) => sector,
1106                    // already committed/deleted
1107                    None => continue,
1108                };
1109
1110            // mark it for deletion
1111            precommits_to_delete.push(sector_number);
1112
1113            // increment deposit to burn
1114            deposit_to_burn += sector.pre_commit_deposit;
1115        }
1116
1117        // Actually delete it.
1118        if !precommits_to_delete.is_empty() {
1119            self.delete_precommitted_sectors(store, &precommits_to_delete)?;
1120        }
1121
1122        self.pre_commit_deposits -= &deposit_to_burn;
1123        if self.pre_commit_deposits.is_negative() {
1124            return Err(anyhow!(
1125                "pre-commit clean up caused negative deposits: {}",
1126                self.pre_commit_deposits
1127            ));
1128        }
1129
1130        Ok(deposit_to_burn)
1131    }
1132
1133    pub fn advance_deadline<BS: Blockstore>(
1134        &mut self,
1135        policy: &Policy,
1136        store: &BS,
1137        current_epoch: ChainEpoch,
1138    ) -> anyhow::Result<AdvanceDeadlineResult> {
1139        let mut pledge_delta = TokenAmount::zero();
1140
1141        let dl_info = self.deadline_info(policy, current_epoch);
1142
1143        if !dl_info.period_started() {
1144            return Ok(AdvanceDeadlineResult {
1145                pledge_delta,
1146                power_delta: PowerPair::zero(),
1147                previously_faulty_power: PowerPair::zero(),
1148                detected_faulty_power: PowerPair::zero(),
1149                total_faulty_power: PowerPair::zero(),
1150            });
1151        }
1152
1153        self.current_deadline = (dl_info.index + 1) % policy.wpost_period_deadlines;
1154        if self.current_deadline == 0 {
1155            self.proving_period_start = dl_info.period_start + policy.wpost_proving_period;
1156        }
1157
1158        let mut deadlines = self.load_deadlines(store)?;
1159
1160        let mut deadline = deadlines.load_deadline(store, dl_info.index)?;
1161
1162        let previously_faulty_power = deadline.faulty_power.clone();
1163
1164        if !deadline.is_live() {
1165            return Ok(AdvanceDeadlineResult {
1166                pledge_delta,
1167                power_delta: PowerPair::zero(),
1168                previously_faulty_power,
1169                detected_faulty_power: PowerPair::zero(),
1170                total_faulty_power: deadline.faulty_power,
1171            });
1172        }
1173
1174        let quant = quant_spec_for_deadline(policy, &dl_info);
1175
1176        // Detect and penalize missing proofs.
1177        let fault_expiration = dl_info.last() + policy.fault_max_age;
1178
1179        let (mut power_delta, detected_faulty_power) =
1180            deadline.process_deadline_end(store, quant, fault_expiration, self.sectors)?;
1181
1182        // Capture deadline's faulty power after new faults have been detected, but before it is
1183        // dropped along with faulty sectors expiring this round.
1184        let total_faulty_power = deadline.faulty_power.clone();
1185
1186        // Expire sectors that are due, either for on-time expiration or "early" faulty-for-too-long.
1187        let expired = deadline.pop_expired_sectors(store, dl_info.last(), quant)?;
1188
1189        // Release pledge requirements for the sectors expiring on-time.
1190        // Pledge for the sectors expiring early is retained to support the termination fee that
1191        // will be assessed when the early termination is processed.
1192        pledge_delta -= &expired.on_time_pledge;
1193        self.add_initial_pledge(&expired.on_time_pledge.neg())?;
1194
1195        // Record reduction in power of the amount of expiring active power.
1196        // Faulty power has already been lost, so the amount expiring can be excluded from the delta.
1197        power_delta -= &expired.active_power;
1198
1199        let no_early_terminations = expired.early_sectors.is_empty();
1200        if !no_early_terminations {
1201            self.early_terminations.set(dl_info.index);
1202        }
1203
1204        deadlines.update_deadline(policy, store, dl_info.index, &deadline)?;
1205
1206        self.save_deadlines(store, deadlines)?;
1207
1208        Ok(AdvanceDeadlineResult {
1209            pledge_delta,
1210            power_delta,
1211            previously_faulty_power,
1212            detected_faulty_power,
1213            total_faulty_power,
1214        })
1215    }
1216
1217    // Loads sectors precommit information from store, requiring it to exist.
1218    pub fn get_precommitted_sectors<BS: Blockstore>(
1219        &self,
1220        store: &BS,
1221        sector_nos: impl IntoIterator<Item = impl Borrow<SectorNumber>>,
1222    ) -> Result<Vec<SectorPreCommitOnChainInfo>, ActorError> {
1223        let mut precommits = Vec::new();
1224        let precommitted =
1225            make_map_with_root_and_bitwidth(&self.pre_committed_sectors, store, HAMT_BIT_WIDTH)
1226                .context_code(
1227                    ExitCode::USR_ILLEGAL_STATE,
1228                    "failed to load precommitted sectors",
1229                )?;
1230        for sector_no in sector_nos.into_iter() {
1231            let sector_no = *sector_no.borrow();
1232            if sector_no > MAX_SECTOR_NUMBER {
1233                return Err(
1234                    actor_error_v13!(illegal_argument; "sector number greater than maximum"),
1235                );
1236            }
1237            let info: &SectorPreCommitOnChainInfo = precommitted
1238                .get(&u64_key(sector_no))
1239                .exit_code(ExitCode::USR_ILLEGAL_STATE)?
1240                .ok_or_else(|| actor_error_v13!(not_found, "sector {} not found", sector_no))?;
1241            precommits.push(info.clone());
1242        }
1243        Ok(precommits)
1244    }
1245}
1246
1247pub struct AdvanceDeadlineResult {
1248    pub pledge_delta: TokenAmount,
1249    pub power_delta: PowerPair,
1250    /// Power that was faulty before this advance (including recovering)
1251    pub previously_faulty_power: PowerPair,
1252    /// Power of new faults and failed recoveries
1253    pub detected_faulty_power: PowerPair,
1254    /// Total faulty power after detecting faults (before expiring sectors)
1255    /// Note that failed recovery power is included in both PreviouslyFaultyPower and
1256    /// DetectedFaultyPower, so TotalFaultyPower is not simply their sum.
1257    pub total_faulty_power: PowerPair,
1258}
1259
1260/// Static information about miner
1261#[derive(Debug, PartialEq, Eq, Serialize_tuple, Deserialize_tuple)]
1262pub struct MinerInfo {
1263    /// Account that owns this miner
1264    /// - Income and returned collateral are paid to this address
1265    /// - This address is also allowed to change the worker address for the miner
1266    pub owner: Address,
1267
1268    /// Worker account for this miner
1269    /// This will be the key that is used to sign blocks created by this miner, and
1270    /// sign messages sent on behalf of this miner to commit sectors, submit PoSts, and
1271    /// other day to day miner activities
1272    pub worker: Address,
1273
1274    /// Additional addresses that are permitted to submit messages controlling this actor (optional).
1275    pub control_addresses: Vec<Address>, // Must all be ID addresses.
1276
1277    /// Optional worker key to update at an epoch
1278    pub pending_worker_key: Option<WorkerKeyChange>,
1279
1280    /// Libp2p identity that should be used when connecting to this miner
1281    #[serde(with = "strict_bytes")]
1282    pub peer_id: Vec<u8>,
1283
1284    /// Vector of byte arrays representing Libp2p multi-addresses used for establishing a connection with this miner.
1285    pub multi_address: Vec<BytesDe>,
1286
1287    /// The proof type used by this miner for sealing sectors.
1288    pub window_post_proof_type: RegisteredPoStProof,
1289
1290    /// Amount of space in each sector committed to the network by this miner
1291    pub sector_size: SectorSize,
1292
1293    /// The number of sectors in each Window PoSt partition (proof).
1294    /// This is computed from the proof type and represented here redundantly.
1295    pub window_post_partition_sectors: u64,
1296
1297    /// The next epoch this miner is eligible for certain permissioned actor methods
1298    /// and winning block elections as a result of being reported for a consensus fault.
1299    pub consensus_fault_elapsed: ChainEpoch,
1300
1301    /// A proposed new owner account for this miner.
1302    /// Must be confirmed by a message from the pending address itself.
1303    pub pending_owner_address: Option<Address>,
1304
1305    /// Account for receive miner benefits, withdraw on miner must send to this address,
1306    /// set owner address by default when create miner
1307    pub beneficiary: Address,
1308
1309    /// beneficiary's total quota, how much quota has been withdraw,
1310    /// and when this beneficiary expired
1311    pub beneficiary_term: BeneficiaryTerm,
1312
1313    /// A proposal new beneficiary message for this miner
1314    pub pending_beneficiary_term: Option<PendingBeneficiaryChange>,
1315}
1316
1317impl MinerInfo {
1318    pub fn new(
1319        owner: ActorID,
1320        worker: ActorID,
1321        control_addresses: Vec<ActorID>,
1322        peer_id: Vec<u8>,
1323        multi_address: Vec<BytesDe>,
1324        window_post_proof_type: RegisteredPoStProof,
1325    ) -> Result<Self, ActorError> {
1326        let sector_size = window_post_proof_type
1327            .sector_size()
1328            .map_err(|e| actor_error_v13!(illegal_argument, "invalid sector size: {}", e))?;
1329
1330        let window_post_partition_sectors = window_post_proof_type
1331            .window_post_partition_sectors()
1332            .map_err(|e| actor_error_v13!(illegal_argument, "invalid partition sectors: {}", e))?;
1333
1334        Ok(Self {
1335            owner: Address::new_id(owner),
1336            worker: Address::new_id(worker),
1337            control_addresses: control_addresses
1338                .into_iter()
1339                .map(Address::new_id)
1340                .collect_vec(),
1341
1342            pending_worker_key: None,
1343            beneficiary: Address::new_id(owner),
1344            beneficiary_term: BeneficiaryTerm::default(),
1345            pending_beneficiary_term: None,
1346            peer_id,
1347            multi_address,
1348            window_post_proof_type,
1349            sector_size,
1350            window_post_partition_sectors,
1351            consensus_fault_elapsed: EPOCH_UNDEFINED,
1352            pending_owner_address: None,
1353        })
1354    }
1355}