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();