referrerpolicy=no-referrer-when-downgrade

pallet_statement/
lib.rs

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