From f2f2a75db02426677125c1766c2c3afd880005fa Mon Sep 17 00:00:00 2001 From: vyzo Date: Wed, 27 Apr 2022 18:50:43 +0300 Subject: [PATCH] miner actor: all but one remaining wpost tests (#159) * test skipped_faults_adjust_power * test skipping_all_sectors_in_a_partition_rejected * test skipping_a_fault_from_the_wrong_partition_is_an_error * test cannot_dispute_posts_when_the_challenge_window_is_open * test can_dispute_up_till_window_end_but_not_after * test cant_dispute_up_with_an_invalid_deadline * test can_dispute_test_after_proving_period_changes Co-authored-by: zenground0 --- actors/miner/tests/miner_actor_test_wpost.rs | 466 ++++++++++++++++++- actors/miner/tests/util.rs | 404 ++++++++++++++-- 2 files changed, 820 insertions(+), 50 deletions(-) diff --git a/actors/miner/tests/miner_actor_test_wpost.rs b/actors/miner/tests/miner_actor_test_wpost.rs index 066d0051d..59e464414 100644 --- a/actors/miner/tests/miner_actor_test_wpost.rs +++ b/actors/miner/tests/miner_actor_test_wpost.rs @@ -1,6 +1,8 @@ +#![allow(clippy::all)] + use fil_actor_miner as miner; use fil_actors_runtime::test_utils::*; - +use fvm_ipld_bitfield::BitField; use fvm_ipld_encoding::RawBytes; use fvm_shared::clock::ChainEpoch; use fvm_shared::crypto::randomness::DomainSeparationTag; @@ -18,6 +20,7 @@ use util::*; const DEFAULT_SECTOR_EXPIRATION: u64 = 220; const BIG_BALANCE: u128 = 1_000_000_000_000_000_000_000_000u128; +const BIG_REWARDS: u128 = 1_000_000_000_000_000_000_000u128; #[test] fn basic_post_and_dispute() { @@ -675,28 +678,469 @@ fn duplicate_proof_rejected_with_many_partitions() { } #[test] -fn successful_recoveries_recover_power() {} +fn skipped_faults_adjust_power() { + let period_offset = ChainEpoch::from(100); + let precommit_epoch = ChainEpoch::from(1); -#[test] -fn skipped_faults_adjust_power() {} + let mut h = ActorHarness::new(period_offset); + h.set_proof_type(RegisteredSealProof::StackedDRG2KiBV1P1); + + let mut rt = h.new_runtime(); + rt.epoch = precommit_epoch; + rt.balance.replace(TokenAmount::from(BIG_BALANCE)); + + h.construct_and_verify(&mut rt); + + let infos = h.commit_and_prove_sectors(&mut rt, 2, DEFAULT_SECTOR_EXPIRATION, vec![], true); + + h.apply_rewards(&mut rt, TokenAmount::from(BIG_REWARDS), TokenAmount::from(0u8)); + + // Skip to the due deadline. + let state = h.get_state(&rt); + let (dlidx, pidx) = state.find_sector(&rt.policy, &rt.store, infos[0].sector_number).unwrap(); + let (dlidx2, pidx2) = state.find_sector(&rt.policy, &rt.store, infos[1].sector_number).unwrap(); + assert_eq!(dlidx, dlidx2); + + let mut dlinfo = h.advance_to_deadline(&mut rt, dlidx); + + // Now submit PoSt with a skipped fault for first sector + // First sector's power should not be activated. + let infos1 = vec![infos[0].clone()]; + let infos2 = vec![infos[1].clone()]; + let power_active = miner::power_for_sectors(h.sector_size, &infos2); + let partition = + miner::PoStPartition { index: pidx, skipped: make_bitfield(&[infos1[0].sector_number]) }; + h.submit_window_post( + &mut rt, + &dlinfo, + vec![partition], + infos2.clone(), + PoStConfig::with_expected_power_delta(&power_active), + ); + + // expect continued fault fee to be charged during cron + let fault_fee = h.continued_fault_penalty(&infos1); + dlinfo = h.advance_deadline(&mut rt, CronConfig::with_continued_faults_penalty(fault_fee)); + + // advance to next proving period, expect no fees + while dlinfo.index != dlidx { + dlinfo = h.advance_deadline(&mut rt, CronConfig::empty()); + } + + // Attempt to skip second sector + let pwr_delta = -miner::power_for_sectors(h.sector_size, &infos2); + let partition = + miner::PoStPartition { index: pidx2, skipped: make_bitfield(&[infos[1].sector_number]) }; + let params = miner::SubmitWindowedPoStParams { + deadline: dlinfo.index, + partitions: vec![partition], + proofs: make_post_proofs(h.window_post_proof_type), + chain_commit_epoch: dlinfo.challenge, + chain_commit_rand: Randomness(b"chaincommitment".to_vec()), + }; + + // Now all sectors are faulty so there's nothing to prove. + let result = h.submit_window_post_raw( + &mut rt, + &dlinfo, + infos2.clone(), + params, + PoStConfig::with_expected_power_delta(&pwr_delta), + ); + expect_abort_contains_message(ExitCode::USR_ILLEGAL_ARGUMENT, "no active sectors", result); + rt.reset(); + + // The second sector is detected faulty but pays nothing yet. + // Expect ongoing fault penalty for only the first, continuing-faulty sector. + let pwr_delta = -miner::power_for_sectors(h.sector_size, &infos2); + let fault_fee = h.continued_fault_penalty(&infos1); + h.advance_deadline( + &mut rt, + CronConfig::with_detected_faults_power_delta_and_continued_faults_penalty( + &pwr_delta, fault_fee, + ), + ); + + check_state_invariants(&rt); +} #[test] -fn skipping_all_sectors_in_a_partition_rejected() {} +fn skipping_all_sectors_in_a_partition_rejected() { + let period_offset = ChainEpoch::from(100); + let precommit_epoch = ChainEpoch::from(1); + + let mut h = ActorHarness::new(period_offset); + h.set_proof_type(RegisteredSealProof::StackedDRG2KiBV1P1); + + let mut rt = h.new_runtime(); + rt.epoch = precommit_epoch; + rt.balance.replace(TokenAmount::from(BIG_BALANCE)); + + h.construct_and_verify(&mut rt); + + let infos = h.commit_and_prove_sectors(&mut rt, 2, DEFAULT_SECTOR_EXPIRATION, vec![], true); + + h.apply_rewards(&mut rt, TokenAmount::from(BIG_REWARDS), TokenAmount::from(0u8)); + + // Skip to the due deadline. + let state = h.get_state(&rt); + let (dlidx, pidx) = state.find_sector(&rt.policy, &rt.store, infos[0].sector_number).unwrap(); + let (dlidx2, pidx2) = state.find_sector(&rt.policy, &rt.store, infos[1].sector_number).unwrap(); + assert_eq!(dlidx, dlidx2); + assert_eq!(pidx, pidx2); + + let dlinfo = h.advance_to_deadline(&mut rt, dlidx); + + // PoSt with all sectors skipped fails to validate, leaving power un-activated. + let partition = miner::PoStPartition { + index: pidx, + skipped: make_bitfield(&[infos[0].sector_number, infos[1].sector_number]), + }; + let params = miner::SubmitWindowedPoStParams { + deadline: dlinfo.index, + partitions: vec![partition], + proofs: make_post_proofs(h.window_post_proof_type), + chain_commit_epoch: dlinfo.challenge, + chain_commit_rand: Randomness(b"chaincommitment".to_vec()), + }; + let result = + h.submit_window_post_raw(&mut rt, &dlinfo, infos.clone(), params, PoStConfig::empty()); + expect_abort(ExitCode::USR_ILLEGAL_ARGUMENT, result); + rt.reset(); + + // These sectors are detected faulty and pay no penalty this time. + h.advance_deadline(&mut rt, CronConfig::with_continued_faults_penalty(TokenAmount::from(0u8))); + check_state_invariants(&rt); +} #[test] -fn skipped_recoveries_are_penalized_and_do_not_recover_power() {} +fn skipped_recoveries_are_penalized_and_do_not_recover_power() { + let period_offset = ChainEpoch::from(100); + let precommit_epoch = ChainEpoch::from(1); + + let mut h = ActorHarness::new(period_offset); + h.set_proof_type(RegisteredSealProof::StackedDRG2KiBV1P1); + + let mut rt = h.new_runtime(); + rt.epoch = precommit_epoch; + rt.balance.replace(TokenAmount::from(BIG_BALANCE)); + + h.construct_and_verify(&mut rt); + + let infos = h.commit_and_prove_sectors(&mut rt, 2, DEFAULT_SECTOR_EXPIRATION, vec![], true); + + h.apply_rewards(&mut rt, TokenAmount::from(BIG_REWARDS), TokenAmount::from(0u8)); + + // Submit first PoSt to ensure we are sufficiently early to add a fault + // advance to next proving period + h.advance_and_submit_posts(&mut rt, &infos); + + // advance deadline and declare fault on the first sector + let infos1 = vec![infos[0].clone()]; + h.advance_deadline(&mut rt, CronConfig::empty()); + h.declare_faults(&mut rt, &infos1); + + // advance a deadline and declare recovery + h.advance_deadline(&mut rt, CronConfig::empty()); + + // declare recovery + let state = h.get_state(&rt); + let (dlidx, pidx) = state.find_sector(&rt.policy, &rt.store, infos[0].sector_number).unwrap(); + let mut bf = BitField::new(); + bf.set(infos[0].sector_number); + h.declare_recoveries(&mut rt, dlidx, pidx, bf, TokenAmount::from(0u8)); + + // Skip to the due deadline. + let dlinfo = h.advance_to_deadline(&mut rt, dlidx); + + // Now submit PoSt and skip recovered sector. + // No power should be returned + let partition = + miner::PoStPartition { index: pidx, skipped: make_bitfield(&[infos[0].sector_number]) }; + h.submit_window_post(&mut rt, &dlinfo, vec![partition], infos.clone(), PoStConfig::empty()); + + // sector will be charged ongoing fee at proving period cron + let ongoing_fee = h.continued_fault_penalty(&infos1); + h.advance_deadline(&mut rt, CronConfig::with_continued_faults_penalty(ongoing_fee)); + + check_state_invariants(&rt); +} #[test] -fn skipping_a_fault_from_the_wrong_partition_is_an_error() {} +fn skipping_a_fault_from_the_wrong_partition_is_an_error() { + let period_offset = ChainEpoch::from(100); + let precommit_epoch = ChainEpoch::from(1); + + let mut h = ActorHarness::new(period_offset); + h.set_proof_type(RegisteredSealProof::StackedDRG2KiBV1P1); + + let mut rt = h.new_runtime(); + rt.epoch = precommit_epoch; + rt.balance.replace(TokenAmount::from(BIG_BALANCE)); + + h.construct_and_verify(&mut rt); + + // create enough sectors that one will be in a different partition + // TODO: remove magic number and derive from seal proof based parameter + const N: usize = 95; + let infos = h.commit_and_prove_sectors(&mut rt, N, DEFAULT_SECTOR_EXPIRATION, vec![], true); + + // Skip to the due deadline. + let state = h.get_state(&rt); + let (dlidx0, pidx0) = state.find_sector(&rt.policy, &rt.store, infos[0].sector_number).unwrap(); + let (dlidx1, pidx1) = + state.find_sector(&rt.policy, &rt.store, infos[N - 1].sector_number).unwrap(); + let dlinfo = h.advance_to_deadline(&mut rt, dlidx0); + + // if these assertions no longer hold, the test must be changed + assert!(dlidx0 < dlidx1); + assert!(pidx0 != pidx1); + + // Now submit PoSt for partition 1 and skip sector from other partition + let partition = miner::PoStPartition { + index: pidx0, + skipped: make_bitfield(&[infos[N - 1].sector_number]), + }; + let params = miner::SubmitWindowedPoStParams { + deadline: dlinfo.index, + partitions: vec![partition], + proofs: make_post_proofs(h.window_post_proof_type), + chain_commit_epoch: dlinfo.challenge, + chain_commit_rand: Randomness(b"chaincommitment".to_vec()), + }; + let result = h.submit_window_post_raw(&mut rt, &dlinfo, infos, params, PoStConfig::empty()); + expect_abort_contains_message( + ExitCode::USR_ILLEGAL_ARGUMENT, + "skipped faults contains sectors outside partition", + result, + ); + + check_state_invariants(&rt); +} #[test] -fn cannot_dispute_posts_when_the_challenge_window_is_open() {} +fn cannot_dispute_posts_when_the_challenge_window_is_open() { + let period_offset = ChainEpoch::from(100); + let precommit_epoch = ChainEpoch::from(1); + + let mut h = ActorHarness::new(period_offset); + h.set_proof_type(RegisteredSealProof::StackedDRG2KiBV1P1); + + let mut rt = h.new_runtime(); + rt.epoch = precommit_epoch; + rt.balance.replace(TokenAmount::from(BIG_BALANCE)); + + h.construct_and_verify(&mut rt); + + let infos = h.commit_and_prove_sectors(&mut rt, 1, DEFAULT_SECTOR_EXPIRATION, vec![], true); + let sector = infos[0].clone(); + let pwr = miner::power_for_sector(h.sector_size, §or); + + // Skip to the due deadline. + let state = h.get_state(&rt); + let (dlidx, pidx) = state.find_sector(&rt.policy, &rt.store, sector.sector_number).unwrap(); + let dlinfo = h.advance_to_deadline(&mut rt, dlidx); + + // Submit PoSt + let partition = miner::PoStPartition { index: pidx, skipped: make_empty_bitfield() }; + h.submit_window_post( + &mut rt, + &dlinfo, + vec![partition], + infos, + PoStConfig::with_expected_power_delta(&pwr), + ); + + // Dispute it. + let params = miner::DisputeWindowedPoStParams { deadline: dlinfo.index, post_index: 0 }; + + rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, h.worker); + rt.expect_validate_caller_type(vec![*ACCOUNT_ACTOR_CODE_ID, *MULTISIG_ACTOR_CODE_ID]); + h.expect_query_network_info(&mut rt); + + let result = rt.call::( + miner::Method::DisputeWindowedPoSt as u64, + &RawBytes::serialize(params).unwrap(), + ); + expect_abort_contains_message( + ExitCode::USR_FORBIDDEN, + "can only dispute window posts during the dispute window", + result, + ); + rt.verify(); +} #[test] -fn can_dispute_up_till_window_end_but_not_after() {} +fn can_dispute_up_till_window_end_but_not_after() { + let period_offset = ChainEpoch::from(100); + let precommit_epoch = ChainEpoch::from(1); + + let mut h = ActorHarness::new(period_offset); + h.set_proof_type(RegisteredSealProof::StackedDRG2KiBV1P1); + + let mut rt = h.new_runtime(); + rt.epoch = precommit_epoch; + rt.balance.replace(TokenAmount::from(BIG_BALANCE)); + + h.construct_and_verify(&mut rt); + + let infos = h.commit_and_prove_sectors(&mut rt, 1, DEFAULT_SECTOR_EXPIRATION, vec![], true); + let sector = infos[0].clone(); + + let state = h.get_state(&rt); + let (dlidx, _) = state.find_sector(&rt.policy, &rt.store, sector.sector_number).unwrap(); + + let nextdl = miner::DeadlineInfo::new( + state.proving_period_start, + dlidx, + rt.epoch, + rt.policy.wpost_period_deadlines, + rt.policy.wpost_proving_period, + rt.policy.wpost_challenge_window, + rt.policy.wpost_challenge_lookback, + rt.policy.fault_declaration_cutoff, + ) + .next_not_elapsed(); + + h.advance_and_submit_posts(&mut rt, &infos); + let window_end = nextdl.close + rt.policy.wpost_dispute_window; + + // first, try to dispute right before the window end. + // We expect this to fail "normally" (fail to disprove). + rt.epoch = window_end - 1; + h.dispute_window_post(&mut rt, &nextdl, 0, &infos, None); + + // Now set the epoch at the window end. We expect a different error. + rt.epoch = window_end; + + // Now try to dispute. + let params = miner::DisputeWindowedPoStParams { deadline: dlidx, post_index: 0 }; + rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, h.worker); + rt.expect_validate_caller_type(vec![*ACCOUNT_ACTOR_CODE_ID, *MULTISIG_ACTOR_CODE_ID]); + + h.expect_query_network_info(&mut rt); + + let result = rt.call::( + miner::Method::DisputeWindowedPoSt as u64, + &RawBytes::serialize(params).unwrap(), + ); + expect_abort_contains_message( + ExitCode::USR_FORBIDDEN, + "can only dispute window posts during the dispute window", + result, + ); + rt.verify(); +} #[test] -fn cant_dispute_up_with_an_invalid_deadline() {} +fn cant_dispute_up_with_an_invalid_deadline() { + let period_offset = ChainEpoch::from(100); + let precommit_epoch = ChainEpoch::from(1); + + let mut h = ActorHarness::new(period_offset); + h.set_proof_type(RegisteredSealProof::StackedDRG2KiBV1P1); + + let mut rt = h.new_runtime(); + rt.epoch = precommit_epoch; + rt.balance.replace(TokenAmount::from(BIG_BALANCE)); + + h.construct_and_verify(&mut rt); + + let params = miner::DisputeWindowedPoStParams { deadline: 50, post_index: 0 }; + + rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, h.worker); + rt.expect_validate_caller_type(vec![*ACCOUNT_ACTOR_CODE_ID, *MULTISIG_ACTOR_CODE_ID]); + + let result = rt.call::( + miner::Method::DisputeWindowedPoSt as u64, + &RawBytes::serialize(params).unwrap(), + ); + expect_abort_contains_message(ExitCode::USR_ILLEGAL_ARGUMENT, "invalid deadline", result); + rt.verify(); +} #[test] -fn can_dispute_test_after_proving_period_changes() {} +fn can_dispute_test_after_proving_period_changes() { + let period_offset = ChainEpoch::from(100); + let precommit_epoch = ChainEpoch::from(1); + + let mut h = ActorHarness::new(period_offset); + h.set_proof_type(RegisteredSealProof::StackedDRG2KiBV1P1); + + let mut rt = h.new_runtime(); + rt.epoch = precommit_epoch; + rt.balance.replace(TokenAmount::from(BIG_BALANCE)); + + h.construct_and_verify(&mut rt); + + let period_start = h.deadline(&rt).next_period_start(); + + // go to the next deadline 0 + rt.epoch = period_start; + + // fill one partition in each mutable deadline. + let num_sectors = h.partition_size * (rt.policy.wpost_period_deadlines - 2); + + // creates a partition in every deadline except 0 and 47 + // TODO: when fixing last wpost test verify that this is true + let sectors = h.commit_and_prove_sectors( + &mut rt, + num_sectors as usize, + DEFAULT_SECTOR_EXPIRATION, + vec![], + true, + ); + + // prove every sector once to activate power. This + // simplifies the test a bit. + h.advance_and_submit_posts(&mut rt, §ors); + + // Make sure we're in the correct deadline. We should + // finish at deadline 2 because precommit takes some + // time. + let dlinfo = h.deadline(&rt); + assert!( + dlinfo.index < 46, + "we need to be before the target deadline for this test to make sense" + ); + + // Now find the sectors in the last partition. + let (_, partition) = h.get_deadline_and_partition(&rt, 46, 0); + let mut target_sectors = Vec::new(); + for i in partition.sectors.iter() { + for sector in sectors.iter() { + if sector.sector_number == i { + target_sectors.push(sector.clone()); + } + } + } + assert!(!target_sectors.is_empty()); + + let pwr = miner::power_for_sectors(h.sector_size, &target_sectors); + + // And challenge the last partition. + let expected_fee = miner::pledge_penalty_for_invalid_windowpost( + &h.epoch_reward_smooth, + &h.epoch_qa_power_smooth, + &pwr.qa, + ); + let post_dispute_result = PoStDisputeResult { + expected_power_delta: Some(-pwr), + expected_penalty: Some(expected_fee), + expected_reward: Some(miner::BASE_REWARD_FOR_DISPUTED_WINDOW_POST.clone()), + expected_pledge_delta: None, + }; + + let target_dlinfo = miner::DeadlineInfo::new( + period_start, + 46, + rt.epoch, + rt.policy.wpost_period_deadlines, + rt.policy.wpost_proving_period, + rt.policy.wpost_challenge_window, + rt.policy.wpost_challenge_lookback, + rt.policy.fault_declaration_cutoff, + ); + + h.dispute_window_post(&mut rt, &target_dlinfo, 0, &target_sectors, Some(post_dispute_result)); +} diff --git a/actors/miner/tests/util.rs b/actors/miner/tests/util.rs index 51155c57d..a1a006e39 100644 --- a/actors/miner/tests/util.rs +++ b/actors/miner/tests/util.rs @@ -1,3 +1,5 @@ +#![allow(clippy::all)] + use fil_actor_account::Method as AccountMethod; use fil_actor_market::{ ActivateDealsParams, ComputeDataCommitmentParams, ComputeDataCommitmentReturn, @@ -5,13 +7,16 @@ use fil_actor_market::{ VerifyDealsForActivationParams, VerifyDealsForActivationReturn, }; use fil_actor_miner::{ - initial_pledge_for_power, new_deadline_info_from_offset_and_epoch, qa_power_for_weight, Actor, - ChangeMultiaddrsParams, ChangePeerIDParams, ConfirmSectorProofsParams, CronEventPayload, - Deadline, DeadlineInfo, Deadlines, DeferredCronEventParams, DisputeWindowedPoStParams, - GetControlAddressesReturn, Method, MinerConstructorParams as ConstructorParams, Partition, - PoStPartition, PowerPair, PreCommitSectorParams, ProveCommitSectorParams, SectorOnChainInfo, - SectorPreCommitOnChainInfo, State, SubmitWindowedPoStParams, VestingFunds, WindowedPoSt, - CRON_EVENT_PROVING_DEADLINE, + initial_pledge_for_power, locked_reward_from_reward, new_deadline_info_from_offset_and_epoch, + pledge_penalty_for_continued_fault, power_for_sectors, qa_power_for_weight, Actor, + ApplyRewardParams, ChangeMultiaddrsParams, ChangePeerIDParams, ConfirmSectorProofsParams, + CronEventPayload, Deadline, DeadlineInfo, Deadlines, DeclareFaultsParams, + DeclareFaultsRecoveredParams, DeferredCronEventParams, DisputeWindowedPoStParams, + FaultDeclaration, GetControlAddressesReturn, Method, + MinerConstructorParams as ConstructorParams, Partition, PoStPartition, PowerPair, + PreCommitSectorParams, ProveCommitSectorParams, RecoveryDeclaration, SectorOnChainInfo, + SectorPreCommitOnChainInfo, Sectors, State, SubmitWindowedPoStParams, VestingFunds, + WindowedPoSt, CRON_EVENT_PROVING_DEADLINE, }; use fil_actor_power::{ CurrentTotalPowerReturn, EnrollCronEventParams, Method as PowerMethod, UpdateClaimedPowerParams, @@ -51,7 +56,7 @@ use multihash::MultihashDigest; use rand::prelude::*; -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; const RECEIVER_ID: u64 = 1000; @@ -175,7 +180,7 @@ impl ActorHarness { self.worker, AccountMethod::PubkeyAddress as u64, RawBytes::default(), - TokenAmount::from(0), + TokenAmount::from(0u8), RawBytes::serialize(self.worker_key).unwrap(), ExitCode::OK, ); @@ -374,7 +379,7 @@ impl ActorHarness { *STORAGE_MARKET_ACTOR_ADDR, MarketMethod::VerifyDealsForActivation as u64, RawBytes::serialize(vdparams).unwrap(), - TokenAmount::from(0), + TokenAmount::from(0u8), RawBytes::serialize(vdreturn).unwrap(), ExitCode::OK, ); @@ -382,7 +387,7 @@ impl ActorHarness { // in the original test the else branch does some redundant checks which we can omit. let state = self.get_state(rt); - if state.fee_debt > TokenAmount::from(0) { + if state.fee_debt > TokenAmount::from(0u8) { rt.expect_send( *BURNT_FUNDS_ACTOR_ADDR, METHOD_SEND, @@ -404,7 +409,7 @@ impl ActorHarness { *STORAGE_POWER_ACTOR_ADDR, PowerMethod::EnrollCronEvent as u64, RawBytes::serialize(cron_params).unwrap(), - TokenAmount::from(0), + TokenAmount::from(0u8), RawBytes::default(), ExitCode::OK, ); @@ -446,7 +451,7 @@ impl ActorHarness { *REWARD_ACTOR_ADDR, RewardMethod::ThisEpochReward as u64, RawBytes::default(), - TokenAmount::from(0), + TokenAmount::from(0u8), RawBytes::serialize(current_reward).unwrap(), ExitCode::OK, ); @@ -454,7 +459,7 @@ impl ActorHarness { *STORAGE_POWER_ACTOR_ADDR, PowerMethod::CurrentTotalPower as u64, RawBytes::default(), - TokenAmount::from(0), + TokenAmount::from(0u8), RawBytes::serialize(current_power).unwrap(), ExitCode::OK, ); @@ -494,7 +499,7 @@ impl ActorHarness { *STORAGE_MARKET_ACTOR_ADDR, MarketMethod::ComputeDataCommitment as u64, RawBytes::serialize(cdc_params).unwrap(), - TokenAmount::from(0), + TokenAmount::from(0u8), RawBytes::serialize(cdc_ret).unwrap(), ExitCode::OK, ); @@ -528,7 +533,7 @@ impl ActorHarness { *STORAGE_POWER_ACTOR_ADDR, PowerMethod::SubmitPoRepForBulkVerify as u64, RawBytes::serialize(seal).unwrap(), - TokenAmount::from(0), + TokenAmount::from(0u8), RawBytes::default(), ExitCode::OK, ); @@ -598,7 +603,7 @@ impl ActorHarness { *STORAGE_MARKET_ACTOR_ADDR, MarketMethod::ActivateDeals as u64, RawBytes::serialize(params).unwrap(), - TokenAmount::from(0), + TokenAmount::from(0u8), RawBytes::default(), exit, ); @@ -608,7 +613,7 @@ impl ActorHarness { } if !valid_pcs.is_empty() { - let mut expected_pledge = TokenAmount::from(0); + let mut expected_pledge = TokenAmount::from(0u8); let mut expected_qa_power = BigInt::from(0); let mut expected_raw_power = BigInt::from(0); @@ -647,12 +652,12 @@ impl ActorHarness { } } - if expected_pledge != TokenAmount::from(0) { + if expected_pledge != TokenAmount::from(0u8) { rt.expect_send( *STORAGE_POWER_ACTOR_ADDR, PowerMethod::UpdatePledgeTotal as u64, RawBytes::serialize(BigIntSer(&expected_pledge)).unwrap(), - TokenAmount::from(0), + TokenAmount::from(0u8), RawBytes::default(), ExitCode::OK, ); @@ -682,7 +687,7 @@ impl ActorHarness { dlinfo } - fn deadline(&self, rt: &MockRuntime) -> DeadlineInfo { + pub fn deadline(&self, rt: &MockRuntime) -> DeadlineInfo { let state = self.get_state(rt); state.recorded_deadline_info(&rt.policy, rt.epoch) } @@ -724,20 +729,20 @@ impl ActorHarness { *STORAGE_POWER_ACTOR_ADDR, PowerMethod::UpdateClaimedPower as u64, RawBytes::serialize(params).unwrap(), - TokenAmount::from(0), + TokenAmount::from(0u8), RawBytes::default(), ExitCode::OK, ); } - let mut penalty_total = TokenAmount::from(0); - let mut pledge_delta = TokenAmount::from(0); + let mut penalty_total = TokenAmount::from(0u8); + let mut pledge_delta = TokenAmount::from(0u8); penalty_total += cfg.continued_faults_penalty.clone(); penalty_total += cfg.repaid_fee_debt.clone(); penalty_total += cfg.expired_precommit_penalty.clone(); - if penalty_total != TokenAmount::from(0) { + if penalty_total != TokenAmount::from(0u8) { rt.expect_send( *BURNT_FUNDS_ACTOR_ADDR, METHOD_SEND, @@ -761,12 +766,12 @@ impl ActorHarness { pledge_delta += cfg.expired_sectors_pledge_delta; pledge_delta -= immediately_vesting_funds(rt, &state); - if pledge_delta != TokenAmount::from(0) { + if pledge_delta != TokenAmount::from(0u8) { rt.expect_send( *STORAGE_POWER_ACTOR_ADDR, PowerMethod::UpdatePledgeTotal as u64, RawBytes::serialize(BigIntSer(&pledge_delta)).unwrap(), - TokenAmount::from(0), + TokenAmount::from(0u8), RawBytes::default(), ExitCode::OK, ); @@ -779,7 +784,7 @@ impl ActorHarness { *STORAGE_POWER_ACTOR_ADDR, PowerMethod::EnrollCronEvent as u64, RawBytes::serialize(params).unwrap(), - TokenAmount::from(0), + TokenAmount::from(0u8), RawBytes::default(), ExitCode::OK, ); @@ -897,7 +902,7 @@ impl ActorHarness { *STORAGE_POWER_ACTOR_ADDR, PowerMethod::UpdateClaimedPower as u64, RawBytes::serialize(claim).unwrap(), - TokenAmount::from(0), + TokenAmount::from(0u8), RawBytes::default(), ExitCode::OK, ); @@ -1004,7 +1009,7 @@ impl ActorHarness { *STORAGE_POWER_ACTOR_ADDR, PowerMethod::UpdateClaimedPower as u64, RawBytes::serialize(claim).unwrap(), - TokenAmount::from(0), + TokenAmount::from(0u8), RawBytes::default(), ExitCode::OK, ); @@ -1040,7 +1045,7 @@ impl ActorHarness { *STORAGE_POWER_ACTOR_ADDR, PowerMethod::UpdatePledgeTotal as u64, RawBytes::serialize(BigIntSer(&expected_pledge_delta)).unwrap(), - TokenAmount::from(0), + TokenAmount::from(0u8), RawBytes::default(), ExitCode::OK, ); @@ -1090,6 +1095,274 @@ impl ActorHarness { caller_addrs.push(self.owner); caller_addrs } + + pub fn apply_rewards(&self, rt: &mut MockRuntime, amt: TokenAmount, penalty: TokenAmount) { + // This harness function does not handle the state where apply rewards is + // on a miner with existing fee debt. This state is not protocol reachable + // because currently fee debt prevents election participation. + // + // We further assume the miner can pay the penalty. If the miner + // goes into debt we can't rely on the harness call + // TODO unify those cases + let (lock_amt, _) = locked_reward_from_reward(amt.clone()); + let pledge_delta = lock_amt - &penalty; + + rt.set_caller(*REWARD_ACTOR_CODE_ID, *REWARD_ACTOR_ADDR); + rt.expect_validate_caller_addr(vec![*REWARD_ACTOR_ADDR]); + // expect pledge update + rt.expect_send( + *STORAGE_POWER_ACTOR_ADDR, + PowerMethod::UpdatePledgeTotal as u64, + RawBytes::serialize(BigIntSer(&pledge_delta)).unwrap(), + TokenAmount::from(0u8), + RawBytes::default(), + ExitCode::OK, + ); + + if penalty > TokenAmount::from(0u8) { + rt.expect_send( + *BURNT_FUNDS_ACTOR_ADDR, + METHOD_SEND, + RawBytes::default(), + penalty.clone(), + RawBytes::default(), + ExitCode::OK, + ); + } + + let params = ApplyRewardParams { reward: amt, penalty: penalty }; + rt.call::(Method::ApplyRewards as u64, &RawBytes::serialize(params).unwrap()) + .unwrap(); + rt.verify(); + } + + pub fn get_locked_funds(&self, rt: &MockRuntime) -> TokenAmount { + let state = self.get_state(rt); + state.locked_funds + } + + pub fn advance_and_submit_posts(&self, rt: &mut MockRuntime, sectors: &[SectorOnChainInfo]) { + // Advance between 0 and 48 deadlines submitting window posts where necessary to keep + // sectors proven. If sectors is empty this is a noop. If sectors is a singleton this + // will advance to that sector's proving deadline running deadline crons up to and + // including this deadline. If sectors includes a sector assigned to the furthest + // away deadline this will process a whole proving period. + let state = self.get_state(rt); + + // this has to go into the loop or else go deal with the borrow checker (hint: you lose) + //let sector_arr = Sectors::load(&rt.store, &state.sectors).unwrap(); + let mut deadlines: BTreeMap> = BTreeMap::new(); + for sector in sectors { + let (dlidx, _) = + state.find_sector(&rt.policy, &rt.store, sector.sector_number).unwrap(); + match deadlines.get_mut(&dlidx) { + Some(dl_sectors) => { + dl_sectors.push(sector.clone()); + } + None => { + deadlines.insert(dlidx, vec![sector.clone()]); + } + } + } + + let mut dlinfo = self.current_deadline(rt); + while deadlines.len() > 0 { + match deadlines.get(&dlinfo.index) { + None => {} + Some(dl_sectors) => { + let mut sector_nos = BitField::new(); + for sector in dl_sectors { + sector_nos.set(sector.sector_number); + } + + let dl_arr = state.load_deadlines(&rt.store).unwrap(); + let dl = dl_arr.load_deadline(&rt.policy, &rt.store, dlinfo.index).unwrap(); + let parts = Array::::load(&dl.partitions, &rt.store).unwrap(); + + let mut partitions: Vec = Vec::new(); + let mut power_delta = PowerPair::zero(); + parts + .for_each(|part_idx, part| { + let sector_arr = Sectors::load(&rt.store, &state.sectors).unwrap(); + let live = part.live_sectors(); + let to_prove = &live & §or_nos; + if to_prove.is_empty() { + return Ok(()); + } + + let mut to_skip = &live - &to_prove; + let not_recovering = &part.faults - &part.recoveries; + + // Don't double-count skips. + to_skip -= ¬_recovering; + + let skipped_proven = &to_skip - &part.unproven; + let mut skipped_proven_sector_infos = Vec::new(); + sector_arr + .amt + .for_each(|i, sector| { + if skipped_proven.get(i) { + skipped_proven_sector_infos.push(sector.clone()); + } + Ok(()) + }) + .unwrap(); + let new_faulty_power = + self.power_pair_for_sectors(&skipped_proven_sector_infos); + + let new_proven = &part.unproven - &to_skip; + let mut new_proven_infos = Vec::new(); + sector_arr + .amt + .for_each(|i, sector| { + if new_proven.get(i) { + new_proven_infos.push(sector.clone()); + } + Ok(()) + }) + .unwrap(); + let new_proven_power = self.power_pair_for_sectors(&new_proven_infos); + + power_delta -= &new_faulty_power; + power_delta += &new_proven_power; + + partitions.push(PoStPartition { + index: part_idx, + skipped: UnvalidatedBitField::Validated(to_skip), + }); + + Ok(()) + }) + .unwrap(); + + self.submit_window_post( + rt, + &dlinfo, + partitions, + dl_sectors.clone(), + PoStConfig::with_expected_power_delta(&power_delta), + ); + deadlines.remove(&dlinfo.index); + } + } + + self.advance_deadline(rt, CronConfig::empty()); + dlinfo = self.current_deadline(rt); + } + } + + pub fn declare_faults( + &self, + rt: &mut MockRuntime, + fault_sector_infos: &[SectorOnChainInfo], + ) -> PowerPair { + rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, self.worker); + rt.expect_validate_caller_addr(self.caller_addrs()); + + let ss = fault_sector_infos[0].seal_proof.sector_size().unwrap(); + let expected_delta = power_for_sectors(ss, fault_sector_infos); + let expected_raw_delta = -expected_delta.raw; + let expected_qa_delta = -expected_delta.qa; + + // expect power update + let claim = UpdateClaimedPowerParams { + raw_byte_delta: expected_raw_delta.clone(), + quality_adjusted_delta: expected_qa_delta.clone(), + }; + rt.expect_send( + *STORAGE_POWER_ACTOR_ADDR, + PowerMethod::UpdateClaimedPower as u64, + RawBytes::serialize(claim).unwrap(), + TokenAmount::from(0u8), + RawBytes::default(), + ExitCode::OK, + ); + + // Calculate params from faulted sector infos + let state = self.get_state(rt); + let params = make_fault_params_from_faulting_sectors(&rt, &state, fault_sector_infos); + rt.call::(Method::DeclareFaults as u64, &RawBytes::serialize(params).unwrap()) + .unwrap(); + rt.verify(); + + PowerPair { raw: expected_raw_delta, qa: expected_qa_delta } + } + + pub fn declare_recoveries( + &self, + rt: &mut MockRuntime, + dlidx: u64, + pidx: u64, + recovery_sectors: BitField, + expected_debt_repaid: TokenAmount, + ) { + rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, self.worker); + rt.expect_validate_caller_addr(self.caller_addrs()); + + if expected_debt_repaid > TokenAmount::from(0u8) { + rt.expect_send( + *BURNT_FUNDS_ACTOR_ADDR, + METHOD_SEND, + RawBytes::default(), + expected_debt_repaid, + RawBytes::default(), + ExitCode::OK, + ); + } + + // Calculate params from faulted sector infos + let recovery = RecoveryDeclaration { + deadline: dlidx, + partition: pidx, + sectors: UnvalidatedBitField::Validated(recovery_sectors), + }; + let params = DeclareFaultsRecoveredParams { recoveries: vec![recovery] }; + rt.call::( + Method::DeclareFaultsRecovered as u64, + &RawBytes::serialize(params).unwrap(), + ) + .unwrap(); + rt.verify(); + } + + pub fn continued_fault_penalty(&self, sectors: &[SectorOnChainInfo]) -> TokenAmount { + let pwr = power_for_sectors(self.sector_size, sectors); + pledge_penalty_for_continued_fault( + &self.epoch_reward_smooth, + &self.epoch_qa_power_smooth, + &pwr.qa, + ) + } + + pub fn find_sector(&self, rt: &MockRuntime, sno: SectorNumber) -> (Deadline, Partition) { + let state = self.get_state(rt); + let (dlidx, pidx) = state.find_sector(&rt.policy, &rt.store, sno).unwrap(); + self.get_deadline_and_partition(rt, dlidx, pidx) + } + + fn current_deadline(&self, rt: &MockRuntime) -> DeadlineInfo { + let state = self.get_state(rt); + state.deadline_info(&rt.policy, rt.epoch) + } + + fn power_pair_for_sectors(&self, sectors: &[SectorOnChainInfo]) -> PowerPair { + power_for_sectors(self.sector_size, sectors) + } + + pub fn get_deadline_and_partition( + &self, + rt: &MockRuntime, + dlidx: u64, + pidx: u64, + ) -> (Deadline, Partition) { + let deadline = self.get_deadline(&rt, dlidx); + let partition = self.get_partition(&rt, &deadline, pidx); + (deadline, partition) + } + + fn get_partition(&self, rt: &MockRuntime, deadline: &Deadline, pidx: u64) -> Partition { + deadline.load_partition(&rt.store, pidx).unwrap() + } } #[allow(dead_code)] @@ -1170,13 +1443,29 @@ impl CronConfig { expected_enrollment: 0, detected_faults_power_delta: None, expired_sectors_power_delta: None, - expired_sectors_pledge_delta: TokenAmount::from(0), - continued_faults_penalty: TokenAmount::from(0), - expired_precommit_penalty: TokenAmount::from(0), - repaid_fee_debt: TokenAmount::from(0), - penalty_from_unlocked: TokenAmount::from(0), + expired_sectors_pledge_delta: TokenAmount::from(0u8), + continued_faults_penalty: TokenAmount::from(0u8), + expired_precommit_penalty: TokenAmount::from(0u8), + repaid_fee_debt: TokenAmount::from(0u8), + penalty_from_unlocked: TokenAmount::from(0u8), } } + + pub fn with_continued_faults_penalty(fault_fee: TokenAmount) -> CronConfig { + let mut cfg = CronConfig::empty(); + cfg.continued_faults_penalty = fault_fee; + cfg + } + + pub fn with_detected_faults_power_delta_and_continued_faults_penalty( + pwr_delta: &PowerPair, + fault_fee: TokenAmount, + ) -> CronConfig { + let mut cfg = CronConfig::empty(); + cfg.detected_faults_power_delta = Some(pwr_delta.clone()); + cfg.continued_faults_penalty = fault_fee; + cfg + } } #[allow(dead_code)] @@ -1207,7 +1496,8 @@ pub fn make_bitfield(bits: &[u64]) -> UnvalidatedBitField { UnvalidatedBitField::Validated(BitField::try_from_bits(bits.iter().copied()).unwrap()) } -fn get_bitfield(ubf: &UnvalidatedBitField) -> BitField { +#[allow(dead_code)] +pub fn get_bitfield(ubf: &UnvalidatedBitField) -> BitField { match ubf { UnvalidatedBitField::Validated(bf) => bf.clone(), UnvalidatedBitField::Unvalidated(bytes) => BitField::from_bytes(bytes).unwrap(), @@ -1226,7 +1516,7 @@ enum MhCode { fn immediately_vesting_funds(rt: &MockRuntime, state: &State) -> TokenAmount { let vesting = rt.store.get_cbor::(&state.vesting_funds).unwrap().unwrap(); - let mut sum = TokenAmount::from(0); + let mut sum = TokenAmount::from(0u8); for vf in vesting.funds { if vf.epoch < rt.epoch { sum += vf.amount; @@ -1270,6 +1560,42 @@ fn make_deferred_cron_event_params( } } +fn make_fault_params_from_faulting_sectors( + rt: &MockRuntime, + state: &State, + fault_sector_infos: &[SectorOnChainInfo], +) -> DeclareFaultsParams { + let mut declaration_map: BTreeMap<(u64, u64), FaultDeclaration> = BTreeMap::new(); + for sector in fault_sector_infos { + let (dlidx, pidx) = state.find_sector(&rt.policy, &rt.store, sector.sector_number).unwrap(); + match declaration_map.get_mut(&(dlidx, pidx)) { + Some(declaration) => { + declaration.sectors.validate_mut().unwrap().set(sector.sector_number); + } + None => { + let mut bf = BitField::new(); + bf.set(sector.sector_number); + + let declaration = FaultDeclaration { + deadline: dlidx, + partition: pidx, + sectors: UnvalidatedBitField::Validated(bf), + }; + + declaration_map.insert((dlidx, pidx), declaration); + } + } + } + + // I want to just write: + // let declarations = declaration_map.values().collect(); + // but the compiler doesn't let me; so I do it by hand like a savange + let keys: Vec<(u64, u64)> = declaration_map.keys().cloned().collect(); + let declarations = keys.iter().map(|k| declaration_map.remove(k).unwrap()).collect(); + + DeclareFaultsParams { faults: declarations } +} + #[allow(dead_code)] pub fn amt_to_vec(rt: &MockRuntime, c: &Cid) -> Vec where @@ -1300,7 +1626,7 @@ fn fixed_hasher(offset: ChainEpoch) -> Box [u8; 32]> { let hash = move |_: &[u8]| -> [u8; 32] { let mut result = [0u8; 32]; for (i, item) in result.iter_mut().enumerate().take(8) { - *item = ((offset >> (7 - i)) & 0xff) as u8; + *item = ((offset >> (8 * (7 - i))) & 0xff) as u8; } result };