From 09d50ad890aa084e07c55296f7cac57879630f10 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Tue, 24 Oct 2023 13:23:45 -0400 Subject: [PATCH] fix: miner: move partitions with expired/terminated sectors (#1455) * fix: miner: move partitions with expired/terminated sectors * add `dispute_remaining_partition_after_move` (#1448) * add dispute_remaining_partition_after_move * add move_partition_with_terminated_sector --------- Co-authored-by: zhiqiangxu <652732310@qq.com> --- actors/miner/src/deadline_state.rs | 6 +- actors/miner/tests/move_partitions_test.rs | 118 ++++++++++++++++++++- 2 files changed, 118 insertions(+), 6 deletions(-) diff --git a/actors/miner/src/deadline_state.rs b/actors/miner/src/deadline_state.rs index be6ed791f..00ff28115 100644 --- a/actors/miner/src/deadline_state.rs +++ b/actors/miner/src/deadline_state.rs @@ -145,16 +145,16 @@ impl Deadlines { let dest_partition_idx = first_dest_partition_idx + i as u64; - // sector_count is both total sector count and total live sector count, since no sector is faulty here. let sector_count = moving_partition.sectors.len(); + let live_sector_count = sector_count - moving_partition.terminated.len(); // start updating orig/dest `Deadline` here orig_deadline.total_sectors -= sector_count; - orig_deadline.live_sectors -= sector_count; + orig_deadline.live_sectors -= live_sector_count; dest_deadline.total_sectors += sector_count; - dest_deadline.live_sectors += sector_count; + dest_deadline.live_sectors += live_sector_count; orig_partitions.set(orig_partition_idx, Partition::new(store)?)?; dest_partitions.set(dest_partition_idx, moving_partition)?; diff --git a/actors/miner/tests/move_partitions_test.rs b/actors/miner/tests/move_partitions_test.rs index 15a91ccb0..a105b4bf2 100644 --- a/actors/miner/tests/move_partitions_test.rs +++ b/actors/miner/tests/move_partitions_test.rs @@ -14,6 +14,7 @@ use fil_actors_runtime::{ }; use fvm_ipld_bitfield::BitField; use fvm_shared::econ::TokenAmount; +use fvm_shared::sector::RegisteredSealProof; use fvm_shared::{clock::ChainEpoch, error::ExitCode}; use num_traits::Zero; @@ -23,11 +24,13 @@ use util::*; const PERIOD_OFFSET: ChainEpoch = 100; fn setup() -> (ActorHarness, MockRuntime) { - let h = ActorHarness::new(PERIOD_OFFSET); + let mut h = ActorHarness::new(PERIOD_OFFSET); + // For a small partition size, necessary for `dispute_remaining_partition_after_move` + h.set_proof_type(RegisteredSealProof::StackedDRG2KiBV1P1); let rt = h.new_runtime(); - h.construct_and_verify(&rt); - rt.balance.replace(BIG_BALANCE.clone()); + rt.balance.replace(BIG_BALANCE.clone()); + h.construct_and_verify(&rt); (h, rt) } @@ -631,3 +634,112 @@ fn dispute_after_move() { h.dispute_window_post(&rt, &dest_deadline, 0, §ors_info, Some(expected_result)); } } + +#[test] +fn dispute_remaining_partition_after_move() { + let (mut h, rt) = setup(); + + // Commit more sectors than fit in one partition in every eligible deadline, overflowing to a second partition. + let sectors_to_commit = (rt.policy.wpost_period_deadlines - 2) * h.partition_size + 1; + let sectors_info = h.commit_and_prove_sectors( + &rt, + sectors_to_commit as usize, + DEFAULT_SECTOR_EXPIRATION, + vec![], + true, + ); + h.advance_and_submit_posts(&rt, §ors_info); + + let last_sector = sectors_info.last().unwrap(); + let st = h.get_state(&rt); + + let (orig_deadline_id, partition_id) = + st.find_sector(&rt.store, last_sector.sector_number).unwrap(); + assert_eq!(partition_id, 1); + + // move a partition from a deadline that still needs WindowPoST verification. + + // at this moment, the current and next deadlines are empty, orig_deadline_id has 2 partitions, and all other deadlines have 1 partition. + let target_sectors = &[ + §ors_info[0..h.partition_size as usize], /* these belong to partition 0 */ + vec![last_sector.clone()].as_slice(), /* these belong to partition 1 */ + ] + .concat(); + // after this call, current epoch will automatically be advanced to `nearest_unsafe_epoch(&rt, &h, orig_deadline_id)` + h.advance_and_submit_posts(&rt, target_sectors); + + let dest_deadline_id = + farthest_possible_to_deadline(&rt, orig_deadline_id, h.current_deadline(&rt)); + + // move the second partition + let result = h.move_partitions( + &rt, + orig_deadline_id, + dest_deadline_id, + bitfield_from_slice(&[partition_id]), + target_sectors, + ); + assert!(result.is_ok()); + + let st = h.get_state(&rt); + let (dl_idx, _) = st.find_sector(&rt.store, last_sector.sector_number).unwrap(); + assert!(dl_idx == dest_deadline_id); + + h.check_state(&rt); + + // Dispute the first partition in the original deadline + { + let orig_deadline = + nearest_occured_deadline_info(rt.policy(), &h.current_deadline(&rt), orig_deadline_id); + + // Check that a failed dispute is ok. A successful dispute is impossible because the Window PoST was synchronously validated when the partition was moved. + h.dispute_window_post(&rt, &orig_deadline, 0, target_sectors, None); + } +} + +#[test] +fn move_partition_with_terminated_sector() { + let (mut h, rt) = setup(); + + // create 2 sectors in partition 0 + let sectors_info = h.commit_and_prove_sectors( + &rt, + 2, + DEFAULT_SECTOR_EXPIRATION, + vec![vec![10], vec![20]], + true, + ); + h.advance_and_submit_posts(&rt, §ors_info); + + // terminate 1 sector + { + // A miner will pay the minimum of termination fee and locked funds. Add some locked funds to ensure + // correct fee calculation is used. + h.apply_rewards(&rt, BIG_REWARDS.clone(), TokenAmount::zero()); + + let expected_fee = calc_expected_fee_for_termination(&h, &rt, sectors_info[1].clone()); + + let sectors = bitfield_from_slice(&[sectors_info[1].sector_number]); + h.terminate_sectors(&rt, §ors, expected_fee); + } + + let st = h.get_state(&rt); + let (orig_deadline_id, partition_id) = + st.find_sector(&rt.store, sectors_info[0].sector_number).unwrap(); + + h.advance_to_epoch_with_cron(&rt, nearest_safe_epoch(&rt, &h, orig_deadline_id)); + + let dest_deadline_id = + farthest_possible_to_deadline(&rt, orig_deadline_id, h.current_deadline(&rt)); + + let result = h.move_partitions( + &rt, + orig_deadline_id, + dest_deadline_id, + bitfield_from_slice(&[partition_id]), + &[], + ); + assert!(result.is_ok()); + + h.check_state(&rt); +}