1
// Copyright 2024 Moonbeam foundation
2
// This file is part of Moonbeam.
3

            
4
// Moonbeam 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
// Moonbeam 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 Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16

            
17
//! # Lazy Migration Pallet
18

            
19
#![allow(non_camel_case_types)]
20
#![cfg_attr(not(feature = "std"), no_std)]
21

            
22
#[cfg(test)]
23
mod mock;
24
#[cfg(test)]
25
mod tests;
26

            
27
pub mod weights;
28
pub use weights::WeightInfo;
29

            
30
use frame_support::pallet;
31

            
32
pub use pallet::*;
33

            
34
const MAX_CONTRACT_CODE_SIZE: u64 = 25 * 1024;
35

            
36
157
#[pallet]
37
pub mod pallet {
38
	use super::*;
39
	use cumulus_primitives_storage_weight_reclaim::get_proof_size;
40
	use frame_support::pallet_prelude::*;
41
	use frame_system::pallet_prelude::*;
42
	use sp_core::H160;
43

            
44
	pub const ARRAY_LIMIT: u32 = 1000;
45
	pub type GetArrayLimit = ConstU32<ARRAY_LIMIT>;
46

            
47
	/// Pallet for multi block migrations
48
30
	#[pallet::pallet]
49
	pub struct Pallet<T>(PhantomData<T>);
50

            
51
1148
	#[pallet::storage]
52
	pub(crate) type StateMigrationStatusValue<T: Config> =
53
		StorageValue<_, (StateMigrationStatus, u64), ValueQuery>;
54

            
55
	pub(crate) type StorageKey = BoundedVec<u8, ConstU32<1_024>>;
56

            
57
228
	#[derive(Clone, Encode, Decode, scale_info::TypeInfo, PartialEq, Eq, MaxEncodedLen, Debug)]
58
	pub enum StateMigrationStatus {
59
1
		NotStarted,
60
207
		Started(StorageKey),
61
1
		Error(BoundedVec<u8, ConstU32<1024>>),
62
200
		Complete,
63
	}
64

            
65
	impl Default for StateMigrationStatus {
66
111
		fn default() -> Self {
67
111
			return StateMigrationStatus::NotStarted;
68
111
		}
69
	}
70

            
71
	/// Configuration trait of this pallet.
72
	#[pallet::config]
73
	pub trait Config: frame_system::Config + pallet_evm::Config + pallet_balances::Config {
74
		type WeightInfo: WeightInfo;
75
	}
76

            
77
4
	#[pallet::error]
78
	pub enum Error<T> {
79
		/// The contract already have metadata
80
		ContractMetadataAlreadySet,
81
		/// Contract not exist
82
		ContractNotExist,
83
		/// The key lengths exceeds the maximum allowed
84
		KeyTooLong,
85
	}
86

            
87
	pub(crate) const MAX_ITEM_PROOF_SIZE: u64 = 30 * 1024; // 30 KB
88
	pub(crate) const PROOF_SIZE_BUFFER: u64 = 100 * 1024; // 100 KB
89

            
90
70
	#[pallet::hooks]
91
	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
92
231
		fn on_idle(_n: BlockNumberFor<T>, remaining_weight: Weight) -> Weight {
93
231
			let proof_size_before: u64 = get_proof_size().unwrap_or(0);
94
231
			let res = Pallet::<T>::handle_migration(remaining_weight);
95
231
			let proof_size_after: u64 = get_proof_size().unwrap_or(0);
96
231
			let proof_size_diff = proof_size_after.saturating_sub(proof_size_before);
97
231

            
98
231
			Weight::from_parts(0, proof_size_diff)
99
231
				.saturating_add(T::DbWeight::get().reads_writes(res.reads, res.writes))
100
231
		}
101
	}
102

            
103
	#[derive(Default, Clone, PartialEq, Eq, Encode, Decode, Debug)]
104
	pub(crate) struct ReadWriteOps {
105
		pub reads: u64,
106
		pub writes: u64,
107
	}
108

            
109
	impl ReadWriteOps {
110
260
		pub fn new() -> Self {
111
260
			Self {
112
260
				reads: 0,
113
260
				writes: 0,
114
260
			}
115
260
		}
116

            
117
259
		pub fn add_one_read(&mut self) {
118
259
			self.reads += 1;
119
259
		}
120

            
121
159
		pub fn add_one_write(&mut self) {
122
159
			self.writes += 1;
123
159
		}
124

            
125
262
		pub fn add_reads(&mut self, reads: u64) {
126
262
			self.reads += reads;
127
262
		}
128

            
129
159
		pub fn add_writes(&mut self, writes: u64) {
130
159
			self.writes += writes;
131
159
		}
132
	}
133

            
134
	#[derive(Clone)]
135
	struct StateMigrationResult {
136
		last_key: Option<StorageKey>,
137
		error: Option<&'static str>,
138
		migrated: u64,
139
		reads: u64,
140
		writes: u64,
141
	}
142

            
143
	enum NextKeyResult {
144
		NextKey(StorageKey),
145
		NoMoreKeys,
146
		Error(&'static str),
147
	}
148

            
149
	impl<T: Config> Pallet<T> {
150
		/// Handle the migration of the storage keys, returns the number of read and write operations
151
231
		pub(crate) fn handle_migration(remaining_weight: Weight) -> ReadWriteOps {
152
231
			let mut read_write_ops = ReadWriteOps::new();
153
231

            
154
231
			// maximum number of items that can be migrated in one block
155
231
			let migration_limit = remaining_weight
156
231
				.proof_size()
157
231
				.saturating_sub(PROOF_SIZE_BUFFER)
158
231
				.saturating_div(MAX_ITEM_PROOF_SIZE);
159
231

            
160
231
			if migration_limit == 0 {
161
1
				return read_write_ops;
162
230
			}
163
230

            
164
230
			let (status, mut migrated_keys) = StateMigrationStatusValue::<T>::get();
165
230
			read_write_ops.add_one_read();
166

            
167
230
			let next_key = match &status {
168
27
				StateMigrationStatus::NotStarted => Default::default(),
169
103
				StateMigrationStatus::Started(storage_key) => {
170
103
					let (reads, next_key_result) = Pallet::<T>::get_next_key(storage_key);
171
103
					read_write_ops.add_reads(reads);
172
103
					match next_key_result {
173
103
						NextKeyResult::NextKey(next_key) => next_key,
174
						NextKeyResult::NoMoreKeys => {
175
							StateMigrationStatusValue::<T>::put((
176
								StateMigrationStatus::Complete,
177
								migrated_keys,
178
							));
179
							read_write_ops.add_one_write();
180
							return read_write_ops;
181
						}
182
						NextKeyResult::Error(e) => {
183
							StateMigrationStatusValue::<T>::put((
184
								StateMigrationStatus::Error(
185
									e.as_bytes().to_vec().try_into().unwrap_or_default(),
186
								),
187
								migrated_keys,
188
							));
189
							read_write_ops.add_one_write();
190
							return read_write_ops;
191
						}
192
					}
193
				}
194
				StateMigrationStatus::Complete | StateMigrationStatus::Error(_) => {
195
100
					return read_write_ops;
196
				}
197
			};
198

            
199
130
			let res = Pallet::<T>::migrate_keys(next_key, migration_limit);
200
130
			migrated_keys += res.migrated;
201
130
			read_write_ops.add_reads(res.reads);
202
130
			read_write_ops.add_writes(res.writes);
203
130

            
204
130
			match (res.last_key, res.error) {
205
25
				(None, None) => {
206
25
					StateMigrationStatusValue::<T>::put((
207
25
						StateMigrationStatus::Complete,
208
25
						migrated_keys,
209
25
					));
210
25
					read_write_ops.add_one_write();
211
25
				}
212
				// maybe we should store the previous key in the storage as well
213
				(_, Some(e)) => {
214
					StateMigrationStatusValue::<T>::put((
215
						StateMigrationStatus::Error(
216
							e.as_bytes().to_vec().try_into().unwrap_or_default(),
217
						),
218
						migrated_keys,
219
					));
220
					read_write_ops.add_one_write();
221
				}
222
105
				(Some(key), None) => {
223
105
					StateMigrationStatusValue::<T>::put((
224
105
						StateMigrationStatus::Started(key),
225
105
						migrated_keys,
226
105
					));
227
105
					read_write_ops.add_one_write();
228
105
				}
229
			}
230

            
231
130
			read_write_ops
232
231
		}
233

            
234
		/// Tries to get the next key in the storage, returns None if there are no more keys to migrate.
235
		/// Returns an error if the key is too long.
236
11073
		fn get_next_key(key: &StorageKey) -> (u64, NextKeyResult) {
237
11073
			if let Some(next) = sp_io::storage::next_key(key) {
238
11048
				let next: Result<StorageKey, _> = next.try_into();
239
11048
				match next {
240
11048
					Ok(next_key) => {
241
11048
						if next_key.as_slice() == sp_core::storage::well_known_keys::CODE {
242
25
							let (reads, next_key_res) = Pallet::<T>::get_next_key(&next_key);
243
25
							return (1 + reads, next_key_res);
244
11023
						}
245
11023
						(1, NextKeyResult::NextKey(next_key))
246
					}
247
					Err(_) => (1, NextKeyResult::Error("Key too long")),
248
				}
249
			} else {
250
25
				(1, NextKeyResult::NoMoreKeys)
251
			}
252
11073
		}
253

            
254
		/// Migrate maximum of `limit` keys starting from `start`, returns the next key to migrate
255
		/// Returns None if there are no more keys to migrate.
256
		/// Returns an error if an error occurred during migration.
257
130
		fn migrate_keys(start: StorageKey, limit: u64) -> StateMigrationResult {
258
130
			let mut key = start;
259
130
			let mut migrated = 0;
260
130
			let mut next_key_reads = 0;
261
130
			let mut writes = 0;
262

            
263
11155
			while migrated < limit {
264
11050
				let data = sp_io::storage::get(&key);
265
11050
				if let Some(data) = data {
266
11023
					sp_io::storage::set(&key, &data);
267
11023
					writes += 1;
268
11023
				}
269

            
270
11050
				migrated += 1;
271
11050

            
272
11050
				if migrated < limit {
273
10945
					let (reads, next_key_res) = Pallet::<T>::get_next_key(&key);
274
10945
					next_key_reads += reads;
275
10945

            
276
10945
					match next_key_res {
277
10920
						NextKeyResult::NextKey(next_key) => {
278
10920
							key = next_key;
279
10920
						}
280
						NextKeyResult::NoMoreKeys => {
281
25
							return StateMigrationResult {
282
25
								last_key: None,
283
25
								error: None,
284
25
								migrated,
285
25
								reads: migrated + next_key_reads,
286
25
								writes,
287
25
							};
288
						}
289
						NextKeyResult::Error(e) => {
290
							return StateMigrationResult {
291
								last_key: Some(key),
292
								error: Some(e),
293
								migrated,
294
								reads: migrated + next_key_reads,
295
								writes,
296
							};
297
						}
298
					};
299
105
				}
300
			}
301

            
302
105
			StateMigrationResult {
303
105
				last_key: Some(key),
304
105
				error: None,
305
105
				migrated,
306
105
				reads: migrated + next_key_reads,
307
105
				writes,
308
105
			}
309
130
		}
310
	}
311

            
312
	#[pallet::call]
313
	impl<T: Config> Pallet<T> {
314
		#[pallet::call_index(2)]
315
		#[pallet::weight(Pallet::<T>::create_contract_metadata_weight(MAX_CONTRACT_CODE_SIZE))]
316
		pub fn create_contract_metadata(
317
			origin: OriginFor<T>,
318
			address: H160,
319
3
		) -> DispatchResultWithPostInfo {
320
3
			ensure_signed(origin)?;
321

            
322
3
			ensure!(
323
3
				pallet_evm::AccountCodesMetadata::<T>::get(address).is_none(),
324
1
				Error::<T>::ContractMetadataAlreadySet
325
			);
326

            
327
			// Ensure contract exist
328
2
			let code = pallet_evm::AccountCodes::<T>::get(address);
329
2
			ensure!(!code.is_empty(), Error::<T>::ContractNotExist);
330

            
331
			// Construct metadata
332
1
			let code_size = code.len() as u64;
333
1
			let code_hash = sp_core::H256::from(sp_io::hashing::keccak_256(&code));
334
1
			let meta = pallet_evm::CodeMetadata {
335
1
				size: code_size,
336
1
				hash: code_hash,
337
1
			};
338
1

            
339
1
			// Set metadata
340
1
			pallet_evm::AccountCodesMetadata::<T>::insert(address, meta);
341
1

            
342
1
			Ok((
343
1
				Some(Self::create_contract_metadata_weight(code_size)),
344
1
				Pays::No,
345
1
			)
346
1
				.into())
347
		}
348
	}
349

            
350
	impl<T: Config> Pallet<T> {
351
1
		fn create_contract_metadata_weight(code_size: u64) -> Weight {
352
1
			// max entry size of AccountCodesMetadata (full key + value)
353
1
			const PROOF_SIZE_CODE_METADATA: u64 = 100;
354
1
			// intermediates nodes might be up to 3Kb
355
1
			const PROOF_SIZE_INTERMEDIATES_NODES: u64 = 3 * 1024;
356
1

            
357
1
			// Account for 2 reads, 1 write
358
1
			<T as frame_system::Config>::DbWeight::get()
359
1
				.reads_writes(2, 1)
360
1
				.set_proof_size(
361
1
					code_size + (PROOF_SIZE_INTERMEDIATES_NODES * 2) + PROOF_SIZE_CODE_METADATA,
362
1
				)
363
1
		}
364
	}
365
}