Skip to content

Commit 4199b6f

Browse files
authored
feat: support for unique linking (#782)
Part of KILTprotocol/ticket#3650. Built on top of #781. ## Trade-off I chose to go this way instead of providing an optional counter, because providing a counter would require one of the following two approaches: 1. Transform the storage double map into a counted one, requiring a migration also for our currently-deployed pallet, which I wanted to avoid 2. Not use a counter, but iterate every time to make sure there are still "spots" left for the current DID. This would require changing the benchmarking logic as now we have a potentially unbounded iteration happening. I also wanted to avoid that. Hence, the solution was to provide a somehow more limited feature of simply specifying whether the links are expected to be unique per DID or not. This, as long as we set `false` for our deployed pallets would not require any storage migration, and does not require any changes in the benchmarks, so I found it a good compromise.
1 parent 6f7f587 commit 4199b6f

File tree

11 files changed

+128
-7
lines changed

11 files changed

+128
-7
lines changed

dip-template/runtimes/dip-provider/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,7 @@ impl pallet_did_lookup::Config for Runtime {
433433
type OriginSuccess = DidRawOrigin<AccountId, DidIdentifier>;
434434
type RuntimeEvent = RuntimeEvent;
435435
type RuntimeHoldReason = RuntimeHoldReason;
436+
type UniqueLinkingEnabled = ConstBool<false>;
436437
type WeightInfo = weights::pallet_did_lookup::WeightInfo<Runtime>;
437438
}
438439

pallets/pallet-did-lookup/src/lib.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ pub mod pallet {
131131

132132
/// Migration manager to handle new created entries
133133
type BalanceMigrationManager: BalanceMigrationManager<AccountIdOf<Self>, BalanceOf<Self, I>>;
134+
135+
// Flag specifying whether there should only ever be a single account <-> DID
136+
// link, or multiple.
137+
#[pallet::constant]
138+
type UniqueLinkingEnabled: Get<bool>;
134139
}
135140

136141
#[pallet::pallet]
@@ -196,6 +201,9 @@ pub mod pallet {
196201
///
197202
/// NOTE: this will only be returned if the storage has inconsistencies.
198203
Migration,
204+
/// The deployed pallet supports a single account <-> link, which has
205+
/// already been previously created for the provided DID.
206+
LinkExisting,
199207
}
200208

201209
#[pallet::genesis_config]
@@ -446,6 +454,14 @@ pub mod pallet {
446454
did: did_identifier.clone(),
447455
};
448456

457+
let is_unique_flag_enabled = <T as Config<I>>::UniqueLinkingEnabled::get();
458+
if is_unique_flag_enabled {
459+
let is_did_already_linked = ConnectedAccounts::<T, I>::iter_key_prefix(&did_identifier)
460+
.next()
461+
.is_some();
462+
ensure!(!is_did_already_linked, Error::<T, I>::LinkExisting);
463+
}
464+
449465
LinkableAccountDepositCollector::<T, I>::create_deposit(
450466
record.clone().deposit.owner,
451467
record.deposit.amount,

pallets/pallet-did-lookup/src/mock.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@
1616

1717
// If you feel like getting in touch with us, you can do so at [email protected]
1818

19-
use frame_support::parameter_types;
19+
use frame_support::{pallet_prelude::ValueQuery, parameter_types, storage_alias};
2020
use frame_system::pallet_prelude::BlockNumberFor;
2121
use kilt_support::{
2222
mock::{mock_origin, SubjectId},
2323
traits::StorageDepositCollector,
2424
};
2525

26+
use sp_core::Get;
2627
use sp_runtime::{
2728
traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify},
2829
BuildStorage, MultiSignature,
@@ -109,6 +110,23 @@ parameter_types! {
109110
pub const DidLookupDeposit: Balance = 10;
110111
}
111112

113+
pub struct UniqueLinkEnabledFlag;
114+
115+
#[storage_alias]
116+
type FlagStorage = StorageValue<DidLookup, bool, ValueQuery>;
117+
118+
impl UniqueLinkEnabledFlag {
119+
fn set(flag: bool) {
120+
FlagStorage::set(flag)
121+
}
122+
}
123+
124+
impl Get<bool> for UniqueLinkEnabledFlag {
125+
fn get() -> bool {
126+
FlagStorage::get()
127+
}
128+
}
129+
112130
impl pallet_did_lookup::Config for Test {
113131
type BalanceMigrationManager = ();
114132
type RuntimeEvent = RuntimeEvent;
@@ -119,6 +137,7 @@ impl pallet_did_lookup::Config for Test {
119137
type OriginSuccess = mock_origin::DoubleOrigin<AccountId, SubjectId>;
120138
type DidIdentifier = SubjectId;
121139
type WeightInfo = ();
140+
type UniqueLinkingEnabled = UniqueLinkEnabledFlag;
122141
}
123142

124143
impl mock_origin::Config for Test {
@@ -163,6 +182,7 @@ pub struct ExtBuilder {
163182
balances: Vec<(AccountId, Balance)>,
164183
/// list of connection (sender, did, connected address)
165184
connections: Vec<(AccountId, SubjectId, LinkableAccountId)>,
185+
unique_flag: bool,
166186
}
167187

168188
impl ExtBuilder {
@@ -179,6 +199,11 @@ impl ExtBuilder {
179199
self
180200
}
181201

202+
pub fn with_unique_connections(mut self) -> Self {
203+
self.unique_flag = true;
204+
self
205+
}
206+
182207
pub fn build(self) -> sp_io::TestExternalities {
183208
let mut storage = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
184209
pallet_balances::GenesisConfig::<Test> {
@@ -197,6 +222,8 @@ impl ExtBuilder {
197222
pallet_did_lookup::Pallet::<Test>::add_association(sender, did, account)
198223
.expect("Should create connection");
199224
}
225+
226+
UniqueLinkEnabledFlag::set(self.unique_flag);
200227
});
201228
ext
202229
}

pallets/pallet-did-lookup/src/tests/associate.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,3 +372,44 @@ fn test_remove_association_account_not_authorized() {
372372
);
373373
});
374374
}
375+
376+
#[test]
377+
fn test_add_association_with_unique_linking_enabled() {
378+
ExtBuilder::default()
379+
.with_balances(vec![
380+
(ACCOUNT_00, <Test as crate::Config>::Deposit::get() * 50),
381+
(ACCOUNT_01, <Test as crate::Config>::Deposit::get() * 50),
382+
])
383+
.with_unique_connections()
384+
.build_and_execute_with_sanity_tests(|| {
385+
// First time linking works.
386+
assert_ok!(DidLookup::associate_sender(
387+
mock_origin::DoubleOrigin(ACCOUNT_00, DID_00).into()
388+
));
389+
assert!(ConnectedDids::<Test>::contains_key(LinkableAccountId::from(ACCOUNT_00)));
390+
assert!(ConnectedAccounts::<Test>::contains_key(
391+
DID_00,
392+
LinkableAccountId::from(ACCOUNT_00)
393+
));
394+
395+
// Changing the DID linked to an account (overriding the previous DID) works.
396+
assert_ok!(DidLookup::associate_sender(
397+
mock_origin::DoubleOrigin(ACCOUNT_00, DID_01).into()
398+
));
399+
assert!(ConnectedDids::<Test>::contains_key(LinkableAccountId::from(ACCOUNT_00)));
400+
assert!(ConnectedAccounts::<Test>::contains_key(
401+
DID_01,
402+
LinkableAccountId::from(ACCOUNT_00)
403+
));
404+
assert!(!ConnectedAccounts::<Test>::contains_key(
405+
DID_00,
406+
LinkableAccountId::from(ACCOUNT_00)
407+
));
408+
409+
// Linking a second account to the same DID fails.
410+
assert_noop!(
411+
DidLookup::associate_sender(mock_origin::DoubleOrigin(ACCOUNT_01, DID_01).into()),
412+
Error::<Test>::LinkExisting
413+
);
414+
})
415+
}

pallets/pallet-did-lookup/src/try_state.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@
1919
use frame_support::ensure;
2020
use kilt_support::test_utils::log_and_return_error_message;
2121
use scale_info::prelude::format;
22-
use sp_runtime::TryRuntimeError;
22+
use sp_runtime::{
23+
traits::{Get, One},
24+
TryRuntimeError,
25+
};
2326

24-
use crate::{Config, ConnectedAccounts, ConnectedDids};
27+
use crate::{Config, ConnectedAccounts, ConnectedDids, ConnectionRecord};
2528

2629
pub(crate) fn do_try_state<T: Config<I>, I: 'static>() -> Result<(), TryRuntimeError> {
30+
// Verify DID -> account link integrity.
2731
ConnectedDids::<T, I>::iter().try_for_each(|(account, record)| -> Result<(), TryRuntimeError> {
2832
ensure!(
2933
ConnectedAccounts::<T, I>::contains_key(&record.did, &account),
@@ -32,6 +36,7 @@ pub(crate) fn do_try_state<T: Config<I>, I: 'static>() -> Result<(), TryRuntimeE
3236
Ok(())
3337
})?;
3438

39+
// Verify account -> DID link integrity.
3540
ConnectedAccounts::<T, I>::iter().try_for_each(
3641
|(did_identifier, linked_account_id, _)| -> Result<(), TryRuntimeError> {
3742
ensure!(
@@ -43,5 +48,24 @@ pub(crate) fn do_try_state<T: Config<I>, I: 'static>() -> Result<(), TryRuntimeE
4348
);
4449
Ok(())
4550
},
46-
)
51+
)?;
52+
53+
// Verify account <-> DID link unicity.
54+
if <T as Config<I>>::UniqueLinkingEnabled::get() {
55+
let mut did_linked_to_accounts = ConnectedDids::<T, I>::iter_values().map(|ConnectionRecord { did, .. }| did);
56+
57+
did_linked_to_accounts.try_for_each(|did_identifier| -> Result<(), TryRuntimeError> {
58+
let linked_accounts = ConnectedAccounts::<T, I>::iter_key_prefix(&did_identifier).count();
59+
ensure!(
60+
linked_accounts.is_one(),
61+
log_and_return_error_message(format!(
62+
"DID {:?} has more than a single account linked: {:?}.",
63+
did_identifier, linked_accounts
64+
))
65+
);
66+
Ok(())
67+
})?;
68+
}
69+
70+
Ok(())
4771
}

pallets/pallet-dip-consumer/src/mock.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use frame_support::{
2323
traits::{BlakeTwo256, IdentityLookup},
2424
AccountId32,
2525
},
26-
traits::{ConstU16, ConstU32, ConstU64, Contains, Currency, Everything},
26+
traits::{ConstBool, ConstU16, ConstU32, ConstU64, Contains, Currency, Everything},
2727
};
2828
use frame_system::{mocking::MockBlock, EnsureSigned};
2929

@@ -94,6 +94,7 @@ impl pallet_did_lookup::Config for TestRuntime {
9494
type OriginSuccess = DipOrigin<AccountId32, AccountId32, ()>;
9595
type RuntimeEvent = RuntimeEvent;
9696
type RuntimeHoldReason = RuntimeHoldReason;
97+
type UniqueLinkingEnabled = ConstBool<false>;
9798
type WeightInfo = ();
9899
}
99100

pallets/pallet-migration/src/mock.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ pub mod runtime {
5757
use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
5858
use public_credentials::InputSubjectIdOf;
5959
use scale_info::TypeInfo;
60-
use sp_core::{ed25519, ConstU128, ConstU32};
60+
use sp_core::{ed25519, ConstBool, ConstU128, ConstU32};
6161
use sp_runtime::{
6262
traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify},
6363
AccountId32, BuildStorage, MultiSignature, MultiSigner, Perquintill, RuntimeDebug,
@@ -309,6 +309,7 @@ pub mod runtime {
309309
type DidIdentifier = SubjectId;
310310
type WeightInfo = ();
311311
type BalanceMigrationManager = Migration;
312+
type UniqueLinkingEnabled = ConstBool<false>;
312313
}
313314

314315
pub(crate) type TestWeb3Name = AsciiWeb3Name<Test>;

runtimes/common/src/dip/mock.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use frame_system::{mocking::MockBlock, pallet_prelude::BlockNumberFor, EnsureRoo
3030
use kilt_dip_primitives::RevealedWeb3Name;
3131
use pallet_did_lookup::{account::AccountId20, linkable_account::LinkableAccountId};
3232
use pallet_web3_names::{web3_name::AsciiWeb3Name, Web3NameOf};
33-
use sp_core::{sr25519, ConstU128, ConstU16, ConstU32, ConstU64};
33+
use sp_core::{sr25519, ConstBool, ConstU128, ConstU16, ConstU32, ConstU64};
3434
use sp_runtime::{traits::IdentityLookup, AccountId32, BoundedVec};
3535

3636
use crate::{
@@ -169,6 +169,7 @@ impl pallet_did_lookup::Config for TestRuntime {
169169
type OriginSuccess = AccountId;
170170
type RuntimeEvent = RuntimeEvent;
171171
type RuntimeHoldReason = RuntimeHoldReason;
172+
type UniqueLinkingEnabled = ConstBool<false>;
172173
type WeightInfo = ();
173174
}
174175

runtimes/kestrel/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,9 @@ impl pallet_did_lookup::Config for Runtime {
437437
type OriginSuccess = did::DidRawOrigin<AccountId, DidIdentifier>;
438438
type BalanceMigrationManager = ();
439439
type WeightInfo = ();
440+
// Do not change the below flag to `true` without also deploying a runtime
441+
// migration which removes any links that point to the same DID!
442+
type UniqueLinkingEnabled = ConstBool<false>;
440443
}
441444

442445
impl pallet_web3_names::Config for Runtime {

runtimes/peregrine/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,9 @@ impl pallet_did_lookup::Config for Runtime {
702702

703703
type WeightInfo = weights::pallet_did_lookup::WeightInfo<Runtime>;
704704
type BalanceMigrationManager = Migration;
705+
// Do not change the below flag to `true` without also deploying a runtime
706+
// migration which removes any links that point to the same DID!
707+
type UniqueLinkingEnabled = ConstBool<false>;
705708
}
706709

707710
impl pallet_web3_names::Config for Runtime {

0 commit comments

Comments
 (0)