From 774e0beec406d801b8f377985486cba363690747 Mon Sep 17 00:00:00 2001 From: brenzi Date: Sun, 1 Oct 2023 07:50:47 +0200 Subject: [PATCH] vouches pallet MVP (#355) * initial commit for new pallet. not compiling yet * builds and test passes * simplify: only one quality per vouch * cleanup warnings * more cleanup * undisputed review comment fixes * enum encoding hints and more specific docs to enums * fix benchmarks * remove hard-coding of enum encoding --- Cargo.lock | 20 +++++++ Cargo.toml | 1 + balances/Cargo.toml | 4 +- primitives/src/lib.rs | 1 + primitives/src/vouches.rs | 91 +++++++++++++++++++++++++++++++ vouches/Cargo.toml | 52 ++++++++++++++++++ vouches/src/benchmarking.rs | 20 +++++++ vouches/src/lib.rs | 105 ++++++++++++++++++++++++++++++++++++ vouches/src/mock.rs | 51 ++++++++++++++++++ vouches/src/tests.rs | 55 +++++++++++++++++++ vouches/src/weights.rs | 73 +++++++++++++++++++++++++ 11 files changed, 470 insertions(+), 3 deletions(-) create mode 100644 primitives/src/vouches.rs create mode 100644 vouches/Cargo.toml create mode 100644 vouches/src/benchmarking.rs create mode 100644 vouches/src/lib.rs create mode 100644 vouches/src/mock.rs create mode 100644 vouches/src/tests.rs create mode 100644 vouches/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 42130633..b9672d78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4415,6 +4415,26 @@ dependencies = [ "test-utils", ] +[[package]] +name = "pallet-encointer-vouches" +version = "1.2.0" +dependencies = [ + "approx", + "encointer-primitives", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "test-utils", +] + [[package]] name = "pallet-timestamp" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 84476cf4..0b544c78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ members = [ 'rpc', 'scheduler', 'test-utils', + 'vouches', ] #[patch."https://github.com/encointer/substrate-fixed"] diff --git a/balances/Cargo.toml b/balances/Cargo.toml index 877acbd8..56e05d7c 100644 --- a/balances/Cargo.toml +++ b/balances/Cargo.toml @@ -1,9 +1,7 @@ [package] name = "pallet-encointer-balances" version = "1.2.0" -authors = [ - "encointer.org and Parity Technologies ", -] +authors = ["encointer.org "] edition = "2021" [dependencies] diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index e7fca231..6984cf25 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -28,6 +28,7 @@ pub mod faucet; pub mod reputation_commitments; pub mod scheduler; pub mod storage; +pub mod vouches; pub use ep_core::*; diff --git a/primitives/src/vouches.rs b/primitives/src/vouches.rs new file mode 100644 index 00000000..50891c85 --- /dev/null +++ b/primitives/src/vouches.rs @@ -0,0 +1,91 @@ +// Copyright (c) 2023 Encointer Association +// This file is part of Encointer +// +// Encointer is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Encointer 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Encointer. If not, see . + +use crate::common::BoundedIpfsCid; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::RuntimeDebug; +use scale_info::TypeInfo; +#[cfg(feature = "serde_derive")] +use serde::{Deserialize, Serialize}; + +/// Did the attester meet the attestee physically, virtually or through asynchronous messages? +#[derive(Default, Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))] +pub enum PresenceType { + /// could be "I have exchanged messages with the person I vouch for" + /// could be "I have watched the replay of the Event or talk I vouch for" + #[default] + Asynchronous, + /// could be "I have attended that online conference remotely" + /// could be "I have visited that place in the metaverse" + /// could be "I have met this person on an video call and they presented this account to me" + LiveVirtual, + /// could be "I met the human I vouch for in-person and scanned the account they presented at the occasion of this physical encounter" + /// could be "I was standing in front of this monument and scanned the QR code on its plate" + /// could be "I ate at this restaurant and scanned the QR code presented at their entrance in order to submit a rating" + LivePhysical, +} + +/// The nature of a vouch +/// this set is most likely incomplete. we leave gaps in the encoding to have room for more kinds +/// which still results in meaningful order +#[derive(Default, Encode, Decode, Copy, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))] +pub enum VouchKind { + /// Unspecified. This should generally be handeled as an invalid vouch or an alien use case + #[default] + Unspecified, + /// This person is know to me and I have verified their account with specified presence type + KnownHuman(PresenceType), + /// I do not claim to know this person, but I encountered a human being providing me with the account I vouch for + EncounteredHuman(PresenceType), + /// I encountered an object showing the account I vouch for + EncounteredObject(PresenceType), + /// I have visited a place labeled with the account I vouch for + VisitedPlace(PresenceType), + /// I have attended an event which identifies with the account I vouch for + AttendedEvent(PresenceType), +} + +/// a scalar expression of quality. Interpretation left to client side per use case +/// could be a 0-5 star rating or a high-resolution 0..255 +pub type Rating = u8; + +/// additional information about the attestee's qualities +#[derive(Default, Encode, Decode, Clone, PartialEq, Eq, Debug, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))] +pub enum VouchQuality { + /// Don't want to submit additional information + #[default] + Unspecified, + /// a generic badge for qualitative attestation stored as a json file on IPFS (json schema TBD) + Badge(BoundedIpfsCid), + /// a quantitative expression of the attestee's quality (i.e. number of stars) + Rating(Rating), +} + +#[derive(Default, Encode, Decode, PartialEq, Eq, RuntimeDebug, Clone, TypeInfo, MaxEncodedLen)] +#[cfg_attr(feature = "serde_derive", derive(Serialize, Deserialize))] +pub struct Vouch { + /// protected vouches can't be purged. unprotected ones can be lazily purged after a time-to-live. (future feature) + pub protected: bool, + /// the timestamp of the block which registers this Vouch + pub timestamp: Moment, + /// what is the nature of this vouch? + pub vouch_kind: VouchKind, + /// additional information about the attestee's qualities + pub quality: VouchQuality, +} diff --git a/vouches/Cargo.toml b/vouches/Cargo.toml new file mode 100644 index 00000000..74ed2f4d --- /dev/null +++ b/vouches/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "pallet-encointer-vouches" +version = "1.2.0" +authors = ["encointer.org and Parity Technologies "] +edition = "2021" + +[dependencies] +approx = { version = "0.5.1", optional = true } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +log = { version = "0.4.14", default-features = false } +scale-info = { version = "2.5.0", default-features = false } + +# local deps +encointer-primitives = { path = "../primitives", default-features = false, features = ["serde_derive"] } + +# substrate deps +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } +frame-support = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } +frame-system = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } +pallet-timestamp = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } +sp-core = { version = "21.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } +sp-runtime = { version = "24.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } +sp-std = { version = "8.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } + +[dev-dependencies] +approx = "0.5.1" +sp-io = { version = "23.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v1.0.0" } +test-utils = { path = "../test-utils" } + +[features] +default = ["std"] +std = [ + "codec/std", + "log/std", + "scale-info/std", + "approx/std", + # local deps + "encointer-primitives/std", + # substrate deps + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "sp-std/std", + "sp-runtime/std", + "sp-core/std", +] + +runtime-benchmarks = ["frame-benchmarking", "approx"] + +try-runtime = [ + "frame-system/try-runtime", +] diff --git a/vouches/src/benchmarking.rs b/vouches/src/benchmarking.rs new file mode 100644 index 00000000..71bcd4f9 --- /dev/null +++ b/vouches/src/benchmarking.rs @@ -0,0 +1,20 @@ +use crate::*; +use encointer_primitives::vouches::{PresenceType, VouchKind, VouchQuality}; +use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite}; +use frame_system::RawOrigin; + +benchmarks! { + vouch_for { + let zoran = account("zoran", 1, 1); + let goran = account("goran", 1, 2); + let vouch_kind = VouchKind::EncounteredHuman(PresenceType::LivePhysical); + let quality = VouchQuality::default(); + assert!(>::iter().next().is_none()); + }: _(RawOrigin::Signed(zoran), goran, vouch_kind, quality) + verify { + assert!(>::iter().next().is_some()); + } + +} + +impl_benchmark_test_suite!(Pallet, crate::mock::new_test_ext(), crate::mock::TestRuntime); diff --git a/vouches/src/lib.rs b/vouches/src/lib.rs new file mode 100644 index 00000000..79845e4b --- /dev/null +++ b/vouches/src/lib.rs @@ -0,0 +1,105 @@ +// Copyright (c) 2019 Encointer Association +// This file is part of Encointer +// +// Encointer is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Encointer 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Encointer. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +use core::marker::PhantomData; +use encointer_primitives::vouches::{Vouch, VouchKind, VouchQuality}; +use frame_system::{self as frame_system, ensure_signed, pallet_prelude::OriginFor}; +use log::info; +pub use pallet::*; +use sp_std::convert::TryInto; +pub use weights::WeightInfo; +// Logger target +const LOG: &str = "encointer::vouches"; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +mod weights; +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + #[pallet::config] + pub trait Config: frame_system::Config + pallet_timestamp::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + type WeightInfo: WeightInfo; + #[pallet::constant] + type MaxVouchesPerAttester: Get; + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight((::WeightInfo::vouch_for(), DispatchClass::Normal, Pays::Yes))] + pub fn vouch_for( + origin: OriginFor, + attestee: T::AccountId, + vouch_kind: VouchKind, + quality: VouchQuality, + ) -> DispatchResultWithPostInfo { + let attester = ensure_signed(origin)?; + let now = >::get(); + let vouch = + Vouch { protected: false, timestamp: now, vouch_kind, quality: quality.clone() }; + >::try_mutate( + &attestee, + &attester, + |vouches| -> DispatchResultWithPostInfo { + vouches.try_push(vouch).map_err(|_| Error::::TooManyVouchesForAttestee)?; + Ok(().into()) + }, + )?; + info!(target: LOG, "vouching: {:?} for {:?}, vouch type: {:?}, quality: {:?}", attester, attestee, vouch_kind, quality); + Self::deposit_event(Event::VouchedFor { attestee, attester, vouch_kind }); + Ok(().into()) + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// someone or something has vouched for someone or something + VouchedFor { attestee: T::AccountId, attester: T::AccountId, vouch_kind: VouchKind }, + } + + #[pallet::error] + pub enum Error { + /// The calling attester has too many vouches for this attestee + TooManyVouchesForAttestee, + } + + #[pallet::storage] + #[pallet::getter(fn vouches)] + pub(super) type Vouches = StorageDoubleMap< + _, + Blake2_128Concat, + T::AccountId, + Blake2_128Concat, + T::AccountId, + BoundedVec, T::MaxVouchesPerAttester>, + ValueQuery, + >; +} diff --git a/vouches/src/mock.rs b/vouches/src/mock.rs new file mode 100644 index 00000000..ae435edd --- /dev/null +++ b/vouches/src/mock.rs @@ -0,0 +1,51 @@ +// Copyright (c) 2019 Alain Brenzikofer +// This file is part of Encointer +// +// Encointer is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Encointer 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Encointer. If not, see . + +//! Mock runtime for the encointer_balances module + +use crate as dut; +use frame_support::{parameter_types, traits::ConstU32}; +use sp_runtime::BuildStorage; +use test_utils::*; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + +frame_support::construct_runtime!( + pub enum TestRuntime + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + EncointerVouches: dut::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! {} + +impl dut::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type MaxVouchesPerAttester = ConstU32<4>; +} + +// boilerplate +impl_frame_system!(TestRuntime); +impl_timestamp!(TestRuntime); + +// genesis values +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + t.into() +} diff --git a/vouches/src/tests.rs b/vouches/src/tests.rs new file mode 100644 index 00000000..d4982e42 --- /dev/null +++ b/vouches/src/tests.rs @@ -0,0 +1,55 @@ +// Copyright (c) 2019 Alain Brenzikofer +// This file is part of Encointer +// +// Encointer is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Encointer 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Encointer. If not, see . + +//! Unit tests for the encointer_reputation_commitments module. + +use super::*; +use crate::mock::{EncointerVouches, RuntimeOrigin}; +use encointer_primitives::{ + common::{BoundedIpfsCid, FromStr}, + vouches::{PresenceType, VouchKind}, +}; +use frame_support::assert_ok; +use mock::{new_test_ext, System, TestRuntime}; +use test_utils::{AccountId, AccountKeyring}; + +#[test] +fn vouch_for_works() { + new_test_ext().execute_with(|| { + System::set_block_number(System::block_number() + 1); // this is needed to assert events + let _ = pallet_timestamp::Pallet::::set(RuntimeOrigin::none(), 42); + + let alice = AccountId::from(AccountKeyring::Alice); + let charlie = AccountId::from(AccountKeyring::Charlie); + + let vouch_kind = VouchKind::EncounteredHuman(PresenceType::LivePhysical); + let quality = VouchQuality::Badge( + BoundedIpfsCid::from_str("QmPK1s3pNYLi9ERiq3BDxKa4XosgWwFRQUydHUtz4YgpqB").unwrap(), + ); + + assert_ok!(EncointerVouches::vouch_for( + RuntimeOrigin::signed(alice.clone()), + charlie.clone(), + vouch_kind, + quality.clone(), + )); + + assert_eq!( + EncointerVouches::vouches(charlie, alice)[0], + Vouch { protected: false, timestamp: 42, vouch_kind, quality } + ); + }); +} diff --git a/vouches/src/weights.rs b/vouches/src/weights.rs new file mode 100644 index 00000000..5c7406af --- /dev/null +++ b/vouches/src/weights.rs @@ -0,0 +1,73 @@ +/* +Copyright 2022 Encointer + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. + +*/ + +//! Autogenerated weights for pallet_encointer_reputation_commitments with reference hardware: +//! * 2.3 GHz 8-Core Intel Core i9 +//! * 16 GB 2400 MHz DDR4 +//! * SSD +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-06-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// target/release/encointer-node-notee +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_encointer_reputation_commitments +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=runtime/src/weights/pallet_encointer_reputation_commitments.rs +// --template=/Users/pigu/Documents/code/encointer/encointer-node/scripts/frame-weight-template-full-info.hbs + +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{ + traits::Get, + weights::{constants::RocksDbWeight, Weight}, +}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_encointer_reputation_commitments. +pub trait WeightInfo { + fn vouch_for() -> Weight; +} + +/// Weights for pallet_encointer_reputation_commitments using the Encointer solo chain node and recommended hardware. +pub struct EncointerWeight(PhantomData); +impl WeightInfo for EncointerWeight { + fn vouch_for() -> Weight { + (Weight::from_parts(54_000_000, 0)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(2)) + } +} + +// For tests +impl WeightInfo for () { + fn vouch_for() -> Weight { + (Weight::from_parts(54_000_000, 0)) + .saturating_add(RocksDbWeight::get().reads(1)) + .saturating_add(RocksDbWeight::get().writes(2)) + } +}