topsoil_core/traits/reality.rs
1// This file is part of Soil.
2
3// Copyright (C) Soil contributors.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later WITH Classpath-exception-2.0
6
7//! Traits concerned with modelling reality.
8
9use core::marker::PhantomData;
10
11use codec::{Decode, DecodeWithMemTracking, Encode, FullCodec, MaxEncodedLen};
12use scale_info::TypeInfo;
13use subsoil::core::ConstU32;
14use subsoil::runtime::{traits::Member, BoundedVec, DispatchError, DispatchResult};
15use topsoil_core::{CloneNoBound, EqNoBound, Parameter, PartialEqNoBound};
16
17/// Identity of personhood.
18///
19/// This is a persistent identifier for every individual. Regardless of what else the individual
20/// changes within the system (such as identity documents, cryptographic keys, etc...) this does not
21/// change. As such, it should never be used in application code.
22pub type PersonalId = u64;
23
24/// Identifier for a specific application in which we may wish to track individual people.
25///
26/// NOTE: This MUST remain equivalent to the type `Context` in the crate `verifiable`.
27pub type Context = [u8; 32];
28
29/// Identifier for a specific individual within an application context.
30///
31/// NOTE: This MUST remain equivalent to the type `Alias` in the crate `verifiable`.
32pub type Alias = [u8; 32];
33
34/// The type we use to identify different rings.
35pub type RingIndex = u32;
36
37/// Data type for arbitrary information handled by the statement oracle.
38pub type Data = BoundedVec<u8, ConstU32<32>>;
39
40/// The maximum length of custom statement data.
41pub const MAX_STATEMENT_DATA_SIZE: u32 = 256;
42
43/// Data type for custom statement information handled by the statement oracle.
44pub type CustomStatement = BoundedVec<u8, ConstU32<MAX_STATEMENT_DATA_SIZE>>;
45
46/// The type used to represent the hash of the evidence used in statements by the statement oracle.
47pub type EvidenceHash = [u8; 32];
48
49/// Maximum length of the context passed in the oracle's judgements.
50pub const CONTEXT_SIZE: u32 = 64;
51
52/// The type used to represent the context of an oracle's judgement.
53pub type JudgementContext = BoundedVec<u8, ConstU32<CONTEXT_SIZE>>;
54
55/// The [`Alias`] type enriched with the originating [`Context`].
56#[derive(
57 Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo, DecodeWithMemTracking,
58)]
59pub struct ContextualAlias {
60 /// The alias of the person.
61 pub alias: Alias,
62 /// The context in which this alias was created.
63 pub context: Context,
64}
65
66/// Trait to recognize people and handle personal id.
67///
68/// `PersonalId` goes through multiple state: free, reserved, used; a used personal id can belong
69/// to a recognized person or a suspended person.
70pub trait AddOnlyPeopleTrait {
71 type Member: Parameter + MaxEncodedLen;
72 /// Reserve a new id for a future person. This id is not recognized, not reserved, and has
73 /// never been reserved in the past.
74 fn reserve_new_id() -> PersonalId;
75 /// Renew a reservation for a personal id. The id is not recognized, but has been reserved in
76 /// the past.
77 ///
78 /// An error is returned if the id is used or wasn't reserved before.
79 fn renew_id_reservation(personal_id: PersonalId) -> Result<(), DispatchError>;
80 /// Cancel the reservation for a personal id
81 ///
82 /// An error is returned if the id wasn't reserved in the first place.
83 fn cancel_id_reservation(personal_id: PersonalId) -> Result<(), DispatchError>;
84 /// Recognized a person.
85 ///
86 /// The personal id must be reserved or the person must have already been recognized and
87 /// suspended in the past.
88 /// If recognizing a new person, a key must be provided. If resuming the personhood then no key
89 /// must be provided.
90 ///
91 /// An error is returned if:
92 /// * `maybe_key` is some and the personal id was not reserved or is used by a recognized or
93 /// suspended person.
94 /// * `maybe_key` is none and the personal id was not recognized before.
95 fn recognize_personhood(
96 who: PersonalId,
97 maybe_key: Option<Self::Member>,
98 ) -> Result<(), DispatchError>;
99 // All stuff for benchmarks.
100 #[cfg(feature = "runtime-benchmarks")]
101 type Secret;
102 #[cfg(feature = "runtime-benchmarks")]
103 fn mock_key(who: PersonalId) -> (Self::Member, Self::Secret);
104}
105
106/// Trait to recognize and suspend people.
107pub trait PeopleTrait: AddOnlyPeopleTrait {
108 /// Suspend a set of people. This operation must be called within a mutation session.
109 ///
110 /// An error is returned if:
111 /// * a suspended personal id was already suspended.
112 /// * a personal id doesn't belong to any person.
113 fn suspend_personhood(suspensions: &[PersonalId]) -> DispatchResult;
114 /// Start a mutation session for setting people.
115 ///
116 /// An error is returned if the mutation session can be started at the moment. It is expected
117 /// to become startable later.
118 fn start_people_set_mutation_session() -> DispatchResult;
119 /// End a mutation session for setting people.
120 ///
121 /// An error is returned if there is no mutation session ongoing.
122 fn end_people_set_mutation_session() -> DispatchResult;
123}
124
125impl AddOnlyPeopleTrait for () {
126 type Member = ();
127 fn reserve_new_id() -> PersonalId {
128 0
129 }
130 fn renew_id_reservation(_: PersonalId) -> Result<(), DispatchError> {
131 Ok(())
132 }
133 fn cancel_id_reservation(_: PersonalId) -> Result<(), DispatchError> {
134 Ok(())
135 }
136 fn recognize_personhood(_: PersonalId, _: Option<Self::Member>) -> Result<(), DispatchError> {
137 Ok(())
138 }
139
140 #[cfg(feature = "runtime-benchmarks")]
141 type Secret = PersonalId;
142 #[cfg(feature = "runtime-benchmarks")]
143 fn mock_key(who: PersonalId) -> (Self::Member, Self::Secret) {
144 ((), who)
145 }
146}
147
148impl PeopleTrait for () {
149 fn suspend_personhood(_: &[PersonalId]) -> DispatchResult {
150 Ok(())
151 }
152 fn start_people_set_mutation_session() -> DispatchResult {
153 Ok(())
154 }
155 fn end_people_set_mutation_session() -> DispatchResult {
156 Ok(())
157 }
158}
159
160/// Trait to get the total number of active members in a set.
161pub trait CountedMembers {
162 /// Returns the number of active members in the set.
163 fn active_count(&self) -> u32;
164}
165
166/// A legitimate verdict on a particular statement.
167#[derive(
168 Clone,
169 Copy,
170 PartialEq,
171 Eq,
172 Debug,
173 Encode,
174 Decode,
175 MaxEncodedLen,
176 TypeInfo,
177 DecodeWithMemTracking,
178)]
179pub enum Truth {
180 /// The evidence can be taken as a clear indication that the statement is true. Doubt may still
181 /// remain but it should be unlikely (no more than 1 chance in 20) that this doubt would be
182 /// substantial enough to contravene the evidence.
183 True,
184 /// The evidence contradicts the statement.
185 False,
186}
187
188/// Judgement passed on the truth and validity of a statement.
189#[derive(
190 Clone,
191 Copy,
192 PartialEq,
193 Eq,
194 Debug,
195 Encode,
196 Decode,
197 MaxEncodedLen,
198 TypeInfo,
199 DecodeWithMemTracking,
200)]
201pub enum Judgement {
202 /// A judgement on the truth of a statement.
203 Truth(Truth),
204 /// The evidence supplied probably (P > 50%) implies contempt for the system. Submitting
205 /// evidence which clearly appears to be manipulated or intentionally provides no indication of
206 /// truth for the statement would imply this outcome.
207 Contempt,
208}
209
210impl Judgement {
211 pub fn matches_intent(&self, j: Self) -> bool {
212 use self::Truth::*;
213 use Judgement::*;
214 matches!(
215 (self, j),
216 (Truth(True), Truth(True)) | (Truth(False), Truth(False)) | (Contempt, Contempt)
217 )
218 }
219}
220
221pub mod identity {
222 use super::*;
223
224 /// Social platforms that statement oracles support.
225 #[derive(
226 Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo, DecodeWithMemTracking,
227 )]
228 pub enum Social {
229 Twitter { username: Data },
230 Github { username: Data },
231 }
232
233 impl Social {
234 pub fn eq_platform(&self, other: &Social) -> bool {
235 matches!(
236 (&self, &other),
237 (Social::Twitter { .. }, Social::Twitter { .. })
238 | (Social::Github { .. }, Social::Github { .. })
239 )
240 }
241 }
242}
243
244/// A statement upon which a [`StatementOracle`] can provide judgement.
245#[derive(Clone, PartialEq, Eq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)]
246pub enum Statement {
247 /// Ask for whether evidence exists to confirm that a particular social credential on a
248 /// supported platform belongs to a person.
249 IdentityCredential { platform: identity::Social, evidence: Data },
250 /// Ask for whether a username meets certain standards.
251 ///
252 /// It is up to the oracle to decide upon username validity,
253 /// but it may be assumed that a username is considered acceptable if it:
254 /// - contains no offensive, discriminatory, or inappropriate content,
255 /// - is visually distinct and readable in user interfaces,
256 /// - complies with other oracle guidelines.
257 UsernameValid { username: Data },
258 /// Ask for a custom statement to be judged. The responsibility of correctly interpreting the
259 /// encoded bytes falls on the [`StatementOracle`] implementation handling this. If no custom
260 /// statements are allowed, the implementation should reject this variant altogether.
261 Custom { id: u8, data: CustomStatement },
262}
263
264/// Describes the location within the runtime of a callback, along with other type information such
265/// as parameters passed into the callback.
266#[derive(
267 CloneNoBound, PartialEqNoBound, EqNoBound, Debug, Encode, Decode, MaxEncodedLen, TypeInfo,
268)]
269#[scale_info(skip_type_params(Params, RuntimeCall))]
270#[codec(mel_bound())]
271pub struct Callback<Params, RuntimeCall> {
272 pallet_index: u8,
273 call_index: u8,
274 phantom: PhantomData<(Params, RuntimeCall)>,
275}
276
277impl<Params: Encode, RuntimeCall: Decode> Callback<Params, RuntimeCall> {
278 pub const fn from_parts(pallet_index: u8, call_index: u8) -> Self {
279 Self { pallet_index, call_index, phantom: PhantomData }
280 }
281 pub fn curry(&self, args: Params) -> Result<RuntimeCall, codec::Error> {
282 (self.pallet_index, self.call_index, args).using_encoded(|mut d| Decode::decode(&mut d))
283 }
284}
285
286/// A provider of wondrous magic: give it a `Statement` and it will tell you if it's true, with
287/// some degree of resilience.
288///
289/// It's asynchronous, so you give it a callback in the form of a `RuntimeCall` stub.
290pub trait StatementOracle<RuntimeCall> {
291 /// A small piece of data which may be used to identify different ongoing judgements.
292 type Ticket: Member + FullCodec + TypeInfo + MaxEncodedLen + Default;
293
294 /// Judge a `statement` and get a Judgement.
295 ///
296 /// We only care about the pallet/call index of `callback`; it must take exactly three
297 /// arguments:
298 ///
299 /// - `Self::Ticket`: The ticket which was returned here to identify the judgement.
300 /// - `JudgementContext`: The value of `context` which was passed in to this call.
301 /// - `Judgement`: The judgement given by the oracle.
302 ///
303 /// It is assumed that all costs associated with this oraclisation have already been paid for
304 /// or are absorbed by the system acting in its own interests.
305 fn judge_statement(
306 statement: Statement,
307 context: JudgementContext,
308 callback: Callback<(Self::Ticket, JudgementContext, Judgement), RuntimeCall>,
309 ) -> Result<Self::Ticket, DispatchError>;
310}
311
312impl<C> StatementOracle<C> for () {
313 type Ticket = ();
314 fn judge_statement(
315 _: Statement,
316 _: JudgementContext,
317 _: Callback<(Self::Ticket, JudgementContext, Judgement), C>,
318 ) -> Result<(), DispatchError> {
319 Err(DispatchError::Unavailable)
320 }
321}