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::mock::{Domains, System, Test};
330    use crate::pallet::{NextRuntimeId, RuntimeRegistry, ScheduledRuntimeUpgrades};
331    use crate::runtime_registry::Error as RuntimeRegistryError;
332    use crate::tests::{ReadRuntimeVersion, TEST_RUNTIME_APIS, new_test_ext};
333    use domain_runtime_primitives::Hash;
334    use frame_support::dispatch::RawOrigin;
335    use frame_support::traits::OnInitialize;
336    use frame_support::{assert_err, assert_ok};
337    use parity_scale_codec::{Decode, Encode};
338    use sp_domains::storage::RawGenesis;
339    use sp_domains::{DomainsDigestItem, RuntimeId, RuntimeObject, RuntimeType};
340    use sp_runtime::traits::BlockNumberProvider;
341    use sp_runtime::{Digest, DispatchError};
342    use sp_version::{RuntimeVersion, create_apis_vec};
343
344    #[test]
345    fn create_domain_runtime() {
346        let version = RuntimeVersion {
347            spec_name: "test".into(),
348            impl_name: Default::default(),
349            authoring_version: 0,
350            spec_version: 1,
351            impl_version: 1,
352            apis: Default::default(),
353            transaction_version: 1,
354            system_version: 0,
355        };
356        let read_runtime_version = ReadRuntimeVersion(version.encode());
357
358        let mut ext = new_test_ext();
359        ext.register_extension(sp_core::traits::ReadRuntimeVersionExt::new(
360            read_runtime_version,
361        ));
362        ext.execute_with(|| {
363            let raw_genesis_storage = RawGenesis::dummy(vec![1, 2, 3, 4]).encode();
364            let res = crate::Pallet::<Test>::register_domain_runtime(
365                RawOrigin::Root.into(),
366                "evm".to_owned(),
367                RuntimeType::Evm,
368                raw_genesis_storage,
369            );
370
371            assert_ok!(res);
372            let runtime_obj = RuntimeRegistry::<Test>::get(0).unwrap();
373            assert_eq!(runtime_obj.version, version);
374            assert_eq!(NextRuntimeId::<Test>::get(), 1)
375        })
376    }
377
378    #[test]
379    fn schedule_domain_runtime_upgrade() {
380        let mut ext = new_test_ext();
381        ext.execute_with(|| {
382            RuntimeRegistry::<Test>::insert(
383                0,
384                RuntimeObject {
385                    runtime_name: "evm".to_owned(),
386                    runtime_type: Default::default(),
387                    runtime_upgrades: 0,
388                    hash: Default::default(),
389                    raw_genesis: RawGenesis::dummy(vec![1, 2, 3, 4]),
390                    version: RuntimeVersion {
391                        spec_name: "test".into(),
392                        spec_version: 1,
393                        impl_version: 1,
394                        transaction_version: 1,
395                        apis: create_apis_vec!(TEST_RUNTIME_APIS),
396                        ..Default::default()
397                    },
398                    created_at: Default::default(),
399                    updated_at: Default::default(),
400                    instance_count: 0,
401                },
402            );
403
404            NextRuntimeId::<Test>::set(1);
405        });
406
407        let test_data = vec![
408            (
409                "test1",
410                1,
411                Err(Error::<Test>::RuntimeRegistry(
412                    RuntimeRegistryError::InvalidSpecName,
413                )),
414            ),
415            (
416                "test",
417                1,
418                Err(Error::<Test>::RuntimeRegistry(
419                    RuntimeRegistryError::SpecVersionNeedsToIncrease,
420                )),
421            ),
422            ("test", 2, Ok(())),
423        ];
424
425        for (spec_name, spec_version, expected) in test_data.into_iter() {
426            let version = RuntimeVersion {
427                spec_name: spec_name.into(),
428                spec_version,
429                impl_version: 1,
430                transaction_version: 1,
431                apis: create_apis_vec!(TEST_RUNTIME_APIS),
432                ..Default::default()
433            };
434            let read_runtime_version = ReadRuntimeVersion(version.encode());
435            ext.register_extension(sp_core::traits::ReadRuntimeVersionExt::new(
436                read_runtime_version,
437            ));
438
439            ext.execute_with(|| {
440                frame_system::Pallet::<Test>::set_block_number(100u32);
441                let res = crate::Pallet::<Test>::upgrade_domain_runtime(
442                    RawOrigin::Root.into(),
443                    0,
444                    RawGenesis::dummy(vec![6, 7, 8, 9]).encode(),
445                );
446
447                assert_eq!(res, expected.map_err(DispatchError::from))
448            })
449        }
450
451        // will not be able to override an already scheduled upgrade
452        ext.execute_with(|| {
453            frame_system::Pallet::<Test>::set_block_number(100u32);
454            let res = crate::Pallet::<Test>::upgrade_domain_runtime(
455                RawOrigin::Root.into(),
456                0,
457                RawGenesis::dummy(vec![6, 7, 8, 9]).encode(),
458            );
459
460            assert_err!(
461                res,
462                Error::<Test>::RuntimeRegistry(
463                    RuntimeRegistryError::RuntimeUpgradeAlreadyScheduled
464                )
465            );
466        });
467
468        // verify upgrade
469        ext.execute_with(|| {
470            let runtime_obj = RuntimeRegistry::<Test>::get(0).unwrap();
471            assert_eq!(
472                runtime_obj.version,
473                RuntimeVersion {
474                    spec_name: "test".into(),
475                    spec_version: 1,
476                    impl_version: 1,
477                    transaction_version: 1,
478                    apis: create_apis_vec!(TEST_RUNTIME_APIS),
479                    ..Default::default()
480                }
481            );
482            assert_eq!(runtime_obj.runtime_upgrades, 0);
483            assert_eq!(runtime_obj.raw_genesis, RawGenesis::dummy(vec![1, 2, 3, 4]),);
484
485            let block_number = frame_system::Pallet::<Test>::current_block_number();
486            let scheduled_block_number = block_number.checked_add(1).unwrap();
487            let scheduled_upgrade =
488                ScheduledRuntimeUpgrades::<Test>::get(scheduled_block_number, 0).unwrap();
489            assert_eq!(
490                scheduled_upgrade.version,
491                RuntimeVersion {
492                    spec_name: "test".into(),
493                    spec_version: 2,
494                    impl_version: 1,
495                    transaction_version: 1,
496                    apis: create_apis_vec!(TEST_RUNTIME_APIS),
497                    ..Default::default()
498                }
499            )
500        })
501    }
502
503    fn go_to_block(block: u32) {
504        for i in System::block_number() + 1..=block {
505            let parent_hash = if System::block_number() > 1 {
506                let header = System::finalize();
507                header.hash()
508            } else {
509                System::parent_hash()
510            };
511
512            System::reset_events();
513            let digest = sp_runtime::testing::Digest { logs: vec![] };
514            System::initialize(&i, &parent_hash, &digest);
515            Domains::on_initialize(i);
516        }
517    }
518
519    fn fetch_upgraded_runtime_from_digest(digest: Digest) -> Option<RuntimeId> {
520        for log in digest.logs {
521            match log.as_domain_runtime_upgrade() {
522                None => continue,
523                Some(runtime_id) => return Some(runtime_id),
524            }
525        }
526
527        None
528    }
529
530    #[test]
531    fn upgrade_scheduled_domain_runtime() {
532        let mut ext = new_test_ext();
533        let mut version = RuntimeVersion {
534            spec_name: "test".into(),
535            impl_name: Default::default(),
536            authoring_version: 0,
537            spec_version: 1,
538            impl_version: 1,
539            apis: create_apis_vec!(TEST_RUNTIME_APIS),
540            transaction_version: 1,
541            system_version: 0,
542        };
543
544        ext.execute_with(|| {
545            RuntimeRegistry::<Test>::insert(
546                0,
547                RuntimeObject {
548                    runtime_name: "evm".to_owned(),
549                    runtime_type: Default::default(),
550                    runtime_upgrades: 0,
551                    hash: Default::default(),
552                    raw_genesis: RawGenesis::dummy(vec![1, 2, 3, 4]),
553                    version: version.clone(),
554                    created_at: Default::default(),
555                    updated_at: Default::default(),
556                    instance_count: 0,
557                },
558            );
559
560            NextRuntimeId::<Test>::set(1);
561        });
562
563        version.spec_version = 2;
564        let read_runtime_version = ReadRuntimeVersion(version.encode());
565        ext.register_extension(sp_core::traits::ReadRuntimeVersionExt::new(
566            read_runtime_version,
567        ));
568
569        ext.execute_with(|| {
570            let res = crate::Pallet::<Test>::upgrade_domain_runtime(
571                RawOrigin::Root.into(),
572                0,
573                RawGenesis::dummy(vec![6, 7, 8, 9]).encode(),
574            );
575            assert_ok!(res);
576
577            let current_block = frame_system::Pallet::<Test>::current_block_number();
578            let scheduled_block_number = current_block.checked_add(1).unwrap();
579
580            go_to_block(scheduled_block_number);
581            assert_eq!(
582                ScheduledRuntimeUpgrades::<Test>::get(scheduled_block_number, 0),
583                None
584            );
585
586            let runtime_obj = RuntimeRegistry::<Test>::get(0).unwrap();
587            assert_eq!(runtime_obj.version, version);
588            assert_eq!(runtime_obj.created_at, 0);
589            assert_eq!(runtime_obj.updated_at, 1);
590
591            let digest = System::digest();
592            assert_eq!(Some(0), fetch_upgraded_runtime_from_digest(digest))
593        });
594    }
595
596    #[test]
597    fn test_runtime_version_encode_decode_with_core_api() {
598        let runtime_obj = RuntimeObject {
599            runtime_name: "evm".to_owned(),
600            runtime_type: Default::default(),
601            runtime_upgrades: 100,
602            hash: Default::default(),
603            raw_genesis: RawGenesis::dummy(vec![1, 2, 3, 4]),
604            version: RuntimeVersion {
605                spec_name: "test".into(),
606                spec_version: 100,
607                impl_version: 34,
608                transaction_version: 256,
609                apis: create_apis_vec!(TEST_RUNTIME_APIS),
610                ..Default::default()
611            },
612            created_at: 100,
613            updated_at: 200,
614            instance_count: 500,
615        };
616
617        let encoded = runtime_obj.encode();
618        let decoded = RuntimeObject::<u32, Hash>::decode(&mut &encoded[..]).unwrap();
619        assert_eq!(decoded, runtime_obj);
620    }
621
622    #[test]
623    fn test_runtime_version_encode_decode_without_core_api() {
624        let runtime_obj = RuntimeObject {
625            runtime_name: "evm".to_owned(),
626            runtime_type: Default::default(),
627            runtime_upgrades: 100,
628            hash: Default::default(),
629            raw_genesis: RawGenesis::dummy(vec![1, 2, 3, 4]),
630            version: RuntimeVersion {
631                spec_name: "test".into(),
632                spec_version: 100,
633                impl_version: 34,
634                transaction_version: 256,
635                ..Default::default()
636            },
637            created_at: 100,
638            updated_at: 200,
639            instance_count: 500,
640        };
641
642        let encoded = runtime_obj.encode();
643        let decoded = RuntimeObject::<u32, Hash>::decode(&mut &encoded[..]).unwrap();
644        assert_ne!(decoded, runtime_obj);
645    }
646}