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