diff --git a/Cargo.lock b/Cargo.lock index 801bb4ec4..efb21d6d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5318,6 +5318,8 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "hex", + "log", "pallet-balances", "pallet-timestamp", "parity-scale-codec", diff --git a/pallets/ddc-nodes/Cargo.toml b/pallets/ddc-nodes/Cargo.toml index 20551c5dc..f8e6e47c6 100644 --- a/pallets/ddc-nodes/Cargo.toml +++ b/pallets/ddc-nodes/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true [dependencies] # 3rd-party dependencies codec = { workspace = true } +log = { workspace = true } scale-info = { workspace = true } serde = { workspace = true } @@ -18,6 +19,7 @@ serde = { workspace = true } frame-benchmarking = { workspace = true, optional = true } frame-support = { workspace = true } frame-system = { workspace = true } +hex = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } @@ -35,6 +37,7 @@ substrate-test-utils = { workspace = true, default-features = true } [features] default = ["std"] std = [ + "hex/std", "codec/std", "ddc-primitives/std", "frame-benchmarking/std", diff --git a/pallets/ddc-nodes/src/lib.rs b/pallets/ddc-nodes/src/lib.rs index 54af6aa03..2a0e2fdc8 100644 --- a/pallets/ddc-nodes/src/lib.rs +++ b/pallets/ddc-nodes/src/lib.rs @@ -32,12 +32,13 @@ use ddc_primitives::{ node::{NodeCreator, NodeVisitor}, staking::StakingVisitor, }, - ClusterId, NodeParams, NodePubKey, StorageNodeParams, StorageNodePubKey, + ClusterId, NodeParams, NodePubKey, NodeUsage, StorageNodeParams, StorageNodePubKey, }; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; pub use pallet::*; use sp_std::prelude::*; +pub mod migrations; mod node; mod storage_node; @@ -53,7 +54,7 @@ pub mod pallet { /// The current storage version. const STORAGE_VERSION: frame_support::traits::StorageVersion = - frame_support::traits::StorageVersion::new(0); + frame_support::traits::StorageVersion::new(1); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] @@ -259,6 +260,13 @@ pub mod pallet { }, } } + + fn get_total_usage(node_pub_key: &NodePubKey) -> Result, DispatchError> { + let node = Self::get(node_pub_key.clone()).map_err(|_| Error::::NodeDoesNotExist)?; + let total_usage = node.get_total_usage().clone(); + + Ok(total_usage) + } } impl NodeCreator for Pallet { diff --git a/pallets/ddc-nodes/src/migrations.rs b/pallets/ddc-nodes/src/migrations.rs new file mode 100644 index 000000000..8f50f2f1f --- /dev/null +++ b/pallets/ddc-nodes/src/migrations.rs @@ -0,0 +1,203 @@ +#[cfg(feature = "try-runtime")] +use ddc_primitives::StorageNodePubKey; +#[cfg(feature = "try-runtime")] +use frame_support::ensure; +use frame_support::{ + storage_alias, + traits::{Get, GetStorageVersion, OnRuntimeUpgrade, StorageVersion}, + weights::Weight, +}; +use log::info; +use serde::{Deserialize, Serialize}; + +use super::*; +use crate::{ + storage_node::{StorageNode, StorageNodeProps}, + ClusterId, +}; + +const LOG_TARGET: &str = "ddc-customers"; + +pub mod v0 { + use frame_support::pallet_prelude::*; + + use super::*; + + // Define the old storage node structure + #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq, Serialize, Deserialize)] + #[scale_info(skip_type_params(T))] + pub struct StorageNode { + pub pub_key: StorageNodePubKey, + pub provider_id: ::AccountId, + pub cluster_id: Option, + pub props: StorageNodeProps, + } + + #[storage_alias] + pub type StorageNodes = + StorageMap, Blake2_128Concat, StorageNodePubKey, StorageNode>; +} +pub fn migrate_to_v1() -> Weight { + let on_chain_version = Pallet::::on_chain_storage_version(); + let current_version = Pallet::::current_storage_version(); + + info!( + target: LOG_TARGET, + "Running migration with current storage version {:?} / onchain {:?}", + current_version, + on_chain_version + ); + + if on_chain_version == 0 && current_version == 1 { + let weight = T::DbWeight::get().reads(1); + + let count = v0::StorageNodes::::iter().count(); + info!( + target: LOG_TARGET, + " >>> Updating DDC Storage Nodes. Migrating {} nodes...", count + ); + StorageNodes::::translate::, _>(|_, old: v0::StorageNode| { + let node_pub_key_ref: &[u8; 32] = old.pub_key.as_ref(); + let node_pub_key_string = hex::encode(node_pub_key_ref); + info!(target: LOG_TARGET, " Migrating node for node ID {:?}...", node_pub_key_string); + + Some(StorageNode { + pub_key: old.pub_key, + provider_id: old.provider_id, + cluster_id: old.cluster_id, + props: old.props, + total_usage: None, // Default value for the new field + }) + }); + + // Update storage version. + StorageVersion::new(1).put::>(); + let count = StorageNodes::::iter().count(); + info!( + target: LOG_TARGET, + "Upgraded {} records, storage to version {:?}", + count, + current_version + ); + + weight.saturating_add(T::DbWeight::get().reads_writes(count as u64 + 1, count as u64 + 1)) + } else { + info!(target: LOG_TARGET, " >>> Unused migration!"); + T::DbWeight::get().reads(1) + } +} + +pub struct MigrateToV1(PhantomData); + +impl OnRuntimeUpgrade for MigrateToV1 { + fn on_runtime_upgrade() -> Weight { + migrate_to_v1::() + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::DispatchError> { + let prev_count = StorageNodes::::iter().count(); + + Ok((prev_count as u64).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(prev_state: Vec) -> Result<(), sp_runtime::DispatchError> { + let prev_count: u64 = + Decode::decode(&mut &prev_state[..]).expect("pre_upgrade provides a valid state; qed"); + + let post_count = StorageNodes::::iter().count() as u64; + ensure!( + prev_count == post_count, + "the storage node count before and after the migration should be the same" + ); + + let current_version = Pallet::::current_storage_version(); + let on_chain_version = Pallet::::on_chain_storage_version(); + + ensure!(current_version == 1, "must_upgrade"); + ensure!( + current_version == on_chain_version, + "after migration, the current_version and on_chain_version should be the same" + ); + + // Ensure all nodes have total_usage set to None + for (_key, node) in StorageNodes::::iter() { + ensure!(node.total_usage.is_none(), "total_usage should be None"); + } + + Ok(()) + } +} + +#[cfg(test)] +#[cfg(feature = "try-runtime")] +mod test { + use ddc_primitives::StorageNodeMode; + use frame_support::pallet_prelude::StorageVersion; + + use super::*; + use crate::mock::{Test as T, *}; + + #[test] + fn storage_node_migration_works() { + ExtBuilder.build_and_execute(|| { + let node_pub_key0 = StorageNodePubKey::from([0; 32]); + let node_pub_key1 = StorageNodePubKey::from([1; 32]); + let node_pub_key2 = StorageNodePubKey::from([2; 32]); + let provider_id = AccountId::from([1; 32]); + let cluster_id = Some(ClusterId::from([1; 20])); + + assert_eq!(StorageVersion::get::>(), 0); + + let node1 = v0::StorageNode { + pub_key: node_pub_key1.clone(), + provider_id: provider_id.clone(), + cluster_id, + props: StorageNodeProps { + mode: StorageNodeMode::Storage, + host: vec![3u8; 255].try_into().unwrap(), + domain: vec![4u8; 255].try_into().unwrap(), + ssl: true, + http_port: 45000u16, + grpc_port: 55000u16, + p2p_port: 65000u16, + }, + }; + + v0::StorageNodes::::insert(node_pub_key1.clone(), node1); + let node2 = v0::StorageNode { + pub_key: node_pub_key2.clone(), + provider_id: provider_id.clone(), + cluster_id, + props: StorageNodeProps { + mode: StorageNodeMode::Storage, + host: vec![3u8; 255].try_into().unwrap(), + domain: vec![4u8; 255].try_into().unwrap(), + ssl: true, + http_port: 45000u16, + grpc_port: 55000u16, + p2p_port: 65000u16, + }, + }; + + v0::StorageNodes::::insert(node_pub_key2.clone(), node2); + let node_count = v0::StorageNodes::::iter_values().count() as u32; + + assert_eq!(node_count, 2); + let state = MigrateToV1::::pre_upgrade().unwrap(); + let _weight = MigrateToV1::::on_runtime_upgrade(); + MigrateToV1::::post_upgrade(state).unwrap(); + + let node_count_after_upgrade = StorageNodes::::iter_values().count() as u32; + + assert_eq!(StorageVersion::get::>(), 1); + assert_eq!(node_count_after_upgrade, 2); + assert_eq!(StorageNodes::::get(node_pub_key0), None); + assert!(StorageNodes::::get(node_pub_key1.clone()).is_some()); + assert!(StorageNodes::::get(node_pub_key2.clone()).is_some()); + assert_eq!(StorageNodes::::get(node_pub_key1).unwrap().total_usage, None); + assert_eq!(StorageNodes::::get(node_pub_key2).unwrap().total_usage, None); + }); + } +} diff --git a/pallets/ddc-nodes/src/mock.rs b/pallets/ddc-nodes/src/mock.rs index 1e8933b8d..5d99c016b 100644 --- a/pallets/ddc-nodes/src/mock.rs +++ b/pallets/ddc-nodes/src/mock.rs @@ -12,17 +12,18 @@ use frame_system::mocking::{MockBlock, MockUncheckedExtrinsic}; use sp_core::H256; use sp_io::TestExternalities; use sp_runtime::{ - traits::{BlakeTwo256, IdentityLookup}, - BuildStorage, + traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + BuildStorage, MultiSignature, }; use crate::{self as pallet_ddc_nodes, *}; /// The AccountId alias in this test module. -pub(crate) type AccountId = u64; +pub type AccountId = <::Signer as IdentifyAccount>::AccountId; pub(crate) type AccountIndex = u64; pub(crate) type BlockNumber = u64; pub(crate) type Balance = u128; +pub type Signature = MultiSignature; type UncheckedExtrinsic = MockUncheckedExtrinsic; type Block = MockBlock; @@ -125,9 +126,12 @@ impl ExtBuilder { sp_tracing::try_init_simple(); let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - - let _ = pallet_balances::GenesisConfig:: { balances: vec![(1, 100), (2, 100)] } - .assimilate_storage(&mut t); + let account_id1 = AccountId::from([1; 32]); + let account_id2 = AccountId::from([2; 32]); + let _ = pallet_balances::GenesisConfig:: { + balances: vec![(account_id1, 100), (account_id2, 100)], + } + .assimilate_storage(&mut t); TestExternalities::new(t) } diff --git a/pallets/ddc-nodes/src/node.rs b/pallets/ddc-nodes/src/node.rs index 617d9f501..b738d0667 100644 --- a/pallets/ddc-nodes/src/node.rs +++ b/pallets/ddc-nodes/src/node.rs @@ -1,7 +1,7 @@ #![allow(clippy::needless_lifetimes)] // ToDo use codec::{Decode, Encode}; -use ddc_primitives::{NodeParams, NodePubKey, NodeType}; +use ddc_primitives::{NodeParams, NodePubKey, NodeType, NodeUsage}; use scale_info::TypeInfo; use sp_runtime::RuntimeDebug; @@ -31,6 +31,7 @@ pub trait NodeTrait { fn get_cluster_id(&self) -> &Option; fn set_cluster_id(&mut self, cluster_id: Option); fn get_type(&self) -> NodeType; + fn get_total_usage(&self) -> &Option; } impl Node { @@ -77,6 +78,11 @@ impl NodeTrait for Node { Node::Storage(node) => node.get_cluster_id(), } } + fn get_total_usage(&self) -> &Option { + match &self { + Node::Storage(node) => node.get_total_usage(), + } + } fn set_cluster_id(&mut self, cluster_id: Option) { match self { Node::Storage(node) => node.set_cluster_id(cluster_id), diff --git a/pallets/ddc-nodes/src/storage_node.rs b/pallets/ddc-nodes/src/storage_node.rs index 43d4d06fd..3831bcf17 100644 --- a/pallets/ddc-nodes/src/storage_node.rs +++ b/pallets/ddc-nodes/src/storage_node.rs @@ -1,6 +1,6 @@ use codec::{Decode, Encode}; use ddc_primitives::{ - ClusterId, NodeParams, NodePubKey, NodeType, StorageNodeMode, StorageNodePubKey, + ClusterId, NodeParams, NodePubKey, NodeType, NodeUsage, StorageNodeMode, StorageNodePubKey, }; use frame_support::{parameter_types, BoundedVec}; use scale_info::TypeInfo; @@ -21,6 +21,7 @@ pub struct StorageNode { pub provider_id: T::AccountId, pub cluster_id: Option, pub props: StorageNodeProps, + pub total_usage: Option, } #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq, Serialize, Deserialize)] @@ -61,6 +62,7 @@ impl StorageNode { grpc_port: node_params.grpc_port, p2p_port: node_params.p2p_port, }, + total_usage: None, }), }, } @@ -106,6 +108,9 @@ impl NodeTrait for StorageNode { fn get_cluster_id(&self) -> &Option { &self.cluster_id } + fn get_total_usage(&self) -> &Option { + &self.total_usage + } fn set_cluster_id(&mut self, cluster_id: Option) { self.cluster_id = cluster_id; } diff --git a/pallets/ddc-nodes/src/tests.rs b/pallets/ddc-nodes/src/tests.rs index 618a01c8e..eb3de5d31 100644 --- a/pallets/ddc-nodes/src/tests.rs +++ b/pallets/ddc-nodes/src/tests.rs @@ -23,10 +23,12 @@ fn create_storage_node_works() { p2p_port: 15000u16, }; + let account_id1 = AccountId::from([1; 32]); + // Host length exceeds limit assert_noop!( DdcNodes::create_node( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account_id1.clone()), NodePubKey::StoragePubKey(node_pub_key.clone()), NodeParams::StorageParams(StorageNodeParams { mode: StorageNodeMode::Storage, @@ -44,7 +46,7 @@ fn create_storage_node_works() { // Host length exceeds limit assert_noop!( DdcNodes::create_node( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account_id1.clone()), NodePubKey::StoragePubKey(node_pub_key.clone()), NodeParams::StorageParams(StorageNodeParams { mode: StorageNodeMode::Storage, @@ -61,7 +63,7 @@ fn create_storage_node_works() { // Node created assert_ok!(DdcNodes::create_node( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account_id1.clone()), NodePubKey::StoragePubKey(node_pub_key.clone()), NodeParams::StorageParams(storage_node_params.clone()) )); @@ -73,7 +75,7 @@ fn create_storage_node_works() { storage_node_params.clone().domain.try_into().unwrap(); assert_eq!(created_storage_node.pub_key, node_pub_key); - assert_eq!(created_storage_node.provider_id, 1); + assert_eq!(created_storage_node.provider_id, account_id1); assert_eq!(created_storage_node.cluster_id, None); assert_eq!(created_storage_node.props.host, expected_host); assert_eq!(created_storage_node.props.domain, expected_domain); @@ -97,7 +99,7 @@ fn create_storage_node_works() { // Node already exists assert_noop!( DdcNodes::create_node( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account_id1), NodePubKey::StoragePubKey(node_pub_key.clone()), NodeParams::StorageParams(storage_node_params) ), @@ -127,11 +129,12 @@ fn create_storage_node_with_node_creator() { grpc_port: 25000u16, p2p_port: 15000u16, }; + let account_id1 = AccountId::from([1; 32]); // Node created assert_ok!(>::create_node( NodePubKey::StoragePubKey(node_pub_key.clone()), - 1u64, + account_id1, NodeParams::StorageParams(storage_node_params) )); @@ -164,10 +167,13 @@ fn set_storage_node_params_works() { p2p_port: 15000u16, }; + let account_id1 = AccountId::from([1; 32]); + let account_id2 = AccountId::from([2; 32]); + // Node doesn't exist assert_noop!( DdcNodes::set_node_params( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account_id1.clone()), NodePubKey::StoragePubKey(node_pub_key.clone()), NodeParams::StorageParams(storage_node_params.clone()) ), @@ -176,7 +182,7 @@ fn set_storage_node_params_works() { // Node created assert_ok!(DdcNodes::create_node( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account_id1.clone()), NodePubKey::StoragePubKey(node_pub_key.clone()), NodeParams::StorageParams(storage_node_params.clone()) )); @@ -193,7 +199,7 @@ fn set_storage_node_params_works() { // Set node params assert_ok!(DdcNodes::set_node_params( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account_id1.clone()), NodePubKey::StoragePubKey(node_pub_key.clone()), NodeParams::StorageParams(updated_params.clone()) )); @@ -204,7 +210,7 @@ fn set_storage_node_params_works() { updated_params.domain.try_into().unwrap(); assert_eq!(updated_storage_node.pub_key, node_pub_key); - assert_eq!(updated_storage_node.provider_id, 1); + assert_eq!(updated_storage_node.provider_id, account_id1.clone()); assert_eq!(updated_storage_node.cluster_id, None); assert_eq!(updated_storage_node.props.host, expected_host); assert_eq!(updated_storage_node.props.domain, expected_domain); @@ -217,7 +223,7 @@ fn set_storage_node_params_works() { // Only node provider can set params assert_noop!( DdcNodes::set_node_params( - RuntimeOrigin::signed(2), + RuntimeOrigin::signed(account_id2.clone()), NodePubKey::StoragePubKey(node_pub_key.clone()), NodeParams::StorageParams(storage_node_params.clone()) ), @@ -228,7 +234,7 @@ fn set_storage_node_params_works() { let node_pub_key_2 = AccountId32::from(bytes_2); let node = Node::::new( NodePubKey::StoragePubKey(node_pub_key_2), - 2u64, + account_id2, NodeParams::StorageParams(storage_node_params), ) .unwrap(); @@ -242,7 +248,7 @@ fn set_storage_node_params_works() { // Storage host length exceeds limit assert_noop!( DdcNodes::set_node_params( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account_id1.clone()), NodePubKey::StoragePubKey(node_pub_key.clone()), NodeParams::StorageParams(StorageNodeParams { mode: StorageNodeMode::Storage, @@ -260,7 +266,7 @@ fn set_storage_node_params_works() { // Storage domain length exceeds limit assert_noop!( DdcNodes::set_node_params( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account_id1), NodePubKey::StoragePubKey(node_pub_key.clone()), NodeParams::StorageParams(StorageNodeParams { mode: StorageNodeMode::Storage, @@ -299,11 +305,13 @@ fn delete_storage_node_works() { grpc_port: 25000u16, p2p_port: 15000u16, }; + let account_id1 = AccountId::from([1; 32]); + let account_id2 = AccountId::from([2; 32]); // Node doesn't exist assert_noop!( DdcNodes::delete_node( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account_id1.clone()), NodePubKey::StoragePubKey(node_pub_key.clone()) ), Error::::NodeDoesNotExist @@ -311,7 +319,7 @@ fn delete_storage_node_works() { // Create node assert_ok!(DdcNodes::create_node( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account_id1.clone()), NodePubKey::StoragePubKey(node_pub_key.clone()), NodeParams::StorageParams(storage_node_params) )); @@ -319,7 +327,7 @@ fn delete_storage_node_works() { // Only node provider can delete assert_noop!( DdcNodes::delete_node( - RuntimeOrigin::signed(2), + RuntimeOrigin::signed(account_id2), NodePubKey::StoragePubKey(node_pub_key.clone()) ), Error::::OnlyNodeProvider @@ -327,7 +335,7 @@ fn delete_storage_node_works() { // Delete node assert_ok!(DdcNodes::delete_node( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account_id1), NodePubKey::StoragePubKey(node_pub_key.clone()), )); diff --git a/pallets/ddc-payouts/src/lib.rs b/pallets/ddc-payouts/src/lib.rs index 362f9b246..e7ffc16d0 100644 --- a/pallets/ddc-payouts/src/lib.rs +++ b/pallets/ddc-payouts/src/lib.rs @@ -33,6 +33,7 @@ use ddc_primitives::{ customer::{ CustomerCharger as CustomerChargerType, CustomerDepositor as CustomerDepositorType, }, + node::NodeVisitor as NodeVisitorType, pallet::PalletVisitor as PalletVisitorType, payout::PayoutVisitor, }, @@ -119,6 +120,7 @@ pub mod pallet { type Currency: LockableCurrency>; type CustomerCharger: CustomerChargerType; type BucketVisitor: BucketVisitorType; + type NodeVisitor: NodeVisitorType; type CustomerDepositor: CustomerDepositorType; type TreasuryVisitor: PalletVisitorType; type ClusterProtocol: ClusterProtocolType>; @@ -747,7 +749,8 @@ pub mod pallet { cluster_id: ClusterId, era: DdcEra, batch_index: BatchIndex, - payees: Vec<(T::AccountId, NodeUsage)>, + payees: Vec<(T::AccountId, NodeUsage)>, /* todo! we need to pass NodePubKey inside + * NodeUsage and more provider_id */ batch_proof: MMRProof, ) -> DispatchResult { let caller = ensure_signed(origin)?; @@ -787,7 +790,23 @@ pub mod pallet { let max_dust = MaxDust::get().saturated_into::>(); let mut updated_billing_report = billing_report.clone(); - for (node_provider_id, node_usage) in payees { + for (node_provider_id, delta_node_usage) in payees { + // todo! deduce node_provider_id from delta_node_usage.node_id + // todo! get T::NodeVisitor::get_total_usage(delta_node_usage.node_id).stored_bytes + let mut total_node_stored_bytes: i64 = 0; + + total_node_stored_bytes = total_node_stored_bytes + .checked_add(delta_node_usage.stored_bytes) + .ok_or(Error::::ArithmeticOverflow)? + .max(0); + + let node_usage = NodeUsage { + stored_bytes: total_node_stored_bytes, + transferred_bytes: delta_node_usage.transferred_bytes, + number_of_puts: delta_node_usage.number_of_puts, + number_of_gets: delta_node_usage.number_of_gets, + }; + let node_reward = get_node_reward( &node_usage, &billing_report.total_node_usage, @@ -833,6 +852,8 @@ pub mod pallet { ExistenceRequirement::AllowDeath, )?; + // todo! update total usage of node with NodeManager + reward_ = reward.saturated_into::(); updated_billing_report.total_distributed_reward = updated_billing_report @@ -1087,11 +1108,10 @@ pub mod pallet { .map_err(Into::>::into)? .map_or(0, |customer_usage| customer_usage.stored_bytes); - ensure!(total_stored_bytes >= 0, Error::::TotalStoredBytesLessThanZero); - total_stored_bytes = total_stored_bytes .checked_add(usage.stored_bytes) - .ok_or(Error::::ArithmeticOverflow)?; + .ok_or(Error::::ArithmeticOverflow)? + .max(0); total.storage = fraction_of_month * (|| -> Option { diff --git a/pallets/ddc-payouts/src/mock.rs b/pallets/ddc-payouts/src/mock.rs index 096299c53..354e2863d 100644 --- a/pallets/ddc-payouts/src/mock.rs +++ b/pallets/ddc-payouts/src/mock.rs @@ -7,11 +7,13 @@ use ddc_primitives::{ bucket::BucketVisitor, cluster::{ClusterCreator, ClusterProtocol}, customer::{CustomerCharger, CustomerDepositor}, + node::NodeVisitor, pallet::PalletVisitor, ClusterQuery, ValidatorVisitor, }, BucketVisitorError, ClusterBondingParams, ClusterFeesParams, ClusterParams, - ClusterPricingParams, ClusterProtocolParams, ClusterStatus, NodeType, DOLLARS, + ClusterPricingParams, ClusterProtocolParams, ClusterStatus, NodeParams, NodePubKey, NodeType, + DOLLARS, }; use frame_election_provider_support::SortedListProvider; use frame_support::{ @@ -136,11 +138,33 @@ impl crate::pallet::Config for Test { type VoteScoreToU64 = Identity; type WeightInfo = (); type ValidatorVisitor = MockValidatorVisitor; + type NodeVisitor = MockNodeVisitor; type AccountIdConverter = AccountId; } -pub struct MockValidatorVisitor; +pub struct MockNodeVisitor; +impl NodeVisitor for MockNodeVisitor +where + ::AccountId: From, +{ + fn get_total_usage(_node_pub_key: &NodePubKey) -> Result, DispatchError> { + unimplemented!() + } + fn get_cluster_id(_node_pub_key: &NodePubKey) -> Result, DispatchError> { + unimplemented!() + } + fn exists(_node_pub_key: &NodePubKey) -> bool { + unimplemented!() + } + fn get_node_provider_id(_node_pub_key: &NodePubKey) -> Result { + unimplemented!() + } + fn get_node_params(_node_pub_key: &NodePubKey) -> Result { + unimplemented!() + } +} +pub struct MockValidatorVisitor; impl ValidatorVisitor for MockValidatorVisitor where ::AccountId: From, diff --git a/pallets/ddc-payouts/src/tests.rs b/pallets/ddc-payouts/src/tests.rs index aa481cb3d..0dc84251f 100644 --- a/pallets/ddc-payouts/src/tests.rs +++ b/pallets/ddc-payouts/src/tests.rs @@ -284,18 +284,6 @@ fn send_charging_customers_batch_fails_uninitialised() { max_batch_index, )); - assert_noop!( - DdcPayouts::send_charging_customers_batch( - RuntimeOrigin::signed(dac_account.clone()), - cluster_id, - era, - batch_index, - payers1.clone(), - MMRProof::default(), - ), - Error::::ArithmeticOverflow - ); - let payers1 = vec![(user2, bucket_id2, CustomerUsage::default())]; assert_ok!(DdcPayouts::send_charging_customers_batch( RuntimeOrigin::signed(dac_account.clone()), diff --git a/pallets/ddc-verification/src/lib.rs b/pallets/ddc-verification/src/lib.rs index 12e3dd47b..019ef2400 100644 --- a/pallets/ddc-verification/src/lib.rs +++ b/pallets/ddc-verification/src/lib.rs @@ -269,9 +269,9 @@ pub mod pallet { ValidatorKeySet { validator: T::AccountId, }, - TotalNodeUsageLessThanZero { + FailedToFetchNodeTotalUsage { cluster_id: ClusterId, - era_id: DdcEra, + node_pub_key: NodePubKey, validator: T::AccountId, }, EraValidationRootsPosted { @@ -382,9 +382,9 @@ pub mod pallet { }, FailedToFetchCurrentValidator, FailedToFetchNodeProvider, - TotalNodeUsageLessThanZero { + FailedToFetchNodeTotalUsage { cluster_id: ClusterId, - era_id: DdcEra, + node_pub_key: NodePubKey, }, } @@ -1572,6 +1572,13 @@ pub mod pallet { if let Some((era_id, start, end)) = Self::get_era_for_payout(cluster_id, EraValidationStatus::PayoutInProgress) { + let nodes_total_usages = Self::get_nodes_total_usage(cluster_id, dac_nodes)?; + + let nodes_total_usage: i64 = nodes_total_usages + .iter() + .filter_map(|usage| usage.as_ref().map(|u| u.stored_bytes)) + .sum(); + if T::PayoutVisitor::get_billing_report_status(cluster_id, era_id) == PayoutState::CustomersChargedWithFees { @@ -1590,6 +1597,7 @@ pub mod pallet { era_id, nodes_activity_in_consensus, nodes_activity_batch_roots, + nodes_total_usage, ) } else { let era_activity = EraActivity { id: era_id, start, end }; @@ -1617,6 +1625,7 @@ pub mod pallet { era_id, nodes_activity_in_consensus, nodes_activity_batch_roots, + nodes_total_usage, ) } else { Ok(None) @@ -1635,6 +1644,7 @@ pub mod pallet { era_id: DdcEra, nodes_activity_in_consensus: Vec, nodes_activity_batch_roots: Vec, + current_nodes_total_usage: i64, ) -> Result, Vec> { if let Some(max_batch_index) = nodes_activity_batch_roots.len().checked_sub(1) // -1 cause payout expects max_index, not length @@ -1643,33 +1653,19 @@ pub mod pallet { vec![OCWError::BatchIndexConversionFailed { cluster_id: *cluster_id, era_id }] })?; - let total_node_usage = nodes_activity_in_consensus - .into_iter() - .try_fold( - NodeUsage { - transferred_bytes: 0, - stored_bytes: 0, - number_of_puts: 0, - number_of_gets: 0, - }, - |mut acc: NodeUsage, activity| { - let total_stored_bytes = acc.stored_bytes + activity.stored_bytes; - - if total_stored_bytes < 0 { - Err(OCWError::TotalNodeUsageLessThanZero { - cluster_id: *cluster_id, - era_id, - }) - } else { - acc.transferred_bytes += activity.transferred_bytes; - acc.stored_bytes = total_stored_bytes; - acc.number_of_puts += activity.number_of_puts; - acc.number_of_gets += activity.number_of_gets; - Ok(acc) - } - }, - ) - .map_err(|e| vec![e])?; + let mut total_node_usage = NodeUsage { + transferred_bytes: 0, + stored_bytes: current_nodes_total_usage, + number_of_puts: 0, + number_of_gets: 0, + }; + + for activity in nodes_activity_in_consensus { + total_node_usage.transferred_bytes += activity.transferred_bytes; + total_node_usage.stored_bytes += activity.stored_bytes; + total_node_usage.number_of_puts += activity.number_of_puts; + total_node_usage.number_of_gets += activity.number_of_gets; + } Ok(Some((era_id, max_batch_index, total_node_usage))) } else { @@ -1906,6 +1902,32 @@ pub mod pallet { sp_io::offchain::local_storage_set(StorageKind::PERSISTENT, &key, &encoded_tuple); } + pub(crate) fn get_nodes_total_usage( + cluster_id: &ClusterId, + dac_nodes: &[(NodePubKey, StorageNodeParams)], + ) -> Result>, Vec> { + let mut results: Vec> = Vec::new(); + let mut errors: Vec = Vec::new(); + + for (node_pub_key, _params) in dac_nodes.iter() { + match T::NodeVisitor::get_total_usage(node_pub_key) { + Ok(usage) => results.push(usage), + Err(_err) => { + errors.push(OCWError::FailedToFetchNodeTotalUsage { + cluster_id: *cluster_id, + node_pub_key: node_pub_key.clone(), + }); + }, + } + } + + if !errors.is_empty() { + return Err(errors); + } + + Ok(results) + } + #[allow(clippy::type_complexity)] pub(crate) fn fetch_validation_activities( // todo! (4) add tests @@ -2868,10 +2890,10 @@ pub mod pallet { validator: caller.clone(), }); }, - OCWError::TotalNodeUsageLessThanZero { cluster_id, era_id } => { - Self::deposit_event(Event::TotalNodeUsageLessThanZero { + OCWError::FailedToFetchNodeTotalUsage { cluster_id, node_pub_key } => { + Self::deposit_event(Event::FailedToFetchNodeTotalUsage { cluster_id, - era_id, + node_pub_key, validator: caller.clone(), }); }, diff --git a/pallets/ddc-verification/src/mock.rs b/pallets/ddc-verification/src/mock.rs index 5f6604f2f..c089a4b22 100644 --- a/pallets/ddc-verification/src/mock.rs +++ b/pallets/ddc-verification/src/mock.rs @@ -427,6 +427,10 @@ impl PayoutVisitor for MockPayoutVisitor { pub struct MockNodeVisitor; impl NodeVisitor for MockNodeVisitor { + fn get_total_usage(_node_pub_key: &NodePubKey) -> Result, DispatchError> { + Ok(None) // todo! add more complex mock + } + fn get_node_params(node_pub_key: &NodePubKey) -> Result { let key1 = NodePubKey::StoragePubKey(StorageNodePubKey::new(array_bytes::hex_n_into_unchecked( diff --git a/pallets/ddc-verification/src/tests.rs b/pallets/ddc-verification/src/tests.rs index f3c1394b5..5a28bb9fa 100644 --- a/pallets/ddc-verification/src/tests.rs +++ b/pallets/ddc-verification/src/tests.rs @@ -13,7 +13,7 @@ use sp_io::TestExternalities; use sp_keystore::{testing::MemoryKeystore, Keystore, KeystoreExt}; use sp_runtime::AccountId32; -use crate::{mock::*, Error, NodeActivity, OCWError, OCWError::TotalNodeUsageLessThanZero, *}; +use crate::{mock::*, Error, NodeActivity, OCWError, *}; #[allow(dead_code)] fn register_validators(validators: Vec) { @@ -2049,13 +2049,33 @@ fn fetch_reward_activities_works() { let leaves = [a, b, c, d, e]; let era_id = 1; + let total_usage: i64 = 56; let result = DdcVerification::fetch_reward_activities( &cluster_id, era_id, get_node_activities(), leaves.to_vec(), + total_usage, ); - assert_eq!(result.unwrap_err(), vec![TotalNodeUsageLessThanZero { cluster_id, era_id }]); + let usage = get_node_activities().iter().fold( + NodeUsage { transferred_bytes: 0, stored_bytes: 0, number_of_puts: 0, number_of_gets: 0 }, + |mut acc, activity| { + acc.transferred_bytes += activity.transferred_bytes; + acc.stored_bytes += activity.stored_bytes; + acc.number_of_puts += activity.number_of_puts; + acc.number_of_gets += activity.number_of_gets; + acc + }, + ); + + let ex_result = NodeUsage { + stored_bytes: total_usage + usage.stored_bytes, + number_of_puts: usage.number_of_puts, + number_of_gets: usage.number_of_gets, + transferred_bytes: usage.transferred_bytes, + }; + + assert_eq!(result.unwrap(), Some((era_id, (leaves.len() - 1) as u16, ex_result))); } diff --git a/primitives/src/traits/node.rs b/primitives/src/traits/node.rs index fe51dd6fb..4d24adb60 100644 --- a/primitives/src/traits/node.rs +++ b/primitives/src/traits/node.rs @@ -2,13 +2,14 @@ use frame_support::dispatch::DispatchResult; use frame_system::Config; use sp_runtime::DispatchError; -use crate::{ClusterId, NodeParams, NodePubKey}; +use crate::{ClusterId, NodeParams, NodePubKey, NodeUsage}; pub trait NodeVisitor { fn get_cluster_id(node_pub_key: &NodePubKey) -> Result, DispatchError>; fn exists(node_pub_key: &NodePubKey) -> bool; fn get_node_provider_id(node_pub_key: &NodePubKey) -> Result; fn get_node_params(node_pub_key: &NodePubKey) -> Result; + fn get_total_usage(node_pub_key: &NodePubKey) -> Result, DispatchError>; } pub trait NodeCreator { diff --git a/runtime/cere-dev/src/lib.rs b/runtime/cere-dev/src/lib.rs index 25f13f340..d6eccafdb 100644 --- a/runtime/cere-dev/src/lib.rs +++ b/runtime/cere-dev/src/lib.rs @@ -149,7 +149,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 54112, + spec_version: 54113, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 19, @@ -1218,6 +1218,7 @@ impl pallet_ddc_payouts::Config for Runtime { type WeightInfo = pallet_ddc_payouts::weights::SubstrateWeight; type VoteScoreToU64 = IdentityConvert; // used for UseNominatorsAndValidatorsMap type ValidatorVisitor = pallet_ddc_verification::Pallet; + type NodeVisitor = pallet_ddc_nodes::Pallet; type AccountIdConverter = AccountId32; } @@ -1407,7 +1408,7 @@ pub type SignedPayload = generic::SignedPayload; pub type CheckedExtrinsic = generic::CheckedExtrinsic; /// Runtime migrations -type Migrations = (); +type Migrations = (pallet_ddc_nodes::migrations::MigrateToV1,); /// Executive: handles dispatch to the various modules. pub type Executive = frame_executive::Executive< diff --git a/runtime/cere/src/lib.rs b/runtime/cere/src/lib.rs index 8c2354551..668df4edb 100644 --- a/runtime/cere/src/lib.rs +++ b/runtime/cere/src/lib.rs @@ -143,7 +143,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to 0. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 54105, + spec_version: 54113, impl_version: 0, apis: RUNTIME_API_VERSIONS, transaction_version: 19, @@ -1207,6 +1207,7 @@ impl pallet_ddc_payouts::Config for Runtime { type WeightInfo = pallet_ddc_payouts::weights::SubstrateWeight; type VoteScoreToU64 = IdentityConvert; type ValidatorVisitor = pallet_ddc_verification::Pallet; + type NodeVisitor = pallet_ddc_nodes::Pallet; type AccountIdConverter = AccountId32; } @@ -1421,6 +1422,7 @@ type Migrations = ( pallet_ddc_staking::migrations::v1::MigrateToV1, pallet_ddc_customers::migration::MigrateToV2, migrations::Unreleased, + pallet_ddc_nodes::migrations::MigrateToV1, ); pub mod migrations {