Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 1000i100-test
  • 105_gitlab_container_registry
  • cgeek/issue-297-cpu
  • ci_cache
  • debug/podman
  • elois-compose-metrics
  • elois-duniter-storage
  • elois-smoldot
  • feature/dc-dump
  • feature/distance-rule
  • feature/show_milestone
  • fix-252
  • fix_picked_up_file_in_runtime_release
  • gdev-800-tests
  • hugo-release/runtime-701
  • hugo-tmp-dockerfile-cache
  • hugo/195-doc
  • hugo/195-graphql-schema
  • hugo/distance-precompute
  • hugo/endpoint-gossip
  • hugo/tmp-0.9.1
  • master
  • network/gdev-800
  • network/gdev-802
  • network/gdev-803
  • network/gdev-900
  • network/gtest-1000
  • pini-check-password
  • release/client-800.2
  • release/hugo-chainspec-gdev5
  • release/poka-chainspec-gdev5
  • release/poka-chainspec-gdev5-pini-docker
  • release/runtime-100
  • release/runtime-200
  • release/runtime-300
  • release/runtime-400
  • release/runtime-401
  • release/runtime-500
  • release/runtime-600
  • release/runtime-700
  • release/runtime-701
  • release/runtime-800
  • runtime/gtest-1000
  • tests/distance-with-oracle
  • tuxmain/anonymous-tx
  • tuxmain/benchmark-distance
  • tuxmain/fix-change-owner-key
  • update-docker-compose-rpc-squid-names
  • upgradable-multisig
  • gdev-800
  • gdev-800-0.8.0
  • gdev-802
  • gdev-803
  • gdev-900-0.10.0
  • gdev-900-0.10.1
  • gdev-900-0.9.0
  • gdev-900-0.9.1
  • gdev-900-0.9.2
  • gtest-1000
  • gtest-1000-0.11.0
  • gtest-1000-0.11.1
  • runtime-100
  • runtime-101
  • runtime-102
  • runtime-103
  • runtime-104
  • runtime-105
  • runtime-200
  • runtime-201
  • runtime-300
  • runtime-301
  • runtime-302
  • runtime-303
  • runtime-400
  • runtime-401
  • runtime-500
  • runtime-600
  • runtime-700
  • runtime-701
  • runtime-800
  • runtime-800-backup
  • runtime-800-bis
  • runtime-801
  • v0.1.0
  • v0.2.0
  • v0.3.0
  • v0.4.0
  • v0.4.1
88 results

Target

Select target project
  • nodes/rust/duniter-v2s
  • llaq/lc-core-substrate
  • pini-gh/duniter-v2s
  • vincentux/duniter-v2s
  • mildred/duniter-v2s
  • d0p1/duniter-v2s
  • bgallois/duniter-v2s
  • Nicolas80/duniter-v2s
8 results
Select Git revision
  • archive_upgrade_polkadot_v0.9.42
  • david-wot-scenarios-cucumber
  • distance
  • elois-ci-binary-release
  • elois-compose-metrics
  • elois-duniter-storage
  • elois-fix-85
  • elois-fix-idty-post-genesis
  • elois-fix-sufficients-change-owner-key
  • elois-opti-cert
  • elois-remove-renewable-period
  • elois-revoc-with-old-key
  • elois-rework-certs
  • elois-smish-members-cant-change-or-rem-idty
  • elois-smoldot
  • elois-substrate-v0.9.23
  • elois-technical-commitee
  • hugo-gtest
  • hugo-remove-duniter-account
  • hugo-rework-genesis
  • hugo-tmp
  • jrx/workspace_tomls
  • master
  • no-bootnodes
  • pallet-benchmark
  • release/poka-chainspec-gdev5
  • release/poka-chainspec-gdev5-pini-docker
  • release/runtime-100
  • release/runtime-200
  • release/runtime-300
  • release/runtime-400
  • test-gen-new-owner-key-msg
  • ts-types
  • ud-time-64
  • upgrade_polkadot_v0.9.42
  • runtime-100
  • runtime-101
  • runtime-102
  • runtime-103
  • runtime-104
  • runtime-105
  • runtime-200
  • runtime-201
  • runtime-300
  • runtime-301
  • runtime-302
  • runtime-303
  • runtime-400
  • v0.1.0
  • v0.2.0
  • v0.3.0
  • v0.4.0
52 results
Show changes
Showing
with 3010 additions and 504 deletions
[package] [package]
authors = ['librelois <c@elo.tf>'] authors.workspace = true
description = 'FRAME pallet certification.' description = "duniter pallet certification"
edition = "2021" edition.workspace = true
homepage = 'https://duniter.org' homepage.workspace = true
license = 'AGPL-3.0' license.workspace = true
name = 'pallet-certification' name = "pallet-certification"
readme = 'README.md' repository.workspace = true
repository = 'https://git.duniter.org/nodes/rust/duniter-v2s' version.workspace = true
version = '3.0.0'
[features] [features]
default = ['std'] default = ["std"]
runtime-benchmarks = ['frame-benchmarking'] runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
std = [ std = [
'codec/std', "codec/std",
'frame-support/std', "duniter-primitives/std",
'frame-system/std', "frame-benchmarking?/std",
'frame-benchmarking/std', "frame-support/std",
'serde', "frame-system/std",
'sp-core/std', "scale-info/std",
'sp-runtime/std', "sp-core/std",
'sp-std/std', "sp-io/std",
"sp-keystore/std",
"sp-runtime/std",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"sp-runtime/try-runtime",
] ]
try-runtime = ['frame-support/try-runtime']
[dependencies]
# substrate
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
[dependencies.codec]
default-features = false
features = ['derive']
package = 'parity-scale-codec'
version = "3.1.5"
[dependencies.frame-benchmarking]
default-features = false
git = 'https://github.com/duniter/substrate'
optional = true
branch = 'duniter-substrate-v0.9.32'
[dependencies.frame-support]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.frame-system]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.serde]
version = "1.0.101"
optional = true
features = ["derive"]
[dependencies.sp-core]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.sp-runtime]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.sp-std]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
### DOC ###
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ['x86_64-unknown-linux-gnu']
[dev-dependencies.serde]
version = '1.0.119'
### DEV ###
[dev-dependencies.maplit]
version = '1.0.2'
[dev-dependencies.sp-io]
default-features = false default-features = false
git = 'https://github.com/duniter/substrate' targets = ["x86_64-unknown-linux-gnu"]
branch = 'duniter-substrate-v0.9.32'
[dependencies]
codec = { workspace = true, features = ["derive"] }
duniter-primitives = { workspace = true }
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
sp-core = { workspace = true }
sp-runtime = { workspace = true }
[dev-dependencies]
maplit = { workspace = true, default-features = true }
sp-io = { workspace = true, default-features = true }
sp-keystore = { workspace = true, default-features = true }
# Duniter certification pallet
Duniter certifications are the *edges* in the Duniter [Web of Trust](../duniter-wot/). They can have different meanings:
- in the case of the main WoT, they mean "I have met this person IRL and trust them" (see Ğ1 Licence)
- in the case of the smith sub-WoT, they mean "I trust this person to be able to run Duniter securely" (see smith Licence)
This pallet manages certifications creation, deletion...
\ No newline at end of file
// Copyright 2021-2023 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use frame_benchmarking::v2::*;
use frame_system::RawOrigin;
use sp_runtime::traits::Zero;
use crate::Pallet;
#[benchmarks(
where
T::IdtyIndex: From<u32>,
)]
mod benchmarks {
use super::*;
fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
frame_system::Pallet::<T>::assert_has_event(generic_event.into());
}
fn add_certs<T: Config>(i: u32, receiver: T::IdtyIndex) -> Result<(), &'static str> {
Pallet::<T>::remove_all_certs_received_by(RawOrigin::Root.into(), receiver)?;
for j in 1..i + 1 {
Pallet::<T>::do_add_cert_checked(j.into(), receiver, false)?;
}
assert!(
CertsByReceiver::<T>::get(receiver).len() as u32 == i,
"Certs not added",
);
Ok(())
}
#[benchmark]
fn add_cert() -> Result<(), BenchmarkError> {
let issuer: T::IdtyIndex = 1.into();
let caller: T::AccountId = T::IdtyAttr::owner_key(issuer).unwrap();
let receiver: T::IdtyIndex = 2.into();
Pallet::<T>::del_cert(RawOrigin::Root.into(), issuer, receiver)?;
frame_system::pallet::Pallet::<T>::set_block_number(T::CertPeriod::get());
#[extrinsic_call]
_(RawOrigin::Signed(caller), receiver);
assert_has_event::<T>(Event::<T>::CertAdded { issuer, receiver }.into());
Ok(())
}
#[benchmark]
fn renew_cert() -> Result<(), BenchmarkError> {
let issuer: T::IdtyIndex = 1.into();
let caller: T::AccountId = T::IdtyAttr::owner_key(issuer).unwrap();
let receiver: T::IdtyIndex = 2.into();
Pallet::<T>::del_cert(RawOrigin::Root.into(), issuer, receiver)?;
frame_system::pallet::Pallet::<T>::set_block_number(T::CertPeriod::get());
Pallet::<T>::add_cert(RawOrigin::Signed(caller.clone()).into(), receiver)?;
frame_system::pallet::Pallet::<T>::set_block_number(
T::CertPeriod::get() + T::CertPeriod::get(),
);
#[extrinsic_call]
_(RawOrigin::Signed(caller), receiver);
assert_has_event::<T>(Event::<T>::CertAdded { issuer, receiver }.into());
Ok(())
}
#[benchmark]
fn del_cert() {
let issuer: T::IdtyIndex = 1.into();
let receiver: T::IdtyIndex = 2.into();
// try to add cert if missing, else ignore
// this depends on initial data
let _ = Pallet::<T>::do_add_cert_checked(issuer, receiver, false);
#[extrinsic_call]
_(RawOrigin::Root, issuer, receiver);
assert_has_event::<T>(
Event::<T>::CertRemoved {
issuer,
receiver,
expiration: false,
}
.into(),
);
}
#[benchmark]
fn remove_all_certs_received_by(i: Linear<2, 1_000>) -> Result<(), BenchmarkError> {
let receiver: T::IdtyIndex = 0.into();
add_certs::<T>(i, receiver)?;
#[extrinsic_call]
_(RawOrigin::Root, receiver);
assert!(CertsByReceiver::<T>::get(receiver).is_empty());
Ok(())
}
#[benchmark]
fn on_initialize() {
assert!(CertsRemovableOn::<T>::try_get(BlockNumberFor::<T>::zero()).is_err());
#[block]
{
Pallet::<T>::on_initialize(BlockNumberFor::<T>::zero());
}
}
#[benchmark]
fn do_remove_cert_noop() {
#[block]
{
Pallet::<T>::do_remove_cert(100.into(), 101.into(), Some(BlockNumberFor::<T>::zero()));
}
}
#[benchmark]
fn do_remove_cert() -> Result<(), BenchmarkError> {
let issuer: T::IdtyIndex = 1.into();
let receiver: T::IdtyIndex = 0.into();
Pallet::<T>::do_remove_cert(issuer, receiver, None);
Pallet::<T>::do_add_cert_checked(issuer, receiver, false)?;
let block_number = T::ValidityPeriod::get();
frame_system::pallet::Pallet::<T>::set_block_number(block_number);
#[block]
{
Pallet::<T>::do_remove_cert(issuer, receiver, Some(block_number));
}
assert_has_event::<T>(
Event::<T>::CertRemoved {
issuer,
receiver,
expiration: true,
}
.into(),
);
Ok(())
}
#[benchmark]
fn do_remove_all_certs_received_by(i: Linear<2, 100>) -> Result<(), BenchmarkError> {
let receiver: T::IdtyIndex = 0.into();
add_certs::<T>(i, receiver)?;
#[block]
{
Pallet::<T>::do_remove_all_certs_received_by(receiver);
}
for issuer in 1..i + 1 {
assert_has_event::<T>(
Event::<T>::CertRemoved {
issuer: issuer.into(),
receiver,
expiration: false,
}
.into(),
);
}
Ok(())
}
impl_benchmark_test_suite!(
Pallet,
crate::mock::new_test_ext(crate::mock::DefaultCertificationConfig {
apply_cert_period_at_genesis: true,
certs_by_receiver: maplit::btreemap![
0 => maplit::btreemap![
1 => Some(7),
2 => Some(9),
],
1 => maplit::btreemap![
0 => Some(10),
2 => Some(3),
],
],
}),
crate::mock::Test
);
}
...@@ -14,10 +14,22 @@ ...@@ -14,10 +14,22 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
//! # Duniter Certification Pallet
//!
//! This pallet manages certification creation and deletion.
//!
//! Duniter certifications are the *edges* in the Duniter [Web of Trust](../duniter-wot/). They can have different meanings:
//!
//! - In the case of the main WoT, they mean "I have met this person in real life and trust them" (see Ğ1 Licence).
//! - In the case of the smith sub-WoT, they mean "I trust this person to be able to run Duniter securely" (see smith Licence).
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
pub mod benchmarking;
pub mod traits; pub mod traits;
mod types; mod types;
pub mod weights;
#[cfg(test)] #[cfg(test)]
mod mock; mod mock;
...@@ -25,38 +37,40 @@ mod mock; ...@@ -25,38 +37,40 @@ mod mock;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use frame_system::pallet_prelude::BlockNumberFor;
pub use pallet::*; pub use pallet::*;
pub use types::*; pub use types::*;
pub use weights::WeightInfo;
use crate::traits::*; use crate::traits::*;
use codec::Codec; use codec::Codec;
use frame_support::pallet_prelude::*; use duniter_primitives::Idty;
use frame_support::traits::StorageVersion; use frame_support::{pallet_prelude::*, traits::StorageVersion};
use scale_info::prelude::{collections::BTreeMap, fmt::Debug, vec::Vec};
use sp_runtime::traits::AtLeast32BitUnsigned; use sp_runtime::traits::AtLeast32BitUnsigned;
use sp_std::{fmt::Debug, vec::Vec};
#[allow(unreachable_patterns)]
#[frame_support::pallet] #[frame_support::pallet]
pub mod pallet { pub mod pallet {
use super::*; use super::*;
use frame_system::pallet_prelude::*; use frame_system::pallet_prelude::*;
use sp_runtime::traits::{Convert, Saturating}; use sp_runtime::traits::Saturating;
use sp_std::collections::btree_map::BTreeMap;
/// The current storage version. /// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet] #[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::storage_version(STORAGE_VERSION)] #[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info] #[pallet::without_storage_info]
pub struct Pallet<T, I = ()>(PhantomData<(T, I)>); pub struct Pallet<T>(PhantomData<T>);
#[pallet::config] #[pallet::config]
pub trait Config<I: 'static = ()>: frame_system::Config { pub trait Config: frame_system::Config {
/// The minimum duration (in blocks) between two certifications issued by the same issuer.
#[pallet::constant] #[pallet::constant]
/// Minimum duration between two certifications issued by the same issuer type CertPeriod: Get<BlockNumberFor<Self>>;
type CertPeriod: Get<Self::BlockNumber>;
/// A short identity index. /// A short identity index type.
type IdtyIndex: Parameter type IdtyIndex: Parameter
+ Member + Member
+ AtLeast32BitUnsigned + AtLeast32BitUnsigned
...@@ -66,40 +80,50 @@ pub mod pallet { ...@@ -66,40 +80,50 @@ pub mod pallet {
+ MaybeSerializeDeserialize + MaybeSerializeDeserialize
+ Debug + Debug
+ MaxEncodedLen; + MaxEncodedLen;
/// Something that give the owner key of an identity
type OwnerKeyOf: Convert<Self::IdtyIndex, Option<Self::AccountId>>; /// A type that provides methods to get the IdtyIndex of an AccountId and vice versa.
/// type IdtyAttr: duniter_primitives::Idty<Self::IdtyIndex, Self::AccountId>;
/// A type that provides a method to check if issuing a certification is allowed.
type CheckCertAllowed: CheckCertAllowed<Self::IdtyIndex>; type CheckCertAllowed: CheckCertAllowed<Self::IdtyIndex>;
/// The maximum number of active certifications that can be issued by a single issuer.
#[pallet::constant] #[pallet::constant]
/// Maximum number of active certifications by issuer
type MaxByIssuer: Get<u32>; type MaxByIssuer: Get<u32>;
/// Minimum number of certifications that must be received to be able to issue
/// certifications. /// The minimum number of certifications received that an identity must have
/// to be allowed to issue a certification.
#[pallet::constant]
type MinReceivedCertToBeAbleToIssueCert: Get<u32>; type MinReceivedCertToBeAbleToIssueCert: Get<u32>;
/// Handler for NewCert event
/// A handler that is called when a new certification event (`NewCert`) occurs.
type OnNewcert: OnNewcert<Self::IdtyIndex>; type OnNewcert: OnNewcert<Self::IdtyIndex>;
/// Handler for Removed event
/// A handler that is called when a certification is removed (`RemovedCert`).
type OnRemovedCert: OnRemovedCert<Self::IdtyIndex>; type OnRemovedCert: OnRemovedCert<Self::IdtyIndex>;
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type RuntimeEvent: From<Event<Self, I>> /// The overarching event type.
+ IsType<<Self as frame_system::Config>::RuntimeEvent>; type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Type representing the weight of this pallet
type WeightInfo: WeightInfo;
/// The duration (in blocks) for which a certification remains valid.
#[pallet::constant] #[pallet::constant]
/// Duration of validity of a certification type ValidityPeriod: Get<BlockNumberFor<Self>>;
type ValidityPeriod: Get<Self::BlockNumber>;
} }
// GENESIS STUFF // // GENESIS STUFF //
#[pallet::genesis_config] #[pallet::genesis_config]
#[allow(clippy::type_complexity)] #[allow(clippy::type_complexity)]
pub struct GenesisConfig<T: Config<I>, I: 'static = ()> { pub struct GenesisConfig<T: Config> {
pub apply_cert_period_at_genesis: bool, pub apply_cert_period_at_genesis: bool,
pub certs_by_receiver: pub certs_by_receiver:
BTreeMap<T::IdtyIndex, BTreeMap<T::IdtyIndex, Option<T::BlockNumber>>>, BTreeMap<T::IdtyIndex, BTreeMap<T::IdtyIndex, Option<BlockNumberFor<T>>>>,
} }
#[cfg(feature = "std")] impl<T: Config> Default for GenesisConfig<T> {
impl<T: Config<I>, I: 'static> Default for GenesisConfig<T, I> {
fn default() -> Self { fn default() -> Self {
Self { Self {
apply_cert_period_at_genesis: false, apply_cert_period_at_genesis: false,
...@@ -109,12 +133,12 @@ pub mod pallet { ...@@ -109,12 +133,12 @@ pub mod pallet {
} }
#[pallet::genesis_build] #[pallet::genesis_build]
impl<T: Config<I>, I: 'static> GenesisBuild<T, I> for GenesisConfig<T, I> { impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) { fn build(&self) {
let mut cert_meta_by_issuer = let mut cert_meta_by_issuer =
BTreeMap::<T::IdtyIndex, IdtyCertMeta<T::BlockNumber>>::new(); BTreeMap::<T::IdtyIndex, IdtyCertMeta<BlockNumberFor<T>>>::new();
let mut certs_removable_on = let mut certs_removable_on =
BTreeMap::<T::BlockNumber, Vec<(T::IdtyIndex, T::IdtyIndex)>>::new(); BTreeMap::<BlockNumberFor<T>, Vec<(T::IdtyIndex, T::IdtyIndex)>>::new();
for (receiver, issuers) in &self.certs_by_receiver { for (receiver, issuers) in &self.certs_by_receiver {
// Forbid self-cert // Forbid self-cert
assert!( assert!(
...@@ -168,7 +192,7 @@ pub mod pallet { ...@@ -168,7 +192,7 @@ pub mod pallet {
// Write CertsByReceiver // Write CertsByReceiver
issuers_.sort(); issuers_.sort();
CertsByReceiver::<T, I>::insert(receiver, issuers_); CertsByReceiver::<T>::insert(receiver, issuers_);
} }
// Write StorageIdtyCertMeta // Write StorageIdtyCertMeta
...@@ -183,60 +207,63 @@ pub mod pallet { ...@@ -183,60 +207,63 @@ pub mod pallet {
"Identity n°{:?} not respect MinReceivedCertToBeAbleToIssueCert.", "Identity n°{:?} not respect MinReceivedCertToBeAbleToIssueCert.",
issuer issuer
); );
StorageIdtyCertMeta::<T, I>::insert(issuer, cert_meta); StorageIdtyCertMeta::<T>::insert(issuer, cert_meta);
} }
// Write storage StorageCertsRemovableOn // Write storage CertsRemovableOn
for (removable_on, certs) in certs_removable_on { for (removable_on, certs) in certs_removable_on {
StorageCertsRemovableOn::<T, I>::insert(removable_on, certs); CertsRemovableOn::<T>::insert(removable_on, certs);
} }
} }
} }
// STORAGE // // STORAGE //
/// Certifications metada by issuer /// The certification metadata for each issuer.
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn idty_cert_meta)] #[pallet::getter(fn idty_cert_meta)]
pub type StorageIdtyCertMeta<T: Config<I>, I: 'static = ()> = pub type StorageIdtyCertMeta<T: Config> =
StorageMap<_, Twox64Concat, T::IdtyIndex, IdtyCertMeta<T::BlockNumber>, ValueQuery>; StorageMap<_, Twox64Concat, T::IdtyIndex, IdtyCertMeta<BlockNumberFor<T>>, ValueQuery>;
/// Certifications by receiver /// The certifications for each receiver.
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn certs_by_receiver)] #[pallet::getter(fn certs_by_receiver)]
pub type CertsByReceiver<T: Config<I>, I: 'static = ()> = pub type CertsByReceiver<T: Config> = StorageMap<
StorageMap<_, Twox64Concat, T::IdtyIndex, Vec<(T::IdtyIndex, T::BlockNumber)>, ValueQuery>; _,
Twox64Concat,
/// Certifications removable on T::IdtyIndex,
Vec<(T::IdtyIndex, BlockNumberFor<T>)>,
ValueQuery,
>;
/// The certifications that should expire at a given block.
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn certs_removable_on)] #[pallet::getter(fn certs_removable_on)]
pub type StorageCertsRemovableOn<T: Config<I>, I: 'static = ()> = pub type CertsRemovableOn<T: Config> = StorageMap<
StorageMap<_, Twox64Concat, T::BlockNumber, Vec<(T::IdtyIndex, T::IdtyIndex)>, OptionQuery>; _,
Twox64Concat,
BlockNumberFor<T>,
Vec<(T::IdtyIndex, T::IdtyIndex)>,
OptionQuery,
>;
// EVENTS // // EVENTS //
#[pallet::event] #[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)] #[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config<I>, I: 'static = ()> { pub enum Event<T: Config> {
/// New certification /// A new certification was added.
/// [issuer, issuer_issued_count, receiver, receiver_received_count] CertAdded {
NewCert {
issuer: T::IdtyIndex, issuer: T::IdtyIndex,
issuer_issued_count: u32,
receiver: T::IdtyIndex, receiver: T::IdtyIndex,
receiver_received_count: u32,
}, },
/// Removed certification /// A certification was removed.
/// [issuer, issuer_issued_count, receiver, receiver_received_count, expiration] CertRemoved {
RemovedCert {
issuer: T::IdtyIndex, issuer: T::IdtyIndex,
issuer_issued_count: u32,
receiver: T::IdtyIndex, receiver: T::IdtyIndex,
receiver_received_count: u32,
expiration: bool, expiration: bool,
}, },
/// Renewed certification /// A certification was renewed.
/// [issuer, receiver] CertRenewed {
RenewedCert {
issuer: T::IdtyIndex, issuer: T::IdtyIndex,
receiver: T::IdtyIndex, receiver: T::IdtyIndex,
}, },
...@@ -245,222 +272,268 @@ pub mod pallet { ...@@ -245,222 +272,268 @@ pub mod pallet {
// ERRORS // // ERRORS //
#[pallet::error] #[pallet::error]
pub enum Error<T, I = ()> { pub enum Error<T> {
/// An identity cannot certify itself /// Issuer of a certification must have an identity
OriginMustHaveAnIdentity,
/// Identity cannot certify itself.
CannotCertifySelf, CannotCertifySelf,
/// This identity has already issued the maximum number of certifications /// Identity has already issued the maximum number of certifications.
IssuedTooManyCert, IssuedTooManyCert,
/// Issuer not found /// Insufficient certifications received.
IssuerNotFound,
/// Not enough certifications received
NotEnoughCertReceived, NotEnoughCertReceived,
/// This identity has already issued a certification too recently /// Identity has issued a certification too recently.
NotRespectCertPeriod, NotRespectCertPeriod,
/// Can not add an already-existing cert
CertAlreadyExists,
/// Can not renew a non-existing cert
CertDoesNotExist,
} }
#[pallet::hooks] #[pallet::hooks]
impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> { impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(n: T::BlockNumber) -> Weight { fn on_initialize(n: BlockNumberFor<T>) -> Weight {
Self::prune_certifications(n) Self::prune_certifications(n).saturating_add(T::WeightInfo::on_initialize())
} }
} }
// CALLS // // CALLS //
#[pallet::call] #[pallet::call]
impl<T: Config<I>, I: 'static> Pallet<T, I> { impl<T: Config> Pallet<T> {
#[pallet::weight(1_000_000_000)] /// Add a new certification.
pub fn force_add_cert( #[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::add_cert())]
pub fn add_cert(
origin: OriginFor<T>, origin: OriginFor<T>,
issuer: T::IdtyIndex,
receiver: T::IdtyIndex, receiver: T::IdtyIndex,
verify_rules: bool,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
ensure_root(origin)?; let issuer = Self::origin_to_index(origin)?;
// Forbid self cert
ensure!(issuer != receiver, Error::<T, I>::CannotCertifySelf);
let block_number = frame_system::pallet::Pallet::<T>::block_number(); let block_number = frame_system::pallet::Pallet::<T>::block_number();
Self::check_add_cert(issuer, receiver, block_number)?;
if verify_rules { Self::try_add_cert(block_number, issuer, receiver)?;
// Verify rule MinReceivedCertToBeAbleToIssueCert Ok(().into())
let issuer_idty_cert_meta = StorageIdtyCertMeta::<T, I>::get(issuer);
ensure!(
issuer_idty_cert_meta.received_count
>= T::MinReceivedCertToBeAbleToIssueCert::get(),
Error::<T, I>::NotEnoughCertReceived
);
// Verify rule MaxByIssuer
ensure!(
issuer_idty_cert_meta.issued_count < T::MaxByIssuer::get(),
Error::<T, I>::IssuedTooManyCert
);
// Verify rule CertPeriod
ensure!(
block_number >= issuer_idty_cert_meta.next_issuable_on,
Error::<T, I>::NotRespectCertPeriod
);
};
Self::do_add_cert(block_number, issuer, receiver)
} }
/// Add a new certification or renew an existing one
/// /// Renew an existing certification.
/// - `receiver`: the account receiving the certification from the origin #[pallet::call_index(3)]
/// #[pallet::weight(T::WeightInfo::renew_cert())]
/// The origin must be allow to certify. pub fn renew_cert(
#[pallet::weight(1_000_000_000)]
pub fn add_cert(
origin: OriginFor<T>, origin: OriginFor<T>,
issuer: T::IdtyIndex,
receiver: T::IdtyIndex, receiver: T::IdtyIndex,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?; let issuer = Self::origin_to_index(origin)?;
// Forbid self cert
ensure!(issuer != receiver, Error::<T, I>::CannotCertifySelf);
// Verify caller ownership
let issuer_owner_key =
T::OwnerKeyOf::convert(issuer).ok_or(Error::<T, I>::IssuerNotFound)?;
ensure!(issuer_owner_key == who, DispatchError::BadOrigin);
// Verify compatibility with other pallets state
T::CheckCertAllowed::check_cert_allowed(issuer, receiver)?;
// Verify rule MinReceivedCertToBeAbleToIssueCert
let issuer_idty_cert_meta = <StorageIdtyCertMeta<T, I>>::get(issuer);
ensure!(
issuer_idty_cert_meta.received_count
>= T::MinReceivedCertToBeAbleToIssueCert::get(),
Error::<T, I>::NotEnoughCertReceived
);
// Verify rule MaxByIssuer
ensure!(
issuer_idty_cert_meta.issued_count < T::MaxByIssuer::get(),
Error::<T, I>::IssuedTooManyCert
);
// Verify rule CertPeriod
let block_number = frame_system::pallet::Pallet::<T>::block_number(); let block_number = frame_system::pallet::Pallet::<T>::block_number();
ensure!( Self::check_renew_cert(issuer, receiver, block_number)?;
block_number >= issuer_idty_cert_meta.next_issuable_on, Self::try_renew_cert(block_number, issuer, receiver)?;
Error::<T, I>::NotRespectCertPeriod Ok(().into())
);
Self::do_add_cert(block_number, issuer, receiver)
} }
#[pallet::weight(1_000_000_000)] /// Remove one certification given the issuer and the receiver.
///
/// - `origin`: Must be `Root`.
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::del_cert())]
pub fn del_cert( pub fn del_cert(
origin: OriginFor<T>, origin: OriginFor<T>,
issuer: T::IdtyIndex, issuer: T::IdtyIndex,
receiver: T::IdtyIndex, receiver: T::IdtyIndex,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
ensure_root(origin)?; ensure_root(origin)?;
Self::remove_cert_inner(issuer, receiver, None); Self::do_remove_cert(issuer, receiver, None);
Ok(().into()) Ok(().into())
} }
#[pallet::weight(1_000_000_000)] /// Remove all certifications received by an identity.
///
/// - `origin`: Must be `Root`.
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::remove_all_certs_received_by(CertsByReceiver::<T>::get(idty_index).len() as u32))]
pub fn remove_all_certs_received_by( pub fn remove_all_certs_received_by(
origin: OriginFor<T>, origin: OriginFor<T>,
idty_index: T::IdtyIndex, idty_index: T::IdtyIndex,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
ensure_root(origin)?; ensure_root(origin)?;
for (issuer, _) in CertsByReceiver::<T, I>::get(idty_index) { let _ = Self::do_remove_all_certs_received_by(idty_index);
Self::remove_cert_inner(issuer, idty_index, None);
}
Ok(().into()) Ok(().into())
} }
} }
// INTERNAL FUNCTIONS // // INTERNAL FUNCTIONS //
impl<T: Config<I>, I: 'static> Pallet<T, I> { impl<T: Config> Pallet<T> {
fn do_add_cert( /// Perform removal of all certifications received by an identity.
block_number: T::BlockNumber, pub fn do_remove_all_certs_received_by(idty_index: T::IdtyIndex) -> Weight {
let received_certs = CertsByReceiver::<T>::take(idty_index);
for (receiver_received_count, (issuer, _)) in received_certs.iter().enumerate().rev() {
let issuer_issued_count =
<StorageIdtyCertMeta<T>>::mutate_exists(issuer, |cert_meta_opt| {
let cert_meta = cert_meta_opt.get_or_insert(IdtyCertMeta::default());
cert_meta.issued_count = cert_meta.issued_count.saturating_sub(1);
cert_meta.issued_count
});
T::OnRemovedCert::on_removed_cert(
*issuer,
issuer_issued_count,
idty_index,
receiver_received_count as u32,
false,
);
Self::deposit_event(Event::CertRemoved {
issuer: *issuer,
receiver: idty_index,
expiration: false,
});
}
T::WeightInfo::do_remove_all_certs_received_by(received_certs.len() as u32)
}
/// Get the issuer index from the origin.
pub fn origin_to_index(origin: OriginFor<T>) -> Result<T::IdtyIndex, DispatchError> {
let who = ensure_signed(origin)?;
T::IdtyAttr::idty_index(who).ok_or(Error::<T>::OriginMustHaveAnIdentity.into())
}
/// Add a certification without performing checks.
///
/// This function is used during identity creation to add the first certification without
/// validation checks.
// The weight is approximated based on the worst-case scenario path.
pub fn do_add_cert_checked(
issuer: T::IdtyIndex, issuer: T::IdtyIndex,
receiver: T::IdtyIndex, receiver: T::IdtyIndex,
verify_rules: bool,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
// Write StorageCertsRemovableOn let block_number = frame_system::pallet::Pallet::<T>::block_number();
if verify_rules {
// only verify internal rules if asked
Self::check_add_cert_internal(issuer, receiver, block_number)?;
};
Self::try_add_cert(block_number, issuer, receiver)?;
Ok(().into())
}
/// Perform certification addition if it does not already exist, otherwise return `CertAlreadyExists`.
// must be transactional
fn try_add_cert(
block_number: BlockNumberFor<T>,
issuer: T::IdtyIndex,
receiver: T::IdtyIndex,
) -> DispatchResultWithPostInfo {
// Write CertsRemovableOn
let removable_on = block_number + T::ValidityPeriod::get(); let removable_on = block_number + T::ValidityPeriod::get();
<StorageCertsRemovableOn<T, I>>::append(removable_on, (issuer, receiver)); <CertsRemovableOn<T>>::append(removable_on, (issuer, receiver));
// Write CertsByReceiver // Write CertsByReceiver
let mut created = false; CertsByReceiver::<T>::mutate_exists(receiver, |maybe_issuers| {
CertsByReceiver::<T, I>::mutate_exists(receiver, |maybe_issuers| {
let issuers = maybe_issuers.get_or_insert(Vec::with_capacity(0)); let issuers = maybe_issuers.get_or_insert(Vec::with_capacity(0));
if let Err(index) = issuers.binary_search_by(|(issuer_, _)| issuer.cmp(issuer_)) { // cert does not exist, must be created
if let Err(index) = issuers.binary_search_by(|(issuer_, _)| issuer_.cmp(&issuer)) {
issuers.insert(index, (issuer, removable_on)); issuers.insert(index, (issuer, removable_on));
created = true; Ok(())
} else {
// cert exists, must be renewed instead
Err(Error::<T>::CertAlreadyExists)
} }
}); })?;
if created {
// Write StorageIdtyCertMeta for issuer // Write StorageIdtyCertMeta for issuer
let issuer_issued_count = let issuer_issued_count =
StorageIdtyCertMeta::<T, I>::mutate(issuer, |issuer_idty_cert_meta| { StorageIdtyCertMeta::<T>::mutate(issuer, |issuer_idty_cert_meta| {
issuer_idty_cert_meta.issued_count = issuer_idty_cert_meta.issued_count =
issuer_idty_cert_meta.issued_count.saturating_add(1); issuer_idty_cert_meta.issued_count.saturating_add(1);
issuer_idty_cert_meta.next_issuable_on = issuer_idty_cert_meta.next_issuable_on = block_number + T::CertPeriod::get();
block_number + T::CertPeriod::get();
issuer_idty_cert_meta.issued_count issuer_idty_cert_meta.issued_count
}); });
// Write StorageIdtyCertMeta for receiver // Write StorageIdtyCertMeta for receiver
let receiver_received_count = let receiver_received_count =
<StorageIdtyCertMeta<T, I>>::mutate_exists(receiver, |cert_meta_opt| { <StorageIdtyCertMeta<T>>::mutate_exists(receiver, |cert_meta_opt| {
let cert_meta = cert_meta_opt.get_or_insert(IdtyCertMeta::default()); let cert_meta = cert_meta_opt.get_or_insert(IdtyCertMeta::default());
cert_meta.received_count = cert_meta.received_count.saturating_add(1); cert_meta.received_count = cert_meta.received_count.saturating_add(1);
cert_meta.received_count cert_meta.received_count
}); });
Self::deposit_event(Event::NewCert { // emit CertAdded event
issuer, Self::deposit_event(Event::CertAdded { issuer, receiver });
issuer_issued_count,
receiver,
receiver_received_count,
});
T::OnNewcert::on_new_cert( T::OnNewcert::on_new_cert(
issuer, issuer,
issuer_issued_count, issuer_issued_count,
receiver, receiver,
receiver_received_count, receiver_received_count,
); );
} else { Ok(().into())
Self::deposit_event(Event::RenewedCert { issuer, receiver });
} }
/// Perform certification renewal if it exists, otherwise return an error indicating `CertDoesNotExist`.
// must be used in transactional context
// (it can fail if certification does not exist after having modified state)
fn try_renew_cert(
block_number: BlockNumberFor<T>,
issuer: T::IdtyIndex,
receiver: T::IdtyIndex,
) -> DispatchResultWithPostInfo {
// Write CertsRemovableOn
let removable_on = block_number + T::ValidityPeriod::get();
<CertsRemovableOn<T>>::append(removable_on, (issuer, receiver));
// Write CertsByReceiver
CertsByReceiver::<T>::mutate_exists(receiver, |maybe_issuers| {
let issuers = maybe_issuers.get_or_insert(Vec::with_capacity(0));
// cert exists, can be renewed
if let Ok(index) = issuers.binary_search_by(|(issuer_, _)| issuer_.cmp(&issuer)) {
issuers[index] = (issuer, removable_on);
Ok(())
} else {
// cert does not exist, must be created
Err(Error::<T>::CertDoesNotExist)
}
})?;
// Update next_issuable_on in StorageIdtyCertMeta for issuer
StorageIdtyCertMeta::<T>::mutate(issuer, |issuer_idty_cert_meta| {
issuer_idty_cert_meta.next_issuable_on = block_number + T::CertPeriod::get();
});
// emit CertRenewed event
Self::deposit_event(Event::CertRenewed { issuer, receiver });
Ok(().into()) Ok(().into())
} }
fn prune_certifications(block_number: T::BlockNumber) -> Weight {
let mut total_weight = Weight::zero();
if let Some(certs) = StorageCertsRemovableOn::<T, I>::take(block_number) { /// Remove certifications that are due to expire on the given block.
// (run at on_initialize step)
fn prune_certifications(block_number: BlockNumberFor<T>) -> Weight {
// See on initialize for the overhead weight accounting
let mut weight = Weight::zero();
if let Some(certs) = CertsRemovableOn::<T>::take(block_number) {
for (issuer, receiver) in certs { for (issuer, receiver) in certs {
total_weight += Self::remove_cert_inner(issuer, receiver, Some(block_number)); weight = weight.saturating_add(Self::do_remove_cert(
issuer,
receiver,
Some(block_number),
));
} }
} }
weight
total_weight
} }
fn remove_cert_inner(
/// Perform the certification removal.
///
/// If a block number is provided, this function removes certifications only if they are still
/// scheduled to expire at that block number.
// This function is used because the unscheduling of certification expiry (#110) is not yet implemented.
pub fn do_remove_cert(
issuer: T::IdtyIndex, issuer: T::IdtyIndex,
receiver: T::IdtyIndex, receiver: T::IdtyIndex,
block_number_opt: Option<T::BlockNumber>, block_number_opt: Option<BlockNumberFor<T>>,
) -> Weight { ) -> Weight {
let mut total_weight = Weight::zero(); let mut total_weight = Weight::zero();
let mut removed = false; let mut removed = false;
CertsByReceiver::<T, I>::mutate_exists(receiver, |issuers_opt| { CertsByReceiver::<T>::mutate_exists(receiver, |issuers_opt| {
let issuers = issuers_opt.get_or_insert(Vec::with_capacity(0)); let issuers = issuers_opt.get_or_insert(Vec::with_capacity(0));
if let Ok(index) = issuers.binary_search_by(|(issuer_, _)| issuer.cmp(issuer_)) { if let Ok(index) = issuers.binary_search_by(|(issuer_, _)| issuer_.cmp(&issuer)) {
if let Some(block_number) = block_number_opt { if let Some(block_number) = block_number_opt {
if let Some((_, removable_on)) = issuers.get(index) { if let Some((_, removable_on)) = issuers.get(index) {
// only remove cert if block number is matching
if *removable_on == block_number { if *removable_on == block_number {
issuers.remove(index); issuers.remove(index);
removed = true; removed = true;
...@@ -470,47 +543,119 @@ pub mod pallet { ...@@ -470,47 +543,119 @@ pub mod pallet {
issuers.remove(index); issuers.remove(index);
removed = true; removed = true;
} }
} else {
total_weight += T::WeightInfo::do_remove_cert_noop();
} }
}); });
if removed { if removed {
let issuer_issued_count = let issuer_issued_count =
<StorageIdtyCertMeta<T, I>>::mutate_exists(issuer, |cert_meta_opt| { <StorageIdtyCertMeta<T>>::mutate_exists(issuer, |cert_meta_opt| {
let cert_meta = cert_meta_opt.get_or_insert(IdtyCertMeta::default()); let cert_meta = cert_meta_opt.get_or_insert(IdtyCertMeta::default());
cert_meta.issued_count = cert_meta.issued_count.saturating_sub(1); cert_meta.issued_count = cert_meta.issued_count.saturating_sub(1);
cert_meta.issued_count cert_meta.issued_count
}); });
let receiver_received_count = let receiver_received_count =
<StorageIdtyCertMeta<T, I>>::mutate_exists(receiver, |cert_meta_opt| { <StorageIdtyCertMeta<T>>::mutate_exists(receiver, |cert_meta_opt| {
let cert_meta = cert_meta_opt.get_or_insert(IdtyCertMeta::default()); let cert_meta = cert_meta_opt.get_or_insert(IdtyCertMeta::default());
cert_meta.received_count = cert_meta.received_count.saturating_sub(1); cert_meta.received_count = cert_meta.received_count.saturating_sub(1);
cert_meta.received_count cert_meta.received_count
}); });
Self::deposit_event(Event::RemovedCert { Self::deposit_event(Event::CertRemoved {
issuer, issuer,
issuer_issued_count,
receiver, receiver,
receiver_received_count,
expiration: block_number_opt.is_some(), expiration: block_number_opt.is_some(),
}); });
total_weight += T::OnRemovedCert::on_removed_cert( T::OnRemovedCert::on_removed_cert(
issuer, issuer,
issuer_issued_count, issuer_issued_count,
receiver, receiver,
receiver_received_count, receiver_received_count,
block_number_opt.is_some(), block_number_opt.is_some(),
); );
// Pessimistic overhead estimation based on the worst path of a successfull
// certificate removal to avoid multiplying benchmarks for every branching,
// include the OnRemovedCert weight.
total_weight.saturating_add(T::WeightInfo::do_remove_cert());
} }
total_weight total_weight
} }
/// Check if adding a certification is allowed.
// 1. no self cert
// 2. issuer received cert count
// 3. issuer max emitted cert
// 4. issuer cert period
fn check_add_cert_internal(
issuer: T::IdtyIndex,
receiver: T::IdtyIndex,
block_number: BlockNumberFor<T>,
) -> DispatchResult {
// 1. Forbid self cert
ensure!(issuer != receiver, Error::<T>::CannotCertifySelf);
// 2. Verify rule MinReceivedCertToBeAbleToIssueCert
// (this number can differ from the one necessary to be member)
let issuer_idty_cert_meta = <StorageIdtyCertMeta<T>>::get(issuer);
ensure!(
issuer_idty_cert_meta.received_count
>= T::MinReceivedCertToBeAbleToIssueCert::get(),
Error::<T>::NotEnoughCertReceived
);
// 3. Verify rule MaxByIssuer
ensure!(
issuer_idty_cert_meta.issued_count < T::MaxByIssuer::get(),
Error::<T>::IssuedTooManyCert
);
// 4. Verify rule CertPeriod
ensure!(
block_number >= issuer_idty_cert_meta.next_issuable_on,
Error::<T>::NotRespectCertPeriod
);
Ok(())
}
/// Check if adding a certification is allowed.
// first internal checks
// then external checks
fn check_add_cert(
issuer: T::IdtyIndex,
receiver: T::IdtyIndex,
block_number: BlockNumberFor<T>,
) -> DispatchResult {
// internal checks
Self::check_add_cert_internal(issuer, receiver, block_number)?;
// --- then external checks
// - issuer is member
// - receiver is confirmed
// - receiver is not revoked
T::CheckCertAllowed::check_cert_allowed(issuer, receiver)?;
Ok(())
}
/// Check if renewing a certification is allowed based.
fn check_renew_cert(
issuer: T::IdtyIndex,
receiver: T::IdtyIndex,
block_number: BlockNumberFor<T>,
) -> DispatchResult {
Self::check_add_cert_internal(issuer, receiver, block_number)?;
T::CheckCertAllowed::check_cert_allowed(issuer, receiver)?;
Ok(())
}
} }
} }
impl<T: Config<I>, I: 'static> SetNextIssuableOn<T::BlockNumber, T::IdtyIndex> for Pallet<T, I> { // implement setting next_issuable_on for certification period
fn set_next_issuable_on(idty_index: T::IdtyIndex, next_issuable_on: T::BlockNumber) -> Weight { impl<T: Config> SetNextIssuableOn<BlockNumberFor<T>, T::IdtyIndex> for Pallet<T> {
<StorageIdtyCertMeta<T, I>>::mutate_exists(idty_index, |cert_meta_opt| { fn set_next_issuable_on(idty_index: T::IdtyIndex, next_issuable_on: BlockNumberFor<T>) {
<StorageIdtyCertMeta<T>>::mutate_exists(idty_index, |cert_meta_opt| {
let cert_meta = cert_meta_opt.get_or_insert(IdtyCertMeta::default()); let cert_meta = cert_meta_opt.get_or_insert(IdtyCertMeta::default());
cert_meta.next_issuable_on = next_issuable_on; cert_meta.next_issuable_on = next_issuable_on;
}); });
Weight::zero()
} }
} }
...@@ -16,32 +16,25 @@ ...@@ -16,32 +16,25 @@
use crate::{self as pallet_certification}; use crate::{self as pallet_certification};
use frame_support::{ use frame_support::{
parameter_types, derive_impl, parameter_types,
traits::{Everything, OnFinalize, OnInitialize}, traits::{Everything, OnFinalize, OnInitialize},
}; };
use frame_system as system; use frame_system as system;
use sp_core::H256; use sp_core::H256;
use sp_runtime::{ use sp_runtime::{
testing::Header,
traits::{BlakeTwo256, IdentityLookup}, traits::{BlakeTwo256, IdentityLookup},
BuildStorage, BuildStorage,
}; };
type AccountId = u64; type AccountId = u64;
type BlockNumber = u64;
type Block = frame_system::mocking::MockBlock<Test>; type Block = frame_system::mocking::MockBlock<Test>;
pub type IdtyIndex = u64; pub type IdtyIndex = u64;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
// Configure a mock runtime to test the pallet. // Configure a mock runtime to test the pallet.
frame_support::construct_runtime!( frame_support::construct_runtime!(
pub enum Test where pub enum Test {
Block = Block, System: frame_system,
NodeBlock = Block, DefaultCertification: pallet_certification,
UncheckedExtrinsic = UncheckedExtrinsic,
{
System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
DefaultCertification: pallet_certification::{Pallet, Call, Event<T>, Storage, Config<T>},
} }
); );
...@@ -50,45 +43,22 @@ parameter_types! { ...@@ -50,45 +43,22 @@ parameter_types! {
pub const SS58Prefix: u8 = 42; pub const SS58Prefix: u8 = 42;
} }
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl system::Config for Test { impl system::Config for Test {
type AccountId = AccountId;
type BaseCallFilter = Everything; type BaseCallFilter = Everything;
type BlockWeights = (); type Block = Block;
type BlockLength = (); type BlockHashCount = BlockHashCount;
type DbWeight = ();
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type Index = u64;
type BlockNumber = BlockNumber;
type Hash = H256; type Hash = H256;
type Hashing = BlakeTwo256; type Hashing = BlakeTwo256;
type AccountId = AccountId;
type Lookup = IdentityLookup<Self::AccountId>; type Lookup = IdentityLookup<Self::AccountId>;
type Header = Header; type MaxConsumers = frame_support::traits::ConstU32<16>;
type RuntimeEvent = RuntimeEvent; type Nonce = u64;
type BlockHashCount = BlockHashCount;
type Version = ();
type PalletInfo = PalletInfo; type PalletInfo = PalletInfo;
type AccountData = (); type RuntimeCall = RuntimeCall;
type OnNewAccount = (); type RuntimeEvent = RuntimeEvent;
type OnKilledAccount = (); type RuntimeOrigin = RuntimeOrigin;
type SystemWeightInfo = ();
type SS58Prefix = SS58Prefix; type SS58Prefix = SS58Prefix;
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
pub struct EnsureRoot;
impl frame_support::traits::EnsureOrigin<(RuntimeOrigin, IdtyIndex, IdtyIndex)> for EnsureRoot {
type Success = ();
fn try_origin(
o: (RuntimeOrigin, IdtyIndex, IdtyIndex),
) -> Result<Self::Success, (RuntimeOrigin, IdtyIndex, IdtyIndex)> {
match o.0.clone().into() {
Ok(system::RawOrigin::Root) => Ok(()),
_ => Err(o),
}
}
} }
parameter_types! { parameter_types! {
...@@ -100,22 +70,23 @@ parameter_types! { ...@@ -100,22 +70,23 @@ parameter_types! {
impl pallet_certification::Config for Test { impl pallet_certification::Config for Test {
type CertPeriod = CertPeriod; type CertPeriod = CertPeriod;
type IdtyIndex = IdtyIndex;
type OwnerKeyOf = sp_runtime::traits::ConvertInto;
type CheckCertAllowed = (); type CheckCertAllowed = ();
type IdtyAttr = ();
type IdtyIndex = IdtyIndex;
type MaxByIssuer = MaxByIssuer; type MaxByIssuer = MaxByIssuer;
type MinReceivedCertToBeAbleToIssueCert = MinReceivedCertToBeAbleToIssueCert; type MinReceivedCertToBeAbleToIssueCert = MinReceivedCertToBeAbleToIssueCert;
type OnNewcert = (); type OnNewcert = ();
type OnRemovedCert = (); type OnRemovedCert = ();
type RuntimeEvent = RuntimeEvent; type RuntimeEvent = RuntimeEvent;
type ValidityPeriod = ValidityPeriod; type ValidityPeriod = ValidityPeriod;
type WeightInfo = ();
} }
// Build genesis storage according to the mock runtime. // Build genesis storage according to the mock runtime.
pub fn new_test_ext( pub fn new_test_ext(
gen_conf: pallet_certification::GenesisConfig<Test>, gen_conf: pallet_certification::GenesisConfig<Test>,
) -> sp_io::TestExternalities { ) -> sp_io::TestExternalities {
GenesisConfig { RuntimeGenesisConfig {
system: SystemConfig::default(), system: SystemConfig::default(),
default_certification: gen_conf, default_certification: gen_conf,
} }
...@@ -128,6 +99,7 @@ pub fn run_to_block(n: u64) { ...@@ -128,6 +99,7 @@ pub fn run_to_block(n: u64) {
while System::block_number() < n { while System::block_number() < n {
DefaultCertification::on_finalize(System::block_number()); DefaultCertification::on_finalize(System::block_number());
System::on_finalize(System::block_number()); System::on_finalize(System::block_number());
System::reset_events();
System::set_block_number(System::block_number() + 1); System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number()); System::on_initialize(System::block_number());
DefaultCertification::on_initialize(System::block_number()); DefaultCertification::on_initialize(System::block_number());
......
...@@ -14,11 +14,10 @@ ...@@ -14,11 +14,10 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use crate::mock::*; use crate::{mock::*, Error, Event};
use crate::{Error, Event}; use frame_support::{assert_noop, assert_ok};
use frame_support::assert_ok;
use maplit::btreemap; use maplit::btreemap;
use sp_std::collections::btree_map::BTreeMap; use scale_info::prelude::{collections::BTreeMap, vec};
#[test] #[test]
fn test_must_receive_cert_before_can_issue() { fn test_must_receive_cert_before_can_issue() {
...@@ -28,8 +27,8 @@ fn test_must_receive_cert_before_can_issue() { ...@@ -28,8 +27,8 @@ fn test_must_receive_cert_before_can_issue() {
}) })
.execute_with(|| { .execute_with(|| {
assert_eq!( assert_eq!(
DefaultCertification::add_cert(RuntimeOrigin::signed(0), 0, 1), DefaultCertification::add_cert(RuntimeOrigin::signed(0), 1),
Err(Error::<Test, _>::NotEnoughCertReceived.into()) Err(Error::<Test>::NotEnoughCertReceived.into())
); );
}); });
} }
...@@ -49,8 +48,8 @@ fn test_cannot_certify_self() { ...@@ -49,8 +48,8 @@ fn test_cannot_certify_self() {
run_to_block(2); run_to_block(2);
assert_eq!( assert_eq!(
DefaultCertification::add_cert(RuntimeOrigin::signed(0), 0, 0), DefaultCertification::add_cert(RuntimeOrigin::signed(0), 0),
Err(Error::<Test, _>::CannotCertifySelf.into()) Err(Error::<Test>::CannotCertifySelf.into())
); );
}); });
} }
...@@ -113,11 +112,9 @@ fn test_genesis_build() { ...@@ -113,11 +112,9 @@ fn test_genesis_build() {
// Cert 2->1 must have expired // Cert 2->1 must have expired
assert_eq!( assert_eq!(
System::events()[0].event, System::events()[0].event,
RuntimeEvent::DefaultCertification(Event::RemovedCert { RuntimeEvent::DefaultCertification(Event::CertRemoved {
issuer: 2, issuer: 2,
issuer_issued_count: 1,
receiver: 1, receiver: 1,
receiver_received_count: 1,
expiration: true, expiration: true,
},) },)
); );
...@@ -153,25 +150,200 @@ fn test_cert_period() { ...@@ -153,25 +150,200 @@ fn test_cert_period() {
} }
); );
assert_eq!( assert_eq!(
DefaultCertification::add_cert(RuntimeOrigin::signed(0), 0, 3), DefaultCertification::add_cert(RuntimeOrigin::signed(0), 3),
Err(Error::<Test, _>::NotRespectCertPeriod.into()) Err(Error::<Test>::NotRespectCertPeriod.into())
); );
run_to_block(CertPeriod::get()); run_to_block(CertPeriod::get());
assert_ok!(DefaultCertification::add_cert( assert_ok!(DefaultCertification::add_cert(RuntimeOrigin::signed(0), 3));
RuntimeOrigin::signed(0),
0,
3
));
run_to_block(CertPeriod::get() + 1); run_to_block(CertPeriod::get() + 1);
assert_eq!( assert_eq!(
DefaultCertification::add_cert(RuntimeOrigin::signed(0), 0, 4), DefaultCertification::add_cert(RuntimeOrigin::signed(0), 4),
Err(Error::<Test, _>::NotRespectCertPeriod.into()) Err(Error::<Test>::NotRespectCertPeriod.into())
); );
run_to_block((2 * CertPeriod::get()) + 1); run_to_block((2 * CertPeriod::get()) + 1);
assert_ok!(DefaultCertification::add_cert( assert_ok!(DefaultCertification::add_cert(RuntimeOrigin::signed(0), 4));
RuntimeOrigin::signed(0), });
0, }
4
)); // after given validity period, a certification should expire
#[test]
fn test_cert_expiry() {
new_test_ext(DefaultCertificationConfig {
apply_cert_period_at_genesis: true,
certs_by_receiver: btreemap![
0 => btreemap![
1 => Some(5),
2 => Some(5),
],
1 => btreemap![
0 => Some(6),
2 => Some(6),
],
2 => btreemap![
0 => Some(7),
1 => Some(7),
],
],
})
.execute_with(|| {
run_to_block(5);
// Expiry of cert by issuer 1
System::assert_has_event(RuntimeEvent::DefaultCertification(Event::CertRemoved {
issuer: 1,
receiver: 0,
expiration: true,
}));
// Expiry of cert by issuer 2
System::assert_has_event(RuntimeEvent::DefaultCertification(Event::CertRemoved {
receiver: 0,
issuer: 2,
expiration: true,
}));
});
}
// when renewing a certification, it should not expire now, but later
#[test]
fn test_cert_renewal() {
new_test_ext(DefaultCertificationConfig {
apply_cert_period_at_genesis: false,
certs_by_receiver: btreemap![
0 => btreemap![
1 => Some(5),
2 => Some(20),
],
1 => btreemap![
0 => Some(20),
2 => Some(20),
],
2 => btreemap![
0 => Some(20),
1 => Some(20),
],
],
})
.execute_with(|| {
run_to_block(2);
// renew certification from bob to alice
// this certification should expire 10 blocks later (at block 12)
assert_eq!(
DefaultCertification::renew_cert(RuntimeOrigin::signed(1), 0),
Ok(().into())
);
System::assert_last_event(RuntimeEvent::DefaultCertification(Event::CertRenewed {
issuer: 1,
receiver: 0,
}));
run_to_block(12);
// expiry of previously renewed cert
System::assert_last_event(RuntimeEvent::DefaultCertification(Event::CertRemoved {
issuer: 1,
receiver: 0,
expiration: true,
}));
});
}
// when renewing a certification, issuer should not be able to emit a new cert before certification delay
#[test]
fn test_cert_renewal_cert_delay() {
new_test_ext(DefaultCertificationConfig {
apply_cert_period_at_genesis: false,
certs_by_receiver: btreemap![
0 => btreemap![
1 => Some(5),
2 => Some(20),
],
1 => btreemap![
0 => Some(20),
2 => Some(20),
],
2 => btreemap![
0 => Some(20),
1 => Some(20),
],
],
})
.execute_with(|| {
run_to_block(2);
// renew certification from bob to alice
assert_eq!(
DefaultCertification::renew_cert(RuntimeOrigin::signed(1), 0),
Ok(().into())
);
System::assert_last_event(RuntimeEvent::DefaultCertification(Event::CertRenewed {
issuer: 1,
receiver: 0,
}));
run_to_block(3);
// try to renew again
assert_noop!(
DefaultCertification::add_cert(RuntimeOrigin::signed(1), 0),
Error::<Test>::NotRespectCertPeriod,
);
// no renewal event should be emitted
assert_eq!(System::events().last(), None);
});
}
// when renewing a certification, the certification should not expire before new expiration
#[test]
fn test_cert_renewal_expiration() {
new_test_ext(DefaultCertificationConfig {
apply_cert_period_at_genesis: false,
certs_by_receiver: btreemap![
0 => btreemap![
1 => Some(5),
2 => Some(20),
],
1 => btreemap![
0 => Some(20),
2 => Some(20),
],
2 => btreemap![
0 => Some(20),
1 => Some(20),
],
],
})
.execute_with(|| {
run_to_block(2);
// renew certification from bob to alice
// this certification should expire 10 blocks later (at block 12)
assert_eq!(
DefaultCertification::renew_cert(RuntimeOrigin::signed(1), 0),
Ok(().into())
);
System::assert_last_event(RuntimeEvent::DefaultCertification(Event::CertRenewed {
issuer: 1,
receiver: 0,
}));
run_to_block(4);
// renew certification from bob to alice again
// this certification should expire 10 blocks later (at block 14)
assert_eq!(
DefaultCertification::renew_cert(RuntimeOrigin::signed(1), 0),
Ok(().into())
);
System::assert_last_event(RuntimeEvent::DefaultCertification(Event::CertRenewed {
issuer: 1,
receiver: 0,
}));
// no certification should expire at these blocks
// hint : prune_certifications checks that the certification has not been renewed
run_to_block(12);
assert_eq!(System::events().last(), None);
run_to_block(14);
// expiry of previously renewed cert
System::assert_last_event(RuntimeEvent::DefaultCertification(Event::CertRemoved {
issuer: 1,
receiver: 0,
expiration: true,
}));
}); });
} }
...@@ -16,7 +16,9 @@ ...@@ -16,7 +16,9 @@
use frame_support::pallet_prelude::*; use frame_support::pallet_prelude::*;
/// Trait for checking if a certification is allowed between two identities.
pub trait CheckCertAllowed<IdtyIndex> { pub trait CheckCertAllowed<IdtyIndex> {
/// Check if the certification is allowed from the issuer to the receiver.
fn check_cert_allowed(issuer: IdtyIndex, receiver: IdtyIndex) -> Result<(), DispatchError>; fn check_cert_allowed(issuer: IdtyIndex, receiver: IdtyIndex) -> Result<(), DispatchError>;
} }
...@@ -26,34 +28,39 @@ impl<IdtyIndex> CheckCertAllowed<IdtyIndex> for () { ...@@ -26,34 +28,39 @@ impl<IdtyIndex> CheckCertAllowed<IdtyIndex> for () {
} }
} }
/// Trait for handling actions to take when a new certification is issued.
pub trait OnNewcert<IdtyIndex> { pub trait OnNewcert<IdtyIndex> {
/// Called when a new certification is issued.
fn on_new_cert( fn on_new_cert(
issuer: IdtyIndex, issuer: IdtyIndex,
issuer_issued_count: u32, issuer_issued_count: u32,
receiver: IdtyIndex, receiver: IdtyIndex,
receiver_received_count: u32, receiver_received_count: u32,
) -> frame_support::dispatch::Weight; );
} }
impl<IdtyIndex> OnNewcert<IdtyIndex> for () { impl<IdtyIndex> OnNewcert<IdtyIndex> for () {
fn on_new_cert( fn on_new_cert(
_issuer: IdtyIndex, _issuer: IdtyIndex,
_issuer_issued_count: u32, _issuer_issued_count: u32,
_receiver: IdtyIndex, _receiver: IdtyIndex,
_receiver_received_count: u32, _receiver_received_count: u32,
) -> frame_support::dispatch::Weight { ) {
Weight::zero()
} }
} }
/// Trait for handling actions to take when a certification is removed.
pub trait OnRemovedCert<IdtyIndex> { pub trait OnRemovedCert<IdtyIndex> {
/// Called when a certification is removed.
fn on_removed_cert( fn on_removed_cert(
issuer: IdtyIndex, issuer: IdtyIndex,
issuer_issued_count: u32, issuer_issued_count: u32,
receiver: IdtyIndex, receiver: IdtyIndex,
receiver_received_count: u32, receiver_received_count: u32,
expiration: bool, expiration: bool,
) -> frame_support::dispatch::Weight; );
} }
impl<IdtyIndex> OnRemovedCert<IdtyIndex> for () { impl<IdtyIndex> OnRemovedCert<IdtyIndex> for () {
fn on_removed_cert( fn on_removed_cert(
_issuer: IdtyIndex, _issuer: IdtyIndex,
...@@ -61,14 +68,12 @@ impl<IdtyIndex> OnRemovedCert<IdtyIndex> for () { ...@@ -61,14 +68,12 @@ impl<IdtyIndex> OnRemovedCert<IdtyIndex> for () {
_receiver: IdtyIndex, _receiver: IdtyIndex,
_receiver_received_count: u32, _receiver_received_count: u32,
_expiration: bool, _expiration: bool,
) -> frame_support::dispatch::Weight { ) {
Weight::zero()
} }
} }
/// Trait for setting the next issuable block number for an identity.
pub trait SetNextIssuableOn<BlockNumber, IdtyIndex> { pub trait SetNextIssuableOn<BlockNumber, IdtyIndex> {
fn set_next_issuable_on( /// Set the next block number when the identity can issue a certification.
idty_index: IdtyIndex, fn set_next_issuable_on(idty_index: IdtyIndex, next_issuable_on: BlockNumber);
next_issuable_on: BlockNumber,
) -> frame_support::dispatch::Weight;
} }
...@@ -20,10 +20,14 @@ use codec::{Decode, Encode}; ...@@ -20,10 +20,14 @@ use codec::{Decode, Encode};
use frame_support::pallet_prelude::*; use frame_support::pallet_prelude::*;
use scale_info::TypeInfo; use scale_info::TypeInfo;
/// Represents the certification metadata attached to an identity.
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)] #[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub struct IdtyCertMeta<BlockNumber: Default> { pub struct IdtyCertMeta<BlockNumber: Default> {
/// Number of certifications issued by this identity.
pub issued_count: u32, pub issued_count: u32,
/// Block number before which the identity is not allowed to issue a new certification.
pub next_issuable_on: BlockNumber, pub next_issuable_on: BlockNumber,
/// Number of certifications received by this identity.
pub received_count: u32, pub received_count: u32,
} }
......
// Copyright 2021-2022 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
#![allow(clippy::unnecessary_cast)]
use frame_support::weights::{constants::RocksDbWeight, Weight};
/// Weight functions needed for pallet_universal_dividend.
pub trait WeightInfo {
fn add_cert() -> Weight;
fn renew_cert() -> Weight;
fn del_cert() -> Weight;
fn remove_all_certs_received_by(i: u32) -> Weight;
fn on_initialize() -> Weight;
fn do_remove_cert_noop() -> Weight;
fn do_remove_cert() -> Weight;
fn do_remove_all_certs_received_by(i: u32) -> Weight;
}
// Insecure weights implementation, use it for tests only!
impl WeightInfo for () {
// Storage: Identity Identities (r:2 w:0)
// Storage: Cert StorageIdtyCertMeta (r:2 w:2)
// Storage: Parameters ParametersStorage (r:1 w:0)
// Storage: Cert CertsRemovableOn (r:1 w:1)
// Storage: Cert CertsByReceiver (r:1 w:1)
fn add_cert() -> Weight {
// Minimum execution time: 259_247 nanoseconds.
Weight::from_parts(269_348_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(7 as u64))
.saturating_add(RocksDbWeight::get().writes(4 as u64))
}
// Storage: Identity Identities (r:2 w:0)
// Storage: Cert StorageIdtyCertMeta (r:2 w:2)
// Storage: Parameters ParametersStorage (r:1 w:0)
// Storage: Cert CertsRemovableOn (r:1 w:1)
// Storage: Cert CertsByReceiver (r:1 w:1)
fn renew_cert() -> Weight {
// Minimum execution time: 259_247 nanoseconds.
Weight::from_parts(269_348_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(7 as u64))
.saturating_add(RocksDbWeight::get().writes(4 as u64))
}
// Storage: Cert CertsByReceiver (r:1 w:1)
// Storage: Cert StorageIdtyCertMeta (r:2 w:2)
// Storage: Parameters ParametersStorage (r:1 w:0)
// Storage: Membership Membership (r:1 w:0)
fn del_cert() -> Weight {
// Minimum execution time: 216_762 nanoseconds.
Weight::from_parts(222_570_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(5 as u64))
.saturating_add(RocksDbWeight::get().writes(3 as u64))
}
// Storage: Cert CertsByReceiver (r:1 w:1)
// Storage: Cert StorageIdtyCertMeta (r:2 w:2)
// Storage: Parameters ParametersStorage (r:1 w:0)
// Storage: Membership Membership (r:1 w:0)
/// The range of component `i` is `[2, 1000]`.
fn remove_all_certs_received_by(i: u32) -> Weight {
// Minimum execution time: 223_292 nanoseconds.
Weight::from_parts(233_586_000 as u64, 0)
// Standard Error: 598_929
.saturating_add(Weight::from_parts(53_659_501 as u64, 0).saturating_mul(i as u64))
.saturating_add(RocksDbWeight::get().reads(3 as u64))
.saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(i as u64)))
.saturating_add(RocksDbWeight::get().writes(1 as u64))
.saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64)))
}
fn on_initialize() -> Weight {
// Minimum execution time: 259_247 nanoseconds.
Weight::from_parts(269_348_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(7 as u64))
.saturating_add(RocksDbWeight::get().writes(4 as u64))
}
fn do_remove_cert_noop() -> Weight {
// Minimum execution time: 259_247 nanoseconds.
Weight::from_parts(269_348_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(7 as u64))
.saturating_add(RocksDbWeight::get().writes(4 as u64))
}
fn do_remove_cert() -> Weight {
// Minimum execution time: 259_247 nanoseconds.
Weight::from_parts(269_348_000 as u64, 0)
.saturating_add(RocksDbWeight::get().reads(7 as u64))
.saturating_add(RocksDbWeight::get().writes(4 as u64))
}
// Storage: Cert CertsByReceiver (r:1 w:1)
// Storage: Cert StorageIdtyCertMeta (r:2 w:2)
// Storage: Parameters ParametersStorage (r:1 w:0)
// Storage: Membership Membership (r:1 w:0)
/// The range of component `i` is `[2, 1000]`.
fn do_remove_all_certs_received_by(i: u32) -> Weight {
// Minimum execution time: 223_292 nanoseconds.
Weight::from_parts(233_586_000 as u64, 0)
// Standard Error: 598_929
.saturating_add(Weight::from_parts(53_659_501 as u64, 0).saturating_mul(i as u64))
.saturating_add(RocksDbWeight::get().reads(3 as u64))
.saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(i as u64)))
.saturating_add(RocksDbWeight::get().writes(1 as u64))
.saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64)))
}
}
[package]
authors.workspace = true
description = "duniter pallet distance"
edition.workspace = true
homepage.workspace = true
license.workspace = true
name = "pallet-distance"
repository.workspace = true
version.workspace = true
[features]
default = ["std"]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-authority-members/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-identity/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
std = [
"codec/std",
"frame-benchmarking?/std",
"frame-support/std",
"frame-system/std",
"pallet-authority-members/std",
"pallet-authorship/std",
"pallet-balances/std",
"pallet-identity/std",
"pallet-session/std",
"scale-info/std",
"sp-consensus-babe/std",
"sp-core/std",
"sp-distance/std",
"sp-inherents/std",
"sp-io/std",
"sp-keystore/std",
"sp-runtime/std",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-authority-members/try-runtime",
"pallet-authorship/try-runtime",
"pallet-balances/try-runtime",
"pallet-identity/try-runtime",
"pallet-session/try-runtime",
"sp-distance/try-runtime",
"sp-runtime/try-runtime",
]
[package.metadata.docs.rs]
default-features = false
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true, features = ["derive"] }
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
pallet-authority-members = { workspace = true }
pallet-authorship = { workspace = true }
pallet-balances = { workspace = true }
pallet-identity = { workspace = true }
pallet-session = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
sp-consensus-babe = { workspace = true }
sp-core = { workspace = true }
sp-distance = { workspace = true }
sp-inherents = { workspace = true }
sp-runtime = { workspace = true }
[dev-dependencies]
sp-io = { workspace = true, default-features = true }
sp-keystore = { workspace = true, default-features = true }
// Copyright 2021-2023 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
#![cfg(feature = "runtime-benchmarks")]
#![allow(clippy::multiple_bound_locations)]
use super::*;
use codec::Encode;
use frame_benchmarking::v2::*;
use frame_support::traits::{fungible::Mutate, Get, OnFinalize, OnInitialize};
use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin};
use scale_info::prelude::vec;
use sp_runtime::Perbill;
use crate::Pallet;
#[benchmarks(
where
T: pallet_balances::Config,
BalanceOf<T>: From<u32>,
BlockNumberFor<T>: From<u32>,
)]
mod benchmarks {
use super::*;
fn assert_has_event<T: Config>(generic_event: <T as Config>::RuntimeEvent) {
frame_system::Pallet::<T>::assert_has_event(generic_event.into());
}
fn populate_pool<T: Config>(i: u32) -> Result<(), &'static str> {
EvaluationPool0::<T>::mutate(|current_pool| -> Result<(), &'static str> {
for j in 0..i {
current_pool
.evaluations
.try_push((j, median::MedianAcc::new()))
.map_err(|_| Error::<T>::QueueFull)?;
}
Ok(())
})
}
#[benchmark]
fn request_distance_evaluation() {
// More than membership renewal to avoid antispam
frame_system::pallet::Pallet::<T>::set_block_number(500_000_000u32.into());
let idty = T::IdtyIndex::one();
let caller: T::AccountId = pallet_identity::Identities::<T>::get(idty)
.unwrap()
.owner_key;
let _ = T::Currency::set_balance(&caller, u32::MAX.into());
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()));
assert!(
PendingEvaluationRequest::<T>::get(idty) == Some(caller.clone()),
"Request not added"
);
assert_has_event::<T>(
Event::<T>::EvaluationRequested {
idty_index: idty,
who: caller,
}
.into(),
);
}
#[benchmark]
fn request_distance_evaluation_for() {
// More than membership renewal to avoid antispam
frame_system::pallet::Pallet::<T>::set_block_number(500_000_000u32.into());
let idty = T::IdtyIndex::one();
let caller: T::AccountId = pallet_identity::Identities::<T>::get(idty)
.unwrap()
.owner_key;
T::Currency::set_balance(&caller, u32::MAX.into());
let target: T::IdtyIndex = 2u32;
// set target status since targeted distance evaluation only allowed for unvalidated
pallet_identity::Identities::<T>::mutate(target, |idty_val| {
idty_val.as_mut().unwrap().status = pallet_identity::IdtyStatus::Unvalidated
});
#[extrinsic_call]
_(RawOrigin::Signed(caller.clone()), target);
assert!(
PendingEvaluationRequest::<T>::get(target) == Some(caller.clone()),
"Request not added"
);
assert_has_event::<T>(
Event::<T>::EvaluationRequested {
idty_index: target,
who: caller,
}
.into(),
);
}
#[benchmark]
fn update_evaluation(i: Linear<1, MAX_EVALUATIONS_PER_SESSION>) -> Result<(), BenchmarkError> {
let digest_data = sp_consensus_babe::digests::PreDigest::SecondaryPlain(
sp_consensus_babe::digests::SecondaryPlainPreDigest {
authority_index: 0u32,
slot: Default::default(),
},
);
// A BABE digest item is needed to check authorship
let digest = sp_runtime::DigestItem::PreRuntime(*b"BABE", digest_data.encode());
<frame_system::Pallet<T>>::deposit_log(digest);
populate_pool::<T>(i)?;
#[extrinsic_call]
_(
RawOrigin::None,
ComputationResult {
distances: vec![Perbill::one(); i as usize],
},
);
Ok(())
}
#[benchmark]
fn force_update_evaluation(
i: Linear<1, MAX_EVALUATIONS_PER_SESSION>,
) -> Result<(), BenchmarkError> {
let idty = T::IdtyIndex::one();
let caller: T::AccountId = pallet_identity::Identities::<T>::get(idty)
.unwrap()
.owner_key;
populate_pool::<T>(i)?;
#[extrinsic_call]
_(
RawOrigin::Root,
caller,
ComputationResult {
distances: vec![Perbill::one(); i as usize],
},
);
Ok(())
}
#[benchmark]
fn force_valid_distance_status() {
let idty = T::IdtyIndex::one();
#[extrinsic_call]
_(RawOrigin::Root, idty);
assert_has_event::<T>(
Event::<T>::EvaluatedValid {
idty_index: idty,
distance: Perbill::one(),
}
.into(),
);
}
#[benchmark]
fn on_initialize_overhead() {
// Benchmark on_initialize with no on_finalize and no do_evaluation.
let block_number: BlockNumberFor<T> = (T::EvaluationPeriod::get() + 1).into();
#[block]
{
Pallet::<T>::on_initialize(block_number);
}
}
#[benchmark]
fn do_evaluation_success() -> Result<(), BenchmarkError> {
// Benchmarking do_evaluation in case of a single success.
CurrentPeriodIndex::<T>::put(0);
// More than membership renewal to avoid antispam
frame_system::pallet::Pallet::<T>::set_block_number(500_000_000u32.into());
let idty = T::IdtyIndex::one();
let caller: T::AccountId = pallet_identity::Identities::<T>::get(idty)
.unwrap()
.owner_key;
let _ = T::Currency::set_balance(&caller, u32::MAX.into());
Pallet::<T>::request_distance_evaluation(RawOrigin::Signed(caller.clone()).into())?;
assert_has_event::<T>(
Event::<T>::EvaluationRequested {
idty_index: idty,
who: caller.clone(),
}
.into(),
);
CurrentPeriodIndex::<T>::put(2);
Pallet::<T>::force_update_evaluation(
RawOrigin::Root.into(),
caller,
ComputationResult {
distances: vec![Perbill::one()],
},
)?;
#[block]
{
Pallet::<T>::do_evaluation(0);
}
assert_has_event::<T>(
Event::<T>::EvaluatedValid {
idty_index: idty,
distance: Perbill::one(),
}
.into(),
);
Ok(())
}
#[benchmark]
fn do_evaluation_failure() -> Result<(), BenchmarkError> {
// Benchmarking do_evaluation in case of a single failure.
CurrentPeriodIndex::<T>::put(0);
// More than membership renewal to avoid antispam
frame_system::pallet::Pallet::<T>::set_block_number(500_000_000u32.into());
let idty = T::IdtyIndex::one();
let caller: T::AccountId = pallet_identity::Identities::<T>::get(idty)
.unwrap()
.owner_key;
let _ = T::Currency::set_balance(&caller, u32::MAX.into());
Pallet::<T>::request_distance_evaluation(RawOrigin::Signed(caller.clone()).into())?;
assert_has_event::<T>(
Event::<T>::EvaluationRequested {
idty_index: idty,
who: caller.clone(),
}
.into(),
);
CurrentPeriodIndex::<T>::put(2);
Pallet::<T>::force_update_evaluation(
RawOrigin::Root.into(),
caller,
ComputationResult {
distances: vec![Perbill::zero()],
},
)?;
#[block]
{
Pallet::<T>::do_evaluation(0);
}
assert_has_event::<T>(
Event::<T>::EvaluatedInvalid {
idty_index: idty,
distance: Perbill::zero(),
}
.into(),
);
Ok(())
}
#[benchmark]
fn do_evaluation_overhead() -> Result<(), BenchmarkError> {
#[block]
{
Pallet::<T>::do_evaluation(0);
}
Ok(())
}
#[benchmark]
fn on_finalize() {
DidUpdate::<T>::set(true);
#[block]
{
Pallet::<T>::on_finalize(Default::default());
}
assert!(!DidUpdate::<T>::get());
}
impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::Test);
}
// Copyright 2022 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
//! # Distance Pallet
//!
//! The distance pallet utilizes results provided in a file by the `distance-oracle` offchain worker.
//! At a some point, an inherent is called to submit the results of this file to the blockchain.
//! The pallet then selects the median of the results (reach perbill) from an evaluation pool and fills the storage with the result status.
//! The status of an identity can be:
//!
//! - **Non-existent**: Distance evaluation has not been requested or has expired.
//! - **Pending**: Distance evaluation for this identity has been requested and is awaiting results after two evaluation periods.
//! - **Valid**: Distance has been evaluated positively for this identity.
//!
//! The evaluation result is used by the `duniter-wot` pallet to determine if an identity can gain or should lose membership in the web of trust.
//!
//! ## Process
//!
//! Any account can request a distance evaluation for a given identity provided it has enough currency to reserve. In this case, the distance status is marked as pending, and in the next evaluation period, inherents can start to publish results.
//!
//! This is the process for publishing a result:
//!
//! 1. A local worker creates a file containing the computation result.
//! 2. An inherent is created with the data from this file.
//! 3. The author is registered as an evaluator.
//! 4. The result is added to the current evaluation pool.
//! 5. A flag is set to prevent other distance evaluations in the same block.
//!
//! At the start of each new evaluation period:
//!
//! 1. Old results set to expire at this period are removed.
//! 2. Results from the current pool (results from the previous period's pool) are processed, and for each identity:
//! - The median of the distance results for this identity is chosen.
//! - If the distance is acceptable, it is marked as valid.
//! - If the distance is not acceptable, the result for this identity is discarded, and reserved currency is slashed (from the account which requested the evaluation).
//!
//! Then, in other pallets, when a membership is claimed, it is possible to check if there is a valid distance evaluation for this identity.
//!
//! ## Pools
//!
//! Evaluation pools consist of two components:
//!
//! - A set of evaluators.
//! - A vector of results.
//!
//! The evaluations are divided into three pools:
//!
//! - Pool number N - 1 % 3: Results from the previous evaluation period used in the current one (emptied for the next evaluation period).
//! - Pool number N + 0 % 3: Inherent results are added here.
//! - Pool number N + 1 % 3: Identities are added here for evaluation.
#![cfg_attr(not(feature = "std"), no_std)]
mod median;
pub mod traits;
mod types;
mod weights;
pub mod benchmarking;
#[cfg(test)]
mod mock;
#[cfg(test)]
mod tests;
pub use pallet::*;
pub use traits::*;
pub use types::*;
pub use weights::WeightInfo;
use frame_support::{
traits::{
fungible::{self, hold, Credit, Mutate, MutateHold},
tokens::Precision,
OnUnbalanced, StorageVersion,
},
DefaultNoBound,
};
use sp_distance::{InherentError, INHERENT_IDENTIFIER};
use sp_inherents::{InherentData, InherentIdentifier};
use sp_runtime::{
traits::{One, Zero},
Saturating,
};
type IdtyIndex = u32;
/// Maximum number of identities to be evaluated in an evaluation period.
pub const MAX_EVALUATIONS_PER_SESSION: u32 = 1_300; // See https://git.duniter.org/nodes/rust/duniter-v2s/-/merge_requests/252
/// Maximum number of evaluators in an evaluation period.
pub const MAX_EVALUATORS_PER_SESSION: u32 = 100;
#[allow(unreachable_patterns)]
#[frame_support::pallet()]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use sp_runtime::Perbill;
pub type AccountIdOf<T> = <T as frame_system::Config>::AccountId;
pub type BalanceOf<T> = <<T as Config>::Currency as fungible::Inspect<AccountIdOf<T>>>::Balance;
#[pallet::composite_enum]
pub enum HoldReason {
/// The funds are held as deposit for the distance evaluation.
DistanceHold,
}
/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
#[pallet::without_storage_info]
pub struct Pallet<T>(PhantomData<T>);
#[pallet::config]
pub trait Config:
frame_system::Config
+ pallet_authorship::Config
+ pallet_identity::Config<IdtyIndex = IdtyIndex>
{
/// Currency type used in this pallet for reserve and slash operations.
type Currency: Mutate<Self::AccountId>
+ MutateHold<Self::AccountId, Reason = Self::RuntimeHoldReason>
+ hold::Balanced<Self::AccountId>;
/// The overarching hold reason type.
type RuntimeHoldReason: From<HoldReason>;
/// The amount reserved during evaluation.
#[pallet::constant]
type EvaluationPrice: Get<BalanceOf<Self>>;
/// The evaluation period in blocks.
/// Since the evaluation uses 3 pools, the total evaluation time will be 3 * EvaluationPeriod.
#[pallet::constant]
type EvaluationPeriod: Get<u32>;
/// The maximum distance used to define a referee's accessibility.
/// This value is not used by the runtime but is needed by the client distance oracle.
#[pallet::constant]
type MaxRefereeDistance: Get<u32>;
/// The minimum ratio of accessible referees required.
#[pallet::constant]
type MinAccessibleReferees: Get<Perbill>;
/// Handler for unbalanced reduction when invalid distance causes a slash.
type OnUnbalanced: OnUnbalanced<Credit<Self::AccountId, Self::Currency>>;
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
/// Type representing the weight of this pallet
type WeightInfo: WeightInfo;
/// A handler that is called when a distance evaluation is successfully validated.
type OnValidDistanceStatus: OnValidDistanceStatus<Self>;
/// A trait that provides a method to check if a distance evaluation request is allowed.
type CheckRequestDistanceEvaluation: CheckRequestDistanceEvaluation<Self>;
}
// STORAGE //
/// The first evaluation pool for distance evaluation queuing identities to evaluate for a given
/// evaluator account.
#[pallet::storage]
#[pallet::getter(fn evaluation_pool_0)]
pub type EvaluationPool0<T: Config> = StorageValue<
_,
EvaluationPool<
<T as frame_system::Config>::AccountId,
<T as pallet_identity::Config>::IdtyIndex,
>,
ValueQuery,
>;
/// The second evaluation pool for distance evaluation queuing identities to evaluate for a given
/// evaluator account.
#[pallet::storage]
#[pallet::getter(fn evaluation_pool_1)]
pub type EvaluationPool1<T: Config> = StorageValue<
_,
EvaluationPool<
<T as frame_system::Config>::AccountId,
<T as pallet_identity::Config>::IdtyIndex,
>,
ValueQuery,
>;
/// The third evaluation pool for distance evaluation queuing identities to evaluate for a given
/// evaluator account.
#[pallet::storage]
#[pallet::getter(fn evaluation_pool_2)]
pub type EvaluationPool2<T: Config> = StorageValue<
_,
EvaluationPool<
<T as frame_system::Config>::AccountId,
<T as pallet_identity::Config>::IdtyIndex,
>,
ValueQuery,
>;
/// The block at which the distance is evaluated.
#[pallet::storage]
pub type EvaluationBlock<T: Config> =
StorageValue<_, <T as frame_system::Config>::Hash, ValueQuery>;
/// The pending evaluation requesters.
#[pallet::storage]
#[pallet::getter(fn pending_evaluation_request)]
pub type PendingEvaluationRequest<T: Config> = StorageMap<
_,
Twox64Concat,
<T as pallet_identity::Config>::IdtyIndex,
<T as frame_system::Config>::AccountId,
OptionQuery,
>;
/// Store if the evaluation was updated in this block.
#[pallet::storage]
pub(super) type DidUpdate<T: Config> = StorageValue<_, bool, ValueQuery>;
/// The current evaluation period index.
#[pallet::storage]
#[pallet::getter(fn current_period_index)]
pub(super) type CurrentPeriodIndex<T: Config> = StorageValue<_, u32, ValueQuery>;
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// A distance evaluation was requested.
EvaluationRequested {
idty_index: T::IdtyIndex,
who: T::AccountId,
},
/// Distance rule was found valid.
EvaluatedValid {
idty_index: T::IdtyIndex,
distance: Perbill,
},
/// Distance rule was found invalid.
EvaluatedInvalid {
idty_index: T::IdtyIndex,
distance: Perbill,
},
}
// ERRORS //
#[pallet::error]
pub enum Error<T> {
/// Distance is already under evaluation.
AlreadyInEvaluation,
/// Too many evaluations requested by author.
TooManyEvaluationsByAuthor,
/// Too many evaluations for this block.
TooManyEvaluationsInBlock,
/// No author for this block.
NoAuthor,
/// Caller has no identity.
CallerHasNoIdentity,
/// Caller identity not found.
CallerIdentityNotFound,
/// Caller not member.
CallerNotMember,
// Caller status can only be Unvalidated, Member or NotMember.
CallerStatusInvalid,
/// Target identity not found.
TargetIdentityNotFound,
/// Evaluation queue is full.
QueueFull,
/// Too many evaluators in the current evaluation pool.
TooManyEvaluators,
/// Evaluation result has a wrong length.
WrongResultLength,
/// Targeted distance evaluation request is only possible for an unvalidated identity.
TargetMustBeUnvalidated,
}
#[pallet::genesis_config]
#[derive(DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
pub _config: core::marker::PhantomData<T>,
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
CurrentPeriodIndex::<T>::put(0u32);
}
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(block: BlockNumberFor<T>) -> Weight
where
BlockNumberFor<T>: From<u32>,
{
let mut weight = <T as pallet::Config>::WeightInfo::on_initialize_overhead();
if block % BlockNumberFor::<T>::one().saturating_mul(T::EvaluationPeriod::get().into())
== BlockNumberFor::<T>::zero()
{
let index = CurrentPeriodIndex::<T>::get() + 1;
CurrentPeriodIndex::<T>::put(index);
weight = weight
.saturating_add(Self::do_evaluation(index % 3))
.saturating_add(T::DbWeight::get().reads_writes(1, 1));
}
weight.saturating_add(<T as pallet::Config>::WeightInfo::on_finalize())
}
fn on_finalize(_n: BlockNumberFor<T>) {
DidUpdate::<T>::take();
}
}
// CALLS //
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Request evaluation of the caller's identity distance.
///
/// This function allows the caller to request an evaluation of their distance.
/// A positive evaluation will lead to claiming or renewing membership, while a negative
/// evaluation will result in slashing for the caller.
#[pallet::call_index(0)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::request_distance_evaluation())]
pub fn request_distance_evaluation(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
let idty = Self::check_request_distance_evaluation_self(&who)?;
Pallet::<T>::do_request_distance_evaluation(&who, idty)?;
Ok(().into())
}
/// Request evaluation of a target identity's distance.
///
/// This function allows the caller to request an evaluation of a specific target identity's distance.
/// This action is only permitted for unvalidated identities.
#[pallet::call_index(4)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::request_distance_evaluation_for())]
pub fn request_distance_evaluation_for(
origin: OriginFor<T>,
target: T::IdtyIndex,
) -> DispatchResultWithPostInfo {
let who = ensure_signed(origin)?;
Self::check_request_distance_evaluation_for(&who, target)?;
Pallet::<T>::do_request_distance_evaluation(&who, target)?;
Ok(().into())
}
/// Push an evaluation result to the pool.
///
/// This inherent function is called internally by validators to push an evaluation result
/// to the evaluation pool.
#[pallet::call_index(1)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::update_evaluation(MAX_EVALUATIONS_PER_SESSION))]
pub fn update_evaluation(
origin: OriginFor<T>,
computation_result: ComputationResult,
) -> DispatchResult {
// no origin = inherent
ensure_none(origin)?;
ensure!(
!DidUpdate::<T>::exists(),
Error::<T>::TooManyEvaluationsInBlock,
);
let author = pallet_authorship::Pallet::<T>::author().ok_or(Error::<T>::NoAuthor)?;
Pallet::<T>::do_update_evaluation(author, computation_result)?;
DidUpdate::<T>::set(true);
Ok(())
}
/// Force push an evaluation result to the pool.
///
/// It is primarily used for testing purposes.
///
/// - `origin`: Must be `Root`.
#[pallet::call_index(2)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::force_update_evaluation(MAX_EVALUATIONS_PER_SESSION))]
pub fn force_update_evaluation(
origin: OriginFor<T>,
evaluator: <T as frame_system::Config>::AccountId,
computation_result: ComputationResult,
) -> DispatchResult {
ensure_root(origin)?;
Pallet::<T>::do_update_evaluation(evaluator, computation_result)
}
/// Force set the distance evaluation status of an identity.
///
/// It is primarily used for testing purposes.
///
/// - `origin`: Must be `Root`.
#[pallet::call_index(3)]
#[pallet::weight(<T as pallet::Config>::WeightInfo::force_valid_distance_status())]
pub fn force_valid_distance_status(
origin: OriginFor<T>,
identity: <T as pallet_identity::Config>::IdtyIndex,
) -> DispatchResult {
ensure_root(origin)?;
Self::do_valid_distance_status(identity, Perbill::one());
Ok(())
}
}
// INTERNAL FUNCTIONS //
impl<T: Config> Pallet<T> {
/// Mutate the evaluation pool containing:
/// * when this period begins: the evaluation results to be applied.
/// * when this period ends: the evaluation requests.
fn mutate_current_pool<
R,
F: FnOnce(
&mut EvaluationPool<
<T as frame_system::Config>::AccountId,
<T as pallet_identity::Config>::IdtyIndex,
>,
) -> R,
>(
index: u32,
f: F,
) -> R {
match index {
0 => EvaluationPool2::<T>::mutate(f),
1 => EvaluationPool0::<T>::mutate(f),
2 => EvaluationPool1::<T>::mutate(f),
_ => unreachable!("index < 3"),
}
}
/// Mutate the evaluation pool containing the results sent by evaluators for this period.
fn mutate_next_pool<
R,
F: FnOnce(
&mut EvaluationPool<
<T as frame_system::Config>::AccountId,
<T as pallet_identity::Config>::IdtyIndex,
>,
) -> R,
>(
index: u32,
f: F,
) -> R {
match index {
0 => EvaluationPool0::<T>::mutate(f),
1 => EvaluationPool1::<T>::mutate(f),
2 => EvaluationPool2::<T>::mutate(f),
_ => unreachable!("index < 3"),
}
}
/// Take (*and leave empty*) the evaluation pool containing:
/// * when this period begins: the evaluation results to be applied.
/// * when this period ends: the evaluation requests.
#[allow(clippy::type_complexity)]
fn take_current_pool(
index: u32,
) -> EvaluationPool<
<T as frame_system::Config>::AccountId,
<T as pallet_identity::Config>::IdtyIndex,
> {
match index {
0 => EvaluationPool2::<T>::take(),
1 => EvaluationPool0::<T>::take(),
2 => EvaluationPool1::<T>::take(),
_ => unreachable!("index % 3 < 3"),
}
}
/// Check if requested distance evaluation is allowed.
fn check_request_distance_evaluation_self(
who: &T::AccountId,
) -> Result<<T as pallet_identity::Config>::IdtyIndex, DispatchError> {
// caller has an identity
let idty_index = pallet_identity::IdentityIndexOf::<T>::get(who)
.ok_or(Error::<T>::CallerHasNoIdentity)?;
let idty = pallet_identity::Identities::<T>::get(idty_index)
.ok_or(Error::<T>::CallerIdentityNotFound)?;
// caller is (Unvalidated, Member, NotMember)
ensure!(
idty.status == pallet_identity::IdtyStatus::Unvalidated
|| idty.status == pallet_identity::IdtyStatus::Member
|| idty.status == pallet_identity::IdtyStatus::NotMember,
Error::<T>::CallerStatusInvalid
);
Self::check_request_distance_evaluation_common(idty_index)?;
Ok(idty_index)
}
/// check that targeted request distance evaluation is allowed
fn check_request_distance_evaluation_for(
who: &T::AccountId,
target: <T as pallet_identity::Config>::IdtyIndex,
) -> Result<(), DispatchError> {
// caller has an identity
let caller_idty_index = pallet_identity::IdentityIndexOf::<T>::get(who)
.ok_or(Error::<T>::CallerHasNoIdentity)?;
let caller_idty = pallet_identity::Identities::<T>::get(caller_idty_index)
.ok_or(Error::<T>::CallerIdentityNotFound)?;
// caller is member
ensure!(
caller_idty.status == pallet_identity::IdtyStatus::Member,
Error::<T>::CallerNotMember
);
// target has an identity
let target_idty = pallet_identity::Identities::<T>::get(target)
.ok_or(Error::<T>::TargetIdentityNotFound)?;
// target is unvalidated
ensure!(
target_idty.status == pallet_identity::IdtyStatus::Unvalidated,
Error::<T>::TargetMustBeUnvalidated
);
Self::check_request_distance_evaluation_common(target)?;
Ok(())
}
// common checks between check_request_distance_evaluation _self and _for
fn check_request_distance_evaluation_common(
target: <T as pallet_identity::Config>::IdtyIndex,
) -> Result<(), DispatchError> {
// no pending evaluation request
ensure!(
PendingEvaluationRequest::<T>::get(target).is_none(),
Error::<T>::AlreadyInEvaluation
);
// external validation (wot)
// - membership renewal antispam
// - target has received enough certifications
T::CheckRequestDistanceEvaluation::check_request_distance_evaluation(target)
}
/// Request distance evaluation in the current pool.
fn do_request_distance_evaluation(
who: &T::AccountId,
idty_index: <T as pallet_identity::Config>::IdtyIndex,
) -> Result<(), DispatchError> {
Pallet::<T>::mutate_current_pool(CurrentPeriodIndex::<T>::get() % 3, |current_pool| {
// extrinsics are transactional by default, this check might not be needed
ensure!(
current_pool.evaluations.len() < (MAX_EVALUATIONS_PER_SESSION as usize),
Error::<T>::QueueFull
);
T::Currency::hold(
&HoldReason::DistanceHold.into(),
who,
<T as Config>::EvaluationPrice::get(),
)?;
current_pool
.evaluations
.try_push((idty_index, median::MedianAcc::new()))
.map_err(|_| Error::<T>::QueueFull)?;
PendingEvaluationRequest::<T>::insert(idty_index, who);
Self::deposit_event(Event::EvaluationRequested {
idty_index,
who: who.clone(),
});
Ok(())
})
}
/// Update distance evaluation in the next pool.
fn do_update_evaluation(
evaluator: <T as frame_system::Config>::AccountId,
computation_result: ComputationResult,
) -> DispatchResult {
Pallet::<T>::mutate_next_pool(CurrentPeriodIndex::<T>::get() % 3, |result_pool| {
// evaluation must be provided for all identities (no more, no less)
ensure!(
computation_result.distances.len() == result_pool.evaluations.len(),
Error::<T>::WrongResultLength
);
// insert the evaluator if not already there
if result_pool
.evaluators
.try_insert(evaluator.clone())
.map_err(|_| Error::<T>::TooManyEvaluators)?
{
// update the median accumulator with the new result
for (distance_value, (_identity, median_acc)) in computation_result
.distances
.into_iter()
.zip(result_pool.evaluations.iter_mut())
{
median_acc.push(distance_value);
}
Ok(())
} else {
// one author can only submit one evaluation
Err(Error::<T>::TooManyEvaluationsByAuthor.into())
}
})
}
/// Set the distance status using for an identity.
pub fn do_valid_distance_status(
idty: <T as pallet_identity::Config>::IdtyIndex,
distance: Perbill,
) {
// callback
T::OnValidDistanceStatus::on_valid_distance_status(idty);
// deposit event
Self::deposit_event(Event::EvaluatedValid {
idty_index: idty,
distance,
});
}
/// Perform evaluation for a specified pool.
///
/// This function executes evaluation logic based on the provided pool index. It retrieves the current
/// evaluation pool for the index, processes each evaluation, and handles the outcomes based on the
/// computed median distances. If a positive evaluation result is obtained, it releases reserved funds
/// and updates the distance status accordingly. For negative or inconclusive results, it slashes funds
/// or releases them, respectively.
pub fn do_evaluation(index: u32) -> Weight {
let mut weight = <T as pallet::Config>::WeightInfo::do_evaluation_overhead();
// set evaluation block
EvaluationBlock::<T>::set(frame_system::Pallet::<T>::parent_hash());
// Apply the results from the current pool (which was previous period's result pool)
// We take the results so the pool is left empty for the new period.
#[allow(clippy::type_complexity)]
let current_pool: EvaluationPool<
<T as frame_system::Config>::AccountId,
<T as pallet_identity::Config>::IdtyIndex,
> = Pallet::<T>::take_current_pool(index);
for (idty, median_acc) in current_pool.evaluations.into_iter() {
let mut distance_result: Option<Perbill> = None;
// Retrieve the result of the computation from the median accumulator
if let Some(median_result) = median_acc.get_median() {
let distance = match median_result {
MedianResult::One(m) => m,
MedianResult::Two(m1, m2) => m1 + (m2 - m1) / 2, // Avoid overflow (since max is 1)
};
// Update distance result
distance_result = Some(distance);
}
// If there's a pending evaluation request with the provided identity
if let Some(requester) = PendingEvaluationRequest::<T>::take(idty) {
// If distance_result is available
if let Some(distance) = distance_result {
if distance >= T::MinAccessibleReferees::get() {
// Positive result, unreserve and apply
let _ = T::Currency::release(
&HoldReason::DistanceHold.into(),
&requester,
<T as Config>::EvaluationPrice::get(),
Precision::Exact,
);
Self::do_valid_distance_status(idty, distance);
weight = weight.saturating_add(
<T as pallet::Config>::WeightInfo::do_evaluation_success()
.saturating_sub(
<T as pallet::Config>::WeightInfo::do_evaluation_overhead(),
),
);
} else {
// Negative result, slash and deposit event
let (imbalance, _) = <T::Currency as hold::Balanced<_>>::slash(
&HoldReason::DistanceHold.into(),
&requester,
<T as Config>::EvaluationPrice::get(),
);
T::OnUnbalanced::on_unbalanced(imbalance);
Self::deposit_event(Event::EvaluatedInvalid {
idty_index: idty,
distance,
});
weight = weight.saturating_add(
<T as pallet::Config>::WeightInfo::do_evaluation_failure()
.saturating_sub(
<T as pallet::Config>::WeightInfo::do_evaluation_overhead(),
),
);
}
} else {
// No result, unreserve
let _ = T::Currency::release(
&HoldReason::DistanceHold.into(),
&requester,
<T as Config>::EvaluationPrice::get(),
Precision::Exact,
);
weight = weight.saturating_add(
<T as pallet::Config>::WeightInfo::do_evaluation_failure()
.saturating_sub(
<T as pallet::Config>::WeightInfo::do_evaluation_overhead(),
),
);
}
}
// If evaluation happened without request, it's ok to do nothing
}
weight
}
}
#[pallet::inherent]
impl<T: Config> ProvideInherent for Pallet<T> {
type Call = Call<T>;
type Error = InherentError;
const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
fn create_inherent(data: &InherentData) -> Option<Self::Call> {
data.get_data::<ComputationResult>(&INHERENT_IDENTIFIER)
.expect("Distance inherent data not correctly encoded")
.map(|inherent_data| Call::update_evaluation {
computation_result: inherent_data,
})
}
fn is_inherent(call: &Self::Call) -> bool {
matches!(call, Self::Call::update_evaluation { .. })
}
}
}
// Copyright 2022 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use frame_support::pallet_prelude::*;
use scale_info::prelude::cmp::Ordering;
/// Represents a median accumulator.
#[derive(Clone, Debug, Decode, Default, Encode, TypeInfo)]
pub struct MedianAcc<
T: Clone + Decode + Encode + Ord + TypeInfo,
const S: u32, /*Get<u32> + TypeInfo*/
> {
samples: BoundedVec<(T, u32), ConstU32<S>>,
median_index: Option<u32>,
median_subindex: u32,
}
/*impl<T: 'static + Clone + Decode + Encode + Ord + TypeInfo, S: 'static + Get<u32>> TypeInfo
for MedianAcc<T, S>
{
type Identity = Self;
fn type_info() -> scale_info::Type<scale_info::form::MetaForm> {}
}*/
/// Represents the result of a median calculation.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum MedianResult<T: Clone + Ord> {
One(T),
Two(T, T),
}
impl<T: Clone + Decode + Encode + Ord + TypeInfo, const S: u32 /*Get<u32> + TypeInfo*/>
MedianAcc<T, S>
{
pub fn new() -> Self {
Self {
samples: BoundedVec::default(),
median_index: None,
median_subindex: 0,
}
}
pub fn push(&mut self, sample: T) {
if let Some(median_index) = &mut self.median_index {
match self
.samples
.binary_search_by_key(&sample, |(s, _n)| s.clone())
{
Ok(sample_index) => {
self.samples.get_mut(sample_index).expect("unreachable").1 += 1;
match (sample_index as u32).cmp(median_index) {
Ordering::Greater => {
if self.median_subindex
== self
.samples
.get(*median_index as usize)
.expect("unreachable")
.1
* 2
- 1
{
self.median_subindex = 0;
*median_index += 1;
} else {
self.median_subindex += 1;
}
}
Ordering::Equal => {
self.median_subindex += 1;
}
Ordering::Less => {
if self.median_subindex == 0 {
*median_index -= 1;
self.median_subindex = self
.samples
.get(*median_index as usize)
.expect("unreachable")
.1
* 2
- 1;
} else {
self.median_subindex -= 1;
}
}
}
}
Err(sample_index) => {
self.samples.try_insert(sample_index, (sample, 1)).ok();
if *median_index as usize >= sample_index {
if self.median_subindex == 0 {
self.median_subindex = self
.samples
.get(*median_index as usize)
.expect("unreachable")
.1
* 2
- 1;
} else {
self.median_subindex -= 1;
*median_index += 1;
}
} else if self.median_subindex
== self
.samples
.get(*median_index as usize)
.expect("unreachable")
.1
* 2
- 1
{
self.median_subindex = 0;
*median_index += 1;
} else {
self.median_subindex += 1;
}
}
}
} else {
self.samples.try_push((sample, 1)).ok();
self.median_index = Some(0);
}
}
pub fn get_median(&self) -> Option<MedianResult<T>> {
self.median_index.map(|median_index| {
let (median_sample, median_n) = self
.samples
.get(median_index as usize)
.expect("unreachable");
if self.median_subindex == median_n * 2 - 1 {
MedianResult::Two(
median_sample.clone(),
self.samples
.get(median_index as usize + 1)
.expect("unreachable")
.0
.clone(),
)
} else {
MedianResult::One(median_sample.clone())
}
})
}
}
// Copyright 2023 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use crate::{self as pallet_distance};
use core::marker::PhantomData;
use frame_support::{
derive_impl, parameter_types,
traits::{Everything, OnFinalize, OnInitialize},
};
use frame_system as system;
use pallet_balances::AccountData;
use pallet_session::ShouldEndSession;
use sp_core::{ConstU32, H256};
use sp_runtime::{
impl_opaque_keys,
key_types::DUMMY,
testing::{TestSignature as SubtrateTestSignature, UintAuthorityId},
traits::{BlakeTwo256, ConvertInto, IdentityLookup, IsMember, OpaqueKeys},
BuildStorage, KeyTypeId, Perbill,
};
type Balance = u64;
type Block = frame_system::mocking::MockBlock<Test>;
pub type AccountId = u64;
pub struct AccountId32Mock(u64);
impl From<AccountId32Mock> for u64 {
fn from(account: AccountId32Mock) -> u64 {
account.0
}
}
impl From<[u8; 32]> for AccountId32Mock {
fn from(bytes: [u8; 32]) -> Self {
Self(u64::from_be_bytes([
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7],
]))
}
}
/// Test signature that impl From<ed25519::Signature> (required to compile pallet identity)
#[derive(
Clone,
codec::DecodeWithMemTracking,
codec::Decode,
Debug,
Eq,
codec::Encode,
PartialEq,
scale_info::TypeInfo,
)]
pub struct TestSignature(SubtrateTestSignature);
impl From<sp_core::ed25519::Signature> for TestSignature {
fn from(_: sp_core::ed25519::Signature) -> Self {
// Implementation here only to satisfy traits bounds at compilation
// This convertion should not be used inside pallet distance tests
unimplemented!()
}
}
impl sp_runtime::traits::Verify for TestSignature {
type Signer = UintAuthorityId;
fn verify<L: sp_runtime::traits::Lazy<[u8]>>(&self, msg: L, signer: &u64) -> bool {
<SubtrateTestSignature as sp_runtime::traits::Verify>::verify::<L>(&self.0, msg, signer)
}
}
impl_opaque_keys! {
pub struct MockSessionKeys {
pub dummy: UintAuthorityId,
}
}
impl From<UintAuthorityId> for MockSessionKeys {
fn from(dummy: UintAuthorityId) -> Self {
Self { dummy }
}
}
// Configure a mock runtime to test the pallet.
frame_support::construct_runtime!(
pub enum Test {
System: frame_system,
Session: pallet_session,
Authorship: pallet_authorship,
AuthorityMembers: pallet_authority_members,
Balances: pallet_balances,
Identity: pallet_identity,
Distance: pallet_distance,
}
);
parameter_types! {
pub const BlockHashCount: u64 = 250;
pub const SS58Prefix: u8 = 42;
}
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl system::Config for Test {
type AccountData = AccountData<u64>;
type AccountId = AccountId;
type BaseCallFilter = Everything;
type Block = Block;
type BlockHashCount = BlockHashCount;
type Hash = H256;
type Hashing = BlakeTwo256;
type Lookup = IdentityLookup<Self::AccountId>;
type MaxConsumers = frame_support::traits::ConstU32<16>;
type Nonce = u64;
type PalletInfo = PalletInfo;
type RuntimeCall = RuntimeCall;
type RuntimeEvent = RuntimeEvent;
type RuntimeOrigin = RuntimeOrigin;
type SS58Prefix = SS58Prefix;
}
pub struct TestSessionHandler;
impl pallet_session::SessionHandler<AccountId> for TestSessionHandler {
const KEY_TYPE_IDS: &'static [KeyTypeId] = &[DUMMY];
fn on_new_session<Ks: OpaqueKeys>(
_changed: bool,
_validators: &[(AccountId, Ks)],
_queued_validators: &[(AccountId, Ks)],
) {
}
fn on_disabled(_validator_index: u32) {}
fn on_genesis_session<Ks: OpaqueKeys>(_validators: &[(AccountId, Ks)]) {}
}
const SESSION_LENGTH: u64 = 5;
pub struct TestShouldEndSession;
impl ShouldEndSession<u64> for TestShouldEndSession {
fn should_end_session(now: u64) -> bool {
now % SESSION_LENGTH == 0
}
}
impl pallet_session::Config for Test {
type DisablingStrategy = ();
type Keys = MockSessionKeys;
type NextSessionRotation = ();
type RuntimeEvent = RuntimeEvent;
type SessionHandler = TestSessionHandler;
type SessionManager = AuthorityMembers;
type ShouldEndSession = TestShouldEndSession;
type ValidatorId = AccountId;
type ValidatorIdOf = ConvertInto;
type WeightInfo = ();
}
pub struct FullIdentificationOfImpl;
impl sp_runtime::traits::Convert<AccountId, Option<()>> for FullIdentificationOfImpl {
fn convert(_: AccountId) -> Option<()> {
Some(())
}
}
impl pallet_session::historical::Config for Test {
type FullIdentification = ();
type FullIdentificationOf = FullIdentificationOfImpl;
}
pub struct ConstantAuthor<T>(PhantomData<T>);
impl<T: From<u64>> frame_support::traits::FindAuthor<T> for ConstantAuthor<T> {
fn find_author<'a, I>(_: I) -> Option<T>
where
I: 'a + IntoIterator<Item = (sp_runtime::ConsensusEngineId, &'a [u8])>,
{
Some(1u64.into())
}
}
impl pallet_authorship::Config for Test {
type EventHandler = ();
type FindAuthor = ConstantAuthor<Self::AccountId>;
}
pub struct TestIsSmithMember;
impl IsMember<u32> for TestIsSmithMember {
fn is_member(member_id: &u32) -> bool {
member_id % 3 == 0
}
}
pub struct IdentityIndexOf<T: pallet_identity::Config>(PhantomData<T>);
impl<T: pallet_identity::Config> sp_runtime::traits::Convert<T::AccountId, Option<T::IdtyIndex>>
for IdentityIndexOf<T>
{
fn convert(account_id: T::AccountId) -> Option<T::IdtyIndex> {
pallet_identity::Pallet::<T>::identity_index_of(account_id)
}
}
impl pallet_authority_members::Config for Test {
type IsMember = TestIsSmithMember;
type MaxAuthorities = ConstU32<4>;
type MemberId = u32;
type MemberIdOf = IdentityIndexOf<Self>;
type OnIncomingMember = ();
type OnNewSession = ();
type OnOutgoingMember = ();
type RemoveMemberOrigin = system::EnsureRoot<AccountId>;
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
}
parameter_types! {
pub const ExistentialDeposit: Balance = 10;
pub const MaxLocks: u32 = 50;
}
impl pallet_balances::Config for Test {
type AccountStore = System;
type Balance = Balance;
type DoneSlashHandler = ();
type DustRemoval = ();
type ExistentialDeposit = ExistentialDeposit;
type FreezeIdentifier = ();
type MaxFreezes = ConstU32<0>;
type MaxLocks = MaxLocks;
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type RuntimeEvent = RuntimeEvent;
type RuntimeFreezeReason = ();
type RuntimeHoldReason = RuntimeHoldReason;
type WeightInfo = pallet_balances::weights::SubstrateWeight<Test>;
}
parameter_types! {
pub const ChangeOwnerKeyPeriod: u64 = 10;
pub const ConfirmPeriod: u64 = 2;
pub const ValidationPeriod: u64 = 3;
pub const AutorevocationPeriod: u64 = 5;
pub const DeletionPeriod: u64 = 7;
pub const IdtyCreationPeriod: u64 = 3;
}
pub struct IdtyNameValidatorTestImpl;
impl pallet_identity::traits::IdtyNameValidator for IdtyNameValidatorTestImpl {
fn validate(idty_name: &pallet_identity::IdtyName) -> bool {
idty_name.0.len() < 16
}
}
impl pallet_identity::Config for Test {
type AccountId32 = AccountId32Mock;
type AccountLinker = ();
type AutorevocationPeriod = AutorevocationPeriod;
type ChangeOwnerKeyPeriod = ChangeOwnerKeyPeriod;
type CheckAccountWorthiness = ();
type CheckIdtyCallAllowed = ();
type ConfirmPeriod = ConfirmPeriod;
type DeletionPeriod = DeletionPeriod;
type IdtyCreationPeriod = IdtyCreationPeriod;
type IdtyData = ();
type IdtyIndex = u32;
type IdtyNameValidator = IdtyNameValidatorTestImpl;
type OnKeyChange = ();
type OnNewIdty = ();
type OnRemoveIdty = ();
type RuntimeEvent = RuntimeEvent;
type Signature = TestSignature;
type Signer = UintAuthorityId;
type ValidationPeriod = ValidationPeriod;
type WeightInfo = ();
}
parameter_types! {
pub const MinAccessibleReferees: Perbill = Perbill::from_percent(80);
}
impl pallet_distance::Config for Test {
type CheckRequestDistanceEvaluation = ();
type Currency = Balances;
type EvaluationPeriod = frame_support::traits::ConstU32<300>;
type EvaluationPrice = frame_support::traits::ConstU64<1000>;
type MaxRefereeDistance = frame_support::traits::ConstU32<5>;
type MinAccessibleReferees = MinAccessibleReferees;
type OnUnbalanced = ();
type OnValidDistanceStatus = ();
type RuntimeEvent = RuntimeEvent;
type RuntimeHoldReason = RuntimeHoldReason;
type WeightInfo = ();
}
// Build genesis storage according to the mock runtime.
#[allow(dead_code)] // ??? Clippy triggers dead code for new_test_ext while it is used during test benchmark
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::<Test>::default()
.build_storage()
.unwrap();
pub const NAMES: [&str; 6] = ["Alice", "Bob", "Charlie", "Dave", "Eve", "Ferdie"];
pallet_identity::GenesisConfig::<Test> {
identities: (1..=4)
.map(|i| pallet_identity::GenesisIdty {
index: i as u32,
name: pallet_identity::IdtyName::from(NAMES[i - 1]),
value: pallet_identity::IdtyValue {
data: (),
next_creatable_identity_on: 0,
owner_key: i as u64,
old_owner_key: None,
next_scheduled: 0,
status: pallet_identity::IdtyStatus::Member,
},
})
.collect(),
}
.assimilate_storage(&mut t)
.unwrap();
sp_io::TestExternalities::new(t)
}
pub fn run_to_block(n: u64) {
while System::block_number() < n {
Session::on_finalize(System::block_number());
System::on_finalize(System::block_number());
System::reset_events();
System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number());
Session::on_initialize(System::block_number());
}
}
// Copyright 2023 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use crate::{mock::*, *};
use frame_support::{assert_noop, assert_ok, traits::fungible::Mutate};
// allow request distance evaluation for oneself
#[test]
fn test_request_distance_evaluation() {
new_test_ext().execute_with(|| {
run_to_block(1);
// give enough for reserve
Balances::set_balance(&1, 10_000);
// call request
assert_ok!(Distance::request_distance_evaluation(
RuntimeOrigin::signed(1)
));
System::assert_has_event(RuntimeEvent::Distance(Event::EvaluationRequested {
idty_index: 1,
who: 1,
}));
// currency was reserved
assert_eq!(Balances::reserved_balance(1), 1000);
});
}
// allow request distance evaluation for an unvalidated identity
#[test]
fn test_request_distance_evaluation_for() {
new_test_ext().execute_with(|| {
run_to_block(1);
// give enough for reserve
Balances::set_balance(&1, 10_000);
assert_ok!(Identity::create_identity(RuntimeOrigin::signed(1), 5));
assert_ok!(Identity::confirm_identity(
RuntimeOrigin::signed(5),
"Eeeve".into()
));
// call request
assert_ok!(Distance::request_distance_evaluation_for(
RuntimeOrigin::signed(1),
5
));
System::assert_has_event(RuntimeEvent::Distance(Event::EvaluationRequested {
idty_index: 5,
who: 1,
}));
// currency was reserved
assert_eq!(Balances::reserved_balance(1), 1000);
});
}
// non member can not request distance evaluation
#[test]
fn test_request_distance_evaluation_non_member() {
new_test_ext().execute_with(|| {
run_to_block(1);
// give enough for reserve
Balances::set_balance(&5, 10_000);
assert_noop!(
Distance::request_distance_evaluation_for(RuntimeOrigin::signed(5), 1),
Error::<Test>::CallerHasNoIdentity
);
assert_ok!(Identity::create_identity(RuntimeOrigin::signed(1), 5));
assert_noop!(
Distance::request_distance_evaluation_for(RuntimeOrigin::signed(5), 1),
Error::<Test>::CallerNotMember
);
});
}
// can not request distance eval if already in evaluation
#[test]
fn test_request_distance_evaluation_twice() {
new_test_ext().execute_with(|| {
run_to_block(1);
// give enough for reserve
Balances::set_balance(&1, 10_000);
assert_ok!(Distance::request_distance_evaluation(
RuntimeOrigin::signed(1)
));
assert_noop!(
Distance::request_distance_evaluation(RuntimeOrigin::signed(1)),
Error::<Test>::AlreadyInEvaluation
);
});
}
// Copyright 2023 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use crate::*;
use frame_support::pallet_prelude::*;
/// Trait for handling actions when an identity has a valid distance status.
pub trait OnValidDistanceStatus<T: Config> {
/// Called when an identity has been determined to have a valid distance status.
fn on_valid_distance_status(idty_index: T::IdtyIndex);
}
impl<T: Config> OnValidDistanceStatus<T> for () {
fn on_valid_distance_status(_idty_index: T::IdtyIndex) {}
}
/// Trait for checking if a request for distance evaluation is allowed.
pub trait CheckRequestDistanceEvaluation<T: Config> {
/// Check if the request for distance evaluation is allowed for the given identity.
fn check_request_distance_evaluation(idty_index: T::IdtyIndex) -> Result<(), DispatchError>;
}
impl<T: Config> CheckRequestDistanceEvaluation<T> for () {
fn check_request_distance_evaluation(_idty_index: T::IdtyIndex) -> Result<(), DispatchError> {
Ok(())
}
}
// Copyright 2022 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
pub use crate::{median::*, MAX_EVALUATIONS_PER_SESSION, MAX_EVALUATORS_PER_SESSION};
pub use sp_distance::ComputationResult;
use codec::{Decode, Encode};
use frame_support::pallet_prelude::*;
use sp_runtime::Perbill;
/// Status of the distance evaluation of an identity.
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub enum DistanceStatus {
/// Identity is in evaluation.
Pending,
/// Identity respects the distance.
Valid,
/// Identity doesn't respect the distance.
Invalid,
}
/// Represents a pool where distance evaluation requests and results are stored.
///
/// Depending on the pool rotation, this may not be complete and may still be accepting
/// new evaluation requests (with empty median accumulators) or new evaluations (with evaluators and new samples in the median accumulators).
#[derive(Encode, Decode, Clone, RuntimeDebug, TypeInfo)]
pub struct EvaluationPool<AccountId: Ord, IdtyIndex> {
/// List of identities with their evaluation result.
/// The result is the median of all the evaluations.
pub evaluations: BoundedVec<
(IdtyIndex, MedianAcc<Perbill, MAX_EVALUATORS_PER_SESSION>),
ConstU32<MAX_EVALUATIONS_PER_SESSION>,
>,
/// Evaluators who have published a result.
/// Its length should be the same as the number of samples
/// in each evaluation result `MedianAcc`.
/// An evaluator is not allowed to publish twice in a single session.
pub evaluators: BoundedBTreeSet<AccountId, ConstU32<MAX_EVALUATORS_PER_SESSION>>,
}
impl<AccountId: Ord, IdtyIndex> Default for EvaluationPool<AccountId, IdtyIndex> {
fn default() -> Self {
Self {
evaluations: BoundedVec::default(),
evaluators: BoundedBTreeSet::new(),
}
}
}
// Copyright 2021-2023 Axiom-Team
//
// This file is part of Duniter-v2S.
//
// Duniter-v2S is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
#![allow(clippy::unnecessary_cast)]
use frame_support::weights::{constants::RocksDbWeight, Weight};
pub trait WeightInfo {
fn request_distance_evaluation() -> Weight;
fn request_distance_evaluation_for() -> Weight;
fn update_evaluation(i: u32) -> Weight;
fn force_update_evaluation(i: u32) -> Weight;
fn force_valid_distance_status() -> Weight;
fn on_initialize_overhead() -> Weight;
fn do_evaluation_overhead() -> Weight;
fn do_evaluation_success() -> Weight;
fn do_evaluation_failure() -> Weight;
fn on_finalize() -> Weight;
}
// Insecure weights implementation, use it for tests only!
impl WeightInfo for () {
fn request_distance_evaluation() -> Weight {
// Proof Size summary in bytes:
// Measured: `1280`
// Estimated: `4745`
// Minimum execution time: 876_053_000 picoseconds.
Weight::from_parts(898_445_000, 0)
.saturating_add(Weight::from_parts(0, 4745))
.saturating_add(RocksDbWeight::get().reads(8))
.saturating_add(RocksDbWeight::get().writes(3))
}
fn request_distance_evaluation_for() -> Weight {
// Proof Size summary in bytes:
// Measured: `1485`
// Estimated: `7425`
// Minimum execution time: 1_118_982_000 picoseconds.
Weight::from_parts(1_292_782_000, 0)
.saturating_add(Weight::from_parts(0, 7425))
.saturating_add(RocksDbWeight::get().reads(10))
.saturating_add(RocksDbWeight::get().writes(3))
}
fn update_evaluation(i: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `773 + i * (10 ±0)`
// Estimated: `2256 + i * (10 ±0)`
// Minimum execution time: 463_878_000 picoseconds.
Weight::from_parts(743_823_548, 0)
.saturating_add(Weight::from_parts(0, 2256))
// Standard Error: 292_144
.saturating_add(Weight::from_parts(1_326_639, 0).saturating_mul(i.into()))
.saturating_add(RocksDbWeight::get().reads(6))
.saturating_add(RocksDbWeight::get().writes(3))
.saturating_add(Weight::from_parts(0, 10).saturating_mul(i.into()))
}
fn force_update_evaluation(i: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `612 + i * (10 ±0)`
// Estimated: `2095 + i * (10 ±0)`
// Minimum execution time: 208_812_000 picoseconds.
Weight::from_parts(257_150_521, 0)
.saturating_add(Weight::from_parts(0, 2095))
// Standard Error: 53_366
.saturating_add(Weight::from_parts(1_841_329, 0).saturating_mul(i.into()))
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
.saturating_add(Weight::from_parts(0, 10).saturating_mul(i.into()))
}
fn force_valid_distance_status() -> Weight {
// Proof Size summary in bytes:
// Measured: `1181`
// Estimated: `7121`
// Minimum execution time: 873_892_000 picoseconds.
Weight::from_parts(1_081_510_000, 0)
.saturating_add(Weight::from_parts(0, 7121))
.saturating_add(RocksDbWeight::get().reads(7))
.saturating_add(RocksDbWeight::get().writes(5))
}
fn do_evaluation_success() -> Weight {
// Proof Size summary in bytes:
// Measured: `612 + i * (10 ±0)`
// Estimated: `2095 + i * (10 ±0)`
// Minimum execution time: 208_812_000 picoseconds.
Weight::from_parts(257_150_521, 0)
.saturating_add(Weight::from_parts(0, 2095))
// Standard Error: 53_366
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
fn do_evaluation_failure() -> Weight {
// Proof Size summary in bytes:
// Measured: `612 + i * (10 ±0)`
// Estimated: `2095 + i * (10 ±0)`
// Minimum execution time: 208_812_000 picoseconds.
Weight::from_parts(257_150_521, 0)
.saturating_add(Weight::from_parts(0, 2095))
// Standard Error: 53_366
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
fn do_evaluation_overhead() -> Weight {
// Proof Size summary in bytes:
// Measured: `612 + i * (10 ±0)`
// Estimated: `2095 + i * (10 ±0)`
// Minimum execution time: 208_812_000 picoseconds.
Weight::from_parts(257_150_521, 0)
.saturating_add(Weight::from_parts(0, 2095))
// Standard Error: 53_366
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
fn on_initialize_overhead() -> Weight {
// Proof Size summary in bytes:
// Measured: `170`
// Estimated: `1655`
// Minimum execution time: 93_595_000 picoseconds.
Weight::from_parts(109_467_000, 0)
.saturating_add(Weight::from_parts(0, 1655))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
fn on_finalize() -> Weight {
// Proof Size summary in bytes:
// Measured: `170`
// Estimated: `1655`
// Minimum execution time: 93_595_000 picoseconds.
Weight::from_parts(109_467_000, 0)
.saturating_add(Weight::from_parts(0, 1655))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
}
[package] [package]
authors = ['librelois <c@elo.tf>'] authors.workspace = true
description = 'FRAME pallet duniter account.' description = "duniter pallet for account management"
edition = "2021" edition.workspace = true
homepage = 'https://duniter.org' homepage.workspace = true
license = 'AGPL-3.0' license.workspace = true
name = 'pallet-duniter-account' name = "pallet-duniter-account"
readme = 'README.md' repository.workspace = true
repository = 'https://git.duniter.org/nodes/rust/duniter-v2s' version.workspace = true
version = '3.0.0'
[features] [features]
default = ['std'] default = ["std"]
runtime-benchmarks = ['frame-benchmarking'] runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-identity/runtime-benchmarks",
"pallet-quota/runtime-benchmarks",
"pallet-transaction-payment/runtime-benchmarks",
"pallet-treasury/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
std = [ std = [
'codec/std', "codec/std",
'frame-support/std', "duniter-primitives/std",
'frame-system/std', "frame-benchmarking?/std",
'frame-benchmarking/std', "frame-support/std",
'pallet-balances/std', "frame-system/std",
'pallet-provide-randomness/std', "log/std",
'pallet-treasury/std', "pallet-balances/std",
'serde', "pallet-identity/std",
'sp-core/std', "pallet-quota/std",
'sp-io/std', "pallet-transaction-payment/std",
'sp-runtime/std', "pallet-treasury/std",
'sp-std/std', "scale-info/std",
"serde/std",
"sp-api/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-balances/try-runtime",
"pallet-identity/try-runtime",
"pallet-quota/try-runtime",
"pallet-transaction-payment/try-runtime",
"pallet-treasury/try-runtime",
"sp-runtime/try-runtime",
] ]
try-runtime = ['frame-support/try-runtime']
[dependencies]
# local
pallet-provide-randomness = { path = "../provide-randomness", default-features = false }
# crates.io
codec = { package = 'parity-scale-codec', version = "3.1.5", default-features = false, features = ["derive"] }
log = { version = "0.4.14", default-features = false }
scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
# substrate
[dependencies.frame-benchmarking]
default-features = false
git = 'https://github.com/duniter/substrate'
optional = true
branch = 'duniter-substrate-v0.9.32'
[dependencies.frame-support]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.frame-system]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.pallet-balances]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.pallet-treasury]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.serde]
version = "1.0.101"
optional = true
features = ["derive"]
[dependencies.sp-core]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.sp-io]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.sp-runtime]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dependencies.sp-std]
default-features = false
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
### DOC ###
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ['x86_64-unknown-linux-gnu'] default-features = false
targets = ["x86_64-unknown-linux-gnu"]
### DEV ###
[dev-dependencies.pallet-balances]
git = 'https://github.com/duniter/substrate'
branch = 'duniter-substrate-v0.9.32'
[dev-dependencies.maplit]
version = '1.0.2'
[dev-dependencies.serde]
version = '1.0.119'
[dev-dependencies.sp-io] [dependencies]
git = 'https://github.com/duniter/substrate' # local
branch = 'duniter-substrate-v0.9.32' pallet-quota = { workspace = true }
pallet-identity = { workspace = true }
duniter-primitives = { workspace = true }
codec = { workspace = true, features = ["derive"] }
log = { workspace = true }
pallet-balances = { workspace = true }
pallet-treasury = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
sp-api = { workspace = true }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
serde = { workspace = true, features = ["derive"] }
pallet-transaction-payment = { workspace = true }
# Duniter account pallet
Duniter customizes the `AccountData` of the `Balances` Substrate pallet. In particular, it adds a field `RandomId`.
## RandomID
The RandomId field was added with the idea to provide a unique id that can not be controlled by user to serve as a basis for robust identification. The discussion is available on the forum.
https://forum.duniter.org/t/la-solution-pour-des-identicones-securisees-le-random-id/9126
## Account creation fee
DuniterAccount defines a creation fee that is preleved to the account one block after its creation. This fee goes to the treasury.
## Sufficient
DuniterAccount tweaks the substrate AccountInfo to allow identity accounts to exist without existential deposit. This allows to spare the creation fee.
\ No newline at end of file