Skip to main content

pezpallet_statement/
lib.rs

1// This file is part of Bizinikiwi.
2
3// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! Supporting pezpallet for the statement store.
19//!
20//! - [`Pezpallet`]
21//!
22//! ## Overview
23//!
24//! The Statement pezpallet provides means to create and validate statements for the statement
25//! store.
26//!
27//! For each statement validation function calculates the following three values based on the
28//! statement author balance:
29//! `max_count`: Maximum number of statements allowed for the author (signer) of this statement.
30//! `max_size`: Maximum total size of statements allowed for the author (signer) of this statement.
31//!
32//! This pezpallet also contains an offchain worker that turns on-chain statement events into
33//! statements. These statements are placed in the store and propagated over the network.
34
35#![cfg_attr(not(feature = "std"), no_std)]
36
37use pezframe_support::{
38	pezpallet_prelude::*,
39	pezsp_runtime::{traits::CheckedDiv, SaturatedConversion},
40	traits::fungible::Inspect,
41};
42use pezframe_system::pezpallet_prelude::*;
43use pezsp_statement_store::{
44	runtime_api::{InvalidStatement, StatementSource, ValidStatement},
45	Proof, SignatureVerificationResult, Statement,
46};
47
48#[cfg(test)]
49// We do not declare all features used by `construct_runtime`
50#[allow(unexpected_cfgs)]
51mod mock;
52#[cfg(test)]
53mod tests;
54
55pub use pezpallet::*;
56
57const LOG_TARGET: &str = "runtime::statement";
58
59#[pezframe_support::pezpallet]
60pub mod pezpallet {
61	use super::*;
62
63	pub type BalanceOf<T> =
64		<<T as Config>::Currency as Inspect<<T as pezframe_system::Config>::AccountId>>::Balance;
65
66	pub type AccountIdOf<T> = <T as pezframe_system::Config>::AccountId;
67
68	#[pezpallet::config]
69	pub trait Config: pezframe_system::Config
70	where
71		<Self as pezframe_system::Config>::AccountId: From<pezsp_statement_store::AccountId>,
72	{
73		/// The overarching event type.
74		#[allow(deprecated)]
75		type RuntimeEvent: From<Event<Self>>
76			+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
77		/// The currency which is used to calculate account limits.
78		type Currency: Inspect<Self::AccountId>;
79		/// Min balance for priority statements.
80		#[pezpallet::constant]
81		type StatementCost: Get<BalanceOf<Self>>;
82		/// Cost of data byte used for priority calculation.
83		#[pezpallet::constant]
84		type ByteCost: Get<BalanceOf<Self>>;
85		/// Minimum number of statements allowed per account.
86		#[pezpallet::constant]
87		type MinAllowedStatements: Get<u32>;
88		/// Maximum number of statements allowed per account.
89		#[pezpallet::constant]
90		type MaxAllowedStatements: Get<u32>;
91		/// Minimum data bytes allowed per account.
92		#[pezpallet::constant]
93		type MinAllowedBytes: Get<u32>;
94		/// Maximum data bytes allowed per account.
95		#[pezpallet::constant]
96		type MaxAllowedBytes: Get<u32>;
97	}
98
99	#[pezpallet::pezpallet]
100	pub struct Pezpallet<T>(core::marker::PhantomData<T>);
101
102	#[pezpallet::event]
103	#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
104	pub enum Event<T: Config>
105	where
106		<T as pezframe_system::Config>::AccountId: From<pezsp_statement_store::AccountId>,
107	{
108		/// A new statement is submitted
109		NewStatement { account: T::AccountId, statement: Statement },
110	}
111
112	#[pezpallet::hooks]
113	impl<T: Config> Hooks<BlockNumberFor<T>> for Pezpallet<T>
114	where
115		<T as pezframe_system::Config>::AccountId: From<pezsp_statement_store::AccountId>,
116		pezsp_statement_store::AccountId: From<<T as pezframe_system::Config>::AccountId>,
117		<T as pezframe_system::Config>::RuntimeEvent: From<pezpallet::Event<T>>,
118		<T as pezframe_system::Config>::RuntimeEvent: TryInto<pezpallet::Event<T>>,
119		pezsp_statement_store::BlockHash: From<<T as pezframe_system::Config>::Hash>,
120	{
121		fn offchain_worker(now: BlockNumberFor<T>) {
122			log::trace!(target: LOG_TARGET, "Collecting statements at #{:?}", now);
123			Pezpallet::<T>::collect_statements();
124		}
125	}
126}
127
128impl<T: Config> Pezpallet<T>
129where
130	<T as pezframe_system::Config>::AccountId: From<pezsp_statement_store::AccountId>,
131	pezsp_statement_store::AccountId: From<<T as pezframe_system::Config>::AccountId>,
132	<T as pezframe_system::Config>::RuntimeEvent: From<pezpallet::Event<T>>,
133	<T as pezframe_system::Config>::RuntimeEvent: TryInto<pezpallet::Event<T>>,
134	pezsp_statement_store::BlockHash: From<<T as pezframe_system::Config>::Hash>,
135{
136	/// Validate a statement against current state. This is supposed to be called by the statement
137	/// store on the host side.
138	pub fn validate_statement(
139		_source: StatementSource,
140		mut statement: Statement,
141	) -> Result<ValidStatement, InvalidStatement> {
142		pezsp_io::init_tracing();
143		log::debug!(target: LOG_TARGET, "Validating statement {:?}", statement);
144		let account: T::AccountId = match statement.proof() {
145			Some(Proof::OnChain { who, block_hash, event_index }) => {
146				if pezframe_system::Pezpallet::<T>::parent_hash().as_ref() != block_hash.as_slice()
147				{
148					log::debug!(target: LOG_TARGET, "Bad block hash.");
149					return Err(InvalidStatement::BadProof);
150				}
151				let account: T::AccountId = (*who).into();
152				match pezframe_system::Pezpallet::<T>::event_no_consensus(*event_index as usize) {
153					Some(e) => {
154						statement.remove_proof();
155						if let Ok(Event::NewStatement { account: a, statement: s }) = e.try_into() {
156							if a != account || s != statement {
157								log::debug!(target: LOG_TARGET, "Event data mismatch");
158								return Err(InvalidStatement::BadProof);
159							}
160						} else {
161							log::debug!(target: LOG_TARGET, "Event type mismatch");
162							return Err(InvalidStatement::BadProof);
163						}
164					},
165					_ => {
166						log::debug!(target: LOG_TARGET, "Bad event index");
167						return Err(InvalidStatement::BadProof);
168					},
169				}
170				account
171			},
172			_ => match statement.verify_signature() {
173				SignatureVerificationResult::Valid(account) => account.into(),
174				SignatureVerificationResult::Invalid => {
175					log::debug!(target: LOG_TARGET, "Bad statement signature.");
176					return Err(InvalidStatement::BadProof);
177				},
178				SignatureVerificationResult::NoSignature => {
179					log::debug!(target: LOG_TARGET, "Missing statement signature.");
180					return Err(InvalidStatement::NoProof);
181				},
182			},
183		};
184		let statement_cost = T::StatementCost::get();
185		let byte_cost = T::ByteCost::get();
186		let balance = <T::Currency as Inspect<AccountIdOf<T>>>::balance(&account);
187		let min_allowed_statements = T::MinAllowedStatements::get();
188		let max_allowed_statements = T::MaxAllowedStatements::get();
189		let min_allowed_bytes = T::MinAllowedBytes::get();
190		let max_allowed_bytes = T::MaxAllowedBytes::get();
191		let max_count = balance
192			.checked_div(&statement_cost)
193			.unwrap_or_default()
194			.saturated_into::<u32>()
195			.clamp(min_allowed_statements, max_allowed_statements);
196		let max_size = balance
197			.checked_div(&byte_cost)
198			.unwrap_or_default()
199			.saturated_into::<u32>()
200			.clamp(min_allowed_bytes, max_allowed_bytes);
201
202		Ok(ValidStatement { max_count, max_size })
203	}
204
205	/// Submit a statement event. The statement will be picked up by the offchain worker and
206	/// broadcast to the network.
207	pub fn submit_statement(account: T::AccountId, statement: Statement) {
208		Self::deposit_event(Event::NewStatement { account, statement });
209	}
210
211	fn collect_statements() {
212		// Find `NewStatement` events and submit them to the store
213		for (index, event) in
214			pezframe_system::Pezpallet::<T>::read_events_no_consensus().enumerate()
215		{
216			if let Ok(Event::<T>::NewStatement { account, mut statement }) = event.event.try_into()
217			{
218				if statement.proof().is_none() {
219					let proof = Proof::OnChain {
220						who: account.into(),
221						block_hash: pezframe_system::Pezpallet::<T>::parent_hash().into(),
222						event_index: index as u64,
223					};
224					statement.set_proof(proof);
225				}
226				pezsp_statement_store::runtime_api::statement_store::submit_statement(statement);
227			}
228		}
229	}
230}