From 65ff4720e1a0564b0a29bdf0c99ad5aa5dfe713e Mon Sep 17 00:00:00 2001 From: aie0 <149175774+aie0@users.noreply.github.com> Date: Fri, 28 Jun 2024 12:06:40 +0300 Subject: [PATCH] begin billing -> end billing (without batches) (#378) ## Description ## Types of Changes Please select the branch type you are merging and fill in the relevant template. - [ ] Hotfix - [ ] Release - [ ] Fix or Feature ## Fix or Feature ### Types of Changes - [ ] Tech Debt (Code improvements) - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Dependency upgrade (A change in substrate or any 3rd party crate version) ### Migrations and Hooks - [ ] This change requires a runtime migration. - [ ] Modifies `on_initialize` - [ ] Modifies `on_finalize` ### Checklist for Fix or Feature - [ ] Change has been tested locally. - [ ] Change adds / updates tests if applicable. - [ ] Changelog doc updated. - [ ] `spec_version` has been incremented. - [ ] `network-relayer`'s [events](https://github.com/Cerebellum-Network/network-relayer/blob/dev-cere/shared/substrate/events.go) have been updated according to the blockchain events if applicable. - [ ] All CI checks have been passed successfully ## Checklist for Hotfix - [ ] Changelog has been updated. - [ ] Crate version has been updated. - [ ] `spec_version` has been incremented. - [ ] Transaction version has been updated if required. - [ ] Pull Request to `dev` has been created. - [ ] Pull Request to `staging` has been created. - [ ] `network-relayer`'s [events](https://github.com/Cerebellum-Network/network-relayer/blob/dev-cere/shared/substrate/events.go) have been updated according to the blockchain events if applicable. - [ ] All CI checks have been passed successfully ## Checklist for Release - [ ] Change has been deployed to Devnet. - [ ] Change has been tested in Devnet. - [ ] Change has been deployed to Qanet. - [ ] Change has been tested in Qanet. - [ ] Change has been deployed to Testnet. - [ ] Change has been tested in Testnet. - [ ] Changelog has been updated. - [ ] Crate version has been updated. - [ ] Spec version has been updated. - [ ] Transaction version has been updated if required. - [ ] All CI checks have been passed successfully --- pallets/ddc-payouts/src/lib.rs | 98 ++++-- pallets/ddc-verification/src/lib.rs | 479 ++++++++++++++++++++++++++- pallets/ddc-verification/src/mock.rs | 25 +- primitives/src/lib.rs | 5 + primitives/src/traits/payout.rs | 17 +- 5 files changed, 593 insertions(+), 31 deletions(-) diff --git a/pallets/ddc-payouts/src/lib.rs b/pallets/ddc-payouts/src/lib.rs index cd0f20dd4..a8746979f 100644 --- a/pallets/ddc-payouts/src/lib.rs +++ b/pallets/ddc-payouts/src/lib.rs @@ -36,7 +36,7 @@ use ddc_primitives::{ pallet::PalletVisitor as PalletVisitorType, payout::PayoutVisitor, }, - BatchIndex, BucketId, ClusterId, CustomerUsage, DdcEra, NodeUsage, PayoutState, + BatchIndex, BucketId, ClusterId, CustomerUsage, DdcEra, NodeUsage, PayoutError, PayoutState, MAX_PAYOUT_BATCH_COUNT, MAX_PAYOUT_BATCH_SIZE, MILLICENTS, }; use frame_election_provider_support::SortedListProvider; @@ -570,7 +570,7 @@ pub mod pallet { billing_report.state == PayoutState::ChargingCustomers, Error::::NotExpectedState ); - validate_batches::( + Self::validate_batches( &billing_report.charging_processed_batches, &billing_report.charging_max_batch_index, )?; @@ -841,7 +841,7 @@ pub mod pallet { Error::::NotExpectedState ); - validate_batches::( + Self::validate_batches( &billing_report.rewarding_processed_batches, &billing_report.rewarding_max_batch_index, )?; @@ -1050,22 +1050,6 @@ pub mod pallet { Ok(total) } - fn validate_batches( - batches: &BoundedBTreeSet, - max_batch_index: &BatchIndex, - ) -> DispatchResult { - // Check if the Vec contains all integers between 1 and rewarding_max_batch_index - ensure!(!batches.is_empty(), Error::::BatchesMissed); - - ensure!((*max_batch_index + 1) as usize == batches.len(), Error::::BatchesMissed); - - for index in 0..*max_batch_index + 1 { - ensure!(batches.contains(&index), Error::::BatchesMissed); - } - - Ok(()) - } - #[pallet::genesis_config] pub struct GenesisConfig { pub feeder_account: Option, @@ -1223,6 +1207,66 @@ pub mod pallet { None => PayoutState::NotInitialized, // Return NotInitialized if entry doesn't exist } } + + fn all_customer_batches_processed(cluster_id: &ClusterId, era_id: DdcEra) -> bool { + let billing_report = match ActiveBillingReports::::try_get(cluster_id, era_id) { + Ok(report) => report, + Err(_) => return false, /* Return false if there's any error (e.g., + * BillingReportDoesNotExist) */ + }; + + Self::validate_batches( + &billing_report.charging_processed_batches, + &billing_report.charging_max_batch_index, + ) + .is_ok() + } + + fn all_provider_batches_processed(cluster_id: &ClusterId, era_id: DdcEra) -> bool { + let billing_report = match ActiveBillingReports::::try_get(cluster_id, era_id) { + Ok(report) => report, + Err(_) => return false, /* Return false if there's any error (e.g., + * BillingReportDoesNotExist) */ + }; + + Self::validate_batches( + &billing_report.rewarding_processed_batches, + &billing_report.rewarding_max_batch_index, + ) + .is_ok() + } + + fn get_next_customer_batch_for_payment( + cluster_id: &ClusterId, + era_id: DdcEra, + ) -> Result, PayoutError> { + let billing_report = ActiveBillingReports::::try_get(cluster_id, era_id) + .map_err(|_| PayoutError::BillingReportDoesNotExist)?; + + for batch_index in 0..=billing_report.charging_max_batch_index { + if !billing_report.charging_processed_batches.contains(&batch_index) { + return Ok(Some(batch_index)); + } + } + + Ok(None) + } + + fn get_next_provider_batch_for_payment( + cluster_id: &ClusterId, + era_id: DdcEra, + ) -> Result, PayoutError> { + let billing_report = ActiveBillingReports::::try_get(cluster_id, era_id) + .map_err(|_| PayoutError::BillingReportDoesNotExist)?; + + for batch_index in 0..=billing_report.rewarding_max_batch_index { + if !billing_report.rewarding_processed_batches.contains(&batch_index) { + return Ok(Some(batch_index)); + } + } + + Ok(None) + } } impl Pallet { @@ -1241,5 +1285,21 @@ pub mod pallet { // be fulfilled with trailing zeros. T::PalletId::get().into_sub_account_truncating(hash) } + + pub(crate) fn validate_batches( + batches: &BoundedBTreeSet, + max_batch_index: &BatchIndex, + ) -> DispatchResult { + // Check if the Vec contains all integers between 1 and rewarding_max_batch_index + ensure!(!batches.is_empty(), Error::::BatchesMissed); + + ensure!((*max_batch_index + 1) as usize == batches.len(), Error::::BatchesMissed); + + for index in 0..*max_batch_index + 1 { + ensure!(batches.contains(&index), Error::::BatchesMissed); + } + + Ok(()) + } } } diff --git a/pallets/ddc-verification/src/lib.rs b/pallets/ddc-verification/src/lib.rs index cd8429200..3b3f3c39d 100644 --- a/pallets/ddc-verification/src/lib.rs +++ b/pallets/ddc-verification/src/lib.rs @@ -192,6 +192,37 @@ pub mod pallet { era_id: DdcEra, validator: T::AccountId, }, + SendChargingCustomersBatchTransactionError { + cluster_id: ClusterId, + era_id: DdcEra, + batch_index: BatchIndex, + validator: T::AccountId, + }, + EndChargingCustomersTransactionError { + cluster_id: ClusterId, + era_id: DdcEra, + validator: T::AccountId, + }, + BeginRewardingProvidersTransactionError { + cluster_id: ClusterId, + era_id: DdcEra, + validator: T::AccountId, + }, + EndRewardingProvidersTransactionError { + cluster_id: ClusterId, + era_id: DdcEra, + validator: T::AccountId, + }, + EndBillingReportTransactionError { + cluster_id: ClusterId, + era_id: DdcEra, + validator: T::AccountId, + }, + BillingReportDoesNotExist { + cluster_id: ClusterId, + era_id: DdcEra, + validator: T::AccountId, + }, EmptyCustomerActivity { cluster_id: ClusterId, era_id: DdcEra, @@ -259,6 +290,31 @@ pub mod pallet { cluster_id: ClusterId, era_id: DdcEra, }, + SendChargingCustomersBatchTransactionError { + cluster_id: ClusterId, + era_id: DdcEra, + batch_index: BatchIndex, + }, + EndChargingCustomersTransactionError { + cluster_id: ClusterId, + era_id: DdcEra, + }, + BeginRewardingProvidersTransactionError { + cluster_id: ClusterId, + era_id: DdcEra, + }, + EndRewardingProvidersTransactionError { + cluster_id: ClusterId, + era_id: DdcEra, + }, + EndBillingReportTransactionError { + cluster_id: ClusterId, + era_id: DdcEra, + }, + BillingReportDoesNotExist { + cluster_id: ClusterId, + era_id: DdcEra, + }, EmptyCustomerActivity { cluster_id: ClusterId, era_id: DdcEra, @@ -618,7 +674,7 @@ pub mod pallet { match Self::prepare_begin_charging_customers(&cluster_id) { Ok(Some((era_id, max_batch_index))) => { log::info!( - "process_start_payout processed successfully for cluster_id: {:?}, era_id: {:?}", + "prepare_begin_charging_customers processed successfully for cluster_id: {:?}, era_id: {:?}", cluster_id, era_id ); @@ -665,6 +721,211 @@ pub mod pallet { }, } + // todo! factor out as macro as this is repetitive + match Self::prepare_end_charging_customers(&cluster_id) { + Ok(Some(era_id)) => { + log::info!( + "prepare_end_charging_customers processed successfully for cluster_id: {:?}, era_id: {:?}", + cluster_id, + era_id + ); + + if let Some((_, res)) = signer.send_signed_transaction(|_acc| { + Call::end_charging_customers { cluster_id, era_id } + }) { + match res { + Ok(_) => { + // Extrinsic call succeeded + log::info!( + "Sent end_charging_customers successfully for cluster_id: {:?}, era_id: {:?}", + cluster_id, + era_id + ); + }, + Err(e) => { + log::error!( + "Error to post end_charging_customers for cluster_id: {:?}, era_id: {:?}: {:?}", + cluster_id, + era_id, + e + ); + // Extrinsic call failed + errors.push(OCWError::EndChargingCustomersTransactionError { + cluster_id, + era_id, + }); + }, + } + } else { + log::error!("No account available to sign the transaction"); + errors.push(OCWError::NoAvailableSigner); + } + }, + Ok(None) => { + log::info!( + "No era for end_charging_customers for cluster_id: {:?}", + cluster_id + ); + }, + Err(e) => { + errors.push(e); + }, + } + + // todo! factor out as macro as this is repetitive + match Self::prepare_begin_rewarding_providers(&cluster_id) { + Ok(Some((era_id, max_batch_index, total_node_usage))) => { + log::info!( + "prepare_begin_rewarding_providers processed successfully for cluster_id: {:?}, era_id: {:?}", + cluster_id, + era_id + ); + + if let Some((_, res)) = + signer.send_signed_transaction(|_acc| Call::begin_rewarding_providers { + cluster_id, + era_id, + max_batch_index, + total_node_usage: total_node_usage.clone(), + }) { + match res { + Ok(_) => { + // Extrinsic call succeeded + log::info!( + "Sent begin_rewarding_providers successfully for cluster_id: {:?}, era_id: {:?}", + cluster_id, + era_id + ); + }, + Err(e) => { + log::error!( + "Error to post begin_rewarding_providers for cluster_id: {:?}, era_id: {:?}: {:?}", + cluster_id, + era_id, + e + ); + // Extrinsic call failed + errors.push(OCWError::BeginRewardingProvidersTransactionError { + cluster_id, + era_id, + }); + }, + } + } else { + log::error!("No account available to sign the transaction"); + errors.push(OCWError::NoAvailableSigner); + } + }, + Ok(None) => { + log::info!( + "No era for begin_rewarding_providers for cluster_id: {:?}", + cluster_id + ); + }, + Err(e) => { + errors.push(e); + }, + } + + // todo! factor out as macro as this is repetitive + match Self::prepare_end_rewarding_providers(&cluster_id) { + Ok(Some(era_id)) => { + log::info!( + "prepare_end_rewarding_providers processed successfully for cluster_id: {:?}, era_id: {:?}", + cluster_id, + era_id + ); + + if let Some((_, res)) = signer.send_signed_transaction(|_acc| { + Call::end_rewarding_providers { cluster_id, era_id } + }) { + match res { + Ok(_) => { + // Extrinsic call succeeded + log::info!( + "Sent end_rewarding_providers successfully for cluster_id: {:?}, era_id: {:?}", + cluster_id, + era_id + ); + }, + Err(e) => { + log::error!( + "Error to post end_rewarding_providers for cluster_id: {:?}, era_id: {:?}: {:?}", + cluster_id, + era_id, + e + ); + // Extrinsic call failed + errors.push(OCWError::EndRewardingProvidersTransactionError { + cluster_id, + era_id, + }); + }, + } + } else { + log::error!("No account available to sign the transaction"); + errors.push(OCWError::NoAvailableSigner); + } + }, + Ok(None) => { + log::info!( + "No era for end_rewarding_providers for cluster_id: {:?}", + cluster_id + ); + }, + Err(e) => { + errors.push(e); + }, + } + + // todo! factor out as macro as this is repetitive + match Self::prepare_end_billing_report(&cluster_id) { + Ok(Some(era_id)) => { + log::info!( + "prepare_end_billing_report processed successfully for cluster_id: {:?}, era_id: {:?}", + cluster_id, + era_id + ); + + if let Some((_, res)) = signer.send_signed_transaction(|_acc| { + Call::end_billing_report { cluster_id, era_id } + }) { + match res { + Ok(_) => { + // Extrinsic call succeeded + log::info!( + "Sent end_billing_report successfully for cluster_id: {:?}, era_id: {:?}", + cluster_id, + era_id + ); + }, + Err(e) => { + log::error!( + "Error to post end_billing_report for cluster_id: {:?}, era_id: {:?}: {:?}", + cluster_id, + era_id, + e + ); + // Extrinsic call failed + errors.push(OCWError::EndBillingReportTransactionError { + cluster_id, + era_id, + }); + }, + } + } else { + log::error!("No account available to sign the transaction"); + errors.push(OCWError::NoAvailableSigner); + } + }, + Ok(None) => { + log::info!("No era for end_billing_report for cluster_id: {:?}", cluster_id); + }, + Err(e) => { + errors.push(e); + }, + } + if !errors.is_empty() { // Send errors as extrinsics if let Some((_, res)) = signer.send_signed_transaction(|_account| { @@ -785,16 +1046,134 @@ pub mod pallet { Self::fetch_validation_activities::( cluster_id, era_id, ) { - if let Some(batch_index) = + if let Some(max_batch_index) = customers_activity_batch_roots.len().checked_sub(1) + // todo! remove this -1 to support empty usage { - let batch_index: u16 = batch_index.try_into().map_err(|_| { - OCWError::BatchIndexConversionFailed { - cluster_id: *cluster_id, - era_id, - } - })?; - return Ok(Some((era_id, batch_index))); + let max_batch_index: u16 = + max_batch_index.try_into().map_err(|_| { + OCWError::BatchIndexConversionFailed { + cluster_id: *cluster_id, + era_id, + } + })?; + return Ok(Some((era_id, max_batch_index))); + } else { + return Err(OCWError::EmptyCustomerActivity { + cluster_id: *cluster_id, + era_id, + }); + } + } /*else { + // todo! no data - reconstruct the data from DAC + }*/ + } + } + Ok(None) + } + + #[allow(dead_code)] + pub(crate) fn prepare_send_charging_customers_batch( + cluster_id: &ClusterId, + ) -> Result, OCWError> { + if let Some((era_id, _start, _end)) = + Self::get_era_for_payout(cluster_id, EraValidationStatus::PayoutInProgress) + { + if T::PayoutVisitor::get_billing_report_status(cluster_id, era_id) == + PayoutState::ChargingCustomers + { + if let Some(( + _customers_activity_in_consensus, + _customers_activity_root, + _customers_activity_batch_roots, + _, + _, + _, + )) = Self::fetch_validation_activities::( + cluster_id, era_id, + ) { + let _batch_index = T::PayoutVisitor::get_next_customer_batch_for_payment( + cluster_id, era_id, + ) + .map_err(|_| OCWError::BillingReportDoesNotExist { + cluster_id: *cluster_id, + era_id, + })?; + // todo! get root for a batch_index + // todo! get batch by batch_index + // todo! create proof + } /*else { + // todo! no data - reconstruct the data from DAC + }*/ + } + } + Ok(None) + } + + pub(crate) fn prepare_end_charging_customers( + cluster_id: &ClusterId, + ) -> Result, OCWError> { + if let Some((era_id, _start, _end)) = + Self::get_era_for_payout(cluster_id, EraValidationStatus::PayoutInProgress) + { + if T::PayoutVisitor::get_billing_report_status(cluster_id, era_id) == + PayoutState::ChargingCustomers && + T::PayoutVisitor::all_customer_batches_processed(cluster_id, era_id) + { + return Ok(Some(era_id)); + } + } + Ok(None) + } + + pub(crate) fn prepare_begin_rewarding_providers( + cluster_id: &ClusterId, + ) -> Result, OCWError> { + if let Some((era_id, _start, _end)) = + Self::get_era_for_payout(cluster_id, EraValidationStatus::PayoutInProgress) + { + if T::PayoutVisitor::get_billing_report_status(cluster_id, era_id) == + PayoutState::CustomersChargedWithFees + { + if let Some(( + _, + _, + _, + nodes_activity_in_consensus, + _, + nodes_activity_batch_roots, + )) = Self::fetch_validation_activities::( + cluster_id, era_id, + ) { + if let Some(max_batch_index) = + nodes_activity_batch_roots.len().checked_sub(1) + { + // todo! remove this -1 to support empty usage + let max_batch_index: u16 = + max_batch_index.try_into().map_err(|_| { + OCWError::BatchIndexConversionFailed { + cluster_id: *cluster_id, + era_id, + } + })?; + + let total_node_usage = nodes_activity_in_consensus.into_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 + }, + ); + + return Ok(Some((era_id, max_batch_index, total_node_usage))); } else { return Err(OCWError::EmptyCustomerActivity { cluster_id: *cluster_id, @@ -802,19 +1181,51 @@ pub mod pallet { }); } } /*else { - // todo! no data - what to do? + // todo! no data - reconstruct the data from DAC }*/ } } Ok(None) } + pub(crate) fn prepare_end_rewarding_providers( + cluster_id: &ClusterId, + ) -> Result, OCWError> { + if let Some((era_id, _start, _end)) = + Self::get_era_for_payout(cluster_id, EraValidationStatus::PayoutInProgress) + { + if T::PayoutVisitor::get_billing_report_status(cluster_id, era_id) == + PayoutState::RewardingProviders && + T::PayoutVisitor::all_provider_batches_processed(cluster_id, era_id) + { + return Ok(Some(era_id)); + } + } + Ok(None) + } + + pub(crate) fn prepare_end_billing_report( + cluster_id: &ClusterId, + ) -> Result, OCWError> { + if let Some((era_id, _start, _end)) = + Self::get_era_for_payout(cluster_id, EraValidationStatus::PayoutInProgress) + { + if T::PayoutVisitor::get_billing_report_status(cluster_id, era_id) == + PayoutState::ProvidersRewarded + { + return Ok(Some(era_id)); + } + } + Ok(None) + } + pub(crate) fn derive_key(cluster_id: &ClusterId, era_id: DdcEra) -> Vec { format!("offchain::activities::{:?}::{:?}", cluster_id, era_id).into_bytes() } #[allow(clippy::too_many_arguments)] // todo! (2) refactor into 2 different methods (for customers and nodes) + use type info for // derive_key + // todo! introduce new struct for input and remove clippy::type_complexity pub(crate) fn store_validation_activities( // todo! (3) add tests cluster_id: &ClusterId, @@ -844,6 +1255,7 @@ pub mod pallet { #[allow(clippy::type_complexity)] pub(crate) fn fetch_validation_activities( // todo! (4) add tests + // todo! introduce new struct for results and remove clippy::type_complexity cluster_id: &ClusterId, era_id: DdcEra, ) -> Option<( @@ -1571,6 +1983,53 @@ pub mod pallet { validator: caller.clone(), }); }, + OCWError::SendChargingCustomersBatchTransactionError { + cluster_id, + era_id, + batch_index, + } => { + Self::deposit_event(Event::SendChargingCustomersBatchTransactionError { + cluster_id, + era_id, + batch_index, + validator: caller.clone(), + }); + }, + OCWError::EndChargingCustomersTransactionError { cluster_id, era_id } => { + Self::deposit_event(Event::EndChargingCustomersTransactionError { + cluster_id, + era_id, + validator: caller.clone(), + }); + }, + OCWError::BeginRewardingProvidersTransactionError { cluster_id, era_id } => { + Self::deposit_event(Event::BeginRewardingProvidersTransactionError { + cluster_id, + era_id, + validator: caller.clone(), + }); + }, + OCWError::EndRewardingProvidersTransactionError { cluster_id, era_id } => { + Self::deposit_event(Event::EndRewardingProvidersTransactionError { + cluster_id, + era_id, + validator: caller.clone(), + }); + }, + OCWError::EndBillingReportTransactionError { cluster_id, era_id } => { + Self::deposit_event(Event::EndBillingReportTransactionError { + cluster_id, + era_id, + validator: caller.clone(), + }); + }, + OCWError::BillingReportDoesNotExist { cluster_id, era_id } => { + Self::deposit_event(Event::BillingReportDoesNotExist { + cluster_id, + era_id, + validator: caller.clone(), + }); + }, OCWError::EmptyCustomerActivity { cluster_id, era_id } => { Self::deposit_event(Event::EmptyCustomerActivity { cluster_id, diff --git a/pallets/ddc-verification/src/mock.rs b/pallets/ddc-verification/src/mock.rs index 01a6a2ff8..a8e1a2544 100644 --- a/pallets/ddc-verification/src/mock.rs +++ b/pallets/ddc-verification/src/mock.rs @@ -2,7 +2,8 @@ use ddc_primitives::{ crypto, sr25519, traits::{ClusterManager, ClusterQuery}, BucketId, ClusterNodeKind, ClusterNodeState, ClusterNodeStatus, ClusterNodesStats, - ClusterStatus, PayoutState, StorageNodePubKey, MAX_PAYOUT_BATCH_COUNT, MAX_PAYOUT_BATCH_SIZE, + ClusterStatus, PayoutError, PayoutState, StorageNodePubKey, MAX_PAYOUT_BATCH_COUNT, + MAX_PAYOUT_BATCH_SIZE, }; use frame_election_provider_support::{ bounds::{ElectionBounds, ElectionBoundsBuilder}, @@ -311,6 +312,28 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pub struct MockPayoutVisitor; impl PayoutVisitor for MockPayoutVisitor { + fn get_next_customer_batch_for_payment( + _cluster_id: &ClusterId, + _era_id: DdcEra, + ) -> Result, PayoutError> { + Ok(None) + } + + fn get_next_provider_batch_for_payment( + _cluster_id: &ClusterId, + _era_id: DdcEra, + ) -> Result, PayoutError> { + Ok(None) + } + + fn all_customer_batches_processed(_cluster_id: &ClusterId, _era_id: DdcEra) -> bool { + true + } + + fn all_provider_batches_processed(_cluster_id: &ClusterId, _era_id: DdcEra) -> bool { + true + } + fn get_billing_report_status(_cluster_id: &ClusterId, _era_id: DdcEra) -> PayoutState { PayoutState::NotInitialized } diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index bd1fee83e..55c964548 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -247,6 +247,11 @@ pub enum NodeRepositoryError { StorageNodeDoesNotExist, } +#[derive(Debug, PartialEq)] +pub enum PayoutError { + BillingReportDoesNotExist, +} + #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo, PartialEq, Default)] // don't remove or change numbers, if needed add a new state to the end with new number // DAC uses the state value for integration! diff --git a/primitives/src/traits/payout.rs b/primitives/src/traits/payout.rs index 50763709c..f2cca9906 100644 --- a/primitives/src/traits/payout.rs +++ b/primitives/src/traits/payout.rs @@ -2,7 +2,8 @@ use scale_info::prelude::vec::Vec; use sp_runtime::DispatchResult; use crate::{ - ActivityHash, BatchIndex, BucketId, ClusterId, CustomerUsage, DdcEra, NodeUsage, PayoutState, + ActivityHash, BatchIndex, BucketId, ClusterId, CustomerUsage, DdcEra, NodeUsage, PayoutError, + PayoutState, }; pub trait PayoutProcessor {} @@ -84,4 +85,18 @@ pub trait PayoutVisitor { ) -> DispatchResult; fn get_billing_report_status(cluster_id: &ClusterId, era: DdcEra) -> PayoutState; + + fn all_customer_batches_processed(cluster_id: &ClusterId, era_id: DdcEra) -> bool; + + fn all_provider_batches_processed(cluster_id: &ClusterId, era_id: DdcEra) -> bool; + + fn get_next_customer_batch_for_payment( + cluster_id: &ClusterId, + era_id: DdcEra, + ) -> Result, PayoutError>; + + fn get_next_provider_batch_for_payment( + cluster_id: &ClusterId, + era_id: DdcEra, + ) -> Result, PayoutError>; }