From 41cfff9f6d3b1fbd1fde0beb75995e5fc73eda19 Mon Sep 17 00:00:00 2001 From: Liam Date: Mon, 18 Nov 2024 15:21:00 +0700 Subject: [PATCH] crv3 implementation --- Cargo.lock | 4 + pallets/subtensor/Cargo.toml | 5 +- pallets/subtensor/src/coinbase/block_step.rs | 2 - .../subtensor/src/coinbase/run_coinbase.rs | 160 +++++++++++++++--- pallets/subtensor/src/lib.rs | 11 +- pallets/subtensor/src/subnets/weights.rs | 54 +++--- runtime/src/lib.rs | 5 +- 7 files changed, 176 insertions(+), 65 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0f643ec54..61a786107 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6441,6 +6441,8 @@ dependencies = [ name = "pallet-subtensor" version = "4.0.0-dev" dependencies = [ + "ark-bls12-381", + "ark-serialize", "frame-benchmarking", "frame-support", "frame-system", @@ -6474,6 +6476,8 @@ dependencies = [ "sp-version", "substrate-fixed", "subtensor-macros", + "tle", + "w3f-bls", ] [[package]] diff --git a/pallets/subtensor/Cargo.toml b/pallets/subtensor/Cargo.toml index 88ebe0a70..ec046a90d 100644 --- a/pallets/subtensor/Cargo.toml +++ b/pallets/subtensor/Cargo.toml @@ -46,7 +46,10 @@ pallet-drand = { path = "../drand", default-features = false } pallet-membership = { workspace = true } hex-literal = { workspace = true } num-traits = { version = "0.2.19", default-features = false, features = ["libm"] } - +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 } [dev-dependencies] pallet-balances = { workspace = true, features = ["std"] } diff --git a/pallets/subtensor/src/coinbase/block_step.rs b/pallets/subtensor/src/coinbase/block_step.rs index a8ecce9e2..22addb3ba 100644 --- a/pallets/subtensor/src/coinbase/block_step.rs +++ b/pallets/subtensor/src/coinbase/block_step.rs @@ -11,8 +11,6 @@ impl Pallet { Self::adjust_registration_terms_for_networks(); // --- 2. Run emission through network. Self::run_coinbase(); - // --- 3. Reveal commits - Self::reveal_commits(); // Return ok. Ok(()) } diff --git a/pallets/subtensor/src/coinbase/run_coinbase.rs b/pallets/subtensor/src/coinbase/run_coinbase.rs index 17f2fa241..fb1daf219 100644 --- a/pallets/subtensor/src/coinbase/run_coinbase.rs +++ b/pallets/subtensor/src/coinbase/run_coinbase.rs @@ -2,19 +2,19 @@ use super::*; use substrate_fixed::types::I64F64; use substrate_fixed::types::I96F32; -impl Pallet { - /// The `reveal_crv3_commits` function is used for revealing commitments whos drand round has - /// passed. - pub fn reveal_commits() { - // TODO: Get drand pallet round number - - // TODO: Get encrypted commits - - // TODO: Get oldest commit round - - // TODO: While oldest commit round < drand round, decrypt and store results in Weights - } +/// Contains all necesarry information to set weights. +/// +/// In the context of commit-reveal v3, this is the payload which should be +/// encrypted, compressed, serialized, and submitted to the `commit_crv3_weights` +/// extrinsic. +#[derive(Encode, Decode)] +pub struct WeightsPayload { + pub uids: Vec, + pub values: Vec, + pub version_key: u64, +} +impl Pallet { /// The `coinbase` function performs a four-part emission distribution process involving /// subnets, epochs, hotkeys, and nominators. // It is divided into several steps, each handling a specific part of the distribution: @@ -89,7 +89,16 @@ impl Pallet { for netuid in subnets.clone().iter() { // --- 4.1 Check to see if the subnet should run its epoch. if Self::should_run_epoch(*netuid, current_block) { - // --- 4.2 Drain the subnet emission. + // --- 4.2 Reveal weights from the n-2nd epoch. + if let Err(e) = Self::reveal_crv3_commits(*netuid) { + log::warn!( + "Failed to reveal commits for subnet {} due to error: {:?}", + *netuid, + e + ); + }; + + // --- 4.3 Drain the subnet emission. let mut subnet_emission: u64 = PendingEmission::::get(*netuid); PendingEmission::::insert(*netuid, 0); log::debug!( @@ -98,7 +107,7 @@ impl Pallet { subnet_emission ); - // --- 4.3 Set last step counter. + // --- 4.4 Set last step counter. Self::set_blocks_since_last_step(*netuid, 0); Self::set_last_mechanism_step_block(*netuid, current_block); @@ -107,30 +116,30 @@ impl Pallet { continue; } - // --- 4.4 Distribute owner take. + // --- 4.5 Distribute owner take. if SubnetOwner::::contains_key(netuid) { // Does the subnet have an owner? - // --- 4.4.1 Compute the subnet owner cut. + // --- 4.5.1 Compute the subnet owner cut. let owner_cut: I96F32 = I96F32::from_num(subnet_emission).saturating_mul( I96F32::from_num(Self::get_subnet_owner_cut()) .saturating_div(I96F32::from_num(u16::MAX)), ); - // --- 4.4.2 Remove the cut from the subnet emission + // --- 4.5.2 Remove the cut from the subnet emission subnet_emission = subnet_emission.saturating_sub(owner_cut.to_num::()); - // --- 4.4.3 Add the cut to the balance of the owner + // --- 4.5.3 Add the cut to the balance of the owner Self::add_balance_to_coldkey_account( &Self::get_subnet_owner(*netuid), owner_cut.to_num::(), ); - // --- 4.4.4 Increase total issuance on the chain. + // --- 4.5.4 Increase total issuance on the chain. Self::coinbase(owner_cut.to_num::()); } - // 4.3 Pass emission through epoch() --> hotkey emission. + // 4.6 Pass emission through epoch() --> hotkey emission. let hotkey_emission: Vec<(T::AccountId, u64, u64)> = Self::epoch(*netuid, subnet_emission); log::debug!( @@ -139,9 +148,9 @@ impl Pallet { hotkey_emission ); - // 4.4 Accumulate the tuples on hotkeys: + // 4.7 Accumulate the tuples on hotkeys: for (hotkey, mining_emission, validator_emission) in hotkey_emission { - // 4.5 Accumulate the emission on the hotkey and parent hotkeys. + // 4.8 Accumulate the emission on the hotkey and parent hotkeys. Self::accumulate_hotkey_emission( &hotkey, *netuid, @@ -191,6 +200,113 @@ impl Pallet { } } + /// The `reveal_crv3_commits` function is run at the very beginning of epoch `n`, + /// revealing commitments from epoch `n - 2`. + /// n - 2. + pub fn reveal_crv3_commits(netuid: u16) -> dispatch::DispatchResult { + use ark_serialize::CanonicalDeserialize; + use frame_support::traits::OriginTrait; + use tle::curves::drand::TinyBLS381; + use tle::tlock::TLECiphertext; + use w3f_bls::EngineBLS; + + let cur_block = Self::get_current_block_as_u64(); + let cur_epoch = Self::get_epoch_index(netuid, cur_block); + + // No commits to reveal until at least epoch 2. + if cur_epoch < 2 { + 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); + + let mut entries = CRV3WeightCommits::::take(netuid, reveal_epoch); + + // Keep popping item off the end of the queue until we sucessfully reveal a commit. + while let Some((who, serialized_compresssed_commit, round_number)) = entries.pop_front() { + let reader = &mut &serialized_compresssed_commit[..]; + let commit = match TLECiphertext::::deserialize_compressed(reader) { + Ok(c) => c, + Err(e) => { + log::warn!( + "Failed to reveal commit for subnet {} submitted by {:?} due to deserialization error: {:?}", + netuid, + who, + e + ); + continue; + } + }; + + // Try to get the round number from pallet_drand. + let pulse = match pallet_drand::Pulses::::get(round_number) { + Some(p) => p, + None => { + // Round number used was not found on the chain. Skip this commit. + log::warn!( + "Failed to reveal commit for subnet {} submitted by {:?} due to missing round number {} at time of reveal.", + netuid, + who, + round_number + ); + continue; + } + }; + let sig_reader = &mut &pulse.signature[..]; + let sig = + match ::SignatureGroup::deserialize_compressed(sig_reader) + { + Ok(s) => s, + Err(e) => { + log::error!( + "Failed to reveal commit for subnet {} submitted by {:?} due to deserialization error: {:?}", + netuid, + who, + e + ); + continue; + } + }; + + let decrypted_bytes: Vec = match commit.tld(sig) { + Ok(d) => d.message, + Err(e) => { + log::warn!( + "Failed to reveal commit for subnet {} submitted by {:?} due to decryption error: {:?}", + netuid, + who, + e + ); + continue; + } + }; + + // Decrypt the bytes into Vec<(u64, u64)> + let mut reader = &decrypted_bytes[..]; + let payload: WeightsPayload = match Decode::decode(&mut reader) { + Ok(w) => w, + Err(e) => { + log::warn!("Failed to reveal commit for subnet {} submitted by {:?} due to deserialization error: {:?}", netuid, who, e); + continue; + } + }; + + // TODO: Set Weights + Self::do_set_weights( + T::RuntimeOrigin::signed(who), + netuid, + payload.values, + payload.uids, + payload.version_key, + )?; + return Ok(()); + } + + return Ok(()); + } + /// Accumulates the mining and validator emissions on a hotkey and distributes the validator emission among its parents. /// /// This function is responsible for accumulating the mining and validator emissions associated with a hotkey onto a hotkey. diff --git a/pallets/subtensor/src/lib.rs b/pallets/subtensor/src/lib.rs index 0bab74697..934b72b44 100644 --- a/pallets/subtensor/src/lib.rs +++ b/pallets/subtensor/src/lib.rs @@ -72,6 +72,7 @@ pub mod pallet { BoundedVec, }; use frame_system::pallet_prelude::*; + use pallet_drand::types::RoundNumber; use sp_core::{ConstU32, H256}; use sp_runtime::traits::{Dispatchable, TrailingZeroInput}; use sp_std::collections::vec_deque::VecDeque; @@ -1292,19 +1293,19 @@ pub mod pallet { OptionQuery, >; #[pallet::storage] - /// --- MAP (netuid, who) --> VecDeque<(commit, commit_block, reveal_round)> | Stores a queue of v3 commits for an account on a given netuid. + /// --- MAP (netuid, commit_epoch) --> VecDeque<(who, serialized_compressed_commit, reveal_round)> | Stores a queue of v3 commits for an account on a given netuid. pub type CRV3WeightCommits = StorageDoubleMap< _, Twox64Concat, u16, Twox64Concat, - T::AccountId, + u64, VecDeque<( + T::AccountId, BoundedVec>, - u64, - u64, + RoundNumber, )>, - OptionQuery, + ValueQuery, >; #[pallet::storage] /// --- Map (netuid) --> Number of epochs allowed for commit reveal periods diff --git a/pallets/subtensor/src/subnets/weights.rs b/pallets/subtensor/src/subnets/weights.rs index 9018aad6c..e59ed5a61 100644 --- a/pallets/subtensor/src/subnets/weights.rs +++ b/pallets/subtensor/src/subnets/weights.rs @@ -118,7 +118,17 @@ impl Pallet { /// - The u16 network identifier. /// /// * `commit` (`Vec`): - /// - The encrypted commit + /// - The encrypted compressed commit. + /// The steps for this are: + /// 1. Instantiate [`WeightsPayload`] + /// 2. Serialize it using the `parity_scale_codec::Encode` trait + /// 3. Encrypt it following the steps (here)[https://github.com/ideal-lab5/tle/blob/f8e6019f0fb02c380ebfa6b30efb61786dede07b/timelock/src/tlock.rs#L283-L336] + /// to produce a [`TLECiphertext`] type. + /// 4. Serialize and compress using the `ark-serialize` `CanonicalSerialize` trait. + /// + /// * reveal_round (`u64`): + /// - The drand reveal round which will be avaliable during epoch `n+1` from the current + /// epoch. /// /// # Raises: /// * `CommitRevealDisabled`: @@ -171,51 +181,29 @@ impl Pallet { Error::::CommittingWeightsTooFast ); - // 5. Calculate the reveal blocks based on network tempo and reveal period. - // TODO: Double check this is not done on-chain for CRV3 - // let (first_reveal_block, last_reveal_block) = Self::get_reveal_blocks(netuid, commit_block); - - // 6. Retrieve or initialize the VecDeque of commits for the hotkey. - CRV3WeightCommits::::try_mutate(netuid, &who, |maybe_commits| -> DispatchResult { - let mut commits: VecDeque<( - BoundedVec>, - u64, - u64, - )> = maybe_commits.take().unwrap_or_default(); - - // TODO: Check it is OK to remove this code block, and do the "expiring"/"reveal" step - // entirely in block_step - // 7. Remove any expired commits from the front of the queue. - // while let Some((_, commit_block_existing, _, _)) = commits.front() { - // if Self::is_commit_expired(netuid, *commit_block_existing) { - // commits.pop_front(); - // } else { - // break; - // } - // } - - // 8. Verify that the number of unrevealed commits is within the allowed limit. + // 5. Retrieve or initialize the VecDeque of commits for the hotkey. + let cur_block = Self::get_current_block_as_u64(); + let cur_epoch = Self::get_epoch_index(netuid, cur_block); + CRV3WeightCommits::::try_mutate(netuid, &cur_epoch, |commits| -> DispatchResult { + // 6. Verify that the number of unrevealed commits is within the allowed limit. ensure!(commits.len() < 10, Error::::TooManyUnrevealedCommits); - // 9. Append the new commit with calculated reveal blocks. + // 7. Append the new commit with calculated reveal blocks. // Hash the commit before it is moved, for the event let commit_hash = BlakeTwo256::hash(&commit); - commits.push_back((commit, commit_block, reveal_round)); + commits.push_back((who.clone(), commit, reveal_round)); - // 10. Store the updated commits queue back to storage. - *maybe_commits = Some(commits); - - // 11. Emit the WeightsCommitted event + // 8. Emit the WeightsCommitted event Self::deposit_event(Event::CRV3WeightsCommitted( who.clone(), netuid, commit_hash, )); - // 12. Update the last commit block for the hotkey's UID. + // 9. Update the last commit block for the hotkey's UID. Self::set_last_crv3_update_for_uid(netuid, neuron_uid, commit_block); - // 13. Return success. + // 10. Return success. Ok(()) }) } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 960925efb..5e9858771 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -2067,13 +2067,14 @@ impl_runtime_apis! { #[cfg(test)] mod tests { - use ark_serialize::CanonicalDeserialize; + use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use rand_chacha::rand_core::SeedableRng; use rand_chacha::ChaCha20Rng; use sha2::Digest; + use tle::curves::drand::TinyBLS381; use tle::ibe::fullident::Identity; use tle::tlock::tle; - use w3f_bls::{EngineBLS, Message, TinyBLS381}; + use w3f_bls::{EngineBLS, Message}; #[test] pub fn tlock_encrypt_decrypt_drand_quicknet_works() {