pallet_governed_map/
lib.rs

1//! Pallet exposing key-value pairs sourced from the Cardano ledger as part of the Governed Map feature.
2//!
3//! # Purpose of this pallet
4//!
5//! This pallet stores the most recent state of the key-value pairs in the Governed Map on Cardano for use
6//! by other runtime components. It also exposes hooks for other components to be notified when particular
7//! key-value pair is inserted, updated or deleted.
8//!
9//! # Usage
10//!
11//! ## Adding to runtime
12//!
13//! ### Defining size limits
14//!
15//! Before adding the pallet to your runtime, first decide on the limits for the data that it will process:
16//!
17//! #### `MaxChanges`
18//! The maximum number of changes that will be processed in one inherent invocation. Changes
19//! are understood as the diff between previously stored mappings and the currently observed
20//! ones, as opposed to raw on-chain events altering those mappings.
21//!
22//! **Important**: This number must be high enough to guarantee the inherent will always have
23//! the capacity required to process incoming changes. If the number of changes exceeds this
24//! limit, [TooManyChanges][InherentError::TooManyChanges] error will be raised stalling block production.
25//!
26//! If this error occurs on a live chain, then the only way of fixing it is to change the
27//! mappings on Cardano close enough to the last state registered in the pallet to bring the
28//! change count below the limit.
29//! Setting this limit above the expected number of keys in use is a safe option.
30//!
31//! #### `MaxKeyLength`
32//! maximum length of keys that can be used.
33//!
34//! Important: If a key is set in the Governed Map that exceeds this lenght limit, the
35//! [KeyExceedsBounds][InherentError::KeyExceedsBounds] error will be raised stalling block production, with
36//! the only recovery path being removal of this key so it is no longer in the change set.
37//!
38//! #### `MaxValueLength`
39//! Maximum length of the value under a key. Same considerations as for `MaxKeyLength` apply.
40//!
41//! #### Defining the values in the code
42//!
43//! Once the limit values are decided, define them in your runtime, like so:
44//! ```rust
45//! frame_support::parameter_types! {
46//!        pub const MaxChanges: u32 = 16;
47//!        pub const MaxKeyLength: u32 = 64;
48//!        pub const MaxValueLength: u32 = 512;
49//! }
50//! ```
51//!
52//! If at any point a need arises to either support higher volume of parameter changes or increase the maximum
53//! length of keys and values in the mappings, it can be achieved through a runtime upgrade that modifies the
54//! pallet's configuration.
55//!
56//! ### Implementing on-change handler
57//!
58//! The pallet allows the runtime implementer to define a handler that will be called for all key-value
59//! changes registered by the pallet. If your runtime needs to react to changes, crate a type implementing
60//! the [OnGovernedMappingChange] trait, eg:
61//! ```rust
62//! # use frame_support::BoundedVec;
63//! # use sidechain_domain::byte_string::BoundedString;
64//! # use sp_core::Get;
65//! struct ChangeHandler;
66//!
67//! impl<MaxKeyLength, MaxValueLength> sp_governed_map::OnGovernedMappingChange<MaxKeyLength, MaxValueLength> for ChangeHandler
68//! where
69//!    MaxKeyLength: Get<u32>,
70//!    MaxValueLength: Get<u32>,
71//! {
72//!    fn on_governed_mapping_change(
73//!        key: BoundedString<MaxKeyLength>,
74//!        new_value: Option<BoundedVec<u8, MaxValueLength>>,
75//!        old_value: Option<BoundedVec<u8, MaxValueLength>>,
76//!    ) {
77//!        log::info!("Governed Map change for key {key}: old value: {old_value:?}, new value: {new_value:?}");
78//!    }
79//! }
80//! ```
81//! If any handling is not needed, a no-op implementation for [()] can be used instead.
82//!
83//! ### Weights and Benchmarking
84//!
85//! The pallet comes with pre-defined weights for its extrinsics that can be used during initial development
86//! through the [SubstrateWeight][crate::weights::SubstrateWeight] type.
87//!
88//! However, since data size limits and the on-change logic both can affect the weights, it is advisable to run
89//! your own benchmark to account for their impact. See the documentation on [crate::benchmarking] for details.
90//!
91//! ### Configuring the pallet
92//!
93//! Once the above are defined, the pallet can finally be added to the runtime and configured like this:
94//!
95//! ```rust,ignore
96//! impl pallet_governed_map::Config for Runtime {
97//!     type MaxChanges = MaxChanges;
98//!     type MaxKeyLength = MaxKeyLength;
99//!     type MaxValueLength = MaxValueLength;
100//!     type WeightInfo = pallet_governed_map::weights::SubstrateWeight<Runtime>;
101//!
102//!     type OnGovernedMappingChange = ChangeHandler;
103//!
104//!     #[cfg(feature = "runtime-benchmarks")]
105//!     type BenchmarkHelper = ();
106//! }
107//! ```
108//!
109//! ### Setting the main chain scripts
110//!
111//! For the data sources to be able to observe the Governed Map state on Cardano, the pallet stores and exposes
112//! relevant addresses and script hashes which are necessary to query main chain state. Their values need to be
113//! set before the feature can be fully functional. How this is done depends on whether the pallet is present from
114//! the genesis block or added later to a live chain.
115//!
116//! #### Configuring the addresses at genesis
117//!
118//! If the pallet is included in the runtime from genesis block, the scripts can be configured in the genesis config
119//! of your runtime:
120//! ```rust
121//! # use sidechain_domain::*;
122//! # use std::str::FromStr;
123//! # fn build_genesis<T: pallet_governed_map::Config>() -> pallet_governed_map::GenesisConfig<T> {
124//! pallet_governed_map::GenesisConfig {
125//!     main_chain_scripts: Some(pallet_governed_map::MainChainScriptsV1 {
126//!         asset_policy_id: PolicyId::from_hex_unsafe("00000000000000000000000000000000000000000000000000000001"),
127//!         validator_address: MainchainAddress::from_str("test_addr1").unwrap(),
128//!     }),
129//!     ..Default::default()
130//! }
131//! # }
132//! ```
133//! At the same time `main_chain_scripts` field is optional and can be set to [None] if you wish to postpone setting
134//! the scripts for whatever reason.
135//!
136//! #### Setting the addresses via extrinsic
137//!
138//! If the pallet is added to a running chain, it will initailly have no main chain scripts set and remain inactive
139//! until they are set. See section "Updating main chain scripts" for more information.
140//!
141//! ## Updating main chain scripts
142//!
143//! To allow the Partner Chain's governance to set and update main chain script values, the pallet provides the
144//! [set_main_chain_scripts][Call::set_main_chain_scripts] extrinsic which updates the script values in its storage.
145//! This extrinsic is required to be run with root access either via the `sudo` pallet or other governance mechanism.
146//!
147//! Every time [set_main_chain_scripts][Call::set_main_chain_scripts] is successfuly invoked, the pallet will update
148//! its tracked Governed Map state to be congruent with the mappings pointed to by the updates scripts on the next
149//! Partner Chain block.
150#![cfg_attr(not(feature = "std"), no_std)]
151#![deny(missing_docs)]
152
153extern crate alloc;
154
155pub use pallet::*;
156pub use sp_governed_map::MainChainScriptsV1;
157
158use crate::alloc::string::{String, ToString};
159use crate::weights::WeightInfo;
160use frame_support::pallet_prelude::*;
161use frame_system::pallet_prelude::*;
162use sidechain_domain::byte_string::*;
163use sp_governed_map::*;
164
165pub mod weights;
166
167#[cfg(test)]
168mod tests;
169
170#[cfg(test)]
171mod mock;
172
173#[cfg(feature = "runtime-benchmarks")]
174pub mod benchmarking;
175
176#[frame_support::pallet]
177pub mod pallet {
178	use super::*;
179
180	#[pallet::pallet]
181	pub struct Pallet<T>(_);
182
183	/// Current pallet version
184	pub const PALLET_VERSION: u32 = 1;
185
186	#[pallet::config]
187	pub trait Config: frame_system::Config {
188		/// Maximum number of changes that can be registered in a single inherent.
189		///
190		/// This value *must* be high enough for all changes to be registered in one block.
191		/// Setting this to a value higher than the total number of parameters in the Governed Map guarantees that.
192		#[pallet::constant]
193		type MaxChanges: Get<u32>;
194
195		/// Maximum length of the key in the Governed Map in bytes.
196		///
197		/// This value *must* be high enough not to be exceeded by any key stored on Cardano.
198		#[pallet::constant]
199		type MaxKeyLength: Get<u32>;
200
201		/// Maximum length of data stored under a single key in the Governed Map
202		///
203		/// This value *must* be high enough not to be exceeded by any value stored on Cardano.
204		#[pallet::constant]
205		type MaxValueLength: Get<u32>;
206
207		/// Handler called for each change in the governed mappings.
208		///
209		/// If your runtime does not need to react to any changes, a no-op implementation for [()] can be used.
210		/// Otherwise, it is advised to benchmark the runtime and use your own weights to include weight consumed
211		/// by the handler.
212		type OnGovernedMappingChange: OnGovernedMappingChange<Self::MaxKeyLength, Self::MaxValueLength>;
213
214		/// Origin for governance calls
215		type MainChainScriptsOrigin: EnsureOrigin<Self::RuntimeOrigin>;
216
217		/// Weight functions for the pallet's extrinsics
218		type WeightInfo: weights::WeightInfo;
219
220		/// Helper functions required by the pallet's benchmarks to construct realistic input data.
221		///
222		/// An implementation for [()] is provided for simplicity. Chains that require more precise weights or
223		/// expect an unusual number of parameter changes should implement this trait themselves in their runtime.
224		#[cfg(feature = "runtime-benchmarks")]
225		type BenchmarkHelper: crate::benchmarking::BenchmarkHelper<Self>;
226	}
227
228	/// Error type used  by this pallet's extrinsics
229	#[pallet::error]
230	pub enum Error<T> {
231		/// Signals that the inherent has been called again in the same block
232		InherentCalledTwice,
233		/// MainChainScript is not set, registration of changes is not allowed
234		MainChainScriptNotSet,
235	}
236
237	/// Governed Map key type
238	pub type MapKey<T> = BoundedString<<T as Config>::MaxKeyLength>;
239	/// Governed Map value type
240	pub type MapValue<T> = BoundedVec<u8, <T as Config>::MaxValueLength>;
241	/// Governed Map change list
242	pub type Changes<T> =
243		BoundedBTreeMap<MapKey<T>, Option<MapValue<T>>, <T as Config>::MaxChanges>;
244
245	/// Stores the initialization state of the pallet
246	///
247	/// The pallet is considered uninitialized if no inherent was executed since the genesis block or
248	/// since the last change of the main chain scripts.
249	#[pallet::storage]
250	pub type Initialized<T: Config> = StorageValue<_, bool, ValueQuery>;
251
252	/// Stores the block number of the last time mapping changes were registered
253	#[pallet::storage]
254	pub type LastUpdateBlock<T: Config> = StorageValue<_, BlockNumberFor<T>, OptionQuery>;
255
256	/// Stores the latest state of the Governed Map that was observed on Cardano.
257	#[pallet::storage]
258	pub type Mapping<T: Config> = StorageMap<_, Twox64Concat, MapKey<T>, MapValue<T>, OptionQuery>;
259
260	/// Cardano address of the Governed Map validator.
261	///
262	/// This address is used by the observability component to query current state of the mapping
263	#[pallet::storage]
264	pub type MainChainScripts<T: Config> = StorageValue<_, MainChainScriptsV1, OptionQuery>;
265
266	#[pallet::genesis_config]
267	#[derive(frame_support::DefaultNoBound)]
268	pub struct GenesisConfig<T: Config> {
269		/// Initial address of the Governed Map validator.
270		///
271		/// If it is left empty, the Governance Map pallet will be inactive until the address is set via extrinsic.
272		pub main_chain_scripts: Option<MainChainScriptsV1>,
273		/// Phantom data marker
274		pub _marker: PhantomData<T>,
275	}
276
277	#[pallet::genesis_build]
278	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
279		fn build(&self) {
280			MainChainScripts::<T>::set(self.main_chain_scripts.clone());
281		}
282	}
283
284	#[pallet::inherent]
285	impl<T: Config> ProvideInherent for Pallet<T> {
286		type Call = Call<T>;
287		type Error = InherentError;
288		const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;
289
290		fn create_inherent(data: &InherentData) -> Option<Self::Call> {
291			Self::create_inherent_or_err(data).expect("Creating Governed Map inherent failed")
292		}
293
294		fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
295			let Some(expected_call) = Self::create_inherent(data) else {
296				return Err(Self::Error::InherentNotExpected);
297			};
298
299			if *call != expected_call {
300				return Err(Self::Error::IncorrectInherent);
301			}
302
303			Ok(())
304		}
305
306		fn is_inherent(call: &Self::Call) -> bool {
307			matches!(call, Call::register_changes { .. })
308		}
309
310		fn is_inherent_required(data: &InherentData) -> Result<Option<Self::Error>, Self::Error> {
311			match Self::decode_inherent_data(data) {
312				None => Ok(None),
313				Some(changes) if changes.is_empty() && Initialized::<T>::get() => Ok(None),
314				Some(_) => Ok(Some(Self::Error::InherentMissing)),
315			}
316		}
317	}
318
319	impl<T: Config> Pallet<T> {
320		fn decode_inherent_data(data: &InherentData) -> Option<GovernedMapInherentDataV1> {
321			data.get_data::<GovernedMapInherentDataV1>(&INHERENT_IDENTIFIER)
322				.expect("Governed Map inherent data is not encoded correctly")
323		}
324
325		fn create_inherent_or_err(data: &InherentData) -> Result<Option<Call<T>>, InherentError> {
326			use InherentError::*;
327
328			let Some(raw_changes) = Self::decode_inherent_data(data) else { return Ok(None) };
329
330			if raw_changes.is_empty() && Initialized::<T>::get() {
331				return Ok(None);
332			}
333
334			if raw_changes.len() > T::MaxChanges::get() as usize {
335				return Err(TooManyChanges);
336			}
337
338			let mut changes = Changes::<T>::new();
339			for (key, new_value) in raw_changes {
340				let new_value = match new_value {
341					Some(new_value) => Some(Self::bound_value(&key, new_value)?),
342					None => None,
343				};
344				let key = Self::bound_key(&key)?;
345				changes.try_insert(key, new_value).expect("Number of changes is below maximum");
346			}
347			Ok(Some(Call::register_changes { changes }))
348		}
349
350		fn bound_key(key: &str) -> Result<MapKey<T>, InherentError> {
351			key.try_into().map_err(|_| InherentError::KeyExceedsBounds(key.into()))
352		}
353
354		fn bound_value(key: &str, value: ByteString) -> Result<MapValue<T>, InherentError> {
355			(value.0.clone().try_into())
356				.map_err(|_| InherentError::ValueExceedsBounds(key.into(), value))
357		}
358	}
359
360	#[pallet::call]
361	impl<T: Config> Pallet<T> {
362		/// Inherent to register any changes in the state of the Governed Map on Cardano compared to the state currently stored in the pallet.
363		#[pallet::call_index(0)]
364		#[pallet::weight((T::WeightInfo::register_changes(changes.len() as u32), DispatchClass::Mandatory))]
365		pub fn register_changes(origin: OriginFor<T>, changes: Changes<T>) -> DispatchResult {
366			ensure_none(origin)?;
367			let current_block = frame_system::Pallet::<T>::block_number();
368			ensure!(
369				LastUpdateBlock::<T>::get().map_or(true, |last_block| last_block < current_block),
370				Error::<T>::InherentCalledTwice
371			);
372			LastUpdateBlock::<T>::put(current_block);
373
374			ensure!(MainChainScripts::<T>::exists(), Error::<T>::MainChainScriptNotSet);
375
376			if Initialized::<T>::get() {
377				log::info!("💾 Registering {} Governed Map changes", changes.len(),);
378			} else {
379				log::info!(
380					"💾 Reinitializing the Governed Map pallet with {} changes",
381					changes.len(),
382				);
383				Initialized::<T>::set(true);
384			}
385
386			for (key, value) in changes {
387				let old_value = Mapping::<T>::get(&key);
388				Mapping::<T>::set(&key, value.clone());
389				T::OnGovernedMappingChange::on_governed_mapping_change(key, value, old_value);
390			}
391
392			Ok(())
393		}
394
395		/// Changes the address of the Governed Map validator used for observation.
396		///
397		/// This extrinsic must be run either using `sudo` or some other chain governance mechanism.
398		#[pallet::call_index(1)]
399		#[pallet::weight((T::WeightInfo::set_main_chain_scripts(), DispatchClass::Normal))]
400		pub fn set_main_chain_scripts(
401			origin: OriginFor<T>,
402			new_main_chain_script: MainChainScriptsV1,
403		) -> DispatchResult {
404			T::MainChainScriptsOrigin::ensure_origin(origin)?;
405			MainChainScripts::<T>::put(new_main_chain_script.clone());
406			Initialized::<T>::set(false);
407			log::info!("🗂️ Governed Map main chain scripts updated to {new_main_chain_script:?}");
408			Ok(())
409		}
410	}
411
412	impl<T: Config> Pallet<T> {
413		/// Returns the value under `key` or [None] otherwise.
414		pub fn get_key_value(key: &MapKey<T>) -> Option<BoundedVec<u8, T::MaxValueLength>> {
415			Mapping::<T>::get(key)
416		}
417
418		/// Returns an iterator over all key-value pairs in the pallet storage.
419		pub fn get_all_key_value_pairs() -> impl Iterator<Item = (MapKey<T>, MapValue<T>)> {
420			Mapping::<T>::iter()
421		}
422
423		/// Returns an iterator over all key-value pairs in the pallet storage, using unbound types.
424		pub fn get_all_key_value_pairs_unbounded() -> impl Iterator<Item = (String, ByteString)> {
425			Self::get_all_key_value_pairs()
426				.map(|(key, value)| (key.to_string(), value.to_vec().into()))
427		}
428
429		/// Returns initialization status of the pallet
430		pub fn is_initialized() -> bool {
431			Initialized::<T>::get()
432		}
433
434		/// Returns current pallet version.
435		pub fn get_version() -> u32 {
436			PALLET_VERSION
437		}
438
439		/// Returns the current main chain scripts
440		pub fn get_main_chain_scripts() -> Option<MainChainScriptsV1> {
441			MainChainScripts::<T>::get()
442		}
443	}
444}