1
// Copyright Moonsong Labs
2
// This file is part of Moonkit.
3

            
4
// Moonkit is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8

            
9
// Moonkit is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13

            
14
// You should have received a copy of the GNU General Public License
15
// along with Moonkit.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
//! # Relay Storage Roots Pallet
18
//!
19
//! This pallet stores the latest `MaxStorageRoots` relay storage roots, which can be used to
20
//! verify state proofs against an old state of the relay chain.
21
//!
22
//! This is useful when the proof needs to be generated by an end user, because
23
//! by the time their transaction is included in a block, the latest relay
24
//! block will probably have changed and therefore the proof will be invalid.
25
//! To avoid that, we expect the user to generate a proof against the latest relay block stored
26
//! in this pallet. This proof will then be valid as long as that relay block is not removed from
27
//! here.
28
//!
29
//! This pallet SHOULD NOT be used for data that can change quickly, because we allow the user to
30
//! submit a proof of an old state. Therefore a valid proof does not imply that the current relay
31
//! state is the expected one.
32
//!
33
//! ### Design
34
//!
35
//! The relay storage roots are inserted in the `on_finalize` hook, so the storage root of the
36
//! current relay block will not be available in the mapping until the next block, but it can be
37
//! read using the `RelaychainStateProvider` at any point after the `on_initialize` of
38
//! `cumulus_pallet_parachain_system`.
39
//!
40
//! One storage root is inserted per parachain block, but there may be more than one relay block in
41
//! between two parachain blocks. In that case, there will be a gap in the `RelayStorageRoot`
42
//! mapping. When creating a proof, users should ensure that they are using the latest storage root
43
//! available in the mapping, otherwise it may not be possible to validate their proof.
44
//!
45
//! The `RelayStorageRoot` mapping is bounded by `MaxStorageRoots`. To ensure that oldest storage
46
//! roots are removed first, there is an additional `RelayStorageRootKeys` storage item that stores
47
//! a sorted list of all the keys. This is needed because it is not possible to iterate over a
48
//! mapping in order (unless if using an `Identity` hash). The `MaxStorageRoots` limit applies to
49
//! the number of items, not to their age. Decreasing the value of `MaxStorageRoots` is a breaking
50
//! change and needs a migration to clean the `RelayStorageRoots` mapping.
51

            
52
#![cfg_attr(not(feature = "std"), no_std)]
53

            
54
pub use crate::weights::WeightInfo;
55
use cumulus_pallet_parachain_system::RelaychainStateProvider;
56
use cumulus_primitives_core::relay_chain::BlockNumber as RelayBlockNumber;
57
use frame_support::pallet;
58
use frame_support::pallet_prelude::*;
59
use frame_system::pallet_prelude::*;
60
pub use pallet::*;
61
use sp_core::Get;
62
use sp_core::H256;
63
use sp_std::collections::vec_deque::VecDeque;
64

            
65
#[cfg(any(test, feature = "runtime-benchmarks"))]
66
mod benchmarks;
67
pub mod weights;
68

            
69
#[cfg(test)]
70
mod mock;
71
#[cfg(test)]
72
mod tests;
73

            
74
#[pallet]
75
pub mod pallet {
76
	use super::*;
77

            
78
	#[pallet::pallet]
79
	pub struct Pallet<T>(PhantomData<T>);
80

            
81
	/// Configuration trait of this pallet.
82
	#[pallet::config]
83
	pub trait Config: frame_system::Config {
84
		type RelaychainStateProvider: RelaychainStateProvider;
85
		/// Limit the number of relay storage roots that will be stored.
86
		/// This limit applies to the number of items, not to their age. Decreasing the value of
87
		/// `MaxStorageRoots` is a breaking change and needs a migration to clean the
88
		/// `RelayStorageRoots` mapping.
89
		#[pallet::constant]
90
		type MaxStorageRoots: Get<u32>;
91
		/// Weight info
92
		type WeightInfo: WeightInfo;
93
	}
94

            
95
	/// Map of relay block number to relay storage root
96
	#[pallet::storage]
97
	pub type RelayStorageRoot<T: Config> =
98
		StorageMap<_, Twox64Concat, RelayBlockNumber, H256, OptionQuery>;
99

            
100
	/// List of all the keys in `RelayStorageRoot`.
101
	/// Used to remove the oldest key without having to iterate over all of them.
102
	#[pallet::storage]
103
	pub type RelayStorageRootKeys<T: Config> =
104
		StorageValue<_, BoundedVec<RelayBlockNumber, T::MaxStorageRoots>, ValueQuery>;
105

            
106
	impl<T: Config> Pallet<T> {
107
		/// Populates `RelayStorageRoot` using `RelaychainStateProvider`.
108
		pub fn set_relay_storage_root() {
109
			let relay_state = T::RelaychainStateProvider::current_relay_chain_state();
110

            
111
			// If this relay block number has already been stored, skip it.
112
			if <RelayStorageRoot<T>>::contains_key(relay_state.number) {
113
				return;
114
			}
115

            
116
			<RelayStorageRoot<T>>::insert(relay_state.number, relay_state.state_root);
117
			let mut keys: VecDeque<_> = <RelayStorageRootKeys<T>>::get().into_inner().into();
118
			if keys.is_empty() && <RelayStorageRootKeys<T>>::exists() {
119
				// If it is empty but it exists in storage, it most likely is corrupted.
120
				log::error!("Corrupted storage `RelayStorageRootKeys`. Need to manually cleanup entries from `RelayStorageRoot` map.");
121
			}
122
			keys.push_back(relay_state.number);
123
			// Delete the oldest stored root if the total number is greater than MaxStorageRoots
124
			if u32::try_from(keys.len()).unwrap_or(u32::MAX) > T::MaxStorageRoots::get() {
125
				if let Some(first_key) = keys.pop_front() {
126
					<RelayStorageRoot<T>>::remove(first_key);
127
				}
128
			}
129

            
130
			let keys = BoundedVec::truncate_from(keys.into());
131
			<RelayStorageRootKeys<T>>::put(keys);
132
		}
133
	}
134

            
135
	#[pallet::hooks]
136
	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
137
		fn on_initialize(_now: BlockNumberFor<T>) -> Weight {
138
			// Account for weight used in on_finalize
139
			T::WeightInfo::set_relay_storage_root()
140
		}
141
		fn on_finalize(_now: BlockNumberFor<T>) {
142
			Self::set_relay_storage_root();
143
		}
144
	}
145
}