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