Skip to content

Commit

Permalink
feat: activation_block logic
Browse files Browse the repository at this point in the history
  • Loading branch information
Supremesource committed Nov 20, 2024
1 parent 723f089 commit 193dff9
Show file tree
Hide file tree
Showing 14 changed files with 104 additions and 55 deletions.
2 changes: 1 addition & 1 deletion pallets/offworker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use sp_std::collections::btree_map::BTreeMap;
use pallet_subnet_emission::{ConsensusParameters, Weights};
use pallet_subspace::{
math::{inplace_normalize_64, vec_fixed64_to_fixed32},
Consensus, CopierMargin, MaxEncryptionPeriod, MinFees,
Consensus, CopierMargin, MinFees,
};
use parity_scale_codec::{Decode, Encode};
use scale_info::prelude::marker::PhantomData;
Expand Down
6 changes: 2 additions & 4 deletions pallets/offworker/src/process.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,8 @@ impl<T: Config> Pallet<T> {
}

let copier_margin = CopierMargin::<T>::get(subnet_id);
let max_encryption_period = MaxEncryptionPeriod::<T>::get(subnet_id)
.map_or(T::MaxEncryptionDuration::get(), |period| {
T::MaxEncryptionDuration::get().min(period)
});
let max_encryption_period =
pallet_subnet_emission::Pallet::<T>::get_max_encryption_interval(&subnet_id);

let (last_processed_block, simulation_result) = Self::get_subnet_state(
subnet_id,
Expand Down
10 changes: 10 additions & 0 deletions pallets/offworker/src/profitability.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,15 @@ pub fn is_copying_irrational<T: pallet_subspace::Config>(
}: ConsensusSimulationResult<T>,
block_number: u64,
) -> (bool, I64F64) {
log::info!(
"Checking if copying is irrational: creation_block: {}, max_encryption_period: {}, copier_margin: {}, cumulative_avg_delegate_divs: {}, cumulative_copier_divs: {}, block_number: {}",
creation_block,
max_encryption_period,
copier_margin,
cumulative_avg_delegate_divs,
cumulative_copier_divs,
block_number
);
let encryption_window_len = block_number.saturating_sub(creation_block);
if encryption_window_len >= max_encryption_period {
log::info!(
Expand All @@ -25,6 +34,7 @@ pub fn is_copying_irrational<T: pallet_subspace::Config>(
let one = I64F64::from_num(1);
let threshold = one.saturating_add(copier_margin).saturating_mul(cumulative_avg_delegate_divs);
let delta = cumulative_copier_divs.saturating_sub(threshold);
log::info!("the delta is {:?}", delta);
(delta.is_negative(), delta)
}

Expand Down
5 changes: 5 additions & 0 deletions pallets/offworker/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,9 +168,12 @@ pub fn should_decrypt_weights<T: Config>(

let decrypted_weights = decrypted_weights_map.into_iter().collect::<Vec<_>>();
if decrypted_weights.is_empty() {
log::info!("subnet {subnet_id} does not have any decrypted weights");
return ShouldDecryptResult::<T>::default();
}

log::info!("simulation yuma params for subnet {subnet_id} are {simulation_yuma_params:?}");

// Run consensus simulation with error handling
let simulation_yuma_output =
match YumaEpoch::<T>::new(subnet_id, simulation_yuma_params).run(decrypted_weights) {
Expand All @@ -184,6 +187,8 @@ pub fn should_decrypt_weights<T: Config>(
// Get delegation fee (this is not a Result type)
let delegation_fee = MinFees::<T>::get().stake_delegation_fee;

log::info!("simulation yuma output for subnet {subnet_id} is {simulation_yuma_output:?}");

// Update simulation result
simulation_result.update(simulation_yuma_output, copier_uid, delegation_fee);

Expand Down
81 changes: 63 additions & 18 deletions pallets/subnet_emission/src/decryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::{
distribute_emission::update_pending_emission,
subnet_consensus::{util::params::ConsensusParams, yuma::YumaEpoch},
};
use pallet_subspace::UseWeightsEncryption;
use pallet_subspace::{MaxEncryptionPeriod, MaxEncryptionPeriodDefaultValue, UseWeightsEncryption};
use sp_runtime::traits::Get;
use sp_std::collections::btree_map::BTreeMap;

Expand Down Expand Up @@ -52,19 +52,27 @@ impl<T: Config> Pallet<T> {

pub fn distribute_subnets_to_nodes(block: u64) {
// Filter out nodes that haven't sent a ping within required interval
log::info!("running distribution to nodes at block {block}");
let active_nodes = match Self::get_active_nodes(block) {
Some(nodes) => nodes,
None => return,
None => {
log::info!("no active nodes found in distributing to nodes");
return;
}
};
log::info!("active nodes are: {active_nodes:?}");

for netuid in pallet_subspace::N::<T>::iter_keys() {
log::info!("decryption process for subnet {netuid:?}");
if !UseWeightsEncryption::<T>::get(netuid) {
log::info!("there is no use weights encryption for subnet {netuid:?}");
continue;
}

let data = SubnetDecryptionData::<T>::get(netuid);
if data.is_some_and(|_data| true) {
return;
log::info!("subnet {netuid:?} has some decryption data");
continue;
}

let mut current = DecryptionNodeCursor::<T>::get() as usize;
Expand All @@ -73,17 +81,22 @@ impl<T: Config> Pallet<T> {
}

if let Some(node_info) = active_nodes.get(current) {
log::info!("found node info at cursor position {current}");
SubnetDecryptionData::<T>::set(
netuid,
Some(SubnetDecryptionInfo {
node_id: node_info.node_id.clone(),
node_public_key: node_info.node_public_key.clone(),
block_assigned: block,
activation_block: None, /* will be set based on the first encrypted
* weight
* occurrence */
last_keep_alive: block,
}),
);

DecryptionNodeCursor::<T>::set((current.saturating_add(1)) as u16);
} else {
log::info!("no node info found at cursor position {current}");
}
}
}
Expand Down Expand Up @@ -322,8 +335,12 @@ impl<T: Config> Pallet<T> {

fn rotate_decryption_node_if_needed(netuid: u16, info: SubnetDecryptionInfo<T>) {
let block_number = pallet_subspace::Pallet::<T>::get_current_block_number();
if block_number.saturating_sub(info.block_assigned)
< T::DecryptionNodeRotationInterval::get()
let activation_block = match info.activation_block {
Some(block) => block,
None => return,
};

if block_number.saturating_sub(activation_block) < T::DecryptionNodeRotationInterval::get()
{
return;
}
Expand All @@ -343,7 +360,8 @@ impl<T: Config> Pallet<T> {
Some(SubnetDecryptionInfo {
node_id: new_node.node_id,
node_public_key: new_node.node_public_key,
block_assigned: block_number,
activation_block: None, /* This will get updated based on the first encrypted
* weights */
last_keep_alive: block_number,
}),
);
Expand Down Expand Up @@ -388,7 +406,7 @@ impl<T: Config> Pallet<T> {
node_id: account_id.clone(),
node_public_key: public_key,
last_keep_alive: current_block,
block_assigned: current_block,
activation_block: None,
});
}
}
Expand Down Expand Up @@ -430,12 +448,14 @@ impl<T: Config> Pallet<T> {
(with_encryption, without_encryption)
}

pub fn get_max_encryption_interval(netuid: &u16) -> u64 {
MaxEncryptionPeriod::<T>::get(netuid)
.unwrap_or_else(|| MaxEncryptionPeriodDefaultValue::get().unwrap_or(10_800))
}

pub fn cancel_expired_offchain_workers(block_number: u64) {
// TODO: make sure that this is the boundry even for the subnet owner
// / the offchain worker has max lenght this exact interval present in the
// `is_copying_irrational` otherwise we could run into race conditions
let max_inactivity_blocks =
T::PingInterval::get().saturating_mul(T::MaxFailedPings::get() as u64);
T::PingInterval::get().saturating_mul(T::MissedPingsForInactivity::get() as u64);

// Get only subnets that use encryption and have encrypted weights
let (with_encryption, _) = Self::get_valid_subnets(None);
Expand All @@ -445,13 +465,11 @@ impl<T: Config> Pallet<T> {
.filter_map(|subnet_id| {
SubnetDecryptionData::<T>::get(subnet_id).map(|info| (subnet_id, info))
})
.filter(|(_, info)| {
.filter(|(subnet_id, info)| {
block_number.saturating_sub(info.last_keep_alive) > max_inactivity_blocks
|| block_number.saturating_sub(info.block_assigned)
> T::MaxEncryptionDuration::get().saturating_add(100) // TODO: the buffer
// has to be a
// constant defined in
// the runtime
|| block_number.saturating_sub(info.activation_block.unwrap_or(u64::MAX))
> Self::get_max_encryption_interval(subnet_id)
.saturating_add(T::EncryptionPeriodBuffer::get())
})
.for_each(|(subnet_id, info)| Self::cancel_offchain_worker(subnet_id, &info));
}
Expand All @@ -464,6 +482,7 @@ impl<T: Config> Pallet<T> {
increase_pending_emission: bool,
clear_node_assing: bool,
) -> u64 {
let _ = WeightEncryptionData::<T>::clear_prefix(subnet_id, u32::MAX, None);
// Sum up and clear ConsensusParameters
let total_emission = ConsensusParameters::<T>::iter_prefix(subnet_id)
.fold(0u64, |acc, (_, params)| {
Expand Down Expand Up @@ -529,4 +548,30 @@ impl<T: Config> Pallet<T> {

BannedDecryptionNodes::<T>::insert(node_id, ban_expiry);
}

/// Assigns activation blocks to subnets when they first receive encrypted weight data.
/// This function runs on every block and sets the activation_block field in
/// SubnetDecryptionInfo when weight encryption data is first detected for a subnet. The
/// activation block is only set once per subnet and remains unchanged afterwards.
///
/// # Arguments
/// * `block_number` - The current block number when this function is called
pub(crate) fn assign_activation_blocks(block_number: u64) {
// Iterate through all subnets in SubnetDecryptionData
for (subnet_id, mut subnet_info) in SubnetDecryptionData::<T>::iter() {
// Check if subnet doesn't already have an activation block
if subnet_info.activation_block.is_none() {
// Check if there's any weight encryption data for this subnet
let has_encrypted_weights =
WeightEncryptionData::<T>::iter_prefix(subnet_id).next().is_some();

// If there's encrypted weight data and no activation block set yet,
// set the current block as the activation block
if has_encrypted_weights {
subnet_info.activation_block = Some(block_number);
SubnetDecryptionData::<T>::insert(subnet_id, subnet_info);
}
}
}
}
}
13 changes: 8 additions & 5 deletions pallets/subnet_emission/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub mod pallet {
use subnet_pricing::root::RootPricing;

#[cfg(feature = "testnet")]
const STORAGE_VERSION: StorageVersion = StorageVersion::new(13);
const STORAGE_VERSION: StorageVersion = StorageVersion::new(14);

#[cfg(not(feature = "testnet"))]
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
Expand All @@ -59,6 +59,7 @@ pub mod pallet {
+ pallet_subspace::Config
+ pallet_governance_api::GovernanceApi<<Self as frame_system::Config>::AccountId>
+ scale_info::TypeInfo
+ sp_std::fmt::Debug
{
/// The events emitted on proposal changes.
#[pallet::no_default_bounds]
Expand Down Expand Up @@ -89,10 +90,6 @@ pub mod pallet {
#[pallet::constant]
type OffchainWorkerBanDuration: Get<u64>;

/// Maximum number of failed pings before a decryption node is banned
#[pallet::constant]
type MaxFailedPings: Get<u8>;

/// The number of consecutive missed pings after which a decryption node is considered
/// inactive
#[pallet::constant]
Expand All @@ -101,6 +98,11 @@ pub mod pallet {
/// The interval (in blocks) at which the decryption node should send a keep-alive
#[pallet::constant]
type PingInterval: Get<u64>;

/// The extra buffer period in blocks that runtime will wait before banning a decryption
/// node. So the final count is `MaxEncryptionPeriod + EncryptionPeriodBuffer`
#[pallet::constant]
type EncryptionPeriodBuffer: Get<u64>;
}

type BalanceOf<T> =
Expand Down Expand Up @@ -213,6 +215,7 @@ pub mod pallet {

Self::distribute_subnets_to_nodes(block_number);
log::info!("Distributed subnets to nodes");
Self::assign_activation_blocks(block_number);
Self::cancel_expired_offchain_workers(block_number);
log::info!("Cancelled expired offchain workers");
let emission_per_block = Self::get_total_emission_per_block();
Expand Down
5 changes: 3 additions & 2 deletions pallets/subnet_emission/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ pub type PublicKey = (Vec<u8>, Vec<u8>);
pub type BlockWeights = (u64, Vec<(u16, Vec<(u16, u16)>, Vec<u8>)>);
pub type KeylessBlockWeights = (u64, Vec<(u16, Vec<(u16, u16)>)>);

#[derive(Clone, Encode, Decode, TypeInfo)]
#[derive(Clone, Encode, Decode, TypeInfo, Debug)]
pub struct SubnetDecryptionInfo<T>
where
T: Config + pallet_subspace::Config + TypeInfo,
{
pub node_id: T::AccountId,
pub node_public_key: PublicKey,
pub block_assigned: u64,
// gets assigned when first encrypted weights appear on the subnet
pub activation_block: Option<u64>,
pub last_keep_alive: u64,
}
2 changes: 1 addition & 1 deletion pallets/subspace/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ pub mod pallet {
IncentiveRatio: u16 = 50,
ModuleBurnConfig,
RegistrationsThisInterval,
MaxEncryptionPeriod,
MaxEncryptionPeriod: Option<u64> = Some(10_800),
CopierMargin: I64F64 = I64F64::from_num(0),
UseWeightsEncryption,
AlphaValues: (u16, u16) = (45875, 58982),
Expand Down
12 changes: 6 additions & 6 deletions pallets/subspace/src/params/subnet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ impl<T: Config> DefaultSubnetParams<T> {
// --- Weight Encryption ---
use_weights_encryption: T::DefaultUseWeightsEncryption::get(),
copier_margin: CopierMarginDefaultValue::get(),
max_encryption_period: None,
max_encryption_period: MaxEncryptionPeriodDefaultValue::get(),
}
}
}
Expand Down Expand Up @@ -112,8 +112,8 @@ const MIN_ALLOWED_WEIGHTS: u16 = 1;
const MAX_VALIDATOR_STAKE: u64 = 250_000_000_000_000;
const MAX_COPIER_MARGIN: f64 = 1.0;
const MIN_ALLOWED_VALIDATORS: u16 = 10;
const MIN_ENCRYPTION_PERIOD: u64 = 360;
const MIN_SET_WEIGHT_CALLS: u16 = 1;
const MAX_ENCRYPTION_DURATION: u64 = 10_800 * 2; // 2 days

impl<T: Config> ValidatedSubnetParams<T> {
pub fn new(params: SubnetParams<T>, netuid: Option<u16>) -> Result<Self, DispatchError> {
Expand Down Expand Up @@ -178,9 +178,9 @@ impl<T: Config> ValidatedSubnetParams<T> {
}

// Validate tempo and weight age
ensure!(*tempo >= MIN_TEMPO, Error::<T>::InvalidTempo);
ensure!(tempo >= &MIN_TEMPO, Error::<T>::InvalidTempo);
ensure!(
*max_weight_age > *tempo as u64,
*max_weight_age > u64::from(*tempo),
Error::<T>::InvalidMaxWeightAge
);

Expand Down Expand Up @@ -233,8 +233,8 @@ impl<T: Config> ValidatedSubnetParams<T> {

if let Some(encryption_period) = max_encryption_period {
ensure!(
*encryption_period >= MIN_ENCRYPTION_PERIOD
&& *encryption_period <= T::MaxEncryptionDuration::get(),
*encryption_period >= u64::from(*tempo)
&& *encryption_period <= MAX_ENCRYPTION_DURATION,
Error::<T>::InvalidMaxEncryptionPeriod
);
}
Expand Down
5 changes: 0 additions & 5 deletions pallets/subspace/src/selections/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,5 @@ pub mod config {
type WeightInfo: WeightInfo;
type EnforceWhitelist: Get<bool>;
type DefaultUseWeightsEncryption: Get<bool>;

/// Maximum number of blocks, weights can stay encrypted for. Controls boundaries of subnet
/// param: `max_encryption_period`
#[pallet::constant]
type MaxEncryptionDuration: Get<u64>;
}
}
Loading

0 comments on commit 193dff9

Please sign in to comment.