Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate Delegate Identities On Chain #605

Draft
wants to merge 11 commits into
base: main
Choose a base branch
from
Draft
4 changes: 4 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

394 changes: 394 additions & 0 deletions docs/delegate-info.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions pallets/registry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ scale-info = { workspace = true, features = ["derive"] }
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
log = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
sp-core = { workspace = true }
sp-runtime = { workspace = true }
sp-std = { workspace = true }
enumflags2 = { workspace = true }
Expand Down
28 changes: 28 additions & 0 deletions pallets/registry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
mod tests;

mod benchmarking;
pub mod migration;
pub mod types;
pub mod utils;
pub mod weights;

pub use pallet::*;
Expand Down Expand Up @@ -87,6 +89,12 @@ pub mod pallet {
TooManyFieldsInIdentityInfo,
/// Account doesn't have a registered identity
NotRegistered,
/// The old hotkey does not exist in the identity map.
OldHotkeyNotFound,
/// The new hotkey is already in use.
NewHotkeyInUse,
/// Attempting to set an Identity for a hotkey that already has one.
IdentityAlreadyExists,
}

/// Enum to hold reasons for putting funds on hold.
Expand All @@ -107,6 +115,26 @@ pub mod pallet {
OptionQuery,
>;

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_runtime_upgrade() -> frame_support::weights::Weight {
// --- Migrate storage
use crate::migration;
let mut weight = frame_support::weights::Weight::from_parts(0, 0);

weight = weight
// Initializes storage version (to 1)
.saturating_add(migration::migrate_set_hotkey_identities::<T>());

log::info!(
"Runtime upgrade migration in registry pallet, total weight = ({})",
weight
);

weight
}
}

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Register an identity for an account. This will overwrite any existing identity.
Expand Down
151 changes: 151 additions & 0 deletions pallets/registry/src/migration.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
use codec::Decode;
use scale_info::prelude::{
string::{String, ToString},
vec::Vec,
};
use serde::Deserialize;
use sp_core::{crypto::Ss58Codec, ConstU32};
use sp_runtime::{AccountId32, BoundedVec};
use sp_std::vec;

use super::*;
use frame_support::{
traits::{Get, GetStorageVersion, StorageVersion},
weights::Weight,
};
use log;

#[derive(Deserialize, Debug)]
struct RegistrationRecordJSON {
address: String,
name: String,
url: String,
description: String,
}

fn string_to_bounded_vec(input: &String) -> Result<BoundedVec<u8, ConstU32<64>>, &'static str> {
let vec_u8: Vec<u8> = input.clone().into_bytes();

// Check if the length is within bounds
if vec_u8.len() > 64 {
return Err("Input string is too long");
}

// Convert to BoundedVec
BoundedVec::<u8, ConstU32<64>>::try_from(vec_u8).map_err(|_| "Failed to convert to BoundedVec")
}

pub fn migrate_set_hotkey_identities<T: Config>() -> Weight {
let new_storage_version = 1;
let migration_name = "set hotkey identities";
let mut weight = T::DbWeight::get().reads_writes(1, 1);

let title = "description".to_string();

let onchain_version = Pallet::<T>::on_chain_storage_version();
log::info!("Current on-chain storage version: {:?}", onchain_version);
if onchain_version < new_storage_version {
log::info!("Starting migration: {}.", migration_name);

// Include the JSON file with delegate info
let data = include_str!("../../../docs/delegate-info.json");

// Deserialize the JSON data into a HashMap
if let Ok(delegates) = serde_json::from_str::<Vec<RegistrationRecordJSON>>(data) {
log::info!("{} delegate records loaded", delegates.len());

// Iterate through the delegates
for delegate in delegates.iter() {
// Convert fields to bounded vecs
let name_result = string_to_bounded_vec(&delegate.name);
let desc_result = string_to_bounded_vec(&truncate_string(&delegate.description));
let url_result = string_to_bounded_vec(&delegate.url);

// Convert string address into AccountID
let maybe_account_id_32 = AccountId32::from_ss58check(&delegate.address);
let account_id = if maybe_account_id_32.is_ok() {
let account_id_32 = maybe_account_id_32.unwrap();
if let Ok(acc_id) = T::AccountId::decode(&mut account_id_32.as_ref()) {
Some(acc_id)
} else {
None
}
} else {
None
};

if name_result.is_ok()
&& desc_result.is_ok()
&& url_result.is_ok()
&& account_id.is_some()
{
let desc_title = Data::Raw(string_to_bounded_vec(&title).unwrap());
let desc_data = Data::Raw(desc_result.unwrap());
let desc_item = BoundedVec::try_from(vec![(desc_title, desc_data)]).unwrap();

let info: IdentityInfo<T::MaxAdditionalFields> = IdentityInfo {
display: Data::Raw(name_result.unwrap()),
additional: desc_item,
legal: Data::None,
web: Data::Raw(url_result.unwrap()),
riot: Data::None,
email: Data::None,
pgp_fingerprint: None,
image: Data::None,
twitter: Data::None,
};

// Insert delegate hotkeys info
let reg: Registration<BalanceOf<T>, T::MaxAdditionalFields> = Registration {
deposit: Zero::zero(),
info,
};

IdentityOf::<T>::insert(account_id.unwrap(), reg);
weight.saturating_accrue(T::DbWeight::get().reads_writes(0, 1));
} else {
log::info!(
"Migration {} couldn't be completed, bad JSON item for: {}",
migration_name,
delegate.address
);
if !name_result.is_ok() {
log::info!("Name is bad");
}
if !desc_result.is_ok() {
log::info!("Description is bad");
}
if !url_result.is_ok() {
log::info!("URL is bad");
}
if !account_id.is_some() {
log::info!("Account ID is bad");
}
}
}
} else {
log::info!(
"Migration {} couldn't be completed, bad JSON file: {}",
migration_name,
data
);
return weight;
}

StorageVersion::new(new_storage_version).put::<Pallet<T>>();
} else {
log::info!("Migration already done: {}", migration_name);
}

log::info!("Final weight: {:?}", weight);
weight
}

fn truncate_string(s: &str) -> String {
let max_len: usize = 64;
if s.len() > max_len {
s.chars().take(max_len).collect()
} else {
s.to_string()
}
}
104 changes: 104 additions & 0 deletions pallets/registry/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use super::*;
use sp_runtime::DispatchResult;
use sp_std::vec::Vec;

impl<T: Config> Pallet<T> {
/// Retrieves the identity information of a given delegate account.
///
/// # Parameters
/// - `account`: A reference to the account ID of the delegate.
///
/// # Returns
/// - `Option<IdentityInfo<T::MaxAdditionalFields>>`: An `Option` containing the identity information of the delegate
/// if it exists, otherwise `None`.
pub fn get_identity_of_delegate(
account: &T::AccountId,
) -> Option<IdentityInfo<T::MaxAdditionalFields>> {
if let Some(value) = IdentityOf::<T>::get(account) {
return Some(value.info);
}

None
}

/// Retrieves the identity information of all delegates.
///
/// # Returns
/// - `Option<Vec<IdentityInfo<T::MaxAdditionalFields>>>`: An `Option` containing a vector of identity information
/// of all delegates if any exist, otherwise `None`.
pub fn get_delegate_identitities() -> Option<Vec<IdentityInfo<T::MaxAdditionalFields>>> {
let mut identities = Vec::<IdentityInfo<T::MaxAdditionalFields>>::new();
for id in IdentityOf::<T>::iter_keys() {
let identity_info = Self::get_identity_of_delegate(&id);

match identity_info {
Some(identity) => {
identities.push(identity);
}
None => continue,
}
}

if identities.len() > 0 {
return Some(identities);
}

None
}

/// Sets the identity information for a given delegate account with provided values.
///
/// # Parameters
/// - `account`: A reference to the account ID of the delegate.
/// - `info`: The identity information to set for the delegate.
///
/// # Returns
/// - `DispatchResult`: Returns `Ok(())` if the identity is set successfully, otherwise returns
/// a `DispatchError`.
///
/// # Errors
/// - `IdentityAlreadyExists`: Returned if the delegate already has an identity set.
pub fn set_identity_for_delegate(
account: &T::AccountId,
info: IdentityInfo<T::MaxAdditionalFields>,
) -> DispatchResult {
if IdentityOf::<T>::contains_key(account) {
return Err(<Error<T>>::IdentityAlreadyExists.into());
}

let reg: Registration<BalanceOf<T>, T::MaxAdditionalFields> = Registration {
deposit: Zero::zero(),
info,
};

IdentityOf::<T>::insert(account, reg);
Ok(()) // Identity set successfully
}

/// Swaps the hotkey of a delegate identity from an old account ID to a new account ID.
///
/// # Parameters
/// - `old_hotkey`: A reference to the current account ID (old hotkey) of the delegate identity.
/// - `new_hotkey`: A reference to the new account ID (new hotkey) to be assigned to the delegate identity.
///
/// # Returns
/// - `Result<(), SwapError>`: Returns `Ok(())` if the swap is successful. Returns `Err(SwapError)` otherwise.
pub fn swap_delegate_identity_hotkey(
old_hotkey: &T::AccountId,
new_hotkey: &T::AccountId,
) -> DispatchResult {
// Check if the old hotkey exists in the identity map.
if let Some(identity) = IdentityOf::<T>::take(old_hotkey) {
// Check if the new hotkey is already in use.
if IdentityOf::<T>::contains_key(new_hotkey) {
// Reinsert the old hotkey back into the identity map to maintain consistency.
IdentityOf::<T>::insert(old_hotkey, identity);
return Err(Error::<T>::NewHotkeyInUse.into()); // New hotkey is already in use.
}
IdentityOf::<T>::insert(new_hotkey, identity);
return Ok(());
}

Err(Error::<T>::OldHotkeyNotFound.into()) // Old hotkey does not exist in Identities.
}
}
1 change: 1 addition & 0 deletions pallets/subtensor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pallet-transaction-payment = { workspace = true }
pallet-utility = { workspace = true }
ndarray = { workspace = true }
hex = { workspace = true }
pallet-registry= { default-features = false, path = "../registry" }

# Used for sudo decentralization
pallet-collective = { version = "4.0.0-dev", default-features = false, path = "../collective" }
Expand Down
31 changes: 31 additions & 0 deletions pallets/subtensor/rpc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ pub trait SubtensorCustomApi<BlockHash> {
delegatee_account_vec: Vec<u8>,
at: Option<BlockHash>,
) -> RpcResult<Vec<u8>>;
#[method(name = "delegateInfo_getIdentityOfDelegate")]
fn get_identity_of_delegate(
&self,
delegate_account_vec: Vec<u8>,
at: Option<BlockHash>,
) -> RpcResult<Vec<u8>>;
#[method(name = "delegateInfo_getDelegateIdentities")]
fn get_delegate_identities(&self, at: Option<BlockHash>) -> RpcResult<Vec<u8>>;

#[method(name = "neuronInfo_getNeuronsLite")]
fn get_neurons_lite(&self, netuid: u16, at: Option<BlockHash>) -> RpcResult<Vec<u8>>;
Expand Down Expand Up @@ -135,6 +143,29 @@ where
})
}

fn get_identity_of_delegate(
&self,
delegatee_account_vec: Vec<u8>,
at: Option<<Block as BlockT>::Hash>,
) -> RpcResult<Vec<u8>> {
let api = self.client.runtime_api();
let at = at.unwrap_or_else(|| self.client.info().best_hash);

api.get_identity_of_delegate(at, delegatee_account_vec)
.map_err(|e| {
Error::RuntimeError(format!("Unable to get delegates info: {:?}", e)).into()
})
}

fn get_delegate_identities(&self, at: Option<<Block as BlockT>::Hash>) -> RpcResult<Vec<u8>> {
let api = self.client.runtime_api();
let at = at.unwrap_or_else(|| self.client.info().best_hash);

api.get_delegate_identitities(at).map_err(|e| {
Error::RuntimeError(format!("Unable to get delegates info: {:?}", e)).into()
})
}

fn get_neurons_lite(
&self,
netuid: u16,
Expand Down
2 changes: 2 additions & 0 deletions pallets/subtensor/runtime-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ sp_api::decl_runtime_apis! {
fn get_delegates() -> Vec<u8>;
fn get_delegate( delegate_account_vec: Vec<u8> ) -> Vec<u8>;
fn get_delegated( delegatee_account_vec: Vec<u8> ) -> Vec<u8>;
fn get_delegate_identitities() -> Vec<u8>;
fn get_identity_of_delegate( delegatee_account_vec: Vec<u8> ) -> Vec<u8>;
}

pub trait NeuronInfoRuntimeApi {
Expand Down
Loading