From 61f7eda792335b4ff97b6daa5120b1c102298971 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Wed, 18 Jan 2023 21:18:57 +0000 Subject: [PATCH] Merge remote-tracking branch 'origin/feat/set-sub-identities' into develop --- --- a/Makefile +++ b/Makefile @@ -146,5 +146,5 @@ make _bench PALLET=app-promotion PALLET_DIR=app-promotion .PHONY: bench -# Disabled: bench-scheduler, bench-collator-selection, bench-rmrk-core, bench-rmrk-equip -bench: bench-evm-migration bench-unique bench-structure bench-fungible bench-refungible bench-nonfungible bench-configuration bench-foreign-assets bench-identity +# Disabled: bench-scheduler, bench-collator-selection, bench-identity, bench-rmrk-core, bench-rmrk-equip +bench: bench-evm-migration bench-unique bench-structure bench-fungible bench-refungible bench-nonfungible bench-configuration bench-foreign-assets --- a/pallets/identity/src/benchmarking.rs +++ b/pallets/identity/src/benchmarking.rs @@ -417,7 +417,7 @@ let n in 0..600; use frame_benchmarking::account; let identities = (0..n).map(|i| ( - account("caller", i, 0), + account("caller", i, SEED), Registration::, T::MaxRegistrars, T::MaxAdditionalFields> { judgements: Default::default(), deposit: Default::default(), @@ -433,7 +433,7 @@ use frame_benchmarking::account; let origin = T::ForceOrigin::successful_origin(); let identities = (0..n).map(|i| ( - account("caller", i, 0), + account("caller", i, SEED), Registration::, T::MaxRegistrars, T::MaxAdditionalFields> { judgements: Default::default(), deposit: Default::default(), @@ -446,6 +446,20 @@ let identities = identities.into_iter().map(|(acc, _)| acc).collect::>(); }: _(origin, identities) + force_set_subs { + let s in 0 .. T::MaxSubAccounts::get(); + let n in 0..600; + use frame_benchmarking::account; + let identities = (0..n).map(|i| ( + account("caller", i, SEED), + ( + BalanceOf::::max_value(), + create_sub_accounts::(&caller, s)?.try_into().unwrap(), + ), + )).collect::>(); + let origin = T::ForceOrigin::successful_origin(); + }: _(origin, identities) + add_sub { let s in 0 .. T::MaxSubAccounts::get() - 1; --- a/pallets/identity/src/lib.rs +++ b/pallets/identity/src/lib.rs @@ -314,6 +314,8 @@ main: T::AccountId, deposit: BalanceOf, }, + /// A number of identities were forcibly updated with new sub-identities. + SubIdentitiesInserted { amount: u32 }, } #[pallet::call] @@ -1137,13 +1139,64 @@ ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; for identity in identities.clone() { - IdentityOf::::set(identity, None); + let (_, sub_ids) = >::take(&identity); + >::remove(&identity); + for sub in sub_ids.iter() { + >::remove(sub); + } } Self::deposit_event(Event::IdentitiesRemoved { amount: identities.len() as u32, }); Ok(()) } + + /// Set sub-identities to be associated with the provided accounts as force origin. + /// + /// This is not meant to operate in tandem with the identity pallet as is, + /// and be instead used to keep identities made and verified externally, + /// forbidden from interacting with an ordinary user, since it ignores any safety mechanism. + #[pallet::call_index(17)] + #[pallet::weight(T::WeightInfo::force_set_subs( + T::MaxSubAccounts::get(), // S + subs.len() as u32, // N + ))] + pub fn force_set_subs( + origin: OriginFor, + subs: Vec<( + T::AccountId, + ( + BalanceOf, + BoundedVec<(T::AccountId, Data), T::MaxSubAccounts>, + ), + )>, + ) -> DispatchResult { + T::ForceOrigin::ensure_origin(origin)?; + for identity in subs.clone() { + let account = identity.0; + let (_, old_subs) = >::get(&account); + for old_sub in old_subs { + >::remove(old_sub); + } + + let mut ids = BoundedVec::::default(); + for (id, name) in identity.1 .1 { + >::insert(&id, (account.clone(), name)); + ids.try_push(id) + .expect("subs length is less than T::MaxSubAccounts; qed"); + } + + if ids.is_empty() { + >::remove(&account); + } else { + >::insert(account, (identity.1 .0, ids)); + } + } + Self::deposit_event(Event::SubIdentitiesInserted { + amount: subs.len() as u32, + }); + Ok(()) + } } } --- a/pallets/identity/src/weights.rs +++ b/pallets/identity/src/weights.rs @@ -78,6 +78,7 @@ fn kill_identity(r: u32, s: u32, x: u32, ) -> Weight; fn force_insert_identities(x: u32, n: u32, ) -> Weight; fn force_remove_identities(x: u32, n: u32, ) -> Weight; + fn force_set_subs(s: u32, n: u32, ) -> Weight; fn add_sub(s: u32, ) -> Weight; fn rename_sub(s: u32, ) -> Weight; fn remove_sub(s: u32, ) -> Weight; @@ -273,6 +274,20 @@ .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64).saturating_mul(n as u64)) } + // Storage: Identity IdentityOf (r:1 w:1) + // todo:collator + /// The range of component `s` is `[0, 100]`. + /// The range of component `n` is `[0, 600]`. + fn force_set_subs(s: u32, n: u32) -> Weight { + // Minimum execution time: 41_872 nanoseconds. + Weight::from_ref_time(40_230_216 as u64) + // Standard Error: 2_342 + .saturating_add(Weight::from_ref_time(145_168 as u64)) + // Standard Error: 457 + .saturating_add(Weight::from_ref_time(291_732 as u64).saturating_mul(s as u64)) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64).saturating_mul(n as u64)) + } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) @@ -509,6 +524,20 @@ .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64).saturating_mul(n as u64)) } + // Storage: Identity IdentityOf (r:1 w:1) + // todo:collator + /// The range of component `xs is `[0, 100]`. + /// The range of component `n` is `[0, 600]`. + fn force_set_subs(s: u32, n: u32) -> Weight { + // Minimum execution time: 41_872 nanoseconds. + Weight::from_ref_time(40_230_216 as u64) + // Standard Error: 2_342 + .saturating_add(Weight::from_ref_time(145_168 as u64)) + // Standard Error: 457 + .saturating_add(Weight::from_ref_time(291_732 as u64).saturating_mul(s as u64)) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64).saturating_mul(n as u64)) + } // Storage: Identity IdentityOf (r:1 w:0) // Storage: Identity SuperOf (r:1 w:1) // Storage: Identity SubsOf (r:1 w:1) --- a/tests/src/collator-selection/identity.seqtest.ts +++ b/tests/src/collator-selection/identity.seqtest.ts @@ -29,6 +29,14 @@ return (await getIdentities(helper)).flatMap(([key, _value]) => key); } +async function getSubIdentityAccounts(helper: UniqueHelper, address: string) { + return ((await helper.getApi().query.identity.subsOf(address)).toHuman() as any)[1]; +} + +async function getSubIdentityName(helper: UniqueHelper, address: string) { + return ((await helper.getApi().query.identity.superOf(address)).toHuman() as any); +} + describe('Integration Test: Identities Manipulation', () => { let superuser: IKeyringPair; @@ -47,49 +55,220 @@ .to.be.rejectedWith(/Transaction call is not expected/); }); - itSub('Sets identities', async ({helper}) => { - const oldIdentitiesCount = (await getIdentityAccounts(helper)).length; + describe('Identities', () => { + itSub('Sets identities', async ({helper}) => { + const oldIdentitiesCount = (await getIdentityAccounts(helper)).length; - const crowdSize = 10; - const crowd = await helper.arrange.createCrowd(crowdSize, 0n, superuser); - const identities = crowd.map((acc, i) => [acc.address, {info: {display: {Raw: `accounter #${i}`}}}]); - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identities]); + const crowdSize = 10; + const crowd = await helper.arrange.createCrowd(crowdSize, 0n, superuser); + const identities = crowd.map((acc, i) => [acc.address, {info: {display: {Raw: `accounter #${i}`}}}]); + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identities]); - expect((await getIdentityAccounts(helper)).length).to.be.equal(oldIdentitiesCount + crowdSize); - }); + expect((await getIdentityAccounts(helper)).length).to.be.equal(oldIdentitiesCount + crowdSize); + }); - itSub('Setting identities does not delete existing but does overwrite', async ({helper}) => { - const crowd = await helper.arrange.createCrowd(10, 0n, superuser); - const identities = crowd.map((acc, i) => [acc.address, {info: {display: {Raw: `accounter #${i}`}}}]); + itSub('Setting identities does not delete existing but does overwrite', async ({helper}) => { + const crowd = await helper.arrange.createCrowd(10, 0n, superuser); + const identities = crowd.map((acc, i) => [acc.address, {info: {display: {Raw: `accounter #${i}`}}}]); + + // insert a single identity + let singleIdentity = identities.pop()!; + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [[singleIdentity]]); - // insert a single identity - let singleIdentity = identities.pop()!; - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [[singleIdentity]]); + const oldIdentitiesCount = (await getIdentityAccounts(helper)).length; - const oldIdentitiesCount = (await getIdentityAccounts(helper)).length; + // change an identity and push it with a few new others + singleIdentity = [singleIdentity[0], {info: {display: {Raw: 'something special'}}}]; + identities.push(singleIdentity); + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identities]); - // change an identity and push it with a few new others - singleIdentity = [singleIdentity[0], {info: {display: {Raw: 'something special'}}}]; - identities.push(singleIdentity); - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identities]); + // oldIdentitiesCount + 9 because one identity is overwritten, not inserted on top + expect((await getIdentityAccounts(helper)).length).to.be.equal(oldIdentitiesCount + 9); + expect((await helper.callRpc('api.query.identity.identityOf', [singleIdentity[0]])).toHuman().info.display) + .to.be.deep.equal({Raw: 'something special'}); + }); - // oldIdentitiesCount + 9 because one identity is overwritten, not inserted on top - expect((await getIdentityAccounts(helper)).length).to.be.equal(oldIdentitiesCount + 9); - expect((await helper.callRpc('api.query.identity.identityOf', [singleIdentity[0]])).toHuman().info.display) - .to.be.deep.equal({Raw: 'something special'}); + itSub('Removes identities', async ({helper}) => { + const crowd = await helper.arrange.createCrowd(10, 0n, superuser); + const identities = crowd.map((acc, i) => [acc.address, {info: {display: {Raw: `accounter #${i}`}}}]); + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identities]); + const oldIdentities = await getIdentityAccounts(helper); + + // delete a couple, check that they are no longer there + const scapegoats = [crowd.pop()!.address, crowd.pop()!.address]; + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceRemoveIdentities', [scapegoats]); + const newIdentities = await getIdentityAccounts(helper); + expect(newIdentities.concat(scapegoats)).to.be.have.members(oldIdentities); + }); }); - itSub('Removes identities', async ({helper}) => { - const crowd = await helper.arrange.createCrowd(10, 0n, superuser); - const identities = crowd.map((acc, i) => [acc.address, {info: {display: {Raw: `accounter #${i}`}}}]); - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identities]); - const oldIdentities = await getIdentityAccounts(helper); + describe('Sub-identities', () => { + itSub('Sets subs', async ({helper}) => { + const crowdSize = 18; + const crowd = await helper.arrange.createCrowd(crowdSize, 0n, superuser); + const supers = [crowd.pop()!, crowd.pop()!, crowd.pop()!]; - // delete a couple, check that they are no longer there - const scapegoats = [crowd.pop()!.address, crowd.pop()!.address]; - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceRemoveIdentities', [scapegoats]); - const newIdentities = await getIdentityAccounts(helper); - expect(newIdentities.concat(scapegoats)).to.be.have.members(oldIdentities); + const subsPerSup = crowd.length / supers.length; + let subCount = 0; + const subs = [ + crowd.slice(subCount, subCount += subsPerSup + 1), + crowd.slice(subCount, subCount += subsPerSup), + crowd.slice(subCount, subCount += subsPerSup - 1), + ]; + + const subsInfo = supers.map((acc, i) => [ + acc.address, [ + 1000000n + BigInt(i + 1), + subs[i].map((sub, j) => [ + sub.address, {Raw: `accounter #${j}`}, + ]), + ], + ]); + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceSetSubs', [subsInfo]); + + for (let i = 0; i < supers.length; i++) { + // check deposit + expect(((await helper.getApi().query.identity.subsOf(supers[i].address)).toJSON() as any)[0]).to.be.equal(1000001 + i); + + const subsAccounts = await getSubIdentityAccounts(helper, supers[i].address); + // check sub-identities as account ids + expect(subsAccounts).to.include.members(subs[i].map(x => x.address)); + + for (let j = 0; j < subsAccounts.length; j++) { + // check sub-identities' names + expect((await getSubIdentityName(helper, subsAccounts[j]))[1]).to.be.deep.equal({Raw: `accounter #${j}`}); + } + } + }); + + itSub('Setting sub-identities does not delete other existing but does overwrite own', async ({helper}) => { + const crowdSize = 18; + const crowd = await helper.arrange.createCrowd(crowdSize, 0n, superuser); + const supers = [crowd.pop()!, crowd.pop()!, crowd.pop()!]; + + const subsPerSup = crowd.length / supers.length; + let subCount = 0; + const subs = [ + crowd.slice(subCount, subCount += subsPerSup + 1), + crowd.slice(subCount, subCount += subsPerSup), + crowd.slice(subCount, subCount += subsPerSup - 1), + ]; + + const subsInfo1 = supers.map((acc, i) => [ + acc.address, [ + 1000000n + BigInt(i + 1), + subs[i].map((sub, j) => [ + sub.address, {Raw: `accounter #${j}`}, + ]), + ], + ]); + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceSetSubs', [subsInfo1]); + + // change some sub-identities... + subs[2].pop(); subs[2].pop(); subs[2].push(...await helper.arrange.createAccounts([0n], superuser)); + + // ...and set them + const subsInfo2 = [[ + supers[2].address, [ + 999999n, + subs[2].map((sub, j) => [ + sub.address, {Raw: `discounter #${j}`}, + ]), + ], + ]]; + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceSetSubs', [subsInfo2]); + + // make sure everything else is the same + for (let i = 0; i < supers.length - 1; i++) { + // check deposit + expect(((await helper.getApi().query.identity.subsOf(supers[i].address)).toJSON() as any)[0]).to.be.equal(1000001 + i); + + const subsAccounts = await getSubIdentityAccounts(helper, supers[i].address); + // check sub-identities as account ids + expect(subsAccounts).to.include.members(subs[i].map(x => x.address)); + + for (let j = 0; j < subsAccounts; j++) { + // check sub-identities' names + expect((await getSubIdentityName(helper, subsAccounts[j]))[1]).to.be.deep.equal({Raw: `accounter #${j}`}); + } + } + + // check deposit + expect(((await helper.getApi().query.identity.subsOf(supers[2].address)).toJSON() as any)[0]).to.be.equal(999999); + + const subsAccounts = await getSubIdentityAccounts(helper, supers[2].address); + // check sub-identities as account ids + expect(subsAccounts).to.include.members(subs[2].map(x => x.address)); + + for (let j = 0; j < subsAccounts.length; j++) { + // check sub-identities' names + expect((await getSubIdentityName(helper, subsAccounts[j]))[1]).to.be.deep.equal({Raw: `discounter #${j}`}); + } + }); + + itSub('Removes sub-identities', async ({helper}) => { + const crowdSize = 3; + const crowd = await helper.arrange.createCrowd(crowdSize, 0n, superuser); + const sup = crowd.pop()!; + + const subsInfo1 = [[ + sup.address, [ + 1000000n, + crowd.map((sub, j) => [ + sub.address, {Raw: `accounter #${j}`}, + ]), + ], + ]]; + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceSetSubs', [subsInfo1]); + + // empty sub-identities should delete the records + const subsInfo2 = [[ + sup.address, [ + 1000000n, + [], + ], + ]]; + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceSetSubs', [subsInfo2]); + + // check deposit + expect((await helper.getApi().query.identity.subsOf(sup.address)).toHuman()).to.be.deep.equal(['0', []]); + + for (let j = 0; j < crowd.length; j++) { + // check sub-identities' names + expect((await getSubIdentityName(helper, crowd[j].address))).to.be.null; + } + }); + + itSub('Removing identities deletes associated sub-identities', async ({helper}) => { + const crowd = await helper.arrange.createCrowd(3, 0n, superuser); + const sup = crowd.pop()!; + + // insert identity + const identities = [[sup.address, {info: {display: {Raw: 'mental'}}}]]; + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identities]); + + // and its sub-identities + const subsInfo = [[ + sup.address, [ + 1000000n, + crowd.map((sub, j) => [ + sub.address, {Raw: `accounter #${j}`}, + ]), + ], + ]]; + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceSetSubs', [subsInfo]); + + // delete top identity + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceRemoveIdentities', [[sup.address]]); + + // check that sub-identities are deleted + expect((await helper.getApi().query.identity.subsOf(sup.address)).toHuman()).to.be.deep.equal(['0', []]); + + for (let j = 0; j < crowd.length; j++) { + // check sub-identities' names + expect((await getSubIdentityName(helper, crowd[j].address))).to.be.null; + } + }); }); after(async function() { --- a/tests/src/util/identitySetter.ts +++ b/tests/src/util/identitySetter.ts @@ -1,5 +1,8 @@ // Copyright 2019-2022 Unique Network (Gibraltar) Ltd. // SPDX-License-Identifier: Apache-2.0 +// +// Usage: `yarn setIdentities [relay WS URL] [parachain WS URL] [sudo key]` +// Example: `yarn setIdentities wss://polkadot-rpc.dwellir.com ws://localhost:9944 escape pattern miracle train sudden cart adapt embark wedding alien lamp mesh` import {encodeAddress} from '@polkadot/keyring'; import {usingPlaygrounds, Pallets} from './index'; @@ -9,36 +12,81 @@ const paraUrl = process.argv[3] ?? 'ws://localhost:9944'; const key = process.argv.length > 4 ? process.argv.slice(4).join(' ') : '//Alice'; +function extractAccountId(key: any): string { + return (key as any).toHuman()[0]; +} + function extractIdentity(key: any, value: any): [string, any] { - return [(key as any).toHuman()[0], (value as any).unwrap()]; + return [extractAccountId(key), (value as any).unwrap()]; } -async function getIdentities(helper: ChainHelperBase) { +async function getIdentities(helper: ChainHelperBase, noneCasePredicate?: (key: any, value: any) => void) { const identities: [string, any][] = []; - for(const [key, value] of await helper.getApi().query.identity.identityOf.entries()) + for(const [key, v] of await helper.getApi().query.identity.identityOf.entries()) { + const value = v as any; + if (value.isNone) { + if (noneCasePredicate) noneCasePredicate(key, value); + continue; + } identities.push(extractIdentity(key, value)); + } return identities; } -// This is a utility for pulling +function constructSubInfo(identityAccount: string, subQuery: any, supers: any[], ss58?: number): [string, any] { + const deposit = subQuery.toJSON()[0]; + const subIdentities = subQuery.toHuman()[1]; + subIdentities.map((sub: string) => supers.find((sup: any) => sup[0] === sub)); + // supers.find((x: any) => x[0] === subIdentities[0])![1].toHuman(); + return [ + encodeAddress(identityAccount, ss58), [ + deposit, + subIdentities.map((sub: string) => [ + encodeAddress(sub, ss58), + supers.find((sup: any) => sup[0] === sub)![1].toJSON()[1], + ]), + ], + ]; +} + +async function getSubs(helper: ChainHelperBase) { + return (await helper.getApi().query.identity.subsOf.entries()).map(([key, value]) => [extractAccountId(key), value as any]); +} + +async function getSupers(helper: ChainHelperBase) { + return (await helper.getApi().query.identity.superOf.entries()).map(([key, value]) => [extractAccountId(key), value as any]); +} + +// The utility for pulling identity and sub-identity data const forceInsertIdentities = async (): Promise => { const identitiesOnRelay: any[] = []; + const subsOnRelay: any[] = []; const identitiesToRemove: string[] = []; await usingPlaygrounds(async helper => { try { // iterate over every identity - for(const [key, v] of await helper.getApi().query.identity.identityOf.entries()) { - const value = v as any; - if (value.isNone) { - // in the nigh-impossible case that storage map would actually give None for a value, might as well delete it - identitiesToRemove.push((key as any).toHuman()[0]); - continue; - } - + for(const [key, value] of await getIdentities(helper, (key, _value) => identitiesToRemove.push((key as any).toHuman()[0]))) { // if any of the judgements resulted in a good confirmed outcome, keep this identity - if (value.unwrap().toHuman().judgements.filter((x: any) => x[1] == 'Reasonable' || x[1] == 'KnownGood').length == 0) continue; - identitiesOnRelay.push(extractIdentity(key, value)); + if (value.toHuman().judgements.filter((x: any) => x[1] == 'Reasonable' || x[1] == 'KnownGood').length == 0) continue; + identitiesOnRelay.push([key, value]); + } + + const sublessIdentities = [...identitiesOnRelay]; + const supersOfSubs = await getSupers(helper); + + // iterate over every sub-identity + for(const [key, value] of await getSubs(helper)) { + // only get subs of the identities interesting to us + const identityIndex = sublessIdentities.findIndex((x: any) => x[0] == key); + if (identityIndex == -1) continue; + sublessIdentities.splice(identityIndex, 1); + subsOnRelay.push(constructSubInfo(key, value, supersOfSubs)); } + + // mark the rest of sub-identities for deletion with empty arrays + /*for(const [account, _identity] of sublessIdentities) { + subsOnRelay.push([account, ['0', []]]); + }*/ } catch (error) { console.error(error); throw Error('Error during fetching identities'); @@ -60,7 +108,7 @@ const identity = paraIdentities.find(i => i[0] === encodedKey); if (identity) { // only update if the identity info does not exist or is changed - if (value.toString() === identity[1].toString()) { + if (JSON.stringify(value) === JSON.stringify(identity[1])) { continue; } } @@ -71,11 +119,36 @@ // identitiesToRemove.push((key as any).toHuman()[0]); } - // await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceRemoveIdentities', [identitiesToRemove]); - await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identitiesToAdd]); - console.log(`Tried to upload ${identitiesToAdd.length} identities ` - + `and found ${identitiesToRemove.length} identities for potential removal. ` - + `Now there are ${(await helper.getApi().query.identity.identityOf.keys()).length}.`); + const paraSubs = await getSubs(helper); + const supersOfSubs = await getSupers(helper); + const subsToUpdate: any[] = []; + + if (identitiesToRemove.length != 0) + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceRemoveIdentities', [identitiesToRemove]); + if (identitiesToAdd.length != 0) + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceInsertIdentities', [identitiesToAdd]); + + console.log(`Tried to upload ${identitiesToAdd.length} identities` + + ` and found ${identitiesToRemove.length} identities for potential removal.` + + ` Now there are ${(await helper.getApi().query.identity.identityOf.keys()).length}.`); + + for (const [key, value] of subsOnRelay) { + const encodedKey = encodeAddress(key, ss58Format); + const sub = paraSubs.find(i => i[0] === encodedKey); + if (sub) { + // only update if the sub-identity info does not exist or is changed + if (JSON.stringify(value) === JSON.stringify(constructSubInfo(sub[0], sub[1], supersOfSubs)[1])) { + continue; + } + } + subsToUpdate.push([key, value]); + } + + if (subsToUpdate.length != 0) + await helper.getSudo().executeExtrinsic(superuser, 'api.tx.identity.forceSetSubs', [subsToUpdate]); + + console.log(`Also tried to update ${subsToUpdate.length} identities with their sub-identities.` + + ` Now there are ${(await helper.getApi().query.identity.subsOf.keys()).length} identities with subs.`); } catch (error) { console.error(error); throw Error('Error during setting identities'); -- gitstuff