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

feat: update role key against new validators #414

Merged
merged 8 commits into from
Jan 12, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Update validation of profile updates
drewstone committed Jan 10, 2024
commit abb08103dbe95b537088c85d2812245a4a590a85
6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -41,7 +41,7 @@ resolver = "2"
substrate-wasm-builder = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0", default-features = false }
substrate-build-script-utils = "3.0.0"

hex-literal = '0.4.1'
hex-literal = "0.4.1"
log = { version = "0.4.20", default-features = false }
scale-info = { version = "2.9.0", default-features = false, features = ["derive"] }
serde = { version = "1.0.101", default-features = false, features = ["derive"] }
@@ -281,9 +281,9 @@ sc-transaction-pool-api = { git = "https://github.com/paritytech/polkadot-sdk",
sc-consensus-manual-seal = { git = "https://github.com/paritytech/polkadot-sdk", branch = "release-polkadot-v1.1.0" }

# Tangle dependencies
tangle-primitives = { path = 'primitives', default-features = false }
tangle-primitives = { path = "primitives", default-features = false }
tangle-crypto-primitives = { path = "primitives/crypto", default-features = false }
pallet-transaction-pause = { path = 'pallets/transaction-pause', default-features = false }
pallet-transaction-pause = { path = "pallets/transaction-pause", default-features = false }

# Light client dependencies
pallet-eth2-light-client = { git = "https://github.com/webb-tools/pallet-eth2-light-client", default-features = false, tag = "v0.5.0" }
95 changes: 80 additions & 15 deletions pallets/roles/src/impls.rs
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@ use sp_runtime::{

use sp_staking::offence::Offence;
use tangle_primitives::{
jobs::{traits::JobsHandler, ReportValidatorOffence},
jobs::{traits::JobsHandler, JobId, ReportValidatorOffence},
roles::traits::RolesHandler,
};

@@ -75,7 +75,10 @@ impl<T: Config> RolesHandler<T::AccountId> for Pallet<T> {
/// Functions for the pallet.
impl<T: Config> Pallet<T> {
/// Validate updated profile for the given account.
/// This function will validate the updated profile for the given account.
/// This function will validate the updated profile for the given account by
/// checking if the account has any active jobs for the removed roles. If the
/// account has any active jobs for the removed roles, then it will return
/// the error `RoleCannotBeRemoved`.
///
/// # Parameters
/// - `account`: The account ID of the validator.
@@ -85,10 +88,10 @@ impl<T: Config> Pallet<T> {
updated_profile: Profile<T>,
) -> DispatchResult {
let ledger = Self::ledger(&account).ok_or(Error::<T>::NoProfileFound)?;
let active_jobs: Vec<(RoleType, JobId)> = T::JobsHandler::get_active_jobs(account.clone());
// Check if the account has any active jobs for the removed roles.
let removed_roles = ledger.profile.get_removed_roles(&updated_profile);
if !removed_roles.is_empty() {
let active_jobs = T::JobsHandler::get_active_jobs(account.clone());
// Check removed roles has any active jobs.
for role in removed_roles {
for job in active_jobs.clone() {
let role_type = job.0;
@@ -98,21 +101,83 @@ impl<T: Config> Pallet<T> {
}
}
};

let records = updated_profile.get_records();
let min_restaking_bond = MinRestakingBond::<T>::get();

for record in records {
if updated_profile.is_independent() {
// TODO: User cannot update profile to lower the restaking amount if there are any
// active services.
let record_restake = record.amount.unwrap_or_default();
// Restaking amount of record should meet min restaking amount requirement.
// Changing a current independent profile to shared profile is not allowed if there are
// any active jobs for any role that is currently in the profile. The reason we don't
// allow this is because a user who requested the active job might have done so because
// they wanted independent risk for the security of their application. If the validator
// fails to perform the job of a different role, their stake for "this" role won't be
// affected. It won't fall below the minimum and any removal protocol will only be triggered
// on the role that failed to perform the job. If the validator is now shared, then the
// stake for all roles will be affected.
//
// *** Perhaps this is entirely unnecessary, and I am overthinking it. ***
if updated_profile.is_shared() && ledger.profile.is_independent() {
ensure!(active_jobs.len() == 0, Error::<T>::HasRoleAssigned);
return Ok(())
}
// Changing a current shared profile to an independent profile is allowed if there are
// active jobs as long as the stake allocated to the active roles is at least as much as
// the shared profile restaking amount. This is because the shared restaking profile for an
// active role is entirely allocated to that role (as it is shared between all selected
// roles). Thus, we allow the user to change to an independent profile as long as the
// restaking amount for the active roles is at least as much as the shared restaking amount.
if updated_profile.is_independent() && ledger.profile.is_shared() {
let roles_with_active_jobs: Vec<RoleType> =
active_jobs.iter().map(|job| job.0).fold(Vec::new(), |mut acc, role| {
if !acc.contains(&role) {
acc.push(role);
}
acc
});
// For each role with an active job, ensure its stake is greater than or equal to the
// existing ledger's shared restaking amount.
for role in roles_with_active_jobs {
let updated_role_restaking_amount = updated_profile
.get_records()
.iter()
.find_map(|record| if record.role == role { record.amount } else { None })
.unwrap_or_else(|| Zero::zero());
ensure!(
record_restake >= min_restaking_bond,
updated_role_restaking_amount >= ledger.profile.get_total_profile_restake(),
Error::<T>::InsufficientRestakingBond
);
}

return Ok(())
}
// For each role with an active job, ensure its stake is greater than or equal to the
// existing ledger's restaking amount for that role. If it's a shared profile, then the
// restaking amount for that role is the entire shared restaking amount.
let min_restaking_bond = MinRestakingBond::<T>::get();
let roles_with_active_jobs: Vec<RoleType> =
active_jobs.iter().map(|job| job.0).fold(Vec::new(), |mut acc, role| {
if !acc.contains(&role) {
acc.push(role);
}
acc
});
for record in updated_profile.clone().get_records() {
match updated_profile.clone() {
Profile::Independent(_) =>
if roles_with_active_jobs.contains(&record.role) {
ensure!(
record.amount.unwrap_or_default() >= min_restaking_bond,
Error::<T>::InsufficientRestakingBond
);
ensure!(
record.amount.unwrap_or_default() >= ledger.restake_for(&record.role),
Error::<T>::InsufficientRestakingBond
);
},
Profile::Shared(profile) =>
if roles_with_active_jobs.contains(&record.role) {
ensure!(
record.amount.unwrap_or_default() >=
ledger.profile.get_total_profile_restake(),
Error::<T>::InsufficientRestakingBond
);
},
}
}
Ok(())
}
40 changes: 28 additions & 12 deletions pallets/roles/src/lib.rs
Original file line number Diff line number Diff line change
@@ -92,6 +92,14 @@ impl<T: Config> RoleStakingLedger<T> {
pub fn total_restake(&self) -> BalanceOf<T> {
self.total
}

/// Returns the amount of the stash's balance that is restaked for the given role.
/// If the role is not found, returns zero.
pub fn restake_for(&self, role: &RoleType) -> BalanceOf<T> {
self.roles
.get(role)
.map_or_else(Zero::zero, |record| record.amount.unwrap_or_default())
}
}

pub type CurrencyOf<T> = <T as pallet_staking::Config>::Currency;
@@ -374,28 +382,36 @@ pub mod pallet {
Error::<T>::NotValidator
);
let mut ledger = Ledger::<T>::get(&stash_account).ok_or(Error::<T>::NoProfileFound)?;

let total_profile_restake = updated_profile.get_total_profile_restake();
// Restaking amount of record should meet min restaking amount requirement.
let min_restaking_bond = MinRestakingBond::<T>::get();
ensure!(
total_profile_restake >= min_restaking_bond,
Error::<T>::InsufficientRestakingBond
);
match updated_profile.clone() {
Profile::Shared(profile) => {
ensure!(
profile.amount >= MinRestakingBond::<T>::get(),
Error::<T>::InsufficientRestakingBond
);
},
Profile::Independent(profile) =>
for record in profile.records.iter() {
let record_restake = record.amount.unwrap_or_default();
ensure!(
record_restake >= MinRestakingBond::<T>::get(),
Error::<T>::InsufficientRestakingBond
);
},
};

// Total restaking amount should not exceed `max_restaking_amount`.
let staking_ledger =
pallet_staking::Ledger::<T>::get(&stash_account).ok_or(Error::<T>::NotValidator)?;

let max_restaking_bond = Self::calculate_max_restake_amount(staking_ledger.active);
// Total restaking amount should not exceed `max_restaking_amount`.
ensure!(
total_profile_restake <= max_restaking_bond,
updated_profile.get_total_profile_restake() <= max_restaking_bond,
Error::<T>::ExceedsMaxRestakeValue
);

Self::validate_updated_profile(stash_account.clone(), updated_profile.clone())?;
ledger.profile = updated_profile.clone();
ledger.total = total_profile_restake;
ledger.total = updated_profile.get_total_profile_restake().into();

let profile_roles: BoundedVec<RoleType, T::MaxRolesPerAccount> =
BoundedVec::try_from(updated_profile.get_roles())
@@ -406,7 +422,7 @@ pub mod pallet {

Self::deposit_event(Event::<T>::ProfileUpdated {
account: stash_account.clone(),
total_profile_restake,
total_profile_restake: updated_profile.get_total_profile_restake().into(),
roles: updated_profile.get_roles(),
});

1 change: 1 addition & 0 deletions pallets/roles/src/tests.rs
Original file line number Diff line number Diff line change
@@ -175,6 +175,7 @@ fn test_create_profile_should_fail_if_min_required_restake_condition_is_not_met_
#[test]
fn test_update_profile_from_independent_to_shared() {
new_test_ext(vec![1, 2, 3, 4]).execute_with(|| {
println!("{:?}", pallet::MinRestakingBond::<Runtime>::get());
// Lets create independent profile.
let profile = independent_profile();
assert_ok!(Roles::create_profile(RuntimeOrigin::signed(mock_pub_key(1)), profile.clone()));