Skip to content

Commit

Permalink
feat(FIP-0077): add create miner deposit
Browse files Browse the repository at this point in the history
  • Loading branch information
tediou5 committed Jan 13, 2025
1 parent b4ed8cf commit fb28a97
Show file tree
Hide file tree
Showing 13 changed files with 358 additions and 48 deletions.
63 changes: 61 additions & 2 deletions actors/miner/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ pub use expiration_queue::*;
use fil_actors_runtime::cbor::{serialize, serialize_vec};
use fil_actors_runtime::reward::{FilterEstimate, ThisEpochRewardReturn};
use fil_actors_runtime::runtime::builtins::Type;
use fil_actors_runtime::runtime::policy_constants::MAX_SECTOR_NUMBER;
use fil_actors_runtime::runtime::policy_constants::{MAX_SECTOR_NUMBER, MINIMUM_CONSENSUS_POWER};
use fil_actors_runtime::runtime::{ActorCode, DomainSeparationTag, Policy, Runtime};
use fil_actors_runtime::{
actor_dispatch, actor_error, deserialize_block, extract_send_result, util, ActorContext,
Expand Down Expand Up @@ -181,6 +181,14 @@ impl Actor {
check_peer_info(rt.policy(), &params.peer_id, &params.multi_addresses)?;
check_valid_post_proof_type(rt.policy(), params.window_post_proof_type)?;

let balance = rt.current_balance();
let deposit = calculate_create_miner_deposit(rt, params.network_qap)?;
if balance < deposit {
return Err(actor_error!(insufficient_funds;
"not enough balance to lock for create miner deposit: \
sent balance {} < deposit {}", balance.atto(), deposit.atto()));
}

let owner = rt.resolve_address(&params.owner).ok_or_else(|| {
actor_error!(illegal_argument, "unable to resolve owner address: {}", params.owner)
})?;
Expand Down Expand Up @@ -239,7 +247,10 @@ impl Actor {
e.downcast_default(ExitCode::USR_ILLEGAL_STATE, "failed to construct illegal state")
})?;

let st = State::new(policy, rt.store(), info_cid, period_start, deadline_idx)?;
let store = rt.store();
let mut st = State::new(policy, store, info_cid, period_start, deadline_idx)?;
st.add_locked_funds(store, rt.curr_epoch(), &deposit, &REWARD_VESTING_SPEC)
.map_err(|e| actor_error!(illegal_state, e))?;
rt.create(&st)?;
Ok(())
}
Expand Down Expand Up @@ -756,6 +767,7 @@ impl Actor {

Ok(())
}

/// Checks state of the corresponding sector pre-commitments and verifies aggregate proof of replication
/// of these sectors. If valid, the sectors' deals are activated, sectors are assigned a deadline and charged pledge
/// and precommit state is removed.
Expand Down Expand Up @@ -5642,6 +5654,53 @@ fn activate_new_sector_infos(
Ok(())
}

/// Calculate create miner deposit by MINIMUM_CONSENSUS_POWER x StateMinerInitialPledgeCollateral
/// See FIP-0077, https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0077.md
pub fn calculate_create_miner_deposit(
rt: &impl Runtime,
network_qap: FilterEstimate,
) -> Result<TokenAmount, ActorError> {
// set network pledge inputs
let rew = request_current_epoch_block_reward(rt)?;
let pwr = request_current_total_power(rt)?;
let circulating_supply = rt.total_fil_circ_supply();
let pledge_inputs = NetworkPledgeInputs {
network_qap,
network_baseline: rew.this_epoch_baseline_power,
circulating_supply,
epoch_reward: rew.this_epoch_reward_smoothed,
epochs_since_ramp_start: rt.curr_epoch() - pwr.ramp_start_epoch,
ramp_duration_epochs: pwr.ramp_duration_epochs,
};

/// set sector size with min power
#[cfg(feature = "min-power-2k")]
let sector_size = SectorSize::_2KiB;
#[cfg(feature = "min-power-2g")]
let sector_size = SectorSize::_8MiB;
#[cfg(feature = "min-power-32g")]
let sector_size = SectorSize::_512MiB;
#[cfg(not(any(
feature = "min-power-2k",
feature = "min-power-2g",
feature = "min-power-32g"
)))]
let sector_size = SectorSize::_32GiB;

let sector_number = MINIMUM_CONSENSUS_POWER / sector_size as i64;
let power = qa_power_for_weight(sector_size, MIN_SECTOR_EXPIRATION, &BigInt::zero());
let sector_initial_pledge = initial_pledge_for_power(
&power,
&pledge_inputs.network_baseline,
&pledge_inputs.epoch_reward,
&pledge_inputs.network_qap,
&pledge_inputs.circulating_supply,
pledge_inputs.epochs_since_ramp_start,
pledge_inputs.ramp_duration_epochs,
);
Ok(sector_initial_pledge * sector_number)
}

pub struct SectorPiecesActivationInput {
pub piece_manifests: Vec<PieceActivationManifest>,
pub sector_expiry: ChainEpoch,
Expand Down
3 changes: 3 additions & 0 deletions actors/miner/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ impl State {
pub fn deadline_info(&self, policy: &Policy, current_epoch: ChainEpoch) -> DeadlineInfo {
new_deadline_info_from_offset_and_epoch(policy, self.proving_period_start, current_epoch)
}

// Returns deadline calculations for the state recorded proving period and deadline.
// This is out of date if the a miner does not have an active miner cron
pub fn recorded_deadline_info(
Expand Down Expand Up @@ -877,6 +878,7 @@ impl State {
amount_unlocked
));
}

// add locked funds now
vesting_funds.add_locked_funds(current_epoch, vesting_sum, self.proving_period_start, spec);
self.locked_funds += vesting_sum;
Expand Down Expand Up @@ -937,6 +939,7 @@ impl State {

Ok(std::mem::take(&mut self.fee_debt))
}

/// Unlocks an amount of funds that have *not yet vested*, if possible.
/// The soonest-vesting entries are unlocked first.
/// Returns the amount actually unlocked.
Expand Down
1 change: 1 addition & 0 deletions actors/miner/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ pub struct MinerConstructorParams {
#[serde(with = "strict_bytes")]
pub peer_id: Vec<u8>,
pub multi_addresses: Vec<BytesDe>,
pub network_qap: FilterEstimate,
}

#[derive(Serialize_tuple, Deserialize_tuple)]
Expand Down
144 changes: 137 additions & 7 deletions actors/miner/tests/miner_actor_test_construction.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
use fil_actors_runtime::test_utils::*;
use fil_actors_runtime::INIT_ACTOR_ADDR;

use fil_actor_account::Method as AccountMethod;
use fil_actor_miner::{
Actor, Deadline, Deadlines, Method, MinerConstructorParams as ConstructorParams, State,
};
use fil_actor_power::{CurrentTotalPowerReturn, Method as PowerMethod};
use fil_actor_reward::{Method as RewardMethod, ThisEpochRewardReturn};
use fil_actors_runtime::reward::FilterEstimate;
use fil_actors_runtime::{test_utils::*, STORAGE_POWER_ACTOR_ADDR};
use fil_actors_runtime::{INIT_ACTOR_ADDR, REWARD_ACTOR_ADDR};

use fvm_ipld_encoding::{BytesDe, CborStore};
use fvm_shared::address::Address;
use fvm_shared::bigint::BigInt;
use fvm_shared::econ::TokenAmount;
use fvm_shared::error::ExitCode;
use fvm_shared::sector::{RegisteredPoStProof, SectorSize};
use fvm_shared::sector::{RegisteredPoStProof, SectorSize, StoragePower};

use cid::Cid;
use fvm_ipld_encoding::ipld_block::IpldBlock;
use num_traits::Zero;
use num_traits::{FromPrimitive, Zero};

mod util;

Expand All @@ -27,10 +30,17 @@ struct TestEnv {
control_addrs: Vec<Address>,
peer_id: Vec<u8>,
multiaddrs: Vec<BytesDe>,
power: StoragePower,
reward: TokenAmount,
epoch_reward_smooth: FilterEstimate,
rt: MockRuntime,
}

fn prepare_env() -> TestEnv {
let reward = TokenAmount::from_whole(10);
let power = StoragePower::from_i128(1 << 50).unwrap();
let epoch_reward_smooth = FilterEstimate::new(reward.atto().clone(), BigInt::from(0u8));

let mut env = TestEnv {
receiver: Address::new_id(1000),
owner: Address::new_id(100),
Expand All @@ -39,6 +49,9 @@ fn prepare_env() -> TestEnv {
control_addrs: vec![Address::new_id(999), Address::new_id(998)],
peer_id: vec![1, 2, 3],
multiaddrs: vec![BytesDe(vec![1, 2, 3])],
power,
reward,
epoch_reward_smooth,
rt: MockRuntime::default(),
};

Expand All @@ -50,6 +63,8 @@ fn prepare_env() -> TestEnv {
env.rt.hash_func = Box::new(hash);
env.rt.caller.replace(INIT_ACTOR_ADDR);
env.rt.caller_type.replace(*INIT_ACTOR_CODE_ID);
// add balance for create miner deposit
env.rt.add_balance(TokenAmount::from_atto(633318697598976000u64));
env
}

Expand All @@ -61,16 +76,46 @@ fn constructor_params(env: &TestEnv) -> ConstructorParams {
window_post_proof_type: RegisteredPoStProof::StackedDRGWindow32GiBV1P1,
peer_id: env.peer_id.clone(),
multi_addresses: env.multiaddrs.clone(),
network_qap: env.epoch_reward_smooth.clone(),
}
}

#[test]
fn simple_construction() {
let env = prepare_env();
let current_reward = ThisEpochRewardReturn {
this_epoch_baseline_power: env.power.clone(),
this_epoch_reward_smoothed: env.epoch_reward_smooth.clone(),
};
let current_total_power = CurrentTotalPowerReturn {
raw_byte_power: Default::default(),
quality_adj_power: Default::default(),
pledge_collateral: Default::default(),
quality_adj_power_smoothed: Default::default(),
ramp_start_epoch: Default::default(),
ramp_duration_epochs: Default::default(),
};

let params = constructor_params(&env);

env.rt.set_caller(*INIT_ACTOR_CODE_ID, INIT_ACTOR_ADDR);
env.rt.expect_validate_caller_addr(vec![INIT_ACTOR_ADDR]);
env.rt.expect_send_simple(
REWARD_ACTOR_ADDR,
RewardMethod::ThisEpochReward as u64,
None,
TokenAmount::zero(),
IpldBlock::serialize_cbor(&current_reward).unwrap(),
ExitCode::OK,
);
env.rt.expect_send_simple(
STORAGE_POWER_ACTOR_ADDR,
PowerMethod::CurrentTotalPower as u64,
Default::default(),
TokenAmount::zero(),
IpldBlock::serialize_cbor(&current_total_power).unwrap(),
ExitCode::OK,
);
env.rt.expect_send_simple(
env.worker,
AccountMethod::PubkeyAddress as u64,
Expand All @@ -87,7 +132,7 @@ fn simple_construction() {
expect_empty(result);
env.rt.verify();

let state = env.rt.get_state::<State>();
let mut state = env.rt.get_state::<State>();

let info = state.get_info(&env.rt.store).unwrap();
assert_eq!(env.owner, info.owner);
Expand All @@ -100,10 +145,21 @@ fn simple_construction() {
assert_eq!(2349, info.window_post_partition_sectors);

assert_eq!(TokenAmount::zero(), state.pre_commit_deposits);
assert_eq!(TokenAmount::zero(), state.locked_funds);
assert_eq!(TokenAmount::from_atto(633318697598976000u64), state.locked_funds);
assert_eq!(180, state.load_vesting_funds(&env.rt.store).unwrap().funds.len());
assert_ne!(Cid::default(), state.pre_committed_sectors);
assert_ne!(Cid::default(), state.sectors);

// reset create miner deposit vesting funds
state.save_vesting_funds(&env.rt.store, &fil_actor_miner::VestingFunds::new()).unwrap();
state.locked_funds = TokenAmount::zero();
env.rt.replace_state(&state);

let state = env.rt.get_state::<State>();
let create_depost_vesting_funds = state.load_vesting_funds(&env.rt.store).unwrap();
assert!(create_depost_vesting_funds.funds.is_empty());
assert!(state.locked_funds.is_zero());

// according to original specs-actors test, this is set by running the code; magic...
let proving_period_start = -2222;
assert_eq!(proving_period_start, state.proving_period_start);
Expand All @@ -128,9 +184,67 @@ fn simple_construction() {
util::check_state_invariants_from_mock_runtime(&env.rt);
}

#[test]
fn fails_if_insufficient_to_cover_the_miner_creation_deposit() {
let env = prepare_env();
env.rt.set_balance(TokenAmount::zero());
let current_reward = ThisEpochRewardReturn {
this_epoch_baseline_power: env.power.clone(),
this_epoch_reward_smoothed: env.epoch_reward_smooth.clone(),
};
let current_total_power = CurrentTotalPowerReturn {
raw_byte_power: Default::default(),
quality_adj_power: Default::default(),
pledge_collateral: Default::default(),
quality_adj_power_smoothed: Default::default(),
ramp_start_epoch: Default::default(),
ramp_duration_epochs: Default::default(),
};

let params = constructor_params(&env);

env.rt.set_caller(*INIT_ACTOR_CODE_ID, INIT_ACTOR_ADDR);
env.rt.expect_validate_caller_addr(vec![INIT_ACTOR_ADDR]);
env.rt.expect_send_simple(
REWARD_ACTOR_ADDR,
RewardMethod::ThisEpochReward as u64,
None,
TokenAmount::zero(),
IpldBlock::serialize_cbor(&current_reward).unwrap(),
ExitCode::OK,
);
env.rt.expect_send_simple(
STORAGE_POWER_ACTOR_ADDR,
PowerMethod::CurrentTotalPower as u64,
Default::default(),
TokenAmount::zero(),
IpldBlock::serialize_cbor(&current_total_power).unwrap(),
ExitCode::OK,
);

expect_abort(
ExitCode::USR_INSUFFICIENT_FUNDS,
env.rt
.call::<Actor>(Method::Constructor as u64, IpldBlock::serialize_cbor(&params).unwrap()),
);
env.rt.verify();
}

#[test]
fn control_addresses_are_resolved_during_construction() {
let mut env = prepare_env();
let current_reward = ThisEpochRewardReturn {
this_epoch_baseline_power: env.power.clone(),
this_epoch_reward_smoothed: env.epoch_reward_smooth.clone(),
};
let current_total_power = CurrentTotalPowerReturn {
raw_byte_power: Default::default(),
quality_adj_power: Default::default(),
pledge_collateral: Default::default(),
quality_adj_power_smoothed: Default::default(),
ramp_start_epoch: Default::default(),
ramp_duration_epochs: Default::default(),
};

let control1 = new_bls_addr(1);
let control1id = Address::new_id(555);
Expand All @@ -146,6 +260,22 @@ fn control_addresses_are_resolved_during_construction() {
let params = constructor_params(&env);
env.rt.set_caller(*INIT_ACTOR_CODE_ID, INIT_ACTOR_ADDR);
env.rt.expect_validate_caller_addr(vec![INIT_ACTOR_ADDR]);
env.rt.expect_send_simple(
REWARD_ACTOR_ADDR,
RewardMethod::ThisEpochReward as u64,
None,
TokenAmount::zero(),
IpldBlock::serialize_cbor(&current_reward).unwrap(),
ExitCode::OK,
);
env.rt.expect_send_simple(
STORAGE_POWER_ACTOR_ADDR,
PowerMethod::CurrentTotalPower as u64,
Default::default(),
TokenAmount::zero(),
IpldBlock::serialize_cbor(&current_total_power).unwrap(),
ExitCode::OK,
);
env.rt.expect_send_simple(
env.worker,
AccountMethod::PubkeyAddress as u64,
Expand Down
Loading

0 comments on commit fb28a97

Please sign in to comment.