Skip to content

Commit c982579

Browse files
authored
Merge pull request #3711 from autonomys/offline_operator_detection
Offline operator detection
2 parents 177c423 + 3cec4fb commit c982579

File tree

14 files changed

+1103
-232
lines changed

14 files changed

+1103
-232
lines changed

Cargo.lock

Lines changed: 4 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ serde_json = "1.0.133"
206206
sha2 = { version = "0.10.7", default-features = false }
207207
sp-api = { git = "https://github.com/subspace/polkadot-sdk", rev = "e831132867930ca90a7088c7246301ab29f015ba", default-features = false }
208208
sp-application-crypto = { git = "https://github.com/subspace/polkadot-sdk", rev = "e831132867930ca90a7088c7246301ab29f015ba", default-features = false }
209-
sp-arithmetic = { git = "https://github.com/subspace/polkadot-sdk", rev = "e831132867930ca90a7088c7246301ab29f015ba" }
209+
sp-arithmetic = { git = "https://github.com/subspace/polkadot-sdk", rev = "e831132867930ca90a7088c7246301ab29f015ba", default-features = false }
210210
sp-auto-id = { version = "0.1.0", path = "domains/primitives/auto-id", default-features = false }
211211
sp-block-builder = { git = "https://github.com/subspace/polkadot-sdk", rev = "e831132867930ca90a7088c7246301ab29f015ba", default-features = false }
212212
sp-block-fees = { version = "0.1.0", path = "domains/primitives/block-fees", default-features = false }

crates/pallet-domains/src/lib.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,15 @@ pub trait HoldIdentifier<T: Config> {
9797
}
9898

9999
pub trait BlockSlot<T: frame_system::Config> {
100-
// Return the future slot of the given `block_number`
100+
/// Returns the highest valid slot for the given `block_number`.
101+
/// Returns `None` if that block number is too far in the past, or too far in the future.
101102
fn future_slot(block_number: BlockNumberFor<T>) -> Option<sp_consensus_slots::Slot>;
102103

103-
// Return the latest block number whose slot is less than the given `to_check` slot
104+
/// Returns the latest block number whose slot is less than the given `to_check` slot
104105
fn slot_produced_after(to_check: sp_consensus_slots::Slot) -> Option<BlockNumberFor<T>>;
106+
107+
/// Returns the slot at the current block height
108+
fn current_slot() -> sp_consensus_slots::Slot;
105109
}
106110

107111
pub type ExecutionReceiptOf<T> = ExecutionReceipt<
@@ -281,9 +285,11 @@ mod pallet {
281285
use frame_support::{Identity, PalletError};
282286
use frame_system::pallet_prelude::*;
283287
use parity_scale_codec::FullCodec;
288+
use sp_consensus_slots::Slot;
284289
use sp_core::H256;
285290
use sp_domains::bundle::BundleDigest;
286291
use sp_domains::bundle_producer_election::ProofOfElectionError;
292+
use sp_domains::offline_operators::OperatorEpochExpectations;
287293
use sp_domains::{
288294
BundleAndExecutionReceiptVersion, DomainBundleSubmitted, DomainId, DomainOwner,
289295
DomainSudoCall, DomainsTransfersTracker, EpochIndex,
@@ -816,6 +822,15 @@ mod pallet {
816822
pub type PreviousBundleAndExecutionReceiptVersions<T> =
817823
StorageValue<_, BTreeMap<BlockNumberFor<T>, BundleAndExecutionReceiptVersion>, ValueQuery>;
818824

825+
/// Stores the slot at which a new epoch has started.
826+
#[pallet::storage]
827+
pub type EpochStartSlot<T> = StorageValue<_, Slot, OptionQuery>;
828+
829+
/// Stores the number of bundles each operator submitted in a given epoch.
830+
/// Storage is cleared at the end of epoch.
831+
#[pallet::storage]
832+
pub type OperatorBundleCountInEpoch<T> = StorageMap<_, Identity, OperatorId, u64, ValueQuery>;
833+
819834
#[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
820835
pub enum BundleError {
821836
/// Can not find the operator for given operator id.
@@ -1140,6 +1155,12 @@ mod pallet {
11401155
operator_id: OperatorId,
11411156
domain_id: DomainId,
11421157
},
1158+
OperatorOffline {
1159+
operator_id: OperatorId,
1160+
domain_id: DomainId,
1161+
submitted_bundles: u64,
1162+
expectations: OperatorEpochExpectations,
1163+
},
11431164
}
11441165

11451166
#[pallet::origin]
@@ -1192,6 +1213,9 @@ mod pallet {
11921213
#[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
11931214
let receipt_block_number = *receipt.domain_block_number();
11941215

1216+
// increment the operator bundle count in the epoch.
1217+
OperatorBundleCountInEpoch::<T>::mutate(operator_id, |c| c.saturating_add(1));
1218+
11951219
#[cfg(not(feature = "runtime-benchmarks"))]
11961220
let mut actual_weight = T::WeightInfo::submit_bundle();
11971221
#[cfg(feature = "runtime-benchmarks")]

crates/pallet-domains/src/staking_epoch.rs

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
//! Staking epoch transition for domain
22
use crate::bundle_storage_fund::deposit_reserve_for_storage_fund;
33
use crate::pallet::{
4-
AccumulatedTreasuryFunds, Deposits, DomainStakingSummary, LastEpochStakingDistribution,
5-
OperatorIdOwner, Operators, PendingSlashes, PendingStakingOperationCount, Withdrawals,
4+
AccumulatedTreasuryFunds, Deposits, DomainRegistry, DomainStakingSummary,
5+
LastEpochStakingDistribution, OperatorIdOwner, Operators, PendingSlashes,
6+
PendingStakingOperationCount, Withdrawals,
67
};
78
use crate::staking::{
89
DomainEpoch, Error as TransitionError, OperatorStatus, SharePrice, WithdrawalInShares,
910
do_cleanup_operator, do_convert_previous_epoch_deposits, do_convert_previous_epoch_withdrawal,
1011
};
1112
use crate::{
12-
BalanceOf, Config, DeactivatedOperators, DepositOnHold, DeregisteredOperators,
13-
DomainChainRewards, ElectionVerificationParams, Event, HoldIdentifier, InvalidBundleAuthors,
14-
OperatorEpochSharePrice, Pallet, bundle_storage_fund,
13+
BalanceOf, BlockSlot, Config, DeactivatedOperators, DepositOnHold, DeregisteredOperators,
14+
DomainChainRewards, ElectionVerificationParams, EpochStartSlot, Event, HoldIdentifier,
15+
InvalidBundleAuthors, OperatorBundleCountInEpoch, OperatorEpochSharePrice, Pallet,
16+
bundle_storage_fund,
1517
};
1618
use frame_support::traits::fungible::{Inspect, Mutate, MutateHold};
1719
use frame_support::traits::tokens::{
@@ -21,9 +23,12 @@ use frame_support::{PalletError, StorageDoubleMap};
2123
use parity_scale_codec::{Decode, Encode};
2224
use scale_info::TypeInfo;
2325
use sp_core::Get;
26+
use sp_domains::offline_operators::{
27+
E_BASE, LN_1_OVER_TAU_1_PERCENT, operator_expected_bundles_in_epoch,
28+
};
2429
use sp_domains::{DomainId, EpochIndex, OperatorId, OperatorRewardSource};
2530
use sp_runtime::traits::{CheckedAdd, CheckedSub, One, Zero};
26-
use sp_runtime::{Perquintill, Saturating};
31+
use sp_runtime::{Perquintill, SaturatedConversion, Saturating};
2732
use sp_std::collections::btree_map::BTreeMap;
2833
use sp_std::collections::btree_set::BTreeSet;
2934

@@ -237,6 +242,17 @@ pub(crate) fn do_finalize_domain_epoch_staking<T: Config>(
237242
let mut total_domain_stake = BalanceOf::<T>::zero();
238243
let mut current_operators = BTreeMap::new();
239244
let mut next_operators = BTreeSet::new();
245+
let current_slot = T::BlockSlot::current_slot();
246+
let total_epoch_slots = EpochStartSlot::<T>::get()
247+
.map(|start_slot| current_slot - start_slot + 1)
248+
.unwrap_or_default();
249+
let epoch_total_stake = stake_summary.current_total_stake;
250+
let operators_total_bundle_count =
251+
OperatorBundleCountInEpoch::<T>::drain().collect::<BTreeMap<_, _>>();
252+
let bundle_slot_probability = DomainRegistry::<T>::get(domain_id)
253+
.ok_or(TransitionError::DomainNotInitialized)?
254+
.domain_config
255+
.bundle_slot_probability;
240256
for next_operator_id in &stake_summary.next_operators {
241257
// If an operator is pending to slash then similar to the slashed operator it should not be added
242258
// into the `next_operators/current_operators` and we should not `do_finalize_operator_epoch_staking`
@@ -245,6 +261,32 @@ pub(crate) fn do_finalize_domain_epoch_staking<T: Config>(
245261
continue;
246262
}
247263

264+
// check operator performance in the previous epoch if the operator was part of the previous epoch set
265+
// if they are not part of the previous epoch set, their performance will be checked in the next epoch.
266+
if let Some(operator_stake) = stake_summary.current_operators.get(next_operator_id)
267+
&& let Some(bundle_count) = operators_total_bundle_count.get(next_operator_id)
268+
{
269+
let maybe_operator_epoch_expectations = operator_expected_bundles_in_epoch(
270+
total_epoch_slots.into(),
271+
(*operator_stake).saturated_into(),
272+
epoch_total_stake.saturated_into(),
273+
bundle_slot_probability,
274+
LN_1_OVER_TAU_1_PERCENT,
275+
E_BASE,
276+
);
277+
278+
if let Some(operator_epoch_expectations) = maybe_operator_epoch_expectations
279+
&& bundle_count < &operator_epoch_expectations.min_required_bundles
280+
{
281+
Pallet::<T>::deposit_event(Event::OperatorOffline {
282+
operator_id: *next_operator_id,
283+
domain_id,
284+
submitted_bundles: *bundle_count,
285+
expectations: operator_epoch_expectations,
286+
});
287+
};
288+
}
289+
248290
let (operator_stake, stake_changed) = do_finalize_operator_epoch_staking::<T>(
249291
domain_id,
250292
*next_operator_id,
@@ -263,6 +305,9 @@ pub(crate) fn do_finalize_domain_epoch_staking<T: Config>(
263305
}
264306
}
265307

308+
// set epoch start slot
309+
EpochStartSlot::<T>::put(current_slot);
310+
266311
// we finalized the operators who are in the next operator set.
267312
// But there will be deposits/withdrawals for operators who are not part of the next operator set.
268313
// So they need to have share price for the previous epoch

crates/pallet-domains/src/tests.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,13 +217,17 @@ impl StorageFee<Balance> for DummyStorageFee {
217217
pub struct DummyBlockSlot;
218218

219219
impl BlockSlot<Test> for DummyBlockSlot {
220-
fn future_slot(_block_number: BlockNumberFor<Test>) -> Option<sp_consensus_slots::Slot> {
220+
fn future_slot(_block_number: BlockNumberFor<Test>) -> Option<Slot> {
221221
None
222222
}
223223

224224
fn slot_produced_after(_slot: sp_consensus_slots::Slot) -> Option<BlockNumberFor<Test>> {
225225
Some(0u32)
226226
}
227+
228+
fn current_slot() -> Slot {
229+
Slot::from(0)
230+
}
227231
}
228232

229233
pub struct MockDomainsTransfersTracker;

0 commit comments

Comments
 (0)