Skip to content

Commit

Permalink
Update role metadata structure and submit remove job request (#312)
Browse files Browse the repository at this point in the history
* submit clear job request to jobs handler

* update metadata struct & submit remove job request

* send pending job details and error
  • Loading branch information
salman01zp authored Nov 23, 2023
1 parent 8f28726 commit 973f230
Show file tree
Hide file tree
Showing 6 changed files with 161 additions and 85 deletions.
19 changes: 2 additions & 17 deletions pallets/roles/src/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,8 @@ impl<T: Config> RolesHandler<T::AccountId> for Pallet<T> {
/// Returns `true` if the validator is permitted to work with this job type, otherwise `false`.
fn is_validator(address: T::AccountId, job_key: JobKey) -> bool {
let assigned_roles = AccountRolesMapping::<T>::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
let job_role = job_key.get_role_type();
assigned_roles.contains(&job_role)
}

/// Slash validator stake for the reported offence. The function should be a best effort
Expand Down
64 changes: 51 additions & 13 deletions pallets/roles/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;
use sp_runtime::{codec, traits::Zero, Saturating};
use sp_std::{convert::TryInto, prelude::*, vec};
use tangle_primitives::roles::RoleType;
use tangle_primitives::{
roles::{RoleType, RoleTypeMetadata},
traits::jobs::JobsHandler,
};
mod impls;
#[cfg(test)]
pub(crate) mod mock;
Expand Down Expand Up @@ -66,8 +69,8 @@ pub struct RoleStakingLedger<T: Config> {
#[derive(PartialEqNoBound, EqNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, Clone)]
#[scale_info(skip_type_params(T))]
pub struct RoleStakingRecord<T: Config> {
/// Role type
pub role: RoleType,
/// Metadata associated with the role.
pub metadata: RoleTypeMetadata,
/// The total amount of the stash's balance that is re-staked for selected role.
#[codec(compact)]
pub re_staked: BalanceOf<T>,
Expand All @@ -94,7 +97,10 @@ pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use tangle_primitives::traits::jobs::MPCHandler;
use tangle_primitives::{
jobs::{JobId, JobKey},
traits::jobs::MPCHandler,
};

#[pallet::pallet]
#[pallet::without_storage_info]
Expand All @@ -106,6 +112,10 @@ pub mod pallet {
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;

/// The job manager mechanism.
type JobsHandler: JobsHandler<Self::AccountId>;

/// Max roles per account.
#[pallet::constant]
type MaxRolesPerAccount: Get<u32>;

Expand All @@ -124,6 +134,8 @@ pub mod pallet {
RoleRemoved { account: T::AccountId, role: RoleType },
/// Slashed validator.
Slashed { account: T::AccountId, amount: BalanceOf<T> },
/// Pending jobs,that cannot be opted out at the moment.
PendingJobs { pending_jobs: Vec<(JobKey, JobId)> },
}

#[pallet::error]
Expand All @@ -144,6 +156,8 @@ pub mod pallet {
AccountAlreadyPaired,
/// Stash controller account not found in Roles Ledger.
AccountNotPaired,
/// Role clear request failed due to pending jobs, which can't be opted out at the moment.
RoleClearRequestFailed,
}

/// Map from all "controller" accounts to the info regarding the staking.
Expand Down Expand Up @@ -204,7 +218,7 @@ pub mod pallet {

// Validate role staking records.
for record in records.clone() {
let role = record.role;
let role = record.metadata.get_role_type();
let re_stake_amount = record.re_staked;
// Check if role is already assigned.
ensure!(
Expand All @@ -215,7 +229,7 @@ pub mod pallet {
// validate the metadata
T::MPCHandler::validate_authority_key(
stash_account.clone(),
role.clone().get_authority_key(),
record.metadata.get_authority_key(),
)?;

// Re-staking amount of record should meet min re-staking amount requirement.
Expand All @@ -232,16 +246,16 @@ pub mod pallet {
);

ledger.total = ledger.total.saturating_add(re_stake_amount);
let role_info = RoleStakingRecord { role, re_staked: re_stake_amount };
ledger.roles.push(role_info);
ledger.roles.push(record);
}

// Now that records are validated we can add them and update ledger
for record in records {
Self::add_role(stash_account.clone(), record.role.clone())?;
let role = record.metadata.get_role_type();
Self::add_role(stash_account.clone(), role.clone())?;
Self::deposit_event(Event::<T>::RoleAssigned {
account: stash_account.clone(),
role: record.role,
role,
});
}
Self::update_ledger(&stash_account, &ledger);
Expand Down Expand Up @@ -276,9 +290,33 @@ pub mod pallet {
Error::<T>::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
// Get active jobs for the role.
let active_jobs = T::JobsHandler::get_active_jobs(stash_account.clone());
let mut role_cleared = true;
let mut pending_jobs = Vec::new();
for job in active_jobs {
let job_key = job.0;
if job_key.get_role_type() == role {
// Submit request to exit from the known set.
let res = T::JobsHandler::exit_from_known_set(
stash_account.clone(),
job_key.clone(),
job.1,
);

if res.is_err() {
role_cleared = false;
pending_jobs.push((job_key.clone(), job.1));
}
}
}

if !role_cleared {
// Role clear request failed due to pending jobs, which can't be opted out at the
// moment.
Self::deposit_event(Event::<T>::PendingJobs { pending_jobs });
return Err(Error::<T>::RoleClearRequestFailed.into())
};

// Remove role from the mapping.
Self::remove_role(stash_account.clone(), role.clone())?;
Expand Down
17 changes: 17 additions & 0 deletions pallets/roles/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,25 @@ impl pallet_staking::Config for Runtime {
type WeightInfo = ();
}

pub struct MockJobsHandler;

impl JobsHandler<AccountId> for MockJobsHandler {
fn get_active_jobs(_validator: AccountId) -> Vec<(JobKey, JobId)> {
Default::default()
}

fn exit_from_known_set(
_validator: AccountId,
_job_key: JobKey,
_job_id: JobId,
) -> sp_runtime::DispatchResult {
Ok(())
}
}

impl Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type JobsHandler = MockJobsHandler;
type MaxRolesPerAccount = ConstU32<2>;
type MPCHandler = MockMPCHandler;
type WeightInfo = ();
Expand Down
97 changes: 51 additions & 46 deletions pallets/roles/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,29 +24,24 @@ 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(Default::default()), re_staked: 5000 }];
let role_records = vec![RoleStakingRecord {
metadata: RoleTypeMetadata::Tss(Default::default()),
re_staked: 5000,
}];

assert_ok!(Roles::assign_roles(RuntimeOrigin::signed(1), role_records));
assert_ok!(Roles::assign_roles(RuntimeOrigin::signed(1), role_records.clone()));

assert_events(vec![RuntimeEvent::Roles(crate::Event::RoleAssigned {
account: 1,
role: RoleType::Tss(Default::default()),
role: RoleType::Tss,
})]);

// Lets verify role assigned to account.
assert_eq!(Roles::has_role(1, RoleType::Tss(Default::default())), true);
assert_eq!(Roles::has_role(1, RoleType::Tss), true);
// Verify ledger mapping
assert_eq!(
Roles::ledger(1),
Some(RoleStakingLedger {
stash: 1,
total: 5000,
roles: vec![RoleStakingRecord {
role: RoleType::Tss(Default::default()),
re_staked: 5000
}]
})
Some(RoleStakingLedger { stash: 1, total: 5000, roles: role_records })
);
});
}
Expand All @@ -59,31 +54,27 @@ fn test_assign_multiple_roles() {

// Roles user is interested in re-staking.
let role_records = vec![
RoleStakingRecord { role: RoleType::Tss(Default::default()), re_staked: 2500 },
RoleStakingRecord { role: RoleType::ZkSaas(Default::default()), re_staked: 2500 },
RoleStakingRecord {
metadata: RoleTypeMetadata::Tss(Default::default()),
re_staked: 2500,
},
RoleStakingRecord {
metadata: RoleTypeMetadata::ZkSaas(Default::default()),
re_staked: 2500,
},
];

assert_ok!(Roles::assign_roles(RuntimeOrigin::signed(1), role_records));
assert_ok!(Roles::assign_roles(RuntimeOrigin::signed(1), role_records.clone()));

// Lets verify role assigned to account.
assert_eq!(Roles::has_role(1, RoleType::Tss(Default::default())), true);
assert_eq!(Roles::has_role(1, RoleType::Tss), true);

// Lets verify role assigned to account.
assert_eq!(Roles::has_role(1, RoleType::ZkSaas(Default::default())), true);
assert_eq!(Roles::has_role(1, RoleType::ZkSaas), true);

assert_eq!(
Roles::ledger(1),
Some(RoleStakingLedger {
stash: 1,
total: 5000,
roles: vec![
RoleStakingRecord { role: RoleType::Tss(Default::default()), re_staked: 2500 },
RoleStakingRecord {
role: RoleType::ZkSaas(Default::default()),
re_staked: 2500
},
]
})
Some(RoleStakingLedger { stash: 1, total: 5000, roles: role_records })
);
});
}
Expand All @@ -97,8 +88,14 @@ 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(Default::default()), re_staked: 5000 },
RoleStakingRecord { role: RoleType::ZkSaas(Default::default()), re_staked: 5000 },
RoleStakingRecord {
metadata: RoleTypeMetadata::Tss(Default::default()),
re_staked: 5000,
},
RoleStakingRecord {
metadata: RoleTypeMetadata::ZkSaas(Default::default()),
re_staked: 5000,
},
];
// Since max re_stake limit is 5000 it should fail with `ExceedsMaxReStakeValue` error.
assert_err!(
Expand All @@ -114,21 +111,23 @@ 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(Default::default()), re_staked: 5000 }];
let role_records = vec![RoleStakingRecord {
metadata: RoleTypeMetadata::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(Default::default())));
assert_ok!(Roles::clear_role(RuntimeOrigin::signed(1), RoleType::Tss));

assert_events(vec![RuntimeEvent::Roles(crate::Event::RoleRemoved {
account: 1,
role: RoleType::Tss(Default::default()),
role: RoleType::Tss,
})]);

// Role should be removed from account role mappings.
assert_eq!(Roles::has_role(1, RoleType::Tss(Default::default())), false);
assert_eq!(Roles::has_role(1, RoleType::Tss), false);

// Ledger should be removed from ledger mappings.
assert_eq!(Roles::ledger(1), None);
Expand All @@ -141,8 +140,10 @@ 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(Default::default()), re_staked: 5000 }];
let role_records = vec![RoleStakingRecord {
metadata: RoleTypeMetadata::Tss(Default::default()),
re_staked: 5000,
}];

assert_err!(
Roles::assign_roles(RuntimeOrigin::signed(5), role_records),
Expand All @@ -158,19 +159,21 @@ 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(Default::default()), re_staked: 5000 }];
let role_records = vec![RoleStakingRecord {
metadata: RoleTypeMetadata::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(Default::default())), true);
assert_eq!(Roles::has_role(1, RoleType::Tss), true);

// Lets clear the role.
assert_ok!(Roles::clear_role(RuntimeOrigin::signed(1), RoleType::Tss(Default::default())));
assert_ok!(Roles::clear_role(RuntimeOrigin::signed(1), RoleType::Tss));

// Role should be removed from account role mappings.
assert_eq!(Roles::has_role(1, RoleType::Tss(Default::default())), false);
assert_eq!(Roles::has_role(1, RoleType::Tss), false);

// unbound funds.
assert_ok!(Roles::unbound_funds(RuntimeOrigin::signed(1), 5000));
Expand All @@ -195,13 +198,15 @@ 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(Default::default()), re_staked: 5000 }];
let role_records = vec![RoleStakingRecord {
metadata: RoleTypeMetadata::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(Default::default())), true);
assert_eq!(Roles::has_role(1, RoleType::Tss), true);

// Lets try to unbound funds.
assert_err!(
Expand Down
Loading

0 comments on commit 973f230

Please sign in to comment.