From b5b7cd81dfcb9dde2bb03a0ccc5d30bc797a8a3f Mon Sep 17 00:00:00 2001 From: Brian Wu Date: Sat, 29 Jun 2024 11:44:22 +0800 Subject: [PATCH] Implement the calculate spower offchain feature (#944) Implement the feature to support spower calculation offchain, this needs to work together with the crust-spower service https://github.com/crustio/crust-spower. Main changes: swork::report_works doesn't invoke market::upsert_replicas and market::delete_replicas anymore. The crust-spower service will index the work reports from chain, and aggregate multiple work reports, then call the newly added market::update_replicas extrinsic to update the replicas data in batch. Update the market::calculate_rewards extrinsic to only liquidate, renew, or close file, but do not update replicas and spower anymore. The crust-spower service will index the market::FilesV2 data from chain, and perform spower calculation for changed files in batch, then call the newly added swork::update_spower extrinsic to update the sworker spower and file spower. The newly added market::update_replicas extrinsic and swork::update_spower extrinsic can only be called by specific register spower superior account, the account need to be set Unit tests have been updated per these changes. --- .gitignore | 3 + cstrml/market/src/lib.rs | 583 ++++++++++++------- cstrml/market/src/mock.rs | 39 +- cstrml/market/src/tests.rs | 821 ++++++++++++++------------- cstrml/market/src/weight.rs | 5 + cstrml/staking/src/mock.rs | 5 +- cstrml/swork/benchmarking/src/lib.rs | 2 +- cstrml/swork/src/lib.rs | 188 ++++-- cstrml/swork/src/mock.rs | 42 +- cstrml/swork/src/tests.rs | 162 +++++- cstrml/swork/src/weight.rs | 7 + primitives/src/constants.rs | 2 +- primitives/src/traits.rs | 18 +- runtime/src/lib.rs | 2 +- 14 files changed, 1196 insertions(+), 683 deletions(-) diff --git a/.gitignore b/.gitignore index 1250f749..46351ece 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ ## Integration test db/ + +# logs +*.log \ No newline at end of file diff --git a/cstrml/market/src/lib.rs b/cstrml/market/src/lib.rs index ac75fd3e..6e9c4cbe 100644 --- a/cstrml/market/src/lib.rs +++ b/cstrml/market/src/lib.rs @@ -4,16 +4,18 @@ #![cfg_attr(not(feature = "std"), no_std)] #![feature(option_result_contains)] +use core::option::Option::None; + use codec::{Decode, Encode}; use frame_support::{ decl_event, decl_module, decl_storage, decl_error, - dispatch::DispatchResult, ensure, + dispatch::{DispatchResult, DispatchResultWithPostInfo}, ensure, traits::{ Currency, ReservableCurrency, Get, LockableCurrency, ExistenceRequirement, ExistenceRequirement::{AllowDeath, KeepAlive}, WithdrawReasons, Imbalance }, - weights::Weight + weights::{Weight, Pays} }; use sp_std::{prelude::*, convert::TryInto, collections::btree_set::BTreeSet, collections::btree_map::BTreeMap}; use frame_system::{self as system, ensure_signed, ensure_root}; @@ -34,12 +36,9 @@ mod tests; pub mod benchmarking; use primitives::{ - MerkleRoot, BlockNumber, SworkerAnchor, - constants::market::*, - traits::{ - UsableCurrency, MarketInterface, - SworkerInterface, BenefitInterface - } + constants::market::*, traits::{ + BenefitInterface, MarketInterface, SworkerInterface, UsableCurrency + }, BlockNumber, MerkleRoot, ReportSlot, SworkerAnchor }; pub(crate) const LOG_TARGET: &'static str = "market"; @@ -62,6 +61,7 @@ pub trait WeightInfo { fn place_storage_order() -> Weight; fn calculate_reward() -> Weight; fn reward_merchant() -> Weight; + fn update_replicas() -> Weight; } #[derive(Debug, PartialEq, Encode, Decode, Default, Clone)] @@ -106,124 +106,25 @@ pub struct Replica { pub created_at: Option } +#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, Default)] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +pub struct ReplicaToUpdate { + pub reporter: AccountId, + pub owner: AccountId, + pub sworker_anchor: SworkerAnchor, + pub report_slot: ReportSlot, + pub report_block: BlockNumber, + pub valid_at: BlockNumber, + pub is_added: bool +} +type ReplicaToUpdateOf = ReplicaToUpdate<::AccountId>; + type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type PositiveImbalanceOf = <::Currency as Currency<::AccountId>>::PositiveImbalance; type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; impl MarketInterface<::AccountId, BalanceOf> for Module { - /// Upsert new replica - /// Accept id(who, anchor), reported_file_size, cid, valid_at and maybe_member - /// Returns the real storage power of this file and whether this file is in the market system - /// storage power is decided by market - fn upsert_replica(who: &::AccountId, - owner: ::AccountId, - cid: &MerkleRoot, - reported_file_size: u64, - anchor: &SworkerAnchor, - valid_at: BlockNumber - ) -> (u64, bool) { - // Judge if file_info.file_size == reported_file_size or not - Self::maybe_upsert_file_size(who, cid, reported_file_size); - - // `is_counted` is a concept in swork-side, which means if this `cid`'s `storage power` size is counted by `(who, anchor)` - // if the file doesn't exist/exceed-replicas(aka. is_counted == false), return false(doesn't increase storage power) cause it's junk. - // if the file exist, is_counted == true, will change it later. - let mut spower: u64 = 0; - let mut is_valid_cid: bool = false; - if let Some(mut file_info) = >::get(cid) { - is_valid_cid = true; - // 1. Check if the length of the groups exceed MAX_REPLICAS or not - if file_info.replicas.len() < MAX_REPLICAS { - // 2. Check if the file is stored by other members - if !file_info.replicas.contains_key(&owner) { - let new_replica = Replica { - who: who.clone(), - valid_at, - anchor: anchor.clone(), - is_reported: true, - // set created_at to some - created_at: Some(valid_at) - }; - file_info.replicas.insert(owner.clone(), new_replica); - file_info.reported_replica_count += 1; - // Always return the file size for this [who] reported first time - spower = file_info.file_size; - - if file_info.remaining_paid_count > 0 { - let reward_amount = Self::calculate_reward_amount(file_info.remaining_paid_count, &file_info.amount); - if let Some(new_reward) = Self::has_enough_collateral(&owner, &reward_amount) { - T::BenefitInterface::update_reward(&owner, new_reward); - file_info.amount = file_info.amount.saturating_sub(reward_amount); - file_info.remaining_paid_count = file_info.remaining_paid_count.saturating_sub(1); - } - } - } - } - - // 3. The first join the replicas and file become live(expired_at > calculated_at) - if file_info.expired_at == 0 { - let curr_bn = Self::get_current_block_number(); - file_info.calculated_at = curr_bn; - file_info.expired_at = curr_bn + T::FileDuration::get(); - } - - // 4. Update files - >::insert(cid, file_info); - } - (spower, is_valid_cid) - } - - /// Node who delete the replica - /// Accept id(who, anchor), cid and current block number - /// Returns the real storage power of this file and whether this file is in the market system - fn delete_replica(who: &::AccountId, - owner: ::AccountId, - cid: &MerkleRoot, - anchor: &SworkerAnchor) -> (u64, bool) { - let mut spower: u64 = 0; - let mut is_valid_cid: bool = false; - // 1. Delete replica from file_info - if let Some(mut file_info) = >::get(cid) { - is_valid_cid = true; - let mut to_decrease_count = 0; - // None => No such file - // Some(true) => Already pass the SpowerReadyPeriod, decrease the spower - // Some(false) => Still in SpowerReadyPeriod, decrease the file_size - let mut is_spower_counted: Option = None; - let maybe_replica = file_info.replicas.get(&owner); - if let Some(replica) = maybe_replica { - if replica.who == *who { - if replica.anchor == *anchor { - // We added it before - if replica.created_at.is_none() { is_spower_counted = Some(true); } else { is_spower_counted = Some(false); }; - } - if replica.is_reported { - // if this anchor didn't report work, we already decrease the `reported_replica_count` in `update_replicas` - to_decrease_count += 1; - } - file_info.replicas.remove(&owner); - } - } - - // 2. Return the original storage power in wr - if let Some(is_spower_counted) = is_spower_counted { - if is_spower_counted { - spower = file_info.spower; - } else { - spower = file_info.file_size; - } - } - - // 3. Decrease the reported_replica_count - if to_decrease_count != 0 { - file_info.reported_replica_count = file_info.reported_replica_count.saturating_sub(to_decrease_count); - } - >::insert(cid, file_info); - } - (spower, is_valid_cid) - } - /// Withdraw market staking pot for distributing staking reward fn withdraw_staking_pot() -> BalanceOf { let staking_pot = Self::staking_pot(); @@ -254,6 +155,28 @@ impl MarketInterface<::AccountId, BalanceOf> } staking_amount } + + fn update_files_spower(changed_files: &Vec<(MerkleRoot, u64, Vec<(T::AccountId, T::AccountId, SworkerAnchor, Option)>)>) { + for (cid, new_spower, changed_replicas) in changed_files { + if let Some(mut file_info) = >::get(&cid) { + // Update file spower + file_info.spower = *new_spower; + + // Update the create_at + for (owner, who, anchor, created_at) in changed_replicas { + let maybe_replica = file_info.replicas.get_mut(owner); + if let Some(mut replica) = maybe_replica { + if replica.who == *who && replica.anchor == *anchor { + replica.created_at = *created_at; + } + } + } + + // Write back to storage + >::insert(&cid, file_info); + } + } + } } /// The module's configuration trait. @@ -360,6 +283,9 @@ decl_storage! { /// The crust-spower service account pub SpowerSuperior get(fn spower_superior): Option; + + /// The last replicas update block + pub LastReplicasUpdateBlock get (fn last_replicas_update_block): BlockNumber = 0; } add_extra_genesis { build(|_config| { @@ -393,6 +319,10 @@ decl_error! { PlaceOrderNotAvailable, /// The file does not exist. Please check the cid again. FileNotExist, + /// The spower superior account is not set. Please call the set_spower_superior extrinsic first. + SpowerSuperiorNotSet, + /// The caller account is not the spower superior account. Please check the caller account again. + IllegalSpowerSuperior, } } @@ -542,6 +472,69 @@ decl_module! { Ok(()) } + /// Update file replicas from crust-spower offchain service + /// Emits `ReplicasUpdateSuccess` event if the call is success + /// # params + /// - file_infos_Map: file replicas info map with the data structure as belowed: + /// Map<(CID, file_size, Vec<(reporter, owner, sworker_anchor, report_slot, report_block, valid_at, is_added))>> + /// The key is the file CID, and the value is a vector of file replicas info. + /// PS: We're not using the ReplicaToUpdate type in the argument directly, because this would fail traditional apps + /// which would need to decode extrinsics, which will then error out with 'Unable to decode on ReplicaToUpdate'. + /// So we directly use the raw types and tuple here as the argument + #[weight = T::WeightInfo::update_replicas()] + pub fn update_replicas( + origin, + file_infos_map: Vec<(MerkleRoot, u64, Vec<(T::AccountId, T::AccountId, SworkerAnchor, ReportSlot, BlockNumber, BlockNumber, bool)>)>, + last_processed_block_wrs: BlockNumber + ) -> DispatchResultWithPostInfo { + let caller = ensure_signed(origin)?; + let maybe_superior = Self::spower_superior(); + + // 1. Check if superior exist + ensure!(maybe_superior.is_some(), Error::::SpowerSuperiorNotSet); + // 2. Check if caller is superior + ensure!(Some(&caller) == maybe_superior.as_ref(), Error::::IllegalSpowerSuperior); + + // 3. Internal update replicas + let mut file_infos_map_ex: Vec<(MerkleRoot, u64, Vec>)> = vec![]; + for (cid, file_size, replicas) in file_infos_map { + let mut replica_list: Vec> = vec![]; + for (reporter, owner, sworker_anchor, report_slot, report_block, valid_at, is_added) in replicas { + let replica_to_update = ReplicaToUpdate { + reporter: reporter, + owner: owner, + sworker_anchor: sworker_anchor, + report_slot: report_slot, + report_block: report_block, + valid_at: valid_at, + is_added: is_added + }; + replica_list.push(replica_to_update); + } + file_infos_map_ex.push((cid, file_size, replica_list)); + } + let (changed_files_count, sworker_changed_spower_map, illegal_file_replicas_map) = Self::internal_update_replicas(file_infos_map_ex); + + // 4. Update the last processed block of work reports in pallet_swork + T::SworkerInterface::update_last_processed_block_of_work_reports(last_processed_block_wrs); + + // 5. Update the changed spower of sworkers + T::SworkerInterface::update_sworkers_changed_spower(&sworker_changed_spower_map); + + // 5. Update illegal file replicas count in pallet_swork + T::SworkerInterface::update_illegal_file_replicas_count(&illegal_file_replicas_map); + + // 6. Update the LastReplicasUpdateBlock + let curr_bn = Self::get_current_block_number(); + LastReplicasUpdateBlock::put(curr_bn); + + // 7. Emit the event + Self::deposit_event(RawEvent::UpdateReplicasSuccess(caller, curr_bn, changed_files_count, last_processed_block_wrs)); + + // Do not charge fee for management extrinsic + Ok(Pays::No.into()) + } + /// Calculate the reward for a file #[weight = T::WeightInfo::calculate_reward()] pub fn calculate_reward( @@ -564,13 +557,10 @@ decl_module! { // 3. Maybe reward liquidator when he try to close outdated file Self::maybe_reward_liquidator(&cid, curr_bn, &liquidator)?; - // 4. Refresh the status of the file and calculate the reward for merchants - Self::update_replicas(&cid, curr_bn); - - // 5. Try to renew file if prepaid is not zero + // 4. Try to renew file if prepaid is not zero Self::try_to_renew_file(&cid, curr_bn, &liquidator)?; - // 6. Try to close file + // 5. Try to close file Self::try_to_close_file(&cid, curr_bn)?; Self::deposit_event(RawEvent::CalculateSuccess(cid)); @@ -722,54 +712,224 @@ impl Module { T::ModuleId::get().into_sub_account("rese") } - /// This function will update replicas - /// and (maybe) insert file's status(delete file) - /// input: - /// - cid: MerkleRoot - /// - curr_bn: BlockNumber - pub fn update_replicas(cid: &MerkleRoot, curr_bn: BlockNumber) + /// + pub fn internal_update_replicas( + file_infos_map: Vec<(MerkleRoot, u64, Vec>)> + ) -> (u32, BTreeMap, BTreeMap) { - // 1. File must exist - if Self::filesv2(cid).is_none() { return; } - - // 2. File must already started - let mut file_info = Self::filesv2(cid).unwrap_or_default(); + let mut changed_files_count = 0; + let mut sworker_changed_spower_map: BTreeMap = BTreeMap::new(); + let mut illegal_file_replicas_map: BTreeMap = BTreeMap::new(); + 'file_loop: for (cid, reported_file_size, file_replicas) in file_infos_map { + + // Split the replicas array into added_replicas and deleted_replicas + // let (mut added_replicas, mut deleted_replicas): + // (Vec>,Vec>) = file_replicas + // .into_iter() + // .partition(|replica| replica.is_added); + + let mut added_replicas: Vec> = vec![]; + let mut deleted_replicas: Vec> = vec![]; + for replica in file_replicas { + if replica.is_added { + added_replicas.push(replica); + } else { + deleted_replicas.push(replica); + } + } + + // Sort each array by report_block + added_replicas.sort_by(|a, b| a.report_block.cmp(&b.report_block)); + deleted_replicas.sort_by(|a, b| a.report_block.cmp(&b.report_block)); + + // Get the file_info object from storage for 1 time db read + let maybe_file_info = Self::filesv2(&cid); + if maybe_file_info.is_none() { + // If the cid doesn't exist in the market, either this is a non-exist cid, or has been removed by illegal file size, or has been liquidated and closed + // Since we haven't changed the sworker's spower during the swork.report_works call, so we can just ignore all replicas here without any side-effects + + // Invalid cid's replicas count should be subtracted from Swork::Added_Files_Count + if added_replicas.len() > 0 { + let ReplicaToUpdate { report_slot, ..} = added_replicas[0]; + if let Some(count) = illegal_file_replicas_map.get_mut(&report_slot) { + *count += added_replicas.len() as u32; + } else { + illegal_file_replicas_map.insert(report_slot, added_replicas.len() as u32); + } + } + + // Just continue to next cid + continue; + } + let mut file_info = maybe_file_info.unwrap(); + + // --------------------------------------------------------- + // --- Handle upsert replicas --- + for file_replica in added_replicas.iter() { + + let ReplicaToUpdate { reporter, owner, sworker_anchor, report_slot, valid_at, ..} = file_replica; + + // 1. Check if file_info.file_size == reported_file_size or not + let is_valid_cid = Self::maybe_upsert_file_size(&mut file_info, &reporter, &cid, reported_file_size); + if !is_valid_cid { + // This is a invalid cid with illegal file size, which has been removed in maybe_upsert_file_size + + // We simply add all added_replicas count as of the first replica's report_slot, which is almost the case + if let Some(count) = illegal_file_replicas_map.get_mut(report_slot) { + *count += added_replicas.len() as u32; + } else { + illegal_file_replicas_map.insert(*report_slot, added_replicas.len() as u32); + } + + changed_files_count += 1; + // We don't need to process all subsequent replicas anymore. + continue 'file_loop; + } + + // 2. Add replica data to storage + let is_replica_added = Self::upsert_replica(&mut file_info, &reporter, &owner, &sworker_anchor, *valid_at); + // If the replica is not added (due to exceed MAX_REPLICA, or same owner reported), just ignore this replica + if is_replica_added { + // Update related sworker's changed spower + if let Some(changed_spower) = sworker_changed_spower_map.get_mut(sworker_anchor) { + *changed_spower += file_info.file_size as i64; + } else { + sworker_changed_spower_map.insert(sworker_anchor.clone(), file_info.file_size as i64); + } + } + } + + // --------------------------------------------------------- + // --- Handle delete replicas --- + for file_replica in deleted_replicas.iter() { + + let ReplicaToUpdate { reporter, owner, sworker_anchor, ..} = file_replica; + + let (is_replica_deleted, to_delete_spower) = Self::delete_replica(&mut file_info,&reporter, owner, &sworker_anchor); + if is_replica_deleted { + // Update replicated sworker's changed spower + if let Some(changed_spower) = sworker_changed_spower_map.get_mut(sworker_anchor) { + *changed_spower -= to_delete_spower as i64; + } else { + sworker_changed_spower_map.insert(sworker_anchor.clone(), 0-(to_delete_spower as i64)); + } + } + } + + // Update the file info with all the above changes in one DB write + >::insert(cid.clone(), file_info.clone()); + changed_files_count += 1; + } + + (changed_files_count, sworker_changed_spower_map, illegal_file_replicas_map) + } + + fn maybe_upsert_file_size(file_info: &mut FileInfoV2>, + who: &T::AccountId, cid: &MerkleRoot, reported_file_size: u64) -> bool { - // 3. File already expired - if file_info.expired_at <= file_info.calculated_at { return; } - - let calculated_block = curr_bn.min(file_info.expired_at); - let mut new_replicas = BTreeMap::>::new(); - let mut new_reported_replica_count = 0u32; - - // 4. Loop replicas and update reported replica count - for (owner, replica) in file_info.replicas.iter() { - if !T::SworkerInterface::is_wr_reported(&replica.anchor, curr_bn) { - let mut invalid_replica = replica.clone(); - // update the valid_at to the curr_bn - invalid_replica.valid_at = curr_bn; - invalid_replica.is_reported = false; - new_replicas.insert(owner.clone(), invalid_replica); + let mut is_valid_cid = true; + // 1. Judge if file_info.file_size == reported_file_size or not + if file_info.replicas.len().is_zero() { + // ordered_file_size == reported_file_size, return it + if file_info.file_size == reported_file_size { + return true; + // ordered_file_size > reported_file_size, correct it + } else if file_info.file_size > reported_file_size { + file_info.file_size = reported_file_size; + // ordered_file_size < reported_file_size, close it with notification } else { - let mut valid_replica = replica.clone(); - valid_replica.is_reported = true; - new_replicas.insert(owner.clone(), valid_replica); - new_reported_replica_count += 1; + let total_amount = file_info.amount + file_info.prepaid; + if !Self::maybe_reward_merchant(who, &total_amount) { + // This should not have error => discard the result + let _ = T::Currency::transfer(&Self::storage_pot(), &Self::reserved_pot(), total_amount, KeepAlive); + } + >::remove(cid); + FileKeysCount::mutate(|count| *count = count.saturating_sub(1)); + OrdersCount::mutate(|count| {*count = count.saturating_sub(1)}); + Self::deposit_event(RawEvent::IllegalFileClosed(cid.clone())); + + is_valid_cid = false; } } - // 5 Update file info - file_info.reported_replica_count = new_reported_replica_count; - file_info.replicas = new_replicas; + is_valid_cid + } - // 6. Update spower info - // TODO: add this weight into place_storage_order - let _ = Self::update_replicas_spower(&mut file_info, Some(curr_bn)); + fn upsert_replica(file_info: &mut FileInfoV2>, + who: &::AccountId, + owner: &::AccountId, + anchor: &SworkerAnchor, + valid_at: BlockNumber + ) -> bool { + + let mut is_replica_added = false; + let curr_bn = Self::get_current_block_number(); + // 1. Check if the length of the groups exceed MAX_REPLICAS or not + if file_info.replicas.len() < MAX_REPLICAS { + // 2. Check if the file is stored by other members + if !file_info.replicas.contains_key(&owner) { + let new_replica = Replica { + who: who.clone(), + valid_at, + anchor: anchor.clone(), + is_reported: true, + created_at: Some(valid_at) + }; + file_info.replicas.insert(owner.clone(), new_replica); + file_info.reported_replica_count += 1; + is_replica_added = true; + + // Reward the first 4 merchants which submits the replica report + if file_info.remaining_paid_count > 0 { + let reward_amount = Self::calculate_reward_amount(file_info.remaining_paid_count, &file_info.amount); + if let Some(new_reward) = Self::has_enough_collateral(&owner, &reward_amount) { + T::BenefitInterface::update_reward(&owner, new_reward); + file_info.amount = file_info.amount.saturating_sub(reward_amount); + file_info.remaining_paid_count = file_info.remaining_paid_count.saturating_sub(1); + } + } + } + } + + // 3. The first join the replicas and file become live(expired_at > calculated_at) + if file_info.expired_at == 0 { + file_info.calculated_at = curr_bn; + file_info.expired_at = curr_bn + T::FileDuration::get(); + } + + is_replica_added + } + + fn delete_replica(file_info: &mut FileInfoV2>, + who: &::AccountId, + owner: &::AccountId, + anchor: &SworkerAnchor, + ) -> (bool, u64) { + + let mut spower: u64 = 0; + let mut is_replica_deleted: bool = false; + + // 1. Delete replica from file_info + let maybe_replica = file_info.replicas.get(owner); + if let Some(replica) = maybe_replica { + if replica.who == *who { + // Only decreate the spower if it's the same anchor, because for new anchor, the spower has been reset to 0 after re-register + if replica.anchor == *anchor { + if replica.created_at.is_none() { + // It means the replica is already using the spower value, because created_at would be set to None when use the spower value + spower = file_info.spower; + } else { + spower = file_info.file_size; + }; + } + // Don't need to check the replica.is_reported here, because we don't use the calculate_rewards->update_replicas right now + file_info.reported_replica_count = file_info.reported_replica_count.saturating_sub(1); + file_info.replicas.remove(owner); + is_replica_deleted = true; + } + } - // 6. File status might become ready to be closed if calculated_block == expired_at - file_info.calculated_at = calculated_block; - // 7. Update files - >::insert(cid, file_info); + (is_replica_deleted, spower) } /// Close file, maybe move into trash @@ -788,6 +948,7 @@ impl Module { // Remove files >::remove(&cid); FileKeysCount::mutate(|count| *count = count.saturating_sub(1)); + Self::deposit_event(RawEvent::FileClosed(cid.clone())); }; } Ok(()) @@ -836,8 +997,10 @@ impl Module { let reward_liquidator_amount = file_info.amount; file_info.amount = Zero::zero(); T::Currency::transfer(&Self::storage_pot(), liquidator, reward_liquidator_amount, KeepAlive)?; - >::insert(cid, file_info); } + + file_info.calculated_at = curr_bn.min(file_info.expired_at); + >::insert(cid, file_info); } Ok(()) } @@ -1035,10 +1198,11 @@ impl Module { Ok(storage_amount) } - fn get_discount_ratio(who: &T::AccountId) -> Perbill { - let discount_max_ratio = Perbill::one().saturating_sub(T::StakingRatio::get()).saturating_sub(T::StorageRatio::get()); - T::BenefitInterface::get_market_funds_ratio(who).min(discount_max_ratio) - } + // discount feature is not implemented yet, comment out first to remove the build warning + // fn get_discount_ratio(who: &T::AccountId) -> Perbill { + // let discount_max_ratio = Perbill::one().saturating_sub(T::StakingRatio::get()).saturating_sub(T::StorageRatio::get()); + // T::BenefitInterface::get_market_funds_ratio(who).min(discount_max_ratio) + // } fn get_current_block_number() -> BlockNumber { @@ -1046,32 +1210,6 @@ impl Module { TryInto::::try_into(current_block_number).ok().unwrap() } - fn maybe_upsert_file_size(who: &T::AccountId, cid: &MerkleRoot, reported_file_size: u64) { - if let Some(mut file_info) = Self::filesv2(cid) { - if file_info.replicas.len().is_zero() { - // ordered_file_size == reported_file_size, return it - if file_info.file_size == reported_file_size { - return - // ordered_file_size > reported_file_size, correct it - } else if file_info.file_size > reported_file_size { - file_info.file_size = reported_file_size; - >::insert(cid, file_info); - // ordered_file_size < reported_file_size, close it with notification - } else { - let total_amount = file_info.amount + file_info.prepaid; - if !Self::maybe_reward_merchant(who, &total_amount) { - // This should not have error => discard the result - let _ = T::Currency::transfer(&Self::storage_pot(), &Self::reserved_pot(), total_amount, KeepAlive); - } - >::remove(cid); - FileKeysCount::mutate(|count| *count = count.saturating_sub(1)); - OrdersCount::mutate(|count| {*count = count.saturating_sub(1)}); - Self::deposit_event(RawEvent::IllegalFileClosed(cid.clone())); - } - } - } - } - fn maybe_reward_merchant(who: &T::AccountId, amount: &BalanceOf) -> bool { if let Some(owner) = T::SworkerInterface::get_owner(who) { if let Some(new_reward) = Self::has_enough_collateral(&owner, amount) { @@ -1124,31 +1262,29 @@ impl Module { } pub fn calculate_spower(file_size: u64, reported_replica_count: u32) -> u64 { - let (integer, numerator, denominator): (u64, u64, u64) = match reported_replica_count { - 0 => (0, 0, 1), - 1..=8 => (1, 1, 20), - 9..=16 => (1, 1, 5), - 17..=24 => (1, 1, 2), - 25..=32 => (2, 0, 1), - 33..=40 => (2, 3, 5), - 41..=48 => (3, 3, 10), - 49..=55 => (4, 0, 1), - 56..=65 => (5, 0, 1), - 66..=74 => (6, 0, 1), - 75..=83 => (7, 0, 1), - 84..=92 => (8, 0, 1), - 93..=100 => (8, 1, 2), - 101..=115 => (8, 4, 5), - 116..=127 => (9, 0, 1), - 128..=142 => (9, 1, 5), - 143..=157 => (9, 2, 5), - 158..=167 => (9, 3, 5), - 168..=182 => (9, 4, 5), - 183..=200 => (10, 0, 1), - _ => (10, 0, 1), // larger than 200 => 200 + let (alpha, multiplier): (f64, u64) = match reported_replica_count { + 0 => (0.0, 1), + 1..=8 => (0.1, 10), + 9..=16 => (1.0, 1), + 17..=24 => (3.0, 1), + 25..=32 => (7.0, 1), + 33..=40 => (9.0, 1), + 41..=48 => (14.0, 1), + 49..=55 => (19.0, 1), + 56..=65 => (49.0, 1), + 66..=74 => (79.0, 1), + 75..=83 => (99.0, 1), + 84..=92 => (119.0, 1), + 93..=100 => (149.0, 1), + 101..=115 => (159.0, 1), + 116..=127 => (169.0, 1), + 128..=142 => (179.0, 1), + 143..=157 => (189.0, 1), + 158..=200 => (199.0, 1), + _ => (199.0, 1), // larger than 200 => 200 }; - integer * file_size + file_size / denominator * numerator + file_size + file_size * ((alpha * multiplier as f64) as u64) / multiplier } } @@ -1186,5 +1322,14 @@ decl_event!( SetBaseFeeSuccess(Balance), /// Set the crust-spower service superior account. SetSpowerSuperiorSuccess(AccountId), + /// Update replicas success + /// The first item is the account who update the replicas. + /// The second item is the current block number + /// The third item is the changed files count + /// The fourth item is the last processed block of work reports + UpdateReplicasSuccess(AccountId, BlockNumber, u32, BlockNumber), + /// A file is closed due to expired + /// The first item is the cid of the file + FileClosed(MerkleRoot), } ); diff --git a/cstrml/market/src/mock.rs b/cstrml/market/src/mock.rs index b10ea373..622663db 100644 --- a/cstrml/market/src/mock.rs +++ b/cstrml/market/src/mock.rs @@ -32,6 +32,7 @@ pub const MERCHANT: AccountId32 = AccountId32::new([5u8; 32]); pub const DAVE: AccountId32 = AccountId32::new([6u8; 32]); pub const FERDIE: AccountId32 = AccountId32::new([7u8; 32]); pub const ZIKUN: AccountId32 = AccountId32::new([8u8; 32]); +pub const SPOWER: AccountId32 = AccountId32::new([9u8; 32]); #[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, Default)] pub struct MockMerchantLedger { @@ -301,8 +302,42 @@ pub fn init_swork_setup() { } // fake for report_works -pub fn add_who_into_replica(cid: &MerkleRoot, reported_size: u64, who: AccountId, owner: AccountId, anchor: SworkerAnchor, created_at: Option, _maybe_members: Option>) -> u64 { - Market::upsert_replica(&who, owner, cid, reported_size, &anchor, created_at.unwrap_or(TryInto::::try_into(System::block_number()).ok().unwrap())).0 +pub fn add_who_into_replica( + cid: &MerkleRoot, + reported_size: u64, + who: AccountId, + owner: AccountId, + anchor: SworkerAnchor, + report_slot: ReportSlot, + report_block: BlockNumber, + valid_at: BlockNumber) { + + assert_ok!(Market::update_replicas( + Origin::signed(SPOWER.clone()), + vec![(cid.clone(), + reported_size, + vec![(who.clone(), owner.clone(), anchor.clone(), report_slot, report_block, valid_at, true)] + )], + 400)); +} + +pub fn delete_replica( + cid: &MerkleRoot, + reported_size: u64, + who: AccountId, + owner: AccountId, + anchor: SworkerAnchor, + report_slot: ReportSlot, + report_block: BlockNumber, + valid_at: BlockNumber) { + + assert_ok!(Market::update_replicas( + Origin::signed(SPOWER.clone()), + vec![(cid.clone(), + reported_size, + vec![(who.clone(), owner.clone(), anchor.clone(), report_slot, report_block, valid_at, false)] + )], + 400)); } pub fn legal_work_report_with_added_files() -> ReportWorksInfo { diff --git a/cstrml/market/src/tests.rs b/cstrml/market/src/tests.rs index 59004fac..8b5d4217 100644 --- a/cstrml/market/src/tests.rs +++ b/cstrml/market/src/tests.rs @@ -104,6 +104,7 @@ fn place_storage_order_should_work_for_extend_scenarios() { let source = ALICE; let merchant = MERCHANT; + let spower = SPOWER; let cid = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oF".as_bytes().to_vec(); @@ -183,48 +184,45 @@ fn place_storage_order_should_work_for_extend_scenarios() { legal_wr_info.files_root, legal_wr_info.sig )); - run_to_block(700); - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); + // Swork::report_works should not change filesv2 right now assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, - spower: Market::calculate_spower(file_size, 1), - expired_at: 1400, - calculated_at: 700, - amount: 39990, // ( 1000 * 129 + 0 ) * 0.18 * 2 - ( 1000 * 129 + 0 ) * 0.025 * 2 + spower: 0, + expired_at: 0, + calculated_at: 50, + amount: 46440, // ( 1000 * 129 + 0 ) * 0.18 * 2 prepaid: 0, - reported_replica_count: 1, - remaining_paid_count: 3, - replicas: BTreeMap::from_iter(vec![(merchant.clone(), Replica { - who: merchant.clone(), - valid_at: 303, - anchor: legal_pk.clone(), - is_reported: true, - created_at: Some(303) - })]) + reported_replica_count: 0, + remaining_paid_count: 4, + replicas: BTreeMap::from_iter(vec![].into_iter()) } ); - - // Calculate reward should work - run_to_block(800); - ::insert(legal_pk.clone(), 0, true); - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); + run_to_block(700); + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + assert_ok!(Market::update_replicas( + Origin::signed(spower.clone()), + vec![(cid.clone(), + file_size, + vec![(merchant.clone(), merchant.clone(), legal_pk.clone(), legal_wr_info.block_number, 400, 303, true)] + )], + 400)); assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, - spower: Market::calculate_spower(file_size, 1), - expired_at: 1400, - calculated_at: 800, + spower: 0, + expired_at: 1700, // Market::update_replicas is called at block 700, so expired_at should be 1700 + calculated_at: 700, amount: 39990, // ( 1000 * 129 + 0 ) * 0.18 * 2 - ( 1000 * 129 + 0 ) * 0.025 * 2 prepaid: 0, - reported_replica_count: 1, + reported_replica_count: 1, // Replicas should have been added after Market::update_replicas call remaining_paid_count: 3, replicas: BTreeMap::from_iter(vec![(merchant.clone(), Replica { who: merchant.clone(), valid_at: 303, anchor: legal_pk.clone(), is_reported: true, - created_at: Some(303) + created_at: Some(303) })]) } ); @@ -239,9 +237,9 @@ fn place_storage_order_should_work_for_extend_scenarios() { assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, - spower: Market::calculate_spower(file_size, 1), + spower: 0, expired_at: 1900, - calculated_at: 800, + calculated_at: 700, amount: 63210, // 39990 + 23220 prepaid: 0, reported_replica_count: 1, @@ -266,10 +264,10 @@ fn place_storage_order_should_work_for_extend_scenarios() { assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, - spower: Market::calculate_spower(file_size, 1), - expired_at: 2000, - calculated_at: 800, - amount: 86630, // 39990 + 23220 + spower: 0, + expired_at: 2000, // Should extend expired_at + calculated_at: 700, // Should remain unchanged + amount: 86630, // 63210 + 23220 + 200(tips) prepaid: 0, reported_replica_count: 1, remaining_paid_count: 3, @@ -283,63 +281,31 @@ fn place_storage_order_should_work_for_extend_scenarios() { } ); assert_eq!(Market::orders_count(), 4); - }); -} - -#[test] -fn update_replicas_should_fail_due_to_not_live() { - new_test_ext().execute_with(|| { - // generate 50 blocks first - run_to_block(50); - - let source = ALICE; - let merchant = MERCHANT; - - let cid = - "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oF".as_bytes().to_vec(); - let file_size = 100; // should less than merchant - - let _ = Balances::make_free_balance_be(&source, 20000); - let _ = Balances::make_free_balance_be(&merchant, 20000); - - // collateral is 60 < 121 reward - mock_bond_owner(&merchant, &merchant); - add_collateral(&merchant, 6000); - - assert_ok!(Market::place_storage_order( - Origin::signed(source), cid.clone(), - file_size, 0, vec![] - )); - + // 5. Update spower should work + assert_ok!(Swork::set_spower_superior(Origin::root(), spower.clone())); + let file_spower = Market::calculate_spower(file_size, 1); + assert_ok!(Swork::update_spower( + Origin::signed(spower.clone()), + vec![(legal_pk.clone(), (file_spower - file_size).try_into().unwrap())], + vec![(cid.clone(), file_spower, vec![(merchant.clone(), merchant.clone(), legal_pk.clone(), None)])])); assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, - spower: 0, - expired_at: 0, - calculated_at: 50, - amount: 180, - prepaid: 0, - reported_replica_count: 0, - remaining_paid_count: 4, - replicas: BTreeMap::from_iter(vec![].into_iter()) - } - ); - - run_to_block(1506); - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); - - assert_eq!(Market::filesv2(&cid).unwrap_or_default(), - FileInfoV2 { - file_size, - spower: 0, - expired_at: 0, - calculated_at: 50, - amount: 180, + spower: file_spower, // spower value should have been set to file_spower + expired_at: 2000, + calculated_at: 700, + amount: 86630, prepaid: 0, - reported_replica_count: 0, - remaining_paid_count: 4, - replicas: BTreeMap::from_iter(vec![].into_iter()) + reported_replica_count: 1, + remaining_paid_count: 3, + replicas: BTreeMap::from_iter(vec![(merchant.clone(), Replica { + who: merchant.clone(), + valid_at: 303, + anchor: legal_pk.clone(), + is_reported: true, + created_at: None // created_at should have been set to None + })]) } ); }); @@ -357,6 +323,7 @@ fn update_replicas_should_work_for_more_replicas() { let dave = DAVE; let eve = EVE; let ferdie = FERDIE; + let spower = SPOWER; let cid = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oF".as_bytes().to_vec(); @@ -366,7 +333,7 @@ fn update_replicas_should_work_for_more_replicas() { for who in merchants.iter() { let _ = Balances::make_free_balance_be(&who, 20_000_000); mock_bond_owner(&who, &who); - add_collateral(who, 6_000_000); + add_collateral(&who, 6_000_000); } assert_ok!(Market::place_storage_order( @@ -388,15 +355,17 @@ fn update_replicas_should_work_for_more_replicas() { } ); - run_to_block(303); + run_to_block(203); let legal_wr_info = legal_work_report_with_added_files(); let legal_pk = legal_wr_info.curr_pk.clone(); - add_who_into_replica(&cid, file_size, ferdie.clone(), ferdie.clone(), legal_pk.clone(), Some(303u32), None); - add_who_into_replica(&cid, file_size, charlie.clone(), charlie.clone(), legal_pk.clone(), Some(403u32), None); - add_who_into_replica(&cid, file_size, dave.clone(), dave.clone(), legal_pk.clone(), Some(503u32), None); + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + add_who_into_replica(&cid, file_size, ferdie.clone(), ferdie.clone(), legal_pk.clone(),legal_wr_info.block_number, 203, 103); + add_who_into_replica(&cid, file_size, charlie.clone(), charlie.clone(), legal_pk.clone(), legal_wr_info.block_number, 203, 203); + add_who_into_replica(&cid, file_size, dave.clone(), dave.clone(), legal_pk.clone(), legal_wr_info.block_number, 203, 203); + run_to_block(303); register(&legal_pk, LegalCode::get()); assert_ok!(Swork::report_works( @@ -413,16 +382,18 @@ fn update_replicas_should_work_for_more_replicas() { legal_wr_info.files_root, legal_wr_info.sig )); - - add_who_into_replica(&cid, file_size, eve.clone(), eve.clone(), legal_pk.clone(), Some(503u32), None); ::insert(legal_pk.clone(), 0, true); - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); + add_who_into_replica(&cid, file_size, merchant.clone(), merchant.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); + + run_to_block(503); + add_who_into_replica(&cid, file_size, eve.clone(), eve.clone(), legal_pk.clone(), legal_wr_info.block_number, 503, 503); + assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, - spower: Market::calculate_spower(file_size, 5), - expired_at: 1303, - calculated_at: 303, + spower: 0, + expired_at: 1203, + calculated_at: 203, amount: 10320, prepaid: 0, reported_replica_count: 5, @@ -431,67 +402,26 @@ fn update_replicas_should_work_for_more_replicas() { (ferdie.clone(), Replica { who: ferdie.clone(), - valid_at: 303, - anchor: legal_pk.clone(), - is_reported: true, - created_at: Some(303) - }), - (merchant.clone(), - Replica { - who: merchant.clone(), - valid_at: 303, + valid_at: 103, anchor: legal_pk.clone(), is_reported: true, - created_at: Some(303) + created_at: Some(103) }), (charlie.clone(), Replica { who: charlie.clone(), - valid_at: 403, + valid_at: 203, anchor: legal_pk.clone(), is_reported: true, - created_at: Some(403) + created_at: Some(203) }), (dave.clone(), Replica { who: dave.clone(), - valid_at: 503, - anchor: legal_pk.clone(), - is_reported: true, - created_at: Some(503) - }), - (eve.clone(), - Replica { - who: eve.clone(), - valid_at: 503, - anchor: legal_pk.clone(), - is_reported: true, - created_at: Some(503) - }) - ]) - } - ); - run_to_block(503); - ::insert(legal_pk.clone(), 0, true); - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); - assert_eq!(Market::filesv2(&cid).unwrap_or_default(), - FileInfoV2 { - file_size, - spower: Market::calculate_spower(file_size, 5), - expired_at: 1303, - calculated_at: 503, - amount: 10320, - prepaid: 0, - reported_replica_count: 5, - remaining_paid_count: 0, - replicas: BTreeMap::from_iter(vec![ - (ferdie.clone(), - Replica { - who: ferdie.clone(), - valid_at: 303, + valid_at: 203, anchor: legal_pk.clone(), is_reported: true, - created_at: Some(303) + created_at: Some(203) }), (merchant.clone(), Replica { @@ -501,22 +431,6 @@ fn update_replicas_should_work_for_more_replicas() { is_reported: true, created_at: Some(303) }), - (charlie.clone(), - Replica { - who: charlie.clone(), - valid_at: 403, - anchor: legal_pk.clone(), - is_reported: true, - created_at: Some(403) - }), - (dave.clone(), - Replica { - who: dave.clone(), - valid_at: 503, - anchor: legal_pk.clone(), - is_reported: true, - created_at: Some(503) - }), (eve.clone(), Replica { who: eve.clone(), @@ -528,20 +442,20 @@ fn update_replicas_should_work_for_more_replicas() { ]) } ); - + assert_eq!(merchant_ledgers(&ferdie), MockMerchantLedger { collateral: 6_000_000, reward: 3225 }); - assert_eq!(merchant_ledgers(&merchant), MockMerchantLedger { + assert_eq!(merchant_ledgers(&charlie), MockMerchantLedger { collateral: 6_000_000, reward: 3225 }); - assert_eq!(merchant_ledgers(&charlie), MockMerchantLedger { + assert_eq!(merchant_ledgers(&dave), MockMerchantLedger { collateral: 6_000_000, reward: 3225 }); - assert_eq!(merchant_ledgers(&dave), MockMerchantLedger { + assert_eq!(merchant_ledgers(&merchant), MockMerchantLedger { collateral: 6_000_000, reward: 3225 }); @@ -564,6 +478,7 @@ fn update_replicas_should_only_pay_the_groups() { let dave = DAVE; let eve = EVE; let ferdie = FERDIE; + let spower = SPOWER; let cid = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oF".as_bytes().to_vec(); @@ -571,6 +486,8 @@ fn update_replicas_should_only_pay_the_groups() { let _ = Balances::make_free_balance_be(&source, 20_000_000); let merchants = vec![merchant.clone(), charlie.clone(), dave.clone(), eve.clone(), ferdie.clone()]; + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + assert_ok!(Market::place_storage_order( Origin::signed(source), cid.clone(), file_size, 0, vec![] @@ -593,10 +510,10 @@ fn update_replicas_should_only_pay_the_groups() { for who in merchants.iter() { let _ = Balances::make_free_balance_be(&who, 20_000_000); mock_bond_owner(&who, &who); - add_collateral(who, 6_000_000); + add_collateral(&who, 6_000_000); } - run_to_block(303); + run_to_block(203); let legal_wr_info = legal_work_report_with_added_files(); let legal_pk = legal_wr_info.curr_pk.clone(); @@ -611,9 +528,11 @@ fn update_replicas_should_only_pay_the_groups() { punishment_deadline: 0, group: None }); - add_who_into_replica(&cid, file_size, ferdie.clone(), ferdie.clone(), hex::decode("11").unwrap(), Some(303u32), Some(BTreeSet::from_iter(vec![charlie.clone(), ferdie.clone()].into_iter()))); - add_who_into_replica(&cid, file_size, charlie.clone(), ferdie.clone(), hex::decode("22").unwrap(), Some(403u32), Some(BTreeSet::from_iter(vec![charlie.clone(), ferdie.clone()].into_iter()))); - add_who_into_replica(&cid, file_size, dave.clone(), dave.clone(), hex::decode("33").unwrap(), Some(503u32), None); + add_who_into_replica(&cid, file_size, ferdie.clone(), ferdie.clone(), hex::decode("11").unwrap(), legal_wr_info.block_number, 203, 103); + add_who_into_replica(&cid, file_size, charlie.clone(), ferdie.clone(), hex::decode("22").unwrap(),legal_wr_info.block_number, 203, 203); + add_who_into_replica(&cid, file_size, dave.clone(), dave.clone(), hex::decode("33").unwrap(), legal_wr_info.block_number, 203, 203); + + run_to_block(303); register(&legal_pk, LegalCode::get()); @@ -632,18 +551,17 @@ fn update_replicas_should_only_pay_the_groups() { legal_wr_info.sig )); - add_who_into_replica(&cid, file_size, eve.clone(), eve.clone(), legal_pk.clone(), Some(503u32), None); - ::insert(legal_pk.clone(), 0, true); - ::insert(hex::decode("11").unwrap(), 0, true); - ::insert(hex::decode("22").unwrap(), 0, true); - ::insert(hex::decode("33").unwrap(), 0, true); - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); + add_who_into_replica(&cid, file_size, merchant.clone(), merchant.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); + + run_to_block(503); + add_who_into_replica(&cid, file_size, eve.clone(), eve.clone(), legal_pk.clone(), legal_wr_info.block_number, 503, 503); + assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, - spower: Market::calculate_spower(file_size, 4), - expired_at: 1303, - calculated_at: 303, + spower: 0, + expired_at: 1203, + calculated_at: 203, amount: 10320, prepaid: 0, reported_replica_count: 4, @@ -652,10 +570,18 @@ fn update_replicas_should_only_pay_the_groups() { (ferdie.clone(), Replica { who: ferdie.clone(), - valid_at: 303, + valid_at: 103, anchor: hex::decode("11").unwrap(), is_reported: true, - created_at: Some(303) + created_at: Some(103) + }), + (dave.clone(), + Replica { + who: dave.clone(), + valid_at: 203, + anchor: hex::decode("33").unwrap(), + is_reported: true, + created_at: Some(203) }), (merchant.clone(), Replica { @@ -665,14 +591,6 @@ fn update_replicas_should_only_pay_the_groups() { is_reported: true, created_at: Some(303) }), - (dave.clone(), - Replica { - who: dave.clone(), - valid_at: 503, - anchor: hex::decode("33").unwrap(), - is_reported: true, - created_at: Some(503) - }), (eve.clone(), Replica { who: eve.clone(), @@ -689,10 +607,6 @@ fn update_replicas_should_only_pay_the_groups() { collateral: 6_000_000, reward: 3225 }); - assert_eq!(merchant_ledgers(&merchant), MockMerchantLedger { - collateral: 6_000_000, - reward: 3225 - }); // charlie won't get payed assert_eq!(merchant_ledgers(&charlie), MockMerchantLedger { collateral: 6_000_000, @@ -702,6 +616,10 @@ fn update_replicas_should_only_pay_the_groups() { collateral: 6_000_000, reward: 3225 }); + assert_eq!(merchant_ledgers(&merchant), MockMerchantLedger { + collateral: 6_000_000, + reward: 3225 + }); assert_eq!(merchant_ledgers(&eve), MockMerchantLedger { collateral: 6_000_000, reward: 3225 @@ -984,6 +902,7 @@ fn scenario_test_for_reported_file_size_is_not_same_with_file_size() { let source = ALICE; let merchant = MERCHANT; + let spower = SPOWER; let cid1 = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oF".as_bytes().to_vec(); @@ -998,6 +917,8 @@ fn scenario_test_for_reported_file_size_is_not_same_with_file_size() { let _ = Balances::make_free_balance_be(&source, 20000); let _ = Balances::make_free_balance_be(&merchant, 20000); + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + mock_bond_owner(&merchant, &merchant); add_collateral(&merchant, 6000); @@ -1028,7 +949,7 @@ fn scenario_test_for_reported_file_size_is_not_same_with_file_size() { let legal_pk = legal_wr_info.curr_pk.clone(); register(&legal_pk, LegalCode::get()); // reported_file_size_cid1 = 90 < 100 => update file size in file info - add_who_into_replica(&cid1, reported_file_size_cid1, merchant.clone(), merchant.clone(), legal_pk.clone(), None, None); + add_who_into_replica(&cid1, reported_file_size_cid1, merchant.clone(), merchant.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); assert_eq!(Market::filesv2(&cid1).unwrap_or_default(), FileInfoV2 { file_size: reported_file_size_cid1, @@ -1051,7 +972,7 @@ fn scenario_test_for_reported_file_size_is_not_same_with_file_size() { assert_eq!(Market::orders_count(), 2); assert_eq!(Balances::free_balance(&storage_pot), 361); // reported_file_size_cid2 = 1000 > 100 => close this file - add_who_into_replica(&cid2, reported_file_size_cid2, merchant.clone(), merchant.clone(), legal_pk.clone(), None, None); + add_who_into_replica(&cid2, reported_file_size_cid2, merchant.clone(), merchant.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); assert_eq!(Market::filesv2(&cid2).is_none(), true); assert_eq!(merchant_ledgers(&merchant), MockMerchantLedger { collateral: 6000, @@ -1071,6 +992,7 @@ fn double_place_storage_order_file_size_check_should_work() { let source = ALICE; let merchant = MERCHANT; + let spower = SPOWER; let cid1 = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oF".as_bytes().to_vec(); @@ -1082,6 +1004,8 @@ fn double_place_storage_order_file_size_check_should_work() { mock_bond_owner(&merchant, &merchant); add_collateral(&merchant, 6000); + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + assert_ok!(Market::place_storage_order( Origin::signed(source.clone()), cid1.clone(), file_size, 0, vec![] @@ -1104,7 +1028,7 @@ fn double_place_storage_order_file_size_check_should_work() { let legal_wr_info = legal_work_report_with_added_files(); let legal_pk = legal_wr_info.curr_pk.clone(); register(&legal_pk, LegalCode::get()); - add_who_into_replica(&cid1, reported_file_size_cid1, merchant.clone(), merchant.clone(), legal_pk.clone(), None, None); + add_who_into_replica(&cid1, reported_file_size_cid1, merchant.clone(), merchant.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); assert_eq!(Market::filesv2(&cid1).unwrap_or_default(), FileInfoV2 { file_size: reported_file_size_cid1, @@ -1177,6 +1101,7 @@ fn place_storage_order_for_expired_file_should_inherit_the_status() { let charlie = CHARLIE; let dave = DAVE; let eve = EVE; + let spower = SPOWER; let staking_pot = Market::staking_pot(); let reserved_pot = Market::reserved_pot(); @@ -1187,9 +1112,11 @@ fn place_storage_order_for_expired_file_should_inherit_the_status() { for who in merchants.iter() { let _ = Balances::make_free_balance_be(&who, 20_000_000); mock_bond_owner(&who, &who); - add_collateral(who, 6_000_000); + add_collateral(&who, 6_000_000); } + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + assert_ok!(Market::place_storage_order( Origin::signed(source.clone()), cid.clone(), file_size, 0, vec![] @@ -1230,7 +1157,7 @@ fn place_storage_order_for_expired_file_should_inherit_the_status() { legal_wr_info.files_root, legal_wr_info.sig )); - + add_who_into_replica(&cid, file_size, merchant.clone(), merchant.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, @@ -1252,14 +1179,19 @@ fn place_storage_order_for_expired_file_should_inherit_the_status() { ); run_to_block(503); - ::insert(legal_pk.clone(), 0, true); - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); + assert_ok!(Swork::set_spower_superior(Origin::root(), spower.clone())); + let file_spower = Market::calculate_spower(file_size, 1); + assert_ok!(Swork::update_spower( + Origin::signed(spower.clone()), + vec![(legal_pk.clone(), (file_spower - file_size).try_into().unwrap())], + vec![(cid.clone(), file_spower, vec![(merchant.clone(), merchant.clone(), legal_pk.clone(), None)])])); + assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, - spower: Market::calculate_spower(file_size, 1), + spower: file_spower, expired_at: 1303, - calculated_at: 503, + calculated_at: 303, amount: 19995, prepaid: 0, reported_replica_count: 1, @@ -1269,7 +1201,7 @@ fn place_storage_order_for_expired_file_should_inherit_the_status() { valid_at: 303, anchor: legal_pk.clone(), is_reported: true, - created_at: Some(303) + created_at: None })]) } ); @@ -1278,13 +1210,13 @@ fn place_storage_order_for_expired_file_should_inherit_the_status() { reward: 3225 }); - add_who_into_replica(&cid, file_size, charlie.clone(), charlie.clone(), legal_pk.clone(), None, None); + add_who_into_replica(&cid, file_size, charlie.clone(), charlie.clone(), legal_pk.clone(), legal_wr_info.block_number, 503, 503); assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, - spower: Market::calculate_spower(file_size, 1), + spower: file_spower, expired_at: 1303, - calculated_at: 503, + calculated_at: 303, amount: 16770, prepaid: 0, reported_replica_count: 2, @@ -1295,7 +1227,7 @@ fn place_storage_order_for_expired_file_should_inherit_the_status() { valid_at: 303, anchor: legal_pk.clone(), is_reported: true, - created_at: Some(303) + created_at: None }), (charlie.clone(), Replica { who: charlie.clone(), @@ -1316,9 +1248,9 @@ fn place_storage_order_for_expired_file_should_inherit_the_status() { assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, - spower: Market::calculate_spower(file_size, 1), + spower: file_spower, expired_at: 2803, - calculated_at: 503, + calculated_at: 303, amount: 39990, prepaid: 0, reported_replica_count: 2, @@ -1329,7 +1261,7 @@ fn place_storage_order_for_expired_file_should_inherit_the_status() { valid_at: 303, anchor: legal_pk.clone(), is_reported: true, - created_at: Some(303) + created_at: None }), (charlie.clone(), Replica { who: charlie.clone(), @@ -1367,6 +1299,7 @@ fn place_storage_order_for_file_should_make_it_pending_if_replicas_is_zero() { let charlie = CHARLIE; let dave = DAVE; let eve = EVE; + let spower = SPOWER; let staking_pot = Market::staking_pot(); let reserved_pot = Market::reserved_pot(); @@ -1377,9 +1310,11 @@ fn place_storage_order_for_file_should_make_it_pending_if_replicas_is_zero() { for who in merchants.iter() { let _ = Balances::make_free_balance_be(&who, 20_000_000); mock_bond_owner(&who, &who); - add_collateral(who, 6_000_000); + add_collateral(&who, 6_000_000); } + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + assert_ok!(Market::place_storage_order( Origin::signed(source.clone()), cid.clone(), file_size, 0, vec![] @@ -1421,6 +1356,8 @@ fn place_storage_order_for_file_should_make_it_pending_if_replicas_is_zero() { legal_wr_info.sig )); + add_who_into_replica(&cid, file_size, merchant.clone(), merchant.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); + assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, @@ -1442,14 +1379,19 @@ fn place_storage_order_for_file_should_make_it_pending_if_replicas_is_zero() { ); run_to_block(503); - ::insert(legal_pk.clone(), 0, true); - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); + assert_ok!(Swork::set_spower_superior(Origin::root(), spower.clone())); + let file_spower = Market::calculate_spower(file_size, 1); + assert_ok!(Swork::update_spower( + Origin::signed(spower.clone()), + vec![(legal_pk.clone(), (file_spower - file_size).try_into().unwrap())], + vec![(cid.clone(), file_spower, vec![(merchant.clone(), merchant.clone(), legal_pk.clone(), None)])])); + assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, - spower: Market::calculate_spower(file_size, 1), + spower: file_spower, expired_at: 1303, - calculated_at: 503, + calculated_at: 303, amount: 19995, prepaid: 0, reported_replica_count: 1, @@ -1459,7 +1401,7 @@ fn place_storage_order_for_file_should_make_it_pending_if_replicas_is_zero() { valid_at: 303, anchor: legal_pk.clone(), is_reported: true, - created_at: Some(303) + created_at: None })]) } ); @@ -1468,13 +1410,14 @@ fn place_storage_order_for_file_should_make_it_pending_if_replicas_is_zero() { reward: 3225 }); - add_who_into_replica(&cid, file_size, charlie.clone(), charlie.clone(), legal_pk.clone(), None, None); + run_to_block(503); + add_who_into_replica(&cid, file_size, charlie.clone(), charlie.clone(), legal_pk.clone(), legal_wr_info.block_number, 503, 503); assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, - spower: Market::calculate_spower(file_size, 1), + spower: file_spower, expired_at: 1303, - calculated_at: 503, + calculated_at: 303, amount: 16770, prepaid: 0, reported_replica_count: 2, @@ -1485,7 +1428,7 @@ fn place_storage_order_for_file_should_make_it_pending_if_replicas_is_zero() { valid_at: 303, anchor: legal_pk.clone(), is_reported: true, - created_at: Some(303) + created_at: None }), (charlie.clone(), Replica { who: charlie.clone(), @@ -1496,18 +1439,22 @@ fn place_storage_order_for_file_should_make_it_pending_if_replicas_is_zero() { })]) } ); - Market::delete_replica(&merchant, merchant.clone(), &cid, &legal_pk); - Market::delete_replica(&charlie, charlie.clone(), &cid, &legal_pk); + delete_replica(&cid, file_size, merchant.clone(), merchant.clone(), legal_pk.clone(), legal_wr_info.block_number, 503, 503); + delete_replica(&cid, file_size, charlie.clone(), charlie.clone(), legal_pk.clone(), legal_wr_info.block_number, 503, 503); run_to_block(903); - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); - // calculated_at should be updated + let new_file_spower = file_size; // 0 replicas use file_size as file_spower + assert_ok!(Swork::update_spower( + Origin::signed(spower.clone()), + vec![(legal_pk.clone(), (file_size as i64 - file_spower as i64))], + vec![(cid.clone(), new_file_spower, vec![(merchant.clone(), merchant.clone(), legal_pk.clone(), None)])])); + assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, - spower: 0, + spower: new_file_spower, expired_at: 1303, - calculated_at: 903, + calculated_at: 303, amount: 16770, prepaid: 0, reported_replica_count: 0, @@ -1525,9 +1472,9 @@ fn place_storage_order_for_file_should_make_it_pending_if_replicas_is_zero() { assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, - spower: 0, + spower: new_file_spower, expired_at: 0, - calculated_at: 903, + calculated_at: 303, amount: 39990, prepaid: 0, reported_replica_count: 0, @@ -1546,6 +1493,7 @@ fn dynamic_spower_should_work() { let source = ALICE; let merchant = MERCHANT; + let spower = SPOWER; let cid = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oF".as_bytes().to_vec(); @@ -1555,9 +1503,12 @@ fn dynamic_spower_should_work() { for who in merchants.iter() { let _ = Balances::make_free_balance_be(&who, 20_000_000); mock_bond_owner(&who, &who); - add_collateral(who, 6_000_000); + add_collateral(&who, 6_000_000); } + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + assert_ok!(Swork::set_spower_superior(Origin::root(), spower.clone())); + assert_ok!(Market::place_storage_order( Origin::signed(source), cid.clone(), file_size, 0, vec![] @@ -1584,29 +1535,53 @@ fn dynamic_spower_should_work() { ::insert(legal_pk.clone(), 0, true); for index in 0..10 { - add_who_into_replica(&cid, file_size, AccountId32::new([index as u8; 32]), AccountId32::new([index as u8; 32]), legal_pk.clone(), Some(303u32), None); + add_who_into_replica(&cid, file_size, AccountId32::new([index as u8; 32]), AccountId32::new([index as u8; 32]), legal_pk.clone(), legal_wr_info.block_number, 303, 303); } - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); + let file_spower_10 = Market::calculate_spower(file_size, 10); + assert_ok!(Swork::update_spower( + Origin::signed(spower.clone()), + vec![(legal_pk.clone(), (file_spower_10 as i64 - file_size as i64))], + vec![(cid.clone(), file_spower_10, vec![(merchant.clone(), merchant.clone(), legal_pk.clone(), None)])])); + assert_eq!(Market::filesv2(&cid).unwrap_or_default().spower, Market::calculate_spower(file_size, 10)); assert_eq!(Market::filesv2(&cid).unwrap_or_default().reported_replica_count, 10); for index in 10..20 { - add_who_into_replica(&cid, file_size, AccountId32::new([index as u8; 32]), AccountId32::new([index as u8; 32]), legal_pk.clone(), Some(303u32), None); + add_who_into_replica(&cid, file_size, AccountId32::new([index as u8; 32]), AccountId32::new([index as u8; 32]), legal_pk.clone(), legal_wr_info.block_number, 303, 303); } - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); + let file_spower_20 = Market::calculate_spower(file_size, 20); + assert_ok!(Swork::update_spower( + Origin::signed(spower.clone()), + vec![(legal_pk.clone(), (file_spower_20 as i64 - file_spower_10 as i64))], + vec![(cid.clone(), file_spower_20, vec![(merchant.clone(), merchant.clone(), legal_pk.clone(), None)])])); + assert_eq!(Market::filesv2(&cid).unwrap_or_default().spower, Market::calculate_spower(file_size, 20)); assert_eq!(Market::filesv2(&cid).unwrap_or_default().reported_replica_count, 20); + + for index in 20..220 { - add_who_into_replica(&cid, file_size, AccountId32::new([index as u8; 32]), AccountId32::new([index as u8; 32]), legal_pk.clone(), Some(303u32), None); + add_who_into_replica(&cid, file_size, AccountId32::new([index as u8; 32]), AccountId32::new([index as u8; 32]), legal_pk.clone(), legal_wr_info.block_number, 303, 303); } - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); + let file_spower_200 = Market::calculate_spower(file_size, 200); + assert_ok!(Swork::update_spower( + Origin::signed(spower.clone()), + vec![(legal_pk.clone(), (file_spower_200 as i64 - file_spower_20 as i64))], + vec![(cid.clone(), file_spower_200, vec![(merchant.clone(), merchant.clone(), legal_pk.clone(), None)])])); + assert_eq!(Market::filesv2(&cid).unwrap_or_default().spower, Market::calculate_spower(file_size, 200)); assert_eq!(Market::filesv2(&cid).unwrap_or_default().reported_replica_count, 200); + + for index in 0..140 { - Market::delete_replica(&AccountId32::new([index as u8; 32]), AccountId32::new([index as u8; 32]), &cid, &legal_pk); + delete_replica(&cid, file_size, AccountId32::new([index as u8; 32]), AccountId32::new([index as u8; 32]), legal_pk.clone(), legal_wr_info.block_number, 303, 303); } - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); + let file_spower_60 = Market::calculate_spower(file_size, 60); + assert_ok!(Swork::update_spower( + Origin::signed(spower.clone()), + vec![(legal_pk.clone(), (file_spower_60 as i64 - file_spower_200 as i64))], + vec![(cid.clone(), file_spower_60, vec![(merchant.clone(), merchant.clone(), legal_pk.clone(), None)])])); + assert_eq!(Market::filesv2(&cid).unwrap_or_default().spower, Market::calculate_spower(file_size, 60)); assert_eq!(Market::filesv2(&cid).unwrap_or_default().reported_replica_count, 60); }); @@ -1616,12 +1591,12 @@ fn dynamic_spower_should_work() { fn calculate_spower_should_work() { new_test_ext().execute_with(|| { let file_size = 1000; - assert_eq!(Market::calculate_spower(file_size, 0) , 0); - assert_eq!(Market::calculate_spower(file_size, 200) , file_size * 10); - assert_eq!(Market::calculate_spower(file_size, 250) , file_size * 10); - assert_eq!(Market::calculate_spower(file_size, 146) , file_size * 9 + file_size * 2 / 5); - assert_eq!(Market::calculate_spower(file_size, 16) , file_size + file_size / 5); - assert_eq!(Market::calculate_spower(file_size, 128) , file_size * 9 + file_size / 5); + assert_eq!(Market::calculate_spower(file_size, 0) , file_size); + assert_eq!(Market::calculate_spower(file_size, 200) , file_size * 200); + assert_eq!(Market::calculate_spower(file_size, 250) , file_size * 200); + assert_eq!(Market::calculate_spower(file_size, 146) , file_size * 190); + assert_eq!(Market::calculate_spower(file_size, 16) , file_size * 2); + assert_eq!(Market::calculate_spower(file_size, 128) , file_size * 180); }); } @@ -1635,6 +1610,7 @@ fn delete_spower_should_work() { let bob = BOB; let charlie = CHARLIE; let merchant = MERCHANT; + let spower = SPOWER; let cid = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oF".as_bytes().to_vec(); @@ -1644,9 +1620,12 @@ fn delete_spower_should_work() { for who in merchants.iter() { let _ = Balances::make_free_balance_be(&who, 20_000_000); mock_bond_owner(&who, &who); - add_collateral(who, 6_000_000); + add_collateral(&who, 6_000_000); } + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + assert_ok!(Swork::set_spower_superior(Origin::root(), spower.clone())); + assert_ok!(Market::place_storage_order( Origin::signed(source), cid.clone(), file_size, 0, vec![] @@ -1671,30 +1650,75 @@ fn delete_spower_should_work() { let mut expected_groups = BTreeMap::new(); for i in 10..28 { let key = hex::decode(i.to_string()).unwrap(); - add_who_into_replica(&cid, file_size, AccountId32::new([i as u8; 32]), AccountId32::new([i as u8; 32]), key.clone(), Some(303u32), None); + add_who_into_replica(&cid, file_size, AccountId32::new([i as u8; 32]), AccountId32::new([i as u8; 32]), key.clone(), 300, 303, 303); ::insert(key.clone(), 0, true); expected_groups.insert(key.clone(), true); } - add_who_into_replica(&cid, file_size, bob.clone(), bob.clone(), hex::decode("29").unwrap(), Some(303u32), None); + add_who_into_replica(&cid, file_size, bob.clone(), bob.clone(), hex::decode("29").unwrap(), 300, 303, 303); ::insert(hex::decode("29").unwrap(), 0, true); - add_who_into_replica(&cid, file_size, charlie.clone(), charlie.clone(), hex::decode("30").unwrap(), Some(303u32), None); + add_who_into_replica(&cid, file_size, charlie.clone(), charlie.clone(), hex::decode("30").unwrap(), 300, 303, 303); ::insert(hex::decode("30").unwrap(), 0, true); - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); + + let file_spower_20 = Market::calculate_spower(file_size, 20); + let mut changed_spowers = vec![]; + let mut changed_replicas = vec![]; + for i in 10..28 { + let key = hex::decode(i.to_string()).unwrap(); + changed_spowers.push((key.clone(), (file_spower_20 as i64 - file_size as i64))); + changed_replicas.push((AccountId32::new([i as u8; 32]), AccountId32::new([i as u8; 32]), key.clone(), None)); + } + changed_spowers.push((hex::decode("29").unwrap(), (file_spower_20 as i64 - file_size as i64))); + changed_replicas.push((bob.clone(), bob.clone(), hex::decode("29").unwrap(), None)); + changed_spowers.push((hex::decode("30").unwrap(), (file_spower_20 as i64 - file_size as i64))); + changed_replicas.push((charlie.clone(), charlie.clone(), hex::decode("30").unwrap(), None)); + + assert_ok!(Swork::update_spower( + Origin::signed(spower.clone()), + changed_spowers, + vec![(cid.clone(), file_spower_20, changed_replicas)])); + assert_eq!(Market::filesv2(&cid).unwrap_or_default().spower, Market::calculate_spower(file_size, 20)); assert_eq!(Market::filesv2(&cid).unwrap_or_default().reported_replica_count, 20); - Market::delete_replica(&AccountId32::new([10u8; 32]), AccountId32::new([10u8; 32]), &cid, &hex::decode("10").unwrap()); + + // + delete_replica(&cid, file_size, AccountId32::new([10u8; 32]), AccountId32::new([10u8; 32]), hex::decode("10").unwrap(), 300, 303, 303); expected_groups.remove(&hex::decode("10").unwrap()); - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); + + let file_spower_19 = Market::calculate_spower(file_size, 19); + let mut changed_spowers_19 = vec![]; + for i in 10..28 { + let key = hex::decode(i.to_string()).unwrap(); + changed_spowers_19.push((key.clone(), (file_spower_19 as i64 - file_spower_20 as i64))); + } + changed_spowers_19.push((hex::decode("29").unwrap(), (file_spower_19 as i64 - file_spower_20 as i64))); + changed_spowers_19.push((hex::decode("30").unwrap(), (file_spower_19 as i64 - file_spower_20 as i64))); + + assert_ok!(Swork::update_spower( + Origin::signed(spower.clone()), + changed_spowers_19, + vec![(cid.clone(), file_spower_19, vec![])])); + assert_eq!(Market::filesv2(&cid).unwrap_or_default().spower, Market::calculate_spower(file_size, 19)); assert_eq!(Market::filesv2(&cid).unwrap_or_default().reported_replica_count, 19); - for i in 28..30 { + // delete 29, 29 won't be deleted twice + delete_replica(&cid, file_size, bob.clone(), bob.clone(), hex::decode("29").unwrap(), 300, 303, 303); + expected_groups.remove(&hex::decode("29").unwrap()); + + let file_spower_18 = Market::calculate_spower(file_size, 19); + let mut changed_spowers_18 = vec![]; + for i in 10..28 { let key = hex::decode(i.to_string()).unwrap(); - ::insert(key.clone(), 300, true); - expected_groups.insert(key.clone(), true); + changed_spowers_18.push((key.clone(), (file_spower_18 as i64 - file_spower_19 as i64))); } - Market::delete_replica(&bob, bob.clone(), &cid, &hex::decode("29").unwrap()); // delete 21. 21 won't be deleted twice. - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); + changed_spowers_18.push((hex::decode("29").unwrap(), (file_spower_19 as i64 - file_spower_20 as i64))); + changed_spowers_18.push((hex::decode("30").unwrap(), (file_spower_19 as i64 - file_spower_20 as i64))); + + assert_ok!(Swork::update_spower( + Origin::signed(spower.clone()), + changed_spowers_18, + vec![(cid.clone(), file_spower_18, vec![])])); + assert_eq!(Market::filesv2(&cid).unwrap_or_default().spower, Market::calculate_spower(file_size, 18)); assert_eq!(Market::filesv2(&cid).unwrap_or_default().reported_replica_count, 18); }); @@ -1713,6 +1737,7 @@ fn reward_liquidator_should_work() { let source = ALICE; let merchant = BOB; let charlie = CHARLIE; + let spower = SPOWER; let storage_pot = Market::storage_pot(); assert_eq!(Balances::free_balance(&storage_pot), 0); @@ -1723,9 +1748,11 @@ fn reward_liquidator_should_work() { for who in merchants.iter() { let _ = Balances::make_free_balance_be(&who, 20_000_000); mock_bond_owner(&who, &who); - add_collateral(who, 6_000_000); + add_collateral(&who, 6_000_000); } + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + assert_ok!(Market::calculate_reward(Origin::signed(charlie.clone()), cid.clone())); assert_ok!(Market::place_storage_order( @@ -1763,14 +1790,16 @@ fn reward_liquidator_should_work() { legal_wr_info.sig )); - // // Calculate reward cannot work in the middle of the file - // assert_noop!( - // Market::calculate_reward(Origin::signed(charlie.clone()), cid.clone()), - // DispatchError::Module { - // index: 3, - // error: 6, - // message: Some("NotInRewardPeriod") - // }); + // Calculate reward cannot work in the middle of the file + assert_noop!( + Market::calculate_reward(Origin::signed(charlie.clone()), cid.clone()), + DispatchError::Module { + index: 3, + error: 2, + message: Some("NotInRewardPeriod") + }); + + add_who_into_replica(&cid, file_size, merchant.clone(), merchant.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); run_to_block(2503); // all would be rewarded to liquidator charlie @@ -1878,6 +1907,7 @@ fn renew_file_should_work() { let source = ALICE; let merchant = BOB; let charlie = CHARLIE; + let spower = SPOWER; let storage_pot = Market::storage_pot(); let reserved_pot = Market::reserved_pot(); @@ -1890,8 +1920,11 @@ fn renew_file_should_work() { for who in merchants.iter() { let _ = Balances::make_free_balance_be(&who, 20_000_000); mock_bond_owner(&who, &who); - add_collateral(who, 6_000_000); + add_collateral(&who, 6_000_000); } + + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + assert_ok!(Swork::set_spower_superior(Origin::root(), spower.clone())); assert_noop!( Market::add_prepaid(Origin::signed(source.clone()), cid.clone(), 400_000), @@ -1927,6 +1960,8 @@ fn renew_file_should_work() { legal_wr_info.files_root, legal_wr_info.sig )); + + add_who_into_replica(&cid, file_size, merchant.clone(), merchant.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); assert_ok!(Market::add_prepaid(Origin::signed(source.clone()), cid.clone(), 400_000)); assert_eq!(Market::filesv2(&cid).unwrap_or_default(), @@ -1961,13 +1996,13 @@ fn renew_file_should_work() { calculated_at: 2503, amount: 23220, // 23220 prepaid: 270000, - reported_replica_count: 0, + reported_replica_count: 1, remaining_paid_count: 3, replicas: BTreeMap::from_iter(vec![(merchant.clone(), Replica { who: merchant.clone(), - valid_at: 2503, + valid_at: 303, anchor: legal_pk.clone(), - is_reported: false, + is_reported: true, created_at: Some(303) })]) } @@ -1979,22 +2014,23 @@ fn renew_file_should_work() { run_to_block(8000); // expired_on 2303 => all reward to liquidator charlie assert_ok!(Market::calculate_reward(Origin::signed(charlie.clone()), cid.clone())); + assert_eq!(Balances::free_balance(&charlie), 43215); // 19995 + 23220 assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, - spower: Market::calculate_spower(file_size, 0), + spower: 0, expired_at: 9000, calculated_at: 8000, amount: 23220, prepaid: 140000, - reported_replica_count: 0, + reported_replica_count: 1, remaining_paid_count: 3, replicas: BTreeMap::from_iter(vec![(merchant.clone(), Replica { who: merchant.clone(), - valid_at: 8000, + valid_at: 303, anchor: legal_pk.clone(), - is_reported: false, + is_reported: true, created_at: Some(303) })]) } @@ -2008,18 +2044,18 @@ fn renew_file_should_work() { assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, - spower: Market::calculate_spower(file_size, 0), + spower: 0, expired_at: 11000, calculated_at: 10000, amount: 23220, prepaid: 10000, - reported_replica_count: 0, + reported_replica_count: 1, remaining_paid_count: 3, replicas: BTreeMap::from_iter(vec![(merchant.clone(), Replica { who: merchant.clone(), - valid_at: 10000, + valid_at: 303, anchor: legal_pk.clone(), - is_reported: false, + is_reported: true, created_at: Some(303) })]) } @@ -2048,6 +2084,7 @@ fn renew_onging_file_should_not_work() { let source = ALICE; let merchant = BOB; let charlie = CHARLIE; + let spower = SPOWER; let storage_pot = Market::storage_pot(); let reserved_pot = Market::reserved_pot(); @@ -2060,9 +2097,12 @@ fn renew_onging_file_should_not_work() { for who in merchants.iter() { let _ = Balances::make_free_balance_be(&who, 20_000_000); mock_bond_owner(&who, &who); - add_collateral(who, 6_000_000); + add_collateral(&who, 6_000_000); } + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + assert_ok!(Swork::set_spower_superior(Origin::root(), spower.clone())); + assert_noop!( Market::add_prepaid(Origin::signed(source.clone()), cid.clone(), 400_000), DispatchError::Module { @@ -2097,6 +2137,7 @@ fn renew_onging_file_should_not_work() { legal_wr_info.files_root, legal_wr_info.sig )); + add_who_into_replica(&cid, file_size, merchant.clone(), merchant.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); assert_ok!(Market::add_prepaid(Origin::signed(source.clone()), cid.clone(), 400_000)); assert_eq!(Market::filesv2(&cid).unwrap_or_default(), @@ -2131,13 +2172,13 @@ fn renew_onging_file_should_not_work() { calculated_at: 503, amount: 19995, prepaid: 400_000, - reported_replica_count: 0, + reported_replica_count: 1, remaining_paid_count: 3, replicas: BTreeMap::from_iter(vec![(merchant.clone(), Replica { who: merchant.clone(), - valid_at: 503, + valid_at: 303, anchor: legal_pk.clone(), - is_reported: false, + is_reported: true, created_at: Some(303) })]) } @@ -2153,6 +2194,7 @@ fn change_base_fee_should_work() { let source = ALICE; let merchant = MERCHANT; + let spower = SPOWER; let cid = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oF".as_bytes().to_vec(); @@ -2166,6 +2208,8 @@ fn change_base_fee_should_work() { add_collateral(&merchant, 60_000); + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + // Change base fee to 10000 assert_ok!(Market::set_base_fee(Origin::root(), 50000)); @@ -2213,6 +2257,8 @@ fn change_base_fee_should_work() { legal_wr_info.sig )); + add_who_into_replica(&cid, file_size, merchant.clone(), merchant.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); + assert_ok!(Market::add_prepaid(Origin::signed(source.clone()), cid.clone(), 200_000)); assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { @@ -2240,18 +2286,18 @@ fn change_base_fee_should_work() { assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, - spower: Market::calculate_spower(file_size, 0), + spower: 0, expired_at: 3503, calculated_at: 2503, amount: 23220, // 23_220 prepaid: 21000, // 200000 -129000 - 50000 - reported_replica_count: 0, + reported_replica_count: 1, remaining_paid_count: 3, replicas: BTreeMap::from_iter(vec![(merchant.clone(), Replica { who: merchant.clone(), - valid_at: 2503, + valid_at: 303, anchor: legal_pk.clone(), - is_reported: false, + is_reported: true, created_at: Some(303) })]) } @@ -2270,6 +2316,8 @@ fn storage_pot_should_be_balanced() { let source = ALICE; let merchant = BOB; let charlie = CHARLIE; + let spower = SPOWER; + let storage_pot = Market::storage_pot(); let reserved_pot = Market::reserved_pot(); let _ = Balances::make_free_balance_be(&storage_pot, 1); @@ -2280,9 +2328,11 @@ fn storage_pot_should_be_balanced() { for who in merchants.iter() { let _ = Balances::make_free_balance_be(&who, 20_000_000); mock_bond_owner(&who, &who); - add_collateral(who, 6_000_000); + add_collateral(&who, 6_000_000); } + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + assert_ok!(Market::place_storage_order( Origin::signed(source.clone()), cid.clone(), file_size, 0, vec![] @@ -2308,6 +2358,8 @@ fn storage_pot_should_be_balanced() { legal_wr_info.sig )); + add_who_into_replica(&cid, file_size, merchant.clone(), merchant.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); + assert_ok!(Market::add_prepaid(Origin::signed(source.clone()), cid.clone(), 400_000)); assert_eq!(Balances::free_balance(&storage_pot), 423221); @@ -2346,6 +2398,7 @@ fn one_owner_should_work() { let bob = BOB; // owner 1, have merchant, charlie and dave let zikun = ZIKUN; // owner 2 have eve and ferdie + let spower = SPOWER; let cid = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oF".as_bytes().to_vec(); @@ -2376,10 +2429,12 @@ fn one_owner_should_work() { let legal_wr_info = legal_work_report_with_added_files(); let legal_pk = legal_wr_info.curr_pk.clone(); - add_who_into_replica(&cid, file_size, ferdie.clone(), zikun.clone(), legal_pk.clone(), Some(303u32), None); - add_who_into_replica(&cid, file_size, charlie.clone(), bob.clone(), legal_pk.clone(), Some(403u32), None); - add_who_into_replica(&cid, file_size, dave.clone(), bob.clone(), legal_pk.clone(), Some(503u32), None); + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + add_who_into_replica(&cid, file_size, ferdie.clone(), zikun.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); + add_who_into_replica(&cid, file_size, charlie.clone(), bob.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); + add_who_into_replica(&cid, file_size, dave.clone(), bob.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); + run_to_block(403); register(&legal_pk, LegalCode::get()); assert_ok!(Swork::report_works( @@ -2397,11 +2452,9 @@ fn one_owner_should_work() { legal_wr_info.sig )); - add_who_into_replica(&cid, file_size, eve.clone(), zikun.clone(), legal_pk.clone(), Some(503u32), None); + add_who_into_replica(&cid, file_size, eve.clone(), zikun.clone(), legal_pk.clone(), legal_wr_info.block_number, 403, 403); run_to_block(503); - ::insert(legal_pk.clone(), 0, true); - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); assert_eq!(merchant_ledgers(&bob), MockMerchantLedger { collateral: 6_000_000, @@ -2523,6 +2576,7 @@ fn spower_delay_should_work() { let charlie = CHARLIE; let dave = DAVE; let eve = EVE; + let spower = SPOWER; SpowerReadyPeriod::put(300); let staking_pot = Market::staking_pot(); @@ -2534,9 +2588,12 @@ fn spower_delay_should_work() { for who in merchants.iter() { let _ = Balances::make_free_balance_be(&who, 20_000_000); mock_bond_owner(&who, &who); - add_collateral(who, 6_000_000); + add_collateral(&who, 6_000_000); } + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + assert_ok!(Swork::set_spower_superior(Origin::root(), spower.clone())); + assert_ok!(Market::place_storage_order( Origin::signed(source), cid.clone(), file_size, 0, vec![] @@ -2578,6 +2635,8 @@ fn spower_delay_should_work() { legal_wr_info.sig )); + add_who_into_replica(&cid, file_size, merchant.clone(), merchant.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); + assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, @@ -2599,14 +2658,18 @@ fn spower_delay_should_work() { ); run_to_block(503); - ::insert(legal_pk.clone(), 0, true); - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); + let file_spower = Market::calculate_spower(file_size, 1); + assert_ok!(Swork::update_spower( + Origin::signed(spower.clone()), + vec![(legal_pk.clone(), (file_spower as i64 - file_size as i64))], + vec![(cid.clone(), file_spower, vec![(merchant.clone(), merchant.clone(), legal_pk.clone(), None)])])); + assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, spower: Market::calculate_spower(file_size, 1), expired_at: 1303, - calculated_at: 503, + calculated_at: 303, amount: 19995, prepaid: 0, reported_replica_count: 1, @@ -2616,7 +2679,7 @@ fn spower_delay_should_work() { valid_at: 303, anchor: legal_pk.clone(), is_reported: true, - created_at: Some(303) + created_at: None })]) } ); @@ -2625,17 +2688,16 @@ fn spower_delay_should_work() { reward: 3225 }); - add_who_into_replica(&cid, file_size, charlie.clone(), charlie.clone(), legal_pk.clone(), None, None); + add_who_into_replica(&cid, file_size, charlie.clone(), charlie.clone(), legal_pk.clone(), legal_wr_info.block_number, 503, 503); run_to_block(603); - ::insert(legal_pk.clone(), 300, true); - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); + assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, spower: Market::calculate_spower(file_size, 1), expired_at: 1303, - calculated_at: 603, + calculated_at: 303, amount: 16770, prepaid: 0, reported_replica_count: 2, @@ -2659,18 +2721,17 @@ fn spower_delay_should_work() { } ); - add_who_into_replica(&cid, file_size, dave.clone(), dave.clone(), hex::decode("11").unwrap(), None, None); run_to_block(703); - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); + add_who_into_replica(&cid, file_size, dave.clone(), dave.clone(), hex::decode("11").unwrap(), legal_wr_info.block_number, 703, 703); assert_eq!(Market::filesv2(&cid).unwrap_or_default(), FileInfoV2 { file_size, spower: Market::calculate_spower(file_size, 1), expired_at: 1303, - calculated_at: 703, + calculated_at: 303, amount: 13545, prepaid: 0, - reported_replica_count: 2, + reported_replica_count: 3, remaining_paid_count: 1, replicas: BTreeMap::from_iter(vec![ (merchant.clone(), Replica { @@ -2691,91 +2752,93 @@ fn spower_delay_should_work() { who: dave.clone(), valid_at: 703, anchor: hex::decode("11").unwrap(), - is_reported: false, - created_at: Some(603) - }) - ]) - } - ); - - run_to_block(903); - ::insert(hex::decode("11").unwrap(), 600, true); - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); - assert_eq!(Market::filesv2(&cid).unwrap_or_default(), - FileInfoV2 { - file_size, - spower: Market::calculate_spower(file_size, 1), - expired_at: 1303, - calculated_at: 903, - amount: 13545, - prepaid: 0, - reported_replica_count: 1, - remaining_paid_count: 1, - replicas: BTreeMap::from_iter(vec![ - (merchant.clone(), Replica { - who: merchant.clone(), - valid_at: 903, - anchor: legal_pk.clone(), - is_reported: false, - created_at: None - }), - (charlie.clone(), Replica { - who: charlie.clone(), - valid_at: 903, - anchor: legal_pk.clone(), - is_reported: false, - created_at: None - }), - (dave.clone(), Replica { - who: dave.clone(), - valid_at: 703, - anchor: hex::decode("11").unwrap(), is_reported: true, - created_at: None + created_at: Some(703) }) ]) } ); - run_to_block(1203); - ::insert(hex::decode("11").unwrap(), 900, true); - ::insert(legal_pk.clone(), 900, true); - Market::update_replicas(&cid, System::block_number().try_into().unwrap()); - assert_eq!(Market::filesv2(&cid).unwrap_or_default(), - FileInfoV2 { - file_size, - spower: Market::calculate_spower(file_size, 1), - expired_at: 1303, - calculated_at: 1203, - amount: 13545, - prepaid: 0, - reported_replica_count: 3, - remaining_paid_count: 1, - replicas: BTreeMap::from_iter(vec![ - (merchant.clone(), Replica { - who: merchant.clone(), - valid_at: 903, - anchor: legal_pk.clone(), - is_reported: true, - created_at: None - }), - (charlie.clone(), Replica { - who: charlie.clone(), - valid_at: 903, - anchor: legal_pk.clone(), - is_reported: true, - created_at: None - }), - (dave.clone(), Replica { - who: dave.clone(), - valid_at: 703, - anchor: hex::decode("11").unwrap(), - is_reported: true, - created_at: None - }) - ]) - } - ); + // TODO: Revisit these test cases after implement the batch files' replicas valid_at and is_reported update logic in crust-spower service + + // run_to_block(903); + // ::insert(hex::decode("11").unwrap(), 600, true); + // Market::update_replicas(&cid, System::block_number().try_into().unwrap()); + // assert_eq!(Market::filesv2(&cid).unwrap_or_default(), + // FileInfoV2 { + // file_size, + // spower: Market::calculate_spower(file_size, 1), + // expired_at: 1303, + // calculated_at: 903, + // amount: 13545, + // prepaid: 0, + // reported_replica_count: 1, + // remaining_paid_count: 1, + // replicas: BTreeMap::from_iter(vec![ + // (merchant.clone(), Replica { + // who: merchant.clone(), + // valid_at: 903, + // anchor: legal_pk.clone(), + // is_reported: false, + // created_at: None + // }), + // (charlie.clone(), Replica { + // who: charlie.clone(), + // valid_at: 903, + // anchor: legal_pk.clone(), + // is_reported: false, + // created_at: None + // }), + // (dave.clone(), Replica { + // who: dave.clone(), + // valid_at: 703, + // anchor: hex::decode("11").unwrap(), + // is_reported: true, + // created_at: None + // }) + // ]) + // } + // ); + + // run_to_block(1203); + // ::insert(hex::decode("11").unwrap(), 900, true); + // ::insert(legal_pk.clone(), 900, true); + // Market::update_replicas(&cid, System::block_number().try_into().unwrap()); + // assert_eq!(Market::filesv2(&cid).unwrap_or_default(), + // FileInfoV2 { + // file_size, + // spower: Market::calculate_spower(file_size, 1), + // expired_at: 1303, + // calculated_at: 1203, + // amount: 13545, + // prepaid: 0, + // reported_replica_count: 3, + // remaining_paid_count: 1, + // replicas: BTreeMap::from_iter(vec![ + // (merchant.clone(), Replica { + // who: merchant.clone(), + // valid_at: 903, + // anchor: legal_pk.clone(), + // is_reported: true, + // created_at: None + // }), + // (charlie.clone(), Replica { + // who: charlie.clone(), + // valid_at: 903, + // anchor: legal_pk.clone(), + // is_reported: true, + // created_at: None + // }), + // (dave.clone(), Replica { + // who: dave.clone(), + // valid_at: 703, + // anchor: hex::decode("11").unwrap(), + // is_reported: true, + // created_at: None + // }) + // ]) + // } + // ); }); } diff --git a/cstrml/market/src/weight.rs b/cstrml/market/src/weight.rs index e25edf7a..ab28571b 100644 --- a/cstrml/market/src/weight.rs +++ b/cstrml/market/src/weight.rs @@ -47,4 +47,9 @@ impl crate::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().reads(8 as Weight)) .saturating_add(T::DbWeight::get().writes(4 as Weight)) } + fn update_replicas() -> Weight { + (1_000_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(500 as Weight)) + .saturating_add(T::DbWeight::get().writes(500 as Weight)) + } } diff --git a/cstrml/staking/src/mock.rs b/cstrml/staking/src/mock.rs index 2e2da234..1414522a 100644 --- a/cstrml/staking/src/mock.rs +++ b/cstrml/staking/src/mock.rs @@ -226,11 +226,12 @@ impl swork::Works for TestStaking { } impl MarketInterface> for TestStaking { - fn upsert_replica(_: &AID, _: AID, _: &MerkleRoot, _: u64, _: &SworkerAnchor, _: u32) -> (u64, bool) { (0, true) } - fn delete_replica(_: &AID, _: AID, _: &MerkleRoot, _: &SworkerAnchor) -> (u64, bool) { (0, true) } fn withdraw_staking_pot() -> BalanceOf { BalanceOf::::from(DSM_STAKING_PAYOUT.with(|v| *v.borrow())) } + + fn update_files_spower(_changed_files: &Vec<(MerkleRoot, u64, Vec<(AID, AID, SworkerAnchor, Option)>)>) { + } } pub struct TestBenefitInterface; diff --git a/cstrml/swork/benchmarking/src/lib.rs b/cstrml/swork/benchmarking/src/lib.rs index 5bb23cef..b69b7689 100644 --- a/cstrml/swork/benchmarking/src/lib.rs +++ b/cstrml/swork/benchmarking/src/lib.rs @@ -133,7 +133,7 @@ fn legal_work_report_with_deleted_files() -> ReportWorksInfo { } } -fn add_market_files(files: Vec<(MerkleRoot, u64, u64)>, user: T::AccountId, pub_key: Vec) { +fn add_market_files(files: Vec<(MerkleRoot, u64, u64)>, _user: T::AccountId, pub_key: Vec) { for (file, file_size, _) in files.clone().iter() { let mut replicas = BTreeMap::>::new(); for index in 0..200 { diff --git a/cstrml/swork/src/lib.rs b/cstrml/swork/src/lib.rs index 40c2754d..1128f5f8 100644 --- a/cstrml/swork/src/lib.rs +++ b/cstrml/swork/src/lib.rs @@ -71,6 +71,7 @@ pub trait WeightInfo { fn set_code() -> Weight; fn register() -> Weight; fn report_works(added: u32, deleted: u32) -> Weight; + fn update_spower(changed_sworkers_count: u32, changed_files_count: u32) -> Weight; fn create_group() -> Weight; fn join_group() -> Weight; fn quit_group() -> Weight; @@ -178,6 +179,37 @@ impl SworkerInterface for Module { fn get_owner(who: &T::AccountId) -> Option { Self::identities(who).unwrap_or_default().group } + + // Update the last processed block of work reports + fn update_last_processed_block_of_work_reports(last_processed_block: BlockNumber) { + LastProcessedBlockWorkReports::put(last_processed_block); + } + + // Update changed spower of sworkers + fn update_sworkers_changed_spower(sworker_spower_changed_map: &BTreeMap) { + for (anchor, changed_spower) in sworker_spower_changed_map { + WorkReports::mutate_exists(anchor, |maybe_wr| match *maybe_wr { + Some(WorkReport { ref mut spower, .. }) => { + if *changed_spower >= 0 { + *spower = spower.saturating_add(changed_spower.abs() as u64); + } else { + *spower = spower.saturating_sub(changed_spower.abs() as u64); + } + }, + ref mut i => *i = None, + }); + } + } + + // Update illegal file replicas count + fn update_illegal_file_replicas_count(illegal_file_replicas_map: &BTreeMap) { + // For those legacy report slots, we just ignore them because 'AddedFilesCount' is reset per report slot + if let Some(illegal_count) = illegal_file_replicas_map.get(&Self::get_current_reported_slot()) { + // Substract the illegal count + AddedFilesCount::mutate(|count| {*count = count.saturating_sub(*illegal_count)}); + } + } + } /// The module's configuration trait. @@ -241,9 +273,15 @@ decl_storage! { pub WorkReports get(fn work_reports): map hasher(twox_64_concat) SworkerAnchor => Option; + /// The last procssed block for the WorkReportsToProcess data, which is used by the crust-spower service for fresh new start + pub LastProcessedBlockWorkReports get (fn last_processed_block_work_reports): BlockNumber = 0; + /// The crust-spower service account pub SpowerSuperior get(fn spower_superior): Option; + /// The last spower update block, which is set during updateSpower call by Crust-Spower service + pub LastSpowerUpdateBlock get(fn last_spower_update_block): BlockNumber = 0; + /// The current report slot block number, this value should be a multiple of report slot block. pub CurrentReportSlot get(fn current_report_slot): ReportSlot = 0; @@ -333,7 +371,11 @@ decl_error! { /// Code has not been expired CodeNotExpired, /// Tee signature is not valid - InvalidTeeSignature + InvalidTeeSignature, + /// The spower superior account is not set. Please call the set_spower_superior extrinsic first. + SpowerSuperiorNotSet, + /// The caller account is not the spower superior account. Please check the caller account again. + IllegalSpowerSuperior } } @@ -712,12 +754,26 @@ decl_module! { slot, ); - // 12. Emit work report event + // 12. Emit QueueWorkReportSuccess event to be processed by the crust-spower off-chain service + let id = Self::identities(&reporter).unwrap_or_default(); + let owner = if let Some(group) = id.group { group.clone() } else { reporter.clone() }; + if added_files.len() > 0 || deleted_files.len() > 0 { + let curr_bn = Self::get_current_block_number(); + + // Update the LastProcessedBlockWorkReports field if this is the first time + if LastProcessedBlockWorkReports::get() == 0 { + // The curr_bn is the first block need to be indexed, so the last indexed block is the curr_bn - 1 + LastProcessedBlockWorkReports::put(curr_bn - 1); + } + + // Emit the QueueWorkReportSuccess event + Self::deposit_event(RawEvent::QueueWorkReportSuccess(anchor, reporter.clone(), owner.clone())); + } + + // 13. Emit work report event Self::deposit_event(RawEvent::WorksReportSuccess(reporter.clone(), curr_pk.clone())); - // 13. Try to free count limitation - let id = Self::identities(&reporter).unwrap_or_default(); - let owner = if let Some(group) = id.group { group } else { reporter }; + // 14. Try to free count limitation if T::BenefitInterface::maybe_free_count(&owner) { return Ok(Pays::No.into()); } @@ -736,6 +792,56 @@ decl_module! { Ok(()) } + /// Update sworker spower, which is called by Crust-Spower service + /// Arguments: + /// changed_spowers: + /// Vec<(SworkerAnchor, changed_spower_value)> + /// changed_files: + /// Specify the files changed spower data, the data structure is + /// Vec<(cid, spower, Vec<(owner, who, anchor, created_at)>)> + #[weight = T::WeightInfo::update_spower(changed_spowers.len() as u32, changed_files.len() as u32)] + pub fn update_spower( + origin, + changed_spowers: Vec<(SworkerAnchor, i64)>, + changed_files: Vec<(MerkleRoot, u64, Vec<(T::AccountId, T::AccountId, SworkerAnchor, Option)>)>, + ) -> DispatchResultWithPostInfo { + let caller = ensure_signed(origin)?; + let maybe_superior = Self::spower_superior(); + + // 1. Check if superior is set and the caller is superior + ensure!(maybe_superior.is_some(), Error::::SpowerSuperiorNotSet); + ensure!(Some(&caller) == maybe_superior.as_ref(), Error::::IllegalSpowerSuperior); + + // 2. Update sworker spower + for (anchor, changed_spower) in changed_spowers.iter() { + WorkReports::mutate_exists(anchor, |maybe_wr| match *maybe_wr { + Some(WorkReport { ref mut spower, .. }) => { + if *changed_spower >= 0 { + *spower = spower.saturating_add(changed_spower.abs() as u64); + } else { + *spower = spower.saturating_sub(changed_spower.abs() as u64); + } + }, + ref mut i => *i = None, + }); + } + + // 3. Update the file new spower in pallet_market + T::MarketInterface::update_files_spower(&changed_files); + + // 4. Set the LastSpowerUpdateBlock value, which is the latest block in updated_blocks + LastSpowerUpdateBlock::put(Self::get_current_block_number()); + + // 5. Emit the event + Self::deposit_event(RawEvent::UpdateSpowerSuccess(caller, + >::block_number(), + changed_spowers.len() as u32, + changed_files.len() as u32)); + + // Do not charge fee for management extrinsic + Ok(Pays::No.into()) + } + /// Create a group. One account can only create one group once. #[weight = T::WeightInfo::create_group()] pub fn create_group( @@ -1076,12 +1182,12 @@ impl Module { /// 3. update `Spower` and `Free` /// 4. call `Works::report_works` interface fn maybe_upsert_work_report( - reporter: &T::AccountId, + _reporter: &T::AccountId, anchor: &SworkerAnchor, reported_srd_size: u64, reported_files_size: u64, added_files: &Vec<(MerkleRoot, u64, u64)>, - deleted_files: &Vec<(MerkleRoot, u64, u64)>, + _deleted_files: &Vec<(MerkleRoot, u64, u64)>, reported_srd_root: &MerkleRoot, reported_files_root: &MerkleRoot, report_slot: u64, @@ -1093,13 +1199,8 @@ impl Module { // 1. Mark who has reported in this (report)slot ReportedInSlot::insert(&anchor, report_slot, true); - // 2. Update sOrder and get changed size - // loop added. if not exist, calculate spower. - // loop deleted, need to check each key whether we should delete it or not - let (added_files_size, added_files_count)= Self::update_files(reporter, added_files, &anchor, true); - let (deleted_files_size, _) = Self::update_files(reporter, deleted_files, &anchor, false); - - AddedFilesCount::mutate(|count| {*count = count.saturating_add(added_files_count)}); + // 2. Update sOrder + AddedFilesCount::mutate(|count| {*count = count.saturating_add(added_files.len() as u32)}); // 3. If contains work report if let Some(old_wr) = Self::work_reports(&anchor) { @@ -1109,10 +1210,11 @@ impl Module { } // 4. Construct work report - let spower = old_spower.saturating_add(added_files_size).saturating_sub(deleted_files_size); + // Do not change the spower here, it would be updated by the crust-spower service + // let spower = old_spower.saturating_add(added_files_size).saturating_sub(deleted_files_size); let wr = WorkReport { report_slot, - spower, + spower: old_spower, free: reported_srd_size, reported_files_size, reported_srd_root: reported_srd_root.clone(), @@ -1130,49 +1232,6 @@ impl Module { ReportedFilesSize::put(total_reported_files_size); } - /// Update sOrder information based on changed files, return the changed_file_size and changed_file_count - fn update_files( - reporter: &T::AccountId, - changed_files: &Vec<(MerkleRoot, u64, u64)>, - anchor: &SworkerPubKey, - is_added: bool) -> (u64, u32) { - let mut changed_spower: u64 = 0; - let mut changed_files_count: u32 = 0; - - // 1. Loop changed files - if is_added { - for (cid, size, valid_at) in changed_files { - let mut owner = reporter.clone(); - if let Some(identity) = Self::identities(reporter) { - if let Some(group_owner) = identity.group { - owner = group_owner; - } - }; - let (added_spower, is_valid_cid) = T::MarketInterface::upsert_replica(reporter, owner, cid, *size, anchor, TryInto::::try_into(*valid_at).ok().unwrap()); - changed_spower = changed_spower.saturating_add(added_spower); - if is_valid_cid { - changed_files_count += 1; - } - } - } else { - for (cid, _, _) in changed_files { - // 2. If mapping to storage orders - let mut owner = reporter.clone(); - if let Some(identity) = Self::identities(reporter) { - if let Some(group_owner) = identity.group { - owner = group_owner; - } - }; - let (deleted_spower, is_valid_cid) = T::MarketInterface::delete_replica(reporter, owner, cid, anchor); - changed_spower = changed_spower.saturating_add(deleted_spower); - if is_valid_cid { - changed_files_count += 1; - } - } - } - (changed_spower, changed_files_count) - } - /// Get workload by reporter account, /// this function should only be called in the 2nd last session of new era /// otherwise, it will be an void in this recursive loop, it mainly includes: @@ -1485,7 +1544,18 @@ decl_event!( SetPunishmentSuccess(bool), /// Remove the expired code success RemoveCodeSuccess(SworkerCode), + /// Work report has been added to the WorkReportsToProcess queue success + /// The first item is the sworker anchor + /// The second item is the account who send the work report + /// The third item is the owner account + QueueWorkReportSuccess(SworkerAnchor, AccountId, AccountId), /// Set the crust-spower service superior account. SetSpowerSuperiorSuccess(AccountId), + /// Update spower success + /// The first item is the account who update the spower. + /// The second item is the current block number + /// The third item is the updated sworkers count for sworker::WorkReports + /// The fourth item is the updated files count for market::FilesV2 + UpdateSpowerSuccess(AccountId, BlockNumber, u32, u32), } ); diff --git a/cstrml/swork/src/mock.rs b/cstrml/swork/src/mock.rs index 2adf6eba..dc20b210 100644 --- a/cstrml/swork/src/mock.rs +++ b/cstrml/swork/src/mock.rs @@ -23,6 +23,8 @@ pub use std::{cell::RefCell, collections::HashMap, borrow::Borrow, iter::FromIte pub type AccountId = AccountId32; pub type Balance = u64; +pub const SPOWER: AccountId32 = AccountId32::new([9u8; 32]); + thread_local! { static EXISTENTIAL_DEPOSIT: RefCell = RefCell::new(0); static LEGAL_PK: Vec = hex::decode("cb8a7b27493749c939da4bba7266f1476bb960e74891817544503212620dce3c94e1c26c622ccb9a840415881deef5412b548f22a7d5e5c05fb412cfdc8e5464").unwrap(); @@ -778,7 +780,7 @@ pub fn add_live_files(who: &AccountId, anchor: &SworkerAnchor) { fn insert_file(f_id: &MerkleRoot, calculated_at: u32, expired_at: u32, amount: Balance, prepaid: Balance, reported_replica_count: u32, replicas: BTreeMap::>, file_size: u64) { let file_info = FileInfoV2 { file_size, - spower: Market::calculate_spower(file_size, replicas.len() as u32), + spower: 0, expired_at, calculated_at, amount, @@ -795,3 +797,41 @@ pub fn update_identities() { Swork::on_initialize(REPORT_SLOT - UPDATE_OFFSET as u64); Swork::on_initialize(REPORT_SLOT - UPDATE_OFFSET as u64); } + +pub fn add_who_into_replica( + cid: &MerkleRoot, + reported_size: u64, + who: AccountId, + owner: AccountId, + anchor: SworkerAnchor, + report_slot: ReportSlot, + report_block: BlockNumber, + valid_at: BlockNumber) { + + assert_ok!(Market::update_replicas( + Origin::signed(SPOWER.clone()), + vec![(cid.clone(), + reported_size, + vec![(who.clone(), owner.clone(), anchor.clone(), report_slot, report_block, valid_at, true)] + )], + 400)); +} + +pub fn delete_replica( + cid: &MerkleRoot, + reported_size: u64, + who: AccountId, + owner: AccountId, + anchor: SworkerAnchor, + report_slot: ReportSlot, + report_block: BlockNumber, + valid_at: BlockNumber) { + + assert_ok!(Market::update_replicas( + Origin::signed(SPOWER.clone()), + vec![(cid.clone(), + reported_size, + vec![(who.clone(), owner.clone(), anchor.clone(), report_slot, report_block, valid_at, false)] + )], + 400)); +} diff --git a/cstrml/swork/src/tests.rs b/cstrml/swork/src/tests.rs index ea798177..d495f689 100644 --- a/cstrml/swork/src/tests.rs +++ b/cstrml/swork/src/tests.rs @@ -393,6 +393,8 @@ fn report_works_should_work() { run_to_block(303); let reporter: AccountId = Sr25519Keyring::Alice.to_account_id(); + let spower = SPOWER; + let legal_wr_info = legal_work_report_with_added_files(); let legal_pk = legal_wr_info.curr_pk.clone(); let legal_wr = WorkReport { @@ -404,6 +406,11 @@ fn report_works_should_work() { reported_files_root: legal_wr_info.files_root.clone() }; + let file_f = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oF".as_bytes().to_vec(); // F file + let file_h = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oH".as_bytes().to_vec(); // H file + + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + register(&legal_pk, LegalCode::get()); add_not_live_files(); @@ -425,6 +432,8 @@ fn report_works_should_work() { legal_wr_info.files_root, legal_wr_info.sig )); + add_who_into_replica(&file_f, 134289408, reporter.clone(), reporter.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); + add_who_into_replica(&file_h, 268578816, reporter.clone(), reporter.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); // Check work report assert_eq!(Swork::work_reports(&legal_pk).unwrap(), legal_wr); @@ -497,6 +506,8 @@ fn report_works_for_invalid_cids_should_work() { run_to_block(303); let reporter: AccountId = Sr25519Keyring::Alice.to_account_id(); + let spower = SPOWER; + let legal_wr_info = legal_work_report_with_added_files(); let legal_pk = legal_wr_info.curr_pk.clone(); let legal_wr = WorkReport { @@ -508,6 +519,11 @@ fn report_works_for_invalid_cids_should_work() { reported_files_root: legal_wr_info.files_root.clone() }; + let file_f = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oF".as_bytes().to_vec(); // F file + let file_h = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oH".as_bytes().to_vec(); // H file + + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + register(&legal_pk, LegalCode::get()); // Check workloads before reporting @@ -528,6 +544,10 @@ fn report_works_for_invalid_cids_should_work() { legal_wr_info.files_root, legal_wr_info.sig )); + // Since file cid is not in FilesV2, the following call should trigger the T::SworkerInterface::update_illegal_file_replicas_count + // which should make the Swork::Added_Files_Count to become 0 again + add_who_into_replica(&file_f, 134289408, reporter.clone(), reporter.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); + add_who_into_replica(&file_h, 268578816, reporter.clone(), reporter.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); // Check work report assert_eq!(Swork::work_reports(&legal_pk).unwrap(), legal_wr); @@ -891,6 +911,11 @@ fn incremental_report_should_work_with_files_change() { ExtBuilder::default() .build() .execute_with(|| { + + let spower = SPOWER; + + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + // Generate 303 blocks first run_to_block(303); @@ -927,12 +952,16 @@ fn incremental_report_should_work_with_files_change() { legal_wr_info.free, legal_wr_info.spower, legal_wr_info.added_files, - legal_wr_info.deleted_files, + legal_wr_info.deleted_files.clone(), legal_wr_info.srd_root, legal_wr_info.files_root, legal_wr_info.sig )); + for deleted_file in legal_wr_info.deleted_files { + delete_replica(&deleted_file.0, deleted_file.1, reporter.clone(), reporter.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); + } + // Check work report assert_eq!(Swork::work_reports(&legal_pk).unwrap(), legal_wr); @@ -1163,12 +1192,16 @@ fn ab_upgrade_should_work() { .build() .execute_with(|| { let reporter: AccountId = Sr25519Keyring::Alice.to_account_id(); + let spower = SPOWER; + let a_wr_info = legal_work_report(); let b_wr_info_1 = ab_upgrade_work_report(); let b_wr_info_2 = continuous_ab_upgrade_work_report(); let a_pk = a_wr_info.curr_pk.clone(); let b_pk = b_wr_info_1.curr_pk.clone(); + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + // 0. Initial setup register(&a_pk, LegalCode::get()); register_identity(&reporter, &a_pk, &a_pk); @@ -1275,6 +1308,11 @@ fn ab_upgrade_should_work() { b_wr_info_2.files_root, b_wr_info_2.sig )); + // b_wr_info_2 contains 1 added_file (file_c) and 1 deleted_file (file_b) + let file_b = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oB".as_bytes().to_vec(); // B file + let file_c = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oC".as_bytes().to_vec(); // C file + add_who_into_replica(&file_c, 37, reporter.clone(), reporter.clone(), a_pk.clone(), b_wr_info_2.block_number, 909, 903); + delete_replica(&file_b, 7, reporter.clone(), reporter.clone(), a_pk.clone(), b_wr_info_2.block_number, 909, 903); // 11. Check B's work report and free & spower again assert_eq!(Swork::work_reports(&a_pk).unwrap(), WorkReport { @@ -1892,6 +1930,9 @@ fn join_group_should_work_for_spower_in_work_report() { let bob = Sr25519Keyring::Bob.to_account_id(); let eve = Sr25519Keyring::Eve.to_account_id(); let ferdie = Sr25519Keyring::Ferdie.to_account_id(); + let spower = SPOWER; + + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); // Get work report in 300 slot fo alice, bob and eve let alice_wr_info = group_work_report_alice_300(); @@ -1968,6 +2009,10 @@ fn join_group_should_work_for_spower_in_work_report() { alice_wr_info.sig )); + add_who_into_replica(&file_a, 13, alice.clone(), ferdie.clone(), a_pk.clone(), alice_wr_info.block_number, 303, 303); + add_who_into_replica(&file_b, 7, alice.clone(), ferdie.clone(), a_pk.clone(), alice_wr_info.block_number, 303, 303); + add_who_into_replica(&file_c, 37, alice.clone(), ferdie.clone(), a_pk.clone(), alice_wr_info.block_number, 303, 303); + assert_eq!(Market::filesv2(&file_a).unwrap_or_default(), FileInfoV2 { file_size: 13, @@ -2053,6 +2098,10 @@ fn join_group_should_work_for_spower_in_work_report() { bob_wr_info.sig )); + add_who_into_replica(&file_b, 7, bob.clone(), ferdie.clone(), b_pk.clone(), bob_wr_info.block_number, 303, 303); + add_who_into_replica(&file_c, 37, bob.clone(), ferdie.clone(), b_pk.clone(), bob_wr_info.block_number, 303, 303); + add_who_into_replica(&file_d, 55, bob.clone(), ferdie.clone(), b_pk.clone(), bob_wr_info.block_number, 303, 303); + assert_eq!(Market::filesv2(&file_b).unwrap_or_default(), FileInfoV2 { file_size: 7, @@ -2166,6 +2215,10 @@ fn join_group_should_work_for_spower_in_work_report() { eve_wr_info.sig )); + add_who_into_replica(&file_c, 37, eve.clone(), ferdie.clone(), c_pk.clone(), eve_wr_info.block_number, 303, 303); + add_who_into_replica(&file_d, 55, eve.clone(), ferdie.clone(), c_pk.clone(), eve_wr_info.block_number, 303, 303); + add_who_into_replica(&file_e, 22, eve.clone(), ferdie.clone(), c_pk.clone(), eve_wr_info.block_number, 303, 303); + assert_eq!(Market::filesv2(&file_c).unwrap_or_default(), FileInfoV2 { file_size: 37, @@ -2270,6 +2323,8 @@ fn join_group_should_work_for_spower_in_work_report() { bob_wr_info.files_root, bob_wr_info.sig )); + delete_replica(&file_b, 7, bob.clone(), ferdie.clone(), b_pk.clone(), bob_wr_info.block_number, 603, 603); + delete_replica(&file_c, 37, bob.clone(), ferdie.clone(), b_pk.clone(), bob_wr_info.block_number, 603, 603); assert_eq!(Market::filesv2(&file_b).unwrap_or_default(), FileInfoV2 { @@ -2361,6 +2416,10 @@ fn join_group_should_work_for_spower_in_work_report() { eve_wr_info.files_root, eve_wr_info.sig )); + delete_replica(&file_c, 37, eve.clone(), ferdie.clone(), c_pk.clone(), eve_wr_info.block_number, 603, 603); + delete_replica(&file_d, 55, eve.clone(), ferdie.clone(), c_pk.clone(), eve_wr_info.block_number, 603, 603); + delete_replica(&file_e, 22, eve.clone(), ferdie.clone(), c_pk.clone(), eve_wr_info.block_number, 603, 603); + assert_eq!(Market::filesv2(&file_c).unwrap_or_default(), FileInfoV2 { file_size: 37, @@ -2440,7 +2499,7 @@ fn join_group_should_work_for_spower_in_work_report() { assert_eq!(Swork::work_reports(&a_pk).unwrap(), WorkReport { report_slot: 300, - spower: 20, + spower: 57, free: 4294967296, reported_files_size: 57, reported_srd_root: hex::decode("00").unwrap(), @@ -2449,7 +2508,7 @@ fn join_group_should_work_for_spower_in_work_report() { assert_eq!(Swork::work_reports(&b_pk).unwrap(), WorkReport { report_slot: 600, - spower: 0, + spower: 55, free: 4294967296, reported_files_size: 55, reported_srd_root: hex::decode("00").unwrap(), @@ -2490,6 +2549,9 @@ fn join_group_should_work_for_spower_in_work_report() { alice_wr_info.files_root, alice_wr_info.sig )); + delete_replica(&file_a, 13, alice.clone(), ferdie.clone(), a_pk.clone(), alice_wr_info.block_number, 1503, 1503); + delete_replica(&file_b, 7, alice.clone(), ferdie.clone(), a_pk.clone(), alice_wr_info.block_number, 1503, 1503); + delete_replica(&file_c, 37, alice.clone(), ferdie.clone(), a_pk.clone(), alice_wr_info.block_number, 1503, 1503); // delete won't call calculate payout anymore and won't close the file assert_eq!(Market::filesv2(&file_a).is_some(), true); @@ -2512,10 +2574,19 @@ fn join_group_should_work_for_spower_in_work_report() { assert_eq!(Market::filesv2(&file_a), None); assert_eq!(Market::filesv2(&file_b), None); + // calculate_reward doesn't update spower now, the offchain crust-spower service will listen to the FileClosed event + // and update the spower for file_c and file_d + assert_ok!(Swork::set_spower_superior(Origin::root(), spower.clone())); + assert_ok!(Swork::update_spower( + Origin::signed(spower.clone()), + vec![(b_pk.clone(), (0 as i64 - 55 as i64)), + (a_pk.clone(), (0 as i64 - 37 as i64))], + vec![])); + // d has gone! assert_eq!(Swork::work_reports(&b_pk).unwrap(), WorkReport { report_slot: 600, - spower: 0, + spower: 0, free: 4294967296, reported_files_size: 55, reported_srd_root: hex::decode("00").unwrap(), @@ -2543,6 +2614,7 @@ fn join_group_should_work_for_stake_limit() { let bob = Sr25519Keyring::Bob.to_account_id(); let eve = Sr25519Keyring::Eve.to_account_id(); let ferdie = Sr25519Keyring::Ferdie.to_account_id(); + let spower = SPOWER; let alice_wr_info = group_work_report_alice_300(); let bob_wr_info = group_work_report_bob_300(); @@ -2551,6 +2623,14 @@ fn join_group_should_work_for_stake_limit() { let b_pk = bob_wr_info.curr_pk.clone(); let c_pk = eve_wr_info.curr_pk.clone(); + let file_a = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oA".as_bytes().to_vec(); // A file + let file_b = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oB".as_bytes().to_vec(); // B file + let file_c = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oC".as_bytes().to_vec(); // C file + let file_d = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oD".as_bytes().to_vec(); // D file + let file_e = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oE".as_bytes().to_vec(); // E file + + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + register(&a_pk, LegalCode::get()); register(&b_pk, LegalCode::get()); register(&c_pk, LegalCode::get()); @@ -2611,6 +2691,9 @@ fn join_group_should_work_for_stake_limit() { alice_wr_info.files_root, alice_wr_info.sig )); + add_who_into_replica(&file_a, 13, alice.clone(), ferdie.clone(), a_pk.clone(), alice_wr_info.block_number, 303, 303); + add_who_into_replica(&file_b, 7, alice.clone(), ferdie.clone(), a_pk.clone(), alice_wr_info.block_number, 303, 303); + add_who_into_replica(&file_c, 37, alice.clone(), ferdie.clone(), a_pk.clone(), alice_wr_info.block_number, 303, 303); assert_ok!(Swork::report_works( Origin::signed(bob.clone()), bob_wr_info.curr_pk, @@ -2625,6 +2708,9 @@ fn join_group_should_work_for_stake_limit() { bob_wr_info.files_root, bob_wr_info.sig )); + add_who_into_replica(&file_b, 7, bob.clone(), ferdie.clone(), b_pk.clone(), bob_wr_info.block_number, 303, 303); + add_who_into_replica(&file_c, 37, bob.clone(), ferdie.clone(), b_pk.clone(), bob_wr_info.block_number, 303, 303); + add_who_into_replica(&file_d, 55, bob.clone(), ferdie.clone(), b_pk.clone(), bob_wr_info.block_number, 303, 303); assert_ok!(Swork::report_works( Origin::signed(eve.clone()), eve_wr_info.curr_pk, @@ -2639,6 +2725,9 @@ fn join_group_should_work_for_stake_limit() { eve_wr_info.files_root, eve_wr_info.sig )); + add_who_into_replica(&file_c, 37, eve.clone(), ferdie.clone(), c_pk.clone(), eve_wr_info.block_number, 303, 303); + add_who_into_replica(&file_d, 55, eve.clone(), ferdie.clone(), c_pk.clone(), eve_wr_info.block_number, 303, 303); + add_who_into_replica(&file_e, 22, eve.clone(), ferdie.clone(), c_pk.clone(), eve_wr_info.block_number, 303, 303); run_to_block(603); update_identities(); @@ -2753,6 +2842,13 @@ fn kick_out_should_work_for_stake_limit() { let bob = Sr25519Keyring::Bob.to_account_id(); let eve = Sr25519Keyring::Eve.to_account_id(); let ferdie = Sr25519Keyring::Ferdie.to_account_id(); + let spower = SPOWER; + + let file_a = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oA".as_bytes().to_vec(); // A file + let file_b = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oB".as_bytes().to_vec(); // B file + let file_c = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oC".as_bytes().to_vec(); // C file + + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); assert_noop!( Swork::kick_out( @@ -2821,6 +2917,10 @@ fn kick_out_should_work_for_stake_limit() { alice_wr_info.files_root, alice_wr_info.sig )); + add_who_into_replica(&file_a, 13, alice.clone(), ferdie.clone(), a_pk.clone(), alice_wr_info.block_number, 303, 303); + add_who_into_replica(&file_b, 7, alice.clone(), ferdie.clone(), a_pk.clone(), alice_wr_info.block_number, 303, 303); + add_who_into_replica(&file_c, 37, alice.clone(), ferdie.clone(), a_pk.clone(), alice_wr_info.block_number, 303, 303); + run_to_block(603); @@ -2861,10 +2961,17 @@ fn punishment_by_offline_should_work_for_stake_limit() { .execute_with(|| { let alice = Sr25519Keyring::Alice.to_account_id(); let ferdie = Sr25519Keyring::Ferdie.to_account_id(); + let spower = SPOWER; + + let file_a = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oA".as_bytes().to_vec(); // A file + let file_b = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oB".as_bytes().to_vec(); // B file + let file_c = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oC".as_bytes().to_vec(); // C file let alice_wr_info = group_work_report_alice_300(); let a_pk = alice_wr_info.curr_pk.clone(); + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + register(&a_pk, LegalCode::get()); register_identity(&alice, &a_pk, &a_pk); @@ -2901,6 +3008,9 @@ fn punishment_by_offline_should_work_for_stake_limit() { alice_wr_info.files_root, alice_wr_info.sig )); + add_who_into_replica(&file_a, 13, alice.clone(), ferdie.clone(), a_pk.clone(), alice_wr_info.block_number, 303, 303); + add_who_into_replica(&file_b, 7, alice.clone(), ferdie.clone(), a_pk.clone(), alice_wr_info.block_number, 303, 303); + add_who_into_replica(&file_c, 37, alice.clone(), ferdie.clone(), a_pk.clone(), alice_wr_info.block_number, 303, 303); run_to_block(603); update_identities(); @@ -3134,14 +3244,15 @@ fn update_identities_timeline_should_work() { }); add_live_files(&reporter, &legal_pk); - run_to_block(202); + run_to_block(252); // Start update identity - Swork::on_initialize(200); + // UPDATE_OFFSET has changed to REPORT_SLOT/6, so change the block from 200 to 250 + Swork::on_initialize(250); assert_eq!(Swork::workload().is_some(), true); assert_eq!(Swork::identity_previous_key().is_none(), true); assert_eq!(WorkloadMap::get().borrow().get(&reporter).is_none(), true); - Swork::on_initialize(201); + Swork::on_initialize(251); assert_eq!(Swork::workload().is_none(), true); assert_eq!(Swork::identity_previous_key().is_none(), true); assert_eq!(*WorkloadMap::get().borrow().get(&reporter).unwrap(), 2u128); @@ -3175,20 +3286,22 @@ fn update_identities_timeline_should_work() { assert_eq!(*WorkloadMap::get().borrow().get(&reporter).unwrap(), 2u128); assert_eq!(Swork::current_report_slot(), 0); // Start update identity - Swork::on_initialize(500); + // UPDATE_OFFSET has changed to REPORT_SLOT/6, so change the block from 500 to 550 + Swork::on_initialize(550); assert_eq!(Swork::workload().is_some(), true); assert_eq!(Swork::identity_previous_key().is_none(), true); assert_eq!(*WorkloadMap::get().borrow().get(&reporter).unwrap(), 2u128); assert_eq!(Swork::current_report_slot(), 0); - Swork::on_initialize(500); + Swork::on_initialize(550); assert_eq!(Swork::workload().is_none(), true); assert_eq!(Swork::identity_previous_key().is_none(), true); assert_eq!(*WorkloadMap::get().borrow().get(&reporter).unwrap(), 4u128); assert_eq!(Swork::current_report_slot(), 300); - run_to_block(800); + run_to_block(850); // Start update identity, report_in_slot is false => no workload - Swork::on_initialize(800); + // UPDATE_OFFSET has changed to REPORT_SLOT/6, so change the block from 800 to 850 + Swork::on_initialize(850); assert_eq!(Swork::workload().is_some(), true); assert_eq!(Swork::identity_previous_key().is_none(), true); assert_eq!(*WorkloadMap::get().borrow().get(&reporter).unwrap(), 4u128); @@ -3453,6 +3566,8 @@ fn spower_delay_should_work() { market::SpowerReadyPeriod::put(300); let reporter: AccountId = Sr25519Keyring::Alice.to_account_id(); + let spower = SPOWER; + let legal_wr_info = legal_work_report_with_added_files(); let legal_pk = legal_wr_info.curr_pk.clone(); let legal_wr = WorkReport { @@ -3464,6 +3579,12 @@ fn spower_delay_should_work() { reported_files_root: legal_wr_info.files_root.clone() }; + let file_f = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oF".as_bytes().to_vec(); // F file + let file_h = "QmdwgqZy1MZBfWPi7GcxVsYgJEtmvHg6rsLzbCej3tf3oH".as_bytes().to_vec(); // H file + + assert_ok!(Market::set_spower_superior(Origin::root(), spower.clone())); + assert_ok!(Swork::set_spower_superior(Origin::root(), spower.clone())); + register(&legal_pk, LegalCode::get()); add_not_live_files(); @@ -3485,6 +3606,8 @@ fn spower_delay_should_work() { legal_wr_info.files_root, legal_wr_info.sig )); + add_who_into_replica(&file_f, 134289408, reporter.clone(), reporter.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); + add_who_into_replica(&file_h, 268578816, reporter.clone(), reporter.clone(), legal_pk.clone(), legal_wr_info.block_number, 303, 303); // Check work report assert_eq!(Swork::work_reports(&legal_pk).unwrap(), legal_wr); @@ -3544,6 +3667,15 @@ fn spower_delay_should_work() { run_to_block(606); assert_ok!(Market::calculate_reward(Origin::signed(reporter.clone()), legal_wr_info.added_files[0].0.clone())); + // calculate_reward doesn't update spower anymore, it's done by the offchain crust-spower + let cid_0 = legal_wr_info.added_files[0].0.clone(); + let file_size_0 = legal_wr_info.added_files[0].1; + let file_spower_0 = Market::calculate_spower(file_size_0, 1); + assert_ok!(Swork::update_spower( + Origin::signed(spower.clone()), + vec![(legal_pk.clone(), (file_spower_0 as i64 - file_size_0 as i64))], + vec![(cid_0.clone(), file_spower_0, vec![(reporter.clone(), reporter.clone(), legal_pk.clone(), None)])])); + assert_eq!(Swork::work_reports(&legal_pk).unwrap(), WorkReport { report_slot: 300, spower: Market::calculate_spower(134289408, 1) + 268578816, @@ -3554,6 +3686,14 @@ fn spower_delay_should_work() { }); assert_ok!(Market::calculate_reward(Origin::signed(reporter.clone()), legal_wr_info.added_files[1].0.clone())); + let cid_1 = legal_wr_info.added_files[1].0.clone(); + let file_size_1 = legal_wr_info.added_files[1].1; + let file_spower_1 = Market::calculate_spower(file_size_1, 1); + assert_ok!(Swork::update_spower( + Origin::signed(spower.clone()), + vec![(legal_pk.clone(), (file_spower_1 as i64 - file_size_1 as i64))], + vec![(cid_1.clone(), file_spower_1, vec![(reporter.clone(), reporter.clone(), legal_pk.clone(), None)])])); + assert_eq!(Swork::work_reports(&legal_pk).unwrap(), WorkReport { report_slot: 300, spower: Market::calculate_spower(134289408, 1) + Market::calculate_spower(268578816, 1), diff --git a/cstrml/swork/src/weight.rs b/cstrml/swork/src/weight.rs index cf5e3ae0..c9ccb5e7 100644 --- a/cstrml/swork/src/weight.rs +++ b/cstrml/swork/src/weight.rs @@ -50,6 +50,13 @@ impl crate::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().reads(2 as Weight).saturating_mul(deleted as Weight)) .saturating_add(T::DbWeight::get().writes(2 as Weight).saturating_mul(deleted as Weight)) } + fn update_spower(changed_sworkers_count: u32, changed_files_count: u32) -> Weight { + (100_000_000 as Weight) + .saturating_add(T::DbWeight::get().reads(1 as Weight).saturating_mul(changed_sworkers_count as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight).saturating_mul(changed_sworkers_count as Weight)) + .saturating_add(T::DbWeight::get().reads(1 as Weight).saturating_mul(changed_files_count as Weight)) + .saturating_add(T::DbWeight::get().writes(1 as Weight).saturating_mul(changed_files_count as Weight)) + } fn create_group() -> Weight { (58_000_000 as Weight) .saturating_add(T::DbWeight::get().reads(6 as Weight)) diff --git a/primitives/src/constants.rs b/primitives/src/constants.rs index 0c581926..e02a9aa8 100644 --- a/primitives/src/constants.rs +++ b/primitives/src/constants.rs @@ -70,7 +70,7 @@ pub mod swork { #[cfg(not(feature = "test"))] pub const REPORT_SLOT: u64 = EPOCH_DURATION_IN_BLOCKS as u64; - pub const UPDATE_OFFSET: u32 = (REPORT_SLOT / 3) as u32; + pub const UPDATE_OFFSET: u32 = (REPORT_SLOT / 6) as u32; pub const END_OFFSET: u32 = 1; } diff --git a/primitives/src/traits.rs b/primitives/src/traits.rs index 4f357c87..b04b3763 100644 --- a/primitives/src/traits.rs +++ b/primitives/src/traits.rs @@ -2,8 +2,10 @@ // This file is part of Crust. use frame_support::traits::{LockableCurrency, WithdrawReasons}; -use crate::{SworkerAnchor, MerkleRoot, BlockNumber, EraIndex}; +use crate::{BlockNumber, EraIndex, MerkleRoot, ReportSlot, SworkerAnchor}; use sp_runtime::{DispatchError, Perbill}; +use sp_std::collections::btree_map::BTreeMap; +use sp_std::vec::Vec; /// A currency whose accounts can have liquidity restrictions. pub trait UsableCurrency: LockableCurrency { @@ -26,18 +28,20 @@ pub trait SworkerInterface { fn get_added_files_count_and_clear_record() -> u32; // Get owner of this member fn get_owner(who: &AccountId) -> Option; + // Update the last processed block of work reports + fn update_last_processed_block_of_work_reports(last_processed_block: BlockNumber); + // Update changed spower of sworkers + fn update_sworkers_changed_spower(sworker_spower_changed_map: &BTreeMap); + // Update illegal file replicas count + fn update_illegal_file_replicas_count(illegal_file_replicas_map: &BTreeMap); } /// Means for interacting with a specialized version of the `market` trait. pub trait MarketInterface { - // used for `added_files` - // return real spower of this file and whether this file is in the market system - fn upsert_replica(who: &AccountId, owner: AccountId, cid: &MerkleRoot, reported_file_size: u64, anchor: &SworkerAnchor, valid_at: BlockNumber) -> (u64, bool); - // used for `delete_files` - // return real spower of this file and whether this file is in the market system - fn delete_replica(who: &AccountId, owner: AccountId, cid: &MerkleRoot, anchor: &SworkerAnchor) -> (u64, bool); // used for distribute market staking payout fn withdraw_staking_pot() -> Balance; + // Update files spower in market::FilesV2 + fn update_files_spower(changed_files: &Vec<(MerkleRoot, u64, Vec<(AccountId, AccountId, SworkerAnchor, Option)>)>); } pub trait BenefitInterface { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index b4a9bcfe..f32aabc2 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -103,7 +103,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("crust"), impl_name: create_runtime_str!("crustio-crust"), authoring_version: 1, - spec_version: 22, + spec_version: 23, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 1