Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 132 additions & 6 deletions pallets/subtensor/src/extensions/subtensor.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use crate::{
Call, CheckColdkeySwap, CheckDelegateTake, CheckEvmKeyAssociation, CheckRateLimits,
CheckServingEndpoints, CheckWeights, Config, Error, guards::applicable_call,
CheckServingEndpoints, CheckWeights, Config, Error, Pallet, guards::applicable_call,
};
use codec::{Decode, DecodeWithMemTracking, Encode};
use frame_support::{
dispatch::{DispatchExtension, DispatchInfo, PostDispatchInfo},
traits::{IsSubType, OriginTrait},
traits::{Get, IsSubType, OriginTrait},
weights::Weight,
};
use scale_info::TypeInfo;
Expand All @@ -14,7 +14,7 @@ use sp_runtime::traits::{
};
use sp_runtime::{
impl_tx_ext_default,
transaction_validity::{TransactionSource, TransactionValidityError},
transaction_validity::{TransactionSource, TransactionValidityError, ValidTransaction},
};
use sp_std::marker::PhantomData;
use subtensor_macros::freeze_struct;
Expand Down Expand Up @@ -77,9 +77,10 @@ impl<T: Config + Send + Sync + TypeInfo> SubtensorTransactionExtension<T> {

fn check(origin: &OriginOf<T>, call: &CallOf<T>) -> Result<(), Error<T>>
where
T: pallet_shield::Config,
T: pallet_commitments::Config + pallet_shield::Config,
CallOf<T>: Dispatchable<RuntimeOrigin = OriginOf<T>>
+ IsSubType<Call<T>>
+ IsSubType<pallet_commitments::Call<T>>
+ IsSubType<pallet_shield::Call<T>>,
OriginOf<T>: OriginTrait<AccountId = T::AccountId>,
{
Expand All @@ -89,6 +90,16 @@ impl<T: Config + Send + Sync + TypeInfo> SubtensorTransactionExtension<T> {

CheckColdkeySwap::<T>::check(who, call)?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL] Bump the runtime spec version

This changes runtime transaction validation while runtime/src/lib.rs still declares spec_version: 442. During rollout, a node containing this native runtime may substitute it for the existing on-chain Wasm runtime with the same version, causing nodes to apply different validation behavior. Increment VERSION.spec_version for this runtime-affecting change.


let commitment_call: Option<&pallet_commitments::Call<T>> = call.is_sub_type();
if let Some(pallet_commitments::Call::set_commitment { netuid, .. }) = commitment_call {
Comment thread
UnArbosSix marked this conversation as resolved.
Comment thread
UnArbosSix marked this conversation as resolved.
if !Pallet::<T>::if_subnet_exist(*netuid) {
return Err(Error::<T>::SubnetNotExists);
}
if !Pallet::<T>::is_hotkey_registered_on_network(*netuid, who) {
return Err(Error::<T>::HotKeyNotRegisteredInSubNet);
}
}

if let Some(call) = applicable_call(call, CheckWeights::<T>::applies_to) {
CheckWeights::<T>::check(who, call)?;
}
Expand All @@ -107,13 +118,30 @@ impl<T: Config + Send + Sync + TypeInfo> SubtensorTransactionExtension<T> {

Ok(())
}

fn commitment_weight(call: &CallOf<T>) -> Weight
where
T: pallet_commitments::Config,
CallOf<T>: IsSubType<pallet_commitments::Call<T>>,
{
let commitment_call: Option<&pallet_commitments::Call<T>> = call.is_sub_type();
if matches!(
commitment_call,
Some(pallet_commitments::Call::set_commitment { .. })
) {
T::DbWeight::get().reads(2)
} else {
Weight::zero()
}
}
}

impl<T> TransactionExtension<CallOf<T>> for SubtensorTransactionExtension<T>
where
T: Config + pallet_shield::Config + Send + Sync + TypeInfo,
T: Config + pallet_commitments::Config + pallet_shield::Config + Send + Sync + TypeInfo,
CallOf<T>: Dispatchable<RuntimeOrigin = OriginOf<T>, Info = DispatchInfo, PostInfo = PostDispatchInfo>
+ IsSubType<Call<T>>
+ IsSubType<pallet_commitments::Call<T>>
+ IsSubType<pallet_shield::Call<T>>,
OriginOf<T>: Clone + OriginTrait<AccountId = T::AccountId>,
{
Expand All @@ -131,6 +159,7 @@ where
.saturating_add(<CheckDelegateTake<T> as DE<CallOf<T>>>::weight(call))
.saturating_add(<CheckServingEndpoints<T> as DE<CallOf<T>>>::weight(call))
.saturating_add(<CheckEvmKeyAssociation<T> as DE<CallOf<T>>>::weight(call))
.saturating_add(Self::commitment_weight(call))
}

fn validate(
Expand All @@ -144,7 +173,17 @@ where
_source: TransactionSource,
) -> ValidateResult<Self::Val, CallOf<T>> {
Self::check(&origin, call)
.map(|()| (Default::default(), (), origin))
.map(|()| {
let mut validity = ValidTransaction::default();
if let Some(who) = origin.as_signer()
&& let Some(call) = applicable_call(call, CheckRateLimits::<T>::applies_to)
{
validity
.provides
.extend(CheckRateLimits::<T>::provides_tags(who, call));
}
(validity, (), origin)
})
.map_err(|error| TransactionValidityError::from(CustomTransactionError::from(error)))
}

Expand Down Expand Up @@ -206,6 +245,9 @@ mod tests {
.saturating_add(<CheckEvmKeyAssociation<Test> as DE<RuntimeCall>>::weight(
call,
))
.saturating_add(SubtensorTransactionExtension::<Test>::commitment_weight(
call,
))
}

#[test]
Expand Down Expand Up @@ -278,6 +320,84 @@ mod tests {
});
}

#[test]
fn validate_rejects_ineligible_metadata_commitment() {
new_test_ext(0).execute_with(|| {
let netuid = NetUid::from(1);
let hotkey = U256::from(1);
let coldkey = U256::from(2);
let commitment_call = || {
RuntimeCall::Commitments(pallet_commitments::Call::set_commitment {
netuid,
info: Box::new(pallet_commitments::CommitmentInfo {
fields: frame_support::BoundedVec::default(),
}),
})
};

assert_eq!(
validate_signed(hotkey, &commitment_call()).unwrap_err(),
CustomTransactionError::SubnetNotExists.into()
);

add_network(netuid, 1, 0);
assert_eq!(
validate_signed(hotkey, &commitment_call()).unwrap_err(),
CustomTransactionError::UidNotFound.into()
);

setup_reserves(
netuid,
1_000_000_000_000_u64.into(),
1_000_000_000_000_u64.into(),
);
register_ok_neuron(netuid, hotkey, coldkey, 0);
assert_ok!(validate_signed(hotkey, &commitment_call()));
});
}

#[test]
fn timelocked_commits_reject_at_validity_and_conflict_in_pool() {
new_test_ext(0).execute_with(|| {
let netuid = NetUid::from(1);
let hotkey = U256::from(1);
let coldkey = U256::from(2);

add_network(netuid, 1, 0);
setup_reserves(
netuid,
1_000_000_000_000_u64.into(),
1_000_000_000_000_u64.into(),
);
register_ok_neuron(netuid, hotkey, coldkey, 0);
SubtensorModule::set_stake_threshold(0);
SubtensorModule::set_weights_set_rate_limit(netuid, 100);
System::set_block_number(10_u64);
let uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey).unwrap();
let netuid_index = SubtensorModule::get_mechanism_storage_index(netuid, MechId::MAIN);
SubtensorModule::set_last_update_for_uid(netuid_index, uid, 10);

let call =
RuntimeCall::SubtensorModule(SubtensorCall::commit_timelocked_mechanism_weights {
netuid,
mecid: MechId::MAIN,
commit: Default::default(),
reveal_round: 1,
commit_reveal_version: 4,
});
assert_eq!(
validate_signed(hotkey, &call).unwrap_err(),
CustomTransactionError::RateLimitExceeded.into()
);

System::set_block_number(200_u64);
let first = validate_signed(hotkey, &call).unwrap();
let second = validate_signed(hotkey, &call).unwrap();
assert_eq!(first.provides.len(), 1);
assert_eq!(first.provides, second.provides);
});
}

#[test]
fn weight_matches_top_level_dispatch_extension_checks() {
new_test_ext(1).execute_with(|| {
Expand All @@ -293,6 +413,12 @@ mod tests {
RuntimeCall::SubtensorModule(SubtensorCall::register_network {
hotkey: U256::from(9),
}),
RuntimeCall::Commitments(pallet_commitments::Call::set_commitment {
netuid: NetUid::from(1),
info: Box::new(pallet_commitments::CommitmentInfo {
fields: frame_support::BoundedVec::default(),
}),
}),
];

for call in calls {
Expand Down
58 changes: 57 additions & 1 deletion pallets/subtensor/src/guards/check_rate_limits.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
use super::{CallOf, DispatchableOriginOf, applicable_call};
use crate::weights::WeightInfo;
use crate::{Call, Config, Error, Pallet, TransactionType};
use codec::Encode;
use frame_support::{
dispatch::{DispatchErrorWithPostInfo, DispatchExtension, DispatchInfo, PostDispatchInfo},
pallet_prelude::*,
traits::{IsSubType, OriginTrait},
};
use sp_runtime::traits::Dispatchable;
use sp_std::marker::PhantomData;
use sp_std::{marker::PhantomData, vec, vec::Vec};
use subtensor_runtime_common::{NetUid, NetUidStorageIndex};

/// Dispatch extension for rate-limit checks that are safe to reject before dispatch.
Expand All @@ -22,6 +23,9 @@ impl<T: Config> CheckRateLimits<T> {
call,
Call::commit_weights { .. }
| Call::commit_mechanism_weights { .. }
| Call::commit_timelocked_weights { .. }
| Call::commit_timelocked_mechanism_weights { .. }
| Call::commit_crv3_mechanism_weights { .. }
| Call::set_weights { .. }
| Call::set_mechanism_weights { .. }
| Call::register_network { .. }
Expand Down Expand Up @@ -60,6 +64,21 @@ impl<T: Config> CheckRateLimits<T> {
Pallet::<T>::get_mechanism_storage_index(*netuid, *mecid),
Error::<T>::CommittingWeightsTooFast,
),
Call::commit_timelocked_weights { netuid, .. } => Self::check_weights_rate_limit(
who,
*netuid,
NetUidStorageIndex::from(*netuid),
Error::<T>::CommittingWeightsTooFast,
),
Call::commit_timelocked_mechanism_weights { netuid, mecid, .. }
| Call::commit_crv3_mechanism_weights { netuid, mecid, .. } => {
Self::check_weights_rate_limit(
who,
*netuid,
Pallet::<T>::get_mechanism_storage_index(*netuid, *mecid),
Error::<T>::CommittingWeightsTooFast,
)
}
Call::set_weights { netuid, .. }
if !Pallet::<T>::get_commit_reveal_weights_enabled(*netuid) =>
{
Expand Down Expand Up @@ -88,6 +107,24 @@ impl<T: Config> CheckRateLimits<T> {
_ => Ok(()),
}
}

/// One pending commit per hotkey and mechanism. Calls sharing this tag also share the same
/// on-chain rate limit, so the pool keeps only one candidate instead of landing the rest as
/// deterministic `CommittingWeightsTooFast` failures.
pub(crate) fn provides_tags(who: &T::AccountId, call: &Call<T>) -> Vec<Vec<u8>> {
let netuid_index = match call {
Call::commit_weights { netuid, .. }
| Call::commit_timelocked_weights { netuid, .. } => NetUidStorageIndex::from(*netuid),
Call::commit_mechanism_weights { netuid, mecid, .. }
| Call::commit_timelocked_mechanism_weights { netuid, mecid, .. }
| Call::commit_crv3_mechanism_weights { netuid, mecid, .. } => {
Pallet::<T>::get_mechanism_storage_index(*netuid, *mecid)
}
_ => return Vec::new(),
};

vec![(b"weight-commit", who, netuid_index).encode()]
}
}

impl<T> DispatchExtension<CallOf<T>> for CheckRateLimits<T>
Expand Down Expand Up @@ -191,6 +228,25 @@ mod tests {
mecid: MechId::MAIN,
commit_hash: sp_core::H256::zero(),
}),
RuntimeCall::SubtensorModule(SubtensorCall::commit_timelocked_weights {
netuid,
commit: Default::default(),
reveal_round: 1,
commit_reveal_version: 4,
}),
RuntimeCall::SubtensorModule(SubtensorCall::commit_timelocked_mechanism_weights {
netuid,
mecid: MechId::MAIN,
commit: Default::default(),
reveal_round: 1,
commit_reveal_version: 4,
}),
RuntimeCall::SubtensorModule(SubtensorCall::commit_crv3_mechanism_weights {
netuid,
mecid: MechId::MAIN,
commit: Default::default(),
reveal_round: 1,
}),
set_weights_call(netuid, 0),
RuntimeCall::SubtensorModule(SubtensorCall::set_mechanism_weights {
netuid,
Expand Down
Loading