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, 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/// 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#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
48pub struct DomainRuntimeUpgradeEntry<Hash> {
49    // The consensus block hash at which the upgrade happened
50    pub at_hash: Hash,
51    // The expected number of ER (from different domains) that derive from the consensus
52    // block `at_hash`, the `reference_count` will decrease by one as one such ER is
53    // confirmed and the whole entry will remove from the state after it become zero.
54    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 are set to 1 for new accounts
69            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
82// Return a complete raw genesis with runtime code and domain id set properly
83pub 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
157/// Extracts the runtime version of the provided code.
158pub(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
164/// Upgrades current runtime with new runtime.
165// TODO: we can use upstream's `can_set_code` after some adjustments
166pub(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
183/// Registers a new domain runtime..
184pub(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
222// TODO: Remove once `do_register_runtime` works at genesis.
223/// Registers a new domain runtime at genesis.
224pub(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
262/// Schedules a runtime upgrade after `DomainRuntimeUpgradeDelay` from current block number.
263pub(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    // we schedule it in the next consensus block
285    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        // Record the runtime upgrade
314        DomainRuntimeUpgrades::<T>::mutate(|upgrades| upgrades.push(runtime_id));
315
316        // deposit digest log for light clients
317        frame_system::Pallet::<T>::deposit_log(DigestItem::domain_runtime_upgrade(runtime_id));
318
319        // deposit event to signal runtime upgrade is complete
320        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        // will not be able to override an already scheduled upgrade
453        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        // verify upgrade
470        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}