From 8f28726c78baa6a73c38cd6c684b40d4b5d4900e Mon Sep 17 00:00:00 2001 From: 1xstj <106580853+1xstj@users.noreply.github.com> Date: Wed, 22 Nov 2023 13:08:07 +0000 Subject: [PATCH] feat: add roles metadata for validators (#309) * feat: add roles metadata for validators * merge fixes * clippy fixes * clippy fixes --- pallets/jobs/src/lib.rs | 20 ++++-------- pallets/jobs/src/mock.rs | 14 ++++++--- pallets/roles/src/impls.rs | 28 ++++++++++++++--- pallets/roles/src/lib.rs | 24 +++++++++++--- pallets/roles/src/mock.rs | 30 +++++++++++++++++- pallets/roles/src/tests.rs | 57 ++++++++++++++++++++-------------- primitives/src/traits/jobs.rs | 23 +++++++++++++- primitives/src/traits/roles.rs | 6 ++-- primitives/src/types/roles.rs | 41 ++++++++++++++---------- 9 files changed, 171 insertions(+), 72 deletions(-) diff --git a/pallets/jobs/src/lib.rs b/pallets/jobs/src/lib.rs index 5bf4a75a0..d9571fe43 100644 --- a/pallets/jobs/src/lib.rs +++ b/pallets/jobs/src/lib.rs @@ -32,7 +32,7 @@ use sp_std::{prelude::*, vec::Vec}; use tangle_primitives::{ jobs::{JobId, JobInfo, JobKey, PhaseOneResult, ValidatorOffence}, traits::{ - jobs::{JobResultVerifier, JobToFee}, + jobs::{JobToFee, MPCHandler}, roles::RolesHandler, }, }; @@ -71,11 +71,7 @@ pub mod module { type RolesHandler: RolesHandler; /// The job result verifying mechanism - type JobResultVerifier: JobResultVerifier< - Self::AccountId, - BlockNumberFor, - BalanceOf, - >; + type MPCHandler: MPCHandler, BalanceOf>; /// The origin which may set filter. type ForceOrigin: EnsureOrigin; @@ -210,7 +206,7 @@ pub mod module { for participant in participants { ensure!( - T::RolesHandler::is_validator(participant.clone(), job_key.clone().into()), + T::RolesHandler::is_validator(participant.clone(), job_key.clone()), Error::::InvalidValidator ); @@ -238,7 +234,7 @@ pub mod module { // Ensure the phase one participants are still validators for participant in result.participants { ensure!( - T::RolesHandler::is_validator(participant.clone(), job_key.clone().into()), + T::RolesHandler::is_validator(participant.clone(), job_key.clone()), Error::::InvalidValidator ); @@ -339,7 +335,7 @@ pub mod module { }; // Validate the result - T::JobResultVerifier::verify(&job_info, phase1_result.clone(), result.clone())?; + T::MPCHandler::verify(&job_info, phase1_result.clone(), result.clone())?; // If phase 1, store in known result if job_info.job_type.is_phase_one() { @@ -483,11 +479,7 @@ pub mod module { ensure!(participants.contains(&validator), Error::::JobNotFound); // Validate the result - T::JobResultVerifier::verify_validator_report( - validator.clone(), - offence.clone(), - signatures, - )?; + T::MPCHandler::verify_validator_report(validator.clone(), offence.clone(), signatures)?; // Slash the validator T::RolesHandler::slash_validator(validator.clone(), offence)?; diff --git a/pallets/jobs/src/mock.rs b/pallets/jobs/src/mock.rs index 358e1e9c5..64e201e8d 100644 --- a/pallets/jobs/src/mock.rs +++ b/pallets/jobs/src/mock.rs @@ -28,7 +28,7 @@ pub type AccountId = u128; pub type Balance = u128; pub type BlockNumber = u64; -use tangle_primitives::{jobs::*, roles::RoleType}; +use tangle_primitives::jobs::*; impl frame_system::Config for Runtime { type RuntimeOrigin = RuntimeOrigin; @@ -128,7 +128,7 @@ impl JobToFee for MockJobToFeeHandler { pub struct MockRolesHandler; impl RolesHandler for MockRolesHandler { - fn is_validator(address: AccountId, _role_type: RoleType) -> bool { + fn is_validator(address: AccountId, _role_type: JobKey) -> bool { let validators = [1, 2, 3, 4, 5]; validators.contains(&address) } @@ -138,9 +138,9 @@ impl RolesHandler for MockRolesHandler { } } -pub struct MockJobResultVerifier; +pub struct MockMPCHandler; -impl JobResultVerifier for MockJobResultVerifier { +impl MPCHandler for MockMPCHandler { fn verify( job: &JobInfo, phase_one_data: Option>, @@ -161,6 +161,10 @@ impl JobResultVerifier for MockJobResultVerifie ) -> DispatchResult { Ok(()) } + + fn validate_authority_key(_validator: AccountId, _authority_key: Vec) -> DispatchResult { + Ok(()) + } } parameter_types! { @@ -173,7 +177,7 @@ impl Config for Runtime { type Currency = Balances; type JobToFee = MockJobToFeeHandler; type RolesHandler = MockRolesHandler; - type JobResultVerifier = MockJobResultVerifier; + type MPCHandler = MockMPCHandler; type PalletId = JobsPalletId; type WeightInfo = (); } diff --git a/pallets/roles/src/impls.rs b/pallets/roles/src/impls.rs index cb6d23b2f..dff5eb2f1 100644 --- a/pallets/roles/src/impls.rs +++ b/pallets/roles/src/impls.rs @@ -15,8 +15,9 @@ // along with Tangle. If not, see . use super::*; -use sp_runtime::{DispatchResult, Percent, Saturating}; -use tangle_primitives::{roles::RoleType, traits::roles::RolesHandler}; +use frame_support::pallet_prelude::DispatchResult; +use sp_runtime::{Percent, Saturating}; +use tangle_primitives::{jobs::JobKey, traits::roles::RolesHandler}; /// Implements RolesHandler for the pallet. impl RolesHandler for Pallet { @@ -24,12 +25,29 @@ impl RolesHandler for Pallet { /// /// # Parameters /// - `address`: The account ID of the validator. - /// - `role`: The key representing the type of job. + /// - `job`: The key representing the type of job. /// /// # Returns /// Returns `true` if the validator is permitted to work with this job type, otherwise `false`. - fn is_validator(address: T::AccountId, role: RoleType) -> bool { - Self::has_role(address, role) + fn is_validator(address: T::AccountId, job_key: JobKey) -> bool { + let assigned_roles = AccountRolesMapping::::get(address); + + let mut found_role = false; + + for assigned_role in assigned_roles { + match job_key { + JobKey::DKG | JobKey::DKGSignature => + if assigned_role.is_tss() { + found_role = true; + }, + JobKey::ZkSaasPhaseOne | JobKey::ZkSaasPhaseTwo => + if assigned_role.is_zksaas() { + found_role = true; + }, + } + } + + return found_role } /// Slash validator stake for the reported offence. The function should be a best effort diff --git a/pallets/roles/src/lib.rs b/pallets/roles/src/lib.rs index d33d35c60..77b758780 100644 --- a/pallets/roles/src/lib.rs +++ b/pallets/roles/src/lib.rs @@ -94,6 +94,7 @@ pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + use tangle_primitives::traits::jobs::MPCHandler; #[pallet::pallet] #[pallet::without_storage_info] @@ -108,6 +109,9 @@ pub mod pallet { #[pallet::constant] type MaxRolesPerAccount: Get; + /// The config that verifies MPC related functions + type MPCHandler: MPCHandler, BalanceOf>; + type WeightInfo: WeightInfo; } @@ -203,7 +207,16 @@ pub mod pallet { let role = record.role; let re_stake_amount = record.re_staked; // Check if role is already assigned. - ensure!(!Self::has_role(stash_account.clone(), role), Error::::HasRoleAssigned); + ensure!( + !Self::has_role(stash_account.clone(), role.clone()), + Error::::HasRoleAssigned + ); + + // validate the metadata + T::MPCHandler::validate_authority_key( + stash_account.clone(), + role.clone().get_authority_key(), + )?; // Re-staking amount of record should meet min re-staking amount requirement. let min_re_staking_bond = MinReStakingBond::::get(); @@ -225,7 +238,7 @@ pub mod pallet { // Now that records are validated we can add them and update ledger for record in records { - Self::add_role(stash_account.clone(), record.role)?; + Self::add_role(stash_account.clone(), record.role.clone())?; Self::deposit_event(Event::::RoleAssigned { account: stash_account.clone(), role: record.role, @@ -258,14 +271,17 @@ pub mod pallet { ); // check if role is assigned. - ensure!(Self::has_role(stash_account.clone(), role), Error::::NoRoleAssigned); + ensure!( + Self::has_role(stash_account.clone(), role.clone()), + Error::::NoRoleAssigned + ); // TODO: Call jobs manager to remove the services. // On successful removal of services, remove the role from the mapping. // Issue link for reference : https://github.com/webb-tools/tangle/issues/292 // Remove role from the mapping. - Self::remove_role(stash_account.clone(), role)?; + Self::remove_role(stash_account.clone(), role.clone())?; // Remove stash account related info. Self::kill_stash(&stash_account); diff --git a/pallets/roles/src/mock.rs b/pallets/roles/src/mock.rs index 8ac61a502..1cf3de7fb 100644 --- a/pallets/roles/src/mock.rs +++ b/pallets/roles/src/mock.rs @@ -27,10 +27,13 @@ use sp_core::H256; use sp_runtime::{ testing::{Header, UintAuthorityId}, traits::IdentityLookup, - BuildStorage, Perbill, + BuildStorage, DispatchResult, Perbill, }; +use tangle_primitives::{jobs::*, traits::jobs::MPCHandler}; + pub type AccountId = u64; pub type Balance = u128; +pub type BlockNumber = u64; impl frame_system::Config for Runtime { type RuntimeOrigin = RuntimeOrigin; @@ -74,6 +77,30 @@ impl pallet_balances::Config for Runtime { type MaxFreezes = (); } +pub struct MockMPCHandler; + +impl MPCHandler for MockMPCHandler { + fn verify( + _job: &JobInfo, + _phase_one_data: Option>, + _result: Vec, + ) -> DispatchResult { + Ok(()) + } + + fn verify_validator_report( + _validator: AccountId, + _offence: ValidatorOffence, + _report: Vec, + ) -> DispatchResult { + Ok(()) + } + + fn validate_authority_key(_validator: AccountId, _authority_key: Vec) -> DispatchResult { + Ok(()) + } +} + impl pallet_timestamp::Config for Runtime { type Moment = u64; type OnTimestampSet = (); @@ -203,6 +230,7 @@ impl pallet_staking::Config for Runtime { impl Config for Runtime { type RuntimeEvent = RuntimeEvent; type MaxRolesPerAccount = ConstU32<2>; + type MPCHandler = MockMPCHandler; type WeightInfo = (); } diff --git a/pallets/roles/src/tests.rs b/pallets/roles/src/tests.rs index efdde5155..511715642 100644 --- a/pallets/roles/src/tests.rs +++ b/pallets/roles/src/tests.rs @@ -24,24 +24,28 @@ fn test_assign_roles() { // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens // Roles user is interested in re-staking. - let role_records = vec![RoleStakingRecord { role: RoleType::Tss, re_staked: 5000 }]; + let role_records = + vec![RoleStakingRecord { role: RoleType::Tss(Default::default()), re_staked: 5000 }]; assert_ok!(Roles::assign_roles(RuntimeOrigin::signed(1), role_records)); assert_events(vec![RuntimeEvent::Roles(crate::Event::RoleAssigned { account: 1, - role: RoleType::Tss, + role: RoleType::Tss(Default::default()), })]); // Lets verify role assigned to account. - assert_eq!(Roles::has_role(1, RoleType::Tss), true); + assert_eq!(Roles::has_role(1, RoleType::Tss(Default::default())), true); // Verify ledger mapping assert_eq!( Roles::ledger(1), Some(RoleStakingLedger { stash: 1, total: 5000, - roles: vec![RoleStakingRecord { role: RoleType::Tss, re_staked: 5000 }] + roles: vec![RoleStakingRecord { + role: RoleType::Tss(Default::default()), + re_staked: 5000 + }] }) ); }); @@ -55,17 +59,17 @@ fn test_assign_multiple_roles() { // Roles user is interested in re-staking. let role_records = vec![ - RoleStakingRecord { role: RoleType::Tss, re_staked: 2500 }, - RoleStakingRecord { role: RoleType::ZkSaas, re_staked: 2500 }, + RoleStakingRecord { role: RoleType::Tss(Default::default()), re_staked: 2500 }, + RoleStakingRecord { role: RoleType::ZkSaas(Default::default()), re_staked: 2500 }, ]; assert_ok!(Roles::assign_roles(RuntimeOrigin::signed(1), role_records)); // Lets verify role assigned to account. - assert_eq!(Roles::has_role(1, RoleType::Tss), true); + assert_eq!(Roles::has_role(1, RoleType::Tss(Default::default())), true); // Lets verify role assigned to account. - assert_eq!(Roles::has_role(1, RoleType::ZkSaas), true); + assert_eq!(Roles::has_role(1, RoleType::ZkSaas(Default::default())), true); assert_eq!( Roles::ledger(1), @@ -73,8 +77,11 @@ fn test_assign_multiple_roles() { stash: 1, total: 5000, roles: vec![ - RoleStakingRecord { role: RoleType::Tss, re_staked: 2500 }, - RoleStakingRecord { role: RoleType::ZkSaas, re_staked: 2500 }, + RoleStakingRecord { role: RoleType::Tss(Default::default()), re_staked: 2500 }, + RoleStakingRecord { + role: RoleType::ZkSaas(Default::default()), + re_staked: 2500 + }, ] }) ); @@ -90,8 +97,8 @@ fn test_assign_roles_should_fail_if_total_re_stake_value_exceeds_max_re_stake_va // Roles user is interested in re-staking. let role_records = vec![ - RoleStakingRecord { role: RoleType::Tss, re_staked: 5000 }, - RoleStakingRecord { role: RoleType::ZkSaas, re_staked: 5000 }, + RoleStakingRecord { role: RoleType::Tss(Default::default()), re_staked: 5000 }, + RoleStakingRecord { role: RoleType::ZkSaas(Default::default()), re_staked: 5000 }, ]; // Since max re_stake limit is 5000 it should fail with `ExceedsMaxReStakeValue` error. assert_err!( @@ -107,20 +114,21 @@ fn test_clear_role() { // Initially account if funded with 10000 tokens and we are trying to bond 5000 tokens // Roles user is interested in re-staking. - let role_records = vec![RoleStakingRecord { role: RoleType::Tss, re_staked: 5000 }]; + let role_records = + vec![RoleStakingRecord { role: RoleType::Tss(Default::default()), re_staked: 5000 }]; assert_ok!(Roles::assign_roles(RuntimeOrigin::signed(1), role_records)); // Now lets clear the role - assert_ok!(Roles::clear_role(RuntimeOrigin::signed(1), RoleType::Tss)); + assert_ok!(Roles::clear_role(RuntimeOrigin::signed(1), RoleType::Tss(Default::default()))); assert_events(vec![RuntimeEvent::Roles(crate::Event::RoleRemoved { account: 1, - role: RoleType::Tss, + role: RoleType::Tss(Default::default()), })]); // Role should be removed from account role mappings. - assert_eq!(Roles::has_role(1, RoleType::Tss), false); + assert_eq!(Roles::has_role(1, RoleType::Tss(Default::default())), false); // Ledger should be removed from ledger mappings. assert_eq!(Roles::ledger(1), None); @@ -133,7 +141,8 @@ fn test_assign_roles_should_fail_if_not_validator() { // we will use account 5 which is not a validator // Roles user is interested in re-staking. - let role_records = vec![RoleStakingRecord { role: RoleType::Tss, re_staked: 5000 }]; + let role_records = + vec![RoleStakingRecord { role: RoleType::Tss(Default::default()), re_staked: 5000 }]; assert_err!( Roles::assign_roles(RuntimeOrigin::signed(5), role_records), @@ -149,18 +158,19 @@ fn test_unbound_funds_should_work() { // for providing TSS services. // Roles user is interested in re-staking. - let role_records = vec![RoleStakingRecord { role: RoleType::Tss, re_staked: 5000 }]; + let role_records = + vec![RoleStakingRecord { role: RoleType::Tss(Default::default()), re_staked: 5000 }]; assert_ok!(Roles::assign_roles(RuntimeOrigin::signed(1), role_records)); // Lets verify role is assigned to account. - assert_eq!(Roles::has_role(1, RoleType::Tss), true); + assert_eq!(Roles::has_role(1, RoleType::Tss(Default::default())), true); // Lets clear the role. - assert_ok!(Roles::clear_role(RuntimeOrigin::signed(1), RoleType::Tss)); + assert_ok!(Roles::clear_role(RuntimeOrigin::signed(1), RoleType::Tss(Default::default()))); // Role should be removed from account role mappings. - assert_eq!(Roles::has_role(1, RoleType::Tss), false); + assert_eq!(Roles::has_role(1, RoleType::Tss(Default::default())), false); // unbound funds. assert_ok!(Roles::unbound_funds(RuntimeOrigin::signed(1), 5000)); @@ -185,12 +195,13 @@ fn test_unbound_funds_should_fail_if_role_assigned() { // for providing TSS services. // Roles user is interested in re-staking. - let role_records = vec![RoleStakingRecord { role: RoleType::Tss, re_staked: 5000 }]; + let role_records = + vec![RoleStakingRecord { role: RoleType::Tss(Default::default()), re_staked: 5000 }]; assert_ok!(Roles::assign_roles(RuntimeOrigin::signed(1), role_records)); // Lets verify role is assigned to account. - assert_eq!(Roles::has_role(1, RoleType::Tss), true); + assert_eq!(Roles::has_role(1, RoleType::Tss(Default::default())), true); // Lets try to unbound funds. assert_err!( diff --git a/primitives/src/traits/jobs.rs b/primitives/src/traits/jobs.rs index a7a335a03..208409bd9 100644 --- a/primitives/src/traits/jobs.rs +++ b/primitives/src/traits/jobs.rs @@ -38,7 +38,7 @@ pub trait JobToFee { } /// A trait that describes the job result verification. -pub trait JobResultVerifier { +pub trait MPCHandler { /// Verifies the result of a job. /// /// # Parameters @@ -57,11 +57,32 @@ pub trait JobResultVerifier { result: Vec, ) -> DispatchResult; + // Verify a validator report + /// + /// This function is responsible for verifying a report against a specific validator's + /// offence and taking appropriate actions based on the report. + /// + /// # Arguments + /// + /// - `validator`: The account ID of the validator being reported. + /// - `offence`: Details of the offence reported against the validator. + /// - `report`: The report data provided by the reporting entity. fn verify_validator_report( validator: AccountId, offence: ValidatorOffence, report: Vec, ) -> DispatchResult; + + /// Validate the authority key associated with a specific validator. + /// + /// This function is responsible for validating the authority key associated with a given + /// validator. + /// + /// # Arguments + /// + /// - `validator`: The account ID of the validator whose authority key is to be validated. + /// - `authority_key`: The authority key to be validated. + fn validate_authority_key(validator: AccountId, authority_key: Vec) -> DispatchResult; } /// A trait that handles various aspects of jobs for a validator. diff --git a/primitives/src/traits/roles.rs b/primitives/src/traits/roles.rs index 05823416e..77f5dc041 100644 --- a/primitives/src/traits/roles.rs +++ b/primitives/src/traits/roles.rs @@ -14,7 +14,7 @@ // You should have received a copy of the GNU General Public License // along with Tangle. If not, see . -use crate::{jobs::ValidatorOffence, roles::RoleType}; +use crate::jobs::{JobKey, ValidatorOffence}; use sp_runtime::DispatchResult; /// A trait that handles roles associated with job types. @@ -24,12 +24,12 @@ pub trait RolesHandler { /// # Parameters /// /// - `address`: The account ID of the validator. - /// - `role_type`: The type of role + /// - `job_key`: The type of job /// /// # Returns /// /// Returns `true` if the validator is permitted to work with this job type, otherwise `false`. - fn is_validator(address: AccountId, role_type: RoleType) -> bool; + fn is_validator(address: AccountId, job_key: JobKey) -> bool; /// Slash validator stake for the reported offence. The function should be a best effort /// slashing, slash upto max possible by the offence type. diff --git a/primitives/src/types/roles.rs b/primitives/src/types/roles.rs index e69212299..f0fbc53d2 100644 --- a/primitives/src/types/roles.rs +++ b/primitives/src/types/roles.rs @@ -13,40 +13,49 @@ // // You should have received a copy of the GNU General Public License // along with Tangle. If not, see . - -use crate::jobs::JobKey; -use frame_support::pallet_prelude::*; +use frame_support::{dispatch::Vec, pallet_prelude::*}; /// Role type to be used in the system. -#[derive(Encode, Decode, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)] +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, TypeInfo)] pub enum RoleType { - Tss, - ZkSaas, + Tss(TssRoleMetadata), + ZkSaas(ZkSaasRoleMetadata), } impl RoleType { /// Checks if the role type is a TSS role. pub fn is_tss(self) -> bool { - self == RoleType::Tss + matches!(self, RoleType::Tss(_)) } /// Checks if the role type is a Zk-Saas role. pub fn is_zksaas(self) -> bool { - self == RoleType::ZkSaas + matches!(self, RoleType::ZkSaas(_)) } -} -impl From for RoleType { - fn from(job_key: JobKey) -> Self { - match job_key { - JobKey::DKG => RoleType::Tss, - JobKey::DKGSignature => RoleType::Tss, - JobKey::ZkSaasPhaseOne => RoleType::ZkSaas, - JobKey::ZkSaasPhaseTwo => RoleType::ZkSaas, + pub fn get_authority_key(self) -> Vec { + match self { + RoleType::Tss(metadata) => metadata.authority_key, + RoleType::ZkSaas(metadata) => metadata.authority_key, } } } +/// Associated metadata needed for a DKG role +#[derive(Encode, Decode, Clone, Debug, PartialEq, Default, Eq, TypeInfo)] +pub struct TssRoleMetadata { + /// The authority key associated with the role. + authority_key: Vec, +} + +/// Associated metadata needed for a zkSaas role +#[derive(Encode, Decode, Clone, Debug, PartialEq, Default, Eq, TypeInfo)] +pub struct ZkSaasRoleMetadata { + /// The authority key associated with the role. + // TODO : Expand this + authority_key: Vec, +} + /// Role type to be used in the system. #[derive(Encode, Decode, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)] pub enum ReStakingOption {