1#[cfg(not(feature = "std"))]
4extern crate alloc;
5
6use crate::pallet::{
7 DomainRuntimeUpgrades, NextRuntimeId, RuntimeRegistry, ScheduledRuntimeUpgrades,
8};
9use crate::{BalanceOf, Config, Event};
10#[cfg(not(feature = "std"))]
11use alloc::string::String;
12#[cfg(not(feature = "std"))]
13use alloc::vec::Vec;
14use domain_runtime_primitives::{AccountId20, MultiAccountId, TryConvertBack};
15use frame_support::{PalletError, ensure};
16use frame_system::AccountInfo;
17use frame_system::pallet_prelude::*;
18use parity_scale_codec::{Decode, Encode};
19use scale_info::TypeInfo;
20use sp_core::Hasher;
21use sp_core::crypto::AccountId32;
22use sp_domains::storage::{RawGenesis, StorageData, StorageKey};
23use sp_domains::{
24 AutoIdDomainRuntimeConfig, DomainId, DomainRuntimeInfo, DomainsDigestItem, RuntimeId,
25 RuntimeObject, RuntimeType,
26};
27use sp_runtime::DigestItem;
28use sp_runtime::traits::{CheckedAdd, Zero};
29use sp_std::vec;
30use sp_version::RuntimeVersion;
31
32#[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
34pub enum Error {
35 FailedToExtractRuntimeVersion,
36 InvalidSpecName,
37 SpecVersionNeedsToIncrease,
38 MaxRuntimeId,
39 MissingRuntimeObject,
40 RuntimeUpgradeAlreadyScheduled,
41 MaxScheduledBlockNumber,
42 FailedToDecodeRawGenesis,
43 RuntimeCodeNotFoundInRawGenesis,
44 InvalidAccountIdType,
45}
46
47#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
48pub struct DomainRuntimeUpgradeEntry<Hash> {
49 pub at_hash: Hash,
51 pub reference_count: u32,
55}
56
57fn derive_initial_balances_storages<T: Config, AccountId: Encode>(
58 total_issuance: BalanceOf<T>,
59 balances: Vec<(AccountId, BalanceOf<T>)>,
60) -> Vec<(StorageKey, StorageData)> {
61 let total_issuance_key = sp_domains::domain_total_issuance_storage_key();
62 let mut initial_storages = vec![(total_issuance_key, StorageData(total_issuance.encode()))];
63 for (account_id, balance) in balances {
64 let account_storage_key = sp_domains::domain_account_storage_key(account_id);
65 let account_info = AccountInfo {
66 nonce: domain_runtime_primitives::Nonce::zero(),
67 consumers: 0,
68 providers: 1,
70 sufficients: 0,
71 data: pallet_balances::AccountData {
72 free: balance,
73 ..Default::default()
74 },
75 };
76 initial_storages.push((account_storage_key, StorageData(account_info.encode())))
77 }
78
79 initial_storages
80}
81
82pub fn into_complete_raw_genesis<T: Config>(
84 runtime_obj: RuntimeObject<BlockNumberFor<T>, T::Hash>,
85 domain_id: DomainId,
86 domain_runtime_info: &DomainRuntimeInfo,
87 total_issuance: BalanceOf<T>,
88 initial_balances: Vec<(MultiAccountId, BalanceOf<T>)>,
89) -> Result<RawGenesis, Error> {
90 let RuntimeObject {
91 mut raw_genesis, ..
92 } = runtime_obj;
93 raw_genesis.set_domain_id(domain_id);
94 match domain_runtime_info {
95 DomainRuntimeInfo::Evm {
96 chain_id,
97 domain_runtime_config,
98 } => {
99 raw_genesis.set_evm_chain_id(*chain_id);
100 if let Some(initial_contract_creation_allow_list) = domain_runtime_config
101 .evm_type
102 .initial_contract_creation_allow_list()
103 {
104 raw_genesis
105 .set_evm_contract_creation_allowed_by(initial_contract_creation_allow_list);
106 }
107
108 let initial_balances = initial_balances.into_iter().try_fold(
109 Vec::<(AccountId20, BalanceOf<T>)>::new(),
110 |mut balances, (account_id, balance)| {
111 let account_id =
112 domain_runtime_primitives::AccountId20Converter::try_convert_back(
113 account_id,
114 )
115 .ok_or(Error::InvalidAccountIdType)?;
116
117 balances.push((account_id, balance));
118 Ok(balances)
119 },
120 )?;
121 raw_genesis.set_top_storages(derive_initial_balances_storages::<T, _>(
122 total_issuance,
123 initial_balances,
124 ));
125 }
126 DomainRuntimeInfo::AutoId {
127 domain_runtime_config: AutoIdDomainRuntimeConfig {},
128 } => {
129 let initial_balances = initial_balances.into_iter().try_fold(
130 Vec::<(AccountId32, BalanceOf<T>)>::new(),
131 |mut balances, (account_id, balance)| {
132 let account_id =
133 domain_runtime_primitives::AccountIdConverter::try_convert_back(account_id)
134 .ok_or(Error::InvalidAccountIdType)?;
135
136 balances.push((account_id, balance));
137 Ok(balances)
138 },
139 )?;
140 raw_genesis.set_top_storages(derive_initial_balances_storages::<T, _>(
141 total_issuance,
142 initial_balances,
143 ));
144 }
145 }
146
147 Ok(raw_genesis)
148}
149
150#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
151pub struct ScheduledRuntimeUpgrade<Hash> {
152 pub raw_genesis: RawGenesis,
153 pub version: RuntimeVersion,
154 pub hash: Hash,
155}
156
157pub(crate) fn runtime_version(code: &[u8]) -> Result<RuntimeVersion, Error> {
159 sp_io::misc::runtime_version(code)
160 .and_then(|v| RuntimeVersion::decode(&mut &v[..]).ok())
161 .ok_or(Error::FailedToExtractRuntimeVersion)
162}
163
164pub(crate) fn can_upgrade_code(
167 current_version: &RuntimeVersion,
168 update_code: &[u8],
169) -> Result<RuntimeVersion, Error> {
170 let new_version = runtime_version(update_code)?;
171
172 if new_version.spec_name != current_version.spec_name {
173 return Err(Error::InvalidSpecName);
174 }
175
176 if new_version.spec_version <= current_version.spec_version {
177 return Err(Error::SpecVersionNeedsToIncrease);
178 }
179
180 Ok(new_version)
181}
182
183pub(crate) fn do_register_runtime<T: Config>(
185 runtime_name: String,
186 runtime_type: RuntimeType,
187 raw_genesis_storage: Vec<u8>,
188 at: BlockNumberFor<T>,
189) -> Result<RuntimeId, Error> {
190 let raw_genesis: RawGenesis = Decode::decode(&mut raw_genesis_storage.as_slice())
191 .map_err(|_| Error::FailedToDecodeRawGenesis)?;
192
193 let code = raw_genesis
194 .get_runtime_code()
195 .ok_or(Error::RuntimeCodeNotFoundInRawGenesis)?;
196
197 let version = runtime_version(code)?;
198 let runtime_hash = T::Hashing::hash(code);
199 let runtime_id = NextRuntimeId::<T>::get();
200
201 RuntimeRegistry::<T>::insert(
202 runtime_id,
203 RuntimeObject {
204 runtime_name,
205 runtime_type,
206 hash: runtime_hash,
207 raw_genesis,
208 version,
209 created_at: at,
210 updated_at: at,
211 runtime_upgrades: 0u32,
212 instance_count: 0,
213 },
214 );
215
216 let next_runtime_id = runtime_id.checked_add(1).ok_or(Error::MaxRuntimeId)?;
217 NextRuntimeId::<T>::set(next_runtime_id);
218
219 Ok(runtime_id)
220}
221
222pub(crate) fn register_runtime_at_genesis<T: Config>(
225 runtime_name: String,
226 runtime_type: RuntimeType,
227 runtime_version: RuntimeVersion,
228 raw_genesis_storage: Vec<u8>,
229 at: BlockNumberFor<T>,
230) -> Result<RuntimeId, Error> {
231 let raw_genesis: RawGenesis = Decode::decode(&mut raw_genesis_storage.as_slice())
232 .map_err(|_| Error::FailedToDecodeRawGenesis)?;
233
234 let code = raw_genesis
235 .get_runtime_code()
236 .ok_or(Error::RuntimeCodeNotFoundInRawGenesis)?;
237
238 let runtime_hash = T::Hashing::hash(code);
239 let runtime_id = NextRuntimeId::<T>::get();
240
241 RuntimeRegistry::<T>::insert(
242 runtime_id,
243 RuntimeObject {
244 runtime_name,
245 runtime_type,
246 hash: runtime_hash,
247 raw_genesis,
248 version: runtime_version,
249 created_at: at,
250 updated_at: at,
251 runtime_upgrades: 0u32,
252 instance_count: 0,
253 },
254 );
255
256 let next_runtime_id = runtime_id.checked_add(1).ok_or(Error::MaxRuntimeId)?;
257 NextRuntimeId::<T>::set(next_runtime_id);
258
259 Ok(runtime_id)
260}
261
262pub(crate) fn do_schedule_runtime_upgrade<T: Config>(
264 runtime_id: RuntimeId,
265 raw_genesis_storage: Vec<u8>,
266 current_block_number: BlockNumberFor<T>,
267) -> Result<BlockNumberFor<T>, Error> {
268 let runtime_obj = RuntimeRegistry::<T>::get(runtime_id).ok_or(Error::MissingRuntimeObject)?;
269
270 let new_raw_genesis: RawGenesis = Decode::decode(&mut raw_genesis_storage.as_slice())
271 .map_err(|_| Error::FailedToDecodeRawGenesis)?;
272
273 let new_code = new_raw_genesis
274 .get_runtime_code()
275 .ok_or(Error::RuntimeCodeNotFoundInRawGenesis)?;
276
277 let new_runtime_version = can_upgrade_code(&runtime_obj.version, new_code)?;
278 let new_runtime_hash = T::Hashing::hash(new_code);
279 let scheduled_upgrade = ScheduledRuntimeUpgrade {
280 raw_genesis: new_raw_genesis,
281 version: new_runtime_version,
282 hash: new_runtime_hash,
283 };
284 let scheduled_at = current_block_number
286 .checked_add(&BlockNumberFor::<T>::from(1u32))
287 .ok_or(Error::MaxScheduledBlockNumber)?;
288
289 ensure!(
290 !ScheduledRuntimeUpgrades::<T>::contains_key(scheduled_at, runtime_id),
291 Error::RuntimeUpgradeAlreadyScheduled
292 );
293
294 ScheduledRuntimeUpgrades::<T>::insert(scheduled_at, runtime_id, scheduled_upgrade);
295
296 Ok(scheduled_at)
297}
298
299pub(crate) fn do_upgrade_runtimes<T: Config>(at: BlockNumberFor<T>) {
300 for (runtime_id, scheduled_update) in ScheduledRuntimeUpgrades::<T>::drain_prefix(at) {
301 RuntimeRegistry::<T>::mutate(runtime_id, |maybe_runtime_object| {
302 let runtime_obj = maybe_runtime_object
303 .as_mut()
304 .expect("Runtime object exists since an upgrade is scheduled after verification");
305
306 runtime_obj.raw_genesis = scheduled_update.raw_genesis;
307 runtime_obj.version = scheduled_update.version;
308 runtime_obj.hash = scheduled_update.hash;
309 runtime_obj.runtime_upgrades = runtime_obj.runtime_upgrades.saturating_add(1);
310 runtime_obj.updated_at = at;
311 });
312
313 DomainRuntimeUpgrades::<T>::mutate(|upgrades| upgrades.push(runtime_id));
315
316 frame_system::Pallet::<T>::deposit_log(DigestItem::domain_runtime_upgrade(runtime_id));
318
319 frame_system::Pallet::<T>::deposit_event(<T as Config>::RuntimeEvent::from(
321 Event::DomainRuntimeUpgraded { runtime_id },
322 ));
323 }
324}
325
326#[cfg(test)]
327mod tests {
328 use crate::Error;
329 use crate::pallet::{NextRuntimeId, RuntimeRegistry, ScheduledRuntimeUpgrades};
330 use crate::runtime_registry::Error as RuntimeRegistryError;
331 use crate::tests::{
332 Domains, ReadRuntimeVersion, System, TEST_RUNTIME_APIS, Test, new_test_ext,
333 };
334 use domain_runtime_primitives::Hash;
335 use frame_support::dispatch::RawOrigin;
336 use frame_support::traits::OnInitialize;
337 use frame_support::{assert_err, assert_ok};
338 use parity_scale_codec::{Decode, Encode};
339 use sp_domains::storage::RawGenesis;
340 use sp_domains::{DomainsDigestItem, RuntimeId, RuntimeObject, RuntimeType};
341 use sp_runtime::traits::BlockNumberProvider;
342 use sp_runtime::{Digest, DispatchError};
343 use sp_version::{RuntimeVersion, create_apis_vec};
344
345 #[test]
346 fn create_domain_runtime() {
347 let version = RuntimeVersion {
348 spec_name: "test".into(),
349 impl_name: Default::default(),
350 authoring_version: 0,
351 spec_version: 1,
352 impl_version: 1,
353 apis: Default::default(),
354 transaction_version: 1,
355 system_version: 0,
356 };
357 let read_runtime_version = ReadRuntimeVersion(version.encode());
358
359 let mut ext = new_test_ext();
360 ext.register_extension(sp_core::traits::ReadRuntimeVersionExt::new(
361 read_runtime_version,
362 ));
363 ext.execute_with(|| {
364 let raw_genesis_storage = RawGenesis::dummy(vec![1, 2, 3, 4]).encode();
365 let res = crate::Pallet::<Test>::register_domain_runtime(
366 RawOrigin::Root.into(),
367 "evm".to_owned(),
368 RuntimeType::Evm,
369 raw_genesis_storage,
370 );
371
372 assert_ok!(res);
373 let runtime_obj = RuntimeRegistry::<Test>::get(0).unwrap();
374 assert_eq!(runtime_obj.version, version);
375 assert_eq!(NextRuntimeId::<Test>::get(), 1)
376 })
377 }
378
379 #[test]
380 fn schedule_domain_runtime_upgrade() {
381 let mut ext = new_test_ext();
382 ext.execute_with(|| {
383 RuntimeRegistry::<Test>::insert(
384 0,
385 RuntimeObject {
386 runtime_name: "evm".to_owned(),
387 runtime_type: Default::default(),
388 runtime_upgrades: 0,
389 hash: Default::default(),
390 raw_genesis: RawGenesis::dummy(vec![1, 2, 3, 4]),
391 version: RuntimeVersion {
392 spec_name: "test".into(),
393 spec_version: 1,
394 impl_version: 1,
395 transaction_version: 1,
396 apis: create_apis_vec!(TEST_RUNTIME_APIS),
397 ..Default::default()
398 },
399 created_at: Default::default(),
400 updated_at: Default::default(),
401 instance_count: 0,
402 },
403 );
404
405 NextRuntimeId::<Test>::set(1);
406 });
407
408 let test_data = vec![
409 (
410 "test1",
411 1,
412 Err(Error::<Test>::RuntimeRegistry(
413 RuntimeRegistryError::InvalidSpecName,
414 )),
415 ),
416 (
417 "test",
418 1,
419 Err(Error::<Test>::RuntimeRegistry(
420 RuntimeRegistryError::SpecVersionNeedsToIncrease,
421 )),
422 ),
423 ("test", 2, Ok(())),
424 ];
425
426 for (spec_name, spec_version, expected) in test_data.into_iter() {
427 let version = RuntimeVersion {
428 spec_name: spec_name.into(),
429 spec_version,
430 impl_version: 1,
431 transaction_version: 1,
432 apis: create_apis_vec!(TEST_RUNTIME_APIS),
433 ..Default::default()
434 };
435 let read_runtime_version = ReadRuntimeVersion(version.encode());
436 ext.register_extension(sp_core::traits::ReadRuntimeVersionExt::new(
437 read_runtime_version,
438 ));
439
440 ext.execute_with(|| {
441 frame_system::Pallet::<Test>::set_block_number(100u32);
442 let res = crate::Pallet::<Test>::upgrade_domain_runtime(
443 RawOrigin::Root.into(),
444 0,
445 RawGenesis::dummy(vec![6, 7, 8, 9]).encode(),
446 );
447
448 assert_eq!(res, expected.map_err(DispatchError::from))
449 })
450 }
451
452 ext.execute_with(|| {
454 frame_system::Pallet::<Test>::set_block_number(100u32);
455 let res = crate::Pallet::<Test>::upgrade_domain_runtime(
456 RawOrigin::Root.into(),
457 0,
458 RawGenesis::dummy(vec![6, 7, 8, 9]).encode(),
459 );
460
461 assert_err!(
462 res,
463 Error::<Test>::RuntimeRegistry(
464 RuntimeRegistryError::RuntimeUpgradeAlreadyScheduled
465 )
466 );
467 });
468
469 ext.execute_with(|| {
471 let runtime_obj = RuntimeRegistry::<Test>::get(0).unwrap();
472 assert_eq!(
473 runtime_obj.version,
474 RuntimeVersion {
475 spec_name: "test".into(),
476 spec_version: 1,
477 impl_version: 1,
478 transaction_version: 1,
479 apis: create_apis_vec!(TEST_RUNTIME_APIS),
480 ..Default::default()
481 }
482 );
483 assert_eq!(runtime_obj.runtime_upgrades, 0);
484 assert_eq!(runtime_obj.raw_genesis, RawGenesis::dummy(vec![1, 2, 3, 4]),);
485
486 let block_number = frame_system::Pallet::<Test>::current_block_number();
487 let scheduled_block_number = block_number.checked_add(1).unwrap();
488 let scheduled_upgrade =
489 ScheduledRuntimeUpgrades::<Test>::get(scheduled_block_number, 0).unwrap();
490 assert_eq!(
491 scheduled_upgrade.version,
492 RuntimeVersion {
493 spec_name: "test".into(),
494 spec_version: 2,
495 impl_version: 1,
496 transaction_version: 1,
497 apis: create_apis_vec!(TEST_RUNTIME_APIS),
498 ..Default::default()
499 }
500 )
501 })
502 }
503
504 fn go_to_block(block: u32) {
505 for i in System::block_number() + 1..=block {
506 let parent_hash = if System::block_number() > 1 {
507 let header = System::finalize();
508 header.hash()
509 } else {
510 System::parent_hash()
511 };
512
513 System::reset_events();
514 let digest = sp_runtime::testing::Digest { logs: vec![] };
515 System::initialize(&i, &parent_hash, &digest);
516 Domains::on_initialize(i);
517 }
518 }
519
520 fn fetch_upgraded_runtime_from_digest(digest: Digest) -> Option<RuntimeId> {
521 for log in digest.logs {
522 match log.as_domain_runtime_upgrade() {
523 None => continue,
524 Some(runtime_id) => return Some(runtime_id),
525 }
526 }
527
528 None
529 }
530
531 #[test]
532 fn upgrade_scheduled_domain_runtime() {
533 let mut ext = new_test_ext();
534 let mut version = RuntimeVersion {
535 spec_name: "test".into(),
536 impl_name: Default::default(),
537 authoring_version: 0,
538 spec_version: 1,
539 impl_version: 1,
540 apis: create_apis_vec!(TEST_RUNTIME_APIS),
541 transaction_version: 1,
542 system_version: 0,
543 };
544
545 ext.execute_with(|| {
546 RuntimeRegistry::<Test>::insert(
547 0,
548 RuntimeObject {
549 runtime_name: "evm".to_owned(),
550 runtime_type: Default::default(),
551 runtime_upgrades: 0,
552 hash: Default::default(),
553 raw_genesis: RawGenesis::dummy(vec![1, 2, 3, 4]),
554 version: version.clone(),
555 created_at: Default::default(),
556 updated_at: Default::default(),
557 instance_count: 0,
558 },
559 );
560
561 NextRuntimeId::<Test>::set(1);
562 });
563
564 version.spec_version = 2;
565 let read_runtime_version = ReadRuntimeVersion(version.encode());
566 ext.register_extension(sp_core::traits::ReadRuntimeVersionExt::new(
567 read_runtime_version,
568 ));
569
570 ext.execute_with(|| {
571 let res = crate::Pallet::<Test>::upgrade_domain_runtime(
572 RawOrigin::Root.into(),
573 0,
574 RawGenesis::dummy(vec![6, 7, 8, 9]).encode(),
575 );
576 assert_ok!(res);
577
578 let current_block = frame_system::Pallet::<Test>::current_block_number();
579 let scheduled_block_number = current_block.checked_add(1).unwrap();
580
581 go_to_block(scheduled_block_number);
582 assert_eq!(
583 ScheduledRuntimeUpgrades::<Test>::get(scheduled_block_number, 0),
584 None
585 );
586
587 let runtime_obj = RuntimeRegistry::<Test>::get(0).unwrap();
588 assert_eq!(runtime_obj.version, version);
589 assert_eq!(runtime_obj.created_at, 0);
590 assert_eq!(runtime_obj.updated_at, 1);
591
592 let digest = System::digest();
593 assert_eq!(Some(0), fetch_upgraded_runtime_from_digest(digest))
594 });
595 }
596
597 #[test]
598 fn test_runtime_version_encode_decode_with_core_api() {
599 let runtime_obj = RuntimeObject {
600 runtime_name: "evm".to_owned(),
601 runtime_type: Default::default(),
602 runtime_upgrades: 100,
603 hash: Default::default(),
604 raw_genesis: RawGenesis::dummy(vec![1, 2, 3, 4]),
605 version: RuntimeVersion {
606 spec_name: "test".into(),
607 spec_version: 100,
608 impl_version: 34,
609 transaction_version: 256,
610 apis: create_apis_vec!(TEST_RUNTIME_APIS),
611 ..Default::default()
612 },
613 created_at: 100,
614 updated_at: 200,
615 instance_count: 500,
616 };
617
618 let encoded = runtime_obj.encode();
619 let decoded = RuntimeObject::<u32, Hash>::decode(&mut &encoded[..]).unwrap();
620 assert_eq!(decoded, runtime_obj);
621 }
622
623 #[test]
624 fn test_runtime_version_encode_decode_without_core_api() {
625 let runtime_obj = RuntimeObject {
626 runtime_name: "evm".to_owned(),
627 runtime_type: Default::default(),
628 runtime_upgrades: 100,
629 hash: Default::default(),
630 raw_genesis: RawGenesis::dummy(vec![1, 2, 3, 4]),
631 version: RuntimeVersion {
632 spec_name: "test".into(),
633 spec_version: 100,
634 impl_version: 34,
635 transaction_version: 256,
636 ..Default::default()
637 },
638 created_at: 100,
639 updated_at: 200,
640 instance_count: 500,
641 };
642
643 let encoded = runtime_obj.encode();
644 let decoded = RuntimeObject::<u32, Hash>::decode(&mut &encoded[..]).unwrap();
645 assert_ne!(decoded, runtime_obj);
646 }
647}