use log::{error, info, warn};
mod bitfield_queue;
mod deadline_assignment;
mod deadline_state;
mod deadlines;
mod expiration_queue;
mod monies;
mod partition_state;
mod policy;
mod sector_map;
mod sectors;
mod state;
mod termination;
mod types;
mod vesting_state;
pub use bitfield_queue::*;
pub use deadline_assignment::*;
pub use deadline_state::*;
pub use deadlines::*;
pub use expiration_queue::*;
pub use monies::*;
pub use partition_state::*;
pub use policy::*;
pub use sector_map::*;
pub use sectors::*;
pub use state::*;
pub use termination::*;
pub use types::*;
pub use vesting_state::*;
use crate::{
account::Method as AccountMethod,
actor_error,
market::{self, ActivateDealsParams, ComputeDataCommitmentReturn, SectorDataSpec, SectorDeals},
power::MAX_MINER_PROVE_COMMITS_PER_EPOCH,
};
use crate::{
is_principal, smooth::FilterEstimate, ACCOUNT_ACTOR_CODE_ID, BURNT_FUNDS_ACTOR_ADDR,
CALLER_TYPES_SIGNABLE, INIT_ACTOR_ADDR, REWARD_ACTOR_ADDR, STORAGE_MARKET_ACTOR_ADDR,
STORAGE_POWER_ACTOR_ADDR,
};
use crate::{
market::{
ComputeDataCommitmentParamsRef, Method as MarketMethod, OnMinerSectorsTerminateParams,
OnMinerSectorsTerminateParamsRef, VerifyDealsForActivationParamsRef,
VerifyDealsForActivationReturn,
},
power::CurrentTotalPowerReturn,
};
use crate::{
power::{EnrollCronEventParams, Method as PowerMethod},
reward::ThisEpochRewardReturn,
ActorDowncast,
};
use address::{Address, Payload, Protocol};
use bitfield::{BitField, UnvalidatedBitField, Validate};
use byteorder::{BigEndian, ByteOrder, WriteBytesExt};
use cid::{Cid, Code::Blake2b256, Prefix};
use clock::ChainEpoch;
use crypto::DomainSeparationTag::{
self, InteractiveSealChallengeSeed, SealRandomness, WindowedPoStChallengeSeed,
};
use encoding::{from_slice, BytesDe, Cbor};
use fil_types::{
deadlines::DeadlineInfo, AggregateSealVerifyInfo, AggregateSealVerifyProofAndInfos,
InteractiveSealRandomness, PoStProof, PoStRandomness, RegisteredPoStProof, RegisteredSealProof,
SealRandomness as SealRandom, SealVerifyInfo, SealVerifyParams, SectorID, SectorInfo,
SectorNumber, SectorSize, WindowPoStVerifyInfo, MAX_SECTOR_NUMBER, RANDOMNESS_LENGTH,
};
use ipld_blockstore::BlockStore;
use num_bigint::BigInt;
use num_bigint::{bigint_ser::BigIntSer, Integer};
use num_derive::FromPrimitive;
use num_traits::{FromPrimitive, Signed, Zero};
use runtime::{ActorCode, Runtime};
use std::collections::{hash_map::Entry, HashMap};
use std::error::Error as StdError;
use std::{iter, ops::Neg};
use vm::{
ActorError, DealID, ExitCode, MethodNum, Serialized, TokenAmount, METHOD_CONSTRUCTOR,
METHOD_SEND,
};
use ExitCode::ErrPlaceholder as ErrBalanceInvariantBroken;
#[derive(FromPrimitive)]
#[repr(u64)]
pub enum Method {
Constructor = METHOD_CONSTRUCTOR,
ControlAddresses = 2,
ChangeWorkerAddress = 3,
ChangePeerID = 4,
SubmitWindowedPoSt = 5,
PreCommitSector = 6,
ProveCommitSector = 7,
ExtendSectorExpiration = 8,
TerminateSectors = 9,
DeclareFaults = 10,
DeclareFaultsRecovered = 11,
OnDeferredCronEvent = 12,
CheckSectorProven = 13,
ApplyRewards = 14,
ReportConsensusFault = 15,
WithdrawBalance = 16,
ConfirmSectorProofsValid = 17,
ChangeMultiaddrs = 18,
CompactPartitions = 19,
CompactSectorNumbers = 20,
ConfirmUpdateWorkerKey = 21,
RepayDebt = 22,
ChangeOwnerAddress = 23,
DisputeWindowedPoSt = 24,
PreCommitSectorBatch = 25,
ProveCommitAggregate = 26,
}
pub struct Actor;
impl Actor {
pub fn constructor<BS, RT>(
rt: &mut RT,
params: MinerConstructorParams,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
rt.validate_immediate_caller_is(&[*INIT_ACTOR_ADDR])?;
check_control_addresses(¶ms.control_addresses)?;
check_peer_info(¶ms.peer_id, ¶ms.multi_addresses)?;
check_valid_post_proof_type(params.window_post_proof_type)?;
let owner = resolve_control_address(rt, params.owner)?;
let worker = resolve_worker_address(rt, params.worker)?;
let control_addresses: Vec<_> = params
.control_addresses
.into_iter()
.map(|address| resolve_control_address(rt, address))
.collect::<Result<_, _>>()?;
let current_epoch = rt.curr_epoch();
let blake2b = |b: &[u8]| rt.hash_blake2b(b);
let offset = assign_proving_period_offset(*rt.message().receiver(), current_epoch, blake2b)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrSerialization,
"failed to assign proving period offset",
)
})?;
let period_start = current_proving_period_start(current_epoch, offset);
if period_start > current_epoch {
return Err(actor_error!(
ErrIllegalState,
"computed proving period start {} after current epoch {}",
period_start,
current_epoch
));
}
let deadline_idx = current_deadline_index(current_epoch, period_start);
if deadline_idx >= WPOST_PERIOD_DEADLINES as usize {
return Err(actor_error!(
ErrIllegalState,
"computed proving deadline index {} invalid",
deadline_idx
));
}
let info = MinerInfo::new(
owner,
worker,
control_addresses,
params.peer_id,
params.multi_addresses,
params.window_post_proof_type,
)?;
let info_cid = rt.store().put(&info, Blake2b256).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to construct illegal state",
)
})?;
let st = State::new(rt.store(), info_cid, period_start, deadline_idx).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to construct state")
})?;
rt.create(&st)?;
Ok(())
}
fn control_addresses<BS, RT>(rt: &mut RT) -> Result<GetControlAddressesReturn, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
rt.validate_immediate_caller_accept_any()?;
let state: State = rt.state()?;
let info = get_miner_info(rt.store(), &state)?;
Ok(GetControlAddressesReturn {
owner: info.owner,
worker: info.worker,
control_addresses: info.control_addresses,
})
}
fn change_worker_address<BS, RT>(
rt: &mut RT,
params: ChangeWorkerAddressParams,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
check_control_addresses(¶ms.new_control_addresses)?;
let new_worker = resolve_worker_address(rt, params.new_worker)?;
let control_addresses: Vec<Address> = params
.new_control_addresses
.into_iter()
.map(|address| resolve_control_address(rt, address))
.collect::<Result<_, _>>()?;
rt.transaction(|state: &mut State, rt| {
let mut info = get_miner_info(rt.store(), state)?;
rt.validate_immediate_caller_is(std::iter::once(&info.owner))?;
info.control_addresses = control_addresses;
if new_worker != info.worker && info.pending_worker_key.is_none() {
info.pending_worker_key = Some(WorkerKeyChange {
new_worker,
effective_at: rt.curr_epoch() + WORKER_KEY_CHANGE_DELAY,
})
}
state.save_info(rt.store(), &info).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "could not save miner info")
})?;
Ok(())
})?;
Ok(())
}
fn confirm_update_worker_key<BS, RT>(rt: &mut RT) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
rt.transaction(|state: &mut State, rt| {
let mut info = get_miner_info(rt.store(), state)?;
rt.validate_immediate_caller_is(std::iter::once(&info.owner))?;
process_pending_worker(&mut info, rt, state)?;
Ok(())
})
}
fn change_owner_address<BS, RT>(rt: &mut RT, new_address: Address) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
if !matches!(new_address.protocol(), Protocol::ID) {
return Err(actor_error!(
ErrIllegalArgument,
"owner address must be an ID address"
));
}
rt.transaction(|state: &mut State, rt| {
let mut info = get_miner_info(rt.store(), state)?;
if rt.message().caller() == &info.owner || info.pending_owner_address.is_none() {
rt.validate_immediate_caller_is(std::iter::once(&info.owner))?;
info.pending_owner_address = Some(new_address);
} else {
let pending_address = info.pending_owner_address.unwrap();
rt.validate_immediate_caller_is(std::iter::once(&pending_address))?;
if new_address != pending_address {
return Err(actor_error!(
ErrIllegalArgument,
"expected confirmation of {} got {}",
pending_address,
new_address
));
}
info.owner = pending_address;
}
if let Some(p_addr) = info.pending_owner_address {
if p_addr == info.owner {
info.pending_owner_address = None;
}
}
state.save_info(rt.store(), &info).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to save miner info")
})?;
Ok(())
})
}
fn change_peer_id<BS, RT>(rt: &mut RT, params: ChangePeerIDParams) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
check_peer_info(¶ms.new_id, &[])?;
rt.transaction(|state: &mut State, rt| {
let mut info = get_miner_info(rt.store(), state)?;
rt.validate_immediate_caller_is(
info.control_addresses
.iter()
.chain(&[info.worker, info.owner]),
)?;
info.peer_id = params.new_id;
state.save_info(rt.store(), &info).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "could not save miner info")
})?;
Ok(())
})?;
Ok(())
}
fn change_multiaddresses<BS, RT>(
rt: &mut RT,
params: ChangeMultiaddrsParams,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
check_peer_info(&[], ¶ms.new_multi_addrs)?;
rt.transaction(|state: &mut State, rt| {
let mut info = get_miner_info(rt.store(), state)?;
rt.validate_immediate_caller_is(
info.control_addresses
.iter()
.chain(&[info.worker, info.owner]),
)?;
info.multi_address = params.new_multi_addrs;
state.save_info(rt.store(), &info).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "could not save miner info")
})?;
Ok(())
})?;
Ok(())
}
fn submit_windowed_post<BS, RT>(
rt: &mut RT,
mut params: SubmitWindowedPoStParams,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let current_epoch = rt.curr_epoch();
if params.proofs.len() != 1 {
return Err(actor_error!(
ErrIllegalArgument,
"expected exactly one proof, got {}",
params.proofs.len()
));
}
if check_valid_post_proof_type(params.proofs[0].post_proof).is_err() {
return Err(actor_error!(
ErrIllegalArgument,
"proof type {:?} not allowed",
params.proofs[0].post_proof
));
}
if params.deadline >= WPOST_PERIOD_DEADLINES as usize {
return Err(actor_error!(
ErrIllegalArgument,
"invalid deadline {} of {}",
params.deadline,
WPOST_PERIOD_DEADLINES
));
}
if params.chain_commit_rand.0.len() > RANDOMNESS_LENGTH {
return Err(actor_error!(
ErrIllegalArgument,
"expected at most {} bytes of randomness, got {}",
RANDOMNESS_LENGTH,
params.chain_commit_rand.0.len()
));
}
let post_result = rt.transaction(|state: &mut State, rt| {
let info = get_miner_info(rt.store(), state)?;
let max_proof_size = info.window_post_proof_type.proof_size().map_err(|e| {
actor_error!(
ErrIllegalState,
"failed to determine max window post proof size: {}",
e
)
})?;
rt.validate_immediate_caller_is(
info.control_addresses
.iter()
.chain(&[info.worker, info.owner]),
)?;
if params.proofs.len() != 1 {
return Err(actor_error!(
ErrIllegalArgument,
"expected exactly one proof, got {}",
params.proofs.len()
));
}
if params.proofs[0].post_proof != info.window_post_proof_type {
return Err(actor_error!(
ErrIllegalArgument,
"expected proof of type {:?}, got {:?}",
params.proofs[0].post_proof,
info.window_post_proof_type
));
}
let max_size = max_proof_size * params.partitions.len();
if params.proofs[0].proof_bytes.len() > max_size {
return Err(actor_error!(
ErrIllegalArgument,
"expect proof to be smaller than {} bytes",
max_size
));
}
let submission_partition_limit =
load_partitions_sectors_max(info.window_post_partition_sectors);
if params.partitions.len() as u64 > submission_partition_limit {
return Err(actor_error!(
ErrIllegalArgument,
"too many partitions {}, limit {}",
params.partitions.len(),
submission_partition_limit
));
}
let current_deadline = state.deadline_info(current_epoch);
if !current_deadline.is_open() {
return Err(actor_error!(
ErrIllegalState,
"proving period {} not yet open at {}",
current_deadline.period_start,
current_epoch
));
}
if params.deadline != current_deadline.index as usize {
return Err(actor_error!(
ErrIllegalArgument,
"invalid deadline {} at epoch {}, expected {}",
params.deadline,
current_epoch,
current_deadline.index
));
}
if params.chain_commit_epoch < current_deadline.challenge {
return Err(actor_error!(
ErrIllegalArgument,
"expected chain commit epoch {} to be after {}",
params.chain_commit_epoch,
current_deadline.challenge
));
}
if params.chain_commit_epoch >= current_epoch {
return Err(actor_error!(
ErrIllegalArgument,
"chain commit epoch {} must be less tha the current epoch {}",
params.chain_commit_epoch,
current_epoch
));
}
let comm_rand = rt.get_randomness_from_tickets(
DomainSeparationTag::PoStChainCommit,
params.chain_commit_epoch,
&[],
)?;
if comm_rand != params.chain_commit_rand {
return Err(actor_error!(
ErrIllegalArgument,
"post commit randomness mismatched"
));
}
let sectors = Sectors::load(rt.store(), &state.sectors).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to load sectors")
})?;
let mut deadlines = state
.load_deadlines(rt.store())
.map_err(|e| e.wrap("failed to load deadlines"))?;
let mut deadline = deadlines
.load_deadline(rt.store(), params.deadline)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to load deadline {}", params.deadline),
)
})?;
let fault_expiration = current_deadline.last() + FAULT_MAX_AGE;
let post_result = deadline
.record_proven_sectors(
rt.store(),
§ors,
info.sector_size,
current_deadline.quant_spec(),
fault_expiration,
&mut params.partitions,
)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!(
"failed to process post submission for deadline {}",
params.deadline
),
)
})?;
let proven_sectors = &post_result.sectors - &post_result.ignored_sectors;
if proven_sectors.is_empty() {
return Err(actor_error!(
ErrIllegalArgument,
"cannot prove partitions with no active sectors"
));
}
if post_result.recovered_power.is_zero() {
deadline
.record_post_proofs(rt.store(), &post_result.partitions, ¶ms.proofs)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to record proof for optimistic verification",
)
})?
} else {
let sector_infos = sectors
.load_for_proof(&post_result.sectors, &post_result.ignored_sectors)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to load sectors for post verification",
)
})?;
verify_windowed_post(rt, current_deadline.challenge, §or_infos, params.proofs)
.map_err(|e| e.wrap("window post failed"))?;
}
let deadline_idx = params.deadline;
deadlines
.update_deadline(rt.store(), params.deadline, &deadline)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to update deadline {}", deadline_idx),
)
})?;
state.save_deadlines(rt.store(), deadlines).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to save deadlines")
})?;
Ok(post_result)
})?;
request_update_power(rt, post_result.power_delta)?;
let state: State = rt.state()?;
state
.check_balance_invariants(&rt.current_balance()?)
.map_err(|e| {
ActorError::new(
ErrBalanceInvariantBroken,
format!("balance invariants broken: {}", e),
)
})?;
Ok(())
}
fn prove_commit_aggregate<BS, RT>(
rt: &mut RT,
mut params: ProveCommitAggregateParams,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let sector_numbers = params.sector_numbers.validate().map_err(|e| {
actor_error!(
ErrIllegalState,
"Failed to validate bitfield for aggregated sectors: {}",
e
)
})?;
let agg_sectors_count = sector_numbers.len();
if agg_sectors_count > MAX_AGGREGATED_SECTORS {
return Err(actor_error!(
ErrIllegalArgument,
"too many sectors addressed, addressed {} want <= {}",
agg_sectors_count,
MAX_AGGREGATED_SECTORS
));
} else if agg_sectors_count < MIN_AGGREGATED_SECTORS {
return Err(actor_error!(
ErrIllegalArgument,
"too few sectors addressed, addressed {} want >= {}",
agg_sectors_count,
MIN_AGGREGATED_SECTORS
));
}
if params.aggregate_proof.len() > MAX_AGGREGATED_PROOF_SIZE {
return Err(actor_error!(
ErrIllegalArgument,
"sector prove-commit proof of size {} exceeds max size of {}",
params.aggregate_proof.len(),
MAX_AGGREGATED_PROOF_SIZE
));
}
let state: State = rt.state()?;
let info = get_miner_info(rt.store(), &state)?;
rt.validate_immediate_caller_is(
info.control_addresses
.iter()
.chain(&[info.worker, info.owner]),
)?;
let store = rt.store();
let precommits = state
.get_all_precommitted_sectors(store, sector_numbers)
.map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to get precommits")
})?;
let mut compute_data_commitments_inputs = Vec::with_capacity(precommits.len());
let mut precommits_to_confirm = Vec::new();
for (i, precommit) in precommits.iter().enumerate() {
let msd = max_prove_commit_duration(precommit.info.seal_proof).ok_or_else(|| {
actor_error!(
ErrIllegalState,
"no max seal duration for proof type: {}",
i64::from(precommit.info.seal_proof)
)
})?;
let prove_commit_due = precommit.pre_commit_epoch + msd;
if rt.curr_epoch() > prove_commit_due {
log::warn!(
"skipping commitment for sector {}, too late at {}, due {}",
precommit.info.sector_number,
rt.curr_epoch(),
prove_commit_due,
)
} else {
precommits_to_confirm.push(precommit.clone());
}
if i >= 1 {
let prev_seal_proof = precommits[i - 1].info.seal_proof;
if prev_seal_proof != precommit.info.seal_proof {
return Err(actor_error!(
ErrIllegalState,
"aggregate contains mismatched seal proofs {} and {}",
i64::from(prev_seal_proof),
i64::from(precommit.info.seal_proof)
));
}
}
compute_data_commitments_inputs.push(SectorDataSpec {
deal_ids: precommit.info.deal_ids.clone(),
sector_type: precommit.info.seal_proof,
});
}
let comm_ds = request_unsealed_sector_cids(rt, &compute_data_commitments_inputs)?;
let mut svis = Vec::new();
let miner_actor_id: u64 = if let Payload::ID(i) = rt.message().receiver().payload() {
*i
} else {
return Err(actor_error!(
ErrIllegalState,
"runtime provided non-ID receiver address {}",
rt.message().receiver()
));
};
let receiver_bytes = rt.message().receiver().marshal_cbor().map_err(|e| {
ActorError::from(e).wrap("failed to marshal address for seal verification challenge")
})?;
for (i, precommit) in precommits.iter().enumerate() {
let interactive_epoch = precommit.pre_commit_epoch + PRE_COMMIT_CHALLENGE_DELAY;
if rt.curr_epoch() <= interactive_epoch {
return Err(actor_error!(
ErrForbidden,
"too early to prove sector {}",
precommit.info.sector_number
));
}
let sv_info_randomness = rt.get_randomness_from_tickets(
SealRandomness,
precommit.info.seal_rand_epoch,
&receiver_bytes,
)?;
let sv_info_interactive_randomness = rt.get_randomness_from_beacon(
InteractiveSealChallengeSeed,
interactive_epoch,
&receiver_bytes,
)?;
let svi = AggregateSealVerifyInfo {
sector_number: precommit.info.sector_number,
randomness: sv_info_randomness,
interactive_randomness: sv_info_interactive_randomness,
sealed_cid: precommit.info.sealed_cid,
unsealed_cid: comm_ds[i],
};
svis.push(svi);
}
let seal_proof = precommits[0].info.seal_proof;
if precommits.is_empty() {
return Err(actor_error!(
ErrIllegalState,
"bitfield non-empty but zero precommits read from state"
));
}
rt.verify_aggregate_seals(&AggregateSealVerifyProofAndInfos {
miner: miner_actor_id,
seal_proof,
aggregate_proof: fil_types::RegisteredAggregateProof::SnarkPackV1,
proof: params.aggregate_proof,
infos: svis,
})
.map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalArgument, "aggregate seal verify failed")
})?;
let rew = request_current_epoch_block_reward(rt)?;
let pwr = request_current_total_power(rt)?;
confirm_sector_proofs_valid_internal(
rt,
precommits_to_confirm.clone(),
&rew.this_epoch_baseline_power,
&rew.this_epoch_reward_smoothed,
&pwr.quality_adj_power_smoothed,
)?;
let state: State = rt.state()?;
let aggregate_fee =
aggregate_prove_commit_network_fee(precommits_to_confirm.len() as i64, rt.base_fee());
let unlocked_balance = state
.get_unlocked_balance(&rt.current_balance()?)
.map_err(|_e| actor_error!(ErrIllegalState, "failed to determine unlocked balance"))?;
if unlocked_balance < aggregate_fee {
return Err(actor_error!(
ErrInsufficientFunds,
"remaining unlocked funds after prove-commit {} are insufficient to pay aggregation fee of {}",
unlocked_balance,
aggregate_fee
));
}
burn_funds(rt, aggregate_fee)?;
state
.check_balance_invariants(&rt.current_balance()?)
.map_err(|e| {
ActorError::new(
ErrBalanceInvariantBroken,
format!("balance invariants broken: {}", e),
)
})?;
Ok(())
}
fn dispute_windowed_post<BS, RT>(
rt: &mut RT,
params: DisputeWindowedPoStParams,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
rt.validate_immediate_caller_type(CALLER_TYPES_SIGNABLE.iter())?;
let reporter = *rt.message().caller();
if params.deadline >= WPOST_PERIOD_DEADLINES as usize {
return Err(actor_error!(
ErrIllegalArgument,
"invalid deadline {} of {}",
params.deadline,
WPOST_PERIOD_DEADLINES
));
}
let current_epoch = rt.curr_epoch();
let epoch_reward = request_current_epoch_block_reward(rt)?;
let power_total = request_current_total_power(rt)?;
let (pledge_delta, mut to_burn, power_delta, to_reward) =
rt.transaction(|st: &mut State, rt| {
let dl_info = st.deadline_info(current_epoch);
if !deadline_available_for_optimistic_post_dispute(
dl_info.period_start,
params.deadline,
current_epoch,
) {
return Err(actor_error!(
ErrForbidden,
"can only dispute window posts during the dispute window\
({} epochs after the challenge window closes)",
WPOST_DISPUTE_WINDOW
));
}
let info = get_miner_info(rt.store(), st)?;
let mut pp_start = dl_info.period_start;
if dl_info.index < params.deadline as u64 {
pp_start -= WPOST_PROVING_PERIOD
}
let target_deadline = new_deadline_info(pp_start, params.deadline, current_epoch);
let mut deadlines_current = st
.load_deadlines(rt.store())
.map_err(|e| e.wrap("failed to load deadlines"))?;
let mut dl_current = deadlines_current
.load_deadline(rt.store(), params.deadline)
.map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to load deadline")
})?;
let (partitions, proofs) = dl_current
.take_post_proofs(rt.store(), params.post_index)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to load proof for dispute",
)
})?;
let mut dispute_info = dl_current
.load_partitions_for_dispute(rt.store(), partitions)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to load partition for dispute",
)
})?;
let penalised_power = dispute_info.disputed_power.clone();
let sectors = Sectors::load(rt.store(), &st.sectors).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to load sectors array")
})?;
let sector_infos = sectors
.load_for_proof(
&dispute_info.all_sector_nos,
&dispute_info.ignored_sector_nos,
)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to load sectors to dispute window post",
)
})?;
match verify_windowed_post(rt, target_deadline.challenge, §or_infos, proofs) {
Err(e) => {
info!("Successfully disputed post: {}", e);
}
Ok(false) => {
info!("Successfully disputed post: window post was invalid");
}
Ok(true) => {
return Err(actor_error!(
ErrIllegalArgument,
"failed to dispute valid post"
));
}
}
let fault_expiration_epoch = target_deadline.last() + FAULT_MAX_AGE;
let power_delta = dl_current
.record_faults(
rt.store(),
§ors,
info.sector_size,
quant_spec_for_deadline(&target_deadline),
fault_expiration_epoch,
&mut dispute_info.disputed_sectors,
)
.map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to declare faults")
})?;
deadlines_current
.update_deadline(rt.store(), params.deadline, &dl_current)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to update deadline {}", params.deadline),
)
})?;
st.save_deadlines(rt.store(), deadlines_current)
.map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to save deadlines")
})?;
let penalty_base = pledge_penalty_for_invalid_windowpost(
&epoch_reward.this_epoch_reward_smoothed,
&power_total.quality_adj_power_smoothed,
&penalised_power.qa,
);
let reward_target =
reward_for_disputed_window_post(info.window_post_proof_type, penalised_power);
let penalty_target = &penalty_base + &reward_target;
st.apply_penalty(&penalty_target)
.map_err(|e| actor_error!(ErrIllegalState, "failed to apply penalty {}", e))?;
let (penalty_from_vesting, penalty_from_balance) = st
.repay_partial_debt_in_priority_order(
rt.store(),
current_epoch,
&rt.current_balance()?,
)
.map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to pay debt")
})?;
let to_burn = &penalty_from_vesting + &penalty_from_balance;
let to_reward = std::cmp::min(&to_burn, &reward_target);
let to_burn = &to_burn - to_reward;
let pledge_delta = penalty_from_vesting.neg();
Ok((pledge_delta, to_burn, power_delta, to_reward.clone()))
})?;
request_update_power(rt, power_delta)?;
if !to_reward.is_zero() {
if let Err(e) = rt.send(
reporter,
METHOD_SEND,
Serialized::default(),
to_reward.clone(),
) {
error!("failed to send reward: {}", e);
to_burn += to_reward;
}
}
burn_funds(rt, to_burn)?;
notify_pledge_changed(rt, &pledge_delta)?;
let st: State = rt.state()?;
st.check_balance_invariants(&rt.current_balance()?)
.map_err(|e| {
ActorError::new(
ErrBalanceInvariantBroken,
format!("balance invariants broken: {}", e),
)
})?;
Ok(())
}
fn pre_commit_sector<BS, RT>(
rt: &mut RT,
params: PreCommitSectorParams,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let batch_params = PreCommitSectorBatchParams {
sectors: vec![params],
};
Self::pre_commit_sector_batch(rt, batch_params)
}
fn pre_commit_sector_batch<BS, RT>(
rt: &mut RT,
params: PreCommitSectorBatchParams,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let curr_epoch = rt.curr_epoch();
if params.sectors.is_empty() {
return Err(actor_error!(ErrIllegalArgument, "batch empty"));
} else if params.sectors.len() > PRE_COMMIT_SECTOR_BATCH_MAX_SIZE {
return Err(actor_error!(
ErrIllegalArgument,
"batch of {} too large, max {}",
params.sectors.len(),
PRE_COMMIT_SECTOR_BATCH_MAX_SIZE
));
}
let challenge_earliest = curr_epoch - MAX_PRE_COMMIT_RANDOMNESS_LOOKBACK;
let mut sectors_deals = Vec::with_capacity(params.sectors.len());
let mut sector_numbers = BitField::new();
for precommit in params.sectors.iter() {
let set = sector_numbers.get(precommit.sector_number as usize);
if set {
return Err(actor_error!(
ErrIllegalArgument,
"duplicate sector number {}",
precommit.sector_number
));
}
sector_numbers.set(precommit.sector_number as usize);
if !can_pre_commit_seal_proof(precommit.seal_proof) {
return Err(actor_error!(
ErrIllegalArgument,
"unsupported seal proof type {}",
i64::from(precommit.seal_proof)
));
}
if precommit.sector_number > MAX_SECTOR_NUMBER {
return Err(actor_error!(
ErrIllegalArgument,
"sector number {} out of range 0..(2^63-1)",
precommit.sector_number
));
}
if Prefix::from(precommit.sealed_cid) != SEALED_CID_PREFIX {
return Err(actor_error!(
ErrIllegalArgument,
"sealed CID had wrong prefix"
));
}
if precommit.seal_rand_epoch >= curr_epoch {
return Err(actor_error!(
ErrIllegalArgument,
"seal challenge epoch {} must be before now {}",
precommit.seal_rand_epoch,
curr_epoch
));
}
if precommit.seal_rand_epoch < challenge_earliest {
return Err(actor_error!(
ErrIllegalArgument,
"seal challenge epoch {} too old, must be after {}",
precommit.seal_rand_epoch,
challenge_earliest
));
}
let max_activation =
curr_epoch + max_prove_commit_duration(precommit.seal_proof).unwrap_or_default();
validate_expiration(
rt,
max_activation,
precommit.expiration,
precommit.seal_proof,
)?;
if precommit.replace_capacity && precommit.deal_ids.is_empty() {
return Err(actor_error!(
ErrIllegalArgument,
"cannot replace sector without committing deals"
));
}
if precommit.replace_sector_deadline as u64 >= WPOST_PERIOD_DEADLINES {
return Err(actor_error!(
ErrIllegalArgument,
"invalid deadline {}",
precommit.replace_sector_deadline
));
}
if precommit.replace_sector_number > MAX_SECTOR_NUMBER {
return Err(actor_error!(
ErrIllegalArgument,
"invalid sector number {}",
precommit.replace_sector_number
));
}
sectors_deals.push(SectorDeals {
sector_expiry: precommit.expiration,
deal_ids: precommit.deal_ids.clone(),
})
}
let reward_stats = request_current_epoch_block_reward(rt)?;
let power_total = request_current_total_power(rt)?;
let deal_weights = request_deal_weights(rt, §ors_deals)?;
if deal_weights.sectors.len() != params.sectors.len() {
return Err(actor_error!(
ErrIllegalState,
"deal weight request returned {} records, expected {}",
deal_weights.sectors.len(),
params.sectors.len()
));
}
let mut fee_to_burn = TokenAmount::from(0);
let mut needs_cron = false;
rt.transaction(|state: &mut State, rt|{
if params.sectors.len() > 1 {
let aggregate_fee = aggregate_pre_commit_network_fee(params.sectors.len() as i64, rt.base_fee()); state.apply_penalty(&aggregate_fee)
.map_err(|e| {
actor_error!(
ErrIllegalState,
"failed to apply penalty: {}",
e
)
})?;
}
let available_balance = state
.get_available_balance(&rt.current_balance()?)
.map_err(|e| {
actor_error!(
ErrIllegalState,
"failed to calculate available balance: {}",
e
)
})?;
fee_to_burn = repay_debts_or_abort(rt, state)?;
let info = get_miner_info(rt.store(), state)?;
rt.validate_immediate_caller_is(
info.control_addresses
.iter()
.chain(&[info.worker, info.owner]),
)?;
let store = rt.store();
if consensus_fault_active(&info, curr_epoch) {
return Err(actor_error!(ErrForbidden, "pre-commit not allowed during active consensus fault"));
}
let mut chain_infos = Vec::with_capacity(params.sectors.len());
let mut total_deposit_required= BigInt::zero();
let mut clean_up_events: HashMap<ChainEpoch,Vec<u64>> = HashMap::new();
let deal_count_max = sector_deals_max(info.sector_size);
for (i, precommit) in params.sectors.iter().enumerate() {
let sector_wpost_proof = precommit.seal_proof
.registered_window_post_proof()
.map_err(|_e|
actor_error!(
ErrIllegalArgument,
"failed to lookup Window PoSt proof type for sector seal proof {}",
i64::from(precommit.seal_proof)
))?;
if sector_wpost_proof != info.window_post_proof_type {
return Err(actor_error!(ErrIllegalArgument, "sector Window PoSt proof type %d must match miner Window PoSt proof type {} (seal proof type {})", i64::from(sector_wpost_proof), i64::from(info.window_post_proof_type)));
}
if precommit.deal_ids.len() > deal_count_max as usize {
return Err(actor_error!(ErrIllegalArgument, "too many deals for sector {} > {}", precommit.deal_ids.len(), deal_count_max));
}
let deal_weight = &deal_weights.sectors[i];
if deal_weight.deal_space > info.sector_size as u64 {
return Err(actor_error!(ErrIllegalArgument, "deals too large to fit in sector {} > {}", deal_weight.deal_space, info.sector_size));
}
if precommit.replace_capacity {
validate_replace_sector(state, store, precommit)?
}
let duration = precommit.expiration - curr_epoch;
let sector_weight = qa_power_for_weight(info.sector_size, duration, &deal_weight.deal_weight, &deal_weight.verified_deal_weight);
let deposit_req = pre_commit_deposit_for_power(&reward_stats.this_epoch_reward_smoothed,&power_total.quality_adj_power_smoothed , §or_weight);
chain_infos.push(SectorPreCommitOnChainInfo{
info: precommit.clone(),
pre_commit_deposit: deposit_req.clone(),
pre_commit_epoch: curr_epoch,
deal_weight: deal_weight.deal_weight.clone(),
verified_deal_weight: deal_weight.verified_deal_weight.clone(),
});
total_deposit_required += deposit_req;
let msd = max_prove_commit_duration(precommit.seal_proof)
.ok_or_else(|| actor_error!(ErrIllegalArgument, "no max seal duration set for proof type: {}", i64::from(precommit.seal_proof)))?;
let clean_up_bound = curr_epoch + msd + EXPIRED_PRE_COMMIT_CLEAN_UP_DELAY;
if let Some(cleanups) = clean_up_events.get_mut(&clean_up_bound) {
cleanups.push(precommit.sector_number);
} else {
clean_up_events.insert(clean_up_bound, vec![precommit.sector_number]);
}
}
if available_balance < total_deposit_required {
return Err(actor_error!(ErrInsufficientFunds, "insufficient funds {} for pre-commit deposit: {}", available_balance, total_deposit_required));
}
state.add_pre_commit_deposit(&total_deposit_required)
.map_err(|e|
actor_error!(
ErrIllegalState,
"failed to add pre-commit deposit {}: {}",
total_deposit_required, e
))?;
state.allocate_sector_numbers(store, §or_numbers, CollisionPolicy::DenyCollisions)
.map_err(|e|
e.wrap("failed to allocate sector numbers")
)?;
state.put_precommitted_sectors(store, chain_infos)
.map_err(|e|
e.downcast_default(ExitCode::ErrIllegalState, "failed to write pre-committed sectors")
)?;
state.add_pre_commit_clean_ups(store, clean_up_events)
.map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to add pre-commit expiry to queue")
})?;
needs_cron = !state.deadline_cron_active;
state.deadline_cron_active = true;
Ok(())
})?;
burn_funds(rt, fee_to_burn)?;
let state: State = rt.state()?;
state
.check_balance_invariants(&rt.current_balance()?)
.map_err(|e| {
ActorError::new(
ErrBalanceInvariantBroken,
format!("balance invariant broken: {}", e),
)
})?;
if needs_cron {
let new_dl_info = state.deadline_info(curr_epoch);
enroll_cron_event(
rt,
new_dl_info.last(),
CronEventPayload {
event_type: CRON_EVENT_PROVING_DEADLINE,
},
)?;
}
Ok(())
}
fn prove_commit_sector<BS, RT>(
rt: &mut RT,
params: ProveCommitSectorParams,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
rt.validate_immediate_caller_accept_any()?;
if params.sector_number > MAX_SECTOR_NUMBER {
return Err(actor_error!(
ErrIllegalArgument,
"sector number greater than maximum"
));
}
let sector_number = params.sector_number;
let st: State = rt.state()?;
let precommit = st
.get_precommitted_sector(rt.store(), sector_number)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to load pre-committed sector {}", sector_number),
)
})?
.ok_or_else(|| actor_error!(ErrNotFound, "no pre-commited sector {}", sector_number))?;
let max_proof_size = precommit.info.seal_proof.proof_size().map_err(|e| {
actor_error!(
ErrIllegalState,
"failed to determine max proof size for sector {}: {}",
sector_number,
e
)
})?;
if params.proof.len() > max_proof_size {
return Err(actor_error!(
ErrIllegalArgument,
"sector prove-commit proof of size {} exceeds max size of {}",
params.proof.len(),
max_proof_size
));
}
let msd = max_prove_commit_duration(precommit.info.seal_proof).ok_or_else(|| {
actor_error!(
ErrIllegalState,
"no max seal duration set for proof type: {:?}",
precommit.info.seal_proof
)
})?;
let prove_commit_due = precommit.pre_commit_epoch + msd;
if rt.curr_epoch() > prove_commit_due {
return Err(actor_error!(
ErrIllegalArgument,
"commitment proof for {} too late at {}, due {}",
sector_number,
rt.curr_epoch(),
prove_commit_due
));
}
let svi = get_verify_info(
rt,
SealVerifyParams {
sealed_cid: precommit.info.sealed_cid,
interactive_epoch: precommit.pre_commit_epoch + PRE_COMMIT_CHALLENGE_DELAY,
seal_rand_epoch: precommit.info.seal_rand_epoch,
proof: params.proof,
deal_ids: precommit.info.deal_ids.clone(),
sector_num: precommit.info.sector_number,
registered_seal_proof: precommit.info.seal_proof,
},
)?;
rt.send(
*STORAGE_POWER_ACTOR_ADDR,
PowerMethod::SubmitPoRepForBulkVerify as u64,
Serialized::serialize(&svi)?,
BigInt::zero(),
)?;
Ok(())
}
fn confirm_sector_proofs_valid<BS, RT>(
rt: &mut RT,
params: ConfirmSectorProofsParams,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
rt.validate_immediate_caller_is(iter::once(&*STORAGE_POWER_ACTOR_ADDR))?;
if params.sectors.len() > MAX_MINER_PROVE_COMMITS_PER_EPOCH {
warn!(
"confirmed more prove commits in an epoch than permitted: {} > {}",
params.sectors.len(),
MAX_MINER_PROVE_COMMITS_PER_EPOCH
);
}
let st: State = rt.state()?;
let store = rt.store();
let precommited_sectors = st
.find_precommitted_sectors(store, ¶ms.sectors)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to load pre-committed sectors",
)
})?;
confirm_sector_proofs_valid_internal(
rt,
precommited_sectors,
¶ms.reward_baseline_power,
¶ms.reward_smoothed,
¶ms.quality_adj_power_smoothed,
)
}
fn check_sector_proven<BS, RT>(
rt: &mut RT,
params: CheckSectorProvenParams,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
rt.validate_immediate_caller_accept_any()?;
if params.sector_number > MAX_SECTOR_NUMBER {
return Err(actor_error!(
ErrIllegalArgument,
"sector number out of range"
));
}
let st: State = rt.state()?;
match st.get_sector(rt.store(), params.sector_number) {
Err(e) => Err(actor_error!(
ErrIllegalState,
"failed to load proven sector {}: {}",
params.sector_number,
e
)),
Ok(None) => Err(actor_error!(
ErrNotFound,
"sector {} not proven",
params.sector_number
)),
Ok(Some(_sector)) => Ok(()),
}
}
fn extend_sector_expiration<BS, RT>(
rt: &mut RT,
mut params: ExtendSectorExpirationParams,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
if params.extensions.len() as u64 > DELCARATIONS_MAX {
return Err(actor_error!(
ErrIllegalArgument,
"too many declarations {}, max {}",
params.extensions.len(),
DELCARATIONS_MAX
));
}
let mut sector_count: u64 = 0;
for decl in &mut params.extensions {
if decl.deadline >= WPOST_PERIOD_DEADLINES as usize {
return Err(actor_error!(
ErrIllegalArgument,
"deadline {} not in range 0..{}",
decl.deadline,
WPOST_PERIOD_DEADLINES
));
}
let sectors = match decl.sectors.validate() {
Ok(sectors) => sectors,
Err(e) => {
return Err(actor_error!(
ErrIllegalArgument,
"failed to validate sectors for deadline {}, partition {}: {}",
decl.deadline,
decl.partition,
e
))
}
};
match sector_count.checked_add(sectors.len() as u64) {
Some(sum) => sector_count = sum,
None => {
return Err(actor_error!(
ErrIllegalArgument,
"sector bitfield integer overflow"
));
}
}
}
if sector_count > ADDRESSED_SECTORS_MAX {
return Err(actor_error!(
ErrIllegalArgument,
"too many sectors for declaration {}, max {}",
sector_count,
ADDRESSED_SECTORS_MAX
));
}
let curr_epoch = rt.curr_epoch();
let (power_delta, pledge_delta) = rt.transaction(|state: &mut State, rt| {
let info = get_miner_info(rt.store(), state)?;
let nv = rt.network_version();
rt.validate_immediate_caller_is(
info.control_addresses
.iter()
.chain(&[info.worker, info.owner]),
)?;
let store = rt.store();
let mut deadlines = state
.load_deadlines(rt.store())
.map_err(|e| e.wrap("failed to load deadlines"))?;
let mut decls_by_deadline = HashMap::<usize, Vec<ExpirationExtension>>::new();
let mut deadlines_to_load = Vec::<usize>::new();
for decl in params.extensions {
decls_by_deadline
.entry(decl.deadline)
.or_insert_with(|| {
deadlines_to_load.push(decl.deadline);
Vec::new()
})
.push(decl);
}
let mut sectors = Sectors::load(rt.store(), &state.sectors).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to load sectors array")
})?;
let mut power_delta = PowerPair::zero();
let mut pledge_delta = TokenAmount::zero();
for deadline_idx in deadlines_to_load {
let mut deadline = deadlines.load_deadline(store, deadline_idx).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to load deadline {}", deadline_idx),
)
})?;
let mut partitions = deadline.partitions_amt(store).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to load partitions for deadline {}", deadline_idx),
)
})?;
let quant = state.quant_spec_for_deadline(deadline_idx);
let mut partitions_by_new_epoch = HashMap::<ChainEpoch, Vec<usize>>::new();
let mut epochs_to_reschedule = Vec::<ChainEpoch>::new();
for decl in decls_by_deadline.get_mut(&deadline_idx).unwrap() {
let key = PartitionKey {
deadline: deadline_idx,
partition: decl.partition,
};
let mut partition = partitions
.get(decl.partition)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to load partition {:?}", key),
)
})?
.cloned()
.ok_or_else(|| actor_error!(ErrNotFound, "no such partition {:?}", key))?;
let old_sectors = sectors
.load_sector(&mut decl.sectors)
.map_err(|e| e.wrap("failed to load sectors"))?;
let new_sectors: Vec<SectorOnChainInfo> = old_sectors
.iter()
.map(|sector| {
if !can_extend_seal_proof_type(sector.seal_proof, nv) {
return Err(actor_error!(
ErrForbidden,
"cannot extend expiration for sector {} with unsupported \
seal type {:?}",
sector.sector_number,
sector.seal_proof
));
}
if sector.expiration < rt.curr_epoch() {
return Err(actor_error!(
ErrForbidden,
"cannot extend expiration for expired sector {} at {}",
sector.sector_number,
sector.expiration
));
}
if decl.new_expiration < sector.expiration {
return Err(actor_error!(
ErrIllegalArgument,
"cannot reduce sector {} expiration to {} from {}",
sector.sector_number,
decl.new_expiration,
sector.expiration
));
}
validate_expiration(
rt,
sector.activation,
decl.new_expiration,
sector.seal_proof,
)?;
let new_deal_weight = (§or.deal_weight
* (sector.expiration - curr_epoch))
.div_floor(&BigInt::from(sector.expiration - sector.activation));
let new_verified_deal_weight = (§or.verified_deal_weight
* (sector.expiration - curr_epoch))
.div_floor(&BigInt::from(sector.expiration - sector.activation));
let mut sector = sector.clone();
sector.expiration = decl.new_expiration;
sector.deal_weight = new_deal_weight;
sector.verified_deal_weight = new_verified_deal_weight;
Ok(sector)
})
.collect::<Result<_, _>>()?;
sectors.store(new_sectors.clone()).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to update sectors {:?}", decl.sectors),
)
})?;
let (partition_power_delta, partition_pledge_delta) = partition
.replace_sectors(store, &old_sectors, &new_sectors, info.sector_size, quant)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to replace sector expirations at {:?}", key),
)
})?;
power_delta += &partition_power_delta;
pledge_delta += partition_pledge_delta;
partitions.set(decl.partition, partition).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to save partition {:?}", key),
)
})?;
let prev_epoch_partitions = partitions_by_new_epoch.entry(decl.new_expiration);
let not_exists = matches!(prev_epoch_partitions, Entry::Vacant(_));
prev_epoch_partitions
.or_insert_with(Vec::new)
.push(decl.partition);
if not_exists {
epochs_to_reschedule.push(decl.new_expiration);
}
}
deadline.partitions = partitions.flush().map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to save partitions for deadline {}", deadline_idx),
)
})?;
for epoch in epochs_to_reschedule {
let p_idxs = partitions_by_new_epoch.get(&epoch).unwrap();
deadline
.add_expiration_partitions(store, epoch, p_idxs, quant)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!(
"failed to add expiration partitions to \
deadline {} epoch {}",
deadline_idx, epoch
),
)
})?;
}
deadlines
.update_deadline(store, deadline_idx, &deadline)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to save deadline {}", deadline_idx),
)
})?;
}
state.sectors = sectors.amt.flush().map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to save sectors")
})?;
state.save_deadlines(store, deadlines).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to save deadlines")
})?;
Ok((power_delta, pledge_delta))
})?;
request_update_power(rt, power_delta)?;
notify_pledge_changed(rt, &pledge_delta)?;
Ok(())
}
fn terminate_sectors<BS, RT>(
rt: &mut RT,
params: TerminateSectorsParams,
) -> Result<TerminateSectorsReturn, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
if params.terminations.len() as u64 > DELCARATIONS_MAX {
return Err(actor_error!(
ErrIllegalArgument,
"too many declarations when terminating sectors: {} > {}",
params.terminations.len(),
DELCARATIONS_MAX
));
}
let mut to_process = DeadlineSectorMap::new();
for term in params.terminations {
let deadline = term.deadline;
let partition = term.partition;
to_process
.add(deadline, partition, term.sectors)
.map_err(|e| {
actor_error!(
ErrIllegalArgument,
"failed to process deadline {}, partition {}: {}",
deadline,
partition,
e
)
})?;
}
to_process
.check(ADDRESSED_PARTITIONS_MAX, ADDRESSED_SECTORS_MAX)
.map_err(|e| {
actor_error!(
ErrIllegalArgument,
"cannot process requested parameters: {}",
e
)
})?;
let (had_early_terminations, power_delta) = rt.transaction(|state: &mut State, rt| {
let had_early_terminations = have_pending_early_terminations(state);
let info = get_miner_info(rt.store(), state)?;
rt.validate_immediate_caller_is(
info.control_addresses
.iter()
.chain(&[info.worker, info.owner]),
)?;
let store = rt.store();
let curr_epoch = rt.curr_epoch();
let mut power_delta = PowerPair::zero();
let mut deadlines = state
.load_deadlines(store)
.map_err(|e| e.wrap("failed to load deadlines"))?;
let sectors = Sectors::load(store, &state.sectors).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to load sectors")
})?;
for (deadline_idx, partition_sectors) in to_process.iter() {
if !deadline_is_mutable(
state.current_proving_period_start(curr_epoch),
deadline_idx,
curr_epoch,
) {
return Err(actor_error!(
ErrIllegalArgument,
"cannot terminate sectors in immutable deadline {}",
deadline_idx
));
}
let quant = state.quant_spec_for_deadline(deadline_idx);
let mut deadline = deadlines.load_deadline(store, deadline_idx).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to load deadline {}", deadline_idx),
)
})?;
let removed_power = deadline
.terminate_sectors(
store,
§ors,
curr_epoch,
partition_sectors,
info.sector_size,
quant,
)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to terminate sectors in deadline {}", deadline_idx),
)
})?;
state.early_terminations.set(deadline_idx as usize);
power_delta -= &removed_power;
deadlines
.update_deadline(store, deadline_idx, &deadline)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to update deadline {}", deadline_idx),
)
})?;
}
state.save_deadlines(store, deadlines).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to save deadlines")
})?;
Ok((had_early_terminations, power_delta))
})?;
let epoch_reward = request_current_epoch_block_reward(rt)?;
let pwr_total = request_current_total_power(rt)?;
let more = process_early_terminations(
rt,
&epoch_reward.this_epoch_reward_smoothed,
&pwr_total.quality_adj_power_smoothed,
)?;
if more && !had_early_terminations {
schedule_early_termination_work(rt)?;
}
let state: State = rt.state()?;
state
.check_balance_invariants(&rt.current_balance()?)
.map_err(|e| {
ActorError::new(
ErrBalanceInvariantBroken,
format!("balance invariant broken: {}", e),
)
})?;
request_update_power(rt, power_delta)?;
Ok(TerminateSectorsReturn { done: !more })
}
fn declare_faults<BS, RT>(rt: &mut RT, params: DeclareFaultsParams) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
if params.faults.len() as u64 > DELCARATIONS_MAX {
return Err(actor_error!(
ErrIllegalArgument,
"too many fault declarations for a single message: {} > {}",
params.faults.len(),
DELCARATIONS_MAX
));
}
let mut to_process = DeadlineSectorMap::new();
for term in params.faults {
let deadline = term.deadline;
let partition = term.partition;
to_process
.add(deadline, partition, term.sectors)
.map_err(|e| {
actor_error!(
ErrIllegalArgument,
"failed to process deadline {}, partition {}: {}",
deadline,
partition,
e
)
})?;
}
to_process
.check(ADDRESSED_PARTITIONS_MAX, ADDRESSED_SECTORS_MAX)
.map_err(|e| {
actor_error!(
ErrIllegalArgument,
"cannot process requested parameters: {}",
e
)
})?;
let power_delta = rt.transaction(|state: &mut State, rt| {
let info = get_miner_info(rt.store(), state)?;
rt.validate_immediate_caller_is(
info.control_addresses
.iter()
.chain(&[info.worker, info.owner]),
)?;
let store = rt.store();
let mut deadlines = state
.load_deadlines(store)
.map_err(|e| e.wrap("failed to load deadlines"))?;
let sectors = Sectors::load(store, &state.sectors).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to load sectors array")
})?;
let mut new_fault_power_total = PowerPair::zero();
let curr_epoch = rt.curr_epoch();
for (deadline_idx, partition_map) in to_process.iter() {
let target_deadline = declaration_deadline_info(
state.current_proving_period_start(curr_epoch),
deadline_idx,
curr_epoch,
)
.map_err(|e| {
actor_error!(
ErrIllegalArgument,
"invalid fault declaration deadline {}: {}",
deadline_idx,
e
)
})?;
validate_fr_declaration_deadline(&target_deadline).map_err(|e| {
actor_error!(
ErrIllegalArgument,
"failed fault declaration at deadline {}: {}",
deadline_idx,
e
)
})?;
let mut deadline = deadlines.load_deadline(store, deadline_idx).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to load deadline {}", deadline_idx),
)
})?;
let fault_expiration_epoch = target_deadline.last() + FAULT_MAX_AGE;
let deadline_power_delta = deadline
.record_faults(
store,
§ors,
info.sector_size,
target_deadline.quant_spec(),
fault_expiration_epoch,
partition_map,
)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to declare faults for deadline {}", deadline_idx),
)
})?;
deadlines
.update_deadline(store, deadline_idx, &deadline)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to store deadline {} partitions", deadline_idx),
)
})?;
new_fault_power_total += &deadline_power_delta;
}
state.save_deadlines(store, deadlines).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to save deadlines")
})?;
Ok(new_fault_power_total)
})?;
request_update_power(rt, power_delta)?;
Ok(())
}
fn declare_faults_recovered<BS, RT>(
rt: &mut RT,
params: DeclareFaultsRecoveredParams,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
if params.recoveries.len() as u64 > DELCARATIONS_MAX {
return Err(actor_error!(
ErrIllegalArgument,
"too many recovery declarations for a single message: {} > {}",
params.recoveries.len(),
DELCARATIONS_MAX
));
}
let mut to_process = DeadlineSectorMap::new();
for term in params.recoveries {
let deadline = term.deadline;
let partition = term.partition;
to_process
.add(deadline, partition, term.sectors)
.map_err(|e| {
actor_error!(
ErrIllegalArgument,
"failed to process deadline {}, partition {}: {}",
deadline,
partition,
e
)
})?;
}
to_process
.check(ADDRESSED_PARTITIONS_MAX, ADDRESSED_SECTORS_MAX)
.map_err(|e| {
actor_error!(
ErrIllegalArgument,
"cannot process requested parameters: {}",
e
)
})?;
let fee_to_burn = rt.transaction(|state: &mut State, rt| {
let fee_to_burn = repay_debts_or_abort(rt, state)?;
let info = get_miner_info(rt.store(), state)?;
rt.validate_immediate_caller_is(
info.control_addresses
.iter()
.chain(&[info.worker, info.owner]),
)?;
if consensus_fault_active(&info, rt.curr_epoch()) {
return Err(actor_error!(
ErrForbidden,
"recovery not allowed during active consensus fault"
));
}
let store = rt.store();
let mut deadlines = state
.load_deadlines(store)
.map_err(|e| e.wrap("failed to load deadlines"))?;
let sectors = Sectors::load(store, &state.sectors).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to load sectors array")
})?;
let curr_epoch = rt.curr_epoch();
for (deadline_idx, partition_map) in to_process.iter() {
let target_deadline = declaration_deadline_info(
state.current_proving_period_start(curr_epoch),
deadline_idx,
curr_epoch,
)
.map_err(|e| {
actor_error!(
ErrIllegalArgument,
"invalid recovery declaration deadline {}: {}",
deadline_idx,
e
)
})?;
validate_fr_declaration_deadline(&target_deadline).map_err(|e| {
actor_error!(
ErrIllegalArgument,
"failed recovery declaration at deadline {}: {}",
deadline_idx,
e
)
})?;
let mut deadline = deadlines.load_deadline(store, deadline_idx).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to load deadline {}", deadline_idx),
)
})?;
deadline
.declare_faults_recovered(store, §ors, info.sector_size, partition_map)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to declare recoveries for deadline {}", deadline_idx),
)
})?;
deadlines
.update_deadline(store, deadline_idx, &deadline)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to store deadline {}", deadline_idx),
)
})?;
}
state.save_deadlines(store, deadlines).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to save deadlines")
})?;
Ok(fee_to_burn)
})?;
burn_funds(rt, fee_to_burn)?;
let state: State = rt.state()?;
state
.check_balance_invariants(&rt.current_balance()?)
.map_err(|e| {
ActorError::new(
ErrBalanceInvariantBroken,
format!("balance invariants broken: {}", e),
)
})?;
Ok(())
}
fn compact_partitions<BS, RT>(
rt: &mut RT,
mut params: CompactPartitionsParams,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
if params.deadline >= WPOST_PERIOD_DEADLINES as usize {
return Err(actor_error!(
ErrIllegalArgument,
"invalid deadline {}",
params.deadline
));
}
let partitions = params.partitions.validate().map_err(|e| {
actor_error!(
ErrIllegalArgument,
"failed to parse partitions bitfield: {}",
e
)
})?;
let partition_count = partitions.len() as u64;
let params_deadline = params.deadline;
rt.transaction(|state: &mut State, rt| {
let info = get_miner_info(rt.store(), state)?;
rt.validate_immediate_caller_is(
info.control_addresses
.iter()
.chain(&[info.worker, info.owner]),
)?;
let store = rt.store();
if !deadline_available_for_compaction(
state.current_proving_period_start(rt.curr_epoch()),
params_deadline,
rt.curr_epoch(),
) {
return Err(actor_error!(
ErrForbidden,
"cannot compact deadline {} during its challenge window, \
or the prior challenge window,
or before {} epochs have passed since its last challenge window ended",
params_deadline,
WPOST_DISPUTE_WINDOW
));
}
let submission_partition_limit =
load_partitions_sectors_max(info.window_post_partition_sectors);
if partition_count > submission_partition_limit {
return Err(actor_error!(
ErrIllegalArgument,
"too many partitions {}, limit {}",
partition_count,
submission_partition_limit
));
}
let quant = state.quant_spec_for_deadline(params_deadline);
let mut deadlines = state
.load_deadlines(store)
.map_err(|e| e.wrap("failed to load deadlines"))?;
let mut deadline = deadlines
.load_deadline(store, params_deadline)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to load deadline {}", params_deadline),
)
})?;
let (live, dead, removed_power) = deadline
.remove_partitions(store, partitions, quant)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!(
"failed to remove partitions from deadline {}",
params_deadline
),
)
})?;
state.delete_sectors(store, &dead).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to delete dead sectors")
})?;
let sectors = state.load_sector_infos(store, &live).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to load moved sectors")
})?;
let proven = true;
let added_power = deadline
.add_sectors(
store,
info.window_post_partition_sectors,
proven,
§ors,
info.sector_size,
quant,
)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to add back moved sectors",
)
})?;
if removed_power != added_power {
return Err(actor_error!(
ErrIllegalState,
"power changed when compacting partitions: was {:?}, is now {:?}",
removed_power,
added_power
));
}
deadlines
.update_deadline(store, params_deadline, &deadline)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to update deadline {}", params_deadline),
)
})?;
state.save_deadlines(store, deadlines).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to save deadline {}", params_deadline),
)
})?;
Ok(())
})?;
Ok(())
}
fn compact_sector_numbers<BS, RT>(
rt: &mut RT,
mut params: CompactSectorNumbersParams,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let mask_sector_numbers = params
.mask_sector_numbers
.validate()
.map_err(|e| actor_error!(ErrIllegalArgument, "invalid mask bitfield: {}", e))?;
let last_sector_number = mask_sector_numbers.last().map_err(|e| {
actor_error!(
ErrIllegalArgument,
"invalid mask bitfield, no sectors set: {}",
e
)
})?;
#[allow(clippy::absurd_extreme_comparisons)]
if (last_sector_number as u64) > MAX_SECTOR_NUMBER {
return Err(actor_error!(
ErrIllegalArgument,
"masked sector number {} exceeded max sector number",
last_sector_number
));
}
rt.transaction(|state: &mut State, rt| {
let info = get_miner_info(rt.store(), state)?;
rt.validate_immediate_caller_is(
info.control_addresses
.iter()
.chain(&[info.worker, info.owner]),
)?;
state.allocate_sector_numbers(
rt.store(),
mask_sector_numbers,
CollisionPolicy::AllowCollisions,
)
})?;
Ok(())
}
fn apply_rewards<BS, RT>(rt: &mut RT, params: ApplyRewardParams) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
if params.reward.is_negative() {
return Err(actor_error!(
ErrIllegalArgument,
"cannot lock up a negative amount of funds"
));
}
if params.penalty.is_negative() {
return Err(actor_error!(
ErrIllegalArgument,
"cannot penalize a negative amount of funds"
));
}
let (pledge_delta_total, to_burn) = rt.transaction(|st: &mut State, rt| {
let mut pledge_delta_total = TokenAmount::zero();
rt.validate_immediate_caller_is(std::iter::once(&*REWARD_ACTOR_ADDR))?;
let (reward_to_lock, locked_reward_vesting_spec) =
locked_reward_from_reward(params.reward);
let unlocked_balance =
st.get_unlocked_balance(&rt.current_balance()?)
.map_err(|e| {
actor_error!(
ErrIllegalState,
"failed to calculate unlocked balance: {}",
e
)
})?;
if unlocked_balance < reward_to_lock {
return Err(actor_error!(
ErrInsufficientFunds,
"insufficient funds to lock, available: {}, requested: {}",
unlocked_balance,
reward_to_lock
));
}
let newly_vested = st
.add_locked_funds(
rt.store(),
rt.curr_epoch(),
&reward_to_lock,
locked_reward_vesting_spec,
)
.map_err(|e| {
actor_error!(
ErrIllegalState,
"failed to lock funds in vesting table: {}",
e
)
})?;
pledge_delta_total -= &newly_vested;
pledge_delta_total += &reward_to_lock;
st.apply_penalty(¶ms.penalty)
.map_err(|e| actor_error!(ErrIllegalState, "failed to apply penalty: {}", e))?;
let (penalty_from_vesting, penalty_from_balance) = st
.repay_partial_debt_in_priority_order(
rt.store(),
rt.curr_epoch(),
&rt.current_balance()?,
)
.map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to repay penalty")
})?;
pledge_delta_total -= &penalty_from_vesting;
let to_burn = penalty_from_vesting + penalty_from_balance;
Ok((pledge_delta_total, to_burn))
})?;
notify_pledge_changed(rt, &pledge_delta_total)?;
burn_funds(rt, to_burn)?;
let st: State = rt.state()?;
st.check_balance_invariants(&rt.current_balance()?)
.map_err(|e| {
ActorError::new(
ErrBalanceInvariantBroken,
format!("balance invariants broken: {}", e),
)
})?;
Ok(())
}
fn report_consensus_fault<BS, RT>(
rt: &mut RT,
params: ReportConsensusFaultParams,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
rt.validate_immediate_caller_type(CALLER_TYPES_SIGNABLE.iter())?;
let reporter = *rt.message().caller();
let fault = rt
.verify_consensus_fault(¶ms.header1, ¶ms.header2, ¶ms.header_extra)
.map_err(|e| e.downcast_default(ExitCode::ErrIllegalArgument, "fault not verified"))?
.ok_or_else(|| actor_error!(ErrIllegalArgument, "No consensus fault found"))?;
if fault.target != *rt.message().receiver() {
return Err(actor_error!(
ErrIllegalArgument,
"fault by {} reported to miner {}",
fault.target,
rt.message().receiver()
));
}
let fault_age = rt.curr_epoch() - fault.epoch;
if fault_age <= 0 {
return Err(actor_error!(
ErrIllegalArgument,
"invalid fault epoch {} ahead of current {}",
fault.epoch,
rt.curr_epoch()
));
}
let reward_stats = request_current_epoch_block_reward(rt)?;
let this_epoch_reward = reward_stats.this_epoch_reward_smoothed.estimate();
let fault_penalty = consensus_fault_penalty(this_epoch_reward.clone());
let slasher_reward = reward_for_consensus_slash_report(&this_epoch_reward);
let mut pledge_delta = TokenAmount::from(0);
let (burn_amount, reward_amount) = rt.transaction(|st: &mut State, rt| {
let mut info = get_miner_info(rt.store(), st)?;
if fault.epoch < info.consensus_fault_elapsed {
return Err(actor_error!(
ErrForbidden,
"fault epoch {} is too old, last exclusion period ended at {}",
fault.epoch,
info.consensus_fault_elapsed
));
}
st.apply_penalty(&fault_penalty).map_err(|e| {
actor_error!(ErrIllegalState, format!("failed to apply penalty: {}", e))
})?;
let (penalty_from_vesting, penalty_from_balance) = st
.repay_partial_debt_in_priority_order(
rt.store(),
rt.curr_epoch(),
&rt.current_balance()?,
)
.map_err(|e| e.downcast_default(ExitCode::ErrIllegalState, "failed to pay fees"))?;
let mut burn_amount = &penalty_from_vesting + &penalty_from_balance;
pledge_delta -= penalty_from_vesting;
let reward_amount = std::cmp::min(&burn_amount, &slasher_reward).clone();
burn_amount -= &reward_amount;
info.consensus_fault_elapsed = rt.curr_epoch() + CONSENSUS_FAULT_INELIGIBILITY_DURATION;
st.save_info(rt.store(), &info).map_err(|e| {
e.downcast_default(ExitCode::ErrSerialization, "failed to save miner info")
})?;
Ok((burn_amount, reward_amount))
})?;
if let Err(e) = rt.send(reporter, METHOD_SEND, Serialized::default(), reward_amount) {
error!("failed to send reward: {}", e);
}
burn_funds(rt, burn_amount)?;
notify_pledge_changed(rt, &pledge_delta)?;
let state: State = rt.state()?;
state
.check_balance_invariants(&rt.current_balance()?)
.map_err(|e| {
ActorError::new(
ErrBalanceInvariantBroken,
format!("balance invariants broken: {}", e),
)
})?;
Ok(())
}
fn withdraw_balance<BS, RT>(
rt: &mut RT,
params: WithdrawBalanceParams,
) -> Result<WithdrawBalanceReturn, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
if params.amount_requested.is_negative() {
return Err(actor_error!(
ErrIllegalArgument,
"negative fund requested for withdrawal: {}",
params.amount_requested
));
}
let (info, newly_vested, fee_to_burn, available_balance, state) =
rt.transaction(|state: &mut State, rt| {
let info = get_miner_info(rt.store(), state)?;
rt.validate_immediate_caller_is(&[info.owner])?;
if !state.early_terminations.is_empty() {
return Err(actor_error!(
ErrForbidden,
"cannot withdraw funds while {} deadlines have terminated sectors \
with outstanding fees",
state.early_terminations.len()
));
}
let newly_vested = state
.unlock_vested_funds(rt.store(), rt.curr_epoch())
.map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "Failed to vest fund")
})?;
let available_balance = state
.get_available_balance(&rt.current_balance()?)
.map_err(|e| {
actor_error!(
ErrIllegalState,
format!("failed to calculate available balance: {}", e)
)
})?;
let fee_to_burn = repay_debts_or_abort(rt, state)?;
Ok((
info,
newly_vested,
fee_to_burn,
available_balance,
state.clone(),
))
})?;
let amount_withdrawn = std::cmp::min(&available_balance, ¶ms.amount_requested);
if amount_withdrawn.is_negative() {
return Err(actor_error!(
ErrIllegalState,
"negative amount to withdraw: {}",
amount_withdrawn
));
}
if amount_withdrawn > &available_balance {
return Err(actor_error!(
ErrIllegalState,
"amount to withdraw {} < available {}",
amount_withdrawn,
available_balance
));
}
if amount_withdrawn.is_positive() {
rt.send(
info.owner,
METHOD_SEND,
Serialized::default(),
amount_withdrawn.clone(),
)?;
}
burn_funds(rt, fee_to_burn)?;
notify_pledge_changed(rt, &newly_vested.neg())?;
state
.check_balance_invariants(&rt.current_balance()?)
.map_err(|e| {
ActorError::new(
ErrBalanceInvariantBroken,
format!("balance invariants broken: {}", e),
)
})?;
Ok(WithdrawBalanceReturn {
amount_withdrawn: amount_withdrawn.clone(),
})
}
fn repay_debt<BS, RT>(rt: &mut RT) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let (from_vesting, from_balance, state) = rt.transaction(|state: &mut State, rt| {
let info = get_miner_info(rt.store(), state)?;
rt.validate_immediate_caller_is(
info.control_addresses
.iter()
.chain(&[info.worker, info.owner]),
)?;
let (from_vesting, from_balance) = state
.repay_partial_debt_in_priority_order(
rt.store(),
rt.curr_epoch(),
&rt.current_balance()?,
)
.map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to unlock fee debt")
})?;
Ok((from_vesting, from_balance, state.clone()))
})?;
let burn_amount = from_balance + &from_vesting;
notify_pledge_changed(rt, &from_vesting.neg())?;
burn_funds(rt, burn_amount)?;
state
.check_balance_invariants(&rt.current_balance()?)
.map_err(|e| {
ActorError::new(
ErrBalanceInvariantBroken,
format!("balance invariants broken: {}", e),
)
})?;
Ok(())
}
fn on_deferred_cron_event<BS, RT>(
rt: &mut RT,
params: DeferredCronEventParams,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
rt.validate_immediate_caller_is(std::iter::once(&*STORAGE_POWER_ACTOR_ADDR))?;
let payload: CronEventPayload = from_slice(¶ms.event_payload).map_err(|e| {
actor_error!(
ErrIllegalState,
format!(
"failed to unmarshal miner cron payload into expected structure: {}",
e
)
)
})?;
match payload.event_type {
CRON_EVENT_PROVING_DEADLINE => handle_proving_deadline(
rt,
¶ms.reward_smoothed,
¶ms.quality_adj_power_smoothed,
)?,
CRON_EVENT_PROCESS_EARLY_TERMINATIONS => {
if process_early_terminations(
rt,
¶ms.reward_smoothed,
¶ms.quality_adj_power_smoothed,
)? {
schedule_early_termination_work(rt)?
}
}
_ => {
error!(
"onDeferredCronEvent invalid event type: {}",
payload.event_type
);
}
};
let state: State = rt.state()?;
state
.check_balance_invariants(&rt.current_balance()?)
.map_err(|e| {
ActorError::new(
ErrBalanceInvariantBroken,
format!("balance invariants broken: {}", e),
)
})?;
Ok(())
}
}
fn process_early_terminations<BS, RT>(
rt: &mut RT,
reward_smoothed: &FilterEstimate,
quality_adj_power_smoothed: &FilterEstimate,
) -> Result< bool, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let (result, more, deals_to_terminate, penalty, pledge_delta) =
rt.transaction(|state: &mut State, rt| {
let store = rt.store();
let (result, more) = state
.pop_early_terminations(store, ADDRESSED_PARTITIONS_MAX, ADDRESSED_SECTORS_MAX)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to pop early terminations",
)
})?;
if result.is_empty() {
info!("no early terminations (maybe cron callback hasn't happened yet?)");
return Ok((
result,
more,
Vec::new(),
TokenAmount::zero(),
TokenAmount::zero(),
));
}
let info = get_miner_info(rt.store(), state)?;
let sectors = Sectors::load(store, &state.sectors).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to load sectors array")
})?;
let mut total_initial_pledge = TokenAmount::zero();
let mut deals_to_terminate =
Vec::<OnMinerSectorsTerminateParams>::with_capacity(result.sectors.len());
let mut penalty = TokenAmount::zero();
for (epoch, sector_numbers) in result.iter() {
let sectors = sectors
.load_sector(sector_numbers)
.map_err(|e| e.wrap("failed to load sector infos"))?;
penalty += termination_penalty(
info.sector_size,
epoch,
reward_smoothed,
quality_adj_power_smoothed,
§ors,
);
let mut deal_ids = Vec::<DealID>::with_capacity(sectors.len());
for sector in sectors {
deal_ids.extend(sector.deal_ids);
total_initial_pledge += sector.initial_pledge;
}
let params = OnMinerSectorsTerminateParams { epoch, deal_ids };
deals_to_terminate.push(params);
}
state
.apply_penalty(&penalty)
.map_err(|e| actor_error!(ErrIllegalState, "failed to apply penalty: {}", e))?;
let mut pledge_delta = -total_initial_pledge;
state.add_initial_pledge(&pledge_delta).map_err(|e| {
actor_error!(
ErrIllegalState,
"failed to add initial pledge {}: {}",
pledge_delta,
e
)
})?;
let (penalty_from_vesting, penalty_from_balance) = state
.repay_partial_debt_in_priority_order(
rt.store(),
rt.curr_epoch(),
&rt.current_balance()?,
)
.map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to repay penalty")
})?;
penalty = &penalty_from_vesting + penalty_from_balance;
pledge_delta -= penalty_from_vesting;
Ok((result, more, deals_to_terminate, penalty, pledge_delta))
})?;
if result.is_empty() {
info!("no early terminations");
return Ok(more);
}
log::debug!(
"storage provider {} penalized {} for sector termination",
rt.message().receiver(),
penalty
);
burn_funds(rt, penalty)?;
notify_pledge_changed(rt, &pledge_delta)?;
for params in deals_to_terminate {
request_terminate_deals(rt, params.epoch, params.deal_ids)?;
}
Ok(more)
}
fn handle_proving_deadline<BS, RT>(
rt: &mut RT,
reward_smoothed: &FilterEstimate,
quality_adj_power_smoothed: &FilterEstimate,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let curr_epoch = rt.curr_epoch();
let mut had_early_terminations = false;
let mut power_delta_total = PowerPair::zero();
let mut penalty_total = TokenAmount::zero();
let mut pledge_delta_total = TokenAmount::zero();
let mut continue_cron = false;
let state: State = rt.transaction(|state: &mut State, rt| {
let newly_vested = state
.unlock_vested_funds(rt.store(), rt.curr_epoch())
.map_err(|e| e.downcast_default(ExitCode::ErrIllegalState, "failed to vest funds"))?;
pledge_delta_total -= newly_vested;
let mut info = get_miner_info(rt.store(), state)?;
process_pending_worker(&mut info, rt, state)?;
let deposit_to_burn = state
.cleanup_expired_pre_commits(rt.store(), rt.curr_epoch())
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to expire pre-committed sectors",
)
})?;
state
.apply_penalty(&deposit_to_burn)
.map_err(|e| actor_error!(ErrIllegalState, "failed to apply penalty: {}", e))?;
log::debug!(
"storage provider {} penalized {} for expired pre commits",
rt.message().receiver(),
deposit_to_burn
);
had_early_terminations = have_pending_early_terminations(state);
let result = state
.advance_deadline(rt.store(), rt.curr_epoch())
.map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to advance deadline")
})?;
let penalty_target = pledge_penalty_for_continued_fault(
reward_smoothed,
quality_adj_power_smoothed,
&result.previously_faulty_power.qa,
);
power_delta_total += &result.power_delta;
pledge_delta_total += &result.pledge_delta;
state
.apply_penalty(&penalty_target)
.map_err(|e| actor_error!(ErrIllegalState, "failed to apply penalty: {}", e))?;
log::debug!(
"storage provider {} penalized {} for continued fault",
rt.message().receiver(),
penalty_target
);
let (penalty_from_vesting, penalty_from_balance) = state
.repay_partial_debt_in_priority_order(
rt.store(),
rt.curr_epoch(),
&rt.current_balance()?,
)
.map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to unlock penalty")
})?;
penalty_total = &penalty_from_vesting + penalty_from_balance;
pledge_delta_total -= penalty_from_vesting;
continue_cron = state.continue_deadline_cron();
if !continue_cron {
state.deadline_cron_active = false;
}
Ok(state.clone())
})?;
request_update_power(rt, power_delta_total)?;
burn_funds(rt, penalty_total)?;
notify_pledge_changed(rt, &pledge_delta_total)?;
if continue_cron {
let new_deadline_info = state.deadline_info(curr_epoch + 1);
enroll_cron_event(
rt,
new_deadline_info.last(),
CronEventPayload {
event_type: CRON_EVENT_PROVING_DEADLINE,
},
)?;
} else {
info!(
"miner {} going inactive, deadline cron discontinued",
rt.message().receiver()
)
}
let has_early_terminations = have_pending_early_terminations(&state);
if !had_early_terminations && has_early_terminations {
if process_early_terminations(rt, reward_smoothed, quality_adj_power_smoothed)? {
schedule_early_termination_work(rt)?;
}
}
Ok(())
}
fn validate_expiration<BS, RT>(
rt: &RT,
activation: ChainEpoch,
expiration: ChainEpoch,
seal_proof: RegisteredSealProof,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
if expiration <= activation {
return Err(actor_error!(
ErrIllegalArgument,
"sector expiration {} must be after activation {}",
expiration,
activation
));
}
if expiration - activation < MIN_SECTOR_EXPIRATION {
return Err(actor_error!(
ErrIllegalArgument,
"invalid expiration {}, total sector lifetime ({}) must exceed {} after activation {}",
expiration,
expiration - activation,
MIN_SECTOR_EXPIRATION,
activation
));
}
if expiration > rt.curr_epoch() + MAX_SECTOR_EXPIRATION_EXTENSION {
return Err(actor_error!(
ErrIllegalArgument,
"invalid expiration {}, cannot be more than {} past current epoch {}",
expiration,
MAX_SECTOR_EXPIRATION_EXTENSION,
rt.curr_epoch()
));
}
let max_lifetime = seal_proof_sector_maximum_lifetime(seal_proof, rt.network_version())
.ok_or_else(|| {
actor_error!(
ErrIllegalArgument,
"unrecognized seal proof type {:?}",
seal_proof
)
})?;
if expiration - activation > max_lifetime {
return Err(actor_error!(
ErrIllegalArgument,
"invalid expiration {}, total sector lifetime ({}) cannot exceed {} after activation {}",
expiration,
expiration - activation,
max_lifetime,
activation
));
}
Ok(())
}
fn validate_replace_sector<BS>(
state: &State,
store: &BS,
params: &SectorPreCommitInfo,
) -> Result<(), ActorError>
where
BS: BlockStore,
{
let replace_sector = state
.get_sector(store, params.replace_sector_number)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to load sector {}", params.replace_sector_number),
)
})?
.ok_or_else(|| {
actor_error!(
ErrNotFound,
"no such sector {} to replace",
params.replace_sector_number
)
})?;
if !replace_sector.deal_ids.is_empty() {
return Err(actor_error!(
ErrIllegalArgument,
"cannot replace sector {} which has deals",
params.replace_sector_number
));
}
let replace_w_post_proof = replace_sector
.seal_proof
.registered_window_post_proof()
.map_err(|e| {
actor_error!(
ErrIllegalState,
"failed to lookup Window PoSt proof type for sector seal proof {:?}: {}",
replace_sector.seal_proof,
e
)
})?;
let new_w_post_proof = params
.seal_proof
.registered_window_post_proof()
.map_err(|e| {
actor_error!(
ErrIllegalArgument,
"failed to lookup Window PoSt proof type for new seal proof {:?}: {}",
replace_sector.seal_proof,
e
)
})?;
if replace_w_post_proof != new_w_post_proof {
return Err(actor_error!(
ErrIllegalArgument,
"new sector window PoSt proof type {:?} must match replaced proof type {:?} (seal proof type {:?})",
replace_w_post_proof,
new_w_post_proof,
params.seal_proof
));
}
if params.expiration < replace_sector.expiration {
return Err(actor_error!(
ErrIllegalArgument,
"cannot replace sector {} expiration {} with sooner expiration {}",
params.replace_sector_number,
replace_sector.expiration,
params.expiration
));
}
state
.check_sector_health(
store,
params.replace_sector_deadline,
params.replace_sector_partition,
params.replace_sector_number,
)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
format!("failed to replace sector {}", params.replace_sector_number),
)
})?;
Ok(())
}
fn enroll_cron_event<BS, RT>(
rt: &mut RT,
event_epoch: ChainEpoch,
cb: CronEventPayload,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let payload = Serialized::serialize(cb)
.map_err(|e| ActorError::from(e).wrap("failed to serialize payload: {}"))?;
let ser_params = Serialized::serialize(EnrollCronEventParams {
event_epoch,
payload,
})?;
rt.send(
*STORAGE_POWER_ACTOR_ADDR,
PowerMethod::EnrollCronEvent as u64,
ser_params,
TokenAmount::zero(),
)?;
Ok(())
}
fn request_update_power<BS, RT>(rt: &mut RT, delta: PowerPair) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
if delta.is_zero() {
return Ok(());
}
let delta_clone = delta.clone();
rt.send(
*STORAGE_POWER_ACTOR_ADDR,
crate::power::Method::UpdateClaimedPower as MethodNum,
Serialized::serialize(crate::power::UpdateClaimedPowerParams {
raw_byte_delta: delta.raw,
quality_adjusted_delta: delta.qa,
})?,
TokenAmount::zero(),
)
.map_err(|e| e.wrap(format!("failed to update power with {:?}", delta_clone)))?;
Ok(())
}
fn request_terminate_deals<BS, RT>(
rt: &mut RT,
epoch: ChainEpoch,
deal_ids: Vec<DealID>,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
const MAX_LENGTH: usize = 8192;
for chunk in deal_ids.chunks(MAX_LENGTH) {
rt.send(
*STORAGE_MARKET_ACTOR_ADDR,
MarketMethod::OnMinerSectorsTerminate as u64,
Serialized::serialize(OnMinerSectorsTerminateParamsRef {
epoch,
deal_ids: chunk,
})?,
TokenAmount::zero(),
)?;
}
Ok(())
}
fn schedule_early_termination_work<BS, RT>(rt: &mut RT) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
info!("scheduling early terminations with cron...");
enroll_cron_event(
rt,
rt.curr_epoch() + 1,
CronEventPayload {
event_type: CRON_EVENT_PROCESS_EARLY_TERMINATIONS,
},
)
}
fn have_pending_early_terminations(state: &State) -> bool {
let no_early_terminations = state.early_terminations.is_empty();
!no_early_terminations
}
fn verify_windowed_post<BS, RT>(
rt: &RT,
challenge_epoch: ChainEpoch,
sectors: &[SectorOnChainInfo],
proofs: Vec<PoStProof>,
) -> Result<bool, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let miner_actor_id: u64 = if let Payload::ID(i) = rt.message().receiver().payload() {
*i
} else {
return Err(actor_error!(
ErrIllegalState,
"runtime provided bad receiver address {}",
rt.message().receiver()
));
};
let entropy = rt.message().receiver().marshal_cbor().map_err(|e| {
ActorError::from(e).wrap("failed to marshal address for window post challenge")
})?;
let randomness: PoStRandomness =
rt.get_randomness_from_beacon(WindowedPoStChallengeSeed, challenge_epoch, &entropy)?;
let challenged_sectors = sectors
.iter()
.map(|s| SectorInfo {
proof: s.seal_proof,
sector_number: s.sector_number,
sealed_cid: s.sealed_cid,
})
.collect();
let pv_info = WindowPoStVerifyInfo {
randomness,
proofs,
challenged_sectors,
prover: miner_actor_id,
};
rt.verify_post(&pv_info).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalArgument,
format!(
"invalid PoSt: proofs({:?}), randomness({:?})",
pv_info.proofs, pv_info.randomness
),
)
})?;
Ok(true)
}
fn get_verify_info<BS, RT>(
rt: &mut RT,
params: SealVerifyParams,
) -> Result<SealVerifyInfo, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
if rt.curr_epoch() <= params.interactive_epoch {
return Err(actor_error!(ErrForbidden, "too early to prove sector"));
}
let commds = request_unsealed_sector_cids(
rt,
&[SectorDataSpec {
deal_ids: params.deal_ids.clone(),
sector_type: params.registered_seal_proof,
}],
)?;
let miner_actor_id: u64 = if let Payload::ID(i) = rt.message().receiver().payload() {
*i
} else {
return Err(actor_error!(
ErrIllegalState,
"runtime provided non ID receiver address {}",
rt.message().receiver()
));
};
let entropy =
rt.message().receiver().marshal_cbor().map_err(|e| {
ActorError::from(e).wrap("failed to marshal address for get verify info")
})?;
let randomness: SealRandom =
rt.get_randomness_from_tickets(SealRandomness, params.seal_rand_epoch, &entropy)?;
let interactive_randomness: InteractiveSealRandomness = rt.get_randomness_from_beacon(
InteractiveSealChallengeSeed,
params.interactive_epoch,
&entropy,
)?;
Ok(SealVerifyInfo {
registered_proof: params.registered_seal_proof,
sector_id: SectorID {
miner: miner_actor_id,
number: params.sector_num,
},
deal_ids: params.deal_ids,
interactive_randomness,
proof: params.proof,
randomness,
sealed_cid: params.sealed_cid,
unsealed_cid: commds[0],
})
}
fn request_unsealed_sector_cids<BS, RT>(
rt: &mut RT,
data_commitment_inputs: &[SectorDataSpec],
) -> Result<Vec<Cid>, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
if data_commitment_inputs.is_empty() {
return Ok(vec![]);
}
let ret: ComputeDataCommitmentReturn = rt
.send(
*STORAGE_MARKET_ACTOR_ADDR,
MarketMethod::ComputeDataCommitment as u64,
Serialized::serialize(ComputeDataCommitmentParamsRef {
inputs: data_commitment_inputs,
})?,
TokenAmount::zero(),
)?
.deserialize()?;
if data_commitment_inputs.len() != ret.commds.len() {
return Err(actor_error!(ErrIllegalState,
"number of data commitments computed {} does not match number of data commitment inputs {}",
ret.commds.len(), data_commitment_inputs.len()
));
}
Ok(ret.commds)
}
fn request_deal_weights<BS, RT>(
rt: &mut RT,
sectors: &[market::SectorDeals],
) -> Result<VerifyDealsForActivationReturn, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let mut deal_count = 0;
for sector in sectors {
deal_count += sector.deal_ids.len();
}
if deal_count == 0 {
let mut empty_result = VerifyDealsForActivationReturn {
sectors: Vec::with_capacity(sectors.len()),
};
for _ in 0..sectors.len() {
empty_result.sectors.push(market::SectorWeights {
deal_space: 0,
deal_weight: 0.into(),
verified_deal_weight: 0.into(),
});
}
return Ok(empty_result);
}
let serialized = rt.send(
*STORAGE_MARKET_ACTOR_ADDR,
MarketMethod::VerifyDealsForActivation as u64,
Serialized::serialize(VerifyDealsForActivationParamsRef { sectors })?,
TokenAmount::zero(),
)?;
Ok(serialized.deserialize()?)
}
fn request_current_epoch_block_reward<BS, RT>(
rt: &mut RT,
) -> Result<ThisEpochRewardReturn, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let ret = rt
.send(
*REWARD_ACTOR_ADDR,
crate::reward::Method::ThisEpochReward as MethodNum,
Default::default(),
TokenAmount::zero(),
)
.map_err(|e| e.wrap("failed to check epoch baseline power"))?;
let ret: ThisEpochRewardReturn = ret
.deserialize()
.map_err(|e| ActorError::from(e).wrap("failed to unmarshal target power value"))?;
Ok(ret)
}
fn request_current_total_power<BS, RT>(rt: &mut RT) -> Result<CurrentTotalPowerReturn, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let ret = rt
.send(
*STORAGE_POWER_ACTOR_ADDR,
crate::power::Method::CurrentTotalPower as MethodNum,
Default::default(),
TokenAmount::zero(),
)
.map_err(|e| e.wrap("failed to check current power"))?;
let power: CurrentTotalPowerReturn = ret
.deserialize()
.map_err(|e| ActorError::from(e).wrap("failed to unmarshal power total value"))?;
Ok(power)
}
fn resolve_control_address<BS, RT>(rt: &RT, raw: Address) -> Result<Address, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let resolved = rt
.resolve_address(&raw)?
.ok_or_else(|| actor_error!(ErrIllegalArgument, "unable to resolve address: {}", raw))?;
let owner_code = rt
.get_actor_code_cid(&resolved)?
.ok_or_else(|| actor_error!(ErrIllegalArgument, "no code for address: {}", resolved))?;
if !is_principal(&owner_code) {
return Err(actor_error!(
ErrIllegalArgument,
"owner actor type must be a principal, was {}",
owner_code
));
}
Ok(resolved)
}
fn resolve_worker_address<BS, RT>(rt: &mut RT, raw: Address) -> Result<Address, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let resolved = rt
.resolve_address(&raw)?
.ok_or_else(|| actor_error!(ErrIllegalArgument, "unable to resolve address: {}", raw))?;
let worker_code = rt
.get_actor_code_cid(&resolved)?
.ok_or_else(|| actor_error!(ErrIllegalArgument, "no code for address: {}", resolved))?;
if worker_code != *ACCOUNT_ACTOR_CODE_ID {
return Err(actor_error!(
ErrIllegalArgument,
"worker actor type must be an account, was {}",
worker_code
));
}
if raw.protocol() != Protocol::BLS {
let ret = rt.send(
resolved,
AccountMethod::PubkeyAddress as u64,
Serialized::default(),
TokenAmount::zero(),
)?;
let pub_key: Address = ret.deserialize().map_err(|e| {
ActorError::from(e).wrap(format!("failed to deserialize address result: {:?}", ret))
})?;
if pub_key.protocol() != Protocol::BLS {
return Err(actor_error!(
ErrIllegalArgument,
"worker account {} must have BLS pubkey, was {}",
resolved,
pub_key.protocol()
));
}
}
Ok(resolved)
}
fn burn_funds<BS, RT>(rt: &mut RT, amount: TokenAmount) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
log::debug!(
"storage provder {} burning {}",
rt.message().receiver(),
amount
);
if amount.is_positive() {
rt.send(
*BURNT_FUNDS_ACTOR_ADDR,
METHOD_SEND,
Serialized::default(),
amount,
)?;
}
Ok(())
}
fn notify_pledge_changed<BS, RT>(rt: &mut RT, pledge_delta: &BigInt) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
if !pledge_delta.is_zero() {
rt.send(
*STORAGE_POWER_ACTOR_ADDR,
PowerMethod::UpdatePledgeTotal as u64,
Serialized::serialize(BigIntSer(pledge_delta))?,
TokenAmount::zero(),
)?;
}
Ok(())
}
fn assign_proving_period_offset(
addr: Address,
current_epoch: ChainEpoch,
blake2b: impl FnOnce(&[u8]) -> Result<[u8; 32], Box<dyn StdError>>,
) -> Result<ChainEpoch, Box<dyn StdError>> {
let mut my_addr = addr.marshal_cbor()?;
my_addr.write_i64::<BigEndian>(current_epoch)?;
let digest = blake2b(&my_addr)?;
let mut offset: u64 = BigEndian::read_u64(&digest);
offset %= WPOST_PROVING_PERIOD as u64;
Ok(offset as ChainEpoch)
}
fn current_proving_period_start(current_epoch: ChainEpoch, offset: ChainEpoch) -> ChainEpoch {
let curr_modulus = current_epoch % WPOST_PROVING_PERIOD;
let period_progress = if curr_modulus >= offset {
curr_modulus - offset
} else {
WPOST_PROVING_PERIOD - (offset - curr_modulus)
};
current_epoch - period_progress
}
fn current_deadline_index(current_epoch: ChainEpoch, period_start: ChainEpoch) -> usize {
((current_epoch - period_start) / WPOST_CHALLENGE_WINDOW) as usize
}
fn declaration_deadline_info(
period_start: ChainEpoch,
deadline_idx: usize,
current_epoch: ChainEpoch,
) -> Result<DeadlineInfo, String> {
if deadline_idx >= WPOST_PERIOD_DEADLINES as usize {
return Err(format!(
"invalid deadline {}, must be < {}",
deadline_idx, WPOST_PERIOD_DEADLINES
));
}
let deadline = new_deadline_info(period_start, deadline_idx, current_epoch).next_not_elapsed();
Ok(deadline)
}
fn validate_fr_declaration_deadline(deadline: &DeadlineInfo) -> Result<(), String> {
if deadline.fault_cutoff_passed() {
Err("late fault or recovery declaration".to_string())
} else {
Ok(())
}
}
fn validate_partition_contains_sectors(
partition: &Partition,
sectors: &mut UnvalidatedBitField,
) -> Result<(), String> {
let sectors = sectors
.validate()
.map_err(|e| format!("failed to check sectors: {}", e))?;
if partition.sectors.contains_all(sectors) {
Ok(())
} else {
Err("not all sectors are assigned to the partition".to_string())
}
}
fn termination_penalty(
sector_size: SectorSize,
current_epoch: ChainEpoch,
reward_estimate: &FilterEstimate,
network_qa_power_estimate: &FilterEstimate,
sectors: &[SectorOnChainInfo],
) -> TokenAmount {
let mut total_fee = TokenAmount::zero();
for sector in sectors {
let sector_power = qa_power_for_sector(sector_size, sector);
let fee = pledge_penalty_for_termination(
§or.expected_day_reward,
current_epoch - sector.activation,
§or.expected_storage_pledge,
network_qa_power_estimate,
§or_power,
reward_estimate,
§or.replaced_day_reward,
sector.replaced_sector_age,
);
total_fee += fee;
}
total_fee
}
fn consensus_fault_active(info: &MinerInfo, curr_epoch: ChainEpoch) -> bool {
curr_epoch <= info.consensus_fault_elapsed
}
fn power_for_sector(sector_size: SectorSize, sector: &SectorOnChainInfo) -> PowerPair {
PowerPair {
raw: BigInt::from(sector_size as u64),
qa: qa_power_for_sector(sector_size, sector),
}
}
fn power_for_sectors(sector_size: SectorSize, sectors: &[SectorOnChainInfo]) -> PowerPair {
let qa = sectors
.iter()
.map(|s| qa_power_for_sector(sector_size, s))
.sum();
PowerPair {
raw: BigInt::from(sector_size as u64) * BigInt::from(sectors.len()),
qa,
}
}
fn get_miner_info<BS>(store: &BS, state: &State) -> Result<MinerInfo, ActorError>
where
BS: BlockStore,
{
state
.get_info(store)
.map_err(|e| e.downcast_default(ExitCode::ErrIllegalState, "could not read miner info"))
}
fn process_pending_worker<BS, RT>(
info: &mut MinerInfo,
rt: &RT,
state: &mut State,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let pending_worker_key = if let Some(k) = &info.pending_worker_key {
k
} else {
return Ok(());
};
if rt.curr_epoch() < pending_worker_key.effective_at {
return Ok(());
}
info.worker = pending_worker_key.new_worker;
info.pending_worker_key = None;
state
.save_info(rt.store(), info)
.map_err(|e| e.downcast_default(ExitCode::ErrIllegalState, "failed to save miner info"))
}
fn repay_debts_or_abort<BS, RT>(rt: &RT, state: &mut State) -> Result<TokenAmount, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let res = state.repay_debts(&rt.current_balance()?).map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"unlocked balance can not repay fee debt",
)
})?;
Ok(res)
}
fn replaced_sector_parameters(
curr_epoch: ChainEpoch,
precommit: &SectorPreCommitOnChainInfo,
replaced_by_num: &HashMap<SectorNumber, SectorOnChainInfo>,
) -> Result<(TokenAmount, ChainEpoch, TokenAmount), ActorError> {
if !precommit.info.replace_capacity {
return Ok(Default::default());
}
let replaced = replaced_by_num
.get(&precommit.info.replace_sector_number)
.ok_or_else(|| {
actor_error!(
ErrNotFound,
"no such sector {} to replace",
precommit.info.replace_sector_number
)
})?;
let age = std::cmp::max(0, curr_epoch - replaced.activation);
Ok((
replaced.initial_pledge.clone(),
age,
replaced.expected_day_reward.clone(),
))
}
fn check_control_addresses(control_addrs: &[Address]) -> Result<(), ActorError> {
if control_addrs.len() > MAX_CONTROL_ADDRESSES {
return Err(actor_error!(
ErrIllegalArgument,
"control addresses length {} exceeds max control addresses length {}",
control_addrs.len(),
MAX_CONTROL_ADDRESSES
));
}
Ok(())
}
fn check_valid_post_proof_type(proof_type: RegisteredPoStProof) -> Result<(), ActorError> {
match proof_type {
RegisteredPoStProof::StackedDRGWindow32GiBV1
| RegisteredPoStProof::StackedDRGWindow64GiBV1 => Ok(()),
_ => Err(actor_error!(
ErrIllegalArgument,
"proof type {:?} not allowed for new miner actors",
proof_type
)),
}
}
fn check_peer_info(peer_id: &[u8], multiaddrs: &[BytesDe]) -> Result<(), ActorError> {
if peer_id.len() > MAX_PEER_ID_LENGTH {
return Err(actor_error!(
ErrIllegalArgument,
"peer ID size of {} exceeds maximum size of {}",
peer_id.len(),
MAX_PEER_ID_LENGTH
));
}
let mut total_size = 0;
for ma in multiaddrs {
if ma.0.is_empty() {
return Err(actor_error!(ErrIllegalArgument, "invalid empty multiaddr"));
}
total_size += ma.0.len();
}
if total_size > MAX_MULTIADDR_DATA {
return Err(actor_error!(
ErrIllegalArgument,
"multiaddr size of {} exceeds maximum of {}",
total_size,
MAX_MULTIADDR_DATA
));
}
Ok(())
}
fn confirm_sector_proofs_valid_internal<BS, RT>(
rt: &mut RT,
pre_commits: Vec<SectorPreCommitOnChainInfo>,
this_epoch_baseline_power: &BigInt,
this_epoch_reward_smoothed: &FilterEstimate,
quality_adj_power_smoothed: &FilterEstimate,
) -> Result<(), ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
let circulating_supply = rt.total_fil_circ_supply()?;
let mut replace_sectors = DeadlineSectorMap::new();
let activation = rt.curr_epoch();
let mut valid_pre_commits = Vec::<SectorPreCommitOnChainInfo>::new();
for pre_commit in pre_commits {
if !pre_commit.info.deal_ids.is_empty() {
let res = rt.send(
*STORAGE_MARKET_ACTOR_ADDR,
crate::market::Method::ActivateDeals as MethodNum,
Serialized::serialize(ActivateDealsParams {
deal_ids: pre_commit.info.deal_ids.clone(),
sector_expiry: pre_commit.info.expiration,
})?,
TokenAmount::zero(),
);
if let Err(e) = res {
info!(
"failed to activate deals on sector {}, dropping from prove commit set: {}",
pre_commit.info.sector_number,
e.msg()
);
continue;
}
}
if pre_commit.info.replace_capacity {
replace_sectors
.add_values(
pre_commit.info.replace_sector_deadline,
pre_commit.info.replace_sector_partition,
&[pre_commit.info.replace_sector_number],
)
.map_err(|e| {
actor_error!(
ErrIllegalArgument,
"failed to record sectors for replacement: {}",
e
)
})?;
}
valid_pre_commits.push(pre_commit);
}
if valid_pre_commits.is_empty() {
return Err(actor_error!(
ErrIllegalArgument,
"all prove commits failed to validate"
));
}
let (total_pledge, newly_vested) = rt.transaction(|state: &mut State, rt| {
let store = rt.store();
let info = get_miner_info(store, state)?;
let replaced = state
.reschedule_sector_expirations(
store,
rt.curr_epoch(),
info.sector_size,
replace_sectors,
)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to replace sector expirations",
)
})?;
let replaced_by_sector_number: HashMap<u64, SectorOnChainInfo> =
replaced.into_iter().map(|s| (s.sector_number, s)).collect();
let mut new_sector_numbers = Vec::<SectorNumber>::with_capacity(valid_pre_commits.len());
let mut deposit_to_unlock = TokenAmount::zero();
let mut new_sectors = Vec::<SectorOnChainInfo>::new();
let mut total_pledge = TokenAmount::zero();
for pre_commit in valid_pre_commits {
let duration = pre_commit.info.expiration - activation;
if duration < MIN_SECTOR_EXPIRATION {
warn!(
"precommit {} has lifetime {} less than minimum {}. ignoring",
pre_commit.info.sector_number, duration, MIN_SECTOR_EXPIRATION,
);
continue;
}
let power = qa_power_for_weight(
info.sector_size,
duration,
&pre_commit.deal_weight,
&pre_commit.verified_deal_weight,
);
let day_reward = expected_reward_for_power(
this_epoch_reward_smoothed,
quality_adj_power_smoothed,
&power,
crate::EPOCHS_IN_DAY,
);
let storage_pledge = expected_reward_for_power(
this_epoch_reward_smoothed,
quality_adj_power_smoothed,
&power,
INITIAL_PLEDGE_PROJECTION_PERIOD,
);
let mut initial_pledge = initial_pledge_for_power(
&power,
this_epoch_baseline_power,
this_epoch_reward_smoothed,
quality_adj_power_smoothed,
&circulating_supply,
);
let (replaced_pledge, replaced_sector_age, replaced_day_reward) =
replaced_sector_parameters(
rt.curr_epoch(),
&pre_commit,
&replaced_by_sector_number,
)?;
initial_pledge = std::cmp::max(initial_pledge, replaced_pledge);
deposit_to_unlock += &pre_commit.pre_commit_deposit;
total_pledge += &initial_pledge;
let new_sector_info = SectorOnChainInfo {
sector_number: pre_commit.info.sector_number,
seal_proof: pre_commit.info.seal_proof,
sealed_cid: pre_commit.info.sealed_cid,
deal_ids: pre_commit.info.deal_ids,
expiration: pre_commit.info.expiration,
activation,
deal_weight: pre_commit.deal_weight,
verified_deal_weight: pre_commit.verified_deal_weight,
initial_pledge,
expected_day_reward: day_reward,
expected_storage_pledge: storage_pledge,
replaced_sector_age,
replaced_day_reward,
};
new_sector_numbers.push(new_sector_info.sector_number);
new_sectors.push(new_sector_info);
}
state.put_sectors(store, new_sectors.clone()).map_err(|e| {
e.downcast_default(ExitCode::ErrIllegalState, "failed to put new sectors")
})?;
state
.delete_precommitted_sectors(store, &new_sector_numbers)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to delete precommited sectors",
)
})?;
state
.assign_sectors_to_deadlines(
store,
rt.curr_epoch(),
new_sectors,
info.window_post_partition_sectors,
info.sector_size,
)
.map_err(|e| {
e.downcast_default(
ExitCode::ErrIllegalState,
"failed to assign new sectors to deadlines",
)
})?;
let newly_vested = TokenAmount::zero();
state
.add_pre_commit_deposit(&(-deposit_to_unlock))
.map_err(|e| actor_error!(ErrIllegalState, "failed to add precommit deposit: {}", e))?;
let unlocked_balance = state
.get_unlocked_balance(&rt.current_balance()?)
.map_err(|e| {
actor_error!(
ErrIllegalState,
"failed to calculate unlocked balance: {}",
e
)
})?;
if unlocked_balance < total_pledge {
return Err(actor_error!(
ErrInsufficientFunds,
"insufficient funds for aggregate initial pledge requirement {}, available: {}",
total_pledge,
unlocked_balance
));
}
state
.add_initial_pledge(&total_pledge)
.map_err(|e| actor_error!(ErrIllegalState, "failed to add initial pledge: {}", e))?;
state
.check_balance_invariants(&rt.current_balance()?)
.map_err(|e| {
ActorError::new(
ErrBalanceInvariantBroken,
format!("balance invariant broken: {}", e),
)
})?;
Ok((total_pledge, newly_vested))
})?;
notify_pledge_changed(rt, &(total_pledge - newly_vested))?;
Ok(())
}
impl ActorCode for Actor {
fn invoke_method<BS, RT>(
rt: &mut RT,
method: MethodNum,
params: &Serialized,
) -> Result<Serialized, ActorError>
where
BS: BlockStore,
RT: Runtime<BS>,
{
match FromPrimitive::from_u64(method) {
Some(Method::Constructor) => {
Self::constructor(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::ControlAddresses) => {
let res = Self::control_addresses(rt)?;
Ok(Serialized::serialize(&res)?)
}
Some(Method::ChangeWorkerAddress) => {
Self::change_worker_address(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::ChangePeerID) => {
Self::change_peer_id(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::SubmitWindowedPoSt) => {
Self::submit_windowed_post(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::PreCommitSector) => {
Self::pre_commit_sector(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::ProveCommitSector) => {
Self::prove_commit_sector(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::ExtendSectorExpiration) => {
Self::extend_sector_expiration(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::TerminateSectors) => {
let ret = Self::terminate_sectors(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::serialize(ret)?)
}
Some(Method::DeclareFaults) => {
Self::declare_faults(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::DeclareFaultsRecovered) => {
Self::declare_faults_recovered(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::OnDeferredCronEvent) => {
Self::on_deferred_cron_event(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::CheckSectorProven) => {
Self::check_sector_proven(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::ApplyRewards) => {
Self::apply_rewards(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::ReportConsensusFault) => {
Self::report_consensus_fault(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::WithdrawBalance) => {
let res = Self::withdraw_balance(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::serialize(&res)?)
}
Some(Method::ConfirmSectorProofsValid) => {
Self::confirm_sector_proofs_valid(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::ChangeMultiaddrs) => {
Self::change_multiaddresses(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::CompactPartitions) => {
Self::compact_partitions(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::CompactSectorNumbers) => {
Self::compact_sector_numbers(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::ConfirmUpdateWorkerKey) => {
Self::confirm_update_worker_key(rt)?;
Ok(Serialized::default())
}
Some(Method::RepayDebt) => {
Self::repay_debt(rt)?;
Ok(Serialized::default())
}
Some(Method::ChangeOwnerAddress) => {
Self::change_owner_address(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::DisputeWindowedPoSt) => {
Self::dispute_windowed_post(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::PreCommitSectorBatch) => {
Self::pre_commit_sector_batch(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
Some(Method::ProveCommitAggregate) => {
Self::prove_commit_aggregate(rt, rt.deserialize_params(params)?)?;
Ok(Serialized::default())
}
None => Err(actor_error!(SysErrInvalidMethod, "Invalid method")),
}
}
}