diff --git a/actors/power/tests/harness/mod.rs b/actors/power/tests/harness/mod.rs index 88dc20d93..a0aa0150d 100644 --- a/actors/power/tests/harness/mod.rs +++ b/actors/power/tests/harness/mod.rs @@ -24,7 +24,10 @@ use serde::Serialize; use fil_actor_power::ext::init::ExecParams; use fil_actor_power::ext::miner::MinerConstructorParams; -use fil_actor_power::{ext, Claim, CreateMinerParams, CreateMinerReturn, Method, State}; +use fil_actor_power::{ + ext, Claim, CreateMinerParams, CreateMinerReturn, CurrentTotalPowerReturn, Method, State, + UpdateClaimedPowerParams, +}; use fil_actors_runtime::builtin::HAMT_BIT_WIDTH; use fil_actors_runtime::runtime::Runtime; use fil_actors_runtime::test_utils::{ @@ -75,7 +78,7 @@ pub fn setup() -> (Harness, MockRuntime) { pub struct Harness { miner_seq: i64, seal_proof: RegisteredSealProof, - window_post_proof: RegisteredPoStProof, + pub window_post_proof: RegisteredPoStProof, this_epoch_baseline_power: StoragePower, this_epoch_reward_smoothed: FilterEstimate, } @@ -159,6 +162,30 @@ impl Harness { Ok(()) } + pub fn create_miner_basic( + &mut self, + rt: &mut MockRuntime, + owner: Address, + worker: Address, + miner: Address, + ) -> Result<(), ActorError> { + let label = format!("{}", self.miner_seq); + let actr_addr = Address::new_actor(label.as_bytes()); + self.miner_seq += 1; + let peer = label.as_bytes().to_vec(); + self.create_miner( + rt, + &owner, + &worker, + &miner, + &actr_addr, + peer, + vec![], + self.window_post_proof, + &TokenAmount::from(0), + ) + } + pub fn list_miners(&self, rt: &MockRuntime) -> Vec
{ let st: State = rt.get_state(); let claims: Map<_, Claim> = @@ -228,6 +255,90 @@ impl Harness { pub fn check_state(&self) { // TODO: https://github.com/filecoin-project/builtin-actors/issues/44 } + + pub fn update_pledge_total(&self, rt: &mut MockRuntime, miner: Address, delta: &TokenAmount) { + let st: State = rt.get_state(); + let prev = st.total_pledge_collateral; + + rt.set_caller(*MINER_ACTOR_CODE_ID, miner); + rt.expect_validate_caller_type(vec![*MINER_ACTOR_CODE_ID]); + rt.call::( + Method::UpdatePledgeTotal as MethodNum, + &RawBytes::serialize(BigIntDe(delta.clone())).unwrap(), + ) + .unwrap(); + rt.verify(); + + let st: State = rt.get_state(); + assert_eq!(prev + delta, st.total_pledge_collateral); + } + + pub fn current_power_total(&self, rt: &mut MockRuntime) -> CurrentTotalPowerReturn { + rt.expect_validate_caller_any(); + let ret: CurrentTotalPowerReturn = rt + .call::(Method::CurrentTotalPower as u64, &RawBytes::default()) + .unwrap() + .deserialize() + .unwrap(); + rt.verify(); + ret + } + + pub fn update_claimed_power( + &self, + rt: &mut MockRuntime, + miner: Address, + raw_delta: &StoragePower, + qa_delta: &StoragePower, + ) { + let prev_cl = self.get_claim(rt, &miner).unwrap(); + + let params = UpdateClaimedPowerParams { + raw_byte_delta: raw_delta.clone(), + quality_adjusted_delta: qa_delta.clone(), + }; + rt.set_caller(*MINER_ACTOR_CODE_ID, miner); + rt.expect_validate_caller_type(vec![*MINER_ACTOR_CODE_ID]); + rt.call::( + Method::UpdateClaimedPower as MethodNum, + &RawBytes::serialize(params).unwrap(), + ) + .unwrap(); + rt.verify(); + + let cl = self.get_claim(rt, &miner).unwrap(); + let expected_raw = &prev_cl.raw_byte_power + raw_delta; + let expected_adjusted = &prev_cl.quality_adj_power + qa_delta; + if expected_raw.is_zero() { + assert!(cl.raw_byte_power.is_zero()); + } else { + assert_eq!(prev_cl.raw_byte_power + raw_delta, cl.raw_byte_power); + } + + if expected_adjusted.is_zero() { + assert!(cl.quality_adj_power.is_zero()); + } else { + assert_eq!(prev_cl.quality_adj_power + qa_delta, cl.quality_adj_power); + } + } + + pub fn expect_total_power_eager( + &self, + rt: &mut MockRuntime, + expected_raw: &StoragePower, + expected_qa: &StoragePower, + ) { + let st: State = rt.get_state(); + + let (raw_byte_power, quality_adj_power) = st.current_total_power(); + assert_eq!(expected_raw, &raw_byte_power); + assert_eq!(expected_qa, &quality_adj_power); + } + + pub fn expect_total_pledge_eager(&self, rt: &mut MockRuntime, expected_pledge: &TokenAmount) { + let st: State = rt.get_state(); + assert_eq!(expected_pledge, &st.total_pledge_collateral); + } } /// Collects all keys from a map into a vector. diff --git a/actors/power/tests/power_actor_tests.rs b/actors/power/tests/power_actor_tests.rs index 30c0c3c25..7e500e7b4 100644 --- a/actors/power/tests/power_actor_tests.rs +++ b/actors/power/tests/power_actor_tests.rs @@ -4,7 +4,7 @@ use fil_actors_runtime::test_utils::{ expect_abort, expect_abort_contains_message, ACCOUNT_ACTOR_CODE_ID, CALLER_TYPES_SIGNABLE, MINER_ACTOR_CODE_ID, SYSTEM_ACTOR_CODE_ID, }; -use fil_actors_runtime::INIT_ACTOR_ADDR; +use fil_actors_runtime::{runtime::Policy, INIT_ACTOR_ADDR}; use fvm_ipld_encoding::{BytesDe, RawBytes}; use fvm_shared::address::Address; use fvm_shared::bigint::bigint_ser::BigIntSer; @@ -13,10 +13,11 @@ use fvm_shared::econ::TokenAmount; use fvm_shared::error::ExitCode; use fvm_shared::sector::{RegisteredPoStProof, StoragePower}; use num_traits::Zero; +use std::ops::Neg; use fil_actor_power::{ - Actor as PowerActor, CreateMinerParams, EnrollCronEventParams, Method, State, - UpdateClaimedPowerParams, + consensus_miner_min_power, Actor as PowerActor, CreateMinerParams, EnrollCronEventParams, + Method, State, UpdateClaimedPowerParams, CONSENSUS_MINER_MIN_MINERS, }; use crate::harness::*; @@ -203,6 +204,63 @@ fn claimed_power_given_claim_does_not_exist_should_fail() { h.check_state(); } +const MINER1: Address = Address::new_id(111); +const MINER2: Address = Address::new_id(112); +const MINER3: Address = Address::new_id(113); +const MINER4: Address = Address::new_id(114); +const MINER5: Address = Address::new_id(115); + +#[test] +fn power_and_pledge_accounted_below_threshold() { + assert_eq!(CONSENSUS_MINER_MIN_MINERS, 4); + + let small_power_unit = &StoragePower::from(1_000_000); + let small_power_unit_x2 = &(small_power_unit * 2); + let small_power_unit_x3 = &(small_power_unit * 3); + + let (mut h, mut rt) = setup(); + + h.create_miner_basic(&mut rt, *OWNER, *OWNER, MINER1).unwrap(); + h.create_miner_basic(&mut rt, *OWNER, *OWNER, MINER2).unwrap(); + + let ret = h.current_power_total(&mut rt); + assert_eq!(StoragePower::zero(), ret.raw_byte_power); + assert_eq!(StoragePower::zero(), ret.quality_adj_power); + assert_eq!(TokenAmount::zero(), ret.pledge_collateral); + + // Add power for miner1 + h.update_claimed_power(&mut rt, MINER1, small_power_unit, small_power_unit_x2); + h.expect_total_power_eager(&mut rt, small_power_unit, small_power_unit_x2); + + // Add power and pledge for miner2 + h.update_claimed_power(&mut rt, MINER2, small_power_unit, small_power_unit); + h.update_pledge_total(&mut rt, MINER1, &TokenAmount::from(1_000_000)); + h.expect_total_power_eager(&mut rt, small_power_unit_x2, small_power_unit_x3); + h.expect_total_pledge_eager(&mut rt, &TokenAmount::from(1_000_000)); + + rt.verify(); + + // Verify claims in state. + let claim1 = h.get_claim(&rt, &MINER1).unwrap(); + assert_eq!(small_power_unit, &claim1.raw_byte_power); + assert_eq!(small_power_unit_x2, &claim1.quality_adj_power); + + let claim2 = h.get_claim(&rt, &MINER2).unwrap(); + assert_eq!(small_power_unit, &claim2.raw_byte_power); + assert_eq!(small_power_unit, &claim2.quality_adj_power); + + // Subtract power and some pledge for miner2 + h.update_claimed_power(&mut rt, MINER2, &small_power_unit.neg(), &small_power_unit.neg()); + h.update_pledge_total(&mut rt, MINER2, &TokenAmount::from(100_000).neg()); + h.expect_total_power_eager(&mut rt, small_power_unit, small_power_unit_x2); + h.expect_total_pledge_eager(&mut rt, &TokenAmount::from(900_000)); + + let claim2 = h.get_claim(&rt, &MINER2).unwrap(); + assert!(claim2.raw_byte_power.is_zero()); + assert!(claim2.quality_adj_power.is_zero()); + h.check_state(); +} + #[test] fn enroll_cron_epoch_multiple_events() { let (h, mut rt) = setup(); @@ -315,6 +373,77 @@ fn enroll_cron_epoch_before_current_epoch() { h.check_state(); } +#[test] +fn new_miner_updates_miner_above_min_power_count() { + struct TestCase { + proof: RegisteredPoStProof, + expected_miners: i64, + } + + let test_cases = [ + TestCase { proof: RegisteredPoStProof::StackedDRGWindow2KiBV1, expected_miners: 0 }, + TestCase { proof: RegisteredPoStProof::StackedDRGWindow32GiBV1, expected_miners: 0 }, + ]; + + for test in test_cases { + let (mut h, mut rt) = setup(); + h.window_post_proof = test.proof; + h.create_miner_basic(&mut rt, *OWNER, *OWNER, MINER1).unwrap(); + + let st: State = rt.get_state(); + assert_eq!(test.expected_miners, st.miner_above_min_power_count); + } +} + +#[test] +fn power_accounting_crossing_threshold() { + let small_power_unit = &StoragePower::from(1_000_000); + let small_power_unit_x10 = &(small_power_unit * 10); + + let power_unit = &consensus_miner_min_power( + &Policy::default(), + RegisteredPoStProof::StackedDRGWindow32GiBV1, + ) + .unwrap(); + let power_unit_x10 = &(power_unit * 10); + + assert!(small_power_unit < power_unit); + + let (mut h, mut rt) = setup(); + + h.create_miner_basic(&mut rt, *OWNER, *OWNER, MINER1).unwrap(); + h.create_miner_basic(&mut rt, *OWNER, *OWNER, MINER2).unwrap(); + h.create_miner_basic(&mut rt, *OWNER, *OWNER, MINER3).unwrap(); + h.create_miner_basic(&mut rt, *OWNER, *OWNER, MINER4).unwrap(); + h.create_miner_basic(&mut rt, *OWNER, *OWNER, MINER5).unwrap(); + + // Use qa power 10x raw power to show it's not being used for threshold calculations. + h.update_claimed_power(&mut rt, MINER1, small_power_unit, small_power_unit_x10); + h.update_claimed_power(&mut rt, MINER2, small_power_unit, small_power_unit_x10); + + h.update_claimed_power(&mut rt, MINER3, power_unit, power_unit_x10); + h.update_claimed_power(&mut rt, MINER4, power_unit, power_unit_x10); + h.update_claimed_power(&mut rt, MINER5, power_unit, power_unit_x10); + + // Below threshold small miner power is counted + let expected_total_below = small_power_unit * 2 + power_unit * 3; + h.expect_total_power_eager(&mut rt, &expected_total_below, &(&expected_total_below * 10)); + + // Above threshold (power.ConsensusMinerMinMiners = 4) small miner power is ignored + let delta = &(power_unit - small_power_unit); + h.update_claimed_power(&mut rt, MINER2, delta, &(delta * 10)); + let expected_total_above = &(power_unit * 4); + h.expect_total_power_eager(&mut rt, expected_total_above, &(expected_total_above * 10)); + + let st: State = rt.get_state(); + assert_eq!(4, st.miner_above_min_power_count); + + // Less than 4 miners above threshold again small miner power is counted again + h.update_claimed_power(&mut rt, MINER4, &delta.neg(), &(delta.neg() * 10)); + h.expect_total_power_eager(&mut rt, &expected_total_below, &(&expected_total_below * 10)); + h.check_state(); +} + #[test] fn enroll_cron_epoch_given_negative_epoch_should_fail() { let (h, mut rt) = setup();