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