Skip to content

Commit 421b73d

Browse files
authored
fix(miner): termination fee should use full sector duration (#1659)
Kudos to @beck-8 for noticing this @ #1639 (comment)
1 parent 651cb59 commit 421b73d

File tree

2 files changed

+64
-20
lines changed

2 files changed

+64
-20
lines changed

actors/miner/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4355,7 +4355,7 @@ fn process_early_terminations(
43554355
total_initial_pledge += &sector.initial_pledge;
43564356
let sector_power = qa_power_for_sector(info.sector_size, sector);
43574357
terminated_sector_nums.push(sector.sector_number);
4358-
let sector_age = epoch - sector.power_base_epoch;
4358+
let sector_age = epoch - sector.activation;
43594359
let fault_fee = pledge_penalty_for_continued_fault(
43604360
reward_smoothed,
43614361
quality_adj_power_smoothed,

actors/miner/tests/terminate_sectors_test.rs

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
use fil_actor_miner::{
22
pledge_penalty_for_continued_fault, pledge_penalty_for_termination, power_for_sector,
3-
qa_power_for_sector, Actor, CronEventPayload, DeferredCronEventParams, MaxTerminationFeeParams,
4-
MaxTerminationFeeReturn, Method, SectorOnChainInfo, State, TerminateSectorsParams,
5-
TerminationDeclaration, CRON_EVENT_PROCESS_EARLY_TERMINATIONS,
6-
TERM_FEE_MAX_FAULT_FEE_MULTIPLE_DENOM, TERM_FEE_MAX_FAULT_FEE_MULTIPLE_NUM,
7-
TERM_FEE_PLEDGE_MULTIPLE_DENOM, TERM_FEE_PLEDGE_MULTIPLE_NUM,
3+
qa_power_for_sector, Actor, CronEventPayload, DeferredCronEventParams, ExpirationExtension2,
4+
ExtendSectorExpiration2Params, MaxTerminationFeeParams, MaxTerminationFeeReturn, Method,
5+
SectorOnChainInfo, State, TerminateSectorsParams, TerminationDeclaration,
6+
CRON_EVENT_PROCESS_EARLY_TERMINATIONS, TERM_FEE_MAX_FAULT_FEE_MULTIPLE_DENOM,
7+
TERM_FEE_MAX_FAULT_FEE_MULTIPLE_NUM, TERM_FEE_PLEDGE_MULTIPLE_DENOM,
8+
TERM_FEE_PLEDGE_MULTIPLE_NUM,
89
};
910
use fil_actors_runtime::{
11+
reward::FilterEstimate,
1012
runtime::Runtime,
1113
test_utils::{expect_abort_contains_message, MockRuntime, ACCOUNT_ACTOR_CODE_ID},
12-
BURNT_FUNDS_ACTOR_ADDR, STORAGE_MARKET_ACTOR_ADDR, STORAGE_POWER_ACTOR_ADDR, SYSTEM_ACTOR_ADDR,
14+
BatchReturn, BURNT_FUNDS_ACTOR_ADDR, STORAGE_MARKET_ACTOR_ADDR, STORAGE_POWER_ACTOR_ADDR,
15+
SYSTEM_ACTOR_ADDR,
1316
};
1417
use fvm_ipld_bitfield::BitField;
1518
use fvm_shared::{
16-
econ::TokenAmount, error::ExitCode, sector::StoragePower, MethodNum, METHOD_SEND,
19+
bigint::BigInt, econ::TokenAmount, error::ExitCode, sector::StoragePower, MethodNum,
20+
METHOD_SEND,
1721
};
1822
use std::collections::HashMap;
1923

@@ -47,32 +51,72 @@ fn setup() -> (ActorHarness, MockRuntime) {
4751
}
4852

4953
#[test]
50-
fn removes_sector_with_correct_accounting() {
54+
fn removes_sectors_with_correct_accounting() {
5155
let (mut h, rt) = setup();
5256

53-
let sector_info =
54-
h.commit_and_prove_sectors(&rt, 1, DEFAULT_SECTOR_EXPIRATION, Vec::new(), true);
57+
// set the reward such that our termination fees don't bottom out at the fault fee multiple and
58+
// instead invoke the duration multipler so we can also test that
59+
h.epoch_reward_smooth = FilterEstimate::new(BigInt::from(1e12 as u64), BigInt::zero());
5560

56-
assert_eq!(sector_info.len(), 1);
57-
h.advance_and_submit_posts(&rt, &sector_info);
58-
let sector = sector_info.into_iter().next().unwrap();
61+
// 3 sectors: one left alone, one that we'll extend and one that we'll update, they should all
62+
// have the same termination fee
63+
let sectors = h.commit_and_prove_sectors(&rt, 3, DEFAULT_SECTOR_EXPIRATION, Vec::new(), true);
64+
assert_eq!(sectors.len(), 3);
65+
66+
// advance enough to make the duration component of the termination fee large enough to not hit
67+
// the minimum bound
68+
for _ in 0..40 {
69+
h.advance_and_submit_posts(&rt, &sectors);
70+
}
71+
72+
// extend the second sector, shouldn't affect the termination fee
73+
let state: State = rt.get_state();
74+
let (deadline_index, partition_index) =
75+
state.find_sector(rt.store(), sectors[1].sector_number).unwrap();
76+
let extension = 42 * rt.policy.wpost_proving_period;
77+
let new_expiration = sectors[1].expiration + extension;
78+
let params = ExtendSectorExpiration2Params {
79+
extensions: vec![ExpirationExtension2 {
80+
deadline: deadline_index,
81+
partition: partition_index,
82+
sectors: make_bitfield(&[sectors[1].sector_number]),
83+
sectors_with_claims: vec![],
84+
new_expiration,
85+
}],
86+
};
87+
h.extend_sectors2(&rt, params, HashMap::new()).unwrap();
88+
89+
// update the third sector with no pieces, should not affect the termination fee
90+
let updates = make_update_manifest(&rt.get_state(), rt.store(), sectors[2].sector_number, &[]); // No pieces
91+
let (result, _, _) = h
92+
.prove_replica_updates3_batch(
93+
&rt,
94+
&[updates],
95+
true,
96+
true,
97+
ProveReplicaUpdatesConfig::default(),
98+
)
99+
.unwrap();
100+
assert_eq!(BatchReturn::of(&[ExitCode::OK; 1]), result.activation_results);
59101

60102
// A miner will pay the minimum of termination fee and locked funds. Add some locked funds to ensure
61103
// correct fee calculation is used.
62104
h.apply_rewards(&rt, BIG_REWARDS.clone(), TokenAmount::zero());
63105
let state: State = rt.get_state();
64106
let initial_locked_funds = state.locked_funds;
65107

66-
let expected_fee = calc_expected_fee_for_termination(&h, &rt, &sector);
108+
// fee for all sectors is the same, so we can just use the first one
109+
let expected_fee = calc_expected_fee_for_termination(&h, &rt, &sectors[0]) * 3;
67110

68-
let sectors = bitfield_from_slice(&[sector.sector_number]);
69-
h.terminate_sectors(&rt, &sectors, expected_fee.clone());
111+
let bf = bitfield_from_slice(&sectors.iter().map(|s| s.sector_number).collect::<Vec<u64>>());
112+
h.terminate_sectors(&rt, &bf, expected_fee.clone());
70113

71114
// expect sector to be marked as terminated and the early termination queue to be empty (having been fully processed)
72115
let state: State = rt.get_state();
73-
let (_, mut partition) = h.find_sector(&rt, sector.sector_number);
74-
let terminated = partition.terminated.get(sector.sector_number);
75-
assert!(terminated);
116+
let (_, mut partition) = h.find_sector(&rt, sectors[0].sector_number);
117+
for s in &sectors {
118+
assert!(partition.terminated.get(s.sector_number));
119+
}
76120

77121
let (result, _) = partition.pop_early_terminations(rt.store(), 1000).unwrap();
78122
assert!(result.is_empty());

0 commit comments

Comments
 (0)