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