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}