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}