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
  • distance
  • elois-ci-binary-release
  • elois-compose-metrics
  • elois-duniter-storage
  • elois-fix-85
  • elois-opti-cert
  • elois-remove-renewable-period
  • elois-rework-certs
  • elois-smoldot
  • elois-substrate-v0.9.23
  • elois-technical-commitee
  • hugo-cucumber-identity
  • master
  • no-bootnodes
  • poc-oneshot-accounts
  • release/runtime-100
  • release/runtime-200
  • ts-types
  • ud-time-64
  • runtime-100
  • runtime-101
  • runtime-102
  • runtime-103
  • runtime-104
  • runtime-105
  • runtime-200
  • runtime-201
  • v0.1.0
28 results
Show changes
Showing
with 3812 additions and 1467 deletions
// 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};
/// Weight functions needed for pallet_universal_dividend.
pub trait WeightInfo {
fn unlink_identity() -> Weight;
fn on_revoke_identity() -> Weight;
}
// Insecure weights implementation, use it for tests only!
impl WeightInfo for () {
/// Storage: System Account (r:1 w:0)
/// Proof: System Account (max_values: None, max_size: Some(126), added: 2601, mode: MaxEncodedLen)
fn unlink_identity() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `3591`
// Minimum execution time: 95_130_000 picoseconds.
Weight::from_parts(110_501_000, 0)
.saturating_add(Weight::from_parts(0, 3591))
.saturating_add(RocksDbWeight::get().reads(1))
}
fn on_revoke_identity() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `3591`
// Minimum execution time: 95_130_000 picoseconds.
Weight::from_parts(110_501_000, 0)
.saturating_add(Weight::from_parts(0, 3591))
.saturating_add(RocksDbWeight::get().reads(1))
}
}
[package] [package]
authors = ['librelois <c@elo.tf>'] authors.workspace = true
description = 'Duniter test parameters.' description = "duniter pallet test parameters"
edition = '2018' edition.workspace = true
homepage = 'https://substrate.dev' homepage.workspace = true
license = 'AGPL-3.0' license.workspace = true
name = 'pallet-duniter-test-parameters' name = "pallet-duniter-test-parameters"
repository = 'https://git.duniter.org/nodes/rust/duniter-v2s' repository.workspace = true
version = '3.0.0' version.workspace = true
[features] [features]
default = ['std'] default = ["std"]
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', "frame-support/std",
'frame-system/std', "frame-system/std",
'frame-benchmarking/std', "scale-info/std",
'serde', "serde/std",
"sp-core/std",
"sp-io/std", "sp-io/std",
"sp-std/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]
pallet-duniter-test-parameters-macro = { path = "macro" }
serde = { version = "1.0.101", features = ["derive"], optional = true }
# substrate
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
[dependencies.codec]
default-features = false
features = ['derive']
package = 'parity-scale-codec'
version = '2.3.1'
[dependencies.frame-benchmarking]
default-features = false
git = 'https://github.com/librelois/substrate.git'
optional = true
branch = 'duniter-monthly-2022-02'
[dependencies.frame-support]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.frame-system]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-io]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-std]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-runtime] [package.metadata.docs.rs]
default-features = false default-features = false
git = 'https://github.com/librelois/substrate.git' targets = ["x86_64-unknown-linux-gnu"]
branch = 'duniter-monthly-2022-02'
### DOC ###
[package.metadata.docs.rs] [dependencies]
targets = ['x86_64-unknown-linux-gnu'] codec = { workspace = true, features = ["derive"] }
frame-support = { workspace = true }
frame-system = { workspace = true }
pallet-duniter-test-parameters-macro = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
serde = { workspace = true, features = ["derive"] }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
[package] [package]
authors = ['librelois <c@elo.tf>'] authors.workspace = true
description = 'Duniter test parameters macro.' description = "duniter test parameters macro"
edition = '2018' edition.workspace = true
homepage = 'https://substrate.dev' homepage.workspace = true
license = 'AGPL-3.0' license.workspace = true
name = 'pallet-duniter-test-parameters-macro' name = "pallet-duniter-test-parameters-macro"
repository = 'https://git.duniter.org/nodes/rust/duniter-v2s' repository.workspace = true
version = '3.0.0' version.workspace = true
[lib] [lib]
proc-macro = true proc-macro = true
[dependencies] [dependencies]
num_enum = { version = "0.5.3", default-features = false } proc-macro2 = { workspace = true, default-features = false }
proc-macro2 = "1.0" quote = { workspace = true, default-features = false }
quote = "1.0" syn = { workspace = true, features = ["extra-traits", "fold", "full", "visit"] }
syn = { version = "1.0", features = [ "extra-traits", "fold", "full", "visit" ] }
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
#![crate_type = "proc-macro"] #![crate_type = "proc-macro"]
extern crate proc_macro; extern crate proc_macro;
......
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
//! # Duniter Test Parameters Pallet
//!
//! This pallet allows ĞDev runtime to tweak parameter values instead of having it as runtime constants.
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
...@@ -24,58 +28,54 @@ pub mod types { ...@@ -24,58 +28,54 @@ pub mod types {
use codec::{Decode, Encode}; use codec::{Decode, Encode};
use frame_support::pallet_prelude::*; use frame_support::pallet_prelude::*;
use pallet_duniter_test_parameters_macro::generate_fields_getters; use pallet_duniter_test_parameters_macro::generate_fields_getters;
use scale_info::TypeInfo;
#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};
#[generate_fields_getters] #[generate_fields_getters]
#[cfg_attr(feature = "std", derive(Deserialize, Serialize))] #[derive(
#[derive(Encode, Decode, Default, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)] Default,
Encode,
Decode,
Clone,
PartialEq,
serde::Serialize,
serde::Deserialize,
scale_info::TypeInfo,
)]
pub struct Parameters< pub struct Parameters<
BlockNumber: Default + Parameter, BlockNumber: Default + Parameter,
CertCount: Default + Parameter, CertCount: Default + Parameter,
PeriodCount: Default + Parameter, PeriodCount: Default + Parameter,
SessionCount: Default + Parameter,
> { > {
pub babe_epoch_duration: PeriodCount, pub babe_epoch_duration: PeriodCount,
pub cert_period: BlockNumber, pub cert_period: BlockNumber,
pub cert_max_by_issuer: CertCount, pub cert_max_by_issuer: CertCount,
pub cert_min_received_cert_to_issue_cert: CertCount, pub cert_min_received_cert_to_issue_cert: CertCount,
pub cert_renewable_period: BlockNumber,
pub cert_validity_period: BlockNumber, pub cert_validity_period: BlockNumber,
pub idty_confirm_period: BlockNumber, pub idty_confirm_period: BlockNumber,
pub idty_creation_period: BlockNumber, pub idty_creation_period: BlockNumber,
pub membership_period: BlockNumber, pub membership_period: BlockNumber,
pub membership_renewable_period: BlockNumber, pub membership_renewal_period: BlockNumber,
pub pending_membership_period: BlockNumber, pub ud_creation_period: PeriodCount,
pub ud_creation_period: BlockNumber, pub ud_reeval_period: PeriodCount,
pub ud_reeval_period: BlockNumber,
pub smith_cert_period: BlockNumber,
pub smith_cert_max_by_issuer: CertCount, pub smith_cert_max_by_issuer: CertCount,
pub smith_cert_min_received_cert_to_issue_cert: CertCount, pub smith_wot_min_cert_for_membership: CertCount,
pub smith_cert_renewable_period: BlockNumber, pub smith_inactivity_max_duration: SessionCount,
pub smith_cert_validity_period: BlockNumber,
pub smith_membership_period: BlockNumber,
pub smith_membership_renewable_period: BlockNumber,
pub smith_pending_membership_period: BlockNumber,
pub smiths_wot_first_cert_issuable_on: BlockNumber,
pub smiths_wot_min_cert_for_membership: CertCount,
pub wot_first_cert_issuable_on: BlockNumber, pub wot_first_cert_issuable_on: BlockNumber,
pub wot_min_cert_for_create_idty_right: CertCount, pub wot_min_cert_for_create_idty_right: CertCount,
pub wot_min_cert_for_membership: CertCount, pub wot_min_cert_for_membership: CertCount,
} }
} }
#[allow(unreachable_patterns)]
#[frame_support::pallet] #[frame_support::pallet]
pub mod pallet { pub mod pallet {
use super::*; use super::*;
use frame_support::pallet_prelude::*; use frame_support::{pallet_prelude::*, traits::StorageVersion};
use frame_support::traits::StorageVersion;
/// 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>(_); pub struct Pallet<T>(_);
...@@ -84,25 +84,29 @@ pub mod pallet { ...@@ -84,25 +84,29 @@ pub mod pallet {
#[pallet::config] #[pallet::config]
pub trait Config: frame_system::Config { pub trait Config: frame_system::Config {
type BlockNumber: Default + MaybeSerializeDeserialize + Parameter;
type CertCount: Default + MaybeSerializeDeserialize + Parameter; type CertCount: Default + MaybeSerializeDeserialize + Parameter;
type PeriodCount: Default + MaybeSerializeDeserialize + Parameter; type PeriodCount: Default + MaybeSerializeDeserialize + Parameter;
type SessionCount: Default + MaybeSerializeDeserialize + Parameter;
} }
// STORAGE // // STORAGE //
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn parameters)] #[pallet::getter(fn parameters)]
pub type ParametersStorage<T: Config> = pub type ParametersStorage<T: Config> = StorageValue<
StorageValue<_, Parameters<T::BlockNumber, T::CertCount, T::PeriodCount>, ValueQuery>; _,
Parameters<T::BlockNumber, T::CertCount, T::PeriodCount, T::SessionCount>,
ValueQuery,
>;
// GENESIS // GENESIS
#[pallet::genesis_config] #[pallet::genesis_config]
pub struct GenesisConfig<T: Config> { pub struct GenesisConfig<T: Config> {
pub parameters: Parameters<T::BlockNumber, T::CertCount, T::PeriodCount>, pub parameters: Parameters<T::BlockNumber, T::CertCount, T::PeriodCount, T::SessionCount>,
} }
#[cfg(feature = "std")]
impl<T: Config> Default for GenesisConfig<T> { impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self { fn default() -> Self {
Self { Self {
...@@ -112,7 +116,7 @@ pub mod pallet { ...@@ -112,7 +116,7 @@ pub mod pallet {
} }
#[pallet::genesis_build] #[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> { impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) { fn build(&self) {
<ParametersStorage<T>>::put(self.parameters.clone()); <ParametersStorage<T>>::put(self.parameters.clone());
} }
......
[package] [package]
authors = ['librelois <c@elo.tf>'] authors.workspace = true
description = 'FRAME pallet duniter wot.' description = "duniter pallet for web of trust"
edition = '2018' edition.workspace = true
homepage = 'https://substrate.dev' homepage.workspace = true
license = 'AGPL-3.0' license.workspace = true
name = 'pallet-duniter-wot' name = "pallet-duniter-wot"
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-certification/runtime-benchmarks",
"pallet-distance/runtime-benchmarks",
"pallet-identity/runtime-benchmarks",
"pallet-membership/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
std = [ std = [
'codec/std', "codec/std",
'frame-support/std', "frame-benchmarking?/std",
'frame-system/std', "frame-support/std",
'frame-benchmarking/std', "frame-system/std",
'pallet-certification/std', "pallet-certification/std",
'pallet-identity/std', "pallet-distance/std",
'pallet-membership/std', "pallet-identity/std",
'serde', "pallet-membership/std",
'sp-core/std', "scale-info/std",
'sp-io/std', "sp-core/std",
'sp-membership/std', "sp-io/std",
'sp-runtime/std', "sp-membership/std",
'sp-std/std', "sp-runtime/std",
"sp-state-machine/std",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-certification/try-runtime",
"pallet-distance/try-runtime",
"pallet-identity/try-runtime",
"pallet-membership/try-runtime",
"sp-membership/try-runtime",
"sp-runtime/try-runtime",
] ]
try-runtime = ['frame-support/try-runtime']
[dependencies]
pallet-certification = { path = "../certification", default-features = false }
pallet-identity = { path = "../identity", default-features = false }
pallet-membership = { path = "../membership", default-features = false }
sp-membership = { path = "../../primitives/membership", default-features = false }
# substrate
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
[dependencies.codec]
default-features = false
features = ['derive']
package = 'parity-scale-codec'
version = '2.3.1'
[dependencies.frame-benchmarking]
default-features = false
git = 'https://github.com/librelois/substrate.git'
optional = true
branch = 'duniter-monthly-2022-02'
[dependencies.frame-support]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.frame-system]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.serde]
version = "1.0.101"
optional = true
features = ["derive"]
[dependencies.sp-core]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-io]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-runtime]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-std]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
### DOC ###
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ['x86_64-unknown-linux-gnu'] default-features = false
[dev-dependencies.serde] targets = ["x86_64-unknown-linux-gnu"]
version = '1.0.119'
### DEV ###
[dev-dependencies.sp-io] [dependencies]
git = 'https://github.com/librelois/substrate.git' codec = { workspace = true, features = ["derive"] }
branch = 'duniter-monthly-2022-02' frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
pallet-certification = { workspace = true }
pallet-distance = { workspace = true }
pallet-identity = { workspace = true }
pallet-membership = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-membership = { workspace = true }
sp-runtime = { workspace = true }
[dev-dependencies]
sp-state-machine = { workspace = true, default-features = true }
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
//! # Duniter Web of Trust Pallet
//!
//! Duniter Web of Trust (WoT) lies at the heart of its identity system, representing a significant improvement over PGP Web of Trust. It functions as a dynamic directed graph where nodes are [identities](../identity/) and edges are [certifications](../certification/).
//!
//! ## Instances
//!
//! Duniter WoT consists of two distinct instances:
//!
//! - **Main WoT**: Designed for every human participant in the Duniter network.
//! - **Smith Sub-WoT**: Intended for authorities.
//!
//! ## Rules
//!
//! The Duniter WoT operates under a set of static and dynamic rules that govern membership conditions.
//!
//! ### Static Rules
//!
//! - **Minimum Received Certifications (Min Indegree)**: Specifies the minimum number of certifications an identity must receive to join the WoT.
//! - **Maximum Emitted Certifications (Max Outdegree)**: Limits the maximum number of certifications an identity can issue.
//! - **Distance Criterion**: Governed by the distance pallet, it defines the permissible distance between identities within the WoT graph.
//!
//! ### Dynamic Rules
//!
//! - **Time Interval Between Certifications**: Sets the minimum time interval required between two consecutive certifications issued by the same identity.
//! - **Certification Duration**: Managed by the certification pallet, it determines the validity duration of a certification.
//! - **Membership Renewal**: Regulates the frequency and conditions under which an identity must renew its membership within the WoT.
//!
//! This pallet is responsible for enforcing and validating the rules of the Duniter Web of Trust. It ensures compliance with both static prerequisites for joining and dynamic conditions for ongoing participation.
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
//pub mod traits;
mod types;
#[cfg(test)] #[cfg(test)]
mod mock; mod mock;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
/*#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;*/
pub use pallet::*; pub use pallet::*;
pub use types::*;
use frame_support::pallet_prelude::*; use frame_support::pallet_prelude::*;
use frame_system::RawOrigin;
use pallet_certification::traits::SetNextIssuableOn; use pallet_certification::traits::SetNextIssuableOn;
use pallet_identity::{IdtyEvent, IdtyStatus}; use pallet_identity::IdtyStatus;
use sp_membership::traits::IsInPendingMemberships; use pallet_membership::MembershipRemovalReason;
use sp_runtime::traits::IsMember;
type IdtyIndex = u32; type IdtyIndex = u32;
#[allow(unreachable_patterns)]
#[frame_support::pallet] #[frame_support::pallet]
pub mod pallet { pub mod pallet {
use super::*; use super::*;
use frame_support::dispatch::UnfilteredDispatchable;
use frame_support::traits::StorageVersion; use frame_support::traits::StorageVersion;
/// 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 = ()>(_); pub struct Pallet<T>(_);
// CONFIG // // CONFIG //
#[pallet::config] #[pallet::config]
pub trait Config<I: 'static = ()>: pub trait Config:
frame_system::Config frame_system::Config
+ pallet_certification::Config<I, IdtyIndex = IdtyIndex> + pallet_certification::Config<IdtyIndex = IdtyIndex>
+ pallet_identity::Config<IdtyIndex = IdtyIndex> + pallet_identity::Config<IdtyIndex = IdtyIndex>
+ pallet_membership::Config<I, IdtyId = IdtyIndex> + pallet_membership::Config<IdtyId = IdtyIndex>
{ {
type FirstIssuableOn: Get<Self::BlockNumber>; /// The block number from which the first certification can be issued.
type IsSubWot: Get<bool>; #[pallet::constant]
type FirstIssuableOn: Get<frame_system::pallet_prelude::BlockNumberFor<Self>>;
/// The minimum number of certifications required for membership eligibility.
#[pallet::constant]
type MinCertForMembership: Get<u32>; type MinCertForMembership: Get<u32>;
/// The minimum number of certifications required to create an identity.
#[pallet::constant]
type MinCertForCreateIdtyRight: Get<u32>; type MinCertForCreateIdtyRight: Get<u32>;
} }
// INTERNAL FUNCTIONS // // INTERNAL FUNCTIONS //
impl<T: Config<I>, I: 'static> Pallet<T, I> { impl<T: Config> Pallet<T> {
pub(super) fn do_apply_first_issuable_on(idty_index: IdtyIndex) { pub(super) fn do_apply_first_issuable_on(idty_index: IdtyIndex) {
let block_number = frame_system::pallet::Pallet::<T>::block_number(); let block_number = frame_system::pallet::Pallet::<T>::block_number();
pallet_certification::Pallet::<T, I>::set_next_issuable_on( pallet_certification::Pallet::<T>::set_next_issuable_on(
idty_index, idty_index,
block_number + T::FirstIssuableOn::get(), block_number + T::FirstIssuableOn::get(),
); );
} }
pub(super) fn dispath_idty_call(idty_call: pallet_identity::Call<T>) -> bool {
if !T::IsSubWot::get() {
if let Err(e) = idty_call.dispatch_bypass_filter(RawOrigin::Root.into()) {
sp_std::if_std! {
println!("fail to dispatch idty call: {:?}", e)
}
return false;
}
}
true
} }
// ERRORS //
#[pallet::error]
pub enum Error<T> {
/// Insufficient certifications received.
NotEnoughCerts,
/// Target status is incompatible with this operation.
// - Membership can not be added/renewed with this status
// - Certification can not be added to identity with this status
TargetStatusInvalid,
/// Identity creation period not respected.
IdtyCreationPeriodNotRespected,
/// Insufficient received certifications to create identity.
NotEnoughReceivedCertsToCreateIdty,
/// Maximum number of emitted certifications reached.
MaxEmittedCertsReached,
/// Issuer cannot emit a certification because it is not member.
IssuerNotMember,
/// Issuer or receiver not found.
IdtyNotFound,
/// Membership can only be renewed after an antispam delay.
MembershipRenewalPeriodNotRespected,
} }
} }
impl<AccountId, T: Config<I>, I: 'static> pallet_identity::traits::EnsureIdtyCallAllowed<T> /// Implementing identity call allowance check for the pallet.
for Pallet<T, I> impl<AccountId, T: Config> pallet_identity::traits::CheckIdtyCallAllowed<T> for Pallet<T>
where where
T: frame_system::Config<AccountId = AccountId> T: frame_system::Config<AccountId = AccountId> + pallet_membership::Config,
+ pallet_membership::Config<I, MetaData = MembershipMetaData<AccountId>>,
{ {
fn can_create_identity(creator: IdtyIndex) -> bool { /// Checks if identity creation is allowed.
let cert_meta = pallet_certification::Pallet::<T, I>::idty_cert_meta(creator); /// This implementation checks the following:
cert_meta.received_count >= T::MinCertForCreateIdtyRight::get() ///
&& cert_meta.next_issuable_on <= frame_system::pallet::Pallet::<T>::block_number() /// - Whether the identity has the right to create an identity.
&& cert_meta.issued_count < T::MaxByIssuer::get() /// - Whether the issuer can emit a certification.
} /// - Whether the issuer respect creation period.
fn can_confirm_identity(idty_index: IdtyIndex, owner_key: AccountId) -> bool { fn check_create_identity(creator: IdtyIndex) -> Result<(), DispatchError> {
pallet_membership::Pallet::<T, I>::force_request_membership( let cert_meta = pallet_certification::Pallet::<T>::idty_cert_meta(creator);
RawOrigin::Root.into(),
idty_index,
MembershipMetaData(owner_key),
)
.is_ok()
}
fn can_validate_identity(idty_index: IdtyIndex) -> bool {
pallet_membership::Pallet::<T, I>::claim_membership(
RawOrigin::Root.into(),
Some(idty_index),
)
.is_ok()
}
}
impl<T: Config<I>, I: 'static> pallet_certification::traits::IsCertAllowed<IdtyIndex> // 1. Check that the identity has the right to create an identity
for Pallet<T, I> // Identity can be a member with 5 certifications and still not reach the identity creation threshold, which could be higher (6, 7...)
{ ensure!(
fn is_cert_allowed(issuer: IdtyIndex, receiver: IdtyIndex) -> bool { cert_meta.received_count >= T::MinCertForCreateIdtyRight::get(),
if let Some(issuer_data) = pallet_identity::Pallet::<T>::identity(issuer) { Error::<T>::NotEnoughReceivedCertsToCreateIdty
if issuer_data.status != IdtyStatus::Validated { );
return false;
} // 2. Check that the issuer can emit one more certification (partial check)
if let Some(receiver_data) = pallet_identity::Pallet::<T>::identity(receiver) { ensure!(
match receiver_data.status { cert_meta.issued_count < T::MaxByIssuer::get(),
IdtyStatus::ConfirmedByOwner => true, Error::<T>::MaxEmittedCertsReached
IdtyStatus::Created => false, );
IdtyStatus::Validated => {
pallet_membership::Pallet::<T, I>::is_member(&receiver) // 3. Check that the issuer respects certification creation period
|| pallet_membership::Pallet::<T, I>::is_in_pending_memberships( ensure!(
receiver, cert_meta.next_issuable_on <= frame_system::pallet::Pallet::<T>::block_number(),
) Error::<T>::IdtyCreationPeriodNotRespected
} );
} Ok(())
} else {
// Receiver not found
false
} }
} else {
// Issuer not found
false
} }
/// Implementing certification allowance check for the pallet.
impl<T: Config> pallet_certification::traits::CheckCertAllowed<IdtyIndex> for Pallet<T> {
/// Checks if certification is allowed.
/// This implementation checks the following:
///
/// - Whether the issuer has an identity.
/// - Whether the issuer's identity is a member.
/// - Whether the receiver has an identity.
/// - Whether the receiver's identity is confirmed and not revoked.
fn check_cert_allowed(issuer: IdtyIndex, receiver: IdtyIndex) -> Result<(), DispatchError> {
// Issuer checks
// Ensure issuer is a member
let issuer_data =
pallet_identity::Pallet::<T>::identity(issuer).ok_or(Error::<T>::IdtyNotFound)?;
ensure!(
issuer_data.status == IdtyStatus::Member,
Error::<T>::IssuerNotMember
);
// Receiver checks
// Ensure receiver identity is confirmed and not revoked
let receiver_data =
pallet_identity::Pallet::<T>::identity(receiver).ok_or(Error::<T>::IdtyNotFound)?;
ensure!(
receiver_data.status == IdtyStatus::Unvalidated
|| receiver_data.status == IdtyStatus::Member
|| receiver_data.status == IdtyStatus::NotMember,
Error::<T>::TargetStatusInvalid
);
Ok(())
} }
} }
impl<T: Config<I>, I: 'static> sp_membership::traits::IsIdtyAllowedToRenewMembership<IdtyIndex> /// Implementing membership operation checks for the pallet.
for Pallet<T, I> impl<T: Config> sp_membership::traits::CheckMembershipOpAllowed<IdtyIndex> for Pallet<T> {
{ /// This implementation checks the following:
fn is_idty_allowed_to_renew_membership(idty_index: &IdtyIndex) -> bool { ///
if let Some(idty_value) = pallet_identity::Pallet::<T>::identity(idty_index) { /// - Whether the identity's status is unvalidated or not a member.
idty_value.status == IdtyStatus::Validated /// - The count of certifications associated with the identity.
} else { fn check_add_membership(idty_index: IdtyIndex) -> Result<(), DispatchError> {
false // Check identity status
let idty_value =
pallet_identity::Pallet::<T>::identity(idty_index).ok_or(Error::<T>::IdtyNotFound)?;
ensure!(
idty_value.status == IdtyStatus::Unvalidated
|| idty_value.status == IdtyStatus::NotMember,
Error::<T>::TargetStatusInvalid
);
// Check certificate count
check_cert_count::<T>(idty_index)?;
Ok(())
} }
/// This implementation checks the following:
///
/// - Whether the identity's status is member.
///
/// Note: There is no need to check certification count since losing certifications makes membership expire.
/// Membership renewal is only possible when identity is member.
fn check_renew_membership(idty_index: IdtyIndex) -> Result<(), DispatchError> {
let idty_value =
pallet_identity::Pallet::<T>::identity(idty_index).ok_or(Error::<T>::IdtyNotFound)?;
ensure!(
idty_value.status == IdtyStatus::Member,
Error::<T>::TargetStatusInvalid
);
Ok(())
} }
} }
impl<T: Config<I>, I: 'static> sp_membership::traits::IsIdtyAllowedToRequestMembership<IdtyIndex> /// Implementing membership event handling for the pallet.
for Pallet<T, I> impl<T: Config> sp_membership::traits::OnNewMembership<IdtyIndex> for Pallet<T>
where
T: pallet_membership::Config,
{ {
fn is_idty_allowed_to_request_membership(idty_index: &IdtyIndex) -> bool { /// This implementation notifies the identity pallet when a main membership is acquired.
if let Some(idty_value) = pallet_identity::Pallet::<T>::identity(idty_index) { /// It is only used on the first membership acquisition.
T::IsSubWot::get() && idty_value.status == IdtyStatus::Validated fn on_created(idty_index: &IdtyIndex) {
} else { pallet_identity::Pallet::<T>::membership_added(*idty_index);
false
}
} }
fn on_renewed(_idty_index: &IdtyIndex) {}
} }
impl<T: Config<I>, I: 'static, MetaData> sp_membership::traits::OnEvent<IdtyIndex, MetaData> /// Implementing membership removal event handling for the pallet.
for Pallet<T, I> impl<T: Config> sp_membership::traits::OnRemoveMembership<IdtyIndex> for Pallet<T>
where where
T: pallet_membership::Config<I, MetaData = MetaData>, T: pallet_membership::Config,
{
fn on_event(membership_event: &sp_membership::Event<IdtyIndex, MetaData>) -> Weight {
match membership_event {
sp_membership::Event::<IdtyIndex, MetaData>::MembershipAcquired(_, _) => {}
sp_membership::Event::<IdtyIndex, MetaData>::MembershipExpired(idty_index)
| sp_membership::Event::<IdtyIndex, MetaData>::MembershipRevoked(idty_index) => {
Self::dispath_idty_call(pallet_identity::Call::remove_identity {
idty_index: *idty_index,
idty_name: None,
});
}
sp_membership::Event::<IdtyIndex, MetaData>::MembershipRenewed(_) => {}
sp_membership::Event::<IdtyIndex, MetaData>::MembershipRequested(idty_index) => {
let idty_cert_meta =
pallet_certification::Pallet::<T, I>::idty_cert_meta(idty_index);
let received_count = idty_cert_meta.received_count;
// TODO insert `receiver` in distance queue if received_count >= MinCertForMembership
if received_count >= T::MinCertForMembership::get() as u32 {
// TODO insert `receiver` in distance queue
if Self::dispath_idty_call(pallet_identity::Call::validate_identity {
idty_index: *idty_index,
}) && received_count == T::MinReceivedCertToBeAbleToIssueCert::get()
{ {
Self::do_apply_first_issuable_on(*idty_index); /// This implementation notifies the identity pallet when a main membership is lost.
} fn on_removed(idty_index: &IdtyIndex) -> Weight {
} pallet_identity::Pallet::<T>::membership_removed(*idty_index)
}
sp_membership::Event::<IdtyIndex, MetaData>::PendingMembershipExpired(idty_index) => {
Self::dispath_idty_call(pallet_identity::Call::remove_identity {
idty_index: *idty_index,
idty_name: None,
});
}
}
0
} }
} }
impl<T: Config<I>, I: 'static> pallet_identity::traits::OnIdtyChange<T> for Pallet<T, I> { /// Implementing the identity event handler for the pallet.
fn on_idty_change(idty_index: IdtyIndex, idty_event: IdtyEvent<T>) -> Weight { impl<T: Config> pallet_identity::traits::OnNewIdty<T> for Pallet<T> {
match idty_event { /// This implementation adds a certificate when a new identity is created.
IdtyEvent::Created { creator } => { fn on_created(idty_index: &IdtyIndex, creator: &IdtyIndex) {
if let Err(e) = <pallet_certification::Pallet<T, I>>::force_add_cert( if let Err(e) =
frame_system::Origin::<T>::Root.into(), <pallet_certification::Pallet<T>>::do_add_cert_checked(*creator, *idty_index, true)
creator, {
idty_index, #[cfg(feature = "std")]
true, println!("fail to force add cert: {:?}", e)
) {
sp_std::if_std! {
println!("fail to force add cert: {:?}", e)
} }
} }
} }
IdtyEvent::Confirmed => {}
IdtyEvent::Validated => {} /// Implementing identity removal event handling for the pallet.
IdtyEvent::Removed => {} impl<T: Config> pallet_identity::traits::OnRemoveIdty<T> for Pallet<T> {
/// This implementation removes both membership and certificates associated with the identity.
fn on_removed(idty_index: &IdtyIndex) -> Weight {
let mut weight = Self::on_revoked(idty_index);
weight = weight.saturating_add(
<pallet_certification::Pallet<T>>::do_remove_all_certs_received_by(*idty_index),
);
weight
} }
0
/// This implementation removes membership only.
fn on_revoked(idty_index: &IdtyIndex) -> Weight {
let mut weight = Weight::zero();
weight = weight.saturating_add(<pallet_membership::Pallet<T>>::do_remove_membership(
*idty_index,
MembershipRemovalReason::Revoked,
));
weight
} }
} }
impl<T: Config<I>, I: 'static> pallet_certification::traits::OnNewcert<IdtyIndex> for Pallet<T, I> { /// Implementing the certification event handler for the pallet.
impl<T: Config> pallet_certification::traits::OnNewcert<IdtyIndex> for Pallet<T> {
/// This implementation checks if the receiver has received enough certificates to be able to issue certificates,
/// and applies the first issuable if the condition is met.
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,
) -> Weight {
if pallet_membership::Pallet::<T, I>::is_member(&receiver) {
if receiver_received_count == T::MinReceivedCertToBeAbleToIssueCert::get() {
Self::do_apply_first_issuable_on(receiver);
}
} else if pallet_membership::Pallet::<T, I>::is_in_pending_memberships(receiver)
&& receiver_received_count >= T::MinCertForMembership::get()
{
if T::IsSubWot::get() {
if let Err(e) = pallet_membership::Pallet::<T, I>::claim_membership(
RawOrigin::Root.into(),
Some(receiver),
) { ) {
sp_std::if_std! {
println!("fail to claim membership: {:?}", e)
}
}
} else {
// TODO insert `receiver` in distance queue
Self::dispath_idty_call(pallet_identity::Call::validate_identity {
idty_index: receiver,
});
}
if receiver_received_count == T::MinReceivedCertToBeAbleToIssueCert::get() { if receiver_received_count == T::MinReceivedCertToBeAbleToIssueCert::get() {
Self::do_apply_first_issuable_on(receiver); Self::do_apply_first_issuable_on(receiver);
} }
} }
0
}
} }
impl<T: Config<I>, I: 'static> pallet_certification::traits::OnRemovedCert<IdtyIndex> /// Implementing the certification removal event handler for the pallet.
for Pallet<T, I> impl<T: Config> pallet_certification::traits::OnRemovedCert<IdtyIndex> for Pallet<T> {
{ /// This implementation checks if the receiver has received fewer certificates than required for membership,
/// and if so, and the receiver is a member, it expires the receiver's membership.
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,
) -> Weight { ) {
if receiver_received_count < T::MinCertForMembership::get() if receiver_received_count < T::MinCertForMembership::get()
&& pallet_membership::Pallet::<T, I>::is_member(&receiver) && pallet_membership::Pallet::<T>::is_member(&receiver)
{ {
// Revoke receiver membership and disable their identity // Expire receiver membership
if let Err(e) = pallet_membership::Pallet::<T, I>::revoke_membership( <pallet_membership::Pallet<T>>::do_remove_membership(
RawOrigin::Root.into(), receiver,
Some(receiver), MembershipRemovalReason::NotEnoughCerts,
) { );
sp_std::if_std! { }
println!("fail to revoke membership: {:?}", e) }
}
/// Implementing the valid distance status event handler for the pallet.
impl<T: Config + pallet_distance::Config> pallet_distance::traits::OnValidDistanceStatus<T>
for Pallet<T>
{
/// This implementation handles different scenarios based on the identity's status:
///
/// - For `Unconfirmed` or `Revoked` identities, no action is taken.
/// - For `Unvalidated` or `NotMember` identities, an attempt is made to add membership.
/// - For `Member` identities, an attempt is made to renew membership.
fn on_valid_distance_status(idty_index: IdtyIndex) {
if let Some(identity) = pallet_identity::Identities::<T>::get(idty_index) {
match identity.status {
IdtyStatus::Unconfirmed | IdtyStatus::Revoked => {
// IdtyStatus::Unconfirmed
// distance evaluation request should never happen for unconfirmed identity
// IdtyStatus::Revoked
// the identity can have been revoked during distance evaluation by the oracle
}
IdtyStatus::Unvalidated | IdtyStatus::NotMember => {
// IdtyStatus::Unvalidated
// normal scenario for first entry
// IdtyStatus::NotMember
// normal scenario for re-entry
// the following can fail if a certification expired during distance evaluation
// otherwise it should succeed
let _ = pallet_membership::Pallet::<T>::try_add_membership(idty_index);
// sp_std::if_std! {
// if let Err(e) = r {
// print!("failed to claim identity when distance status was found ok: ");
// println!("{:?}", idty_index);
// println!("reason: {:?}", e);
// }
// }
}
IdtyStatus::Member => {
// IdtyStatus::Member
// normal scenario for renewal
// should succeed
let _ = pallet_membership::Pallet::<T>::try_renew_membership(idty_index);
// sp_std::if_std! {
// if let Err(e) = r {
// print!("failed to renew identity when distance status was found ok: ");
// println!("{:?}", idty_index);
// println!("reason: {:?}", e);
// }
// }
} }
} }
} else {
// identity was removed before distance status was found
// so it's ok to do nothing
#[cfg(feature = "std")]
println!(
"identity was removed before distance status was found: {:?}",
idty_index
);
}
}
}
/// Implementing the request distance evaluation check for the pallet.
impl<T: Config + pallet_distance::Config> pallet_distance::traits::CheckRequestDistanceEvaluation<T>
for Pallet<T>
{
/// This implementation performs the following checks:
///
/// - Membership renewal anti-spam check: Ensures that membership renewal requests respect the anti-spam period.
/// - Certificate count check: Ensures that the identity has a sufficient number of certificates.
fn check_request_distance_evaluation(idty_index: IdtyIndex) -> Result<(), DispatchError> {
// Check membership renewal anti-spam
let maybe_membership_data = pallet_membership::Pallet::<T>::membership(idty_index);
if let Some(membership_data) = maybe_membership_data {
// If membership data exists, this is for a renewal, apply anti-spam
ensure!(
// current_block > expiration block - membership period + renewal period
membership_data.expire_on
+ <T as pallet_membership::Config>::MembershipRenewalPeriod::get()
< frame_system::Pallet::<T>::block_number()
+ <T as pallet_membership::Config>::MembershipPeriod::get(),
Error::<T>::MembershipRenewalPeriodNotRespected
);
};
// Check certificate count
check_cert_count::<T>(idty_index)?;
Ok(())
} }
0
} }
/// Checks the certificate count for an identity.
fn check_cert_count<T: Config>(idty_index: IdtyIndex) -> Result<(), DispatchError> {
let idty_cert_meta = pallet_certification::Pallet::<T>::idty_cert_meta(idty_index);
ensure!(
idty_cert_meta.received_count >= T::MinCertForMembership::get(),
Error::<T>::NotEnoughCerts
);
Ok(())
} }
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use super::*; use super::*;
use crate::{self as pallet_duniter_wot}; use crate::{self as pallet_duniter_wot};
use frame_support::{parameter_types, traits::Everything}; use frame_support::{derive_impl, parameter_types, traits::Everything};
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, TestSignature, UintAuthorityId}, testing::{TestSignature as SubtrateTestSignature, UintAuthorityId},
traits::{BlakeTwo256, IdentityLookup}, traits::{BlakeTwo256, IdentityLookup},
BuildStorage,
}; };
use sp_state_machine::BasicExternalities;
use std::collections::BTreeMap; use std::collections::BTreeMap;
type AccountId = u64; type AccountId = u64;
type Block = frame_system::mocking::MockBlock<Test>; type Block = frame_system::mocking::MockBlock<Test>;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>;
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 TestSignature {
pub fn new(signer: u64, message: Vec<u8>) -> Self {
Self(SubtrateTestSignature(signer, message))
}
}
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)
}
}
// 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, DuniterWot: pallet_duniter_wot,
UncheckedExtrinsic = UncheckedExtrinsic, Identity: pallet_identity,
{ Membership: pallet_membership,
System: frame_system::{Pallet, Call, Config, Storage, Event<T>}, Cert: pallet_certification,
DuniterWot: pallet_duniter_wot::<Instance1>::{Pallet},
Identity: pallet_identity::{Pallet, Call, Config<T>, Storage, Event<T>},
Membership: pallet_membership::<Instance1>::{Pallet, Call, Config<T>, Storage, Event<T>},
Cert: pallet_certification::<Instance1>::{Pallet, Call, Config<T>, Storage, Event<T>},
SmithsSubWot: pallet_duniter_wot::<Instance2>::{Pallet},
SmithsMembership: pallet_membership::<Instance2>::{Pallet, Call, Config<T>, Storage, Event<T>},
SmithsCert: pallet_certification::<Instance2>::{Pallet, Call, Config<T>, Storage, Event<T>},
} }
); );
...@@ -53,52 +96,45 @@ parameter_types! { ...@@ -53,52 +96,45 @@ 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 Origin = Origin;
type Call = Call;
type Index = u64;
type BlockNumber = u64;
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 Event = Event; 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>;
} }
// DuniterWot // DuniterWot
parameter_types! { parameter_types! {
pub const MinCertForMembership: u32 = 2; pub const MinCertForMembership: u32 = 2;
pub const MinCertForCreateIdtyRigh: u32 = 4; pub const MinCertForCreateIdtyRight: u32 = 4;
pub const FirstIssuableOn: u64 = 2; pub const FirstIssuableOn: u64 = 2;
} }
impl pallet_duniter_wot::Config<Instance1> for Test { impl pallet_duniter_wot::Config for Test {
type IsSubWot = frame_support::traits::ConstBool<false>;
type MinCertForMembership = MinCertForMembership;
type MinCertForCreateIdtyRight = MinCertForCreateIdtyRigh;
type FirstIssuableOn = FirstIssuableOn; type FirstIssuableOn = FirstIssuableOn;
type MinCertForCreateIdtyRight = MinCertForCreateIdtyRight;
type MinCertForMembership = MinCertForMembership;
} }
// Identity // Identity
parameter_types! { parameter_types! {
pub const ChangeOwnerKeyPeriod: u64 = 10;
pub const ConfirmPeriod: u64 = 2; pub const ConfirmPeriod: u64 = 2;
pub const ValidationPeriod: u64 = 5;
pub const AutorevocationPeriod: u64 = 6;
pub const DeletionPeriod: u64 = 7;
pub const IdtyCreationPeriod: u64 = 3; pub const IdtyCreationPeriod: u64 = 3;
pub const ValidationPeriod: u64 = 2;
} }
pub struct IdtyNameValidatorTestImpl; pub struct IdtyNameValidatorTestImpl;
...@@ -109,133 +145,76 @@ impl pallet_identity::traits::IdtyNameValidator for IdtyNameValidatorTestImpl { ...@@ -109,133 +145,76 @@ impl pallet_identity::traits::IdtyNameValidator for IdtyNameValidatorTestImpl {
} }
impl pallet_identity::Config for Test { impl pallet_identity::Config for Test {
type AccountId32 = AccountId32Mock;
type AccountLinker = ();
type AutorevocationPeriod = AutorevocationPeriod;
type ChangeOwnerKeyPeriod = ChangeOwnerKeyPeriod;
type CheckAccountWorthiness = ();
type CheckIdtyCallAllowed = DuniterWot;
type ConfirmPeriod = ConfirmPeriod; type ConfirmPeriod = ConfirmPeriod;
type Event = Event; type DeletionPeriod = DeletionPeriod;
type EnsureIdtyCallAllowed = DuniterWot;
type IdtyCreationPeriod = IdtyCreationPeriod; type IdtyCreationPeriod = IdtyCreationPeriod;
type IdtyNameValidator = IdtyNameValidatorTestImpl; type IdtyData = ();
type IdtyIndex = IdtyIndex; type IdtyIndex = IdtyIndex;
type IdtyValidationOrigin = system::EnsureRoot<AccountId>; type IdtyNameValidator = IdtyNameValidatorTestImpl;
type IsMember = Membership; type OnKeyChange = ();
type OnIdtyChange = DuniterWot; type OnNewIdty = DuniterWot;
type RemoveIdentityConsumers = (); type OnRemoveIdty = DuniterWot;
type RevocationSigner = UintAuthorityId; type RuntimeEvent = RuntimeEvent;
type RevocationSignature = TestSignature; type Signature = TestSignature;
type Signer = UintAuthorityId;
type ValidationPeriod = ValidationPeriod;
type WeightInfo = ();
} }
// Membership // Membership
parameter_types! { parameter_types! {
pub const MembershipPeriod: u64 = 8; pub const MembershipPeriod: u64 = 8;
pub const PendingMembershipPeriod: u64 = 3; pub const MembershipRenewalPeriod: u64 = 2;
pub const RenewablePeriod: u64 = 2;
pub const RevocationPeriod: u64 = 4;
} }
impl pallet_membership::Config<Instance1> for Test { impl pallet_membership::Config for Test {
type IsIdtyAllowedToRenewMembership = DuniterWot; #[cfg(feature = "runtime-benchmarks")]
type IsIdtyAllowedToRequestMembership = DuniterWot; type BenchmarkSetupHandler = ();
type Event = Event; type CheckMembershipOpAllowed = DuniterWot;
type IdtyAttr = Identity;
type IdtyId = IdtyIndex; type IdtyId = IdtyIndex;
type IdtyIdOf = Identity;
type MembershipPeriod = MembershipPeriod; type MembershipPeriod = MembershipPeriod;
type MetaData = crate::MembershipMetaData<u64>; type MembershipRenewalPeriod = MembershipRenewalPeriod;
type OnEvent = DuniterWot; type OnNewMembership = DuniterWot;
type PendingMembershipPeriod = PendingMembershipPeriod; type OnRemoveMembership = DuniterWot;
type RenewablePeriod = RenewablePeriod; type RuntimeEvent = RuntimeEvent;
type RevocationPeriod = RevocationPeriod; type WeightInfo = ();
} }
// Cert // Cert
parameter_types! { parameter_types! {
pub const MaxByIssuer: u8 = 8; pub const MaxByIssuer: u8 = 8;
pub const MinReceivedCertToBeAbleToIssueCert: u32 = 2; pub const MinReceivedCertToBeAbleToIssueCert: u32 = 2;
pub const CertRenewablePeriod: u64 = 4;
pub const CertPeriod: u64 = 2; pub const CertPeriod: u64 = 2;
pub const ValidityPeriod: u64 = 10; pub const ValidityPeriod: u64 = 20;
} }
impl pallet_certification::Config<Instance1> for Test { impl pallet_certification::Config for Test {
type CertPeriod = CertPeriod; type CertPeriod = CertPeriod;
type Event = Event; type CheckCertAllowed = DuniterWot;
type IdtyAttr = Identity;
type IdtyIndex = IdtyIndex; type IdtyIndex = IdtyIndex;
type IdtyIndexOf = Identity;
type IsCertAllowed = DuniterWot;
type MaxByIssuer = MaxByIssuer; type MaxByIssuer = MaxByIssuer;
type MinReceivedCertToBeAbleToIssueCert = MinReceivedCertToBeAbleToIssueCert; type MinReceivedCertToBeAbleToIssueCert = MinReceivedCertToBeAbleToIssueCert;
type OnNewcert = DuniterWot; type OnNewcert = DuniterWot;
type OnRemovedCert = DuniterWot; type OnRemovedCert = DuniterWot;
type CertRenewablePeriod = CertRenewablePeriod; type RuntimeEvent = RuntimeEvent;
type ValidityPeriod = ValidityPeriod; type ValidityPeriod = ValidityPeriod;
} type WeightInfo = ();
// SMITHS SUB-WOT //
parameter_types! {
pub const SmithsMinCertForMembership: u32 = 2;
pub const SmithsFirstIssuableOn: u64 = 2;
}
impl pallet_duniter_wot::Config<Instance2> for Test {
type IsSubWot = frame_support::traits::ConstBool<true>;
type MinCertForMembership = SmithsMinCertForMembership;
type MinCertForCreateIdtyRight = frame_support::traits::ConstU32<0>;
type FirstIssuableOn = SmithsFirstIssuableOn;
}
// SmithsMembership
parameter_types! {
pub const SmithsMembershipPeriod: u64 = 20;
pub const SmithsPendingMembershipPeriod: u64 = 3;
pub const SmithsRenewablePeriod: u64 = 2;
pub const SmithsRevocationPeriod: u64 = 4;
}
impl pallet_membership::Config<Instance2> for Test {
type IsIdtyAllowedToRenewMembership = SmithsSubWot;
type IsIdtyAllowedToRequestMembership = SmithsSubWot;
type Event = Event;
type IdtyId = IdtyIndex;
type IdtyIdOf = Identity;
type MembershipPeriod = SmithsMembershipPeriod;
type MetaData = crate::MembershipMetaData<u64>;
type OnEvent = SmithsSubWot;
type PendingMembershipPeriod = SmithsPendingMembershipPeriod;
type RenewablePeriod = SmithsRenewablePeriod;
type RevocationPeriod = SmithsRevocationPeriod;
}
// SmithsCert
parameter_types! {
pub const SmithsMaxByIssuer: u8 = 8;
pub const SmithsMinReceivedCertToBeAbleToIssueCert: u32 = 2;
pub const SmithsCertRenewablePeriod: u64 = 4;
pub const SmithsCertPeriod: u64 = 2;
pub const SmithsValidityPeriod: u64 = 10;
}
impl pallet_certification::Config<Instance2> for Test {
type CertPeriod = SmithsCertPeriod;
type Event = Event;
type IdtyIndex = IdtyIndex;
type IdtyIndexOf = Identity;
type IsCertAllowed = DuniterWot;
type MaxByIssuer = SmithsMaxByIssuer;
type MinReceivedCertToBeAbleToIssueCert = SmithsMinReceivedCertToBeAbleToIssueCert;
type OnNewcert = DuniterWot;
type OnRemovedCert = DuniterWot;
type CertRenewablePeriod = SmithsCertRenewablePeriod;
type ValidityPeriod = SmithsValidityPeriod;
} }
pub const NAMES: [&str; 6] = ["Alice", "Bob", "Charlie", "Dave", "Eve", "Ferdie"]; pub const NAMES: [&str; 6] = ["Alice", "Bob", "Charlie", "Dave", "Eve", "Ferdie"];
// 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(initial_identities_len: usize) -> sp_io::TestExternalities {
initial_identities_len: usize, let mut t = frame_system::GenesisConfig::<Test>::default()
initial_smiths_len: usize, .build_storage()
) -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::default()
.build_storage::<Test>()
.unwrap(); .unwrap();
pallet_identity::GenesisConfig::<Test> { pallet_identity::GenesisConfig::<Test> {
...@@ -244,10 +223,12 @@ pub fn new_test_ext( ...@@ -244,10 +223,12 @@ pub fn new_test_ext(
index: i as u32, index: i as u32,
name: pallet_identity::IdtyName::from(NAMES[i - 1]), name: pallet_identity::IdtyName::from(NAMES[i - 1]),
value: pallet_identity::IdtyValue { value: pallet_identity::IdtyValue {
data: (),
next_creatable_identity_on: 0, next_creatable_identity_on: 0,
owner_key: i as u64, owner_key: i as u64,
removable_on: 0, old_owner_key: None,
status: pallet_identity::IdtyStatus::Validated, next_scheduled: 0,
status: pallet_identity::IdtyStatus::Member,
}, },
}) })
.collect(), .collect(),
...@@ -255,37 +236,13 @@ pub fn new_test_ext( ...@@ -255,37 +236,13 @@ pub fn new_test_ext(
.assimilate_storage(&mut t) .assimilate_storage(&mut t)
.unwrap(); .unwrap();
pallet_membership::GenesisConfig::<Test, Instance1> { pallet_membership::GenesisConfig::<Test> {
memberships: (1..=initial_identities_len) memberships: (1..=initial_identities_len)
.map(|i| { .map(|i| {
( (
i as u32, i as u32,
sp_membership::MembershipData { sp_membership::MembershipData {
expire_on: MembershipPeriod::get(), expire_on: MembershipPeriod::get(),
renewable_on: RenewablePeriod::get(),
},
)
})
.collect(),
}
.assimilate_storage(&mut t)
.unwrap();
pallet_certification::GenesisConfig::<Test, Instance1> {
certs_by_issuer: clique_wot(initial_identities_len, ValidityPeriod::get()),
apply_cert_period_at_genesis: true,
}
.assimilate_storage(&mut t)
.unwrap();
pallet_membership::GenesisConfig::<Test, Instance2> {
memberships: (1..=initial_smiths_len)
.map(|i| {
(
i as u32,
sp_membership::MembershipData {
expire_on: SmithsMembershipPeriod::get(),
renewable_on: SmithsRenewablePeriod::get(),
}, },
) )
}) })
...@@ -294,16 +251,16 @@ pub fn new_test_ext( ...@@ -294,16 +251,16 @@ pub fn new_test_ext(
.assimilate_storage(&mut t) .assimilate_storage(&mut t)
.unwrap(); .unwrap();
pallet_certification::GenesisConfig::<Test, Instance2> { pallet_certification::GenesisConfig::<Test> {
certs_by_issuer: clique_wot(initial_smiths_len, ValidityPeriod::get()),
apply_cert_period_at_genesis: true, apply_cert_period_at_genesis: true,
certs_by_receiver: clique_wot(initial_identities_len, ValidityPeriod::get()),
} }
.assimilate_storage(&mut t) .assimilate_storage(&mut t)
.unwrap(); .unwrap();
frame_support::BasicExternalities::execute_with_storage(&mut t, || { BasicExternalities::execute_with_storage(&mut t, || {
// manually increment genesis identities sufficient counter // manually increment genesis identities sufficient counter
// In real world, this should be handle manually by genesis creator // In real world, this is done by pallet-identity
for i in 1..=initial_identities_len { for i in 1..=initial_identities_len {
frame_system::Pallet::<Test>::inc_sufficients(&(i as u64)); frame_system::Pallet::<Test>::inc_sufficients(&(i as u64));
} }
...@@ -317,31 +274,28 @@ pub fn new_test_ext( ...@@ -317,31 +274,28 @@ pub fn new_test_ext(
pub fn run_to_block(n: u64) { pub fn run_to_block(n: u64) {
while System::block_number() < n { while System::block_number() < n {
// finalize previous block
DuniterWot::on_finalize(System::block_number()); DuniterWot::on_finalize(System::block_number());
Identity::on_finalize(System::block_number()); Identity::on_finalize(System::block_number());
Membership::on_finalize(System::block_number()); Membership::on_finalize(System::block_number());
Cert::on_finalize(System::block_number()); Cert::on_finalize(System::block_number());
SmithsSubWot::on_finalize(System::block_number());
SmithsMembership::on_finalize(System::block_number());
SmithsCert::on_finalize(System::block_number());
System::on_finalize(System::block_number()); System::on_finalize(System::block_number());
// reset events and change block number
System::reset_events(); System::reset_events();
System::set_block_number(System::block_number() + 1); System::set_block_number(System::block_number() + 1);
// initialize next block
System::on_initialize(System::block_number()); System::on_initialize(System::block_number());
DuniterWot::on_initialize(System::block_number()); DuniterWot::on_initialize(System::block_number());
Identity::on_initialize(System::block_number()); Identity::on_initialize(System::block_number());
Membership::on_initialize(System::block_number()); Membership::on_initialize(System::block_number());
Cert::on_initialize(System::block_number()); Cert::on_initialize(System::block_number());
SmithsSubWot::on_initialize(System::block_number());
SmithsMembership::on_initialize(System::block_number());
SmithsCert::on_initialize(System::block_number());
} }
} }
fn clique_wot( fn clique_wot(
initial_identities_len: usize, initial_identities_len: usize,
cert_validity_period: u64, cert_validity_period: u64,
) -> BTreeMap<IdtyIndex, BTreeMap<IdtyIndex, u64>> { ) -> BTreeMap<IdtyIndex, BTreeMap<IdtyIndex, Option<u64>>> {
let mut certs_by_issuer = BTreeMap::new(); let mut certs_by_issuer = BTreeMap::new();
for i in 1..=initial_identities_len { for i in 1..=initial_identities_len {
certs_by_issuer.insert( certs_by_issuer.insert(
...@@ -349,7 +303,7 @@ fn clique_wot( ...@@ -349,7 +303,7 @@ fn clique_wot(
(1..=initial_identities_len) (1..=initial_identities_len)
.filter_map(|j| { .filter_map(|j| {
if i != j { if i != j {
Some((j as IdtyIndex, cert_validity_period)) Some((j as IdtyIndex, Some(cert_validity_period)))
} else { } else {
None None
} }
......
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use crate::mock::Identity; use crate::{mock::*, pallet as pallet_duniter_wot};
use crate::mock::*; use codec::Encode;
use frame_support::assert_err; use frame_support::{assert_noop, assert_ok};
use frame_support::assert_ok; use pallet_identity::{
use frame_support::instances::Instance1; IdtyName, IdtyStatus, RevocationPayload, RevocationReason, REVOCATION_PAYLOAD_PREFIX,
use frame_system::{EventRecord, Phase}; };
use pallet_identity::{IdtyName, IdtyStatus}; use pallet_membership::MembershipRemovalReason;
/// test that genesis builder creates the good number of identities
/// and good identity and certification metadate
#[test] #[test]
fn test_genesis_build() { fn test_genesis_build() {
new_test_ext(3, 2).execute_with(|| { new_test_ext(3).execute_with(|| {
run_to_block(1); run_to_block(1);
// Verify state // Verify state
assert_eq!(Identity::identities_count(), 3); assert_eq!(Identity::identities_count(), 3);
assert_eq!(Identity::identity(1).unwrap().next_creatable_identity_on, 0); assert_eq!(Identity::identity(1).unwrap().next_creatable_identity_on, 0);
assert_eq!( assert_eq!(
pallet_certification::Pallet::<Test, Instance1>::idty_cert_meta(1).next_issuable_on, pallet_certification::Pallet::<Test>::idty_cert_meta(1).next_issuable_on,
2 2
); );
}); });
} }
/// test that Alice is not able to create an identity when she received too few certs (2 of 4)
#[test] #[test]
fn test_creator_not_allowed_to_create_idty() { fn test_creator_not_allowed_to_create_idty() {
new_test_ext(3, 2).execute_with(|| { new_test_ext(3).execute_with(|| {
run_to_block(1); run_to_block(1);
// Alice should not be able te create an identity before block #2 // Alice did not receive enough certs
// because Alice.next_issuable_on = 2 // (anyway Alice should not be able to create an identity before block #2
assert_err!( // because Alice.next_issuable_on = 2)
Identity::create_identity(Origin::signed(1), 4), assert_noop!(
pallet_identity::Error::<Test>::CreatorNotAllowedToCreateIdty Identity::create_identity(RuntimeOrigin::signed(1), 4),
pallet_duniter_wot::Error::<Test>::NotEnoughReceivedCertsToCreateIdty
); );
}); });
} }
/// test that Alice is able to create an identity when she received enough certs (4)
#[test] #[test]
fn test_join_smiths() { fn test_creator_allowed_to_create_idty() {
new_test_ext(5, 3).execute_with(|| { new_test_ext(5).execute_with(|| {
run_to_block(2); run_to_block(2);
// Dave shoud be able to requst smith membership // Alice should be able to create an identity
assert_ok!(SmithsMembership::request_membership( assert_ok!(
Origin::signed(4), Identity::create_identity(RuntimeOrigin::signed(1), 6),
crate::MembershipMetaData(4) // pallet_duniter_wot::Error::<Test>::NotEnoughReceivedCertsToCreateIdty
));
run_to_block(3);
// Then, Alice should be able to send a smith cert to Dave
assert_ok!(SmithsCert::add_cert(Origin::signed(1), 4));
});
}
#[test]
fn test_revoke_smiths_them_rejoin() {
new_test_ext(5, 4).execute_with(|| {
run_to_block(2);
// Dave shoud be able to revoke is smith membership
assert_ok!(SmithsMembership::revoke_membership(
Origin::signed(4),
Some(4)
));
// Dave should not be able te re-request membership before the RevocationPeriod end
run_to_block(3);
assert_err!(
SmithsMembership::request_membership(Origin::signed(4), crate::MembershipMetaData(4)),
pallet_membership::Error::<Test, crate::Instance2>::MembershipRevokedRecently
); );
// At bloc #6, Dave shoud be able to request smith membership
run_to_block(6);
assert_ok!(SmithsMembership::request_membership(
Origin::signed(4),
crate::MembershipMetaData(4)
));
// Then, Alice should be able to send a smith cert to Dave
assert_ok!(SmithsCert::add_cert(Origin::signed(1), 4));
}); });
} }
/// test identity creation and that a first cert is emitted
#[test] #[test]
fn test_create_idty_ok() { fn test_create_idty_ok() {
new_test_ext(5, 2).execute_with(|| { new_test_ext(5).execute_with(|| {
run_to_block(2); run_to_block(2);
// Alice should be able te create an identity at block #2 // Alice should be able to create an identity at block #2
assert_ok!(Identity::create_identity(Origin::signed(1), 6)); assert_ok!(Identity::create_identity(RuntimeOrigin::signed(1), 6));
// 2 events should have occurred: IdtyCreated and NewCert // 2 events should have occurred: IdtyCreated and CertAdded
let events = System::events(); System::assert_has_event(RuntimeEvent::Identity(
assert_eq!(events.len(), 2); pallet_identity::Event::IdtyCreated {
assert_eq!(
events[0],
EventRecord {
phase: Phase::Initialization,
event: Event::Identity(pallet_identity::Event::IdtyCreated {
idty_index: 6, idty_index: 6,
owner_key: 6, owner_key: 6,
}), },
topics: vec![], ));
} System::assert_has_event(RuntimeEvent::Cert(pallet_certification::Event::CertAdded {
);
assert_eq!(
events[1],
EventRecord {
phase: Phase::Initialization,
event: Event::Cert(pallet_certification::Event::NewCert {
issuer: 1, issuer: 1,
issuer_issued_count: 5,
receiver: 6, receiver: 6,
receiver_received_count: 1 }));
}),
topics: vec![], assert_eq!(
} Identity::identity(6).unwrap().status,
IdtyStatus::Unconfirmed
); );
assert_eq!(Identity::identity(6).unwrap().status, IdtyStatus::Created); assert_eq!(Identity::identity(6).unwrap().next_scheduled, 2 + 2);
assert_eq!(Identity::identity(6).unwrap().removable_on, 4);
}); });
} }
/// test identity validation
#[test] #[test]
fn test_new_idty_validation() { fn test_new_idty_validation() {
new_test_ext(5, 2).execute_with(|| { new_test_ext(5).execute_with(|| {
// Alice create Ferdie identity // Alice creates Ferdie identity
run_to_block(2); run_to_block(2);
assert_ok!(Identity::create_identity(Origin::signed(1), 6)); assert_ok!(Identity::create_identity(RuntimeOrigin::signed(1), 6));
// Ferdie confirm it's identity // Ferdie confirms his identity
run_to_block(3); run_to_block(3);
assert_ok!(Identity::confirm_identity( assert_ok!(Identity::confirm_identity(
Origin::signed(6), RuntimeOrigin::signed(6),
IdtyName::from("Ferdie"), IdtyName::from("Ferdie"),
)); ));
// Bob should be able to certify Ferdie // Bob should be able to certify Ferdie
run_to_block(4); run_to_block(4);
assert_ok!(Cert::add_cert(Origin::signed(2), 6)); assert_ok!(Cert::add_cert(RuntimeOrigin::signed(2), 6));
System::assert_has_event(RuntimeEvent::Cert(pallet_certification::Event::CertAdded {
let events = System::events();
// 3 events should have occurred: NewCert, MembershipAcquired and IdtyValidated
assert_eq!(events.len(), 3);
assert_eq!(
events[0],
EventRecord {
phase: Phase::Initialization,
event: Event::Cert(pallet_certification::Event::NewCert {
issuer: 2, issuer: 2,
issuer_issued_count: 5,
receiver: 6, receiver: 6,
receiver_received_count: 2 }));
}),
topics: vec![], // Ferdie should be able to claim membership
} run_to_block(5);
); assert_ok!(Membership::try_add_membership(6));
assert_eq!( System::assert_has_event(RuntimeEvent::Membership(
events[1], pallet_membership::Event::MembershipAdded {
EventRecord { member: 6,
phase: Phase::Initialization, expire_on: 5 + <Test as pallet_membership::Config>::MembershipPeriod::get(),
event: Event::Membership(pallet_membership::Event::MembershipAcquired(6)), },
topics: vec![], ));
} System::assert_has_event(RuntimeEvent::Identity(
); pallet_identity::Event::IdtyValidated { idty_index: 6 },
));
// After PendingMembershipPeriod, Ferdie identity should not expire
run_to_block(6);
assert_eq!( assert_eq!(
events[2], Identity::identity(6),
EventRecord { Some(pallet_identity::IdtyValue {
phase: Phase::Initialization, data: (),
event: Event::Identity(pallet_identity::Event::IdtyValidated { idty_index: 6 }), next_creatable_identity_on: 0,
topics: vec![], old_owner_key: None,
} owner_key: 6,
next_scheduled: 0,
status: IdtyStatus::Member,
})
); );
}); });
} }
/// test that Ferdie can confirm an identity created for him by Alice
#[test] #[test]
fn test_confirm_idty_ok() { fn test_confirm_idty_ok() {
new_test_ext(5, 2).execute_with(|| { new_test_ext(5).execute_with(|| {
run_to_block(2); run_to_block(2);
// Alice create Ferdie identity // Alice creates Ferdie identity
assert_ok!(Identity::create_identity(Origin::signed(1), 6)); assert_ok!(Identity::create_identity(RuntimeOrigin::signed(1), 6));
run_to_block(3); run_to_block(3);
// Ferdie should be able to confirm it's identity // Ferdie should be able to confirm his identity
assert_ok!(Identity::confirm_identity( assert_ok!(Identity::confirm_identity(
Origin::signed(6), RuntimeOrigin::signed(6),
IdtyName::from("Ferdie"), IdtyName::from("Ferdie"),
)); ));
let events = System::events(); System::assert_has_event(RuntimeEvent::Identity(
// 2 events should have occurred: MembershipRequested and IdtyConfirmed pallet_identity::Event::IdtyConfirmed {
assert_eq!(events.len(), 2);
//println!("{:?}", events[0]);
assert_eq!(
events[0],
EventRecord {
phase: Phase::Initialization,
event: Event::Membership(pallet_membership::Event::MembershipRequested(6)),
topics: vec![],
}
);
assert_eq!(
events[1],
EventRecord {
phase: Phase::Initialization,
event: Event::Identity(pallet_identity::Event::IdtyConfirmed {
idty_index: 6, idty_index: 6,
owner_key: 6,
name: IdtyName::from("Ferdie"), name: IdtyName::from("Ferdie"),
}), },
topics: vec![], ));
});
} }
);
/// test identity revocation
/// - anyone can submit a revocation certificate signed by bob
#[test]
fn test_revoke_idty() {
new_test_ext(5).execute_with(|| {
run_to_block(2);
// Alice identity can be revoked
assert_ok!(Identity::revoke_identity(
RuntimeOrigin::signed(1),
1,
1,
TestSignature::new(
1,
(
REVOCATION_PAYLOAD_PREFIX,
RevocationPayload {
idty_index: 1u32,
genesis_hash: System::block_hash(0),
}
)
.encode()
)
));
// her membership should be removed
System::assert_has_event(RuntimeEvent::Membership(
pallet_membership::Event::MembershipRemoved {
member: 1,
reason: pallet_membership::MembershipRemovalReason::Revoked,
},
));
// Anyone should be able to submit Bob revocation certificate
assert_ok!(Identity::revoke_identity(
RuntimeOrigin::signed(42),
2,
2,
TestSignature::new(
2,
(
REVOCATION_PAYLOAD_PREFIX,
RevocationPayload {
idty_index: 2u32,
genesis_hash: System::block_hash(0),
}
)
.encode()
)
));
System::assert_has_event(RuntimeEvent::Identity(
pallet_identity::Event::IdtyRevoked {
idty_index: 2,
reason: pallet_identity::RevocationReason::User,
},
));
}); });
} }
/// test that expired membership lose the identity after a delay
#[test] #[test]
fn test_idty_membership_expire_them_requested() { fn test_idty_membership_expire() {
new_test_ext(3, 2).execute_with(|| { new_test_ext(3).execute_with(|| {
run_to_block(4); run_to_block(4);
// Alice renew her membership // Alice renews her membership
assert_ok!(Membership::renew_membership(Origin::signed(1), None)); assert_ok!(Membership::try_renew_membership(1));
// Bob renew his membership // Bob renews his membership
assert_ok!(Membership::renew_membership(Origin::signed(2), None)); assert_ok!(Membership::try_renew_membership(2));
run_to_block(5);
// renew certifications so that Alice can still issue cert at block 22
assert_ok!(Cert::renew_cert(RuntimeOrigin::signed(2), 1));
assert_ok!(Cert::renew_cert(RuntimeOrigin::signed(3), 1));
// Charlie's membership should expire at block #8 // Charlie's membership should expire at block #8
run_to_block(8); run_to_block(8);
assert_ok!(Membership::try_renew_membership(1));
assert!(Membership::membership(3).is_none()); assert!(Membership::membership(3).is_none());
let events = System::events();
println!("{:?}", events); System::assert_has_event(RuntimeEvent::Membership(
assert_eq!(events.len(), 2); pallet_membership::Event::MembershipRemoved {
member: 3,
reason: MembershipRemovalReason::Expired,
},
));
assert_eq!( assert_eq!(
events[0], Identity::identity(3),
EventRecord { Some(pallet_identity::IdtyValue {
phase: Phase::Initialization, data: (),
event: Event::Membership(pallet_membership::Event::MembershipExpired(3)), next_creatable_identity_on: 0,
topics: vec![], old_owner_key: None,
} owner_key: 3,
next_scheduled: 14, // = 8 (membership removal block) + 6 (auto revocation period)
status: IdtyStatus::NotMember,
})
); );
// check that identity is added to auto-revoke list (currently IdentityChangeSchedule)
assert_eq!(Identity::next_scheduled(14), vec!(3));
run_to_block(14);
assert_ok!(Membership::try_renew_membership(1));
// Charlie's identity should be auto-revoked at block #11 (8 + 3)
System::assert_has_event(RuntimeEvent::Identity(
pallet_identity::Event::IdtyRevoked {
idty_index: 3,
reason: pallet_identity::RevocationReason::Expired,
},
));
assert_eq!( assert_eq!(
events[1], Identity::identity(3),
EventRecord { Some(pallet_identity::IdtyValue {
phase: Phase::Initialization, data: (),
event: Event::Identity(pallet_identity::Event::IdtyRemoved { idty_index: 3 }), next_creatable_identity_on: 0,
topics: vec![], old_owner_key: None,
} owner_key: 3,
next_scheduled: 21, // = 14 (revocation block) + 7 (deletion period)
status: IdtyStatus::Revoked,
})
);
// Alice can't certify revoked identity
assert_noop!(
Cert::add_cert(RuntimeOrigin::signed(1), 3),
pallet_duniter_wot::Error::<Test>::TargetStatusInvalid
); );
// Charlie's identity should be removed at block #8 run_to_block(21);
assert!(Identity::identity(3).is_none()); System::assert_has_event(RuntimeEvent::Identity(
pallet_identity::Event::IdtyRemoved {
// Alice can't renew it's cert to Charlie idty_index: 3,
assert_err!( reason: pallet_identity::RemovalReason::Revoked,
Cert::add_cert(Origin::signed(1), 3), },
pallet_certification::Error::<Test, Instance1>::CertNotAllowed ));
// Alice can't certify removed identity
assert_noop!(
Cert::add_cert(RuntimeOrigin::signed(1), 3),
pallet_duniter_wot::Error::<Test>::IdtyNotFound
); );
}); });
} }
/// when an identity is confirmed and not validated, the certification received should be removed
#[test]
fn test_unvalidated_idty_certs_removal() {
new_test_ext(5).execute_with(|| {
// Alice creates Ferdie identity
run_to_block(2);
assert_ok!(Identity::create_identity(RuntimeOrigin::signed(1), 6));
// Ferdie confirms his identity
run_to_block(3);
assert_ok!(Identity::confirm_identity(
RuntimeOrigin::signed(6),
IdtyName::from("Ferdie"),
));
assert_eq!(Cert::certs_by_receiver(6).len(), 1);
// After ValidationPeriod, Ferdie identity should be automatically removed
// and his received certifications should be removed
run_to_block(8);
assert_eq!(Cert::certs_by_receiver(6).len(), 0);
});
}
/// test what happens when certification expire
#[test]
fn test_certification_expire() {
new_test_ext(3).execute_with(|| {
// smith cert Bob → Alice not renewed
// cert Bob → Alice not renewed
// --- BLOCK 2 ---
run_to_block(2);
assert_ok!(Cert::renew_cert(RuntimeOrigin::signed(1), 2));
assert_ok!(Cert::renew_cert(RuntimeOrigin::signed(2), 3));
assert_ok!(Cert::renew_cert(RuntimeOrigin::signed(3), 1));
// --- BLOCK 4 ---
run_to_block(4);
assert_ok!(Cert::renew_cert(RuntimeOrigin::signed(1), 3));
assert_ok!(Cert::renew_cert(RuntimeOrigin::signed(3), 2));
// --- BLOCK 7 ---
run_to_block(7);
assert_ok!(Membership::try_renew_membership(1));
assert_ok!(Membership::try_renew_membership(2));
assert_ok!(Membership::try_renew_membership(3));
// --- BLOCK 14 ---
run_to_block(14);
assert_ok!(Membership::try_renew_membership(1));
assert_ok!(Membership::try_renew_membership(2));
assert_ok!(Membership::try_renew_membership(3));
// normal cert Bob → Alice expires at block 20
run_to_block(20);
// println!("{:?}", System::events());
System::assert_has_event(RuntimeEvent::Cert(
pallet_certification::Event::CertRemoved {
issuer: 2, // Bob
receiver: 1, // Alice
expiration: true,
},
));
// in consequence, since Alice has only 1/2 normal certification remaining, she looses normal membership
System::assert_has_event(RuntimeEvent::Membership(
pallet_membership::Event::MembershipRemoved {
member: 1,
reason: MembershipRemovalReason::NotEnoughCerts,
},
));
// --- BLOCK 21 ---
// Bob and Charlie can renew their membership
run_to_block(21);
assert_ok!(Membership::try_renew_membership(2));
assert_ok!(Membership::try_renew_membership(3));
// Alice can not renew her membership which does not exist
assert_noop!(
Membership::try_renew_membership(1),
pallet_membership::Error::<Test>::MembershipNotFound
);
// Alice can not claim her membership because she does not have enough certifications
assert_noop!(
Membership::try_add_membership(1),
pallet_duniter_wot::Error::<Test>::NotEnoughCerts
);
// --- BLOCK 23 ---
run_to_block(26);
// println!("{:?}", System::events());
// after a delay, the non member identity is automatically revoked
System::assert_has_event(RuntimeEvent::Identity(
pallet_identity::Event::IdtyRevoked {
idty_index: 1,
reason: RevocationReason::Expired,
},
));
})
}
/// test some cases where identity should not be able to issue cert
// - when source or target is not member (sub wot)
// - when source or target membership is pending (both wot)
#[test]
fn test_cert_can_not_be_issued() {
new_test_ext(4).execute_with(|| {
// smith cert Bob → Alice not renewed
// cert Bob → Alice not renewed
// --- BLOCK 2 ---
run_to_block(2);
assert_ok!(Cert::renew_cert(RuntimeOrigin::signed(1), 2)); // +20
assert_ok!(Cert::renew_cert(RuntimeOrigin::signed(2), 3)); // +20
assert_ok!(Cert::renew_cert(RuntimeOrigin::signed(3), 4)); // +20
assert_ok!(Cert::renew_cert(RuntimeOrigin::signed(4), 1)); // +20
// --- BLOCK 4 ---
run_to_block(4);
assert_ok!(Cert::renew_cert(RuntimeOrigin::signed(2), 4)); // +20
assert_ok!(Cert::renew_cert(RuntimeOrigin::signed(3), 2)); // +20
assert_ok!(Cert::renew_cert(RuntimeOrigin::signed(4), 3)); // +20
// --- BLOCK 7 ---
run_to_block(7);
assert_ok!(Membership::try_renew_membership(1)); // + 8
assert_ok!(Membership::try_renew_membership(2)); // + 8
assert_ok!(Membership::try_renew_membership(3)); // + 8
assert_ok!(Membership::try_renew_membership(4)); // + 8
run_to_block(14);
assert_ok!(Membership::try_renew_membership(1)); // + 8
assert_ok!(Membership::try_renew_membership(2)); // + 8
assert_ok!(Membership::try_renew_membership(3)); // + 8
assert_ok!(Membership::try_renew_membership(4)); // + 8
run_to_block(20);
// println!("{:?}", System::events());
System::assert_has_event(RuntimeEvent::Cert(
pallet_certification::Event::CertRemoved {
issuer: 2, // Bob
receiver: 1, // Alice
expiration: true,
},
));
// other certifications expire, but not Dave → Alice
// in consequence, since Alice has only 1/2 certification remaining, she looses membership
System::assert_has_event(RuntimeEvent::Membership(
pallet_membership::Event::MembershipRemoved {
member: 1,
reason: MembershipRemovalReason::NotEnoughCerts,
}, // pending membership expires at 23
));
run_to_block(21);
// println!("{:?}", System::events());
// Charlie certifies Alice so she again has enough certs
assert_ok!(Cert::add_cert(RuntimeOrigin::signed(3), 1));
assert_ok!(Cert::renew_cert(RuntimeOrigin::signed(4), 1));
// renew
// Alice did not claim membership, she is not member
// but her cert delay has been reset (→ 23)
assert_eq!(Membership::membership(1), None);
// run_to_block(23);
// if identity of alice was not removed because pending for too long
// she would have been able to emit a cert without being member
})
}
// Copyright 2021 Axiom-Team
//
// This file is part of Substrate-Libre-Currency.
//
// Substrate-Libre-Currency 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.
//
// Substrate-Libre-Currency 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>.
use crate::IdtyIndex;
use frame_support::pallet_prelude::*;
#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub struct MembershipMetaData<AccountId>(pub AccountId);
impl<AccountId: Eq> sp_membership::traits::Validate<AccountId> for MembershipMetaData<AccountId> {
fn validate(&self, account_id: &AccountId) -> bool {
&self.0 == account_id
}
}
/*impl From<AccountId> for MembershipMetaData {
fn from(account_id: AccountId) -> Self {
Self(account_id)
}
}*/
/*impl Into<AccountId> for MembershipMetaData {
fn into(self) -> AccountId {
self.0
}
}*/
#[cfg_attr(feature = "std", derive(Debug))]
#[derive(codec::Decode, codec::Encode, Eq, PartialEq, TypeInfo)]
pub enum WotDiff {
AddNode(IdtyIndex),
AddPendingLink(IdtyIndex, IdtyIndex),
AddLink(IdtyIndex, IdtyIndex),
DelLink(IdtyIndex, IdtyIndex),
DisableNode(IdtyIndex),
}
impl Default for WotDiff {
fn default() -> Self {
unreachable!()
}
}
[package] [package]
authors = ['librelois <c@elo.tf>'] authors.workspace = true
description = 'FRAME pallet identity.' description = "duniter pallet identity"
edition = '2018' edition.workspace = true
homepage = 'https://substrate.dev' homepage.workspace = true
license = 'AGPL-3.0' license.workspace = true
name = 'pallet-identity' name = "pallet-identity"
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', "serde/std",
'sp-std/std', "sp-core/std",
"sp-io/std",
"sp-keystore/std",
"sp-runtime/std",
"sp-state-machine/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 = "1.0", default-features = false, features = ["derive"] }
[dependencies.codec]
default-features = false
features = ['derive']
package = 'parity-scale-codec'
version = '2.3.1'
[dependencies.frame-benchmarking]
default-features = false
git = 'https://github.com/librelois/substrate.git'
optional = true
branch = 'duniter-monthly-2022-02'
[dependencies.frame-support]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.frame-system]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.serde]
version = "1.0.101"
optional = true
features = ["derive"]
[dependencies.sp-core]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-runtime]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-std]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
### DOC ###
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ['x86_64-unknown-linux-gnu'] default-features = false
[dev-dependencies.serde] targets = ["x86_64-unknown-linux-gnu"]
version = '1.0.119'
### DEV ###
[dev-dependencies.maplit]
version = '1.0.2'
[dev-dependencies.sp-io] [dependencies]
git = 'https://github.com/librelois/substrate.git' base64 = { workspace = true }
branch = 'duniter-monthly-2022-02' bs58 = { workspace = true }
codec = { workspace = true, features = ["derive"] }
duniter-primitives = { workspace = true }
frame-benchmarking = { workspace = true, optional = true }
frame-support = { workspace = true }
frame-system = { workspace = true }
log = { workspace = true }
scale-info = { workspace = true, features = ["derive"] }
serde = { workspace = true, features = ["derive"] }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-runtime = { workspace = true }
[dev-dependencies]
sp-keystore = { workspace = true, default-features = true }
sp-state-machine = { 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")]
use super::*;
use codec::Encode;
use frame_benchmarking::{account, v2::*};
use frame_support::traits::OnInitialize;
use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin};
use sp_core::{crypto::Ss58Codec, Get};
use sp_io::crypto::{sr25519_generate, sr25519_sign};
use sp_runtime::{AccountId32, MultiSigner};
use crate::Pallet;
#[benchmarks(
where
T::Signature: From<sp_core::sr25519::Signature>,
T::AccountId: From<AccountId32>,
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());
}
struct Account<T: Config> {
key: T::AccountId,
index: T::IdtyIndex,
origin: <T as frame_system::Config>::RuntimeOrigin,
}
// Create and confirm one account using Alice authorized account.
// key, origin, name and index are returned.
// Alice next_creatable_identity_on is reinitialized at the end so several account can be
// created in a row.
fn create_one_identity<T: Config>(owner_key: T::AccountId) -> Result<Account<T>, &'static str> {
// get Alice account to create identity
let caller: T::AccountId = Identities::<T>::get(T::IdtyIndex::from(1u32))
.unwrap()
.owner_key;
let caller_origin: <T as frame_system::Config>::RuntimeOrigin =
RawOrigin::Signed(caller.clone()).into();
let owner_key_origin: <T as frame_system::Config>::RuntimeOrigin =
RawOrigin::Signed(owner_key.clone()).into();
T::CheckAccountWorthiness::set_worthy(&owner_key);
Pallet::<T>::create_identity(caller_origin.clone(), owner_key.clone())?;
let name = IdtyName("new_identity".into());
Pallet::<T>::confirm_identity(owner_key_origin.clone(), name.clone())?;
let idty_index = IdentityIndexOf::<T>::get(&owner_key).unwrap();
// make identity member
<Identities<T>>::mutate_exists(idty_index, |idty_val_opt| {
if let Some(ref mut idty_val) = idty_val_opt {
idty_val.status = IdtyStatus::Member;
}
});
// Reset next_creatable_identity_on to add more identities with Alice
<Identities<T>>::mutate_exists(T::IdtyIndex::from(1u32), |idty_val_opt| {
if let Some(ref mut idty_val) = idty_val_opt {
idty_val.next_creatable_identity_on = BlockNumberFor::<T>::zero();
}
});
Ok(Account {
key: owner_key,
index: idty_index,
origin: owner_key_origin,
// name: name,
})
}
// Create a dummy identity bypassing all the checks.
fn create_dummy_identity<T: Config>(i: u32) -> Result<(), &'static str> {
let idty_index: T::IdtyIndex = i.into();
let owner_key: T::AccountId = account("Bob", i, 1);
let next_scheduled = BlockNumberFor::<T>::zero();
let value = IdtyValue {
data: Default::default(),
next_creatable_identity_on: BlockNumberFor::<T>::zero(),
old_owner_key: None,
owner_key: owner_key.clone(),
next_scheduled,
status: IdtyStatus::Unvalidated,
};
let name = i.to_le_bytes();
let idty_name = IdtyName(name.into());
frame_system::Pallet::<T>::inc_sufficients(&owner_key);
<Identities<T>>::insert(idty_index, value);
IdentityChangeSchedule::<T>::append(next_scheduled, idty_index);
IdentityIndexOf::<T>::insert(owner_key.clone(), idty_index);
<IdentitiesNames<T>>::insert(idty_name.clone(), idty_index);
Ok(())
}
// Add `i` dummy identities.
fn create_identities<T: Config>(i: u32) -> Result<(), &'static str> {
let identities_count = Pallet::<T>::identities_count();
for j in 0..i {
create_dummy_identity::<T>(j + identities_count + 1)?;
}
assert!(
identities_count + i == Pallet::<T>::identities_count(),
"Identities not created"
);
Ok(())
}
#[benchmark]
fn create_identity() {
let caller: T::AccountId = Identities::<T>::get(T::IdtyIndex::one()).unwrap().owner_key; // Alice
let owner_key: T::AccountId = account("new_identity", 2, 1);
T::CheckAccountWorthiness::set_worthy(&owner_key);
#[extrinsic_call]
_(RawOrigin::Signed(caller), owner_key.clone());
let idty_index = IdentityIndexOf::<T>::get(&owner_key);
assert!(idty_index.is_some(), "Identity not added");
assert_has_event::<T>(
Event::<T>::IdtyCreated {
idty_index: idty_index.unwrap(),
owner_key,
}
.into(),
);
}
#[benchmark]
fn confirm_identity() -> Result<(), BenchmarkError> {
let caller: T::AccountId = Identities::<T>::get(T::IdtyIndex::one()).unwrap().owner_key;
let caller_origin: <T as frame_system::Config>::RuntimeOrigin =
RawOrigin::Signed(caller.clone()).into();
let owner_key: T::AccountId = account("new_identity", 2, 1);
T::CheckAccountWorthiness::set_worthy(&owner_key);
Pallet::<T>::create_identity(caller_origin.clone(), owner_key.clone())?;
#[extrinsic_call]
_(
RawOrigin::Signed(owner_key.clone()),
IdtyName("new_identity".into()),
);
let idty_index = IdentityIndexOf::<T>::get(&owner_key);
assert_has_event::<T>(
Event::<T>::IdtyConfirmed {
idty_index: idty_index.unwrap(),
name: IdtyName("new_identity".into()),
}
.into(),
);
Ok(())
}
#[benchmark]
fn change_owner_key() -> Result<(), BenchmarkError> {
let old_key: T::AccountId = account("new_identity", 2, 1);
let account: Account<T> = create_one_identity(old_key.clone())?;
// Change key a first time to add an old-old key
let genesis_hash = frame_system::Pallet::<T>::block_hash(BlockNumberFor::<T>::zero());
let new_key_payload = IdtyIndexAccountIdPayload {
genesis_hash: &genesis_hash,
idty_index: account.index,
old_owner_key: &account.key,
};
let message = (NEW_OWNER_KEY_PAYLOAD_PREFIX, new_key_payload).encode();
let caller_public = sr25519_generate(0.into(), None);
let caller: T::AccountId = MultiSigner::Sr25519(caller_public).into_account().into();
let signature = sr25519_sign(0.into(), &caller_public, &message)
.unwrap()
.into();
Pallet::<T>::change_owner_key(account.origin.clone(), caller.clone(), signature)?;
// Change key a second time to benchmark
// The sufficients for the old_old key will drop to 0 during benchmark
let caller_origin = RawOrigin::Signed(caller.clone());
let genesis_hash = frame_system::Pallet::<T>::block_hash(BlockNumberFor::<T>::zero());
let new_key_payload = IdtyIndexAccountIdPayload {
genesis_hash: &genesis_hash,
idty_index: account.index,
old_owner_key: &caller_public,
};
let message = (NEW_OWNER_KEY_PAYLOAD_PREFIX, new_key_payload).encode();
let caller_public = sr25519_generate(0.into(), None);
let caller: T::AccountId = MultiSigner::Sr25519(caller_public).into_account().into();
let signature = sr25519_sign(0.into(), &caller_public, &message)
.unwrap()
.into();
<frame_system::Pallet<T>>::set_block_number(
<frame_system::Pallet<T>>::block_number() + T::ChangeOwnerKeyPeriod::get(),
);
#[extrinsic_call]
_(caller_origin, caller.clone(), signature);
assert_has_event::<T>(
Event::<T>::IdtyChangedOwnerKey {
idty_index: account.index,
new_owner_key: caller.clone(),
}
.into(),
);
assert!(
IdentityIndexOf::<T>::get(&caller).unwrap() == account.index,
"Owner key not changed"
);
Ok(())
}
#[benchmark]
fn revoke_identity() -> Result<(), BenchmarkError> {
let old_key: T::AccountId = account("new_identity", 2, 1);
let account: Account<T> = create_one_identity(old_key.clone())?;
// Change key
// The sufficients for the old key will drop to 0 during benchmark (not for revoke, only for remove)
let genesis_hash = frame_system::Pallet::<T>::block_hash(BlockNumberFor::<T>::zero());
let new_key_payload = IdtyIndexAccountIdPayload {
genesis_hash: &genesis_hash,
idty_index: account.index,
old_owner_key: &account.key,
};
let message = (NEW_OWNER_KEY_PAYLOAD_PREFIX, new_key_payload).encode();
let caller_public = sr25519_generate(0.into(), None);
let caller: T::AccountId = MultiSigner::Sr25519(caller_public).into_account().into();
let signature = sr25519_sign(0.into(), &caller_public, &message)
.unwrap()
.into();
Pallet::<T>::change_owner_key(account.origin.clone(), caller.clone(), signature)?;
let genesis_hash = frame_system::Pallet::<T>::block_hash(BlockNumberFor::<T>::zero());
let revocation_payload = RevocationPayload {
genesis_hash: &genesis_hash,
idty_index: account.index,
};
let message = (REVOCATION_PAYLOAD_PREFIX, revocation_payload).encode();
let signature = sr25519_sign(0.into(), &caller_public, &message)
.unwrap()
.into();
#[extrinsic_call]
_(
RawOrigin::Signed(account.key),
account.index,
caller.clone(),
signature,
);
assert_has_event::<T>(
Event::<T>::IdtyRevoked {
idty_index: account.index,
reason: RevocationReason::User,
}
.into(),
);
Ok(())
}
#[benchmark]
fn prune_item_identities_names(i: Linear<2, 1_000>) -> Result<(), BenchmarkError> {
// The complexity depends on the number of identities to prune
// Populate identities
let identities_count = Pallet::<T>::identities_count();
create_identities::<T>(i)?;
let mut names = Vec::<IdtyName>::new();
for k in 1..i {
let name: IdtyName = IdtyName((k + identities_count).to_le_bytes().into());
assert!(
IdentitiesNames::<T>::contains_key(&name),
"Name not existing"
);
names.push(name);
}
#[extrinsic_call]
_(RawOrigin::Root, names.clone());
for name in names {
assert!(!IdentitiesNames::<T>::contains_key(&name), "Name existing");
}
Ok(())
}
#[benchmark]
fn fix_sufficients() -> Result<(), BenchmarkError> {
let new_identity: T::AccountId = account("Bob", 2, 1);
let account: Account<T> = create_one_identity(new_identity)?;
let sufficient = frame_system::Pallet::<T>::sufficients(&account.key);
#[extrinsic_call]
_(RawOrigin::Root, account.key.clone(), true);
assert!(
sufficient < frame_system::Pallet::<T>::sufficients(&account.key),
"Sufficient not incremented"
);
Ok(())
}
#[benchmark]
fn link_account() -> Result<(), BenchmarkError> {
let alice_origin =
RawOrigin::Signed(Identities::<T>::get(T::IdtyIndex::one()).unwrap().owner_key);
let bob_public = sr25519_generate(0.into(), None);
let bob: T::AccountId = MultiSigner::Sr25519(bob_public).into_account().into();
frame_system::Pallet::<T>::inc_providers(&bob);
let genesis_hash = frame_system::Pallet::<T>::block_hash(BlockNumberFor::<T>::zero());
let payload = (
LINK_IDTY_PAYLOAD_PREFIX,
genesis_hash,
T::IdtyIndex::one(),
bob.clone(),
)
.encode();
let signature = sr25519_sign(0.into(), &bob_public, &payload)
.unwrap()
.into();
#[extrinsic_call]
_(alice_origin, bob, signature);
Ok(())
}
#[benchmark]
fn on_initialize() {
// Base weight of an empty initialize
#[block]
{
Pallet::<T>::on_initialize(BlockNumberFor::<T>::zero());
}
}
#[benchmark]
fn do_revoke_identity_noop() {
let idty_index: T::IdtyIndex = 0u32.into();
assert!(Identities::<T>::get(idty_index).is_none());
#[block]
{
Pallet::<T>::do_revoke_identity(idty_index, RevocationReason::Root);
}
}
#[benchmark]
fn do_revoke_identity() {
let idty_index: T::IdtyIndex = 1u32.into();
let new_identity: T::AccountId = account("Bob", 2, 1);
assert!(Identities::<T>::get(idty_index).is_some());
Identities::<T>::mutate(idty_index, |id| {
if let Some(id) = id {
id.old_owner_key = Some((new_identity, BlockNumberFor::<T>::zero()));
}
});
assert!(Identities::<T>::get(idty_index)
.unwrap()
.old_owner_key
.is_some());
#[block]
{
Pallet::<T>::do_revoke_identity(idty_index, RevocationReason::Root);
}
assert_has_event::<T>(
Event::<T>::IdtyRevoked {
idty_index,
reason: RevocationReason::Root,
}
.into(),
);
}
#[benchmark]
fn do_remove_identity_noop() {
let idty_index: T::IdtyIndex = 0u32.into();
assert!(Identities::<T>::get(idty_index).is_none());
#[block]
{
Pallet::<T>::do_remove_identity(idty_index, RemovalReason::Revoked);
}
}
#[benchmark]
fn do_remove_identity() {
let idty_index: T::IdtyIndex = 1u32.into();
let new_identity: T::AccountId = account("Bob", 2, 1);
assert!(Identities::<T>::get(idty_index).is_some());
frame_system::Pallet::<T>::inc_sufficients(&new_identity);
Identities::<T>::mutate(idty_index, |id| {
if let Some(id) = id {
id.old_owner_key = Some((new_identity, BlockNumberFor::<T>::zero()));
}
});
assert!(Identities::<T>::get(idty_index)
.unwrap()
.old_owner_key
.is_some());
#[block]
{
Pallet::<T>::do_remove_identity(idty_index, RemovalReason::Revoked);
}
assert_has_event::<T>(
Event::<T>::IdtyRemoved {
idty_index,
reason: RemovalReason::Revoked,
}
.into(),
);
}
#[benchmark]
fn do_remove_identity_handler() {
let idty_index: T::IdtyIndex = 1u32.into();
let new_identity: T::AccountId = account("Bob", 2, 1);
assert!(Identities::<T>::get(idty_index).is_some());
frame_system::Pallet::<T>::inc_sufficients(&new_identity);
Identities::<T>::mutate(idty_index, |id| {
if let Some(id) = id {
id.old_owner_key = Some((new_identity, BlockNumberFor::<T>::zero()));
}
});
assert!(Identities::<T>::get(idty_index)
.unwrap()
.old_owner_key
.is_some());
#[block]
{
T::OnRemoveIdty::on_removed(&idty_index);
}
}
#[benchmark]
fn membership_removed() -> Result<(), BenchmarkError> {
let key: T::AccountId = account("new_identity", 2, 1);
let account: Account<T> = create_one_identity(key)?;
assert_eq!(
Identities::<T>::get(account.index).unwrap().status,
IdtyStatus::Member
);
#[block]
{
Pallet::<T>::membership_removed(account.index);
}
assert_eq!(
Identities::<T>::get(account.index).unwrap().status,
IdtyStatus::NotMember
);
Ok(())
}
#[benchmark]
fn prune_identities_noop() {
assert!(IdentityChangeSchedule::<T>::try_get(BlockNumberFor::<T>::zero()).is_err());
#[block]
{
Pallet::<T>::prune_identities(BlockNumberFor::<T>::zero());
}
}
#[benchmark]
fn prune_identities_none() {
let idty_index: T::IdtyIndex = 100u32.into();
IdentityChangeSchedule::<T>::append(BlockNumberFor::<T>::zero(), idty_index);
assert!(IdentityChangeSchedule::<T>::try_get(BlockNumberFor::<T>::zero()).is_ok());
assert!(<Identities<T>>::try_get(idty_index).is_err());
#[block]
{
Pallet::<T>::prune_identities(BlockNumberFor::<T>::zero());
}
}
#[benchmark]
fn prune_identities_err() -> Result<(), BenchmarkError> {
let idty_index: T::IdtyIndex = 100u32.into();
create_dummy_identity::<T>(100u32)?;
IdentityChangeSchedule::<T>::append(BlockNumberFor::<T>::zero(), idty_index);
#[block]
{
Pallet::<T>::prune_identities(BlockNumberFor::<T>::zero());
}
Ok(())
}
#[benchmark]
fn revoke_identity_legacy() -> Result<(), BenchmarkError> {
let caller_index = T::IdtyIndex::from(1u32);
let caller: T::AccountId = Identities::<T>::get(caller_index).unwrap().owner_key;
let idty_index: T::IdtyIndex = 102.into();
let owner_key: T::AccountId =
AccountId32::from_ss58check("5H2nLXGku46iztpqdRwsCAiP6vHZbShhKmSV4yyufQgEUFvV")
.unwrap()
.into();
let next_scheduled = BlockNumberFor::<T>::zero();
let value = IdtyValue {
data: Default::default(),
next_creatable_identity_on: BlockNumberFor::<T>::zero(),
old_owner_key: None,
owner_key: owner_key.clone(),
next_scheduled,
status: IdtyStatus::Member,
};
let name = "Charlie";
let idty_name = IdtyName(name.into());
frame_system::Pallet::<T>::inc_sufficients(&owner_key);
<Identities<T>>::insert(idty_index, value);
IdentityChangeSchedule::<T>::append(next_scheduled, idty_index);
IdentityIndexOf::<T>::insert(owner_key.clone(), idty_index);
<IdentitiesNames<T>>::insert(idty_name.clone(), idty_index);
let document = r"Version: 10
Type: Revocation
Currency: g1
Issuer: Fnf2xaxYdQpB4kU45DMLQ9Ey4bd6DtoebKJajRkLBUXm
IdtyUniqueID: Charlie
IdtyTimestamp: 42-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
IdtySignature: 7KUagcMiQw05rwbkBsRrnNqPRHu/Y5ukCLoAEpb/1tXAQsSNf2gRi1h5PWIGs9y/vHnFXvF5epKsOjA6X75vDg==
CfiG4xhcWS+/DgxY0xFIyOA9TVr4Im3XEXcCApNgXC+Ns9jy2yrNoC3NF8MCD63cZ8QTRfrr4Iv6n3leYCCcDQ==
";
#[extrinsic_call]
_(RawOrigin::Signed(caller), document.into());
assert_has_event::<T>(
Event::<T>::IdtyRevoked {
idty_index,
reason: RevocationReason::User,
}
.into(),
);
Ok(())
}
impl_benchmark_test_suite!(
Pallet,
// Create genesis identity Alice to test benchmark in mock
crate::mock::new_test_ext(crate::mock::IdentityConfig {
identities: vec![
crate::GenesisIdty {
index: 1,
name: crate::IdtyName::from("Alice"),
value: crate::IdtyValue {
data: (),
next_creatable_identity_on: 0,
old_owner_key: None,
owner_key: frame_benchmarking::account("Alice", 1, 1),
next_scheduled: 0,
status: crate::IdtyStatus::Member,
},
},
crate::GenesisIdty {
index: 2,
name: crate::IdtyName::from("Bob"),
value: crate::IdtyValue {
data: (),
next_creatable_identity_on: 0,
old_owner_key: None,
owner_key: frame_benchmarking::account("Bob", 1, 1),
next_scheduled: 0,
status: crate::IdtyStatus::Unconfirmed,
},
},
]
}),
crate::mock::Test,
);
}
// Copyright 2021 Axiom-Team // Copyright 2021-2023 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
//! # Duniter Identity Pallet
//!
//! Duniter features a built-in identity system that does not rely on external registrars, unlike the [Parity Identity Pallet](https://github.com/paritytech/substrate/tree/master/frame/identity).
//!
//! ## Duniter Identity Structure
//!
//! A Duniter identity comprises several key components:
//!
//! ### Name
//!
//! Each identity is declared with a name emitted during the confirmation event. Duniter maintains a hashed list of identity names to ensure uniqueness.
//!
//! ### Owner Key
//!
//! The owner key allows users to maintain a fixed identity while changing keys for security reasons, such as when a device with the keys might have been compromised. Changes are subject to frequency limits, and the old owner key can still revoke the identity for a given period.
//!
//! ### Status / Removable Date
//!
//! The status is a temporary value that allows pruning of identities before they become full members:
//! - **Unconfirmed**: Created by a member identity but not yet confirmed by the owner.
//! - **Unvalidated**: Confirmed by the owner, including assignment of a name.
//! - **Member**: Part of the main Web of Trust (WoT).
//! - **NotMember**: Not part of the main WoT.
//! - **Revoked**: Automatically or manually revoked.
//!
//! An identity that is not yet validated (e.g., not a member of the WoT) can be removed when its removable date is reached. The removable date of a validated identity is set to block zero.
//!
//! ### Next Certification
//!
//! The next certification specifies the block number from which the identity can issue its next certification, acting as a rate limit for certification issuance and identity creation.
//!
//! ### Revocation
//!
//! Revoking an identity essentially means deleting it from the system.
//!
//! Additional runtime-defined data may also be attached to identities, such the number of the first Universal Dividends (UD) it is eligible to.
#![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(not(feature = "std"), no_std)]
#![allow(clippy::type_complexity)] #![allow(clippy::type_complexity)]
pub mod traits; pub mod traits;
mod types; mod types;
pub mod weights;
#[cfg(test)] #[cfg(test)]
mod mock; mod mock;
...@@ -26,31 +64,37 @@ mod mock; ...@@ -26,31 +64,37 @@ mod mock;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
/*#[cfg(feature = "runtime-benchmarks")] pub mod benchmarking;
mod benchmarking;*/
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::dispatch::Weight; use frame_support::pallet_prelude::Weight;
use scale_info::prelude::{collections::BTreeSet, fmt::Debug, vec::Vec};
use sp_runtime::traits::{AtLeast32BitUnsigned, IdentifyAccount, One, Saturating, Verify, Zero}; use sp_runtime::traits::{AtLeast32BitUnsigned, IdentifyAccount, One, Saturating, Verify, Zero};
use sp_std::fmt::Debug;
use sp_std::prelude::*;
// icok = identity change owner key
pub const NEW_OWNER_KEY_PAYLOAD_PREFIX: [u8; 4] = [b'i', b'c', b'o', b'k'];
// revo = revocation
pub const REVOCATION_PAYLOAD_PREFIX: [u8; 4] = [b'r', b'e', b'v', b'o'];
// link = link (identity with account)
pub const LINK_IDTY_PAYLOAD_PREFIX: [u8; 4] = [b'l', b'i', b'n', b'k'];
#[allow(unreachable_patterns)]
#[frame_support::pallet] #[frame_support::pallet]
pub mod pallet { pub mod pallet {
use super::*; use super::*;
use frame_support::pallet_prelude::*; use base64::Engine;
use frame_support::traits::StorageVersion; use frame_support::{pallet_prelude::*, traits::StorageVersion};
use frame_system::pallet_prelude::*; use frame_system::pallet_prelude::*;
/// 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>(_); pub struct Pallet<T>(_);
...@@ -59,16 +103,53 @@ pub mod pallet { ...@@ -59,16 +103,53 @@ pub mod pallet {
#[pallet::config] #[pallet::config]
pub trait Config: frame_system::Config { pub trait Config: frame_system::Config {
/// The way to deserialize accound id from 32 raw bytes
type AccountId32: From<[u8; 32]> + Into<Self::AccountId>;
/// The period during which the owner can confirm the new identity.
#[pallet::constant]
type ConfirmPeriod: Get<BlockNumberFor<Self>>;
/// The period during which the identity has to be validated to become a member.
#[pallet::constant]
type ValidationPeriod: Get<BlockNumberFor<Self>>;
/// The period before which an identity that lost membership is automatically revoked.
#[pallet::constant]
type AutorevocationPeriod: Get<BlockNumberFor<Self>>;
/// The period after which a revoked identity is removed and the keys are freed.
#[pallet::constant] #[pallet::constant]
/// Period during which the owner can confirm the new identity. type DeletionPeriod: Get<BlockNumberFor<Self>>;
type ConfirmPeriod: Get<Self::BlockNumber>;
/// Because this pallet emits events, it depends on the runtime's definition of an event. /// The minimum duration between two owner key changes to prevent identity theft.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>; #[pallet::constant]
/// Management of the authorizations of the different calls. (The default implementation only allows root) type ChangeOwnerKeyPeriod: Get<BlockNumberFor<Self>>;
type EnsureIdtyCallAllowed: EnsureIdtyCallAllowed<Self>;
/// Minimum duration between the creation of 2 identities by the same creator /// The minimum duration between the creation of two identities by the same creator.
type IdtyCreationPeriod: Get<Self::BlockNumber>; /// Should be greater than or equal to the certification period defined in the certification pallet.
/// A short identity index. #[pallet::constant]
type IdtyCreationPeriod: Get<BlockNumberFor<Self>>;
/// Management of the authorizations of the different calls related to identity.
type CheckIdtyCallAllowed: CheckIdtyCallAllowed<Self>;
/// The type used to check account worthiness.
type CheckAccountWorthiness: CheckAccountWorthiness<Self>;
/// A handler called when an identity's owner key change.
type OnKeyChange: KeyChange<Self>;
/// Custom data to store in each identity.
type IdtyData: Clone
+ Codec
+ Default
+ Eq
+ TypeInfo
+ MaybeSerializeDeserialize
+ MaxEncodedLen;
/// A short identity index type.
type IdtyIndex: Parameter type IdtyIndex: Parameter
+ Member + Member
+ AtLeast32BitUnsigned + AtLeast32BitUnsigned
...@@ -78,31 +159,42 @@ pub mod pallet { ...@@ -78,31 +159,42 @@ pub mod pallet {
+ MaybeSerializeDeserialize + MaybeSerializeDeserialize
+ Debug + Debug
+ MaxEncodedLen; + MaxEncodedLen;
/// Handle logic to validate an identity name
/// A type for linking account data to identity.
type AccountLinker: LinkIdty<Self::AccountId, Self::IdtyIndex>;
/// Handle logic to validate an identity name.
type IdtyNameValidator: IdtyNameValidator; type IdtyNameValidator: IdtyNameValidator;
/// Origin allowed to validate identity
type IdtyValidationOrigin: EnsureOrigin<Self::Origin>; /// Handler called when a new identity is created.
/// type OnNewIdty: OnNewIdty<Self>;
type IsMember: sp_runtime::traits::IsMember<Self::IdtyIndex>;
/// On identity confirmed by it's owner /// Handler called when an identity is removed.
type OnIdtyChange: OnIdtyChange<Self>; type OnRemoveIdty: OnRemoveIdty<Self>;
/// Handle the logic that remove all identity consumers.
/// "identity consumers" mean all things that rely on the existence of the identity. /// Signing key type used for payload signatures.
type RemoveIdentityConsumers: RemoveIdentityConsumers<Self::IdtyIndex>; type Signer: IdentifyAccount<AccountId = Self::AccountId>;
/// Signing key of revocation payload
type RevocationSigner: IdentifyAccount<AccountId = Self::AccountId>; /// Signature type for payload verification.
/// Signature of revocation payload type Signature: Parameter
type RevocationSignature: Parameter + Verify<Signer = Self::RevocationSigner>; + Verify<Signer = Self::Signer>
+ From<sp_core::ed25519::Signature>;
/// 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;
} }
// GENESIS STUFF // // GENESIS STUFF //
#[cfg_attr(feature = "std", derive(serde::Deserialize, serde::Serialize))] #[derive(Encode, Decode, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[derive(Encode, Decode, Clone, PartialEq, Eq)] #[serde(deny_unknown_fields, bound(serialize = ""), bound(deserialize = ""))]
pub struct GenesisIdty<T: Config> { pub struct GenesisIdty<T: Config> {
pub index: T::IdtyIndex, pub index: T::IdtyIndex,
pub name: IdtyName, pub name: IdtyName,
pub value: IdtyValue<T::BlockNumber, T::AccountId>, pub value: IdtyValue<BlockNumberFor<T>, T::AccountId, T::IdtyData>,
} }
#[pallet::genesis_config] #[pallet::genesis_config]
...@@ -110,7 +202,6 @@ pub mod pallet { ...@@ -110,7 +202,6 @@ pub mod pallet {
pub identities: Vec<GenesisIdty<T>>, pub identities: Vec<GenesisIdty<T>>,
} }
#[cfg(feature = "std")]
impl<T: Config> Default for GenesisConfig<T> { impl<T: Config> Default for GenesisConfig<T> {
fn default() -> Self { fn default() -> Self {
Self { Self {
...@@ -120,16 +211,15 @@ pub mod pallet { ...@@ -120,16 +211,15 @@ pub mod pallet {
} }
#[pallet::genesis_build] #[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> { impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) { fn build(&self) {
let mut names = sp_std::collections::btree_set::BTreeSet::new(); let mut names = BTreeSet::new();
for idty in &self.identities { for idty in &self.identities {
assert!( assert!(
!names.contains(&idty.name), !names.contains(&idty.name),
"Idty name {:?} is present twice", "Idty name {:?} is present twice",
&idty.name &idty.name
); );
assert!(idty.value.removable_on == T::BlockNumber::zero());
names.insert(idty.name.clone()); names.insert(idty.name.clone());
} }
...@@ -137,59 +227,69 @@ pub mod pallet { ...@@ -137,59 +227,69 @@ pub mod pallet {
identities.sort_unstable_by(|a, b| a.index.cmp(&b.index)); identities.sort_unstable_by(|a, b| a.index.cmp(&b.index));
for idty in identities.into_iter() { for idty in identities.into_iter() {
let idty_index = Pallet::<T>::get_next_idty_index(); // if get_next_idty_index() is not called
if idty.value.removable_on > T::BlockNumber::zero() { // then NextIdtyIndex is not incremented
<IdentitiesRemovableOn<T>>::append( let _ = Pallet::<T>::get_next_idty_index();
idty.value.removable_on, // use instead custom provided index
(idty_index, idty.value.status), let idty_index = idty.index;
) if idty.value.next_scheduled > BlockNumberFor::<T>::zero() {
<IdentityChangeSchedule<T>>::append(idty.value.next_scheduled, idty_index)
} }
<Identities<T>>::insert(idty_index, idty.value.clone()); <Identities<T>>::insert(idty_index, idty.value.clone());
IdentitiesNames::<T>::insert(idty.name.clone(), ()); IdentitiesNames::<T>::insert(idty.name.clone(), idty_index);
IdentityIndexOf::<T>::insert(idty.value.owner_key, idty_index); IdentityIndexOf::<T>::insert(&idty.value.owner_key, idty_index);
frame_system::Pallet::<T>::inc_sufficients(&idty.value.owner_key);
if let Some((old_owner_key, _last_change)) = idty.value.old_owner_key {
frame_system::Pallet::<T>::inc_sufficients(&old_owner_key);
}
} }
} }
} }
// STORAGE // // STORAGE //
/// The identity value for each identity.
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn identity)] #[pallet::getter(fn identity)]
pub type Identities<T: Config> = CountedStorageMap< pub type Identities<T: Config> = CountedStorageMap<
_, _,
Twox64Concat, Twox64Concat,
T::IdtyIndex, T::IdtyIndex,
IdtyValue<T::BlockNumber, T::AccountId>, IdtyValue<BlockNumberFor<T>, T::AccountId, T::IdtyData>,
OptionQuery, OptionQuery,
>; >;
/// The identity associated with each account.
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn identity_index_of)] #[pallet::getter(fn identity_index_of)]
pub type IdentityIndexOf<T: Config> = pub type IdentityIndexOf<T: Config> =
StorageMap<_, Blake2_128, T::AccountId, T::IdtyIndex, OptionQuery>; StorageMap<_, Blake2_128Concat, T::AccountId, T::IdtyIndex, OptionQuery>;
/// The name associated with each identity.
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn identity_by_did)] #[pallet::getter(fn identity_by_did)]
pub type IdentitiesNames<T: Config> = StorageMap<_, Blake2_128, IdtyName, (), OptionQuery>; pub type IdentitiesNames<T: Config> =
StorageMap<_, Blake2_128Concat, IdtyName, T::IdtyIndex, OptionQuery>;
/// The identity index to assign to the next created identity.
#[pallet::storage] #[pallet::storage]
pub(super) type NextIdtyIndex<T: Config> = StorageValue<_, T::IdtyIndex, ValueQuery>; pub(super) type NextIdtyIndex<T: Config> = StorageValue<_, T::IdtyIndex, ValueQuery>;
/// Identities by removed block /// The identities to remove at a given block.
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn removable_on)] #[pallet::getter(fn next_scheduled)]
pub type IdentitiesRemovableOn<T: Config> = pub type IdentityChangeSchedule<T: Config> =
StorageMap<_, Twox64Concat, T::BlockNumber, Vec<(T::IdtyIndex, IdtyStatus)>, ValueQuery>; StorageMap<_, Twox64Concat, BlockNumberFor<T>, Vec<T::IdtyIndex>, ValueQuery>;
// HOOKS // // HOOKS //
#[pallet::hooks] #[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(n: T::BlockNumber) -> Weight { fn on_initialize(n: BlockNumberFor<T>) -> Weight {
if n > T::BlockNumber::zero() { if n > BlockNumberFor::<T>::zero() {
Self::prune_identities(n) Self::prune_identities(n).saturating_add(T::WeightInfo::on_initialize())
} else { } else {
0 T::WeightInfo::on_initialize()
} }
} }
} }
...@@ -201,25 +301,32 @@ pub mod pallet { ...@@ -201,25 +301,32 @@ pub mod pallet {
#[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> { pub enum Event<T: Config> {
/// A new identity has been created /// A new identity has been created.
/// [idty_index, owner_key]
IdtyCreated { IdtyCreated {
idty_index: T::IdtyIndex, idty_index: T::IdtyIndex,
owner_key: T::AccountId, owner_key: T::AccountId,
}, },
/// An identity has been confirmed by it's owner /// An identity has been confirmed by its owner.
/// [idty_index, owner_key, name]
IdtyConfirmed { IdtyConfirmed {
idty_index: T::IdtyIndex, idty_index: T::IdtyIndex,
owner_key: T::AccountId,
name: IdtyName, name: IdtyName,
}, },
/// An identity has been validated /// An identity has been validated.
/// [idty_index]
IdtyValidated { idty_index: T::IdtyIndex }, IdtyValidated { idty_index: T::IdtyIndex },
/// An identity has been removed IdtyChangedOwnerKey {
/// [idty_index] idty_index: T::IdtyIndex,
IdtyRemoved { idty_index: T::IdtyIndex }, new_owner_key: T::AccountId,
},
/// An identity has been revoked.
IdtyRevoked {
idty_index: T::IdtyIndex,
reason: RevocationReason,
},
/// An identity has been removed.
IdtyRemoved {
idty_index: T::IdtyIndex,
reason: RemovalReason,
},
} }
// CALLS // // CALLS //
...@@ -229,7 +336,13 @@ pub mod pallet { ...@@ -229,7 +336,13 @@ pub mod pallet {
// Dispatchable functions must be annotated with a weight and must return a DispatchResult. // Dispatchable functions must be annotated with a weight and must return a DispatchResult.
#[pallet::call] #[pallet::call]
impl<T: Config> Pallet<T> { impl<T: Config> Pallet<T> {
#[pallet::weight(1_000_000_000)] /// Create an identity for an existing account
///
/// - `owner_key`: the public key corresponding to the identity to be created
///
/// The origin must be allowed to create an identity.
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::create_identity())]
pub fn create_identity( pub fn create_identity(
origin: OriginFor<T>, origin: OriginFor<T>,
owner_key: T::AccountId, owner_key: T::AccountId,
...@@ -237,61 +350,51 @@ pub mod pallet { ...@@ -237,61 +350,51 @@ pub mod pallet {
// Verification phase // // Verification phase //
let who = ensure_signed(origin)?; let who = ensure_signed(origin)?;
let creator =
IdentityIndexOf::<T>::try_get(&who).map_err(|_| Error::<T>::IdtyIndexNotFound)?;
let creator_idty_val =
Identities::<T>::try_get(&creator).map_err(|_| Error::<T>::IdtyNotFound)?;
ensure!(
frame_system::Pallet::<T>::account_exists(&owner_key),
Error::<T>::OwnerAccountNotExist
);
if IdentityIndexOf::<T>::contains_key(&owner_key) {
return Err(Error::<T>::IdtyAlreadyCreated.into());
}
if !T::EnsureIdtyCallAllowed::can_create_identity(creator) {
return Err(Error::<T>::CreatorNotAllowedToCreateIdty.into());
}
let block_number = frame_system::pallet::Pallet::<T>::block_number(); let block_number = frame_system::pallet::Pallet::<T>::block_number();
let creator_index = Self::check_create_identity(&who, &owner_key, block_number)?;
if creator_idty_val.next_creatable_identity_on > block_number {
return Err(Error::<T>::NotRespectIdtyCreationPeriod.into());
}
// Apply phase // // Apply phase //
frame_system::Pallet::<T>::inc_sufficients(&owner_key); frame_system::Pallet::<T>::inc_sufficients(&owner_key);
<Identities<T>>::mutate_exists(creator, |idty_val_opt| { <Identities<T>>::mutate_exists(creator_index, |idty_val_opt| {
if let Some(ref mut idty_val) = idty_val_opt { if let Some(ref mut idty_val) = idty_val_opt {
idty_val.next_creatable_identity_on = idty_val.next_creatable_identity_on =
block_number + T::IdtyCreationPeriod::get(); block_number + T::IdtyCreationPeriod::get();
} }
}); });
let removable_on = block_number + T::ConfirmPeriod::get();
let idty_index = Self::get_next_idty_index(); let idty_index = Self::get_next_idty_index();
<Identities<T>>::insert( <Identities<T>>::insert(
idty_index, idty_index,
IdtyValue { IdtyValue {
next_creatable_identity_on: T::BlockNumber::zero(), data: Default::default(),
next_creatable_identity_on: BlockNumberFor::<T>::zero(),
old_owner_key: None,
owner_key: owner_key.clone(), owner_key: owner_key.clone(),
removable_on, next_scheduled: Self::schedule_identity_change(
status: IdtyStatus::Created, idty_index,
T::ConfirmPeriod::get(),
),
status: IdtyStatus::Unconfirmed,
}, },
); );
IdentitiesRemovableOn::<T>::append(removable_on, (idty_index, IdtyStatus::Created));
IdentityIndexOf::<T>::insert(owner_key.clone(), idty_index); IdentityIndexOf::<T>::insert(owner_key.clone(), idty_index);
Self::deposit_event(Event::IdtyCreated { Self::deposit_event(Event::IdtyCreated {
idty_index, idty_index,
owner_key, owner_key: owner_key.clone(),
}); });
T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Created { creator }); T::AccountLinker::link_identity(&owner_key, idty_index)?;
T::OnNewIdty::on_created(&idty_index, &creator_index);
Ok(().into()) Ok(().into())
} }
#[pallet::weight(1_000_000_000)]
/// Confirm the creation of an identity and give it a name
///
/// - `idty_name`: the name uniquely associated to this identity. Must match the validation rules defined by the runtime.
///
/// The identity must have been created using `create_identity` before it can be confirmed.
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::confirm_identity())]
pub fn confirm_identity( pub fn confirm_identity(
origin: OriginFor<T>, origin: OriginFor<T>,
idty_name: IdtyName, idty_name: IdtyName,
...@@ -302,10 +405,10 @@ pub mod pallet { ...@@ -302,10 +405,10 @@ pub mod pallet {
let idty_index = let idty_index =
IdentityIndexOf::<T>::try_get(&who).map_err(|_| Error::<T>::IdtyIndexNotFound)?; IdentityIndexOf::<T>::try_get(&who).map_err(|_| Error::<T>::IdtyIndexNotFound)?;
let mut idty_value = let idty_value =
Identities::<T>::try_get(idty_index).map_err(|_| Error::<T>::IdtyNotFound)?; Identities::<T>::try_get(idty_index).map_err(|_| Error::<T>::IdtyNotFound)?;
if idty_value.status != IdtyStatus::Created { if idty_value.status != IdtyStatus::Unconfirmed {
return Err(Error::<T>::IdtyAlreadyConfirmed.into()); return Err(Error::<T>::IdtyAlreadyConfirmed.into());
} }
if !T::IdtyNameValidator::validate(&idty_name) { if !T::IdtyNameValidator::validate(&idty_name) {
...@@ -314,95 +417,279 @@ pub mod pallet { ...@@ -314,95 +417,279 @@ pub mod pallet {
if <IdentitiesNames<T>>::contains_key(&idty_name) { if <IdentitiesNames<T>>::contains_key(&idty_name) {
return Err(Error::<T>::IdtyNameAlreadyExist.into()); return Err(Error::<T>::IdtyNameAlreadyExist.into());
} }
if !T::EnsureIdtyCallAllowed::can_confirm_identity(idty_index, who.clone()) {
return Err(Error::<T>::NotAllowedToConfirmIdty.into());
}
// Apply phase // // Apply phase //
idty_value.status = IdtyStatus::ConfirmedByOwner; Self::update_identity_status(
idty_index,
idty_value,
IdtyStatus::Unvalidated,
T::ValidationPeriod::get(),
);
<Identities<T>>::insert(idty_index, idty_value); <IdentitiesNames<T>>::insert(idty_name.clone(), idty_index);
<IdentitiesNames<T>>::insert(idty_name.clone(), ());
Self::deposit_event(Event::IdtyConfirmed { Self::deposit_event(Event::IdtyConfirmed {
idty_index, idty_index,
owner_key: who,
name: idty_name, name: idty_name,
}); });
T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Confirmed);
Ok(().into()) Ok(().into())
} }
#[pallet::weight(1_000_000_000)]
pub fn validate_identity( /// Change identity owner key.
///
/// - `new_key`: the new owner key.
/// - `new_key_sig`: the signature of the encoded form of `IdtyIndexAccountIdPayload`.
/// Must be signed by `new_key`.
///
/// The origin should be the old identity owner key.
#[pallet::call_index(3)]
#[pallet::weight(T::WeightInfo::change_owner_key())]
pub fn change_owner_key(
origin: OriginFor<T>, origin: OriginFor<T>,
idty_index: T::IdtyIndex, new_key: T::AccountId,
new_key_sig: T::Signature,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
// Verification phase // // verification phase
T::IdtyValidationOrigin::ensure_origin(origin)?; let who = ensure_signed(origin)?;
let idty_index =
IdentityIndexOf::<T>::get(&who).ok_or(Error::<T>::IdtyIndexNotFound)?;
let mut idty_value = let mut idty_value =
Identities::<T>::try_get(idty_index).map_err(|_| Error::<T>::IdtyNotFound)?; Identities::<T>::get(idty_index).ok_or(Error::<T>::IdtyNotFound)?;
match idty_value.status { ensure!(
IdtyStatus::Created => return Err(Error::<T>::IdtyNotConfirmedByOwner.into()), IdentityIndexOf::<T>::get(&new_key).is_none(),
IdtyStatus::ConfirmedByOwner => { Error::<T>::OwnerKeyAlreadyUsed
if !T::EnsureIdtyCallAllowed::can_validate_identity(idty_index) { );
return Err(Error::<T>::NotAllowedToValidateIdty.into());
}
}
IdtyStatus::Validated => return Err(Error::<T>::IdtyAlreadyValidated.into()),
}
// Apply phase // let block_number = frame_system::Pallet::<T>::block_number();
idty_value.removable_on = T::BlockNumber::zero(); let maybe_old_old_owner_key =
idty_value.status = IdtyStatus::Validated; if let Some((old_owner_key, last_change)) = idty_value.old_owner_key {
ensure!(
block_number >= last_change + T::ChangeOwnerKeyPeriod::get(),
Error::<T>::OwnerKeyAlreadyRecentlyChanged
);
ensure!(
old_owner_key != new_key,
Error::<T>::ProhibitedToRevertToAnOldKey
);
Some(old_owner_key)
} else {
None
};
<Identities<T>>::insert(idty_index, idty_value); let genesis_hash = frame_system::Pallet::<T>::block_hash(BlockNumberFor::<T>::zero());
Self::deposit_event(Event::IdtyValidated { idty_index }); let new_key_payload = IdtyIndexAccountIdPayload {
T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Validated); genesis_hash: &genesis_hash,
idty_index,
old_owner_key: &idty_value.owner_key,
};
ensure!(
(NEW_OWNER_KEY_PAYLOAD_PREFIX, new_key_payload)
.using_encoded(|bytes| new_key_sig.verify(bytes, &new_key)),
Error::<T>::InvalidSignature
);
// Apply phase
if let Some(old_old_owner_key) = maybe_old_old_owner_key {
frame_system::Pallet::<T>::dec_sufficients(&old_old_owner_key);
}
IdentityIndexOf::<T>::remove(&idty_value.owner_key);
idty_value.old_owner_key = Some((idty_value.owner_key.clone(), block_number));
idty_value.owner_key = new_key.clone();
frame_system::Pallet::<T>::inc_sufficients(&idty_value.owner_key);
IdentityIndexOf::<T>::insert(&idty_value.owner_key, idty_index);
Identities::<T>::insert(idty_index, idty_value);
T::AccountLinker::link_identity(&new_key, idty_index)?;
T::OnKeyChange::on_changed(idty_index, new_key.clone())?;
Self::deposit_event(Event::IdtyChangedOwnerKey {
idty_index,
new_owner_key: new_key,
});
Ok(().into()) Ok(().into())
} }
#[pallet::weight(1_000_000_000)]
/// Revoke an identity using a revocation signature
///
/// - `idty_index`: the index of the identity to be revoked.
/// - `revocation_key`: the key used to sign the revocation payload.
/// - `revocation_sig`: the signature of the encoded form of `RevocationPayload`.
/// Must be signed by `revocation_key`.
///
/// Any signed origin can execute this call.
#[pallet::call_index(4)]
#[pallet::weight(T::WeightInfo::revoke_identity())]
pub fn revoke_identity( pub fn revoke_identity(
origin: OriginFor<T>, origin: OriginFor<T>,
payload: RevocationPayload<T::AccountId, T::Hash>, idty_index: T::IdtyIndex,
payload_sig: T::RevocationSignature, revocation_key: T::AccountId,
revocation_sig: T::Signature,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
let _ = ensure_signed(origin)?; let _ = ensure_signed(origin)?;
let idty_value = Identities::<T>::get(idty_index).ok_or(Error::<T>::IdtyNotFound)?;
match idty_value.status {
IdtyStatus::Unconfirmed => Err(Error::<T>::CanNotRevokeUnconfirmed),
IdtyStatus::Unvalidated => Err(Error::<T>::CanNotRevokeUnvalidated),
IdtyStatus::Member => Ok(()),
IdtyStatus::NotMember => Ok(()),
IdtyStatus::Revoked => Err(Error::<T>::AlreadyRevoked),
}?;
ensure!( ensure!(
payload.genesis_hash if let Some((ref old_owner_key, last_change)) = idty_value.old_owner_key {
== frame_system::Pallet::<T>::block_hash(T::BlockNumber::zero()), // old owner key can also revoke the identity until the period expired
Error::<T>::InvalidGenesisHash revocation_key == idty_value.owner_key
|| (&revocation_key == old_owner_key
&& frame_system::Pallet::<T>::block_number()
< last_change + T::ChangeOwnerKeyPeriod::get())
} else {
revocation_key == idty_value.owner_key
},
Error::<T>::InvalidRevocationKey
); );
// then check payload signature
let genesis_hash = frame_system::Pallet::<T>::block_hash(BlockNumberFor::<T>::zero());
let revocation_payload = RevocationPayload {
genesis_hash,
idty_index,
};
ensure!( ensure!(
payload.using_encoded(|bytes| payload_sig.verify(bytes, &payload.owner_key)), (REVOCATION_PAYLOAD_PREFIX, revocation_payload)
Error::<T>::InvalidRevocationProof .using_encoded(|bytes| revocation_sig.verify(bytes, &revocation_key)),
Error::<T>::InvalidSignature
); );
if let Some(idty_index) = <IdentityIndexOf<T>>::take(&payload.owner_key) {
Self::do_remove_identity(idty_index, Some(&payload.owner_key)); // finally if all checks pass, remove identity
Self::do_revoke_identity(idty_index, RevocationReason::User);
Ok(().into()) Ok(().into())
} else {
Err(Error::<T>::IdtyNotFound.into())
}
} }
#[pallet::weight(1_000_000_000)] /// Revoke an identity using a legacy (DUBP) revocation document
pub fn remove_identity( ///
/// - `revocation document`: the full-length revocation document, signature included
///
/// Any signed origin can execute this call.
#[pallet::call_index(9)]
#[pallet::weight(T::WeightInfo::revoke_identity_legacy())]
pub fn revoke_identity_legacy(
origin: OriginFor<T>, origin: OriginFor<T>,
idty_index: T::IdtyIndex, revocation_document: Vec<u8>,
idty_name: Option<IdtyName>,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
ensure_root(origin)?; let _ = ensure_signed(origin)?;
Self::do_remove_identity(idty_index, None); // Strip possible Unicode magic number that is not part of the protocol
if let Some(idty_name) = idty_name { let revocation_document = revocation_document
<IdentitiesNames<T>>::remove(idty_name); .strip_prefix(b"\xef\xbb\xbf")
} .unwrap_or(&revocation_document);
let mut lines = revocation_document.split(|b| *b == b'\n');
ensure!(
lines.next() == Some(b"Version: 10"),
Error::<T>::InvalidLegacyRevocationFormat
);
ensure!(
lines.next() == Some(b"Type: Revocation"),
Error::<T>::InvalidLegacyRevocationFormat
);
ensure!(
lines.next() == Some(b"Currency: g1"),
Error::<T>::InvalidLegacyRevocationFormat
);
let line_issuer = lines
.next()
.ok_or(Error::<T>::InvalidLegacyRevocationFormat)?;
let line_username = lines
.next()
.ok_or(Error::<T>::InvalidLegacyRevocationFormat)?;
let _line_blockstamp = lines
.next()
.ok_or(Error::<T>::InvalidLegacyRevocationFormat)?;
let _line_idty_signature = lines
.next()
.ok_or(Error::<T>::InvalidLegacyRevocationFormat)?;
let line_signature = lines
.next()
.ok_or(Error::<T>::InvalidLegacyRevocationFormat)?;
ensure!(
lines.next() == Some(b""),
Error::<T>::InvalidLegacyRevocationFormat
);
ensure!(
lines.next().is_none(),
Error::<T>::InvalidLegacyRevocationFormat
);
let document = revocation_document
.get(0..revocation_document.len().saturating_sub(89))
.ok_or(Error::<T>::InvalidLegacyRevocationFormat)?;
let mut signature = [0; 64];
base64::prelude::BASE64_STANDARD
.decode_slice(line_signature, &mut signature)
.map_err(|_| Error::<T>::InvalidLegacyRevocationFormat)?;
let issuer = bs58::decode(
line_issuer
.get(8..)
.ok_or(Error::<T>::InvalidLegacyRevocationFormat)?,
)
.into_array_const::<32>()
.map_err(|_| Error::<T>::InvalidLegacyRevocationFormat)?;
// Verify signature
let revocation_key = T::AccountId32::from(issuer).into();
let signature = T::Signature::from(sp_core::ed25519::Signature::from(signature));
ensure!(
signature.verify(document, &revocation_key),
Error::<T>::InvalidSignature
);
let username = line_username
.get(14..)
.ok_or(Error::<T>::InvalidLegacyRevocationFormat)?;
let idty_index = <IdentitiesNames<T>>::get(IdtyName(username.into()))
.ok_or(Error::<T>::IdtyNotFound)?;
let idty_value = Identities::<T>::get(idty_index).ok_or(Error::<T>::IdtyNotFound)?;
match idty_value.status {
IdtyStatus::Unconfirmed => Err(Error::<T>::CanNotRevokeUnconfirmed),
IdtyStatus::Unvalidated => Err(Error::<T>::CanNotRevokeUnvalidated),
IdtyStatus::Member => Ok(()),
IdtyStatus::NotMember => Ok(()),
IdtyStatus::Revoked => Err(Error::<T>::AlreadyRevoked),
}?;
ensure!(
if let Some((ref old_owner_key, last_change)) = idty_value.old_owner_key {
// old owner key can also revoke the identity until the period expired
revocation_key == idty_value.owner_key
|| (&revocation_key == old_owner_key
&& frame_system::Pallet::<T>::block_number()
< last_change + T::ChangeOwnerKeyPeriod::get())
} else {
revocation_key == idty_value.owner_key
},
Error::<T>::InvalidRevocationKey
);
// finally if all checks pass, remove identity
Self::do_revoke_identity(idty_index, RevocationReason::User);
Ok(().into()) Ok(().into())
} }
#[pallet::weight(1_000_000_000)] /// Remove identity names from storage.
///
/// This function allows a privileged root origin to remove multiple identity names from storage
/// in bulk.
///
/// - `origin` - The origin of the call. It must be root.
/// - `names` - A vector containing the identity names to be removed from storage.
#[pallet::call_index(6)]
#[pallet::weight(T::WeightInfo::prune_item_identities_names(names.len() as u32))]
pub fn prune_item_identities_names( pub fn prune_item_identities_names(
origin: OriginFor<T>, origin: OriginFor<T>,
names: Vec<IdtyName>, names: Vec<IdtyName>,
...@@ -416,23 +703,68 @@ pub mod pallet { ...@@ -416,23 +703,68 @@ pub mod pallet {
Ok(().into()) Ok(().into())
} }
#[pallet::weight(1_000_000_000)] /// Change sufficient reference count for a given key.
pub fn prune_item_identity_index_of( ///
/// This function allows a privileged root origin to increment or decrement the sufficient
/// reference count associated with a specified owner key.
///
/// - `origin` - The origin of the call. It must be root.
/// - `owner_key` - The account whose sufficient reference count will be modified.
/// - `inc` - A boolean indicating whether to increment (`true`) or decrement (`false`) the count.
///
#[pallet::call_index(7)]
#[pallet::weight(T::WeightInfo::fix_sufficients())]
pub fn fix_sufficients(
origin: OriginFor<T>, origin: OriginFor<T>,
accounts_ids: Vec<T::AccountId>, owner_key: T::AccountId,
inc: bool,
) -> DispatchResultWithPostInfo { ) -> DispatchResultWithPostInfo {
ensure_root(origin)?; ensure_root(origin)?;
accounts_ids if inc {
.into_iter() frame_system::Pallet::<T>::inc_sufficients(&owner_key);
.filter(|account_id| {
if let Ok(idty_index) = IdentityIndexOf::<T>::try_get(&account_id) {
!Identities::<T>::contains_key(&idty_index)
} else { } else {
false frame_system::Pallet::<T>::dec_sufficients(&owner_key);
} }
})
.for_each(IdentityIndexOf::<T>::remove); Ok(().into())
}
/// Link an account to an identity.
///
/// This function links a specified account to an identity, requiring both the account and the
/// identity to sign the operation.
///
/// - `origin` - The origin of the call, which must have an associated identity index.
/// - `account_id` - The account ID to link, which must sign the payload.
/// - `payload_sig` - The signature with the linked identity.
// can be used for quota system
// re-uses new owner key payload for simplicity
// with other custom prefix
#[pallet::call_index(8)]
#[pallet::weight(T::WeightInfo::link_account())]
pub fn link_account(
origin: OriginFor<T>,
account_id: T::AccountId,
payload_sig: T::Signature,
) -> DispatchResultWithPostInfo {
// verif
let who = ensure_signed(origin)?;
let idty_index =
IdentityIndexOf::<T>::get(&who).ok_or(Error::<T>::IdtyIndexNotFound)?;
let genesis_hash = frame_system::Pallet::<T>::block_hash(BlockNumberFor::<T>::zero());
let payload = IdtyIndexAccountIdPayload {
genesis_hash: &genesis_hash,
idty_index,
old_owner_key: &account_id,
};
ensure!(
(LINK_IDTY_PAYLOAD_PREFIX, payload)
.using_encoded(|bytes| payload_sig.verify(bytes, &account_id)),
Error::<T>::InvalidSignature
);
// apply
T::AccountLinker::link_identity(&account_id, idty_index)?;
Ok(().into()) Ok(().into())
} }
...@@ -442,78 +774,143 @@ pub mod pallet { ...@@ -442,78 +774,143 @@ pub mod pallet {
#[pallet::error] #[pallet::error]
pub enum Error<T> { pub enum Error<T> {
/// Genesis hash does not match /// Identity already confirmed.
InvalidGenesisHash,
/// Revocation payload signature is invalid
InvalidRevocationProof,
/// Creator not allowed to create identities
CreatorNotAllowedToCreateIdty,
/// Identity already confirmed
IdtyAlreadyConfirmed, IdtyAlreadyConfirmed,
/// Identity already created /// Identity already created.
IdtyAlreadyCreated, IdtyAlreadyCreated,
/// Identity already validated /// Identity index not found.
IdtyAlreadyValidated,
/// You are not allowed to create a new identity now
IdtyCreationNotAllowed,
/// Identity index not found
IdtyIndexNotFound, IdtyIndexNotFound,
/// Identity name already exist /// Identity name already exists.
IdtyNameAlreadyExist, IdtyNameAlreadyExist,
/// Idty name invalid /// Invalid identity name.
IdtyNameInvalid, IdtyNameInvalid,
/// Identity not confirmed by owner /// Identity not found.
IdtyNotConfirmedByOwner,
/// Identity not found
IdtyNotFound, IdtyNotFound,
/// Idty not member /// Invalid payload signature.
IdtyNotMember, InvalidSignature,
/// Identity not validated /// Key used as validator.
IdtyNotValidated, OwnerKeyUsedAsValidator,
/// Identity not yet renewable /// Key in bound period.
IdtyNotYetRenewable, OwnerKeyInBound,
/// Not allowed to confirm identity /// Invalid revocation key.
NotAllowedToConfirmIdty, InvalidRevocationKey,
/// Not allowed to validate identity /// Issuer is not member and can not perform this action.
NotAllowedToValidateIdty, IssuerNotMember,
/// Not same identity name /// Identity creation period is not respected.
NotSameIdtyName,
/// Right already added
RightAlreadyAdded,
/// Right not exist
RightNotExist,
/// Not respect IdtyCreationPeriod
NotRespectIdtyCreationPeriod, NotRespectIdtyCreationPeriod,
/// Owner account not exist /// Owner key already changed recently.
OwnerAccountNotExist, OwnerKeyAlreadyRecentlyChanged,
/// Owner key already used.
OwnerKeyAlreadyUsed,
/// Reverting to an old key is prohibited.
ProhibitedToRevertToAnOldKey,
/// Already revoked.
AlreadyRevoked,
/// Can not revoke identity that never was member.
CanNotRevokeUnconfirmed,
/// Can not revoke identity that never was member.
CanNotRevokeUnvalidated,
/// Cannot link to an inexisting account.
AccountNotExist,
/// Insufficient balance to create an identity.
InsufficientBalance,
/// Legacy revocation document format is invalid
InvalidLegacyRevocationFormat,
} }
// PUBLIC FUNCTIONS // // INTERNAL FUNCTIONS //
impl<T: Config> Pallet<T> { impl<T: Config> Pallet<T> {
/// Get the number of identities.
pub fn identities_count() -> u32 { pub fn identities_count() -> u32 {
Identities::<T>::count() Identities::<T>::count()
} }
/// Handle the addition of membership to an identity.
///
/// This function is called when an identity transitions to a member status. It updates
/// the identity's status, unschedules any pending identity change actions, and resets
/// the identity's next scheduled action to zero.
pub fn membership_added(idty_index: T::IdtyIndex) {
if let Some(mut idty_value) = Identities::<T>::get(idty_index) {
Self::unschedule_identity_change(idty_index, idty_value.next_scheduled);
idty_value.next_scheduled = BlockNumberFor::<T>::zero();
if idty_value.status == IdtyStatus::Unvalidated {
// only submit event first time, after that, only membership events are relevant
Self::deposit_event(Event::IdtyValidated { idty_index });
};
idty_value.status = IdtyStatus::Member;
<Identities<T>>::insert(idty_index, idty_value);
}
// else should not happen
} }
// INTERNAL FUNCTIONS // /// Handle the removal of membership from an identity.
///
/// This function is called when membership is revoked from an identity. It checks
/// if the identity is currently a member, and if so, updates its status to `NotMember`.
/// If the identity is already revoked, this function does nothing.
pub fn membership_removed(idty_index: T::IdtyIndex) -> Weight {
if let Some(idty_value) = Identities::<T>::get(idty_index) {
if idty_value.status == IdtyStatus::Member {
Self::update_identity_status(
idty_index,
idty_value,
IdtyStatus::NotMember,
T::AutorevocationPeriod::get(),
);
}
}
T::WeightInfo::membership_removed()
// else should not happen
}
impl<T: Config> Pallet<T> { /// Perform the removal of an identity.
pub(super) fn do_remove_identity( ///
idty_index: T::IdtyIndex, /// This function acts as a garbage collector for identities. It should not be called
maybe_owner_key: Option<&T::AccountId>, /// while the identity is still a member; otherwise, there will still be a membership
) -> Weight { /// in storage, but no more identity.
if let Some(idty_val) = Identities::<T>::take(idty_index) { pub fn do_remove_identity(idty_index: T::IdtyIndex, reason: RemovalReason) -> Weight {
T::RemoveIdentityConsumers::remove_idty_consumers(idty_index); if let Some(idty_value) = Identities::<T>::get(idty_index) {
if let Some(owner_key) = maybe_owner_key { // this line allows the owner key to be used after that
IdentityIndexOf::<T>::remove(owner_key); IdentityIndexOf::<T>::remove(&idty_value.owner_key);
// Identity should be removed after the consumers of the identity
Identities::<T>::remove(idty_index);
frame_system::Pallet::<T>::dec_sufficients(&idty_value.owner_key);
if let Some((old_owner_key, _last_change)) = idty_value.old_owner_key {
frame_system::Pallet::<T>::dec_sufficients(&old_owner_key);
}
Self::deposit_event(Event::IdtyRemoved { idty_index, reason });
let weight = T::OnRemoveIdty::on_removed(&idty_index);
return weight.saturating_add(
T::WeightInfo::do_remove_identity()
.saturating_sub(T::WeightInfo::do_remove_identity_handler()),
);
}
T::WeightInfo::do_remove_identity_noop()
} }
frame_system::Pallet::<T>::dec_sufficients(&idty_val.owner_key);
Self::deposit_event(Event::IdtyRemoved { idty_index }); /// Revoke an identity.
T::OnIdtyChange::on_idty_change(idty_index, IdtyEvent::Removed); ///
/// This function revokes an identity, updating its status to `Revoked` and scheduling
/// it for removal after the specified deletion period.
pub fn do_revoke_identity(idty_index: T::IdtyIndex, reason: RevocationReason) -> Weight {
if let Some(idty_value) = Identities::<T>::get(idty_index) {
Self::update_identity_status(
idty_index,
idty_value,
IdtyStatus::Revoked,
T::DeletionPeriod::get(),
);
Self::deposit_event(Event::IdtyRevoked { idty_index, reason });
T::OnRemoveIdty::on_revoked(&idty_index);
return T::WeightInfo::do_revoke_identity();
} }
0 T::WeightInfo::do_revoke_identity_noop()
} }
/// incremental counter for identity index
fn get_next_idty_index() -> T::IdtyIndex { fn get_next_idty_index() -> T::IdtyIndex {
if let Ok(next_index) = <NextIdtyIndex<T>>::try_get() { if let Ok(next_index) = <NextIdtyIndex<T>>::try_get() {
<NextIdtyIndex<T>>::put(next_index.saturating_add(T::IdtyIndex::one())); <NextIdtyIndex<T>>::put(next_index.saturating_add(T::IdtyIndex::one()));
...@@ -523,25 +920,203 @@ pub mod pallet { ...@@ -523,25 +920,203 @@ pub mod pallet {
T::IdtyIndex::one() T::IdtyIndex::one()
} }
} }
fn prune_identities(block_number: T::BlockNumber) -> Weight {
let mut total_weight: Weight = 0;
for (idty_index, idty_status) in IdentitiesRemovableOn::<T>::take(block_number) { /// Prune identities planned for removal at the given block number.
pub fn prune_identities(block_number: BlockNumberFor<T>) -> Weight {
let mut total_weight = Weight::zero();
for idty_index in IdentityChangeSchedule::<T>::take(block_number) {
if let Ok(idty_val) = <Identities<T>>::try_get(idty_index) { if let Ok(idty_val) = <Identities<T>>::try_get(idty_index) {
if idty_val.removable_on == block_number && idty_val.status == idty_status { if idty_val.next_scheduled == block_number {
total_weight += match idty_val.status {
Self::do_remove_identity(idty_index, Some(&idty_val.owner_key)) IdtyStatus::Unconfirmed => {
total_weight =
total_weight.saturating_add(Self::do_remove_identity(
idty_index,
RemovalReason::Unconfirmed,
));
}
IdtyStatus::Unvalidated => {
total_weight =
total_weight.saturating_add(Self::do_remove_identity(
idty_index,
RemovalReason::Unvalidated,
));
}
IdtyStatus::Revoked => {
total_weight = total_weight.saturating_add(
Self::do_remove_identity(idty_index, RemovalReason::Revoked),
);
}
IdtyStatus::NotMember => {
total_weight = total_weight.saturating_add(
Self::do_revoke_identity(idty_index, RevocationReason::Expired),
);
}
IdtyStatus::Member => { // do not touch identities of member accounts
// this should not happen
} }
} }
} else {
total_weight = total_weight.saturating_add(
T::WeightInfo::prune_identities_err()
.saturating_sub(T::WeightInfo::prune_identities_none()),
);
}
} else {
total_weight = total_weight.saturating_add(
T::WeightInfo::prune_identities_none()
.saturating_sub(T::WeightInfo::prune_identities_noop()),
);
}
}
total_weight.saturating_add(T::WeightInfo::prune_identities_noop())
} }
total_weight /// Change the identity status and reschedule the next action accordingly.
fn update_identity_status(
idty_index: T::IdtyIndex,
mut idty_value: IdtyValue<BlockNumberFor<T>, T::AccountId, T::IdtyData>,
new_status: IdtyStatus,
period: BlockNumberFor<T>,
) {
Self::unschedule_identity_change(idty_index, idty_value.next_scheduled);
idty_value.next_scheduled = Self::schedule_identity_change(idty_index, period);
idty_value.status = new_status;
<Identities<T>>::insert(idty_index, idty_value);
} }
/// Unschedules the change related to an identity.
fn unschedule_identity_change(idty_id: T::IdtyIndex, block_number: BlockNumberFor<T>) {
let mut scheduled = IdentityChangeSchedule::<T>::get(block_number);
if let Some(pos) = scheduled.iter().position(|x| *x == idty_id) {
scheduled.swap_remove(pos);
IdentityChangeSchedule::<T>::set(block_number, scheduled);
} }
} }
impl<T: Config> sp_runtime::traits::Convert<T::AccountId, Option<T::IdtyIndex>> for Pallet<T> { /// Schedule an identity change after a specified period.
fn convert(account_id: T::AccountId) -> Option<T::IdtyIndex> { fn schedule_identity_change(
Self::identity_index_of(account_id) idty_id: T::IdtyIndex,
period: BlockNumberFor<T>,
) -> BlockNumberFor<T> {
let block_number = frame_system::pallet::Pallet::<T>::block_number();
let next_scheduled = block_number + period;
IdentityChangeSchedule::<T>::append(next_scheduled, idty_id);
next_scheduled
}
/// Check if creating an identity is allowed.
// first internal checks
// then other pallet checks trough trait
fn check_create_identity(
issuer_key: &T::AccountId,
receiver_key: &T::AccountId,
block_number: BlockNumberFor<T>,
) -> Result<T::IdtyIndex, DispatchError> {
// first get issuer details
let creator_index = IdentityIndexOf::<T>::try_get(issuer_key)
.map_err(|_| Error::<T>::IdtyIndexNotFound)?;
let creator_idty_val =
Identities::<T>::try_get(creator_index).map_err(|_| Error::<T>::IdtyNotFound)?;
// --- some checks can be done internally
// 1. issuer is member
ensure!(
creator_idty_val.status == IdtyStatus::Member,
Error::<T>::IssuerNotMember
);
// 2. issuer respects identity creation period
ensure!(
creator_idty_val.next_creatable_identity_on <= block_number,
Error::<T>::NotRespectIdtyCreationPeriod
);
// 3. receiver key is not already used by another identity
ensure!(
!IdentityIndexOf::<T>::contains_key(receiver_key),
Error::<T>::IdtyAlreadyCreated
);
// --- other checks depend on other pallets
// run checks for identity creation
T::CheckIdtyCallAllowed::check_create_identity(creator_index)?;
T::CheckAccountWorthiness::check_account_worthiness(receiver_key)?;
Ok(creator_index)
}
}
}
// implement getting owner key of identity index
impl<T: Config> sp_runtime::traits::Convert<T::IdtyIndex, Option<T::AccountId>> for Pallet<T> {
fn convert(idty_index: T::IdtyIndex) -> Option<T::AccountId> {
Identities::<T>::get(idty_index).map(|idty_val| idty_val.owner_key)
}
}
// implement Idty trait
impl<T: Config> duniter_primitives::Idty<T::IdtyIndex, T::AccountId> for Pallet<T> {
fn idty_index(owner_key: T::AccountId) -> Option<T::IdtyIndex> {
IdentityIndexOf::<T>::get(owner_key)
}
fn owner_key(idty_index: T::IdtyIndex) -> Option<T::AccountId> {
Identities::<T>::get(idty_index).map(|idty_val| idty_val.owner_key)
}
}
// implement StoredMap trait for this pallet
impl<T> frame_support::traits::StoredMap<T::AccountId, T::IdtyData> for Pallet<T>
where
T: Config,
{
/// Get identity data for an account.
fn get(key: &T::AccountId) -> T::IdtyData {
if let Some(idty_index) = Self::identity_index_of(key) {
if let Some(idty_val) = Identities::<T>::get(idty_index) {
idty_val.data
} else {
Default::default()
}
} else {
Default::default()
}
}
/// Mutate an account in function of its data.
fn try_mutate_exists<R, E: From<sp_runtime::DispatchError>>(
key: &T::AccountId,
f: impl FnOnce(&mut Option<T::IdtyData>) -> Result<R, E>,
) -> Result<R, E> {
let maybe_idty_index = Self::identity_index_of(key);
let mut maybe_idty_data = if let Some(idty_index) = maybe_idty_index {
if let Some(idty_val) = Identities::<T>::get(idty_index) {
Some(idty_val.data)
} else {
None
}
} else {
None
};
let result = f(&mut maybe_idty_data)?;
if let Some(idty_index) = maybe_idty_index {
Identities::<T>::mutate_exists(idty_index, |idty_val_opt| {
if let Some(ref mut idty_val) = idty_val_opt {
idty_val.data = maybe_idty_data.unwrap_or_default();
} else if maybe_idty_data.is_some() {
return Err(sp_runtime::DispatchError::Other(
"Tring to set IdtyData for a non-existing identity!",
));
}
Ok(())
})?;
} else if maybe_idty_data.is_some() {
return Err(sp_runtime::DispatchError::Other(
"Tring to set IdtyData for a non-existing identity!",
)
.into());
}
Ok(result)
} }
} }
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use super::*; use super::*;
use crate::{self as pallet_identity}; use crate::{self as pallet_identity};
use frame_support::{ use frame_support::{
parameter_types, derive_impl, parameter_types,
traits::{Everything, GenesisBuild, OnFinalize, OnInitialize}, traits::{Everything, OnFinalize, OnInitialize},
}; };
use frame_system as system; use frame_system as system;
use sp_core::H256; use sp_core::{Pair, H256};
use sp_keystore::{testing::MemoryKeystore, KeystoreExt};
use sp_runtime::{ use sp_runtime::{
testing::{Header, TestSignature, UintAuthorityId}, traits::{BlakeTwo256, IdentityLookup},
traits::{BlakeTwo256, IdentityLookup, IsMember}, BuildStorage, MultiSignature, MultiSigner,
}; };
use sp_state_machine::BasicExternalities;
use std::sync::Arc;
type AccountId = u64;
type Block = frame_system::mocking::MockBlock<Test>; type Block = frame_system::mocking::MockBlock<Test>;
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Test>; pub type Signature = MultiSignature;
pub type AccountPublic = <Signature as Verify>::Signer;
pub type AccountId = <AccountPublic as IdentifyAccount>::AccountId;
fn account(id: u8) -> AccountId {
let pair = sp_core::sr25519::Pair::from_seed(&[id; 32]);
MultiSigner::Sr25519(pair.public()).into_account()
}
// 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,
NodeBlock = Block,
UncheckedExtrinsic = UncheckedExtrinsic,
{ {
System: frame_system::{Pallet, Call, Config, Storage, Event<T>}, System: frame_system,
Identity: pallet_identity::{Pallet, Call, Storage, Config<T>, Event<T>}, Identity: pallet_identity,
} }
); );
...@@ -48,39 +54,31 @@ parameter_types! { ...@@ -48,39 +54,31 @@ 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 Origin = Origin;
type Call = Call;
type Index = u64;
type BlockNumber = u64;
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 Event = Event; 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>;
} }
parameter_types! { parameter_types! {
pub const ChangeOwnerKeyPeriod: u64 = 10;
pub const ConfirmPeriod: u64 = 2; 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 const IdtyCreationPeriod: u64 = 3;
pub const MaxInactivityPeriod: u64 = 5;
pub const RenewablePeriod: u64 = 3;
pub const ValidationPeriod: u64 = 2;
} }
pub struct IdtyNameValidatorTestImpl; pub struct IdtyNameValidatorTestImpl;
...@@ -90,43 +88,47 @@ impl pallet_identity::traits::IdtyNameValidator for IdtyNameValidatorTestImpl { ...@@ -90,43 +88,47 @@ impl pallet_identity::traits::IdtyNameValidator for IdtyNameValidatorTestImpl {
} }
} }
pub struct IsMemberTestImpl;
impl IsMember<u64> for IsMemberTestImpl {
fn is_member(_: &u64) -> bool {
true
}
}
impl pallet_identity::Config for Test { impl pallet_identity::Config for Test {
type AccountId32 = AccountId;
type AccountLinker = ();
type AutorevocationPeriod = AutorevocationPeriod;
type ChangeOwnerKeyPeriod = ChangeOwnerKeyPeriod;
type CheckAccountWorthiness = ();
type CheckIdtyCallAllowed = ();
type ConfirmPeriod = ConfirmPeriod; type ConfirmPeriod = ConfirmPeriod;
type Event = Event; type DeletionPeriod = DeletionPeriod;
type EnsureIdtyCallAllowed = ();
type IdtyCreationPeriod = IdtyCreationPeriod; type IdtyCreationPeriod = IdtyCreationPeriod;
type IdtyNameValidator = IdtyNameValidatorTestImpl; type IdtyData = ();
type IdtyIndex = u64; type IdtyIndex = u64;
type IdtyValidationOrigin = system::EnsureRoot<AccountId>; type IdtyNameValidator = IdtyNameValidatorTestImpl;
type IsMember = IsMemberTestImpl; type OnKeyChange = ();
type OnIdtyChange = (); type OnNewIdty = ();
type RemoveIdentityConsumers = (); type OnRemoveIdty = ();
type RevocationSigner = UintAuthorityId; type RuntimeEvent = RuntimeEvent;
type RevocationSignature = TestSignature; type Signature = Signature;
type Signer = AccountPublic;
type ValidationPeriod = ValidationPeriod;
type WeightInfo = ();
} }
// Build genesis storage according to the mock runtime. // Build genesis storage according to the mock runtime.
pub fn new_test_ext(gen_conf: pallet_identity::GenesisConfig<Test>) -> sp_io::TestExternalities { pub fn new_test_ext(gen_conf: pallet_identity::GenesisConfig<Test>) -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::default() let mut t = frame_system::GenesisConfig::<Test>::default()
.build_storage::<Test>() .build_storage()
.unwrap(); .unwrap();
gen_conf.assimilate_storage(&mut t).unwrap(); gen_conf.assimilate_storage(&mut t).unwrap();
frame_support::BasicExternalities::execute_with_storage(&mut t, || { BasicExternalities::execute_with_storage(&mut t, || {
// Some dedicated test account frame_system::Pallet::<Test>::inc_providers(&account(2));
frame_system::Pallet::<Test>::inc_providers(&2); frame_system::Pallet::<Test>::inc_providers(&account(3));
frame_system::Pallet::<Test>::inc_providers(&3);
}); });
sp_io::TestExternalities::new(t) let keystore = MemoryKeystore::new();
let mut ext = sp_io::TestExternalities::new(t);
ext.register_extension(KeystoreExt(Arc::new(keystore)));
ext.execute_with(|| System::set_block_number(1));
ext
} }
pub fn run_to_block(n: u64) { pub fn run_to_block(n: u64) {
......
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use crate::mock::*; use core::str::FromStr;
use crate::{Error, GenesisIdty, IdtyName, IdtyValue, RevocationPayload};
use crate::{mock::*, *};
use codec::Encode; use codec::Encode;
//use frame_support::assert_err; use frame_support::{assert_noop, assert_ok, dispatch::DispatchResultWithPostInfo};
use frame_support::assert_ok; use sp_core::{sr25519::Pair as KeyPair, Pair};
use frame_system::{EventRecord, Phase}; use sp_runtime::{AccountId32, MultiSignature, MultiSigner};
use sp_runtime::testing::TestSignature;
type IdtyVal = IdtyValue<u64, AccountId, ()>;
// Store the account id and the key pair to sign payload
struct Account {
id: AccountId,
signer: KeyPair,
}
type IdtyVal = IdtyValue<u64, u64>; // Create an Account given a u8
fn account(id: u8) -> Account {
let pair = sp_core::sr25519::Pair::from_seed(&[id; 32]);
Account {
id: MultiSigner::Sr25519(pair.public()).into_account(),
signer: pair,
}
}
// Sign a payload using a key pair
fn test_signature(key_pair: KeyPair, payload: Vec<u8>) -> MultiSignature {
MultiSignature::Sr25519(key_pair.sign(&payload))
}
fn alice() -> GenesisIdty<Test> { fn alice() -> GenesisIdty<Test> {
GenesisIdty { GenesisIdty {
index: 1, index: 1,
name: IdtyName::from("Alice"), name: IdtyName::from("Alice"),
value: IdtyVal { value: IdtyVal {
data: (),
next_creatable_identity_on: 0,
old_owner_key: None,
owner_key: account(1).id,
next_scheduled: 0,
status: crate::IdtyStatus::Member,
},
}
}
fn bob() -> GenesisIdty<Test> {
GenesisIdty {
index: 2,
name: IdtyName::from("Bob"),
value: IdtyVal {
data: (),
next_creatable_identity_on: 0,
old_owner_key: None,
owner_key: account(2).id,
next_scheduled: 0,
status: crate::IdtyStatus::Member,
},
}
}
fn inactive_bob() -> GenesisIdty<Test> {
GenesisIdty {
index: 2,
name: IdtyName::from("Bob"),
value: IdtyVal {
data: (),
next_creatable_identity_on: 0,
old_owner_key: None,
owner_key: account(2).id,
next_scheduled: 2,
status: crate::IdtyStatus::NotMember,
},
}
}
// From legacy credentials Charlie:Charlie
fn legacy_charlie() -> GenesisIdty<Test> {
GenesisIdty {
index: 102,
name: IdtyName::from("Charlie"),
value: IdtyVal {
data: (),
next_creatable_identity_on: 0, next_creatable_identity_on: 0,
owner_key: 1, old_owner_key: None,
removable_on: 0, owner_key: AccountId32::from_str("5H2nLXGku46iztpqdRwsCAiP6vHZbShhKmSV4yyufQgEUFvV")
status: crate::IdtyStatus::Validated, .unwrap(),
next_scheduled: 0,
status: crate::IdtyStatus::Member,
}, },
} }
} }
...@@ -47,6 +116,19 @@ fn test_no_identity() { ...@@ -47,6 +116,19 @@ fn test_no_identity() {
}); });
} }
/// test that next identity index is correctly initialized and incremented
#[test]
fn test_identity_index() {
new_test_ext(IdentityConfig {
identities: vec![alice(), bob()],
})
.execute_with(|| {
assert_eq!(Identity::identities_count(), 2);
// assert_eq!(NextIdtyIndex, 3); // TODO check how to test that
// ... create identity and check it was incremented
});
}
#[test] #[test]
fn test_create_identity_ok() { fn test_create_identity_ok() {
new_test_ext(IdentityConfig { new_test_ext(IdentityConfig {
...@@ -56,21 +138,16 @@ fn test_create_identity_ok() { ...@@ -56,21 +138,16 @@ fn test_create_identity_ok() {
// We need to initialize at least one block before any call // We need to initialize at least one block before any call
run_to_block(1); run_to_block(1);
// Alice should be able te create an identity // Alice should be able to create an identity
assert_ok!(Identity::create_identity(Origin::signed(1), 2)); assert_ok!(Identity::create_identity(
let events = System::events(); RuntimeOrigin::signed(account(1).id),
assert_eq!(events.len(), 1); account(2).id
assert_eq!( ));
events[0],
EventRecord { System::assert_has_event(RuntimeEvent::Identity(crate::Event::IdtyCreated {
phase: Phase::Initialization,
event: Event::Identity(crate::Event::IdtyCreated {
idty_index: 2, idty_index: 2,
owner_key: 2, owner_key: account(2).id,
}), }));
topics: vec![],
}
);
}); });
} }
...@@ -83,38 +160,31 @@ fn test_create_identity_but_not_confirm_it() { ...@@ -83,38 +160,31 @@ fn test_create_identity_but_not_confirm_it() {
// We need to initialize at least one block before any call // We need to initialize at least one block before any call
run_to_block(1); run_to_block(1);
// Alice should be able te create an identity // Alice should be able to create an identity
assert_ok!(Identity::create_identity(Origin::signed(1), 2)); assert_ok!(Identity::create_identity(
RuntimeOrigin::signed(account(1).id),
account(2).id
));
// The identity shoud expire in blocs #3 // The identity should expire in blocs #3
run_to_block(3); run_to_block(3);
let events = System::events();
assert_eq!(events.len(), 1); System::assert_has_event(RuntimeEvent::Identity(crate::Event::IdtyRemoved {
assert_eq!( idty_index: 2,
events[0], reason: RemovalReason::Unconfirmed,
EventRecord { }));
phase: Phase::Initialization,
event: Event::Identity(crate::Event::IdtyRemoved { idty_index: 2 }),
topics: vec![],
}
);
// We shoud be able to recreate the identity // We shoud be able to recreate the identity
run_to_block(4); run_to_block(4);
assert_ok!(Identity::create_identity(Origin::signed(1), 2)); assert_ok!(Identity::create_identity(
let events = System::events(); RuntimeOrigin::signed(account(1).id),
assert_eq!(events.len(), 1); account(2).id
assert_eq!( ));
events[0],
EventRecord { System::assert_has_event(RuntimeEvent::Identity(crate::Event::IdtyCreated {
phase: Phase::Initialization,
event: Event::Identity(crate::Event::IdtyCreated {
idty_index: 3, idty_index: 3,
owner_key: 2, owner_key: account(2).id,
}), }));
topics: vec![],
}
);
}); });
} }
...@@ -127,101 +197,596 @@ fn test_idty_creation_period() { ...@@ -127,101 +197,596 @@ fn test_idty_creation_period() {
// We need to initialize at least one block before any call // We need to initialize at least one block before any call
run_to_block(1); run_to_block(1);
// Alice should be able te create an identity // Alice should be able to create an identity
assert_ok!(Identity::create_identity(Origin::signed(1), 2)); assert_ok!(Identity::create_identity(
let events = System::events(); RuntimeOrigin::signed(account(1).id),
assert_eq!(events.len(), 1); account(2).id
assert_eq!( ));
events[0],
EventRecord { System::assert_has_event(RuntimeEvent::Identity(crate::Event::IdtyCreated {
phase: Phase::Initialization,
event: Event::Identity(crate::Event::IdtyCreated {
idty_index: 2, idty_index: 2,
owner_key: 2, owner_key: account(2).id,
}), }));
topics: vec![],
}
);
assert_eq!(Identity::identity(1).unwrap().next_creatable_identity_on, 4); assert_eq!(Identity::identity(1).unwrap().next_creatable_identity_on, 4);
// Alice cannot create a new identity before block #4 // Alice cannot create a new identity before block #4
run_to_block(2); run_to_block(2);
assert_eq!( assert_eq!(
Identity::create_identity(Origin::signed(1), 3), Identity::create_identity(RuntimeOrigin::signed(account(1).id), account(3).id),
Err(Error::<Test>::NotRespectIdtyCreationPeriod.into()) Err(Error::<Test>::NotRespectIdtyCreationPeriod.into())
); );
// Alice should be able te create a second identity after block #4 // Alice should be able to create a second identity after block #4
run_to_block(4); run_to_block(4);
assert_ok!(Identity::create_identity(Origin::signed(1), 3)); assert_ok!(Identity::create_identity(
let events = System::events(); RuntimeOrigin::signed(account(1).id),
assert_eq!(events.len(), 1); account(3).id
assert_eq!( ));
events[0],
EventRecord { System::assert_has_event(RuntimeEvent::Identity(crate::Event::IdtyCreated {
phase: Phase::Initialization,
event: Event::Identity(crate::Event::IdtyCreated {
idty_index: 3, idty_index: 3,
owner_key: 3, owner_key: account(3).id,
}), }));
topics: vec![], });
} }
//
#[test]
fn test_change_owner_key() {
new_test_ext(IdentityConfig {
identities: vec![alice(), bob()],
})
.execute_with(|| {
let genesis_hash = System::block_hash(0);
let old_owner_key = account(1).id;
let mut new_key_payload = IdtyIndexAccountIdPayload {
genesis_hash: &genesis_hash,
idty_index: 1u64,
old_owner_key: &old_owner_key,
};
// We need to initialize at least one block before any call
run_to_block(1);
// Verify genesis data
assert_eq!(System::sufficients(&account(1).id), 1);
assert_eq!(System::sufficients(&account(10).id), 0);
// Caller should have an associated identity
assert_noop!(
Identity::change_owner_key(
RuntimeOrigin::signed(account(42).id),
account(10).id,
test_signature(
account(10).signer,
(NEW_OWNER_KEY_PAYLOAD_PREFIX, new_key_payload.clone()).encode()
)
),
Error::<Test>::IdtyIndexNotFound
);
// Payload must be signed by the new key
assert_noop!(
Identity::change_owner_key(
RuntimeOrigin::signed(account(1).id),
account(10).id,
test_signature(
account(42).signer,
(NEW_OWNER_KEY_PAYLOAD_PREFIX, new_key_payload.clone()).encode()
)
),
Error::<Test>::InvalidSignature
);
// Payload must be prefixed
assert_noop!(
Identity::change_owner_key(
RuntimeOrigin::signed(account(1).id),
account(10).id,
test_signature(account(10).signer, new_key_payload.clone().encode())
),
Error::<Test>::InvalidSignature
);
// New owner key should not be used by another identity
assert_noop!(
Identity::change_owner_key(
RuntimeOrigin::signed(account(1).id),
account(2).id,
test_signature(
account(2).signer,
(NEW_OWNER_KEY_PAYLOAD_PREFIX, new_key_payload.clone()).encode()
)
),
Error::<Test>::OwnerKeyAlreadyUsed
);
// Alice can change her owner key
assert_ok!(Identity::change_owner_key(
RuntimeOrigin::signed(account(1).id),
account(10).id,
test_signature(
account(10).signer,
(NEW_OWNER_KEY_PAYLOAD_PREFIX, new_key_payload.clone()).encode()
)
));
assert_eq!(
Identity::identity(1),
Some(IdtyVal {
data: (),
next_creatable_identity_on: 0,
old_owner_key: Some((account(1).id, 1)),
owner_key: account(10).id,
next_scheduled: 0,
status: crate::IdtyStatus::Member,
})
);
// Alice still sufficient
assert_eq!(System::sufficients(&account(1).id), 1);
// New owner key should become a sufficient account
assert_eq!(System::sufficients(&account(10).id), 1);
run_to_block(2);
//
// Alice can't re-change her owner key too early
let old = account(10).id;
new_key_payload.old_owner_key = &old;
assert_noop!(
Identity::change_owner_key(
RuntimeOrigin::signed(account(10).id),
account(100).id,
test_signature(
account(100).signer,
(NEW_OWNER_KEY_PAYLOAD_PREFIX, new_key_payload.clone()).encode()
)
),
Error::<Test>::OwnerKeyAlreadyRecentlyChanged
); );
// Alice can re-change her owner key after ChangeOwnerKeyPeriod blocs
run_to_block(2 + <Test as crate::Config>::ChangeOwnerKeyPeriod::get());
assert_ok!(Identity::change_owner_key(
RuntimeOrigin::signed(account(10).id),
account(100).id,
test_signature(
account(100).signer,
(NEW_OWNER_KEY_PAYLOAD_PREFIX, new_key_payload.clone()).encode()
)
));
// Old old owner key should not be sufficient anymore
assert_eq!(System::sufficients(&account(1).id), 0);
// Old owner key should still sufficient
assert_eq!(System::sufficients(&account(10).id), 1);
// New owner key should become a sufficient account
assert_eq!(System::sufficients(&account(100).id), 1);
// Revoke identity 1
assert_ok!(Identity::revoke_identity(
RuntimeOrigin::signed(account(42).id),
1,
account(100).id,
test_signature(
account(100).signer,
(
REVOCATION_PAYLOAD_PREFIX,
RevocationPayload {
idty_index: 1u64,
genesis_hash: System::block_hash(0),
}
)
.encode()
)
));
// Old owner key is still sufficient (identity is revoked but not removed)
assert_eq!(System::sufficients(&account(10).id), 1);
// Last owner key should still be sufficient (identity is revoked but not removed)
assert_eq!(System::sufficients(&account(100).id), 1);
}); });
} }
// test link identity (does nothing because of AccountLinker type)
#[test] #[test]
fn test_idty_revocation() { fn test_link_account() {
new_test_ext(IdentityConfig {
identities: vec![alice(), bob()],
})
.execute_with(|| {
let genesis_hash = System::block_hash(0);
let account_id = account(10).id;
let payload = IdtyIndexAccountIdPayload {
genesis_hash: &genesis_hash,
idty_index: 1u64,
old_owner_key: &account_id,
};
run_to_block(1);
// Caller should have an associated identity
assert_noop!(
Identity::link_account(
RuntimeOrigin::signed(account(42).id),
account(10).id,
test_signature(
account(10).signer,
(LINK_IDTY_PAYLOAD_PREFIX, payload.clone()).encode()
)
),
Error::<Test>::IdtyIndexNotFound
);
// Payload must be signed by the new key
assert_noop!(
Identity::link_account(
RuntimeOrigin::signed(account(1).id),
account(10).id,
test_signature(
account(42).signer,
(LINK_IDTY_PAYLOAD_PREFIX, payload.clone()).encode()
)
),
Error::<Test>::InvalidSignature
);
// Payload must be prefixed
assert_noop!(
Identity::link_account(
RuntimeOrigin::signed(account(1).id),
account(10).id,
test_signature(account(10).signer, payload.clone().encode())
),
Error::<Test>::InvalidSignature
);
// Alice can call link_account successfully
assert_ok!(Identity::link_account(
RuntimeOrigin::signed(account(1).id),
account(10).id,
test_signature(
account(10).signer,
(LINK_IDTY_PAYLOAD_PREFIX, payload.clone()).encode()
)
));
});
}
#[test]
fn test_idty_revocation_with_old_key() {
new_test_ext(IdentityConfig {
identities: vec![alice()],
})
.execute_with(|| {
let genesis_hash = System::block_hash(0);
let new_key_payload = IdtyIndexAccountIdPayload {
genesis_hash: &genesis_hash,
idty_index: 1u64,
old_owner_key: &account(1).id,
};
let revocation_payload = RevocationPayload {
idty_index: 1u64,
genesis_hash,
};
// We need to initialize at least one block before any call
run_to_block(1);
// Change alice owner key
assert_ok!(Identity::change_owner_key(
RuntimeOrigin::signed(account(1).id),
account(10).id,
test_signature(
account(10).signer,
(NEW_OWNER_KEY_PAYLOAD_PREFIX, new_key_payload).encode()
)
));
assert!(Identity::identity(1).is_some());
let idty_val = Identity::identity(1).unwrap();
assert_eq!(idty_val.owner_key, account(10).id);
assert_eq!(idty_val.old_owner_key, Some((account(1).id, 1)));
// We should be able to revoke Alice identity with old key
run_to_block(2);
assert_ok!(Identity::revoke_identity(
RuntimeOrigin::signed(account(42).id),
1,
account(1).id,
test_signature(
account(1).signer,
(REVOCATION_PAYLOAD_PREFIX, revocation_payload).encode()
)
));
//run_to_block(2 + <Test as crate::Config>::ChangeOwnerKeyPeriod::get());
});
}
#[test]
fn test_idty_revocation_with_old_key_after_old_key_expiration() {
new_test_ext(IdentityConfig { new_test_ext(IdentityConfig {
identities: vec![alice()], identities: vec![alice()],
}) })
.execute_with(|| { .execute_with(|| {
let genesis_hash = System::block_hash(0);
let new_key_payload = IdtyIndexAccountIdPayload {
genesis_hash: &genesis_hash,
idty_index: 1u64,
old_owner_key: &account(1).id,
};
let revocation_payload = RevocationPayload {
idty_index: 1u64,
genesis_hash,
};
// We need to initialize at least one block before any call // We need to initialize at least one block before any call
run_to_block(1); run_to_block(1);
// Change alice owner key
assert_ok!(Identity::change_owner_key(
RuntimeOrigin::signed(account(1).id),
account(10).id,
test_signature(
account(10).signer,
(NEW_OWNER_KEY_PAYLOAD_PREFIX, new_key_payload).encode()
)
));
assert!(Identity::identity(1).is_some());
let idty_val = Identity::identity(1).unwrap();
assert_eq!(idty_val.owner_key, account(10).id);
assert_eq!(idty_val.old_owner_key, Some((account(1).id, 1)));
// We should not be able to revoke Alice identity with old key after ChangeOwnerKeyPeriod
run_to_block(2 + <Test as crate::Config>::ChangeOwnerKeyPeriod::get());
assert_noop!(
Identity::revoke_identity(
RuntimeOrigin::signed(account(42).id),
1,
account(1).id,
test_signature(
account(1).signer,
(REVOCATION_PAYLOAD_PREFIX, revocation_payload).encode()
)
),
Error::<Test>::InvalidRevocationKey
);
});
}
#[test]
fn test_idty_revocation() {
new_test_ext(IdentityConfig {
identities: vec![alice()],
})
.execute_with(|| {
let revocation_payload = RevocationPayload { let revocation_payload = RevocationPayload {
owner_key: 1, idty_index: 1u64,
genesis_hash: System::block_hash(0), genesis_hash: System::block_hash(0),
}; };
// We need to initialize at least one block before any call
run_to_block(1);
// Payload must be signed by the right identity // Payload must be signed by the right identity
assert_eq!( assert_eq!(
Identity::revoke_identity( Identity::revoke_identity(
Origin::signed(1), RuntimeOrigin::signed(account(1).id),
revocation_payload.clone(), 1,
TestSignature(42, revocation_payload.encode()) account(42).id,
test_signature(
account(42).signer,
(REVOCATION_PAYLOAD_PREFIX, revocation_payload).encode()
)
), ),
Err(Error::<Test>::InvalidRevocationProof.into()) Err(Error::<Test>::InvalidRevocationKey.into())
);
// Payload must be prefixed
assert_eq!(
Identity::revoke_identity(
RuntimeOrigin::signed(account(1).id),
1,
account(1).id,
test_signature(account(1).signer, revocation_payload.encode())
),
Err(Error::<Test>::InvalidSignature.into())
); );
// Anyone can submit a revocation payload // Anyone can submit a revocation payload
assert_ok!(Identity::revoke_identity( assert_ok!(Identity::revoke_identity(
Origin::signed(42), RuntimeOrigin::signed(account(42).id),
revocation_payload.clone(), 1,
TestSignature(1, revocation_payload.encode()) account(1).id,
test_signature(
account(1).signer,
(REVOCATION_PAYLOAD_PREFIX, revocation_payload).encode()
)
)); ));
let events = System::events(); // // account is not killed anymore for revoked identity
assert_eq!(events.len(), 1); // System::assert_has_event(RuntimeEvent::System(frame_system::Event::KilledAccount {
assert_eq!( // account: account(1).id,
events[0], // }));
EventRecord { // // identity is not removed immediately after revocation
phase: Phase::Initialization, // System::assert_has_event(RuntimeEvent::Identity(crate::Event::IdtyRemoved {
event: Event::Identity(crate::Event::IdtyRemoved { idty_index: 1 }), // idty_index: 1,
topics: vec![], // reason: RemovalReason::Revoked,
} // }));
); System::assert_has_event(RuntimeEvent::Identity(crate::Event::IdtyRevoked {
idty_index: 1,
reason: RevocationReason::User,
}));
run_to_block(2); run_to_block(2);
// The identity no longer exists // The identity can not be revoked multiple times
assert_eq!( assert_eq!(
Identity::revoke_identity( Identity::revoke_identity(
Origin::signed(1), RuntimeOrigin::signed(account(1).id),
revocation_payload.clone(), 1,
TestSignature(1, revocation_payload.encode()) account(1).id,
test_signature(
account(1).signer,
(REVOCATION_PAYLOAD_PREFIX, revocation_payload).encode()
)
),
Err(Error::<Test>::AlreadyRevoked.into())
);
});
}
// # Generate dummy revocation documents in Python.
// # The seed derivation is not the same as sr25519.from_seed so this doesn't work yet.
// ```python
// import duniterpy, substrateinterface
// s = duniterpy.key.SigningKey.from_credentials("Charlie", "Charlie")
// block = duniterpy.documents.BlockID(42, "A"*64)
// idty = duniterpy.documents.Identity(s.pubkey, "Charlie", block, s)
// r = duniterpy.documents.Revocation(idty, s)
// print("SS58 address:", substrateinterface.base.ss58_encode(s.vk))
// print(r.signed_raw())
// ```
#[test]
fn test_idty_revocation_legacy() {
new_test_ext(IdentityConfig {
identities: vec![alice(), legacy_charlie()],
})
.execute_with(|| {
// We need to initialize at least one block before any call
run_to_block(1);
let valid_revocation_document = r"Version: 10
Type: Revocation
Currency: g1
Issuer: Fnf2xaxYdQpB4kU45DMLQ9Ey4bd6DtoebKJajRkLBUXm
IdtyUniqueID: Charlie
IdtyTimestamp: 42-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
IdtySignature: 7KUagcMiQw05rwbkBsRrnNqPRHu/Y5ukCLoAEpb/1tXAQsSNf2gRi1h5PWIGs9y/vHnFXvF5epKsOjA6X75vDg==
CfiG4xhcWS+/DgxY0xFIyOA9TVr4Im3XEXcCApNgXC+Ns9jy2yrNoC3NF8MCD63cZ8QTRfrr4Iv6n3leYCCcDQ==
";
let revocation_document_bad_username = r"Version: 10
Type: Revocation
Currency: g1
Issuer: Fnf2xaxYdQpB4kU45DMLQ9Ey4bd6DtoebKJajRkLBUXm
IdtyUniqueID: Alice
IdtyTimestamp: 42-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
IdtySignature: dqO8nnYWZDDDzadMXpOVwehJXQ9wocE9QTKsVBa88rPONLhz12QA6Ytib2+VtPU+gnewO2mRVOvzdYKXemQPDg==
0q/Dy4jwLTjZGSOu4GWdkfW+SqXRAPHUwwvWQenqiuNuL2eEc0x2hM0MWhIOuSLy2ifNq6PfSH/dBrV5CgYIAw==
";
let revocation_document_bad_signer = r"Version: 10
Type: Revocation
Currency: g1
Issuer: 9cFLFh12MZSL8HHW9KvEDGTEEyKALGjEdHpNP8rqmmw3
IdtyUniqueID: Charlie
IdtyTimestamp: 42-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
IdtySignature: 8P2vjDHZf4tHaGpZYTuTXJ9Xe+3qQ0FAM6fypvwl2mYqLs1ZfE07gp4mqRpNY90rC9+CIIi7eHvv2uAlFVpfCQ==
iWOssQ1y2svWeUD4byjJx6n+/Xgf0pgMe1FDhnR9oN76Ri9B8SQfP+hFD3GCth7sZRD162sR83g3UvYpFHJLBQ==
";
assert_eq!(
Identity::revoke_identity_legacy(
RuntimeOrigin::signed(account(1).id),
revocation_document_bad_username.into()
),
Err(Error::<Test>::InvalidRevocationKey.into())
);
assert_eq!(
Identity::revoke_identity_legacy(
RuntimeOrigin::signed(account(1).id),
revocation_document_bad_signer.into()
), ),
Err(Error::<Test>::IdtyNotFound.into()) Err(Error::<Test>::InvalidRevocationKey.into())
); );
// Anyone can submit a revocation payload
assert_ok!(Identity::revoke_identity_legacy(
RuntimeOrigin::signed(account(42).id),
valid_revocation_document.into()
));
System::assert_has_event(RuntimeEvent::Identity(crate::Event::IdtyRevoked {
idty_index: 102,
reason: RevocationReason::User,
}));
});
}
#[test]
fn test_inactive_genesis_members() {
new_test_ext(IdentityConfig {
identities: vec![alice(), inactive_bob()],
})
.execute_with(|| {
let alice = alice();
let bob = inactive_bob();
assert!(pallet::Identities::<Test>::get(alice.index).is_some());
assert!(pallet::Identities::<Test>::get(bob.index).is_some());
assert!(pallet::IdentityIndexOf::<Test>::get(&alice.value.owner_key).is_some());
assert!(pallet::IdentityIndexOf::<Test>::get(&bob.value.owner_key).is_some());
run_to_block(2);
// alice identity remains untouched
assert!(pallet::Identities::<Test>::get(alice.index).is_some());
assert!(pallet::IdentityIndexOf::<Test>::get(&alice.value.owner_key).is_some());
// but bob identity has been revoked
assert!(pallet::Identities::<Test>::get(bob.index).is_some());
assert!(pallet::IdentityIndexOf::<Test>::get(&bob.value.owner_key).is_some());
System::assert_has_event(RuntimeEvent::Identity(crate::Event::IdtyRevoked {
idty_index: bob.index,
reason: RevocationReason::Expired,
}));
});
}
#[test]
fn test_revocation_of_genesis_member() {
let alice = alice();
let bob = inactive_bob();
new_test_ext(IdentityConfig {
identities: vec![alice.clone(), bob.clone()],
})
.execute_with(|| {
assert!(pallet::Identities::<Test>::get(alice.index).is_some());
assert!(pallet::Identities::<Test>::get(bob.index).is_some());
assert!(pallet::IdentityIndexOf::<Test>::get(&alice.value.owner_key).is_some());
assert!(pallet::IdentityIndexOf::<Test>::get(&bob.value.owner_key).is_some());
// Necessary to go to block#1 to allow extrinsics consumption
run_to_block(1);
assert_ok!(revoke_self_identity(bob.clone()));
// alice identity remains untouched
assert!(pallet::Identities::<Test>::get(alice.index).is_some());
assert!(pallet::IdentityIndexOf::<Test>::get(&alice.value.owner_key).is_some());
// but bob identity has been revoked
assert!(pallet::Identities::<Test>::get(bob.index).is_some());
assert!(pallet::IdentityIndexOf::<Test>::get(&bob.value.owner_key).is_some());
System::assert_has_event(RuntimeEvent::Identity(crate::Event::IdtyRevoked {
idty_index: bob.index,
reason: RevocationReason::User, // because called manually by revoke_self_identity
}));
}); });
} }
fn revoke_self_identity(idty: GenesisIdty<Test>) -> DispatchResultWithPostInfo {
Identity::revoke_identity(
RuntimeOrigin::signed(account(idty.index as u8).id),
idty.index,
account(idty.index as u8).id,
test_signature(
account(idty.index as u8).signer,
(
REVOCATION_PAYLOAD_PREFIX,
RevocationPayload {
idty_index: idty.index,
genesis_hash: System::block_hash(0),
},
)
.encode(),
),
)
}
// TODO add tests for all periods (confirmation, validation, autorevocation, deletion)
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
use crate::*; use crate::*;
use frame_support::pallet_prelude::*; use frame_support::pallet_prelude::*;
pub trait EnsureIdtyCallAllowed<T: Config> { /// Trait defining operations for checking if identity-related calls are allowed.
fn can_create_identity(creator: T::IdtyIndex) -> bool; pub trait CheckIdtyCallAllowed<T: Config> {
fn can_confirm_identity(idty_index: T::IdtyIndex, owner_key: T::AccountId) -> bool; /// Check if creating an identity is allowed.
fn can_validate_identity(idty_index: T::IdtyIndex) -> bool; fn check_create_identity(creator: T::IdtyIndex) -> Result<(), DispatchError>;
} }
impl<T: Config> EnsureIdtyCallAllowed<T> for () { impl<T: Config> CheckIdtyCallAllowed<T> for () {
fn can_create_identity(_: T::IdtyIndex) -> bool { fn check_create_identity(_creator: T::IdtyIndex) -> Result<(), DispatchError> {
true Ok(())
} }
fn can_confirm_identity(_: T::IdtyIndex, _: T::AccountId) -> bool {
true
} }
fn can_validate_identity(_: T::IdtyIndex) -> bool {
true /// Trait to check the worthiness of an account.
pub trait CheckAccountWorthiness<T: Config> {
/// Check the worthiness of an account.
fn check_account_worthiness(account: &T::AccountId) -> Result<(), DispatchError>;
/// Set an account as worthy. Only available for runtime benchmarks.
#[cfg(feature = "runtime-benchmarks")]
fn set_worthy(account: &T::AccountId);
} }
impl<T: Config> CheckAccountWorthiness<T> for () {
fn check_account_worthiness(_account: &T::AccountId) -> Result<(), DispatchError> {
Ok(())
} }
#[cfg(feature = "runtime-benchmarks")]
fn set_worthy(_account: &T::AccountId) {}
}
/// Trait defining operations for validating identity names.
pub trait IdtyNameValidator { pub trait IdtyNameValidator {
/// Validate an identity name.
fn validate(idty_name: &IdtyName) -> bool; fn validate(idty_name: &IdtyName) -> bool;
} }
pub trait OnIdtyChange<T: Config> { /// Trait defining behavior for handling new identities creation.
fn on_idty_change(idty_index: T::IdtyIndex, idty_event: IdtyEvent<T>) -> Weight; pub trait OnNewIdty<T: Config> {
/// Called when a new identity is created.
fn on_created(idty_index: &T::IdtyIndex, creator: &T::IdtyIndex);
} }
impl<T: Config> OnIdtyChange<T> for () {
fn on_idty_change(_idty_index: T::IdtyIndex, _idty_event: IdtyEvent<T>) -> Weight { /// Trait defining behavior for handling removed identities.
0 /// As the weight accounting can be complicated it should be done
/// at the handler level.
pub trait OnRemoveIdty<T: Config> {
/// Called when an identity is removed.
fn on_removed(idty_index: &T::IdtyIndex) -> Weight;
/// Called when an identity is revoked.
fn on_revoked(idty_index: &T::IdtyIndex) -> Weight;
} }
impl<T: Config> OnNewIdty<T> for () {
fn on_created(_idty_index: &T::IdtyIndex, _creator: &T::IdtyIndex) {}
}
impl<T: Config> OnRemoveIdty<T> for () {
fn on_removed(_idty_index: &T::IdtyIndex) -> Weight {
Weight::zero()
} }
pub trait RemoveIdentityConsumers<IndtyIndex> { fn on_revoked(_idty_index: &T::IdtyIndex) -> Weight {
fn remove_idty_consumers(idty_index: IndtyIndex) -> Weight; Weight::zero()
}
} }
impl<IndtyIndex> RemoveIdentityConsumers<IndtyIndex> for () {
fn remove_idty_consumers(_: IndtyIndex) -> Weight { /// Trait defining operations for linking identities to accounts.
0 pub trait LinkIdty<AccountId, IdtyIndex> {
/// Links an identity to an account.
fn link_identity(account_id: &AccountId, idty_index: IdtyIndex) -> Result<(), DispatchError>;
}
impl<AccountId, IdtyIndex> LinkIdty<AccountId, IdtyIndex> for () {
fn link_identity(_: &AccountId, _: IdtyIndex) -> Result<(), DispatchError> {
Ok(())
}
}
/// A trait for handling identity owner key changes.
pub trait KeyChange<T: Config> {
fn on_changed(id: T::IdtyIndex, account_id: T::AccountId) -> Result<(), DispatchError>;
}
impl<T: Config> KeyChange<T> for () {
/// Called when an identity's owner key has changed.
fn on_changed(_id: T::IdtyIndex, _account_id: T::AccountId) -> Result<(), DispatchError> {
Ok(())
} }
} }
// Copyright 2021 Axiom-Team // Copyright 2021 Axiom-Team
// //
// This file is part of Substrate-Libre-Currency. // This file is part of Duniter-v2S.
// //
// Substrate-Libre-Currency is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, version 3 of the License. // the Free Software Foundation, version 3 of the License.
// //
// Substrate-Libre-Currency is distributed in the hope that it will be useful, // Duniter-v2S is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of // but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details. // GNU Affero General Public License for more details.
// //
// 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 Substrate-Libre-Currency. If not, see <https://www.gnu.org/licenses/>. // along with Duniter-v2S. If not, see <https://www.gnu.org/licenses/>.
//! Various basic types for use in the identity pallet. //! Various basic types for use in the identity pallet.
use codec::{Decode, Encode}; use codec::{Decode, DecodeWithMemTracking, Encode};
use frame_support::pallet_prelude::*; use frame_support::pallet_prelude::*;
use scale_info::TypeInfo; use scale_info::{prelude::vec::Vec, TypeInfo};
#[cfg(feature = "std")]
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use sp_std::vec::Vec;
/// Internal events related to identity.
pub enum IdtyEvent<T: crate::Config> { pub enum IdtyEvent<T: crate::Config> {
Created { creator: T::IdtyIndex }, /// Creation of a new identity by another.
Confirmed, // pallet account links account to identity
Validated, // pallet wot adds certification
Removed, // pallet quota adds storage item for this identity
Created {
/// Identity of the creator.
creator: T::IdtyIndex,
/// Account of the identity owner.
owner_key: T::AccountId,
},
/// Removing an identity (unvalidated or revoked).
// pallet wot removes associated certifications if status is not revoked
// pallet quota removes associated quota
// pallet smith-members exclude smith
Removed {
/// Status of the identity.
status: IdtyStatus,
},
// TODO add a way to unlink accounts corresponding to revoked or removed identities
} }
#[derive(Encode, Decode, Default, Clone, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug)] /// Reasons for revocation.
pub struct IdtyName(pub Vec<u8>); #[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub enum RevocationReason {
impl scale_info::TypeInfo for IdtyName { /// Revoked by root (e.g., governance or migration).
type Identity = str; Root,
/// Revoked by user action (revocation document).
fn type_info() -> scale_info::Type { User,
Self::Identity::type_info() /// Revoked due to inactive period.
Expired,
} }
/// Reasons for removal.
#[derive(Encode, Decode, Clone, DecodeWithMemTracking, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub enum RemovalReason {
/// Removed by root.
Root,
/// Removed because unconfirmed.
Unconfirmed,
/// Removed because unvalidated.
Unvalidated,
/// Removed automatically after revocation buffer.
Revoked,
} }
#[cfg(feature = "std")] /// Represents the name of an identity, ASCII encoded.
#[derive(
Encode,
Decode,
DecodeWithMemTracking,
Default,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
RuntimeDebug,
Serialize,
Deserialize,
TypeInfo,
)]
pub struct IdtyName(pub Vec<u8>);
impl From<&str> for IdtyName { impl From<&str> for IdtyName {
fn from(s: &str) -> Self { fn from(s: &str) -> Self {
Self(s.as_bytes().to_vec()) Self(s.as_bytes().to_vec())
} }
} }
#[cfg(feature = "std")] /// State of an identity.
impl serde::Serialize for IdtyName { #[derive(
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> { Encode,
std::str::from_utf8(&self.0) Decode,
.map_err(|e| serde::ser::Error::custom(format!("{:?}", e)))? Default,
.serialize(serializer) Clone,
} Copy,
} PartialEq,
Eq,
#[cfg(feature = "std")] RuntimeDebug,
impl<'de> serde::Deserialize<'de> for IdtyName { TypeInfo,
fn deserialize<D: serde::Deserializer<'de>>(de: D) -> Result<Self, D::Error> { Deserialize,
Ok(Self(String::deserialize(de)?.as_bytes().to_vec())) Serialize,
} )]
}
#[cfg_attr(feature = "std", derive(Deserialize, Serialize))]
#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo)]
pub enum IdtyStatus { pub enum IdtyStatus {
Created, /// Created through a first certification but unconfirmed.
ConfirmedByOwner, #[default]
Validated, Unconfirmed,
} /// Confirmed by key owner with a name published but unvalidated.
impl Default for IdtyStatus { Unvalidated,
fn default() -> Self { /// Member of the main web of trust.
IdtyStatus::Created // (there must be a membership in membership pallet storage)
} Member,
/// Not a member of the main web of trust, auto-revocation planned.
NotMember,
/// Revoked manually or automatically, deletion possible.
Revoked,
} }
#[cfg_attr(feature = "std", derive(Deserialize, Serialize))] /// Identity value structure.
#[derive(Encode, Decode, Clone, PartialEq, Eq, TypeInfo)] ///
pub struct IdtyValue<BlockNumber, AccountId> { /// Represents the value associated with an identity, akin to key/value pairs.
#[derive(Serialize, Deserialize, Debug, Encode, Decode, Clone, PartialEq, Eq, TypeInfo)]
pub struct IdtyValue<BlockNumber, AccountId, IdtyData> {
/// Data shared between pallets defined by runtime.
/// Only contains `first_eligible_ud` in our case.
pub data: IdtyData,
/// Block before which creating a new identity is not allowed.
pub next_creatable_identity_on: BlockNumber, pub next_creatable_identity_on: BlockNumber,
/// Previous owner key of this identity (optional).
pub old_owner_key: Option<(AccountId, BlockNumber)>,
/// Current owner key of this identity.
pub owner_key: AccountId, pub owner_key: AccountId,
pub removable_on: BlockNumber, /// Next action scheduled on identity.
///
/// `0` if no action is scheduled.
pub next_scheduled: BlockNumber,
/// Current status of the identity (until validation).
pub status: IdtyStatus, pub status: IdtyStatus,
} }
#[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo, RuntimeDebug)] /// Reprensent the payload to define a new owner key.
pub struct RevocationPayload<AccountId, Hash> { #[derive(Clone, Copy, Encode, RuntimeDebug)]
pub owner_key: AccountId, pub struct IdtyIndexAccountIdPayload<'a, AccountId, IdtyIndex, Hash> {
// Avoid replay attack between blockchains /// Hash of the genesis block.
// Used to avoid replay attacks across networks.
pub genesis_hash: &'a Hash,
/// Identity index.
pub idty_index: IdtyIndex,
/// Old owner key of the identity.
pub old_owner_key: &'a AccountId,
}
/// Represents the payload for identity revocation.
#[derive(Clone, Copy, Encode, Decode, PartialEq, Eq, TypeInfo, RuntimeDebug)]
pub struct RevocationPayload<IdtyIndex, Hash> {
/// Hash of the genesis block.
// Used to avoid replay attacks across networks.
pub genesis_hash: Hash, pub genesis_hash: Hash,
/// Identity index.
pub idty_index: IdtyIndex,
} }
// 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 create_identity() -> Weight;
fn confirm_identity() -> Weight;
fn change_owner_key() -> Weight;
fn revoke_identity() -> Weight;
fn revoke_identity_legacy() -> Weight;
fn prune_item_identities_names(i: u32) -> Weight;
fn fix_sufficients() -> Weight;
fn link_account() -> Weight;
fn on_initialize() -> Weight;
fn do_revoke_identity_noop() -> Weight;
fn do_revoke_identity() -> Weight;
fn do_remove_identity_noop() -> Weight;
fn do_remove_identity_handler() -> Weight;
fn do_remove_identity() -> Weight;
fn prune_identities_noop() -> Weight;
fn prune_identities_none() -> Weight;
fn prune_identities_err() -> Weight;
fn membership_removed() -> Weight;
}
// Insecure weights implementation, use it for tests only!
impl WeightInfo for () {
fn create_identity() -> Weight {
// Proof Size summary in bytes:
// Measured: `1165`
// Estimated: `7105`
// Minimum execution time: 1_643_969_000 picoseconds.
Weight::from_parts(1_781_521_000, 0)
.saturating_add(Weight::from_parts(0, 7105))
.saturating_add(RocksDbWeight::get().reads(14))
.saturating_add(RocksDbWeight::get().writes(12))
}
fn confirm_identity() -> Weight {
// Proof Size summary in bytes:
// Measured: `661`
// Estimated: `6601`
// Minimum execution time: 564_892_000 picoseconds.
Weight::from_parts(588_761_000, 0)
.saturating_add(Weight::from_parts(0, 6601))
.saturating_add(RocksDbWeight::get().reads(5))
.saturating_add(RocksDbWeight::get().writes(4))
}
fn change_owner_key() -> Weight {
// Proof Size summary in bytes:
// Measured: `837`
// Estimated: `6777`
// Minimum execution time: 991_641_000 picoseconds.
Weight::from_parts(1_071_332_000, 0)
.saturating_add(Weight::from_parts(0, 6777))
.saturating_add(RocksDbWeight::get().reads(7))
.saturating_add(RocksDbWeight::get().writes(5))
}
fn revoke_identity() -> Weight {
// Proof Size summary in bytes:
// Measured: `778`
// Estimated: `6718`
// Minimum execution time: 829_174_000 picoseconds.
Weight::from_parts(869_308_000, 0)
.saturating_add(Weight::from_parts(0, 6718))
.saturating_add(RocksDbWeight::get().reads(6))
.saturating_add(RocksDbWeight::get().writes(6))
}
fn prune_item_identities_names(i: u32) -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 51_362_000 picoseconds.
Weight::from_parts(80_389_000, 0)
.saturating_add(Weight::from_parts(0, 0))
// Standard Error: 75_232
.saturating_add(Weight::from_parts(30_016_649, 0).saturating_mul(i.into()))
.saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(i.into())))
}
fn fix_sufficients() -> Weight {
// Proof Size summary in bytes:
// Measured: `67`
// Estimated: `3591`
// Minimum execution time: 154_343_000 picoseconds.
Weight::from_parts(156_117_000, 0)
.saturating_add(Weight::from_parts(0, 3591))
.saturating_add(RocksDbWeight::get().reads(1))
.saturating_add(RocksDbWeight::get().writes(1))
}
fn link_account() -> Weight {
// Proof Size summary in bytes:
// Measured: `307`
// Estimated: `3772`
// Minimum execution time: 538_773_000 picoseconds.
Weight::from_parts(591_354_000, 0)
.saturating_add(Weight::from_parts(0, 3772))
.saturating_add(RocksDbWeight::get().reads(3))
.saturating_add(RocksDbWeight::get().writes(1))
}
fn on_initialize() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 4_529_000 picoseconds.
Weight::from_parts(7_360_000, 0).saturating_add(Weight::from_parts(0, 0))
}
fn do_revoke_identity_noop() -> Weight {
// Proof Size summary in bytes:
// Measured: `269`
// Estimated: `3734`
// Minimum execution time: 103_668_000 picoseconds.
Weight::from_parts(107_679_000, 0)
.saturating_add(Weight::from_parts(0, 3734))
.saturating_add(RocksDbWeight::get().reads(1))
}
fn do_revoke_identity() -> Weight {
// Proof Size summary in bytes:
// Measured: `1525`
// Estimated: `7465`
// Minimum execution time: 2_204_911_000 picoseconds.
Weight::from_parts(2_225_493_000, 0)
.saturating_add(Weight::from_parts(0, 7465))
.saturating_add(RocksDbWeight::get().reads(17))
.saturating_add(RocksDbWeight::get().writes(20))
}
fn revoke_identity_legacy() -> Weight {
// Proof Size summary in bytes:
// Measured: `1525`
// Estimated: `7465`
// Minimum execution time: 2_204_911_000 picoseconds.
Weight::from_parts(2_225_493_000, 0)
.saturating_add(Weight::from_parts(0, 7465))
.saturating_add(RocksDbWeight::get().reads(17))
.saturating_add(RocksDbWeight::get().writes(20))
}
fn do_remove_identity_noop() -> Weight {
// Proof Size summary in bytes:
// Measured: `269`
// Estimated: `3734`
// Minimum execution time: 104_296_000 picoseconds.
Weight::from_parts(115_316_000, 0)
.saturating_add(Weight::from_parts(0, 3734))
.saturating_add(RocksDbWeight::get().reads(1))
}
fn do_remove_identity_handler() -> Weight {
// Proof Size summary in bytes:
// Measured: `269`
// Estimated: `3734`
// Minimum execution time: 104_296_000 picoseconds.
Weight::from_parts(115_316_000, 0)
.saturating_add(Weight::from_parts(0, 3734))
.saturating_add(RocksDbWeight::get().reads(1))
}
fn do_remove_identity() -> Weight {
// Proof Size summary in bytes:
// Measured: `1432`
// Estimated: `6192`
// Minimum execution time: 2_870_497_000 picoseconds.
Weight::from_parts(4_159_994_000, 0)
.saturating_add(Weight::from_parts(0, 6192))
.saturating_add(RocksDbWeight::get().reads(16))
.saturating_add(RocksDbWeight::get().writes(22))
}
fn prune_identities_noop() -> Weight {
// Proof Size summary in bytes:
// Measured: `108`
// Estimated: `3573`
// Minimum execution time: 68_859_000 picoseconds.
Weight::from_parts(71_836_000, 0)
.saturating_add(Weight::from_parts(0, 3573))
.saturating_add(RocksDbWeight::get().reads(1))
}
fn prune_identities_none() -> Weight {
// Proof Size summary in bytes:
// Measured: `292`
// Estimated: `3757`
// Minimum execution time: 178_332_000 picoseconds.
Weight::from_parts(186_982_000, 0)
.saturating_add(Weight::from_parts(0, 3757))
.saturating_add(RocksDbWeight::get().reads(2))
.saturating_add(RocksDbWeight::get().writes(1))
}
fn prune_identities_err() -> Weight {
// Proof Size summary in bytes:
// Measured: `1177`
// Estimated: `4642`
// Minimum execution time: 1_427_848_000 picoseconds.
Weight::from_parts(2_637_229_000, 0)
.saturating_add(Weight::from_parts(0, 4642))
.saturating_add(RocksDbWeight::get().reads(8))
.saturating_add(RocksDbWeight::get().writes(8))
}
fn membership_removed() -> Weight {
// Proof Size summary in bytes:
// Measured: `1177`
// Estimated: `4642`
// Minimum execution time: 1_427_848_000 picoseconds.
Weight::from_parts(2_637_229_000, 0)
.saturating_add(Weight::from_parts(0, 4642))
.saturating_add(RocksDbWeight::get().reads(8))
.saturating_add(RocksDbWeight::get().writes(8))
}
}
[package] [package]
authors = ['librelois <c@elo.tf>'] authors.workspace = true
description = 'FRAME pallet membership.' description = "duniter pallet membership"
edition = '2018' edition.workspace = true
homepage = 'https://substrate.dev' homepage.workspace = true
license = 'AGPL-3.0' license.workspace = true
name = 'pallet-membership' name = "pallet-membership"
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', "frame-benchmarking?/std",
'frame-system/std', "frame-support/std",
'frame-benchmarking/std', "frame-system/std",
'serde', "scale-info/std",
'sp-core/std', "sp-core/std",
'sp-membership/std', "sp-io/std",
'sp-runtime/std', "sp-membership/std",
'sp-std/std', "sp-runtime/std",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"sp-membership/try-runtime",
"sp-runtime/try-runtime",
] ]
try-runtime = ['frame-support/try-runtime']
[dependencies]
sp-membership = { path = "../../primitives/membership", default-features = false }
# substrate
scale-info = { version = "1.0", default-features = false, features = ["derive"] }
[dependencies.codec]
default-features = false
features = ['derive']
package = 'parity-scale-codec'
version = '2.3.1'
[dependencies.frame-benchmarking]
default-features = false
git = 'https://github.com/librelois/substrate.git'
optional = true
branch = 'duniter-monthly-2022-02'
[dependencies.frame-support]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.frame-system]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.serde]
version = "1.0.101"
optional = true
features = ["derive"]
[dependencies.sp-core]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-runtime]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
[dependencies.sp-std]
default-features = false
git = 'https://github.com/librelois/substrate.git'
branch = 'duniter-monthly-2022-02'
### DOC ###
[package.metadata.docs.rs] [package.metadata.docs.rs]
targets = ['x86_64-unknown-linux-gnu'] targets = ["x86_64-unknown-linux-gnu"]
[dev-dependencies.serde]
version = '1.0.119'
### DEV ###
[dev-dependencies.maplit] [dependencies]
version = '1.0.2' duniter-primitives = { workspace = true }
codec = { workspace = true, features = ["derive"] }
[dev-dependencies.sp-io] frame-benchmarking = { workspace = true, optional = true }
default-features = false frame-support = { workspace = true }
git = 'https://github.com/librelois/substrate.git' frame-system = { workspace = true }
branch = 'duniter-monthly-2022-02' scale-info = { workspace = true, features = ["derive"] }
sp-core = { workspace = true }
sp-io = { workspace = true }
sp-membership = { workspace = true }
sp-runtime = { workspace = true }
[dev-dependencies]
maplit = { workspace = true, default-features = true }
sp-io = { workspace = true, default-features = true }
// 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/>.
#![cfg(feature = "runtime-benchmarks")]
use super::*;
use frame_benchmarking::v2::*;
use frame_system::pallet_prelude::BlockNumberFor;
use crate::Pallet;
#[benchmarks(
where
T::IdtyId: From<u32>,
BlockNumberFor<T>: From<u32>,
)]
mod benchmarks {
use super::*;
// TODO membership add and renewal should be included to distance on_new_session as worst case scenario
#[benchmark]
fn on_initialize() {
// Base weight of an empty initialize
#[block]
{
Pallet::<T>::on_initialize(BlockNumberFor::<T>::zero());
}
}
#[benchmark]
fn expire_memberships(i: Linear<0, 3>) {
// Limited by the number of validators
// Arbitrarily high, to be in the worst case of wot instance,
// this will overcount the weight in hooks see https://git.duniter.org/nodes/rust/duniter-v2s/-/issues/167
let block_number: BlockNumberFor<T> = 10_000_000.into();
frame_system::pallet::Pallet::<T>::set_block_number(block_number);
let mut idties: Vec<T::IdtyId> = Vec::new();
for j in 1..i + 1 {
let j: T::IdtyId = j.into();
Membership::<T>::insert(j, MembershipData::<BlockNumberFor<T>>::default());
idties.push(j);
}
MembershipsExpireOn::<T>::insert(block_number, idties);
assert_eq!(
MembershipsExpireOn::<T>::get(block_number).len(),
i as usize
);
#[block]
{
Pallet::<T>::expire_memberships(block_number);
}
assert_eq!(MembershipsExpireOn::<T>::get(block_number).len(), 0_usize);
}
impl_benchmark_test_suite!(
Pallet,
crate::mock::new_test_ext(crate::mock::MembershipConfig {
memberships: maplit::btreemap![
3 => crate::MembershipData {
expire_on: 3,
},
],
}),
crate::mock::Test
);
}