diff --git a/Cargo.lock b/Cargo.lock index 60a28eb32c..aba126a7d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6014,6 +6014,7 @@ dependencies = [ "frame-system", "log", "pallet-balances", + "pallet-drand", "pallet-scheduler", "pallet-subtensor", "parity-scale-codec", @@ -6463,12 +6464,14 @@ dependencies = [ "parity-scale-codec", "parity-util-mem", "rand", + "rand_chacha", "scale-info", "serde", "serde-tuple-vec-map", "serde_bytes", "serde_json", "serde_with", + "sha2 0.10.8", "sp-core", "sp-io", "sp-runtime", diff --git a/pallets/admin-utils/Cargo.toml b/pallets/admin-utils/Cargo.toml index c67c009143..c5cf200d1e 100644 --- a/pallets/admin-utils/Cargo.toml +++ b/pallets/admin-utils/Cargo.toml @@ -29,6 +29,7 @@ log = { workspace = true } pallet-subtensor = { version = "4.0.0-dev", default-features = false, path = "../subtensor" } sp-weights = { workspace = true } substrate-fixed = { workspace = true } +pallet-drand = { workspace = true, default-features = false } [dev-dependencies] diff --git a/pallets/admin-utils/src/lib.rs b/pallets/admin-utils/src/lib.rs index 6810ac259c..5f4ec9d270 100644 --- a/pallets/admin-utils/src/lib.rs +++ b/pallets/admin-utils/src/lib.rs @@ -1209,6 +1209,34 @@ pub mod pallet { ); Ok(()) } + + /// The extrinsic sets the commit-reveal-3 weights set rate limit for a subnet. + /// It is only callable by the root account or subnet owner. + /// The extrinsic will call the Subtensor pallet to set the weights set rate limit. + #[pallet::call_index(58)] + #[pallet::weight(::WeightInfo::sudo_set_weights_set_rate_limit())] + pub fn sudo_set_commit_reveal_3_weights_set_rate_limit( + origin: OriginFor, + netuid: u16, + weights_set_rate_limit: u64, + ) -> DispatchResult { + pallet_subtensor::Pallet::::ensure_subnet_owner_or_root(origin, netuid)?; + + ensure!( + pallet_subtensor::Pallet::::if_subnet_exist(netuid), + Error::::SubnetDoesNotExist + ); + pallet_subtensor::Pallet::::set_v3_weights_set_rate_limit( + netuid, + weights_set_rate_limit, + ); + log::debug!( + "WeightsSetRateLimitSet( netuid: {:?} weights_set_rate_limit: {:?} ) ", + netuid, + weights_set_rate_limit + ); + Ok(()) + } } } diff --git a/pallets/admin-utils/tests/mock.rs b/pallets/admin-utils/tests/mock.rs index dca08ab72e..86444933c3 100644 --- a/pallets/admin-utils/tests/mock.rs +++ b/pallets/admin-utils/tests/mock.rs @@ -11,8 +11,9 @@ use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_core::U256; use sp_core::{ConstU64, H256}; use sp_runtime::{ + testing::TestXt, traits::{BlakeTwo256, ConstU32, IdentityLookup}, - BuildStorage, Perbill, + BuildStorage, KeyTypeId, Perbill, }; use sp_std::cmp::Ordering; use sp_weights::Weight; @@ -21,13 +22,13 @@ type Block = frame_system::mocking::MockBlock; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( - pub enum Test - { + pub enum Test { System: frame_system = 1, Balances: pallet_balances = 2, AdminUtils: pallet_admin_utils = 3, SubtensorModule: pallet_subtensor::{Pallet, Call, Storage, Event, Error} = 4, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event} = 5, + Drand: pallet_drand::{Pallet, Call, Storage, Event} = 6, } ); @@ -63,6 +64,10 @@ pub type Balance = u64; #[allow(dead_code)] pub type BlockNumber = u64; +pub type TestAuthId = test_crypto::TestAuthId; +pub type Index = u64; +pub type UncheckedExtrinsic = TestXt; + parameter_types! { pub const InitialMinAllowedWeights: u16 = 0; pub const InitialEmissionValue: u16 = 0; @@ -272,6 +277,74 @@ impl pallet_scheduler::Config for Test { type Preimages = (); } +impl pallet_drand::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_drand::weights::SubstrateWeight; + type AuthorityId = TestAuthId; + type Verifier = pallet_drand::verifier::QuicknetVerifier; + type UnsignedPriority = ConstU64<{ 1 << 20 }>; + type HttpFetchTimeout = ConstU64<1_000>; +} + +impl frame_system::offchain::SigningTypes for Test { + type Public = test_crypto::Public; + type Signature = test_crypto::Signature; +} + +pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"test"); + +mod test_crypto { + use super::KEY_TYPE; + use sp_core::sr25519::{Public as Sr25519Public, Signature as Sr25519Signature}; + use sp_core::U256; + use sp_runtime::{ + app_crypto::{app_crypto, sr25519}, + traits::IdentifyAccount, + }; + + app_crypto!(sr25519, KEY_TYPE); + + pub struct TestAuthId; + + impl frame_system::offchain::AppCrypto for TestAuthId { + type RuntimeAppPublic = Public; + type GenericSignature = Sr25519Signature; + type GenericPublic = Sr25519Public; + } + + impl IdentifyAccount for Public { + type AccountId = U256; + + fn into_account(self) -> U256 { + let mut bytes = [0u8; 32]; + bytes.copy_from_slice(self.as_ref()); + U256::from_big_endian(&bytes) + } + } +} + +impl frame_system::offchain::CreateSignedTransaction> for Test { + fn create_transaction>( + call: RuntimeCall, + _public: Self::Public, + _account: Self::AccountId, + nonce: Index, + ) -> Option<( + RuntimeCall, + ::SignaturePayload, + )> { + Some((call, (nonce, ()))) + } +} + +impl frame_system::offchain::SendTransactionTypes for Test +where + RuntimeCall: From, +{ + type Extrinsic = UncheckedExtrinsic; + type OverarchingCall = RuntimeCall; +} + // Build genesis storage according to the mock runtime. pub fn new_test_ext() -> sp_io::TestExternalities { sp_tracing::try_init_simple(); diff --git a/pallets/subtensor/Cargo.toml b/pallets/subtensor/Cargo.toml index cf78d263cf..49b1f3fb6c 100644 --- a/pallets/subtensor/Cargo.toml +++ b/pallets/subtensor/Cargo.toml @@ -50,6 +50,8 @@ tle = { workspace = true, default-features = false } ark-bls12-381 = { workspace = true, default-features = false } ark-serialize = { workspace = true, default-features = false } w3f-bls = { workspace = true, default-features = false } +sha2 = { workspace = true } +rand_chacha = { workspace = true } [dev-dependencies] pallet-balances = { workspace = true, features = ["std"] } diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 6ee200bff6..64dff95837 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -220,9 +220,8 @@ impl Pallet { return Ok(()); } - // This fn is run at the VERY BEGINNING of epoch `n`, therefore the weights revealed - // must have been committed during epoch `n-2`. - let reveal_epoch = cur_epoch.saturating_sub(2); + // Weights revealed must have been committed during epoch `cur_epoch - reveal_period`. + let reveal_epoch = cur_epoch.saturating_sub(Self::get_reveal_period(netuid)); let mut entries = CRV3WeightCommits::::take(netuid, reveal_epoch); diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 8bad536dfa..4c3ca64aaa 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -1174,10 +1174,6 @@ pub mod pallet { StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; #[pallet::storage] /// --- DMAP ( netuid ) --> last_update - pub type LastCRV3Update = - StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; - #[pallet::storage] - /// --- DMAP ( netuid ) --> last_update pub type LastUpdate = StorageMap<_, Identity, u16, Vec, ValueQuery, EmptyU64Vec>; #[pallet::storage] diff --git a/pallets/subtensor/src/subnets/weights.rs b/pallets/subtensor/src/subnets/weights.rs index 85818f8223..7e7f0a2652 100644 --- a/pallets/subtensor/src/subnets/weights.rs +++ b/pallets/subtensor/src/subnets/weights.rs @@ -201,7 +201,7 @@ impl Pallet { )); // 9. Update the last commit block for the hotkey's UID. - Self::set_last_crv3_update_for_uid(netuid, neuron_uid, commit_block); + Self::set_last_update_for_uid(netuid, neuron_uid, commit_block); // 10. Return success. Ok(()) @@ -727,7 +727,7 @@ impl Pallet { pub fn check_crv3_rate_limit(netuid: u16, neuron_uid: u16, current_block: u64) -> bool { if Self::is_uid_exist_on_network(netuid, neuron_uid) { // --- 1. Ensure that the diff between current and last_set weights is greater than limit. - let last_set_weights: u64 = Self::get_last_crv3_update_for_uid(netuid, neuron_uid); + let last_set_weights: u64 = Self::get_last_update_for_uid(netuid, neuron_uid); if last_set_weights == 0 { return true; } // (Storage default) Never set weights. diff --git a/pallets/subtensor/src/utils/misc.rs b/pallets/subtensor/src/utils/misc.rs index 8296161bf4..538d1bc95d 100644 --- a/pallets/subtensor/src/utils/misc.rs +++ b/pallets/subtensor/src/utils/misc.rs @@ -90,9 +90,6 @@ impl Pallet { pub fn get_dividends(netuid: u16) -> Vec { Dividends::::get(netuid) } - pub fn get_last_crv3_update(netuid: u16) -> Vec { - LastCRV3Update::::get(netuid) - } pub fn get_last_update(netuid: u16) -> Vec { LastUpdate::::get(netuid) } @@ -109,16 +106,8 @@ impl Pallet { // ================================== // ==== YumaConsensus UID params ==== // ================================== - pub fn set_last_crv3_update_for_uid(netuid: u16, uid: u16, last_update: u64) { - let mut updated_last_update_vec = Self::get_last_crv3_update(netuid); - let Some(updated_last_update) = updated_last_update_vec.get_mut(uid as usize) else { - return; - }; - *updated_last_update = last_update; - LastCRV3Update::::insert(netuid, updated_last_update_vec); - } pub fn set_last_update_for_uid(netuid: u16, uid: u16, last_update: u64) { - let mut updated_last_update_vec = Self::get_last_crv3_update(netuid); + let mut updated_last_update_vec = Self::get_last_update(netuid); let Some(updated_last_update) = updated_last_update_vec.get_mut(uid as usize) else { return; }; @@ -208,10 +197,6 @@ impl Pallet { let vec = Dividends::::get(netuid); vec.get(uid as usize).copied().unwrap_or(0) } - pub fn get_last_crv3_update_for_uid(netuid: u16, uid: u16) -> u64 { - let vec = LastCRV3Update::::get(netuid); - vec.get(uid as usize).copied().unwrap_or(0) - } pub fn get_last_update_for_uid(netuid: u16, uid: u16) -> u64 { let vec = LastUpdate::::get(netuid); vec.get(uid as usize).copied().unwrap_or(0) @@ -412,6 +397,9 @@ impl Pallet { Self::deposit_event(Event::WeightsVersionKeySet(netuid, weights_version_key)); } + pub fn set_v3_weights_set_rate_limit(netuid: u16, weights_set_rate_limit: u64) { + V3WeightsSetRateLimit::::insert(netuid, weights_set_rate_limit); + } pub fn get_v3_weights_set_rate_limit(netuid: u16) -> u64 { V3WeightsSetRateLimit::::get(netuid) } diff --git a/pallets/subtensor/tests/mock.rs b/pallets/subtensor/tests/mock.rs index 7a2967e8cf..2de16b8175 100644 --- a/pallets/subtensor/tests/mock.rs +++ b/pallets/subtensor/tests/mock.rs @@ -10,7 +10,7 @@ use frame_support::{ use frame_system as system; use frame_system::{limits, EnsureNever, EnsureRoot, RawOrigin}; use pallet_collective::MemberCount; -use sp_core::{Get, H256, U256}; +use sp_core::{offchain::KeyTypeId, ConstU64, Get, H256, U256}; use sp_runtime::Perbill; use sp_runtime::{ traits::{BlakeTwo256, IdentityLookup}, @@ -34,6 +34,7 @@ frame_support::construct_runtime!( Utility: pallet_utility::{Pallet, Call, Storage, Event} = 8, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event} = 9, Preimage: pallet_preimage::{Pallet, Call, Storage, Event} = 10, + Drand: pallet_drand::{Pallet, Call, Storage, Event} = 11, } ); @@ -49,10 +50,9 @@ pub type BalanceCall = pallet_balances::Call; #[allow(dead_code)] pub type TestRuntimeCall = frame_system::Call; -parameter_types! { - pub const BlockHashCount: u64 = 250; - pub const SS58Prefix: u8 = 42; -} +pub type Index = u64; + +pub const KEY_TYPE: KeyTypeId = KeyTypeId(*b"test"); #[allow(dead_code)] pub type AccountId = U256; @@ -112,6 +112,11 @@ impl system::Config for Test { type Block = Block; } +parameter_types! { + pub const BlockHashCount: u64 = 250; + pub const SS58Prefix: u8 = 42; +} + parameter_types! { pub const InitialMinAllowedWeights: u16 = 0; pub const InitialEmissionValue: u16 = 0; @@ -452,6 +457,78 @@ impl pallet_preimage::Config for Test { type Consideration = (); } +mod test_crypto { + use super::KEY_TYPE; + use sp_core::{ + sr25519::{Public as Sr25519Public, Signature as Sr25519Signature}, + U256, + }; + use sp_runtime::{ + app_crypto::{app_crypto, sr25519}, + traits::IdentifyAccount, + }; + + app_crypto!(sr25519, KEY_TYPE); + + pub struct TestAuthId; + + impl frame_system::offchain::AppCrypto for TestAuthId { + type RuntimeAppPublic = Public; + type GenericSignature = Sr25519Signature; + type GenericPublic = Sr25519Public; + } + + impl IdentifyAccount for Public { + type AccountId = U256; + + fn into_account(self) -> U256 { + let mut bytes = [0u8; 32]; + bytes.copy_from_slice(self.as_ref()); + U256::from_big_endian(&bytes) + } + } +} + +pub type TestAuthId = test_crypto::TestAuthId; + +impl frame_system::offchain::SendTransactionTypes for Test +where + RuntimeCall: From, +{ + type Extrinsic = UncheckedExtrinsic; + type OverarchingCall = RuntimeCall; +} + +impl pallet_drand::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_drand::weights::SubstrateWeight; + type AuthorityId = TestAuthId; + type Verifier = pallet_drand::verifier::QuicknetVerifier; + type UnsignedPriority = ConstU64<{ 1 << 20 }>; + type HttpFetchTimeout = ConstU64<1_000>; +} + +impl frame_system::offchain::SigningTypes for Test { + type Public = test_crypto::Public; + type Signature = test_crypto::Signature; +} + +pub type UncheckedExtrinsic = sp_runtime::testing::TestXt; + +impl frame_system::offchain::CreateSignedTransaction> for Test { + fn create_transaction>( + call: RuntimeCall, + _public: Self::Public, + _account: Self::AccountId, + nonce: Index, + ) -> Option<( + RuntimeCall, + ::SignaturePayload, + )> { + Some((call, (nonce, ()))) + } +} + #[allow(dead_code)] // Build genesis storage according to the mock runtime. pub fn new_test_ext(block_number: BlockNumber) -> sp_io::TestExternalities { @@ -531,7 +608,7 @@ pub(crate) fn step_epochs(count: u16, netuid: u16) { } } -/// Increments current block by `1`, running all hooks associated with doing so, and asserts +/// Increments current block by 1, running all hooks associated with doing so, and asserts /// that the block number was in fact incremented. /// /// Returns the new block number. diff --git a/pallets/subtensor/tests/weights.rs b/pallets/subtensor/tests/weights.rs index 3d3452fba4..a4f4ef6133 100644 --- a/pallets/subtensor/tests/weights.rs +++ b/pallets/subtensor/tests/weights.rs @@ -16,6 +16,11 @@ use sp_runtime::{ }; use sp_std::collections::vec_deque::VecDeque; use substrate_fixed::types::I32F32; +use ark_serialize::CanonicalDeserialize; +use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng}; +use sha2::Digest; +use tle::{tlock::{tld, tle}, ibe::fullident::Identity, curves::drand::TinyBLS381, stream_ciphers::AESGCMStreamCipherProvider}; +use w3f_bls::EngineBLS; /*************************** pub fn set_weights() tests @@ -4190,25 +4195,49 @@ fn test_commit_weights_rate_limit() { } #[test] -fn test_commit_weights_dispatch_info_ok() { - new_test_ext(0).execute_with(|| { - let dests = vec![1, 1]; - let weights = vec![1, 1]; - let netuid: u16 = 1; - let salt: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8]; - let version_key: u64 = 0; - let hotkey: U256 = U256::from(1); - - let commit_hash: H256 = - BlakeTwo256::hash_of(&(hotkey, netuid, dests, weights, salt, version_key)); - - let call = RuntimeCall::SubtensorModule(SubtensorCall::commit_weights { - netuid, - commit_hash, - }); - let dispatch_info = call.get_dispatch_info(); - - assert_eq!(dispatch_info.class, DispatchClass::Normal); - assert_eq!(dispatch_info.pays_fee, Pays::No); - }); +pub fn tlock_encrypt_decrypt_drand_quicknet_works() { + // using a pulse from drand's QuickNet + // https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/1000 + // the beacon public key + let pk_bytes = +b"83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a" +; // a round number that we know a signature for + let round: u64 = 1000; + // the signature produced in that round + let signature = +b"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39" +; + + // Convert hex string to bytes + let pub_key_bytes = hex::decode(pk_bytes).expect("Decoding failed"); + // Deserialize to G1Affine + let pub_key = + ::PublicKeyGroup::deserialize_compressed(&*pub_key_bytes) + .unwrap(); + + // then we tlock a message for the pubkey + let plaintext = b"this is a test".as_slice(); + let esk = [2; 32]; + + let sig_bytes = hex::decode(signature).expect("The signature should be well formatted"); + let sig = + ::SignatureGroup::deserialize_compressed(&*sig_bytes).unwrap(); + + let message = { + let mut hasher = sha2::Sha256::new(); + hasher.update(round.to_be_bytes()); + hasher.finalize().to_vec() + }; + + let identity = Identity::new(b"", vec![message]); + + let rng = ChaCha20Rng::seed_from_u64(0); + let ct = tle::( + pub_key, esk, plaintext, identity, rng, + ) + .unwrap(); + + // then we can decrypt the ciphertext using the signature + let result = tld::(ct, sig).unwrap(); + assert!(result == plaintext); } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index a383c34af6..0ff816a3be 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -2067,64 +2067,6 @@ impl_runtime_apis! { #[cfg(test)] mod tests { - use ark_serialize::CanonicalDeserialize; - use rand_chacha::rand_core::SeedableRng; - use rand_chacha::ChaCha20Rng; - use sha2::Digest; - use tle::ibe::fullident::Identity; - use tle::tlock::tld; - use tle::tlock::tle; - use tle::{curves::drand::TinyBLS381, stream_ciphers::AESGCMStreamCipherProvider}; - use w3f_bls::EngineBLS; - - #[test] - pub fn tlock_encrypt_decrypt_drand_quicknet_works() { - // using a pulse from drand's QuickNet - // https://api.drand.sh/52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971/public/1000 - // the beacon public key - let pk_bytes = - b"83cf0f2896adee7eb8b5f01fcad3912212c437e0073e911fb90022d3e760183c8c4b450b6a0a6c3ac6a5776a2d1064510d1fec758c921cc22b0e17e63aaf4bcb5ed66304de9cf809bd274ca73bab4af5a6e9c76a4bc09e76eae8991ef5ece45a" - ; // a round number that we know a signature for - let round: u64 = 1000; - // the signature produced in that round - let signature = - b"b44679b9a59af2ec876b1a6b1ad52ea9b1615fc3982b19576350f93447cb1125e342b73a8dd2bacbe47e4b6b63ed5e39" - ; - - // Convert hex string to bytes - let pub_key_bytes = hex::decode(pk_bytes).expect("Decoding failed"); - // Deserialize to G1Affine - let pub_key = - ::PublicKeyGroup::deserialize_compressed(&*pub_key_bytes) - .unwrap(); - - // then we tlock a message for the pubkey - let plaintext = b"this is a test".as_slice(); - let esk = [2; 32]; - - let sig_bytes = hex::decode(signature).expect("The signature should be well formatted"); - let sig = - ::SignatureGroup::deserialize_compressed(&*sig_bytes).unwrap(); - - let message = { - let mut hasher = sha2::Sha256::new(); - hasher.update(round.to_be_bytes()); - hasher.finalize().to_vec() - }; - - let identity = Identity::new(b"", vec![message]); - - let rng = ChaCha20Rng::seed_from_u64(0); - let ct = tle::( - pub_key, esk, plaintext, identity, rng, - ) - .unwrap(); - - // then we can decrypt the ciphertext using the signature - let result = tld::(ct, sig).unwrap(); - assert!(result == plaintext); - } - #[test] fn check_whitelist() { use crate::*;