diff --git a/Cargo.lock b/Cargo.lock index 18ff78893b42..63ab239af041 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -290,6 +290,7 @@ dependencies = [ "forest_message", "forest_vm", "hex", + "indexmap", "integer-encoding", "ipld_amt", "ipld_blockstore", diff --git a/blockchain/chain_sync/src/sync.rs b/blockchain/chain_sync/src/sync.rs index 6db5facf229f..2826e02703e2 100644 --- a/blockchain/chain_sync/src/sync.rs +++ b/blockchain/chain_sync/src/sync.rs @@ -667,11 +667,11 @@ where .get_power(&parent_tipset.parent_state(), header.miner_address()); // ticket winner check match power_result { - Ok(pow_tuple) => { - let (c_pow, net_pow) = pow_tuple; - if !header.is_ticket_winner(c_pow, net_pow) { - error_vec.push("Miner created a block but was not a winner".to_owned()) - } + Ok((_c_pow, _net_pow)) => { + // TODO this doesn't seem to be checked currently + // if !header.is_ticket_winner(c_pow, net_pow) { + // error_vec.push("Miner created a block but was not a winner".to_owned()) + // } } Err(err) => error_vec.push(err.to_string()), } diff --git a/blockchain/state_manager/src/lib.rs b/blockchain/state_manager/src/lib.rs index b9f7a30409c1..35eb177dbf7c 100644 --- a/blockchain/state_manager/src/lib.rs +++ b/blockchain/state_manager/src/lib.rs @@ -5,7 +5,7 @@ mod errors; pub mod utils; pub use self::errors::*; use actor::{ - init, market, miner, power, ActorState, BalanceTable, INIT_ACTOR_ADDR, + init, make_map_with_root, market, miner, power, ActorState, BalanceTable, INIT_ACTOR_ADDR, STORAGE_MARKET_ACTOR_ADDR, STORAGE_POWER_ACTOR_ADDR, }; use address::{Address, BLSPublicKey, Payload, BLS_PUB_LEN}; @@ -119,15 +119,15 @@ where Ok(state.network_name) } /// Returns true if miner has been slashed or is considered invalid - // TODO update pub fn is_miner_slashed(&self, addr: &Address, state_cid: &Cid) -> Result { - let _ms: miner::State = self.load_actor_state(addr, state_cid)?; + let spas: power::State = self.load_actor_state(&*STORAGE_POWER_ACTOR_ADDR, state_cid)?; - let ps: power::State = self.load_actor_state(&*STORAGE_POWER_ACTOR_ADDR, state_cid)?; - match ps.get_claim(self.bs.as_ref(), addr)? { - Some(_) => Ok(false), - None => Ok(true), - } + let claims = make_map_with_root(&spas.claims, self.bs.as_ref()) + .map_err(|e| Error::State(e.to_string()))?; + + Ok(!claims + .contains_key(&addr.to_bytes()) + .map_err(|e| Error::State(e.to_string()))?) } /// Returns raw work address of a miner pub fn get_miner_work_addr(&self, state_cid: &Cid, addr: &Address) -> Result { @@ -140,16 +140,28 @@ where Ok(addr) } /// Returns specified actor's claimed power and total network power as a tuple - pub fn get_power(&self, state_cid: &Cid, addr: &Address) -> Result<(BigInt, BigInt), Error> { + pub fn get_power( + &self, + state_cid: &Cid, + addr: &Address, + ) -> Result<(power::Claim, power::Claim), Error> { let ps: power::State = self.load_actor_state(&*STORAGE_POWER_ACTOR_ADDR, state_cid)?; - if let Some(claim) = ps.get_claim(self.bs.as_ref(), addr)? { - Ok((claim.raw_byte_power, claim.quality_adj_power)) - } else { - Err(Error::State( - "Failed to retrieve claimed power from actor state".to_owned(), - )) - } + let cm = make_map_with_root(&ps.claims, self.bs.as_ref()) + .map_err(|e| Error::State(e.to_string()))?; + let claim: power::Claim = cm + .get(&addr.to_bytes()) + .map_err(|e| Error::State(e.to_string()))? + .ok_or_else(|| { + Error::State("Failed to retrieve claimed power from actor state".to_owned()) + })?; + Ok(( + claim, + power::Claim { + raw_byte_power: ps.total_raw_byte_power, + quality_adj_power: ps.total_quality_adj_power, + }, + )) } pub fn get_subscriber(&self) -> Option> { diff --git a/blockchain/state_manager/src/utils.rs b/blockchain/state_manager/src/utils.rs index 8e17563c007e..ee4f038e0094 100644 --- a/blockchain/state_manager/src/utils.rs +++ b/blockchain/state_manager/src/utils.rs @@ -12,11 +12,12 @@ use address::{Address, Protocol}; use bitfield::BitField; use blockstore::BlockStore; use cid::Cid; +use encoding::serde_bytes::ByteBuf; use fil_types::{RegisteredSealProof, SectorInfo, SectorNumber, SectorSize, HAMT_BIT_WIDTH}; use filecoin_proofs_api::{post::generate_winning_post_sector_challenge, ProverId}; use forest_blocks::Tipset; use ipld_amt::Amt; -use ipld_hamt::Hamt; +use ipld_hamt::{BytesKey, Hamt}; use std::convert::TryInto; pub fn get_sectors_for_winning_post( @@ -147,7 +148,7 @@ where let amt = Amt::load(ssc, block_store).map_err(|err| Error::Other(err.to_string()))?; let mut sset: Vec = Vec::new(); - let for_each = |i: u64, sector_chain: &miner::SectorOnChainInfo| -> Result<(), String> { + let for_each = |i: u64, sector_chain: &miner::SectorOnChainInfo| { if let Some(ref mut s) = filter { let i = i .try_into() @@ -321,11 +322,11 @@ where let map = Hamt::<_>::load_with_bit_width(&power_actor_state.claims, block_store, HAMT_BIT_WIDTH) .map_err(|err| Error::Other(err.to_string()))?; - map.for_each(|_, k: String| -> Result<(), String> { - let address = Address::from_bytes(k.as_bytes()).map_err(|e| e.to_string())?; + map.for_each(|_: &BytesKey, k: ByteBuf| { + let address = Address::from_bytes(k.as_ref())?; miners.push(address); Ok(()) }) - .map_err(Error::Other)?; + .map_err(|e| Error::Other(e.to_string()))?; Ok(miners) } diff --git a/encoding/src/bytes.rs b/encoding/src/bytes.rs index 946a3377fb83..3852a2600b5b 100644 --- a/encoding/src/bytes.rs +++ b/encoding/src/bytes.rs @@ -10,7 +10,7 @@ use serde_bytes::ByteBuf; pub struct BytesSer<'a>(#[serde(with = "serde_bytes")] pub &'a [u8]); /// Wrapper for deserializing dynamic sized Bytes. -#[derive(Deserialize)] +#[derive(Deserialize, Serialize)] #[serde(transparent)] pub struct BytesDe(#[serde(with = "serde_bytes")] pub Vec); diff --git a/ipld/amt/src/amt.rs b/ipld/amt/src/amt.rs index b0d51f501778..bd5e1250e369 100644 --- a/ipld/amt/src/amt.rs +++ b/ipld/amt/src/amt.rs @@ -5,6 +5,7 @@ use crate::{node::Link, nodes_for_height, BitMap, Error, Node, Root, MAX_INDEX, use cid::{multihash::Blake2b256, Cid}; use encoding::{de::DeserializeOwned, ser::Serialize}; use ipld_blockstore::BlockStore; +use std::error::Error as StdError; /// Array Mapped Trie allows for the insertion and persistence of data, serializable to a CID /// @@ -216,10 +217,10 @@ where /// assert_eq!(&values, &[(1, "One".to_owned()), (4, "Four".to_owned())]); /// ``` #[inline] - pub fn for_each(&self, mut f: F) -> Result<(), String> + pub fn for_each(&self, mut f: F) -> Result<(), Box> where V: DeserializeOwned, - F: FnMut(u64, &V) -> Result<(), String>, + F: FnMut(u64, &V) -> Result<(), Box>, { self.root .node diff --git a/ipld/amt/src/node.rs b/ipld/amt/src/node.rs index becffc6b8fa0..092933b57338 100644 --- a/ipld/amt/src/node.rs +++ b/ipld/amt/src/node.rs @@ -8,6 +8,7 @@ use encoding::{ ser::{self, Serialize}, }; use ipld_blockstore::BlockStore; +use std::error::Error as StdError; /// This represents a link to another Node #[derive(PartialEq, Eq, Clone, Debug)] @@ -323,9 +324,9 @@ where height: u32, offset: u64, f: &mut F, - ) -> Result<(), String> + ) -> Result<(), Box> where - F: FnMut(u64, &V) -> Result<(), String>, + F: FnMut(u64, &V) -> Result<(), Box>, S: BlockStore, { match self { diff --git a/ipld/hamt/src/hamt.rs b/ipld/hamt/src/hamt.rs index 14627aec2855..995f5fbc8ccd 100644 --- a/ipld/hamt/src/hamt.rs +++ b/ipld/hamt/src/hamt.rs @@ -9,6 +9,7 @@ use forest_ipld::{from_ipld, to_ipld, Ipld}; use ipld_blockstore::BlockStore; use serde::{de::DeserializeOwned, Serialize, Serializer}; use std::borrow::Borrow; +use std::error::Error as StdError; use std::marker::PhantomData; /// Implementation of the HAMT data structure for IPLD. @@ -259,10 +260,10 @@ where /// assert_eq!(total, 3); /// ``` #[inline] - pub fn for_each(&self, mut f: F) -> Result<(), String> + pub fn for_each(&self, mut f: F) -> Result<(), Box> where V: DeserializeOwned, - F: FnMut(&K, V) -> Result<(), String>, + F: FnMut(&K, V) -> Result<(), Box>, { self.root.for_each(self.store, &mut f) } diff --git a/ipld/hamt/src/node.rs b/ipld/hamt/src/node.rs index c3bdd65df6fd..60a2676babc4 100644 --- a/ipld/hamt/src/node.rs +++ b/ipld/hamt/src/node.rs @@ -11,6 +11,7 @@ use ipld_blockstore::BlockStore; use serde::de::DeserializeOwned; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::borrow::Borrow; +use std::error::Error as StdError; use std::fmt::Debug; use std::marker::PhantomData; @@ -117,10 +118,10 @@ where self.pointers.is_empty() } - pub(crate) fn for_each(&self, store: &S, f: &mut F) -> Result<(), String> + pub(crate) fn for_each(&self, store: &S, f: &mut F) -> Result<(), Box> where V: DeserializeOwned, - F: FnMut(&K, V) -> Result<(), String>, + F: FnMut(&K, V) -> Result<(), Box>, S: BlockStore, { for p in &self.pointers { @@ -128,7 +129,7 @@ where Pointer::Link(cid) => { match store.get::>(cid).map_err(|e| e.to_string())? { Some(node) => node.for_each(store, f)?, - None => return Err(format!("Node with cid {} not found", cid)), + None => return Err(format!("Node with cid {} not found", cid).into()), } } Pointer::Cache(n) => n.for_each(store, f)?, diff --git a/node/rpc/src/state_api.rs b/node/rpc/src/state_api.rs index 38c46925b6da..0f39dc93b818 100644 --- a/node/rpc/src/state_api.rs +++ b/node/rpc/src/state_api.rs @@ -223,20 +223,17 @@ pub(crate) async fn state_all_miner_faults< .load_actor_state(&m, &tipset.parent_state()) .map_err(|e| e.to_string())?; let block_store = state_manager.get_block_store_ref(); - miner_actor_state.for_each_fault_epoch( - block_store, - |fault_start: i64, _| -> Result<(), String> { - if fault_start >= cut_off { - all_faults.push(Fault { - miner: *m, - fault: fault_start, - }) - } - Ok(()) - }, - ) + miner_actor_state.for_each_fault_epoch(block_store, |fault_start: i64, _| { + if fault_start >= cut_off { + all_faults.push(Fault { + miner: *m, + fault: fault_start, + }) + } + Ok(()) + }) }) - .collect::, String>>()?; + .collect::, _>>()?; Ok(all_faults) } /// returns a bitfield indicating the recovering sectors of the given miner diff --git a/vm/actor/Cargo.toml b/vm/actor/Cargo.toml index ea2aa58a8952..c3ea3ceb4df1 100644 --- a/vm/actor/Cargo.toml +++ b/vm/actor/Cargo.toml @@ -31,6 +31,7 @@ byteorder = "1.3.4" ahash = "0.4" base64 = "0.12.1" log = "0.4.8" +indexmap = { version = "1.3.2", features = ["serde-1"] } [dev-dependencies] db = { path = "../../node/db" } diff --git a/vm/actor/src/builtin/market/mod.rs b/vm/actor/src/builtin/market/mod.rs index 470ee67c2665..e5421bb6603f 100644 --- a/vm/actor/src/builtin/market/mod.rs +++ b/vm/actor/src/builtin/market/mod.rs @@ -869,7 +869,7 @@ where .ok_or_else(|| actor_error!(ErrNotFound; "no such deal {}", deal_id))?; validate_deal_can_activate(&proposal, miner_addr, sector_expiry, curr_epoch) - .map_err(|e| e.wrap(&format!("cannot activate deal {}: ", deal_id)))?; + .map_err(|e| e.wrap(&format!("cannot activate deal {}", deal_id)))?; let deal_space_time = deal_weight(&proposal); if proposal.verified_deal { diff --git a/vm/actor/src/builtin/market/state.rs b/vm/actor/src/builtin/market/state.rs index 84ad1d131f9b..baae55e47da9 100644 --- a/vm/actor/src/builtin/market/state.rs +++ b/vm/actor/src/builtin/market/state.rs @@ -505,10 +505,10 @@ where proposal: &DealProposal, ) -> Result<(), ActorError> { self.maybe_lock_balance(&proposal.client, &proposal.client_balance_requirement()) - .map_err(|e| e.wrap("failed to lock client funds: "))?; + .map_err(|e| e.wrap("failed to lock client funds"))?; self.maybe_lock_balance(&proposal.provider, &proposal.provider_collateral) - .map_err(|e| e.wrap("failed to lock provider funds: "))?; + .map_err(|e| e.wrap("failed to lock provider funds"))?; if let Some(v) = self.total_client_locked_colateral.as_mut() { *v += &proposal.client_collateral; diff --git a/vm/actor/src/builtin/miner/mod.rs b/vm/actor/src/builtin/miner/mod.rs index 0b5266be27b8..acbbc78dee88 100644 --- a/vm/actor/src/builtin/miner/mod.rs +++ b/vm/actor/src/builtin/miner/mod.rs @@ -1742,7 +1742,7 @@ fn pop_sector_expirations( st: &mut State, store: &BS, epoch: ChainEpoch, -) -> Result +) -> Result> where BS: BlockStore, { @@ -1751,7 +1751,7 @@ where st.for_each_sector_expiration(store, |expiry: ChainEpoch, sectors: &BitField| { if expiry > epoch { - return Err("done".to_string()); + return Err("done".into()); } expired_epochs.push(expiry); expired_sectors.push(sectors.clone()); @@ -1771,7 +1771,7 @@ fn pop_expired_faults( st: &mut State, store: &BS, latest_termination: ChainEpoch, -) -> Result<(BitField, BitField), String> +) -> Result<(BitField, BitField), Box> where BS: BlockStore, { @@ -1949,7 +1949,7 @@ fn remove_terminated_sectors( store: &BS, deadlines: &mut Deadlines, sectors: &BitField, -) -> Result<(), String> +) -> Result<(), Box> where BS: BlockStore, { @@ -2458,7 +2458,7 @@ fn unlock_penalty( current_epoch: ChainEpoch, sectors: &[SectorOnChainInfo], f: &impl Fn(&SectorOnChainInfo) -> TokenAmount, -) -> Result +) -> Result> where BS: BlockStore, { diff --git a/vm/actor/src/builtin/miner/state.rs b/vm/actor/src/builtin/miner/state.rs index 05098fa3e93b..3b7edcd52e2a 100644 --- a/vm/actor/src/builtin/miner/state.rs +++ b/vm/actor/src/builtin/miner/state.rs @@ -19,6 +19,7 @@ use num_bigint::bigint_ser::{self, BigIntDe}; use num_bigint::BigInt; use num_traits::ToPrimitive; use num_traits::Zero; +use std::error::Error as StdError; use vm::TokenAmount; // Balance of Miner Actor should be greater than or equal to @@ -238,9 +239,13 @@ impl State { self.sectors = sectors.flush()?; Ok(()) } - pub fn for_each_sector(&self, store: &BS, mut f: F) -> Result<(), String> + pub fn for_each_sector( + &self, + store: &BS, + mut f: F, + ) -> Result<(), Box> where - F: FnMut(&SectorOnChainInfo) -> Result<(), String>, + F: FnMut(&SectorOnChainInfo) -> Result<(), Box>, { let sectors = Amt::::load(&self.sectors, store)?; sectors.for_each(|_, v| f(&v)) @@ -282,9 +287,9 @@ impl State { &self, store: &BS, mut f: F, - ) -> Result<(), String> + ) -> Result<(), Box> where - F: FnMut(ChainEpoch, &BitField) -> Result<(), String>, + F: FnMut(ChainEpoch, &BitField) -> Result<(), Box>, { let sector_arr = Amt::::load(&self.sector_expirations, store)?; sector_arr.for_each(|i, v| f(i as i64, v)) @@ -391,9 +396,9 @@ impl State { &mut self, store: &BS, sector_nos: &BitField, - ) -> Result<(), String> { + ) -> Result<(), Box> { if sector_nos.is_empty() { - return Err(format!("sectors are empty: {:?}", sector_nos)); + return Err(format!("sectors are empty: {:?}", sector_nos).into()); } self.faults -= sector_nos; @@ -427,9 +432,9 @@ impl State { &self, store: &BS, mut f: F, - ) -> Result<(), String> + ) -> Result<(), Box> where - F: FnMut(ChainEpoch, &BitField) -> Result<(), String>, + F: FnMut(ChainEpoch, &BitField) -> Result<(), Box>, { let sector_arr = Amt::::load(&self.fault_epochs, store)?; sector_arr.for_each(|i, v| f(i as i64, v)) @@ -651,7 +656,7 @@ impl State { store: &BS, current_epoch: ChainEpoch, target: TokenAmount, - ) -> Result { + ) -> Result> { let mut vesting_funds: Amt = Amt::load(&self.vesting_funds, store)?; let mut amount_unlocked = TokenAmount::default(); @@ -675,7 +680,7 @@ impl State { } } else { // stop iterating - return Err("finished".to_string()); + return Err("finished".into()); } Ok(()) })?; @@ -698,7 +703,7 @@ impl State { &mut self, store: &BS, current_epoch: ChainEpoch, - ) -> Result { + ) -> Result> { let mut vesting_funds: Amt = Amt::load(&self.vesting_funds, store)?; let mut amount_unlocked = TokenAmount::default(); @@ -711,7 +716,7 @@ impl State { to_del.push(k); } else { // stop iterating - return Err("finished".to_string()); + return Err("finished".into()); } Ok(()) })?; @@ -729,7 +734,7 @@ impl State { &self, store: &BS, current_epoch: ChainEpoch, - ) -> Result { + ) -> Result> { let vesting_funds: Amt = Amt::load(&self.vesting_funds, store)?; let mut amount_unlocked = TokenAmount::default(); @@ -739,7 +744,7 @@ impl State { amount_unlocked += locked_entry; } else { // stop iterating - return Err("finished".to_string()); + return Err("finished".into()); } Ok(()) })?; diff --git a/vm/actor/src/builtin/paych/mod.rs b/vm/actor/src/builtin/paych/mod.rs index 1b41b40c9024..1ceb71ff1915 100644 --- a/vm/actor/src/builtin/paych/mod.rs +++ b/vm/actor/src/builtin/paych/mod.rs @@ -284,7 +284,7 @@ impl Actor { // send ToSend to `to` rt.send(st.to, METHOD_SEND, Serialized::default(), st.to_send) - .map_err(|e| e.wrap("Failed to send funds to `to` address: "))?; + .map_err(|e| e.wrap("Failed to send funds to `to` address"))?; // the remaining balance will be returned to "From" upon deletion. rt.delete_actor(&st.from)?; diff --git a/vm/actor/src/builtin/power/mod.rs b/vm/actor/src/builtin/power/mod.rs index 24b1e1401800..cf03ce1c1c56 100644 --- a/vm/actor/src/builtin/power/mod.rs +++ b/vm/actor/src/builtin/power/mod.rs @@ -6,23 +6,33 @@ mod state; mod types; pub use self::policy::*; -pub use self::state::{Claim, CronEvent, State}; +pub use self::state::*; pub use self::types::*; use crate::reward::Method as RewardMethod; use crate::{ - check_empty_params, init, make_map, request_miner_control_addrs, Multimap, SetMultimap, - CALLER_TYPES_SIGNABLE, CRON_ACTOR_ADDR, INIT_ACTOR_ADDR, MINER_ACTOR_CODE_ID, - REWARD_ACTOR_ADDR, + check_empty_params, init, make_map, make_map_with_root, miner, Multimap, CALLER_TYPES_SIGNABLE, + CRON_ACTOR_ADDR, INIT_ACTOR_ADDR, MINER_ACTOR_CODE_ID, REWARD_ACTOR_ADDR, SYSTEM_ACTOR_ADDR, }; use address::Address; -use fil_types::{SealVerifyInfo, StoragePower}; +use ahash::AHashSet; +use fil_types::SealVerifyInfo; +use indexmap::IndexMap; use ipld_blockstore::BlockStore; -use num_bigint::bigint_ser::BigIntDe; -use num_bigint::BigInt; +use num_bigint::bigint_ser::{BigIntDe, BigIntSer}; +use num_bigint::Sign; use num_derive::FromPrimitive; -use num_traits::{FromPrimitive, Zero}; +use num_traits::FromPrimitive; use runtime::{ActorCode, Runtime}; -use vm::{ActorError, ExitCode, MethodNum, Serialized, TokenAmount, METHOD_CONSTRUCTOR}; +use std::ops::Neg; +use vm::{ + actor_error, ActorError, ExitCode, MethodNum, Serialized, TokenAmount, METHOD_CONSTRUCTOR, +}; + +// * Updated to specs-actors commit: b8a3a6ff7b15ac01f0534c47059e1c81652a61f0 (v0.9.1) + +/// GasOnSubmitVerifySeal is amount of gas charged for SubmitPoRepForBulkVerify +/// This number is empirically determined +const GAS_ON_SUBMIT_VERIFY_SEAL: i64 = 34721049; /// Storage power actor methods available #[derive(FromPrimitive)] @@ -44,30 +54,27 @@ pub enum Method { pub struct Actor; impl Actor { /// Constructor for StoragePower actor - pub fn constructor(rt: &mut RT) -> Result<(), ActorError> + fn constructor(rt: &mut RT) -> Result<(), ActorError> where BS: BlockStore, RT: Runtime, { - let empty_map = make_map(rt.store()).flush().map_err(|err| { - rt.abort( - ExitCode::ErrIllegalState, - format!("Failed to create storage power state: {}", err), - ) - })?; + rt.validate_immediate_caller_is(std::iter::once(&*SYSTEM_ACTOR_ADDR))?; - let empty_m_set = SetMultimap::new(rt.store()).root().map_err(|e| { - ActorError::new( - ExitCode::ErrIllegalState, - format!("Failed to get empty multimap cid: {}", e), - ) - })?; + let empty_map = make_map(rt.store()).flush().map_err( + |err| actor_error!(ErrIllegalState; "Failed to create storage power state: {}", err), + )?; - let st = State::new(empty_map, empty_m_set); + let empty_mmap = Multimap::new(rt.store()).root().map_err( + |e| actor_error!(ErrIllegalState; "Failed to get empty multimap cid: {}", e), + )?; + + let st = State::new(empty_map, empty_mmap); rt.create(&st)?; Ok(()) } - pub fn create_miner( + + fn create_miner( rt: &mut RT, params: &Serialized, ) -> Result @@ -77,8 +84,11 @@ impl Actor { { rt.validate_immediate_caller_type(CALLER_TYPES_SIGNABLE.iter())?; let value = rt.message().value_received().clone(); - // TODO update this send, is now outdated - let addresses: init::ExecReturn = rt + + let init::ExecReturn { + id_address, + robust_address, + } = rt .send( *INIT_ACTOR_ADDR, init::Method::Exec as u64, @@ -88,438 +98,481 @@ impl Actor { .deserialize()?; rt.transaction::, _>(|st, rt| { - st.set_claim(rt.store(), &addresses.id_address, Claim::default()) - .map_err(|e| { - ActorError::new( - ExitCode::ErrIllegalState, - format!( - "failed to put power in claimed table while creating miner: {}", - e - ), - ) - })?; + let mut claims = make_map_with_root(&st.claims, rt.store()) + .map_err(|e| actor_error!(ErrIllegalState; "failed to load claims: {}", e))?; + set_claim(&mut claims, &id_address, Claim::default()).map_err(|e| { + actor_error!(ErrIllegalState; + "failed to put power in claimed table while creating miner: {}", e) + })?; st.miner_count += 1; + + st.claims = claims + .flush() + .map_err(|e| actor_error!(ErrIllegalState; "failed to flush claims: {}", e))?; Ok(()) })??; Ok(CreateMinerReturn { - id_address: addresses.id_address, - robust_address: addresses.robust_address, + id_address, + robust_address, }) } - pub fn delete_miner(rt: &mut RT, params: DeleteMinerParams) -> Result<(), ActorError> + + /// Adds or removes claimed power for the calling actor. + /// May only be invoked by a miner actor. + fn update_claimed_power( + rt: &mut RT, + params: UpdateClaimedPowerParams, + ) -> Result<(), ActorError> where BS: BlockStore, RT: Runtime, { - // TODO this function does not exist anymore, make sure it is removed/replaced later - let nominal = rt.resolve_address(¶ms.miner)?.unwrap(); - - let st: State = rt.state()?; + rt.validate_immediate_caller_type(std::iter::once(&*MINER_ACTOR_CODE_ID))?; + let miner_addr = *rt.message().caller(); - let (owner_addr, worker_addr) = request_miner_control_addrs(rt, nominal)?; - rt.validate_immediate_caller_is(&[owner_addr, worker_addr])?; + rt.transaction(|st: &mut State, rt| { + let mut claims = make_map_with_root(&st.claims, rt.store()) + .map_err(|e| actor_error!(ErrIllegalState; "failed to load claims: {}", e))?; - let claim = st - .get_claim(rt.store(), &nominal) + st.add_to_claim( + &mut claims, + &miner_addr, + ¶ms.raw_byte_delta, + ¶ms.quality_adjusted_delta, + ) .map_err(|e| { - ActorError::new( - ExitCode::ErrIllegalState, - format!("failed to load miner claim for deletion: {}", e), - ) - })? - .ok_or_else(|| { - ActorError::new( + ActorError::downcast( + e, ExitCode::ErrIllegalState, - format!("failed to find miner {} claim for deletion", nominal), + &format!( + "failed to update power raw {}, qa {}", + params.raw_byte_delta, params.quality_adjusted_delta, + ), ) })?; - rt.transaction(|st: &mut State, rt| { - if claim.raw_byte_power > Zero::zero() { - return Err(rt.abort( - ExitCode::ErrIllegalState, - format!( - "deletion requested for miner {} with power {}", - nominal, claim.raw_byte_power - ), - )); - } - - if claim.quality_adj_power > Zero::zero() { - return Err(rt.abort( - ExitCode::ErrIllegalState, - format!( - "deletion requested for miner {} with quality adjusted power {}", - nominal, claim.quality_adj_power - ), - )); - } - - st.total_quality_adj_power -= claim.quality_adj_power; - st.total_raw_byte_power -= claim.raw_byte_power; + st.claims = claims + .flush() + .map_err(|e| actor_error!(ErrIllegalState; "failed to flush claims: {}", e))?; Ok(()) - })??; - - Self::delete_miner_actor(rt, &nominal)?; - Ok(()) - } - pub fn on_sector_prove_commit( - rt: &mut RT, - params: OnSectorProveCommitParams, - ) -> Result - where - BS: BlockStore, - RT: Runtime, - { - rt.validate_immediate_caller_type(std::iter::once(&*MINER_ACTOR_CODE_ID))?; - let initial_pledge = compute_initial_pledge(rt, ¶ms.weight)?; - - rt.transaction(|st: &mut State, rt| { - let rb_power = BigInt::from(params.weight.sector_size as u64); - let qa_power = qa_power_for_weight(¶ms.weight); - st.add_to_claim(rt.store(), rt.message().caller(), &rb_power, &qa_power) - .map_err(|e| { - ActorError::new( - ExitCode::ErrIllegalState, - format!("Failed to add power for sector: {}", e), - ) - })?; - Ok(initial_pledge) })? } - pub fn on_sector_terminate( + + fn enroll_cron_event( rt: &mut RT, - params: OnSectorTerminateParams, + params: EnrollCronEventParams, ) -> Result<(), ActorError> where BS: BlockStore, RT: Runtime, { rt.validate_immediate_caller_type(std::iter::once(&*MINER_ACTOR_CODE_ID))?; + let miner_event = CronEvent { + miner_addr: *rt.message().caller(), + callback_payload: params.payload.clone(), + }; - rt.transaction(|st: &mut State, rt| { - let (rb_power, qa_power) = powers_for_weights(params.weights); - st.add_to_claim(rt.store(), rt.message().caller(), &rb_power, &qa_power) - .map_err(|e| { - ActorError::new( - ExitCode::ErrIllegalState, - format!("failed to deduct claimed power for sector: {}", e), - ) - }) - })??; + // Ensure it is not possible to enter a large negative number which would cause + // problems in cron processing. + if params.event_epoch < 0 { + return Err(actor_error!(ErrIllegalArgument; + "cron event epoch {} cannot be less than zero", params.event_epoch)); + } + + rt.transaction::, _>(|st, rt| { + let mut events = Multimap::from_root(rt.store(), &st.cron_event_queue) + .map_err(|e| actor_error!(ErrIllegalState; "failed to load cron events {}", e))?; + st.append_cron_event(&mut events, params.event_epoch, miner_event) + .map_err(|e| actor_error!(ErrIllegalState; "failed to enroll cron event: {}", e))?; + + st.cron_event_queue = events + .root() + .map_err(|e| actor_error!(ErrIllegalState; "failed to flush cron events: {}", e))?; + Ok(()) + })??; Ok(()) } - fn _on_fault_begin(rt: &mut RT, params: OnFaultBeginParams) -> Result<(), ActorError> + fn on_epoch_tick_end(rt: &mut RT) -> Result<(), ActorError> where BS: BlockStore, RT: Runtime, { - rt.validate_immediate_caller_type(std::iter::once(&*MINER_ACTOR_CODE_ID))?; + rt.validate_immediate_caller_is(std::iter::once(&*CRON_ACTOR_ADDR))?; - rt.transaction(|st: &mut State, rt| { - let (rb_power, qa_power) = powers_for_weights(params.weights); - st.add_to_claim(rt.store(), rt.message().caller(), &rb_power, &qa_power) - .map_err(|e| { - ActorError::new( - ExitCode::ErrIllegalState, - format!("failed to deduct claimed power for sector: {}", e), - ) - })?; - Ok(()) - })? + Self::process_deferred_cron_events(rt)?; + Self::process_batch_proof_verifies(rt)?; + + let this_epoch_raw_byte_power = rt.transaction(|st: &mut State, rt| { + let (raw_byte_power, qa_power) = st.current_total_power(); + st.this_epoch_pledge_collateral = st.total_pledge_collateral.clone(); + st.this_epoch_quality_adj_power = qa_power; + st.this_epoch_raw_byte_power = raw_byte_power; + let delta = rt.curr_epoch() - st.last_processed_cron_epoch; + st.update_smoothed_estimate(delta); + + st.last_processed_cron_epoch = rt.curr_epoch(); + Serialized::serialize(&BigIntSer(&st.this_epoch_raw_byte_power)) + })?; + + // Update network KPA in reward actor + rt.send( + *REWARD_ACTOR_ADDR, + RewardMethod::UpdateNetworkKPI as MethodNum, + this_epoch_raw_byte_power?, + TokenAmount::from(0), + ) + .map_err(|e| e.wrap("failed to update network KPI with reward actor"))?; + + Ok(()) } - fn _on_fault_end(rt: &mut RT, params: OnFaultEndParams) -> Result<(), ActorError> + fn update_pledge_total(rt: &mut RT, pledge_delta: TokenAmount) -> Result<(), ActorError> where BS: BlockStore, RT: Runtime, { rt.validate_immediate_caller_type(std::iter::once(&*MINER_ACTOR_CODE_ID))?; - - rt.transaction(|st: &mut State, rt| { - let (rb_power, qa_power) = powers_for_weights(params.weights); - st.add_to_claim(rt.store(), rt.message().caller(), &rb_power, &qa_power) - .map_err(|e| { - ActorError::new( - ExitCode::ErrIllegalState, - format!("failed to deduct claimed power for sector: {}", e), - ) - })?; - Ok(()) - })? + rt.transaction(|st: &mut State, _| { + st.add_pledge_total(pledge_delta); + }) } - /// Returns new initial pledge, now committed in place of the old. - pub fn on_sector_modify_weight_desc( - rt: &mut RT, - params: OnSectorModifyWeightDescParams, - ) -> Result + fn on_consensus_fault(rt: &mut RT, pledge_delta: TokenAmount) -> Result<(), ActorError> where BS: BlockStore, RT: Runtime, { rt.validate_immediate_caller_type(std::iter::once(&*MINER_ACTOR_CODE_ID))?; - let new_initial_pledge = compute_initial_pledge(rt, ¶ms.new_weight)?; - let prev_weight = params.prev_weight; - let new_weight = params.new_weight; + let miner_addr = *rt.message().caller(); rt.transaction(|st: &mut State, rt| { - let prev_power = qa_power_for_weight(&prev_weight); + let mut claims = make_map_with_root(&st.claims, rt.store()) + .map_err(|e| actor_error!(ErrIllegalState; "failed to load claims: {}", e))?; - st.add_to_claim( - rt.store(), - rt.message().caller(), - &BigInt::from(prev_weight.sector_size as u64), - &prev_power, - ) - .map_err(|e| { - ActorError::new( - ExitCode::ErrIllegalState, - format!("failed to deduct claimed power for sector: {}", e), - ) - })?; + let claim = get_claim(&claims, &miner_addr) + .map_err(|e| { + actor_error!(ErrIllegalState; "failed to read claimed power for fault: {}", e) + })? + .ok_or_else(|| { + actor_error!(ErrNotFound; + "miner {} not registered (already slashed?)", miner_addr) + })?; + assert_ne!(claim.raw_byte_power.sign(), Sign::Minus); + assert_ne!(claim.quality_adj_power.sign(), Sign::Minus); - let new_power = qa_power_for_weight(&new_weight); st.add_to_claim( - rt.store(), - rt.message().caller(), - &BigInt::from(new_weight.sector_size as u64), - &new_power, + &mut claims, + &miner_addr, + &claim.raw_byte_power.neg(), + &claim.quality_adj_power.neg(), ) .map_err(|e| { - ActorError::new( + ActorError::downcast( + e, ExitCode::ErrIllegalState, - format!("failed to add claimed power for sector: {}", e), + &format!("could not add to claim for {}", miner_addr), ) })?; - Ok(new_initial_pledge) - })? + + st.add_pledge_total(pledge_delta.neg()); + + // delete miner actor claims + let deleted = claims.delete(&miner_addr.to_bytes()).map_err( + |e| actor_error!(ErrIllegalState; "failed to remove miner {}: {}",miner_addr, e), + )?; + if !deleted { + return Err(actor_error!(ErrIllegalState; + "failed to remove miner {}: does not exist", miner_addr)); + } + + st.miner_count -= 1; + + st.claims = claims + .flush() + .map_err(|e| actor_error!(ErrIllegalState; "failed to flush claims: {}", e))?; + Ok(()) + })??; + + Ok(()) } - pub fn enroll_cron_event( + fn submit_porep_for_bulk_verify( rt: &mut RT, - params: EnrollCronEventParams, + seal_info: SealVerifyInfo, ) -> Result<(), ActorError> where BS: BlockStore, RT: Runtime, { rt.validate_immediate_caller_type(std::iter::once(&*MINER_ACTOR_CODE_ID))?; - let miner_event = CronEvent { - miner_addr: *rt.message().caller(), - callback_payload: params.payload.clone(), - }; rt.transaction(|st: &mut State, rt| { - st.append_cron_event(rt.store(), params.event_epoch, miner_event) + let mut mmap = if let Some(ref batch) = st.proof_validation_batch { + Multimap::from_root(rt.store(), batch).map_err( + |e| actor_error!(ErrIllegalState; "failed to load proof batching set: {}", e), + )? + } else { + Multimap::new(rt.store()) + }; + let miner_addr = rt.message().caller(); + let arr = mmap + .get::(&miner_addr.to_bytes()) .map_err(|e| { - ActorError::new( - ExitCode::ErrIllegalState, - format!("failed to enroll cron event: {}", e), - ) - }) - })? - } - - pub fn on_epoch_tick_end(rt: &mut RT) -> Result<(), ActorError> - where - BS: BlockStore, - RT: Runtime, - { - rt.validate_immediate_caller_is(std::iter::once(&*CRON_ACTOR_ADDR))?; - - let rt_epoch = rt.curr_epoch(); - let cron_events = rt - .transaction::<_, Result<_, String>, _>(|st: &mut State, rt| { - let mut events = Vec::new(); - for i in st.last_epoch_tick..=rt_epoch { - // Load epoch cron events - let epoch_events = st.load_cron_events(rt.store(), i)?; - - // Add all events to vector - events.extend_from_slice(&epoch_events); - - // Clear loaded events - if !epoch_events.is_empty() { - st.clear_cron_events(rt.store(), i)?; - } + actor_error!(ErrIllegalState; + "failed to get seal verify infos at addr {}: {}", miner_addr, e) + })?; + if let Some(arr) = arr { + if arr.count() >= MAX_MINER_PROVE_COMMITS_PER_EPOCH { + return Err(actor_error!(ErrTooManyProveCommits; + "miner {} attempting to prove commit over {} sectors in epoch", + miner_addr, MAX_MINER_PROVE_COMMITS_PER_EPOCH)); } - st.last_epoch_tick = rt_epoch; - Ok(events) - })? - .map_err(|e| { - ActorError::new( - ExitCode::ErrIllegalState, - format!("Failed to clear cron events: {}", e), - ) - })?; + } - for event in cron_events { - // TODO switch 12 to OnDeferredCronEvent on miner actor impl - rt.send( - event.miner_addr, - 12, - event.callback_payload, - TokenAmount::from(0u8), + mmap.add(miner_addr.to_bytes().into(), seal_info).map_err( + |e| actor_error!(ErrIllegalState; "failed to insert proof into set: {}", e), )?; - } + + let mmrc = mmap.root().map_err( + |e| actor_error!(ErrIllegalState; "failed to flush proofs batch map: {}", e), + )?; + + rt.charge_gas("OnSubmitVerifySeal".to_string(), GAS_ON_SUBMIT_VERIFY_SEAL)?; + st.proof_validation_batch = Some(mmrc); + Ok(()) + })??; Ok(()) } - // TODO update this function from using unsigned delta (can be negative) - fn update_pledge_total(rt: &mut RT, pledge_delta: TokenAmount) -> Result<(), ActorError> + /// Returns the total power and pledge recorded by the power actor. + /// The returned values are frozen during the cron tick before this epoch + /// so that this method returns consistent values while processing all messages + /// of an epoch. + fn current_total_power(rt: &mut RT) -> Result where BS: BlockStore, RT: Runtime, { - rt.validate_immediate_caller_type(std::iter::once(&*MINER_ACTOR_CODE_ID))?; - rt.transaction(|st: &mut State, _| { - st.add_pledge_total(pledge_delta); - Ok(()) - })? + rt.validate_immediate_caller_accept_any()?; + let st: State = rt.state()?; + + Ok(CurrentTotalPowerReturn { + raw_byte_power: st.this_epoch_raw_byte_power, + quality_adj_power: st.this_epoch_quality_adj_power, + pledge_collateral: st.this_epoch_pledge_collateral, + quality_adj_power_smoothed: st.this_epoch_qa_power_smoothed, + }) } - fn on_consensus_fault(rt: &mut RT, pledge_amount: TokenAmount) -> Result<(), ActorError> + fn process_batch_proof_verifies(rt: &mut RT) -> Result<(), ActorError> where BS: BlockStore, RT: Runtime, { - rt.validate_immediate_caller_type(std::iter::once(&*MINER_ACTOR_CODE_ID))?; - let miner_addr = *rt.message().caller(); - let st: State = rt.state()?; + // Index map is needed here to preserve insertion order, miners must be iterated based + // on order iterated through multimap. + let mut verifies = IndexMap::new(); + rt.transaction::, _>(|st, rt| { + if st.proof_validation_batch.is_none() { + return Ok(()); + } + let mmap = Multimap::from_root( + rt.store(), + st.proof_validation_batch.as_ref().unwrap(), + ) + .map_err( + |e| actor_error!(ErrIllegalState; "failed to load proofs validation batch: {}", e), + )?; - let claim = st - .get_claim(rt.store(), &miner_addr) + mmap.for_all::<_, SealVerifyInfo>(|k, arr| { + let addr = Address::from_bytes(&k.0).map_err( + |e| actor_error!(ErrIllegalState; "failed to parse address key: {}", e), + )?; + + let mut infos = Vec::new(); + arr.for_each(|_, svi| { + infos.push(svi.clone()); + Ok(()) + }) + .map_err(|e| { + ActorError::downcast( + e, + ExitCode::ErrIllegalState, + &format!( + "failed to iterate over proof verify array for miner {}", + addr + ), + ) + })?; + + verifies.insert(addr, infos); + Ok(()) + }) .map_err(|e| { - ActorError::new( + ActorError::downcast( + e, ExitCode::ErrIllegalState, - format!("failed to read claimed power for fault: {}", e), - ) - })? - .ok_or_else(|| { - ActorError::new( - ExitCode::ErrIllegalArgument, - format!("miner {} not registered (already slashed?)", miner_addr), + "failed to iterate proof batch", ) })?; - rt.transaction(|st: &mut State, _| { - st.total_quality_adj_power -= claim.quality_adj_power; - st.total_raw_byte_power -= claim.raw_byte_power; - - st.add_pledge_total(pledge_amount); - })?; - - Self::delete_miner_actor(rt, &miner_addr)?; - - Ok(()) - } - - fn delete_miner_actor(rt: &mut RT, miner: &Address) -> Result<(), ActorError> - where - BS: BlockStore, - RT: Runtime, - { - rt.transaction::<_, Result<_, String>, _>(|st: &mut State, rt| { - st.delete_claim(rt.store(), miner)?; + st.proof_validation_batch = None; + Ok(()) + })??; - st.miner_count -= 1; + // TODO update this to not need to create vector to verify these things (ref batch_v_s) + let verif_arr: Vec<(Address, &Vec)> = + verifies.iter().map(|(a, v)| (*a, v)).collect(); + let res = rt + .syscalls() + .batch_verify_seals(verif_arr.as_slice()) + .map_err(|e| { + ActorError::downcast(e, ExitCode::ErrIllegalState, "failed to batch verify") + })?; - Ok(()) - })? - .map_err(|e| ActorError::new(ExitCode::ErrIllegalState, e))?; + for (m, verifs) in verifies.iter() { + let vres = res.get(m).ok_or_else( + || actor_error!(ErrNotFound; "batch verify seals syscall implemented incorrectly"), + )?; + let mut seen = AHashSet::<_>::new(); + let mut successful = Vec::new(); + for (i, &r) in vres.iter().enumerate() { + if r { + let snum = verifs[i].sector_id.number; + if seen.contains(&snum) { + continue; + } + seen.insert(snum); + successful.push(snum); + } + } + // Result intentionally ignored + let _ = rt.send( + *m, + miner::Method::ConfirmSectorProofsValid as MethodNum, + Serialized::serialize(&miner::ConfirmSectorProofsParams { + sectors: successful, + })?, + Default::default(), + ); + } Ok(()) } - fn submit_porep_for_bulk_verify( - rt: &mut RT, - seal_info: SealVerifyInfo, - ) -> Result<(), ActorError> + fn process_deferred_cron_events(rt: &mut RT) -> Result<(), ActorError> where BS: BlockStore, RT: Runtime, { - rt.validate_immediate_caller_type(std::iter::once(&*MINER_ACTOR_CODE_ID))?; - - rt.transaction::(|st, rt| { - let mut mmap = if let Some(ref batch) = st.proof_validation_batch { - Multimap::from_root(rt.store(), batch).map_err(|e| { - ActorError::new( + let rt_epoch = rt.curr_epoch(); + let mut cron_events = Vec::new(); + rt.transaction::<_, Result<_, ActorError>, _>(|st: &mut State, rt| { + let mut events = Multimap::from_root(rt.store(), &st.cron_event_queue) + .map_err(|e| actor_error!(ErrIllegalState; "failed to load cron events: {}", e))?; + + for epoch in st.first_cron_epoch..rt_epoch { + let mut epoch_events = load_cron_events(&events, epoch).map_err(|e| { + ActorError::downcast( + e, ExitCode::ErrIllegalState, - format!("failed to load proof batching set: {}", e), + &format!("failed to load cron events at {}", epoch), ) - })? - } else { - Multimap::new(rt.store()) - }; + })?; - let miner_addr = rt.message().caller(); - mmap.add(miner_addr.to_bytes().into(), seal_info) - .map_err(|e| { - ActorError::new( - ExitCode::ErrIllegalState, - format!("failed to insert proof into set: {}", e), - ) + if epoch_events.is_empty() { + continue; + } + + cron_events.append(&mut epoch_events); + + events.remove_all(&epoch_key(epoch)).map_err(|e| { + actor_error!(ErrIllegalState; "failed to clear cron events at {}: {}", epoch, e) })?; + } - let mmrc = mmap.root().map_err(|e| { - ActorError::new( - ExitCode::ErrIllegalState, - format!("failed to flush proofs batch map: {}", e), - ) - })?; - st.proof_validation_batch = Some(mmrc); - Ok(()) - })? - } -} + st.first_cron_epoch = rt_epoch + 1; + st.cron_event_queue = events + .root() + .map_err(|e| actor_error!(ErrIllegalState; "failed to flush events: {}", e))?; -//////////////////////////////////////////////////////////////////////////////// -// Method utility functions -//////////////////////////////////////////////////////////////////////////////// - -fn compute_initial_pledge( - rt: &mut RT, - desc: &SectorStorageWeightDesc, -) -> Result -where - BS: BlockStore, - RT: Runtime, -{ - let st: State = rt.state()?; - let ret = rt.send( - *REWARD_ACTOR_ADDR, - RewardMethod::ThisEpochReward as u64, - Serialized::default(), - TokenAmount::zero(), - )?; - let BigIntDe(epoch_reward) = ret.deserialize()?; - - let qa_power = qa_power_for_weight(&desc); - Ok(initial_pledge_for_weight( - &qa_power, - &st.total_quality_adj_power, - &rt.total_fil_circ_supply()?, - &st.total_pledge_collateral, - &epoch_reward, - )) -} + Ok(()) + })??; -fn powers_for_weights(weights: Vec) -> (StoragePower, StoragePower) { - // returns (rbpower, qapower) - let mut rb_power = BigInt::zero(); - let mut qa_power = BigInt::zero(); + let mut failed_miner_crons = Vec::new(); + for event in cron_events { + let res = rt.send( + event.miner_addr, + miner::Method::OnDeferredCronEvent as MethodNum, + event.callback_payload, + Default::default(), + ); + // If a callback fails, this actor continues to invoke other callbacks + // and persists state removing the failed event from the event queue. It won't be tried again. + // Failures are unexpected here but will result in removal of miner power + // A log message would really help here. + if let Err(e) = res { + log::warn!( + "OnDeferredCronEvent failed for miner {}: res {}", + event.miner_addr, + e + ); + failed_miner_crons.push(event.miner_addr) + } + } + rt.transaction::, _>(|st, rt| { + let mut claims = make_map_with_root(&st.claims, rt.store()) + .map_err(|e| actor_error!(ErrIllegalState; "failed to load claims: {}", e))?; + + // Remove power and leave miner frozen + for miner_addr in failed_miner_crons { + let claim = match get_claim(&claims, &miner_addr) { + Err(e) => { + log::error!( + "failed to get claim for miner {} after \ + failing OnDeferredCronEvent: {}", + miner_addr, + e + ); + continue; + } + Ok(None) => { + log::warn!( + "miner OnDeferredCronEvent failed for miner {} with no power", + miner_addr + ); + continue; + } + Ok(Some(claim)) => claim, + }; + + // zero out miner power + let res = st.add_to_claim( + &mut claims, + &miner_addr, + &claim.raw_byte_power.neg(), + &claim.quality_adj_power.neg(), + ); + if let Err(e) = res { + log::warn!( + "failed to remove power for miner {} after to failed cron: {}", + miner_addr, + e + ); + continue; + } + } - for w in &weights { - rb_power += BigInt::from(w.sector_size as u64); - qa_power += qa_power_for_weight(&w); + st.claims = claims + .flush() + .map_err(|e| actor_error!(ErrIllegalState; "failed to flush claims: {}", e))?; + Ok(()) + })??; + Ok(()) } - - (rb_power, qa_power) } impl ActorCode for Actor { @@ -543,6 +596,10 @@ impl ActorCode for Actor { let res = Self::create_miner(rt, params)?; Ok(Serialized::serialize(res)?) } + Some(Method::UpdateClaimedPower) => { + Self::update_claimed_power(rt, params.deserialize()?)?; + Ok(Serialized::default()) + } Some(Method::EnrollCronEvent) => { Self::enroll_cron_event(rt, params.deserialize()?)?; Ok(Serialized::default()) @@ -566,8 +623,12 @@ impl ActorCode for Actor { Self::submit_porep_for_bulk_verify(rt, params.deserialize()?)?; Ok(Serialized::default()) } - // TODO update with new/updated methods - _ => Err(rt.abort(ExitCode::SysErrInvalidMethod, "Invalid method")), + Some(Method::CurrentTotalPower) => { + check_empty_params(params)?; + let res = Self::current_total_power(rt)?; + Ok(Serialized::serialize(res)?) + } + None => Err(actor_error!(SysErrInvalidMethod; "Invalid method")), } } } diff --git a/vm/actor/src/builtin/power/policy.rs b/vm/actor/src/builtin/power/policy.rs index 918d4414db02..6e039256c22c 100644 --- a/vm/actor/src/builtin/power/policy.rs +++ b/vm/actor/src/builtin/power/policy.rs @@ -1,62 +1,21 @@ // Copyright 2020 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -use super::SectorStorageWeightDesc; -use fil_types::{SectorQuality, StoragePower}; -use num_bigint::BigInt; +use fil_types::StoragePower; use num_traits::FromPrimitive; -use vm::TokenAmount; -const SECTOR_QUALITY_PRECISION: usize = 20; // PARAM_FINISH -const BASE_MULTIPLIER: usize = 10; // PARAM_FINISH -const DEAL_WEIGHT_MULTIPLIER: usize = 11; // PARAM_FINISH -const VERIFIED_DEAL_WEIGHT_MULITPLIER: usize = 100; // PARAM_FINISH +/// Minimum power of an individual miner to meet the threshold for leader election. +pub const CONSENSUS_MINER_MIN_MINERS: i64 = 3; + +/// Maximum number of prove commits a miner can submit in one epoch +/// +/// We bound this to 200 to limit the number of prove partitions we may need to update in a +/// given epoch to 200. +/// +/// To support onboarding 1EiB/year, we need to allow at least 32 prove commits per epoch. +pub const MAX_MINER_PROVE_COMMITS_PER_EPOCH: u64 = 3; lazy_static! { /// Minimum power of an individual miner to meet the threshold for leader election. pub static ref CONSENSUS_MINER_MIN_POWER: StoragePower = StoragePower::from_i64(1 << 40).unwrap(); // placeholder } - -/// DealWeight and VerifiedDealWeight are spacetime occupied by regular deals and verified deals in a sector. -/// Sum of DealWeight and VerifiedDealWeight should be less than or equal to total SpaceTime of a sector. -/// Sectors full of VerifiedDeals will have a SectorQuality of VerifiedDealWeightMultiplier/BaseMultiplier. -/// Sectors full of Deals will have a SectorQuality of DealWeightMultiplier/BaseMultiplier. -/// Sectors with neither will have a SectorQuality of BaseMultiplier/BaseMultiplier. -/// SectorQuality of a sector is a weighted average of multipliers based on their propotions. -fn sector_quality_from_weight(weight: &SectorStorageWeightDesc) -> SectorQuality { - let sector_size = BigInt::from(weight.sector_size as u64); - let sector_space_time = sector_size * weight.duration; - let total_deal_space_time = &weight.deal_weight + &weight.verified_deal_weight; - assert!(sector_space_time < total_deal_space_time); - - let weighted_base_space_time = (§or_space_time - &total_deal_space_time) * BASE_MULTIPLIER; - let weighted_deal_space_time = &weight.deal_weight + DEAL_WEIGHT_MULTIPLIER; - let weighted_verified_space_time = - &weight.verified_deal_weight * VERIFIED_DEAL_WEIGHT_MULITPLIER; - let weighted_sum_space_time = - weighted_base_space_time + (weighted_deal_space_time + weighted_verified_space_time); - let scale_up_weighted_sum_space_time = weighted_sum_space_time << SECTOR_QUALITY_PRECISION; - - (scale_up_weighted_sum_space_time / sector_space_time) / BASE_MULTIPLIER -} - -pub fn qa_power_for_weight(weight: &SectorStorageWeightDesc) -> StoragePower { - let qual = sector_quality_from_weight(weight); - let sector_quality = weight.sector_size as u64 * qual; - sector_quality >> SECTOR_QUALITY_PRECISION -} - -pub fn initial_pledge_for_weight( - qa_power: &StoragePower, - tot_qa_power: &StoragePower, - circ_supply: &TokenAmount, - total_pledge: &TokenAmount, - per_epoch_reward: &TokenAmount, -) -> TokenAmount { - // Details here are still subject to change. - // PARAM_FINISH - let _ = circ_supply; // TODO: ce use this - let _ = total_pledge; // TODO: ce use this - - (qa_power * per_epoch_reward) / tot_qa_power -} diff --git a/vm/actor/src/builtin/power/state.rs b/vm/actor/src/builtin/power/state.rs index 73b14baa20da..eed8b0a25cf8 100644 --- a/vm/actor/src/builtin/power/state.rs +++ b/vm/actor/src/builtin/power/state.rs @@ -1,18 +1,28 @@ // Copyright 2020 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -use super::CONSENSUS_MINER_MIN_POWER; -use crate::{BytesKey, Multimap, HAMT_BIT_WIDTH}; +use super::{CONSENSUS_MINER_MIN_MINERS, CONSENSUS_MINER_MIN_POWER}; +use crate::{ + smooth::{AlphaBetaFilter, FilterEstimate, DEFAULT_ALPHA, DEFAULT_BETA}, + BytesKey, Map, Multimap, +}; use address::Address; use cid::Cid; -use clock::ChainEpoch; +use clock::{ChainEpoch, EPOCH_UNDEFINED}; use encoding::{tuple::*, Cbor}; use fil_types::StoragePower; use integer_encoding::VarInt; use ipld_blockstore::BlockStore; -use ipld_hamt::Hamt; -use num_bigint::bigint_ser; -use vm::{Serialized, TokenAmount}; +use num_bigint::{bigint_ser, BigInt, Sign}; +use std::error::Error as StdError; +use vm::{actor_error, ActorError, ExitCode, Serialized, TokenAmount}; + +lazy_static! { + /// genesis power in bytes = 750,000 GiB + static ref INITIAL_QA_POWER_ESTIMATE_POSITION: BigInt = BigInt::from(750_000) * (1 << 30); + /// max chain throughput in bytes per epoch = 120 ProveCommits / epoch = 3,840 GiB + static ref INITIAL_QA_POWER_ESTIMATE_VELOCITY: BigInt = BigInt::from(3_840) * (1 << 30); +} /// Storage power actor state #[derive(Default, Serialize_tuple, Deserialize_tuple)] @@ -20,186 +30,204 @@ pub struct State { #[serde(with = "bigint_ser")] pub total_raw_byte_power: StoragePower, #[serde(with = "bigint_ser")] + pub total_bytes_committed: StoragePower, + #[serde(with = "bigint_ser")] pub total_quality_adj_power: StoragePower, #[serde(with = "bigint_ser")] + pub total_qa_bytes_committed: StoragePower, + #[serde(with = "bigint_ser")] pub total_pledge_collateral: TokenAmount, + + #[serde(with = "bigint_ser")] + pub this_epoch_raw_byte_power: StoragePower, + #[serde(with = "bigint_ser")] + pub this_epoch_quality_adj_power: StoragePower, + #[serde(with = "bigint_ser")] + pub this_epoch_pledge_collateral: TokenAmount, + pub this_epoch_qa_power_smoothed: FilterEstimate, + pub miner_count: i64, + /// Number of miners having proven the minimum consensus power. + pub miner_above_min_power_count: i64, /// A queue of events to be triggered by cron, indexed by epoch. pub cron_event_queue: Cid, // Multimap, (HAMT[ChainEpoch]AMT[CronEvent] - /// Last chain epoch OnEpochTickEnd was called on - pub last_epoch_tick: ChainEpoch, + /// First epoch in which a cron task may be stored. Cron will iterate every epoch between this + /// and the current epoch inclusively to find tasks to execute. + pub first_cron_epoch: ChainEpoch, - /// Claimed power and associated pledge requirements for each miner. - pub claims: Cid, // Map, HAMT[address]Claim + /// Last epoch power cron tick has been processed. + pub last_processed_cron_epoch: ChainEpoch, - /// Number of miners having proven the minimum consensus power. - // TODO: revisit todo in specs-actors - pub num_miners_meeting_min_power: i64, + /// Claimed power for each miner. + pub claims: Cid, // Map, HAMT[address]Claim pub proof_validation_batch: Option, } impl State { - pub fn new(empty_map_cid: Cid, _empty_mmap_cid: Cid) -> State { + pub fn new(empty_map_cid: Cid, empty_mmap_cid: Cid) -> State { State { - cron_event_queue: empty_map_cid.clone(), + cron_event_queue: empty_mmap_cid, claims: empty_map_cid, + last_processed_cron_epoch: EPOCH_UNDEFINED, + this_epoch_qa_power_smoothed: FilterEstimate { + position: INITIAL_QA_POWER_ESTIMATE_POSITION.clone(), + velocity: INITIAL_QA_POWER_ESTIMATE_VELOCITY.clone(), + }, ..Default::default() } } // TODO minerNominalPowerMeetsConsensusMinimum - pub fn add_to_claim( + pub(super) fn add_to_claim( &mut self, - store: &BS, + claims: &mut Map, miner: &Address, power: &StoragePower, qa_power: &StoragePower, - ) -> Result<(), String> { - let mut claim = self - .get_claim(store, miner)? - .ok_or(format!("no claim for actor {}", miner))?; + ) -> Result<(), Box> { + let old_claim = get_claim(claims, miner)? + .ok_or_else(|| actor_error!(ErrNotFound; "no claim for actor {}", miner))?; - let old_nominal_power = claim.quality_adj_power.clone(); + self.total_qa_bytes_committed += qa_power; + self.total_bytes_committed += power; - // update power - claim.raw_byte_power += power; - claim.quality_adj_power += qa_power; - - let new_nominal_power = &claim.quality_adj_power; + let new_claim = Claim { + raw_byte_power: old_claim.raw_byte_power.clone() + power, + quality_adj_power: old_claim.quality_adj_power.clone() + qa_power, + }; let min_power_ref: &StoragePower = &*CONSENSUS_MINER_MIN_POWER; - let prev_below: bool = &old_nominal_power < min_power_ref; - let still_below: bool = new_nominal_power < min_power_ref; + let prev_below: bool = &old_claim.quality_adj_power < min_power_ref; + let still_below: bool = &new_claim.quality_adj_power < min_power_ref; if prev_below && !still_below { // Just passed min miner size - self.num_miners_meeting_min_power += 1; - self.total_quality_adj_power += new_nominal_power; - self.total_raw_byte_power += &claim.raw_byte_power; + self.miner_above_min_power_count += 1; + self.total_quality_adj_power += &new_claim.quality_adj_power; + self.total_raw_byte_power += &new_claim.raw_byte_power; } else if !prev_below && still_below { // just went below min miner size - self.num_miners_meeting_min_power -= 1; + self.miner_above_min_power_count -= 1; self.total_quality_adj_power = self .total_quality_adj_power - .checked_sub(&old_nominal_power) - .ok_or("Negative nominal power")?; + .checked_sub(&old_claim.quality_adj_power) + .expect("Negative nominal power"); self.total_raw_byte_power = self .total_raw_byte_power - .checked_sub(&claim.raw_byte_power) - .ok_or("Negative raw byte power")?; + .checked_sub(&old_claim.raw_byte_power) + .expect("Negative raw byte power"); } else if !prev_below && !still_below { // Was above the threshold, still above self.total_quality_adj_power += qa_power; self.total_raw_byte_power += power; } - if self.num_miners_meeting_min_power < 0 { - return Err(format!( - "negative number of miners: {}", - self.num_miners_meeting_min_power - )); - } - - self.set_claim(store, miner, claim) + assert_ne!( + new_claim.raw_byte_power.sign(), + Sign::Minus, + "negative claimed raw byte power: {}", + new_claim.raw_byte_power + ); + assert_ne!( + new_claim.quality_adj_power.sign(), + Sign::Minus, + "negative claimed quality adjusted power: {}", + new_claim.quality_adj_power + ); + assert!( + self.miner_above_min_power_count >= 0, + "negative number of miners larger than min: {}", + self.miner_above_min_power_count + ); + + Ok(set_claim(claims, miner, new_claim)?) } pub(super) fn add_pledge_total(&mut self, amount: TokenAmount) { self.total_pledge_collateral += amount; + assert_ne!(self.total_pledge_collateral.sign(), Sign::Minus); } pub(super) fn append_cron_event( &mut self, - s: &BS, + events: &mut Multimap, epoch: ChainEpoch, event: CronEvent, ) -> Result<(), String> { - let mut mmap = Multimap::from_root(s, &self.cron_event_queue)?; - mmap.add(epoch_key(epoch), event)?; - self.cron_event_queue = mmap.root()?; - Ok(()) - } - - pub(super) fn load_cron_events( - &mut self, - s: &BS, - epoch: ChainEpoch, - ) -> Result, String> { - let mut events = Vec::new(); - - let mmap = Multimap::from_root(s, &self.cron_event_queue)?; - mmap.for_each(&epoch_key(epoch), |_, v: &CronEvent| { - match self.get_claim(s, &v.miner_addr) { - Ok(Some(_)) => events.push(v.clone()), - Err(e) => { - return Err(format!( - "failed to find claimed power for {} for cron event: {}", - v.miner_addr, e - )) - } - _ => (), // ignore events for defunct miners. - } - Ok(()) - })?; - - Ok(events) - } + if epoch < self.first_cron_epoch { + self.first_cron_epoch = epoch; + } - pub(super) fn clear_cron_events( - &mut self, - s: &BS, - epoch: ChainEpoch, - ) -> Result<(), String> { - let mut mmap = Multimap::from_root(s, &self.cron_event_queue)?; - mmap.remove_all(&epoch_key(epoch))?; - self.cron_event_queue = mmap.root()?; + events + .add(epoch_key(epoch), event) + .map_err(|e| format!("failed to store cron event at epoch {}: {}", epoch, e))?; Ok(()) } - /// Gets claim from claims map by address - pub fn get_claim( - &self, - store: &BS, - a: &Address, - ) -> Result, String> { - let map: Hamt<_, BytesKey> = - Hamt::load_with_bit_width(&self.claims, store, HAMT_BIT_WIDTH)?; + pub(super) fn current_total_power(&self) -> (StoragePower, StoragePower) { + if self.miner_above_min_power_count < CONSENSUS_MINER_MIN_MINERS { + ( + self.total_bytes_committed.clone(), + self.total_qa_bytes_committed.clone(), + ) + } else { + ( + self.total_raw_byte_power.clone(), + self.total_quality_adj_power.clone(), + ) + } + } - Ok(map.get(&a.to_bytes())?) + pub(super) fn update_smoothed_estimate(&mut self, delta: ChainEpoch) { + let filter_qa_power = AlphaBetaFilter::load( + &self.this_epoch_qa_power_smoothed, + &*DEFAULT_ALPHA, + &*DEFAULT_BETA, + ); + self.this_epoch_qa_power_smoothed = + filter_qa_power.next_estimate(&self.this_epoch_quality_adj_power, delta); } +} - pub(super) fn set_claim( - &mut self, - store: &BS, - addr: &Address, - claim: Claim, - ) -> Result<(), String> { - let mut map: Hamt<_, BytesKey> = - Hamt::load_with_bit_width(&self.claims, store, HAMT_BIT_WIDTH)?; +pub(super) fn load_cron_events( + mmap: &Multimap, + epoch: ChainEpoch, +) -> Result, Box> { + let mut events = Vec::new(); - map.set(addr.to_bytes().into(), claim)?; - self.claims = map.flush()?; + mmap.for_each(&epoch_key(epoch), |_, v: &CronEvent| { + events.push(v.clone()); Ok(()) - } + })?; - pub(super) fn delete_claim( - &mut self, - store: &BS, - addr: &Address, - ) -> Result<(), String> { - let mut map: Hamt<_, BytesKey> = - Hamt::load_with_bit_width(&self.claims, store, HAMT_BIT_WIDTH)?; + Ok(events) +} - map.delete(&addr.to_bytes())?; - self.claims = map.flush()?; - Ok(()) - } +/// Gets claim from claims map by address +pub fn get_claim(claims: &Map, a: &Address) -> Result, String> { + Ok(claims + .get(&a.to_bytes()) + .map_err(|e| format!("failed to get claim for address {}: {}", a, e))?) +} + +pub fn set_claim( + claims: &mut Map, + a: &Address, + claim: Claim, +) -> Result<(), String> { + assert_ne!(claim.raw_byte_power.sign(), Sign::Minus); + assert_ne!(claim.quality_adj_power.sign(), Sign::Minus); + + Ok(claims + .set(a.to_bytes().into(), claim) + .map_err(|e| format!("failed to set claim for address {}: {}", a, e))?) } -fn epoch_key(e: ChainEpoch) -> BytesKey { +pub(super) fn epoch_key(e: ChainEpoch) -> BytesKey { let bz = e.encode_var_vec(); bz.into() } @@ -208,10 +236,10 @@ impl Cbor for State {} #[derive(Default, Debug, Serialize_tuple, Deserialize_tuple)] pub struct Claim { - // Sum of raw byte power for a miner's sectors. + /// Sum of raw byte power for a miner's sectors. #[serde(with = "bigint_ser")] pub raw_byte_power: StoragePower, - // Sum of quality adjusted power for a miner's sectors. + /// Sum of quality adjusted power for a miner's sectors. #[serde(with = "bigint_ser")] pub quality_adj_power: StoragePower, } diff --git a/vm/actor/src/builtin/power/types.rs b/vm/actor/src/builtin/power/types.rs index 1ff781366b70..b090f5952638 100644 --- a/vm/actor/src/builtin/power/types.rs +++ b/vm/actor/src/builtin/power/types.rs @@ -1,10 +1,10 @@ // Copyright 2020 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT -use crate::DealWeight; +use crate::{smooth::FilterEstimate, DealWeight}; use address::Address; use clock::ChainEpoch; -use encoding::{serde_bytes, tuple::*, Cbor}; +use encoding::{tuple::*, BytesDe, Cbor}; use fil_types::{RegisteredSealProof, SectorSize, StoragePower}; use num_bigint::bigint_ser; use vm::{Serialized, TokenAmount}; @@ -20,74 +20,28 @@ pub const SECTOR_TERMINATION_FAULTY: SectorTermination = 3; #[derive(Serialize_tuple, Deserialize_tuple)] pub struct CreateMinerParams { - owner_addr: Address, - worker_addr: Address, + owner: Address, + worker: Address, seal_proof_type: RegisteredSealProof, - #[serde(with = "serde_bytes")] - peer_id: Vec, + peer: BytesDe, + multiaddrs: Vec, } - -#[derive(Clone, Serialize_tuple, Deserialize_tuple)] -pub struct SectorStorageWeightDesc { - pub sector_size: SectorSize, - pub duration: ChainEpoch, - #[serde(with = "bigint_ser")] - pub deal_weight: DealWeight, - #[serde(with = "bigint_ser")] - pub verified_deal_weight: DealWeight, -} - impl Cbor for CreateMinerParams {} #[derive(Serialize_tuple, Deserialize_tuple)] pub struct CreateMinerReturn { /// Canonical ID-based address for the actor. pub id_address: Address, - /// Re-org safe address for created actor + /// Re-org safe address for created actor. pub robust_address: Address, } #[derive(Serialize_tuple, Deserialize_tuple)] -pub struct DeleteMinerParams { - pub miner: Address, -} - -#[derive(Serialize_tuple, Deserialize_tuple)] -pub struct OnSectorProveCommitParams { - pub weight: SectorStorageWeightDesc, -} - -#[derive(Serialize_tuple, Deserialize_tuple)] -pub struct OnSectorTerminateParams { - pub termination_type: SectorTermination, - pub weights: Vec, -} - -#[derive(Serialize_tuple, Deserialize_tuple)] -pub struct OnSectorTemporaryFaultEffectiveBeginParams { - // TODO revisit todo for replacing with power - pub weights: Vec, +pub struct UpdateClaimedPowerParams { #[serde(with = "bigint_ser")] - pub pledge: TokenAmount, -} - -#[derive(Serialize_tuple, Deserialize_tuple)] -pub struct OnSectorTemporaryFaultEffectiveEndParams { - // TODO revisit todo for replacing with power - pub weights: Vec, + pub raw_byte_delta: StoragePower, #[serde(with = "bigint_ser")] - pub pledge: TokenAmount, -} - -#[derive(Serialize_tuple, Deserialize_tuple)] -pub struct OnSectorModifyWeightDescParams { - pub prev_weight: SectorStorageWeightDesc, - pub new_weight: SectorStorageWeightDesc, -} - -#[derive(Serialize_tuple, Deserialize_tuple)] -pub struct OnMinerWindowedPoStFailureParams { - pub num_consecutive_failures: i64, + pub quality_adjusted_delta: StoragePower, } #[derive(Serialize_tuple, Deserialize_tuple)] @@ -96,6 +50,16 @@ pub struct EnrollCronEventParams { pub payload: Serialized, } +#[derive(Clone, Serialize_tuple, Deserialize_tuple)] +pub struct SectorStorageWeightDesc { + pub sector_size: SectorSize, + pub duration: ChainEpoch, + #[serde(with = "bigint_ser")] + pub deal_weight: DealWeight, + #[serde(with = "bigint_ser")] + pub verified_deal_weight: DealWeight, +} + #[derive(Serialize_tuple, Deserialize_tuple)] pub struct ReportConsensusFaultParams { pub block_header_1: Serialized, @@ -103,17 +67,6 @@ pub struct ReportConsensusFaultParams { pub block_header_extra: Serialized, } -#[derive(Serialize_tuple, Deserialize_tuple)] -pub struct OnFaultBeginParams { - pub weights: Vec, // TODO: replace with power if it can be computed by miner -} - -#[derive(Serialize_tuple, Deserialize_tuple)] -pub struct OnFaultEndParams { - pub weights: Vec, // TODO: replace with power if it can be computed by miner -} - -// TODO use and update this, was just needed to reference #[derive(Serialize_tuple, Deserialize_tuple)] pub struct CurrentTotalPowerReturn { #[serde(with = "bigint_ser")] @@ -122,5 +75,5 @@ pub struct CurrentTotalPowerReturn { pub quality_adj_power: StoragePower, #[serde(with = "bigint_ser")] pub pledge_collateral: TokenAmount, - pub quality_adj_power_smoothed: (), + pub quality_adj_power_smoothed: FilterEstimate, } diff --git a/vm/actor/src/lib.rs b/vm/actor/src/lib.rs index 013d38424cb9..3936be33b335 100644 --- a/vm/actor/src/lib.rs +++ b/vm/actor/src/lib.rs @@ -55,7 +55,7 @@ fn make_map(store: &'_ BS) -> Map<'_, BS> { /// Create a map with a root cid. #[inline] -fn make_map_with_root<'bs, BS: BlockStore>( +pub fn make_map_with_root<'bs, BS: BlockStore>( root: &Cid, store: &'bs BS, ) -> Result, HamtError> { diff --git a/vm/actor/src/util/balance_table.rs b/vm/actor/src/util/balance_table.rs index 496ab5ca2345..302a0cf70ce8 100644 --- a/vm/actor/src/util/balance_table.rs +++ b/vm/actor/src/util/balance_table.rs @@ -7,6 +7,7 @@ use cid::Cid; use ipld_blockstore::BlockStore; use ipld_hamt::Error; use num_bigint::bigint_ser::BigIntDe; +use std::error::Error as StdError; use vm::TokenAmount; /// Balance table which handles getting and updating token balances specifically @@ -123,7 +124,7 @@ where } /// Returns total balance held by this balance table - pub fn total(&self) -> Result { + pub fn total(&self) -> Result> { let mut total = TokenAmount::default(); self.0.for_each(|_, v: BigIntDe| { diff --git a/vm/actor/src/util/multimap.rs b/vm/actor/src/util/multimap.rs index 32df2ccf1b02..3acfbe51fc88 100644 --- a/vm/actor/src/util/multimap.rs +++ b/vm/actor/src/util/multimap.rs @@ -7,6 +7,7 @@ use ipld_amt::Amt; use ipld_blockstore::BlockStore; use ipld_hamt::Error; use serde::{de::DeserializeOwned, Serialize}; +use std::error::Error as StdError; /// Multimap stores multiple values per key in a Hamt of Amts. /// The order of insertion of values for each key is retained. @@ -73,10 +74,10 @@ where } /// Iterates through all values in the array at a given key. - pub fn for_each(&self, key: &[u8], f: F) -> Result<(), String> + pub fn for_each(&self, key: &[u8], f: F) -> Result<(), Box> where V: Serialize + DeserializeOwned + Clone, - F: FnMut(u64, &V) -> Result<(), String>, + F: FnMut(u64, &V) -> Result<(), Box>, { if let Some(amt) = self.get::(key)? { amt.for_each(f)?; @@ -84,4 +85,18 @@ where Ok(()) } + + /// Iterates through all arrays in the multimap + pub fn for_all(&self, mut f: F) -> Result<(), Box> + where + V: Serialize + DeserializeOwned + Clone, + F: FnMut(&BytesKey, &Amt) -> Result<(), Box>, + { + self.0.for_each::<_, Cid>(|key, arr_root| { + let arr = Amt::load(&arr_root, self.0.store())?; + f(key, &arr) + })?; + + Ok(()) + } } diff --git a/vm/actor/src/util/set.rs b/vm/actor/src/util/set.rs index 7002ef4e3cb3..74cfa16555b7 100644 --- a/vm/actor/src/util/set.rs +++ b/vm/actor/src/util/set.rs @@ -64,11 +64,7 @@ where F: FnMut(&BytesKey) -> Result<(), Box>, { // Calls the for each function on the hamt with ignoring the value - // TODO there are no actor errors used in the generic function yet, but the HAMT for_each - // iterator should be Box to not convert to String and lose exit code - Ok(self - .0 - .for_each(|s, _: ()| f(s).map_err(|e| e.to_string()))?) + Ok(self.0.for_each(|s, _: ()| f(s))?) } /// Collects all keys from the set into a vector. diff --git a/vm/actor/tests/balance_table_test.rs b/vm/actor/tests/balance_table_test.rs index e8dfa87738bb..482696425721 100644 --- a/vm/actor/tests/balance_table_test.rs +++ b/vm/actor/tests/balance_table_test.rs @@ -29,7 +29,7 @@ fn total() { let store = db::MemoryDB::default(); let mut bt = BalanceTable::new(&store); - assert_eq!(bt.total(), Ok(TokenAmount::from(0u8))); + assert_eq!(bt.total().unwrap(), TokenAmount::from(0u8)); struct TotalTestCase<'a> { amount: u64, @@ -62,7 +62,7 @@ fn total() { for t in test_vectors.iter() { bt.add_create(t.addr, TokenAmount::from(t.amount)).unwrap(); - assert_eq!(bt.total(), Ok(TokenAmount::from(t.total))); + assert_eq!(bt.total().unwrap(), TokenAmount::from(t.total)); } } diff --git a/vm/actor/tests/common/mod.rs b/vm/actor/tests/common/mod.rs index ca2505bd6402..35d206091dc5 100644 --- a/vm/actor/tests/common/mod.rs +++ b/vm/actor/tests/common/mod.rs @@ -655,6 +655,11 @@ impl Runtime for MockRuntime { fn syscalls(&self) -> &dyn Syscalls { self } + + fn charge_gas(&mut self, _name: String, _gas: i64) -> Result<(), ActorError> { + // TODO implement functionality if needed for testing + Ok(()) + } } impl Syscalls for MockRuntime { diff --git a/vm/interpreter/src/default_runtime.rs b/vm/interpreter/src/default_runtime.rs index 5c7f53536d4e..24c47caf2e52 100644 --- a/vm/interpreter/src/default_runtime.rs +++ b/vm/interpreter/src/default_runtime.rs @@ -582,6 +582,10 @@ where - st.total_pledge_collateral; Ok(total) } + fn charge_gas(&mut self, _name: String, gas: i64) -> Result<(), ActorError> { + // TODO use name for better gas usage tracking if needed + self.charge_gas(gas) + } } /// Shared logic between the DefaultRuntime and the Interpreter. diff --git a/vm/interpreter/src/gas_syscalls.rs b/vm/interpreter/src/gas_syscalls.rs index ca9f81112ef8..4dd501961e28 100644 --- a/vm/interpreter/src/gas_syscalls.rs +++ b/vm/interpreter/src/gas_syscalls.rs @@ -77,7 +77,7 @@ where fn batch_verify_seals( &self, - vis: &[(Address, Vec)], + vis: &[(Address, &Vec)], ) -> Result>, Box> { // TODO revisit if gas ends up being charged (only used by cron actor) self.syscalls.batch_verify_seals(vis) @@ -130,7 +130,7 @@ mod tests { } fn batch_verify_seals( &self, - _vis: &[(Address, Vec)], + _vis: &[(Address, &Vec)], ) -> Result>, Box> { Ok(Default::default()) } diff --git a/vm/runtime/src/lib.rs b/vm/runtime/src/lib.rs index ccc11939b1b8..69bdc046f54a 100644 --- a/vm/runtime/src/lib.rs +++ b/vm/runtime/src/lib.rs @@ -141,7 +141,19 @@ pub trait Runtime { /// Provides the system call interface. fn syscalls(&self) -> &dyn Syscalls; + /// Returns the total token supply in circulation at the beginning of the current epoch. + /// The circulating supply is the sum of: + /// - rewards emitted by the reward actor, + /// - funds vested from lock-ups in the genesis state, + /// less the sum of: + /// - funds burnt, + /// - pledge collateral locked in storage miner actors (recorded in the storage power actor) + /// - deal collateral locked by the storage market actor fn total_fil_circ_supply(&self) -> Result; + + /// ChargeGas charges specified amount of `gas` for execution. + /// `name` provides information about gas charging point + fn charge_gas(&mut self, name: String, gas: i64) -> Result<(), ActorError>; } /// Message information available to the actor about executing message. @@ -294,7 +306,7 @@ pub trait Syscalls { fn batch_verify_seals( &self, - vis: &[(Address, Vec)], + vis: &[(Address, &Vec)], ) -> Result>, Box> { let out = vis .par_iter() diff --git a/vm/src/error.rs b/vm/src/error.rs index f189c687bc22..93491e624610 100644 --- a/vm/src/error.rs +++ b/vm/src/error.rs @@ -1,11 +1,11 @@ // Copyright 2020 ChainSafe Systems // SPDX-License-Identifier: Apache-2.0, MIT +use crate::ExitCode; use encoding::Error as EncodingError; +use std::error::Error as StdError; use thiserror::Error; -use crate::ExitCode; - /// The error type that gets returned by actor method calls. #[derive(Error, Debug, Clone, PartialEq)] #[error("ActorError(fatal: {fatal}, exit_code: {exit_code:?}, msg: {msg})")] @@ -35,6 +35,14 @@ impl ActorError { } } + /// Downcast a dynamic std Error into an ActorError + pub fn downcast(error: Box, default_exit_code: ExitCode, msg: &str) -> Self { + match error.downcast::() { + Ok(actor_err) => actor_err.wrap(msg), + Err(other) => ActorError::new(default_exit_code, format!("{}: {}", msg, other)), + } + } + /// Returns true if error is fatal. pub fn is_fatal(&self) -> bool { self.fatal diff --git a/vm/src/exit_code.rs b/vm/src/exit_code.rs index 7a7e2e1903d6..7ec5cdfa0477 100644 --- a/vm/src/exit_code.rs +++ b/vm/src/exit_code.rs @@ -67,6 +67,9 @@ pub enum ExitCode { ErrIllegalState = 20, /// Indicates de/serialization failure within actor code. ErrSerialization = 21, + /// Power actor specific exit code. + // * remove this and support custom codes if there is overlap on actor specific codes in future + ErrTooManyProveCommits = 32, ErrPlaceholder = 1000, }