pallet_sidechain/
lib.rs

1//! Pallet that establishes a Partner Chain as a Cardano sidechain
2//!
3//! # Purpose of this pallet
4//!
5//! This pallet serves as the starting point for building a Partner Chain runtime.
6//! It stores its genesis UTXO which serves as its global identifier and divides
7//! Partner Chain slots into Partner Chain epochs.
8//!
9//! ## Genesis UTXO
10//!
11//! When a Partner Chain governance is initialized on Cardano, the transaction spends
12//! a special _genesis UTXO_. This UTXO serves multiple crucial roles:
13//! - it serves as the unique identifier of the Partner Chain
14//! - it affects the addresses of all Partner Chain Toolkit's Plutus smart contracts
15//!   to allow multiple Partner Chains to exist
16//! - it is included in messages signed by various participants of the Partner Chain
17//!   when submitting transactions (on both Cardano and the Partner Chain) to prevent
18//!   replay attacks
19//!
20//! The genesis UTXO is immutable throughout the lifetime of a Partner Chain in order
21//! to preserve its identity.
22//!
23//! ## Partner Chain Epochs
24//!
25//! When producing blocks, Partner Chains divide time into slots in which a single block
26//! can be produced. These slots are in turn grouped into epochs which other Partner
27//! Chains features use as boundaries for some of their state transitions (eg. a Partner
28//! Chain block producing committees change at epoch boundaries). Both slot and epoch
29//! durations are constant and immutable throughout a Partner Chain's lifetime.
30//!
31//! # Usage
32//!
33//! ## Prerequisites
34//!
35//! Before a Partner Chain can be started, its Cardano governance must be established by
36//! running the _init_ transaction, which also determines the _genesis UTXO_ of the
37//! Partner Chain. Consult `docs/user-guides/governance/governance.md` for instructions.
38//!
39//! As Partner Chains operate on the basis of slots and epochs, your Substrate node should
40//! use a slot-based consensus mechanism such as Aura.
41//!
42//! ### Optional - defining a new epoch hook
43//!
44//! A Partner Chain may need to perform custom logic when a new epoch starts, eg. pay
45//! out block production rewards, update chain participant standings etc. For this purpose,
46//! the pallet can be configured with a handler that will be triggered during initialization
47//! of each first block of a new epoch.
48//!
49//! To create a new epoch handler, simply define a type and have it implement the [OnNewEpoch] trait:
50//! ```rust
51//! use sidechain_domain::{ ScEpochNumber, ScSlotNumber };
52//! use sp_runtime::Weight;
53//!
54//! struct MyNewEpochHandler;
55//! impl sp_sidechain::OnNewEpoch for MyNewEpochHandler {
56//!     fn on_new_epoch(old_epoch: ScEpochNumber, new_epoch: ScEpochNumber) -> Weight {
57//!         log::info!("Partner Chain epoch changed from {old_epoch} to {new_epoch}");
58//!         Weight::zero()
59//!     }
60//! }
61//! ```
62//! The weight returned by `on_new_epoch` should match its real resource use.
63//!
64//! ## Adding to runtime
65//!
66//! The pallet requires minimal configuration, as it is only mandatory to inject the function
67//! that provides the current slot. Assuming that Aura consensus is used, the pallet can
68//! be configured like the following:
69//! ```rust,ignore
70//! impl pallet_sidechain::Config for Runtime {
71//!     fn current_slot_number() -> ScSlotNumber {
72//!         ScSlotNumber(*pallet_aura::CurrentSlot::<Self>::get())
73//!     }
74//!     type OnNewEpoch = MyNewEpochHandler;
75//! }
76//! ```
77//! Optionally, a new epoch handler can be configured like in the example above. Partner Chains
78//! that do not need to run additional logic at epoch change can use the empty implementation
79//! available for [()]:
80//! ```rust
81//! # struct MyNewEpochHandler;
82//! type OnNewEpoch = MyNewEpochHandler;
83//! ```
84//! If multiple handlers need to be added, a tuple can be used for convenience:
85//! ```rust
86//! # struct NewEpochHandler1;
87//! # struct NewEpochHandler2;
88//! type OnNewEpoch = (NewEpochHandler1, NewEpochHandler2);
89//! ```
90//!
91//! ## Genesis configuration
92//!
93//! After the pallet is added to the runtime, configure it in your rust node code:
94//! ```rust
95//! # use std::str::FromStr;
96//! # use sidechain_domain::UtxoId;
97//! # use sidechain_slots::SlotsPerEpoch;
98//! #
99//! # fn create_genesis_config<Runtime>() -> pallet_sidechain::GenesisConfig<Runtime>
100//! # where Runtime: frame_system::Config + pallet_sidechain::Config
101//! # {
102//! pallet_sidechain::GenesisConfig::<Runtime> {
103//!     genesis_utxo: UtxoId::from_str("0000000000000000000000000000000000000000000000000000000000000000#0").unwrap(),
104//!     slots_per_epoch: SlotsPerEpoch(60),
105//!     ..Default::default()
106//! }
107//! # }
108//! ```
109//! or via a chain spec Json file:
110//! ```json
111//! {
112//!     "sidechain_pallet": {
113//!         "genesisUtxo": "0000000000000000000000000000000000000000000000000000000000000000#0",
114//!         "slotsPerEpoch": 60
115//!     }
116//! }
117//! ```
118#![cfg_attr(not(feature = "std"), no_std)]
119#![deny(missing_docs)]
120
121#[cfg(test)]
122#[allow(missing_docs)]
123pub mod mock;
124#[cfg(test)]
125mod tests;
126
127pub use pallet::*;
128
129#[frame_support::pallet]
130pub mod pallet {
131	use frame_support::pallet_prelude::*;
132	use frame_system::pallet_prelude::BlockNumberFor;
133	use sidechain_domain::UtxoId;
134	use sidechain_domain::{ScEpochNumber, ScSlotNumber};
135	use sp_sidechain::OnNewEpoch;
136
137	#[pallet::pallet]
138	pub struct Pallet<T>(_);
139
140	#[pallet::config]
141	pub trait Config: frame_system::Config {
142		/// Should return the slot number of the current block
143		fn current_slot_number() -> ScSlotNumber;
144
145		/// Handler that is called at initialization of the first block of a new Partner Chain epoch
146		type OnNewEpoch: OnNewEpoch;
147	}
148
149	/// Current epoch number
150	#[pallet::storage]
151	pub(super) type EpochNumber<T: Config> = StorageValue<_, ScEpochNumber, ValueQuery>;
152
153	/// Number of slots per epoch. Currently this value must not change for a running chain.
154	#[pallet::storage]
155	pub(super) type SlotsPerEpoch<T: Config> =
156		StorageValue<_, sidechain_slots::SlotsPerEpoch, ValueQuery>;
157
158	/// Genesis Cardano UTXO of the Partner Chain
159	///
160	/// This is the UTXO that is burned by the transaction that establishes Partner Chain
161	/// governance on Cardano and serves as the identifier of the Partner Chain. It is also
162	/// included in various signed messages to prevent replay attacks on other Partner Chains.
163	#[pallet::storage]
164	pub(super) type GenesisUtxo<T: Config> = StorageValue<_, UtxoId, ValueQuery>;
165
166	impl<T: Config> Pallet<T> {
167		/// Returns the genesis UTXO of the Partner Chain
168		pub fn genesis_utxo() -> UtxoId {
169			GenesisUtxo::<T>::get()
170		}
171
172		/// Returns current epoch number, based on slot number returned by [Config::current_slot_number]
173		pub fn current_epoch_number() -> ScEpochNumber {
174			let current_slot = T::current_slot_number();
175			let slots_per_epoch = Self::slots_per_epoch();
176			slots_per_epoch.epoch_number_from_sc_slot(current_slot)
177		}
178
179		/// Returns the configured number of slots per Partner Chain epoch
180		pub fn slots_per_epoch() -> sidechain_slots::SlotsPerEpoch {
181			SlotsPerEpoch::<T>::get()
182		}
183	}
184
185	#[pallet::genesis_config]
186	#[derive(frame_support::DefaultNoBound)]
187	pub struct GenesisConfig<T: Config> {
188		/// Genesis UTXO of the Partner Chain. This value is immutable.
189		pub genesis_utxo: UtxoId,
190		/// Number of slots ber Partner Chain epoch. This value is immutable.
191		pub slots_per_epoch: sidechain_slots::SlotsPerEpoch,
192		#[serde(skip)]
193		#[allow(missing_docs)]
194		pub _config: sp_std::marker::PhantomData<T>,
195	}
196
197	#[pallet::genesis_build]
198	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
199		fn build(&self) {
200			GenesisUtxo::<T>::put(self.genesis_utxo);
201			SlotsPerEpoch::<T>::put(self.slots_per_epoch);
202		}
203	}
204
205	#[pallet::hooks]
206	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
207		fn on_initialize(n: BlockNumberFor<T>) -> Weight {
208			let real_epoch = Self::current_epoch_number();
209
210			match EpochNumber::<T>::try_get().ok() {
211				Some(saved_epoch) if saved_epoch != real_epoch => {
212					log::info!("⏳ New epoch {real_epoch} starting at block {:?}", n);
213					EpochNumber::<T>::put(real_epoch);
214					<T::OnNewEpoch as OnNewEpoch>::on_new_epoch(saved_epoch, real_epoch)
215						.saturating_add(T::DbWeight::get().reads_writes(2, 1))
216				},
217				None => {
218					log::info!("⏳ Initial epoch {real_epoch} starting at block {:?}", n);
219					EpochNumber::<T>::put(real_epoch);
220					T::DbWeight::get().reads_writes(2, 1)
221				},
222				_ => T::DbWeight::get().reads_writes(2, 0),
223			}
224		}
225	}
226}