Skip to content

Commit 057d981

Browse files
Neopalliumadamdossa
authored andcommitted
MESH-1773 & MESH-1777 condition cost function (#1204)
* Improve ComplianceManager benchmarks. * Prevent a large number of empty asset compliance requirements from being added to an asset. * Code cleanup.
1 parent ced8e15 commit 057d981

File tree

6 files changed

+226
-42
lines changed

6 files changed

+226
-42
lines changed

pallets/common/src/traits/compliance_manager.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@
1515

1616
use core::result::Result;
1717
use frame_support::{dispatch::DispatchError, weights::Weight};
18-
use polymesh_primitives::{compliance_manager::AssetComplianceResult, Balance, IdentityId, Ticker};
18+
use polymesh_primitives::{
19+
compliance_manager::{AssetComplianceResult, ComplianceRequirement},
20+
condition::{conditions_total_counts, Condition},
21+
Balance, IdentityId, Ticker,
22+
};
1923

2024
pub trait Config {
2125
fn verify_restriction(
@@ -42,4 +46,33 @@ pub trait WeightInfo {
4246
fn change_compliance_requirement(s: u32, r: u32) -> Weight;
4347
fn replace_asset_compliance(c: u32) -> Weight;
4448
fn reset_asset_compliance() -> Weight;
49+
50+
fn condition_costs(conditions: u32, claims: u32, issuers: u32, claim_types: u32) -> Weight;
51+
52+
fn add_compliance_requirement_full(sender: &[Condition], receiver: &[Condition]) -> Weight {
53+
let (_, claims, issuers, claim_types) =
54+
conditions_total_counts(sender.iter().chain(receiver.iter()));
55+
Self::add_compliance_requirement(sender.len() as u32, receiver.len() as u32)
56+
.saturating_add(Self::condition_costs(0, claims, issuers, claim_types))
57+
}
58+
59+
fn change_compliance_requirement_full(req: &ComplianceRequirement) -> Weight {
60+
let (_, claims, issuers, claim_types) = req.counts();
61+
Self::change_compliance_requirement(
62+
req.sender_conditions.len() as u32,
63+
req.receiver_conditions.len() as u32,
64+
)
65+
.saturating_add(Self::condition_costs(0, claims, issuers, claim_types))
66+
}
67+
68+
fn replace_asset_compliance_full(reqs: &[ComplianceRequirement]) -> Weight {
69+
let (conditions, claims, issuers, claim_types) =
70+
conditions_total_counts(reqs.iter().flat_map(|req| req.conditions()));
71+
Self::replace_asset_compliance(reqs.len() as u32).saturating_add(Self::condition_costs(
72+
conditions,
73+
claims,
74+
issuers,
75+
claim_types,
76+
))
77+
}
4578
}

pallets/compliance-manager/src/benchmarking.rs

Lines changed: 92 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,55 @@ use polymesh_common_utilities::{
2121
benchs::{AccountIdOf, User, UserBuilder},
2222
TestUtilsFn,
2323
};
24-
use polymesh_primitives::{asset::AssetType, TrustedFor, TrustedIssuer};
24+
use polymesh_primitives::{asset::AssetType, ClaimType, Scope, TrustedFor, TrustedIssuer};
25+
use sp_std::convert::TryFrom;
2526

2627
const MAX_DEFAULT_TRUSTED_CLAIM_ISSUERS: u32 = 3;
2728
const MAX_TRUSTED_ISSUER_PER_CONDITION: u32 = 3;
2829
const MAX_SENDER_CONDITIONS_PER_COMPLIANCE: u32 = 3;
2930
const MAX_RECEIVER_CONDITIONS_PER_COMPLIANCE: u32 = 3;
3031
const MAX_COMPLIANCE_REQUIREMENTS: u32 = 2;
3132

33+
const MAX_CONDITIONS: u32 = 10;
34+
const MAX_CONDITION_TYPE_CLAIMS: u32 = 10;
35+
const MAX_CONDITION_ISSUERS: u32 = 10;
36+
const MAX_CONDITION_ISSUER_CLAIM_TYPES: u32 = 10;
37+
38+
const CLAIM_TYPES: &[ClaimType] = &[
39+
ClaimType::Accredited,
40+
ClaimType::Affiliate,
41+
ClaimType::BuyLockup,
42+
ClaimType::SellLockup,
43+
ClaimType::CustomerDueDiligence,
44+
ClaimType::KnowYourCustomer,
45+
ClaimType::Jurisdiction,
46+
ClaimType::Exempted,
47+
ClaimType::Blocked,
48+
ClaimType::InvestorUniqueness,
49+
ClaimType::NoType,
50+
ClaimType::InvestorUniquenessV2,
51+
];
52+
3253
/// Create a token issuer trusted for `Any`.
33-
pub fn make_issuer<T: IdentityConfig + TestUtilsFn<AccountIdOf<T>>>(id: u32) -> TrustedIssuer {
54+
pub fn make_issuer<T: IdentityConfig + TestUtilsFn<AccountIdOf<T>>>(
55+
id: u32,
56+
claim_type_len: Option<usize>,
57+
) -> TrustedIssuer {
3458
let u = UserBuilder::<T>::default()
3559
.generate_did()
3660
.seed(id)
3761
.build("ISSUER");
3862
TrustedIssuer {
3963
issuer: IdentityId::from(u.did.unwrap()),
40-
trusted_for: TrustedFor::Any,
64+
trusted_for: match claim_type_len {
65+
None => TrustedFor::Any,
66+
Some(len) => TrustedFor::Specific(
67+
(0..len)
68+
.into_iter()
69+
.map(|idx| CLAIM_TYPES[idx % CLAIM_TYPES.len()])
70+
.collect(),
71+
),
72+
},
4173
}
4274
}
4375

@@ -46,16 +78,29 @@ pub fn make_issuer<T: IdentityConfig + TestUtilsFn<AccountIdOf<T>>>(id: u32) ->
4678
/// - It could have more complexity if `TrustedIssuer::trusted_for` is a vector but not on
4779
/// benchmarking of add/remove. That could be useful for benchmarking executions/evaluation of
4880
/// complience requiriments.
49-
pub fn make_issuers<T: IdentityConfig + TestUtilsFn<AccountIdOf<T>>>(s: u32) -> Vec<TrustedIssuer> {
50-
(0..s).map(|i| make_issuer::<T>(i)).collect::<Vec<_>>()
81+
pub fn make_issuers<T: IdentityConfig + TestUtilsFn<AccountIdOf<T>>>(
82+
s: u32,
83+
claim_type_len: Option<usize>,
84+
) -> Vec<TrustedIssuer> {
85+
(0..s)
86+
.map(|i| make_issuer::<T>(i, claim_type_len))
87+
.collect()
5188
}
5289

5390
/// Create simple conditions with a variable number of `issuers`.
54-
pub fn make_conditions(s: u32, issuers: &Vec<TrustedIssuer>) -> Vec<Condition> {
91+
pub fn make_conditions(s: u32, claims: Option<usize>, issuers: &[TrustedIssuer]) -> Vec<Condition> {
5592
(0..s)
5693
.map(|_| Condition {
57-
condition_type: ConditionType::IsPresent(Claim::NoData),
58-
issuers: issuers.clone(),
94+
condition_type: match claims {
95+
None => ConditionType::IsPresent(Claim::NoData),
96+
Some(len) => ConditionType::IsAnyOf(
97+
(0..len)
98+
.into_iter()
99+
.map(|_| Claim::Blocked(Scope::Custom(vec![0])))
100+
.collect(),
101+
),
102+
},
103+
issuers: issuers.to_vec(),
59104
})
60105
.collect()
61106
}
@@ -103,7 +148,7 @@ struct ComplianceRequirementInfo<T: Config> {
103148

104149
impl<T: Config + TestUtilsFn<AccountIdOf<T>>> ComplianceRequirementInfo<T> {
105150
pub fn add_default_trusted_claim_issuer(self: &Self, i: u32) {
106-
make_issuers::<T>(i).into_iter().for_each(|issuer| {
151+
make_issuers::<T>(i, None).into_iter().for_each(|issuer| {
107152
Module::<T>::add_default_trusted_claim_issuer(
108153
self.owner.origin.clone().into(),
109154
self.ticker.clone(),
@@ -130,9 +175,9 @@ impl<T: Config + TestUtilsFn<AccountIdOf<T>>> ComplianceRequirementBuilder<T> {
130175
let ticker = make_token::<T>(&owner, b"1".to_vec());
131176

132177
// Create issuers (i) and conditions(s & r).
133-
let issuers = make_issuers::<T>(trusted_issuer_count);
134-
let sender_conditions = make_conditions(sender_conditions_count, &issuers);
135-
let receiver_conditions = make_conditions(receiver_conditions_count, &issuers);
178+
let issuers = make_issuers::<T>(trusted_issuer_count, None);
179+
let sender_conditions = make_conditions(sender_conditions_count, None, &issuers);
180+
let receiver_conditions = make_conditions(receiver_conditions_count, None, &issuers);
136181

137182
let info = ComplianceRequirementInfo {
138183
owner,
@@ -168,9 +213,40 @@ impl<T: Config> ComplianceRequirementBuilder<T> {
168213
}
169214
}
170215

216+
fn setup_conditions_bench<T: Config + TestUtilsFn<AccountIdOf<T>>>(
217+
conditions: u32,
218+
claims: u32,
219+
issuers: u32,
220+
claim_types: u32,
221+
) -> Vec<Condition> {
222+
let issuers = make_issuers::<T>(issuers, Some(claim_types as usize));
223+
let conditions = make_conditions(conditions, Some(claims as usize), &issuers);
224+
conditions
225+
}
226+
227+
fn conditions_bench(conditions: Vec<Condition>) {
228+
let encoded = conditions.encode();
229+
let decoded = Vec::<Condition>::decode(&mut encoded.as_slice())
230+
.expect("This shouldn't fail since we just encoded a `Vec<Condition>` value.");
231+
if !conditions.eq(&decoded) {
232+
panic!("This shouldn't fail.");
233+
}
234+
}
235+
171236
benchmarks! {
172237
where_clause { where T: TestUtilsFn<AccountIdOf<T>> }
173238

239+
condition_costs {
240+
let a in 1..MAX_CONDITIONS;
241+
let b in 1..MAX_CONDITION_TYPE_CLAIMS;
242+
let c in 1..MAX_CONDITION_ISSUERS;
243+
let d in 1..MAX_CONDITION_ISSUER_CLAIM_TYPES;
244+
245+
let conditions = setup_conditions_bench::<T>(a, b, c, d);
246+
}: {
247+
conditions_bench(conditions);
248+
}
249+
174250
add_compliance_requirement {
175251
// INTERNAL: This benchmark only evaluate the adding operation. Its execution should be measured in another module.
176252
let s in 1..MAX_SENDER_CONDITIONS_PER_COMPLIANCE;
@@ -237,7 +313,7 @@ benchmarks! {
237313
d.add_default_trusted_claim_issuer(MAX_DEFAULT_TRUSTED_CLAIM_ISSUERS -1);
238314

239315
// Add one more for benchmarking.
240-
let new_issuer = make_issuer::<T>(MAX_DEFAULT_TRUSTED_CLAIM_ISSUERS);
316+
let new_issuer = make_issuer::<T>(MAX_DEFAULT_TRUSTED_CLAIM_ISSUERS, None);
241317
}: _(d.owner.origin, d.ticker, new_issuer.clone())
242318
verify {
243319
let trusted_issuers = Module::<T>::trusted_claim_issuer(d.ticker);
@@ -300,9 +376,9 @@ benchmarks! {
300376
MAX_RECEIVER_CONDITIONS_PER_COMPLIANCE)
301377
.add_compliance_requirement().build();
302378

303-
let issuers = make_issuers::<T>(MAX_TRUSTED_ISSUER_PER_CONDITION);
304-
let sender_conditions = make_conditions(MAX_SENDER_CONDITIONS_PER_COMPLIANCE, &issuers);
305-
let receiver_conditions = make_conditions(MAX_RECEIVER_CONDITIONS_PER_COMPLIANCE, &issuers);
379+
let issuers = make_issuers::<T>(MAX_TRUSTED_ISSUER_PER_CONDITION, None);
380+
let sender_conditions = make_conditions(MAX_SENDER_CONDITIONS_PER_COMPLIANCE, None, &issuers);
381+
let receiver_conditions = make_conditions(MAX_RECEIVER_CONDITIONS_PER_COMPLIANCE, None, &issuers);
306382

307383
// Add more requirements to the asset, if `c > 1`.
308384
(1..c).for_each( |_i| {

pallets/compliance-manager/src/lib.rs

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,7 @@ use polymesh_primitives::{
103103
proposition, storage_migration_ver, Balance, Claim, Condition, ConditionType, Context,
104104
IdentityId, Ticker, TrustedFor, TrustedIssuer,
105105
};
106-
use sp_std::{
107-
convert::{From, TryFrom},
108-
prelude::*,
109-
};
106+
use sp_std::{convert::From, prelude::*};
110107

111108
type ExternalAgents<T> = pallet_external_agents::Module<T>;
112109
type Identity<T> = pallet_identity::Module<T>;
@@ -190,7 +187,7 @@ decl_module! {
190187
///
191188
/// # Permissions
192189
/// * Asset
193-
#[weight = <T as Config>::WeightInfo::add_compliance_requirement(sender_conditions.len() as u32, receiver_conditions.len() as u32)]
190+
#[weight = <T as Config>::WeightInfo::add_compliance_requirement_full(&sender_conditions, &receiver_conditions)]
194191
pub fn add_compliance_requirement(origin, ticker: Ticker, sender_conditions: Vec<Condition>, receiver_conditions: Vec<Condition>) {
195192
let did = <ExternalAgents<T>>::ensure_perms(origin, ticker)?;
196193

@@ -257,7 +254,7 @@ decl_module! {
257254
///
258255
/// # Permissions
259256
/// * Asset
260-
#[weight = <T as Config>::WeightInfo::replace_asset_compliance(asset_compliance.len() as u32)]
257+
#[weight = <T as Config>::WeightInfo::replace_asset_compliance_full(&asset_compliance)]
261258
pub fn replace_asset_compliance(origin, ticker: Ticker, asset_compliance: Vec<ComplianceRequirement>) {
262259
let did = <ExternalAgents<T>>::ensure_perms(origin, ticker)?;
263260

@@ -392,10 +389,7 @@ decl_module! {
392389
///
393390
/// # Permissions
394391
/// * Asset
395-
#[weight = <T as Config>::WeightInfo::change_compliance_requirement(
396-
new_req.sender_conditions.len() as u32,
397-
new_req.receiver_conditions.len() as u32,
398-
)]
392+
#[weight = <T as Config>::WeightInfo::change_compliance_requirement_full(&new_req)]
399393
pub fn change_compliance_requirement(origin, ticker: Ticker, new_req: ComplianceRequirement) {
400394
let did = <ExternalAgents<T>>::ensure_perms(origin, ticker)?;
401395

@@ -601,17 +595,15 @@ impl<T: Config> Module<T> {
601595
let complexity = asset_compliance
602596
.iter()
603597
.flat_map(|req| req.conditions())
604-
.fold(0usize, |complexity, condition| {
605-
let (claims, issuers) = condition.complexity();
606-
complexity.saturating_add(claims.saturating_mul(match issuers {
607-
0 => default_issuer_count,
608-
_ => issuers,
609-
}))
610-
});
611-
if let Ok(complexity_u32) = u32::try_from(complexity) {
612-
if complexity_u32 <= T::MaxConditionComplexity::get() {
613-
return Ok(());
614-
}
598+
.fold(0u32, |total, condition| {
599+
let complexity = condition.complexity(default_issuer_count);
600+
total.saturating_add(complexity)
601+
})
602+
// NB: If the compliance requirements are empty (0 complexity),
603+
// then use the count of requirements.
604+
.max(asset_compliance.len() as u32);
605+
if complexity <= T::MaxConditionComplexity::get() {
606+
return Ok(());
615607
}
616608
Err(Error::<T>::ComplianceRequirementTooComplex.into())
617609
}

pallets/weights/src/pallet_compliance_manager.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,17 @@ use polymesh_runtime_common::{RocksDbWeight as DbWeight, Weight};
5252
/// Weights for pallet_compliance_manager using the Substrate node and recommended hardware.
5353
pub struct WeightInfo;
5454
impl pallet_compliance_manager::WeightInfo for WeightInfo {
55+
fn condition_costs(a: u32, b: u32, c: u32, d: u32) -> Weight {
56+
(0 as Weight)
57+
// Standard Error: 154_000
58+
.saturating_add((13_470_000 as Weight).saturating_mul(a as Weight))
59+
// Standard Error: 154_000
60+
.saturating_add((5_972_000 as Weight).saturating_mul(b as Weight))
61+
// Standard Error: 154_000
62+
.saturating_add((6_144_000 as Weight).saturating_mul(c as Weight))
63+
// Standard Error: 154_000
64+
.saturating_add((906_000 as Weight).saturating_mul(d as Weight))
65+
}
5566
fn add_compliance_requirement(s: u32, r: u32) -> Weight {
5667
(98_941_000 as Weight)
5768
// Standard Error: 740_000

primitives/src/compliance_manager.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
// You should have received a copy of the GNU General Public License
1414
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1515

16-
use crate::Condition;
16+
use crate::condition::{conditions_total_counts, Condition};
1717
use codec::{Decode, Encode};
1818
#[cfg(feature = "std")]
1919
use sp_runtime::{Deserialize, Serialize};
@@ -51,6 +51,13 @@ impl ComplianceRequirement {
5151
.iter()
5252
.chain(self.receiver_conditions.iter())
5353
}
54+
55+
/// Return the total number of conditions, claims, issuers, and claim_types.
56+
///
57+
/// This is used for weight calculation.
58+
pub fn counts(&self) -> (u32, u32, u32, u32) {
59+
conditions_total_counts(self.conditions())
60+
}
5461
}
5562

5663
/// A compliance requirement along with its evaluation result

0 commit comments

Comments
 (0)