sp_governed_map/
lib.rs

1//! # Governed Map primitives
2//!
3//! This crate provides shared types and logic of the Governed Map feature, together with its inherent
4//! data provider logic.
5//!
6//! ## Usage
7//!
8//!    This crate supports operation of `pallet_governed_map`. Consult the pallet's documentation on how to
9//!    include it in the runtime
10//!
11//! ### Adding to the node
12//!
13//! #### Implementing the runtime API
14//!
15//! [GovernedMapInherentDataProvider] requires access to the pallet's configuration via a runtime API.
16//! Assuming the pallet has been added to the runtime and is named `GovernedMap`, the API should be
17//! implemented like this:
18//!
19//! ```rust,ignore
20//! impl sp_governed_map::GovernedMapIDPApi<Block> for Runtime {
21//!     fn get_main_chain_scripts() -> Option<MainChainScriptsV1> {
22//!         GovernedMap::get_main_chain_scripts()
23//!     }
24//!     fn get_pallet_version() -> u32 {
25//!         GovernedMap::get_version()
26//!     }
27//! }
28//! ```
29//!
30//! #### Adding the data source
31//!
32//! [GovernedMapInherentDataProvider] needs a data source implementing the [GovernedMapDataSource] trait.
33//! For nodes using Db-Sync, one is provided by the `partner_chains_db_sync_data_sources` crate. Consult
34//! its documentation for more information.
35//!
36//! #### Adding the inherent data provider
37//!
38//! The [GovernedMapInherentDataProvider] should be added to your IDP stack using [GovernedMapInherentDataProvider::new]
39//! for both block proposal and validation, like so:
40//! ```rust
41//! # use sp_governed_map::*;
42//! # use sidechain_domain::*;
43//! type InherentDataProviders = (/* other IDPs */ GovernedMapInherentDataProvider);
44//! async fn create_idps<T, Block>(
45//!     client: &T,
46//!     governed_map_data_source: &(impl GovernedMapDataSource + Send + Sync),
47//!     parent_hash: Block::Hash,
48//! #   // Arguments below should be provided by the MC Hash IDP from `sidechain_mc_hash` crate
49//! #   mc_hash: McBlockHash,
50//! #   previous_mc_hash: Option<McBlockHash>,
51//! ) -> Result<InherentDataProviders, Box<dyn std::error::Error + Send + Sync>>
52//! where
53//!     Block: sp_runtime::traits::Block,
54//!     T: sp_api::ProvideRuntimeApi<Block> + Send + Sync,
55//!     T::Api: GovernedMapIDPApi<Block> + Send + Sync
56//! {
57//!     /*
58//!      Create other IDPs
59//!      */
60//!     let governed_map = GovernedMapInherentDataProvider::new(
61//!         client,
62//!         parent_hash,
63//!         mc_hash,
64//!         previous_mc_hash,
65//!         governed_map_data_source
66//!     )
67//!     .await?;
68//!     Ok((/* other IDPs */ governed_map))
69//! # }
70//! ```
71//!
72//! Note that it requires access to the current and previous referenced Cardano block hash (`mc_hash` and `previous_mc_hash`).
73//! These are provided by the Partner Chains Toolkit's MC Hash inherent data provider from the `sidechain_mc_hash` crate.
74//!
75//! ## Adding to a running chain
76//!
77//! As with any other feature, if the Governed Map feature is to be added to an already running chain, a strict order
78//! of operations is required:
79//! 1. The node should be updated according to the steps described above, so that the inherent data provider is present
80//!    in the nodes IDP stack for both proposal and verfication.
81//! 2. The updated node binary should be distributed to the block producers who should update their nodes.
82//! 3. A new runtime version should be released with the pallet added according to its documentation and the runtime
83//!    API implemented as described above.
84//! 4. The Governed Map main chain scripts should be set through the `set_main_chain_scripts` extrinsic in the pallet.
85//!
86//! [GovernedMapInherentDataProvider] is version-aware and will stay inactive until the pallet is added and fully configured.
87#![cfg_attr(not(feature = "std"), no_std)]
88#![deny(missing_docs)]
89
90extern crate alloc;
91
92use alloc::fmt::Debug;
93use alloc::string::String;
94use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
95use scale_info::TypeInfo;
96use sidechain_domain::{byte_string::*, *};
97#[cfg(feature = "std")]
98use sp_api::*;
99use sp_inherents::*;
100use sp_runtime::BoundedVec;
101use sp_runtime::traits::Get;
102
103#[cfg(any(test, feature = "mock"))]
104mod mock;
105#[cfg(test)]
106mod tests;
107
108/// Inherent identifier used by the Governed Map pallet
109pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"govrnmap";
110
111/// Cardano identifiers necessary for observation of the Governed Map
112#[derive(
113	Debug,
114	Clone,
115	PartialEq,
116	Eq,
117	TypeInfo,
118	Encode,
119	Decode,
120	DecodeWithMemTracking,
121	MaxEncodedLen,
122	Default,
123)]
124#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
125pub struct MainChainScriptsV1 {
126	/// Cardano address of the Governed Map validator, at which UTXOs containing key-value pairs are located
127	pub validator_address: MainchainAddress,
128	/// Policy of the asset used to mark the UTXOs containing the Governed Map's key-value pairs
129	pub asset_policy_id: PolicyId,
130}
131
132/// Error type returned when creating or validating the Governed Map inherent
133#[derive(Decode, Encode, Debug, PartialEq)]
134#[cfg_attr(feature = "std", derive(thiserror::Error))]
135pub enum InherentError {
136	/// Signals that the inherent was expected but not produced
137	#[cfg_attr(feature = "std", error("Inherent missing for Governed Map pallet"))]
138	InherentMissing,
139	/// Signals that the inherent was produced when not expected
140	#[cfg_attr(feature = "std", error("Unexpected inherent for Governed Map pallet"))]
141	InherentNotExpected,
142	#[cfg_attr(
143		feature = "std",
144		error("Data in Governed Map pallet inherent differs from inherent data")
145	)]
146	/// Signals that the inherent produced contains incorrect data
147	IncorrectInherent,
148	#[cfg_attr(feature = "std", error("Governed Map key {0} exceeds size bounds"))]
149	/// Signals that a key in the mapping change list is longer than the configured bound
150	KeyExceedsBounds(String),
151	#[cfg_attr(feature = "std", error("Governed Map value {1:?} for key {0} exceeds size bounds"))]
152	/// Signals that a value in the mapping change list is longer than the configured bound
153	ValueExceedsBounds(String, ByteString),
154	/// Signals that the number of unregistered changes to the mapping exceeds the configured upper limit
155	///
156	/// This should not normally occur if the pallet is configured to accept at least as many changes
157	/// as the planned number of keys in use, or if the number of keys exceeds this limit but the number
158	/// of changes is low enough not to overwhelm a non-stalled chain.
159	#[cfg_attr(feature = "std", error("Number of changes to the Governed Map exceeds the limit"))]
160	TooManyChanges,
161}
162
163impl IsFatalError for InherentError {
164	fn is_fatal_error(&self) -> bool {
165		true
166	}
167}
168
169/// Handler trait for runtime components which need to react to changes in the Governed Map.
170pub trait OnGovernedMappingChange<MaxKeyLength, MaxValueLength>
171where
172	MaxKeyLength: Get<u32>,
173	MaxValueLength: Get<u32>,
174{
175	/// Processes a change to a single governed mapping.
176	fn on_governed_mapping_change(
177		key: BoundedString<MaxKeyLength>,
178		new_value: Option<BoundedVec<u8, MaxValueLength>>,
179		old_value: Option<BoundedVec<u8, MaxValueLength>>,
180	);
181}
182
183impl<MaxKeyLength, MaxValueLength> OnGovernedMappingChange<MaxKeyLength, MaxValueLength> for ()
184where
185	MaxKeyLength: Get<u32>,
186	MaxValueLength: Get<u32>,
187{
188	fn on_governed_mapping_change(
189		_key: BoundedString<MaxKeyLength>,
190		_new_value: Option<BoundedVec<u8, MaxValueLength>>,
191		_old_value: Option<BoundedVec<u8, MaxValueLength>>,
192	) {
193	}
194}
195
196macro_rules! impl_tuple_on_governed_mapping_change {
197    ($first_type:ident, $($type:ident),+) => {
198		impl<MaxKeyLength, MaxValueLength, $first_type, $($type),+>
199			OnGovernedMappingChange<MaxKeyLength, MaxValueLength>
200		for ($first_type, $($type),+) where
201			MaxKeyLength: Get<u32>,
202			MaxValueLength: Get<u32>,
203			$first_type: OnGovernedMappingChange<MaxKeyLength, MaxValueLength>,
204			$($type: OnGovernedMappingChange<MaxKeyLength, MaxValueLength>),+
205		{
206			fn on_governed_mapping_change(
207				key: BoundedString<MaxKeyLength>,
208				new_value: Option<BoundedVec<u8, MaxValueLength>>,
209				old_value: Option<BoundedVec<u8, MaxValueLength>>,
210			) {
211				<$first_type as OnGovernedMappingChange<MaxKeyLength, MaxValueLength>>::on_governed_mapping_change(key.clone(), new_value.clone(), old_value.clone());
212				$(<$type as OnGovernedMappingChange<MaxKeyLength, MaxValueLength>>::on_governed_mapping_change(key.clone(), new_value.clone(), old_value.clone());)+
213			}
214		}
215    };
216}
217
218impl_tuple_on_governed_mapping_change!(A, B);
219impl_tuple_on_governed_mapping_change!(A, B, C);
220impl_tuple_on_governed_mapping_change!(A, B, C, D);
221impl_tuple_on_governed_mapping_change!(A, B, C, D, E);
222
223/// Inherent data produced by the Governed Map observation
224///
225/// List of changes that occured since last observation
226/// Elements are key-value pairs where:
227/// - [None] value indicates deletion
228/// - [Some] value indicates insertion or update
229pub type GovernedMapInherentDataV1 = BTreeMap<String, Option<ByteString>>;
230
231/// Inherent data provider providing the list of Governed Map changes that occurred since previous observation.
232#[cfg(feature = "std")]
233#[derive(Debug, PartialEq)]
234pub enum GovernedMapInherentDataProvider {
235	/// Inactive variant that will not provide any data and will not raise any errors.
236	Inert,
237	/// Active variant that will provide data.
238	ActiveV1 {
239		/// Inherent data provided by this IDP
240		data: GovernedMapInherentDataV1,
241	},
242}
243
244#[cfg(feature = "std")]
245#[async_trait::async_trait]
246impl sp_inherents::InherentDataProvider for GovernedMapInherentDataProvider {
247	async fn provide_inherent_data(
248		&self,
249		inherent_data: &mut sp_inherents::InherentData,
250	) -> Result<(), sp_inherents::Error> {
251		match self {
252			Self::ActiveV1 { data } => {
253				inherent_data.put_data(INHERENT_IDENTIFIER, &data)?;
254			},
255			_ => {},
256		}
257		Ok(())
258	}
259
260	async fn try_handle_error(
261		&self,
262		identifier: &InherentIdentifier,
263		mut error: &[u8],
264	) -> Option<Result<(), sp_inherents::Error>> {
265		if identifier == &INHERENT_IDENTIFIER {
266			let error = InherentError::decode(&mut error).ok()?;
267			Some(Err(sp_inherents::Error::Application(Box::from(error))))
268		} else {
269			None
270		}
271	}
272}
273
274/// Cardano observability data source API used by [GovernedMapInherentDataProvider].
275#[cfg(feature = "std")]
276#[async_trait::async_trait]
277pub trait GovernedMapDataSource {
278	/// Returns all key-value mappings stored in the Governed Map on Cardano after execution of `mc_block`.
279	async fn get_state_at_block(
280		&self,
281		mc_block: McBlockHash,
282		main_chain_scripts: MainChainScriptsV1,
283	) -> Result<BTreeMap<String, ByteString>, Box<dyn std::error::Error + Send + Sync>>;
284
285	/// Queries all changes that occurred in the mappings of the Governed Map on Cardano in the given range of blocks.
286	///
287	/// # Arguments:
288	/// - `since_mc_block`: lower bound (exclusive). If [None], the data source should return all changes since the genesis block.
289	/// - `up_to_block`: upper bound (inclusive).
290	///
291	/// # Return value:
292	/// Items in the returned vector should be key-value pairs representing changes to the Governed Map. Inserts and updates should
293	/// be represented as a [Some] containing the new value, while deletions should be indicated by a [None]. The vector should be
294	/// ordered from the oldest change to the newest.
295	async fn get_mapping_changes(
296		&self,
297		since_mc_block: Option<McBlockHash>,
298		up_to_mc_block: McBlockHash,
299		main_chain_scripts: MainChainScriptsV1,
300	) -> Result<Vec<(String, Option<ByteString>)>, Box<dyn std::error::Error + Send + Sync>>;
301}
302
303/// Error type returned when creation of [GovernedMapInherentDataProvider] fails.
304#[cfg(feature = "std")]
305#[derive(Debug, thiserror::Error)]
306pub enum InherentProviderCreationError {
307	/// Signals that a runtime API call failed
308	#[error("Runtime API call failed: {0}")]
309	ApiError(#[from] sp_api::ApiError),
310	/// Signals that the data source returned an error
311	#[error("Data source call failed: {0}")]
312	DataSourceError(Box<dyn std::error::Error + Send + Sync>),
313	/// Signals that the current pallet version on the chain is higher than supported by the node's IDP
314	#[error("Unsupported pallet version {0} (highest supported version: {1})")]
315	UnsupportedPalletVersion(u32, u32),
316}
317
318#[cfg(feature = "std")]
319impl GovernedMapInherentDataProvider {
320	/// Creates a new [GovernedMapInherentDataProvider] with reference to the passed Cardano block hash.
321	///
322	/// This function is version aware and will return:
323	/// - an inert IDP if the pallet is not present in the runtime
324	/// - an inert IDP if the pallet does not have main chain scripts configured yet
325	/// - an IDP compatible with the pallet version signalled through the runtime API
326	///
327	/// Parameters:
328	/// - `client`: runtime client capable of providing [GovernedMapIDPApi] runtime API
329	/// - `parent_hash`: parent hash of the current block
330	/// - `mc_hash`: Cardano block hash referenced by the current block
331	/// - `parent_mc_hash`: Cardano block hash referenced by the parent of the current block
332	/// - `data_source`: data source implementing [GovernedMapDataSource]
333	pub async fn new<T, Block>(
334		client: &T,
335		parent_hash: Block::Hash,
336		mc_hash: McBlockHash,
337		parent_mc_hash: Option<McBlockHash>,
338		data_source: &(dyn GovernedMapDataSource + Send + Sync),
339	) -> Result<Self, InherentProviderCreationError>
340	where
341		Block: sp_runtime::traits::Block,
342		T: ProvideRuntimeApi<Block> + Send + Sync,
343		T::Api: GovernedMapIDPApi<Block>,
344	{
345		let api = client.runtime_api();
346		if !api.has_api::<dyn GovernedMapIDPApi<Block>>(parent_hash)? {
347			log::info!("💤 Skipping Governed Map observation. Pallet not detected in the runtime.");
348			return Ok(Self::Inert);
349		}
350
351		match api.get_pallet_version(parent_hash)? {
352			1 => Self::new_v1(client, parent_hash, mc_hash, parent_mc_hash, data_source).await,
353			unsupported_version => {
354				Err(InherentProviderCreationError::UnsupportedPalletVersion(unsupported_version, 1))
355			},
356		}
357	}
358
359	async fn new_v1<T, Block>(
360		client: &T,
361		parent_hash: Block::Hash,
362		mc_hash: McBlockHash,
363		parent_mc_hash: Option<McBlockHash>,
364		data_source: &(dyn GovernedMapDataSource + Send + Sync),
365	) -> Result<Self, InherentProviderCreationError>
366	where
367		Block: sp_runtime::traits::Block,
368		T: ProvideRuntimeApi<Block> + Send + Sync,
369		T::Api: GovernedMapIDPApi<Block>,
370	{
371		let api = client.runtime_api();
372
373		let Some(main_chain_script) = api.get_main_chain_scripts(parent_hash)? else {
374			log::info!("💤 Skipping Governed Map observation. Main chain scripts not set yet.");
375			return Ok(Self::Inert);
376		};
377
378		if api.is_initialized(parent_hash)? {
379			let raw_changes = data_source
380				.get_mapping_changes(parent_mc_hash, mc_hash, main_chain_script)
381				.await
382				.map_err(InherentProviderCreationError::DataSourceError)?;
383
384			let mut changes = GovernedMapInherentDataV1::new();
385
386			for (key, value) in raw_changes.into_iter() {
387				changes.insert(key, value);
388			}
389
390			Ok(Self::ActiveV1 { data: changes })
391		} else {
392			let new_state = data_source
393				.get_state_at_block(mc_hash, main_chain_script)
394				.await
395				.map_err(InherentProviderCreationError::DataSourceError)?;
396
397			let current_state = api.get_current_state(parent_hash)?;
398
399			let mut changes = GovernedMapInherentDataV1::new();
400
401			for key in current_state.keys() {
402				if !new_state.contains_key(key) {
403					changes.insert(key.clone(), None);
404				}
405			}
406
407			for (key, value) in new_state.into_iter() {
408				if current_state.get(&key) != Some(&value) {
409					changes.insert(key, Some(value));
410				}
411			}
412
413			Ok(Self::ActiveV1 { data: changes })
414		}
415	}
416}
417
418sp_api::decl_runtime_apis! {
419	/// Runtime API exposing data required for the [GovernedMapInherentDataProvider] to operate.
420	#[api_version(1)]
421	pub trait GovernedMapIDPApi
422	{
423		/// Returns initialization state of the pallet
424		fn is_initialized() -> bool;
425		/// Returns all mappings currently stored in the pallet
426		fn get_current_state() -> BTreeMap<String, ByteString>;
427		/// Returns the main chain scripts currently set in the pallet or [None] otherwise
428		fn get_main_chain_scripts() -> Option<MainChainScriptsV1>;
429		/// Returns the current version of the pallet, 1-based.
430		fn get_pallet_version() -> u32;
431	}
432}