Skip to content

Commit bdc670b

Browse files
Neopalliumadamdossa
authored andcommitted
Fix permissions complexity limits (#1189)
* Add permissions complexity limits. * Update permissions limits in benchmark/tests. * Bump spec_version to be one more then mainnet. * Fix typo in MultiSig benchmarks. * Cargo fmt * Comment cleanup and add fold method to SubsetRestriction. * Add docs. A little dedup. * Add minimum complexity cost for names. Large number of pallet/extrinsic permissions cause the most cpu load from many memory allocation calls. Add a minimum complexity cost (minimum name length) to penalize short names. * Cargo fmt
1 parent 50fe182 commit bdc670b

File tree

13 files changed

+130
-31
lines changed

13 files changed

+130
-31
lines changed

pallets/base/src/lib.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,17 @@ pub fn ensure_string_limited<T: Config>(s: &[u8]) -> DispatchResult {
7171
ensure_length_ok::<T>(s.len())
7272
}
7373

74+
/// Ensure that the `len` provided is within the custom length limit.
75+
pub fn ensure_custom_length_ok<T: Config>(len: usize, limit: usize) -> DispatchResult {
76+
ensure!(len <= limit, Error::<T>::TooLong);
77+
Ok(())
78+
}
79+
80+
/// Ensure that `s.len()` is within the custom length limit.
81+
pub fn ensure_custom_string_limited<T: Config>(s: &[u8], limit: usize) -> DispatchResult {
82+
ensure_custom_length_ok::<T>(s.len(), limit)
83+
}
84+
7485
/// Ensure that given `Some(s)`, `s.len()` is within the generic length limit.
7586
pub fn ensure_opt_string_limited<T: Config>(s: Option<&[u8]>) -> DispatchResult {
7687
match s {

pallets/external-agents/src/benchmarking.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use polymesh_primitives::{AuthorizationData, ExtrinsicPermissions, PalletPermiss
2222
use sp_std::prelude::*;
2323

2424
pub(crate) const SEED: u32 = 0;
25-
const MAX_PALLETS: u32 = 1000;
25+
const MAX_PALLETS: u32 = 19;
2626

2727
fn setup<T: Asset + TestUtilsFn<AccountIdOf<T>>>() -> (User<T>, Ticker) {
2828
let owner = user("owner", SEED);

pallets/identity/src/keys.rs

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use frame_support::dispatch::DispatchResult;
2424
use frame_support::traits::{Currency as _, Get as _};
2525
use frame_support::{debug, ensure, StorageMap as _, StorageValue as _};
2626
use frame_system::ensure_signed;
27-
use pallet_base::{ensure_length_ok, ensure_string_limited};
27+
use pallet_base::{ensure_custom_length_ok, ensure_custom_string_limited};
2828
use polymesh_common_utilities::constants::did::USER;
2929
use polymesh_common_utilities::group::GroupTrait;
3030
use polymesh_common_utilities::identity::{SecondaryKeyWithAuth, TargetIdAuthorization};
@@ -44,6 +44,16 @@ use sp_runtime::traits::{AccountIdConversion as _, IdentifyAccount, Verify, Zero
4444
use sp_runtime::{AnySignature, DispatchError};
4545
use sp_std::{vec, vec::Vec};
4646

47+
const MAX_KEYS: usize = 200;
48+
const MAX_ASSETS: usize = 200;
49+
const MAX_PORTFOLIOS: usize = 200;
50+
const MAX_PALLETS: usize = 50;
51+
const MAX_EXTRINSICS: usize = 40;
52+
const MAX_NAME_LEN: usize = 50;
53+
54+
// Limit the maximum memory/cpu cost of an identities `DidRecord`.
55+
const MAX_DIDRECORD_SIZE: usize = 1_000_000;
56+
4757
type System<T> = frame_system::Module<T>;
4858

4959
impl<T: Config> Module<T> {
@@ -411,7 +421,10 @@ impl<T: Config> Module<T> {
411421
let mut record = <DidRecords<T>>::get(did);
412422

413423
// Ensure we won't have too many keys.
414-
ensure_length_ok::<T>(record.secondary_keys.len().saturating_add(keys.len()))?;
424+
let cost = keys.iter().fold(0usize, |cost, auth| {
425+
cost.saturating_add(auth.secondary_key.permissions.complexity())
426+
});
427+
Self::ensure_secondary_keys_limited(&record, keys.len(), cost)?;
415428

416429
// 1. Verify signatures.
417430
for si_with_auth in keys.iter() {
@@ -426,10 +439,7 @@ impl<T: Config> Module<T> {
426439
.ok_or(Error::<T>::InvalidAccountKey)?;
427440

428441
// 1.1. Constraint 1-to-1 account to DID.
429-
ensure!(
430-
Self::can_link_account_key_to_did(account_id),
431-
Error::<T>::AlreadyLinked
432-
);
442+
Self::ensure_key_did_unlinked(account_id)?;
433443

434444
// 1.2. Verify the signature.
435445
let signature = AnySignature::from(Signature::from_h512(si_with_auth.auth_signature));
@@ -475,8 +485,9 @@ impl<T: Config> Module<T> {
475485
// Not really needed unless we allow identities to be deleted.
476486
Self::ensure_id_record_exists(target_did)?;
477487

478-
// Link the secondary key.
479-
Self::ensure_secondary_key_can_be_added(&target_did, &key)?;
488+
// Check that the secondary key can be linked.
489+
Self::ensure_secondary_key_can_be_added(&target_did, &key, &permissions)?;
490+
480491
// Check that the new Identity has a valid CDD claim.
481492
ensure!(Self::has_valid_cdd(target_did), Error::<T>::TargetHasNoCdd);
482493
// Charge the protocol fee after all checks.
@@ -491,17 +502,35 @@ impl<T: Config> Module<T> {
491502
})
492503
}
493504

505+
/// Ensure that the identity can add a new secondary key
506+
/// without going over it's complexity budget.
494507
pub fn ensure_secondary_key_can_be_added(
495508
did: &IdentityId,
496509
key: &T::AccountId,
510+
perms: &Permissions,
497511
) -> DispatchResult {
498512
let record = <DidRecords<T>>::get(did);
499-
ensure_length_ok::<T>(record.secondary_keys.len().saturating_add(1))?;
513+
Self::ensure_secondary_keys_limited(&record, 1, perms.complexity())?;
500514

501515
Self::ensure_key_did_unlinked(&key)?;
502516
Ok(())
503517
}
504518

519+
/// Ensure that multiple secondary keys with `cost` complexity can be
520+
/// added to the identitie's `DidRecords` without going over the complexity budget.
521+
///
522+
/// `keys` - The number of secondary keys to add.
523+
/// `cost` - The complexity cost for the new keys permissions.
524+
pub fn ensure_secondary_keys_limited(
525+
record: &DidRecord<T::AccountId>,
526+
keys: usize,
527+
cost: usize,
528+
) -> DispatchResult {
529+
ensure_custom_length_ok::<T>(record.secondary_keys.len().saturating_add(keys), MAX_KEYS)?;
530+
ensure_custom_length_ok::<T>(record.complexity().saturating_add(cost), MAX_DIDRECORD_SIZE)?;
531+
Ok(())
532+
}
533+
505534
/// Joins a DID as an account based secondary key.
506535
pub fn unsafe_join_identity(
507536
target_did: IdentityId,
@@ -734,21 +763,21 @@ impl<T: Config> Module<T> {
734763

735764
/// Ensures length limits are enforced in `perms`.
736765
crate fn ensure_perms_length_limited(perms: &Permissions) -> DispatchResult {
737-
ensure_length_ok::<T>(perms.asset.complexity())?;
738-
ensure_length_ok::<T>(perms.portfolio.complexity())?;
766+
ensure_custom_length_ok::<T>(perms.asset.complexity(), MAX_ASSETS)?;
767+
ensure_custom_length_ok::<T>(perms.portfolio.complexity(), MAX_PORTFOLIOS)?;
739768
Self::ensure_extrinsic_perms_length_limited(&perms.extrinsic)
740769
}
741770

742771
/// Ensures length limits are enforced in `perms`.
743772
pub fn ensure_extrinsic_perms_length_limited(perms: &ExtrinsicPermissions) -> DispatchResult {
744773
if let Some(set) = perms.inner() {
745-
ensure_length_ok::<T>(set.len())?;
774+
ensure_custom_length_ok::<T>(set.len(), MAX_PALLETS)?;
746775
for elem in set {
747-
ensure_string_limited::<T>(&elem.pallet_name)?;
776+
ensure_custom_string_limited::<T>(&elem.pallet_name, MAX_NAME_LEN)?;
748777
if let Some(set) = elem.dispatchable_names.inner() {
749-
ensure_length_ok::<T>(set.len())?;
778+
ensure_custom_length_ok::<T>(set.len(), MAX_EXTRINSICS)?;
750779
for elem in set {
751-
ensure_string_limited::<T>(elem)?;
780+
ensure_custom_string_limited::<T>(elem, MAX_NAME_LEN)?;
752781
}
753782
}
754783
}

pallets/multisig/src/benchmarking.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,15 @@ pub type MultisigSetupResult<T, AccountId> = (
7474

7575
fn generate_multisig_for_alice_wo_accepting<T: Config + TestUtilsFn<AccountIdOf<T>>>(
7676
total_signers: u32,
77-
singers_required: u32,
77+
signers_required: u32,
7878
) -> Result<MultisigSetupResult<T, T::AccountId>, DispatchError> {
7979
let alice = <UserBuilder<T>>::default().generate_did().build("alice");
8080
let mut signers = vec![Signatory::from(alice.did())];
8181
let multisig = generate_multisig_with_extra_signers::<T>(
8282
&alice,
8383
&mut signers,
8484
total_signers - 1,
85-
singers_required,
85+
signers_required,
8686
)
8787
.unwrap();
8888
let signer_origin = match signers.last().cloned().unwrap() {
@@ -100,10 +100,10 @@ fn generate_multisig_for_alice_wo_accepting<T: Config + TestUtilsFn<AccountIdOf<
100100

101101
fn generate_multisig_for_alice<T: Config + TestUtilsFn<AccountIdOf<T>>>(
102102
total_signers: u32,
103-
singers_required: u32,
103+
signers_required: u32,
104104
) -> Result<MultisigSetupResult<T, T::AccountId>, DispatchError> {
105105
let (alice, multisig, signers, signer_origin, multisig_origin) =
106-
generate_multisig_for_alice_wo_accepting::<T>(total_signers, singers_required).unwrap();
106+
generate_multisig_for_alice_wo_accepting::<T>(total_signers, signers_required).unwrap();
107107
for signer in &signers {
108108
let auth_id = get_last_auth_id::<T>(signer);
109109
<MultiSig<T>>::unsafe_accept_multisig_signer(signer.clone(), auth_id).unwrap();
@@ -129,10 +129,10 @@ pub type ProposalSetupResult<T, AccountId, Proposal> = (
129129

130130
fn generate_multisig_and_proposal_for_alice<T: Config + TestUtilsFn<AccountIdOf<T>>>(
131131
total_signers: u32,
132-
singers_required: u32,
132+
signers_required: u32,
133133
) -> Result<ProposalSetupResult<T, T::AccountId, T::Proposal>, DispatchError> {
134134
let (alice, multisig, signers, signer_origin, _) =
135-
generate_multisig_for_alice::<T>(total_signers, singers_required).unwrap();
135+
generate_multisig_for_alice::<T>(total_signers, signers_required).unwrap();
136136
let proposal_id = <MultiSig<T>>::ms_tx_done(multisig.clone());
137137
let proposal = Box::new(frame_system::Call::<T>::remark(vec![]).into());
138138
Ok((
@@ -148,11 +148,11 @@ fn generate_multisig_and_proposal_for_alice<T: Config + TestUtilsFn<AccountIdOf<
148148

149149
fn generate_multisig_and_create_proposal<T: Config + TestUtilsFn<AccountIdOf<T>>>(
150150
total_signers: u32,
151-
singers_required: u32,
151+
signers_required: u32,
152152
create_as_key: bool,
153153
) -> Result<ProposalSetupResult<T, T::AccountId, T::Proposal>, DispatchError> {
154154
let (alice, multisig, signers, signer_origin, proposal_id, proposal, ephemeral_multisig) =
155-
generate_multisig_and_proposal_for_alice::<T>(total_signers, singers_required).unwrap();
155+
generate_multisig_and_proposal_for_alice::<T>(total_signers, signers_required).unwrap();
156156
if create_as_key {
157157
<MultiSig<T>>::create_proposal_as_key(
158158
signer_origin.clone().into(),

pallets/multisig/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -538,10 +538,11 @@ decl_module! {
538538
Self::ensure_ms(&multisig)?;
539539
Self::verify_sender_is_creator(did, &multisig)?;
540540

541-
<Identity<T>>::ensure_secondary_key_can_be_added(&did, &multisig)?;
541+
let perms = Permissions::empty();
542+
<Identity<T>>::ensure_secondary_key_can_be_added(&did, &multisig, &perms)?;
542543

543544
// Add the multisig as a secondary key with no permissions.
544-
<Identity<T>>::unsafe_join_identity(did, Permissions::empty(), multisig);
545+
<Identity<T>>::unsafe_join_identity(did, perms, multisig);
545546
}
546547

547548
/// Adds a multisig as the primary key of the current did if the current DID is the creator

pallets/runtime/ci/src/runtime.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
6161
// and set impl_version to 0. If only runtime
6262
// implementation changes and behavior does not, then leave spec_version as
6363
// is and increment impl_version.
64-
spec_version: 3001,
64+
spec_version: 3002,
6565
impl_version: 0,
6666
apis: RUNTIME_API_VERSIONS,
6767
transaction_version: 2,

pallets/runtime/develop/src/runtime.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
5959
// and set impl_version to 0. If only runtime
6060
// implementation changes and behavior does not, then leave spec_version as
6161
// is and increment impl_version.
62-
spec_version: 3001,
62+
spec_version: 3002,
6363
impl_version: 0,
6464
apis: RUNTIME_API_VERSIONS,
6565
transaction_version: 2,

pallets/runtime/mainnet/src/runtime.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
5757
// and set impl_version to 0. If only runtime
5858
// implementation changes and behavior does not, then leave spec_version as
5959
// is and increment impl_version.
60-
spec_version: 3001,
60+
spec_version: 3002,
6161
impl_version: 0,
6262
apis: RUNTIME_API_VERSIONS,
6363
transaction_version: 2,

pallets/runtime/testnet/src/runtime.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
6161
// and set impl_version to 0. If only runtime
6262
// implementation changes and behavior does not, then leave spec_version as
6363
// is and increment impl_version.
64-
spec_version: 3001,
64+
spec_version: 3002,
6565
impl_version: 0,
6666
apis: RUNTIME_API_VERSIONS,
6767
transaction_version: 2,

pallets/runtime/tests/src/identity_test.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ fn do_add_permissions_to_multiple_tokens() {
290290
add_secondary_key(alice.did, bob.acc());
291291

292292
// Create some tokens.
293-
let max_tokens = 30;
293+
let max_tokens = 20;
294294
let tokens: Vec<Ticker> = (0..max_tokens)
295295
.map(|i| {
296296
let name = format!("TOKEN_{}", i);

0 commit comments

Comments
 (0)