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