diff --git a/common/src/transaction_error.rs b/common/src/transaction_error.rs index de98cdf5f4..cc3054a30d 100644 --- a/common/src/transaction_error.rs +++ b/common/src/transaction_error.rs @@ -30,6 +30,7 @@ pub enum CustomTransactionError { InvalidRealAccount, FailedShieldedTxParsing, InvalidShieldedTxPubKeyHash, + NonAssociatedColdKey, } impl From for u8 { @@ -62,6 +63,7 @@ impl From for u8 { CustomTransactionError::InvalidRealAccount => 22, CustomTransactionError::FailedShieldedTxParsing => 23, CustomTransactionError::InvalidShieldedTxPubKeyHash => 24, + CustomTransactionError::NonAssociatedColdKey => 25, } } } diff --git a/pallets/subtensor/src/extensions/subtensor.rs b/pallets/subtensor/src/extensions/subtensor.rs index 7455dbb78a..9fdc3af268 100644 --- a/pallets/subtensor/src/extensions/subtensor.rs +++ b/pallets/subtensor/src/extensions/subtensor.rs @@ -51,8 +51,12 @@ where } pub fn result_to_validity(result: Result<(), Error>, priority: u64) -> TransactionValidity { - if let Err(err) = result { - Err(match err { + match result { + Ok(()) => Ok(ValidTransaction { + priority, + ..Default::default() + }), + Err(err) => Err(match err { Error::::AmountTooLow => CustomTransactionError::StakeAmountTooLow, Error::::SubnetNotExists => CustomTransactionError::SubnetNotExists, Error::::NotEnoughBalanceToStake => CustomTransactionError::BalanceTooLow, @@ -73,14 +77,10 @@ where CustomTransactionError::ServingRateLimitExceeded } Error::::InvalidPort => CustomTransactionError::InvalidPort, + Error::::NonAssociatedColdKey => CustomTransactionError::NonAssociatedColdKey, _ => CustomTransactionError::BadRequest, } - .into()) - } else { - Ok(ValidTransaction { - priority, - ..Default::default() - }) + .into()), } } } @@ -115,13 +115,29 @@ where }; match call.is_sub_type() { - Some(Call::commit_weights { netuid, .. }) => { + Some(Call::commit_weights { netuid, .. }) + | Some(Call::commit_mechanism_weights { netuid, .. }) => { if Self::check_weights_min_stake(who, *netuid) { Ok((Default::default(), (), origin)) } else { Err(CustomTransactionError::StakeAmountTooLow.into()) } } + Some(Call::batch_commit_weights { + netuids, + commit_hashes, + }) => { + if netuids.len() != commit_hashes.len() { + return Err(CustomTransactionError::InputLengthsUnequal.into()); + } + for netuid in netuids.iter() { + let netuid: NetUid = (*netuid).into(); + if !Self::check_weights_min_stake(who, netuid) { + return Err(CustomTransactionError::StakeAmountTooLow.into()); + } + } + Ok((Default::default(), (), origin)) + } Some(Call::reveal_weights { netuid, uids, @@ -152,6 +168,37 @@ where Err(CustomTransactionError::StakeAmountTooLow.into()) } } + Some(Call::reveal_mechanism_weights { + netuid, + mecid: _, + uids, + values, + salt, + version_key, + }) => { + if Self::check_weights_min_stake(who, *netuid) { + let provided_hash = Pallet::::get_commit_hash( + who, + NetUidStorageIndex::from(*netuid), + uids, + values, + salt, + *version_key, + ); + match Pallet::::find_commit_block_via_hash(provided_hash) { + Some(commit_block) => { + if Pallet::::is_reveal_block_range(*netuid, commit_block) { + Ok((Default::default(), (), origin)) + } else { + Err(CustomTransactionError::CommitBlockNotInRevealRange.into()) + } + } + None => Err(CustomTransactionError::CommitNotFound.into()), + } + } else { + Err(CustomTransactionError::StakeAmountTooLow.into()) + } + } Some(Call::batch_reveal_weights { netuid, uids_list, @@ -159,54 +206,70 @@ where salts_list, version_keys, }) => { - if Self::check_weights_min_stake(who, *netuid) { - let num_reveals = uids_list.len(); - if num_reveals == values_list.len() - && num_reveals == salts_list.len() - && num_reveals == version_keys.len() - { - let provided_hashes = (0..num_reveals) - .map(|i| { - Pallet::::get_commit_hash( - who, - NetUidStorageIndex::from(*netuid), - uids_list.get(i).unwrap_or(&Vec::new()), - values_list.get(i).unwrap_or(&Vec::new()), - salts_list.get(i).unwrap_or(&Vec::new()), - *version_keys.get(i).unwrap_or(&0_u64), - ) - }) - .collect::>(); + if !Self::check_weights_min_stake(who, *netuid) { + return Err(CustomTransactionError::StakeAmountTooLow.into()); + } - let batch_reveal_block = provided_hashes - .iter() - .filter_map(|hash| Pallet::::find_commit_block_via_hash(*hash)) - .collect::>(); + let num_reveals = uids_list.len(); + if num_reveals == values_list.len() + && num_reveals == salts_list.len() + && num_reveals == version_keys.len() + { + let provided_hashes = (0..num_reveals) + .map(|i| { + Pallet::::get_commit_hash( + who, + NetUidStorageIndex::from(*netuid), + uids_list.get(i).unwrap_or(&Vec::new()), + values_list.get(i).unwrap_or(&Vec::new()), + salts_list.get(i).unwrap_or(&Vec::new()), + *version_keys.get(i).unwrap_or(&0_u64), + ) + }) + .collect::>(); - if provided_hashes.len() == batch_reveal_block.len() { - if Pallet::::is_batch_reveal_block_range(*netuid, batch_reveal_block) - { - Ok((Default::default(), (), origin)) - } else { - Err(CustomTransactionError::CommitBlockNotInRevealRange.into()) - } + let batch_reveal_block = provided_hashes + .iter() + .filter_map(|hash| Pallet::::find_commit_block_via_hash(*hash)) + .collect::>(); + + if provided_hashes.len() == batch_reveal_block.len() { + if Pallet::::is_batch_reveal_block_range(*netuid, batch_reveal_block) { + Ok((Default::default(), (), origin)) } else { - Err(CustomTransactionError::CommitNotFound.into()) + Err(CustomTransactionError::CommitBlockNotInRevealRange.into()) } } else { - Err(CustomTransactionError::InputLengthsUnequal.into()) + Err(CustomTransactionError::CommitNotFound.into()) } } else { - Err(CustomTransactionError::StakeAmountTooLow.into()) + Err(CustomTransactionError::InputLengthsUnequal.into()) } } - Some(Call::set_weights { netuid, .. }) => { + Some(Call::set_weights { netuid, .. }) + | Some(Call::set_mechanism_weights { netuid, .. }) => { if Self::check_weights_min_stake(who, *netuid) { Ok((Default::default(), (), origin)) } else { Err(CustomTransactionError::StakeAmountTooLow.into()) } } + Some(Call::batch_set_weights { + netuids, + weights, + version_keys, + }) => { + if netuids.len() != weights.len() || netuids.len() != version_keys.len() { + return Err(CustomTransactionError::InputLengthsUnequal.into()); + } + for netuid in netuids.iter() { + let netuid: NetUid = (*netuid).into(); + if !Self::check_weights_min_stake(who, netuid) { + return Err(CustomTransactionError::StakeAmountTooLow.into()); + } + } + Ok((Default::default(), (), origin)) + } Some(Call::commit_timelocked_weights { netuid, reveal_round, @@ -221,6 +284,52 @@ where Err(CustomTransactionError::StakeAmountTooLow.into()) } } + Some(Call::commit_timelocked_mechanism_weights { + netuid, + mecid: _, + reveal_round, + .. + }) + | Some(Call::commit_crv3_mechanism_weights { + netuid, + mecid: _, + reveal_round, + .. + }) => { + if Self::check_weights_min_stake(who, *netuid) { + if *reveal_round < pallet_drand::LastStoredRound::::get() { + return Err(CustomTransactionError::InvalidRevealRound.into()); + } + Ok((Default::default(), (), origin)) + } else { + Err(CustomTransactionError::StakeAmountTooLow.into()) + } + } + Some(Call::increase_take { hotkey, take: _ }) + | Some(Call::decrease_take { hotkey, take: _ }) => { + Self::result_to_validity(Pallet::::do_take_checks(who, hotkey), 0u64) + .map(|validity| (validity, (), origin.clone())) + } + + Some(Call::swap_hotkey_v2 { + hotkey, + new_hotkey: _, + netuid: _, + keep_stake: _, + }) => { + if !Pallet::::coldkey_owns_hotkey(who, hotkey) { + return Err(CustomTransactionError::NonAssociatedColdKey.into()); + } + + let block: u64 = Pallet::::get_current_block_as_u64(); + + if Pallet::::exceeds_tx_rate_limit(Pallet::::get_last_tx_block(who), block) { + return Err(CustomTransactionError::RateLimitExceeded.into()); + } + + Ok((Default::default(), (), origin)) + } + Some(Call::serve_axon { netuid, version, @@ -248,6 +357,45 @@ where ) .map(|validity| (validity, (), origin.clone())) } + Some(Call::serve_axon_tls { + netuid, + version, + ip, + port, + ip_type, + protocol, + placeholder1, + placeholder2, + certificate: _, + }) => Self::result_to_validity( + Pallet::::validate_serve_axon( + who, + *netuid, + *version, + *ip, + *port, + *ip_type, + *protocol, + *placeholder1, + *placeholder2, + ), + 0u64, + ) + .map(|validity| (validity, (), origin.clone())), + Some(Call::serve_prometheus { + netuid, + version, + ip, + port, + ip_type, + }) => Self::result_to_validity( + Pallet::::validate_serve_prometheus( + who, *netuid, *version, *ip, *port, *ip_type, + ) + .map(|_| ()), + 0u64, + ) + .map(|validity| (validity, (), origin.clone())), Some(Call::register_network { .. }) => { if !TransactionType::RegisterNetwork.passes_rate_limit::(who) { return Err(CustomTransactionError::RateLimitExceeded.into()); diff --git a/pallets/subtensor/src/subnets/serving.rs b/pallets/subtensor/src/subnets/serving.rs index 29923f5ade..5416e3df5d 100644 --- a/pallets/subtensor/src/subnets/serving.rs +++ b/pallets/subtensor/src/subnets/serving.rs @@ -170,43 +170,11 @@ impl Pallet { // We check the callers (hotkey) signature. let hotkey_id = ensure_signed(origin)?; - // Check the ip signature validity. - ensure!(Self::is_valid_ip_type(ip_type), Error::::InvalidIpType); - ensure!( - Self::is_valid_ip_address(ip_type, ip, false), - Error::::InvalidIpAddress - ); - - // Ensure the hotkey is registered somewhere. - ensure!( - Self::is_hotkey_registered_on_any_network(&hotkey_id), - Error::::HotKeyNotRegisteredInNetwork - ); - - // We get the previous axon info assoicated with this ( netuid, uid ) - let mut prev_prometheus = Self::get_prometheus_info(netuid, &hotkey_id); - let current_block: u64 = Self::get_current_block_as_u64(); - ensure!( - Self::prometheus_passes_rate_limit(netuid, &prev_prometheus, current_block), - Error::::ServingRateLimitExceeded - ); - - // We insert the prometheus meta. - prev_prometheus.block = Self::get_current_block_as_u64(); - prev_prometheus.version = version; - prev_prometheus.ip = ip; - prev_prometheus.port = port; - prev_prometheus.ip_type = ip_type; - - // Validate prometheus data with delegate func - let prom_validated = Self::validate_prometheus_data(&prev_prometheus); - ensure!( - prom_validated.is_ok(), - prom_validated.err().unwrap_or(Error::::InvalidPort) - ); + let updated_prometheus = + Self::validate_serve_prometheus(&hotkey_id, netuid, version, ip, port, ip_type)?; // Insert new prometheus data - Prometheus::::insert(netuid, hotkey_id.clone(), prev_prometheus); + Prometheus::::insert(netuid, hotkey_id.clone(), updated_prometheus); // We deposit prometheus served event. log::debug!("PrometheusServed( hotkey:{:?} ) ", hotkey_id.clone()); @@ -369,4 +337,46 @@ impl Pallet { Ok(()) } + + /// Same checks as [`Self::do_serve_prometheus`] before storage writes (for transaction extension). + pub fn validate_serve_prometheus( + hotkey_id: &T::AccountId, + netuid: NetUid, + version: u32, + ip: u128, + port: u16, + ip_type: u8, + ) -> Result> { + ensure!(Self::is_valid_ip_type(ip_type), Error::::InvalidIpType); + ensure!( + Self::is_valid_ip_address(ip_type, ip, false), + Error::::InvalidIpAddress + ); + + ensure!( + Self::is_hotkey_registered_on_any_network(hotkey_id), + Error::::HotKeyNotRegisteredInNetwork + ); + + let mut prev_prometheus = Self::get_prometheus_info(netuid, hotkey_id); + let current_block: u64 = Self::get_current_block_as_u64(); + ensure!( + Self::prometheus_passes_rate_limit(netuid, &prev_prometheus, current_block), + Error::::ServingRateLimitExceeded + ); + + prev_prometheus.block = Self::get_current_block_as_u64(); + prev_prometheus.version = version; + prev_prometheus.ip = ip; + prev_prometheus.port = port; + prev_prometheus.ip_type = ip_type; + + let prom_validated = Self::validate_prometheus_data(&prev_prometheus); + ensure!( + prom_validated.is_ok(), + prom_validated.err().unwrap_or(Error::::InvalidPort) + ); + + Ok(prev_prometheus) + } } diff --git a/pallets/subtensor/src/tests/mod.rs b/pallets/subtensor/src/tests/mod.rs index 7e0c477c56..c099e8f1c1 100644 --- a/pallets/subtensor/src/tests/mod.rs +++ b/pallets/subtensor/src/tests/mod.rs @@ -28,6 +28,7 @@ mod subnet_emissions; mod swap_coldkey; mod swap_hotkey; mod swap_hotkey_with_subnet; +mod transaction_extension_pays_no; mod uids; mod voting_power; mod weights; diff --git a/pallets/subtensor/src/tests/transaction_extension_pays_no.rs b/pallets/subtensor/src/tests/transaction_extension_pays_no.rs new file mode 100644 index 0000000000..acb49b9bcb --- /dev/null +++ b/pallets/subtensor/src/tests/transaction_extension_pays_no.rs @@ -0,0 +1,694 @@ +//! Transaction extension coverage for extrinsics handled by [`crate::extensions::SubtensorTransactionExtension`]. + +#![allow(clippy::unwrap_used)] + +use super::mock::*; +use crate::extensions::SubtensorTransactionExtension; +use crate::*; +use codec::Compact; +use frame_support::dispatch::GetDispatchInfo; +use frame_support::{BoundedVec, assert_ok, traits::ConstU32}; +use frame_system::RawOrigin; +use pallet_drand::LastStoredRound; +use sp_core::H256; +use sp_core::U256; +use sp_runtime::traits::{DispatchInfoOf, TransactionExtension, TxBaseImplication}; +use sp_runtime::transaction_validity::{ + InvalidTransaction, TransactionSource, TransactionValidityError, +}; +use subtensor_runtime_common::{CustomTransactionError, MechId, NetUid, TaoBalance}; + +fn dispatch_info() -> sp_runtime::traits::DispatchInfoOf<::RuntimeCall> +{ + DispatchInfoOf::<::RuntimeCall>::default() +} + +fn validate_signed( + signer: U256, + call: &RuntimeCall, +) -> Result { + SubtensorTransactionExtension::::new() + .validate( + RawOrigin::Signed(signer).into(), + call, + &dispatch_info(), + 0, + (), + &TxBaseImplication(()), + TransactionSource::External, + ) + .map(|(v, _, _)| v) +} + +#[test] +fn extension_set_weights_rejects_stake_too_low() { + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + let hotkey = U256::from(1); + let coldkey = U256::from(2); + add_network_disable_commit_reveal(netuid, 1, 0); + setup_reserves( + netuid, + 1_000_000_000_000_u64.into(), + 1_000_000_000_000_u64.into(), + ); + SubtensorModule::append_neuron(netuid, &hotkey, 0); + crate::Owner::::insert(hotkey, coldkey); + SubtensorModule::set_stake_threshold(1_000_000_000_000u64); + + let call = RuntimeCall::SubtensorModule(SubtensorCall::set_weights { + netuid, + dests: vec![1], + weights: vec![1], + version_key: 0, + }); + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::StakeAmountTooLow.into()); + }); +} + +#[test] +fn extension_set_mechanism_weights_rejects_stake_too_low() { + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + let hotkey = U256::from(1); + let coldkey = U256::from(2); + add_network_disable_commit_reveal(netuid, 1, 0); + setup_reserves( + netuid, + 1_000_000_000_000_u64.into(), + 1_000_000_000_000_u64.into(), + ); + SubtensorModule::append_neuron(netuid, &hotkey, 0); + crate::Owner::::insert(hotkey, coldkey); + SubtensorModule::set_stake_threshold(1_000_000_000_000u64); + + let call = RuntimeCall::SubtensorModule(SubtensorCall::set_mechanism_weights { + netuid, + mecid: MechId::MAIN, + dests: vec![1], + weights: vec![1], + version_key: 0, + }); + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::StakeAmountTooLow.into()); + }); +} + +#[test] +fn extension_batch_set_weights_rejects_mismatched_lengths() { + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + let hotkey = U256::from(1); + let call = RuntimeCall::SubtensorModule(SubtensorCall::batch_set_weights { + netuids: vec![Compact(netuid)], + weights: vec![], + version_keys: vec![Compact(0_u64)], + }); + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::InputLengthsUnequal.into()); + }); +} + +#[test] +fn extension_batch_set_weights_rejects_stake_too_low() { + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + let hotkey = U256::from(1); + let coldkey = U256::from(2); + add_network_disable_commit_reveal(netuid, 1, 0); + setup_reserves( + netuid, + 1_000_000_000_000_u64.into(), + 1_000_000_000_000_u64.into(), + ); + SubtensorModule::append_neuron(netuid, &hotkey, 0); + crate::Owner::::insert(hotkey, coldkey); + SubtensorModule::set_stake_threshold(1_000_000_000_000u64); + + let call = RuntimeCall::SubtensorModule(SubtensorCall::batch_set_weights { + netuids: vec![Compact(netuid)], + weights: vec![vec![(Compact(0u16), Compact(1u16))]], + version_keys: vec![Compact(0u64)], + }); + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::StakeAmountTooLow.into()); + }); +} + +#[test] +fn extension_commit_weights_rejects_stake_too_low() { + 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(), + ); + SubtensorModule::append_neuron(netuid, &hotkey, 0); + crate::Owner::::insert(hotkey, coldkey); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + SubtensorModule::set_stake_threshold(1_000_000_000_000u64); + + let call = RuntimeCall::SubtensorModule(SubtensorCall::commit_weights { + netuid, + commit_hash: H256::zero(), + }); + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::StakeAmountTooLow.into()); + }); +} + +#[test] +fn extension_commit_mechanism_weights_rejects_stake_too_low() { + 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(), + ); + SubtensorModule::append_neuron(netuid, &hotkey, 0); + crate::Owner::::insert(hotkey, coldkey); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + SubtensorModule::set_stake_threshold(1_000_000_000_000u64); + + let call = RuntimeCall::SubtensorModule(SubtensorCall::commit_mechanism_weights { + netuid, + mecid: MechId::MAIN, + commit_hash: H256::zero(), + }); + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::StakeAmountTooLow.into()); + }); +} + +#[test] +fn extension_batch_commit_weights_rejects_mismatched_lengths() { + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + let hotkey = U256::from(1); + let call = RuntimeCall::SubtensorModule(SubtensorCall::batch_commit_weights { + netuids: vec![Compact(netuid)], + commit_hashes: vec![], + }); + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::InputLengthsUnequal.into()); + }); +} + +#[test] +fn extension_reveal_weights_rejects_stake_too_low() { + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + let hotkey = U256::from(1); + add_network(netuid, 1, 0); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + SubtensorModule::set_stake_threshold(1_000_000_000_000u64); + + let call = RuntimeCall::SubtensorModule(SubtensorCall::reveal_weights { + netuid, + uids: vec![0], + values: vec![1], + salt: vec![1], + version_key: 0, + }); + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::StakeAmountTooLow.into()); + }); +} + +#[test] +fn extension_reveal_weights_rejects_commit_not_found() { + 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(), + ); + SubtensorModule::append_neuron(netuid, &hotkey, 0); + crate::Owner::::insert(hotkey, coldkey); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + SubtensorModule::set_stake_threshold(0); + SubtensorModule::add_balance_to_coldkey_account(&hotkey, u64::MAX.into()); + assert_ok!(SubtensorModule::do_add_stake( + RuntimeOrigin::signed(hotkey), + hotkey, + netuid, + TaoBalance::from(500_000_000_000_u64) + )); + + let call = RuntimeCall::SubtensorModule(SubtensorCall::reveal_weights { + netuid, + uids: vec![0], + values: vec![1], + salt: vec![1], + version_key: 0, + }); + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::CommitNotFound.into()); + }); +} + +#[test] +fn extension_reveal_mechanism_weights_rejects_commit_not_found() { + 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(), + ); + SubtensorModule::append_neuron(netuid, &hotkey, 0); + crate::Owner::::insert(hotkey, coldkey); + SubtensorModule::set_commit_reveal_weights_enabled(netuid, true); + SubtensorModule::set_stake_threshold(0); + SubtensorModule::add_balance_to_coldkey_account(&hotkey, u64::MAX.into()); + assert_ok!(SubtensorModule::do_add_stake( + RuntimeOrigin::signed(hotkey), + hotkey, + netuid, + TaoBalance::from(500_000_000_000_u64) + )); + + let call = RuntimeCall::SubtensorModule(SubtensorCall::reveal_mechanism_weights { + netuid, + mecid: MechId::MAIN, + uids: vec![0], + values: vec![1], + salt: vec![1], + version_key: 0, + }); + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::CommitNotFound.into()); + }); +} + +#[test] +fn extension_batch_reveal_weights_rejects_mismatched_vector_lengths() { + 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); + SubtensorModule::append_neuron(netuid, &hotkey, 0); + crate::Owner::::insert(hotkey, coldkey); + SubtensorModule::set_stake_threshold(0); + + let call = RuntimeCall::SubtensorModule(SubtensorCall::batch_reveal_weights { + netuid, + uids_list: vec![vec![0]], + values_list: vec![], + salts_list: vec![vec![1]], + version_keys: vec![0], + }); + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::InputLengthsUnequal.into()); + }); +} + +#[test] +fn extension_commit_timelocked_weights_rejects_invalid_reveal_round() { + new_test_ext(0).execute_with(|| { + LastStoredRound::::put(1_000_u64); + let netuid = NetUid::from(1); + let hotkey = U256::from(1); + add_network(netuid, 1, 0); + SubtensorModule::set_stake_threshold(0); + + let commit = + BoundedVec::>::try_from(vec![0u8]).unwrap(); + let call = RuntimeCall::SubtensorModule(SubtensorCall::commit_timelocked_weights { + netuid, + commit, + reveal_round: 500, + commit_reveal_version: 0, + }); + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::InvalidRevealRound.into()); + }); +} + +#[test] +fn extension_commit_timelocked_mechanism_weights_rejects_invalid_reveal_round() { + new_test_ext(0).execute_with(|| { + LastStoredRound::::put(2_000_u64); + let netuid = NetUid::from(1); + let hotkey = U256::from(1); + add_network(netuid, 1, 0); + SubtensorModule::set_stake_threshold(0); + + let commit = + BoundedVec::>::try_from(vec![1u8]).unwrap(); + let call = + RuntimeCall::SubtensorModule(SubtensorCall::commit_timelocked_mechanism_weights { + netuid, + mecid: MechId::MAIN, + commit, + reveal_round: 100, + commit_reveal_version: 0, + }); + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::InvalidRevealRound.into()); + }); +} + +#[test] +fn extension_commit_crv3_mechanism_weights_rejects_invalid_reveal_round() { + new_test_ext(0).execute_with(|| { + LastStoredRound::::put(500u64); + let netuid = NetUid::from(1); + let hotkey = U256::from(1); + add_network(netuid, 1, 0); + SubtensorModule::set_stake_threshold(0); + + let commit = + BoundedVec::>::try_from(vec![2u8]).unwrap(); + let call = RuntimeCall::SubtensorModule(SubtensorCall::commit_crv3_mechanism_weights { + netuid, + mecid: MechId::MAIN, + commit, + reveal_round: 100, + }); + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::InvalidRevealRound.into()); + }); +} + +#[test] +fn extension_decrease_take_rejects_non_owner_coldkey() { + new_test_ext(0).execute_with(|| { + let owner_ck = U256::from(1); + let other_ck = U256::from(2); + let hotkey = U256::from(3); + crate::Owner::::insert(hotkey, owner_ck); + + let call = RuntimeCall::SubtensorModule(SubtensorCall::decrease_take { + hotkey, + take: MinDelegateTake::::get(), + }); + let err = validate_signed(other_ck, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::NonAssociatedColdKey.into()); + }); +} + +#[test] +fn extension_decrease_take_rejects_missing_hotkey_owner() { + new_test_ext(0).execute_with(|| { + let coldkey = U256::from(1); + let hotkey = U256::from(99); + let call = RuntimeCall::SubtensorModule(SubtensorCall::decrease_take { + hotkey, + take: MinDelegateTake::::get(), + }); + let err = validate_signed(coldkey, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::HotkeyAccountDoesntExist.into()); + }); +} + +#[test] +fn extension_increase_take_rejects_non_owner_coldkey() { + new_test_ext(0).execute_with(|| { + let owner_ck = U256::from(10); + let other_ck = U256::from(11); + let hotkey = U256::from(12); + crate::Owner::::insert(hotkey, owner_ck); + + let call = RuntimeCall::SubtensorModule(SubtensorCall::increase_take { hotkey, take: 100 }); + let err = validate_signed(other_ck, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::NonAssociatedColdKey.into()); + }); +} + +#[test] +fn extension_swap_hotkey_v2_rejects_non_owner_coldkey() { + new_test_ext(0).execute_with(|| { + let owner_ck = U256::from(20); + let other_ck = U256::from(21); + let old_hk = U256::from(22); + let new_hk = U256::from(23); + crate::Owner::::insert(old_hk, owner_ck); + + let call = RuntimeCall::SubtensorModule(SubtensorCall::swap_hotkey_v2 { + hotkey: old_hk, + new_hotkey: new_hk, + netuid: None, + keep_stake: false, + }); + let err = validate_signed(other_ck, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::NonAssociatedColdKey.into()); + }); +} + +#[test] +fn extension_swap_hotkey_v2_rejects_rate_limited() { + new_test_ext(0).execute_with(|| { + let coldkey = U256::from(30); + let old_hk = U256::from(31); + let new_hk = U256::from(32); + crate::Owner::::insert(old_hk, coldkey); + + SubtensorModule::set_tx_rate_limit(100); + SubtensorModule::set_last_tx_block(&coldkey, 1); + System::set_block_number(1u64.into()); + + let err = SubtensorTransactionExtension::::new() + .validate( + RawOrigin::Signed(coldkey).into(), + &RuntimeCall::SubtensorModule(SubtensorCall::swap_hotkey_v2 { + hotkey: old_hk, + new_hotkey: new_hk, + netuid: None, + keep_stake: false, + }), + &dispatch_info(), + 0, + (), + &TxBaseImplication(()), + TransactionSource::External, + ) + .unwrap_err(); + assert_eq!(err, CustomTransactionError::RateLimitExceeded.into()); + }); +} + +#[test] +fn extension_serve_axon_rejects_unregistered_hotkey() { + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + let hotkey = U256::from(40); + let ip = u128::from(u32::from_be_bytes([8, 8, 8, 8])); + let call = RuntimeCall::SubtensorModule(SubtensorCall::serve_axon { + netuid, + version: 1, + ip, + port: 1, + ip_type: 4, + protocol: 0, + placeholder1: 0, + placeholder2: 0, + }); + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!( + err, + CustomTransactionError::HotKeyNotRegisteredInNetwork.into() + ); + }); +} + +#[test] +fn extension_serve_axon_tls_rejects_unregistered_hotkey() { + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + let hotkey = U256::from(41); + let ip = u128::from(u32::from_be_bytes([8, 8, 8, 8])); + let call = RuntimeCall::SubtensorModule(SubtensorCall::serve_axon_tls { + netuid, + version: 1, + ip, + port: 1, + ip_type: 4, + protocol: 0, + placeholder1: 0, + placeholder2: 0, + certificate: vec![], + }); + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!( + err, + CustomTransactionError::HotKeyNotRegisteredInNetwork.into() + ); + }); +} + +#[test] +fn extension_serve_prometheus_rejects_unregistered_hotkey() { + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + let hotkey = U256::from(99); + let ip = u128::from(u32::from_be_bytes([8, 8, 8, 8])); + let call = RuntimeCall::SubtensorModule(SubtensorCall::serve_prometheus { + netuid, + version: 1, + ip, + port: 1, + ip_type: 4, + }); + let info = call.get_dispatch_info(); + assert_eq!(info.pays_fee, frame_support::dispatch::Pays::No); + + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!( + err, + CustomTransactionError::HotKeyNotRegisteredInNetwork.into() + ); + }); +} + +#[test] +fn extension_associate_evm_key_rejects_uid_not_found() { + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + add_network(netuid, 1, 0); + let hotkey = U256::from(50); + + let call = RuntimeCall::SubtensorModule(SubtensorCall::associate_evm_key { + netuid, + evm_key: sp_core::H160::zero(), + block_number: 0, + signature: sp_core::ecdsa::Signature::from_raw([0u8; 65]), + }); + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::UidNotFound.into()); + }); +} + +#[test] +fn extension_add_stake_burn_rejects_not_subnet_owner() { + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + let owner = U256::from(60); + let not_owner = U256::from(61); + let hotkey = U256::from(62); + add_network(netuid, 1, 0); + SubnetOwner::::insert(netuid, owner); + + let call = RuntimeCall::SubtensorModule(SubtensorCall::add_stake_burn { + hotkey, + netuid, + amount: TaoBalance::from(1u64), + limit: None, + }); + let err = SubtensorTransactionExtension::::new() + .validate( + RawOrigin::Signed(not_owner).into(), + &call, + &dispatch_info(), + 0, + (), + &TxBaseImplication(()), + TransactionSource::External, + ) + .unwrap_err(); + assert_eq!( + err, + TransactionValidityError::Invalid(InvalidTransaction::BadSigner) + ); + }); +} + +#[test] +fn extension_register_network_rejects_global_rate_limit() { + new_test_ext(0).execute_with(|| { + let limit = 50u64; + NetworkRateLimit::::put(limit); + System::set_block_number(200u64.into()); + SubtensorModule::set_network_last_lock_block(170); + + let coldkey = U256::from(70); + let hotkey = U256::from(71); + let call = RuntimeCall::SubtensorModule(SubtensorCall::register_network { hotkey }); + let err = validate_signed(coldkey, &call).unwrap_err(); + assert_eq!(err, CustomTransactionError::RateLimitExceeded.into()); + }); +} + +#[test] +fn extension_register_network_accepts_after_global_cooldown() { + new_test_ext(0).execute_with(|| { + let limit = 50u64; + NetworkRateLimit::::put(limit); + System::set_block_number(200u64.into()); + SubtensorModule::set_network_last_lock_block(150); + + let coldkey = U256::from(72); + let hotkey = U256::from(73); + let call = RuntimeCall::SubtensorModule(SubtensorCall::register_network { hotkey }); + assert!(validate_signed(coldkey, &call).is_ok()); + }); +} + +#[test] +fn extension_associate_evm_key_rejects_associate_rate_limit() { + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + let tempo: u16 = 2; + let modality: u16 = 2; + add_network(netuid, tempo, modality); + + let coldkey = U256::from(80); + let hotkey = U256::from(81); + SubtensorModule::create_account_if_non_existent(&coldkey, &hotkey); + register_ok_neuron(netuid, hotkey, coldkey, 0); + + let uid = SubtensorModule::get_uid_for_net_and_hotkey(netuid, &hotkey).unwrap(); + System::set_block_number(300u64.into()); + let now = SubtensorModule::get_current_block_as_u64(); + AssociatedEvmAddress::::insert(netuid, uid, (sp_core::H160::zero(), now)); + + let call = RuntimeCall::SubtensorModule(SubtensorCall::associate_evm_key { + netuid, + evm_key: sp_core::H160::zero(), + block_number: 0, + signature: sp_core::ecdsa::Signature::from_raw([0u8; 65]), + }); + let err = validate_signed(hotkey, &call).unwrap_err(); + assert_eq!( + err, + CustomTransactionError::EvmKeyAssociateRateLimitExceeded.into() + ); + }); +} + +#[test] +fn extension_add_stake_burn_boosts_priority_for_subnet_owner() { + new_test_ext(0).execute_with(|| { + let netuid = NetUid::from(1); + let owner = U256::from(90); + let hotkey = U256::from(91); + add_network(netuid, 1, 0); + SubnetOwner::::insert(netuid, owner); + + let call = RuntimeCall::SubtensorModule(SubtensorCall::add_stake_burn { + hotkey, + netuid, + amount: TaoBalance::from(1u64), + limit: None, + }); + let v = validate_signed(owner, &call).unwrap(); + assert_eq!(v.priority, 100); + }); +} diff --git a/pallets/subtensor/src/tests/weights.rs b/pallets/subtensor/src/tests/weights.rs index 5237cca131..f685b5e185 100644 --- a/pallets/subtensor/src/tests/weights.rs +++ b/pallets/subtensor/src/tests/weights.rs @@ -244,9 +244,9 @@ fn test_set_weights_validate() { version_key: 0, }); - // Create netuid - add_network(netuid, 1, 0); - mock::setup_reserves( + // Create netuid (commit-reveal off so `set_weights` matches extension / extrinsic) + add_network_disable_commit_reveal(netuid, 1, 0); + setup_reserves( netuid, 1_000_000_000_000_u64.into(), 1_000_000_000_000_u64.into(),