pallet_domains/
runtime_registry.rs

1//! Runtime registry for domains
2
3#[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/// Runtime specific errors
33#[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/// Domain runtime specific information to create domain raw genesis.
48#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
49pub enum DomainRuntimeInfo {
50    Evm {
51        /// The dynamic EVM chain id for this domain.
52        chain_id: EVMChainId,
53        /// The EVM-specific domain runtime config.
54        domain_runtime_config: EvmDomainRuntimeConfig,
55    },
56    AutoId {
57        /// The AutoId-specific domain runtime config.
58        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    /// Returns the inner config as a `DomainRuntimeConfig`.
73    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    /// If this is an EVM runtime, returns the chain id.
87    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    // The consensus block hash at which the upgrade happened
118    pub at_hash: Hash,
119    // The expected number of ER (from differnt domains) that derive from the consensus
120    // block `at_hash`, the `reference_count` will decrease by one as one such ER is
121    // confirmed and the whole entry will remove from the state after it become zero.
122    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 are set to 1 for new accounts
137            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
150// Return a complete raw genesis with runtime code and domain id set properly
151pub 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
225/// Extracts the runtime version of the provided code.
226pub(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
232/// Upgrades current runtime with new runtime.
233// TODO: we can use upstream's `can_set_code` after some adjustments
234pub(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
251/// Registers a new domain runtime..
252pub(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
290// TODO: Remove once `do_register_runtime` works at genesis.
291/// Registers a new domain runtime at genesis.
292pub(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
330/// Schedules a runtime upgrade after `DomainRuntimeUpgradeDelay` from current block number.
331pub(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    // we schedule it in the next consensus block
353    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        // Record the runtime upgrade
382        DomainRuntimeUpgrades::<T>::mutate(|upgrades| upgrades.push(runtime_id));
383
384        // deposit digest log for light clients
385        frame_system::Pallet::<T>::deposit_log(DigestItem::domain_runtime_upgrade(runtime_id));
386
387        // deposit event to signal runtime upgrade is complete
388        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        // will not be able to override an already scheduled upgrade
516        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        // verify upgrade
533        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}