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//! time 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 epochs which other Partner
26//! Chains features use as boundaries for some of their state transitions (eg. a Partner
27//! Chain block producing committees change at epoch boundaries). Epoch duration should
28//! be constant and immutable throughout a Partner Chain's lifetime.
29//!
30//! # Usage
31//!
32//! ## Prerequisites
33//!
34//! Before a Partner Chain can be started, its Cardano governance must be established by
35//! running the _init_ transaction, which also determines the _genesis UTXO_ of the
36//! Partner Chain. Consult `docs/user-guides/governance/governance.md` for instructions.
37//!
38//! ### Optional - defining a new epoch hook
39//!
40//! A Partner Chain may need to perform custom logic when a new epoch starts, eg. pay
41//! out block production rewards, update chain participant standings etc. For this purpose,
42//! the pallet can be configured with a handler that will be triggered during initialization
43//! of each first block of a new epoch.
44//!
45//! To create a new epoch handler, simply define a type and have it implement the [sp_sidechain::OnNewEpoch] trait:
46//! ```rust
47//! use sidechain_domain::ScEpochNumber;
48//! use sp_runtime::Weight;
49//!
50//! struct MyNewEpochHandler;
51//! impl sp_sidechain::OnNewEpoch for MyNewEpochHandler {
52//!     fn on_new_epoch(old_epoch: ScEpochNumber, new_epoch: ScEpochNumber) -> Weight {
53//!         log::info!("Partner Chain epoch changed from {old_epoch} to {new_epoch}");
54//!         Weight::zero()
55//!     }
56//! }
57//! ```
58//! The weight returned by `on_new_epoch` should match its real resource use.
59//!
60//! ## Adding to runtime
61//!
62//! The pallet requires minimal configuration, as it is only mandatory to inject the function
63//! that provides a *reference timestamp*. This timestamp is not required to be the exact timestamp
64//! of the block but must be close enough to it for the purpose of epoch calculation. For example,
65//! a Partner Chain that uses Aura can use the starting time of the current slot as the reference.
66//!
67//! *Important*: The reference timestamp must be available during block initialization. Due to this,
68//!              the timestamp stored by `pallet_timestamp` can not be used as it is only set during
69//!              block execution. Chain builders that wish to use the exact block timestamp as the
70//!              reference timestamp should store it in the pre-runtime digest.
71//!
72//! A complete configuration can like the following:
73//! ```rust,ignore
74//! pub const SLOT_DURATION: u64 = 6000;
75//!
76//! impl pallet_sidechain::Config for Runtime {
77//!     fn reference_timestamp_millis() -> u64 {
78//!         *pallet_aura::CurrentSlot::<Runtime>::get() * SLOT_DURATION
79//!     }
80//!     type OnNewEpoch = MyNewEpochHandler;
81//! }
82//! ```
83//! Optionally, a new epoch handler can be configured like in the example above. Partner Chains
84//! that do not need to run additional logic at epoch change can use the empty implementation
85//! available for [()]:
86//! ```rust
87//! # struct MyNewEpochHandler;
88//! type OnNewEpoch = MyNewEpochHandler;
89//! ```
90//! If multiple handlers need to be added, a tuple can be used for convenience:
91//! ```rust
92//! # struct NewEpochHandler1;
93//! # struct NewEpochHandler2;
94//! type OnNewEpoch = (NewEpochHandler1, NewEpochHandler2);
95//! ```
96//!
97//! ## Genesis configuration
98//!
99//! After the pallet is added to the runtime, configure it in your rust node code:
100//! ```rust
101//! # use std::str::FromStr;
102//! # use sidechain_domain::UtxoId;
103//! #
104//! # fn create_genesis_config<Runtime>() -> pallet_sidechain::GenesisConfig<Runtime>
105//! # where Runtime: frame_system::Config + pallet_sidechain::Config
106//! # {
107//! pallet_sidechain::GenesisConfig::<Runtime> {
108//!     genesis_utxo: UtxoId::from_str("0000000000000000000000000000000000000000000000000000000000000000#0").unwrap(),
109//!     ..Default::default()
110//! }
111//! # }
112//! ```
113//! or via a chain spec Json file:
114//! ```json
115//! {
116//!     "sidechain_pallet": {
117//!         "genesisUtxo": "0000000000000000000000000000000000000000000000000000000000000000#0"
118//!     }
119//! }
120//! ```
121#![cfg_attr(not(feature = "std"), no_std)]
122#![deny(missing_docs)]
123
124#[cfg(test)]
125#[allow(missing_docs)]
126pub mod mock;
127#[cfg(test)]
128mod tests;
129
130/// Storage migrations for previous versions of `pallet-sidechain`
131pub mod migrations;
132
133pub use pallet::*;
134
135#[frame_support::pallet]
136pub mod pallet {
137	use frame_support::pallet_prelude::*;
138	use frame_system::pallet_prelude::BlockNumberFor;
139	use sidechain_domain::ScEpochDuration;
140	use sidechain_domain::ScEpochNumber;
141	use sidechain_domain::UtxoId;
142	use sp_sidechain::OnNewEpoch;
143
144	const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
145
146	#[pallet::pallet]
147	#[pallet::storage_version(STORAGE_VERSION)]
148	pub struct Pallet<T>(_);
149
150	#[pallet::config]
151	pub trait Config: frame_system::Config {
152		/// Should return the reference timestamp of the current block in milliseconds
153		///
154		/// The reference timestamp does not necessarily have to be the exact timestamp of the block
155		/// but must be one close enough for the purpose of epoch calculation, eg. the slot starting
156		/// time in case of chains using a slot-based consensus such as Aura or Babe.
157		///
158		/// Warning: this function must be safe to call during block initialisation, which means that
159		/// in particular block timestamp stored in `pallet_timestamp` can not be used.
160		fn reference_timestamp_millis() -> u64;
161
162		/// Handler that is called at initialization of the first block of a new Partner Chain epoch
163		type OnNewEpoch: OnNewEpoch;
164	}
165
166	/// Current epoch number
167	#[pallet::storage]
168	pub(super) type EpochNumber<T: Config> = StorageValue<_, ScEpochNumber, ValueQuery>;
169
170	/// Partner Chain epoch duration in milliseconds. This value must not be changed.
171	#[pallet::storage]
172	pub(crate) type EpochDurationMillis<T: Config> = StorageValue<_, ScEpochDuration, ValueQuery>;
173
174	/// Number of slots per epoch. Currently this value must not change for a running chain.
175	#[pallet::storage]
176	#[deprecated(
177		since = "1.9.0",
178		note = "This storage is left for migration purposes and will be removed in later version"
179	)]
180	pub(crate) type SlotsPerEpoch<T: Config> = StorageValue<_, u32, ValueQuery>;
181
182	/// Genesis Cardano UTXO of the Partner Chain
183	///
184	/// This is the UTXO that is burned by the transaction that establishes Partner Chain
185	/// governance on Cardano and serves as the identifier of the Partner Chain. It is also
186	/// included in various signed messages to prevent replay attacks on other Partner Chains.
187	#[pallet::storage]
188	pub(super) type GenesisUtxo<T: Config> = StorageValue<_, UtxoId, ValueQuery>;
189
190	impl<T: Config> Pallet<T> {
191		/// Returns the genesis UTXO of the Partner Chain
192		pub fn genesis_utxo() -> UtxoId {
193			GenesisUtxo::<T>::get()
194		}
195
196		/// Returns the Partner Chain epoch duration in milliseconds
197		pub fn epoch_duration_millis() -> u64 {
198			EpochDurationMillis::<T>::get().millis()
199		}
200
201		/// Returns current epoch number
202		pub fn current_epoch_number() -> ScEpochNumber {
203			ScEpochNumber(T::reference_timestamp_millis() / Self::epoch_duration_millis())
204		}
205	}
206
207	#[pallet::genesis_config]
208	#[derive(frame_support::DefaultNoBound)]
209	pub struct GenesisConfig<T: Config> {
210		/// Genesis UTXO of the Partner Chain. This value is immutable.
211		pub genesis_utxo: UtxoId,
212		/// Duration of Partner Chain epoch in milliseconds
213		pub epoch_duration: ScEpochDuration,
214		#[serde(skip)]
215		#[allow(missing_docs)]
216		pub _config: sp_std::marker::PhantomData<T>,
217	}
218
219	#[pallet::genesis_build]
220	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
221		fn build(&self) {
222			GenesisUtxo::<T>::put(self.genesis_utxo);
223			EpochDurationMillis::<T>::put(self.epoch_duration);
224		}
225	}
226
227	#[pallet::hooks]
228	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
229		fn on_initialize(n: BlockNumberFor<T>) -> Weight {
230			let real_epoch = Self::current_epoch_number();
231
232			match EpochNumber::<T>::try_get().ok() {
233				Some(saved_epoch) if saved_epoch != real_epoch => {
234					log::info!("⏳ New epoch {real_epoch} starting at block {:?}", n);
235					EpochNumber::<T>::put(real_epoch);
236					<T::OnNewEpoch as OnNewEpoch>::on_new_epoch(saved_epoch, real_epoch)
237						.saturating_add(T::DbWeight::get().reads_writes(2, 1))
238				},
239				None => {
240					log::info!("⏳ Initial epoch {real_epoch} starting at block {:?}", n);
241					EpochNumber::<T>::put(real_epoch);
242					T::DbWeight::get().reads_writes(2, 1)
243				},
244				_ => T::DbWeight::get().reads_writes(2, 0),
245			}
246		}
247	}
248}