1#![cfg_attr(not(feature = "std"), no_std)]
4#![expect(clippy::clone_on_copy, reason = "Comes from Substrate")]
6#![cfg_attr(any(feature = "fuzz", test), feature(variant_count))]
7
8#[cfg(feature = "runtime-benchmarks")]
9mod benchmarking;
10
11pub mod block_tree;
12pub mod bundle_storage_fund;
13pub mod domain_registry;
14pub mod extensions;
15#[cfg(feature = "fuzz")]
16pub mod fuzz;
17pub mod migrations;
18#[cfg(any(feature = "fuzz", test,))]
19pub(crate) mod mock;
20mod nominator_position;
21pub mod runtime_registry;
22pub mod staking;
23mod staking_epoch;
24#[cfg(test)]
25mod tests;
26pub mod weights;
27
28extern crate alloc;
29
30use crate::block_tree::{Error as BlockTreeError, verify_execution_receipt};
31use crate::bundle_storage_fund::{charge_bundle_storage_fee, storage_fund_account};
32use crate::domain_registry::{DomainConfig, Error as DomainRegistryError};
33use crate::runtime_registry::into_complete_raw_genesis;
34#[cfg(feature = "runtime-benchmarks")]
35pub use crate::staking::do_register_operator;
36use crate::staking_epoch::EpochTransitionResult;
37#[cfg(not(feature = "std"))]
38use alloc::boxed::Box;
39use alloc::collections::btree_map::BTreeMap;
40#[cfg(not(feature = "std"))]
41use alloc::vec::Vec;
42use domain_runtime_primitives::EthereumAccountId;
43use frame_support::dispatch::DispatchResult;
44use frame_support::ensure;
45use frame_support::pallet_prelude::{RuntimeDebug, StorageVersion};
46use frame_support::traits::fungible::{Inspect, InspectHold};
47use frame_support::traits::tokens::{Fortitude, Preservation};
48use frame_support::traits::{EnsureOrigin, Get, Randomness as RandomnessT, Time};
49use frame_support::weights::Weight;
50use frame_system::offchain::SubmitTransaction;
51use frame_system::pallet_prelude::*;
52pub use pallet::*;
53use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
54use scale_info::TypeInfo;
55use sp_consensus_subspace::WrappedPotOutput;
56use sp_consensus_subspace::consensus::is_proof_of_time_valid;
57use sp_core::H256;
58use sp_domains::bundle::{Bundle, BundleVersion, OpaqueBundle};
59use sp_domains::bundle_producer_election::BundleProducerElectionParams;
60use sp_domains::execution_receipt::{
61 ExecutionReceipt, ExecutionReceiptRef, ExecutionReceiptVersion, SealedSingletonReceipt,
62};
63use sp_domains::{
64 BundleAndExecutionReceiptVersion, DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT, DomainBundleLimit,
65 DomainId, DomainInstanceData, EMPTY_EXTRINSIC_ROOT, OperatorId, OperatorPublicKey,
66 OperatorSignature, ProofOfElection, RuntimeId,
67};
68use sp_domains_fraud_proof::fraud_proof::{
69 DomainRuntimeCodeAt, FraudProof, FraudProofVariant, InvalidBlockFeesProof,
70 InvalidDomainBlockHashProof, InvalidTransfersProof,
71};
72use sp_domains_fraud_proof::storage_proof::{self, BasicStorageProof, DomainRuntimeCodeProof};
73use sp_domains_fraud_proof::verification::{
74 verify_invalid_block_fees_fraud_proof, verify_invalid_bundles_fraud_proof,
75 verify_invalid_domain_block_hash_fraud_proof,
76 verify_invalid_domain_extrinsics_root_fraud_proof, verify_invalid_state_transition_fraud_proof,
77 verify_invalid_transfers_fraud_proof, verify_valid_bundle_fraud_proof,
78};
79use sp_runtime::traits::{BlockNumberProvider, CheckedSub, Hash, Header, One, Zero};
80use sp_runtime::transaction_validity::TransactionPriority;
81use sp_runtime::{RuntimeAppPublic, SaturatedConversion, Saturating};
82use sp_subspace_mmr::{ConsensusChainMmrLeafProof, MmrProofVerifier};
83pub use staking::OperatorConfig;
84use subspace_core_primitives::pot::PotOutput;
85use subspace_core_primitives::{BlockHash, SlotNumber, U256};
86use subspace_runtime_primitives::{Balance, CreateUnsigned, Moment, StorageFee};
87
88pub const MAX_NOMINATORS_TO_SLASH: u32 = 10;
90
91pub(crate) type BalanceOf<T> = <T as Config>::Balance;
92
93pub(crate) type FungibleHoldId<T> =
94 <<T as Config>::Currency as InspectHold<<T as frame_system::Config>::AccountId>>::Reason;
95
96pub(crate) type NominatorId<T> = <T as frame_system::Config>::AccountId;
97
98pub trait HoldIdentifier<T: Config> {
99 fn staking_staked() -> FungibleHoldId<T>;
100 fn domain_instantiation_id() -> FungibleHoldId<T>;
101 fn storage_fund_withdrawal() -> FungibleHoldId<T>;
102}
103
104pub trait BlockSlot<T: frame_system::Config> {
105 fn future_slot(block_number: BlockNumberFor<T>) -> Option<sp_consensus_slots::Slot>;
108
109 fn slot_produced_after(to_check: sp_consensus_slots::Slot) -> Option<BlockNumberFor<T>>;
111
112 fn current_slot() -> sp_consensus_slots::Slot;
114}
115
116pub type ExecutionReceiptOf<T> = ExecutionReceipt<
117 BlockNumberFor<T>,
118 <T as frame_system::Config>::Hash,
119 DomainBlockNumberFor<T>,
120 <T as Config>::DomainHash,
121 BalanceOf<T>,
122>;
123
124pub type ExecutionReceiptRefOf<'a, T> = ExecutionReceiptRef<
125 'a,
126 BlockNumberFor<T>,
127 <T as frame_system::Config>::Hash,
128 DomainBlockNumberFor<T>,
129 <T as Config>::DomainHash,
130 BalanceOf<T>,
131>;
132
133pub type OpaqueBundleOf<T> = OpaqueBundle<
134 BlockNumberFor<T>,
135 <T as frame_system::Config>::Hash,
136 <T as Config>::DomainHeader,
137 BalanceOf<T>,
138>;
139
140pub type SingletonReceiptOf<T> = SealedSingletonReceipt<
141 BlockNumberFor<T>,
142 <T as frame_system::Config>::Hash,
143 <T as Config>::DomainHeader,
144 BalanceOf<T>,
145>;
146
147pub type FraudProofFor<T> = FraudProof<
148 BlockNumberFor<T>,
149 <T as frame_system::Config>::Hash,
150 <T as Config>::DomainHeader,
151 <T as Config>::MmrHash,
152>;
153
154#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
156pub(crate) struct ElectionVerificationParams<Balance> {
157 operators: BTreeMap<OperatorId, Balance>,
158 total_domain_stake: Balance,
159}
160
161pub type DomainBlockNumberFor<T> = <<T as Config>::DomainHeader as Header>::Number;
162pub type DomainHashingFor<T> = <<T as Config>::DomainHeader as Header>::Hashing;
163pub type ReceiptHashFor<T> = <<T as Config>::DomainHeader as Header>::Hash;
164
165pub type BlockTreeNodeFor<T> = crate::block_tree::BlockTreeNode<
166 BlockNumberFor<T>,
167 <T as frame_system::Config>::Hash,
168 DomainBlockNumberFor<T>,
169 <T as Config>::DomainHash,
170 BalanceOf<T>,
171>;
172
173#[derive(
175 PartialEq,
176 Eq,
177 Clone,
178 Encode,
179 Decode,
180 RuntimeDebug,
181 TypeInfo,
182 MaxEncodedLen,
183 DecodeWithMemTracking,
184)]
185pub enum RawOrigin {
186 ValidatedUnsigned,
187}
188
189pub struct EnsureDomainOrigin;
191impl<O: Into<Result<RawOrigin, O>> + From<RawOrigin>> EnsureOrigin<O> for EnsureDomainOrigin {
192 type Success = ();
193
194 fn try_origin(o: O) -> Result<Self::Success, O> {
195 o.into().map(|o| match o {
196 RawOrigin::ValidatedUnsigned => (),
197 })
198 }
199
200 #[cfg(feature = "runtime-benchmarks")]
201 fn try_successful_origin() -> Result<O, ()> {
202 Ok(O::from(RawOrigin::ValidatedUnsigned))
203 }
204}
205
206const STORAGE_VERSION: StorageVersion = StorageVersion::new(6);
208
209const MAX_BUNDLE_PER_BLOCK: u32 = 100;
214
215pub(crate) type StateRootOf<T> = <<T as frame_system::Config>::Hashing as Hash>::Output;
216
217pub trait WeightInfo {
219 fn submit_bundle() -> Weight;
220 fn submit_fraud_proof() -> Weight;
221 fn handle_bad_receipt(n: u32) -> Weight;
222 fn confirm_domain_block(n: u32, s: u32) -> Weight;
223 fn operator_reward_tax_and_restake(n: u32) -> Weight;
224 fn slash_operator(n: u32) -> Weight;
225 fn finalize_domain_epoch_staking(p: u32) -> Weight;
226 fn register_domain_runtime() -> Weight;
227 fn upgrade_domain_runtime() -> Weight;
228 fn instantiate_domain() -> Weight;
229 fn register_operator() -> Weight;
230 fn nominate_operator() -> Weight;
231 fn deregister_operator() -> Weight;
232 fn withdraw_stake() -> Weight;
233 fn unlock_funds(w: u32) -> Weight;
234 fn unlock_nominator() -> Weight;
235 fn update_domain_operator_allow_list() -> Weight;
236 fn transfer_treasury_funds() -> Weight;
237 fn submit_receipt() -> Weight;
238 fn validate_submit_bundle() -> Weight;
239 fn validate_singleton_receipt() -> Weight;
240 fn fraud_proof_pre_check() -> Weight;
241 fn deactivate_operator() -> Weight;
242 fn reactivate_operator() -> Weight;
243 fn deregister_deactivated_operator() -> Weight;
244 fn withdraw_stake_from_deactivated_operator() -> Weight;
245}
246
247#[frame_support::pallet]
248mod pallet {
249 #[cfg(not(feature = "runtime-benchmarks"))]
250 use crate::DomainHashingFor;
251 #[cfg(not(feature = "runtime-benchmarks"))]
252 use crate::MAX_NOMINATORS_TO_SLASH;
253 use crate::block_tree::{
254 AcceptedReceiptType, BlockTreeNode, Error as BlockTreeError, ReceiptType,
255 execution_receipt_type, process_execution_receipt, prune_receipt,
256 };
257 use crate::bundle_storage_fund::Error as BundleStorageFundError;
258 #[cfg(not(feature = "runtime-benchmarks"))]
259 use crate::bundle_storage_fund::refund_storage_fee;
260 use crate::domain_registry::{
261 DomainConfigParams, DomainObject, Error as DomainRegistryError, do_instantiate_domain,
262 do_update_domain_allow_list,
263 };
264 use crate::runtime_registry::{
265 DomainRuntimeUpgradeEntry, Error as RuntimeRegistryError, ScheduledRuntimeUpgrade,
266 do_register_runtime, do_schedule_runtime_upgrade, do_upgrade_runtimes,
267 register_runtime_at_genesis,
268 };
269 #[cfg(not(feature = "runtime-benchmarks"))]
270 use crate::staking::do_reward_operators;
271 use crate::staking::{
272 Deposit, DomainEpoch, Error as StakingError, Operator, OperatorConfig, SharePrice,
273 StakingSummary, Withdrawal, do_deregister_operator, do_mark_invalid_bundle_authors,
274 do_mark_operators_as_slashed, do_nominate_operator, do_register_operator, do_unlock_funds,
275 do_unlock_nominator, do_unmark_invalid_bundle_authors, do_withdraw_stake,
276 };
277 #[cfg(not(feature = "runtime-benchmarks"))]
278 use crate::staking_epoch::do_slash_operator;
279 use crate::staking_epoch::{Error as StakingEpochError, do_finalize_domain_current_epoch};
280 use crate::storage_proof::InherentExtrinsicData;
281 use crate::{
282 BalanceOf, BlockSlot, BlockTreeNodeFor, DomainBlockNumberFor, ElectionVerificationParams,
283 ExecutionReceiptOf, FraudProofFor, HoldIdentifier, MAX_BUNDLE_PER_BLOCK, NominatorId,
284 OpaqueBundleOf, RawOrigin, ReceiptHashFor, STORAGE_VERSION, SingletonReceiptOf,
285 StateRootOf, WeightInfo,
286 };
287 #[cfg(not(feature = "std"))]
288 use alloc::string::String;
289 #[cfg(not(feature = "std"))]
290 use alloc::vec;
291 #[cfg(not(feature = "std"))]
292 use alloc::vec::Vec;
293 use domain_runtime_primitives::{EVMChainId, EthereumAccountId};
294 use frame_support::pallet_prelude::*;
295 use frame_support::traits::fungible::{Inspect, InspectHold, Mutate, MutateHold};
296 use frame_support::traits::tokens::Preservation;
297 use frame_support::traits::{Randomness as RandomnessT, Time};
298 use frame_support::weights::Weight;
299 use frame_support::{Identity, PalletError};
300 use frame_system::pallet_prelude::*;
301 use parity_scale_codec::FullCodec;
302 use sp_consensus_slots::Slot;
303 use sp_core::H256;
304 use sp_domains::bundle::BundleDigest;
305 use sp_domains::bundle_producer_election::ProofOfElectionError;
306 use sp_domains::offline_operators::OperatorEpochExpectations;
307 use sp_domains::{
308 BundleAndExecutionReceiptVersion, DomainBundleSubmitted, DomainId, DomainOwner,
309 DomainSudoCall, DomainsTransfersTracker, EpochIndex,
310 EvmDomainContractCreationAllowedByCall, GenesisDomain, OnChainRewards,
311 OnDomainInstantiated, OperatorAllowList, OperatorId, OperatorRewardSource, RuntimeId,
312 RuntimeObject, RuntimeType,
313 };
314 use sp_domains_fraud_proof::fraud_proof_runtime_interface::domain_runtime_call;
315 use sp_domains_fraud_proof::storage_proof::{self, FraudProofStorageKeyProvider};
316 use sp_domains_fraud_proof::{InvalidTransactionCode, StatelessDomainRuntimeCall};
317 use sp_runtime::Saturating;
318 use sp_runtime::traits::{
319 AtLeast32BitUnsigned, BlockNumberProvider, CheckEqual, CheckedAdd, Header as HeaderT,
320 MaybeDisplay, One, SimpleBitOps, Zero,
321 };
322 use sp_std::boxed::Box;
323 use sp_std::collections::btree_map::BTreeMap;
324 use sp_std::collections::btree_set::BTreeSet;
325 use sp_std::fmt::Debug;
326 use sp_subspace_mmr::MmrProofVerifier;
327 use subspace_core_primitives::{Randomness, U256};
328 use subspace_runtime_primitives::StorageFee;
329
330 #[pallet::config]
331 pub trait Config:
332 frame_system::Config<Hash: Into<H256> + From<H256>, RuntimeEvent: From<Event<Self>>>
333 {
334 type DomainOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = ()>;
336
337 type DomainHash: Parameter
342 + Member
343 + MaybeSerializeDeserialize
344 + Debug
345 + MaybeDisplay
346 + SimpleBitOps
347 + Ord
348 + Default
349 + Copy
350 + CheckEqual
351 + sp_std::hash::Hash
352 + AsRef<[u8]>
353 + AsMut<[u8]>
354 + MaxEncodedLen
355 + Into<H256>
356 + From<H256>;
357
358 type Balance: Parameter
360 + Member
361 + MaybeSerializeDeserialize
362 + AtLeast32BitUnsigned
363 + FullCodec
364 + Debug
365 + MaybeDisplay
366 + Default
367 + Copy
368 + MaxEncodedLen
369 + From<u64>;
370
371 type DomainHeader: HeaderT<Hash = Self::DomainHash>;
373
374 #[pallet::constant]
376 type ConfirmationDepthK: Get<BlockNumberFor<Self>>;
377
378 type Currency: Inspect<Self::AccountId, Balance = Self::Balance>
380 + Mutate<Self::AccountId>
381 + InspectHold<Self::AccountId>
382 + MutateHold<Self::AccountId>;
383
384 type Share: Parameter
386 + Member
387 + MaybeSerializeDeserialize
388 + Debug
389 + AtLeast32BitUnsigned
390 + FullCodec
391 + Copy
392 + Default
393 + TypeInfo
394 + MaxEncodedLen
395 + IsType<BalanceOf<Self>>;
396
397 type HoldIdentifier: HoldIdentifier<Self>;
399
400 #[pallet::constant]
402 type BlockTreePruningDepth: Get<DomainBlockNumberFor<Self>>;
403
404 #[pallet::constant]
406 type ConsensusSlotProbability: Get<(u64, u64)>;
407
408 #[pallet::constant]
410 type MaxDomainBlockSize: Get<u32>;
411
412 #[pallet::constant]
414 type MaxDomainBlockWeight: Get<Weight>;
415
416 #[pallet::constant]
418 type MaxDomainNameLength: Get<u32>;
419
420 #[pallet::constant]
422 type DomainInstantiationDeposit: Get<BalanceOf<Self>>;
423
424 type WeightInfo: WeightInfo;
426
427 #[pallet::constant]
429 type InitialDomainTxRange: Get<u64>;
430
431 #[pallet::constant]
433 type DomainTxRangeAdjustmentInterval: Get<u64>;
434
435 #[pallet::constant]
437 type MinOperatorStake: Get<BalanceOf<Self>>;
438
439 #[pallet::constant]
441 type MinNominatorStake: Get<BalanceOf<Self>>;
442
443 #[pallet::constant]
445 type StakeWithdrawalLockingPeriod: Get<DomainBlockNumberFor<Self>>;
446
447 #[pallet::constant]
449 type StakeEpochDuration: Get<DomainBlockNumberFor<Self>>;
450
451 #[pallet::constant]
453 type TreasuryAccount: Get<Self::AccountId>;
454
455 #[pallet::constant]
457 type MaxPendingStakingOperation: Get<u32>;
458
459 type Randomness: RandomnessT<Self::Hash, BlockNumberFor<Self>>;
461
462 #[pallet::constant]
464 type PalletId: Get<frame_support::PalletId>;
465
466 type StorageFee: StorageFee<BalanceOf<Self>>;
468
469 type BlockTimestamp: Time;
471
472 type BlockSlot: BlockSlot<Self>;
474
475 type DomainsTransfersTracker: DomainsTransfersTracker<BalanceOf<Self>>;
477
478 type MaxInitialDomainAccounts: Get<u32>;
480
481 type MinInitialDomainAccountBalance: Get<BalanceOf<Self>>;
483
484 #[pallet::constant]
486 type BundleLongevity: Get<u32>;
487
488 type DomainBundleSubmitted: DomainBundleSubmitted;
490
491 type OnDomainInstantiated: OnDomainInstantiated;
493
494 type MmrHash: Parameter + Member + Default + Clone;
496
497 type MmrProofVerifier: MmrProofVerifier<Self::MmrHash, BlockNumberFor<Self>, StateRootOf<Self>>;
499
500 type FraudProofStorageKeyProvider: FraudProofStorageKeyProvider<BlockNumberFor<Self>>;
502
503 type OnChainRewards: OnChainRewards<BalanceOf<Self>>;
505
506 #[pallet::constant]
510 type WithdrawalLimit: Get<u32>;
511
512 #[pallet::constant]
514 type CurrentBundleAndExecutionReceiptVersion: Get<BundleAndExecutionReceiptVersion>;
515
516 #[pallet::constant]
518 type OperatorActivationDelayInEpochs: Get<EpochIndex>;
519 }
520
521 #[pallet::pallet]
522 #[pallet::without_storage_info]
523 #[pallet::storage_version(STORAGE_VERSION)]
524 pub struct Pallet<T>(_);
525
526 #[pallet::storage]
528 pub type SuccessfulBundles<T> = StorageMap<_, Identity, DomainId, Vec<H256>, ValueQuery>;
529
530 #[pallet::storage]
532 pub(super) type NextRuntimeId<T> = StorageValue<_, RuntimeId, ValueQuery>;
533
534 #[pallet::storage]
536 pub type EvmChainIds<T: Config> = StorageMap<_, Identity, EVMChainId, DomainId, OptionQuery>;
537
538 #[pallet::storage]
539 pub type RuntimeRegistry<T: Config> =
540 StorageMap<_, Identity, RuntimeId, RuntimeObject<BlockNumberFor<T>, T::Hash>, OptionQuery>;
541
542 #[pallet::storage]
543 pub(super) type ScheduledRuntimeUpgrades<T: Config> = StorageDoubleMap<
544 _,
545 Identity,
546 BlockNumberFor<T>,
547 Identity,
548 RuntimeId,
549 ScheduledRuntimeUpgrade<T::Hash>,
550 OptionQuery,
551 >;
552
553 #[pallet::storage]
554 pub(super) type NextOperatorId<T> = StorageValue<_, OperatorId, ValueQuery>;
555
556 #[pallet::storage]
557 pub(super) type OperatorIdOwner<T: Config> =
558 StorageMap<_, Identity, OperatorId, T::AccountId, OptionQuery>;
559
560 #[pallet::storage]
561 #[pallet::getter(fn domain_staking_summary)]
562 pub(crate) type DomainStakingSummary<T: Config> =
563 StorageMap<_, Identity, DomainId, StakingSummary<OperatorId, BalanceOf<T>>, OptionQuery>;
564
565 #[pallet::storage]
567 pub(super) type Operators<T: Config> = StorageMap<
568 _,
569 Identity,
570 OperatorId,
571 Operator<BalanceOf<T>, T::Share, DomainBlockNumberFor<T>, ReceiptHashFor<T>>,
572 OptionQuery,
573 >;
574
575 #[pallet::storage]
577 pub(super) type OperatorHighestSlot<T: Config> =
578 StorageMap<_, Identity, OperatorId, u64, ValueQuery>;
579
580 #[pallet::storage]
583 pub(super) type OperatorBundleSlot<T: Config> =
584 StorageMap<_, Identity, OperatorId, BTreeSet<u64>, ValueQuery>;
585
586 #[pallet::storage]
589 pub type OperatorEpochSharePrice<T: Config> =
590 StorageDoubleMap<_, Identity, OperatorId, Identity, DomainEpoch, SharePrice, OptionQuery>;
591
592 #[pallet::storage]
594 pub(crate) type Deposits<T: Config> = StorageDoubleMap<
595 _,
596 Identity,
597 OperatorId,
598 Identity,
599 NominatorId<T>,
600 Deposit<T::Share, BalanceOf<T>>,
601 OptionQuery,
602 >;
603
604 #[pallet::storage]
606 pub(crate) type Withdrawals<T: Config> = StorageDoubleMap<
607 _,
608 Identity,
609 OperatorId,
610 Identity,
611 NominatorId<T>,
612 Withdrawal<BalanceOf<T>, T::Share, DomainBlockNumberFor<T>>,
613 OptionQuery,
614 >;
615
616 #[pallet::storage]
618 pub(super) type DepositOnHold<T: Config> =
619 StorageMap<_, Identity, (OperatorId, NominatorId<T>), BalanceOf<T>, ValueQuery>;
620
621 #[pallet::storage]
625 pub(crate) type PendingSlashes<T: Config> =
626 StorageMap<_, Identity, DomainId, BTreeSet<OperatorId>, OptionQuery>;
627
628 #[pallet::storage]
631 pub(super) type PendingStakingOperationCount<T: Config> =
632 StorageMap<_, Identity, DomainId, u32, ValueQuery>;
633
634 #[pallet::storage]
636 #[pallet::getter(fn next_domain_id)]
637 pub(super) type NextDomainId<T> = StorageValue<_, DomainId, ValueQuery>;
638
639 #[pallet::storage]
641 pub(super) type DomainRegistry<T: Config> = StorageMap<
642 _,
643 Identity,
644 DomainId,
645 DomainObject<BlockNumberFor<T>, ReceiptHashFor<T>, T::AccountId, BalanceOf<T>>,
646 OptionQuery,
647 >;
648
649 #[pallet::storage]
652 pub(super) type BlockTree<T: Config> = StorageDoubleMap<
653 _,
654 Identity,
655 DomainId,
656 Identity,
657 DomainBlockNumberFor<T>,
658 ReceiptHashFor<T>,
659 OptionQuery,
660 >;
661
662 #[pallet::storage]
664 pub(super) type BlockTreeNodes<T: Config> =
665 StorageMap<_, Identity, ReceiptHashFor<T>, BlockTreeNodeFor<T>, OptionQuery>;
666
667 #[pallet::storage]
669 pub(super) type HeadReceiptNumber<T: Config> =
670 StorageMap<_, Identity, DomainId, DomainBlockNumberFor<T>, ValueQuery>;
671
672 #[pallet::storage]
676 pub(super) type NewAddedHeadReceipt<T: Config> =
677 StorageMap<_, Identity, DomainId, T::DomainHash, OptionQuery>;
678
679 #[pallet::storage]
685 #[pallet::getter(fn consensus_block_info)]
686 pub type ConsensusBlockHash<T: Config> =
687 StorageDoubleMap<_, Identity, DomainId, Identity, BlockNumberFor<T>, T::Hash, OptionQuery>;
688
689 #[pallet::storage]
695 pub type ExecutionInbox<T: Config> = StorageNMap<
696 _,
697 (
698 NMapKey<Identity, DomainId>,
699 NMapKey<Identity, DomainBlockNumberFor<T>>,
700 NMapKey<Identity, BlockNumberFor<T>>,
701 ),
702 Vec<BundleDigest<T::DomainHash>>,
703 ValueQuery,
704 >;
705
706 #[pallet::storage]
710 pub(super) type InboxedBundleAuthor<T: Config> =
711 StorageMap<_, Identity, T::DomainHash, OperatorId, OptionQuery>;
712
713 #[pallet::storage]
722 pub(crate) type HeadDomainNumber<T: Config> =
723 StorageMap<_, Identity, DomainId, DomainBlockNumberFor<T>, ValueQuery>;
724
725 #[pallet::storage]
731 pub(super) type LastEpochStakingDistribution<T: Config> =
732 StorageMap<_, Identity, DomainId, ElectionVerificationParams<BalanceOf<T>>, OptionQuery>;
733
734 #[pallet::storage]
736 #[pallet::getter(fn latest_confirmed_domain_execution_receipt)]
737 pub type LatestConfirmedDomainExecutionReceipt<T: Config> =
738 StorageMap<_, Identity, DomainId, ExecutionReceiptOf<T>, OptionQuery>;
739
740 #[pallet::storage]
742 #[pallet::getter(fn domain_genesis_block_execution_receipt)]
743 pub type DomainGenesisBlockExecutionReceipt<T: Config> =
744 StorageMap<_, Identity, DomainId, ExecutionReceiptOf<T>, OptionQuery>;
745
746 #[pallet::storage]
753 #[pallet::getter(fn latest_submitted_er)]
754 pub(super) type LatestSubmittedER<T: Config> =
755 StorageMap<_, Identity, (DomainId, OperatorId), DomainBlockNumberFor<T>, ValueQuery>;
756
757 #[pallet::storage]
759 pub(super) type PermissionedActionAllowedBy<T: Config> =
760 StorageValue<_, sp_domains::PermissionedActionAllowedBy<T::AccountId>, OptionQuery>;
761
762 #[pallet::storage]
765 pub(super) type AccumulatedTreasuryFunds<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;
766
767 #[pallet::storage]
769 pub(super) type DomainRuntimeUpgradeRecords<T: Config> = StorageMap<
770 _,
771 Identity,
772 RuntimeId,
773 BTreeMap<BlockNumberFor<T>, DomainRuntimeUpgradeEntry<T::Hash>>,
774 ValueQuery,
775 >;
776
777 #[pallet::storage]
780 pub type DomainRuntimeUpgrades<T> = StorageValue<_, Vec<RuntimeId>, ValueQuery>;
781
782 #[pallet::storage]
787 pub type DomainSudoCalls<T: Config> =
788 StorageMap<_, Identity, DomainId, DomainSudoCall, ValueQuery>;
789
790 #[pallet::storage]
794 pub type FrozenDomains<T> = StorageValue<_, BTreeSet<DomainId>, ValueQuery>;
795
796 #[pallet::storage]
801 pub type EvmDomainContractCreationAllowedByCalls<T: Config> =
802 StorageMap<_, Identity, DomainId, EvmDomainContractCreationAllowedByCall, ValueQuery>;
803
804 #[pallet::storage]
807 pub type DomainChainRewards<T: Config> =
808 StorageMap<_, Identity, DomainId, BalanceOf<T>, ValueQuery>;
809
810 #[pallet::storage]
813 pub type InvalidBundleAuthors<T: Config> =
814 StorageMap<_, Identity, DomainId, BTreeSet<OperatorId>, ValueQuery>;
815
816 #[pallet::storage]
819 pub type DeactivatedOperators<T: Config> =
820 StorageMap<_, Identity, DomainId, BTreeSet<OperatorId>, ValueQuery>;
821
822 #[pallet::storage]
825 pub type DeregisteredOperators<T: Config> =
826 StorageMap<_, Identity, DomainId, BTreeSet<OperatorId>, ValueQuery>;
827
828 #[pallet::storage]
836 pub type PreviousBundleAndExecutionReceiptVersions<T> =
837 StorageValue<_, BTreeMap<BlockNumberFor<T>, BundleAndExecutionReceiptVersion>, ValueQuery>;
838
839 #[pallet::storage]
841 pub type EpochStartSlot<T> = StorageValue<_, Slot, OptionQuery>;
842
843 #[pallet::storage]
846 pub type OperatorBundleCountInEpoch<T> = StorageMap<_, Identity, OperatorId, u64, ValueQuery>;
847
848 #[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
849 pub enum BundleError {
850 InvalidOperatorId,
852 BadBundleSignature,
854 BadVrfSignature,
856 InvalidDomainId,
858 BadOperator,
860 ThresholdUnsatisfied,
862 InvalidThreshold,
864 Receipt(BlockTreeError),
866 BundleTooLarge,
868 InvalidExtrinsicRoot,
870 InvalidProofOfTime,
872 SlotInTheFuture,
874 SlotInThePast,
876 BundleTooHeavy,
878 SlotSmallerThanPreviousBlockBundle,
881 EquivocatedBundle,
883 DomainFrozen,
885 UnableToPayBundleStorageFee,
887 UnexpectedReceiptGap,
889 ExpectingReceiptGap,
891 FailedToGetMissedUpgradeCount,
893 BundleVersionMismatch,
895 ExecutionVersionMismatch,
897 ExecutionVersionMissing,
899 }
900
901 #[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq, DecodeWithMemTracking)]
902 pub enum FraudProofError {
903 BadReceiptNotFound,
906 ChallengingGenesisReceipt,
908 DescendantsOfFraudulentERNotPruned,
910 InvalidBlockFeesFraudProof,
912 InvalidTransfersFraudProof,
914 InvalidDomainBlockHashFraudProof,
916 InvalidExtrinsicRootFraudProof,
918 InvalidStateTransitionFraudProof,
920 ParentReceiptNotFound,
922 InvalidBundleFraudProof,
924 BadValidBundleFraudProof,
926 MissingOperator,
928 UnexpectedFraudProof,
930 BadReceiptAlreadyReported,
932 BadMmrProof,
934 UnexpectedMmrProof,
936 MissingMmrProof,
938 RuntimeNotFound,
940 DomainRuntimeCodeProofNotFound,
942 UnexpectedDomainRuntimeCodeProof,
944 StorageProof(storage_proof::VerificationError),
946 }
947
948 impl From<BundleError> for TransactionValidity {
949 fn from(e: BundleError) -> Self {
950 if BundleError::UnableToPayBundleStorageFee == e {
951 InvalidTransactionCode::BundleStorageFeePayment.into()
952 } else if let BundleError::Receipt(_) = e {
953 InvalidTransactionCode::ExecutionReceipt.into()
954 } else {
955 InvalidTransactionCode::Bundle.into()
956 }
957 }
958 }
959
960 impl From<storage_proof::VerificationError> for FraudProofError {
961 fn from(err: storage_proof::VerificationError) -> Self {
962 FraudProofError::StorageProof(err)
963 }
964 }
965
966 impl<T> From<FraudProofError> for Error<T> {
967 fn from(err: FraudProofError) -> Self {
968 Error::FraudProof(err)
969 }
970 }
971
972 impl<T> From<RuntimeRegistryError> for Error<T> {
973 fn from(err: RuntimeRegistryError) -> Self {
974 Error::RuntimeRegistry(err)
975 }
976 }
977
978 impl<T> From<StakingError> for Error<T> {
979 fn from(err: StakingError) -> Self {
980 Error::Staking(err)
981 }
982 }
983
984 impl<T> From<StakingEpochError> for Error<T> {
985 fn from(err: StakingEpochError) -> Self {
986 Error::StakingEpoch(err)
987 }
988 }
989
990 impl<T> From<DomainRegistryError> for Error<T> {
991 fn from(err: DomainRegistryError) -> Self {
992 Error::DomainRegistry(err)
993 }
994 }
995
996 impl<T> From<BlockTreeError> for Error<T> {
997 fn from(err: BlockTreeError) -> Self {
998 Error::BlockTree(err)
999 }
1000 }
1001
1002 impl From<ProofOfElectionError> for BundleError {
1003 fn from(err: ProofOfElectionError) -> Self {
1004 match err {
1005 ProofOfElectionError::BadVrfProof => Self::BadVrfSignature,
1006 ProofOfElectionError::ThresholdUnsatisfied => Self::ThresholdUnsatisfied,
1007 ProofOfElectionError::InvalidThreshold => Self::InvalidThreshold,
1008 }
1009 }
1010 }
1011
1012 impl<T> From<BundleStorageFundError> for Error<T> {
1013 fn from(err: BundleStorageFundError) -> Self {
1014 Error::BundleStorageFund(err)
1015 }
1016 }
1017
1018 #[pallet::error]
1019 pub enum Error<T> {
1020 FraudProof(FraudProofError),
1022 RuntimeRegistry(RuntimeRegistryError),
1024 Staking(StakingError),
1026 StakingEpoch(StakingEpochError),
1028 DomainRegistry(DomainRegistryError),
1030 BlockTree(BlockTreeError),
1032 BundleStorageFund(BundleStorageFundError),
1034 PermissionedActionNotAllowed,
1036 DomainSudoCallExists,
1038 InvalidDomainSudoCall,
1040 DomainNotFrozen,
1042 NotPrivateEvmDomain,
1044 NotDomainOwnerOrRoot,
1046 EvmDomainContractCreationAllowedByCallExists,
1048 }
1049
1050 #[derive(Clone, Debug, PartialEq, Encode, Decode, TypeInfo, DecodeWithMemTracking)]
1052 pub enum SlashedReason<DomainBlock, ReceiptHash> {
1053 InvalidBundle(DomainBlock),
1055 BadExecutionReceipt(ReceiptHash),
1057 }
1058
1059 #[pallet::event]
1060 #[pallet::generate_deposit(pub (super) fn deposit_event)]
1061 pub enum Event<T: Config> {
1062 BundleStored {
1064 domain_id: DomainId,
1065 bundle_hash: H256,
1066 bundle_author: OperatorId,
1067 },
1068 DomainRuntimeCreated {
1069 runtime_id: RuntimeId,
1070 runtime_type: RuntimeType,
1071 },
1072 DomainRuntimeUpgradeScheduled {
1073 runtime_id: RuntimeId,
1074 scheduled_at: BlockNumberFor<T>,
1075 },
1076 DomainRuntimeUpgraded {
1077 runtime_id: RuntimeId,
1078 },
1079 OperatorRegistered {
1080 operator_id: OperatorId,
1081 domain_id: DomainId,
1082 },
1083 NominatedStakedUnlocked {
1084 operator_id: OperatorId,
1085 nominator_id: NominatorId<T>,
1086 unlocked_amount: BalanceOf<T>,
1087 },
1088 StorageFeeUnlocked {
1089 operator_id: OperatorId,
1090 nominator_id: NominatorId<T>,
1091 storage_fee: BalanceOf<T>,
1092 },
1093 OperatorNominated {
1094 operator_id: OperatorId,
1095 nominator_id: NominatorId<T>,
1096 amount: BalanceOf<T>,
1097 },
1098 DomainInstantiated {
1099 domain_id: DomainId,
1100 },
1101 OperatorSwitchedDomain {
1102 old_domain_id: DomainId,
1103 new_domain_id: DomainId,
1104 },
1105 OperatorDeregistered {
1106 operator_id: OperatorId,
1107 },
1108 NominatorUnlocked {
1109 operator_id: OperatorId,
1110 nominator_id: NominatorId<T>,
1111 },
1112 WithdrewStake {
1113 operator_id: OperatorId,
1114 nominator_id: NominatorId<T>,
1115 },
1116 PreferredOperator {
1117 operator_id: OperatorId,
1118 nominator_id: NominatorId<T>,
1119 },
1120 OperatorRewarded {
1121 source: OperatorRewardSource<BlockNumberFor<T>>,
1122 operator_id: OperatorId,
1123 reward: BalanceOf<T>,
1124 },
1125 OperatorTaxCollected {
1126 operator_id: OperatorId,
1127 tax: BalanceOf<T>,
1128 },
1129 DomainEpochCompleted {
1130 domain_id: DomainId,
1131 completed_epoch_index: EpochIndex,
1132 },
1133 ForceDomainEpochTransition {
1134 domain_id: DomainId,
1135 completed_epoch_index: EpochIndex,
1136 },
1137 FraudProofProcessed {
1138 domain_id: DomainId,
1139 new_head_receipt_number: Option<DomainBlockNumberFor<T>>,
1140 },
1141 DomainOperatorAllowListUpdated {
1142 domain_id: DomainId,
1143 },
1144 OperatorSlashed {
1145 operator_id: OperatorId,
1146 reason: SlashedReason<DomainBlockNumberFor<T>, ReceiptHashFor<T>>,
1147 },
1148 StorageFeeDeposited {
1149 operator_id: OperatorId,
1150 nominator_id: NominatorId<T>,
1151 amount: BalanceOf<T>,
1152 },
1153 DomainFrozen {
1154 domain_id: DomainId,
1155 },
1156 DomainUnfrozen {
1157 domain_id: DomainId,
1158 },
1159 PrunedExecutionReceipt {
1160 domain_id: DomainId,
1161 new_head_receipt_number: Option<DomainBlockNumberFor<T>>,
1162 },
1163 OperatorDeactivated {
1164 domain_id: DomainId,
1165 operator_id: OperatorId,
1166 reactivation_delay: EpochIndex,
1167 },
1168 OperatorReactivated {
1169 operator_id: OperatorId,
1170 domain_id: DomainId,
1171 },
1172 OperatorOffline {
1173 operator_id: OperatorId,
1174 domain_id: DomainId,
1175 submitted_bundles: u64,
1176 expectations: OperatorEpochExpectations,
1177 },
1178 }
1179
1180 #[pallet::origin]
1181 pub type Origin = RawOrigin;
1182
1183 #[derive(Debug, Default, Decode, Encode, TypeInfo, PartialEq, Eq)]
1185 pub struct TxRangeState {
1186 pub tx_range: U256,
1188
1189 pub interval_blocks: u64,
1191
1192 pub interval_bundles: u64,
1194 }
1195
1196 impl TxRangeState {
1197 pub fn on_bundle(&mut self) {
1199 self.interval_bundles += 1;
1200 }
1201 }
1202
1203 #[pallet::storage]
1204 pub(super) type DomainTxRangeState<T: Config> =
1205 StorageMap<_, Identity, DomainId, TxRangeState, OptionQuery>;
1206
1207 #[pallet::call]
1208 impl<T: Config> Pallet<T> {
1209 #[pallet::call_index(0)]
1210 #[pallet::weight(Pallet::<T>::max_submit_bundle_weight())]
1211 pub fn submit_bundle(
1212 origin: OriginFor<T>,
1213 opaque_bundle: OpaqueBundleOf<T>,
1214 ) -> DispatchResultWithPostInfo {
1215 T::DomainOrigin::ensure_origin(origin)?;
1216
1217 log::trace!("Processing bundle: {opaque_bundle:?}");
1218
1219 let domain_id = opaque_bundle.domain_id();
1220 let bundle_hash = opaque_bundle.hash();
1221 let bundle_header_hash = opaque_bundle.sealed_header().pre_hash();
1222 let extrinsics_root = opaque_bundle.extrinsics_root();
1223 let operator_id = opaque_bundle.operator_id();
1224 let bundle_size = opaque_bundle.size();
1225 let slot_number = opaque_bundle.slot_number();
1226 let receipt = opaque_bundle.into_receipt();
1227 #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
1228 let receipt_block_number = *receipt.domain_block_number();
1229
1230 OperatorBundleCountInEpoch::<T>::mutate(operator_id, |c| {
1232 *c = c.saturating_add(1);
1233 });
1234
1235 #[cfg(not(feature = "runtime-benchmarks"))]
1236 let mut actual_weight = T::WeightInfo::submit_bundle();
1237 #[cfg(feature = "runtime-benchmarks")]
1238 let actual_weight = T::WeightInfo::submit_bundle();
1239
1240 match execution_receipt_type::<T>(domain_id, &receipt.as_execution_receipt_ref()) {
1241 ReceiptType::Rejected(rejected_receipt_type) => {
1242 return Err(Error::<T>::BlockTree(rejected_receipt_type.into()).into());
1243 }
1244 ReceiptType::Accepted(accepted_receipt_type) => {
1246 #[cfg(not(feature = "runtime-benchmarks"))]
1252 if accepted_receipt_type == AcceptedReceiptType::NewHead
1253 && let Some(BlockTreeNode {
1254 execution_receipt,
1255 operator_ids,
1256 }) = prune_receipt::<T>(domain_id, receipt_block_number)
1257 .map_err(Error::<T>::from)?
1258 {
1259 actual_weight = actual_weight.saturating_add(
1260 T::WeightInfo::handle_bad_receipt(operator_ids.len() as u32),
1261 );
1262
1263 let bad_receipt_hash = execution_receipt.hash::<DomainHashingFor<T>>();
1264 do_mark_operators_as_slashed::<T>(
1265 operator_ids.into_iter(),
1266 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1267 )
1268 .map_err(Error::<T>::from)?;
1269
1270 do_unmark_invalid_bundle_authors::<T>(domain_id, &execution_receipt)
1271 .map_err(Error::<T>::from)?;
1272 }
1273
1274 if accepted_receipt_type == AcceptedReceiptType::NewHead {
1275 do_mark_invalid_bundle_authors::<T>(domain_id, &receipt)
1278 .map_err(Error::<T>::Staking)?;
1279 }
1280
1281 #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
1282 let maybe_confirmed_domain_block_info = process_execution_receipt::<T>(
1283 domain_id,
1284 operator_id,
1285 receipt,
1286 accepted_receipt_type,
1287 )
1288 .map_err(Error::<T>::from)?;
1289
1290 #[cfg(not(feature = "runtime-benchmarks"))]
1296 if let Some(confirmed_block_info) = maybe_confirmed_domain_block_info {
1297 actual_weight =
1298 actual_weight.saturating_add(T::WeightInfo::confirm_domain_block(
1299 confirmed_block_info.operator_ids.len() as u32,
1300 confirmed_block_info.invalid_bundle_authors.len() as u32,
1301 ));
1302
1303 refund_storage_fee::<T>(
1304 confirmed_block_info.total_storage_fee,
1305 confirmed_block_info.paid_bundle_storage_fees,
1306 )
1307 .map_err(Error::<T>::from)?;
1308
1309 do_reward_operators::<T>(
1310 domain_id,
1311 OperatorRewardSource::Bundle {
1312 at_block_number: confirmed_block_info.consensus_block_number,
1313 },
1314 confirmed_block_info.operator_ids.into_iter(),
1315 confirmed_block_info.rewards,
1316 )
1317 .map_err(Error::<T>::from)?;
1318
1319 do_mark_operators_as_slashed::<T>(
1320 confirmed_block_info.invalid_bundle_authors.into_iter(),
1321 SlashedReason::InvalidBundle(confirmed_block_info.domain_block_number),
1322 )
1323 .map_err(Error::<T>::from)?;
1324 }
1325 }
1326 }
1327
1328 if SuccessfulBundles::<T>::get(domain_id).is_empty() {
1332 let missed_upgrade =
1339 Self::missed_domain_runtime_upgrade(domain_id).map_err(Error::<T>::from)?;
1340
1341 let next_number = HeadDomainNumber::<T>::get(domain_id)
1342 .checked_add(&One::one())
1343 .ok_or::<Error<T>>(BlockTreeError::MaxHeadDomainNumber.into())?
1344 .checked_add(&missed_upgrade.into())
1345 .ok_or::<Error<T>>(BlockTreeError::MaxHeadDomainNumber.into())?;
1346
1347 #[cfg(not(feature = "runtime-benchmarks"))]
1349 if next_number % T::StakeEpochDuration::get() == Zero::zero() {
1350 let epoch_transition_res = do_finalize_domain_current_epoch::<T>(domain_id)
1351 .map_err(Error::<T>::from)?;
1352
1353 Self::deposit_event(Event::DomainEpochCompleted {
1354 domain_id,
1355 completed_epoch_index: epoch_transition_res.completed_epoch_index,
1356 });
1357
1358 actual_weight = actual_weight
1359 .saturating_add(Self::actual_epoch_transition_weight(epoch_transition_res));
1360 }
1361
1362 HeadDomainNumber::<T>::set(domain_id, next_number);
1363 }
1364
1365 let head_domain_number = HeadDomainNumber::<T>::get(domain_id);
1367 let consensus_block_number = frame_system::Pallet::<T>::current_block_number();
1368 ExecutionInbox::<T>::append(
1369 (domain_id, head_domain_number, consensus_block_number),
1370 BundleDigest {
1371 header_hash: bundle_header_hash,
1372 extrinsics_root,
1373 size: bundle_size,
1374 },
1375 );
1376
1377 InboxedBundleAuthor::<T>::insert(bundle_header_hash, operator_id);
1378
1379 SuccessfulBundles::<T>::append(domain_id, bundle_hash);
1380
1381 OperatorBundleSlot::<T>::mutate(operator_id, |slot_set| slot_set.insert(slot_number));
1382
1383 #[cfg(not(feature = "runtime-benchmarks"))]
1385 {
1386 let slashed_nominator_count =
1387 do_slash_operator::<T>(domain_id, MAX_NOMINATORS_TO_SLASH)
1388 .map_err(Error::<T>::from)?;
1389 actual_weight = actual_weight
1390 .saturating_add(T::WeightInfo::slash_operator(slashed_nominator_count));
1391 }
1392
1393 Self::deposit_event(Event::BundleStored {
1394 domain_id,
1395 bundle_hash,
1396 bundle_author: operator_id,
1397 });
1398
1399 Ok(Some(actual_weight.min(Self::max_submit_bundle_weight())).into())
1401 }
1402
1403 #[pallet::call_index(15)]
1404 #[pallet::weight((
1405 T::WeightInfo::submit_fraud_proof().saturating_add(
1406 T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK)
1407 ),
1408 DispatchClass::Operational
1409 ))]
1410 pub fn submit_fraud_proof(
1411 origin: OriginFor<T>,
1412 fraud_proof: Box<FraudProofFor<T>>,
1413 ) -> DispatchResultWithPostInfo {
1414 T::DomainOrigin::ensure_origin(origin)?;
1415
1416 log::trace!("Processing fraud proof: {fraud_proof:?}");
1417
1418 #[cfg(not(feature = "runtime-benchmarks"))]
1419 let mut actual_weight = T::WeightInfo::submit_fraud_proof();
1420 #[cfg(feature = "runtime-benchmarks")]
1421 let actual_weight = T::WeightInfo::submit_fraud_proof();
1422
1423 let domain_id = fraud_proof.domain_id();
1424 let bad_receipt_hash = fraud_proof.targeted_bad_receipt_hash();
1425 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
1426 let bad_receipt_number = *BlockTreeNodes::<T>::get(bad_receipt_hash)
1427 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?
1428 .execution_receipt
1429 .domain_block_number();
1430 ensure!(
1434 head_receipt_number >= bad_receipt_number,
1435 Error::<T>::from(FraudProofError::BadReceiptNotFound),
1436 );
1437
1438 #[cfg(not(feature = "runtime-benchmarks"))]
1445 {
1446 let BlockTreeNode {
1447 execution_receipt,
1448 operator_ids,
1449 } = prune_receipt::<T>(domain_id, bad_receipt_number)
1450 .map_err(Error::<T>::from)?
1451 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?;
1452
1453 actual_weight = actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt(
1454 (operator_ids.len() as u32).min(MAX_BUNDLE_PER_BLOCK),
1455 ));
1456
1457 do_mark_operators_as_slashed::<T>(
1458 operator_ids.into_iter(),
1459 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1460 )
1461 .map_err(Error::<T>::from)?;
1462
1463 do_unmark_invalid_bundle_authors::<T>(domain_id, &execution_receipt)
1465 .map_err(Error::<T>::Staking)?;
1466 }
1467
1468 let new_head_receipt_number = bad_receipt_number.saturating_sub(One::one());
1470 HeadReceiptNumber::<T>::insert(domain_id, new_head_receipt_number);
1471
1472 Self::deposit_event(Event::FraudProofProcessed {
1473 domain_id,
1474 new_head_receipt_number: Some(new_head_receipt_number),
1475 });
1476
1477 Ok(Some(actual_weight).into())
1478 }
1479
1480 #[pallet::call_index(2)]
1481 #[pallet::weight(T::WeightInfo::register_domain_runtime())]
1482 pub fn register_domain_runtime(
1483 origin: OriginFor<T>,
1484 runtime_name: String,
1485 runtime_type: RuntimeType,
1486 raw_genesis_storage: Vec<u8>,
1490 ) -> DispatchResult {
1491 ensure_root(origin)?;
1492
1493 let block_number = frame_system::Pallet::<T>::current_block_number();
1494 let runtime_id = do_register_runtime::<T>(
1495 runtime_name,
1496 runtime_type,
1497 raw_genesis_storage,
1498 block_number,
1499 )
1500 .map_err(Error::<T>::from)?;
1501
1502 Self::deposit_event(Event::DomainRuntimeCreated {
1503 runtime_id,
1504 runtime_type,
1505 });
1506
1507 Ok(())
1508 }
1509
1510 #[pallet::call_index(3)]
1511 #[pallet::weight(T::WeightInfo::upgrade_domain_runtime())]
1512 pub fn upgrade_domain_runtime(
1513 origin: OriginFor<T>,
1514 runtime_id: RuntimeId,
1515 raw_genesis_storage: Vec<u8>,
1516 ) -> DispatchResult {
1517 ensure_root(origin)?;
1518
1519 let block_number = frame_system::Pallet::<T>::current_block_number();
1520 let scheduled_at =
1521 do_schedule_runtime_upgrade::<T>(runtime_id, raw_genesis_storage, block_number)
1522 .map_err(Error::<T>::from)?;
1523
1524 Self::deposit_event(Event::DomainRuntimeUpgradeScheduled {
1525 runtime_id,
1526 scheduled_at,
1527 });
1528
1529 Ok(())
1530 }
1531
1532 #[pallet::call_index(4)]
1533 #[pallet::weight(T::WeightInfo::register_operator())]
1534 pub fn register_operator(
1535 origin: OriginFor<T>,
1536 domain_id: DomainId,
1537 amount: BalanceOf<T>,
1538 config: OperatorConfig<BalanceOf<T>>,
1539 ) -> DispatchResult {
1540 let owner = ensure_signed(origin)?;
1541
1542 let (operator_id, current_epoch_index) =
1543 do_register_operator::<T>(owner, domain_id, amount, config)
1544 .map_err(Error::<T>::from)?;
1545
1546 Self::deposit_event(Event::OperatorRegistered {
1547 operator_id,
1548 domain_id,
1549 });
1550
1551 if current_epoch_index.is_zero() {
1554 do_finalize_domain_current_epoch::<T>(domain_id).map_err(Error::<T>::from)?;
1555 }
1556
1557 Ok(())
1558 }
1559
1560 #[pallet::call_index(5)]
1561 #[pallet::weight(T::WeightInfo::nominate_operator())]
1562 pub fn nominate_operator(
1563 origin: OriginFor<T>,
1564 operator_id: OperatorId,
1565 amount: BalanceOf<T>,
1566 ) -> DispatchResult {
1567 let nominator_id = ensure_signed(origin)?;
1568
1569 do_nominate_operator::<T>(operator_id, nominator_id.clone(), amount)
1570 .map_err(Error::<T>::from)?;
1571
1572 Ok(())
1573 }
1574
1575 #[pallet::call_index(6)]
1576 #[pallet::weight(T::WeightInfo::instantiate_domain())]
1577 pub fn instantiate_domain(
1578 origin: OriginFor<T>,
1579 domain_config_params: DomainConfigParams<T::AccountId, BalanceOf<T>>,
1580 ) -> DispatchResult {
1581 let who = ensure_signed(origin)?;
1582 ensure!(
1583 PermissionedActionAllowedBy::<T>::get()
1584 .map(|allowed_by| allowed_by.is_allowed(&who))
1585 .unwrap_or_default(),
1586 Error::<T>::PermissionedActionNotAllowed
1587 );
1588
1589 let created_at = frame_system::Pallet::<T>::current_block_number();
1590
1591 let domain_id = do_instantiate_domain::<T>(domain_config_params, who, created_at)
1592 .map_err(Error::<T>::from)?;
1593
1594 Self::deposit_event(Event::DomainInstantiated { domain_id });
1595
1596 Ok(())
1597 }
1598
1599 #[pallet::call_index(8)]
1600 #[pallet::weight(Pallet::<T>::max_deregister_operator())]
1601 pub fn deregister_operator(
1602 origin: OriginFor<T>,
1603 operator_id: OperatorId,
1604 ) -> DispatchResultWithPostInfo {
1605 let who = ensure_signed(origin)?;
1606
1607 let executed_weight =
1608 do_deregister_operator::<T>(who, operator_id).map_err(Error::<T>::from)?;
1609
1610 Self::deposit_event(Event::OperatorDeregistered { operator_id });
1611
1612 let actual_weight = executed_weight.min(Pallet::<T>::max_deregister_operator());
1613 Ok(Some(actual_weight).into())
1614 }
1615
1616 #[pallet::call_index(9)]
1617 #[pallet::weight(Pallet::<T>::max_withdraw_stake())]
1618 pub fn withdraw_stake(
1619 origin: OriginFor<T>,
1620 operator_id: OperatorId,
1621 to_withdraw: T::Share,
1622 ) -> DispatchResultWithPostInfo {
1623 let who = ensure_signed(origin)?;
1624
1625 let executed_weight = do_withdraw_stake::<T>(operator_id, who.clone(), to_withdraw)
1626 .map_err(Error::<T>::from)?;
1627
1628 Self::deposit_event(Event::WithdrewStake {
1629 operator_id,
1630 nominator_id: who,
1631 });
1632
1633 let actual_weight = executed_weight.min(Pallet::<T>::max_withdraw_stake());
1634 Ok(Some(actual_weight).into())
1635 }
1636
1637 #[pallet::call_index(10)]
1641 #[pallet::weight(T::WeightInfo::unlock_funds(T::WithdrawalLimit::get()))]
1642 pub fn unlock_funds(
1643 origin: OriginFor<T>,
1644 operator_id: OperatorId,
1645 ) -> DispatchResultWithPostInfo {
1646 let nominator_id = ensure_signed(origin)?;
1647 let withdrawal_count = do_unlock_funds::<T>(operator_id, nominator_id.clone())
1648 .map_err(crate::pallet::Error::<T>::from)?;
1649
1650 Ok(Some(T::WeightInfo::unlock_funds(
1651 withdrawal_count.min(T::WithdrawalLimit::get()),
1652 ))
1653 .into())
1654 }
1655
1656 #[pallet::call_index(11)]
1659 #[pallet::weight(T::WeightInfo::unlock_nominator())]
1660 pub fn unlock_nominator(origin: OriginFor<T>, operator_id: OperatorId) -> DispatchResult {
1661 let nominator = ensure_signed(origin)?;
1662
1663 do_unlock_nominator::<T>(operator_id, nominator.clone())
1664 .map_err(crate::pallet::Error::<T>::from)?;
1665
1666 Self::deposit_event(Event::NominatorUnlocked {
1667 operator_id,
1668 nominator_id: nominator,
1669 });
1670
1671 Ok(())
1672 }
1673
1674 #[pallet::call_index(12)]
1682 #[pallet::weight(T::WeightInfo::update_domain_operator_allow_list())]
1683 pub fn update_domain_operator_allow_list(
1684 origin: OriginFor<T>,
1685 domain_id: DomainId,
1686 operator_allow_list: OperatorAllowList<T::AccountId>,
1687 ) -> DispatchResult {
1688 let who = ensure_signed(origin)?;
1689 do_update_domain_allow_list::<T>(who, domain_id, operator_allow_list)
1690 .map_err(Error::<T>::from)?;
1691 Self::deposit_event(crate::pallet::Event::DomainOperatorAllowListUpdated { domain_id });
1692 Ok(())
1693 }
1694
1695 #[pallet::call_index(13)]
1697 #[pallet::weight(Pallet::<T>::max_staking_epoch_transition())]
1698 pub fn force_staking_epoch_transition(
1699 origin: OriginFor<T>,
1700 domain_id: DomainId,
1701 ) -> DispatchResultWithPostInfo {
1702 ensure_root(origin)?;
1703
1704 let epoch_transition_res =
1705 do_finalize_domain_current_epoch::<T>(domain_id).map_err(Error::<T>::from)?;
1706
1707 Self::deposit_event(Event::ForceDomainEpochTransition {
1708 domain_id,
1709 completed_epoch_index: epoch_transition_res.completed_epoch_index,
1710 });
1711
1712 let actual_weight = Self::actual_epoch_transition_weight(epoch_transition_res)
1714 .min(Self::max_staking_epoch_transition());
1715
1716 Ok(Some(actual_weight).into())
1717 }
1718
1719 #[pallet::call_index(14)]
1721 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
1722 pub fn set_permissioned_action_allowed_by(
1723 origin: OriginFor<T>,
1724 permissioned_action_allowed_by: sp_domains::PermissionedActionAllowedBy<T::AccountId>,
1725 ) -> DispatchResult {
1726 ensure_root(origin)?;
1727 PermissionedActionAllowedBy::<T>::put(permissioned_action_allowed_by);
1728 Ok(())
1729 }
1730
1731 #[pallet::call_index(16)]
1733 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(3, 1))]
1734 pub fn send_domain_sudo_call(
1735 origin: OriginFor<T>,
1736 domain_id: DomainId,
1737 call: Vec<u8>,
1738 ) -> DispatchResult {
1739 ensure_root(origin)?;
1740 ensure!(
1741 DomainSudoCalls::<T>::get(domain_id).maybe_call.is_none(),
1742 Error::<T>::DomainSudoCallExists
1743 );
1744
1745 let domain_runtime = Self::domain_runtime_code(domain_id).ok_or(
1746 Error::<T>::DomainRegistry(DomainRegistryError::DomainNotFound),
1747 )?;
1748 ensure!(
1749 domain_runtime_call(
1750 domain_runtime,
1751 StatelessDomainRuntimeCall::IsValidDomainSudoCall(call.clone()),
1752 )
1753 .unwrap_or(false),
1754 Error::<T>::InvalidDomainSudoCall
1755 );
1756
1757 DomainSudoCalls::<T>::set(
1758 domain_id,
1759 DomainSudoCall {
1760 maybe_call: Some(call),
1761 },
1762 );
1763 Ok(())
1764 }
1765
1766 #[pallet::call_index(17)]
1769 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
1770 pub fn freeze_domain(origin: OriginFor<T>, domain_id: DomainId) -> DispatchResult {
1771 ensure_root(origin)?;
1772 FrozenDomains::<T>::mutate(|frozen_domains| frozen_domains.insert(domain_id));
1773 Self::deposit_event(Event::DomainFrozen { domain_id });
1774 Ok(())
1775 }
1776
1777 #[pallet::call_index(18)]
1779 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
1780 pub fn unfreeze_domain(origin: OriginFor<T>, domain_id: DomainId) -> DispatchResult {
1781 ensure_root(origin)?;
1782 FrozenDomains::<T>::mutate(|frozen_domains| frozen_domains.remove(&domain_id));
1783 Self::deposit_event(Event::DomainUnfrozen { domain_id });
1784 Ok(())
1785 }
1786
1787 #[pallet::call_index(19)]
1791 #[pallet::weight(Pallet::<T>::max_prune_domain_execution_receipt())]
1792 pub fn prune_domain_execution_receipt(
1793 origin: OriginFor<T>,
1794 domain_id: DomainId,
1795 bad_receipt_hash: ReceiptHashFor<T>,
1796 ) -> DispatchResultWithPostInfo {
1797 ensure_root(origin)?;
1798 ensure!(
1799 FrozenDomains::<T>::get().contains(&domain_id),
1800 Error::<T>::DomainNotFrozen
1801 );
1802
1803 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
1804 let bad_receipt_number = *BlockTreeNodes::<T>::get(bad_receipt_hash)
1805 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?
1806 .execution_receipt
1807 .domain_block_number();
1808 ensure!(
1811 head_receipt_number >= bad_receipt_number,
1812 Error::<T>::from(FraudProofError::BadReceiptNotFound),
1813 );
1814
1815 let mut actual_weight = T::DbWeight::get().reads(3);
1816
1817 let BlockTreeNode {
1819 execution_receipt,
1820 operator_ids,
1821 } = prune_receipt::<T>(domain_id, bad_receipt_number)
1822 .map_err(Error::<T>::from)?
1823 .ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?;
1824
1825 actual_weight = actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt(
1826 (operator_ids.len() as u32).min(MAX_BUNDLE_PER_BLOCK),
1827 ));
1828
1829 do_mark_operators_as_slashed::<T>(
1830 operator_ids.into_iter(),
1831 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1832 )
1833 .map_err(Error::<T>::from)?;
1834
1835 do_unmark_invalid_bundle_authors::<T>(domain_id, &execution_receipt)
1837 .map_err(Error::<T>::Staking)?;
1838
1839 let new_head_receipt_number = bad_receipt_number.saturating_sub(One::one());
1841 HeadReceiptNumber::<T>::insert(domain_id, new_head_receipt_number);
1842 actual_weight = actual_weight.saturating_add(T::DbWeight::get().reads_writes(0, 1));
1843
1844 Self::deposit_event(Event::PrunedExecutionReceipt {
1845 domain_id,
1846 new_head_receipt_number: Some(new_head_receipt_number),
1847 });
1848
1849 Ok(Some(actual_weight).into())
1850 }
1851
1852 #[pallet::call_index(20)]
1854 #[pallet::weight(T::WeightInfo::transfer_treasury_funds())]
1855 pub fn transfer_treasury_funds(
1856 origin: OriginFor<T>,
1857 account_id: T::AccountId,
1858 balance: BalanceOf<T>,
1859 ) -> DispatchResult {
1860 ensure_root(origin)?;
1861 T::Currency::transfer(
1862 &T::TreasuryAccount::get(),
1863 &account_id,
1864 balance,
1865 Preservation::Preserve,
1866 )?;
1867 Ok(())
1868 }
1869
1870 #[pallet::call_index(21)]
1871 #[pallet::weight(Pallet::<T>::max_submit_receipt_weight())]
1872 pub fn submit_receipt(
1873 origin: OriginFor<T>,
1874 singleton_receipt: SingletonReceiptOf<T>,
1875 ) -> DispatchResultWithPostInfo {
1876 T::DomainOrigin::ensure_origin(origin)?;
1877
1878 let domain_id = singleton_receipt.domain_id();
1879 let operator_id = singleton_receipt.operator_id();
1880 let receipt = singleton_receipt.into_receipt();
1881
1882 #[cfg(not(feature = "runtime-benchmarks"))]
1883 let mut actual_weight = T::WeightInfo::submit_receipt();
1884 #[cfg(feature = "runtime-benchmarks")]
1885 let actual_weight = T::WeightInfo::submit_receipt();
1886
1887 match execution_receipt_type::<T>(domain_id, &receipt.as_execution_receipt_ref()) {
1888 ReceiptType::Rejected(rejected_receipt_type) => {
1889 return Err(Error::<T>::BlockTree(rejected_receipt_type.into()).into());
1890 }
1891 ReceiptType::Accepted(accepted_receipt_type) => {
1893 #[cfg(not(feature = "runtime-benchmarks"))]
1899 if accepted_receipt_type == AcceptedReceiptType::NewHead
1900 && let Some(BlockTreeNode {
1901 execution_receipt,
1902 operator_ids,
1903 }) = prune_receipt::<T>(domain_id, *receipt.domain_block_number())
1904 .map_err(Error::<T>::from)?
1905 {
1906 actual_weight = actual_weight.saturating_add(
1907 T::WeightInfo::handle_bad_receipt(operator_ids.len() as u32),
1908 );
1909
1910 let bad_receipt_hash = execution_receipt.hash::<DomainHashingFor<T>>();
1911 do_mark_operators_as_slashed::<T>(
1912 operator_ids.into_iter(),
1913 SlashedReason::BadExecutionReceipt(bad_receipt_hash),
1914 )
1915 .map_err(Error::<T>::from)?;
1916
1917 do_unmark_invalid_bundle_authors::<T>(domain_id, &execution_receipt)
1918 .map_err(Error::<T>::from)?;
1919 }
1920
1921 if accepted_receipt_type == AcceptedReceiptType::NewHead {
1922 do_mark_invalid_bundle_authors::<T>(domain_id, &receipt)
1925 .map_err(Error::<T>::Staking)?;
1926 }
1927
1928 #[cfg_attr(feature = "runtime-benchmarks", allow(unused_variables))]
1929 let maybe_confirmed_domain_block_info = process_execution_receipt::<T>(
1930 domain_id,
1931 operator_id,
1932 receipt,
1933 accepted_receipt_type,
1934 )
1935 .map_err(Error::<T>::from)?;
1936
1937 #[cfg(not(feature = "runtime-benchmarks"))]
1940 if let Some(confirmed_block_info) = maybe_confirmed_domain_block_info {
1941 actual_weight =
1942 actual_weight.saturating_add(T::WeightInfo::confirm_domain_block(
1943 confirmed_block_info.operator_ids.len() as u32,
1944 confirmed_block_info.invalid_bundle_authors.len() as u32,
1945 ));
1946
1947 refund_storage_fee::<T>(
1948 confirmed_block_info.total_storage_fee,
1949 confirmed_block_info.paid_bundle_storage_fees,
1950 )
1951 .map_err(Error::<T>::from)?;
1952
1953 do_reward_operators::<T>(
1954 domain_id,
1955 OperatorRewardSource::Bundle {
1956 at_block_number: confirmed_block_info.consensus_block_number,
1957 },
1958 confirmed_block_info.operator_ids.into_iter(),
1959 confirmed_block_info.rewards,
1960 )
1961 .map_err(Error::<T>::from)?;
1962
1963 do_mark_operators_as_slashed::<T>(
1964 confirmed_block_info.invalid_bundle_authors.into_iter(),
1965 SlashedReason::InvalidBundle(confirmed_block_info.domain_block_number),
1966 )
1967 .map_err(Error::<T>::from)?;
1968 }
1969 }
1970 }
1971
1972 #[cfg(not(feature = "runtime-benchmarks"))]
1974 {
1975 let slashed_nominator_count =
1976 do_slash_operator::<T>(domain_id, MAX_NOMINATORS_TO_SLASH)
1977 .map_err(Error::<T>::from)?;
1978 actual_weight = actual_weight
1979 .saturating_add(T::WeightInfo::slash_operator(slashed_nominator_count));
1980 }
1981
1982 Ok(Some(actual_weight.min(Self::max_submit_receipt_weight())).into())
1984 }
1985
1986 #[pallet::call_index(22)]
1988 #[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(3, 1))]
1989 pub fn send_evm_domain_set_contract_creation_allowed_by_call(
1990 origin: OriginFor<T>,
1991 domain_id: DomainId,
1992 contract_creation_allowed_by: sp_domains::PermissionedActionAllowedBy<
1993 EthereumAccountId,
1994 >,
1995 ) -> DispatchResult {
1996 let signer = ensure_signed_or_root(origin)?;
1997
1998 ensure!(
1999 Pallet::<T>::is_private_evm_domain(domain_id),
2000 Error::<T>::NotPrivateEvmDomain,
2001 );
2002 if let Some(non_root_signer) = signer {
2003 ensure!(
2004 Pallet::<T>::is_domain_owner(domain_id, non_root_signer),
2005 Error::<T>::NotDomainOwnerOrRoot,
2006 );
2007 }
2008 ensure!(
2009 EvmDomainContractCreationAllowedByCalls::<T>::get(domain_id)
2010 .maybe_call
2011 .is_none(),
2012 Error::<T>::EvmDomainContractCreationAllowedByCallExists,
2013 );
2014
2015 EvmDomainContractCreationAllowedByCalls::<T>::set(
2016 domain_id,
2017 EvmDomainContractCreationAllowedByCall {
2018 maybe_call: Some(contract_creation_allowed_by),
2019 },
2020 );
2021
2022 Ok(())
2023 }
2024
2025 #[pallet::call_index(23)]
2027 #[pallet::weight(T::WeightInfo::deactivate_operator())]
2028 pub fn deactivate_operator(
2029 origin: OriginFor<T>,
2030 operator_id: OperatorId,
2031 ) -> DispatchResult {
2032 ensure_root(origin)?;
2033 crate::staking::do_deactivate_operator::<T>(operator_id).map_err(Error::<T>::from)?;
2034 Ok(())
2035 }
2036
2037 #[pallet::call_index(24)]
2040 #[pallet::weight(T::WeightInfo::reactivate_operator())]
2041 pub fn reactivate_operator(
2042 origin: OriginFor<T>,
2043 operator_id: OperatorId,
2044 ) -> DispatchResult {
2045 ensure_root(origin)?;
2046 crate::staking::do_reactivate_operator::<T>(operator_id).map_err(Error::<T>::from)?;
2047 Ok(())
2048 }
2049 }
2050
2051 #[pallet::genesis_config]
2052 pub struct GenesisConfig<T: Config> {
2053 pub permissioned_action_allowed_by:
2054 Option<sp_domains::PermissionedActionAllowedBy<T::AccountId>>,
2055 pub genesis_domains: Vec<GenesisDomain<T::AccountId, BalanceOf<T>>>,
2056 }
2057
2058 impl<T: Config> Default for GenesisConfig<T> {
2059 fn default() -> Self {
2060 GenesisConfig {
2061 permissioned_action_allowed_by: None,
2062 genesis_domains: vec![],
2063 }
2064 }
2065 }
2066
2067 #[pallet::genesis_build]
2068 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
2069 fn build(&self) {
2070 if let Some(permissioned_action_allowed_by) =
2071 self.permissioned_action_allowed_by.as_ref().cloned()
2072 {
2073 PermissionedActionAllowedBy::<T>::put(permissioned_action_allowed_by)
2074 }
2075
2076 self.genesis_domains
2077 .clone()
2078 .into_iter()
2079 .for_each(|genesis_domain| {
2080 let runtime_id = register_runtime_at_genesis::<T>(
2082 genesis_domain.runtime_name,
2083 genesis_domain.runtime_type,
2084 genesis_domain.runtime_version,
2085 genesis_domain.raw_genesis_storage,
2086 Zero::zero(),
2087 )
2088 .expect("Genesis runtime registration must always succeed");
2089
2090 let domain_config_params = DomainConfigParams {
2092 domain_name: genesis_domain.domain_name,
2093 runtime_id,
2094 maybe_bundle_limit: None,
2095 bundle_slot_probability: genesis_domain.bundle_slot_probability,
2096 operator_allow_list: genesis_domain.operator_allow_list,
2097 initial_balances: genesis_domain.initial_balances,
2098 domain_runtime_info: genesis_domain.domain_runtime_info,
2099 };
2100 let domain_owner = genesis_domain.owner_account_id;
2101 let domain_id = do_instantiate_domain::<T>(
2102 domain_config_params,
2103 domain_owner.clone(),
2104 Zero::zero(),
2105 )
2106 .expect("Genesis domain instantiation must always succeed");
2107
2108 let operator_config = OperatorConfig {
2110 signing_key: genesis_domain.signing_key.clone(),
2111 minimum_nominator_stake: genesis_domain.minimum_nominator_stake,
2112 nomination_tax: genesis_domain.nomination_tax,
2113 };
2114 let operator_stake = T::MinOperatorStake::get();
2115 do_register_operator::<T>(
2116 domain_owner,
2117 domain_id,
2118 operator_stake,
2119 operator_config,
2120 )
2121 .expect("Genesis operator registration must succeed");
2122
2123 do_finalize_domain_current_epoch::<T>(domain_id)
2124 .expect("Genesis epoch must succeed");
2125 });
2126 }
2127 }
2128
2129 #[pallet::storage]
2131 pub type BlockInherentExtrinsicData<T> = StorageValue<_, InherentExtrinsicData>;
2132
2133 #[pallet::hooks]
2134 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
2136 fn on_initialize(block_number: BlockNumberFor<T>) -> Weight {
2137 let parent_number = block_number - One::one();
2138 let parent_hash = frame_system::Pallet::<T>::block_hash(parent_number);
2139
2140 for runtime_id in DomainRuntimeUpgrades::<T>::take() {
2142 let reference_count = RuntimeRegistry::<T>::get(runtime_id)
2143 .expect("Runtime object must be present since domain is insantiated; qed")
2144 .instance_count;
2145 if !reference_count.is_zero() {
2146 DomainRuntimeUpgradeRecords::<T>::mutate(runtime_id, |upgrade_record| {
2147 upgrade_record.insert(
2148 parent_number,
2149 DomainRuntimeUpgradeEntry {
2150 at_hash: parent_hash,
2151 reference_count,
2152 },
2153 )
2154 });
2155 }
2156 }
2157 DomainRuntimeUpgrades::<T>::set(Vec::new());
2160 do_upgrade_runtimes::<T>(block_number);
2163
2164 for (domain_id, _) in SuccessfulBundles::<T>::drain() {
2167 ConsensusBlockHash::<T>::insert(domain_id, parent_number, parent_hash);
2168 T::DomainBundleSubmitted::domain_bundle_submitted(domain_id);
2169
2170 DomainSudoCalls::<T>::mutate(domain_id, |sudo_call| {
2172 sudo_call.clear();
2173 });
2174 EvmDomainContractCreationAllowedByCalls::<T>::mutate(
2175 domain_id,
2176 |evm_contract_call| {
2177 evm_contract_call.clear();
2178 },
2179 );
2180 }
2181
2182 for (operator_id, slot_set) in OperatorBundleSlot::<T>::drain() {
2183 if let Some(highest_slot) = slot_set.last() {
2186 OperatorHighestSlot::<T>::insert(operator_id, highest_slot);
2187 }
2188 }
2189
2190 BlockInherentExtrinsicData::<T>::kill();
2191
2192 Weight::zero()
2193 }
2194
2195 fn on_finalize(_: BlockNumberFor<T>) {
2196 if SuccessfulBundles::<T>::iter_keys().count() > 0
2199 || !DomainRuntimeUpgrades::<T>::get().is_empty()
2200 {
2201 let extrinsics_shuffling_seed = Randomness::from(
2202 Into::<H256>::into(Self::extrinsics_shuffling_seed_value()).to_fixed_bytes(),
2203 );
2204
2205 let timestamp = Self::timestamp_value();
2208
2209 let consensus_transaction_byte_fee = Self::consensus_transaction_byte_fee_value();
2211
2212 let inherent_extrinsic_data = InherentExtrinsicData {
2213 extrinsics_shuffling_seed,
2214 timestamp,
2215 consensus_transaction_byte_fee,
2216 };
2217
2218 BlockInherentExtrinsicData::<T>::set(Some(inherent_extrinsic_data));
2219 }
2220
2221 let _ = LastEpochStakingDistribution::<T>::clear(u32::MAX, None);
2222 let _ = NewAddedHeadReceipt::<T>::clear(u32::MAX, None);
2223 }
2224 }
2225}
2226
2227impl<T: Config> Pallet<T> {
2228 fn log_bundle_error(err: &BundleError, domain_id: DomainId, operator_id: OperatorId) {
2229 match err {
2230 BundleError::Receipt(BlockTreeError::InFutureReceipt)
2233 | BundleError::Receipt(BlockTreeError::StaleReceipt)
2234 | BundleError::Receipt(BlockTreeError::NewBranchReceipt)
2235 | BundleError::Receipt(BlockTreeError::UnavailableConsensusBlockHash)
2236 | BundleError::Receipt(BlockTreeError::BuiltOnUnknownConsensusBlock)
2237 | BundleError::SlotInThePast
2238 | BundleError::SlotInTheFuture
2239 | BundleError::InvalidProofOfTime
2240 | BundleError::SlotSmallerThanPreviousBlockBundle
2241 | BundleError::ExpectingReceiptGap
2242 | BundleError::UnexpectedReceiptGap => {
2243 log::debug!(
2244 "Bad bundle/receipt, domain {domain_id:?}, operator {operator_id:?}, error: {err:?}",
2245 );
2246 }
2247 _ => {
2248 log::warn!(
2249 "Bad bundle/receipt, domain {domain_id:?}, operator {operator_id:?}, error: {err:?}",
2250 );
2251 }
2252 }
2253 }
2254
2255 pub fn successful_bundles(domain_id: DomainId) -> Vec<H256> {
2256 SuccessfulBundles::<T>::get(domain_id)
2257 }
2258
2259 pub fn domain_runtime_code(domain_id: DomainId) -> Option<Vec<u8>> {
2260 RuntimeRegistry::<T>::get(Self::runtime_id(domain_id)?)
2261 .and_then(|mut runtime_object| runtime_object.raw_genesis.take_runtime_code())
2262 }
2263
2264 pub fn domain_best_number(domain_id: DomainId) -> Result<DomainBlockNumberFor<T>, BundleError> {
2265 let missed_upgrade = Self::missed_domain_runtime_upgrade(domain_id)
2268 .map_err(|_| BundleError::FailedToGetMissedUpgradeCount)?;
2269
2270 Ok(HeadDomainNumber::<T>::get(domain_id) + missed_upgrade.into())
2271 }
2272
2273 pub fn runtime_id(domain_id: DomainId) -> Option<RuntimeId> {
2275 DomainRegistry::<T>::get(domain_id)
2276 .map(|domain_object| domain_object.domain_config.runtime_id)
2277 }
2278
2279 pub fn runtime_upgrades() -> Vec<RuntimeId> {
2281 DomainRuntimeUpgrades::<T>::get()
2282 }
2283
2284 pub fn domain_instance_data(
2285 domain_id: DomainId,
2286 ) -> Option<(DomainInstanceData, BlockNumberFor<T>)> {
2287 let domain_obj = DomainRegistry::<T>::get(domain_id)?;
2288 let runtime_object = RuntimeRegistry::<T>::get(domain_obj.domain_config.runtime_id)?;
2289 let runtime_type = runtime_object.runtime_type;
2290 let total_issuance = domain_obj.domain_config.total_issuance()?;
2291 let raw_genesis = into_complete_raw_genesis::<T>(
2292 runtime_object,
2293 domain_id,
2294 &domain_obj.domain_runtime_info,
2295 total_issuance,
2296 domain_obj.domain_config.initial_balances,
2297 )
2298 .ok()?;
2299 Some((
2300 DomainInstanceData {
2301 runtime_type,
2302 raw_genesis,
2303 },
2304 domain_obj.created_at,
2305 ))
2306 }
2307
2308 pub fn domain_tx_range(domain_id: DomainId) -> U256 {
2310 DomainTxRangeState::<T>::try_get(domain_id)
2311 .map(|state| state.tx_range)
2312 .ok()
2313 .unwrap_or_else(Self::initial_tx_range)
2314 }
2315
2316 pub fn bundle_producer_election_params(
2317 domain_id: DomainId,
2318 ) -> Option<BundleProducerElectionParams<BalanceOf<T>>> {
2319 match (
2320 DomainRegistry::<T>::get(domain_id),
2321 DomainStakingSummary::<T>::get(domain_id),
2322 ) {
2323 (Some(domain_object), Some(stake_summary)) => Some(BundleProducerElectionParams {
2324 total_domain_stake: stake_summary.current_total_stake,
2325 bundle_slot_probability: domain_object.domain_config.bundle_slot_probability,
2326 }),
2327 _ => None,
2328 }
2329 }
2330
2331 pub fn operator(operator_id: OperatorId) -> Option<(OperatorPublicKey, BalanceOf<T>)> {
2332 Operators::<T>::get(operator_id)
2333 .map(|operator| (operator.signing_key, operator.current_total_stake))
2334 }
2335
2336 fn check_extrinsics_root(opaque_bundle: &OpaqueBundleOf<T>) -> Result<(), BundleError> {
2337 let expected_extrinsics_root = <T::DomainHeader as Header>::Hashing::ordered_trie_root(
2338 opaque_bundle
2339 .extrinsics()
2340 .iter()
2341 .map(|xt| xt.encode())
2342 .collect(),
2343 sp_core::storage::StateVersion::V1,
2344 );
2345 ensure!(
2346 expected_extrinsics_root == opaque_bundle.extrinsics_root(),
2347 BundleError::InvalidExtrinsicRoot
2348 );
2349 Ok(())
2350 }
2351
2352 fn check_slot_and_proof_of_time(
2353 slot_number: u64,
2354 proof_of_time: PotOutput,
2355 pre_dispatch: bool,
2356 ) -> Result<(), BundleError> {
2357 let current_block_number = frame_system::Pallet::<T>::current_block_number();
2360
2361 if pre_dispatch && let Some(future_slot) = T::BlockSlot::future_slot(current_block_number) {
2367 ensure!(slot_number <= *future_slot, BundleError::SlotInTheFuture)
2368 }
2369
2370 let produced_after_block_number =
2372 match T::BlockSlot::slot_produced_after(slot_number.into()) {
2373 Some(n) => n,
2374 None => {
2375 if current_block_number > T::BundleLongevity::get().into() {
2378 return Err(BundleError::SlotInThePast);
2379 } else {
2380 Zero::zero()
2381 }
2382 }
2383 };
2384 let produced_after_block_hash = if produced_after_block_number == current_block_number {
2385 frame_system::Pallet::<T>::parent_hash()
2387 } else {
2388 frame_system::Pallet::<T>::block_hash(produced_after_block_number)
2389 };
2390 if let Some(last_eligible_block) =
2391 current_block_number.checked_sub(&T::BundleLongevity::get().into())
2392 {
2393 ensure!(
2394 produced_after_block_number >= last_eligible_block,
2395 BundleError::SlotInThePast
2396 );
2397 }
2398
2399 if !is_proof_of_time_valid(
2400 BlockHash::try_from(produced_after_block_hash.as_ref())
2401 .expect("Must be able to convert to block hash type"),
2402 SlotNumber::from(slot_number),
2403 WrappedPotOutput::from(proof_of_time),
2404 !pre_dispatch,
2406 ) {
2407 return Err(BundleError::InvalidProofOfTime);
2408 }
2409
2410 Ok(())
2411 }
2412
2413 fn validate_bundle(
2414 opaque_bundle: &OpaqueBundleOf<T>,
2415 domain_config: &DomainConfig<T::AccountId, BalanceOf<T>>,
2416 ) -> Result<(), BundleError> {
2417 ensure!(
2418 opaque_bundle.body_size() <= domain_config.max_bundle_size,
2419 BundleError::BundleTooLarge
2420 );
2421
2422 ensure!(
2423 opaque_bundle
2424 .estimated_weight()
2425 .all_lte(domain_config.max_bundle_weight),
2426 BundleError::BundleTooHeavy
2427 );
2428
2429 Self::check_extrinsics_root(opaque_bundle)?;
2430
2431 Ok(())
2432 }
2433
2434 fn validate_eligibility(
2435 to_sign: &[u8],
2436 signature: &OperatorSignature,
2437 proof_of_election: &ProofOfElection,
2438 domain_config: &DomainConfig<T::AccountId, BalanceOf<T>>,
2439 pre_dispatch: bool,
2440 ) -> Result<(), BundleError> {
2441 let domain_id = proof_of_election.domain_id;
2442 let operator_id = proof_of_election.operator_id;
2443 let slot_number = proof_of_election.slot_number;
2444
2445 ensure!(
2446 !FrozenDomains::<T>::get().contains(&domain_id),
2447 BundleError::DomainFrozen
2448 );
2449
2450 let operator = Operators::<T>::get(operator_id).ok_or(BundleError::InvalidOperatorId)?;
2451
2452 let can_submit_bundle = operator.can_operator_submit_bundle::<T>(operator_id);
2453 ensure!(can_submit_bundle, BundleError::BadOperator);
2454
2455 if !operator.signing_key.verify(&to_sign, signature) {
2456 return Err(BundleError::BadBundleSignature);
2457 }
2458
2459 ensure!(
2461 slot_number
2462 > Self::operator_highest_slot_from_previous_block(operator_id, pre_dispatch),
2463 BundleError::SlotSmallerThanPreviousBlockBundle,
2464 );
2465
2466 ensure!(
2468 !OperatorBundleSlot::<T>::get(operator_id).contains(&slot_number),
2469 BundleError::EquivocatedBundle,
2470 );
2471
2472 let (operator_stake, total_domain_stake) =
2473 Self::fetch_operator_stake_info(domain_id, &operator_id)?;
2474
2475 Self::check_slot_and_proof_of_time(
2476 slot_number,
2477 proof_of_election.proof_of_time,
2478 pre_dispatch,
2479 )?;
2480
2481 sp_domains::bundle_producer_election::check_proof_of_election(
2482 &operator.signing_key,
2483 domain_config.bundle_slot_probability,
2484 proof_of_election,
2485 operator_stake.saturated_into(),
2486 total_domain_stake.saturated_into(),
2487 )?;
2488
2489 Ok(())
2490 }
2491
2492 fn check_execution_receipt_version(
2495 er_derived_consensus_number: BlockNumberFor<T>,
2496 receipt_version: ExecutionReceiptVersion,
2497 ) -> Result<(), BundleError> {
2498 let expected_execution_receipt_version =
2499 Self::bundle_and_execution_receipt_version_for_consensus_number(
2500 er_derived_consensus_number,
2501 PreviousBundleAndExecutionReceiptVersions::<T>::get(),
2502 T::CurrentBundleAndExecutionReceiptVersion::get(),
2503 )
2504 .ok_or(BundleError::ExecutionVersionMissing)?
2505 .execution_receipt_version;
2506 match (receipt_version, expected_execution_receipt_version) {
2507 (ExecutionReceiptVersion::V0, ExecutionReceiptVersion::V0) => Ok(()),
2508 }
2509 }
2510
2511 fn validate_submit_bundle(
2512 opaque_bundle: &OpaqueBundleOf<T>,
2513 pre_dispatch: bool,
2514 ) -> Result<(), BundleError> {
2515 let current_bundle_version =
2516 T::CurrentBundleAndExecutionReceiptVersion::get().bundle_version;
2517
2518 match (current_bundle_version, opaque_bundle) {
2520 (BundleVersion::V0, Bundle::V0(_)) => Ok::<(), BundleError>(()),
2521 }?;
2522
2523 let domain_id = opaque_bundle.domain_id();
2524 let operator_id = opaque_bundle.operator_id();
2525 let sealed_header = opaque_bundle.sealed_header();
2526
2527 let receipt = sealed_header.receipt();
2528 Self::check_execution_receipt_version(
2529 *receipt.consensus_block_number(),
2530 receipt.version(),
2531 )?;
2532
2533 ensure!(
2537 Self::receipt_gap(domain_id)? <= One::one(),
2538 BundleError::UnexpectedReceiptGap,
2539 );
2540
2541 charge_bundle_storage_fee::<T>(operator_id, opaque_bundle.size())
2542 .map_err(|_| BundleError::UnableToPayBundleStorageFee)?;
2543
2544 let domain_config = &DomainRegistry::<T>::get(domain_id)
2545 .ok_or(BundleError::InvalidDomainId)?
2546 .domain_config;
2547
2548 Self::validate_bundle(opaque_bundle, domain_config)?;
2549
2550 Self::validate_eligibility(
2551 sealed_header.pre_hash().as_ref(),
2552 sealed_header.signature(),
2553 sealed_header.proof_of_election(),
2554 domain_config,
2555 pre_dispatch,
2556 )?;
2557
2558 verify_execution_receipt::<T>(domain_id, &receipt).map_err(BundleError::Receipt)?;
2559
2560 Ok(())
2561 }
2562
2563 fn validate_singleton_receipt(
2564 sealed_singleton_receipt: &SingletonReceiptOf<T>,
2565 pre_dispatch: bool,
2566 ) -> Result<(), BundleError> {
2567 let domain_id = sealed_singleton_receipt.domain_id();
2568 let operator_id = sealed_singleton_receipt.operator_id();
2569
2570 ensure!(
2572 Self::receipt_gap(domain_id)? > One::one(),
2573 BundleError::ExpectingReceiptGap,
2574 );
2575
2576 charge_bundle_storage_fee::<T>(operator_id, sealed_singleton_receipt.size())
2577 .map_err(|_| BundleError::UnableToPayBundleStorageFee)?;
2578
2579 Self::check_execution_receipt_version(
2580 *sealed_singleton_receipt
2581 .singleton_receipt
2582 .receipt
2583 .consensus_block_number(),
2584 sealed_singleton_receipt.singleton_receipt.receipt.version(),
2585 )?;
2586
2587 let domain_config = DomainRegistry::<T>::get(domain_id)
2588 .ok_or(BundleError::InvalidDomainId)?
2589 .domain_config;
2590 Self::validate_eligibility(
2591 sealed_singleton_receipt.pre_hash().as_ref(),
2592 &sealed_singleton_receipt.signature,
2593 &sealed_singleton_receipt.singleton_receipt.proof_of_election,
2594 &domain_config,
2595 pre_dispatch,
2596 )?;
2597
2598 verify_execution_receipt::<T>(
2599 domain_id,
2600 &sealed_singleton_receipt
2601 .singleton_receipt
2602 .receipt
2603 .as_execution_receipt_ref(),
2604 )
2605 .map_err(BundleError::Receipt)?;
2606
2607 Ok(())
2608 }
2609
2610 fn validate_fraud_proof(
2611 fraud_proof: &FraudProofFor<T>,
2612 ) -> Result<(DomainId, TransactionPriority), FraudProofError> {
2613 let domain_id = fraud_proof.domain_id();
2614 let bad_receipt_hash = fraud_proof.targeted_bad_receipt_hash();
2615 let bad_receipt = BlockTreeNodes::<T>::get(bad_receipt_hash)
2616 .ok_or(FraudProofError::BadReceiptNotFound)?
2617 .execution_receipt;
2618 let bad_receipt_domain_block_number = *bad_receipt.domain_block_number();
2619
2620 ensure!(
2621 !bad_receipt_domain_block_number.is_zero(),
2622 FraudProofError::ChallengingGenesisReceipt
2623 );
2624
2625 ensure!(
2626 !Self::is_bad_er_pending_to_prune(domain_id, bad_receipt_domain_block_number),
2627 FraudProofError::BadReceiptAlreadyReported,
2628 );
2629
2630 ensure!(
2631 !fraud_proof.is_unexpected_domain_runtime_code_proof(),
2632 FraudProofError::UnexpectedDomainRuntimeCodeProof,
2633 );
2634
2635 ensure!(
2636 !fraud_proof.is_unexpected_mmr_proof(),
2637 FraudProofError::UnexpectedMmrProof,
2638 );
2639
2640 let maybe_state_root = match &fraud_proof.maybe_mmr_proof {
2641 Some(mmr_proof) => Some(Self::verify_mmr_proof_and_extract_state_root(
2642 mmr_proof.clone(),
2643 *bad_receipt.consensus_block_number(),
2644 )?),
2645 None => None,
2646 };
2647
2648 match &fraud_proof.proof {
2649 FraudProofVariant::InvalidBlockFees(InvalidBlockFeesProof { storage_proof }) => {
2650 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2651 domain_id,
2652 &bad_receipt,
2653 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2654 )?;
2655
2656 verify_invalid_block_fees_fraud_proof::<
2657 T::Block,
2658 DomainBlockNumberFor<T>,
2659 T::DomainHash,
2660 BalanceOf<T>,
2661 DomainHashingFor<T>,
2662 >(bad_receipt, storage_proof, domain_runtime_code)
2663 .map_err(|err| {
2664 log::error!("Block fees proof verification failed: {err:?}");
2665 FraudProofError::InvalidBlockFeesFraudProof
2666 })?;
2667 }
2668 FraudProofVariant::InvalidTransfers(InvalidTransfersProof { storage_proof }) => {
2669 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2670 domain_id,
2671 &bad_receipt,
2672 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2673 )?;
2674
2675 verify_invalid_transfers_fraud_proof::<
2676 T::Block,
2677 DomainBlockNumberFor<T>,
2678 T::DomainHash,
2679 BalanceOf<T>,
2680 DomainHashingFor<T>,
2681 >(bad_receipt, storage_proof, domain_runtime_code)
2682 .map_err(|err| {
2683 log::error!("Domain transfers proof verification failed: {err:?}");
2684 FraudProofError::InvalidTransfersFraudProof
2685 })?;
2686 }
2687 FraudProofVariant::InvalidDomainBlockHash(InvalidDomainBlockHashProof {
2688 digest_storage_proof,
2689 }) => {
2690 let parent_receipt =
2691 BlockTreeNodes::<T>::get(*bad_receipt.parent_domain_block_receipt_hash())
2692 .ok_or(FraudProofError::ParentReceiptNotFound)?
2693 .execution_receipt;
2694 verify_invalid_domain_block_hash_fraud_proof::<
2695 T::Block,
2696 BalanceOf<T>,
2697 T::DomainHeader,
2698 >(
2699 bad_receipt,
2700 digest_storage_proof.clone(),
2701 *parent_receipt.domain_block_hash(),
2702 )
2703 .map_err(|err| {
2704 log::error!("Invalid Domain block hash proof verification failed: {err:?}");
2705 FraudProofError::InvalidDomainBlockHashFraudProof
2706 })?;
2707 }
2708 FraudProofVariant::InvalidExtrinsicsRoot(proof) => {
2709 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2710 domain_id,
2711 &bad_receipt,
2712 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2713 )?;
2714 let runtime_id =
2715 Self::runtime_id(domain_id).ok_or(FraudProofError::RuntimeNotFound)?;
2716 let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?;
2717
2718 verify_invalid_domain_extrinsics_root_fraud_proof::<
2719 T::Block,
2720 BalanceOf<T>,
2721 T::DomainHeader,
2722 T::Hashing,
2723 T::FraudProofStorageKeyProvider,
2724 >(
2725 bad_receipt,
2726 proof,
2727 domain_id,
2728 runtime_id,
2729 state_root,
2730 domain_runtime_code,
2731 )
2732 .map_err(|err| {
2733 log::error!("Invalid Domain extrinsic root proof verification failed: {err:?}");
2734 FraudProofError::InvalidExtrinsicRootFraudProof
2735 })?;
2736 }
2737 FraudProofVariant::InvalidStateTransition(proof) => {
2738 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2739 domain_id,
2740 &bad_receipt,
2741 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2742 )?;
2743 let bad_receipt_parent =
2744 BlockTreeNodes::<T>::get(*bad_receipt.parent_domain_block_receipt_hash())
2745 .ok_or(FraudProofError::ParentReceiptNotFound)?
2746 .execution_receipt;
2747
2748 verify_invalid_state_transition_fraud_proof::<
2749 T::Block,
2750 T::DomainHeader,
2751 BalanceOf<T>,
2752 >(bad_receipt, bad_receipt_parent, proof, domain_runtime_code)
2753 .map_err(|err| {
2754 log::error!("Invalid State transition proof verification failed: {err:?}");
2755 FraudProofError::InvalidStateTransitionFraudProof
2756 })?;
2757 }
2758 FraudProofVariant::InvalidBundles(proof) => {
2759 let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?;
2760 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2761 domain_id,
2762 &bad_receipt,
2763 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2764 )?;
2765
2766 let bad_receipt_parent =
2767 BlockTreeNodes::<T>::get(*bad_receipt.parent_domain_block_receipt_hash())
2768 .ok_or(FraudProofError::ParentReceiptNotFound)?
2769 .execution_receipt;
2770
2771 verify_invalid_bundles_fraud_proof::<
2772 T::Block,
2773 T::DomainHeader,
2774 T::MmrHash,
2775 BalanceOf<T>,
2776 T::FraudProofStorageKeyProvider,
2777 T::MmrProofVerifier,
2778 >(
2779 bad_receipt,
2780 bad_receipt_parent,
2781 proof,
2782 domain_id,
2783 state_root,
2784 domain_runtime_code,
2785 )
2786 .map_err(|err| {
2787 log::error!("Invalid Bundle proof verification failed: {err:?}");
2788 FraudProofError::InvalidBundleFraudProof
2789 })?;
2790 }
2791 FraudProofVariant::ValidBundle(proof) => {
2792 let state_root = maybe_state_root.ok_or(FraudProofError::MissingMmrProof)?;
2793 let domain_runtime_code = Self::get_domain_runtime_code_for_receipt(
2794 domain_id,
2795 &bad_receipt,
2796 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2797 )?;
2798
2799 verify_valid_bundle_fraud_proof::<
2800 T::Block,
2801 T::DomainHeader,
2802 BalanceOf<T>,
2803 T::FraudProofStorageKeyProvider,
2804 >(
2805 bad_receipt,
2806 proof,
2807 domain_id,
2808 state_root,
2809 domain_runtime_code,
2810 )
2811 .map_err(|err| {
2812 log::error!("Valid bundle proof verification failed: {err:?}");
2813 FraudProofError::BadValidBundleFraudProof
2814 })?
2815 }
2816 #[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
2817 FraudProofVariant::Dummy => {
2818 let _ = Self::get_domain_runtime_code_for_receipt(
2825 domain_id,
2826 &bad_receipt,
2827 fraud_proof.maybe_domain_runtime_code_proof.clone(),
2828 )?;
2829 }
2830 }
2831
2832 let block_before_bad_er_confirm = bad_receipt_domain_block_number.saturating_sub(
2835 Self::latest_confirmed_domain_block_number(fraud_proof.domain_id()),
2836 );
2837 let priority =
2838 TransactionPriority::MAX - block_before_bad_er_confirm.saturated_into::<u64>();
2839
2840 let tag = fraud_proof.domain_id();
2843
2844 Ok((tag, priority))
2845 }
2846
2847 fn fetch_operator_stake_info(
2852 domain_id: DomainId,
2853 operator_id: &OperatorId,
2854 ) -> Result<(BalanceOf<T>, BalanceOf<T>), BundleError> {
2855 if let Some(pending_election_params) = LastEpochStakingDistribution::<T>::get(domain_id)
2856 && let Some(operator_stake) = pending_election_params.operators.get(operator_id)
2857 {
2858 return Ok((*operator_stake, pending_election_params.total_domain_stake));
2859 }
2860 let domain_stake_summary =
2861 DomainStakingSummary::<T>::get(domain_id).ok_or(BundleError::InvalidDomainId)?;
2862 let operator_stake = domain_stake_summary
2863 .current_operators
2864 .get(operator_id)
2865 .ok_or(BundleError::BadOperator)?;
2866 Ok((*operator_stake, domain_stake_summary.current_total_stake))
2867 }
2868
2869 fn initial_tx_range() -> U256 {
2871 U256::MAX / T::InitialDomainTxRange::get()
2872 }
2873
2874 pub fn head_receipt_number(domain_id: DomainId) -> DomainBlockNumberFor<T> {
2876 HeadReceiptNumber::<T>::get(domain_id)
2877 }
2878
2879 pub fn oldest_unconfirmed_receipt_number(
2882 domain_id: DomainId,
2883 ) -> Option<DomainBlockNumberFor<T>> {
2884 let oldest_nonconfirmed_er_number =
2885 Self::latest_confirmed_domain_block_number(domain_id).saturating_add(One::one());
2886 let is_er_exist = BlockTree::<T>::get(domain_id, oldest_nonconfirmed_er_number).is_some();
2887 let is_pending_to_prune =
2888 Self::is_bad_er_pending_to_prune(domain_id, oldest_nonconfirmed_er_number);
2889
2890 if is_er_exist && !is_pending_to_prune {
2891 Some(oldest_nonconfirmed_er_number)
2892 } else {
2893 None
2898 }
2899 }
2900
2901 pub fn latest_confirmed_domain_block_number(domain_id: DomainId) -> DomainBlockNumberFor<T> {
2904 LatestConfirmedDomainExecutionReceipt::<T>::get(domain_id)
2905 .map(|er| *er.domain_block_number())
2906 .unwrap_or_default()
2907 }
2908
2909 pub fn latest_confirmed_domain_block(
2910 domain_id: DomainId,
2911 ) -> Option<(DomainBlockNumberFor<T>, T::DomainHash)> {
2912 LatestConfirmedDomainExecutionReceipt::<T>::get(domain_id)
2913 .map(|er| (*er.domain_block_number(), *er.domain_block_hash()))
2914 }
2915
2916 pub fn domain_bundle_limit(
2918 domain_id: DomainId,
2919 ) -> Result<Option<DomainBundleLimit>, DomainRegistryError> {
2920 let domain_config = match DomainRegistry::<T>::get(domain_id) {
2921 None => return Ok(None),
2922 Some(domain_obj) => domain_obj.domain_config,
2923 };
2924
2925 Ok(Some(DomainBundleLimit {
2926 max_bundle_size: domain_config.max_bundle_size,
2927 max_bundle_weight: domain_config.max_bundle_weight,
2928 }))
2929 }
2930
2931 pub fn non_empty_er_exists(domain_id: DomainId) -> bool {
2934 if BlockTree::<T>::contains_key(domain_id, DomainBlockNumberFor::<T>::zero()) {
2935 return true;
2936 }
2937
2938 let mut to_check =
2940 Self::latest_confirmed_domain_block_number(domain_id).saturating_add(One::one());
2941
2942 let head_number = HeadDomainNumber::<T>::get(domain_id);
2947
2948 while to_check <= head_number {
2949 if !ExecutionInbox::<T>::iter_prefix_values((domain_id, to_check)).all(|digests| {
2950 digests
2951 .iter()
2952 .all(|digest| digest.extrinsics_root == EMPTY_EXTRINSIC_ROOT.into())
2953 }) {
2954 return true;
2955 }
2956
2957 to_check = to_check.saturating_add(One::one())
2958 }
2959
2960 false
2961 }
2962
2963 pub fn extrinsics_shuffling_seed() -> T::Hash {
2966 BlockInherentExtrinsicData::<T>::get()
2968 .map(|data| H256::from(*data.extrinsics_shuffling_seed).into())
2969 .unwrap_or_else(|| Self::extrinsics_shuffling_seed_value())
2970 }
2971
2972 fn extrinsics_shuffling_seed_value() -> T::Hash {
2975 let subject = DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT;
2976 let (randomness, _) = T::Randomness::random(subject);
2977 randomness
2978 }
2979
2980 pub fn timestamp() -> Moment {
2983 BlockInherentExtrinsicData::<T>::get()
2985 .map(|data| data.timestamp)
2986 .unwrap_or_else(|| Self::timestamp_value())
2987 }
2988
2989 fn timestamp_value() -> Moment {
2992 T::BlockTimestamp::now()
2995 .try_into()
2996 .map_err(|_| ())
2997 .expect("Moment is the same type in both pallets; qed")
2998 }
2999
3000 pub fn consensus_transaction_byte_fee() -> Balance {
3004 BlockInherentExtrinsicData::<T>::get()
3006 .map(|data| data.consensus_transaction_byte_fee)
3007 .unwrap_or_else(|| Self::consensus_transaction_byte_fee_value())
3008 }
3009
3010 fn consensus_transaction_byte_fee_value() -> Balance {
3013 let transaction_byte_fee: Balance = T::StorageFee::transaction_byte_fee()
3016 .try_into()
3017 .map_err(|_| ())
3018 .expect("Balance is the same type in both pallets; qed");
3019
3020 sp_domains::DOMAIN_STORAGE_FEE_MULTIPLIER * transaction_byte_fee
3021 }
3022
3023 pub fn execution_receipt(receipt_hash: ReceiptHashFor<T>) -> Option<ExecutionReceiptOf<T>> {
3024 BlockTreeNodes::<T>::get(receipt_hash).map(|db| db.execution_receipt)
3025 }
3026
3027 pub(crate) fn bundle_and_execution_receipt_version_for_consensus_number<BEV>(
3030 er_derived_number: BlockNumberFor<T>,
3031 previous_versions: BTreeMap<BlockNumberFor<T>, BEV>,
3032 current_version: BEV,
3033 ) -> Option<BEV>
3034 where
3035 BEV: Copy + Clone,
3036 {
3037 match previous_versions.last_key_value() {
3039 None => {
3041 return Some(current_version);
3042 }
3043 Some((number, version)) => {
3044 if er_derived_number > *number {
3047 return Some(current_version);
3048 }
3049
3050 if er_derived_number == *number {
3053 return Some(*version);
3054 }
3055 }
3056 }
3057
3058 for (upgraded_number, version) in previous_versions.into_iter() {
3061 if er_derived_number <= upgraded_number {
3062 return Some(version);
3063 }
3064 }
3065
3066 None
3068 }
3069
3070 pub fn receipt_hash(
3071 domain_id: DomainId,
3072 domain_number: DomainBlockNumberFor<T>,
3073 ) -> Option<ReceiptHashFor<T>> {
3074 BlockTree::<T>::get(domain_id, domain_number)
3075 }
3076
3077 pub fn confirmed_domain_block_storage_key(domain_id: DomainId) -> Vec<u8> {
3078 LatestConfirmedDomainExecutionReceipt::<T>::hashed_key_for(domain_id)
3079 }
3080
3081 pub fn is_bad_er_pending_to_prune(
3082 domain_id: DomainId,
3083 receipt_number: DomainBlockNumberFor<T>,
3084 ) -> bool {
3085 if receipt_number.is_zero() {
3087 return false;
3088 }
3089
3090 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
3091
3092 head_receipt_number < receipt_number
3095 }
3096
3097 pub fn is_operator_pending_to_slash(domain_id: DomainId, operator_id: OperatorId) -> bool {
3098 let latest_submitted_er = LatestSubmittedER::<T>::get((domain_id, operator_id));
3099
3100 if latest_submitted_er.is_zero() {
3102 return false;
3103 }
3104
3105 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
3106
3107 head_receipt_number < latest_submitted_er
3111 }
3112
3113 pub fn max_submit_bundle_weight() -> Weight {
3114 T::WeightInfo::submit_bundle()
3115 .saturating_add(
3116 T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK).max(
3123 T::WeightInfo::confirm_domain_block(MAX_BUNDLE_PER_BLOCK, MAX_BUNDLE_PER_BLOCK),
3124 ),
3125 )
3126 .saturating_add(Self::max_staking_epoch_transition())
3127 .saturating_add(T::WeightInfo::slash_operator(MAX_NOMINATORS_TO_SLASH))
3128 }
3129
3130 pub fn max_submit_receipt_weight() -> Weight {
3131 T::WeightInfo::submit_bundle()
3132 .saturating_add(
3133 T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK).max(
3140 T::WeightInfo::confirm_domain_block(MAX_BUNDLE_PER_BLOCK, MAX_BUNDLE_PER_BLOCK),
3141 ),
3142 )
3143 .saturating_add(T::WeightInfo::slash_operator(MAX_NOMINATORS_TO_SLASH))
3144 }
3145
3146 pub fn max_staking_epoch_transition() -> Weight {
3147 T::WeightInfo::operator_reward_tax_and_restake(MAX_BUNDLE_PER_BLOCK).saturating_add(
3148 T::WeightInfo::finalize_domain_epoch_staking(T::MaxPendingStakingOperation::get()),
3149 )
3150 }
3151
3152 pub fn max_deregister_operator() -> Weight {
3153 T::WeightInfo::deregister_operator().max(T::WeightInfo::deregister_deactivated_operator())
3154 }
3155
3156 pub fn max_withdraw_stake() -> Weight {
3157 T::WeightInfo::withdraw_stake()
3158 .max(T::WeightInfo::withdraw_stake_from_deactivated_operator())
3159 }
3160
3161 pub fn max_prune_domain_execution_receipt() -> Weight {
3162 T::WeightInfo::handle_bad_receipt(MAX_BUNDLE_PER_BLOCK)
3163 .saturating_add(T::DbWeight::get().reads_writes(3, 1))
3164 }
3165
3166 fn actual_epoch_transition_weight(epoch_transition_res: EpochTransitionResult) -> Weight {
3167 let EpochTransitionResult {
3168 rewarded_operator_count,
3169 finalized_operator_count,
3170 completed_epoch_index: _,
3171 } = epoch_transition_res;
3172
3173 T::WeightInfo::operator_reward_tax_and_restake(rewarded_operator_count).saturating_add(
3174 T::WeightInfo::finalize_domain_epoch_staking(finalized_operator_count),
3175 )
3176 }
3177
3178 pub fn reward_domain_operators(domain_id: DomainId, rewards: BalanceOf<T>) {
3180 DomainChainRewards::<T>::mutate(domain_id, |current_rewards| {
3181 current_rewards.saturating_add(rewards)
3182 });
3183 }
3184
3185 pub fn storage_fund_account_balance(operator_id: OperatorId) -> BalanceOf<T> {
3186 let storage_fund_acc = storage_fund_account::<T>(operator_id);
3187 T::Currency::reducible_balance(&storage_fund_acc, Preservation::Preserve, Fortitude::Polite)
3188 }
3189
3190 pub fn operator_highest_slot_from_previous_block(
3194 operator_id: OperatorId,
3195 pre_dispatch: bool,
3196 ) -> u64 {
3197 if pre_dispatch {
3198 OperatorHighestSlot::<T>::get(operator_id)
3199 } else {
3200 *OperatorBundleSlot::<T>::get(operator_id)
3206 .last()
3207 .unwrap_or(&OperatorHighestSlot::<T>::get(operator_id))
3208 }
3209 }
3210
3211 pub fn get_domain_runtime_code_for_receipt(
3214 domain_id: DomainId,
3215 receipt: &ExecutionReceiptOf<T>,
3216 maybe_domain_runtime_code_at: Option<
3217 DomainRuntimeCodeAt<BlockNumberFor<T>, T::Hash, T::MmrHash>,
3218 >,
3219 ) -> Result<Vec<u8>, FraudProofError> {
3220 let runtime_id = Self::runtime_id(domain_id).ok_or(FraudProofError::RuntimeNotFound)?;
3221 let current_runtime_obj =
3222 RuntimeRegistry::<T>::get(runtime_id).ok_or(FraudProofError::RuntimeNotFound)?;
3223
3224 let at = {
3227 let parent_receipt =
3228 BlockTreeNodes::<T>::get(*receipt.parent_domain_block_receipt_hash())
3229 .ok_or(FraudProofError::ParentReceiptNotFound)?
3230 .execution_receipt;
3231 *parent_receipt.consensus_block_number()
3232 };
3233
3234 let is_domain_runtime_upgraded = current_runtime_obj.updated_at >= at;
3235
3236 let mut runtime_obj = match (is_domain_runtime_upgraded, maybe_domain_runtime_code_at) {
3237 (true, None) => return Err(FraudProofError::DomainRuntimeCodeProofNotFound),
3240 (true, Some(domain_runtime_code_at)) => {
3241 let DomainRuntimeCodeAt {
3242 mmr_proof,
3243 domain_runtime_code_proof,
3244 } = domain_runtime_code_at;
3245
3246 let state_root = Self::verify_mmr_proof_and_extract_state_root(mmr_proof, at)?;
3247
3248 <DomainRuntimeCodeProof as BasicStorageProof<T::Block>>::verify::<
3249 T::FraudProofStorageKeyProvider,
3250 >(domain_runtime_code_proof, runtime_id, &state_root)?
3251 }
3252 (false, Some(_)) => return Err(FraudProofError::UnexpectedDomainRuntimeCodeProof),
3255 (false, None) => current_runtime_obj,
3256 };
3257 let code = runtime_obj
3258 .raw_genesis
3259 .take_runtime_code()
3260 .ok_or(storage_proof::VerificationError::RuntimeCodeNotFound)?;
3261 Ok(code)
3262 }
3263
3264 pub fn is_domain_runtime_upgraded_since(
3265 domain_id: DomainId,
3266 at: BlockNumberFor<T>,
3267 ) -> Option<bool> {
3268 Self::runtime_id(domain_id)
3269 .and_then(RuntimeRegistry::<T>::get)
3270 .map(|runtime_obj| runtime_obj.updated_at >= at)
3271 }
3272
3273 pub fn verify_mmr_proof_and_extract_state_root(
3274 mmr_leaf_proof: ConsensusChainMmrLeafProof<BlockNumberFor<T>, T::Hash, T::MmrHash>,
3275 expected_block_number: BlockNumberFor<T>,
3276 ) -> Result<T::Hash, FraudProofError> {
3277 let leaf_data = T::MmrProofVerifier::verify_proof_and_extract_leaf(mmr_leaf_proof)
3278 .ok_or(FraudProofError::BadMmrProof)?;
3279
3280 if expected_block_number != leaf_data.block_number() {
3282 return Err(FraudProofError::UnexpectedMmrProof);
3283 }
3284
3285 Ok(leaf_data.state_root())
3286 }
3287
3288 fn missed_domain_runtime_upgrade(domain_id: DomainId) -> Result<u32, BlockTreeError> {
3290 let runtime_id = Self::runtime_id(domain_id).ok_or(BlockTreeError::RuntimeNotFound)?;
3291 let last_domain_block_number = HeadDomainNumber::<T>::get(domain_id);
3292
3293 let last_block_at =
3295 ExecutionInbox::<T>::iter_key_prefix((domain_id, last_domain_block_number))
3296 .next()
3297 .or(DomainRegistry::<T>::get(domain_id).map(|domain_obj| domain_obj.created_at))
3301 .ok_or(BlockTreeError::LastBlockNotFound)?;
3302
3303 Ok(DomainRuntimeUpgradeRecords::<T>::get(runtime_id)
3304 .into_keys()
3305 .rev()
3306 .take_while(|upgraded_at| *upgraded_at > last_block_at)
3307 .count() as u32)
3308 }
3309
3310 pub fn is_domain_registered(domain_id: DomainId) -> bool {
3312 DomainStakingSummary::<T>::contains_key(domain_id)
3313 }
3314
3315 pub fn domain_sudo_call(domain_id: DomainId) -> Option<Vec<u8>> {
3317 DomainSudoCalls::<T>::get(domain_id).maybe_call
3318 }
3319
3320 pub fn receipt_gap(domain_id: DomainId) -> Result<DomainBlockNumberFor<T>, BundleError> {
3323 let domain_best_number = Self::domain_best_number(domain_id)?;
3324 let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
3325
3326 Ok(domain_best_number.saturating_sub(head_receipt_number))
3327 }
3328
3329 pub fn is_evm_domain(domain_id: DomainId) -> bool {
3331 if let Some(domain_obj) = DomainRegistry::<T>::get(domain_id) {
3332 domain_obj.domain_runtime_info.is_evm_domain()
3333 } else {
3334 false
3335 }
3336 }
3337
3338 pub fn is_private_evm_domain(domain_id: DomainId) -> bool {
3340 if let Some(domain_obj) = DomainRegistry::<T>::get(domain_id) {
3341 domain_obj.domain_runtime_info.is_private_evm_domain()
3342 } else {
3343 false
3344 }
3345 }
3346
3347 pub fn evm_domain_contract_creation_allowed_by_call(
3349 domain_id: DomainId,
3350 ) -> Option<sp_domains::PermissionedActionAllowedBy<EthereumAccountId>> {
3351 EvmDomainContractCreationAllowedByCalls::<T>::get(domain_id).maybe_call
3352 }
3353
3354 #[must_use = "set PreviousBundleAndExecutionReceiptVersions to the value returned by this function"]
3357 pub(crate) fn calculate_previous_bundle_and_execution_receipt_versions<BEV>(
3358 block_number: BlockNumberFor<T>,
3359 mut previous_versions: BTreeMap<BlockNumberFor<T>, BEV>,
3360 current_version: BEV,
3361 ) -> BTreeMap<BlockNumberFor<T>, BEV>
3362 where
3363 BEV: PartialEq,
3364 {
3365 if previous_versions.is_empty() {
3367 previous_versions.insert(block_number, current_version);
3368 } else {
3369 let (prev_number, prev_version) = previous_versions
3373 .pop_last()
3374 .expect("at least one version is available due to check above");
3375
3376 if prev_version == current_version {
3378 previous_versions.insert(block_number, current_version);
3379 } else {
3380 previous_versions.insert(prev_number, prev_version);
3383 previous_versions.insert(block_number, current_version);
3384 }
3385 }
3386
3387 previous_versions
3388 }
3389
3390 pub fn current_bundle_and_execution_receipt_version() -> BundleAndExecutionReceiptVersion {
3400 let block_number = frame_system::Pallet::<T>::block_number();
3401 let versions = PreviousBundleAndExecutionReceiptVersions::<T>::get();
3402 match versions.get(&block_number) {
3403 None => T::CurrentBundleAndExecutionReceiptVersion::get(),
3405 Some(version) => *version,
3407 }
3408 }
3409
3410 pub fn nominator_position(
3423 operator_id: OperatorId,
3424 nominator_account: T::AccountId,
3425 ) -> Option<sp_domains::NominatorPosition<BalanceOf<T>, DomainBlockNumberFor<T>, T::Share>>
3426 {
3427 nominator_position::nominator_position::<T>(operator_id, nominator_account)
3428 }
3429}
3430
3431impl<T: Config> subspace_runtime_primitives::OnSetCode<BlockNumberFor<T>> for Pallet<T> {
3432 fn set_code(block_number: BlockNumberFor<T>) -> DispatchResult {
3435 PreviousBundleAndExecutionReceiptVersions::<T>::set(
3436 Self::calculate_previous_bundle_and_execution_receipt_versions(
3437 block_number,
3438 PreviousBundleAndExecutionReceiptVersions::<T>::get(),
3439 T::CurrentBundleAndExecutionReceiptVersion::get(),
3440 ),
3441 );
3442
3443 Ok(())
3444 }
3445}
3446
3447impl<T: Config> sp_domains::DomainOwner<T::AccountId> for Pallet<T> {
3448 fn is_domain_owner(domain_id: DomainId, acc: T::AccountId) -> bool {
3449 if let Some(domain_obj) = DomainRegistry::<T>::get(domain_id) {
3450 domain_obj.owner_account_id == acc
3451 } else {
3452 false
3453 }
3454 }
3455}
3456
3457impl<T> Pallet<T>
3458where
3459 T: Config + CreateUnsigned<Call<T>>,
3460{
3461 pub fn submit_bundle_unsigned(opaque_bundle: OpaqueBundleOf<T>) {
3463 let slot = opaque_bundle.slot_number();
3464 let extrinsics_count = opaque_bundle.body_length();
3465
3466 let call = Call::submit_bundle { opaque_bundle };
3467 let ext = T::create_unsigned(call.into());
3468
3469 match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
3470 Ok(()) => {
3471 log::info!("Submitted bundle from slot {slot}, extrinsics: {extrinsics_count}",);
3472 }
3473 Err(()) => {
3474 log::error!("Error submitting bundle");
3475 }
3476 }
3477 }
3478
3479 pub fn submit_receipt_unsigned(singleton_receipt: SingletonReceiptOf<T>) {
3481 let slot = singleton_receipt.slot_number();
3482 let domain_block_number = *singleton_receipt.receipt().domain_block_number();
3483
3484 let call = Call::submit_receipt { singleton_receipt };
3485 let ext = T::create_unsigned(call.into());
3486 match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
3487 Ok(()) => {
3488 log::info!(
3489 "Submitted singleton receipt from slot {slot}, domain_block_number: {domain_block_number:?}",
3490 );
3491 }
3492 Err(()) => {
3493 log::error!("Error submitting singleton receipt");
3494 }
3495 }
3496 }
3497
3498 pub fn submit_fraud_proof_unsigned(fraud_proof: FraudProofFor<T>) {
3500 let call = Call::submit_fraud_proof {
3501 fraud_proof: Box::new(fraud_proof),
3502 };
3503
3504 let ext = T::create_unsigned(call.into());
3505 match SubmitTransaction::<T, Call<T>>::submit_transaction(ext) {
3506 Ok(()) => {
3507 log::info!("Submitted fraud proof");
3508 }
3509 Err(()) => {
3510 log::error!("Error submitting fraud proof");
3511 }
3512 }
3513 }
3514}
3515
3516pub fn calculate_tx_range(
3518 cur_tx_range: U256,
3519 actual_bundle_count: u64,
3520 expected_bundle_count: u64,
3521) -> U256 {
3522 if actual_bundle_count == 0 || expected_bundle_count == 0 {
3523 return cur_tx_range;
3524 }
3525
3526 let Some(new_tx_range) = U256::from(actual_bundle_count)
3527 .saturating_mul(&cur_tx_range)
3528 .checked_div(&U256::from(expected_bundle_count))
3529 else {
3530 return cur_tx_range;
3531 };
3532
3533 let upper_bound = cur_tx_range.saturating_mul(&U256::from(4_u64));
3534 let Some(lower_bound) = cur_tx_range.checked_div(&U256::from(4_u64)) else {
3535 return cur_tx_range;
3536 };
3537 new_tx_range.clamp(lower_bound, upper_bound)
3538}