diff --git a/actors/miner/src/lib.rs b/actors/miner/src/lib.rs index 81d70c305..5ac5bde8f 100644 --- a/actors/miner/src/lib.rs +++ b/actors/miner/src/lib.rs @@ -3556,7 +3556,11 @@ enum ExtensionKind { // ExtendSectorExpiration param struct ExtendExpirationsInner { extensions: Vec, - claims: Option>, + // Map from sector being extended to (check, maintain) + // `check` is the space of active claims, checked to ensure all claims are checked + // `maintain` is the space of claims to maintain + // maintain <= check with equality in the case no claims are dropped + claims: Option>, } #[derive(Clone, Debug, PartialEq)] @@ -3621,7 +3625,7 @@ where BS: Blockstore, RT: Runtime, { - let mut claim_space_by_sector = BTreeMap::::new(); + let mut claim_space_by_sector = BTreeMap::::new(); for decl in &extensions { let policy = rt.policy(); @@ -3635,27 +3639,38 @@ where } for sc in &decl.sectors_with_claims { - let claims = get_claims(rt, &sc.maintain_claims) + let mut drop_claims = sc.drop_claims.clone(); + let mut all_claim_ids = sc.maintain_claims.clone(); + all_claim_ids.append(&mut drop_claims); + let claims = get_claims(rt, &all_claim_ids) .with_context(|| format!("failed to get claims for sector {}", sc.sector_number))?; + let first_drop = sc.maintain_claims.len(); for (i, claim) in claims.iter().enumerate() { // check provider and sector matches if claim.provider != rt.message().receiver().id().unwrap() { - return Err(actor_error!(illegal_argument, "failed to validate declaration sector={}, claim={}, expected claim provider to be {} but found {} ", sc.sector_number, sc.maintain_claims[i], rt.message().receiver().id().unwrap(), claim.provider)); + return Err(actor_error!(illegal_argument, "failed to validate declaration sector={}, claim={}, expected claim provider to be {} but found {} ", sc.sector_number, all_claim_ids[i], rt.message().receiver().id().unwrap(), claim.provider)); } if claim.sector != sc.sector_number { - return Err(actor_error!(illegal_argument, "failed to validate declaration sector={}, claim={} expected claim sector number to be {} but found {} ", sc.sector_number, sc.maintain_claims[i], sc.sector_number, claim.sector)); + return Err(actor_error!(illegal_argument, "failed to validate declaration sector={}, claim={} expected claim sector number to be {} but found {} ", sc.sector_number, all_claim_ids[i], sc.sector_number, claim.sector)); } - // check expiration does not exceed term max - if decl.new_expiration > claim.term_start + claim.term_max { - return Err(actor_error!(forbidden, "failed to validate declaration sector={}, claim={} claim only allows extension to {} but declared new expiration is {}", sc.sector_number, sc.maintain_claims[i], claim.term_start + claim.term_max, decl.new_expiration)); + // If we are not dropping check expiration does not exceed term max + let mut maintain_delta: u64 = 0; + if i < first_drop { + if decl.new_expiration > claim.term_start + claim.term_max { + return Err(actor_error!(forbidden, "failed to validate declaration sector={}, claim={} claim only allows extension to {} but declared new expiration is {}", sc.sector_number, sc.maintain_claims[i], claim.term_start + claim.term_max, decl.new_expiration)); + } + maintain_delta = claim.size.0 } claim_space_by_sector .entry(sc.sector_number) - .and_modify(|size| *size += claim.size.0) - .or_insert(claim.size.0); + .and_modify(|(check, maintain)| { + *check += claim.size.0; + *maintain += maintain_delta; + }) + .or_insert((claim.size.0, maintain_delta)); } } } @@ -3670,13 +3685,13 @@ fn extend_sector_committment( curr_epoch: ChainEpoch, new_expiration: ChainEpoch, sector: &SectorOnChainInfo, - claim_space_by_sector: &BTreeMap, + claim_space_by_sector: &BTreeMap, ) -> Result { validate_extended_expiration(policy, curr_epoch, new_expiration, sector)?; // all simple_qa_power sectors with VerifiedDealWeight > 0 MUST check all claims if sector.simple_qa_power { - extend_simple_qap_sector(new_expiration, sector, claim_space_by_sector) + extend_simple_qap_sector(policy, new_expiration, curr_epoch, sector, claim_space_by_sector) } else { extend_non_simple_qap_sector(new_expiration, curr_epoch, sector) } @@ -3744,33 +3759,51 @@ fn validate_extended_expiration( } fn extend_simple_qap_sector( + policy: &Policy, new_expiration: ChainEpoch, + curr_epoch: ChainEpoch, sector: &SectorOnChainInfo, - claim_space_by_sector: &BTreeMap, + claim_space_by_sector: &BTreeMap, ) -> Result { let mut new_sector = sector.clone(); if sector.verified_deal_weight > BigInt::zero() { let old_duration = sector.expiration - sector.activation; let deal_space = §or.deal_weight / old_duration; - let verified_deal_space = §or.verified_deal_weight / old_duration; - let expected_verified_deal_space = match claim_space_by_sector.get(§or.sector_number) { - None => { - return Err(actor_error!( - illegal_argument, - "claim missing from declaration for sector {}", - sector.sector_number - )) - } - Some(space) => space, - }; - if BigInt::from(*expected_verified_deal_space as i64) != verified_deal_space { - return Err(actor_error!(illegal_argument, "declared verified deal space in claims ({}) does not match verified deal space ({}) for sector {}", expected_verified_deal_space, verified_deal_space, sector.sector_number)); + let old_verified_deal_space = §or.verified_deal_weight / old_duration; + let (expected_verified_deal_space, new_verified_deal_space) = + match claim_space_by_sector.get(§or.sector_number) { + None => { + return Err(actor_error!( + illegal_argument, + "claim missing from declaration for sector {} with non-zero verified deal weight {}", + sector.sector_number, + §or.verified_deal_weight + )) + } + Some(space) => space, + }; + // claims must be completely accounted for + if BigInt::from(*expected_verified_deal_space as i64) != old_verified_deal_space { + return Err(actor_error!(illegal_argument, "declared verified deal space in claims ({}) does not match verified deal space ({}) for sector {}", expected_verified_deal_space, old_verified_deal_space, sector.sector_number)); } + // claim dropping is restricted to extensions at the end of a sector's life + + let dropping_claims = expected_verified_deal_space != new_verified_deal_space; + if dropping_claims && sector.expiration - curr_epoch >= policy.end_of_life_claim_drop_period + { + return Err(actor_error!( + forbidden, + "attempt to drop sectors with {} epochs < end of life claim drop period {} remaining", + sector.expiration - curr_epoch, + policy.end_of_life_claim_drop_period + )); + } + new_sector.expiration = new_expiration; // update deal weights to account for new duration new_sector.deal_weight = deal_space * (new_sector.expiration - new_sector.activation); - new_sector.verified_deal_weight = - verified_deal_space * (new_sector.expiration - new_sector.activation); + new_sector.verified_deal_weight = BigInt::from(*new_verified_deal_space) + * (new_sector.expiration - new_sector.activation); } else { new_sector.expiration = new_expiration } diff --git a/actors/miner/src/types.rs b/actors/miner/src/types.rs index f8155e82a..eceb55006 100644 --- a/actors/miner/src/types.rs +++ b/actors/miner/src/types.rs @@ -161,6 +161,7 @@ impl Cbor for ExtendSectorExpiration2Params {} pub struct SectorClaim { pub sector_number: SectorNumber, pub maintain_claims: Vec, + pub drop_claims: Vec, } impl Cbor for SectorClaim {} diff --git a/actors/miner/tests/extend_sector_expiration_test.rs b/actors/miner/tests/extend_sector_expiration_test.rs index 013f04d5d..25487bfb2 100644 --- a/actors/miner/tests/extend_sector_expiration_test.rs +++ b/actors/miner/tests/extend_sector_expiration_test.rs @@ -498,6 +498,7 @@ fn update_expiration_multiple_claims() { sectors_with_claims: vec![SectorClaim { sector_number: old_sector.sector_number, maintain_claims: claim_ids, + drop_claims: vec![], }], }], }; @@ -570,6 +571,7 @@ fn update_expiration2_failure_cases() { sectors_with_claims: vec![SectorClaim { sector_number: old_sector.sector_number, maintain_claims: vec![claim_ids[0]], + drop_claims: vec![], }], }], }; @@ -608,6 +610,7 @@ fn update_expiration2_failure_cases() { sectors_with_claims: vec![SectorClaim { sector_number: old_sector.sector_number, maintain_claims: claim_ids.clone(), + drop_claims: vec![], }], }], }; @@ -646,6 +649,7 @@ fn update_expiration2_failure_cases() { sectors_with_claims: vec![SectorClaim { sector_number: old_sector.sector_number, maintain_claims: claim_ids, + drop_claims: vec![], }], }], }; diff --git a/runtime/src/runtime/policy.rs b/runtime/src/runtime/policy.rs index ca86cbe23..5559433b0 100644 --- a/runtime/src/runtime/policy.rs +++ b/runtime/src/runtime/policy.rs @@ -146,6 +146,8 @@ pub struct Policy { /// Maximum time a verified allocation can be active without being claimed (epochs). /// Supports recovery of erroneous allocations and prevents indefinite squatting on datacap. pub maximum_verified_allocation_expiration: i64, + // Period of time at the end of a sector's life during which claims can be dropped + pub end_of_life_claim_drop_period: ChainEpoch, // --- market policy --- /// The number of blocks between payouts for deals @@ -240,7 +242,7 @@ impl Default for Policy { maximum_verified_allocation_term: policy_constants::MAXIMUM_VERIFIED_ALLOCATION_TERM, maximum_verified_allocation_expiration: policy_constants::MAXIMUM_VERIFIED_ALLOCATION_EXPIRATION, - + end_of_life_claim_drop_period: policy_constants::END_OF_LIFE_CLAIM_DROP_PERIOD, deal_updates_interval: policy_constants::DEAL_UPDATES_INTERVAL, prov_collateral_percent_supply_num: policy_constants::PROV_COLLATERAL_PERCENT_SUPPLY_NUM, @@ -387,6 +389,7 @@ pub mod policy_constants { pub const MINIMUM_VERIFIED_ALLOCATION_TERM: i64 = 180 * EPOCHS_IN_DAY; pub const MAXIMUM_VERIFIED_ALLOCATION_TERM: i64 = 5 * EPOCHS_IN_YEAR; pub const MAXIMUM_VERIFIED_ALLOCATION_EXPIRATION: i64 = 60 * EPOCHS_IN_DAY; + pub const END_OF_LIFE_CLAIM_DROP_PERIOD: ChainEpoch = 30 * EPOCHS_IN_DAY; /// DealUpdatesInterval is the number of blocks between payouts for deals pub const DEAL_UPDATES_INTERVAL: i64 = EPOCHS_IN_DAY;