diff --git a/pallets/ddc-payouts/src/lib.rs b/pallets/ddc-payouts/src/lib.rs index df7c8a448..213e28e0b 100644 --- a/pallets/ddc-payouts/src/lib.rs +++ b/pallets/ddc-payouts/src/lib.rs @@ -28,7 +28,7 @@ use ddc_primitives::{ pallet::PalletVisitor as PalletVisitorType, payout::PayoutProcessor, }, BatchIndex, BillingReportParams, BucketId, BucketUsage, ClusterId, CustomerCharge, DdcEra, - MMRProof, NodePubKey, NodeUsage, PayoutError, PayoutState, ProviderReward, + MMRProof, NodePubKey, NodeUsage, PayableUsageHash, PayoutError, PayoutState, ProviderReward, MAX_PAYOUT_BATCH_COUNT, MAX_PAYOUT_BATCH_SIZE, MILLICENTS, }; use frame_election_provider_support::SortedListProvider; @@ -42,8 +42,12 @@ use frame_support::{ use frame_system::pallet_prelude::*; pub use pallet::*; use scale_info::prelude::string::String; -use sp_runtime::{traits::Convert, AccountId32, PerThing, Perquintill}; -use sp_std::prelude::*; +use sp_core::H256; +use sp_runtime::{ + traits::{Convert, Hash}, + AccountId32, PerThing, Perquintill, +}; +use sp_std::{collections::btree_set::BTreeSet, prelude::*}; #[derive(PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, Default, Clone)] pub struct BillingReportDebt { @@ -62,6 +66,8 @@ pub type VoteScoreOf = ::AccountId, >>::Score; +pub type Fingerprint = H256; + parameter_types! { pub MaxBatchesCount: u16 = MAX_PAYOUT_BATCH_COUNT; pub MaxDust: u128 = MILLICENTS; @@ -101,6 +107,7 @@ pub mod pallet { type VoteScoreToU64: Convert, u64>; type ValidatorVisitor: ValidatorVisitor; type AccountIdConverter: From + Into; + type FingerprintHasher: Hash; } #[pallet::event] @@ -231,6 +238,7 @@ pub mod pallet { IncorrectClusterId, ClusterProtocolParamsNotSet, TotalStoredBytesLessThanZero, + FingerprintIsAlreadyCommited, } #[pallet::storage] @@ -304,6 +312,34 @@ pub mod pallet { Finalized = 7, } + #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq)] + pub struct BillingReportFingerprint { + pub era_id: DdcEra, + pub start_era: i64, + pub end_era: i64, + pub payers_merkle_root: PayableUsageHash, + pub payees_merkle_root: PayableUsageHash, + pub validators: BTreeSet, + } + + impl BillingReportFingerprint { + fn selective_hash(&self) -> Fingerprint { + let mut data = self.era_id.encode(); + data.extend_from_slice(&self.start_era.encode()); + data.extend_from_slice(&self.end_era.encode()); + data.extend_from_slice(&self.payers_merkle_root.encode()); + data.extend_from_slice(&self.payees_merkle_root.encode()); + // we truncate the `validators` field on purpose as it's appendable collection that is + // used for reaching the quorum on the fingerprint + T::FingerprintHasher::hash(&data).into() + } + } + + #[pallet::storage] + #[pallet::getter(fn fingerprints)] + pub type Fingerprints = + StorageMap<_, Blake2_128Concat, Fingerprint, BillingReportFingerprint>; + #[pallet::call] impl Pallet {} @@ -555,6 +591,45 @@ pub mod pallet { } impl PayoutProcessor for Pallet { + fn commit_billing_report_fingerprint( + validator: T::AccountId, + cluster_id: ClusterId, + era_id: DdcEra, + start_era: i64, + end_era: i64, + payers_merkle_root: PayableUsageHash, + payees_merkle_root: PayableUsageHash, + ) -> DispatchResult { + ensure!(end_era > start_era, Error::::BadRequest); + ensure!(payers_merkle_root != Default::default(), Error::::BadRequest); + ensure!(payees_merkle_root != Default::default(), Error::::BadRequest); + + let inited_fingerprint = BillingReportFingerprint:: { + era_id, + start_era, + end_era, + payers_merkle_root, + payees_merkle_root, + validators: Default::default(), + }; + let hash = inited_fingerprint.selective_hash::(); + + let mut fingerprint = if let Some(commited_fingerprint) = Fingerprints::::get(hash) { + commited_fingerprint + } else { + inited_fingerprint + }; + + ensure!( + fingerprint.validators.insert(validator), + Error::::FingerprintIsAlreadyCommited + ); + + Fingerprints::::insert(hash, fingerprint); + + Ok(()) + } + fn begin_billing_report( cluster_id: ClusterId, era: DdcEra, diff --git a/pallets/ddc-verification/src/lib.rs b/pallets/ddc-verification/src/lib.rs index 450c4d5f6..9588da4a8 100644 --- a/pallets/ddc-verification/src/lib.rs +++ b/pallets/ddc-verification/src/lib.rs @@ -20,8 +20,8 @@ use ddc_primitives::{ ValidatorVisitor, }, ActivityHash, BatchIndex, BillingReportParams, BucketUsage, ClusterId, ClusterStatus, DdcEra, - EraValidation, EraValidationStatus, MMRProof, NodeParams, NodePubKey, NodeUsage, PayoutState, - StorageNodeParams, + EraValidation, EraValidationStatus, MMRProof, NodeParams, NodePubKey, NodeUsage, + PayableUsageHash, PayoutState, StorageNodeParams, }; use frame_support::{ pallet_prelude::*, @@ -3441,7 +3441,7 @@ pub mod pallet { start_era: i64, end_era: i64, ) -> DispatchResult { - let sender = ensure_signed(origin)?; + let sender = ensure_signed(origin.clone())?; ensure!(Self::is_ocw_validator(sender.clone()), Error::::Unauthorized); T::PayoutProcessor::begin_billing_report(cluster_id, era_id, start_era, end_era)?; diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index d887c8be2..3a36135ee 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -10,7 +10,7 @@ use scale_info::{ TypeInfo, }; use serde::{Deserialize, Serialize}; -use sp_core::{crypto::KeyTypeId, hash::H160}; +use sp_core::{crypto::KeyTypeId, hash::H160, H256}; use sp_runtime::{AccountId32, Perquintill, RuntimeDebug}; pub mod traits; @@ -30,7 +30,9 @@ pub type DdcEra = u32; pub type BucketId = u64; pub type ClusterNodesCount = u16; pub type StorageNodePubKey = AccountId32; -pub type ActivityHash = [u8; 32]; +pub type ActivityHash = [u8; 32]; // todo: rename to `DeltaUsageHash` +pub type PayableUsageHash = H256; + pub type BatchIndex = u16; pub const AVG_SECONDS_MONTH: i64 = 2630016; // 30.44 * 24.0 * 3600.0; diff --git a/primitives/src/traits/payout.rs b/primitives/src/traits/payout.rs index 1a0907606..0e6e3c8cd 100644 --- a/primitives/src/traits/payout.rs +++ b/primitives/src/traits/payout.rs @@ -2,10 +2,20 @@ use sp_runtime::DispatchResult; use crate::{ BatchIndex, BillingReportParams, BucketId, BucketUsage, ClusterId, DdcEra, MMRProof, - NodePubKey, NodeUsage, PayoutError, PayoutState, + NodePubKey, NodeUsage, PayableUsageHash, PayoutError, PayoutState, }; pub trait PayoutProcessor { + fn commit_billing_report_fingerprint( + validator: T::AccountId, + cluster_id: ClusterId, + era_id: DdcEra, + start_era: i64, + end_era: i64, + payers_merkle_root: PayableUsageHash, + payees_merkle_root: PayableUsageHash, + ) -> DispatchResult; + fn begin_billing_report( cluster_id: ClusterId, era_id: DdcEra, diff --git a/runtime/cere-dev/src/lib.rs b/runtime/cere-dev/src/lib.rs index e9bd500b1..9fef10417 100644 --- a/runtime/cere-dev/src/lib.rs +++ b/runtime/cere-dev/src/lib.rs @@ -1231,6 +1231,7 @@ impl pallet_ddc_payouts::Config for Runtime { type ValidatorVisitor = pallet_ddc_verification::Pallet; type NodeManager = pallet_ddc_nodes::Pallet; type AccountIdConverter = AccountId32; + type FingerprintHasher = BlakeTwo256; } parameter_types! { diff --git a/runtime/cere/src/lib.rs b/runtime/cere/src/lib.rs index 40ab94a8d..8926aa1d5 100644 --- a/runtime/cere/src/lib.rs +++ b/runtime/cere/src/lib.rs @@ -1233,6 +1233,7 @@ impl pallet_ddc_payouts::Config for Runtime { type ValidatorVisitor = pallet_ddc_verification::Pallet; type NodeManager = pallet_ddc_nodes::Pallet; type AccountIdConverter = AccountId32; + type FingerprintHasher = BlakeTwo256; } parameter_types! {