From 5d1ea3213f33695e581304b53886a2e7319f6487 Mon Sep 17 00:00:00 2001 From: Aayush Rajasekaran Date: Wed, 9 Nov 2022 09:23:27 -0500 Subject: [PATCH] Power actor: Add exported getters for raw power (#810) * Power actor: Add exported getters for raw power * FRC-XXXX is FRC-0042 * Power actor: network_raw_power: Return this_epoch_raw_byte_power * Power actor: miner_raw_power: Return whether above consensus min power * Power actor: types: serialize one-element structs transparently * Address review * Miner actor: Add exported getters for info and monies (#811) * Miner actor: Add exported getters for info and monies * Tweak comment * Miner actor: Replace GetWorker and GetControls with IsControllingAddress * Miner actor: Add exported GetAvailableBalance * Miner actor: Add exported GetVestingFunds * Miner actor: Remove exported monies getters * Miner actor: types: serialize one-element structs transparently * Address review * Address review --- Cargo.lock | 1 + actors/datacap/src/lib.rs | 4 +- actors/init/src/lib.rs | 2 +- actors/miner/src/lib.rs | 94 ++++++++++++++- actors/miner/src/state.rs | 2 +- actors/miner/src/types.rs | 49 +++++++- actors/miner/tests/apply_rewards.rs | 15 ++- actors/miner/tests/exported_getters.rs | 150 ++++++++++++++++++++++++ actors/miner/tests/util.rs | 28 +++-- actors/multisig/src/lib.rs | 2 +- actors/power/Cargo.toml | 1 + actors/power/src/lib.rs | 40 +++++++ actors/power/src/state.rs | 39 +++--- actors/power/src/types.rs | 38 +++++- actors/power/tests/harness/mod.rs | 4 +- actors/power/tests/power_actor_tests.rs | 59 +++++++++- actors/verifreg/src/lib.rs | 2 +- 17 files changed, 480 insertions(+), 50 deletions(-) create mode 100644 actors/miner/tests/exported_getters.rs diff --git a/Cargo.lock b/Cargo.lock index bbf2c946c7..8f6772108f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1583,6 +1583,7 @@ dependencies = [ "cid", "fil_actor_reward", "fil_actors_runtime", + "frc42_dispatch", "fvm_ipld_blockstore", "fvm_ipld_encoding", "fvm_ipld_hamt", diff --git a/actors/datacap/src/lib.rs b/actors/datacap/src/lib.rs index fd9b2df28d..1299345084 100644 --- a/actors/datacap/src/lib.rs +++ b/actors/datacap/src/lib.rs @@ -46,7 +46,7 @@ lazy_static! { /// Datacap actor methods available /// Some methods are available under 2 method nums -- a static number for "private" builtin actor usage, -/// and via FRC-XXXX calling convention, with number determined by method name. +/// and via FRC-0042 calling convention, with number determined by method name. #[derive(FromPrimitive)] #[repr(u64)] pub enum Method { @@ -67,7 +67,7 @@ pub enum Method { Burn = 19, BurnFrom = 20, Allowance = 21, - // Method numbers derived from FRC-XXXX standards + // Method numbers derived from FRC-0042 standards NameExported = frc42_dispatch::method_hash!("Name"), SymbolExported = frc42_dispatch::method_hash!("Symbol"), TotalSupplyExported = frc42_dispatch::method_hash!("TotalSupply"), diff --git a/actors/init/src/lib.rs b/actors/init/src/lib.rs index 34dd4b302f..bb2a7ef376 100644 --- a/actors/init/src/lib.rs +++ b/actors/init/src/lib.rs @@ -34,7 +34,7 @@ pub enum Method { Exec4 = 3, #[cfg(feature = "m2-native")] InstallCode = 4, - // Method numbers derived from FRC-XXXX standards + // Method numbers derived from FRC-0042 standards ExecExported = frc42_dispatch::method_hash!("Exec"), } diff --git a/actors/miner/src/lib.rs b/actors/miner/src/lib.rs index 9b88ed5824..7e99005df8 100644 --- a/actors/miner/src/lib.rs +++ b/actors/miner/src/lib.rs @@ -23,6 +23,7 @@ use fvm_shared::reward::ThisEpochRewardReturn; use fvm_shared::sector::*; use fvm_shared::smooth::FilterEstimate; use fvm_shared::{MethodNum, METHOD_CONSTRUCTOR, METHOD_SEND}; +use itertools::Itertools; use log::{error, info, warn}; use multihash::Code::Blake2b256; use num_derive::FromPrimitive; @@ -123,9 +124,14 @@ pub enum Method { ChangeBeneficiary = 30, GetBeneficiary = 31, ExtendSectorExpiration2 = 32, - // Method numbers derived from FRC-XXXX standards + // Method numbers derived from FRC-0042 standards ChangeBenificiaryExported = frc42_dispatch::method_hash!("ChangeBeneficiary"), GetBeneficiaryExported = frc42_dispatch::method_hash!("GetBeneficiary"), + GetOwnerExported = frc42_dispatch::method_hash!("GetOwner"), + IsControllingAddressExported = frc42_dispatch::method_hash!("IsControllingAddress"), + GetSectorSizeExported = frc42_dispatch::method_hash!("GetSectorSize"), + GetAvailableBalanceExported = frc42_dispatch::method_hash!("GetAvailableBalance"), + GetVestingFundsExported = frc42_dispatch::method_hash!("GetVestingFunds"), } pub const ERR_BALANCE_INVARIANTS_BROKEN: ExitCode = ExitCode::new(1000); @@ -205,6 +211,7 @@ impl Actor { Ok(()) } + /// Returns the "controlling" addresses: the owner, the worker, and all control addresses fn control_addresses(rt: &mut impl Runtime) -> Result { rt.validate_immediate_caller_accept_any()?; let state: State = rt.state()?; @@ -216,6 +223,71 @@ impl Actor { }) } + /// Returns the owner address + fn get_owner(rt: &mut impl Runtime) -> Result { + rt.validate_immediate_caller_accept_any()?; + let state: State = rt.state()?; + let owner = get_miner_info(rt.store(), &state)?.owner; + Ok(GetOwnerReturn { owner }) + } + + /// Returns whether the provided address is "controlling". + /// The "controlling" addresses are the Owner, the Worker, and all Control Addresses. + fn is_controlling_address( + rt: &mut impl Runtime, + params: IsControllingAddressParam, + ) -> Result { + rt.validate_immediate_caller_accept_any()?; + let input = match rt.resolve_address(¶ms.address) { + Some(a) => Address::new_id(a), + None => return Ok(IsControllingAddressReturn { is_controlling: false }), + }; + let state: State = rt.state()?; + let info = get_miner_info(rt.store(), &state)?; + let is_controlling = info + .control_addresses + .iter() + .chain(&[info.worker, info.owner]) + .into_iter() + .any(|a| *a == input); + + Ok(IsControllingAddressReturn { is_controlling }) + } + + /// Returns the miner's sector size + fn get_sector_size(rt: &mut impl Runtime) -> Result { + rt.validate_immediate_caller_accept_any()?; + let state: State = rt.state()?; + let sector_size = get_miner_info(rt.store(), &state)?.sector_size; + Ok(GetSectorSizeReturn { sector_size }) + } + + /// Returns the available balance of this miner. + /// This is calculated as actor balance - (vesting funds + pre-commit deposit + ip requirement + fee debt) + /// Can go negative if the miner is in IP debt. + fn get_available_balance( + rt: &mut impl Runtime, + ) -> Result { + rt.validate_immediate_caller_accept_any()?; + let state: State = rt.state()?; + let available_balance = + state.get_available_balance(&rt.current_balance()).map_err(|e| { + actor_error!(illegal_state, "failed to calculate available balance: {}", e) + })?; + Ok(GetAvailableBalanceReturn { available_balance }) + } + + /// Returns the funds vesting in this miner as a list of (vesting_epoch, vesting_amount) tuples. + fn get_vesting_funds(rt: &mut impl Runtime) -> Result { + rt.validate_immediate_caller_accept_any()?; + let state: State = rt.state()?; + let vesting_funds = state + .load_vesting_funds(rt.store()) + .map_err(|e| actor_error!(illegal_state, "failed to load vesting funds: {}", e))?; + let ret = vesting_funds.funds.into_iter().map(|v| (v.epoch, v.amount)).collect_vec(); + Ok(GetVestingFundsReturn { vesting_funds: ret }) + } + /// Will ALWAYS overwrite the existing control addresses with the control addresses passed in the params. /// If an empty addresses vector is passed, the control addresses will be cleared. /// A worker change will be scheduled if the worker passed in the params is different from the existing worker. @@ -5001,6 +5073,26 @@ impl ActorCode for Actor { Ok(RawBytes::default()) } None => Err(actor_error!(unhandled_message, "Invalid method")), + Some(Method::GetOwnerExported) => { + let res = Self::get_owner(rt)?; + Ok(RawBytes::serialize(res)?) + } + Some(Method::IsControllingAddressExported) => { + let res = Self::is_controlling_address(rt, cbor::deserialize_params(params)?)?; + Ok(RawBytes::serialize(res)?) + } + Some(Method::GetSectorSizeExported) => { + let res = Self::get_sector_size(rt)?; + Ok(RawBytes::serialize(res)?) + } + Some(Method::GetAvailableBalanceExported) => { + let res = Self::get_available_balance(rt)?; + Ok(RawBytes::serialize(res)?) + } + Some(Method::GetVestingFundsExported) => { + let res = Self::get_vesting_funds(rt)?; + Ok(RawBytes::serialize(res)?) + } } } } diff --git a/actors/miner/src/state.rs b/actors/miner/src/state.rs index e59aead7f0..b44c315ca7 100644 --- a/actors/miner/src/state.rs +++ b/actors/miner/src/state.rs @@ -983,7 +983,7 @@ impl State { &self, actor_balance: &TokenAmount, ) -> anyhow::Result { - // (actor_balance - &self.locked_funds) - &self.pre_commit_deposit + // (actor_balance - &self.locked_funds) - &self.pre_commit_deposit - &self.initial_pledge Ok(self.get_unlocked_balance(actor_balance)? - &self.fee_debt) } diff --git a/actors/miner/src/types.rs b/actors/miner/src/types.rs index 0d8dd31f18..49c8290d78 100644 --- a/actors/miner/src/types.rs +++ b/actors/miner/src/types.rs @@ -13,7 +13,7 @@ use fvm_shared::econ::TokenAmount; use fvm_shared::randomness::Randomness; use fvm_shared::sector::{ PoStProof, RegisteredPoStProof, RegisteredSealProof, RegisteredUpdateProof, SectorNumber, - StoragePower, + SectorSize, StoragePower, }; use fvm_shared::smooth::FilterEstimate; @@ -482,3 +482,50 @@ pub struct GetBeneficiaryReturn { } impl Cbor for GetBeneficiaryReturn {} + +#[derive(Serialize_tuple, Deserialize_tuple)] +#[serde(transparent)] +pub struct GetOwnerReturn { + pub owner: Address, +} + +impl Cbor for GetOwnerReturn {} + +#[derive(Serialize_tuple, Deserialize_tuple)] +#[serde(transparent)] +pub struct IsControllingAddressParam { + pub address: Address, +} + +impl Cbor for IsControllingAddressParam {} + +#[derive(Serialize_tuple, Deserialize_tuple)] +#[serde(transparent)] +pub struct IsControllingAddressReturn { + pub is_controlling: bool, +} + +impl Cbor for IsControllingAddressReturn {} + +#[derive(Serialize_tuple, Deserialize_tuple)] +#[serde(transparent)] +pub struct GetSectorSizeReturn { + pub sector_size: SectorSize, +} + +impl Cbor for GetSectorSizeReturn {} + +#[derive(Serialize_tuple, Deserialize_tuple)] +#[serde(transparent)] +pub struct GetAvailableBalanceReturn { + pub available_balance: TokenAmount, +} + +impl Cbor for GetAvailableBalanceReturn {} + +#[derive(Serialize_tuple, Deserialize_tuple)] +pub struct GetVestingFundsReturn { + pub vesting_funds: Vec<(ChainEpoch, TokenAmount)>, +} + +impl Cbor for GetVestingFundsReturn {} diff --git a/actors/miner/tests/apply_rewards.rs b/actors/miner/tests/apply_rewards.rs index 1bec82b515..44f36c3321 100644 --- a/actors/miner/tests/apply_rewards.rs +++ b/actors/miner/tests/apply_rewards.rs @@ -18,6 +18,7 @@ use fvm_shared::error::ExitCode; use fvm_shared::METHOD_SEND; mod util; + use fil_actor_miner::testing::check_state_invariants; use util::*; @@ -165,11 +166,12 @@ fn rewards_pay_back_fee_debt() { assert!(st.locked_funds.is_zero()); let amt = rt.get_balance(); - let available_before = st.get_available_balance(&amt).unwrap(); + let available_before = h.get_available_balance(&mut rt).unwrap(); assert!(available_before.is_positive()); let init_fee_debt: TokenAmount = 2 * &amt; // FeeDebt twice total balance st.fee_debt = init_fee_debt.clone(); - let available_after = st.get_available_balance(&amt).unwrap(); + rt.replace_state(&st); + let available_after = h.get_available_balance(&mut rt).unwrap(); assert!(available_after.is_negative()); rt.replace_state(&st); @@ -178,7 +180,7 @@ fn rewards_pay_back_fee_debt() { let penalty = TokenAmount::zero(); // manually update actor balance to include the added funds from outside let new_balance = &amt + &reward; - rt.set_balance(new_balance.clone()); + rt.set_balance(new_balance); // pledge change is new reward - reward taken for fee debt // 3*LockedRewardFactor*amt - 2*amt = remainingLocked @@ -203,7 +205,7 @@ fn rewards_pay_back_fee_debt() { BURNT_FUNDS_ACTOR_ADDR, METHOD_SEND, RawBytes::default(), - expect_burnt.clone(), + expect_burnt, RawBytes::default(), ExitCode::OK, ); @@ -212,13 +214,10 @@ fn rewards_pay_back_fee_debt() { rt.call::(Method::ApplyRewards as u64, &RawBytes::serialize(params).unwrap()).unwrap(); rt.verify(); - // Set balance to deduct fee - let final_balance = &new_balance - &expect_burnt; - let st = h.get_state(&rt); // balance funds used to pay off fee debt // available balance should be 2 - let available_balance = st.get_available_balance(&final_balance).unwrap(); + let available_balance = h.get_available_balance(&mut rt).unwrap(); assert_eq!(available_before + reward - init_fee_debt - &remaining_locked, available_balance); assert!(!st.fee_debt.is_positive()); // remaining funds locked in vesting table diff --git a/actors/miner/tests/exported_getters.rs b/actors/miner/tests/exported_getters.rs new file mode 100644 index 0000000000..459ec768d9 --- /dev/null +++ b/actors/miner/tests/exported_getters.rs @@ -0,0 +1,150 @@ +use fil_actor_miner::{ + Actor, GetAvailableBalanceReturn, GetOwnerReturn, GetSectorSizeReturn, + IsControllingAddressParam, IsControllingAddressReturn, Method, +}; +use fil_actors_runtime::cbor::serialize; +use fil_actors_runtime::test_utils::make_identity_cid; +use fil_actors_runtime::INIT_ACTOR_ADDR; +use fvm_ipld_encoding::RawBytes; +use fvm_shared::address::Address; +use fvm_shared::{clock::ChainEpoch, econ::TokenAmount, sector::MAX_SECTOR_NUMBER}; +use std::ops::Sub; + +mod util; + +use util::*; + +const PERIOD_OFFSET: ChainEpoch = 100; + +// an expiration ~10 days greater than effective min expiration taking into account 30 days max +// between pre and prove commit +const DEFAULT_SECTOR_EXPIRATION: ChainEpoch = 220; + +#[test] +fn info_getters() { + let h = ActorHarness::new(PERIOD_OFFSET); + let mut rt = h.new_runtime(); + rt.set_balance(BIG_BALANCE.clone()); + h.construct_and_verify(&mut rt); + + // set caller to not-builtin + rt.set_caller(make_identity_cid(b"1234"), Address::new_id(1234)); + + // owner is good + rt.expect_validate_caller_any(); + let owner_ret: GetOwnerReturn = rt + .call::(Method::GetOwnerExported as u64, &RawBytes::default()) + .unwrap() + .deserialize() + .unwrap(); + + rt.verify(); + + assert_eq!(h.owner, owner_ret.owner); + + // check that the controlling addresses all return true + for control in h.control_addrs.iter().chain(&[h.worker, h.owner]) { + rt.expect_validate_caller_any(); + let is_control_ret: IsControllingAddressReturn = rt + .call::( + Method::IsControllingAddressExported as u64, + &serialize(&IsControllingAddressParam { address: *control }, "serializing control") + .unwrap(), + ) + .unwrap() + .deserialize() + .unwrap(); + assert!(is_control_ret.is_controlling); + + rt.verify(); + } + + // check that a non-controlling address doesn't return true + + rt.expect_validate_caller_any(); + let is_control_ret: IsControllingAddressReturn = rt + .call::( + Method::IsControllingAddressExported as u64, + &serialize( + &IsControllingAddressParam { address: INIT_ACTOR_ADDR }, + "serializing control", + ) + .unwrap(), + ) + .unwrap() + .deserialize() + .unwrap(); + assert!(!is_control_ret.is_controlling); + + rt.verify(); + + // sector size is good + rt.expect_validate_caller_any(); + let sector_size_ret: GetSectorSizeReturn = rt + .call::(Method::GetSectorSizeExported as u64, &RawBytes::default()) + .unwrap() + .deserialize() + .unwrap(); + + rt.verify(); + + assert_eq!(h.sector_size, sector_size_ret.sector_size); + + h.check_state(&rt); +} + +#[test] +fn collateral_getters() { + let h = ActorHarness::new(PERIOD_OFFSET); + let mut rt = h.new_runtime(); + rt.balance.replace(BIG_BALANCE.clone()); + + let precommit_epoch = PERIOD_OFFSET + 1; + rt.set_epoch(precommit_epoch); + + h.construct_and_verify(&mut rt); + let dl_info = h.deadline(&rt); + + // Precommit a sector + // Use the max sector number to make sure everything works. + let sector_no = MAX_SECTOR_NUMBER; + let prove_commit_epoch = precommit_epoch + rt.policy.pre_commit_challenge_delay + 1; + let expiration = + dl_info.period_end() + DEFAULT_SECTOR_EXPIRATION * rt.policy.wpost_proving_period; // something on deadline boundary but > 180 days + + let precommit_params = + h.make_pre_commit_params(sector_no, precommit_epoch - 1, expiration, vec![]); + let precommit = + h.pre_commit_sector_and_get(&mut rt, precommit_params, PreCommitConfig::empty(), true); + + // run prove commit logic + rt.set_epoch(prove_commit_epoch); + let actor_balance = TokenAmount::from_whole(1000); + rt.balance.replace(actor_balance.clone()); + let pcc = ProveCommitConfig::empty(); + + let sector = h + .prove_commit_sector_and_confirm( + &mut rt, + &precommit, + h.make_prove_commit_params(sector_no), + pcc, + ) + .unwrap(); + + // query available balance + + rt.expect_validate_caller_any(); + let available_balance_ret: GetAvailableBalanceReturn = rt + .call::(Method::GetAvailableBalanceExported as u64, &RawBytes::default()) + .unwrap() + .deserialize() + .unwrap(); + + rt.verify(); + + // let's be sure we're not vacuously testing this method + assert_eq!(actor_balance.sub(sector.initial_pledge), available_balance_ret.available_balance); + + h.check_state(&rt); +} diff --git a/actors/miner/tests/util.rs b/actors/miner/tests/util.rs index 48861b90b3..12c1a35176 100644 --- a/actors/miner/tests/util.rs +++ b/actors/miner/tests/util.rs @@ -22,14 +22,14 @@ use fil_actor_miner::{ CronEventPayload, Deadline, DeadlineInfo, Deadlines, DeclareFaultsParams, DeclareFaultsRecoveredParams, DeferredCronEventParams, DisputeWindowedPoStParams, ExpirationQueue, ExpirationSet, ExtendSectorExpiration2Params, ExtendSectorExpirationParams, - FaultDeclaration, GetBeneficiaryReturn, GetControlAddressesReturn, Method, - MinerConstructorParams as ConstructorParams, MinerInfo, Partition, PendingBeneficiaryChange, - PoStPartition, PowerPair, PreCommitSectorBatchParams, PreCommitSectorBatchParams2, - PreCommitSectorParams, ProveCommitSectorParams, RecoveryDeclaration, - ReportConsensusFaultParams, SectorOnChainInfo, SectorPreCommitInfo, SectorPreCommitOnChainInfo, - Sectors, State, SubmitWindowedPoStParams, TerminateSectorsParams, TerminationDeclaration, - VestingFunds, WindowedPoSt, WithdrawBalanceParams, WithdrawBalanceReturn, - CRON_EVENT_PROVING_DEADLINE, SECTORS_AMT_BITWIDTH, + FaultDeclaration, GetAvailableBalanceReturn, GetBeneficiaryReturn, GetControlAddressesReturn, + Method, MinerConstructorParams as ConstructorParams, MinerInfo, Partition, + PendingBeneficiaryChange, PoStPartition, PowerPair, PreCommitSectorBatchParams, + PreCommitSectorBatchParams2, PreCommitSectorParams, ProveCommitSectorParams, + RecoveryDeclaration, ReportConsensusFaultParams, SectorOnChainInfo, SectorPreCommitInfo, + SectorPreCommitOnChainInfo, Sectors, State, SubmitWindowedPoStParams, TerminateSectorsParams, + TerminationDeclaration, VestingFunds, WindowedPoSt, WithdrawBalanceParams, + WithdrawBalanceReturn, CRON_EVENT_PROVING_DEADLINE, SECTORS_AMT_BITWIDTH, }; use fil_actor_miner::{Method as MinerMethod, ProveCommitAggregateParams}; use fil_actor_power::{ @@ -861,6 +861,7 @@ impl ActorHarness { pc: &SectorPreCommitOnChainInfo, params: ProveCommitSectorParams, ) -> Result<(), ActorError> { + rt.set_caller(*ACCOUNT_ACTOR_CODE_ID, self.worker); let seal_rand = TEST_RANDOMNESS_ARRAY_FROM_ONE; let seal_int_rand = TEST_RANDOMNESS_ARRAY_FROM_TWO; let interactive_epoch = pc.pre_commit_epoch + rt.policy.pre_commit_challenge_delay; @@ -2532,6 +2533,17 @@ impl ActorHarness { } ret } + + pub fn get_available_balance(&self, rt: &mut MockRuntime) -> Result { + // set caller to non-builtin + rt.set_caller(make_identity_cid(b"1234"), Address::new_id(1234)); + rt.expect_validate_caller_any(); + let available_balance_ret: GetAvailableBalanceReturn = rt + .call::(Method::GetAvailableBalanceExported as u64, &RawBytes::default())? + .deserialize()?; + rt.verify(); + Ok(available_balance_ret.available_balance) + } } #[allow(dead_code)] diff --git a/actors/multisig/src/lib.rs b/actors/multisig/src/lib.rs index 56c4132924..8bd64b74a8 100644 --- a/actors/multisig/src/lib.rs +++ b/actors/multisig/src/lib.rs @@ -42,7 +42,7 @@ pub enum Method { SwapSigner = 7, ChangeNumApprovalsThreshold = 8, LockBalance = 9, - // Method numbers derived from FRC-XXXX standards + // Method numbers derived from FRC-0042 standards ProposeExported = frc42_dispatch::method_hash!("Propose"), ApproveExported = frc42_dispatch::method_hash!("Approve"), CancelExported = frc42_dispatch::method_hash!("Cancel"), diff --git a/actors/power/Cargo.toml b/actors/power/Cargo.toml index 5377d75003..f7e3720bf6 100644 --- a/actors/power/Cargo.toml +++ b/actors/power/Cargo.toml @@ -15,6 +15,7 @@ crate-type = ["cdylib", "lib"] [dependencies] fil_actors_runtime = { version = "10.0.0-alpha.1", path = "../../runtime" } +frc42_dispatch = "1.0.0" fvm_shared = { version = "3.0.0-alpha.11", default-features = false } fvm_ipld_hamt = "0.6.1" num-traits = "0.2.14" diff --git a/actors/power/src/lib.rs b/actors/power/src/lib.rs index 21dc612b6b..43437812c6 100644 --- a/actors/power/src/lib.rs +++ b/actors/power/src/lib.rs @@ -61,12 +61,16 @@ pub enum Method { // OnConsensusFault = 7, SubmitPoRepForBulkVerify = 8, CurrentTotalPower = 9, + // Method numbers derived from FRC-0042 standards + NetworkRawPowerExported = frc42_dispatch::method_hash!("NetworkRawPower"), + MinerRawPowerExported = frc42_dispatch::method_hash!("MinerRawPower"), } pub const ERR_TOO_MANY_PROVE_COMMITS: ExitCode = ExitCode::new(32); /// Storage Power Actor pub struct Actor; + impl Actor { /// Constructor for StoragePower actor fn constructor(rt: &mut impl Runtime) -> Result<(), ActorError> { @@ -364,6 +368,34 @@ impl Actor { }) } + /// Returns the total raw power of the network. + /// This is defined as the sum of the active (i.e. non-faulty) byte commitments + /// of all miners that have more than the consensus minimum amount of storage active. + /// This value is static over an epoch, and does NOT get updated as messages are executed. + /// It is recalculated after all messages at an epoch have been executed. + fn network_raw_power(rt: &mut impl Runtime) -> Result { + rt.validate_immediate_caller_accept_any()?; + let st: State = rt.state()?; + + Ok(NetworkRawPowerReturn { raw_byte_power: st.this_epoch_raw_byte_power }) + } + + /// Returns the raw power claimed by the specified miner, + /// and whether the miner has more than the consensus minimum amount of storage active. + /// The raw power is defined as the active (i.e. non-faulty) byte commitments of the miner. + fn miner_raw_power( + rt: &mut impl Runtime, + params: MinerRawPowerParams, + ) -> Result { + rt.validate_immediate_caller_accept_any()?; + let st: State = rt.state()?; + + let (raw_byte_power, meets_consensus_minimum) = + st.miner_nominal_power_meets_consensus_minimum(rt.policy(), rt.store(), params.miner)?; + + Ok(MinerRawPowerReturn { raw_byte_power, meets_consensus_minimum }) + } + fn process_batch_proof_verifies( rt: &mut impl Runtime, rewret: &ThisEpochRewardReturn, @@ -660,6 +692,14 @@ impl ActorCode for Actor { let res = Self::current_total_power(rt)?; Ok(RawBytes::serialize(res)?) } + Some(Method::NetworkRawPowerExported) => { + let res = Self::network_raw_power(rt)?; + Ok(RawBytes::serialize(res)?) + } + Some(Method::MinerRawPowerExported) => { + let res = Self::miner_raw_power(rt, cbor::deserialize_params(params)?)?; + Ok(RawBytes::serialize(res)?) + } None => Err(actor_error!(unhandled_message; "Invalid method")), } } diff --git a/actors/power/src/state.rs b/actors/power/src/state.rs index 91739d0ebc..fb029d76fb 100644 --- a/actors/power/src/state.rs +++ b/actors/power/src/state.rs @@ -3,12 +3,12 @@ use std::ops::Neg; -use anyhow::{anyhow, Context}; +use anyhow::anyhow; use cid::Cid; use fil_actors_runtime::runtime::Policy; use fil_actors_runtime::{ actor_error, make_empty_map, make_map_with_root, make_map_with_root_and_bitwidth, - ActorDowncast, ActorError, Map, Multimap, + ActorDowncast, ActorError, AsActorError, Map, Multimap, }; use fvm_ipld_blockstore::Blockstore; use fvm_ipld_encoding::tuple::*; @@ -21,7 +21,7 @@ use fvm_shared::econ::TokenAmount; use fvm_shared::error::ExitCode; use fvm_shared::sector::{RegisteredPoStProof, StoragePower}; use fvm_shared::smooth::{AlphaBetaFilter, FilterEstimate, DEFAULT_ALPHA, DEFAULT_BETA}; -use fvm_shared::HAMT_BIT_WIDTH; +use fvm_shared::{ActorID, HAMT_BIT_WIDTH}; use integer_encoding::VarInt; use lazy_static::lazy_static; use num_traits::Signed; @@ -103,26 +103,37 @@ impl State { &self, policy: &Policy, s: &BS, - miner: &Address, - ) -> anyhow::Result { - let claims = make_map_with_root_and_bitwidth(&self.claims, s, HAMT_BIT_WIDTH)?; + miner: ActorID, + ) -> Result<(StoragePower, bool), ActorError> { + let claims = make_map_with_root_and_bitwidth(&self.claims, s, HAMT_BIT_WIDTH) + .with_context_code(ExitCode::USR_ILLEGAL_STATE, || { + format!("failed to load claims for miner: {}", miner) + })?; - let claim = - get_claim(&claims, miner)?.ok_or_else(|| anyhow!("no claim for actor: {}", miner))?; + let claim = get_claim(&claims, &Address::new_id(miner)) + .with_context_code(ExitCode::USR_ILLEGAL_STATE, || { + format!("failed to get claim for miner: {}", miner) + })? + .with_context_code(ExitCode::USR_ILLEGAL_ARGUMENT, || { + format!("no claim for actor: {}", miner) + })?; - let miner_nominal_power = &claim.raw_byte_power; + let miner_nominal_power = claim.raw_byte_power.clone(); let miner_min_power = consensus_miner_min_power(policy, claim.window_post_proof_type) - .context("could not get miner min power from proof type: {}")?; + .context_code( + ExitCode::USR_ILLEGAL_STATE, + "could not get miner min power from proof type: {}", + )?; - if miner_nominal_power >= &miner_min_power { + if miner_nominal_power >= miner_min_power { // If miner is larger than min power requirement, valid - Ok(true) + Ok((miner_nominal_power, true)) } else if self.miner_above_min_power_count >= CONSENSUS_MINER_MIN_MINERS { // if min consensus miners requirement met, return false - Ok(false) + Ok((miner_nominal_power, false)) } else { // if fewer miners than consensus minimum, return true if non-zero power - Ok(miner_nominal_power.is_positive()) + Ok((miner_nominal_power.clone(), miner_nominal_power.is_positive())) } } diff --git a/actors/power/src/types.rs b/actors/power/src/types.rs index 74537f7ada..8e26ab4b09 100644 --- a/actors/power/src/types.rs +++ b/actors/power/src/types.rs @@ -9,6 +9,7 @@ use fvm_shared::clock::ChainEpoch; use fvm_shared::econ::TokenAmount; use fvm_shared::sector::{RegisteredPoStProof, StoragePower}; use fvm_shared::smooth::FilterEstimate; +use fvm_shared::ActorID; pub type SectorTermination = i64; @@ -23,7 +24,7 @@ pub const CRON_QUEUE_HAMT_BITWIDTH: u32 = 6; pub const CRON_QUEUE_AMT_BITWIDTH: u32 = 6; pub const PROOF_VALIDATION_BATCH_AMT_BITWIDTH: u32 = 4; -#[derive(Serialize_tuple, Deserialize_tuple, Clone)] +#[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone, PartialEq)] pub struct CreateMinerParams { pub owner: Address, pub worker: Address, @@ -32,9 +33,10 @@ pub struct CreateMinerParams { pub peer: Vec, pub multiaddrs: Vec, } + impl Cbor for CreateMinerParams {} -#[derive(Serialize_tuple, Deserialize_tuple)] +#[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone, Eq, PartialEq)] pub struct CreateMinerReturn { /// Canonical ID-based address for the actor. pub id_address: Address, @@ -42,7 +44,7 @@ pub struct CreateMinerReturn { pub robust_address: Address, } -#[derive(Serialize_tuple, Deserialize_tuple)] +#[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone, Eq, PartialEq)] pub struct UpdateClaimedPowerParams { #[serde(with = "bigint_ser")] pub raw_byte_delta: StoragePower, @@ -50,13 +52,13 @@ pub struct UpdateClaimedPowerParams { pub quality_adjusted_delta: StoragePower, } -#[derive(Serialize_tuple, Deserialize_tuple)] +#[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone, Eq, PartialEq)] pub struct EnrollCronEventParams { pub event_epoch: ChainEpoch, pub payload: RawBytes, } -#[derive(Serialize_tuple, Deserialize_tuple)] +#[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone, PartialEq)] pub struct CurrentTotalPowerReturn { #[serde(with = "bigint_ser")] pub raw_byte_power: StoragePower, @@ -65,3 +67,29 @@ pub struct CurrentTotalPowerReturn { pub pledge_collateral: TokenAmount, pub quality_adj_power_smoothed: FilterEstimate, } + +#[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone, Eq, PartialEq)] +#[serde(transparent)] +pub struct NetworkRawPowerReturn { + #[serde(with = "bigint_ser")] + pub raw_byte_power: StoragePower, +} + +impl Cbor for NetworkRawPowerReturn {} + +#[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone, Eq, PartialEq)] +#[serde(transparent)] +pub struct MinerRawPowerParams { + pub miner: ActorID, +} + +impl Cbor for MinerRawPowerParams {} + +#[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone, Eq, PartialEq)] +pub struct MinerRawPowerReturn { + #[serde(with = "bigint_ser")] + pub raw_byte_power: StoragePower, + pub meets_consensus_minimum: bool, +} + +impl Cbor for MinerRawPowerReturn {} diff --git a/actors/power/tests/harness/mod.rs b/actors/power/tests/harness/mod.rs index 37ffb8410b..dd53f55fc2 100644 --- a/actors/power/tests/harness/mod.rs +++ b/actors/power/tests/harness/mod.rs @@ -222,9 +222,7 @@ impl Harness { pub fn get_claim(&self, rt: &MockRuntime, miner: &Address) -> Option { let st: State = rt.get_state(); - let claims = - make_map_with_root_and_bitwidth(&st.claims, rt.store(), HAMT_BIT_WIDTH).unwrap(); - claims.get(&miner.to_bytes()).unwrap().cloned() + st.get_claim(rt.store(), miner).unwrap() } pub fn delete_claim(&mut self, rt: &mut MockRuntime, miner: &Address) { diff --git a/actors/power/tests/power_actor_tests.rs b/actors/power/tests/power_actor_tests.rs index 5be4416bd4..5be231f36a 100644 --- a/actors/power/tests/power_actor_tests.rs +++ b/actors/power/tests/power_actor_tests.rs @@ -2,8 +2,8 @@ use fil_actor_power::ext::init::{ExecParams, EXEC_METHOD}; use fil_actor_power::ext::miner::MinerConstructorParams; use fil_actors_runtime::runtime::builtins::Type; use fil_actors_runtime::test_utils::{ - expect_abort, expect_abort_contains_message, ACCOUNT_ACTOR_CODE_ID, MINER_ACTOR_CODE_ID, - SYSTEM_ACTOR_CODE_ID, + expect_abort, expect_abort_contains_message, make_identity_cid, ACCOUNT_ACTOR_CODE_ID, + MINER_ACTOR_CODE_ID, SYSTEM_ACTOR_CODE_ID, }; use fil_actors_runtime::{runtime::Policy, CALLER_TYPES_SIGNABLE, INIT_ACTOR_ADDR}; use fvm_ipld_encoding::{BytesDe, RawBytes}; @@ -17,9 +17,11 @@ use num_traits::Zero; use std::ops::Neg; use fil_actor_power::{ - consensus_miner_min_power, Actor as PowerActor, CreateMinerParams, EnrollCronEventParams, - Method, State, UpdateClaimedPowerParams, CONSENSUS_MINER_MIN_MINERS, + consensus_miner_min_power, Actor as PowerActor, Actor, CreateMinerParams, + EnrollCronEventParams, Method, MinerRawPowerParams, MinerRawPowerReturn, NetworkRawPowerReturn, + State, UpdateClaimedPowerParams, CONSENSUS_MINER_MIN_MINERS, }; +use fil_actors_runtime::cbor::serialize; use crate::harness::*; @@ -589,6 +591,55 @@ fn claimed_power_is_externally_available() { h.check_state(&rt); } +#[test] +fn get_network_and_miner_power() { + let power_unit = &consensus_miner_min_power( + &Policy::default(), + RegisteredPoStProof::StackedDRGWindow32GiBV1, + ) + .unwrap(); + + let (mut h, mut rt) = setup(); + + h.create_miner_basic(&mut rt, *OWNER, *OWNER, MINER1).unwrap(); + h.update_claimed_power(&mut rt, MINER1, power_unit, power_unit); + + // manually update state in lieu of cron running + let mut state: State = rt.get_state(); + state.this_epoch_raw_byte_power = power_unit.clone(); + rt.replace_state(&state); + + // set caller to not-builtin + rt.set_caller(make_identity_cid(b"1234"), Address::new_id(1234)); + + rt.expect_validate_caller_any(); + let network_power: NetworkRawPowerReturn = rt + .call::(Method::NetworkRawPowerExported as u64, &RawBytes::default()) + .unwrap() + .deserialize() + .unwrap(); + + assert_eq!(power_unit, &network_power.raw_byte_power); + + rt.expect_validate_caller_any(); + let miner_power: MinerRawPowerReturn = rt + .call::( + Method::MinerRawPowerExported as u64, + &serialize( + &MinerRawPowerParams { miner: MINER1.id().unwrap() }, + "serializing MinerRawPowerParams", + ) + .unwrap(), + ) + .unwrap() + .deserialize() + .unwrap(); + + assert_eq!(power_unit, &miner_power.raw_byte_power); + + h.check_state(&rt); +} + #[test] fn given_no_miner_claim_update_pledge_total_should_abort() { let (mut h, mut rt) = setup(); diff --git a/actors/verifreg/src/lib.rs b/actors/verifreg/src/lib.rs index eb6716cc28..d7962cfd20 100644 --- a/actors/verifreg/src/lib.rs +++ b/actors/verifreg/src/lib.rs @@ -60,7 +60,7 @@ pub enum Method { GetClaims = 10, ExtendClaimTerms = 11, RemoveExpiredClaims = 12, - // Method numbers derived from FRC-XXXX standards + // Method numbers derived from FRC-0042 standards RemoveExpiredAllocationsExported = frc42_dispatch::method_hash!("RemoveExpiredAllocations"), ExtendClaimTermsExported = frc42_dispatch::method_hash!("ExtendClaimTerms"), RemoveExpiredClaimsExported = frc42_dispatch::method_hash!("RemoveExpiredClaims"),