pallet_chain_manager/lib.rs
1// SPDX-License-Identifier: MPL-2.0
2//
3// Part of Auguth Labs open-source softwares.
4// Built for the Substrate framework.
5//
6// This Source Code Form is subject to the terms of the Mozilla Public
7// License, v. 2.0. If a copy of the MPL was not distributed with this
8// file, You can obtain one at https://mozilla.org/MPL/2.0/.
9//
10// Copyright (c) 2026 Auguth Labs (OPC) Pvt Ltd, India
11
12// ===============================================================================
13// ```````````````````````````` PALLET CHAIN-MANAGER `````````````````````````````
14// ===============================================================================
15
16//! The **Chain Manager pallet** is the primary orchestration layer for
17//! managing **session validators** as a coordinated, session-driven system.
18//!
19//! It governs the full validator lifecycle spanning participation,
20//! election, activation, rewards, and penalties by materializing the
21//! abstractions defined in [`frame_suite::blockchain`]
22//! into a concrete runtime execution model.
23//!
24//! This pallet does not introduce new primitives. Instead, it binds together
25//! traits, plugins, and adapters to drive deterministic validator selection
26//! and behavior across sessions, enabling the game-theoretic guarantees
27//! required for a decentralized network.
28//!
29//! - [`Config`] - Runtime configuration
30//! - [`Call`] - Dispatchable extrinsics (includes unsigned)
31//! - [`Pallet`] - External usage and trait implementations
32//!
33//! ## Overview
34//!
35//! A **validator (author)** in this system is an actor who:
36//!
37//! - signals intent to participate in validation,
38//! - submits affidavit data (backers) for election,
39//! - competes in a session-based selection process,
40//! - becomes an active session validator upon selection,
41//! - receives rewards or penalties based on behavior.
42//!
43//! Validators are modeled as **session-scoped actors**, where
44//! participation, selection, and compensation are tied to
45//! deterministic session transitions.
46//!
47//! ## Architectural Role
48//!
49//! The pallet acts as an **orchestration boundary**, integrating:
50//!
51//! - role management and funding via [`Config::RoleAdapter`]
52//! - election logic via [`Config::ElectionAdapter`]
53//! - asset interactions via [`Config::Asset`]
54//! - session and authorship via [`pallet_session`] and [`pallet_authorship`]
55//! - offence handling via [`pallet_offences`]
56//!
57//! It only tightly integrates with these essential core pallets, allowing
58//! any Substrate runtime that composes them to adopt this pallet
59//! for validator lifecycle and session management.
60//!
61//! ## Validator Lifecycle
62//!
63//! The system progresses through **session-scoped phases**:
64//!
65//! - **Pursuing Validation**
66//! Authors signal intent to validate and may pause participation (`chill`).
67//!
68//! - **Affidavit Phase**
69//! Active participants submit affidavit declarations representing
70//! election weights for the upcoming validator set.
71//!
72//! - **Election Phase**
73//! Elections are executed for all active participants to determine
74//! the next session validators.
75//!
76//! - **Activation**
77//! Elected authors transition into active session validators.
78//!
79//! - **Reward & Penalty**
80//! Validators are compensated or penalized based on behavior.
81//!
82//! All transitions are derived relative to session progression,
83//! ensuring predictable and deterministic execution.
84//!
85//! ## Execution Model
86//!
87//! The pallet operates as a **session-driven orchestration engine**,
88//! primarily driven by **offchain workers and unsigned extrinsics**.
89//!
90//! Once validation intent is externally invoked by an author, the system
91//! progresses automatically:
92//!
93//! - **Offchain workers**
94//! coordinate affidavit submission, election execution, and key rotation
95//!
96//! - **Unsigned extrinsics**
97//! are submitted to finalize deterministic state transitions
98//!
99//! - **Block hooks (`on_initialize`)**
100//! process accumulated state and scheduled transitions
101//!
102//! This enables:
103//!
104//! - automated validator lifecycle progression
105//! - continuous election participation for active candidates
106//! - minimal manual interaction beyond intent signaling
107//!
108//! ## Economic Model
109//!
110//! Rewards and penalties are **externally defined and injected**:
111//!
112//! - [`Config::InflationModel`]: derives total session payout
113//! - [`Config::RewardModel`]: distributes rewards from points
114//! - [`Config::PenaltyModel`]: transforms and normalizes penalties
115//!
116//! Final application is delegated via [`Config::RoleAdapter`],
117//! ensuring consistent integration with role and funding systems.
118//!
119//! ## Design Intent
120//!
121//! This pallet is a **composition layer**, not a monolithic system:
122//!
123//! - structure is defined via traits
124//! - behavior is injected via plugins
125//! - coordination is driven by sessions and routines
126//!
127//! enabling a modular, replaceable, and evolvable validator system
128//! while preserving strong type safety and deterministic execution.
129//!
130//! ## Development Feature Gate
131//!
132//! This pallet includes a `dev` feature gate for development and testing.
133//!
134//! Core functionality is exposed via public APIs for RPC and UI usage.
135//! The `dev` feature provides thin wrapper extrinsics and extended
136//! event emissions for direct inspection.
137//!
138//! This feature must be disabled in production runtimes due to
139//! additional debugging overhead.
140
141#![cfg_attr(not(feature = "std"), no_std)]
142
143// ===============================================================================
144// `````````````````````````````````` MODULES ````````````````````````````````````
145// ===============================================================================
146
147#[cfg(feature = "runtime-benchmarks")]
148mod benchmarking;
149#[cfg(test)]
150pub(crate) mod mock;
151mod mocks {
152 #[path = "balances.rs"]
153 #[cfg(test)]
154 pub mod balances;
155
156 #[path = "xp.rs"]
157 #[cfg(test)]
158 pub mod xp;
159}
160#[cfg(test)]
161mod tests;
162mod blockchain;
163mod offence;
164mod roles;
165mod routines;
166mod session;
167pub mod types;
168pub mod crypto;
169pub mod weights;
170
171// Re-Exports for `Config` usage
172
173pub use crate::crypto::AffidavitCryptoEd25519;
174pub use crate::crypto::AffidavitCryptoSr25519;
175
176// ===============================================================================
177// `````````````````````````````` PALLET MODULE ``````````````````````````````````
178// ===============================================================================
179
180pub use pallet::*;
181
182#[frame_support::pallet]
183pub mod pallet {
184
185 // ===============================================================================
186 // ````````````````````````````````` IMPORTS `````````````````````````````````````
187 // ===============================================================================
188
189 // --- Local crate imports ---
190 use super::*;
191 use crate::{
192 crypto::ValidatePayload,
193 routines::*,
194 types::*,
195 weights::*,
196 };
197
198 // --- Core / Std ---
199 use core::fmt::Debug;
200
201 // --- FRAME Suite ---
202 use frame_suite::{
203 Countable, ForkLocalDepot, ForksHandler, blockchain::*, elections::*, plugin_types, roles::*, routines::*
204 };
205
206 // --- FRAME Support ---
207 use frame_support::{
208 dispatch::DispatchResult,
209 pallet_prelude::{StorageValue, *},
210 traits::{
211 fungible::{Inspect, Mutate},
212 EstimateNextSessionRotation,
213 },
214 };
215
216 // --- FRAME System ---
217 use frame_system::{
218 offchain::{AppCrypto, SignedPayload},
219 pallet_prelude::{BlockNumberFor, *},
220 };
221
222 // --- Substrate primitives ---
223 use sp_core::Get;
224 use sp_runtime::{
225 RuntimeAppPublic, SaturatedConversion, WeakBoundedVec, traits::{Convert, IdentifyAccount, Saturating}
226 };
227
228 // ===============================================================================
229 // `````````````````````````````` PALLET MARKER ``````````````````````````````````
230 // ===============================================================================
231
232 /// Primary Marker type for the **Chain Manager pallet**.
233 ///
234 /// This pallet provides implementations for traits from
235 /// [`blockchain`](frame_suite::blockchain), [`roles`](frame_suite::roles),
236 /// [`session`](pallet_session), [`offences`](sp_staking::offence)
237 ///
238 /// Implemented traits:
239 ///
240 /// - [`AuthorPoints`]
241 /// - [`ElectionAffidavits`]
242 /// - [`SessionManager`](pallet_session::SessionManager)
243 /// - [`OnOffenceHandler`](sp_staking::offence::OnOffenceHandler)
244 /// - [`RoleActivity`]
245 /// - [`Convert<AuthorId, Option<SessionId>>`](sp_runtime::traits::Convert)
246 #[pallet::pallet]
247 pub struct Pallet<T>(PhantomData<T>);
248
249 // ===============================================================================
250 // ```````````````````````````` INTERNAL PALLET MARKER ```````````````````````````
251 // ===============================================================================
252
253 /// Internal helper struct for implementing not-exposable
254 /// [`blockchain`](frame_suite::blockchain) trait operations.
255 ///
256 /// `Internals` implements the blockchain low-level helper traits:
257 ///
258 /// - [`RewardAuthors`](frame_suite::blockchain::RewardAuthors)
259 /// - [`ElectAuthors`](frame_suite::blockchain::ElectAuthors)
260 /// - [`PenalizeAuthors`](frame_suite::blockchain::PenalizeAuthors)
261 pub(crate) struct Internals<T: Config>(PhantomData<T>);
262
263 // ===============================================================================
264 // `````````````````````````````` CONFIG TRAIT ```````````````````````````````````
265 // ===============================================================================
266
267 /// Configuration trait for the Chain Manager pallet.
268 ///
269 /// This trait defines the types, constants, and dependencies
270 /// that the runtime must provide for this pallet to function.
271 ///
272 /// It extends several other FRAME pallets' `Config` traits, ensuring tight
273 /// integration with Substrate's session, authorship, time-management and
274 /// offence-handling subsystems.
275 ///
276 /// ### Dependencies
277 ///
278 /// Dependencies other than [`frame_system::Config`]:
279 /// - [`pallet_authorship::Config`]: Provides access to the current block author
280 /// and authorship tracking, used to assign and record block production points.
281 /// - [`pallet_offences::Config`]: Enables detection and handling of offences for
282 /// misbehaving authors, required for penalty enforcement.
283 /// - [`pallet_session::Config`]: Manages session rotation and validator/author sets;
284 /// supports session-aware election and reward cycles.
285 /// - [`pallet_session::historical::Config`]: Provides access to historical session
286 /// data for offence handling.
287 /// - [`pallet_timestamp::Config`] : Provides access to a monotonic deterministic
288 /// onchain unix timestamp.
289 /// - [`frame_system::offchain::CreateSignedTransaction`]: Enables offchain workers
290 /// to sign and submit transactions. Required for unsigned extrinsics that rely
291 /// on signed payload verification ([`ValidateUnsigned`]).
292 #[pallet::config]
293 pub trait Config: frame_system::Config
294 + pallet_authorship::Config
295 + pallet_offences::Config
296 + pallet_session::Config
297 + pallet_session::historical::Config
298 + pallet_timestamp::Config
299 + frame_system::offchain::CreateSignedTransaction<<Self as frame_system::Config>::RuntimeCall>
300 {
301 // --- Runtime Anchors ---
302
303 /// Extrinsic calls aggregation type for the runtime.
304 type RuntimeCall: From<Call<Self>> + Into<<Self as frame_system::Config>::RuntimeCall>;
305
306 /// Events aggregation type for the runtime.
307 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
308
309 // --- Pallet Adapters ---
310
311 /// Adapter type for **author role, and compensation management**.
312 ///
313 /// This associated type integrates multiple traits that together define how
314 /// authors are **managed**, **rewarded**, and **funded**.
315 ///
316 /// It acts as a unified interface between this pallet and the underlying
317 /// role-management, and reward systems.
318 ///
319 /// ## Required Traits
320 /// - [`RoleManager`]: Core author management - handles registration and membership.
321 /// - [`CompensateRoles`]: Defines logic for author **rewards and penalties**.
322 /// - [`FundRoles`]: Enables **external funding or backing** mechanisms for authors.
323 type RoleAdapter: RoleManager<AuthorOf<Self>>
324 // Since `OnOffenceHandler` uses `Perbill` directly hence
325 + CompensateRoles<AuthorOf<Self>, Ratio = PenaltyRatio>
326 + FundRoles<AuthorOf<Self>>;
327
328 /// Adapter type for role **author election management system**.
329 ///
330 /// This associated type integrates multiple traits that together define how
331 /// authors are **elected**.
332 ///
333 /// ## Required Traits
334 /// - [`ElectionManager`]: Conducts author **elections** on behalf of this pallet.
335 /// - [`InspectWeight`]: Provides APIs to **query election weights** for authors.
336 type ElectionAdapter: ElectionManager<AuthorOf<Self>>
337 + InspectWeight<AuthorOf<Self>, ElectionVia<Self>>;
338
339 /// The **asset type** used for distributing rewards and penalties.
340 ///
341 /// This represents a fungible unit (e.g., a token balance) over which
342 /// the pallet performs **reward**, **penalty**, and **funding** operations.
343 ///
344 /// ## Requirements
345 /// - Must implement [`Inspect`], enabling the pallet to query and verify
346 /// account balances or holdings of authors.
347 ///
348 /// ## Example
349 /// ```ignore
350 /// type Asset = pallet_assets::Pallet<T>;
351 /// ```
352 type Asset: Inspect<
353 AuthorOf<Self>,
354 // Ensures that the pallet's `Asset` type aligns with the assets
355 // used by role-manager system.
356 //
357 // Guarantees that rewards, penalties, and funding operations
358 // use a **consistent asset type**.
359 Balance = AssetOf<Self>,
360 > + Mutate<AuthorOf<Self>>;
361
362 /// Adapter for managing and querying **author points**.
363 ///
364 /// Provides the implementation for tracking points (e.g., block production
365 /// or performance metrics) associated with each author.
366 ///
367 /// This can be set to [`Pallet<Self>`] if using the chain-manager pallet's
368 /// internal implementation, or provided externally via another pallet or adapter.
369 type PointsAdapter: AuthorPoints<AuthorOf<Self>, Self::Points>;
370
371 // --- Pallets-exposed Additional Types ---
372
373 /// Provides the logic to **estimate the length of the next session**.
374 ///
375 /// Used to compute
376 /// - affidavit submission windows,
377 /// - election start blocks, and
378 /// - session timing.
379 type NextSessionRotation: EstimateNextSessionRotation<BlockNumberFor<Self>>;
380
381 /// Custom application crypto for **affidavit submission and rotation**.
382 ///
383 /// Defines the cryptographic scheme used by offchain workers when
384 /// submitting and rotating affidavits. This associated type creates
385 /// a dedicated `KeyId` namespace for affidavit-related operations.
386 ///
387 /// ## Supported Cryptography
388 /// - [`AffidavitCryptoEd25519`]
389 /// - [`AffidavitCryptoSr25519`]
390 ///
391 /// Either scheme may be used depending on the desired security and
392 /// performance characteristics.
393 type AffidavitCrypto: frame_system::offchain::AppCrypto<Self::Public, Self::Signature>;
394
395 // --- Scalars ---
396
397 /// Type representing **good-behaviour points** accumulated by an author.
398 ///
399 /// This is an abstract point type used to measure good behaviour
400 /// (e.g. block production) within a session.
401 ///
402 /// ## Design Notes
403 /// - Points are **session-scoped** and non-transferable.
404 /// - This is **not** an economic asset.
405 /// - Points are interpreted later by reward and penalty logic
406 /// and may be transformed into actual asset movements.
407 type Points: Countable;
408
409 // --- Plugins ---
410
411 // Plugin for computing **inflation-adjusted total payout**.
412 plugin_types!(
413 input: AssetOf<Self>,
414 output: AssetOf<Self>,
415
416 /// Inflation adjustment **plugin model**.
417 ///
418 /// This model defines the logic used to transform the raw total asset
419 /// (`AssetOf`) into an inflation-adjusted payout before distribution
420 /// to authors.
421 ///
422 /// Conceptually similar to `PayoutModel` in `RewardAuthors`, but focused
423 /// specifically on inflation-based adjustments in a more layman-friendly
424 /// abstraction.
425 ///
426 /// ## Input
427 /// - [`AssetOf`]: The raw total asset for the current cycle.
428 ///
429 /// ## Output
430 /// - [`AssetOf`]: The total asset after applying inflation rules.
431 ///
432 /// Designed to be runtime-configurable via [`Self::InflationContext`].
433 ///
434 /// Designed to be selectable using template plugin models in
435 /// [`frame_plugins::rewards::payout`] or custom model defining
436 /// macros via [`frame_suite::plugins`].
437 model: InflationModel,
438
439 /// Runtime **context** for the inflation adjustment plugin model.
440 ///
441 /// Provides configurable parameters that influence how the
442 /// [`Self::InflationModel`] behaves at runtime.
443 ///
444 /// ## Examples
445 /// - Inflation rates
446 /// - Upper or lower payout caps
447 /// - Scaling coefficients or dampening factors
448 ///
449 /// This allows the inflation logic to be tuned without changing the model
450 /// implementation itself.
451 context: InflationContext,
452 );
453
454 // Plugin for computing **per-author rewards** from total payout and points.
455 plugin_types!(
456 input: (AssetOf<Self>, PayoutFor<Self>),
457 output: PayeeList<Self>,
458
459 /// Per-author **reward distribution plugin model**.
460 ///
461 /// Responsible for mapping each author's contribution to a concrete payout.
462 /// Conceptually performs:
463 ///
464 /// `(Author, Points)` -> `(Author, AssetOf)`
465 ///
466 /// ## Input
467 /// - ([`AssetOf`], [`PayoutFor`]):
468 /// The total payout for the cycle and the per-author points allocation.
469 ///
470 /// ## Output
471 /// - [`PayeeList`]:
472 /// The finalized list of authors and their respective rewards.
473 ///
474 /// ## Notes
475 /// - Maps each author's points to their final reward share.
476 /// - Designed to be runtime-configurable via [`Self::RewardContext`].
477 ///
478 /// Designed to be selectable using template plugin models in
479 /// [`frame_plugins::rewards::payee`] or custom model defining
480 /// macros via [`frame_suite::plugins`].
481 model: RewardModel,
482
483 /// Runtime **context** for configuring reward plugin computation.
484 ///
485 /// Supplies parameters that influence how rewards are calculated and
486 /// distributed by the [`Self::RewardModel`].
487 ///
488 /// ## Examples
489 /// - Multipliers or weights
490 /// - Per-author or global caps
491 /// - Curves, thresholds, or smoothing factors
492 ///
493 /// This separation allows payout logic to evolve independently from
494 /// runtime tuning and governance decisions.
495 context: RewardContext,
496 );
497
498 // Plugin for **transforming penalties** of authors.
499 plugin_types!(
500 input: PenaltyFor<Self>,
501 output: PenaltyFor<Self>,
502
503 /// **Penalty transformation plugin model**.
504 ///
505 /// Defines how author penalties are adjusted before being applied.
506 /// Conceptually performs:
507 ///
508 /// `(Author, Penalty)` -> `(Author, Penalty)`
509 ///
510 /// ## Input
511 /// - [`PenaltyFor`]:
512 /// The raw per-author penalties for the current cycle.
513 ///
514 /// ## Output
515 /// - [`PenaltyFor`]:
516 /// The penalties after applying transformation rules.
517 ///
518 /// ## Notes
519 /// - Supports caps, scaling, or other penalty transformation logic.
520 /// - Runtime behavior is influenced by [`Self::PenaltyContext`].
521 ///
522 /// Designed to be selectable using template plugin models in
523 /// [`frame_plugins::penalty`] or custom model defining
524 /// macros via [`frame_suite::plugins`].
525 model: PenaltyModel,
526
527 /// Runtime **context** for penalty plugin transformation.
528 ///
529 /// Supplies parameters that configure how the [`Self::PenaltyModel`] operates
530 /// at runtime.
531 ///
532 /// ## Examples
533 /// - Multipliers or dampening factors
534 /// - Upper or lower penalty caps
535 /// - Thresholds or step functions
536 ///
537 /// This allows penalty policy changes without modifying the model
538 /// implementation.
539 context: PenaltyContext,
540 );
541
542 // --- Weights ---
543
544 /// Weight information for extrinsics in this pallet.
545 type WeightInfo: WeightInfo;
546
547 // --- Constants ---
548
549 /// **Flag for reward inflation model**.
550 ///
551 /// Determines how the total reward pool is computed:
552 /// - `true`: total asset supply is used to calculate inflation/rewards.
553 /// - `false`: total locked stake of authors is used instead.
554 ///
555 /// This allows flexible reward strategies depending on economic design.
556 #[pallet::constant]
557 type InflateViaSupply: Get<bool> + Clone + Debug;
558
559 /// Maximum number of **weights an author can submit in an affidavit**.
560 ///
561 /// Weight represents backers or collateral information essential for
562 /// conducting elections.
563 ///
564 /// Limits storage and ensures predictable handling of submitted affidavits.
565 /// If an author submits more than this number, the weights may be truncated.
566 ///
567 /// Each weight must remain sortable (`Ord`) for ensuring priority ordering during
568 /// truncation to this upper bound.
569 #[pallet::constant]
570 type MaxAffidavitWeights: Get<u32> + Clone + Debug;
571
572 const MAX_FORKS: u32;
573
574 const MAX_FORK_RECOVERY_TRAVERSAL: u32;
575
576 /// Controls emission of [`Event`] via `deposit_event`.
577 ///
578 /// Recommended:
579 /// - `false` for production runtimes (to reduce overhead)
580 /// - `true` for development and mock runtimes (for testing and
581 /// observability)
582 #[pallet::constant]
583 type EmitEvents: Get<bool> + Clone + Debug;
584 }
585
586 // ===============================================================================
587 // ``````````````````````````````` GENESIS CONFIG ````````````````````````````````
588 // ===============================================================================
589
590 /// Genesis configuration for the **Chain Manager pallet**.
591 ///
592 /// Provides the **initial runtime parameters** governing session-driven
593 /// validator orchestration, including affidavit flow, election timing,
594 /// transaction prioritization, and finality safeguards at chain genesis.
595 ///
596 /// These values define the **baseline execution schedule and coordination rules**
597 /// for validator lifecycle before any sessions are processed.
598 #[pallet::genesis_config]
599 pub struct GenesisConfig<T: Config> {
600 /// Whether affidavit submission is enabled.
601 /// - `true`: authors may submit affidavit data during the configured window.
602 /// - `false`: affidavit submission is entirely disabled.
603 pub allow_affidavits: bool,
604
605 /// Relative point within a session at which the affidavit phase begins.
606 ///
607 /// Expressed as a fraction of the session (e.g., `0.2` = 20% into the session).
608 /// Defines when authors can start submitting affidavit.
609 /// Submissions made before this point are not permitted.
610 pub afdvt_begins_at: Duration,
611
612 /// Relative point within a session at which the affidavit phase ends.
613 ///
614 /// Must be greater than `afdvt_begins_at` and within session bounds.
615 /// After this point, no new affidavit submissions are accepted.
616 pub afdvt_ends_at: Duration,
617
618 /// Relative point within a session at which election execution begins.
619 ///
620 /// Typically aligned with or after the affidavit phase to ensure
621 /// all candidate data is available for selection.
622 pub election_begins_at: Duration,
623
624 /// Number of points awarded to the author responsible for executing elections.
625 ///
626 /// Acts as an incentive for offchain workers or authors coordinating
627 /// election execution in a timely manner.
628 pub election_runner_points: T::Points,
629
630 /// Transaction priority assigned to validation-related unsigned extrinsics.
631 ///
632 /// Higher priority ensures inclusion in blocks under contention.
633 /// Should typically be the highest among lifecycle operations.
634 pub validate_tx_priority: TransactionPriority,
635
636 /// Transaction priority assigned to affidavit submission extrinsics.
637 ///
638 /// Balanced to allow fair participation without starving higher-priority
639 /// operations such as validation.
640 pub affidavit_tx_priority: TransactionPriority,
641
642 /// Transaction priority assigned to election execution extrinsics.
643 ///
644 /// Ensures elections are processed in time, but typically lower than
645 /// validation and affidavit priorities.
646 pub election_tx_priority: TransactionPriority,
647
648 /// Time-based delay (in milliseconds) before an operation is considered final.
649 ///
650 /// Helps mitigate premature execution in unstable network conditions
651 /// or during short-lived forks.
652 pub finality_after: u64,
653
654 /// Block-based confirmation threshold for finality.
655 ///
656 /// Represents the number of distinct blocks that must pass before
657 /// an operation is finalized, providing deterministic safety
658 /// against reorgs.
659 pub finality_ticks: BlockNumberFor<T>,
660 }
661
662 impl<T: Config> Default for GenesisConfig<T> {
663 fn default() -> Self {
664 Self {
665 allow_affidavits: false,
666 afdvt_begins_at: Duration::from_rational(2u32, 10u32), // 20%
667 afdvt_ends_at: Duration::from_rational(8u32, 10u32), // 80%
668 election_begins_at: Duration::from_rational(5u32, 10u32), // 50%
669 election_runner_points: 10u8.into(),
670 validate_tx_priority: 1_000_000,
671 affidavit_tx_priority: 850_000,
672 election_tx_priority: 700_000,
673 // 1 minute delay
674 finality_after: 60_000,
675 // 5 distinct blocks
676 finality_ticks: 5u32.into(),
677 }
678 }
679 }
680
681 #[pallet::genesis_build]
682 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
683 fn build(&self) {
684 // --- Affidavit window ---
685 assert!(
686 self.afdvt_begins_at < self.afdvt_ends_at,
687 "`AffidavitBeginsAt` must be less than `AffidavitEndsAt`"
688 );
689 assert!(
690 self.afdvt_ends_at <= Duration::one(),
691 "`AffidavitEndsAt` cannot exceed 100%"
692 );
693 assert!(
694 self.election_begins_at <= Duration::one(),
695 "`ElectionBeginsAt` must be within 0-100% of affidavit window"
696 );
697
698 // --- Tx priorities ---
699 assert!(self.validate_tx_priority > 0);
700 assert!(self.election_tx_priority > 0);
701 assert!(self.affidavit_tx_priority > 0);
702
703 // --- Finality ---
704 assert!(self.finality_after > 0);
705 assert!(self.finality_ticks > Zero::zero());
706
707 AllowAffidavits::<T>::put(self.allow_affidavits);
708 AffidavitBeginsAt::<T>::put(self.afdvt_begins_at);
709 AffidavitEndsAt::<T>::put(self.afdvt_ends_at);
710 ElectionBeginsAt::<T>::put(self.election_begins_at);
711 ElectionRunnerPoints::<T>::put(self.election_runner_points);
712 ValidateTxPriority::<T>::put(self.validate_tx_priority);
713 ElectionTxPriority::<T>::put(self.election_tx_priority);
714 AffidavitTxPriority::<T>::put(self.affidavit_tx_priority);
715 let moment: Moment<T> = self.finality_after.saturated_into();
716 FinalityAfter::<T>::put(moment);
717 FinalityTicks::<T>::put(self.finality_ticks);
718 }
719 }
720
721 // ===============================================================================
722 // ```````````````````````````````` STORAGE TYPES ````````````````````````````````
723 // ===============================================================================
724
725 /// The **current running session index**.
726 ///
727 /// Used to track session-aware operations such as:
728 /// - Author points accumulation ([`AuthorPoints`])
729 /// - Reward and penalty distribution
730 /// - Affidavit submission and election preparation
731 #[pallet::storage]
732 pub type CurrentSession<T: Config> = StorageValue<_, SessionIndex, ValueQuery>;
733
734 /// Storage mapping of **authors' submitted affidavits per session**.
735 ///
736 /// Keyed by:
737 /// 1. [`SessionIndex`] - the session for which the affidavit applies for election.
738 /// 2. [`AuthorOf`] - the author submitting the affidavit
739 ///
740 /// Value is a tuple of:
741 /// - [`BlockNumberFor`]: the block when the affidavit was submitted and,
742 /// - A `WeakBoundedVec` of [`ElectionWeight`]: the author's declared weights,
743 /// bounded to prevent excessive storage
744 ///
745 /// This storage is used for:
746 /// - Preparing election candidates for the next session
747 #[pallet::storage]
748 pub type AuthorAffidavits<T: Config> = StorageNMap<
749 _,
750 (
751 NMapKey<Blake2_128Concat, SessionIndex>,
752 NMapKey<Blake2_128Concat, AuthorOf<T>>,
753 ),
754 (
755 BlockNumberFor<T>,
756 WeakBoundedVec<ElectionWeight<T>, T::MaxAffidavitWeights>,
757 ),
758 OptionQuery,
759 >;
760
761 /// **Author points accumulated per session**.
762 ///
763 /// This storage tracks **block-level points** awarded to authors for good behavior
764 /// during block production. Each increment represents a positive contribution,
765 /// such as successfully producing a block or validating transactions correctly.
766 ///
767 /// Keyed by:
768 /// 1. [`SessionIndex`]: the session in which points are earned.
769 /// 2. [`AuthorOf`]: the author receiving the points.
770 ///
771 /// Value:
772 /// - [`Config::Points`]: the total points accumulated by the author in that session.
773 ///
774 /// Notes:
775 /// - This serves as the **high-level points store** for reward calculation.
776 /// - Each block-level good behavior contributes **one point** to this tally.
777 /// - Points are **session-scoped and ephemeral**; typically cleared after reward distribution.
778 /// - Can be used as a reference for evaluating other contributions or behaviors of authors.
779 #[pallet::storage]
780 pub type BlockPointsStore<T: Config> = StorageNMap<
781 _,
782 (
783 NMapKey<Blake2_128Concat, SessionIndex>,
784 NMapKey<Blake2_128Concat, AuthorOf<T>>,
785 ),
786 T::Points,
787 OptionQuery,
788 >;
789
790 /// **Start block of the current session**.
791 ///
792 /// Tracks the block number when the current session began.
793 /// Used for:
794 /// - Computing session-relative timing for elections and affidavits.
795 #[pallet::storage]
796 pub type SessionStartAt<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
797
798 /// **Flag to enable or disable affidavit submissions**.
799 ///
800 /// Controls whether authors can submit self-reported election weights.
801 /// When `true`, affidavit-related functions are active; when `false`, submissions
802 /// are blocked. Initially, this is expected to be `false` and gradually enabled once
803 /// the required authors are ready.
804 #[pallet::storage]
805 pub type AllowAffidavits<T: Config> = StorageValue<_, bool, ValueQuery>;
806
807 // /// **Flag for reward inflation model**.
808 // ///
809 // /// Determines how the total reward pool is computed:
810 // /// - `true`: total asset supply is used to calculate inflation/rewards.
811 // /// - `false`: total locked stake of authors is used instead.
812 // ///
813 // /// This allows flexible reward strategies depending on economic design.
814 // #[pallet::storage]
815 // pub type InflateViaSupply<T: Config> = StorageValue<_, bool, ValueQuery>;
816
817 /// **Start of affidavit submission window** as a percentage ([`PerThing`]) of the
818 /// current session. Authors submit affidavits for the **next upcoming session**
819 /// starting from this point.
820 ///
821 /// ## Examples
822 /// - If the current session is 1000 blocks long and `AffidavitBeginsAt = 20%`,
823 /// affidavit submissions can start at block `200` of the current session.
824 /// - Authors cannot submit affidavits before this block, even if they are ready.
825 #[pallet::storage]
826 pub type AffidavitBeginsAt<T: Config> = StorageValue<_, Duration, ValueQuery>;
827
828 /// **End of affidavit submission window** as a percentage ([`PerThing`]) of the
829 /// current session.
830 ///
831 /// Authors must submit affidavits **before this block**, leaving room for the
832 /// election process for the next upcoming session.
833 ///
834 /// ## Examples
835 /// - If the current session is 100 blocks long and `AffidavitEndsAt = 80%`,
836 /// affidavit submissions must end by block `800`.
837 /// - The period from `AffidavitBeginsAt` to `AffidavitEndsAt` defines the **affidavit
838 /// submission window**.
839 /// - Elections for the next session should be conducted before this period ends.
840 #[pallet::storage]
841 pub type AffidavitEndsAt<T: Config> = StorageValue<_, Duration, ValueQuery>;
842
843 /// **Start of the election window** as a percentage ([`PerThing`]) of the **affidavit
844 /// submission period** in the current session.
845 ///
846 /// This does **not** refer to a percentage of the full session, but rather the **relative
847 /// position within the affidavit submission window**. Authors submit affidavits first, then
848 /// after this point, the election process can start.
849 ///
850 /// ## Examples
851 /// - Suppose the affidavit window spans blocks 200-800 (`AffidavitBeginsAt = 20%`,
852 /// `AffidavitEndsAt = 80%` in a 100-block session).
853 /// - If `ElectionBeginsAt = 50%`, then the election starts halfway through the
854 /// affidavit window: 200 + 50% * (800 - 200) = block 500.
855 /// - Timeline:
856 /// - 200-500: affidavit submission period
857 /// - 500-800: election preparation
858 #[pallet::storage]
859 pub type ElectionBeginsAt<T: Config> = StorageValue<_, Duration, ValueQuery>;
860
861 /// **Block points allocated for the election runner**.
862 ///
863 /// Determines how many [`Config::Points`] are required or suitable for an author
864 /// to act as the election runner in the upcoming session.
865 ///
866 /// This value is used to assess election participation eligibility or priority.
867 #[pallet::storage]
868 pub type ElectionRunnerPoints<T: Config> = StorageValue<_, T::Points, ValueQuery>;
869
870 /// **Pending upgrade of election runner points**.
871 ///
872 /// When an upgrade occurs, this should be set to `None` after being applied.
873 ///
874 /// ## Notes
875 /// - Upgrades should occur **after all author rewards have been distributed**
876 /// and before the next reward cycle starts.
877 /// - Ensures fairness: if upgraded mid-session, authors who have not yet
878 /// submitted affidavits may be disadvantaged in election chances.
879 /// - Any user of [`ElectionRunnerPoints`] should take this storage value into account
880 /// for the upcoming session.
881 #[pallet::storage]
882 pub type ElectionRunnerPointsUpgrade<T: Config> =
883 StorageValue<_, Option<T::Points>, ValueQuery>;
884
885 /// **Offchain affidavit keys registered by an author for a given session**.
886 ///
887 /// Retrieves the author who submitted affidavit keys, tied to a specific session.
888 ///
889 /// - Authenticate offchain submissions (affidavits) for the upcoming session.
890 /// - Ensure that only valid authors can participate in elections or submit weights.
891 /// - Maintain session-specific separation, so keys do not persist beyond their intended session.
892 ///
893 /// ## Storage Structure
894 /// - [`SessionIndex`]: the session for which the keys are valid.
895 /// - [`AffidavitId`]: the actual affidavit public key ID, type chosen by
896 /// the runtime configuration [`Config::AffidavitCrypto`].
897 /// - [`AuthorOf<T>`]: the author who owns the key-pairs for signing.
898 #[pallet::storage]
899 pub type AffidavitKeys<T: Config> = StorageNMap<
900 _,
901 (
902 NMapKey<Blake2_128Concat, SessionIndex>,
903 NMapKey<Blake2_128Concat, AffidavitId<T>>,
904 ),
905 AuthorOf<T>,
906 OptionQuery,
907 >;
908
909 /// **Tracks the author who prepared the election for a given session**.
910 ///
911 /// Stores the identity of the election runner and the block number at which
912 /// they executed the election process for the **upcoming session**.
913 ///
914 /// This storage is **tied to the session index** and may be **overwritten**
915 /// if multiple election runs occur within the same session.
916 /// This ensures that the latest election runner and block number are always recorded.
917 ///
918 /// This allows the pallet to:
919 /// - Identify which author acted as the election runner for auditing or reward purposes.
920 /// - Record the exact block when the election was conducted, ensuring deterministic session transitions.
921 /// - Keep track of the most recent election preparation for the session.
922 ///
923 /// ## Storage Structure
924 /// - [`SessionIndex`]: the session for which the election was prepared.
925 /// - ([`AuthorOf`], [`BlockNumberFor`]): tuple of the election runner and the block number of execution.
926 /// - [`OptionQuery`]: returns `None` if no election has been prepared for the session or yet.
927 #[pallet::storage]
928 pub type ElectsPreparedBy<T: Config> = StorageMap<
929 _,
930 Blake2_128Concat,
931 SessionIndex,
932 (AuthorOf<T>, BlockNumberFor<T>),
933 OptionQuery,
934 >;
935
936 /// Transaction priority for the [`Pallet::validate`] extrinsic.
937 ///
938 /// Used because `validate` is an **unsigned extrinsic** that carries a
939 /// signed payload, rather than relying on a conventional signed origin.
940 ///
941 /// This extrinsic **is propagated to other peers** and participates in
942 /// normal transaction pool ordering, so its priority directly affects
943 /// inclusion and ordering behavior.
944 #[pallet::storage]
945 pub type ValidateTxPriority<T: Config> = StorageValue<_, TransactionPriority, ValueQuery>;
946
947 /// Transaction priority for the [`Pallet::elect`] extrinsic.
948 ///
949 /// This extrinsic is **submitted locally by an offchain worker** and
950 /// **not propagated to other peers** via the transaction pool.
951 ///
952 /// The priority therefore only affects **local block construction**
953 /// and ordering relative to other locally submitted transactions.
954 #[pallet::storage]
955 pub type ElectionTxPriority<T: Config> = StorageValue<_, TransactionPriority, ValueQuery>;
956
957 /// Transaction priority for the [`Pallet::declare`] extrinsic.
958 ///
959 /// Controls the relative importance of affidavit submissions in the
960 /// transaction pool.
961 ///
962 /// This extrinsic **is propagated to other peers**, so its priority
963 /// influences network-wide inclusion and ordering behavior.
964 #[pallet::storage]
965 pub type AffidavitTxPriority<T: Config> = StorageValue<_, TransactionPriority, ValueQuery>;
966
967 /// Wall-clock delay (in timestamp units) that must elapse after the
968 /// first observation of a value before it becomes eligible to
969 /// strengthen its confidence signal.
970 ///
971 /// Measured from the timestamp of the initial observation:
972 ///
973 /// `current_timestamp >= first_observation_timestamp + FinalityAfter`
974 ///
975 /// This acts as a stability window to prevent immediate confidence
976 /// escalation for newly observed values.
977 ///
978 /// ### Note
979 /// - Must be strictly greater than zero.
980 /// - Should be large enough to tolerate short-lived forks.
981 #[pallet::storage]
982 pub type FinalityAfter<T: Config> = StorageValue<_, Moment<T>, ValueQuery>;
983
984 /// Number of distinct block observations required *after* the
985 /// [`FinalityAfter`] window has elapsed in order to strengthen
986 /// the confidence signal of a value.
987 ///
988 /// Observations are block-scoped:
989 /// - At most one observation per block is counted.
990 /// - Multiple observations within the same block do not increase the count.
991 ///
992 /// A value may strengthen its confidence only when:
993 /// 1. The [`FinalityAfter`] delay has elapsed, and
994 /// 2. It has been observed in at least `FinalityTicks`
995 /// distinct blocks thereafter.
996 ///
997 /// ### Note
998 /// - Must be strictly greater than zero.
999 /// - Larger values increase fork tolerance but delay confidence.
1000 #[pallet::storage]
1001 pub type FinalityTicks<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
1002
1003 // ===============================================================================
1004 // ```````````````````````````````````` ERROR ````````````````````````````````````
1005 // ===============================================================================
1006
1007 #[pallet::error]
1008 pub enum Error<T> {
1009 /// The requested affidavit-ready author was not found.
1010 ///
1011 /// This may happen if:
1012 /// - The author has not declared readiness for validation via
1013 /// `validate` extrinsic
1014 /// - The author has not declared affidavit keys for the upcoming session
1015 /// - The provided affidavit key is invalid
1016 AffidavitAuthorNotFound,
1017
1018 /// Block production points for the specified author were not found.
1019 BlockPointsNotFound,
1020
1021 /// The author has exhausted the maximum allowed block production points
1022 /// for the current session.
1023 ///
1024 /// Consider scaling the points type or constraining accumulation
1025 /// according to block production frequency.
1026 BlockPointsExhausted,
1027
1028 /// The specified author has not submitted an affidavit
1029 /// for the upcoming session.
1030 AffidavitNotFound,
1031
1032 /// Affidavit submissions are not enabled by configuration.
1033 AffidavitsNotAllowed,
1034
1035 /// The affidavit submission window for the upcoming session
1036 /// has not started yet in the current session.
1037 NotAffidavitPeriod,
1038
1039 /// The affidavit submission window for the upcoming session has ended.
1040 ///
1041 /// The author must wait for the next session's affidavit period.
1042 AffidavitPeriodEnded,
1043
1044 /// The election period for the upcoming session
1045 /// has not started yet in the current session.
1046 NotElectionPeriod,
1047
1048 /// Invalid affidavit configuration.
1049 ///
1050 /// `AffidavitBeginsAt` must be less than or equal to `AffidavitEndsAt`.
1051 InvalidAffidavitPeriod,
1052
1053 /// The election period for the upcoming session has ended.
1054 ///
1055 /// The author must wait for the next session's election period.
1056 ElectionPeriodEnded,
1057
1058 /// The author has not registered ownership of the given affidavit key.
1059 ///
1060 /// The key itself may be valid, but it does not belong to the author.
1061 AuthorNotAffidavitOwner,
1062
1063 /// Failed to query the session ID for the author.
1064 ///
1065 /// This usually indicates that the author does not hold
1066 /// an active author role.
1067 SessionIdQueryFailed,
1068
1069 /// The author cannot chill immediately because they are
1070 /// an elected validator in the current session.
1071 ValidatorCannotChill,
1072
1073 /// The author cannot chill immediately because they have submitted
1074 /// an affidavit and are a candidate for election.
1075 CandidateCannotChill,
1076
1077 /// The author cannot chill immediately because they are elected
1078 /// for the upcoming session.
1079 ElectedCannotChill,
1080
1081 /// An active affidavit key already exists in the node's local keystore.
1082 ///
1083 /// No new key generation is required. If the node has a valid author role,
1084 /// it may already be ready for `validate` extrinsic.
1085 ///
1086 /// Or if the author is ready to declare affidavit he is eligible to do so.
1087 AffidavitKeyExists,
1088
1089 /// A next-session affidavit key already exists in the node's local keystore.
1090 ///
1091 /// An affidavit declaration is ready to be posted and the key
1092 /// is ready for rotation.
1093 NextAffidavitKeyExists,
1094
1095 /// Failed to sign the affidavit submission extrinsic payload
1096 /// using the active affidavit key.
1097 CannotSignAffidavitTxPayload,
1098
1099 /// Failed to construct and sign validate payload
1100 /// using the active affidavit key.
1101 CannotSignValidateTxPayload,
1102
1103 /// The node is expected to hold an active affidavit key, but none was found.
1104 ///
1105 /// This indicates an inconsistency between earlier logic and current storage.
1106 /// The OCW will retry, but persistent failure suggests storage corruption.
1107 ExpectedToHoldActiveAffidavitKey,
1108
1109 /// The node is expected to not hold a active affidavit key in offchain storage,
1110 /// but it is available.
1111 ///
1112 /// This indicates an inconsistency between earlier logic and current storage.
1113 /// The OCW will retry, but persistent failure suggests storage corruption.
1114 ExpectedToNotHoldActiveAffidavitKey,
1115
1116 /// The node is expected to hold the next affidavit key during
1117 /// the key-rotation lifecycle, but it is missing.
1118 ///
1119 /// This indicates an inconsistency between earlier logic and current storage.
1120 /// The OCW will retry, but persistent failure suggests storage corruption.
1121 ExpectedToHoldNextAffidavitKey,
1122
1123 /// The node is expected to hold a finalized next affidavit key
1124 /// in offchain storage, but it is missing.
1125 ///
1126 /// This indicates an inconsistency between earlier logic and current storage.
1127 /// The OCW will retry, but persistent failure suggests storage corruption.
1128 ExpectedToHoldFinalizedNextAffidavitKey,
1129
1130 /// An offchain storage operation failed.
1131 ///
1132 /// The OCW halts further decisions for safety.
1133 /// Thus, re-runs in next block execution.
1134 OCWStorageDecisionHalt,
1135
1136 /// The offchain worker failed to submit the affidavit extrinsic
1137 /// for unknown reasons.
1138 CannotSubmitAffidavitTx,
1139
1140 /// Extrinsic submission for declaring an affidavit failed.
1141 FailedToDeclareAffidavit,
1142
1143 /// The election period for the upcoming session
1144 /// has not started yet.
1145 YetToElectAuthors,
1146
1147 /// Failed to sign the election extrinsic payload using
1148 /// the very recently rotated affidavit key.
1149 ///
1150 /// Subsequent OCW executions at next block shall attempt again.
1151 CannotSignElectionTxPayload,
1152
1153 /// The offchain worker failed to submit the election extrinsic
1154 /// for unknown reasons.
1155 ///
1156 /// Subsequent OCW executions at next block shall attempt again.
1157 CannotSubmitElectionTx,
1158
1159 /// Extrinsic submission for electing authors failed.
1160 ///
1161 /// Subsequent OCW executions at next block shall attempt again.
1162 ExtrinsicFailedToElectAuthors,
1163
1164 /// A new affidavit key was generated but failed to persist locally.
1165 ///
1166 /// The OCW will attempt another affidavit submission with a new key.
1167 ///
1168 /// Persistent failure indicates storage corruption.
1169 SetNewAffidavitKeyFailed,
1170
1171 /// A new affidavit key for rotation and promotion as active
1172 /// affidavit key was generated but failed to persist locally.
1173 ///
1174 /// The OCW will attempt another affidavit key-rotation with a new key.
1175 ///
1176 /// Persistent failure indicates storage corruption.
1177 SetNextAffidavitKeyFailed,
1178
1179 /// The active affidavit key is expected in the local keystore
1180 /// but could not be found.
1181 ///
1182 /// Recovery requires regenerating the key under the configured
1183 /// `KeyTypeId` or chilling and restarting validation.
1184 ExpectedActiveAffidavitKeyPairNotFound,
1185
1186 /// The rotated next affidavit key is expected in the local keystore
1187 /// but could not be found.
1188 ///
1189 /// Recovery requires regenerating the key under the configured
1190 /// `KeyTypeId` or chilling and restarting validation.
1191 ExpectedNextAffidavitKeyPairNotFound,
1192
1193 /// Validation has been stopped due to affidavit key rotation failure,
1194 /// storage corruption, or failed submissions.
1195 ///
1196 /// Manual re-validation via `validate` extrinsic is required.
1197 ValidationStopped,
1198
1199 /// The author is actively validating in the current session.
1200 ///
1201 /// The `chill` extrinsic is blocked until validation completes
1202 /// or the session ends.
1203 ActivelyValidating,
1204
1205 /// The author has submitted an affidavit and is actively
1206 /// participating in the election.
1207 ///
1208 /// The author may `chill` if allowed or wait until
1209 /// the election concludes to check assigned duty.
1210 ActivelyContestingElection,
1211
1212 /// The author has been elected for the upcoming session
1213 /// and is preparing for validation duties.
1214 ///
1215 /// The `chill` extrinsic is blocked until duties complete.
1216 ActivelyWarmingForValidation,
1217
1218 /// The author is detected as active, but the specific duty
1219 /// cannot be determined due to inconsistent state.
1220 ///
1221 /// The `chill` extrinsic may be attempted to resolve the state.
1222 CannotDetermineAuthorActiveDuty,
1223
1224 /// Offchain storage Active Affidavit Key is not yet finalized.
1225 ///
1226 /// The key exists optimistically in fork-aware storage, but the block
1227 /// containing the finalized value may still be subject to re-org.
1228 /// Until finalized, speculative forks may observe different values.
1229 ///
1230 /// Until it is finalized subsequent OCWs may result in this state.
1231 ActiveAfdtKeyNotYetFinalized,
1232
1233 /// Failed to decode the finalized Active Affidavit Key public key value
1234 /// from persistent offchain storage.
1235 ///
1236 /// Persistent failure indicates either storage corruption or a type
1237 /// mismatch. Manual intervention may be required. Clearing both
1238 /// fork-aware and persistent offchain storage allows OCWs to
1239 /// reinitialize the key lifecycle.
1240 ActiveAfdtKeyFinalizedValueDecodeFail,
1241
1242 /// Failed to decode the speculative hash of the Active Affidavit Key
1243 /// public key from fork-aware offchain storage.
1244 ///
1245 /// This hash is used to reference the actual value stored in
1246 /// persistent offchain storage. Persistent failure indicates
1247 /// corruption or decoding inconsistencies.
1248 /// Clearing both fork-aware and persistent storage may resolve it.
1249 ActiveAfdtKeySpeculativeHashDecodeFail,
1250
1251 /// Concurrent mutation detected while accessing the finalized
1252 /// Active Affidavit Key public key value.
1253 ///
1254 /// The operation will be retried by the OCW. Persistent failure
1255 /// indicates a potential deadlock or storage corruption.
1256 /// Clearing both fork-aware and persistent offchain storage
1257 /// may be required.
1258 ActiveAfdtKeyFinalizedValueConcurrentMutation,
1259
1260 /// Concurrent mutation detected while accessing the speculative
1261 /// hash of the Active Affidavit Key public key.
1262 ///
1263 /// The hash resides in fork-aware storage while the actual value
1264 /// exists in persistent storage. The operation will be retried
1265 /// by the OCW. Persistent failure indicates storage corruption
1266 /// or a deadlock condition.
1267 ActiveAfdtKeySpeculativeHashConcurrentMutation,
1268
1269 /// A finalized Active Affidavit Key public key value exists in
1270 /// persistent offchain storage without a corresponding
1271 /// fork-aware hash.
1272 ///
1273 /// This results in a hanging value with no canonical reference.
1274 /// The persistent value will be cleared automatically. Any
1275 /// remaining fork-aware entries may also become hanging and
1276 /// will be cleaned on access.
1277 ActiveAfdtKeyFinalizedHangingValue,
1278
1279 /// A speculative fork-aware hash for the Active Affidavit Key exists
1280 /// without a corresponding persistent public key value.
1281 ///
1282 /// Since the actual value cannot be recovered, the hanging
1283 /// speculative hash will be cleared automatically.
1284 ActiveAfdtKeySpeculativeHangingHash,
1285
1286 /// Offchain storage Next Affidavit Key is not yet finalized.
1287 ///
1288 /// The key exists optimistically in fork-aware storage, but the block
1289 /// containing the finalized value may still be subject to re-org.
1290 /// Until finalized, speculative forks may observe different values.
1291 ///
1292 /// Until it is finalized subsequent OCWs may result in this state.
1293 NextAfdtKeyNotYetFinalized,
1294
1295 /// Failed to decode the finalized Next Affidavit Key public key value
1296 /// from persistent offchain storage.
1297 ///
1298 /// Persistent failure indicates either storage corruption or a type
1299 /// mismatch. Manual intervention may be required. Clearing both
1300 /// fork-aware and persistent offchain storage allows OCWs to
1301 /// reinitialize the key lifecycle.
1302 NextAfdtKeyFinalizedValueDecodeFail,
1303
1304 /// Failed to decode the speculative hash of the Next Affidavit Key
1305 /// public key from fork-aware offchain storage.
1306 ///
1307 /// This hash is used to reference the actual value stored in
1308 /// persistent offchain storage. Persistent failure indicates
1309 /// corruption or decoding inconsistencies.
1310 /// Clearing both fork-aware and persistent storage may resolve it.
1311 NextAfdtKeySpeculativeHashDecodeFail,
1312
1313 /// Concurrent mutation detected while accessing the finalized
1314 /// Next Affidavit Key public key value.
1315 ///
1316 /// The operation will be retried by the OCW. Persistent failure
1317 /// indicates a potential deadlock or storage corruption.
1318 /// Clearing both fork-aware and persistent offchain storage
1319 /// may be required.
1320 NextAfdtKeyFinalizedValueConcurrentMutation,
1321
1322 /// Concurrent mutation detected while accessing the speculative
1323 /// hash of the Next Affidavit Key public key.
1324 ///
1325 /// The hash resides in fork-aware storage while the actual value
1326 /// exists in persistent storage. The operation will be retried
1327 /// by the OCW. Persistent failure indicates storage corruption
1328 /// or a deadlock condition.
1329 NextAfdtKeySpeculativeHashConcurrentMutation,
1330
1331 /// A finalized Next Affidavit Key public key value exists in
1332 /// persistent offchain storage without a corresponding
1333 /// fork-aware hash.
1334 ///
1335 /// This results in a hanging value with no canonical reference.
1336 /// The persistent value will be cleared automatically. Any
1337 /// remaining fork-aware entries may also become hanging and
1338 /// will be cleaned on access.
1339 NextAfdtKeyFinalizedHangingValue,
1340
1341 /// A speculative fork-aware hash for the Next Affidavit Key exists
1342 /// without a corresponding persistent public key value.
1343 ///
1344 /// Since the actual value cannot be recovered, the hanging
1345 /// speculative hash will be cleared automatically.
1346 NextAfdtKeySpeculativeHangingHash,
1347
1348 /// The affidavit submission extrinsic has been sent by the OCW
1349 /// and is awaiting on-chain state confirmation and key rotation.
1350 AffidavitTxAwaitingStatus,
1351
1352 /// Not an error.
1353 ///
1354 /// Indicates that a neccessary election attempt was made early,
1355 /// so the OCW will proceed with affidavit submission instead.
1356 ProceedingToAffidavitSubmission,
1357
1358 /// Affidavit submission was declined because the author
1359 /// has already rotated keys for the next session.
1360 ///
1361 /// The author must wait for the next affidavit window.
1362 DeclareDuringNextAffidavitSession,
1363
1364 /// An affidavit is declared and the key is rotated before the
1365 /// current affidavit submission period itself.
1366 DeclaredBeforeAffidavitPeriod,
1367
1368 /// The given affidavit key is not the recently rotated affidavit key,
1369 /// from the recently declared affidavit.
1370 ///
1371 /// This implies the author have declared their affidavit and have rotated
1372 /// a new key for the the election after the upcoming election.
1373 InvalidRotatedAffidavitKey,
1374
1375 /// The requested block author of this very current block is not available.
1376 BlockAuthorNotFound,
1377
1378 /// An election is attempted by a non-block author. Elections can only be
1379 /// processed by block-authors only in their authored blocks only.
1380 TriedElectingByNonBlockAuthor,
1381
1382 /// The active affidavit key is utilized for affidavit-declaration and
1383 /// not for processing election.
1384 ///
1385 /// Election can only be processed by recently rotated affidavit key, which
1386 /// poses for the next affidavit-declaration session also.
1387 ///
1388 /// All these can be valid only if the author started validating, else its
1389 /// a dormant affidavit key
1390 AffidavitKeyForDeclaration,
1391
1392 /// The provided value must be non-zero.
1393 ///
1394 /// Returned when a parameter or input is required to be strictly
1395 /// greater than zero.
1396 ValueCannotBeZero,
1397
1398 /// Internal success signal for the affidavit key initialization routine.
1399 ///
1400 /// Used to indicate successful execution of the initialization flow.
1401 InitAffidavitKeyRoutineSuccess,
1402
1403 /// Internal success signal for the election execution routine.
1404 ///
1405 /// Indicates that the election process completed successfully.
1406 TryElectionRoutineSuccess,
1407
1408 /// Internal success signal for the affidavit declaration routine.
1409 ///
1410 /// Indicates that the affidavit has been successfully declared.
1411 DeclarAffidavitRoutineSuccess,
1412
1413 /// Internal success signal for the affidavit key rotation routine.
1414 ///
1415 /// Indicates that the key rotation process completed successfully.
1416 RotateAffidavitKeyRoutineSuccess,
1417
1418 /// The author is already in a chilled (inactive) state.
1419 ///
1420 /// Returned when attempting to chill an author that is already inactive.
1421 AuthorAlreadyChilling,
1422
1423 /// Authors are successfully elected, but cannot be revealed for
1424 /// some reasons unknown.
1425 ElectedButCannotReveal,
1426
1427 /// Author declared an affidavit, but it could not be retrieved
1428 /// for reasons unknown.
1429 DeclaredAffidavitNotFound,
1430
1431 /// The caller is not the current block author.
1432 ///
1433 /// Returned when an operation requires block authorship,
1434 /// but the caller does not match the current block author.
1435 NotABlockAuthor,
1436
1437 /// No public key matching the given affidavit identifier was found
1438 /// in the node-local keystore.
1439 ///
1440 /// The key may have been lost, never generated on this node, or
1441 /// the identifier does not correspond to any locally held key pair.
1442 AfdtPublicKeyNotFound,
1443
1444 /// No affidavit key is registered for the author in any relevant
1445 /// future session scope (next session or next affidavit session).
1446 ///
1447 /// The author has not called [`Pallet::validate`] or has already chilled.
1448 AffidavitKeyPairNotFound,
1449
1450 /// The election result could not be revealed.
1451 ///
1452 /// No election has been executed for the current session, or the
1453 /// underlying election manager returned no result.
1454 UnableToRevealElected,
1455
1456 /// The fork graph has reached the maximum number of concurrent forks.
1457 ///
1458 /// No new sibling branch can be created until an existing fork is
1459 /// resolved or pruned. This indicates an unusually high degree of
1460 /// chain instability relative to the configured `MAX_FORKS` constant.
1461 MaxOCWForksAttained,
1462
1463 /// Fork-aware offchain storage was accessed before the fork graph
1464 /// was initialized via `ForksHandler::start`.
1465 OCWForksNotEnabled,
1466
1467 /// The fork graph is in an inconsistent state.
1468 ///
1469 /// An internal invariant was violated, such as a branch entry that
1470 /// cannot be decoded or a divider that references a non-existent branch.
1471 /// Persistent failure indicates offchain storage corruption.
1472 OCWForksInconsistent,
1473 }
1474
1475 // ===============================================================================
1476 // ```````````````````````````````````` EVENTS ```````````````````````````````````
1477 // ===============================================================================
1478
1479 #[pallet::event]
1480 #[pallet::generate_deposit(pub(super) fn deposit_event)]
1481 pub enum Event<T: Config> {
1482 /// Emitted when an election attempt fails.
1483 ElectionAttemptFailed {
1484 session: SessionIndex,
1485 error: DispatchError,
1486 runner: AuthorOf<T>,
1487 },
1488
1489 /// Emitted after a successful election instance.
1490 ElectedInstance {
1491 session: SessionIndex,
1492 runner: AuthorOf<T>,
1493 #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
1494 elects: ElectionElects<T>,
1495 },
1496
1497 /// Emitted after a reward is successfully delegated to be
1498 /// applied to an author via [`CompensateRoles`].
1499 RewardInitiated {
1500 author: AuthorOf<T>,
1501 value: AssetOf<T>,
1502 },
1503
1504 /// Emitted when rewarding an author via [`CompensateRoles`] fails.
1505 RewardFailed {
1506 author: AuthorOf<T>,
1507 error: DispatchError,
1508 },
1509
1510 /// Emitted after a penalty is successfully delegated to be
1511 /// applied to an author via [`CompensateRoles`].
1512 PenaltyInitiated {
1513 author: AuthorOf<T>,
1514 penalty: PenaltyRatio,
1515 },
1516
1517 /// Emitted when applying a penalty to an author via
1518 /// [`CompensateRoles`] fails.
1519 PenaltyFailed {
1520 author: AuthorOf<T>,
1521 error: DispatchError,
1522 },
1523
1524 /// Emitted after a successful affidavit submission for the
1525 /// election to be conducted for the upcoming session.
1526 AffidavitSubmitted {
1527 afdt_id: AffidavitId<T>,
1528 session: SessionIndex,
1529 #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
1530 author: AuthorOf<T>,
1531 #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
1532 affidavit: ElectionVia<T>
1533 },
1534
1535 /// Emitted when an author successfully declares their affidavit signing
1536 /// key for the next session
1537 ValidationBegins {
1538 author: AuthorOf<T>,
1539 for_session: SessionIndex
1540 },
1541
1542 /// Emitted when an author successfully initiates the chilling process
1543 /// by removing their affidavit key for a future session.
1544 ChillingBegins {
1545 author: AuthorOf<T>,
1546 for_session: SessionIndex
1547 },
1548
1549 /// Emitted by [`Pallet::inspect_affidavit`] for direct inspection of a
1550 /// stored affidavit and its declared election weights.
1551 #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
1552 InspectAffidavit {
1553 author: AuthorOf<T>,
1554 afdt_id: AffidavitId<T>,
1555 session: SessionIndex,
1556 affidavit: ElectionVia<T>
1557 },
1558
1559 /// Emitted by [`Pallet::inspect_elects`] for direct inspection of the
1560 /// currently revealed elected author set.
1561 #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
1562 InspectElects {
1563 elects: ElectionElects<T>
1564 },
1565
1566 /// Emitted by [`Pallet::prepare_validation_payload`] exposing the signed
1567 /// payload and signature required to submit a [`Pallet::validate`] extrinsic.
1568 #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
1569 InspectValidatePayload {
1570 payload: ValidatePayloadOf<T>,
1571 signature: T::Signature,
1572 },
1573
1574 /// A genesis config parameter was updated forcefully.
1575 GenesisConfigUpdated(ForceGenesisConfig<T>),
1576 }
1577
1578 // ===============================================================================
1579 // ```````````````````````````````````` HOOKS ````````````````````````````````````
1580 // ===============================================================================
1581
1582 #[pallet::hooks]
1583 impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
1584 /// Offchain Worker entry point coordinating affidavit lifecycle and elections.
1585 ///
1586 /// This function acts as a **deterministic orchestrator** for a sequence of
1587 /// structured offchain routines, executed once per block in best-effort mode.
1588 /// It does not implement business logic itself; instead, it delegates all
1589 /// responsibility to well-defined [`Routines`] and [`RoutineOf`] abstractions.
1590 ///
1591 /// ## Execution Model
1592 ///
1593 /// All routines execute inside [`ForksHandler::start`], which resolves and
1594 /// persists the current fork graph branch before any routine runs.
1595 /// Each routine calls [`Routines::run_service`] directly, followed by
1596 /// [`Routines::on_ran_service`] on success. The routines are executed
1597 /// **imperatively and sequentially** with **fail-fast semantics**:
1598 ///
1599 /// 1. Initialize the node-local active affidavit key (if required).
1600 /// 2. Attempt to run the election early (if the election window permits).
1601 /// 3. Declare an affidavit for the upcoming session (if eligible).
1602 /// 4. Rotate affidavit keys for the next session.
1603 ///
1604 /// If any routine fails, execution stops immediately for the current block.
1605 /// All failures are already logged by the routine itself; the OCW hook
1606 /// performs no retries, compensation, or error interpretation.
1607 ///
1608 /// ## Semantics & Guarantees
1609 /// - **Best-effort execution**: no transactional rollback is assumed.
1610 /// - **Idempotent by design**: repeated execution across blocks or forks is safe.
1611 /// - **Authorization-aware**: each routine explicitly determines its authorized
1612 /// signer via [`RoutineOf::who`] before execution.
1613 /// - **Fork-safe**: correctness is preserved across re-orgs through
1614 /// fork-aware ([`frame_suite::ForkAware`]) and
1615 /// finalized ([`frame_suite::Finalized`]) offchain
1616 /// storage semantics.
1617 ///
1618 /// ## Notes
1619 /// - This function intentionally returns early on failure.
1620 /// - All observability is provided via structured logging inside routines.
1621 /// - Progress is achieved through repeated OCW invocations over time.
1622 fn offchain_worker(block: BlockNumberFor<T>) {
1623 <Pallet<T> as ForksHandler<T, ForkLocalDepot>>::start(None, None, ||{
1624
1625 //------ Initiate Affidavit Key ---------
1626
1627 let init = InitAffidavitKey { at: block };
1628 let Ok(_) =
1629 <InitAffidavitKey<T> as Routines<BlockNumberFor<T>>>::run_service(&init) else {
1630 return;
1631 };
1632 <InitAffidavitKey<T> as Routines<BlockNumberFor<T>>>::on_ran_service(&init);
1633
1634 //------ Try Electing Authors Early ---------
1635
1636 let Ok(new_afdt_key) =
1637 <TryElection<T> as RoutineOf<T::Public, BlockNumberFor<T>>>::who(&block) else {
1638 return;
1639 };
1640 let election = TryElection {
1641 by: new_afdt_key,
1642 at: block,
1643 };
1644 let Ok(_) =
1645 <TryElection<T> as Routines<BlockNumberFor<T>>>::run_service(&election) else {
1646 return;
1647 };
1648 <TryElection<T> as Routines<BlockNumberFor<T>>>::on_ran_service(&election);
1649
1650 //------ Declare Affidavit ---------
1651
1652 let Ok(afdt_key) =
1653 <DeclareAffidavit<T> as RoutineOf<T::Public, BlockNumberFor<T>>>::who(&block) else {
1654 return;
1655 };
1656
1657 let declare = DeclareAffidavit {
1658 by: afdt_key,
1659 at: block,
1660 };
1661 let Ok(_) =
1662 <DeclareAffidavit<T> as Routines<BlockNumberFor<T>>>::run_service(&declare) else {
1663 return;
1664 };
1665 <DeclareAffidavit<T> as Routines<BlockNumberFor<T>>>::on_ran_service(&declare);
1666
1667 //------ Rotate Affidavit Keys ---------
1668
1669 let Ok(next_afdt_key) =
1670 <RotateAffidavitKey<T> as RoutineOf<T::Public, BlockNumberFor<T>>>::who(&block) else {
1671 return;
1672 };
1673
1674 let rotate = RotateAffidavitKey {
1675 by: next_afdt_key,
1676 at: block,
1677 };
1678 let Ok(_) =
1679 <RotateAffidavitKey<T> as Routines<BlockNumberFor<T>>>::run_service(&rotate) else {
1680 return;
1681 };
1682 <RotateAffidavitKey<T> as Routines<BlockNumberFor<T>>>::on_ran_service(&rotate);
1683 });
1684 }
1685
1686 /// Called at the beginning of each block.
1687 ///
1688 /// Awards block production points to the current block author.
1689 fn on_initialize(_block: BlockNumberFor<T>) -> Weight {
1690 // Try to get the current block author from the authorship pallet
1691 let Some(author) = pallet_authorship::Pallet::<T>::author() else {
1692 // If no author is found (weight for 1 read operation),
1693 return T::DbWeight::get().reads(1);
1694 };
1695
1696 // Increment the block points for the author.
1697 // Ignoring errors here, as missing role checks or exhausted points are handled elsewhere.
1698 let _ = <Pallet<T> as AuthorPoints<AuthorOf<T>, T::Points>>::add_point(&author);
1699 <T as Config>::WeightInfo::on_initialize_with_author()
1700 }
1701 }
1702
1703 // ===============================================================================
1704 // `````````````````````````````````` EXTRINSICS `````````````````````````````````
1705 // ===============================================================================
1706
1707 #[pallet::call]
1708 impl<T: Config> Pallet<T> {
1709 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1710 // ```````````````````````````````` DISPATCHABLES ````````````````````````````````
1711 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1712
1713 /// Register an author's new **affidavit signing key** for the upcoming session's
1714 /// participation as a validator.
1715 ///
1716 /// This extrinsic allows an author to declare their **off-chain signing key** that
1717 /// will be used to sign affidavits for the next session's election.
1718 ///
1719 /// It ensures that only valid and available authors can register keys.
1720 ///
1721 /// ## Notes
1722 /// - Affidavit Keys are session-specific and are updated each session.
1723 /// - This extrinsic allows submission of affidavit for next session only.
1724 /// - Affidavit Declaration/Submission will rotate keys once every submission
1725 /// for further sessions then.
1726 ///
1727 /// ## Errors
1728 /// Returns a `DispatchError` if author-role authorization fails.
1729 #[pallet::call_index(0)]
1730 #[pallet::weight(<T as Config>::WeightInfo::validate())]
1731 pub fn validate(
1732 origin: OriginFor<T>,
1733 payload: ValidatePayloadOf<T>,
1734 // Signature verification happens via `ValidateUnsigned::validate::unsigned`
1735 // can avoid here
1736 _signature: T::Signature,
1737 ) -> DispatchResult {
1738 // Verify signed origin i.e., author
1739 let author = ensure_signed(origin)?;
1740
1741 // Ensure the author exists and is available in the role system
1742 <T::RoleAdapter as RoleManager<AuthorOf<T>>>::role_exists(&author)?;
1743 <T::RoleAdapter as RoleManager<AuthorOf<T>>>::is_available(&author)?;
1744
1745 // Compute the session for which the key is valid
1746 let for_session = CurrentSession::<T>::get().saturating_add(One::one());
1747 let public = SignedPayload::<T>::public(&payload);
1748 let affidavit_pub: AffidavitId<T> = public.clone().into_account().into();
1749
1750 // Store the key for the upcoming session
1751 AffidavitKeys::<T>::insert((for_session, affidavit_pub), author.clone());
1752
1753 Self::deposit_event(Event::<T>::ValidationBegins { author, for_session });
1754 Ok(())
1755 }
1756
1757 /// Submit an **affidavit for the upcoming session election**.
1758 ///
1759 /// This extrinsic allows an author to declare their election weights
1760 /// (affidavit) for the next session. It also rotates the author's signing
1761 /// key for the subsequent affidavit.
1762 ///
1763 /// ## Parameters
1764 /// - `origin`: Must be a signed account corresponding to the author
1765 /// submitting the affidavit.
1766 /// - `payload`: The payload that was signed off-chain representing the
1767 /// affidavit data.
1768 /// - `signature`: The author's signature of the payload, used for verification.
1769 /// - `new_key`: A new affidavit signing key to replace the current one for the
1770 /// next upcoming session.
1771 ///
1772 /// ## Notes
1773 /// - Affidavit submissions are session-specific.
1774 /// - Key rotation ensures authors maintain fresh signing keys for security.
1775 /// - Only validated and available authors can submit affidavits.
1776 ///
1777 /// ## Errors
1778 /// Returns a `DispatchError` if un-privileged to submit an affidavit.
1779 #[pallet::call_index(1)]
1780 #[pallet::weight(<T as Config>::WeightInfo::declare())]
1781 pub fn declare(
1782 origin: OriginFor<T>,
1783 payload: AffidavitPayloadOf<T>,
1784 // Signature verification happens via `ValidateUnsigned::validate::unsigned`
1785 // can avoid here
1786 _signature: T::Signature,
1787 ) -> DispatchResult {
1788 ensure_none(origin)?;
1789
1790 let public = SignedPayload::<T>::public(&payload);
1791 let new_affidavit_pub = payload.rotate.clone();
1792 let affidavit_pub: AffidavitId<T> = public.clone().into_account().into();
1793
1794 let for_session = CurrentSession::<T>::get().saturating_add(One::one());
1795 // Ensure author has a registered key for the upcoming session
1796 let author = AffidavitKeys::<T>::get((for_session, &affidavit_pub))
1797 .ok_or(Error::<T>::AffidavitAuthorNotFound)?;
1798
1799 // Process the affidavit
1800 <Pallet<T> as ElectionAffidavits<AffidavitId<T>, ElectionVia<T>>>::process_affidavit(
1801 &affidavit_pub.clone(),
1802 )?;
1803
1804 // Rotate key for the next affidavit
1805 AffidavitKeys::<T>::insert(
1806 (
1807 for_session.saturating_add(One::one()),
1808 new_affidavit_pub.clone(),
1809 ),
1810 &author,
1811 );
1812
1813 #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
1814 {
1815 if !T::EmitEvents::get() {
1816 let block = frame_system::Pallet::<T>::block_number();
1817 let Ok(affidavit) = Self::get_affidavit(&affidavit_pub) else {
1818 debug_assert!(
1819 false,
1820 "author declared affidavit for session {:?} at block {:?}, but it could not be retrieved",
1821 for_session, block
1822 );
1823 return Err(Error::<T>::DeclaredAffidavitNotFound.into());
1824 };
1825 Self::deposit_event(Event::<T>::AffidavitSubmitted {
1826 afdt_id: affidavit_pub,
1827 session: for_session,
1828 author,
1829 affidavit,
1830 });
1831 }
1832 }
1833
1834 #[cfg(not(any(feature = "dev", feature = "runtime-benchmarks")))]
1835 {
1836 if !T::EmitEvents::get() {
1837 Self::deposit_event(Event::<T>::AffidavitSubmitted {
1838 afdt_id: affidavit_pub,
1839 session: for_session,
1840 });
1841 }
1842 }
1843 Ok(())
1844 }
1845
1846 /// Execute the election for the upcoming session.
1847 ///
1848 /// This extrinsic allows an author to act as the **election runner**
1849 /// for the election session (next-session). It verifies the author's
1850 /// affidavit-based signature, prepares the election using all submitted
1851 /// affidavits, and records the runner for audit and reward attribution.
1852 ///
1853 /// Since this is an **unsigned extrinsic**, it is constructed and
1854 /// submitted by validator offchain workers (OCWs).
1855 ///
1856 /// Although unsigned, it is treated as a **pseudo-inherent**:
1857 /// - It is authorized via affidavit signature verification
1858 /// - It may carry rewards for successfully running the election
1859 /// - It is **expected** to be submitted locally by validators rather
1860 /// than propagated by external transaction authors
1861 ///
1862 /// ## Errors
1863 /// Returns a `DispatchError` if election execution or authorization fails.
1864 #[pallet::call_index(2)]
1865 #[pallet::weight(<T as Config>::WeightInfo::elect())]
1866 pub fn elect(
1867 origin: OriginFor<T>,
1868 payload: ElectionPayloadOf<T>,
1869 // Signature verification happens via `ValidateUnsigned`
1870 // can avoid here
1871 _signature: T::Signature,
1872 ) -> DispatchResult {
1873 ensure_none(origin)?;
1874
1875 // Determine the session for which this election applies (next session)
1876 let for_session = CurrentSession::<T>::get().saturating_add(One::one());
1877
1878 let public = SignedPayload::<T>::public(&payload);
1879 let affidavit_pub: AffidavitId<T> = public.clone().into_account().into();
1880
1881 // Ensure the author has a valid affidavit key registered
1882 // Election keys are registered for session+2 because:
1883 // - Current session: ongoing
1884 // - Next session: affidavits are submitted
1885 // - Session after next: election is executed
1886 // Hence authors who submitted affidavits, submit next election key, hence
1887 // queriable.
1888 // This makes only authors who submitted affidavits to run election
1889 let author = AffidavitKeys::<T>::get((
1890 for_session.saturating_add(One::one()),
1891 &affidavit_pub.clone(),
1892 ))
1893 .ok_or(Error::<T>::AffidavitAuthorNotFound)?;
1894
1895 let block_author =
1896 pallet_authorship::Pallet::<T>::author().ok_or(Error::<T>::BlockAuthorNotFound)?;
1897 // This ensures authors who have bypassed `ValidateUnsigned` via
1898 // `propagate = false` for `elect` unsigned extrinsic
1899 // shall be captured by the runtime itself
1900 ensure!(
1901 author == block_author,
1902 Error::<T>::TriedElectingByNonBlockAuthor
1903 );
1904
1905 // Run the election preparation logic
1906 if let Err(error) = Internals::<T>::prepare_election(&Some(author.clone())) {
1907 if !T::EmitEvents::get() {
1908 Self::deposit_event(Event::<T>::ElectionAttemptFailed {
1909 session: for_session,
1910 runner: author.clone(),
1911 error,
1912 });
1913 }
1914 return Err(error);
1915 };
1916
1917 // Record the election runner and the block at which the election was conducted
1918 let current_block = frame_system::Pallet::<T>::block_number();
1919 ElectsPreparedBy::<T>::insert(for_session, (&author, current_block));
1920
1921 #[cfg(not(any(feature = "dev", feature = "runtime-benchmarks")))]
1922 {
1923 if !T::EmitEvents::get() {
1924 Self::deposit_event(Event::<T>::ElectedInstance {
1925 session: for_session,
1926 runner: author,
1927 });
1928 }
1929 }
1930
1931 #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
1932 {
1933 if !T::EmitEvents::get() {
1934 let Some(elects) = Internals::<T>::reveal() else {
1935 debug_assert!(
1936 false,
1937 "authors elected for session {:?} at
1938 block {:?} by election runner {:?},
1939 but reveal unavailable",
1940 for_session, current_block, &author
1941 );
1942 return Err(Error::<T>::ElectedButCannotReveal.into());
1943 };
1944 Self::deposit_event(Event::<T>::ElectedInstance {
1945 session: for_session,
1946 runner: author,
1947 elects,
1948 });
1949 }
1950 }
1951 Ok(())
1952 }
1953
1954 /// Request to **step back or "chill" immediately from election participation**
1955 /// by erasing affidavit keys.
1956 ///
1957 /// This safely avoids penalties by pausing an author's duties.
1958 ///
1959 /// This extrinsic allows an author to withdraw from participating in upcoming
1960 /// elections or prevent submitting future affidavits. The actual effect
1961 /// depends on the **current block** relative to the **affidavit submissions
1962 /// and election windows**.
1963 ///
1964 /// Note that its always advised to `chill` validation before `resign` author role
1965 /// to skip unnecessary invalid affidavit declarations.
1966 ///
1967 /// ## Parameters
1968 /// - `origin`: Must be a signed account corresponding to the author i.e.,
1969 /// controller/role account.
1970 /// - `affidavit_pub` : Public affidavit key registered for a new session's
1971 /// affidavit submission.
1972 ///
1973 /// ## Notes
1974 /// - Removing affidavit keys ensures authors cannot unfairly influence
1975 /// future elections.
1976 /// - By inspecting returned errors, callers can **compute the optimal
1977 /// chill window**.
1978 ///
1979 /// ## Errors
1980 /// Returns a `DispatchError` with diagnostic in case of irrevocable
1981 /// duties assigned.
1982 #[pallet::call_index(3)]
1983 #[pallet::weight(<T as Config>::WeightInfo::chill())]
1984 pub fn chill(origin: OriginFor<T>, affidavit_pub: AffidavitId<T>) -> DispatchResult {
1985 let author = ensure_signed(origin)?;
1986
1987 Self::can_chill(author.clone(), affidavit_pub.clone())?;
1988
1989 let current_session = CurrentSession::<T>::get();
1990 let next_session = current_session.saturating_add(One::one());
1991 let next_afdt_session = next_session.saturating_add(One::one());
1992
1993 // If rotated key exists, remove it.
1994 if AffidavitKeys::<T>::contains_key((next_afdt_session, &affidavit_pub)) {
1995 AffidavitKeys::<T>::remove((next_afdt_session, &affidavit_pub));
1996 Self::deposit_event(Event::<T>::ChillingBegins { author, for_session: next_afdt_session });
1997 return Ok(());
1998 }
1999
2000 // Otherwise remove next-session key if it still exists.
2001 if AffidavitKeys::<T>::contains_key((next_session, &affidavit_pub)) {
2002 AffidavitKeys::<T>::remove((next_session, &affidavit_pub));
2003 }
2004 Self::deposit_event(Event::<T>::ChillingBegins { author, for_session: next_session });
2005
2006 Ok(())
2007 }
2008
2009 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2010 // ````````````````````````````````` INSPECTORS ``````````````````````````````````
2011 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2012
2013 /// Emit the currently revealed elected author set as an event for inspection.
2014 ///
2015 /// This is a **read-only convenience extrinsic** that does not mutate any state.
2016 /// It retrieves the election result from the underlying election manager via
2017 /// [`Pallet::get_elects`] and emits it as an [`Event::InspectElects`] event.
2018 ///
2019 /// ## Notes
2020 /// - Gated by the `dev` feature. Must not be included in production runtimes.
2021 /// - Fails if no election result is available, i.e. no election has been
2022 /// executed or the result cannot be revealed.
2023 ///
2024 /// ## Errors
2025 /// Returns [`Error::UnableToRevealElected`] if the election result is unavailable.
2026 #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
2027 #[pallet::call_index(4)]
2028 #[pallet::weight(<T as Config>::WeightInfo::inspect_elects())]
2029 pub fn inspect_elects(
2030 origin: OriginFor<T>,
2031 ) -> DispatchResult {
2032 ensure_signed(origin)?;
2033 let elects = Self::get_elects()?;
2034 Self::deposit_event(Event::InspectElects { elects });
2035 Ok(())
2036 }
2037
2038 /// Construct and emit a signed [`ValidatePayload`] and its signature as an event.
2039 ///
2040 /// This is a **read-only convenience extrinsic** that does not mutate any on-chain state.
2041 /// It is intended to support the [`Self::validate`] extrinsic workflow by producing
2042 /// the signed payload and signature required to call it.
2043 ///
2044 /// Since [`Self::validate`] accepts both a signed origin and an unsigned payload,
2045 /// callers need a way to obtain a correctly signed payload before submitting.
2046 /// This extrinsic bridges that gap by performing the signing on behalf of the
2047 /// local node and emitting the result as an [`Event::InspectValidatePayload`] event.
2048 ///
2049 /// ## Execution Model
2050 /// This extrinsic reads from **node-local offchain storage** to retrieve the
2051 /// active affidavit key. It is therefore **client-specific**: the result reflects
2052 /// the state of the validator node executing the call, and may differ across nodes.
2053 ///
2054 /// ## Notes
2055 /// - Gated by the `dev` feature. Must not be included in production runtimes.
2056 /// - The emitted payload and signature can be submitted directly to [`Self::validate`].
2057 /// - Requires that the node holds a finalized active affidavit key in offchain storage.
2058 ///
2059 /// ## Errors
2060 /// Returns a `DispatchError` if the active affidavit key is not finalized,
2061 /// not found in the local keystore, or signing fails.
2062 #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
2063 #[pallet::call_index(5)]
2064 #[pallet::weight(<T as Config>::WeightInfo::prepare_validation_payload())]
2065 pub fn prepare_validation_payload(
2066 origin: OriginFor<T>,
2067 ) -> DispatchResult {
2068 ensure_signed(origin)?;
2069 let (payload, signature) = Self::sign_validate_payload()?;
2070 Self::deposit_event(Event::InspectValidatePayload { payload, signature });
2071 Ok(())
2072 }
2073
2074 /// Emit the stored affidavit for a given affidavit identifier as an event.
2075 ///
2076 /// This is a **read-only convenience extrinsic** that does not mutate any state.
2077 /// It retrieves the affidavit associated with the provided [`AffidavitId`] from
2078 /// storage and emits it as an [`Event::InspectAffidavit`] event, making the
2079 /// affidavit weights observable without requiring direct storage queries.
2080 ///
2081 /// The retrieved affidavit corresponds to the **upcoming session's election**
2082 /// (current session index + 1).
2083 ///
2084 /// ## Notes
2085 /// - Gated by the `dev` feature. Must not be included in production runtimes.
2086 /// - Useful for verifying that a previously submitted affidavit was stored correctly
2087 /// before the election window opens.
2088 ///
2089 /// ## Errors
2090 /// Returns [`Error::AffidavitAuthorNotFound`] if no author is mapped to the given key,
2091 /// or [`Error::AffidavitNotFound`] if no affidavit has been submitted for that author.
2092 #[cfg(any(feature = "dev", feature = "runtime-benchmarks"))]
2093 #[pallet::call_index(6)]
2094 #[pallet::weight(<T as Config>::WeightInfo::inspect_affidavit())]
2095 pub fn inspect_affidavit(
2096 origin: OriginFor<T>,
2097 afdt_id: AffidavitId<T>,
2098 ) -> DispatchResult {
2099 let author = ensure_signed(origin)?;
2100 let affidavit = Self::get_affidavit(&afdt_id)?;
2101 let for_session = CurrentSession::<T>::get().saturating_add(One::one());
2102 Self::deposit_event(Event::InspectAffidavit { author, session: for_session, afdt_id, affidavit });
2103 Ok(())
2104 }
2105
2106 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2107 // ``````````````````````````````` ROOT PRIVILEGED ```````````````````````````````
2108 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
2109
2110 /// Force-update a selected genesis configuration parameter.
2111 ///
2112 /// **Origin:** Root only.
2113 ///
2114 /// This extrinsic allows privileged modification of runtime parameters
2115 /// that were originally defined at genesis.
2116 ///
2117 /// - `AllowAffidavits` - Enables or disables affidavit submission.
2118 /// - `AffidavitBeginsAt` - Updates the start of the affidavit submission window.
2119 /// - `AffidavitEndsAt` - Updates the end of the affidavit submission window.
2120 /// - `ElectionBeginsAt` - Updates when election execution begins within the session.
2121 /// - `ElectionRunnerPointsUpgrade` - Updates the reward points for election runners.
2122 /// - `ValidateTxPriority` - Updates the priority for validation-related extrinsics.
2123 /// - `ElectionTxPriority` - Updates the priority for election execution extrinsics.
2124 /// - `AffidavitTxPriority` - Updates the priority for affidavit submission extrinsics.
2125 /// - `FinalityAfter` - Updates the time-based delay before operations are considered final.
2126 /// - `FinalityTicks` - Updates the block-based confirmation threshold for finality.
2127 ///
2128 /// The call enforces consistency constraints where applicable:
2129 /// - Affidavit window ordering:
2130 /// - `AffidavitBeginsAt < AffidavitEndsAt`
2131 /// - `AffidavitEndsAt > AffidavitBeginsAt`
2132 /// - The following values must be non-zero:
2133 /// - transaction priorities
2134 /// - finality thresholds (`FinalityAfter`, `FinalityTicks`)
2135 ///
2136 /// This call directly overwrites storage and emits an event containing the
2137 /// updated configuration variant.
2138 #[pallet::call_index(7)]
2139 #[pallet::weight(<T as Config>::WeightInfo::force_allow_affidavits()
2140 .max(<T as Config>::WeightInfo::force_affidavit_begins_at())
2141 .max(<T as Config>::WeightInfo::force_affidavit_ends_at())
2142 .max(<T as Config>::WeightInfo::force_election_begins_at())
2143 .max(<T as Config>::WeightInfo::force_election_runner_points_upgrade())
2144 .max(<T as Config>::WeightInfo::force_validate_tx_priority())
2145 .max(<T as Config>::WeightInfo::force_election_tx_priority())
2146 .max(<T as Config>::WeightInfo::force_affidavit_tx_priority())
2147 .max(<T as Config>::WeightInfo::force_finality_after())
2148 .max(<T as Config>::WeightInfo::force_finality_ticks())
2149 )]
2150 pub fn force_genesis_config(
2151 origin: OriginFor<T>,
2152 field: ForceGenesisConfig<T>,
2153 ) -> DispatchResult {
2154 ensure_root(origin)?;
2155 match field {
2156 ForceGenesisConfig::AllowAffidavits(value) => AllowAffidavits::<T>::put(value),
2157 ForceGenesisConfig::AffidavitBeginsAt(aff_begins) => {
2158 ensure!(
2159 aff_begins < AffidavitEndsAt::<T>::get(),
2160 Error::<T>::InvalidAffidavitPeriod
2161 );
2162 AffidavitBeginsAt::<T>::put(aff_begins);
2163 }
2164 ForceGenesisConfig::AffidavitEndsAt(aff_ends) => {
2165 ensure!(
2166 aff_ends > AffidavitBeginsAt::<T>::get(),
2167 Error::<T>::InvalidAffidavitPeriod
2168 );
2169 AffidavitEndsAt::<T>::put(aff_ends);
2170 }
2171 ForceGenesisConfig::ElectionBeginsAt(elect_begins) => {
2172 ElectionBeginsAt::<T>::put(elect_begins);
2173 }
2174 ForceGenesisConfig::ElectionRunnerPointsUpgrade(points) => {
2175 ElectionRunnerPointsUpgrade::<T>::put(points);
2176 }
2177 ForceGenesisConfig::ValidateTxPriority(priority) => {
2178 ensure!(priority > Zero::zero(), Error::<T>::ValueCannotBeZero);
2179 ValidateTxPriority::<T>::put(priority);
2180 }
2181 ForceGenesisConfig::ElectionTxPriority(priority) => {
2182 ensure!(priority > Zero::zero(), Error::<T>::ValueCannotBeZero);
2183 ElectionTxPriority::<T>::put(priority);
2184 }
2185 ForceGenesisConfig::AffidavitTxPriority(priority) => {
2186 ensure!(priority > Zero::zero(), Error::<T>::ValueCannotBeZero);
2187 AffidavitTxPriority::<T>::put(priority);
2188 }
2189 ForceGenesisConfig::FinalityAfter(moment) => {
2190 ensure!(moment > Zero::zero(), Error::<T>::ValueCannotBeZero);
2191 FinalityAfter::<T>::put(moment);
2192 }
2193 ForceGenesisConfig::FinalityTicks(block) => {
2194 ensure!(block > Zero::zero(), Error::<T>::ValueCannotBeZero);
2195 FinalityTicks::<T>::put(block);
2196 }
2197 }
2198 Self::deposit_event(Event::GenesisConfigUpdated(field));
2199 Ok(())
2200 }
2201 }
2202
2203 // ===============================================================================
2204 // ````````````````````````````````` PUBLIC APIS `````````````````````````````````
2205 // ===============================================================================
2206
2207 impl<T: Config> Pallet<T> {
2208 /// Returns `Ok(())` if the author is permitted to submit the [`Self::chill`] extrinsic
2209 /// using the given affidavit key.
2210 ///
2211 /// Performs a read-only pre-check verifying that chilling does not
2212 /// conflict with any active or pending duty. The check branches on which
2213 /// session scope the key belongs to:
2214 ///
2215 /// - **Next affidavit session (current + 2)**: author has already declared and
2216 /// rotated. Chilling is allowed only after the election window closes and
2217 /// the author is not in the revealed elected set.
2218 ///
2219 /// - **Next session (current + 1)**: author has registered but may not have
2220 /// declared yet. Chilling is allowed before the affidavit window or after
2221 /// it closes without a declaration. Rejected if a declaration exists but
2222 /// the key is stale relative to the expected post-rotation key.
2223 ///
2224 /// ## Parameters
2225 /// - `author`: The author requesting to chill.
2226 /// - `affidavit_pub`: The affidavit key registered via [`Self::validate`]
2227 /// or rotated during [`Self::declare`].
2228 ///
2229 /// ## Returns
2230 /// - `Ok(())` if chilling is permitted.
2231 /// - `Err(ValidatorCannotChill)` if the author is active in the current session.
2232 /// - `Err(CandidateCannotChill)` if the election window is still open.
2233 /// - `Err(ElectedCannotChill)` if the author appears in the revealed elected set.
2234 /// - `Err(InvalidRotatedAffidavitKey)` if a declaration exists but the key is stale.
2235 /// - `Err(DispatchError)` for ownership, timing, or configuration violations.
2236 pub fn can_chill(author: AuthorOf<T>, affidavit_pub: AffidavitId<T>) -> DispatchResult {
2237 let current_session = CurrentSession::<T>::get();
2238 let next_session = current_session.saturating_add(One::one());
2239 let next_afdt_session = next_session.saturating_add(One::one());
2240
2241 // Convert author -> validator id for the current session
2242 // If conversion fails, runtime cannot determine validator status
2243 let Some(validator) =
2244 <Pallet<T> as Convert<AuthorOf<T>, Option<SessionId<T>>>>::convert(author.clone())
2245 else {
2246 return Err(Error::<T>::SessionIdQueryFailed.into());
2247 };
2248
2249 // Active validators of the *current* session are not allowed to chill.
2250 // Reason: they may still be required if elections fail or re-run.
2251 ensure!(
2252 !pallet_session::Pallet::<T>::validators().contains(&validator),
2253 Error::<T>::ValidatorCannotChill
2254 );
2255
2256 // Compute affidavit submission window for the upcoming election
2257 let aff_window = Self::compute_affidavit_window()?;
2258 let start_affidavit = aff_window.start;
2259 let end_affidavit = aff_window.end;
2260 // Ensure configuration is sane: submission window must be valid
2261 debug_assert!(
2262 start_affidavit < end_affidavit,
2263 "Affidavit submission period invalid: starts at {:?}, ends at {:?}",
2264 start_affidavit,
2265 end_affidavit
2266 );
2267 ensure!(
2268 start_affidavit < end_affidavit,
2269 Error::<T>::InvalidAffidavitPeriod
2270 );
2271
2272 let current_block = frame_system::Pallet::<T>::block_number();
2273
2274 // CASE 1: Key belongs to next-next session (already rotated)
2275 //
2276 // Optimistically we need to check.
2277 //
2278 // This means:
2279 // - Author already declared an affidavit for `next_session`
2280 // - During declaration, a new key was rotated for `next_afdt_session`
2281 // Hence this author may very-well be a candidate for the ongoing election.
2282 if let Some(id) = AffidavitKeys::<T>::get((next_afdt_session, &affidavit_pub)) {
2283 ensure!(id == author, Error::<T>::AuthorNotAffidavitOwner);
2284 // Sanity: a rotated key implies the affidavit must have been declared
2285 // within the valid submission window.
2286 debug_assert!(
2287 !(current_block < start_affidavit),
2288 "affidavit key is for next-next session, effectively means it
2289 declared affidavit and rotated next key, but it happened before
2290 affidavit submission period itself? for author {:?} and afdt key {:?},
2291 rotated during elected session {:?}, and rotated for session {:?}",
2292 author,
2293 affidavit_pub,
2294 next_session,
2295 next_afdt_session
2296 );
2297 ensure!(
2298 !(current_block < start_affidavit),
2299 Error::<T>::DeclaredBeforeAffidavitPeriod
2300 );
2301
2302 // If still within affidavit submission window:
2303 // author is an active candidate -> cannot chill.
2304 // It is possible that the author is not present in the elected
2305 // list at this stage.
2306 // In that case, we ideally should purge:
2307 // - the declared affidavit for `next_session`,
2308 // - the affidavit key for `next_session`,
2309 // - and the rotated affidavit key for `next_afdt_session`.
2310 // However, at this point we only have access to the
2311 // `next_afdt_session` affidavit key i.e., rotated key.
2312 ensure!(
2313 current_block >= end_affidavit,
2314 Error::<T>::CandidateCannotChill
2315 );
2316
2317 // After the election period:
2318 // Only authors who were NOT elected are allowed to chill.
2319 //
2320 // We already enforced earlier that active validators of the *current session*
2321 // cannot chill. This guarantees that even if the `reveal` result here is
2322 // slightly outdated or later ignored by the session (e.g. the same validators
2323 // are re-posted due to election issues), safety is preserved.
2324 //
2325 // Therefore:
2326 // - If the author appears in the revealed elected set -> they must not chill.
2327 // - If the author does NOT appear -> they are not part of the upcoming elected set.
2328 // - Even if the reveal is stale, it can only reflect a previous valid election
2329 // outcome, and cannot incorrectly allow a current validator to chill.
2330 //
2331 // Hence, it is safe to rely on this check to prevent elected (or soon-to-be
2332 // elected) authors from chilling.
2333 let elected = <Internals<T> as ElectAuthors<AuthorOf<T>, ElectionVia<T>>>::reveal();
2334 if let Some(elected) = elected {
2335 for elect in elected.into_iter() {
2336 if author == elect {
2337 return Err(Error::<T>::ElectedCannotChill.into());
2338 }
2339 }
2340 }
2341
2342 // Election finished and author not elected:
2343 // author can chill at next-next election.
2344 return Ok(());
2345 }
2346
2347 // CASE 2: Key belongs to next session
2348 //
2349 // This means the author has registered an affidavit key but may NOT yet
2350 // declared the affidavit for the upcoming election.
2351 let id = AffidavitKeys::<T>::get((next_session, &affidavit_pub))
2352 .ok_or(Error::<T>::AffidavitAuthorNotFound)?;
2353 ensure!(id == author, Error::<T>::AuthorNotAffidavitOwner);
2354
2355 // Subcase A: Before affidavit submission window begins
2356 //
2357 // Author is simply opting out before participating in election.
2358 if current_block < start_affidavit {
2359 let afdt_decl = AuthorAffidavits::<T>::contains_key((next_session, &author));
2360
2361 // Sanity: affidavit should not be declared before submission window.
2362 debug_assert!(
2363 !afdt_decl,
2364 "affidavit is declared before affidavit submission period
2365 by author {:?} for election conducted for session {:?} using
2366 afdt-key {:?}",
2367 author, next_session, affidavit_pub
2368 );
2369 ensure!(!afdt_decl, Error::<T>::DeclaredBeforeAffidavitPeriod);
2370
2371 return Ok(());
2372 }
2373
2374 // Subcase B: During or after affidavit submission window
2375 match AuthorAffidavits::<T>::contains_key((next_session, &author)) {
2376 true => {
2377 // Affidavit has already been declared for `next_session`.
2378 //
2379 // Normally, this implies that a new (rotated) affidavit key for `next_afdt_session`
2380 // should have been registered as part of the declaration flow.
2381 //
2382 // If we still reached here with the current key, it likely means the provided key
2383 // is stale or does not correspond to the rotated key expected after declaration.
2384 //
2385 // However, we must not blindly assume rotation always changes the key. In some
2386 // implementations (e.g., custom declaration flows outside this pallet's OCWs),
2387 // the same affidavit key might be reused during rotation.
2388 //
2389 // Therefore, rather than mutating any state based on uncertain assumptions,
2390 // we conservatively reject the call with an explicit error.
2391 return Err(Error::<T>::InvalidRotatedAffidavitKey.into());
2392 }
2393 false => {
2394 // Either still inside submission window but not yet declared,
2395 // or window already ended and author implicitly chilled.
2396 return Ok(());
2397 }
2398 }
2399 }
2400
2401 /// Returns `Ok(())` if the author is eligible to submit a [`Self::validate`] extrinsic.
2402 ///
2403 /// Performs a pre-check for validation readiness by verifying that:
2404 /// - the author is not already an active validator in the current session,
2405 /// - the author exists in the role system via [`Config::RoleAdapter`], and
2406 /// - the author is currently marked as available.
2407 ///
2408 /// Intended for use by offchain workers and RPC consumers before constructing
2409 /// and submitting a [`Self::validate`] extrinsic.
2410 ///
2411 /// ## Parameters
2412 /// - `author`: The author whose validation eligibility is being checked.
2413 ///
2414 /// ## Returns
2415 /// - `Ok(())` if all conditions for validation readiness are satisfied.
2416 /// - `Err(ActivelyValidating)` if the author is already in the active validator set.
2417 /// - `Err(DispatchError)` if the role check or availability check fails.
2418 pub fn can_validate(author: AuthorOf<T>) -> DispatchResult {
2419 if Self::is_validating(author.clone()) {
2420 return Err(Error::<T>::ActivelyValidating.into());
2421 }
2422 <T::RoleAdapter as RoleManager<AuthorOf<T>>>::role_exists(&author)?;
2423 <T::RoleAdapter as RoleManager<AuthorOf<T>>>::is_available(&author)?;
2424 Ok(())
2425 }
2426
2427 /// Returns `true` if the author is an active validator in the **current session**.
2428 ///
2429 /// Converts the author identifier into its session-specific validator id using
2430 /// the configured [`Convert`] implementation, then checks whether that validator
2431 /// appears in the current session's active validator set via [`pallet_session`].
2432 ///
2433 /// Returns `false` if the author-to-validator conversion fails, as the runtime
2434 /// cannot determine validator status without a valid session identity.
2435 ///
2436 /// ## Parameters
2437 /// - `author`: The author whose active validation status is being checked.
2438 pub fn is_validating(author: AuthorOf<T>) -> bool {
2439 // Convert author -> validator id for the current session
2440 let Some(validator) =
2441 <Pallet<T> as Convert<AuthorOf<T>, Option<SessionId<T>>>>::convert(author.clone())
2442 else {
2443 return false;
2444 };
2445
2446 // Active validators of the *current* session.
2447 if pallet_session::Pallet::<T>::validators().contains(&validator) {
2448 return true;
2449 }
2450 false
2451 }
2452
2453 /// Returns `true` if the author has no registered affidavit key in any
2454 /// relevant future session scope and is therefore in a **chilled** state.
2455 ///
2456 /// This is the logical inverse of [`Self::is_pursuing`] and delegates
2457 /// directly to [`Self::get_runtime_afdt_key`].
2458 ///
2459 /// ## Parameters
2460 /// - `author`: The author whose chilling status is being checked.
2461 pub fn is_chilling(author: AuthorOf<T>) -> bool {
2462 Self::get_runtime_afdt_key(author).is_err()
2463 }
2464
2465 /// Retrieve the author's registered affidavit key and its associated session
2466 /// from the two relevant future session scopes.
2467 ///
2468 /// Searches across:
2469 /// - the **next affidavit session** (current + 2), checked first, and
2470 /// - the **next session** (current + 1), checked second.
2471 ///
2472 /// The next affidavit session is checked first because a key stored there
2473 /// indicates a more advanced lifecycle state: the author has already declared
2474 /// an affidavit for the next session and rotated to a fresh key for the
2475 /// session after.
2476 ///
2477 /// ## Parameters
2478 /// - `author`: The author whose registered affidavit key is being retrieved.
2479 ///
2480 /// ## Returns
2481 /// - `Ok((session, affidavit_key))` if a key owned by the author is found
2482 /// in either future session scope.
2483 /// - `Err(AffidavitKeyPairNotFound)` if no registered key exists for the author.
2484 pub fn get_runtime_afdt_key(
2485 author: AuthorOf<T>,
2486 ) -> Result<(SessionIndex, AffidavitId<T>), DispatchError> {
2487 let current_session = CurrentSession::<T>::get();
2488 let next_session = current_session.saturating_add(One::one());
2489 let next_afdt_session = next_session.saturating_add(One::one());
2490
2491 if let Some((affidavit_pub, _)) = AffidavitKeys::<T>::iter_prefix((next_afdt_session,))
2492 .find(|(_, owner)| *owner == author)
2493 {
2494 return Ok((next_afdt_session, affidavit_pub));
2495 }
2496
2497 if let Some((affidavit_pub, _)) =
2498 AffidavitKeys::<T>::iter_prefix((next_session,)).find(|(_, owner)| *owner == author)
2499 {
2500 return Ok((next_session, affidavit_pub));
2501 }
2502
2503 Err(Error::<T>::AffidavitKeyPairNotFound.into())
2504 }
2505
2506 /// Resolve an affidavit account identifier to its corresponding public key
2507 /// in the **node-local keystore**.
2508 ///
2509 /// Iterates all locally available affidavit application keys, derives the
2510 /// account-form identifier for each, and returns the public key whose derived
2511 /// account matches the provided `afdt_key`.
2512 ///
2513 /// ## Parameters
2514 /// - `afdt_key`: The affidavit account identifier to resolve.
2515 ///
2516 /// ## Returns
2517 /// - `Ok(public_key)` if a matching key is found in the local keystore.
2518 /// - `Err(AfdtPublicKeyNotFound)` if no locally held key derives to the given identifier.
2519 ///
2520 /// ## Note
2521 /// This function reads from the node-local keystore and may return different
2522 /// results across nodes depending on which keys each node holds.
2523 pub fn get_public_key(afdt_key: AffidavitId<T>) -> Result<T::Public, DispatchError> {
2524 let all_keys =
2525 <<T::AffidavitCrypto as AppCrypto<T::Public, T::Signature>>::RuntimeAppPublic
2526 as RuntimeAppPublic>::all();
2527 for key in all_keys.into_iter() {
2528 let generic_pub: <T::AffidavitCrypto as AppCrypto<T::Public, T::Signature>>::GenericPublic =
2529 key.into();
2530 let public: T::Public = generic_pub.into();
2531 let account: AffidavitId<T> = public.clone().into_account().into();
2532
2533 if account == afdt_key {
2534 return Ok(public);
2535 }
2536 }
2537 Err(Error::<T>::AfdtPublicKeyNotFound.into())
2538 }
2539
2540 /// Retrieve the currently finalized **active affidavit key** from node-local
2541 /// offchain storage.
2542 ///
2543 /// Reads the active affidavit key using finalized offchain storage semantics
2544 /// and returns it only when its confidence level is [`Confidence::Safe`].
2545 /// Keys that exist but have not yet reached safe confidence are rejected,
2546 /// as they may still be subject to re-org.
2547 ///
2548 /// ## Returns
2549 /// - `Ok(key)` if a finalized key exists and is marked as [`Confidence::Safe`].
2550 /// - `Err(ActiveAfdtKeyFinalizedHangingValue)` if a finalized value exists
2551 /// without a corresponding fork-aware reference.
2552 /// - `Err(ActiveAfdtKeyNotYetFinalized)` if the key exists but has not yet
2553 /// reached safe confidence.
2554 /// - `Err(DispatchError)` if the offchain storage read itself fails.
2555 ///
2556 /// ## Note
2557 /// This value is maintained in node-local offchain storage and is not part
2558 /// of on-chain state. Results may differ across nodes.
2559 pub fn get_finalized_afdt_key() -> Result<AffidavitId<T>, DispatchError> {
2560 let result = Finalized::<T, AffidavitId<T>, DeclareAffidavit<T>, Pallet<T>>::get(
2561 ACTIVE_AFDT_KEY,
2562 LOG_TARGET_AFDT,
2563 None,
2564 );
2565 match result {
2566 Ok(None) => Err(Error::<T>::ActiveAfdtKeyFinalizedHangingValue.into()),
2567 Ok(Some(Confidence::Safe(key))) => Ok(key),
2568 Ok(Some(_)) => Err(Error::<T>::ActiveAfdtKeyNotYetFinalized.into()),
2569 Err(e) => Err(e),
2570 }
2571 }
2572
2573 /// Construct and sign a [`ValidatePayload`] using the currently finalized
2574 /// active affidavit key from node-local offchain storage.
2575 ///
2576 /// Performs the following steps in sequence:
2577 /// - retrieves the finalized active affidavit key via [`Self::get_finalized_afdt_key`],
2578 /// - resolves that key to a local public key via [`Self::get_public_key`],
2579 /// - constructs a [`ValidatePayload`] wrapping the resolved public key, and
2580 /// - signs the payload using the configured [`Config::AffidavitCrypto`] scheme.
2581 ///
2582 /// The resulting payload and signature are the exact inputs required by
2583 /// the [`Self::validate`] extrinsic.
2584 ///
2585 /// ## Returns
2586 /// - `Ok((payload, signature))` if the key is finalized, locally available,
2587 /// and signing succeeds.
2588 /// - `Err(CannotSignValidateTxPayload)` if signing fails.
2589 /// - `Err(DispatchError)` if key retrieval or resolution fails.
2590 ///
2591 /// ## Note
2592 /// This function reads from node-local offchain storage and the local keystore.
2593 /// Results are node-specific and may differ across validators.
2594 pub fn sign_validate_payload() -> Result<(ValidatePayloadOf<T>, T::Signature), DispatchError> {
2595 let active_afdt_key = Self::get_finalized_afdt_key()?;
2596 let afdt_pub = Self::get_public_key(active_afdt_key)?;
2597 let payload = ValidatePayload { public: afdt_pub };
2598 let Some(signature) = <ValidatePayload<T::Public> as SignedPayload<T>>::sign::<
2599 T::AffidavitCrypto,
2600 >(&payload) else {
2601 return Err(Error::<T>::CannotSignValidateTxPayload.into());
2602 };
2603 Ok((payload, signature))
2604 }
2605
2606 /// Retrieve the currently revealed elected author set from the election manager.
2607 ///
2608 /// Delegates to [`ElectAuthors::reveal`] and returns the result as a
2609 /// concrete [`ElectionElects`] value.
2610 ///
2611 /// This function does not trigger or re-run an election. It only reads
2612 /// the result of the most recently completed election preparation.
2613 /// If no election has been executed or the result is unavailable,
2614 /// it returns an error.
2615 ///
2616 /// ## Returns
2617 /// - `Ok(ElectionElects)` containing the elected author set.
2618 /// - `Err(UnableToRevealElected)` if no election result is currently available.
2619 pub fn get_elects() -> Result<ElectionElects<T>, DispatchError> {
2620 let Some(elects) = <Internals<T> as ElectAuthors<AuthorOf<T>, ElectionVia<T>>>::reveal() else {
2621 return Err(Error::<T>::UnableToRevealElected.into())
2622 };
2623 Ok(elects)
2624 }
2625
2626 /// Retrieve the submitted affidavit for a given affidavit identifier
2627 /// targeting the **upcoming session's election**.
2628 ///
2629 /// Resolves the affidavit key to its owning author, then returns the
2630 /// author's declared election weights for the next session (current + 1).
2631 ///
2632 /// ## Parameters
2633 /// - `afdt_id`: The affidavit key identifier to look up.
2634 ///
2635 /// ## Returns
2636 /// - `Ok(ElectionVia)` containing the author's declared election weights.
2637 /// - `Err(DispatchError)` otherwise.
2638 pub fn fetch_affidavit(afdt_id: AffidavitId<T>) -> Result<ElectionVia<T>, DispatchError> {
2639 Self::get_affidavit(&afdt_id)
2640 }
2641
2642 /// Retrieve the submitted affidavit for a given affidavit identifier
2643 /// targeting a **specific session**.
2644 ///
2645 /// Unlike [`Self::fetch_affidavit`], which always targets the upcoming session,
2646 /// this function allows querying affidavits for any session by index.
2647 /// It resolves the affidavit key to its registered author for the given session,
2648 /// then returns that author's declared election weights.
2649 ///
2650 /// ## Parameters
2651 /// - `afdt_id`: The affidavit key identifier to look up.
2652 /// - `session`: The session index for which the affidavit is queried.
2653 ///
2654 /// ## Returns
2655 /// - `Ok(ElectionVia)` containing the author's declared election weights for the session.
2656 /// - `Err(AffidavitKeyPairNotFound)` if the key is not registered for the given session.
2657 /// - `Err(AffidavitNotFound)` if the author has not submitted an affidavit for that session.
2658 pub fn fetch_affidavit_for(afdt_id: AffidavitId<T>, session: SessionIndex) -> Result<ElectionVia<T>, DispatchError> {
2659 let Some(author) = AffidavitKeys::<T>::get((session, afdt_id)) else { return Err(Error::<T>::AffidavitKeyPairNotFound.into()) };
2660 let Some((_, affidavit)) = AuthorAffidavits::<T>::get((session, author)) else { return Err(Error::<T>::AffidavitNotFound.into()) };
2661 Ok(affidavit.into_iter().collect())
2662 }
2663
2664 /// Returns `true` if the author has submitted an affidavit and is
2665 /// actively contesting the **upcoming session's election**.
2666 ///
2667 /// An author is considered contesting when a valid affidavit entry exists
2668 /// in storage for the next session (current + 1). This indicates that the
2669 /// author has declared election weights and is a candidate for selection.
2670 ///
2671 /// ## Parameters
2672 /// - `author`: The author whose election candidacy is being checked.
2673 ///
2674 /// ## Returns
2675 /// - `true` if the author has a stored affidavit for the upcoming session.
2676 /// - `false` otherwise, including when the author has never submitted
2677 /// an affidavit or has withdrawn.
2678 pub fn is_contesting(author: AuthorOf<T>) -> bool {
2679 let for_session = CurrentSession::<T>::get().saturating_add(One::one());
2680 let Some((_, _)) = AuthorAffidavits::<T>::get((for_session, author)) else { return false };
2681 true
2682 }
2683
2684 /// Returns `true` if the author is actively **pursuing validation**
2685 /// by maintaining a registered affidavit key for a future session.
2686 ///
2687 /// An author is considered pursuing when they have a registered affidavit key
2688 /// in either the next session or the next affidavit session scope, meaning
2689 /// they have not chilled and intend to participate in upcoming elections.
2690 ///
2691 /// This is the logical inverse of [`Self::is_chilling`].
2692 ///
2693 /// ## Parameters
2694 /// - `author`: The author whose pursuit status is being checked.
2695 ///
2696 /// ## Returns
2697 /// - `true` if the author holds an affidavit key for any relevant future session.
2698 /// - `false` if the author has no registered keys and is effectively chilled.
2699 pub fn is_pursuing(author: AuthorOf<T>) -> bool {
2700 !Self::is_chilling(author)
2701 }
2702
2703 /// Returns `Ok(())` if the author identified by the given affidavit key
2704 /// is eligible to submit an affidavit declaration for the upcoming session.
2705 ///
2706 /// This function performs a full pre-check for the [`Self::declare`] extrinsic,
2707 /// verifying that:
2708 /// - the affidavit key is registered for the upcoming session,
2709 /// - the global [`AllowAffidavits`] flag is enabled,
2710 /// - the author associated with the key is available in the role system, and
2711 /// - the current block falls within the configured affidavit submission window.
2712 ///
2713 /// Intended for use by offchain workers and RPC consumers to determine
2714 /// whether an affidavit declaration can be safely submitted at the current block.
2715 ///
2716 /// ## Parameters
2717 /// - `afdt_id`: The affidavit key identifier to evaluate.
2718 ///
2719 /// ## Returns
2720 /// - `Ok(())` if all conditions for affidavit submission are satisfied.
2721 /// - `Err(DispatchError)` otherwise.
2722 pub fn can_declare(afdt_id: AffidavitId<T>) -> DispatchResult {
2723 let for_session = CurrentSession::<T>::get().saturating_add(One::one());
2724 ensure!(
2725 AffidavitKeys::<T>::contains_key((for_session, &afdt_id)),
2726 Error::<T>::AffidavitAuthorNotFound
2727 );
2728 Self::can_submit_affidavit(&afdt_id)
2729 }
2730
2731 /// Returns `Ok(())` if the given author is eligible to submit an election
2732 /// extrinsic at the current block.
2733 ///
2734 /// This function performs a full pre-check for the [`Self::elect`] extrinsic,
2735 /// verifying that:
2736 /// - the current block has an identifiable block author,
2737 /// - the provided author matches the current block author, and
2738 /// - the current block falls within the configured election window.
2739 ///
2740 /// Only the block author may run the election for a given block, ensuring
2741 /// that election submissions cannot be spoofed by non-producing validators.
2742 ///
2743 /// ## Parameters
2744 /// - `author`: The author asserting eligibility to run the election.
2745 ///
2746 /// ## Returns
2747 /// - `Ok(())` if the author is the current block author and the election window is open.
2748 /// - `Err(DispatchError)` otherwise.
2749 pub fn can_elect(author: AuthorOf<T>) -> DispatchResult {
2750 let block_author = pallet_authorship::Pallet::<T>::author().ok_or(Error::<T>::BlockAuthorNotFound)?;
2751 ensure!(
2752 author == block_author,
2753 Error::<T>::NotABlockAuthor
2754 );
2755 <Internals<T> as ElectAuthors<AuthorOf<T>, ElectionVia<T>>>::can_process_election(&Some(block_author))?;
2756 Ok(())
2757 }
2758
2759 /// Computes the affidavit submission window for the current session.
2760 ///
2761 /// The window is derived from the session start and average session length:
2762 /// ```text
2763 /// start = session_start + (affidavit_begins_at * avg_session_length)
2764 /// end = session_start + (affidavit_ends_at * avg_session_length)
2765 /// ```
2766 ///
2767 /// Note: `affidavit_begins_at` and `affidavit_ends_at` are **percentages**
2768 /// of the session length and are applied to compute block offsets.
2769 ///
2770 /// ## Returns
2771 /// - `Ok(AffidavitWindow)` containing start and end blocks
2772 /// - `DispatchError` otherwise
2773 ///
2774 /// ## Notes
2775 /// - The resulting window is session-relative and recalculated each session.
2776 /// - Affidavits submitted outside this window should be rejected.
2777 pub fn compute_affidavit_window() -> Result<AffidavitWindow<T>, DispatchError> {
2778 let session_start = SessionStartAt::<T>::get();
2779 let avg_session_len =
2780 <<T as crate::Config>::NextSessionRotation as EstimateNextSessionRotation<
2781 BlockNumberFor<T>,
2782 >>::average_session_length();
2783
2784 let begin_at = AffidavitBeginsAt::<T>::get();
2785 let begin_offset = begin_at.mul_floor(avg_session_len);
2786 let start_block = session_start.saturating_add(begin_offset);
2787
2788 let ends_at = AffidavitEndsAt::<T>::get();
2789 let invariant = ends_at > begin_at;
2790 debug_assert!(
2791 invariant,
2792 "invalid affidavit period configured during genesis or
2793 root update to storage values, affidavit begins at {:?}
2794 and ends at {:?}",
2795 begin_at, ends_at
2796 );
2797 ensure!(invariant, Error::<T>::InvalidAffidavitPeriod);
2798 let end_offset = ends_at.mul_floor(avg_session_len);
2799 let end_block = session_start.saturating_add(end_offset);
2800 let aff_window = AffidavitWindow::<T> {
2801 start: start_block,
2802 end: end_block,
2803 };
2804 Ok(aff_window)
2805 }
2806
2807 /// Computes the election window for the current session.
2808 ///
2809 /// The election window is a sub-range of the affidavit window:
2810 /// ```text
2811 /// start = affidavit_start + (election_begins_at * (affidavit_end - affidavit_start))
2812 /// end = affidavit_end
2813 /// ```
2814 ///
2815 /// Note:
2816 /// - `election_begins_at` is a **percentage** of the affidavit window range.
2817 /// - The election always **ends when the affidavit window ends**.
2818 ///
2819 /// ## Diagram
2820 /// ```text
2821 /// |--------- Affidavit Window ---------|
2822 /// |------|-----------------------------|
2823 /// ^ ^
2824 /// election_start election_end (= affidavit_end)
2825 /// ```
2826 ///
2827 /// ## Returns
2828 /// - `Ok(ElectionWindow)` containing start and end blocks
2829 /// - `DispatchError` otherwise
2830 pub fn compute_election_window() -> Result<ElectionWindow<T>, DispatchError> {
2831 let afdt_window = Self::compute_affidavit_window()?;
2832 let start_affidavit = afdt_window.start;
2833 let end_affidavit = afdt_window.end;
2834 let election_begin_at = ElectionBeginsAt::<T>::get();
2835 let affidavit_range = end_affidavit.saturating_sub(start_affidavit);
2836 let start_portion = election_begin_at.mul_floor(affidavit_range);
2837 let start_election = start_affidavit.saturating_add(start_portion);
2838 let elect_window = ElectionWindow::<T> {
2839 start: start_election,
2840 end: end_affidavit,
2841 };
2842 Ok(elect_window)
2843 }
2844 }
2845
2846 // ===============================================================================
2847 // `````````````````````````````` VALIDATE UNSIGNED ``````````````````````````````
2848 // ===============================================================================
2849
2850 impl<T: Config> ValidateUnsigned for Pallet<T> {
2851 type Call = Call<T>;
2852
2853 fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
2854 let Ok(aff_window) = Self::compute_affidavit_window() else {
2855 return InvalidTransaction::BadProof.into();
2856 };
2857 match call {
2858 Call::declare { payload, signature } => {
2859 if !SignedPayload::<T>::verify::<T::AffidavitCrypto>(payload, signature.clone())
2860 {
2861 return InvalidTransaction::BadProof.into();
2862 }
2863
2864 let end_block = aff_window.end;
2865
2866 // Current block number (fork-relative).
2867 // Safe to use for freshness/expiry checks since validity is evaluated per fork head.
2868 let current_block = frame_system::Pallet::<T>::block_number();
2869
2870 // Reject if the affidavit window has expired.
2871 // This is fork-safe because it is a monotonic freshness check.
2872 if current_block > end_block {
2873 return InvalidTransaction::Stale.into();
2874 }
2875
2876 let public = SignedPayload::<T>::public(payload);
2877 let affidavit_pub: AffidavitId<T> = public.clone().into_account().into();
2878
2879 let for_session = CurrentSession::<T>::get().saturating_add(One::one());
2880
2881 // Ensure the signer is registered as a valid affidavit key for that session.
2882 if !AffidavitKeys::<T>::contains_key((for_session, &affidavit_pub)) {
2883 return InvalidTransaction::BadSigner.into();
2884 }
2885
2886 // Longevity: how long (in blocks) this transaction should remain valid in the pool.
2887 // Derived from remaining time until the affidavit window closes.
2888 let longetivity = end_block.saturating_sub(current_block).into();
2889
2890 return ValidTransaction::with_tag_prefix("declare")
2891 .priority(AffidavitTxPriority::<T>::get())
2892 .longevity(longetivity.low_u64())
2893 // Provide a uniqueness tag to prevent duplicate unsigned submissions
2894 // per (session, validator). The tuple is SCALE-encoded and used as the pool key.
2895 .and_provides((for_session, affidavit_pub))
2896 // Allow propagation to other nodes (normal gossip).
2897 .propagate(true)
2898 .build();
2899 }
2900 Call::validate { payload, signature } => {
2901 if !SignedPayload::<T>::verify::<T::AffidavitCrypto>(payload, signature.clone())
2902 {
2903 return InvalidTransaction::BadProof.into();
2904 }
2905 let avg_session_len = <<T as crate::Config>::NextSessionRotation as EstimateNextSessionRotation<BlockNumberFor<T>>>::average_session_length();
2906 let session_start = SessionStartAt::<T>::get();
2907 let current_block = frame_system::Pallet::<T>::block_number();
2908 // Longevity: remaining blocks until end of session.
2909 // This keeps the tx in the pool only while session is still active.
2910 let longetivity = session_start
2911 .saturating_add(avg_session_len)
2912 .saturating_sub(current_block)
2913 .into();
2914
2915 return ValidTransaction::with_tag_prefix("validate")
2916 .priority(ValidateTxPriority::<T>::get())
2917 .longevity(longetivity.low_u64())
2918 // Provide a uniqueness tag based on the SCALE-encoded payload.
2919 // This prevents duplicate submissions of the exact same validation payload
2920 // from coexisting in the transaction pool (replay/spam protection).
2921 .and_provides(payload)
2922 .propagate(true)
2923 .build();
2924 }
2925 Call::elect { payload, signature } => {
2926 if !SignedPayload::<T>::verify::<T::AffidavitCrypto>(payload, signature.clone())
2927 {
2928 return InvalidTransaction::BadProof.into();
2929 }
2930 let end_affidavit = aff_window.end;
2931 let current_block = frame_system::Pallet::<T>::block_number();
2932
2933 // Reject if election period has expired.
2934 // Fork-safe freshness check.
2935 if current_block > end_affidavit {
2936 return InvalidTransaction::Stale.into();
2937 }
2938
2939 // Elections target the session after next (current + 2), since
2940 // affidavit declaration would have resulted in newly rotated key.
2941 let for_session = CurrentSession::<T>::get().saturating_add(2u32.into());
2942
2943 let public = SignedPayload::<T>::public(payload);
2944 let affidavit_pub: AffidavitId<T> = public.clone().into_account().into();
2945
2946 // Ensure signer is eligible for that future session.
2947 if !AffidavitKeys::<T>::contains_key((for_session, &affidavit_pub.clone())) {
2948 return InvalidTransaction::BadSigner.into();
2949 }
2950
2951 // Longevity tied to remaining election window.
2952 let longetivity = end_affidavit.saturating_sub(current_block).into();
2953
2954 return ValidTransaction::with_tag_prefix("elect")
2955 .priority(ElectionTxPriority::<T>::get())
2956 .longevity(longetivity.low_u64())
2957 // Provide uniqueness per (future session, validator)
2958 // to avoid duplicate elections.
2959 .and_provides((for_session, affidavit_pub))
2960 // Do not propagate: only the block author (local node)
2961 // should include this tx, As its checked in the runtime via
2962 // pallet-authorship.
2963 .propagate(false)
2964 .build();
2965 }
2966 _ => InvalidTransaction::Call.into(),
2967 }
2968 }
2969 }
2970}
2971
2972// ===============================================================================
2973// `````````````````````````````````` API TESTS ``````````````````````````````````
2974// ===============================================================================
2975
2976#[cfg(test)]
2977mod ext_tests {
2978
2979 // ===============================================================================
2980 // ``````````````````````````````````` IMPORTS ```````````````````````````````````
2981 // ===============================================================================
2982
2983 // --- Local crate imports ---
2984 use crate::{mock::*, types::ForceGenesisConfig};
2985
2986 // --- Scale-codec crates ---
2987 use codec::{Decode, Encode};
2988
2989 // --- FRAME Suite ---
2990 use frame_suite::{blockchain::*, roles::*};
2991
2992 // --- FRAME Support ---
2993 use frame_support::{
2994 assert_err, assert_noop, assert_ok,
2995 pallet_prelude::{TransactionSource, ValidateUnsigned},
2996 traits::{Hooks, tokens::{Fortitude, Precision}},
2997 };
2998
2999 // --- Substrate primitives ---
3000 use sp_runtime::{
3001 traits::{Convert, Zero},
3002 transaction_validity::InvalidTransaction,
3003 DispatchError, WeakBoundedVec,
3004 };
3005
3006 // ===============================================================================
3007 // ```````````````````````````````````` HOOKS ````````````````````````````````````
3008 // ===============================================================================
3009
3010 #[test]
3011 fn on_initialize_success() {
3012 chain_manager_test_ext().execute_with(|| {
3013 CurrentSession::put(1);
3014 SessionStartsAt::put(10);
3015
3016 set_user_balance_and_hold(ALICE, 100, 100).unwrap();
3017 RoleAdapter::enroll(&ALICE, 100, Fortitude::Force).unwrap();
3018
3019 set_block_author(ALICE);
3020
3021 assert!(PointsAdapter::points_of(&ALICE).is_err());
3022
3023 System::set_block_number(11);
3024 Pallet::on_initialize(11);
3025
3026 // Check that ALICE received a point for this session
3027 let points = PointsAdapter::points_of(&ALICE).unwrap();
3028 assert_eq!(points, 1);
3029 });
3030 }
3031
3032 #[test]
3033 fn ocw_initializes_active_affidavit_key() {
3034 let mut env = new_ocw_env();
3035 env.ext.execute_with(|| {
3036 ocw_step();
3037 assert!(affidavit_key_count().is_zero());
3038 assert!(get_afdt_key().is_none());
3039 ocw_step();
3040 assert!(get_afdt_key().is_some());
3041 let afdt_key_1 = get_afdt_key().unwrap();
3042 assert!(get_public_key(afdt_key_1.clone()).is_some());
3043
3044 ocw_step();
3045 // Since, key is already initialized, no new key is initialized.
3046 assert!(get_afdt_key().is_some());
3047 let afdt_key_2 = get_afdt_key().unwrap();
3048 assert!(get_public_key(afdt_key_2.clone()).is_some());
3049 assert_eq!(afdt_key_1, afdt_key_2);
3050 });
3051 }
3052
3053 #[test]
3054 fn ocw_declares_affidavit() {
3055 let mut env = new_ocw_env();
3056 env.ext.execute_with(|| {
3057 init_fork_graph();
3058 set_session_config();
3059 CurrentSession::set(1);
3060
3061 set_default_user_balance_and_hold(ALICE).unwrap();
3062 set_default_user_balance_and_hold(ALAN).unwrap();
3063
3064 enroll_authors_with_default_collateral(vec![ALICE]).unwrap();
3065 direct_fund_author(ALAN, ALICE, STANDARD_FUND).unwrap();
3066
3067 assert!(affidavit_key_count().is_zero());
3068 assert!(get_finalized_afdt_key().is_none());
3069 while System::block_number() < AFDT_SUBMISSION_START {
3070 ocw_step();
3071 }
3072 assert!(get_afdt_key().is_some());
3073 let afdt_key_1 = get_afdt_key().unwrap();
3074 assert!(get_public_key(afdt_key_1.clone()).is_some());
3075 assert!(get_next_afdt_key().is_some());
3076
3077 let afdt_key = get_afdt_key().unwrap();
3078 let for_session = CurrentSession::get() + 1;
3079 AffidavitKeys::insert((for_session, afdt_key.clone()), ALICE);
3080
3081 env.pool_state.write().transactions.clear();
3082
3083 let tx = env.pool_state.read().transactions.clone();
3084 assert_eq!(tx.len(), 0);
3085 ocw_step();
3086 let tx = env.pool_state.read().transactions.clone();
3087 assert_eq!(tx.len(), 1);
3088 let submited_ext = env.pool_state.read().transactions.last().unwrap().clone();
3089 let ext_decode = UncheckedExtrinsic::decode(&mut &submited_ext[..]).unwrap();
3090 assert!(matches!(
3091 ext_decode.function,
3092 RuntimeCall::ChainManager(crate::Call::declare { .. })
3093 ));
3094 ocw_step();
3095 let tx = env.pool_state.read().transactions.clone();
3096 assert_eq!(tx.len(), 2);
3097 let submited_ext = env.pool_state.read().transactions.last().unwrap().clone();
3098 let ext_decode = UncheckedExtrinsic::decode(&mut &submited_ext[..]).unwrap();
3099 assert!(matches!(
3100 ext_decode.function,
3101 RuntimeCall::ChainManager(crate::Call::declare { .. })
3102 ));
3103 })
3104 }
3105
3106 #[test]
3107 fn ocw_submits_election_tx_when_eligible() {
3108 let mut env = new_ocw_env();
3109 env.ext.execute_with(|| {
3110 set_session_config();
3111 CurrentSession::put(1);
3112
3113 set_default_user_balance_and_hold(ALICE).unwrap();
3114 set_default_user_balance_and_hold(ALAN).unwrap();
3115
3116 enroll_authors_with_default_collateral(vec![ALICE]).unwrap();
3117 direct_fund_author(ALAN, ALICE, STANDARD_FUND).unwrap();
3118
3119 // prepare active key first.
3120 while System::block_number() < AFDT_SUBMISSION_START {
3121 ocw_step();
3122 }
3123
3124 let afdt_key = get_afdt_key().unwrap();
3125 let for_session = CurrentSession::get() + 1;
3126 AffidavitKeys::insert((for_session, afdt_key.clone()), ALICE);
3127
3128 ocw_step();
3129
3130 let tx = env.pool_state.read().transactions.clone();
3131 assert_eq!(tx.len(), 1);
3132 let submited_ext = env.pool_state.read().transactions.last().unwrap().clone();
3133 let ext_decode = UncheckedExtrinsic::decode(&mut &submited_ext[..]).unwrap();
3134 assert!(matches!(
3135 ext_decode.function,
3136 RuntimeCall::ChainManager(crate::Call::declare { .. })
3137 ));
3138 // declare executed
3139 let next_afdt_key = get_next_afdt_key().unwrap();
3140 AffidavitKeys::insert((for_session + 1, next_afdt_key), ALICE);
3141
3142 // move into just before election-eligible block
3143 while System::block_number() < ELECTION_START {
3144 ocw_step();
3145 }
3146 env.pool_state.write().transactions.clear();
3147
3148 let tx = env.pool_state.read().transactions.clone();
3149 assert_eq!(tx.len(), 0);
3150 ocw_step();
3151 let tx = env.pool_state.read().transactions.clone();
3152 assert_eq!(tx.len(), 1);
3153 let submited_ext = env.pool_state.read().transactions.last().unwrap().clone();
3154 let ext_decode = UncheckedExtrinsic::decode(&mut &submited_ext[..]).unwrap();
3155 assert!(matches!(
3156 ext_decode.function,
3157 RuntimeCall::ChainManager(crate::Call::elect { .. })
3158 ));
3159 });
3160 }
3161
3162 // ===============================================================================
3163 // `````````````````````````````` VALIDATE UNSIGNED ``````````````````````````````
3164 // ===============================================================================
3165
3166 #[test]
3167 fn validate_unsigned_validate_accepts_valid_payload() {
3168 let mut env = new_ocw_env();
3169 env.ext.execute_with(|| {
3170 set_session(1);
3171 set_session_config();
3172 System::set_block_number(1);
3173
3174 let public = generate_affidavit_keypair();
3175
3176 let payload = ValidatePayloadOf {
3177 public: public.clone(),
3178 };
3179 let signature = sign_payload(&payload.encode(), public);
3180
3181 let call = Call::validate { payload, signature };
3182
3183 let validity = Pallet::validate_unsigned(TransactionSource::External, &call);
3184
3185 let valid = validity.unwrap();
3186 assert_eq!(valid.priority, ValidateTxPriority::get());
3187 assert_eq!(valid.longevity, 600);
3188 assert!(valid.propagate);
3189 });
3190 }
3191
3192 #[test]
3193 fn validate_unsigned_validate_rejects_bad_signature() {
3194 let mut env = new_ocw_env();
3195 env.ext.execute_with(|| {
3196 set_session(1);
3197 set_session_config();
3198 System::set_block_number(1);
3199
3200 let public = generate_affidavit_keypair();
3201 let wrong_public = generate_affidavit_keypair();
3202
3203 let payload = ValidatePayloadOf {
3204 public: public.clone(),
3205 };
3206 let bad_signature = sign_payload(&payload.encode(), wrong_public);
3207
3208 let call = Call::validate {
3209 payload,
3210 signature: bad_signature,
3211 };
3212
3213 let validity = Pallet::validate_unsigned(TransactionSource::External, &call);
3214
3215 assert_err!(validity, InvalidTransaction::BadProof);
3216 });
3217 }
3218
3219 #[test]
3220 fn validate_unsigned_declare_affidavit_accepts_registered_key_within_window() {
3221 let mut env = new_ocw_env();
3222 env.ext.execute_with(|| {
3223 set_session(1);
3224 set_session_config();
3225 System::set_block_number(AFDT_SUBMISSION_START);
3226
3227 let afdt_id = generate_affidavit_id();
3228 let public = get_public_key(afdt_id.clone()).unwrap();
3229 let next_afdt_id = generate_affidavit_id();
3230
3231 let for_session = CurrentSession::get() + 1;
3232 register_affidavit_key(for_session, afdt_id, ALICE);
3233
3234 let payload = AffidavitPayloadOf {
3235 public: public.clone(),
3236 rotate: next_afdt_id,
3237 };
3238 let signature = sign_payload(&payload.encode(), public);
3239
3240 let call = Call::declare { payload, signature };
3241
3242 let validity = Pallet::validate_unsigned(TransactionSource::External, &call);
3243
3244 let valid = validity.unwrap();
3245 assert_eq!(valid.priority, AffidavitTxPriority::get());
3246 assert_eq!(valid.longevity, AFDT_SUBMISSION_END - AFDT_SUBMISSION_START);
3247 assert!(valid.propagate);
3248 });
3249 }
3250
3251 #[test]
3252 fn validate_unsigned_declare_affidavit_rejects_bad_signature() {
3253 let mut env = new_ocw_env();
3254 env.ext.execute_with(|| {
3255 set_session(1);
3256 set_session_config();
3257 System::set_block_number(AFDT_SUBMISSION_START);
3258
3259 let afdt_id = generate_affidavit_id();
3260 let public = get_public_key(afdt_id.clone()).unwrap();
3261 let wrong_public = generate_affidavit_keypair();
3262 let next_afdt_id = generate_affidavit_id();
3263
3264 let for_session = CurrentSession::get() + 1;
3265 register_affidavit_key(for_session, afdt_id, ALICE);
3266
3267 let payload = AffidavitPayloadOf {
3268 public: public.clone(),
3269 rotate: next_afdt_id,
3270 };
3271 let bad_signature = sign_payload(&payload.encode(), wrong_public);
3272
3273 let call = Call::declare {
3274 payload,
3275 signature: bad_signature,
3276 };
3277
3278 let validity = Pallet::validate_unsigned(TransactionSource::External, &call);
3279
3280 assert_err!(validity, InvalidTransaction::BadProof);
3281 });
3282 }
3283
3284 #[test]
3285 fn validate_unsigned_declare_affidavit_rejects_unregistered_key() {
3286 let mut env = new_ocw_env();
3287 env.ext.execute_with(|| {
3288 set_session(1);
3289 set_session_config();
3290 System::set_block_number(AFDT_SUBMISSION_START);
3291
3292 let afdt_id = generate_affidavit_id();
3293 let public = get_public_key(afdt_id).unwrap();
3294 let next_afdt_id = generate_affidavit_id();
3295
3296 let payload = AffidavitPayloadOf {
3297 public: public.clone(),
3298 rotate: next_afdt_id,
3299 };
3300 let signature = sign_payload(&payload.encode(), public);
3301
3302 let call = Call::declare { payload, signature };
3303
3304 let validity = Pallet::validate_unsigned(TransactionSource::External, &call);
3305
3306 assert_err!(validity, InvalidTransaction::BadSigner);
3307 });
3308 }
3309
3310 #[test]
3311 fn validate_unsigned_declare_affidavit_rejects_stale_call() {
3312 let mut env = new_ocw_env();
3313 env.ext.execute_with(|| {
3314 set_session(1);
3315 set_session_config();
3316 System::set_block_number(AFDT_SUBMISSION_END + 1);
3317
3318 let afdt_id = generate_affidavit_id();
3319 let public = get_public_key(afdt_id.clone()).unwrap();
3320 let next_afdt_id = generate_affidavit_id();
3321
3322 let for_session = CurrentSession::get() + 1;
3323 register_affidavit_key(for_session, afdt_id, ALICE);
3324
3325 let payload = AffidavitPayloadOf {
3326 public: public.clone(),
3327 rotate: next_afdt_id,
3328 };
3329 let signature = sign_payload(&payload.encode(), public);
3330
3331 let call = Call::declare { payload, signature };
3332
3333 let validity = Pallet::validate_unsigned(TransactionSource::External, &call);
3334
3335 assert_err!(validity, InvalidTransaction::Stale);
3336 });
3337 }
3338
3339 #[test]
3340 fn validate_unsigned_elect_authors_accepts_registered_rotated_key() {
3341 let mut env = new_ocw_env();
3342 env.ext.execute_with(|| {
3343 set_session(1);
3344 set_session_config();
3345 System::set_block_number(ELECTION_START);
3346
3347 let afdt_id = generate_affidavit_id();
3348 let public = get_public_key(afdt_id.clone()).unwrap();
3349
3350 let for_session = CurrentSession::get() + 2;
3351 register_affidavit_key(for_session, afdt_id, ALICE);
3352
3353 let payload = ElectionPayloadOf {
3354 public: public.clone(),
3355 };
3356 let signature = sign_payload(&payload.encode(), public);
3357
3358 let call = Call::elect { payload, signature };
3359
3360 let validity = Pallet::validate_unsigned(TransactionSource::Local, &call);
3361
3362 let valid = validity.unwrap();
3363 assert_eq!(valid.priority, ElectionTxPriority::get());
3364 assert_eq!(valid.longevity, AFDT_SUBMISSION_END - ELECTION_START);
3365 assert!(!valid.propagate);
3366 });
3367 }
3368
3369 #[test]
3370 fn validate_unsigned_elect_authors_rejects_bad_signature() {
3371 let mut env = new_ocw_env();
3372 env.ext.execute_with(|| {
3373 set_session(1);
3374 set_session_config();
3375 System::set_block_number(ELECTION_START);
3376
3377 let afdt_id = generate_affidavit_id();
3378 let public = get_public_key(afdt_id.clone()).unwrap();
3379 let wrong_public = generate_affidavit_keypair();
3380
3381 let for_session = CurrentSession::get() + 2;
3382 register_affidavit_key(for_session, afdt_id, ALICE);
3383
3384 let payload = ElectionPayloadOf {
3385 public: public.clone(),
3386 };
3387 let bad_signature = sign_payload(&payload.encode(), wrong_public);
3388
3389 let call = Call::elect {
3390 payload,
3391 signature: bad_signature,
3392 };
3393
3394 let validity = Pallet::validate_unsigned(TransactionSource::Local, &call);
3395
3396 assert_err!(validity, InvalidTransaction::BadProof);
3397 });
3398 }
3399
3400 #[test]
3401 fn validate_unsigned_elect_authors_rejects_unregistered_key() {
3402 let mut env = new_ocw_env();
3403 env.ext.execute_with(|| {
3404 set_session(1);
3405 set_session_config();
3406 System::set_block_number(ELECTION_START);
3407
3408 let afdt_id = generate_affidavit_id();
3409 let public = get_public_key(afdt_id).unwrap();
3410
3411 let payload = ElectionPayloadOf {
3412 public: public.clone(),
3413 };
3414 let signature = sign_payload(&payload.encode(), public);
3415
3416 let call = Call::elect { payload, signature };
3417
3418 let validity = Pallet::validate_unsigned(TransactionSource::Local, &call);
3419
3420 assert_err!(validity, InvalidTransaction::BadSigner);
3421 });
3422 }
3423
3424 #[test]
3425 fn validate_unsigned_elect_authors_rejects_stale_call() {
3426 let mut env = new_ocw_env();
3427 env.ext.execute_with(|| {
3428 set_session(1);
3429 set_session_config();
3430 System::set_block_number(AFDT_SUBMISSION_END + 1);
3431
3432 let afdt_id = generate_affidavit_id();
3433 let public = get_public_key(afdt_id.clone()).unwrap();
3434
3435 let for_session = CurrentSession::get() + 2;
3436 register_affidavit_key(for_session, afdt_id, ALICE);
3437
3438 let payload = ElectionPayloadOf {
3439 public: public.clone(),
3440 };
3441 let signature = sign_payload(&payload.encode(), public);
3442
3443 let call = Call::elect { payload, signature };
3444
3445 let validity = Pallet::validate_unsigned(TransactionSource::Local, &call);
3446
3447 assert_err!(validity, InvalidTransaction::Stale);
3448 });
3449 }
3450
3451 // ===============================================================================
3452 // ````````````````````````````````` EXTRINSICS ``````````````````````````````````
3453 // ===============================================================================
3454
3455 #[test]
3456 fn validate_success() {
3457 let mut env = new_ocw_env();
3458 env.ext.execute_with(|| {
3459 init_fork_graph();
3460 set_session_config();
3461 CurrentSession::put(1);
3462
3463 set_default_user_balance_and_hold(ALICE).unwrap();
3464
3465 enroll_author_with_default_collateral(ALICE).unwrap();
3466
3467 let afdt_id = generate_affidavit_id();
3468 insert_active_afdt_key(afdt_id.clone()).unwrap();
3469 run_to_block(120);
3470
3471 assert!(!AffidavitKeys::contains_key((2, afdt_id.clone())));
3472
3473 let (payload, signature) = Pallet::sign_validate_payload().unwrap();
3474 assert_ok!(Pallet::validate(
3475 RuntimeOrigin::signed(ALICE),
3476 payload,
3477 signature,
3478 ));
3479
3480 assert!(AffidavitKeys::contains_key((2, afdt_id)));
3481 })
3482 }
3483
3484 #[test]
3485 fn validate_err_bad_origin() {
3486 let mut env = new_ocw_env();
3487 env.ext.execute_with(|| {
3488 init_fork_graph();
3489 set_session_config();
3490 CurrentSession::put(1);
3491
3492 set_default_user_balance_and_hold(ALICE).unwrap();
3493
3494 enroll_author_with_default_collateral(ALICE).unwrap();
3495
3496 let afdt_id = generate_affidavit_id();
3497 insert_active_afdt_key(afdt_id.clone()).unwrap();
3498 run_to_block(120);
3499
3500 let (payload, signature) = Pallet::sign_validate_payload().unwrap();
3501 assert_noop!(
3502 Pallet::validate(RuntimeOrigin::root(), payload, signature,),
3503 DispatchError::BadOrigin
3504 );
3505 })
3506 }
3507
3508 #[test]
3509 fn chill_err_session_id_query_failed() {
3510 let mut env = new_ocw_env();
3511 env.ext.execute_with(|| {
3512 CurrentSession::put(1);
3513
3514 set_default_user_balance_and_hold(ALICE).unwrap();
3515 RoleAdapter::enroll(&ALICE, STANDARD_COLLATERAL, Fortitude::Force).unwrap();
3516
3517 let afdt_id = generate_affidavit_id();
3518
3519 assert_noop!(
3520 Pallet::chill(RuntimeOrigin::signed(BOB), afdt_id),
3521 Error::SessionIdQueryFailed
3522 );
3523 })
3524 }
3525
3526 #[test]
3527 fn chill_err_validator_cannot_chill() {
3528 let mut env = new_ocw_env();
3529 env.ext.execute_with(|| {
3530 CurrentSession::put(1);
3531
3532 set_default_user_balance_and_hold(ALICE).unwrap();
3533 RoleAdapter::enroll(&ALICE, STANDARD_COLLATERAL, Fortitude::Force).unwrap();
3534
3535 let afdt_id = generate_affidavit_id();
3536
3537 let alice_session_id = Pallet::convert(ALICE.clone()).unwrap();
3538 Validators::put(vec![alice_session_id]);
3539
3540 assert_err!(
3541 Pallet::chill(RuntimeOrigin::signed(ALICE), afdt_id),
3542 Error::ValidatorCannotChill
3543 )
3544 })
3545 }
3546
3547 #[test]
3548 fn chill_err_author_not_affidavit_owner() {
3549 let mut env = new_ocw_env();
3550 env.ext.execute_with(|| {
3551 set_session_config();
3552 CurrentSession::put(1);
3553
3554 set_default_user_balance_and_hold(ALICE).unwrap();
3555 set_default_user_balance_and_hold(BOB).unwrap();
3556
3557 RoleAdapter::enroll(&ALICE, STANDARD_COLLATERAL, Fortitude::Force).unwrap();
3558 RoleAdapter::enroll(&BOB, STANDARD_COLLATERAL, Fortitude::Force).unwrap();
3559
3560 let afdt_id = generate_affidavit_id();
3561
3562 System::set_block_number(150);
3563 ext_validate(ALICE, afdt_id.clone()).unwrap();
3564
3565 let nxt_afdt_id = generate_affidavit_id();
3566 let payload = TestAfdtPayload {
3567 active_afdt_pub: afdt_id.clone(),
3568 next_afdt_pub: nxt_afdt_id.clone(),
3569 };
3570 System::set_block_number(AFDT_SUBMISSION_START);
3571 ext_declare_affidavit(ALICE, payload).unwrap();
3572
3573 assert_err!(
3574 Pallet::chill(RuntimeOrigin::signed(BOB), nxt_afdt_id),
3575 Error::AuthorNotAffidavitOwner
3576 )
3577 })
3578 }
3579
3580 #[test]
3581 #[should_panic]
3582 fn chill_err_declared_before_affidavit_period() {
3583 let mut env = new_ocw_env();
3584 env.ext.execute_with(|| {
3585 set_session_config();
3586 CurrentSession::put(1);
3587
3588 set_default_user_balance_and_hold(ALICE).unwrap();
3589
3590 RoleAdapter::enroll(&ALICE, STANDARD_COLLATERAL, Fortitude::Force).unwrap();
3591
3592 let afdt_id = generate_affidavit_id();
3593
3594 System::set_block_number(150);
3595 ext_validate(ALICE, afdt_id.clone()).unwrap();
3596
3597 let nxt_afdt_id = generate_affidavit_id();
3598 let payload = TestAfdtPayload {
3599 active_afdt_pub: afdt_id.clone(),
3600 next_afdt_pub: nxt_afdt_id.clone(),
3601 };
3602 System::set_block_number(AFDT_SUBMISSION_START);
3603 ext_declare_affidavit(ALICE, payload).unwrap();
3604
3605 System::set_block_number(AFDT_SUBMISSION_START - 1);
3606 Pallet::chill(RuntimeOrigin::signed(ALICE), nxt_afdt_id).unwrap();
3607 })
3608 }
3609
3610 #[test]
3611 fn chill_err_candidate_cannot_chill() {
3612 let mut env = new_ocw_env();
3613 env.ext.execute_with(|| {
3614 set_session_config();
3615 CurrentSession::put(1);
3616
3617 set_default_user_balance_and_hold(ALICE).unwrap();
3618
3619 RoleAdapter::enroll(&ALICE, STANDARD_COLLATERAL, Fortitude::Force).unwrap();
3620
3621 let afdt_id = generate_affidavit_id();
3622
3623 System::set_block_number(150);
3624 ext_validate(ALICE, afdt_id.clone()).unwrap();
3625
3626 let nxt_afdt_id = generate_affidavit_id();
3627 let payload = TestAfdtPayload {
3628 active_afdt_pub: afdt_id.clone(),
3629 next_afdt_pub: nxt_afdt_id.clone(),
3630 };
3631 System::set_block_number(AFDT_SUBMISSION_START);
3632 ext_declare_affidavit(ALICE, payload).unwrap();
3633
3634 assert_noop!(
3635 Pallet::chill(RuntimeOrigin::signed(ALICE), nxt_afdt_id),
3636 Error::CandidateCannotChill
3637 );
3638 })
3639 }
3640
3641 #[test]
3642 fn chill_err_elected_cannot_chill() {
3643 let mut env = new_ocw_env();
3644 env.ext.execute_with(|| {
3645 set_session_config();
3646 CurrentSession::put(1);
3647
3648 set_default_user_balance_and_hold(ALICE).unwrap();
3649 set_default_user_balance_and_hold(BOB).unwrap();
3650 set_default_user_balance_and_hold(ALAN).unwrap();
3651 set_default_user_balance_and_hold(CHARLIE).unwrap();
3652 set_default_user_balance_and_hold(AMY).unwrap();
3653 set_default_user_balance_and_hold(NIX).unwrap();
3654
3655 enroll_authors_with_default_collateral(vec![ALICE, BOB, ALAN]).unwrap();
3656 direct_fund_author(NIX, ALICE, STANDARD_FUND).unwrap();
3657 direct_fund_author(AMY, BOB, SMALL_FUND).unwrap();
3658 direct_fund_author(CHARLIE, ALAN, LARGE_FUND).unwrap();
3659
3660 let alice_afdt_id = generate_affidavit_id();
3661 let alan_afdt_id = generate_affidavit_id();
3662 let bob_afdt_id = generate_affidavit_id();
3663
3664 System::set_block_number(150);
3665 ext_validate(ALICE, alice_afdt_id.clone()).unwrap();
3666 ext_validate(BOB, bob_afdt_id.clone()).unwrap();
3667 ext_validate(ALAN, alan_afdt_id.clone()).unwrap();
3668
3669 let alice_nxt_afdt_id = generate_affidavit_id();
3670 let alan_nxt_afdt_id: AccountId = generate_affidavit_id();
3671 let bob_nxt_afdt_id: AccountId = generate_affidavit_id();
3672
3673 let alice_payload = TestAfdtPayload {
3674 active_afdt_pub: alice_afdt_id.clone(),
3675 next_afdt_pub: alice_nxt_afdt_id.clone(),
3676 };
3677
3678 let alan_payload = TestAfdtPayload {
3679 active_afdt_pub: alan_afdt_id.clone(),
3680 next_afdt_pub: alan_nxt_afdt_id.clone(),
3681 };
3682
3683 let bob_payload = TestAfdtPayload {
3684 active_afdt_pub: bob_afdt_id.clone(),
3685 next_afdt_pub: bob_nxt_afdt_id.clone(),
3686 };
3687
3688 System::set_block_number(AFDT_SUBMISSION_START);
3689 ext_declare_affidavit(ALICE, alice_payload).unwrap();
3690 ext_declare_affidavit(ALAN, alan_payload).unwrap();
3691 ext_declare_affidavit(BOB, bob_payload).unwrap();
3692
3693 System::set_block_number(ELECTION_START);
3694 let elected = ext_elect_authors(ALICE, alice_nxt_afdt_id.clone()).unwrap();
3695 assert!(elected.iter().any(|id| *id == ALICE));
3696
3697 System::set_block_number(AFDT_SUBMISSION_END + 1);
3698 assert_noop!(
3699 Pallet::chill(RuntimeOrigin::signed(ALICE), alice_nxt_afdt_id),
3700 Error::ElectedCannotChill
3701 );
3702 })
3703 }
3704
3705 #[test]
3706 fn chill_err_author_not_affidavit_owner_of_the_registered_key() {
3707 let mut env = new_ocw_env();
3708 env.ext.execute_with(|| {
3709 set_session_config();
3710 CurrentSession::put(1);
3711
3712 set_default_user_balance_and_hold(ALICE).unwrap();
3713 set_default_user_balance_and_hold(BOB).unwrap();
3714
3715 RoleAdapter::enroll(&ALICE, STANDARD_COLLATERAL, Fortitude::Force).unwrap();
3716 RoleAdapter::enroll(&BOB, STANDARD_COLLATERAL, Fortitude::Force).unwrap();
3717
3718 let afdt_id = generate_affidavit_id();
3719
3720 System::set_block_number(150);
3721 ext_validate(ALICE, afdt_id.clone()).unwrap();
3722
3723 assert_noop!(
3724 Pallet::chill(RuntimeOrigin::signed(BOB), afdt_id),
3725 Error::AuthorNotAffidavitOwner
3726 );
3727 })
3728 }
3729
3730 #[test]
3731 fn chill_err_invalid_rotated_affidavit_key() {
3732 let mut env = new_ocw_env();
3733 env.ext.execute_with(|| {
3734 set_session_config();
3735 CurrentSession::put(1);
3736 set_default_user_balance_and_hold(ALICE).unwrap();
3737 RoleAdapter::enroll(&ALICE, STANDARD_COLLATERAL, Fortitude::Force).unwrap();
3738
3739 let afdt_id = generate_affidavit_id();
3740 System::set_block_number(150);
3741 ext_validate(ALICE, afdt_id.clone()).unwrap();
3742 let gen_afdt = Pallet::gen_affidavit(&afdt_id).unwrap();
3743 Pallet::submit_affidavit(&afdt_id, &gen_afdt).unwrap();
3744 assert_noop!(
3745 Pallet::chill(RuntimeOrigin::signed(ALICE), afdt_id),
3746 Error::InvalidRotatedAffidavitKey
3747 );
3748 })
3749 }
3750
3751 #[test]
3752 fn chill_success_chilling_unelected_author_after_election() {
3753 let mut env = new_ocw_env();
3754 env.ext.execute_with(|| {
3755 set_session_config();
3756 CurrentSession::put(1);
3757
3758 set_default_user_balance_and_hold(ALICE).unwrap();
3759 set_default_user_balance_and_hold(BOB).unwrap();
3760 set_default_user_balance_and_hold(ALAN).unwrap();
3761 set_default_user_balance_and_hold(CHARLIE).unwrap();
3762 set_default_user_balance_and_hold(AMY).unwrap();
3763 set_default_user_balance_and_hold(NIX).unwrap();
3764 set_default_user_balance_and_hold(MIKE).unwrap();
3765 set_default_user_balance_and_hold(LAYA).unwrap();
3766 set_default_user_balance_and_hold(DAVE).unwrap();
3767 set_default_user_balance_and_hold(JIM).unwrap();
3768
3769 let authors = vec![ALICE, BOB, ALAN, MIKE, LAYA, DAVE, JIM];
3770 enroll_authors_with_default_collateral(authors.clone()).unwrap();
3771 direct_fund_author(NIX, ALICE, STANDARD_FUND).unwrap();
3772 direct_fund_author(AMY, BOB, SMALL_FUND).unwrap();
3773 direct_fund_author(CHARLIE, ALAN, LARGE_FUND).unwrap();
3774
3775 let alice_afdt_id = generate_affidavit_id();
3776 let alan_afdt_id = generate_affidavit_id();
3777 let bob_afdt_id = generate_affidavit_id();
3778 let mike_afdt_id = generate_affidavit_id();
3779 let laya_afdt_id = generate_affidavit_id();
3780 let dev_afdt_id = generate_affidavit_id();
3781 let jim_afdt_id = generate_affidavit_id();
3782
3783 System::set_block_number(150);
3784 ext_validate(ALICE, alice_afdt_id.clone()).unwrap();
3785 ext_validate(BOB, bob_afdt_id.clone()).unwrap();
3786 ext_validate(ALAN, alan_afdt_id.clone()).unwrap();
3787 ext_validate(MIKE, mike_afdt_id.clone()).unwrap();
3788 ext_validate(LAYA, laya_afdt_id.clone()).unwrap();
3789 ext_validate(DAVE, dev_afdt_id.clone()).unwrap();
3790 ext_validate(JIM, jim_afdt_id.clone()).unwrap();
3791
3792 let alice_nxt_afdt_id = generate_affidavit_id();
3793 let alan_nxt_afdt_id = generate_affidavit_id();
3794 let bob_nxt_afdt_id = generate_affidavit_id();
3795 let jim_nxt_afdt_id = generate_affidavit_id();
3796 let mike_nxt_afdt_id = generate_affidavit_id();
3797 let dev_nxt_afdt_id = generate_affidavit_id();
3798 let laya_nxt_afdt_id = generate_affidavit_id();
3799
3800 let alice_payload = TestAfdtPayload {
3801 active_afdt_pub: alice_afdt_id.clone(),
3802 next_afdt_pub: alice_nxt_afdt_id.clone(),
3803 };
3804
3805 let alan_payload = TestAfdtPayload {
3806 active_afdt_pub: alan_afdt_id.clone(),
3807 next_afdt_pub: alan_nxt_afdt_id.clone(),
3808 };
3809
3810 let bob_payload = TestAfdtPayload {
3811 active_afdt_pub: bob_afdt_id.clone(),
3812 next_afdt_pub: bob_nxt_afdt_id.clone(),
3813 };
3814
3815 let mike_payload = TestAfdtPayload {
3816 active_afdt_pub: mike_afdt_id.clone(),
3817 next_afdt_pub: mike_nxt_afdt_id.clone(),
3818 };
3819
3820 let laya_payload = TestAfdtPayload {
3821 active_afdt_pub: laya_afdt_id.clone(),
3822 next_afdt_pub: laya_nxt_afdt_id.clone(),
3823 };
3824
3825 let jim_payload = TestAfdtPayload {
3826 active_afdt_pub: jim_afdt_id.clone(),
3827 next_afdt_pub: jim_nxt_afdt_id.clone(),
3828 };
3829
3830 let dev_payload = TestAfdtPayload {
3831 active_afdt_pub: dev_afdt_id.clone(),
3832 next_afdt_pub: dev_nxt_afdt_id.clone(),
3833 };
3834
3835 System::set_block_number(AFDT_SUBMISSION_START);
3836 ext_declare_affidavit(ALICE, alice_payload).unwrap();
3837 ext_declare_affidavit(ALAN, alan_payload).unwrap();
3838 ext_declare_affidavit(BOB, bob_payload).unwrap();
3839 ext_declare_affidavit(MIKE, mike_payload).unwrap();
3840 ext_declare_affidavit(LAYA, laya_payload).unwrap();
3841 ext_declare_affidavit(JIM, jim_payload).unwrap();
3842 ext_declare_affidavit(DAVE, dev_payload).unwrap();
3843
3844 System::set_block_number(ELECTION_START);
3845 let elected = ext_elect_authors(ALICE, alice_nxt_afdt_id.clone()).unwrap();
3846 let mut not_elected = ALICE;
3847 for author in authors {
3848 if !elected.contains(&author) {
3849 not_elected = author
3850 }
3851 }
3852 // dbg!(not_elected.clone());
3853 assert!(not_elected == JIM);
3854 System::set_block_number(AFDT_SUBMISSION_END + 1);
3855 assert_ok!(Pallet::chill(
3856 RuntimeOrigin::signed(JIM),
3857 jim_nxt_afdt_id.clone()
3858 ),);
3859 let nxt_afdt_session = CurrentSession::get() + 2;
3860 assert!(!AffidavitKeys::contains_key((
3861 nxt_afdt_session,
3862 jim_nxt_afdt_id
3863 )))
3864 })
3865 }
3866
3867 #[test]
3868 fn chill_success_before_start_affidavit() {
3869 let mut env = new_ocw_env();
3870 env.ext.execute_with(|| {
3871 set_session_config();
3872 CurrentSession::put(1);
3873
3874 set_default_user_balance_and_hold(ALICE).unwrap();
3875 RoleAdapter::enroll(&ALICE, 200, Fortitude::Force).unwrap();
3876
3877 let afdt_pub = generate_affidavit_id();
3878 System::set_block_number(AFDT_SUBMISSION_START - 25);
3879 ext_validate(ALICE, afdt_pub.clone()).unwrap();
3880
3881 assert_ok!(Pallet::chill(
3882 RuntimeOrigin::signed(ALICE),
3883 afdt_pub.clone()
3884 ));
3885 let for_session = CurrentSession::get() + 1;
3886 assert!(!AffidavitKeys::contains_key((
3887 for_session,
3888 afdt_pub.clone()
3889 )));
3890 })
3891 }
3892
3893 #[test]
3894 fn chill_success_during_affidavit_period_but_before_declaring() {
3895 let mut env = new_ocw_env();
3896 env.ext.execute_with(|| {
3897 set_session_config();
3898 CurrentSession::put(1);
3899
3900 set_default_user_balance_and_hold(ALICE).unwrap();
3901 RoleAdapter::enroll(&ALICE, 200, Fortitude::Force).unwrap();
3902
3903 let afdt_pub = generate_affidavit_id();
3904 System::set_block_number(AFDT_SUBMISSION_START - 25);
3905 ext_validate(ALICE, afdt_pub.clone()).unwrap();
3906 System::set_block_number(AFDT_SUBMISSION_START + 50);
3907 assert_ok!(Pallet::chill(
3908 RuntimeOrigin::signed(ALICE),
3909 afdt_pub.clone()
3910 ));
3911 let for_session = CurrentSession::get() + 1;
3912 assert!(!AffidavitKeys::contains_key((
3913 for_session,
3914 afdt_pub.clone()
3915 )));
3916 })
3917 }
3918
3919 #[test]
3920 fn chill_success_after_end_affidavit_without_declaring() {
3921 let mut env = new_ocw_env();
3922 env.ext.execute_with(|| {
3923 set_session_config();
3924 CurrentSession::put(1);
3925
3926 set_default_user_balance_and_hold(ALICE).unwrap();
3927 RoleAdapter::enroll(&ALICE, 200, Fortitude::Force).unwrap();
3928
3929 let afdt_pub = generate_affidavit_id();
3930 System::set_block_number(AFDT_SUBMISSION_START - 25);
3931 ext_validate(ALICE, afdt_pub.clone()).unwrap();
3932
3933 System::set_block_number(AFDT_SUBMISSION_END + 1);
3934 assert_ok!(Pallet::chill(
3935 RuntimeOrigin::signed(ALICE),
3936 afdt_pub.clone()
3937 ));
3938 let for_session = CurrentSession::get() + 1;
3939 assert!(!AffidavitKeys::contains_key((
3940 for_session,
3941 afdt_pub.clone()
3942 )));
3943 })
3944 }
3945
3946 #[test]
3947 fn chill_err_bad_origin() {
3948 let mut env = new_ocw_env();
3949 env.ext.execute_with(|| {
3950 set_session_config();
3951 CurrentSession::put(1);
3952
3953 set_default_user_balance_and_hold(ALICE).unwrap();
3954 RoleAdapter::enroll(&ALICE, 200, Fortitude::Force).unwrap();
3955
3956 let afdt_pub = generate_affidavit_id();
3957
3958 let for_session = CurrentSession::get() + 1;
3959 ext_validate(ALICE, afdt_pub.clone()).unwrap();
3960 System::set_block_number(AFDT_SUBMISSION_START - 25);
3961 assert_noop!(
3962 Pallet::chill(RuntimeOrigin::root(), afdt_pub.clone()),
3963 sp_runtime::DispatchError::BadOrigin
3964 );
3965 assert!(AffidavitKeys::contains_key((for_session, afdt_pub.clone())));
3966 })
3967 }
3968
3969 #[test]
3970 fn declare_affidavit_success() {
3971 let mut env = new_ocw_env();
3972 env.ext.execute_with(|| {
3973 init_fork_graph();
3974 set_session_config();
3975 CurrentSession::put(1);
3976
3977 set_default_users_balance_and_hold(vec![ALICE, BOB]).unwrap();
3978
3979 enroll_author_with_default_collateral(ALICE).unwrap();
3980 direct_fund_author(BOB, ALICE, 300).unwrap();
3981
3982 let afdt_id: sp_runtime::AccountId32 = generate_affidavit_id();
3983 insert_active_afdt_key(afdt_id.clone()).unwrap();
3984 ext_validate(ALICE, afdt_id.clone()).unwrap();
3985
3986 let for_session = CurrentSession::get() + 1;
3987 assert!(AffidavitKeys::contains_key((for_session, afdt_id.clone())));
3988
3989 let next_adft_id = generate_affidavit_id();
3990
3991 let public = get_public_key(afdt_id.clone()).unwrap();
3992 let affidavit_payload = AffidavitPayloadOf {
3993 public: public.clone(),
3994 rotate: next_adft_id.clone(),
3995 };
3996 let signature = sign_payload(&affidavit_payload.encode(), public);
3997
3998 assert!(!AuthorOfAffidavits::contains_key((for_session, ALICE)));
3999
4000 System::set_block_number(AFDT_SUBMISSION_START);
4001 assert_ok!(Pallet::declare(
4002 RuntimeOrigin::none(),
4003 affidavit_payload,
4004 signature
4005 ));
4006
4007 assert!(AuthorOfAffidavits::contains_key((for_session, ALICE)));
4008 let actual_afdt = AuthorOfAffidavits::get((for_session, ALICE)).unwrap();
4009 let afdt: WeakBoundedVec<(Funder, u64), _> =
4010 WeakBoundedVec::try_from(vec![(Funder::Direct(BOB), 300)]).unwrap();
4011 let expected_afdt = (AFDT_SUBMISSION_START, afdt);
4012 assert_eq!(actual_afdt, expected_afdt);
4013
4014 let for_session_2 = CurrentSession::get() + 2;
4015
4016 assert!(AffidavitKeys::contains_key((for_session_2, next_adft_id)));
4017
4018 #[cfg(not(feature = "dev"))]
4019 {
4020 System::assert_last_event(Event::AffidavitSubmitted {
4021 afdt_id: afdt_id,
4022 session: for_session
4023 }
4024 .into()
4025 );
4026 }
4027
4028 #[cfg(feature = "dev")]
4029 {
4030 let exp_afdt = Pallet::get_affidavit(&afdt_id).unwrap();
4031 System::assert_last_event(Event::AffidavitSubmitted {
4032 afdt_id: afdt_id,
4033 session: for_session,
4034 author: ALICE,
4035 affidavit: exp_afdt
4036 }
4037 .into()
4038 );
4039 }
4040 })
4041 }
4042
4043 #[test]
4044 fn declare_affidavit_err_bad_origin() {
4045 let mut env = new_ocw_env();
4046 env.ext.execute_with(|| {
4047 init_fork_graph();
4048 set_session_config();
4049 CurrentSession::put(1);
4050
4051 set_default_users_balance_and_hold(vec![ALICE, BOB]).unwrap();
4052
4053 enroll_author_with_default_collateral(ALICE).unwrap();
4054 direct_fund_author(BOB, ALICE, 300).unwrap();
4055
4056 let afdt_id = generate_affidavit_id();
4057 insert_active_afdt_key(afdt_id.clone()).unwrap();
4058 ext_validate(ALICE, afdt_id.clone()).unwrap();
4059
4060 let for_session = CurrentSession::get() + 1;
4061 assert!(AffidavitKeys::contains_key((for_session, afdt_id.clone())));
4062
4063 let next_adft_id = generate_affidavit_id();
4064
4065 let public = get_public_key(afdt_id.clone()).unwrap();
4066 let affidavit_payload = AffidavitPayloadOf {
4067 public: public.clone(),
4068 rotate: next_adft_id.clone(),
4069 };
4070 let signature = sign_payload(&affidavit_payload.encode(), public);
4071
4072 assert!(!AuthorOfAffidavits::contains_key((for_session, ALICE)));
4073
4074 System::set_block_number(AFDT_SUBMISSION_START);
4075
4076 System::set_block_number(136);
4077 assert_noop!(
4078 Pallet::declare(
4079 RuntimeOrigin::signed(ALICE),
4080 affidavit_payload.clone(),
4081 signature.clone()
4082 ),
4083 sp_runtime::DispatchError::BadOrigin
4084 );
4085
4086 assert_noop!(
4087 Pallet::declare(RuntimeOrigin::root(), affidavit_payload, signature),
4088 sp_runtime::DispatchError::BadOrigin
4089 );
4090 })
4091 }
4092
4093 #[test]
4094 fn declare_affidavit_err_affidavit_author_not_found() {
4095 let mut env = new_ocw_env();
4096 env.ext.execute_with(|| {
4097 init_fork_graph();
4098 set_session_config();
4099 CurrentSession::put(1);
4100
4101 set_default_users_balance_and_hold(vec![ALICE, BOB]).unwrap();
4102
4103 enroll_author_with_default_collateral(ALICE).unwrap();
4104 direct_fund_author(BOB, ALICE, 300).unwrap();
4105
4106 let afdt_id = generate_affidavit_id();
4107 insert_active_afdt_key(afdt_id.clone()).unwrap();
4108 ext_validate(ALICE, afdt_id.clone()).unwrap();
4109
4110 let for_session = CurrentSession::get() + 1;
4111 assert!(AffidavitKeys::contains_key((for_session, afdt_id.clone())));
4112
4113 let next_adft_id = generate_affidavit_id();
4114
4115 let _public = get_public_key(afdt_id.clone()).unwrap();
4116
4117 let dummy_public = generate_affidavit_keypair();
4118 let affidavit_payload = AffidavitPayloadOf {
4119 public: dummy_public.clone(),
4120 rotate: next_adft_id.clone(),
4121 };
4122 let signature = sign_payload(&affidavit_payload.encode(), dummy_public);
4123
4124 assert!(!AuthorOfAffidavits::contains_key((for_session, ALICE)));
4125
4126 System::set_block_number(AFDT_SUBMISSION_START);
4127
4128 assert_noop!(
4129 Pallet::declare(RuntimeOrigin::none(), affidavit_payload, signature),
4130 Error::AffidavitAuthorNotFound
4131 );
4132 })
4133 }
4134
4135 #[test]
4136 fn elect_authors_success() {
4137 let mut env = new_ocw_env();
4138 env.ext.execute_with(|| {
4139 set_session_config();
4140 CurrentSession::put(1);
4141 let users = vec![ALICE, BOB, ALAN, CHARLIE, AMY, NIX, MIKE, LAYA, DAVE, JIM];
4142 set_default_users_balance_and_hold(users).unwrap();
4143
4144 let authors = vec![ALICE, BOB, ALAN, MIKE, LAYA, DAVE, JIM];
4145 enroll_authors_with_default_collateral(authors.clone()).unwrap();
4146 direct_fund_author(NIX, ALICE, STANDARD_FUND).unwrap();
4147 direct_fund_author(AMY, BOB, SMALL_FUND).unwrap();
4148 direct_fund_author(CHARLIE, ALAN, LARGE_FUND).unwrap();
4149
4150 let alice_afdt_id = generate_affidavit_id();
4151 let alan_afdt_id = generate_affidavit_id();
4152 let bob_afdt_id = generate_affidavit_id();
4153 let mike_afdt_id = generate_affidavit_id();
4154 let laya_afdt_id = generate_affidavit_id();
4155 let dev_afdt_id = generate_affidavit_id();
4156 let jim_afdt_id = generate_affidavit_id();
4157
4158 System::set_block_number(150);
4159 ext_validate(ALICE, alice_afdt_id.clone()).unwrap();
4160 ext_validate(BOB, bob_afdt_id.clone()).unwrap();
4161 ext_validate(ALAN, alan_afdt_id.clone()).unwrap();
4162 ext_validate(MIKE, mike_afdt_id.clone()).unwrap();
4163 ext_validate(LAYA, laya_afdt_id.clone()).unwrap();
4164 ext_validate(DAVE, dev_afdt_id.clone()).unwrap();
4165 ext_validate(JIM, jim_afdt_id.clone()).unwrap();
4166
4167 let alice_nxt_afdt_id = generate_affidavit_id();
4168 let alan_nxt_afdt_id = generate_affidavit_id();
4169 let bob_nxt_afdt_id = generate_affidavit_id();
4170 let jim_nxt_afdt_id = generate_affidavit_id();
4171 let mike_nxt_afdt_id = generate_affidavit_id();
4172 let dev_nxt_afdt_id = generate_affidavit_id();
4173 let laya_nxt_afdt_id = generate_affidavit_id();
4174
4175 let alice_payload = TestAfdtPayload {
4176 active_afdt_pub: alice_afdt_id.clone(),
4177 next_afdt_pub: alice_nxt_afdt_id.clone(),
4178 };
4179
4180 let alan_payload = TestAfdtPayload {
4181 active_afdt_pub: alan_afdt_id.clone(),
4182 next_afdt_pub: alan_nxt_afdt_id.clone(),
4183 };
4184
4185 let bob_payload = TestAfdtPayload {
4186 active_afdt_pub: bob_afdt_id.clone(),
4187 next_afdt_pub: bob_nxt_afdt_id.clone(),
4188 };
4189
4190 let mike_payload = TestAfdtPayload {
4191 active_afdt_pub: mike_afdt_id.clone(),
4192 next_afdt_pub: mike_nxt_afdt_id.clone(),
4193 };
4194
4195 let laya_payload = TestAfdtPayload {
4196 active_afdt_pub: laya_afdt_id.clone(),
4197 next_afdt_pub: laya_nxt_afdt_id.clone(),
4198 };
4199
4200 let jim_payload = TestAfdtPayload {
4201 active_afdt_pub: jim_afdt_id.clone(),
4202 next_afdt_pub: jim_nxt_afdt_id.clone(),
4203 };
4204
4205 let dev_payload = TestAfdtPayload {
4206 active_afdt_pub: dev_afdt_id.clone(),
4207 next_afdt_pub: dev_nxt_afdt_id.clone(),
4208 };
4209
4210 System::set_block_number(AFDT_SUBMISSION_START);
4211 ext_declare_affidavit(ALICE, alice_payload).unwrap();
4212 ext_declare_affidavit(ALAN, alan_payload).unwrap();
4213 ext_declare_affidavit(BOB, bob_payload).unwrap();
4214 ext_declare_affidavit(MIKE, mike_payload).unwrap();
4215 ext_declare_affidavit(LAYA, laya_payload).unwrap();
4216 ext_declare_affidavit(JIM, jim_payload).unwrap();
4217 ext_declare_affidavit(DAVE, dev_payload).unwrap();
4218
4219 let public = get_public_key(alice_nxt_afdt_id).unwrap();
4220 let payload = ElectionPayloadOf {
4221 public: public.clone(),
4222 };
4223 let signature = sign_payload(&payload.encode(), public);
4224 set_block_author(ALICE);
4225 System::set_block_number(ELECTION_START);
4226 assert_ok!(Pallet::elect(RuntimeOrigin::none(), payload, signature));
4227 let actual_elected = Internals::reveal().unwrap();
4228 let expected_elected = vec![BOB, MIKE, LAYA, DAVE, ALICE, ALAN];
4229 assert_eq!(actual_elected, expected_elected);
4230
4231 let for_session = CurrentSession::get() + 1;
4232 #[cfg(feature = "dev")]
4233 System::assert_last_event(Event::ElectedInstance {
4234 session: for_session,
4235 runner: ALICE,
4236 elects: actual_elected
4237 }
4238 .into()
4239 );
4240
4241 #[cfg(not(feature = "dev"))]
4242 System::assert_last_event(Event::ElectedInstance {
4243 session: for_session,
4244 runner: ALICE,
4245 }
4246 .into()
4247 );
4248 })
4249 }
4250
4251 #[test]
4252 fn elect_authors_err_bad_origin() {
4253 let mut env = new_ocw_env();
4254 env.ext.execute_with(|| {
4255 set_session_config();
4256 CurrentSession::put(1);
4257 let users = vec![ALICE, BOB, ALAN, CHARLIE, AMY, NIX, MIKE, LAYA, DAVE, JIM];
4258 set_default_users_balance_and_hold(users).unwrap();
4259
4260 let authors = vec![ALICE, BOB, ALAN, MIKE, LAYA, DAVE, JIM];
4261 enroll_authors_with_default_collateral(authors.clone()).unwrap();
4262 direct_fund_author(NIX, ALICE, STANDARD_FUND).unwrap();
4263 direct_fund_author(AMY, BOB, SMALL_FUND).unwrap();
4264 direct_fund_author(CHARLIE, ALAN, LARGE_FUND).unwrap();
4265
4266 let alice_afdt_id = generate_affidavit_id();
4267 let alan_afdt_id = generate_affidavit_id();
4268 let bob_afdt_id = generate_affidavit_id();
4269 let mike_afdt_id = generate_affidavit_id();
4270 let laya_afdt_id = generate_affidavit_id();
4271 let dev_afdt_id = generate_affidavit_id();
4272 let jim_afdt_id = generate_affidavit_id();
4273
4274 System::set_block_number(150);
4275 ext_validate(ALICE, alice_afdt_id.clone()).unwrap();
4276 ext_validate(BOB, bob_afdt_id.clone()).unwrap();
4277 ext_validate(ALAN, alan_afdt_id.clone()).unwrap();
4278 ext_validate(MIKE, mike_afdt_id.clone()).unwrap();
4279 ext_validate(LAYA, laya_afdt_id.clone()).unwrap();
4280 ext_validate(DAVE, dev_afdt_id.clone()).unwrap();
4281 ext_validate(JIM, jim_afdt_id.clone()).unwrap();
4282
4283 let alice_nxt_afdt_id = generate_affidavit_id();
4284 let alan_nxt_afdt_id = generate_affidavit_id();
4285 let bob_nxt_afdt_id = generate_affidavit_id();
4286 let jim_nxt_afdt_id = generate_affidavit_id();
4287 let mike_nxt_afdt_id = generate_affidavit_id();
4288 let dev_nxt_afdt_id = generate_affidavit_id();
4289 let laya_nxt_afdt_id = generate_affidavit_id();
4290
4291 let alice_payload = TestAfdtPayload {
4292 active_afdt_pub: alice_afdt_id.clone(),
4293 next_afdt_pub: alice_nxt_afdt_id.clone(),
4294 };
4295
4296 let alan_payload = TestAfdtPayload {
4297 active_afdt_pub: alan_afdt_id.clone(),
4298 next_afdt_pub: alan_nxt_afdt_id.clone(),
4299 };
4300
4301 let bob_payload = TestAfdtPayload {
4302 active_afdt_pub: bob_afdt_id.clone(),
4303 next_afdt_pub: bob_nxt_afdt_id.clone(),
4304 };
4305
4306 let mike_payload = TestAfdtPayload {
4307 active_afdt_pub: mike_afdt_id.clone(),
4308 next_afdt_pub: mike_nxt_afdt_id.clone(),
4309 };
4310
4311 let laya_payload = TestAfdtPayload {
4312 active_afdt_pub: laya_afdt_id.clone(),
4313 next_afdt_pub: laya_nxt_afdt_id.clone(),
4314 };
4315
4316 let jim_payload = TestAfdtPayload {
4317 active_afdt_pub: jim_afdt_id.clone(),
4318 next_afdt_pub: jim_nxt_afdt_id.clone(),
4319 };
4320
4321 let dev_payload = TestAfdtPayload {
4322 active_afdt_pub: dev_afdt_id.clone(),
4323 next_afdt_pub: dev_nxt_afdt_id.clone(),
4324 };
4325
4326 System::set_block_number(AFDT_SUBMISSION_START);
4327 ext_declare_affidavit(ALICE, alice_payload).unwrap();
4328 ext_declare_affidavit(ALAN, alan_payload).unwrap();
4329 ext_declare_affidavit(BOB, bob_payload).unwrap();
4330 ext_declare_affidavit(MIKE, mike_payload).unwrap();
4331 ext_declare_affidavit(LAYA, laya_payload).unwrap();
4332 ext_declare_affidavit(JIM, jim_payload).unwrap();
4333 ext_declare_affidavit(DAVE, dev_payload).unwrap();
4334
4335 let public = get_public_key(alice_nxt_afdt_id).unwrap();
4336 let payload = ElectionPayloadOf {
4337 public: public.clone(),
4338 };
4339 let signature = sign_payload(&payload.encode(), public);
4340 set_block_author(ALICE);
4341 System::set_block_number(ELECTION_START);
4342
4343 assert_err!(
4344 Pallet::elect(
4345 RuntimeOrigin::signed(ALICE),
4346 payload.clone(),
4347 signature.clone()
4348 ),
4349 DispatchError::BadOrigin
4350 );
4351
4352 assert_err!(
4353 Pallet::elect(RuntimeOrigin::root(), payload.clone(), signature.clone()),
4354 DispatchError::BadOrigin
4355 );
4356 })
4357 }
4358
4359 #[test]
4360 fn elect_authors_err_affidavit_author_not_found() {
4361 let mut env = new_ocw_env();
4362 env.ext.execute_with(|| {
4363 set_session_config();
4364 CurrentSession::put(1);
4365 let users = vec![ALICE, BOB, ALAN, CHARLIE, AMY, NIX, MIKE, LAYA, DAVE, JIM];
4366 set_default_users_balance_and_hold(users).unwrap();
4367
4368 let authors = vec![ALICE, BOB, ALAN, MIKE, LAYA, DAVE, JIM];
4369 enroll_authors_with_default_collateral(authors.clone()).unwrap();
4370 direct_fund_author(NIX, ALICE, STANDARD_FUND).unwrap();
4371 direct_fund_author(AMY, BOB, SMALL_FUND).unwrap();
4372 direct_fund_author(CHARLIE, ALAN, LARGE_FUND).unwrap();
4373
4374 let alice_afdt_id = generate_affidavit_id();
4375 let alan_afdt_id = generate_affidavit_id();
4376 let bob_afdt_id = generate_affidavit_id();
4377 let mike_afdt_id = generate_affidavit_id();
4378 let laya_afdt_id = generate_affidavit_id();
4379 let dev_afdt_id = generate_affidavit_id();
4380 let jim_afdt_id = generate_affidavit_id();
4381
4382 System::set_block_number(150);
4383 ext_validate(ALICE, alice_afdt_id.clone()).unwrap();
4384 ext_validate(BOB, bob_afdt_id.clone()).unwrap();
4385 ext_validate(ALAN, alan_afdt_id.clone()).unwrap();
4386 ext_validate(MIKE, mike_afdt_id.clone()).unwrap();
4387 ext_validate(LAYA, laya_afdt_id.clone()).unwrap();
4388 ext_validate(DAVE, dev_afdt_id.clone()).unwrap();
4389 ext_validate(JIM, jim_afdt_id.clone()).unwrap();
4390
4391 let alice_nxt_afdt_id = generate_affidavit_id();
4392 let alan_nxt_afdt_id = generate_affidavit_id();
4393 let bob_nxt_afdt_id = generate_affidavit_id();
4394 let jim_nxt_afdt_id = generate_affidavit_id();
4395 let mike_nxt_afdt_id = generate_affidavit_id();
4396 let dev_nxt_afdt_id = generate_affidavit_id();
4397 let laya_nxt_afdt_id = generate_affidavit_id();
4398
4399 let alice_payload = TestAfdtPayload {
4400 active_afdt_pub: alice_afdt_id.clone(),
4401 next_afdt_pub: alice_nxt_afdt_id.clone(),
4402 };
4403
4404 let alan_payload = TestAfdtPayload {
4405 active_afdt_pub: alan_afdt_id.clone(),
4406 next_afdt_pub: alan_nxt_afdt_id.clone(),
4407 };
4408
4409 let bob_payload = TestAfdtPayload {
4410 active_afdt_pub: bob_afdt_id.clone(),
4411 next_afdt_pub: bob_nxt_afdt_id.clone(),
4412 };
4413
4414 let mike_payload = TestAfdtPayload {
4415 active_afdt_pub: mike_afdt_id.clone(),
4416 next_afdt_pub: mike_nxt_afdt_id.clone(),
4417 };
4418
4419 let laya_payload = TestAfdtPayload {
4420 active_afdt_pub: laya_afdt_id.clone(),
4421 next_afdt_pub: laya_nxt_afdt_id.clone(),
4422 };
4423
4424 let jim_payload = TestAfdtPayload {
4425 active_afdt_pub: jim_afdt_id.clone(),
4426 next_afdt_pub: jim_nxt_afdt_id.clone(),
4427 };
4428
4429 let dev_payload = TestAfdtPayload {
4430 active_afdt_pub: dev_afdt_id.clone(),
4431 next_afdt_pub: dev_nxt_afdt_id.clone(),
4432 };
4433
4434 System::set_block_number(AFDT_SUBMISSION_START);
4435 ext_declare_affidavit(ALICE, alice_payload).unwrap();
4436 ext_declare_affidavit(ALAN, alan_payload).unwrap();
4437 ext_declare_affidavit(BOB, bob_payload).unwrap();
4438 ext_declare_affidavit(MIKE, mike_payload).unwrap();
4439 ext_declare_affidavit(LAYA, laya_payload).unwrap();
4440 ext_declare_affidavit(JIM, jim_payload).unwrap();
4441 ext_declare_affidavit(DAVE, dev_payload).unwrap();
4442
4443 let _public = get_public_key(alice_nxt_afdt_id).unwrap();
4444
4445 let dummy_public = generate_affidavit_keypair();
4446 let payload = ElectionPayloadOf {
4447 public: dummy_public.clone(),
4448 };
4449 let signature = sign_payload(&payload.encode(), dummy_public);
4450 set_block_author(ALICE);
4451 System::set_block_number(ELECTION_START);
4452
4453 assert_err!(
4454 Pallet::elect(RuntimeOrigin::none(), payload, signature),
4455 Error::AffidavitAuthorNotFound
4456 );
4457 })
4458 }
4459
4460 #[test]
4461 fn elect_authors_err_tried_electing_by_non_block_author() {
4462 let mut env = new_ocw_env();
4463 env.ext.execute_with(|| {
4464 set_session_config();
4465 CurrentSession::put(1);
4466 let users = vec![ALICE, BOB, ALAN, CHARLIE, AMY, NIX, MIKE, LAYA, DAVE, JIM];
4467 set_default_users_balance_and_hold(users).unwrap();
4468
4469 let authors = vec![ALICE, BOB, ALAN, MIKE, LAYA, DAVE, JIM];
4470 enroll_authors_with_default_collateral(authors.clone()).unwrap();
4471 direct_fund_author(NIX, ALICE, STANDARD_FUND).unwrap();
4472 direct_fund_author(AMY, BOB, SMALL_FUND).unwrap();
4473 direct_fund_author(CHARLIE, ALAN, LARGE_FUND).unwrap();
4474
4475 let alice_afdt_id = generate_affidavit_id();
4476 let alan_afdt_id = generate_affidavit_id();
4477 let bob_afdt_id = generate_affidavit_id();
4478 let mike_afdt_id = generate_affidavit_id();
4479 let laya_afdt_id = generate_affidavit_id();
4480 let dev_afdt_id = generate_affidavit_id();
4481 let jim_afdt_id = generate_affidavit_id();
4482
4483 System::set_block_number(150);
4484 ext_validate(ALICE, alice_afdt_id.clone()).unwrap();
4485 ext_validate(BOB, bob_afdt_id.clone()).unwrap();
4486 ext_validate(ALAN, alan_afdt_id.clone()).unwrap();
4487 ext_validate(MIKE, mike_afdt_id.clone()).unwrap();
4488 ext_validate(LAYA, laya_afdt_id.clone()).unwrap();
4489 ext_validate(DAVE, dev_afdt_id.clone()).unwrap();
4490 ext_validate(JIM, jim_afdt_id.clone()).unwrap();
4491
4492 let alice_nxt_afdt_id = generate_affidavit_id();
4493 let alan_nxt_afdt_id = generate_affidavit_id();
4494 let bob_nxt_afdt_id = generate_affidavit_id();
4495 let jim_nxt_afdt_id = generate_affidavit_id();
4496 let mike_nxt_afdt_id = generate_affidavit_id();
4497 let dev_nxt_afdt_id = generate_affidavit_id();
4498 let laya_nxt_afdt_id = generate_affidavit_id();
4499
4500 let alice_payload = TestAfdtPayload {
4501 active_afdt_pub: alice_afdt_id.clone(),
4502 next_afdt_pub: alice_nxt_afdt_id.clone(),
4503 };
4504
4505 let alan_payload = TestAfdtPayload {
4506 active_afdt_pub: alan_afdt_id.clone(),
4507 next_afdt_pub: alan_nxt_afdt_id.clone(),
4508 };
4509
4510 let bob_payload = TestAfdtPayload {
4511 active_afdt_pub: bob_afdt_id.clone(),
4512 next_afdt_pub: bob_nxt_afdt_id.clone(),
4513 };
4514
4515 let mike_payload = TestAfdtPayload {
4516 active_afdt_pub: mike_afdt_id.clone(),
4517 next_afdt_pub: mike_nxt_afdt_id.clone(),
4518 };
4519
4520 let laya_payload = TestAfdtPayload {
4521 active_afdt_pub: laya_afdt_id.clone(),
4522 next_afdt_pub: laya_nxt_afdt_id.clone(),
4523 };
4524
4525 let jim_payload = TestAfdtPayload {
4526 active_afdt_pub: jim_afdt_id.clone(),
4527 next_afdt_pub: jim_nxt_afdt_id.clone(),
4528 };
4529
4530 let dev_payload = TestAfdtPayload {
4531 active_afdt_pub: dev_afdt_id.clone(),
4532 next_afdt_pub: dev_nxt_afdt_id.clone(),
4533 };
4534
4535 System::set_block_number(AFDT_SUBMISSION_START);
4536 ext_declare_affidavit(ALICE, alice_payload).unwrap();
4537 ext_declare_affidavit(ALAN, alan_payload).unwrap();
4538 ext_declare_affidavit(BOB, bob_payload).unwrap();
4539 ext_declare_affidavit(MIKE, mike_payload).unwrap();
4540 ext_declare_affidavit(LAYA, laya_payload).unwrap();
4541 ext_declare_affidavit(JIM, jim_payload).unwrap();
4542 ext_declare_affidavit(DAVE, dev_payload).unwrap();
4543
4544 let public = get_public_key(alice_nxt_afdt_id).unwrap();
4545
4546 let payload = ElectionPayloadOf {
4547 public: public.clone(),
4548 };
4549 let signature = sign_payload(&payload.encode(), public);
4550 set_block_author(BOB);
4551 System::set_block_number(ELECTION_START);
4552 assert_err!(
4553 Pallet::elect(RuntimeOrigin::none(), payload, signature),
4554 Error::TriedElectingByNonBlockAuthor
4555 );
4556 })
4557 }
4558
4559 #[cfg(feature = "dev")]
4560 #[test]
4561 fn inspect_elects_success() {
4562 let mut env = new_ocw_env();
4563 env.ext.execute_with(||{
4564 set_session_config();
4565 CurrentSession::put(1);
4566 let users = vec![ALICE, BOB, ALAN, CHARLIE, AMY, NIX, MIKE, LAYA, DAVE, JIM];
4567 set_default_users_balance_and_hold(users).unwrap();
4568
4569 let authors = vec![ALICE, BOB, ALAN, MIKE, LAYA, DAVE, JIM];
4570 enroll_authors_with_default_collateral(authors.clone()).unwrap();
4571 direct_fund_author(NIX, ALICE, STANDARD_FUND).unwrap();
4572 direct_fund_author(AMY, BOB, SMALL_FUND).unwrap();
4573 direct_fund_author(CHARLIE, ALAN, LARGE_FUND).unwrap();
4574
4575 let alice_afdt_id = generate_affidavit_id();
4576 let alan_afdt_id = generate_affidavit_id();
4577 let bob_afdt_id = generate_affidavit_id();
4578 let mike_afdt_id = generate_affidavit_id();
4579 let laya_afdt_id = generate_affidavit_id();
4580 let dev_afdt_id = generate_affidavit_id();
4581 let jim_afdt_id = generate_affidavit_id();
4582
4583 System::set_block_number(150);
4584 ext_validate(ALICE, alice_afdt_id.clone()).unwrap();
4585 ext_validate(BOB, bob_afdt_id.clone()).unwrap();
4586 ext_validate(ALAN, alan_afdt_id.clone()).unwrap();
4587 ext_validate(MIKE, mike_afdt_id.clone()).unwrap();
4588 ext_validate(LAYA, laya_afdt_id.clone()).unwrap();
4589 ext_validate(DAVE, dev_afdt_id.clone()).unwrap();
4590 ext_validate(JIM, jim_afdt_id.clone()).unwrap();
4591
4592 let alice_nxt_afdt_id = generate_affidavit_id();
4593 let alan_nxt_afdt_id = generate_affidavit_id();
4594 let bob_nxt_afdt_id = generate_affidavit_id();
4595 let jim_nxt_afdt_id = generate_affidavit_id();
4596 let mike_nxt_afdt_id = generate_affidavit_id();
4597 let dev_nxt_afdt_id = generate_affidavit_id();
4598 let laya_nxt_afdt_id = generate_affidavit_id();
4599
4600 let alice_payload = TestAfdtPayload {
4601 active_afdt_pub: alice_afdt_id.clone(),
4602 next_afdt_pub: alice_nxt_afdt_id.clone(),
4603 };
4604
4605 let alan_payload = TestAfdtPayload {
4606 active_afdt_pub: alan_afdt_id.clone(),
4607 next_afdt_pub: alan_nxt_afdt_id.clone(),
4608 };
4609
4610 let bob_payload = TestAfdtPayload {
4611 active_afdt_pub: bob_afdt_id.clone(),
4612 next_afdt_pub: bob_nxt_afdt_id.clone(),
4613 };
4614
4615 let mike_payload = TestAfdtPayload {
4616 active_afdt_pub: mike_afdt_id.clone(),
4617 next_afdt_pub: mike_nxt_afdt_id.clone(),
4618 };
4619
4620 let laya_payload = TestAfdtPayload {
4621 active_afdt_pub: laya_afdt_id.clone(),
4622 next_afdt_pub: laya_nxt_afdt_id.clone(),
4623 };
4624
4625 let jim_payload = TestAfdtPayload {
4626 active_afdt_pub: jim_afdt_id.clone(),
4627 next_afdt_pub: jim_nxt_afdt_id.clone(),
4628 };
4629
4630 let dev_payload = TestAfdtPayload {
4631 active_afdt_pub: dev_afdt_id.clone(),
4632 next_afdt_pub: dev_nxt_afdt_id.clone(),
4633 };
4634
4635 System::set_block_number(AFDT_SUBMISSION_START);
4636 ext_declare_affidavit(ALICE, alice_payload).unwrap();
4637 ext_declare_affidavit(ALAN, alan_payload).unwrap();
4638 ext_declare_affidavit(BOB, bob_payload).unwrap();
4639 ext_declare_affidavit(MIKE, mike_payload).unwrap();
4640 ext_declare_affidavit(LAYA, laya_payload).unwrap();
4641 ext_declare_affidavit(JIM, jim_payload).unwrap();
4642 ext_declare_affidavit(DAVE, dev_payload).unwrap();
4643
4644 let public = get_public_key(alice_nxt_afdt_id).unwrap();
4645 let payload = ElectionPayloadOf {
4646 public: public.clone(),
4647 };
4648 let signature = sign_payload(&payload.encode(), public);
4649 set_block_author(ALICE);
4650 System::set_block_number(ELECTION_START);
4651 assert_ok!(Pallet::elect(RuntimeOrigin::none(), payload, signature));
4652 assert_ok!(Pallet::inspect_elects(RuntimeOrigin::signed(AMY)));
4653 let expected_elects = vec![BOB, MIKE, LAYA, DAVE, ALICE, ALAN];
4654 System::assert_last_event(
4655 Event::InspectElects {
4656 elects: expected_elects
4657 }
4658 .into()
4659 );
4660 })
4661 }
4662
4663 #[cfg(feature = "dev")]
4664 #[test]
4665 fn prepare_validation_payload_success() {
4666 let mut env = new_ocw_env();
4667 env.ext.execute_with(|| {
4668 System::set_block_number(10);
4669 Pallet::offchain_worker(10);
4670 assert_err!(
4671 Pallet::prepare_validation_payload(RuntimeOrigin::signed(ALICE)),
4672 Error::ActiveAfdtKeyNotYetFinalized
4673 );
4674
4675 while System::block_number() < 30 {
4676 ocw_step();
4677 }
4678
4679 assert_ok!(Pallet::prepare_validation_payload(RuntimeOrigin::signed(ALICE)));
4680 })
4681 }
4682
4683 #[cfg(feature = "dev")]
4684 #[test]
4685 fn inspect_affidavit_success() {
4686 let mut env = new_ocw_env();
4687 env.ext.execute_with(|| {
4688 set_session_config();
4689 CurrentSession::put(1);
4690 let users = vec![ALICE, BOB, ALAN, AMY, JIM, NIX, LAYA];
4691 set_default_users_balance_and_hold(users).unwrap();
4692
4693 let authors = vec![ALICE, BOB];
4694 enroll_authors_with_default_collateral(authors.clone()).unwrap();
4695 direct_fund_author(NIX, ALICE, STANDARD_FUND).unwrap();
4696 direct_fund_author(AMY, BOB, SMALL_FUND).unwrap();
4697 direct_fund_author(ALAN, BOB, LARGE_FUND).unwrap();
4698 direct_fund_author(JIM, ALICE, SMALL_FUND).unwrap();
4699 direct_fund_author(LAYA, ALICE, STANDARD_FUND).unwrap();
4700
4701 let alice_afdt_id = generate_affidavit_id();
4702 let bob_afdt_id = generate_affidavit_id();
4703
4704 System::set_block_number(150);
4705 ext_validate(ALICE, alice_afdt_id.clone()).unwrap();
4706 ext_validate(BOB, bob_afdt_id.clone()).unwrap();
4707
4708 let alice_nxt_afdt_id = generate_affidavit_id();
4709 let bob_nxt_afdt_id = generate_affidavit_id();
4710
4711
4712 let alice_payload = TestAfdtPayload {
4713 active_afdt_pub: alice_afdt_id.clone(),
4714 next_afdt_pub: alice_nxt_afdt_id.clone(),
4715 };
4716
4717 let bob_payload = TestAfdtPayload {
4718 active_afdt_pub: bob_afdt_id.clone(),
4719 next_afdt_pub: bob_nxt_afdt_id.clone(),
4720 };
4721
4722 System::set_block_number(AFDT_SUBMISSION_START);
4723 ext_declare_affidavit(ALICE, alice_payload).unwrap();
4724 ext_declare_affidavit(BOB, bob_payload).unwrap();
4725
4726 assert_ok!(Pallet::inspect_affidavit(RuntimeOrigin::signed(ALICE), alice_afdt_id.clone()));
4727 let expected_affidavit = vec![(Funder::Direct(LAYA), STANDARD_FUND), (Funder::Direct(NIX), STANDARD_FUND), (Funder::Direct(JIM), SMALL_FUND)];
4728 System::assert_last_event(
4729 Event::InspectAffidavit {
4730 author: ALICE,
4731 afdt_id: alice_afdt_id,
4732 session: 2,
4733 affidavit: expected_affidavit
4734 }
4735 .into()
4736 );
4737 })
4738 }
4739
4740
4741 #[test]
4742 fn force_genesis_config_err_bad_origin() {
4743 chain_manager_test_ext().execute_with(|| {
4744 assert_noop!(
4745 ChainManager::force_genesis_config(
4746 RuntimeOrigin::signed(ALICE),
4747 ForceGenesisConfig::AllowAffidavits(true)
4748 ),
4749 sp_runtime::DispatchError::BadOrigin
4750 );
4751 });
4752 }
4753
4754 #[test]
4755 fn force_allow_affidavits_success() {
4756 chain_manager_test_ext().execute_with(|| {
4757 assert_eq!(AllowAffidavits::get(), false);
4758 assert_ok!(ChainManager::force_genesis_config(
4759 RuntimeOrigin::root(),
4760 ForceGenesisConfig::AllowAffidavits(true)
4761 ));
4762 assert_eq!(AllowAffidavits::get(), true);
4763 });
4764 }
4765
4766 #[test]
4767 fn force_affidavit_begins_at_err_invalid_affidavit_period() {
4768 chain_manager_test_ext().execute_with(|| {
4769 AffidavitEndsAt::put(Duration::from_rational(8u32, 10u32));
4770 assert_noop!(
4771 ChainManager::force_genesis_config(
4772 RuntimeOrigin::root(),
4773 ForceGenesisConfig::AffidavitBeginsAt(Duration::from_rational(8u32, 10u32))
4774 ),
4775 Error::InvalidAffidavitPeriod
4776 );
4777 });
4778 }
4779
4780 #[test]
4781 fn force_affidavit_ends_at_err_invalid_affidavit_period() {
4782 chain_manager_test_ext().execute_with(|| {
4783 AffidavitBeginsAt::put(Duration::from_rational(2u32, 10u32));
4784 assert_noop!(
4785 ChainManager::force_genesis_config(
4786 RuntimeOrigin::root(),
4787 ForceGenesisConfig::AffidavitEndsAt(Duration::from_rational(2u32, 10u32))
4788 ),
4789 Error::InvalidAffidavitPeriod
4790 );
4791 });
4792 }
4793
4794 #[test]
4795 fn force_affidavit_period_updates_correctly() {
4796 chain_manager_test_ext().execute_with(|| {
4797 assert_eq!(
4798 AffidavitBeginsAt::get(),
4799 Duration::from_rational(2u32, 10u32)
4800 );
4801 assert_eq!(AffidavitEndsAt::get(), Duration::from_rational(8u32, 10u32));
4802 assert_eq!(
4803 ElectionBeginsAt::get(),
4804 Duration::from_rational(5u32, 10u32)
4805 );
4806
4807 let begin = Duration::from_rational(3u32, 10u32);
4808 let end = Duration::from_rational(7u32, 10u32);
4809 let election = Duration::from_rational(4u32, 10u32);
4810
4811 assert_ok!(ChainManager::force_genesis_config(
4812 RuntimeOrigin::root(),
4813 ForceGenesisConfig::AffidavitBeginsAt(begin)
4814 ));
4815
4816 assert_ok!(ChainManager::force_genesis_config(
4817 RuntimeOrigin::root(),
4818 ForceGenesisConfig::AffidavitEndsAt(end)
4819 ));
4820
4821 assert_ok!(ChainManager::force_genesis_config(
4822 RuntimeOrigin::root(),
4823 ForceGenesisConfig::ElectionBeginsAt(election)
4824 ));
4825
4826 assert_eq!(AffidavitBeginsAt::get(), begin);
4827 assert_eq!(AffidavitEndsAt::get(), end);
4828 assert_eq!(ElectionBeginsAt::get(), election);
4829 });
4830 }
4831
4832 #[test]
4833 fn force_election_runner_points_success() {
4834 chain_manager_test_ext().execute_with(|| {
4835 assert_eq!(ElectionRunnerPointsUpgrade::get(), None);
4836 assert_ok!(ChainManager::force_genesis_config(
4837 RuntimeOrigin::root(),
4838 ForceGenesisConfig::ElectionRunnerPointsUpgrade(Some(10))
4839 ));
4840 assert_eq!(ElectionRunnerPointsUpgrade::get(), Some(10));
4841
4842 assert_ok!(ChainManager::force_genesis_config(
4843 RuntimeOrigin::root(),
4844 ForceGenesisConfig::ElectionRunnerPointsUpgrade(None)
4845 ));
4846 assert_eq!(ElectionRunnerPointsUpgrade::get(), None);
4847 })
4848 }
4849
4850 #[test]
4851 fn force_validate_tx_priority_success() {
4852 chain_manager_test_ext().execute_with(|| {
4853 assert_eq!(ValidateTxPriority::get(), 1_000_000);
4854 assert_ok!(ChainManager::force_genesis_config(
4855 RuntimeOrigin::root(),
4856 ForceGenesisConfig::ValidateTxPriority(1)
4857 ));
4858 assert_eq!(ValidateTxPriority::get(), 1);
4859 })
4860 }
4861
4862 #[test]
4863 fn force_validate_tx_priority_err_value_cannot_be_zero() {
4864 chain_manager_test_ext().execute_with(|| {
4865 assert_eq!(ValidateTxPriority::get(), 1_000_000);
4866 assert_noop!(
4867 ChainManager::force_genesis_config(
4868 RuntimeOrigin::root(),
4869 ForceGenesisConfig::ValidateTxPriority(0)
4870 ),
4871 Error::ValueCannotBeZero
4872 );
4873 })
4874 }
4875
4876 #[test]
4877 fn force_affidavit_tx_priority_success() {
4878 chain_manager_test_ext().execute_with(|| {
4879 assert_eq!(AffidavitTxPriority::get(), 850_000);
4880 assert_ok!(ChainManager::force_genesis_config(
4881 RuntimeOrigin::root(),
4882 ForceGenesisConfig::AffidavitTxPriority(2)
4883 ));
4884 assert_eq!(AffidavitTxPriority::get(), 2);
4885 })
4886 }
4887
4888 #[test]
4889 fn force_affidavit_tx_priority_err_value_cannot_be_zero() {
4890 chain_manager_test_ext().execute_with(|| {
4891 assert_eq!(AffidavitTxPriority::get(), 850_000);
4892 assert_noop!(
4893 ChainManager::force_genesis_config(
4894 RuntimeOrigin::root(),
4895 ForceGenesisConfig::AffidavitTxPriority(0)
4896 ),
4897 Error::ValueCannotBeZero
4898 );
4899 })
4900 }
4901
4902 #[test]
4903 fn force_election_tx_priority_success() {
4904 chain_manager_test_ext().execute_with(|| {
4905 assert_eq!(ElectionTxPriority::get(), 700_000);
4906 assert_ok!(ChainManager::force_genesis_config(
4907 RuntimeOrigin::root(),
4908 ForceGenesisConfig::ElectionTxPriority(3)
4909 ));
4910 assert_eq!(ElectionTxPriority::get(), 3);
4911 })
4912 }
4913
4914 #[test]
4915 fn force_election_tx_priority_err_value_cannot_be_zero() {
4916 chain_manager_test_ext().execute_with(|| {
4917 assert_eq!(ElectionTxPriority::get(), 700_000);
4918 assert_noop!(
4919 ChainManager::force_genesis_config(
4920 RuntimeOrigin::root(),
4921 ForceGenesisConfig::ElectionTxPriority(0)
4922 ),
4923 Error::ValueCannotBeZero
4924 );
4925 })
4926 }
4927
4928 #[test]
4929 fn force_finality_after_success() {
4930 chain_manager_test_ext().execute_with(|| {
4931 assert_eq!(FinalityAfter::get(), 60_000);
4932 assert_ok!(ChainManager::force_genesis_config(
4933 RuntimeOrigin::root(),
4934 ForceGenesisConfig::FinalityAfter(90_000)
4935 ));
4936 assert_eq!(FinalityAfter::get(), 90_000);
4937 })
4938 }
4939
4940 #[test]
4941 fn force_finality_after_err_value_cannot_be_zero() {
4942 chain_manager_test_ext().execute_with(|| {
4943 assert_eq!(FinalityAfter::get(), 60_000);
4944 assert_noop!(
4945 ChainManager::force_genesis_config(
4946 RuntimeOrigin::root(),
4947 ForceGenesisConfig::FinalityAfter(0)
4948 ),
4949 Error::ValueCannotBeZero
4950 );
4951 })
4952 }
4953
4954 #[test]
4955 fn force_finality_ticks_success() {
4956 chain_manager_test_ext().execute_with(|| {
4957 assert_eq!(FinalityTicks::get(), 5);
4958 assert_ok!(ChainManager::force_genesis_config(
4959 RuntimeOrigin::root(),
4960 ForceGenesisConfig::FinalityTicks(3)
4961 ));
4962 assert_eq!(FinalityTicks::get(), 3);
4963 })
4964 }
4965
4966 #[test]
4967 fn force_finality_ticks_err_value_cannot_be_zero() {
4968 chain_manager_test_ext().execute_with(|| {
4969 assert_eq!(FinalityTicks::get(), 5);
4970 assert_noop!(
4971 ChainManager::force_genesis_config(
4972 RuntimeOrigin::root(),
4973 ForceGenesisConfig::FinalityTicks(0)
4974 ),
4975 Error::ValueCannotBeZero
4976 );
4977 })
4978 }
4979
4980 // ===============================================================================
4981 // ````````````````````````````````` PUBLIC APIS `````````````````````````````````
4982 // ===============================================================================
4983
4984 #[test]
4985 fn is_validating_success() {
4986 chain_manager_test_ext().execute_with(|| {
4987 set_default_user_balance_and_hold(ALICE).unwrap();
4988 set_default_user_balance_and_hold(BOB).unwrap();
4989 set_default_user_balance_and_hold(ALAN).unwrap();
4990 enroll_authors_with_default_collateral(vec![ALICE, BOB, ALAN]).unwrap();
4991
4992 let alice_id = Pallet::convert(ALICE).unwrap();
4993 let bob_id = Pallet::convert(BOB).unwrap();
4994
4995 Validators::put(vec![bob_id, alice_id]);
4996
4997 assert!(Pallet::is_validating(ALICE));
4998 assert!(Pallet::is_validating(BOB));
4999 assert!(!Pallet::is_validating(ALAN));
5000 })
5001 }
5002
5003 #[test]
5004 fn get_runtime_afdt_key_success() {
5005 let mut env = new_ocw_env();
5006 env.ext.execute_with(|| {
5007 set_default_user_balance_and_hold(ALICE).unwrap();
5008 set_default_user_balance_and_hold(BOB).unwrap();
5009 set_default_user_balance_and_hold(ALAN).unwrap();
5010 enroll_authors_with_default_collateral(vec![ALICE, BOB, ALAN]).unwrap();
5011
5012 let alice_afdt_id = generate_affidavit_id();
5013 let alan_afdt_id = generate_affidavit_id();
5014
5015 CurrentSession::put(1);
5016 let next_session = CurrentSession::get() + 1;
5017 let next_afdt_session = next_session + 1;
5018
5019 AffidavitKeys::insert((next_session, alice_afdt_id.clone()), ALICE);
5020 AffidavitKeys::insert((next_afdt_session, alan_afdt_id.clone()), ALAN);
5021
5022 let alice_active_afdt_key = Pallet::get_runtime_afdt_key(ALICE);
5023 let bob_active_afdt_key = Pallet::get_runtime_afdt_key(BOB);
5024 let alan_active_afdt_key = Pallet::get_runtime_afdt_key(ALAN);
5025
5026 assert_ok!(alan_active_afdt_key.clone());
5027 assert_ok!(alice_active_afdt_key. clone());
5028 assert_err!(bob_active_afdt_key, Error::AffidavitKeyPairNotFound);
5029
5030 assert_eq!(
5031 alan_active_afdt_key.unwrap(),
5032 (next_afdt_session, alan_afdt_id)
5033 );
5034 assert_eq!(
5035 alice_active_afdt_key.unwrap(),
5036 (next_session, alice_afdt_id)
5037 );
5038 })
5039 }
5040
5041 #[test]
5042 fn is_chilling_success() {
5043 let mut env = new_ocw_env();
5044 env.ext.execute_with(|| {
5045 set_default_user_balance_and_hold(ALICE).unwrap();
5046 set_default_user_balance_and_hold(BOB).unwrap();
5047 set_default_user_balance_and_hold(ALAN).unwrap();
5048 enroll_authors_with_default_collateral(vec![ALICE, BOB, ALAN]).unwrap();
5049
5050 let alice_afdt_id = generate_affidavit_id();
5051 let alan_afdt_id = generate_affidavit_id();
5052
5053 CurrentSession::put(1);
5054 let next_session = CurrentSession::get() + 1;
5055 let next_afdt_session = next_session + 1;
5056
5057 AffidavitKeys::insert((next_session, alice_afdt_id), ALICE);
5058 AffidavitKeys::insert((next_afdt_session, alan_afdt_id), ALAN);
5059
5060 assert!(!Pallet::is_chilling(ALICE));
5061 assert!(!Pallet::is_chilling(ALAN));
5062 assert!(Pallet::is_chilling(BOB));
5063 })
5064 }
5065
5066 #[test]
5067 fn get_finalized_afdt_key_success() {
5068 let mut env = new_ocw_env();
5069 env.ext.execute_with(|| {
5070 System::set_block_number(10);
5071 Pallet::offchain_worker(10);
5072 let active_afdt = Pallet::get_finalized_afdt_key();
5073 assert_err!(active_afdt, Error::ActiveAfdtKeyNotYetFinalized);
5074
5075 while System::block_number() < 30 {
5076 ocw_step();
5077 }
5078
5079 let active_afdt = Pallet::get_finalized_afdt_key();
5080 assert_ok!(active_afdt);
5081 })
5082 }
5083
5084 #[test]
5085 fn sign_validate_payload_success() {
5086 let mut env = new_ocw_env();
5087 env.ext.execute_with(|| {
5088 System::set_block_number(10);
5089 Pallet::offchain_worker(10);
5090 let sign_payload = Pallet::sign_validate_payload();
5091 assert_err!(sign_payload, Error::ActiveAfdtKeyNotYetFinalized);
5092
5093 while System::block_number() < 30 {
5094 ocw_step();
5095 }
5096
5097 let sign_payload = Pallet::sign_validate_payload();
5098 assert_ok!(sign_payload);
5099 })
5100 }
5101
5102 #[test]
5103 fn get_public_key_sucess() {
5104 let mut env = new_ocw_env();
5105 env.ext.execute_with(|| {
5106 System::set_block_number(10);
5107 Pallet::offchain_worker(10);
5108 while System::block_number() < 30 {
5109 ocw_step();
5110 }
5111
5112 let afdt_key = Pallet::get_finalized_afdt_key().unwrap();
5113 let pub_key = Pallet::get_public_key(afdt_key);
5114 assert_ok!(pub_key);
5115 })
5116 }
5117
5118 #[test]
5119 fn get_elects_success() {
5120 chain_manager_test_ext().execute_with(|| {
5121 let users = vec![ALICE, CHARLIE, ALAN, MIKE, BOB, NIX];
5122 set_default_users_balance_and_hold(users).unwrap();
5123
5124 RoleAdapter::enroll(&ALICE, 200, Fortitude::Force).unwrap();
5125 RoleAdapter::enroll(&BOB, 200, Fortitude::Force).unwrap();
5126 RoleAdapter::enroll(&MIKE, 200, Fortitude::Force).unwrap();
5127
5128 RoleAdapter::fund(
5129 &ALICE,
5130 &Funder::Direct(CHARLIE),
5131 100,
5132 Precision::Exact,
5133 Fortitude::Force,
5134 )
5135 .unwrap();
5136 RoleAdapter::fund(
5137 &BOB,
5138 &Funder::Direct(ALAN),
5139 150,
5140 Precision::Exact,
5141 Fortitude::Force,
5142 )
5143 .unwrap();
5144 RoleAdapter::fund(
5145 &MIKE,
5146 &Funder::Direct(NIX),
5147 125,
5148 Precision::Exact,
5149 Fortitude::Force,
5150 )
5151 .unwrap();
5152
5153 System::set_block_number(10);
5154 SessionStartsAt::put(15);
5155 AffidavitBeginsAt::put(Duration::from_rational(2u32, 10u32));
5156 AffidavitEndsAt::put(Duration::from_rational(8u32, 10u32));
5157 ElectionBeginsAt::put(Duration::from_rational(5u32, 10u32));
5158
5159 System::set_block_number(15);
5160 System::set_block_number(135);
5161 AffidavitKeys::insert((1, AFFIDAVIT_KEY_A), ALICE);
5162 AffidavitKeys::insert((1, AFFIDAVIT_KEY_B), BOB);
5163 AffidavitKeys::insert((1, AFFIDAVIT_KEY_C), MIKE);
5164
5165 let affidavit_alice_id = Pallet::gen_affidavit(&AFFIDAVIT_KEY_A).unwrap();
5166 Pallet::submit_affidavit(&AFFIDAVIT_KEY_A, &affidavit_alice_id).unwrap();
5167 let affidavit_bob_id = Pallet::gen_affidavit(&AFFIDAVIT_KEY_B).unwrap();
5168 Pallet::submit_affidavit(&AFFIDAVIT_KEY_B, &affidavit_bob_id).unwrap();
5169 let affidavit_mike_id = Pallet::gen_affidavit(&AFFIDAVIT_KEY_C).unwrap();
5170 Pallet::submit_affidavit(&AFFIDAVIT_KEY_C, &affidavit_mike_id).unwrap();
5171
5172 System::set_block_number(315);
5173 assert_ok!(Internals::prepare_election(&Some(ALICE)));
5174
5175 let actual_elects = Pallet::get_elects().unwrap();
5176 let expected_reveal = vec![BOB, MIKE, ALICE];
5177 assert_eq!(actual_elects, expected_reveal);
5178 })
5179 }
5180
5181 #[test]
5182 fn get_elects_returns_err_unable_to_reveal_elected() {
5183 chain_manager_test_ext().execute_with(|| {
5184 let users = vec![ALICE, CHARLIE, ALAN, MIKE, BOB, NIX];
5185 set_default_users_balance_and_hold(users).unwrap();
5186
5187 RoleAdapter::enroll(&ALICE, 200, Fortitude::Force).unwrap();
5188 RoleAdapter::enroll(&BOB, 200, Fortitude::Force).unwrap();
5189 RoleAdapter::enroll(&MIKE, 200, Fortitude::Force).unwrap();
5190
5191 RoleAdapter::fund(
5192 &ALICE,
5193 &Funder::Direct(CHARLIE),
5194 100,
5195 Precision::Exact,
5196 Fortitude::Force,
5197 )
5198 .unwrap();
5199 RoleAdapter::fund(
5200 &BOB,
5201 &Funder::Direct(ALAN),
5202 150,
5203 Precision::Exact,
5204 Fortitude::Force,
5205 )
5206 .unwrap();
5207 RoleAdapter::fund(
5208 &MIKE,
5209 &Funder::Direct(NIX),
5210 125,
5211 Precision::Exact,
5212 Fortitude::Force,
5213 )
5214 .unwrap();
5215
5216 System::set_block_number(10);
5217 SessionStartsAt::put(15);
5218 AffidavitBeginsAt::put(Duration::from_rational(2u32, 10u32));
5219 AffidavitEndsAt::put(Duration::from_rational(8u32, 10u32));
5220 ElectionBeginsAt::put(Duration::from_rational(5u32, 10u32));
5221
5222 System::set_block_number(15);
5223 System::set_block_number(135);
5224 AffidavitKeys::insert((1, AFFIDAVIT_KEY_A), ALICE);
5225 AffidavitKeys::insert((1, AFFIDAVIT_KEY_B), BOB);
5226 AffidavitKeys::insert((1, AFFIDAVIT_KEY_C), MIKE);
5227
5228 let affidavit_alice_id = Pallet::gen_affidavit(&AFFIDAVIT_KEY_A).unwrap();
5229 Pallet::submit_affidavit(&AFFIDAVIT_KEY_A, &affidavit_alice_id).unwrap();
5230 let affidavit_bob_id = Pallet::gen_affidavit(&AFFIDAVIT_KEY_B).unwrap();
5231 Pallet::submit_affidavit(&AFFIDAVIT_KEY_B, &affidavit_bob_id).unwrap();
5232 let affidavit_mike_id = Pallet::gen_affidavit(&AFFIDAVIT_KEY_C).unwrap();
5233 Pallet::submit_affidavit(&AFFIDAVIT_KEY_C, &affidavit_mike_id).unwrap();
5234
5235 let elects = Pallet::get_elects();
5236 assert_err!(elects, Error::UnableToRevealElected);
5237 })
5238 }
5239
5240 #[test]
5241 fn fetch_affidavit_success() {
5242 let mut env = new_ocw_env();
5243 env.ext.execute_with(||{
5244 set_session_config();
5245 CurrentSession::put(1);
5246 let users = vec![ALICE, NIX, AMY, CHARLIE];
5247 set_default_users_balance_and_hold(users).unwrap();
5248
5249 enroll_author_with_default_collateral(ALICE).unwrap();
5250 direct_fund_author(NIX, ALICE, STANDARD_FUND).unwrap();
5251 direct_fund_author(AMY, ALICE, SMALL_FUND).unwrap();
5252 direct_fund_author(CHARLIE, ALICE, LARGE_FUND).unwrap();
5253
5254 let alice_afdt_id = generate_affidavit_id();
5255
5256 System::set_block_number(150);
5257 ext_validate(ALICE, alice_afdt_id.clone()).unwrap();
5258
5259 let alice_nxt_afdt_id = generate_affidavit_id();
5260
5261 let alice_payload = TestAfdtPayload {
5262 active_afdt_pub: alice_afdt_id.clone(),
5263 next_afdt_pub: alice_nxt_afdt_id.clone(),
5264 };
5265
5266 System::set_block_number(AFDT_SUBMISSION_START);
5267 ext_declare_affidavit(ALICE, alice_payload).unwrap();
5268
5269 let actual_affidavit = Pallet::fetch_affidavit(alice_afdt_id.clone()).unwrap();
5270 let expected_affidavit = vec![(Funder::Direct(NIX), STANDARD_FUND), (Funder::Direct(CHARLIE), LARGE_FUND), (Funder::Direct(AMY), SMALL_FUND)];
5271 assert_eq!(actual_affidavit, expected_affidavit);
5272
5273 assert!(Pallet::fetch_affidavit(alice_nxt_afdt_id).is_err());
5274 })
5275 }
5276
5277 #[test]
5278 fn fetch_affidavit_for_success() {
5279 let mut env = new_ocw_env();
5280 env.ext.execute_with(||{
5281 set_session_config();
5282 CurrentSession::put(1);
5283 let users = vec![ALICE, NIX, AMY, CHARLIE];
5284 set_default_users_balance_and_hold(users).unwrap();
5285
5286 enroll_author_with_default_collateral(ALICE).unwrap();
5287 direct_fund_author(NIX, ALICE, STANDARD_FUND).unwrap();
5288 direct_fund_author(AMY, ALICE, SMALL_FUND).unwrap();
5289 direct_fund_author(CHARLIE, ALICE, LARGE_FUND).unwrap();
5290
5291 let alice_afdt_id = generate_affidavit_id();
5292
5293 System::set_block_number(150);
5294 ext_validate(ALICE, alice_afdt_id.clone()).unwrap();
5295
5296 let alice_nxt_afdt_id = generate_affidavit_id();
5297
5298 let alice_payload = TestAfdtPayload {
5299 active_afdt_pub: alice_afdt_id.clone(),
5300 next_afdt_pub: alice_nxt_afdt_id.clone(),
5301 };
5302
5303 System::set_block_number(AFDT_SUBMISSION_START);
5304 ext_declare_affidavit(ALICE, alice_payload).unwrap();
5305
5306 let for_session = CurrentSession::get() + 1;
5307 let actual_affidavit = Pallet::fetch_affidavit_for(alice_afdt_id.clone(), for_session).unwrap();
5308 let expected_affidavit = vec![(Funder::Direct(NIX), STANDARD_FUND), (Funder::Direct(CHARLIE), LARGE_FUND), (Funder::Direct(AMY), SMALL_FUND)];
5309 assert_eq!(actual_affidavit, expected_affidavit);
5310
5311 let for_session = CurrentSession::get() + 2;
5312 assert_err!(Pallet::fetch_affidavit_for(alice_afdt_id, for_session), Error::AffidavitKeyPairNotFound);
5313 })
5314 }
5315
5316 #[test]
5317 fn is_contesting_success() {
5318 let mut env = new_ocw_env();
5319 env.ext.execute_with(||{
5320 set_session_config();
5321 CurrentSession::put(1);
5322 let users = vec![ALICE, NIX];
5323 set_default_users_balance_and_hold(users).unwrap();
5324
5325 enroll_author_with_default_collateral(ALICE).unwrap();
5326 direct_fund_author(NIX, ALICE, STANDARD_FUND).unwrap();
5327
5328 let alice_afdt_id = generate_affidavit_id();
5329
5330 System::set_block_number(150);
5331 ext_validate(ALICE, alice_afdt_id.clone()).unwrap();
5332
5333 let alice_nxt_afdt_id = generate_affidavit_id();
5334
5335 let alice_payload = TestAfdtPayload {
5336 active_afdt_pub: alice_afdt_id.clone(),
5337 next_afdt_pub: alice_nxt_afdt_id.clone(),
5338 };
5339
5340 System::set_block_number(AFDT_SUBMISSION_START);
5341 ext_declare_affidavit(ALICE, alice_payload).unwrap();
5342
5343 assert!(Pallet::is_contesting(ALICE));
5344 assert!(!Pallet::is_contesting(NIX));
5345 })
5346 }
5347
5348 #[test]
5349 fn is_pursuing_success() {
5350 let mut env = new_ocw_env();
5351 env.ext.execute_with(|| {
5352 set_default_user_balance_and_hold(ALICE).unwrap();
5353 set_default_user_balance_and_hold(BOB).unwrap();
5354 set_default_user_balance_and_hold(ALAN).unwrap();
5355 enroll_authors_with_default_collateral(vec![ALICE, BOB, ALAN]).unwrap();
5356
5357 let alice_afdt_id = generate_affidavit_id();
5358 let alan_afdt_id = generate_affidavit_id();
5359
5360 CurrentSession::put(1);
5361 let next_session = CurrentSession::get() + 1;
5362 let next_afdt_session = next_session + 1;
5363
5364 AffidavitKeys::insert((next_session, alice_afdt_id), ALICE);
5365 AffidavitKeys::insert((next_afdt_session, alan_afdt_id), ALAN);
5366
5367 assert!(Pallet::is_pursuing(ALICE));
5368 assert!(Pallet::is_pursuing(ALAN));
5369 assert!(!Pallet::is_pursuing(BOB));
5370 })
5371 }
5372
5373 #[test]
5374 fn can_declare_success() {
5375 let mut env = new_ocw_env();
5376 env.ext.execute_with(||{
5377 set_session_config();
5378 CurrentSession::put(1);
5379 set_default_user_balance_and_hold(ALICE).unwrap();
5380 enroll_author_with_default_collateral(ALICE).unwrap();
5381 let alice_afdt_id = generate_affidavit_id();
5382
5383 System::set_block_number(150);
5384 ext_validate(ALICE, alice_afdt_id.clone()).unwrap();
5385
5386 assert_ok!(Pallet::can_declare(alice_afdt_id));
5387 })
5388 }
5389
5390 #[test]
5391 fn can_declare_err_affidavit_author_not_found() {
5392 let mut env = new_ocw_env();
5393 env.ext.execute_with(||{
5394 set_session_config();
5395 CurrentSession::put(1);
5396 set_default_user_balance_and_hold(ALICE).unwrap();
5397 enroll_author_with_default_collateral(ALICE).unwrap();
5398 let alice_afdt_id = generate_affidavit_id();
5399
5400 assert_err!(Pallet::can_declare(alice_afdt_id), Error::AffidavitAuthorNotFound);
5401 })
5402 }
5403
5404 #[test]
5405 fn can_elect_success() {
5406 let mut env = new_ocw_env();
5407 env.ext.execute_with(||{
5408 set_session_config();
5409 CurrentSession::put(1);
5410 set_default_user_balance_and_hold(ALICE).unwrap();
5411 enroll_author_with_default_collateral(ALICE).unwrap();
5412
5413 System::set_block_number(450);
5414 set_block_author(ALICE);
5415 assert_ok!(Pallet::can_elect(ALICE));
5416 })
5417 }
5418
5419 #[test]
5420 fn can_elect_err_not_a_block_author() {
5421 let mut env = new_ocw_env();
5422 env.ext.execute_with(||{
5423 set_session_config();
5424 CurrentSession::put(1);
5425 set_default_user_balance_and_hold(BOB).unwrap();
5426 enroll_author_with_default_collateral(BOB).unwrap();
5427 set_default_user_balance_and_hold(ALICE).unwrap();
5428 enroll_author_with_default_collateral(ALICE).unwrap();
5429
5430 System::set_block_number(450);
5431 set_block_author(ALICE);
5432 assert_err!(Pallet::can_elect(BOB), Error::NotABlockAuthor);
5433 })
5434 }
5435}