1use 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#[derive(Serialize_tuple, Deserialize_tuple, Clone, Debug)]
56pub struct State {
57 pub info: Cid,
59
60 pub pre_commit_deposits: TokenAmount,
62
63 pub locked_funds: TokenAmount,
65
66 pub vesting_funds: Cid,
68
69 pub fee_debt: TokenAmount,
71
72 pub initial_pledge: TokenAmount,
74
75 pub pre_committed_sectors: Cid,
78
79 pub pre_committed_sectors_cleanup: Cid, pub allocated_sectors: Cid, pub sectors: Cid, pub proving_period_start: ChainEpoch,
99
100 pub current_deadline: u64,
104
105 pub deadlines: Cid,
109
110 pub early_terminations: BitField,
112
113 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 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 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 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 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 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 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 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 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 §or_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 §or_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 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 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 §ors,
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 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 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 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 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 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, bool)> {
608 if self.early_terminations.is_empty() {
610 return Ok((Default::default(), false));
611 }
612
613 let mut deadlines = self.load_deadlines(store)?;
615
616 let mut result = TerminationResult::new();
617 let mut to_unset = Vec::new();
618
619 for i in self.early_terminations.iter() {
621 let deadline_idx = i;
622
623 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 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 self.save_deadlines(store, deadlines)?;
659
660 let no_early_terminations = self.early_terminations.is_empty();
662
663 Ok((result, !no_early_terminations))
664 }
665
666 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 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 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 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 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 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 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 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 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 vesting_funds.add_locked_funds(current_epoch, vesting_sum, self.proving_period_start, spec);
875 self.locked_funds += vesting_sum;
876
877 self.save_vesting_funds(store, &vesting_funds)?;
879
880 Ok(amount_unlocked)
881 }
882
883 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, TokenAmount, ),
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 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 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 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 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 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 pub fn get_available_balance(
1015 &self,
1016 actor_balance: &TokenAmount,
1017 ) -> anyhow::Result<TokenAmount> {
1018 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 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 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 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 None => continue,
1108 };
1109
1110 precommits_to_delete.push(sector_number);
1112
1113 deposit_to_burn += sector.pre_commit_deposit;
1115 }
1116
1117 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 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 let total_faulty_power = deadline.faulty_power.clone();
1185
1186 let expired = deadline.pop_expired_sectors(store, dl_info.last(), quant)?;
1188
1189 pledge_delta -= &expired.on_time_pledge;
1193 self.add_initial_pledge(&expired.on_time_pledge.neg())?;
1194
1195 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 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 pub previously_faulty_power: PowerPair,
1252 pub detected_faulty_power: PowerPair,
1254 pub total_faulty_power: PowerPair,
1258}
1259
1260#[derive(Debug, PartialEq, Eq, Serialize_tuple, Deserialize_tuple)]
1262pub struct MinerInfo {
1263 pub owner: Address,
1267
1268 pub worker: Address,
1273
1274 pub control_addresses: Vec<Address>, pub pending_worker_key: Option<WorkerKeyChange>,
1279
1280 #[serde(with = "strict_bytes")]
1282 pub peer_id: Vec<u8>,
1283
1284 pub multi_address: Vec<BytesDe>,
1286
1287 pub window_post_proof_type: RegisteredPoStProof,
1289
1290 pub sector_size: SectorSize,
1292
1293 pub window_post_partition_sectors: u64,
1296
1297 pub consensus_fault_elapsed: ChainEpoch,
1300
1301 pub pending_owner_address: Option<Address>,
1304
1305 pub beneficiary: Address,
1308
1309 pub beneficiary_term: BeneficiaryTerm,
1312
1313 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}