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