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#[cfg(feature = "std")]
133impl MainChainScriptsV1 {
134	/// Reads the main chain script values from environment
135	///
136	/// It expects the following variables to be set:
137	/// - `GOVERNED_MAP_VALIDATOR_ADDRESS`
138	/// - `GOVERNED_MAP_POLICY_ID`
139	pub fn read_from_env() -> Result<Self, envy::Error> {
140		#[derive(serde::Serialize, serde::Deserialize)]
141		pub struct MainChainScriptsEnvConfig {
142			pub governed_map_validator_address: MainchainAddress,
143			pub governed_map_policy_id: PolicyId,
144		}
145
146		let MainChainScriptsEnvConfig { governed_map_validator_address, governed_map_policy_id } =
147			envy::from_env::<MainChainScriptsEnvConfig>()?;
148
149		Ok(Self {
150			validator_address: governed_map_validator_address,
151			asset_policy_id: governed_map_policy_id,
152		})
153	}
154}
155
156/// Error type returned when creating or validating the Governed Map inherent
157#[derive(Decode, Encode, Debug, PartialEq)]
158#[cfg_attr(feature = "std", derive(thiserror::Error))]
159pub enum InherentError {
160	/// Signals that the inherent was expected but not produced
161	#[cfg_attr(feature = "std", error("Inherent missing for Governed Map pallet"))]
162	InherentMissing,
163	/// Signals that the inherent was produced when not expected
164	#[cfg_attr(feature = "std", error("Unexpected inherent for Governed Map pallet"))]
165	InherentNotExpected,
166	#[cfg_attr(
167		feature = "std",
168		error("Data in Governed Map pallet inherent differs from inherent data")
169	)]
170	/// Signals that the inherent produced contains incorrect data
171	IncorrectInherent,
172	#[cfg_attr(feature = "std", error("Governed Map key {0} exceeds size bounds"))]
173	/// Signals that a key in the mapping change list is longer than the configured bound
174	KeyExceedsBounds(String),
175	#[cfg_attr(feature = "std", error("Governed Map value {1:?} for key {0} exceeds size bounds"))]
176	/// Signals that a value in the mapping change list is longer than the configured bound
177	ValueExceedsBounds(String, ByteString),
178	/// Signals that the number of unregistered changes to the mapping exceeds the configured upper limit
179	///
180	/// This should not normally occur if the pallet is configured to accept at least as many changes
181	/// as the planned number of keys in use, or if the number of keys exceeds this limit but the number
182	/// of changes is low enough not to overwhelm a non-stalled chain.
183	#[cfg_attr(feature = "std", error("Number of changes to the Governed Map exceeds the limit"))]
184	TooManyChanges,
185}
186
187impl IsFatalError for InherentError {
188	fn is_fatal_error(&self) -> bool {
189		true
190	}
191}
192
193/// Handler trait for runtime components which need to react to changes in the Governed Map.
194pub trait OnGovernedMappingChange<MaxKeyLength, MaxValueLength>
195where
196	MaxKeyLength: Get<u32>,
197	MaxValueLength: Get<u32>,
198{
199	/// Processes a change to a single governed mapping.
200	fn on_governed_mapping_change(
201		key: BoundedString<MaxKeyLength>,
202		new_value: Option<BoundedVec<u8, MaxValueLength>>,
203		old_value: Option<BoundedVec<u8, MaxValueLength>>,
204	);
205}
206
207impl<MaxKeyLength, MaxValueLength> OnGovernedMappingChange<MaxKeyLength, MaxValueLength> for ()
208where
209	MaxKeyLength: Get<u32>,
210	MaxValueLength: Get<u32>,
211{
212	fn on_governed_mapping_change(
213		_key: BoundedString<MaxKeyLength>,
214		_new_value: Option<BoundedVec<u8, MaxValueLength>>,
215		_old_value: Option<BoundedVec<u8, MaxValueLength>>,
216	) {
217	}
218}
219
220macro_rules! impl_tuple_on_governed_mapping_change {
221    ($first_type:ident, $($type:ident),+) => {
222		impl<MaxKeyLength, MaxValueLength, $first_type, $($type),+>
223			OnGovernedMappingChange<MaxKeyLength, MaxValueLength>
224		for ($first_type, $($type),+) where
225			MaxKeyLength: Get<u32>,
226			MaxValueLength: Get<u32>,
227			$first_type: OnGovernedMappingChange<MaxKeyLength, MaxValueLength>,
228			$($type: OnGovernedMappingChange<MaxKeyLength, MaxValueLength>),+
229		{
230			fn on_governed_mapping_change(
231				key: BoundedString<MaxKeyLength>,
232				new_value: Option<BoundedVec<u8, MaxValueLength>>,
233				old_value: Option<BoundedVec<u8, MaxValueLength>>,
234			) {
235				<$first_type as OnGovernedMappingChange<MaxKeyLength, MaxValueLength>>::on_governed_mapping_change(key.clone(), new_value.clone(), old_value.clone());
236				$(<$type as OnGovernedMappingChange<MaxKeyLength, MaxValueLength>>::on_governed_mapping_change(key.clone(), new_value.clone(), old_value.clone());)+
237			}
238		}
239    };
240}
241
242impl_tuple_on_governed_mapping_change!(A, B);
243impl_tuple_on_governed_mapping_change!(A, B, C);
244impl_tuple_on_governed_mapping_change!(A, B, C, D);
245impl_tuple_on_governed_mapping_change!(A, B, C, D, E);
246
247/// Inherent data produced by the Governed Map observation
248///
249/// List of changes that occured since last observation
250/// Elements are key-value pairs where:
251/// - [None] value indicates deletion
252/// - [Some] value indicates insertion or update
253pub type GovernedMapInherentDataV1 = BTreeMap<String, Option<ByteString>>;
254
255/// Inherent data provider providing the list of Governed Map changes that occurred since previous observation.
256#[cfg(feature = "std")]
257#[derive(Debug, PartialEq)]
258pub enum GovernedMapInherentDataProvider {
259	/// Inactive variant that will not provide any data and will not raise any errors.
260	Inert,
261	/// Active variant that will provide data.
262	ActiveV1 {
263		/// Inherent data provided by this IDP
264		data: GovernedMapInherentDataV1,
265	},
266}
267
268#[cfg(feature = "std")]
269#[async_trait::async_trait]
270impl sp_inherents::InherentDataProvider for GovernedMapInherentDataProvider {
271	async fn provide_inherent_data(
272		&self,
273		inherent_data: &mut sp_inherents::InherentData,
274	) -> Result<(), sp_inherents::Error> {
275		match self {
276			Self::ActiveV1 { data } => {
277				inherent_data.put_data(INHERENT_IDENTIFIER, &data)?;
278			},
279			_ => {},
280		}
281		Ok(())
282	}
283
284	async fn try_handle_error(
285		&self,
286		identifier: &InherentIdentifier,
287		mut error: &[u8],
288	) -> Option<Result<(), sp_inherents::Error>> {
289		if identifier == &INHERENT_IDENTIFIER {
290			let error = InherentError::decode(&mut error).ok()?;
291			Some(Err(sp_inherents::Error::Application(Box::from(error))))
292		} else {
293			None
294		}
295	}
296}
297
298/// Cardano observability data source API used by [GovernedMapInherentDataProvider].
299#[cfg(feature = "std")]
300#[async_trait::async_trait]
301pub trait GovernedMapDataSource {
302	/// Returns all key-value mappings stored in the Governed Map on Cardano after execution of `mc_block`.
303	async fn get_state_at_block(
304		&self,
305		mc_block: McBlockHash,
306		main_chain_scripts: MainChainScriptsV1,
307	) -> Result<BTreeMap<String, ByteString>, Box<dyn std::error::Error + Send + Sync>>;
308
309	/// Queries all changes that occurred in the mappings of the Governed Map on Cardano in the given range of blocks.
310	///
311	/// # Arguments:
312	/// - `since_mc_block`: lower bound (exclusive). If [None], the data source should return all changes since the genesis block.
313	/// - `up_to_block`: upper bound (inclusive).
314	///
315	/// # Return value:
316	/// Items in the returned vector should be key-value pairs representing changes to the Governed Map. Inserts and updates should
317	/// be represented as a [Some] containing the new value, while deletions should be indicated by a [None]. The vector should be
318	/// ordered from the oldest change to the newest.
319	async fn get_mapping_changes(
320		&self,
321		since_mc_block: Option<McBlockHash>,
322		up_to_mc_block: McBlockHash,
323		main_chain_scripts: MainChainScriptsV1,
324	) -> Result<Vec<(String, Option<ByteString>)>, Box<dyn std::error::Error + Send + Sync>>;
325}
326
327/// Error type returned when creation of [GovernedMapInherentDataProvider] fails.
328#[cfg(feature = "std")]
329#[derive(Debug, thiserror::Error)]
330pub enum InherentProviderCreationError {
331	/// Signals that a runtime API call failed
332	#[error("Runtime API call failed: {0}")]
333	ApiError(#[from] sp_api::ApiError),
334	/// Signals that the data source returned an error
335	#[error("Data source call failed: {0}")]
336	DataSourceError(Box<dyn std::error::Error + Send + Sync>),
337	/// Signals that the current pallet version on the chain is higher than supported by the node's IDP
338	#[error("Unsupported pallet version {0} (highest supported version: {1})")]
339	UnsupportedPalletVersion(u32, u32),
340}
341
342#[cfg(feature = "std")]
343impl GovernedMapInherentDataProvider {
344	/// Creates a new [GovernedMapInherentDataProvider] with reference to the passed Cardano block hash.
345	///
346	/// This function is version aware and will return:
347	/// - an inert IDP if the pallet is not present in the runtime
348	/// - an inert IDP if the pallet does not have main chain scripts configured yet
349	/// - an IDP compatible with the pallet version signalled through the runtime API
350	///
351	/// Parameters:
352	/// - `client`: runtime client capable of providing [GovernedMapIDPApi] runtime API
353	/// - `parent_hash`: parent hash of the current block
354	/// - `mc_hash`: Cardano block hash referenced by the current block
355	/// - `parent_mc_hash`: Cardano block hash referenced by the parent of the current block
356	/// - `data_source`: data source implementing [GovernedMapDataSource]
357	pub async fn new<T, Block>(
358		client: &T,
359		parent_hash: Block::Hash,
360		mc_hash: McBlockHash,
361		parent_mc_hash: Option<McBlockHash>,
362		data_source: &(dyn GovernedMapDataSource + Send + Sync),
363	) -> Result<Self, InherentProviderCreationError>
364	where
365		Block: sp_runtime::traits::Block,
366		T: ProvideRuntimeApi<Block> + Send + Sync,
367		T::Api: GovernedMapIDPApi<Block>,
368	{
369		let api = client.runtime_api();
370		if !api.has_api::<dyn GovernedMapIDPApi<Block>>(parent_hash)? {
371			log::info!("💤 Skipping Governed Map observation. Pallet not detected in the runtime.");
372			return Ok(Self::Inert);
373		}
374
375		match api.get_pallet_version(parent_hash)? {
376			1 => Self::new_v1(client, parent_hash, mc_hash, parent_mc_hash, data_source).await,
377			unsupported_version => {
378				Err(InherentProviderCreationError::UnsupportedPalletVersion(unsupported_version, 1))
379			},
380		}
381	}
382
383	async fn new_v1<T, Block>(
384		client: &T,
385		parent_hash: Block::Hash,
386		mc_hash: McBlockHash,
387		parent_mc_hash: Option<McBlockHash>,
388		data_source: &(dyn GovernedMapDataSource + Send + Sync),
389	) -> Result<Self, InherentProviderCreationError>
390	where
391		Block: sp_runtime::traits::Block,
392		T: ProvideRuntimeApi<Block> + Send + Sync,
393		T::Api: GovernedMapIDPApi<Block>,
394	{
395		let api = client.runtime_api();
396
397		let Some(main_chain_script) = api.get_main_chain_scripts(parent_hash)? else {
398			log::info!("💤 Skipping Governed Map observation. Main chain scripts not set yet.");
399			return Ok(Self::Inert);
400		};
401
402		if api.is_initialized(parent_hash)? {
403			let raw_changes = data_source
404				.get_mapping_changes(parent_mc_hash, mc_hash, main_chain_script)
405				.await
406				.map_err(InherentProviderCreationError::DataSourceError)?;
407
408			let mut changes = GovernedMapInherentDataV1::new();
409
410			for (key, value) in raw_changes.into_iter() {
411				changes.insert(key, value);
412			}
413
414			Ok(Self::ActiveV1 { data: changes })
415		} else {
416			let new_state = data_source
417				.get_state_at_block(mc_hash, main_chain_script)
418				.await
419				.map_err(InherentProviderCreationError::DataSourceError)?;
420
421			let current_state = api.get_current_state(parent_hash)?;
422
423			let mut changes = GovernedMapInherentDataV1::new();
424
425			for key in current_state.keys() {
426				if !new_state.contains_key(key) {
427					changes.insert(key.clone(), None);
428				}
429			}
430
431			for (key, value) in new_state.into_iter() {
432				if current_state.get(&key) != Some(&value) {
433					changes.insert(key, Some(value));
434				}
435			}
436
437			Ok(Self::ActiveV1 { data: changes })
438		}
439	}
440}
441
442sp_api::decl_runtime_apis! {
443	/// Runtime API exposing data required for the [GovernedMapInherentDataProvider] to operate.
444	#[api_version(1)]
445	pub trait GovernedMapIDPApi
446	{
447		/// Returns initialization state of the pallet
448		fn is_initialized() -> bool;
449		/// Returns all mappings currently stored in the pallet
450		fn get_current_state() -> BTreeMap<String, ByteString>;
451		/// Returns the main chain scripts currently set in the pallet or [None] otherwise
452		fn get_main_chain_scripts() -> Option<MainChainScriptsV1>;
453		/// Returns the current version of the pallet, 1-based.
454		fn get_pallet_version() -> u32;
455	}
456}