1#![cfg_attr(not(feature = "std"), no_std)]
4#![feature(array_windows, variant_count)]
5
6#[cfg(feature = "runtime-benchmarks")]
7mod benchmarking;
8
9#[cfg(test)]
10mod tests;
11
12pub mod block_tree;
13pub mod bundle_storage_fund;
14pub mod domain_registry;
15pub mod extensions;
16mod nominator_position;
17pub mod runtime_registry;
18pub mod staking;
19mod staking_epoch;
20pub mod weights;
21
22extern crate alloc;
23
24use crate::block_tree::{Error as BlockTreeError, verify_execution_receipt};
25use crate::bundle_storage_fund::{charge_bundle_storage_fee, storage_fund_account};
26use crate::domain_registry::{DomainConfig, Error as DomainRegistryError};
27use crate::runtime_registry::into_complete_raw_genesis;
28use crate::staking::OperatorStatus;
29#[cfg(feature = "runtime-benchmarks")]
30pub use crate::staking::do_register_operator;
31use crate::staking_epoch::EpochTransitionResult;
32pub use crate::weights::WeightInfo;
33#[cfg(not(feature = "std"))]
34use alloc::boxed::Box;
35use alloc::collections::btree_map::BTreeMap;
36#[cfg(not(feature = "std"))]
37use alloc::vec::Vec;
38use domain_runtime_primitives::EthereumAccountId;
39use frame_support::dispatch::DispatchResult;
40use frame_support::ensure;
41use frame_support::pallet_prelude::{RuntimeDebug, StorageVersion};
42use frame_support::traits::fungible::{Inspect, InspectHold};
43use frame_support::traits::tokens::{Fortitude, Preservation};
44use frame_support::traits::{EnsureOrigin, Get, Randomness as RandomnessT, Time};
45use frame_support::weights::Weight;
46use frame_system::offchain::SubmitTransaction;
47use frame_system::pallet_prelude::*;
48pub use pallet::*;
49use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
50use scale_info::TypeInfo;
51use sp_consensus_subspace::WrappedPotOutput;
52use sp_consensus_subspace::consensus::is_proof_of_time_valid;
53use sp_core::H256;
54use sp_domains::bundle::{Bundle, BundleVersion, OpaqueBundle};
55use sp_domains::bundle_producer_election::BundleProducerElectionParams;
56use sp_domains::execution_receipt::{
57 ExecutionReceipt, ExecutionReceiptRef, ExecutionReceiptVersion, SealedSingletonReceipt,
58};
59use sp_domains::{
60 BundleAndExecutionReceiptVersion, DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT, DomainBundleLimit,
61 DomainId, DomainInstanceData, EMPTY_EXTRINSIC_ROOT, OperatorId, OperatorPublicKey,
62 OperatorSignature, ProofOfElection, RuntimeId,
63};
64use sp_domains_fraud_proof::fraud_proof::{
65 DomainRuntimeCodeAt, FraudProof, FraudProofVariant, InvalidBlockFeesProof,
66 InvalidDomainBlockHashProof, InvalidTransfersProof,
67};
68use sp_domains_fraud_proof::storage_proof::{self, BasicStorageProof, DomainRuntimeCodeProof};
69use sp_domains_fraud_proof::verification::{
70 verify_invalid_block_fees_fraud_proof, verify_invalid_bundles_fraud_proof,
71 verify_invalid_domain_block_hash_fraud_proof,
72 verify_invalid_domain_extrinsics_root_fraud_proof, verify_invalid_state_transition_fraud_proof,
73 verify_invalid_transfers_fraud_proof, verify_valid_bundle_fraud_proof,
74};
75use sp_runtime::traits::{BlockNumberProvider, CheckedSub, Hash, Header, One, Zero};
76use sp_runtime::transaction_validity::TransactionPriority;
77use sp_runtime::{RuntimeAppPublic, SaturatedConversion, Saturating};
78use sp_subspace_mmr::{ConsensusChainMmrLeafProof, MmrProofVerifier};
79pub use staking::OperatorConfig;
80use subspace_core_primitives::pot::PotOutput;
81use subspace_core_primitives::{BlockHash, SlotNumber, U256};
82use subspace_runtime_primitives::{Balance, CreateUnsigned, Moment, StorageFee};
83
84pub const MAX_NOMINATORS_TO_SLASH: u32 = 10;
86
87pub(crate) type BalanceOf<T> = <T as Config>::Balance;
88
89pub(crate) type FungibleHoldId<T> =
90 <<T as Config>::Currency as InspectHold<<T as frame_system::Config>::AccountId>>::Reason;
91
92pub(crate) type NominatorId<T> = <T as frame_system::Config>::AccountId;
93
94pub trait HoldIdentifier<T: Config> {
95 fn staking_staked() -> FungibleHoldId<T>;
96 fn domain_instantiation_id() -> FungibleHoldId<T>;
97 fn storage_fund_withdrawal() -> FungibleHoldId<T>;
98}
99
100pub trait BlockSlot<T: frame_system::Config> {
101 fn future_slot(block_number: BlockNumberFor<T>) -> Option<sp_consensus_slots::Slot>;
103
104 fn slot_produced_after(to_check: sp_consensus_slots::Slot) -> Option<BlockNumberFor<T>>;
106}
107
108pub type ExecutionReceiptOf<T> = ExecutionReceipt<
109 BlockNumberFor<T>,
110 <T as frame_system::Config>::Hash,
111 DomainBlockNumberFor<T>,
112 <T as Config>::DomainHash,
113 BalanceOf<T>,
114>;
115
116pub type ExecutionReceiptRefOf<'a, T> = ExecutionReceiptRef<
117 'a,
118 BlockNumberFor<T>,
119 <T as frame_system::Config>::Hash,
120 DomainBlockNumberFor<T>,
121 <T as Config>::DomainHash,
122 BalanceOf<T>,
123>;
124
125pub type OpaqueBundleOf<T> = OpaqueBundle<
126 BlockNumberFor<T>,
127 <T as frame_system::Config>::Hash,
128 <T as Config>::DomainHeader,
129 BalanceOf<T>,
130>;
131
132pub type SingletonReceiptOf<T> = SealedSingletonReceipt<
133 BlockNumberFor<T>,
134 <T as frame_system::Config>::Hash,
135 <T as Config>::DomainHeader,
136 BalanceOf<T>,
137>;
138
139pub type FraudProofFor<T> = FraudProof<
140 BlockNumberFor<T>,
141 <T as frame_system::Config>::Hash,
142 <T as Config>::DomainHeader,
143 <T as Config>::MmrHash,
144>;
145
146#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
148pub(crate) struct ElectionVerificationParams<Balance> {
149 operators: BTreeMap<OperatorId, Balance>,
150 total_domain_stake: Balance,
151}
152
153pub type DomainBlockNumberFor<T> = <<T as Config>::DomainHeader as Header>::Number;
154pub type DomainHashingFor<T> = <<T as Config>::DomainHeader as Header>::Hashing;
155pub type ReceiptHashFor<T> = <<T as Config>::DomainHeader as Header>::Hash;
156
157pub type BlockTreeNodeFor<T> = crate::block_tree::BlockTreeNode<
158 BlockNumberFor<T>,
159 <T as frame_system::Config>::Hash,
160 DomainBlockNumberFor<T>,
161 <T as Config>::DomainHash,
162 BalanceOf<T>,
163>;
164
165#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
167pub enum RawOrigin {
168 ValidatedUnsigned,
169}
170
171pub struct EnsureDomainOrigin;
173impl<O: Into<Result<RawOrigin, O>> + From<RawOrigin>> EnsureOrigin<O> for EnsureDomainOrigin {
174 type Success = ();
175
176 fn try_origin(o: O) -> Result<Self::Success, O> {
177 o.into().map(|o| match o {
178 RawOrigin::ValidatedUnsigned => (),
179 })
180 }
181
182 #[cfg(feature = "runtime-benchmarks")]
183 fn try_successful_origin() -> Result<O, ()> {
184 Ok(O::from(RawOrigin::ValidatedUnsigned))
185 }
186}
187
188const STORAGE_VERSION: StorageVersion = StorageVersion::new(5);
190
191const MAX_BUNDLE_PER_BLOCK: u32 = 100;
196
197pub(crate) type StateRootOf<T> = <<T as frame_system::Config>::Hashing as Hash>::Output;
198
199#[expect(clippy::useless_conversion, reason = "Macro-generated")]
200#[frame_support::pallet]
201mod pallet {
202 #[cfg(not(feature = "runtime-benchmarks"))]
203 use crate::DomainHashingFor;
204 #[cfg(not(feature = "runtime-benchmarks"))]
205 use crate::MAX_NOMINATORS_TO_SLASH;
206 use crate::block_tree::{
207 AcceptedReceiptType, BlockTreeNode, Error as BlockTreeError, ReceiptType,
208 execution_receipt_type, process_execution_receipt, prune_receipt,
209 };
210 use crate::bundle_storage_fund::Error as BundleStorageFundError;
211 #[cfg(not(feature = "runtime-benchmarks"))]
212 use crate::bundle_storage_fund::refund_storage_fee;
213 use crate::domain_registry::{
214 DomainConfigParams, DomainObject, Error as DomainRegistryError, do_instantiate_domain,
215 do_update_domain_allow_list,
216 };
217 use crate::runtime_registry::{
218 DomainRuntimeUpgradeEntry, Error as RuntimeRegistryError, ScheduledRuntimeUpgrade,
219 do_register_runtime, do_schedule_runtime_upgrade, do_upgrade_runtimes,
220 register_runtime_at_genesis,
221 };
222 #[cfg(not(feature = "runtime-benchmarks"))]
223 use crate::staking::do_reward_operators;
224 use crate::staking::{
225 Deposit, DomainEpoch, Error as StakingError, Operator, OperatorConfig, SharePrice,
226 StakingSummary, Withdrawal, do_deregister_operator, do_mark_invalid_bundle_authors,
227 do_mark_operators_as_slashed, do_nominate_operator, do_register_operator, do_unlock_funds,
228 do_unlock_nominator, do_unmark_invalid_bundle_authors, do_withdraw_stake,
229 };
230 #[cfg(not(feature = "runtime-benchmarks"))]
231 use crate::staking_epoch::do_slash_operator;
232 use crate::staking_epoch::{Error as StakingEpochError, do_finalize_domain_current_epoch};
233 use crate::storage_proof::InherentExtrinsicData;
234 use crate::weights::WeightInfo;
235 use crate::{
236 BalanceOf, BlockSlot, BlockTreeNodeFor, DomainBlockNumberFor, ElectionVerificationParams,
237 ExecutionReceiptOf, FraudProofFor, HoldIdentifier, MAX_BUNDLE_PER_BLOCK, NominatorId,
238 OpaqueBundleOf, RawOrigin, ReceiptHashFor, STORAGE_VERSION, SingletonReceiptOf,
239 StateRootOf,
240 };
241 #[cfg(not(feature = "std"))]
242 use alloc::string::String;
243 #[cfg(not(feature = "std"))]
244 use alloc::vec;
245 #[cfg(not(feature = "std"))]
246 use alloc::vec::Vec;
247 use domain_runtime_primitives::{EVMChainId, EthereumAccountId};
248 use frame_support::pallet_prelude::*;
249 use frame_support::traits::fungible::{Inspect, InspectHold, Mutate, MutateHold};
250 use frame_support::traits::tokens::Preservation;
251 use frame_support::traits::{Randomness as RandomnessT, Time};
252 use frame_support::weights::Weight;
253 use frame_support::{Identity, PalletError};
254 use frame_system::pallet_prelude::*;
255 use parity_scale_codec::FullCodec;
256 use sp_core::H256;
257 use sp_domains::bundle::BundleDigest;
258 use sp_domains::bundle_producer_election::ProofOfElectionError;
259 use sp_domains::{
260 BundleAndExecutionReceiptVersion, DomainBundleSubmitted, DomainId, DomainOwner,
261 DomainSudoCall, DomainsTransfersTracker, EpochIndex,
262 EvmDomainContractCreationAllowedByCall, GenesisDomain, OnChainRewards,
263 OnDomainInstantiated, OperatorAllowList, OperatorId, OperatorRewardSource, RuntimeId,
264 RuntimeObject, RuntimeType,
265 };
266 use sp_domains_fraud_proof::fraud_proof_runtime_interface::domain_runtime_call;
267 use sp_domains_fraud_proof::storage_proof::{self, FraudProofStorageKeyProvider};
268 use sp_domains_fraud_proof::{InvalidTransactionCode, StatelessDomainRuntimeCall};
269 use sp_runtime::Saturating;
270 use sp_runtime::traits::{
271 AtLeast32BitUnsigned, BlockNumberProvider, CheckEqual, CheckedAdd, Header as HeaderT,
272 MaybeDisplay, One, SimpleBitOps, Zero,
273 };
274 use sp_std::boxed::Box;
275 use sp_std::collections::btree_map::BTreeMap;
276 use sp_std::collections::btree_set::BTreeSet;
277 use sp_std::fmt::Debug;
278 use sp_subspace_mmr::MmrProofVerifier;
279 use subspace_core_primitives::{Randomness, U256};
280 use subspace_runtime_primitives::StorageFee;
281
282 #[pallet::config]
283 pub trait Config: frame_system::Config<Hash: Into<H256> + From<H256>> {
284 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
285
286 type DomainOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = ()>;
288
289 type DomainHash: Parameter
294 + Member
295 + MaybeSerializeDeserialize
296 + Debug
297 + MaybeDisplay
298 + SimpleBitOps
299 + Ord
300 + Default
301 + Copy
302 + CheckEqual
303 + sp_std::hash::Hash
304 + AsRef<[u8]>
305 + AsMut<[u8]>
306 + MaxEncodedLen
307 + Into<H256>
308 + From<H256>;
309
310 type Balance: Parameter
312 + Member
313 + MaybeSerializeDeserialize
314 + AtLeast32BitUnsigned
315 + FullCodec
316 + Debug
317 + MaybeDisplay
318 + Default
319 + Copy
320 + MaxEncodedLen
321 + From<u64>;
322
323 type DomainHeader: HeaderT<Hash = Self::DomainHash>;
325
326 #[pallet::constant]
328 type ConfirmationDepthK: Get<BlockNumberFor<Self>>;
329
330 type Currency: Inspect<Self::AccountId, Balance = Self::Balance>
332 + Mutate<Self::AccountId>
333 + InspectHold<Self::AccountId>
334 + MutateHold<Self::AccountId>;
335
336 type Share: Parameter
338 + Member
339 + MaybeSerializeDeserialize
340 + Debug
341 + AtLeast32BitUnsigned
342 + FullCodec
343 + Copy
344 + Default
345 + TypeInfo
346 + MaxEncodedLen
347 + IsType<BalanceOf<Self>>;
348
349 type HoldIdentifier: HoldIdentifier<Self>;
351
352 #[pallet::constant]
354 type BlockTreePruningDepth: Get<DomainBlockNumberFor<Self>>;
355
356 #[pallet::constant]
358 type ConsensusSlotProbability: Get<(u64, u64)>;
359
360 #[pallet::constant]
362 type MaxDomainBlockSize: Get<u32>;
363
364 #[pallet::constant]
366 type MaxDomainBlockWeight: Get<Weight>;
367
368 #[pallet::constant]
370 type MaxDomainNameLength: Get<u32>;
371
372 #[pallet::constant]
374 type DomainInstantiationDeposit: Get<BalanceOf<Self>>;
375
376 type WeightInfo: WeightInfo;
378
379 #[pallet::constant]
381 type InitialDomainTxRange: Get<u64>;
382
383 #[pallet::constant]
385 type DomainTxRangeAdjustmentInterval: Get<u64>;
386
387 #[pallet::constant]
389 type MinOperatorStake: Get<BalanceOf<Self>>;
390
391 #[pallet::constant]
393 type MinNominatorStake: Get<BalanceOf<Self>>;
394
395 #[pallet::constant]
397 type StakeWithdrawalLockingPeriod: Get<DomainBlockNumberFor<Self>>;
398
399 #[pallet::constant]
401 type StakeEpochDuration: Get<DomainBlockNumberFor<Self>>;
402
403 #[pallet::constant]
405 type TreasuryAccount: Get<Self::AccountId>;
406
407 #[pallet::constant]
409 type MaxPendingStakingOperation: Get<u32>;
410
411 type Randomness: RandomnessT<Self::Hash, BlockNumberFor<Self>>;
413
414 #[pallet::constant]
416 type PalletId: Get<frame_support::PalletId>;
417
418 type StorageFee: StorageFee<BalanceOf<Self>>;
420
421 type BlockTimestamp: Time;
423
424 type BlockSlot: BlockSlot<Self>;
426
427 type DomainsTransfersTracker: DomainsTransfersTracker<BalanceOf<Self>>;
429
430 type MaxInitialDomainAccounts: Get<u32>;
432
433 type MinInitialDomainAccountBalance: Get<BalanceOf<Self>>;
435
436 #[pallet::constant]
438 type BundleLongevity: Get<u32>;
439
440 type DomainBundleSubmitted: DomainBundleSubmitted;
442
443 type OnDomainInstantiated: OnDomainInstantiated;
445
446 type MmrHash: Parameter + Member + Default + Clone;
448
449 type MmrProofVerifier: MmrProofVerifier<Self::MmrHash, BlockNumberFor<Self>, StateRootOf<Self>>;
451
452 type FraudProofStorageKeyProvider: FraudProofStorageKeyProvider<BlockNumberFor<Self>>;
454
455 type OnChainRewards: OnChainRewards<BalanceOf<Self>>;
457
458 #[pallet::constant]
462 type WithdrawalLimit: Get<u32>;
463
464 #[pallet::constant]
466 type CurrentBundleAndExecutionReceiptVersion: Get<BundleAndExecutionReceiptVersion>;
467 }
468
469 #[pallet::pallet]
470 #[pallet::without_storage_info]
471 #[pallet::storage_version(STORAGE_VERSION)]
472 pub struct Pallet<T>(_);
473
474 #[pallet::storage]
476 pub type SuccessfulBundles<T> = StorageMap<_, Identity, DomainId, Vec<H256>, ValueQuery>;
477
478 #[pallet::storage]
480 pub(super) type NextRuntimeId<T> = StorageValue<_, RuntimeId, ValueQuery>;
481
482 pub struct StartingEVMChainId;
484
485 impl Get<EVMChainId> for StartingEVMChainId {
486 fn get() -> EVMChainId {
487 870
489 }
490 }
491
492 #[pallet::storage]
494 pub(super) type NextEVMChainId<T> = StorageValue<_, EVMChainId, ValueQuery, StartingEVMChainId>;
495
496 #[pallet::storage]
497 pub type RuntimeRegistry<T: Config> =
498 StorageMap<_, Identity, RuntimeId, RuntimeObject<BlockNumberFor<T>, T::Hash>, OptionQuery>;
499
500 #[pallet::storage]
501 pub(super) type ScheduledRuntimeUpgrades<T: Config> = StorageDoubleMap<
502 _,
503 Identity,
504 BlockNumberFor<T>,
505 Identity,
506 RuntimeId,
507 ScheduledRuntimeUpgrade<T::Hash>,
508 OptionQuery,
509 >;
510
511 #[pallet::storage]
512 pub(super) type NextOperatorId<T> = StorageValue<_, OperatorId, ValueQuery>;
513
514 #[pallet::storage]
515 pub(super) type OperatorIdOwner<T: Config> =
516 StorageMap<_, Identity, OperatorId, T::AccountId, OptionQuery>;
517
518 #[pallet::storage]
519 #[pallet::getter(fn domain_staking_summary)]
520 pub(super) type DomainStakingSummary<T: Config> =
521 StorageMap<_, Identity, DomainId, StakingSummary<OperatorId, BalanceOf<T>>, OptionQuery>;
522
523 #[pallet::storage]
525 pub(super) type Operators<T: Config> = StorageMap<
526 _,
527 Identity,
528 OperatorId,
529 Operator<BalanceOf<T>, T::Share, DomainBlockNumberFor<T>, ReceiptHashFor<T>>,
530 OptionQuery,
531 >;
532
533 #[pallet::storage]
535 pub(super) type OperatorHighestSlot<T: Config> =
536 StorageMap<_, Identity, OperatorId, u64, ValueQuery>;
537
538 #[pallet::storage]
541 pub(super) type OperatorBundleSlot<T: Config> =
542 StorageMap<_, Identity, OperatorId, BTreeSet<u64>, ValueQuery>;
543
544 #[pallet::storage]
547 pub type OperatorEpochSharePrice<T: Config> =
548 StorageDoubleMap<_, Identity, OperatorId, Identity, DomainEpoch, SharePrice, OptionQuery>;
549
550 #[pallet::storage]
552 pub(super) type Deposits<T: Config> = StorageDoubleMap<
553 _,
554 Identity,
555 OperatorId,
556 Identity,
557 NominatorId<T>,
558 Deposit<T::Share, BalanceOf<T>>,
559 OptionQuery,
560 >;
561
562 #[pallet::storage]
564 pub(super) type Withdrawals<T: Config> = StorageDoubleMap<
565 _,
566 Identity,
567 OperatorId,
568 Identity,
569 NominatorId<T>,
570 Withdrawal<BalanceOf<T>, T::Share, DomainBlockNumberFor<T>>,
571 OptionQuery,
572 >;
573
574 #[pallet::storage]
576 pub(super) type DepositOnHold<T: Config> =
577 StorageMap<_, Identity, (OperatorId, NominatorId<T>), BalanceOf<T>, ValueQuery>;
578
579 #[pallet::storage]
583 pub(super) type PendingSlashes<T: Config> =
584 StorageMap<_, Identity, DomainId, BTreeSet<OperatorId>, OptionQuery>;
585
586 #[pallet::storage]
589 pub(super) type PendingStakingOperationCount<T: Config> =
590 StorageMap<_, Identity, DomainId, u32, ValueQuery>;
591
592 #[pallet::storage]
594 #[pallet::getter(fn next_domain_id)]
595 pub(super) type NextDomainId<T> = StorageValue<_, DomainId, ValueQuery>;
596
597 #[pallet::storage]
599 pub(super) type DomainRegistry<T: Config> = StorageMap<
600 _,
601 Identity,
602 DomainId,
603 DomainObject<BlockNumberFor<T>, ReceiptHashFor<T>, T::AccountId, BalanceOf<T>>,
604 OptionQuery,
605 >;
606
607 #[pallet::storage]
610 pub(super) type BlockTree<T: Config> = StorageDoubleMap<
611 _,
612 Identity,
613 DomainId,
614 Identity,
615 DomainBlockNumberFor<T>,
616 ReceiptHashFor<T>,
617 OptionQuery,
618 >;
619
620 #[pallet::storage]
622 pub(super) type BlockTreeNodes<T: Config> =
623 StorageMap<_, Identity, ReceiptHashFor<T>, BlockTreeNodeFor<T>, OptionQuery>;
624
625 #[pallet::storage]
627 pub(super) type HeadReceiptNumber<T: Config> =
628 StorageMap<_, Identity, DomainId, DomainBlockNumberFor<T>, ValueQuery>;
629
630 #[pallet::storage]
634 pub(super) type NewAddedHeadReceipt<T: Config> =
635 StorageMap<_, Identity, DomainId, T::DomainHash, OptionQuery>;
636
637 #[pallet::storage]
643 #[pallet::getter(fn consensus_block_info)]
644 pub type ConsensusBlockHash<T: Config> =
645 StorageDoubleMap<_, Identity, DomainId, Identity, BlockNumberFor<T>, T::Hash, OptionQuery>;
646
647 #[pallet::storage]
653 pub type ExecutionInbox<T: Config> = StorageNMap<
654 _,
655 (
656 NMapKey<Identity, DomainId>,
657 NMapKey<Identity, DomainBlockNumberFor<T>>,
658 NMapKey<Identity, BlockNumberFor<T>>,
659 ),
660 Vec<BundleDigest<T::DomainHash>>,
661 ValueQuery,
662 >;
663
664 #[pallet::storage]
668 pub(super) type InboxedBundleAuthor<T: Config> =
669 StorageMap<_, Identity, T::DomainHash, OperatorId, OptionQuery>;
670
671 #[pallet::storage]
680 pub(super) type HeadDomainNumber<T: Config> =
681 StorageMap<_, Identity, DomainId, DomainBlockNumberFor<T>, ValueQuery>;
682
683 #[pallet::storage]
689 pub(super) type LastEpochStakingDistribution<T: Config> =
690 StorageMap<_, Identity, DomainId, ElectionVerificationParams<BalanceOf<T>>, OptionQuery>;
691
692 #[pallet::storage]
694 #[pallet::getter(fn latest_confirmed_domain_execution_receipt)]
695 pub type LatestConfirmedDomainExecutionReceipt<T: Config> =
696 StorageMap<_, Identity, DomainId, ExecutionReceiptOf<T>, OptionQuery>;
697
698 #[pallet::storage]
700 #[pallet::getter(fn domain_genesis_block_execution_receipt)]
701 pub type DomainGenesisBlockExecutionReceipt<T: Config> =
702 StorageMap<_, Identity, DomainId, ExecutionReceiptOf<T>, OptionQuery>;
703
704 #[pallet::storage]
711 #[pallet::getter(fn latest_submitted_er)]
712 pub(super) type LatestSubmittedER<T: Config> =
713 StorageMap<_, Identity, (DomainId, OperatorId), DomainBlockNumberFor<T>, ValueQuery>;
714
715 #[pallet::storage]
717 pub(super) type PermissionedActionAllowedBy<T: Config> =
718 StorageValue<_, sp_domains::PermissionedActionAllowedBy<T::AccountId>, OptionQuery>;
719
720 #[pallet::storage]
723 pub(super) type AccumulatedTreasuryFunds<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
724
725 #[pallet::storage]
727 pub(super) type DomainRuntimeUpgradeRecords<T: Config> = StorageMap<
728 _,
729 Identity,
730 RuntimeId,
731 BTreeMap<BlockNumberFor<T>, DomainRuntimeUpgradeEntry<T::Hash>>,
732 ValueQuery,
733 >;
734
735 #[pallet::storage]
738 pub type DomainRuntimeUpgrades<T> = StorageValue<_, Vec<RuntimeId>, ValueQuery>;
739
740 #[pallet::storage]
745 pub type DomainSudoCalls<T: Config> =
746 StorageMap<_, Identity, DomainId, DomainSudoCall, ValueQuery>;
747
748 #[pallet::storage]
752 pub type FrozenDomains<T> = StorageValue<_, BTreeSet<DomainId>, ValueQuery>;
753
754 #[pallet::storage]
759 pub type EvmDomainContractCreationAllowedByCalls<T: Config> =
760 StorageMap<_, Identity, DomainId, EvmDomainContractCreationAllowedByCall, ValueQuery>;
761
762 #[pallet::storage]
765 pub type DomainChainRewards<T: Config> =
766 StorageMap<_, Identity, DomainId, BalanceOf<T>, ValueQuery>;
767
768 #[pallet::storage]
771 pub type InvalidBundleAuthors<T: Config> =
772 StorageMap<_, Identity, DomainId, BTreeSet<OperatorId>, ValueQuery>;
773
774 #[pallet::storage]
782 pub type PreviousBundleAndExecutionReceiptVersions<T> =
783 StorageValue<_, BTreeMap<BlockNumberFor<T>, BundleAndExecutionReceiptVersion>, ValueQuery>;
784
785 #[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
786 pub enum BundleError {
787 InvalidOperatorId,
789 BadBundleSignature,
791 BadVrfSignature,
793 InvalidDomainId,
795 BadOperator,
797 ThresholdUnsatisfied,
799 InvalidThreshold,
801 Receipt(BlockTreeError),
803 BundleTooLarge,
805 InvalidExtrinsicRoot,
807 InvalidProofOfTime,
809 SlotInTheFuture,
811 SlotInThePast,
813 BundleTooHeavy,
815 SlotSmallerThanPreviousBlockBundle,
818 EquivocatedBundle,
820 DomainFrozen,
822 UnableToPayBundleStorageFee,
824 UnexpectedReceiptGap,
826 ExpectingReceiptGap,
828 FailedToGetMissedUpgradeCount,
830 BundleVersionMismatch,
832 ExecutionVersionMismatch,
834 ExecutionVersionMissing,
836 }
837
838 #[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
839 pub enum FraudProofError {
840 BadReceiptNotFound,
843 ChallengingGenesisReceipt,
845 DescendantsOfFraudulentERNotPruned,
847 InvalidBlockFeesFraudProof,
849 InvalidTransfersFraudProof,
851 InvalidDomainBlockHashFraudProof,
853 InvalidExtrinsicRootFraudProof,
855 InvalidStateTransitionFraudProof,
857 ParentReceiptNotFound,
859 InvalidBundleFraudProof,
861 BadValidBundleFraudProof,
863 MissingOperator,
865 UnexpectedFraudProof,
867 BadReceiptAlreadyReported,
869 BadMmrProof,
871 UnexpectedMmrProof,
873 MissingMmrProof,
875 RuntimeNotFound,
877 DomainRuntimeCodeProofNotFound,
879 UnexpectedDomainRuntimeCodeProof,
881 StorageProof(storage_proof::VerificationError),
883 }
884
885 impl From<BundleError> for TransactionValidity {
886 fn from(e: BundleError) -> Self {
887 if BundleError::UnableToPayBundleStorageFee == e {
888 InvalidTransactionCode::BundleStorageFeePayment.into()
889 } else if let BundleError::Receipt(_) = e {
890 InvalidTransactionCode::ExecutionReceipt.into()
891 } else {
892 InvalidTransactionCode::Bundle.into()
893 }
894 }
895 }
896
897 impl From<storage_proof::VerificationError> for FraudProofError {
898 fn from(err: storage_proof::VerificationError) -> Self {
899 FraudProofError::StorageProof(err)
900 }
901 }
902
903 impl<T> From<FraudProofError> for Error<T> {
904 fn from(err: FraudProofError) -> Self {
905 Error::FraudProof(err)
906 }
907 }
908
909 impl<T> From<RuntimeRegistryError> for Error<T> {
910 fn from(err: RuntimeRegistryError) -> Self {
911 Error::RuntimeRegistry(err)
912 }
913 }
914
915 impl<T> From<StakingError> for Error<T> {
916 fn from(err: StakingError) -> Self {
917 Error::Staking(err)
918 }
919 }
920
921 impl<T> From<StakingEpochError> for Error<T> {
922 fn from(err: StakingEpochError) -> Self {
923 Error::StakingEpoch(err)
924 }
925 }
926
927 impl<T> From<DomainRegistryError> for Error<T> {
928 fn from(err: DomainRegistryError) -> Self {
929 Error::DomainRegistry(err)
930 }
931 }
932
933 impl<T> From<BlockTreeError> for Error<T> {
934 fn from(err: BlockTreeError) -> Self {
935 Error::BlockTree(err)
936 }
937 }
938
939 impl From<ProofOfElectionError> for BundleError {
940 fn from(err: ProofOfElectionError) -> Self {
941 match err {
942 ProofOfElectionError::BadVrfProof => Self::BadVrfSignature,
943 ProofOfElectionError::ThresholdUnsatisfied => Self::ThresholdUnsatisfied,
944 ProofOfElectionError::InvalidThreshold => Self::InvalidThreshold,
945 }
946 }
947 }
948
949 impl<T> From<BundleStorageFundError> for Error<T> {
950 fn from(err: BundleStorageFundError) -> Self {
951 Error::BundleStorageFund(err)
952 }
953 }
954
955 #[pallet::error]
956 pub enum Error<T> {
957 FraudProof(FraudProofError),
959 RuntimeRegistry(RuntimeRegistryError),
961 Staking(StakingError),
963 StakingEpoch(StakingEpochError),
965 DomainRegistry(DomainRegistryError),
967 BlockTree(BlockTreeError),
969 BundleStorageFund(BundleStorageFundError),
971 PermissionedActionNotAllowed,
973 DomainSudoCallExists,
975 InvalidDomainSudoCall,
977 DomainNotFrozen,
979 NotPrivateEvmDomain,
981 NotDomainOwnerOrRoot,
983 EvmDomainContractCreationAllowedByCallExists,
985 }
986
987 #[derive(Clone, Debug, PartialEq, Encode, Decode, TypeInfo)]
989 pub enum SlashedReason<DomainBlock, ReceiptHash> {
990 InvalidBundle(DomainBlock),
992 BadExecutionReceipt(ReceiptHash),
994 }
995
996 #[pallet::event]
997 #[pallet::generate_deposit(pub (super) fn deposit_event)]
998 pub enum Event<T: Config> {
999 BundleStored {
1001 domain_id: DomainId,
1002 bundle_hash: H256,
1003 bundle_author: OperatorId,
1004 },
1005 DomainRuntimeCreated {
1006 runtime_id: RuntimeId,
1007 runtime_type: RuntimeType,
1008 },
1009 DomainRuntimeUpgradeScheduled {
1010 runtime_id: RuntimeId,
1011 scheduled_at: BlockNumberFor<T>,
1012 },
1013 DomainRuntimeUpgraded {
1014 runtime_id: RuntimeId,
1015 },
1016 OperatorRegistered {
1017 operator_id: OperatorId,
1018 domain_id: DomainId,
1019 },
1020 NominatedStakedUnlocked {
1021 operator_id: OperatorId,
1022 nominator_id: NominatorId<T>,
1023 unlocked_amount: BalanceOf<T>,
1024 },
1025 StorageFeeUnlocked {
1026 operator_id: OperatorId,
1027 nominator_id: NominatorId<T>,
1028 storage_fee: BalanceOf<T>,
1029 },
1030 OperatorNominated {
1031 operator_id: OperatorId,
1032 nominator_id: NominatorId<T>,
1033 amount: BalanceOf<T>,
1034 },
1035 DomainInstantiated {
1036 domain_id: DomainId,
1037 },
1038 OperatorSwitchedDomain {
1039 old_domain_id: DomainId,
1040 new_domain_id: DomainId,
1041 },
1042 OperatorDeregistered {
1043 operator_id: OperatorId,
1044 },
1045 NominatorUnlocked {
1046 operator_id: OperatorId,
1047 nominator_id: NominatorId<T>,
1048 },
1049 WithdrewStake {
1050 operator_id: OperatorId,
1051 nominator_id: NominatorId<T>,
1052 },
1053 PreferredOperator {
1054 operator_id: OperatorId,
1055 nominator_id: NominatorId<T>,
1056 },
1057 OperatorRewarded {
1058 source: OperatorRewardSource<BlockNumberFor<T>>,
1059 operator_id: OperatorId,
1060 reward: BalanceOf<T>,
1061 },
1062 OperatorTaxCollected {
1063 operator_id: OperatorId,
1064 tax: BalanceOf<T>,
1065 },
1066 DomainEpochCompleted {
1067 domain_id: DomainId,
1068 completed_epoch_index: EpochIndex,
1069 },
1070 ForceDomainEpochTransition {
1071 domain_id: DomainId,
1072 completed_epoch_index: EpochIndex,
1073 },
1074 FraudProofProcessed {
1075 domain_id: DomainId,
1076 new_head_receipt_number: Option<DomainBlockNumberFor<T>>,
1077 },
1078 DomainOperatorAllowListUpdated {
1079 domain_id: DomainId,
1080 },
1081 OperatorSlashed {
1082 operator_id: OperatorId,
1083 reason: SlashedReason<DomainBlockNumberFor<T>, ReceiptHashFor<T>>,
1084 },
1085 StorageFeeDeposited {
1086 operator_id: OperatorId,
1087 nominator_id: NominatorId<T>,
1088 amount: BalanceOf<T>,
1089 },
1090 DomainFrozen {
1091 domain_id: DomainId,
1092 },
1093 DomainUnfrozen {
1094 domain_id: DomainId,
1095 },
1096 PrunedExecutionReceipt {
1097 domain_id: DomainId,
1098 new_head_receipt_number: Option<DomainBlockNumberFor<T>>,
1099 },
1100 }
1101
1102 #[pallet::origin]
1103 pub type Origin = RawOrigin;
1104
1105 #[derive(Debug, Default, Decode, Encode, TypeInfo, PartialEq, Eq)]
1107 pub struct TxRangeState {
1108 pub tx_range: U256,
1110
1111 pub interval_blocks: u64,
1113
1114 pub interval_bundles: u64,
1116 }
1117
1118 impl TxRangeState {
1119 pub fn on_bundle(&mut self) {
1121 self.interval_bundles += 1;
1122 }
1123 }
1124
1125 #[pallet::storage]
1126 pub(super) type DomainTxRangeState<T: Config> =
1127 StorageMap<_, Identity, DomainId, TxRangeState, OptionQuery>;
1128
1129 #[pallet::call]
1130 impl<T: Config> Pallet<T> {
1131 #[pallet::call_index(0)]
1132 #[pallet::weight(Pallet::<T>::max_submit_bundle_weight())]
1133 pub fn submit_bundle(
1134 origin: OriginFor<T>,
1135 opaque_bundle: OpaqueBundleOf<T>,
1136 ) -> DispatchResultWithPostInfo {
1137 T::DomainOrigin::ensure_origin(origin)?;
1138
1139 log::trace!("Processing bundle: {opaque_bundle:?}");
1140
1141 let domain_id = opaque_bundle.domain_id();
1142 let bundle_hash = opaque_bundle.hash();
1143 let bundle_header_hash = opaque_bundle.sealed_header().pre_hash();
1144 let extrinsics_root = opaque_bundle.extrinsics_root();
1145 let operator_id = opaque_bundle.operator_id();
1146 let bundle_size = opaque_bundle.size();
1147 let slot_number = opaque_bundle.slot_number();
1148 let receipt = opaque_bundle.into_receipt();
1149 #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
1150 let receipt_block_number = *receipt.domain_block_number();
1151
1152 #[cfg(not(feature = "runtime-benchmarks"))]
1153 let mut actual_weight = T::WeightInfo::submit_bundle();
1154 #[cfg(feature = "runtime-benchmarks")]
1155 let actual_weight = T::WeightInfo::submit_bundle();
1156
1157 match execution_receipt_type::<T>(domain_id, &receipt.as_execution_receipt_ref()) {
1158 ReceiptType::Rejected(rejected_receipt_type) => {
1159 return Err(Error::<T>::BlockTree(rejected_receipt_type.into()).into());
1160 }
1161 ReceiptType::Accepted(accepted_receipt_type) => {
1163 #[cfg(not(feature = "runtime-benchmarks"))]
1169 if accepted_receipt_type == AcceptedReceiptType::NewHead
1170 && let Some(BlockTreeNode {
1171 execution_receipt,
1172 operator_ids,
1173 }) = prune_receipt::<T>(domain_id, receipt_block_number)
1174 .map_err(Error::<T>::from)?
1175 {
1176 actual_weight = actual_weight.saturating_add(
1177 T::WeightInfo::handle_bad_receipt(operator_ids.len() as u32),
1178 );
1179
1180 let bad_receipt_hash = execution_receipt.hash::<DomainHashingFor<T>>();
1181 do_mark_operators_as_slashed::<T>(
1182 operator_ids.into_iter(),
1183 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1184 )
1185 .map_err(Error::<T>::from)?;
1186
1187 do_unmark_invalid_bundle_authors::<T>(domain_id, &execution_receipt)
1188 .map_err(Error::<T>::from)?;
1189 }
1190
1191 if accepted_receipt_type == AcceptedReceiptType::NewHead {
1192 do_mark_invalid_bundle_authors::<T>(domain_id, &receipt)
1195 .map_err(Error::<T>::Staking)?;
1196 }
1197
1198 #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
1199 let maybe_confirmed_domain_block_info = process_execution_receipt::<T>(
1200 domain_id,
1201 operator_id,
1202 receipt,
1203 accepted_receipt_type,
1204 )
1205 .map_err(Error::<T>::from)?;
1206
1207 #[cfg(not(feature = "runtime-benchmarks"))]
1213 if let Some(confirmed_block_info) = maybe_confirmed_domain_block_info {
1214 actual_weight =
1215 actual_weight.saturating_add(T::WeightInfo::confirm_domain_block(
1216 confirmed_block_info.operator_ids.len() as u32,
1217 confirmed_block_info.invalid_bundle_authors.len() as u32,
1218 ));
1219
1220 refund_storage_fee::<T>(
1221 confirmed_block_info.total_storage_fee,
1222 confirmed_block_info.paid_bundle_storage_fees,
1223 )
1224 .map_err(Error::<T>::from)?;
1225
1226 do_reward_operators::<T>(
1227 domain_id,
1228 OperatorRewardSource::Bundle {
1229 at_block_number: confirmed_block_info.consensus_block_number,
1230 },
1231 confirmed_block_info.operator_ids.into_iter(),
1232 confirmed_block_info.rewards,
1233 )
1234 .map_err(Error::<T>::from)?;
1235
1236 do_mark_operators_as_slashed::<T>(
1237 confirmed_block_info.invalid_bundle_authors.into_iter(),
1238 SlashedReason::InvalidBundle(confirmed_block_info.domain_block_number),
1239 )
1240 .map_err(Error::<T>::from)?;
1241 }
1242 }
1243 }
1244
1245 if SuccessfulBundles::<T>::get(domain_id).is_empty() {
1249 let missed_upgrade =
1256 Self::missed_domain_runtime_upgrade(domain_id).map_err(Error::<T>::from)?;
1257
1258 let next_number = HeadDomainNumber::<T>::get(domain_id)
1259 .checked_add(&One::one())
1260 .ok_or::<Error<T>>(BlockTreeError::MaxHeadDomainNumber.into())?
1261 .checked_add(&missed_upgrade.into())
1262 .ok_or::<Error<T>>(BlockTreeError::MaxHeadDomainNumber.into())?;
1263
1264 #[cfg(not(feature = "runtime-benchmarks"))]
1266 if next_number % T::StakeEpochDuration::get() == Zero::zero() {
1267 let epoch_transition_res = do_finalize_domain_current_epoch::<T>(domain_id)
1268 .map_err(Error::<T>::from)?;
1269
1270 Self::deposit_event(Event::DomainEpochCompleted {
1271 domain_id,
1272 completed_epoch_index: epoch_transition_res.completed_epoch_index,
1273 });
1274
1275 actual_weight = actual_weight
1276 .saturating_add(Self::actual_epoch_transition_weight(epoch_transition_res));
1277 }
1278
1279 HeadDomainNumber::<T>::set(domain_id, next_number);
1280 }
1281
1282 let head_domain_number = HeadDomainNumber::<T>::get(domain_id);
1284 let consensus_block_number = frame_system::Pallet::<T>::current_block_number();
1285 ExecutionInbox::<T>::append(
1286 (domain_id, head_domain_number, consensus_block_number),
1287 BundleDigest {
1288 header_hash: bundle_header_hash,
1289 extrinsics_root,
1290 size: bundle_size,
1291 },
1292 );
1293
1294 InboxedBundleAuthor::<T>::insert(bundle_header_hash, operator_id);
1295
1296 SuccessfulBundles::<T>::append(domain_id, bundle_hash);
1297
1298 OperatorBundleSlot::<T>::mutate(operator_id, |slot_set| slot_set.insert(slot_number));
1299
1300 #[cfg(not(feature = "runtime-benchmarks"))]
1302 {
1303 let slashed_nominator_count =
1304 do_slash_operator::<T>(domain_id, MAX_NOMINATORS_TO_SLASH)
1305 .map_err(Error::<T>::from)?;
1306 actual_weight = actual_weight
1307 .saturating_add(T::WeightInfo::slash_operator(slashed_nominator_count));
1308 }
1309
1310 Self::deposit_event(Event::BundleStored {
1311 domain_id,
1312 bundle_hash,
1313 bundle_author: operator_id,
1314 });
1315
1316 Ok(Some(actual_weight.min(Self::max_submit_bundle_weight())).into())
1318 }
1319
1320 #[pallet::call_index(15)]
1321 #[pallet::weight((
1322 T::WeightInfo::submit_fraud_proof().saturating_add(
1323 T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK)
1324 ),
1325 DispatchClass::Operational
1326 ))]
1327 pub fn submit_fraud_proof(
1328 origin: OriginFor<T>,
1329 fraud_proof: Box<FraudProofFor<T>>,
1330 ) -> DispatchResultWithPostInfo {
1331 T::DomainOrigin::ensure_origin(origin)?;
1332
1333 log::trace!("Processing fraud proof: {fraud_proof:?}");
1334
1335 #[cfg(not(feature = "runtime-benchmarks"))]
1336 let mut actual_weight = T::WeightInfo::submit_fraud_proof();
1337 #[cfg(feature = "runtime-benchmarks")]
1338 let actual_weight = T::WeightInfo::submit_fraud_proof();
1339
1340 let domain_id = fraud_proof.domain_id();
1341 let bad_receipt_hash = fraud_proof.targeted_bad_receipt_hash();
1342 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
1343 let bad_receipt_number = *BlockTreeNodes::<T>::get(bad_receipt_hash)
1344 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?
1345 .execution_receipt
1346 .domain_block_number();
1347 ensure!(
1351 head_receipt_number >= bad_receipt_number,
1352 Error::<T>::from(FraudProofError::BadReceiptNotFound),
1353 );
1354
1355 #[cfg(not(feature = "runtime-benchmarks"))]
1362 {
1363 let BlockTreeNode {
1364 execution_receipt,
1365 operator_ids,
1366 } = prune_receipt::<T>(domain_id, bad_receipt_number)
1367 .map_err(Error::<T>::from)?
1368 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?;
1369
1370 actual_weight = actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt(
1371 (operator_ids.len() as u32).min(MAX_BUNDLE_PER_BLOCK),
1372 ));
1373
1374 do_mark_operators_as_slashed::<T>(
1375 operator_ids.into_iter(),
1376 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1377 )
1378 .map_err(Error::<T>::from)?;
1379
1380 do_unmark_invalid_bundle_authors::<T>(domain_id, &execution_receipt)
1382 .map_err(Error::<T>::Staking)?;
1383 }
1384
1385 let new_head_receipt_number = bad_receipt_number.saturating_sub(One::one());
1387 HeadReceiptNumber::<T>::insert(domain_id, new_head_receipt_number);
1388
1389 Self::deposit_event(Event::FraudProofProcessed {
1390 domain_id,
1391 new_head_receipt_number: Some(new_head_receipt_number),
1392 });
1393
1394 Ok(Some(actual_weight).into())
1395 }
1396
1397 #[pallet::call_index(2)]
1398 #[pallet::weight(T::WeightInfo::register_domain_runtime())]
1399 pub fn register_domain_runtime(
1400 origin: OriginFor<T>,
1401 runtime_name: String,
1402 runtime_type: RuntimeType,
1403 raw_genesis_storage: Vec<u8>,
1407 ) -> DispatchResult {
1408 ensure_root(origin)?;
1409
1410 let block_number = frame_system::Pallet::<T>::current_block_number();
1411 let runtime_id = do_register_runtime::<T>(
1412 runtime_name,
1413 runtime_type,
1414 raw_genesis_storage,
1415 block_number,
1416 )
1417 .map_err(Error::<T>::from)?;
1418
1419 Self::deposit_event(Event::DomainRuntimeCreated {
1420 runtime_id,
1421 runtime_type,
1422 });
1423
1424 Ok(())
1425 }
1426
1427 #[pallet::call_index(3)]
1428 #[pallet::weight(T::WeightInfo::upgrade_domain_runtime())]
1429 pub fn upgrade_domain_runtime(
1430 origin: OriginFor<T>,
1431 runtime_id: RuntimeId,
1432 raw_genesis_storage: Vec<u8>,
1433 ) -> DispatchResult {
1434 ensure_root(origin)?;
1435
1436 let block_number = frame_system::Pallet::<T>::current_block_number();
1437 let scheduled_at =
1438 do_schedule_runtime_upgrade::<T>(runtime_id, raw_genesis_storage, block_number)
1439 .map_err(Error::<T>::from)?;
1440
1441 Self::deposit_event(Event::DomainRuntimeUpgradeScheduled {
1442 runtime_id,
1443 scheduled_at,
1444 });
1445
1446 Ok(())
1447 }
1448
1449 #[pallet::call_index(4)]
1450 #[pallet::weight(T::WeightInfo::register_operator())]
1451 pub fn register_operator(
1452 origin: OriginFor<T>,
1453 domain_id: DomainId,
1454 amount: BalanceOf<T>,
1455 config: OperatorConfig<BalanceOf<T>>,
1456 ) -> DispatchResult {
1457 let owner = ensure_signed(origin)?;
1458
1459 let (operator_id, current_epoch_index) =
1460 do_register_operator::<T>(owner, domain_id, amount, config)
1461 .map_err(Error::<T>::from)?;
1462
1463 Self::deposit_event(Event::OperatorRegistered {
1464 operator_id,
1465 domain_id,
1466 });
1467
1468 if current_epoch_index.is_zero() {
1471 do_finalize_domain_current_epoch::<T>(domain_id).map_err(Error::<T>::from)?;
1472 }
1473
1474 Ok(())
1475 }
1476
1477 #[pallet::call_index(5)]
1478 #[pallet::weight(T::WeightInfo::nominate_operator())]
1479 pub fn nominate_operator(
1480 origin: OriginFor<T>,
1481 operator_id: OperatorId,
1482 amount: BalanceOf<T>,
1483 ) -> DispatchResult {
1484 let nominator_id = ensure_signed(origin)?;
1485
1486 do_nominate_operator::<T>(operator_id, nominator_id.clone(), amount)
1487 .map_err(Error::<T>::from)?;
1488
1489 Ok(())
1490 }
1491
1492 #[pallet::call_index(6)]
1493 #[pallet::weight(T::WeightInfo::instantiate_domain())]
1494 pub fn instantiate_domain(
1495 origin: OriginFor<T>,
1496 domain_config_params: DomainConfigParams<T::AccountId, BalanceOf<T>>,
1497 ) -> DispatchResult {
1498 let who = ensure_signed(origin)?;
1499 ensure!(
1500 PermissionedActionAllowedBy::<T>::get()
1501 .map(|allowed_by| allowed_by.is_allowed(&who))
1502 .unwrap_or_default(),
1503 Error::<T>::PermissionedActionNotAllowed
1504 );
1505
1506 let created_at = frame_system::Pallet::<T>::current_block_number();
1507
1508 let domain_id = do_instantiate_domain::<T>(domain_config_params, who, created_at)
1509 .map_err(Error::<T>::from)?;
1510
1511 Self::deposit_event(Event::DomainInstantiated { domain_id });
1512
1513 Ok(())
1514 }
1515
1516 #[pallet::call_index(8)]
1517 #[pallet::weight(T::WeightInfo::deregister_operator())]
1518 pub fn deregister_operator(
1519 origin: OriginFor<T>,
1520 operator_id: OperatorId,
1521 ) -> DispatchResult {
1522 let who = ensure_signed(origin)?;
1523
1524 do_deregister_operator::<T>(who, operator_id).map_err(Error::<T>::from)?;
1525
1526 Self::deposit_event(Event::OperatorDeregistered { operator_id });
1527
1528 Ok(())
1529 }
1530
1531 #[pallet::call_index(9)]
1532 #[pallet::weight(T::WeightInfo::withdraw_stake())]
1533 pub fn withdraw_stake(
1534 origin: OriginFor<T>,
1535 operator_id: OperatorId,
1536 to_withdraw: T::Share,
1537 ) -> DispatchResult {
1538 let who = ensure_signed(origin)?;
1539
1540 do_withdraw_stake::<T>(operator_id, who.clone(), to_withdraw)
1541 .map_err(Error::<T>::from)?;
1542
1543 Self::deposit_event(Event::WithdrewStake {
1544 operator_id,
1545 nominator_id: who,
1546 });
1547
1548 Ok(())
1549 }
1550
1551 #[pallet::call_index(10)]
1555 #[pallet::weight(T::WeightInfo::unlock_funds(T::WithdrawalLimit::get()))]
1556 pub fn unlock_funds(
1557 origin: OriginFor<T>,
1558 operator_id: OperatorId,
1559 ) -> DispatchResultWithPostInfo {
1560 let nominator_id = ensure_signed(origin)?;
1561 let withdrawal_count = do_unlock_funds::<T>(operator_id, nominator_id.clone())
1562 .map_err(crate::pallet::Error::<T>::from)?;
1563
1564 Ok(Some(T::WeightInfo::unlock_funds(
1565 withdrawal_count.min(T::WithdrawalLimit::get()),
1566 ))
1567 .into())
1568 }
1569
1570 #[pallet::call_index(11)]
1573 #[pallet::weight(T::WeightInfo::unlock_nominator())]
1574 pub fn unlock_nominator(origin: OriginFor<T>, operator_id: OperatorId) -> DispatchResult {
1575 let nominator = ensure_signed(origin)?;
1576
1577 do_unlock_nominator::<T>(operator_id, nominator.clone())
1578 .map_err(crate::pallet::Error::<T>::from)?;
1579
1580 Self::deposit_event(Event::NominatorUnlocked {
1581 operator_id,
1582 nominator_id: nominator,
1583 });
1584
1585 Ok(())
1586 }
1587
1588 #[pallet::call_index(12)]
1596 #[pallet::weight(T::WeightInfo::update_domain_operator_allow_list())]
1597 pub fn update_domain_operator_allow_list(
1598 origin: OriginFor<T>,
1599 domain_id: DomainId,
1600 operator_allow_list: OperatorAllowList<T::AccountId>,
1601 ) -> DispatchResult {
1602 let who = ensure_signed(origin)?;
1603 do_update_domain_allow_list::<T>(who, domain_id, operator_allow_list)
1604 .map_err(Error::<T>::from)?;
1605 Self::deposit_event(crate::pallet::Event::DomainOperatorAllowListUpdated { domain_id });
1606 Ok(())
1607 }
1608
1609 #[pallet::call_index(13)]
1611 #[pallet::weight(Pallet::<T>::max_staking_epoch_transition())]
1612 pub fn force_staking_epoch_transition(
1613 origin: OriginFor<T>,
1614 domain_id: DomainId,
1615 ) -> DispatchResultWithPostInfo {
1616 ensure_root(origin)?;
1617
1618 let epoch_transition_res =
1619 do_finalize_domain_current_epoch::<T>(domain_id).map_err(Error::<T>::from)?;
1620
1621 Self::deposit_event(Event::ForceDomainEpochTransition {
1622 domain_id,
1623 completed_epoch_index: epoch_transition_res.completed_epoch_index,
1624 });
1625
1626 let actual_weight = Self::actual_epoch_transition_weight(epoch_transition_res)
1628 .min(Self::max_staking_epoch_transition());
1629
1630 Ok(Some(actual_weight).into())
1631 }
1632
1633 #[pallet::call_index(14)]
1635 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
1636 pub fn set_permissioned_action_allowed_by(
1637 origin: OriginFor<T>,
1638 permissioned_action_allowed_by: sp_domains::PermissionedActionAllowedBy<T::AccountId>,
1639 ) -> DispatchResult {
1640 ensure_root(origin)?;
1641 PermissionedActionAllowedBy::<T>::put(permissioned_action_allowed_by);
1642 Ok(())
1643 }
1644
1645 #[pallet::call_index(16)]
1647 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(3, 1))]
1648 pub fn send_domain_sudo_call(
1649 origin: OriginFor<T>,
1650 domain_id: DomainId,
1651 call: Vec<u8>,
1652 ) -> DispatchResult {
1653 ensure_root(origin)?;
1654 ensure!(
1655 DomainSudoCalls::<T>::get(domain_id).maybe_call.is_none(),
1656 Error::<T>::DomainSudoCallExists
1657 );
1658
1659 let domain_runtime = Self::domain_runtime_code(domain_id).ok_or(
1660 Error::<T>::DomainRegistry(DomainRegistryError::DomainNotFound),
1661 )?;
1662 ensure!(
1663 domain_runtime_call(
1664 domain_runtime,
1665 StatelessDomainRuntimeCall::IsValidDomainSudoCall(call.clone()),
1666 )
1667 .unwrap_or(false),
1668 Error::<T>::InvalidDomainSudoCall
1669 );
1670
1671 DomainSudoCalls::<T>::set(
1672 domain_id,
1673 DomainSudoCall {
1674 maybe_call: Some(call),
1675 },
1676 );
1677 Ok(())
1678 }
1679
1680 #[pallet::call_index(17)]
1683 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
1684 pub fn freeze_domain(origin: OriginFor<T>, domain_id: DomainId) -> DispatchResult {
1685 ensure_root(origin)?;
1686 FrozenDomains::<T>::mutate(|frozen_domains| frozen_domains.insert(domain_id));
1687 Self::deposit_event(Event::DomainFrozen { domain_id });
1688 Ok(())
1689 }
1690
1691 #[pallet::call_index(18)]
1693 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
1694 pub fn unfreeze_domain(origin: OriginFor<T>, domain_id: DomainId) -> DispatchResult {
1695 ensure_root(origin)?;
1696 FrozenDomains::<T>::mutate(|frozen_domains| frozen_domains.remove(&domain_id));
1697 Self::deposit_event(Event::DomainUnfrozen { domain_id });
1698 Ok(())
1699 }
1700
1701 #[pallet::call_index(19)]
1705 #[pallet::weight(Pallet::<T>::max_prune_domain_execution_receipt())]
1706 pub fn prune_domain_execution_receipt(
1707 origin: OriginFor<T>,
1708 domain_id: DomainId,
1709 bad_receipt_hash: ReceiptHashFor<T>,
1710 ) -> DispatchResultWithPostInfo {
1711 ensure_root(origin)?;
1712 ensure!(
1713 FrozenDomains::<T>::get().contains(&domain_id),
1714 Error::<T>::DomainNotFrozen
1715 );
1716
1717 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
1718 let bad_receipt_number = *BlockTreeNodes::<T>::get(bad_receipt_hash)
1719 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?
1720 .execution_receipt
1721 .domain_block_number();
1722 ensure!(
1725 head_receipt_number >= bad_receipt_number,
1726 Error::<T>::from(FraudProofError::BadReceiptNotFound),
1727 );
1728
1729 let mut actual_weight = T::DbWeight::get().reads(3);
1730
1731 let BlockTreeNode {
1733 execution_receipt,
1734 operator_ids,
1735 } = prune_receipt::<T>(domain_id, bad_receipt_number)
1736 .map_err(Error::<T>::from)?
1737 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?;
1738
1739 actual_weight = actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt(
1740 (operator_ids.len() as u32).min(MAX_BUNDLE_PER_BLOCK),
1741 ));
1742
1743 do_mark_operators_as_slashed::<T>(
1744 operator_ids.into_iter(),
1745 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1746 )
1747 .map_err(Error::<T>::from)?;
1748
1749 do_unmark_invalid_bundle_authors::<T>(domain_id, &execution_receipt)
1751 .map_err(Error::<T>::Staking)?;
1752
1753 let new_head_receipt_number = bad_receipt_number.saturating_sub(One::one());
1755 HeadReceiptNumber::<T>::insert(domain_id, new_head_receipt_number);
1756 actual_weight = actual_weight.saturating_add(T::DbWeight::get().reads_writes(0, 1));
1757
1758 Self::deposit_event(Event::PrunedExecutionReceipt {
1759 domain_id,
1760 new_head_receipt_number: Some(new_head_receipt_number),
1761 });
1762
1763 Ok(Some(actual_weight).into())
1764 }
1765
1766 #[pallet::call_index(20)]
1768 #[pallet::weight(T::WeightInfo::transfer_treasury_funds())]
1769 pub fn transfer_treasury_funds(
1770 origin: OriginFor<T>,
1771 account_id: T::AccountId,
1772 balance: BalanceOf<T>,
1773 ) -> DispatchResult {
1774 ensure_root(origin)?;
1775 T::Currency::transfer(
1776 &T::TreasuryAccount::get(),
1777 &account_id,
1778 balance,
1779 Preservation::Preserve,
1780 )?;
1781 Ok(())
1782 }
1783
1784 #[pallet::call_index(21)]
1785 #[pallet::weight(Pallet::<T>::max_submit_receipt_weight())]
1786 pub fn submit_receipt(
1787 origin: OriginFor<T>,
1788 singleton_receipt: SingletonReceiptOf<T>,
1789 ) -> DispatchResultWithPostInfo {
1790 T::DomainOrigin::ensure_origin(origin)?;
1791
1792 let domain_id = singleton_receipt.domain_id();
1793 let operator_id = singleton_receipt.operator_id();
1794 let receipt = singleton_receipt.into_receipt();
1795
1796 #[cfg(not(feature = "runtime-benchmarks"))]
1797 let mut actual_weight = T::WeightInfo::submit_receipt();
1798 #[cfg(feature = "runtime-benchmarks")]
1799 let actual_weight = T::WeightInfo::submit_receipt();
1800
1801 match execution_receipt_type::<T>(domain_id, &receipt.as_execution_receipt_ref()) {
1802 ReceiptType::Rejected(rejected_receipt_type) => {
1803 return Err(Error::<T>::BlockTree(rejected_receipt_type.into()).into());
1804 }
1805 ReceiptType::Accepted(accepted_receipt_type) => {
1807 #[cfg(not(feature = "runtime-benchmarks"))]
1813 if accepted_receipt_type == AcceptedReceiptType::NewHead
1814 && let Some(BlockTreeNode {
1815 execution_receipt,
1816 operator_ids,
1817 }) = prune_receipt::<T>(domain_id, *receipt.domain_block_number())
1818 .map_err(Error::<T>::from)?
1819 {
1820 actual_weight = actual_weight.saturating_add(
1821 T::WeightInfo::handle_bad_receipt(operator_ids.len() as u32),
1822 );
1823
1824 let bad_receipt_hash = execution_receipt.hash::<DomainHashingFor<T>>();
1825 do_mark_operators_as_slashed::<T>(
1826 operator_ids.into_iter(),
1827 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1828 )
1829 .map_err(Error::<T>::from)?;
1830
1831 do_unmark_invalid_bundle_authors::<T>(domain_id, &execution_receipt)
1832 .map_err(Error::<T>::from)?;
1833 }
1834
1835 if accepted_receipt_type == AcceptedReceiptType::NewHead {
1836 do_mark_invalid_bundle_authors::<T>(domain_id, &receipt)
1839 .map_err(Error::<T>::Staking)?;
1840 }
1841
1842 #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
1843 let maybe_confirmed_domain_block_info = process_execution_receipt::<T>(
1844 domain_id,
1845 operator_id,
1846 receipt,
1847 accepted_receipt_type,
1848 )
1849 .map_err(Error::<T>::from)?;
1850
1851 #[cfg(not(feature = "runtime-benchmarks"))]
1854 if let Some(confirmed_block_info) = maybe_confirmed_domain_block_info {
1855 actual_weight =
1856 actual_weight.saturating_add(T::WeightInfo::confirm_domain_block(
1857 confirmed_block_info.operator_ids.len() as u32,
1858 confirmed_block_info.invalid_bundle_authors.len() as u32,
1859 ));
1860
1861 refund_storage_fee::<T>(
1862 confirmed_block_info.total_storage_fee,
1863 confirmed_block_info.paid_bundle_storage_fees,
1864 )
1865 .map_err(Error::<T>::from)?;
1866
1867 do_reward_operators::<T>(
1868 domain_id,
1869 OperatorRewardSource::Bundle {
1870 at_block_number: confirmed_block_info.consensus_block_number,
1871 },
1872 confirmed_block_info.operator_ids.into_iter(),
1873 confirmed_block_info.rewards,
1874 )
1875 .map_err(Error::<T>::from)?;
1876
1877 do_mark_operators_as_slashed::<T>(
1878 confirmed_block_info.invalid_bundle_authors.into_iter(),
1879 SlashedReason::InvalidBundle(confirmed_block_info.domain_block_number),
1880 )
1881 .map_err(Error::<T>::from)?;
1882 }
1883 }
1884 }
1885
1886 #[cfg(not(feature = "runtime-benchmarks"))]
1888 {
1889 let slashed_nominator_count =
1890 do_slash_operator::<T>(domain_id, MAX_NOMINATORS_TO_SLASH)
1891 .map_err(Error::<T>::from)?;
1892 actual_weight = actual_weight
1893 .saturating_add(T::WeightInfo::slash_operator(slashed_nominator_count));
1894 }
1895
1896 Ok(Some(actual_weight.min(Self::max_submit_receipt_weight())).into())
1898 }
1899
1900 #[pallet::call_index(22)]
1902 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(3, 1))]
1903 pub fn send_evm_domain_set_contract_creation_allowed_by_call(
1904 origin: OriginFor<T>,
1905 domain_id: DomainId,
1906 contract_creation_allowed_by: sp_domains::PermissionedActionAllowedBy<
1907 EthereumAccountId,
1908 >,
1909 ) -> DispatchResult {
1910 let signer = ensure_signed_or_root(origin)?;
1911
1912 ensure!(
1913 Pallet::<T>::is_private_evm_domain(domain_id),
1914 Error::<T>::NotPrivateEvmDomain,
1915 );
1916 if let Some(non_root_signer) = signer {
1917 ensure!(
1918 Pallet::<T>::is_domain_owner(domain_id, non_root_signer),
1919 Error::<T>::NotDomainOwnerOrRoot,
1920 );
1921 }
1922 ensure!(
1923 EvmDomainContractCreationAllowedByCalls::<T>::get(domain_id)
1924 .maybe_call
1925 .is_none(),
1926 Error::<T>::EvmDomainContractCreationAllowedByCallExists,
1927 );
1928
1929 EvmDomainContractCreationAllowedByCalls::<T>::set(
1930 domain_id,
1931 EvmDomainContractCreationAllowedByCall {
1932 maybe_call: Some(contract_creation_allowed_by),
1933 },
1934 );
1935
1936 Ok(())
1937 }
1938 }
1939
1940 #[pallet::genesis_config]
1941 pub struct GenesisConfig<T: Config> {
1942 pub permissioned_action_allowed_by:
1943 Option<sp_domains::PermissionedActionAllowedBy<T::AccountId>>,
1944 pub genesis_domains: Vec<GenesisDomain<T::AccountId, BalanceOf<T>>>,
1945 }
1946
1947 impl<T: Config> Default for GenesisConfig<T> {
1948 fn default() -> Self {
1949 GenesisConfig {
1950 permissioned_action_allowed_by: None,
1951 genesis_domains: vec![],
1952 }
1953 }
1954 }
1955
1956 #[pallet::genesis_build]
1957 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
1958 fn build(&self) {
1959 if let Some(permissioned_action_allowed_by) =
1960 self.permissioned_action_allowed_by.as_ref().cloned()
1961 {
1962 PermissionedActionAllowedBy::<T>::put(permissioned_action_allowed_by)
1963 }
1964
1965 self.genesis_domains
1966 .clone()
1967 .into_iter()
1968 .for_each(|genesis_domain| {
1969 let runtime_id = register_runtime_at_genesis::<T>(
1971 genesis_domain.runtime_name,
1972 genesis_domain.runtime_type,
1973 genesis_domain.runtime_version,
1974 genesis_domain.raw_genesis_storage,
1975 Zero::zero(),
1976 )
1977 .expect("Genesis runtime registration must always succeed");
1978
1979 let domain_config_params = DomainConfigParams {
1981 domain_name: genesis_domain.domain_name,
1982 runtime_id,
1983 maybe_bundle_limit: None,
1984 bundle_slot_probability: genesis_domain.bundle_slot_probability,
1985 operator_allow_list: genesis_domain.operator_allow_list,
1986 initial_balances: genesis_domain.initial_balances,
1987 domain_runtime_config: genesis_domain.domain_runtime_config,
1988 };
1989 let domain_owner = genesis_domain.owner_account_id;
1990 let domain_id = do_instantiate_domain::<T>(
1991 domain_config_params,
1992 domain_owner.clone(),
1993 Zero::zero(),
1994 )
1995 .expect("Genesis domain instantiation must always succeed");
1996
1997 let operator_config = OperatorConfig {
1999 signing_key: genesis_domain.signing_key.clone(),
2000 minimum_nominator_stake: genesis_domain.minimum_nominator_stake,
2001 nomination_tax: genesis_domain.nomination_tax,
2002 };
2003 let operator_stake = T::MinOperatorStake::get();
2004 do_register_operator::<T>(
2005 domain_owner,
2006 domain_id,
2007 operator_stake,
2008 operator_config,
2009 )
2010 .expect("Genesis operator registration must succeed");
2011
2012 do_finalize_domain_current_epoch::<T>(domain_id)
2013 .expect("Genesis epoch must succeed");
2014 });
2015 }
2016 }
2017
2018 #[pallet::storage]
2020 pub type BlockInherentExtrinsicData<T> = StorageValue<_, InherentExtrinsicData>;
2021
2022 #[pallet::hooks]
2023 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
2025 fn on_initialize(block_number: BlockNumberFor<T>) -> Weight {
2026 let parent_number = block_number - One::one();
2027 let parent_hash = frame_system::Pallet::<T>::block_hash(parent_number);
2028
2029 for runtime_id in DomainRuntimeUpgrades::<T>::take() {
2031 let reference_count = RuntimeRegistry::<T>::get(runtime_id)
2032 .expect("Runtime object must be present since domain is insantiated; qed")
2033 .instance_count;
2034 if !reference_count.is_zero() {
2035 DomainRuntimeUpgradeRecords::<T>::mutate(runtime_id, |upgrade_record| {
2036 upgrade_record.insert(
2037 parent_number,
2038 DomainRuntimeUpgradeEntry {
2039 at_hash: parent_hash,
2040 reference_count,
2041 },
2042 )
2043 });
2044 }
2045 }
2046 DomainRuntimeUpgrades::<T>::set(Vec::new());
2049 do_upgrade_runtimes::<T>(block_number);
2052
2053 for (domain_id, _) in SuccessfulBundles::<T>::drain() {
2056 ConsensusBlockHash::<T>::insert(domain_id, parent_number, parent_hash);
2057 T::DomainBundleSubmitted::domain_bundle_submitted(domain_id);
2058
2059 DomainSudoCalls::<T>::mutate(domain_id, |sudo_call| {
2061 sudo_call.clear();
2062 });
2063 EvmDomainContractCreationAllowedByCalls::<T>::mutate(
2064 domain_id,
2065 |evm_contract_call| {
2066 evm_contract_call.clear();
2067 },
2068 );
2069 }
2070
2071 for (operator_id, slot_set) in OperatorBundleSlot::<T>::drain() {
2072 if let Some(highest_slot) = slot_set.last() {
2075 OperatorHighestSlot::<T>::insert(operator_id, highest_slot);
2076 }
2077 }
2078
2079 BlockInherentExtrinsicData::<T>::kill();
2080
2081 Weight::zero()
2082 }
2083
2084 fn on_finalize(_: BlockNumberFor<T>) {
2085 if SuccessfulBundles::<T>::iter_keys().count() > 0
2088 || !DomainRuntimeUpgrades::<T>::get().is_empty()
2089 {
2090 let extrinsics_shuffling_seed = Randomness::from(
2091 Into::<H256>::into(Self::extrinsics_shuffling_seed_value()).to_fixed_bytes(),
2092 );
2093
2094 let timestamp = Self::timestamp_value();
2097
2098 let consensus_transaction_byte_fee = Self::consensus_transaction_byte_fee_value();
2100
2101 let inherent_extrinsic_data = InherentExtrinsicData {
2102 extrinsics_shuffling_seed,
2103 timestamp,
2104 consensus_transaction_byte_fee,
2105 };
2106
2107 BlockInherentExtrinsicData::<T>::set(Some(inherent_extrinsic_data));
2108 }
2109
2110 let _ = LastEpochStakingDistribution::<T>::clear(u32::MAX, None);
2111 let _ = NewAddedHeadReceipt::<T>::clear(u32::MAX, None);
2112 }
2113 }
2114}
2115
2116impl<T: Config> Pallet<T> {
2117 fn log_bundle_error(err: &BundleError, domain_id: DomainId, operator_id: OperatorId) {
2118 match err {
2119 BundleError::Receipt(BlockTreeError::InFutureReceipt)
2122 | BundleError::Receipt(BlockTreeError::StaleReceipt)
2123 | BundleError::Receipt(BlockTreeError::NewBranchReceipt)
2124 | BundleError::Receipt(BlockTreeError::UnavailableConsensusBlockHash)
2125 | BundleError::Receipt(BlockTreeError::BuiltOnUnknownConsensusBlock)
2126 | BundleError::SlotInThePast
2127 | BundleError::SlotInTheFuture
2128 | BundleError::InvalidProofOfTime
2129 | BundleError::SlotSmallerThanPreviousBlockBundle
2130 | BundleError::ExpectingReceiptGap
2131 | BundleError::UnexpectedReceiptGap => {
2132 log::debug!(
2133 "Bad bundle/receipt, domain {domain_id:?}, operator {operator_id:?}, error: {err:?}",
2134 );
2135 }
2136 _ => {
2137 log::warn!(
2138 "Bad bundle/receipt, domain {domain_id:?}, operator {operator_id:?}, error: {err:?}",
2139 );
2140 }
2141 }
2142 }
2143
2144 pub fn successful_bundles(domain_id: DomainId) -> Vec<H256> {
2145 SuccessfulBundles::<T>::get(domain_id)
2146 }
2147
2148 pub fn domain_runtime_code(domain_id: DomainId) -> Option<Vec<u8>> {
2149 RuntimeRegistry::<T>::get(Self::runtime_id(domain_id)?)
2150 .and_then(|mut runtime_object| runtime_object.raw_genesis.take_runtime_code())
2151 }
2152
2153 pub fn domain_best_number(domain_id: DomainId) -> Result<DomainBlockNumberFor<T>, BundleError> {
2154 let missed_upgrade = Self::missed_domain_runtime_upgrade(domain_id)
2157 .map_err(|_| BundleError::FailedToGetMissedUpgradeCount)?;
2158
2159 Ok(HeadDomainNumber::<T>::get(domain_id) + missed_upgrade.into())
2160 }
2161
2162 pub fn runtime_id(domain_id: DomainId) -> Option<RuntimeId> {
2164 DomainRegistry::<T>::get(domain_id)
2165 .map(|domain_object| domain_object.domain_config.runtime_id)
2166 }
2167
2168 pub fn runtime_upgrades() -> Vec<RuntimeId> {
2170 DomainRuntimeUpgrades::<T>::get()
2171 }
2172
2173 pub fn domain_instance_data(
2174 domain_id: DomainId,
2175 ) -> Option<(DomainInstanceData, BlockNumberFor<T>)> {
2176 let domain_obj = DomainRegistry::<T>::get(domain_id)?;
2177 let runtime_object = RuntimeRegistry::<T>::get(domain_obj.domain_config.runtime_id)?;
2178 let runtime_type = runtime_object.runtime_type;
2179 let total_issuance = domain_obj.domain_config.total_issuance()?;
2180 let raw_genesis = into_complete_raw_genesis::<T>(
2181 runtime_object,
2182 domain_id,
2183 &domain_obj.domain_runtime_info,
2184 total_issuance,
2185 domain_obj.domain_config.initial_balances,
2186 )
2187 .ok()?;
2188 Some((
2189 DomainInstanceData {
2190 runtime_type,
2191 raw_genesis,
2192 },
2193 domain_obj.created_at,
2194 ))
2195 }
2196
2197 pub fn domain_tx_range(domain_id: DomainId) -> U256 {
2199 DomainTxRangeState::<T>::try_get(domain_id)
2200 .map(|state| state.tx_range)
2201 .ok()
2202 .unwrap_or_else(Self::initial_tx_range)
2203 }
2204
2205 pub fn bundle_producer_election_params(
2206 domain_id: DomainId,
2207 ) -> Option<BundleProducerElectionParams<BalanceOf<T>>> {
2208 match (
2209 DomainRegistry::<T>::get(domain_id),
2210 DomainStakingSummary::<T>::get(domain_id),
2211 ) {
2212 (Some(domain_object), Some(stake_summary)) => Some(BundleProducerElectionParams {
2213 total_domain_stake: stake_summary.current_total_stake,
2214 bundle_slot_probability: domain_object.domain_config.bundle_slot_probability,
2215 }),
2216 _ => None,
2217 }
2218 }
2219
2220 pub fn operator(operator_id: OperatorId) -> Option<(OperatorPublicKey, BalanceOf<T>)> {
2221 Operators::<T>::get(operator_id)
2222 .map(|operator| (operator.signing_key, operator.current_total_stake))
2223 }
2224
2225 fn check_extrinsics_root(opaque_bundle: &OpaqueBundleOf<T>) -> Result<(), BundleError> {
2226 let expected_extrinsics_root = <T::DomainHeader as Header>::Hashing::ordered_trie_root(
2227 opaque_bundle
2228 .extrinsics()
2229 .iter()
2230 .map(|xt| xt.encode())
2231 .collect(),
2232 sp_core::storage::StateVersion::V1,
2233 );
2234 ensure!(
2235 expected_extrinsics_root == opaque_bundle.extrinsics_root(),
2236 BundleError::InvalidExtrinsicRoot
2237 );
2238 Ok(())
2239 }
2240
2241 fn check_slot_and_proof_of_time(
2242 slot_number: u64,
2243 proof_of_time: PotOutput,
2244 pre_dispatch: bool,
2245 ) -> Result<(), BundleError> {
2246 let current_block_number = frame_system::Pallet::<T>::current_block_number();
2249
2250 if pre_dispatch && let Some(future_slot) = T::BlockSlot::future_slot(current_block_number) {
2256 ensure!(slot_number <= *future_slot, BundleError::SlotInTheFuture)
2257 }
2258
2259 let produced_after_block_number =
2261 match T::BlockSlot::slot_produced_after(slot_number.into()) {
2262 Some(n) => n,
2263 None => {
2264 if current_block_number > T::BundleLongevity::get().into() {
2267 return Err(BundleError::SlotInThePast);
2268 } else {
2269 Zero::zero()
2270 }
2271 }
2272 };
2273 let produced_after_block_hash = if produced_after_block_number == current_block_number {
2274 frame_system::Pallet::<T>::parent_hash()
2276 } else {
2277 frame_system::Pallet::<T>::block_hash(produced_after_block_number)
2278 };
2279 if let Some(last_eligible_block) =
2280 current_block_number.checked_sub(&T::BundleLongevity::get().into())
2281 {
2282 ensure!(
2283 produced_after_block_number >= last_eligible_block,
2284 BundleError::SlotInThePast
2285 );
2286 }
2287
2288 if !is_proof_of_time_valid(
2289 BlockHash::try_from(produced_after_block_hash.as_ref())
2290 .expect("Must be able to convert to block hash type"),
2291 SlotNumber::from(slot_number),
2292 WrappedPotOutput::from(proof_of_time),
2293 !pre_dispatch,
2295 ) {
2296 return Err(BundleError::InvalidProofOfTime);
2297 }
2298
2299 Ok(())
2300 }
2301
2302 fn validate_bundle(
2303 opaque_bundle: &OpaqueBundleOf<T>,
2304 domain_config: &DomainConfig<T::AccountId, BalanceOf<T>>,
2305 ) -> Result<(), BundleError> {
2306 ensure!(
2307 opaque_bundle.body_size() <= domain_config.max_bundle_size,
2308 BundleError::BundleTooLarge
2309 );
2310
2311 ensure!(
2312 opaque_bundle
2313 .estimated_weight()
2314 .all_lte(domain_config.max_bundle_weight),
2315 BundleError::BundleTooHeavy
2316 );
2317
2318 Self::check_extrinsics_root(opaque_bundle)?;
2319
2320 Ok(())
2321 }
2322
2323 fn validate_eligibility(
2324 to_sign: &[u8],
2325 signature: &OperatorSignature,
2326 proof_of_election: &ProofOfElection,
2327 domain_config: &DomainConfig<T::AccountId, BalanceOf<T>>,
2328 pre_dispatch: bool,
2329 ) -> Result<(), BundleError> {
2330 let domain_id = proof_of_election.domain_id;
2331 let operator_id = proof_of_election.operator_id;
2332 let slot_number = proof_of_election.slot_number;
2333
2334 ensure!(
2335 !FrozenDomains::<T>::get().contains(&domain_id),
2336 BundleError::DomainFrozen
2337 );
2338
2339 let operator = Operators::<T>::get(operator_id).ok_or(BundleError::InvalidOperatorId)?;
2340
2341 let operator_status = operator.status::<T>(operator_id);
2342 ensure!(
2343 *operator_status != OperatorStatus::Slashed
2344 && *operator_status != OperatorStatus::PendingSlash
2345 && !matches!(operator_status, OperatorStatus::InvalidBundle(_)),
2346 BundleError::BadOperator
2347 );
2348
2349 if !operator.signing_key.verify(&to_sign, signature) {
2350 return Err(BundleError::BadBundleSignature);
2351 }
2352
2353 ensure!(
2355 slot_number
2356 > Self::operator_highest_slot_from_previous_block(operator_id, pre_dispatch),
2357 BundleError::SlotSmallerThanPreviousBlockBundle,
2358 );
2359
2360 ensure!(
2362 !OperatorBundleSlot::<T>::get(operator_id).contains(&slot_number),
2363 BundleError::EquivocatedBundle,
2364 );
2365
2366 let (operator_stake, total_domain_stake) =
2367 Self::fetch_operator_stake_info(domain_id, &operator_id)?;
2368
2369 Self::check_slot_and_proof_of_time(
2370 slot_number,
2371 proof_of_election.proof_of_time,
2372 pre_dispatch,
2373 )?;
2374
2375 sp_domains::bundle_producer_election::check_proof_of_election(
2376 &operator.signing_key,
2377 domain_config.bundle_slot_probability,
2378 proof_of_election,
2379 operator_stake.saturated_into(),
2380 total_domain_stake.saturated_into(),
2381 )?;
2382
2383 Ok(())
2384 }
2385
2386 fn check_execution_receipt_version(
2389 er_derived_consensus_number: BlockNumberFor<T>,
2390 receipt_version: ExecutionReceiptVersion,
2391 ) -> Result<(), BundleError> {
2392 let expected_execution_receipt_version =
2393 Self::bundle_and_execution_receipt_version_for_consensus_number(
2394 er_derived_consensus_number,
2395 PreviousBundleAndExecutionReceiptVersions::<T>::get(),
2396 T::CurrentBundleAndExecutionReceiptVersion::get(),
2397 )
2398 .ok_or(BundleError::ExecutionVersionMissing)?
2399 .execution_receipt_version;
2400 match (receipt_version, expected_execution_receipt_version) {
2401 (ExecutionReceiptVersion::V0, ExecutionReceiptVersion::V0) => Ok(()),
2402 }
2403 }
2404
2405 fn validate_submit_bundle(
2406 opaque_bundle: &OpaqueBundleOf<T>,
2407 pre_dispatch: bool,
2408 ) -> Result<(), BundleError> {
2409 let current_bundle_version =
2410 T::CurrentBundleAndExecutionReceiptVersion::get().bundle_version;
2411
2412 match (current_bundle_version, opaque_bundle) {
2414 (BundleVersion::V0, Bundle::V0(_)) => Ok::<(), BundleError>(()),
2415 }?;
2416
2417 let domain_id = opaque_bundle.domain_id();
2418 let operator_id = opaque_bundle.operator_id();
2419 let sealed_header = opaque_bundle.sealed_header();
2420
2421 let receipt = sealed_header.receipt();
2422 Self::check_execution_receipt_version(
2423 *receipt.consensus_block_number(),
2424 receipt.version(),
2425 )?;
2426
2427 ensure!(
2431 Self::receipt_gap(domain_id)? <= One::one(),
2432 BundleError::UnexpectedReceiptGap,
2433 );
2434
2435 let domain_config = &DomainRegistry::<T>::get(domain_id)
2436 .ok_or(BundleError::InvalidDomainId)?
2437 .domain_config;
2438
2439 Self::validate_bundle(opaque_bundle, domain_config)?;
2440
2441 Self::validate_eligibility(
2442 sealed_header.pre_hash().as_ref(),
2443 sealed_header.signature(),
2444 sealed_header.proof_of_election(),
2445 domain_config,
2446 pre_dispatch,
2447 )?;
2448
2449 verify_execution_receipt::<T>(domain_id, &receipt).map_err(BundleError::Receipt)?;
2450
2451 charge_bundle_storage_fee::<T>(operator_id, opaque_bundle.size())
2452 .map_err(|_| BundleError::UnableToPayBundleStorageFee)?;
2453
2454 Ok(())
2455 }
2456
2457 fn validate_singleton_receipt(
2458 sealed_singleton_receipt: &SingletonReceiptOf<T>,
2459 pre_dispatch: bool,
2460 ) -> Result<(), BundleError> {
2461 let domain_id = sealed_singleton_receipt.domain_id();
2462 let operator_id = sealed_singleton_receipt.operator_id();
2463
2464 ensure!(
2466 Self::receipt_gap(domain_id)? > One::one(),
2467 BundleError::ExpectingReceiptGap,
2468 );
2469
2470 Self::check_execution_receipt_version(
2471 *sealed_singleton_receipt
2472 .singleton_receipt
2473 .receipt
2474 .consensus_block_number(),
2475 sealed_singleton_receipt.singleton_receipt.receipt.version(),
2476 )?;
2477
2478 let domain_config = DomainRegistry::<T>::get(domain_id)
2479 .ok_or(BundleError::InvalidDomainId)?
2480 .domain_config;
2481 Self::validate_eligibility(
2482 sealed_singleton_receipt.pre_hash().as_ref(),
2483 &sealed_singleton_receipt.signature,
2484 &sealed_singleton_receipt.singleton_receipt.proof_of_election,
2485 &domain_config,
2486 pre_dispatch,
2487 )?;
2488
2489 verify_execution_receipt::<T>(
2490 domain_id,
2491 &sealed_singleton_receipt
2492 .singleton_receipt
2493 .receipt
2494 .as_execution_receipt_ref(),
2495 )
2496 .map_err(BundleError::Receipt)?;
2497
2498 charge_bundle_storage_fee::<T>(operator_id, sealed_singleton_receipt.size())
2499 .map_err(|_| BundleError::UnableToPayBundleStorageFee)?;
2500
2501 Ok(())
2502 }
2503
2504 fn validate_fraud_proof(
2505 fraud_proof: &FraudProofFor<T>,
2506 ) -> Result<(DomainId, TransactionPriority), FraudProofError> {
2507 let domain_id = fraud_proof.domain_id();
2508 let bad_receipt_hash = fraud_proof.targeted_bad_receipt_hash();
2509 let bad_receipt = BlockTreeNodes::<T>::get(bad_receipt_hash)
2510 .ok_or(FraudProofError::BadReceiptNotFound)?
2511 .execution_receipt;
2512 let bad_receipt_domain_block_number = *bad_receipt.domain_block_number();
2513
2514 ensure!(
2515 !bad_receipt_domain_block_number.is_zero(),
2516 FraudProofError::ChallengingGenesisReceipt
2517 );
2518
2519 ensure!(
2520 !Self::is_bad_er_pending_to_prune(domain_id, bad_receipt_domain_block_number),
2521 FraudProofError::BadReceiptAlreadyReported,
2522 );
2523
2524 ensure!(
2525 !fraud_proof.is_unexpected_domain_runtime_code_proof(),
2526 FraudProofError::UnexpectedDomainRuntimeCodeProof,
2527 );
2528
2529 ensure!(
2530 !fraud_proof.is_unexpected_mmr_proof(),
2531 FraudProofError::UnexpectedMmrProof,
2532 );
2533
2534 let maybe_state_root = match &fraud_proof.maybe_mmr_proof {
2535 Some(mmr_proof) => Some(Self::verify_mmr_proof_and_extract_state_root(
2536 mmr_proof.clone(),
2537 *bad_receipt.consensus_block_number(),
2538 )?),
2539 None => None,
2540 };
2541
2542 match &fraud_proof.proof {
2543 FraudProofVariant::InvalidBlockFees(InvalidBlockFeesProof { storage_proof }) => {
2544 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2545 domain_id,
2546 &bad_receipt,
2547 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2548 )?;
2549
2550 verify_invalid_block_fees_fraud_proof::<
2551 T::Block,
2552 DomainBlockNumberFor<T>,
2553 T::DomainHash,
2554 BalanceOf<T>,
2555 DomainHashingFor<T>,
2556 >(bad_receipt, storage_proof, domain_runtime_code)
2557 .map_err(|err| {
2558 log::error!("Block fees proof verification failed: {err:?}");
2559 FraudProofError::InvalidBlockFeesFraudProof
2560 })?;
2561 }
2562 FraudProofVariant::InvalidTransfers(InvalidTransfersProof { storage_proof }) => {
2563 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2564 domain_id,
2565 &bad_receipt,
2566 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2567 )?;
2568
2569 verify_invalid_transfers_fraud_proof::<
2570 T::Block,
2571 DomainBlockNumberFor<T>,
2572 T::DomainHash,
2573 BalanceOf<T>,
2574 DomainHashingFor<T>,
2575 >(bad_receipt, storage_proof, domain_runtime_code)
2576 .map_err(|err| {
2577 log::error!("Domain transfers proof verification failed: {err:?}");
2578 FraudProofError::InvalidTransfersFraudProof
2579 })?;
2580 }
2581 FraudProofVariant::InvalidDomainBlockHash(InvalidDomainBlockHashProof {
2582 digest_storage_proof,
2583 }) => {
2584 let parent_receipt =
2585 BlockTreeNodes::<T>::get(*bad_receipt.parent_domain_block_receipt_hash())
2586 .ok_or(FraudProofError::ParentReceiptNotFound)?
2587 .execution_receipt;
2588 verify_invalid_domain_block_hash_fraud_proof::<
2589 T::Block,
2590 BalanceOf<T>,
2591 T::DomainHeader,
2592 >(
2593 bad_receipt,
2594 digest_storage_proof.clone(),
2595 *parent_receipt.domain_block_hash(),
2596 )
2597 .map_err(|err| {
2598 log::error!("Invalid Domain block hash proof verification failed: {err:?}");
2599 FraudProofError::InvalidDomainBlockHashFraudProof
2600 })?;
2601 }
2602 FraudProofVariant::InvalidExtrinsicsRoot(proof) => {
2603 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2604 domain_id,
2605 &bad_receipt,
2606 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2607 )?;
2608 let runtime_id =
2609 Self::runtime_id(domain_id).ok_or(FraudProofError::RuntimeNotFound)?;
2610 let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?;
2611
2612 verify_invalid_domain_extrinsics_root_fraud_proof::<
2613 T::Block,
2614 BalanceOf<T>,
2615 T::DomainHeader,
2616 T::Hashing,
2617 T::FraudProofStorageKeyProvider,
2618 >(
2619 bad_receipt,
2620 proof,
2621 domain_id,
2622 runtime_id,
2623 state_root,
2624 domain_runtime_code,
2625 )
2626 .map_err(|err| {
2627 log::error!("Invalid Domain extrinsic root proof verification failed: {err:?}");
2628 FraudProofError::InvalidExtrinsicRootFraudProof
2629 })?;
2630 }
2631 FraudProofVariant::InvalidStateTransition(proof) => {
2632 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2633 domain_id,
2634 &bad_receipt,
2635 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2636 )?;
2637 let bad_receipt_parent =
2638 BlockTreeNodes::<T>::get(*bad_receipt.parent_domain_block_receipt_hash())
2639 .ok_or(FraudProofError::ParentReceiptNotFound)?
2640 .execution_receipt;
2641
2642 verify_invalid_state_transition_fraud_proof::<
2643 T::Block,
2644 T::DomainHeader,
2645 BalanceOf<T>,
2646 >(bad_receipt, bad_receipt_parent, proof, domain_runtime_code)
2647 .map_err(|err| {
2648 log::error!("Invalid State transition proof verification failed: {err:?}");
2649 FraudProofError::InvalidStateTransitionFraudProof
2650 })?;
2651 }
2652 FraudProofVariant::InvalidBundles(proof) => {
2653 let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?;
2654 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2655 domain_id,
2656 &bad_receipt,
2657 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2658 )?;
2659
2660 let bad_receipt_parent =
2661 BlockTreeNodes::<T>::get(*bad_receipt.parent_domain_block_receipt_hash())
2662 .ok_or(FraudProofError::ParentReceiptNotFound)?
2663 .execution_receipt;
2664
2665 verify_invalid_bundles_fraud_proof::<
2666 T::Block,
2667 T::DomainHeader,
2668 T::MmrHash,
2669 BalanceOf<T>,
2670 T::FraudProofStorageKeyProvider,
2671 T::MmrProofVerifier,
2672 >(
2673 bad_receipt,
2674 bad_receipt_parent,
2675 proof,
2676 domain_id,
2677 state_root,
2678 domain_runtime_code,
2679 )
2680 .map_err(|err| {
2681 log::error!("Invalid Bundle proof verification failed: {err:?}");
2682 FraudProofError::InvalidBundleFraudProof
2683 })?;
2684 }
2685 FraudProofVariant::ValidBundle(proof) => {
2686 let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?;
2687 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2688 domain_id,
2689 &bad_receipt,
2690 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2691 )?;
2692
2693 verify_valid_bundle_fraud_proof::<
2694 T::Block,
2695 T::DomainHeader,
2696 BalanceOf<T>,
2697 T::FraudProofStorageKeyProvider,
2698 >(
2699 bad_receipt,
2700 proof,
2701 domain_id,
2702 state_root,
2703 domain_runtime_code,
2704 )
2705 .map_err(|err| {
2706 log::error!("Valid bundle proof verification failed: {err:?}");
2707 FraudProofError::BadValidBundleFraudProof
2708 })?
2709 }
2710 #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
2711 FraudProofVariant::Dummy => {
2712 let _ = Self::get_domain_runtime_code_for_receipt(
2719 domain_id,
2720 &bad_receipt,
2721 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2722 )?;
2723 }
2724 }
2725
2726 let block_before_bad_er_confirm = bad_receipt_domain_block_number.saturating_sub(
2729 Self::latest_confirmed_domain_block_number(fraud_proof.domain_id()),
2730 );
2731 let priority =
2732 TransactionPriority::MAX - block_before_bad_er_confirm.saturated_into::<u64>();
2733
2734 let tag = fraud_proof.domain_id();
2737
2738 Ok((tag, priority))
2739 }
2740
2741 fn fetch_operator_stake_info(
2746 domain_id: DomainId,
2747 operator_id: &OperatorId,
2748 ) -> Result<(BalanceOf<T>, BalanceOf<T>), BundleError> {
2749 if let Some(pending_election_params) = LastEpochStakingDistribution::<T>::get(domain_id)
2750 && let Some(operator_stake) = pending_election_params.operators.get(operator_id)
2751 {
2752 return Ok((*operator_stake, pending_election_params.total_domain_stake));
2753 }
2754 let domain_stake_summary =
2755 DomainStakingSummary::<T>::get(domain_id).ok_or(BundleError::InvalidDomainId)?;
2756 let operator_stake = domain_stake_summary
2757 .current_operators
2758 .get(operator_id)
2759 .ok_or(BundleError::BadOperator)?;
2760 Ok((*operator_stake, domain_stake_summary.current_total_stake))
2761 }
2762
2763 fn initial_tx_range() -> U256 {
2765 U256::MAX / T::InitialDomainTxRange::get()
2766 }
2767
2768 pub fn head_receipt_number(domain_id: DomainId) -> DomainBlockNumberFor<T> {
2770 HeadReceiptNumber::<T>::get(domain_id)
2771 }
2772
2773 pub fn oldest_unconfirmed_receipt_number(
2776 domain_id: DomainId,
2777 ) -> Option<DomainBlockNumberFor<T>> {
2778 let oldest_nonconfirmed_er_number =
2779 Self::latest_confirmed_domain_block_number(domain_id).saturating_add(One::one());
2780 let is_er_exist = BlockTree::<T>::get(domain_id, oldest_nonconfirmed_er_number).is_some();
2781 let is_pending_to_prune =
2782 Self::is_bad_er_pending_to_prune(domain_id, oldest_nonconfirmed_er_number);
2783
2784 if is_er_exist && !is_pending_to_prune {
2785 Some(oldest_nonconfirmed_er_number)
2786 } else {
2787 None
2792 }
2793 }
2794
2795 pub fn latest_confirmed_domain_block_number(domain_id: DomainId) -> DomainBlockNumberFor<T> {
2798 LatestConfirmedDomainExecutionReceipt::<T>::get(domain_id)
2799 .map(|er| *er.domain_block_number())
2800 .unwrap_or_default()
2801 }
2802
2803 pub fn latest_confirmed_domain_block(
2804 domain_id: DomainId,
2805 ) -> Option<(DomainBlockNumberFor<T>, T::DomainHash)> {
2806 LatestConfirmedDomainExecutionReceipt::<T>::get(domain_id)
2807 .map(|er| (*er.domain_block_number(), *er.domain_block_hash()))
2808 }
2809
2810 pub fn domain_bundle_limit(
2812 domain_id: DomainId,
2813 ) -> Result<Option<DomainBundleLimit>, DomainRegistryError> {
2814 let domain_config = match DomainRegistry::<T>::get(domain_id) {
2815 None => return Ok(None),
2816 Some(domain_obj) => domain_obj.domain_config,
2817 };
2818
2819 Ok(Some(DomainBundleLimit {
2820 max_bundle_size: domain_config.max_bundle_size,
2821 max_bundle_weight: domain_config.max_bundle_weight,
2822 }))
2823 }
2824
2825 pub fn non_empty_er_exists(domain_id: DomainId) -> bool {
2828 if BlockTree::<T>::contains_key(domain_id, DomainBlockNumberFor::<T>::zero()) {
2829 return true;
2830 }
2831
2832 let mut to_check =
2834 Self::latest_confirmed_domain_block_number(domain_id).saturating_add(One::one());
2835
2836 let head_number = HeadDomainNumber::<T>::get(domain_id);
2841
2842 while to_check <= head_number {
2843 if !ExecutionInbox::<T>::iter_prefix_values((domain_id, to_check)).all(|digests| {
2844 digests
2845 .iter()
2846 .all(|digest| digest.extrinsics_root == EMPTY_EXTRINSIC_ROOT.into())
2847 }) {
2848 return true;
2849 }
2850
2851 to_check = to_check.saturating_add(One::one())
2852 }
2853
2854 false
2855 }
2856
2857 pub fn extrinsics_shuffling_seed() -> T::Hash {
2860 BlockInherentExtrinsicData::<T>::get()
2862 .map(|data| H256::from(*data.extrinsics_shuffling_seed).into())
2863 .unwrap_or_else(|| Self::extrinsics_shuffling_seed_value())
2864 }
2865
2866 fn extrinsics_shuffling_seed_value() -> T::Hash {
2869 let subject = DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT;
2870 let (randomness, _) = T::Randomness::random(subject);
2871 randomness
2872 }
2873
2874 pub fn timestamp() -> Moment {
2877 BlockInherentExtrinsicData::<T>::get()
2879 .map(|data| data.timestamp)
2880 .unwrap_or_else(|| Self::timestamp_value())
2881 }
2882
2883 fn timestamp_value() -> Moment {
2886 T::BlockTimestamp::now()
2889 .try_into()
2890 .map_err(|_| ())
2891 .expect("Moment is the same type in both pallets; qed")
2892 }
2893
2894 pub fn consensus_transaction_byte_fee() -> Balance {
2898 BlockInherentExtrinsicData::<T>::get()
2900 .map(|data| data.consensus_transaction_byte_fee)
2901 .unwrap_or_else(|| Self::consensus_transaction_byte_fee_value())
2902 }
2903
2904 fn consensus_transaction_byte_fee_value() -> Balance {
2907 let transaction_byte_fee: Balance = T::StorageFee::transaction_byte_fee()
2910 .try_into()
2911 .map_err(|_| ())
2912 .expect("Balance is the same type in both pallets; qed");
2913
2914 sp_domains::DOMAIN_STORAGE_FEE_MULTIPLIER * transaction_byte_fee
2915 }
2916
2917 pub fn execution_receipt(receipt_hash: ReceiptHashFor<T>) -> Option<ExecutionReceiptOf<T>> {
2918 BlockTreeNodes::<T>::get(receipt_hash).map(|db| db.execution_receipt)
2919 }
2920
2921 pub(crate) fn bundle_and_execution_receipt_version_for_consensus_number<BEV>(
2924 er_derived_number: BlockNumberFor<T>,
2925 previous_versions: BTreeMap<BlockNumberFor<T>, BEV>,
2926 current_version: BEV,
2927 ) -> Option<BEV>
2928 where
2929 BEV: Copy + Clone,
2930 {
2931 match previous_versions.last_key_value() {
2933 None => {
2935 return Some(current_version);
2936 }
2937 Some((number, version)) => {
2938 if er_derived_number > *number {
2941 return Some(current_version);
2942 }
2943
2944 if er_derived_number == *number {
2947 return Some(*version);
2948 }
2949 }
2950 }
2951
2952 for (upgraded_number, version) in previous_versions.into_iter() {
2955 if er_derived_number <= upgraded_number {
2956 return Some(version);
2957 }
2958 }
2959
2960 None
2962 }
2963
2964 pub fn receipt_hash(
2965 domain_id: DomainId,
2966 domain_number: DomainBlockNumberFor<T>,
2967 ) -> Option<ReceiptHashFor<T>> {
2968 BlockTree::<T>::get(domain_id, domain_number)
2969 }
2970
2971 pub fn confirmed_domain_block_storage_key(domain_id: DomainId) -> Vec<u8> {
2972 LatestConfirmedDomainExecutionReceipt::<T>::hashed_key_for(domain_id)
2973 }
2974
2975 pub fn is_bad_er_pending_to_prune(
2976 domain_id: DomainId,
2977 receipt_number: DomainBlockNumberFor<T>,
2978 ) -> bool {
2979 if receipt_number.is_zero() {
2981 return false;
2982 }
2983
2984 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
2985
2986 head_receipt_number < receipt_number
2989 }
2990
2991 pub fn is_operator_pending_to_slash(domain_id: DomainId, operator_id: OperatorId) -> bool {
2992 let latest_submitted_er = LatestSubmittedER::<T>::get((domain_id, operator_id));
2993
2994 if latest_submitted_er.is_zero() {
2996 return false;
2997 }
2998
2999 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
3000
3001 head_receipt_number < latest_submitted_er
3005 }
3006
3007 pub fn max_submit_bundle_weight() -> Weight {
3008 T::WeightInfo::submit_bundle()
3009 .saturating_add(
3010 T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK).max(
3017 T::WeightInfo::confirm_domain_block(MAX_BUNDLE_PER_BLOCK, MAX_BUNDLE_PER_BLOCK),
3018 ),
3019 )
3020 .saturating_add(Self::max_staking_epoch_transition())
3021 .saturating_add(T::WeightInfo::slash_operator(MAX_NOMINATORS_TO_SLASH))
3022 }
3023
3024 pub fn max_submit_receipt_weight() -> Weight {
3025 T::WeightInfo::submit_bundle()
3026 .saturating_add(
3027 T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK).max(
3034 T::WeightInfo::confirm_domain_block(MAX_BUNDLE_PER_BLOCK, MAX_BUNDLE_PER_BLOCK),
3035 ),
3036 )
3037 .saturating_add(T::WeightInfo::slash_operator(MAX_NOMINATORS_TO_SLASH))
3038 }
3039
3040 pub fn max_staking_epoch_transition() -> Weight {
3041 T::WeightInfo::operator_reward_tax_and_restake(MAX_BUNDLE_PER_BLOCK).saturating_add(
3042 T::WeightInfo::finalize_domain_epoch_staking(T::MaxPendingStakingOperation::get()),
3043 )
3044 }
3045
3046 pub fn max_prune_domain_execution_receipt() -> Weight {
3047 T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK)
3048 .saturating_add(T::DbWeight::get().reads_writes(3, 1))
3049 }
3050
3051 fn actual_epoch_transition_weight(epoch_transition_res: EpochTransitionResult) -> Weight {
3052 let EpochTransitionResult {
3053 rewarded_operator_count,
3054 finalized_operator_count,
3055 completed_epoch_index: _,
3056 } = epoch_transition_res;
3057
3058 T::WeightInfo::operator_reward_tax_and_restake(rewarded_operator_count).saturating_add(
3059 T::WeightInfo::finalize_domain_epoch_staking(finalized_operator_count),
3060 )
3061 }
3062
3063 pub fn reward_domain_operators(domain_id: DomainId, rewards: BalanceOf<T>) {
3065 DomainChainRewards::<T>::mutate(domain_id, |current_rewards| {
3066 current_rewards.saturating_add(rewards)
3067 });
3068 }
3069
3070 pub fn storage_fund_account_balance(operator_id: OperatorId) -> BalanceOf<T> {
3071 let storage_fund_acc = storage_fund_account::<T>(operator_id);
3072 T::Currency::reducible_balance(&storage_fund_acc, Preservation::Preserve, Fortitude::Polite)
3073 }
3074
3075 pub fn operator_highest_slot_from_previous_block(
3079 operator_id: OperatorId,
3080 pre_dispatch: bool,
3081 ) -> u64 {
3082 if pre_dispatch {
3083 OperatorHighestSlot::<T>::get(operator_id)
3084 } else {
3085 *OperatorBundleSlot::<T>::get(operator_id)
3091 .last()
3092 .unwrap_or(&OperatorHighestSlot::<T>::get(operator_id))
3093 }
3094 }
3095
3096 pub fn get_domain_runtime_code_for_receipt(
3099 domain_id: DomainId,
3100 receipt: &ExecutionReceiptOf<T>,
3101 maybe_domain_runtime_code_at: Option<
3102 DomainRuntimeCodeAt<BlockNumberFor<T>, T::Hash, T::MmrHash>,
3103 >,
3104 ) -> Result<Vec<u8>, FraudProofError> {
3105 let runtime_id = Self::runtime_id(domain_id).ok_or(FraudProofError::RuntimeNotFound)?;
3106 let current_runtime_obj =
3107 RuntimeRegistry::<T>::get(runtime_id).ok_or(FraudProofError::RuntimeNotFound)?;
3108
3109 let at = {
3112 let parent_receipt =
3113 BlockTreeNodes::<T>::get(*receipt.parent_domain_block_receipt_hash())
3114 .ok_or(FraudProofError::ParentReceiptNotFound)?
3115 .execution_receipt;
3116 *parent_receipt.consensus_block_number()
3117 };
3118
3119 let is_domain_runtime_upgraded = current_runtime_obj.updated_at >= at;
3120
3121 let mut runtime_obj = match (is_domain_runtime_upgraded, maybe_domain_runtime_code_at) {
3122 (true, None) => return Err(FraudProofError::DomainRuntimeCodeProofNotFound),
3125 (true, Some(domain_runtime_code_at)) => {
3126 let DomainRuntimeCodeAt {
3127 mmr_proof,
3128 domain_runtime_code_proof,
3129 } = domain_runtime_code_at;
3130
3131 let state_root = Self::verify_mmr_proof_and_extract_state_root(mmr_proof, at)?;
3132
3133 <DomainRuntimeCodeProof as BasicStorageProof<T::Block>>::verify::<
3134 T::FraudProofStorageKeyProvider,
3135 >(domain_runtime_code_proof, runtime_id, &state_root)?
3136 }
3137 (false, Some(_)) => return Err(FraudProofError::UnexpectedDomainRuntimeCodeProof),
3140 (false, None) => current_runtime_obj,
3141 };
3142 let code = runtime_obj
3143 .raw_genesis
3144 .take_runtime_code()
3145 .ok_or(storage_proof::VerificationError::RuntimeCodeNotFound)?;
3146 Ok(code)
3147 }
3148
3149 pub fn is_domain_runtime_upgraded_since(
3150 domain_id: DomainId,
3151 at: BlockNumberFor<T>,
3152 ) -> Option<bool> {
3153 Self::runtime_id(domain_id)
3154 .and_then(RuntimeRegistry::<T>::get)
3155 .map(|runtime_obj| runtime_obj.updated_at >= at)
3156 }
3157
3158 pub fn verify_mmr_proof_and_extract_state_root(
3159 mmr_leaf_proof: ConsensusChainMmrLeafProof<BlockNumberFor<T>, T::Hash, T::MmrHash>,
3160 expected_block_number: BlockNumberFor<T>,
3161 ) -> Result<T::Hash, FraudProofError> {
3162 let leaf_data = T::MmrProofVerifier::verify_proof_and_extract_leaf(mmr_leaf_proof)
3163 .ok_or(FraudProofError::BadMmrProof)?;
3164
3165 if expected_block_number != leaf_data.block_number() {
3167 return Err(FraudProofError::UnexpectedMmrProof);
3168 }
3169
3170 Ok(leaf_data.state_root())
3171 }
3172
3173 fn missed_domain_runtime_upgrade(domain_id: DomainId) -> Result<u32, BlockTreeError> {
3175 let runtime_id = Self::runtime_id(domain_id).ok_or(BlockTreeError::RuntimeNotFound)?;
3176 let last_domain_block_number = HeadDomainNumber::<T>::get(domain_id);
3177
3178 let last_block_at =
3180 ExecutionInbox::<T>::iter_key_prefix((domain_id, last_domain_block_number))
3181 .next()
3182 .or(DomainRegistry::<T>::get(domain_id).map(|domain_obj| domain_obj.created_at))
3186 .ok_or(BlockTreeError::LastBlockNotFound)?;
3187
3188 Ok(DomainRuntimeUpgradeRecords::<T>::get(runtime_id)
3189 .into_keys()
3190 .rev()
3191 .take_while(|upgraded_at| *upgraded_at > last_block_at)
3192 .count() as u32)
3193 }
3194
3195 pub fn is_domain_registered(domain_id: DomainId) -> bool {
3197 DomainStakingSummary::<T>::contains_key(domain_id)
3198 }
3199
3200 pub fn domain_sudo_call(domain_id: DomainId) -> Option<Vec<u8>> {
3202 DomainSudoCalls::<T>::get(domain_id).maybe_call
3203 }
3204
3205 pub fn receipt_gap(domain_id: DomainId) -> Result<DomainBlockNumberFor<T>, BundleError> {
3208 let domain_best_number = Self::domain_best_number(domain_id)?;
3209 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
3210
3211 Ok(domain_best_number.saturating_sub(head_receipt_number))
3212 }
3213
3214 pub fn is_evm_domain(domain_id: DomainId) -> bool {
3216 if let Some(domain_obj) = DomainRegistry::<T>::get(domain_id) {
3217 domain_obj.domain_runtime_info.is_evm_domain()
3218 } else {
3219 false
3220 }
3221 }
3222
3223 pub fn is_private_evm_domain(domain_id: DomainId) -> bool {
3225 if let Some(domain_obj) = DomainRegistry::<T>::get(domain_id) {
3226 domain_obj.domain_runtime_info.is_private_evm_domain()
3227 } else {
3228 false
3229 }
3230 }
3231
3232 pub fn evm_domain_contract_creation_allowed_by_call(
3234 domain_id: DomainId,
3235 ) -> Option<sp_domains::PermissionedActionAllowedBy<EthereumAccountId>> {
3236 EvmDomainContractCreationAllowedByCalls::<T>::get(domain_id).maybe_call
3237 }
3238
3239 #[must_use = "set PreviousBundleAndExecutionReceiptVersions to the value returned by this function"]
3242 pub(crate) fn calculate_previous_bundle_and_execution_receipt_versions<BEV>(
3243 block_number: BlockNumberFor<T>,
3244 mut previous_versions: BTreeMap<BlockNumberFor<T>, BEV>,
3245 current_version: BEV,
3246 ) -> BTreeMap<BlockNumberFor<T>, BEV>
3247 where
3248 BEV: PartialEq,
3249 {
3250 if previous_versions.is_empty() {
3252 previous_versions.insert(block_number, current_version);
3253 } else {
3254 let (prev_number, prev_version) = previous_versions
3258 .pop_last()
3259 .expect("at least one version is available due to check above");
3260
3261 if prev_version == current_version {
3263 previous_versions.insert(block_number, current_version);
3264 } else {
3265 previous_versions.insert(prev_number, prev_version);
3268 previous_versions.insert(block_number, current_version);
3269 }
3270 }
3271
3272 previous_versions
3273 }
3274
3275 pub fn current_bundle_and_execution_receipt_version() -> BundleAndExecutionReceiptVersion {
3285 let block_number = frame_system::Pallet::<T>::block_number();
3286 let versions = PreviousBundleAndExecutionReceiptVersions::<T>::get();
3287 match versions.get(&block_number) {
3288 None => T::CurrentBundleAndExecutionReceiptVersion::get(),
3290 Some(version) => *version,
3292 }
3293 }
3294
3295 pub fn nominator_position(
3308 operator_id: OperatorId,
3309 nominator_account: T::AccountId,
3310 ) -> Option<sp_domains::NominatorPosition<BalanceOf<T>, DomainBlockNumberFor<T>, T::Share>>
3311 {
3312 nominator_position::nominator_position::<T>(operator_id, nominator_account)
3313 }
3314}
3315
3316impl<T: Config> subspace_runtime_primitives::OnSetCode<BlockNumberFor<T>> for Pallet<T> {
3317 fn set_code(block_number: BlockNumberFor<T>) -> DispatchResult {
3320 PreviousBundleAndExecutionReceiptVersions::<T>::set(
3321 Self::calculate_previous_bundle_and_execution_receipt_versions(
3322 block_number,
3323 PreviousBundleAndExecutionReceiptVersions::<T>::get(),
3324 T::CurrentBundleAndExecutionReceiptVersion::get(),
3325 ),
3326 );
3327
3328 Ok(())
3329 }
3330}
3331
3332impl<T: Config> sp_domains::DomainOwner<T::AccountId> for Pallet<T> {
3333 fn is_domain_owner(domain_id: DomainId, acc: T::AccountId) -> bool {
3334 if let Some(domain_obj) = DomainRegistry::<T>::get(domain_id) {
3335 domain_obj.owner_account_id == acc
3336 } else {
3337 false
3338 }
3339 }
3340}
3341
3342impl<T> Pallet<T>
3343where
3344 T: Config + CreateUnsigned<Call<T>>,
3345{
3346 pub fn submit_bundle_unsigned(opaque_bundle: OpaqueBundleOf<T>) {
3348 let slot = opaque_bundle.slot_number();
3349 let extrinsics_count = opaque_bundle.body_length();
3350
3351 let call = Call::submit_bundle { opaque_bundle };
3352 let ext = T::create_unsigned(call.into());
3353
3354 match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
3355 Ok(()) => {
3356 log::info!("Submitted bundle from slot {slot}, extrinsics: {extrinsics_count}",);
3357 }
3358 Err(()) => {
3359 log::error!("Error submitting bundle");
3360 }
3361 }
3362 }
3363
3364 pub fn submit_receipt_unsigned(singleton_receipt: SingletonReceiptOf<T>) {
3366 let slot = singleton_receipt.slot_number();
3367 let domain_block_number = *singleton_receipt.receipt().domain_block_number();
3368
3369 let call = Call::submit_receipt { singleton_receipt };
3370 let ext = T::create_unsigned(call.into());
3371 match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
3372 Ok(()) => {
3373 log::info!(
3374 "Submitted singleton receipt from slot {slot}, domain_block_number: {domain_block_number:?}",
3375 );
3376 }
3377 Err(()) => {
3378 log::error!("Error submitting singleton receipt");
3379 }
3380 }
3381 }
3382
3383 pub fn submit_fraud_proof_unsigned(fraud_proof: FraudProofFor<T>) {
3385 let call = Call::submit_fraud_proof {
3386 fraud_proof: Box::new(fraud_proof),
3387 };
3388
3389 let ext = T::create_unsigned(call.into());
3390 match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
3391 Ok(()) => {
3392 log::info!("Submitted fraud proof");
3393 }
3394 Err(()) => {
3395 log::error!("Error submitting fraud proof");
3396 }
3397 }
3398 }
3399}
3400
3401pub fn calculate_tx_range(
3403 cur_tx_range: U256,
3404 actual_bundle_count: u64,
3405 expected_bundle_count: u64,
3406) -> U256 {
3407 if actual_bundle_count == 0 || expected_bundle_count == 0 {
3408 return cur_tx_range;
3409 }
3410
3411 let Some(new_tx_range) = U256::from(actual_bundle_count)
3412 .saturating_mul(&cur_tx_range)
3413 .checked_div(&U256::from(expected_bundle_count))
3414 else {
3415 return cur_tx_range;
3416 };
3417
3418 let upper_bound = cur_tx_range.saturating_mul(&U256::from(4_u64));
3419 let Some(lower_bound) = cur_tx_range.checked_div(&U256::from(4_u64)) else {
3420 return cur_tx_range;
3421 };
3422 new_tx_range.clamp(lower_bound, upper_bound)
3423}