From ad5df1d23a538c44e77b5773ddc6ffd9b06e8f0d Mon Sep 17 00:00:00 2001 From: h4x3rotab Date: Fri, 29 Apr 2022 15:44:26 +0000 Subject: [PATCH] integrate phala pallets --- pallets/phala/Cargo.toml | 2 +- pallets/phala/mq-runtime-api/Cargo.toml | 2 +- pallets/phala/src/fat.rs | 5 +- pallets/phala/src/lib.rs | 8 +- pallets/phala/src/migrations/mod.rs | 4 +- pallets/phala/src/mining.rs | 206 +++++++-- pallets/phala/src/mq.rs | 4 +- pallets/phala/src/registry.rs | 84 +++- pallets/phala/src/stakepool.rs | 558 +++++++++++++++++------- pallets/phala/src/utils/accumulator.rs | 2 + pallets/phala/src/utils/mod.rs | 2 +- 11 files changed, 653 insertions(+), 224 deletions(-) diff --git a/pallets/phala/Cargo.toml b/pallets/phala/Cargo.toml index aa5a1a66..73b18049 100644 --- a/pallets/phala/Cargo.toml +++ b/pallets/phala/Cargo.toml @@ -1,6 +1,6 @@ [package] authors = ["Phala Network"] -edition = "2018" +edition = "2021" name = "phala-pallets" version = "5.0.0" license = "Apache 2.0" diff --git a/pallets/phala/mq-runtime-api/Cargo.toml b/pallets/phala/mq-runtime-api/Cargo.toml index cf77a640..f18bf3fa 100644 --- a/pallets/phala/mq-runtime-api/Cargo.toml +++ b/pallets/phala/mq-runtime-api/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pallet-mq-runtime-api" version = "0.1.0" -edition = "2018" +edition = "2021" [dependencies] sp-api = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.18", default-features = false} diff --git a/pallets/phala/src/fat.rs b/pallets/phala/src/fat.rs index 27975d4e..285022ca 100644 --- a/pallets/phala/src/fat.rs +++ b/pallets/phala/src/fat.rs @@ -1,4 +1,5 @@ -/// Public key registry for workers and contracts. +//! The Fat Contract registry + pub use self::pallet::*; #[frame_support::pallet] @@ -47,7 +48,7 @@ pub mod pallet { type Event: From> + IsType<::Event>; } - const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(5); #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] diff --git a/pallets/phala/src/lib.rs b/pallets/phala/src/lib.rs index 21b1b8a4..9de173c8 100644 --- a/pallets/phala/src/lib.rs +++ b/pallets/phala/src/lib.rs @@ -3,12 +3,6 @@ //! Phala Pallets //! //! This is the central crate of Phala tightly-coupled pallets. -//! -//! - `phala_legacy`: The legacy `pallet-phala`; will be retired gradually -//! - `mq`: The message queue to connect components in the network -//! - `registry`: Manages the public key of offchain components (i.e. workers and contracts) -//! - `mining`: Manages mining lifecycle, reward and slashes -//! - `stakepool`: Pool for collaboratively mining staking #[cfg(target_arch = "wasm32")] extern crate webpki_wasm as webpki; @@ -20,7 +14,7 @@ extern crate alloc; use utils::{accumulator, attestation, balance_convert, constants, fixed_point}; pub mod migrations; -mod utils; +pub mod utils; pub mod fat; pub mod mining; diff --git a/pallets/phala/src/migrations/mod.rs b/pallets/phala/src/migrations/mod.rs index d728c977..4118afe1 100644 --- a/pallets/phala/src/migrations/mod.rs +++ b/pallets/phala/src/migrations/mod.rs @@ -141,12 +141,14 @@ pub mod v5 { pub fn migrate() -> Weight where T: mining::Config + mq::Config + registry::Config + stakepool::Config, - MiningBalanceOf: balance_convert::FixedPointConvert, + MiningBalanceOf: balance_convert::FixedPointConvert + sp_std::fmt::Display, + T: mining::pallet::Config::Currency>, { if get_versions::() == EXPECTED_STORAGE_VERSION { let mut weight: Weight = 0; log::info!("Ᵽ migrating phala-pallets to v5"); weight += mining::migrations::trigger_unresp_fix::(); + weight += stakepool::Pallet::::migration_remove_assignments(); log::info!("Ᵽ pallets migrated to v5"); StorageVersion::new(5).put::>(); diff --git a/pallets/phala/src/mining.rs b/pallets/phala/src/mining.rs index 1da0c5f9..648e173a 100644 --- a/pallets/phala/src/mining.rs +++ b/pallets/phala/src/mining.rs @@ -1,3 +1,5 @@ +//! Manages mining lifecycle, reward and slashes + pub use self::pallet::*; #[allow(unused_variables)] @@ -76,12 +78,20 @@ pub mod pallet { } } + /// The benchmark information of a worker #[derive(Encode, Decode, TypeInfo, Clone, PartialEq, Eq, RuntimeDebug)] pub struct Benchmark { + /// The initial performance score copied from the registry pallet p_init: u32, + /// The instant performance score p_instant: u32, + /// The latest benchmark iterations + /// + /// Used to calculate `p_instant`. iterations: u64, + /// The unix timestamp of the mining start time mining_start_time: u64, + /// The unix timestamp of block that triggers the last heartbeat challenge challenge_time_last: u64, } @@ -113,16 +123,25 @@ pub mod pallet { } } + /// The state of a miner #[derive(Encode, Decode, TypeInfo, Clone, PartialEq, Eq, RuntimeDebug)] pub struct MinerInfo { + /// The current state of the miner pub state: MinerState, - /// The intiial V, in U64F64 bits + /// The intiial V, in `U64F64` bits pub ve: u128, - /// The last updated V, in U64F64 bits + /// The last updated V, in `U64F64` bits pub v: u128, + /// The unix timestamp of the last V update time v_updated_at: u64, + /// Benchmark info benchmark: Benchmark, + /// The unix timestamp of the cool down starting time + /// + /// The value is meaningless if the state is not in + /// [`MiningCoolingDown`](MinerState::MiningCoolingDown) state. cool_down_start: u64, + /// The statistics of the current mining session stats: MinerStats, } @@ -156,8 +175,10 @@ pub mod pallet { fn on_stopped(worker: &WorkerPublicKey, orig_stake: Balance, slashed: Balance) {} } + /// The stats of a mining session #[derive(Encode, Decode, TypeInfo, Clone, PartialEq, Eq, Default, RuntimeDebug)] pub struct MinerStats { + /// The total received reward in this mining session, in `U32F32` bits total_reward: u128, } @@ -186,7 +207,7 @@ pub mod pallet { type UpdateTokenomicOrigin: EnsureOrigin; } - const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(5); #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] @@ -202,14 +223,14 @@ pub mod pallet { #[pallet::storage] pub type ScheduledTokenomicUpdate = StorageValue<_, TokenomicParams>; - /// Total online miners + /// Total online miners including MiningIdle and MiningUnresponsive workers. /// - /// Increased when a miner is turned to MiningIdle; decreased when turned to CoolingDown + /// Increased when a miner is turned to MiningIdle; decreased when turned to CoolingDown. #[pallet::storage] #[pallet::getter(fn online_miners)] pub type OnlineMiners = StorageValue<_, u32, ValueQuery>; - /// The expected heartbeat count (default: 20) + /// The expected heartbeat count at every block (default: 20) #[pallet::storage] pub type ExpectedHeartbeatCount = StorageValue<_, u32>; @@ -256,33 +277,87 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Cool down expiration changed. \[period\] - CoolDownExpirationChanged(u64), - /// Miner starts mining. \[miner\] - MinerStarted(T::AccountId), - /// Miner stops mining. \[miner\] - MinerStopped(T::AccountId), - /// Miner is reclaimed, with its slash settled. \[miner, original_stake, slashed\] - MinerReclaimed(T::AccountId, BalanceOf, BalanceOf), - /// Miner & worker are bound. \[miner, worker\] - MinerBound(T::AccountId, WorkerPublicKey), - /// Miner & worker are unbound. \[miner, worker\] - MinerUnbound(T::AccountId, WorkerPublicKey), - /// Miner enters unresponsive state. \[miner\] - MinerEnterUnresponsive(T::AccountId), - /// Miner returns to responsive state \[miner\] - MinerExitUnresponsive(T::AccountId), - /// Miner settled successfully. \[miner, v, payout\] - MinerSettled(T::AccountId, u128, u128), - /// Some internal error happened when settling a miner's ledger. \[worker\] - InternalErrorMinerSettleFailed(WorkerPublicKey), - /// Block subsidy halved by 25% + /// Cool down expiration changed (in sec). + /// + /// Indicates a change in [`CoolDownPeriod`]. + CoolDownExpirationChanged { period: u64 }, + /// A miner starts mining. + /// + /// Affected states: + /// - the miner info at [`Miners`] is updated with `MiningIdle` state + /// - [`NextSessionId`] for the miner is incremented + /// - [`Stakes`] for the miner is updated + /// - [`OnlineMiners`] is incremented + MinerStarted { miner: T::AccountId }, + /// Miner stops mining. + /// + /// Affected states: + /// - the miner info at [`Miners`] is updated with `MiningCoolingDown` state + /// - [`OnlineMiners`] is decremented + MinerStopped { miner: T::AccountId }, + /// Miner is reclaimed, with its slash settled. + MinerReclaimed { + miner: T::AccountId, + original_stake: BalanceOf, + slashed: BalanceOf, + }, + /// Miner & worker are bound. + /// + /// Affected states: + /// - [`MinerBindings`] for the miner account is pointed to the worker + /// - [`WorkerBindings`] for the worker is pointed to the miner account + /// - the miner info at [`Miners`] is updated with `Ready` state + MinerBound { + miner: T::AccountId, + worker: WorkerPublicKey, + }, + /// Miner & worker are unbound. + /// + /// Affected states: + /// - [`MinerBindings`] for the miner account is removed + /// - [`WorkerBindings`] for the worker is removed + MinerUnbound { + miner: T::AccountId, + worker: WorkerPublicKey, + }, + /// Miner enters unresponsive state. + /// + /// Affected states: + /// - the miner info at [`Miners`] is updated from `MiningIdle` to `MiningUnresponsive` + MinerEnterUnresponsive { miner: T::AccountId }, + /// Miner returns to responsive state. + /// + /// Affected states: + /// - the miner info at [`Miners`] is updated from `MiningUnresponsive` to `MiningIdle` + MinerExitUnresponsive { miner: T::AccountId }, + /// Miner settled successfully. + /// + /// It results in the v in [`Miners`] being updated. It also indicates the downstream + /// stake pool has received the mining reward (payout), and the treasury has received the + /// tax. + MinerSettled { + miner: T::AccountId, + v_bits: u128, + payout_bits: u128, + }, + /// Some internal error happened when settling a miner's ledger. + InternalErrorMinerSettleFailed { worker: WorkerPublicKey }, + /// Block subsidy halved by 25%. + /// + /// This event will be followed by a [`TokenomicParametersChanged`](#variant.TokenomicParametersChanged) + /// event indicating the change of the block subsidy budget in the parameter. SubsidyBudgetHalved, /// Some internal error happened when trying to halve the subsidy InternalErrorWrongHalvingConfigured, /// Tokenomic parameter changed. + /// + /// Affected states: + /// - [`TokenomicParameters`] is updated. TokenomicParametersChanged, /// A miner settlement was dropped because the on-chain version is more up-to-date. + /// + /// This is a temporary walk-around of the mining staking design. Will be fixed by + /// StakePool v2. MinerSettlementDropped { miner: T::AccountId, v: u128, @@ -292,24 +367,41 @@ pub mod pallet { #[pallet::error] pub enum Error { + /// The transaction is sent by an unauthorized sender BadSender, - InvalidMessage, + /// Deprecated. + _InvalidMessage, + /// The worker is not registered in the registry. WorkerNotRegistered, - GatekeeperNotRegistered, + /// Deprecated + _GatekeeperNotRegistered, + /// Not permitted because the miner is already bound with another worker. DuplicateBoundMiner, + /// There's no benchmark result on the blockchain. BenchmarkMissing, + /// Miner not found. MinerNotFound, + /// Not permitted because the miner is not bound with a worker. MinerNotBound, + /// Miner is not in `Ready` state to proceed. MinerNotReady, + /// Miner is not in `Mining` state to stop mining. MinerNotMining, + /// Not permitted because the worker is not bound with a miner account. WorkerNotBound, + /// Cannot reclaim the worker because it's still in cooldown period. CoolDownNotReady, + /// Cannot start mining because there's too little stake. InsufficientStake, + /// Cannot start mining because there's too much stake (exceeds Vmax). TooMuchStake, + /// Internal error. The tokenomic parameter is not set. InternalErrorBadTokenomicParameters, + /// Not permitted because the worker is already bound with another miner account. DuplicateBoundWorker, /// Indicating the initial benchmark score is too low to start mining. BenchmarkTooLow, + /// Internal error. A miner should never start with existing stake in the storage. InternalErrorCannotStartWithExistingStake, } @@ -325,18 +417,22 @@ pub mod pallet { where BalanceOf: FixedPointConvert, { + /// Sets the cool down expiration time in seconds. + /// + /// Can only be called by root. #[pallet::weight(0)] pub fn set_cool_down_expiration(origin: OriginFor, period: u64) -> DispatchResult { ensure_root(origin)?; CoolDownPeriod::::put(period); - Self::deposit_event(Event::::CoolDownExpirationChanged(period)); + Self::deposit_event(Event::::CoolDownExpirationChanged { period }); Ok(()) } /// Unbinds a worker from the given miner (or pool sub-account). /// - /// It will trigger a force stop of mining if the miner is still in mining state. + /// It will trigger a force stop of mining if the miner is still in mining state. Anyone + /// can call it. #[pallet::weight(0)] pub fn unbind(origin: OriginFor, miner: T::AccountId) -> DispatchResult { let who = ensure_signed(origin)?; @@ -386,7 +482,9 @@ pub mod pallet { Ok(()) } - /// Updates the tokenomic parameters at the end of this block + /// Updates the tokenomic parameters at the end of this block. + /// + /// Can only be called by the tokenomic admin. #[pallet::weight(1)] pub fn update_tokenomic( origin: OriginFor, @@ -520,7 +618,7 @@ pub mod pallet { } miner_info.state = MinerState::MiningUnresponsive; Miners::::insert(&account, &miner_info); - Self::deposit_event(Event::::MinerEnterUnresponsive(account)); + Self::deposit_event(Event::::MinerEnterUnresponsive { miner: account }); Self::push_message(SystemEvent::new_worker_event( worker, WorkerEvent::MiningEnterUnresponsive, @@ -541,7 +639,7 @@ pub mod pallet { } miner_info.state = MinerState::MiningIdle; Miners::::insert(&account, &miner_info); - Self::deposit_event(Event::::MinerExitUnresponsive(account)); + Self::deposit_event(Event::::MinerExitUnresponsive { miner: account }); Self::push_message(SystemEvent::new_worker_event( worker, WorkerEvent::MiningExitUnresponsive, @@ -552,7 +650,9 @@ pub mod pallet { for info in &event.settle { // Do not crash here if Self::try_handle_settle(info, now, emit_ts).is_err() { - Self::deposit_event(Event::::InternalErrorMinerSettleFailed(info.pubkey)) + Self::deposit_event(Event::::InternalErrorMinerSettleFailed { + worker: info.pubkey, + }) } } @@ -590,7 +690,11 @@ pub mod pallet { let treasury_deposit = FixedPointConvert::from_bits(info.treasury); let imbalance = Self::withdraw_imbalance_from_subsidy_pool(treasury_deposit)?; T::OnTreasurySettled::on_unbalanced(imbalance); - Self::deposit_event(Event::::MinerSettled(account, info.v, info.payout)); + Self::deposit_event(Event::::MinerSettled { + miner: account, + v_bits: info.v, + payout_bits: info.payout, + }); } Ok(()) } @@ -626,7 +730,11 @@ pub mod pallet { let orig_stake = Stakes::::take(&miner).unwrap_or_default(); let (_returned, slashed) = miner_info.calc_final_stake(orig_stake); - Self::deposit_event(Event::::MinerReclaimed(miner, orig_stake, slashed)); + Self::deposit_event(Event::::MinerReclaimed { + miner, + original_stake: orig_stake, + slashed, + }); Ok((orig_stake, slashed)) } @@ -684,7 +792,10 @@ pub mod pallet { }, ); - Self::deposit_event(Event::::MinerBound(miner, pubkey)); + Self::deposit_event(Event::::MinerBound { + miner, + worker: pubkey, + }); Ok(()) } @@ -708,7 +819,10 @@ pub mod pallet { } MinerBindings::::remove(miner); WorkerBindings::::remove(&worker); - Self::deposit_event(Event::::MinerUnbound(miner.clone(), worker)); + Self::deposit_event(Event::::MinerUnbound { + miner: miner.clone(), + worker, + }); if notify { T::OnUnbound::on_unbound(&worker, force); } @@ -772,7 +886,7 @@ pub mod pallet { init_p: p, }, )); - Self::deposit_event(Event::::MinerStarted(miner)); + Self::deposit_event(Event::::MinerStarted { miner }); Ok(()) } @@ -806,7 +920,7 @@ pub mod pallet { worker, WorkerEvent::MiningStop, )); - Self::deposit_event(Event::::MinerStopped(miner)); + Self::deposit_event(Event::::MinerStopped { miner }); Ok(()) } @@ -1182,8 +1296,14 @@ pub mod pallet { assert_eq!( take_events().as_slice(), [ - TestEvent::PhalaMining(Event::MinerBound(1, worker_pubkey(1))), - TestEvent::PhalaMining(Event::MinerUnbound(1, worker_pubkey(1))) + TestEvent::PhalaMining(Event::MinerBound { + miner: 1, + worker: worker_pubkey(1) + }), + TestEvent::PhalaMining(Event::MinerUnbound { + miner: 1, + worker: worker_pubkey(1) + }) ] ); // Checks edge cases diff --git a/pallets/phala/src/mq.rs b/pallets/phala/src/mq.rs index 0c15c446..93ec1b1a 100644 --- a/pallets/phala/src/mq.rs +++ b/pallets/phala/src/mq.rs @@ -1,3 +1,5 @@ +//! The message queue to connect components in the network + pub use self::pallet::*; pub use frame_support::storage::generator::StorageMap as StorageMapTrait; @@ -22,7 +24,7 @@ pub mod pallet { type CallMatcher: CallMatcher; } - const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(5); #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] diff --git a/pallets/phala/src/registry.rs b/pallets/phala/src/registry.rs index 4e9c8c47..ffcb0858 100644 --- a/pallets/phala/src/registry.rs +++ b/pallets/phala/src/registry.rs @@ -1,4 +1,5 @@ -/// Public key registry for workers and contracts. +//! Manages the public key of offchain components (i.e. workers and contracts) + pub use self::pallet::*; #[frame_support::pallet] @@ -47,19 +48,23 @@ pub mod pallet { type UnixTime: UnixTime; type AttestationValidator: AttestationValidator; - /// Verify attestation, SHOULD NOT SET FALSE ON PRODUCTION !!! + /// Verify attestation + /// + /// SHOULD NOT SET TO FALSE ON PRODUCTION!!! #[pallet::constant] type VerifyPRuntime: Get; - /// Verify relaychain genesis, SHOULD NOT SET FALSE ON PRODUCTION !!! + /// Verify relaychain genesis + /// + /// SHOULD NOT SET TO FALSE ON PRODUCTION!!! #[pallet::constant] type VerifyRelaychainGenesisBlockHash: Get; - /// Origin used to administer the pallet + /// Origin used to govern the pallet type GovernanceOrigin: EnsureOrigin; } - const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(5); #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] @@ -91,6 +96,7 @@ pub mod pallet { #[pallet::storage] pub type TopicKey = StorageMap<_, Blake2_128Concat, Vec, Vec>; + /// The number of blocks to run the benchmark #[pallet::storage] pub type BenchmarkDuration = StorageValue<_, u32>; @@ -111,6 +117,7 @@ pub mod pallet { #[pallet::event] pub enum Event { + /// A new Gatekeeper is enabled on the blockchain GatekeeperAdded(WorkerPublicKey), } @@ -151,6 +158,7 @@ pub mod pallet { PRuntimeNotFound, // Additional UnknownCluster, + NotImplemented, } #[pallet::call] @@ -158,14 +166,19 @@ pub mod pallet { where T: crate::mq::Config, { + /// Sets [`BenchmarkDuration`] + /// + /// Can only be called by `GovernanceOrigin`. #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn force_set_benchmark_duration(origin: OriginFor, value: u32) -> DispatchResult { - ensure_root(origin)?; + T::GovernanceOrigin::ensure_origin(origin)?; BenchmarkDuration::::put(value); Ok(()) } /// Force register a worker with the given pubkey with sudo permission + /// + /// For test only. #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn force_register_worker( origin: OriginFor, @@ -195,6 +208,8 @@ pub mod pallet { } /// Force register a topic pubkey + /// + /// For test only. #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn force_register_topic_pubkey( origin: OriginFor, @@ -208,7 +223,7 @@ pub mod pallet { /// Register a gatekeeper. /// - /// Must be called by the Root origin. + /// Can only be called by `GovernanceOrigin`. #[pallet::weight(10_000 + T::DbWeight::get().writes(1))] pub fn register_gatekeeper( origin: OriginFor, @@ -246,10 +261,7 @@ pub mod pallet { Ok(()) } - /// Unregister a gatekeeper, must be called by gatekeeper himself - /// - /// Requirements: - // 1. `sig` is the valid signature of specific unregister message + /// Deprecated #[allow(unused_variables)] #[pallet::weight(0)] pub fn unregister_gatekeeper( @@ -257,11 +269,13 @@ pub mod pallet { gatekeeper: WorkerPublicKey, sig: [u8; 64], ) -> DispatchResult { - // TODO.shelven - panic!("unimpleneted"); + Err(Error::::NotImplemented.into()) } - /// (called by anyone on behalf of a worker) + /// Registers a worker on the blockchain + /// + /// Usually called by a bridging relayer program (`pherry` and `prb`). Can be called by + /// anyone on behalf of a worker. #[pallet::weight(0)] pub fn register_worker( origin: OriginFor, @@ -335,7 +349,9 @@ pub mod pallet { Ok(()) } - /// Registers a pRuntime image as the canonical runtime with its digest. + /// Registers a pruntime binary to [`PRuntimeAllowList`] + /// + /// Can only be called by `GovernanceOrigin`. #[pallet::weight(0)] pub fn add_pruntime(origin: OriginFor, pruntime_hash: Vec) -> DispatchResult { T::GovernanceOrigin::ensure_origin(origin)?; @@ -352,6 +368,9 @@ pub mod pallet { Ok(()) } + /// Removes a pruntime binary from [`PRuntimeAllowList`] + /// + /// Can only be called by `GovernanceOrigin`. #[pallet::weight(0)] pub fn remove_pruntime(origin: OriginFor, pruntime_hash: Vec) -> DispatchResult { T::GovernanceOrigin::ensure_origin(origin)?; @@ -371,6 +390,9 @@ pub mod pallet { Ok(()) } + /// Adds an entry in [`RelaychainGenesisBlockHashAllowList`] + /// + /// Can only be called by `GovernanceOrigin`. #[pallet::weight(0)] pub fn add_relaychain_genesis_block_hash( origin: OriginFor, @@ -390,6 +412,9 @@ pub mod pallet { Ok(()) } + /// Deletes an entry in [`RelaychainGenesisBlockHashAllowList`] + /// + /// Can only be called by `GovernanceOrigin`. #[pallet::weight(0)] pub fn remove_relaychain_genesis_block_hash( origin: OriginFor, @@ -528,9 +553,9 @@ pub mod pallet { /// Genesis config to add some genesis worker or gatekeeper for testing purpose. #[pallet::genesis_config] pub struct GenesisConfig { - /// [(identity, ecdh, operator)] + /// List of `(identity, ecdh, operator)` tuple pub workers: Vec<(WorkerPublicKey, Vec, Option)>, - /// [identity] + /// List of Gatekeeper identities pub gatekeepers: Vec, pub benchmark_duration: u32, } @@ -607,19 +632,36 @@ pub mod pallet { type Config = T; } + /// The basic information of a registered worker #[derive(Encode, Decode, TypeInfo, Debug, Clone)] pub struct WorkerInfo { - // identity + /// The identity public key of the worker pub pubkey: WorkerPublicKey, + /// The public key for ECDH communication pub ecdh_pubkey: EcdhPublicKey, - // system + /// The pruntime version of the worker upon registering pub runtime_version: u32, + /// The unix timestamp of the last updated time pub last_updated: u64, + /// The stake pool owner that can control this worker + /// + /// When initializing pruntime, the user can specify an _operator account_. Then this field + /// will be updated when the worker is being registered on the blockchain. Once it's set, + /// the worker can only be added to a stake pool if the pool owner is the same as the + /// operator. It ensures only the trusted person can control the worker. pub operator: Option, - // platform + /// The [confidence level](https://wiki.phala.network/en-us/mine/solo/1-2-confidential-level-evaluation/#confidence-level-of-a-miner) + /// of the worker pub confidence_level: u8, - // scoring + /// The performance score by benchmark + /// + /// When a worker is registered, this field is set to `None`, indicating the worker is + /// requested to run a benchmark. The benchmark lasts [`BenchmarkDuration`] blocks, and + /// this field will be updated with the score once it finishes. + /// + /// The `initial_score` is used as the baseline for mining performance test. pub initial_score: Option, + /// Deprecated pub features: Vec, } diff --git a/pallets/phala/src/stakepool.rs b/pallets/phala/src/stakepool.rs index 8d62388b..1775696a 100644 --- a/pallets/phala/src/stakepool.rs +++ b/pallets/phala/src/stakepool.rs @@ -1,3 +1,5 @@ +//! Pool for collaboratively mining staking + pub use self::pallet::*; use frame_support::traits::Currency; @@ -45,6 +47,7 @@ pub mod pallet { const STAKING_ID: LockIdentifier = *b"phala/sp"; + /// The functions to manage user's native currency lock in the Balances pallet pub trait Ledger { /// Increases the locked amount for a user /// @@ -89,7 +92,7 @@ pub mod pallet { type BackfillOrigin: EnsureOrigin; } - const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(5); #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] @@ -159,35 +162,119 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// \[owner, pid\] - PoolCreated(T::AccountId, u64), - /// The real commission ratio is commission/1_000_000u32. \[pid, commission\] - PoolCommissionSet(u64, u32), - /// \[pid, cap\] - PoolCapacitySet(u64, BalanceOf), - /// \[pid, worker\] - PoolWorkerAdded(u64, WorkerPublicKey), - /// \[pid, user, amount\] - Contribution(u64, T::AccountId, BalanceOf), - /// \[pid, user, amount\] - Withdrawal(u64, T::AccountId, BalanceOf), - /// \[pid, user, amount\] - RewardsWithdrawn(u64, T::AccountId, BalanceOf), - /// \[pid, amount\] - PoolSlashed(u64, BalanceOf), - /// \[pid, account, amount\] - SlashSettled(u64, T::AccountId, BalanceOf), - /// Some reward is dismissed because the worker is no longer bound to a pool. \[worker, amount\] - RewardDismissedNotInPool(WorkerPublicKey, BalanceOf), - /// Some reward is dismissed because the pool doesn't have any share. \[pid, amount\] - RewardDismissedNoShare(u64, BalanceOf), - /// Some reward is dismissed because the amount is too tiny (dust). \[pid, amount\] - RewardDismissedDust(u64, BalanceOf), - /// Some dust stake is removed. \[user, amount\] - DustRemoved(T::AccountId, BalanceOf), + /// A pool is created under an owner + /// + /// Affected states: + /// - a new entry in [`StakePools`] with the pid + PoolCreated { owner: T::AccountId, pid: u64 }, + /// The commission of a pool is updated + /// + /// The commission ratio is represented by an integer. The real value is + /// `commission / 1_000_000u32`. + /// + /// Affected states: + /// - the `payout_commission` field in [`StakePools`] is updated + PoolCommissionSet { pid: u64, commission: u32 }, + /// The stake capacity of the pool is updated + /// + /// Affected states: + /// - the `cap` field in [`StakePools`] is updated + PoolCapacitySet { pid: u64, cap: BalanceOf }, + /// A worker is added to the pool + /// + /// Affected states: + /// - the `worker` is added to the vector `workers` in [`StakePools`] + /// - the worker in the [`WorkerAssignments`] is pointed to `pid` + /// - the worker-miner binding is updated in `mining` pallet ([`WorkerBindings`](mining::pallet::WorkerBindings), + /// [`MinerBindings`](mining::pallet::MinerBindings)) + PoolWorkerAdded { pid: u64, worker: WorkerPublicKey }, + /// Someone contributed to a pool + /// + /// Affected states: + /// - the stake related fields in [`StakePools`] + /// - the user staking account at [`PoolStakers`] + /// - the locking ledger of the contributor at [`StakeLedger`] + /// - when there was any request in the withdraw queue, the action may trigger withdrawals + /// ([`Withdrawal`](#variant.Withdrawal) event) + Contribution { + pid: u64, + user: T::AccountId, + amount: BalanceOf, + }, + /// Some stake was withdrawn from a pool + /// + /// The lock in [`Balances`](pallet_balances::pallet::Pallet) is updated to release the + /// locked stake. + /// + /// Affected states: + /// - the stake related fields in [`StakePools`] + /// - the user staking account at [`PoolStakers`] + /// - the locking ledger of the contributor at [`StakeLedger`] + Withdrawal { + pid: u64, + user: T::AccountId, + amount: BalanceOf, + }, + /// Pending rewards were withdrawn by a user + /// + /// The reward and slash accumulator is resolved, and the reward is sent to the user + /// account. + /// + /// Affected states: + /// - the stake related fields in [`StakePools`] + /// - the user staking account at [`PoolStakers`] + RewardsWithdrawn { + pid: u64, + user: T::AccountId, + amount: BalanceOf, + }, + /// The pool received a slash event from one of its workers (currently disabled) + /// + /// The slash is accured to the pending slash accumulator. + PoolSlashed { pid: u64, amount: BalanceOf }, + /// Some slash is actually settled to a contributor (currently disabled) + SlashSettled { + pid: u64, + user: T::AccountId, + amount: BalanceOf, + }, + /// Some reward is dismissed because the worker is no longer bound to a pool + /// + /// There's no affected state. + RewardDismissedNotInPool { + worker: WorkerPublicKey, + amount: BalanceOf, + }, + /// Some reward is dismissed because the pool doesn't have any share + /// + /// There's no affected state. + RewardDismissedNoShare { pid: u64, amount: BalanceOf }, + /// Some reward is dismissed because the amount is too tiny (dust) + /// + /// There's no affected state. + RewardDismissedDust { pid: u64, amount: BalanceOf }, + /// Some dust stake is removed + /// + /// Triggered when the remaining stake of a user is too small after withdrawal or slash. + /// + /// Affected states: + /// - the balance of the locking ledger of the contributor at [`StakeLedger`] is set to 0 + /// - the user's dust stake is moved to treasury + DustRemoved { + user: T::AccountId, + amount: BalanceOf, + }, /// A worker is removed from a pool. + /// + /// Affected states: + /// - the worker item in [`WorkerAssignments`] is removed + /// - the worker is removed from the [`StakePools`] item PoolWorkerRemoved { pid: u64, worker: WorkerPublicKey }, - /// A withdrawal request is queued by adding or replacing an old one. + /// A withdrawal request is inserted to a queue + /// + /// Affected states: + /// - a new item is inserted to or an old item is being replaced by the new item in the + /// withdraw queue in [`StakePools`] WithdrawalQueued { pid: u64, user: T::AccountId, @@ -197,23 +284,40 @@ pub mod pallet { #[pallet::error] pub enum Error { + /// The worker is not registered in the registry when adding to the pool WorkerNotRegistered, + /// The worker doesn't have a valid benchmark when adding to the pool BenchmarkMissing, + /// The worker is already added to the pool WorkerExists, + /// The target worker is not in the pool WorkerDoesNotExist, + /// The worker is already added to another pool WorkerInAnotherPool, + /// The owner of the pool doesn't have the access to the worker + /// + /// The access to a worker is granted by it's `operator` parameter set by `register_worker` UnauthorizedOperator, + /// The caller is not the owner of the pool UnauthorizedPoolOwner, - /// The stake capacity is set too low for the existing stake + /// The stake capacity is set too low to cover the existing stake InadequateCapacity, + /// The stake added to a pool exceeds its capacity StakeExceedsCapacity, + /// The specified pool doesn't exist PoolDoesNotExist, _PoolIsBusy, + /// The contributed stake is smaller than the minimum threshold InsufficientContribution, + /// Trying to contribute more than the available balance InsufficientBalance, + /// The user doesn't have stake in a pool PoolStakeNotFound, + /// Cannot start mining because there's no enough free stake InsufficientFreeStake, + /// The withdrawal amount is too small (considered as dust) InvalidWithdrawalAmount, + /// Couldn't bind worker and the pool mining subaccount FailedToBindMinerAndWorker, /// Internal error: Cannot withdraw from the subsidy pool. This should never happen. InternalSubsidyPoolCannotWithdraw, @@ -222,6 +326,7 @@ pub mod pallet { /// In this case, no more funds can be contributed to the pool until all the pending slash /// has been resolved. PoolBankrupt, + /// There's no pending reward to claim NoRewardToClaim, /// The StakePool is not enabled yet. FeatureNotEnabled, @@ -275,7 +380,7 @@ pub mod pallet { }, ); PoolCount::::put(pid + 1); - Self::deposit_event(Event::::PoolCreated(owner, pid)); + Self::deposit_event(Event::::PoolCreated { owner, pid }); Ok(()) } @@ -333,7 +438,10 @@ pub mod pallet { workers.push(pubkey); StakePools::::insert(&pid, &pool_info); WorkerAssignments::::insert(&pubkey, pid); - Self::deposit_event(Event::::PoolWorkerAdded(pid, pubkey)); + Self::deposit_event(Event::::PoolWorkerAdded { + pid, + worker: pubkey, + }); Ok(()) } @@ -397,7 +505,7 @@ pub mod pallet { pool_info.cap = Some(cap); StakePools::::insert(&pid, &pool_info); - Self::deposit_event(Event::::PoolCapacitySet(pid, cap)); + Self::deposit_event(Event::::PoolCapacitySet { pid, cap }); Ok(()) } @@ -419,10 +527,10 @@ pub mod pallet { pool_info.payout_commission = Some(payout_commission); StakePools::::insert(&pid, &pool_info); - Self::deposit_event(Event::::PoolCommissionSet( + Self::deposit_event(Event::::PoolCommissionSet { pid, - payout_commission.deconstruct(), - )); + commission: payout_commission.deconstruct(), + }); Ok(()) } @@ -461,7 +569,11 @@ pub mod pallet { if let Some(user_info) = user_info { PoolStakers::::insert(&info_key, &user_info); } - Self::deposit_event(Event::::RewardsWithdrawn(pid, who, rewards)); + Self::deposit_event(Event::::RewardsWithdrawn { + pid, + user: who, + amount: rewards, + }); Ok(()) } @@ -472,6 +584,7 @@ pub mod pallet { /// 1. The pool exists /// 2. After the deposit, the pool doesn't reach the cap #[pallet::weight(0)] + #[frame_support::transactional] pub fn contribute(origin: OriginFor, pid: u64, amount: BalanceOf) -> DispatchResult { let who = ensure_signed(origin)?; let a = amount; // Alias to reduce confusion in the code below @@ -485,13 +598,6 @@ pub mod pallet { ensure!(free - locked >= a, Error::::InsufficientBalance); let mut pool_info = Self::ensure_pool(pid)?; - if let Some(cap) = pool_info.cap { - ensure!( - cap.saturating_sub(pool_info.total_stake) >= a, - Error::::StakeExceedsCapacity - ); - } - // We don't really want to allow to contribute to a bankrupt StakePool. It can avoid // a lot of weird edge cases when dealing with pending slash. ensure!( @@ -528,9 +634,21 @@ pub mod pallet { // We have new free stake now, try to handle the waiting withdraw queue Self::try_process_withdraw_queue(&mut pool_info); + // Post-check to ensure the total stake doesn't exceed the cap + if let Some(cap) = pool_info.cap { + ensure!( + pool_info.total_stake <= cap, + Error::::StakeExceedsCapacity + ); + } + // Persist StakePools::::insert(&pid, &pool_info); - Self::deposit_event(Event::::Contribution(pid, who, a)); + Self::deposit_event(Event::::Contribution { + pid, + user: who, + amount: a, + }); Ok(()) } @@ -753,7 +871,10 @@ pub mod pallet { ) { if rewards > Zero::zero() { if balance_close_to_zero(pool_info.total_shares) { - Self::deposit_event(Event::::RewardDismissedNoShare(pool_info.pid, rewards)); + Self::deposit_event(Event::::RewardDismissedNoShare { + pid: pool_info.pid, + amount: rewards, + }); return; } let commission = pool_info.payout_commission.unwrap_or_default() * rewards; @@ -762,10 +883,10 @@ pub mod pallet { if is_nondust_balance(to_distribute) { pool_info.distribute_reward(to_distribute); } else if to_distribute > Zero::zero() { - Self::deposit_event(Event::::RewardDismissedDust( - pool_info.pid, - to_distribute, - )); + Self::deposit_event(Event::::RewardDismissedDust { + pid: pool_info.pid, + amount: to_distribute, + }); } } } @@ -783,7 +904,10 @@ pub mod pallet { // and creating a logical pending slash. The actual slash happens with the pending // slash to individuals is settled. pool_info.slash(slashed); - Self::deposit_event(Event::::PoolSlashed(pid, slashed)); + Self::deposit_event(Event::::PoolSlashed { + pid, + amount: slashed, + }); } // With the worker being cleaned, those stake now are free @@ -839,11 +963,11 @@ pub mod pallet { .remove_stake(user_info, withdrawing_shares) .expect("There are enough withdrawing_shares; qed."); Self::ledger_reduce(&user_info.user, reduced, dust); - Self::deposit_event(Event::::Withdrawal( - pool_info.pid, - user_info.user.clone(), - reduced, - )); + Self::deposit_event(Event::::Withdrawal { + pid: pool_info.pid, + user: user_info.user.clone(), + amount: reduced, + }); } // Some locked assets haven't been withdrawn (unlocked) to user, add it to the withdraw // queue. When the pool has free stake again, the withdrawal will be fulfilled. @@ -918,11 +1042,11 @@ pub mod pallet { withdraw.shares = shares; // Withdraw the funds Self::ledger_reduce(&user_info.user, reduced, dust); - Self::deposit_event(Event::::Withdrawal( - pool_info.pid, - user_info.user.clone(), - reduced, - )); + Self::deposit_event(Event::::Withdrawal { + pid: pool_info.pid, + user: user_info.user.clone(), + amount: reduced, + }); // Update the pending reward after changing the staked amount pool_info.reset_pending_reward(&mut user_info); PoolStakers::::insert(&info_key, &user_info); @@ -958,7 +1082,10 @@ pub mod pallet { let (imbalance, _remaining) = ::Currency::slash(who, dust); let actual_removed = imbalance.peek(); T::OnSlashed::on_unbalanced(imbalance); - Self::deposit_event(Event::::DustRemoved(who.clone(), actual_removed)); + Self::deposit_event(Event::::DustRemoved { + user: who.clone(), + amount: actual_removed, + }); } } @@ -1025,11 +1152,11 @@ pub mod pallet { // Dust is not considered because it's already merged into the slash if // presents. Self::ledger_reduce(&user.user, actual_slashed, Zero::zero()); - Self::deposit_event(Event::::SlashSettled( - pool.pid, - user.user.clone(), - actual_slashed, - )); + Self::deposit_event(Event::::SlashSettled { + pid: pool.pid, + user: user.user.clone(), + amount: actual_slashed, + }); } _ => (), } @@ -1068,6 +1195,11 @@ pub mod pallet { } WithdrawalTimestamps::::put(&t); } + + pub(crate) fn migration_remove_assignments() -> Weight { + let writes = WorkerAssignments::::drain().count(); + T::DbWeight::get().writes(writes as _) + } } impl mining::OnReward for Pallet @@ -1086,10 +1218,10 @@ pub mod pallet { let pid = match WorkerAssignments::::get(&info.pubkey) { Some(pid) => pid, None => { - Self::deposit_event(Event::::RewardDismissedNotInPool( - info.pubkey, - reward, - )); + Self::deposit_event(Event::::RewardDismissedNotInPool { + worker: info.pubkey, + amount: reward, + }); return; } }; @@ -1176,6 +1308,7 @@ pub mod pallet { .expect("Decoding zero-padded account id should always succeed; qed") } + /// The state of a pool #[derive(Encode, Decode, TypeInfo, Clone, PartialEq, Eq, Default, RuntimeDebug)] pub struct PoolInfo { /// Pool ID @@ -1183,20 +1316,42 @@ pub mod pallet { /// The owner of the pool pub owner: AccountId, /// The commission the pool owner takes + /// + /// For example, 10% commission means 10% of the miner reward goes to the pool owner, and + /// the remaining 90% is distributed to the contributors. Setting to `None` means a + /// commission of 0%. pub payout_commission: Option, /// Claimable owner reward + /// + /// Whenver a miner gets some reward, the commission the pool taken goes to here. The owner + /// can claim their reward at any time. pub owner_reward: Balance, - /// The hard cap of the pool + /// The hard capacity of the pool + /// + /// When it's set, the totals stake a pool can receive will not exceed this capacity. pub cap: Option, - /// The reward accumulator + /// The reward [accumulator](crate::utils::accumulator) + /// + /// An individual user's reward is tracked by [`reward_acc`](PoolInfo::reward_acc), their + /// [`shares`](UserStakeInfo::shares) and the [`reward_debt`](UserStakeInfo::reward_debt). pub reward_acc: CodecFixedPoint, - /// Total shares. Cannot be dust. + /// Total shares + /// + /// It tracks the total number of shared of all the contributors. Guaranteed to be + /// non-dust. pub total_shares: Balance, - /// Total stake. Cannot be dust. + /// Total stake + /// + /// It tracks the total number of the stake the pool received. Guaranteed to be non-dust. pub total_stake: Balance, - /// Total free stake. Can be dust. + /// Total free stake + /// + /// It tracks the total free stake (not used by any miner) in the pool. Can be dust. pub free_stake: Balance, - /// Releasing stake (will be unlocked after worker reclaiming) + /// Releasing stake + /// + /// It tracks the stake that will be unlocked in the future. It's the sum of all the + /// cooling down miners' remaining stake. pub releasing_stake: Balance, /// Bound workers pub workers: Vec, @@ -1351,6 +1506,8 @@ pub mod pallet { /// Settles the pending slash for a pool user. /// + /// The slash is + /// /// Returns the slashed amount if succeeded, otherwise None. fn settle_slash(&self, user: &mut UserStakeInfo) -> Option { let price = self.share_price()?; @@ -1449,23 +1606,30 @@ pub mod pallet { } } + /// A user's staking info #[derive(Encode, Decode, TypeInfo, Clone, PartialEq, Eq, RuntimeDebug)] pub struct UserStakeInfo { - /// User account + /// User's address pub user: AccountId, - /// The actual locked stake + /// The actual locked stake in the pool pub locked: Balance, - /// The share in the pool. Cannot be dust. + /// The share in the pool /// - /// Invariant must hold: - /// StakePools[pid].total_stake == sum(PoolStakers[(pid, user)].shares) + /// Guaranteed to be non-dust. Invariant must hold: + /// - `StakePools[pid].total_stake == sum(PoolStakers[(pid, user)].shares)` pub shares: Balance, - /// Claimable rewards + /// Resolved claimable rewards + /// + /// It's accumulated by resolving "pending stake" from the reward + /// [accumulator](crate::utils::accumulator). pub available_rewards: Balance, - /// The debt of a user's stake subject to the pool reward accumulator + /// The debt of a user's stake + /// + /// It's subject to the pool reward [accumulator](crate::utils::accumulator). pub reward_debt: Balance, } + /// A withdraw request, usually stored in the withdrawal queue #[derive(Encode, Decode, TypeInfo, Clone, PartialEq, Eq, RuntimeDebug)] pub struct WithdrawInfo { /// The withdrawal requester @@ -1516,8 +1680,8 @@ pub mod pallet { assert_matches!( take_events().as_slice(), [ - TestEvent::PhalaStakePool(Event::PoolCreated(1, 0)), - TestEvent::PhalaStakePool(Event::PoolCreated(1, 1)), + TestEvent::PhalaStakePool(Event::PoolCreated { owner: 1, pid: 0 }), + TestEvent::PhalaStakePool(Event::PoolCreated { owner: 1, pid: 1 }), ] ); assert_eq!( @@ -1720,15 +1884,9 @@ pub mod pallet { fn test_pool_cap() { new_test_ext().execute_with(|| { set_block_1(); - let worker1 = worker_pubkey(1); - assert_ok!(PhalaRegistry::force_register_worker( - Origin::root(), - worker1.clone(), - ecdh_pubkey(1), - Some(1) - )); + setup_workers(1); + setup_pool_with_workers(1, &[1]); // pid = 0 - assert_ok!(PhalaStakePool::create(Origin::signed(1))); // pid = 0 assert_eq!(PhalaStakePool::stake_pools(0).unwrap().cap, None); // Pool existence assert_noop!( @@ -1771,6 +1929,29 @@ pub mod pallet { PhalaStakePool::contribute(Origin::signed(2), 0, 900 * DOLLARS), Error::::StakeExceedsCapacity, ); + + // Can stake exceed the cap to swap the withdrawing stake out, as long as the cap + // can be maintained after the contribution + assert_ok!(PhalaStakePool::start_mining( + Origin::signed(1), + 0, + worker_pubkey(1), + 1000 * DOLLARS + )); + assert_ok!(PhalaStakePool::withdraw( + Origin::signed(1), + 0, + 1000 * DOLLARS + )); + assert_noop!( + PhalaStakePool::contribute(Origin::signed(2), 0, 1001 * DOLLARS), + Error::::StakeExceedsCapacity + ); + assert_ok!(PhalaStakePool::contribute( + Origin::signed(2), + 0, + 1000 * DOLLARS + )); }); } @@ -1896,10 +2077,14 @@ pub mod pallet { assert_matches!( ev.as_slice(), [ - TestEvent::PhalaMining(mining::Event::MinerSettled(_, v, 0)), - TestEvent::PhalaMining(mining::Event::MinerStopped(_)), - TestEvent::PhalaMining(mining::Event::MinerReclaimed(_, _, _)), - TestEvent::PhalaStakePool(Event::PoolSlashed(0, slashed)), + TestEvent::PhalaMining(mining::Event::MinerSettled { + miner: _, + v_bits: v, + payout_bits:0 + }), + TestEvent::PhalaMining(mining::Event::MinerStopped { miner: _ }), + TestEvent::PhalaMining(mining::Event::MinerReclaimed { .. }), + TestEvent::PhalaStakePool(Event::PoolSlashed { pid: 0, amount: slashed }), ] if FixedPoint::from_bits(*v) == ve / 2 && *slashed == 250000000000000 @@ -1923,12 +2108,20 @@ pub mod pallet { who: 1, amount: 50000000000000 }), - TestEvent::PhalaStakePool(Event::SlashSettled(0, 1, 50000000000000)), + TestEvent::PhalaStakePool(Event::SlashSettled { + pid: 0, + user: 1, + amount: 50000000000000 + }), TestEvent::Balances(pallet_balances::Event::Slashed { who: 2, amount: 200000000000000 }), - TestEvent::PhalaStakePool(Event::SlashSettled(0, 2, 200000000000000)) + TestEvent::PhalaStakePool(Event::SlashSettled { + pid: 0, + user: 2, + amount: 200000000000000 + }) ] ); // Check slash settled. Remaining: 50 PHA, 200 PHA @@ -1971,14 +2164,21 @@ pub mod pallet { assert_matches!( ev.as_slice(), [ - TestEvent::PhalaMining(mining::Event::MinerSettled(_, _, 0)), - TestEvent::PhalaMining(mining::Event::MinerStopped(_)), - TestEvent::PhalaMining(mining::Event::MinerReclaimed( - _, - 500000000000000, - 250000000000000 - )), - TestEvent::PhalaStakePool(Event::PoolSlashed(0, 250000000000000)), + TestEvent::PhalaMining(mining::Event::MinerSettled { + miner: _, + v_bits: _, + payout_bits: 0 + }), + TestEvent::PhalaMining(mining::Event::MinerStopped { miner: _ }), + TestEvent::PhalaMining(mining::Event::MinerReclaimed { + miner: _, + original_stake: 500000000000000, + slashed: 250000000000000 + }), + TestEvent::PhalaStakePool(Event::PoolSlashed { + pid: 0, + amount: 250000000000000 + }), ] ); // Withdraw & check amount @@ -2010,22 +2210,46 @@ pub mod pallet { who: 1, amount: 25000000000000 }), - TestEvent::PhalaStakePool(Event::SlashSettled(0, 1, 25000000000000)), - TestEvent::PhalaStakePool(Event::Withdrawal(0, 1, 25000000000000)), + TestEvent::PhalaStakePool(Event::SlashSettled { + pid: 0, + user: 1, + amount: 25000000000000 + }), + TestEvent::PhalaStakePool(Event::Withdrawal { + pid: 0, + user: 1, + amount: 25000000000000 + }), // Account2: ~100 PHA remaining TestEvent::Balances(pallet_balances::Event::Slashed { who: 2, amount: 100000000000000 }), - TestEvent::PhalaStakePool(Event::SlashSettled(0, 2, 100000000000000)), - TestEvent::PhalaStakePool(Event::Withdrawal(0, 2, 100000000000000)), + TestEvent::PhalaStakePool(Event::SlashSettled { + pid: 0, + user: 2, + amount: 100000000000000 + }), + TestEvent::PhalaStakePool(Event::Withdrawal { + pid: 0, + user: 2, + amount: 100000000000000 + }), // Account1: ~125 PHA remaining TestEvent::Balances(pallet_balances::Event::Slashed { who: 3, amount: 125000000000001 }), - TestEvent::PhalaStakePool(Event::SlashSettled(0, 3, 125000000000001)), - TestEvent::PhalaStakePool(Event::Withdrawal(0, 3, 125000000000000)) + TestEvent::PhalaStakePool(Event::SlashSettled { + pid: 0, + user: 3, + amount: 125000000000001 + }), + TestEvent::PhalaStakePool(Event::Withdrawal { + pid: 0, + user: 3, + amount: 125000000000000 + }) ] ); }); @@ -2119,7 +2343,11 @@ pub mod pallet { to: 1, amount: 100 * DOLLARS }), - TestEvent::PhalaStakePool(Event::RewardsWithdrawn(0, 1, 100 * DOLLARS)) + TestEvent::PhalaStakePool(Event::RewardsWithdrawn { + pid: 0, + user: 1, + amount: 100 * DOLLARS + }) ] ); let pool = PhalaStakePool::stake_pools(0).unwrap(); @@ -2184,11 +2412,11 @@ pub mod pallet { )); assert_eq!( take_events().as_slice(), - [TestEvent::PhalaStakePool(Event::Withdrawal( - 0, - 1, - 400 * DOLLARS - ))] + [TestEvent::PhalaStakePool(Event::Withdrawal { + pid: 0, + user: 1, + amount: 400 * DOLLARS + })] ); let staker1 = PhalaStakePool::pool_stakers((0, 1)).unwrap(); let staker2 = PhalaStakePool::pool_stakers((0, 2)).unwrap(); @@ -2228,7 +2456,10 @@ pub mod pallet { let ev = take_events(); assert_eq!( ev, - vec![TestEvent::PhalaStakePool(Event::RewardDismissedDust(0, 99))] + vec![TestEvent::PhalaStakePool(Event::RewardDismissedDust { + pid: 0, + amount: 99 + })] ); }); } @@ -2252,10 +2483,10 @@ pub mod pallet { let ev = take_events(); assert_eq!( ev, - vec![TestEvent::PhalaStakePool(Event::RewardDismissedNoShare( - 0, - 500 * DOLLARS - ))] + vec![TestEvent::PhalaStakePool(Event::RewardDismissedNoShare { + pid: 0, + amount: 500 * DOLLARS + })] ); // Simulate the worker is already unbound assert_ok!(PhalaStakePool::remove_worker( @@ -2273,10 +2504,10 @@ pub mod pallet { let ev = take_events(); assert_eq!( ev, - vec![TestEvent::PhalaStakePool(Event::RewardDismissedNotInPool( - worker_pubkey(1), - 500 * DOLLARS - ))] + vec![TestEvent::PhalaStakePool(Event::RewardDismissedNotInPool { + worker: worker_pubkey(1), + amount: 500 * DOLLARS + })] ); }); } @@ -2394,8 +2625,16 @@ pub mod pallet { assert_eq!( take_events().as_slice(), [ - TestEvent::PhalaStakePool(Event::Withdrawal(0, 2, 1 * DOLLARS)), - TestEvent::PhalaStakePool(Event::Contribution(0, 1, 1 * DOLLARS)) + TestEvent::PhalaStakePool(Event::Withdrawal { + pid: 0, + user: 2, + amount: 1 * DOLLARS + }), + TestEvent::PhalaStakePool(Event::Contribution { + pid: 0, + user: 1, + amount: 1 * DOLLARS + }) ] ); let pool = PhalaStakePool::stake_pools(0).unwrap(); @@ -2441,11 +2680,11 @@ pub mod pallet { PhalaStakePool::handle_reclaim(0, 100 * DOLLARS, 0); assert_eq!( take_events().as_slice(), - [TestEvent::PhalaStakePool(Event::Withdrawal( - 0, - 2, - 100 * DOLLARS - )),] + [TestEvent::PhalaStakePool(Event::Withdrawal { + pid: 0, + user: 2, + amount: 100 * DOLLARS + }),] ); let pool = PhalaStakePool::stake_pools(0).unwrap(); let staker1 = PhalaStakePool::pool_stakers((0, 1)).unwrap(); @@ -2462,21 +2701,40 @@ pub mod pallet { assert_eq!( take_events().as_slice(), [ - TestEvent::PhalaStakePool(Event::PoolSlashed(0, 100 * DOLLARS)), + TestEvent::PhalaStakePool(Event::PoolSlashed { + pid: 0, + amount: 100 * DOLLARS + }), // Staker 2 got 75% * 99 PHA back TestEvent::Balances(pallet_balances::Event::Slashed { who: 2, amount: 99_750000000000 }), - TestEvent::PhalaStakePool(Event::SlashSettled(0, 2, 99_750000000000)), - TestEvent::PhalaStakePool(Event::Withdrawal(0, 2, 74_250000000000)), + TestEvent::PhalaStakePool(Event::SlashSettled { + pid: 0, + user: 2, + amount: 99_750000000000 + }), + TestEvent::PhalaStakePool(Event::Withdrawal { + pid: 0, + user: 2, + amount: 74_250000000000 + }), // Staker 1 got 75% * 1 PHA back TestEvent::Balances(pallet_balances::Event::Slashed { who: 1, amount: 250000000000 }), - TestEvent::PhalaStakePool(Event::SlashSettled(0, 1, 250000000000)), - TestEvent::PhalaStakePool(Event::Withdrawal(0, 1, 750000000000)), + TestEvent::PhalaStakePool(Event::SlashSettled { + pid: 0, + user: 1, + amount: 250000000000 + }), + TestEvent::PhalaStakePool(Event::Withdrawal { + pid: 0, + user: 1, + amount: 750000000000 + }), ] ); let pool = PhalaStakePool::stake_pools(0).unwrap(); @@ -2724,13 +2982,21 @@ pub mod pallet { to: 1, amount: 50000000000000 }), - TestEvent::PhalaStakePool(Event::RewardsWithdrawn(0, 1, 50000000000000)), + TestEvent::PhalaStakePool(Event::RewardsWithdrawn { + pid: 0, + user: 1, + amount: 50000000000000 + }), TestEvent::Balances(pallet_balances::Event::Transfer { from: _, to: 2, amount: 49999999999999 }), - TestEvent::PhalaStakePool(Event::RewardsWithdrawn(0, 2, 49999999999999)) + TestEvent::PhalaStakePool(Event::RewardsWithdrawn { + pid: 0, + user: 2, + amount: 49999999999999 + }) ] ); }); @@ -2776,10 +3042,10 @@ pub mod pallet { )); assert_eq!( take_events().as_slice(), - [TestEvent::PhalaStakePool(Event::PoolCommissionSet( - 0, - 1000_000u32 * 50 / 100 - ))] + [TestEvent::PhalaStakePool(Event::PoolCommissionSet { + pid: 0, + commission: 1000_000u32 * 50 / 100 + })] ); assert_ok!(PhalaStakePool::add_worker( Origin::signed(1), diff --git a/pallets/phala/src/utils/accumulator.rs b/pallets/phala/src/utils/accumulator.rs index 21dcaf3a..9ab9b106 100644 --- a/pallets/phala/src/utils/accumulator.rs +++ b/pallets/phala/src/utils/accumulator.rs @@ -1,3 +1,5 @@ +//! Implements accumulator algorithm for passive reward distribution. + use fixed::types::U64F64 as FixedPoint; use crate::balance_convert::{mul, FixedPointConvert}; diff --git a/pallets/phala/src/utils/mod.rs b/pallets/phala/src/utils/mod.rs index 9256f62a..6b7be01b 100644 --- a/pallets/phala/src/utils/mod.rs +++ b/pallets/phala/src/utils/mod.rs @@ -1,4 +1,4 @@ -pub(crate) mod accumulator; +pub mod accumulator; pub(crate) mod attestation; pub(crate) mod balance_convert; pub mod constants;