From 8087446163275811858fbe98a215fe16adad7390 Mon Sep 17 00:00:00 2001 From: Artur Gontijo Date: Mon, 9 Feb 2026 15:49:34 -0300 Subject: [PATCH 01/10] [author-inherent] Move kick_off_authorship_validation() to post_inherents() --- pallets/author-inherent/Cargo.toml | 2 +- pallets/author-inherent/src/lib.rs | 127 ++++++--------------------- pallets/author-inherent/src/mock.rs | 15 +++- pallets/author-inherent/src/tests.rs | 39 ++------ template/runtime/src/lib.rs | 4 +- 5 files changed, 51 insertions(+), 136 deletions(-) diff --git a/pallets/author-inherent/Cargo.toml b/pallets/author-inherent/Cargo.toml index ceb4c9a4..af41f1dd 100644 --- a/pallets/author-inherent/Cargo.toml +++ b/pallets/author-inherent/Cargo.toml @@ -15,6 +15,7 @@ nimbus-primitives = { workspace = true } scale-info = { workspace = true } sp-api = { workspace = true } sp-application-crypto = { workspace = true } +sp-core = { workspace = true } sp-inherents = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } @@ -24,7 +25,6 @@ frame-benchmarking = { workspace = true, optional = true } [dev-dependencies] frame-support-test = { workspace = true } -sp-core = { workspace = true } sp-io = { workspace = true } [features] diff --git a/pallets/author-inherent/src/lib.rs b/pallets/author-inherent/src/lib.rs index a0398da8..9cc952cf 100644 --- a/pallets/author-inherent/src/lib.rs +++ b/pallets/author-inherent/src/lib.rs @@ -22,13 +22,10 @@ extern crate alloc; -use alloc::string::String; use frame_support::traits::{FindAuthor, Get}; -use nimbus_primitives::{ - AccountLookup, CanAuthor, NimbusId, SlotBeacon, INHERENT_IDENTIFIER, NIMBUS_ENGINE_ID, -}; -use parity_scale_codec::{Decode, Encode, FullCodec}; -use sp_inherents::{InherentIdentifier, IsFatalError}; +use nimbus_primitives::{AccountLookup, CanAuthor, NimbusId, SlotBeacon, NIMBUS_ENGINE_ID}; +use parity_scale_codec::{Decode, FullCodec}; +use sp_core::ByteArray; use sp_runtime::ConsensusEngineId; pub use crate::weights::WeightInfo; @@ -98,84 +95,22 @@ pub mod pallet { #[pallet::storage] pub type Author = StorageValue<_, T::AuthorId, OptionQuery>; - /// Check if the inherent was included - #[pallet::storage] - pub type InherentIncluded = StorageValue<_, bool, ValueQuery>; - #[pallet::hooks] impl Hooks> for Pallet { - fn on_initialize(_: BlockNumberFor) -> Weight { - // Now extract the author from the digest - let digest = >::digest(); - let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime()); - if let Some(author) = Self::find_author(pre_runtime_digests) { - // Store the author so we can confirm eligibility after the inherents have executed - >::put(&author); - } - - // on_initialize: 1 write - // on_finalize: 1 read + 1 write - T::DbWeight::get().reads_writes(1, 2) - } - fn on_finalize(_: BlockNumberFor) { - // According to parity, the only way to ensure that a mandatory inherent is included - // is by checking on block finalization that the inherent set a particular storage item: - // https://github.com/paritytech/polkadot-sdk/issues/2841#issuecomment-1876040854 + fn integrity_test() { + // Test that SlotBeacon can be called and returns a valid slot + let slot = T::SlotBeacon::slot(); + // Author storage should be accessible (this is a compile-time check) assert!( - InherentIncluded::::take(), - "Block invalid, missing inherent `kick_off_authorship_validation`" + Author::::get().is_none(), + "Author storage should be none" ); - } - } - - #[pallet::call] - impl Pallet { - /// This inherent is a workaround to run code after the "real" inherents have executed, - /// but before transactions are executed. - // This should go into on_post_inherents when it is ready https://github.com/paritytech/substrate/pull/10128 - // TODO better weight. For now we just set a somewhat conservative fudge factor - #[pallet::call_index(0)] - #[pallet::weight((T::WeightInfo::kick_off_authorship_validation(), DispatchClass::Mandatory))] - pub fn kick_off_authorship_validation(origin: OriginFor) -> DispatchResultWithPostInfo { - ensure_none(origin)?; - - // First check that the slot number is valid (greater than the previous highest) - let new_slot = T::SlotBeacon::slot(); - // Now check that the author is valid in this slot - assert!( - T::CanAuthor::can_author(&Self::get(), &new_slot), - "Block invalid, supplied author is not eligible." + // Test that CanAuthor trait can be called + let _ = Pallet::::can_author( + &NimbusId::from_slice(&[0u8; 32]).expect("Valid NimbusId"), + &slot, ); - - InherentIncluded::::put(true); - - Ok(Pays::No.into()) - } - } - - #[pallet::inherent] - impl ProvideInherent for Pallet { - type Call = Call; - type Error = InherentError; - const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER; - - fn is_inherent_required(_: &InherentData) -> Result, Self::Error> { - // Return Ok(Some(_)) unconditionally because this inherent is required in every block - // If it is not found, throw an AuthorInherentRequired error. - Ok(Some(InherentError::Other(String::from( - "Inherent required to manually initiate author validation", - )))) - } - - // Regardless of whether the client is still supplying the author id, - // we will create the new empty-payload inherent extrinsic. - fn create_inherent(_data: &InherentData) -> Option { - Some(Call::kick_off_authorship_validation {}) - } - - fn is_inherent(call: &Self::Call) -> bool { - matches!(call, Call::kick_off_authorship_validation { .. }) } } @@ -230,28 +165,22 @@ pub mod pallet { } } -#[derive(Encode)] -#[cfg_attr(feature = "std", derive(Debug, Decode))] -pub enum InherentError { - Other(String), -} - -impl IsFatalError for InherentError { - fn is_fatal_error(&self) -> bool { - match *self { - InherentError::Other(_) => true, - } - } -} - -impl InherentError { - /// Try to create an instance ouf of the given identifier and data. - #[cfg(feature = "std")] - pub fn try_from(id: &InherentIdentifier, data: &[u8]) -> Option { - if id == &INHERENT_IDENTIFIER { - ::decode(&mut &data[..]).ok() +impl frame_support::traits::PostInherents for Pallet { + fn post_inherents() { + // Extract the author from the digest + let digest = >::digest(); + let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime()); + if let Some(author) = Self::find_author(pre_runtime_digests) { + // First check that the slot number is valid (greater than the previous highest) + let new_slot = T::SlotBeacon::slot(); + // Now check that the author is valid in this slot + assert!( + T::CanAuthor::can_author(&author, &new_slot), + "Block invalid, supplied author is not eligible." + ); + >::put(&author); } else { - None + panic!("Block invalid, missing author in pre-runtime digest"); } } } diff --git a/pallets/author-inherent/src/mock.rs b/pallets/author-inherent/src/mock.rs index e00e60a1..ad9d740e 100644 --- a/pallets/author-inherent/src/mock.rs +++ b/pallets/author-inherent/src/mock.rs @@ -30,7 +30,7 @@ frame_support::construct_runtime!( pub enum Test { System: frame_system::{Pallet, Call, Config, Storage, Event}, - AuthorInherent: pallet_testing::{Pallet, Call, Storage}, + AuthorInherent: pallet_testing::{Pallet, Storage}, } ); @@ -72,14 +72,14 @@ impl frame_system::Config for Test { type SingleBlockMigrations = (); type MultiBlockMigrator = (); type PreInherents = (); - type PostInherents = (); + type PostInherents = AuthorInherent; type PostTransactions = (); } pub struct DummyBeacon {} impl nimbus_primitives::SlotBeacon for DummyBeacon { fn slot() -> u32 { - 0 + System::block_number() as u32 + 1 } } @@ -98,10 +98,17 @@ impl AccountLookup for MockAccountLookup { } } +pub struct TestCanAuthor; +impl nimbus_primitives::CanAuthor for TestCanAuthor { + fn can_author(author: &u64, slot: &u32) -> bool { + Authors::get().contains(author) && slot > &0 + } +} + impl pallet_testing::Config for Test { type AuthorId = u64; type AccountLookup = MockAccountLookup; - type CanAuthor = (); + type CanAuthor = TestCanAuthor; type SlotBeacon = DummyBeacon; type WeightInfo = (); } diff --git a/pallets/author-inherent/src/tests.rs b/pallets/author-inherent/src/tests.rs index 0bae3104..f4903324 100644 --- a/pallets/author-inherent/src/tests.rs +++ b/pallets/author-inherent/src/tests.rs @@ -16,22 +16,14 @@ use crate::mock::*; use crate::pallet::Author; -use frame_support::traits::{OnFinalize, OnInitialize}; +use frame_support::traits::PostInherents; use nimbus_primitives::{NimbusId, NIMBUS_ENGINE_ID}; use parity_scale_codec::Encode; use sp_core::{ByteArray, H256}; use sp_runtime::{Digest, DigestItem}; #[test] -fn kick_off_authorship_validation_is_mandatory() { - use frame_support::dispatch::{DispatchClass, GetDispatchInfo}; - - let info = crate::Call::::kick_off_authorship_validation {}.get_dispatch_info(); - assert_eq!(info.class, DispatchClass::Mandatory); -} - -#[test] -fn test_author_is_available_after_on_initialize() { +fn test_author_is_extracted_and_stored_from_pre_runtime_digest() { new_test_ext().execute_with(|| { let block_number = 1; System::initialize( @@ -45,29 +37,14 @@ fn test_author_is_available_after_on_initialize() { }, ); - AuthorInherent::on_initialize(block_number); - assert_eq!(Some(ALICE), >::get()); - }); -} + // Initially, no author is set + assert_eq!(None, >::get()); -#[test] -fn test_author_is_still_available_after_on_finalize() { - new_test_ext().execute_with(|| { - let block_number = 1; - System::initialize( - &block_number, - &H256::default(), - &Digest { - logs: vec![DigestItem::PreRuntime( - NIMBUS_ENGINE_ID, - NimbusId::from_slice(&ALICE_NIMBUS).unwrap().encode(), - )], - }, - ); + // Call post_inherents which extracts the author from the digest + // and stores it in the Author storage + AuthorInherent::post_inherents(); - AuthorInherent::on_initialize(block_number); - let _ = AuthorInherent::kick_off_authorship_validation(None.into()); - AuthorInherent::on_finalize(block_number); + // Author should now be stored assert_eq!(Some(ALICE), >::get()); }); } diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index 54544e75..49b877ff 100644 --- a/template/runtime/src/lib.rs +++ b/template/runtime/src/lib.rs @@ -343,6 +343,8 @@ impl frame_system::Config for Runtime { type PostInherents = ( // Validate timestamp provided by the consensus client NimbusAsyncBacking, + // Validate author provided by the consensus client + AuthorInherent, ); type PostTransactions = (); type ExtensionsWeightInfo = (); @@ -698,7 +700,7 @@ construct_runtime!( TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event} = 11, // Nimbus support. The order of these are important and shall not change. - AuthorInherent: pallet_author_inherent::{Pallet, Call, Storage, Inherent} = 20, + AuthorInherent: pallet_author_inherent::{Pallet, Storage} = 20, AuthorFilter: pallet_author_slot_filter::{Pallet, Storage, Event, Config} = 21, PotentialAuthorSet: pallet_account_set::{Pallet, Storage, Config} = 22, NimbusAsyncBacking: pallet_async_backing::{Pallet, Storage} = 23, From 10bda3f21946426c89950758697abde485fdfccb Mon Sep 17 00:00:00 2001 From: Artur Gontijo Date: Thu, 26 Feb 2026 07:49:21 -0300 Subject: [PATCH 02/10] Remove benchmarks and weights files --- pallets/author-inherent/src/benchmarks.rs | 30 ------ pallets/author-inherent/src/lib.rs | 8 -- pallets/author-inherent/src/weights.rs | 107 ---------------------- template/runtime/src/lib.rs | 1 - 4 files changed, 146 deletions(-) delete mode 100644 pallets/author-inherent/src/benchmarks.rs delete mode 100644 pallets/author-inherent/src/weights.rs diff --git a/pallets/author-inherent/src/benchmarks.rs b/pallets/author-inherent/src/benchmarks.rs deleted file mode 100644 index 9793b878..00000000 --- a/pallets/author-inherent/src/benchmarks.rs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright Moonsong Labs -// This file is part of Moonkit. - -// Moonkit is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Moonkit is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Moonkit. If not, see . - -#![cfg(feature = "runtime-benchmarks")] - -use crate::{Call, Config, Pallet}; -use frame_benchmarking::benchmarks; -use frame_system::RawOrigin; -use nimbus_primitives::CanAuthor; -use nimbus_primitives::SlotBeacon; -benchmarks! { - kick_off_authorship_validation { - // The slot inserted needs to be higher than that already in storage - T::SlotBeacon::set_slot(100); - Pallet::::set_eligible_author(&T::SlotBeacon::slot()); - }: _(RawOrigin::None) -} diff --git a/pallets/author-inherent/src/lib.rs b/pallets/author-inherent/src/lib.rs index 9cc952cf..86bb51e4 100644 --- a/pallets/author-inherent/src/lib.rs +++ b/pallets/author-inherent/src/lib.rs @@ -28,15 +28,9 @@ use parity_scale_codec::{Decode, FullCodec}; use sp_core::ByteArray; use sp_runtime::ConsensusEngineId; -pub use crate::weights::WeightInfo; pub use exec::BlockExecutor; pub use pallet::*; -#[cfg(any(test, feature = "runtime-benchmarks"))] -mod benchmarks; - -pub mod weights; - mod exec; #[cfg(test)] @@ -73,8 +67,6 @@ pub mod pallet { /// Some way of determining the current slot for purposes of verifying the author's eligibility type SlotBeacon: SlotBeacon; - - type WeightInfo: WeightInfo; } impl sp_runtime::BoundToRuntimeAppPublic for Pallet { diff --git a/pallets/author-inherent/src/weights.rs b/pallets/author-inherent/src/weights.rs deleted file mode 100644 index b65d0ef0..00000000 --- a/pallets/author-inherent/src/weights.rs +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright Moonsong Labs -// This file is part of Moonkit. - -// Moonkit is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// Moonkit is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Moonkit. If not, see . - - -//! Autogenerated weights for pallet_author_inherent -//! -//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-05-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `benchmarker`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 - -// Executed Command: -// ./target/release/moonbeam -// benchmark -// pallet -// --execution=wasm -// --wasm-execution=compiled -// --pallet -// * -// --extrinsic -// * -// --steps -// 50 -// --repeat -// 20 -// --template=./benchmarking/frame-weight-template.hbs -// --json-file -// raw.json -// --output -// weights/ - -#![cfg_attr(rustfmt, rustfmt_skip)] -#![allow(unused_parens)] -#![allow(unused_imports)] - -use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; - -/// Weight functions needed for pallet_author_inherent. -pub trait WeightInfo { - fn kick_off_authorship_validation() -> Weight; -} - -/// Weights for pallet_author_inherent using the Substrate node and recommended hardware. -pub struct SubstrateWeight(PhantomData); -impl WeightInfo for SubstrateWeight { - /// Storage: ParachainSystem ValidationData (r:1 w:0) - /// Proof Skipped: ParachainSystem ValidationData (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: AuthorInherent HighestSlotSeen (r:1 w:1) - /// Proof: AuthorInherent HighestSlotSeen (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: AuthorInherent Author (r:1 w:0) - /// Proof: AuthorInherent Author (max_values: Some(1), max_size: Some(20), added: 515, mode: MaxEncodedLen) - /// Storage: ParachainStaking SelectedCandidates (r:1 w:0) - /// Proof Skipped: ParachainStaking SelectedCandidates (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: AuthorFilter EligibleCount (r:1 w:0) - /// Proof Skipped: AuthorFilter EligibleCount (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Randomness PreviousLocalVrfOutput (r:1 w:0) - /// Proof Skipped: Randomness PreviousLocalVrfOutput (max_values: Some(1), max_size: None, mode: Measured) - fn kick_off_authorship_validation() -> Weight { - // Proof Size summary in bytes: - // Measured: `371` - // Estimated: `10418` - // Minimum execution time: 25_775_000 picoseconds. - Weight::from_parts(26_398_000, 10418) - .saturating_add(T::DbWeight::get().reads(6_u64)) - .saturating_add(T::DbWeight::get().writes(2_u64)) - } -} - -// For backwards compatibility and tests -impl WeightInfo for () { - /// Storage: ParachainSystem ValidationData (r:1 w:0) - /// Proof Skipped: ParachainSystem ValidationData (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: AuthorInherent HighestSlotSeen (r:1 w:1) - /// Proof: AuthorInherent HighestSlotSeen (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) - /// Storage: AuthorInherent Author (r:1 w:0) - /// Proof: AuthorInherent Author (max_values: Some(1), max_size: Some(20), added: 515, mode: MaxEncodedLen) - /// Storage: ParachainStaking SelectedCandidates (r:1 w:0) - /// Proof Skipped: ParachainStaking SelectedCandidates (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: AuthorFilter EligibleCount (r:1 w:0) - /// Proof Skipped: AuthorFilter EligibleCount (max_values: Some(1), max_size: None, mode: Measured) - /// Storage: Randomness PreviousLocalVrfOutput (r:1 w:0) - /// Proof Skipped: Randomness PreviousLocalVrfOutput (max_values: Some(1), max_size: None, mode: Measured) - fn kick_off_authorship_validation() -> Weight { - // Proof Size summary in bytes: - // Measured: `371` - // Estimated: `10418` - // Minimum execution time: 25_775_000 picoseconds. - Weight::from_parts(26_398_000, 10418) - .saturating_add(RocksDbWeight::get().reads(6_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } -} \ No newline at end of file diff --git a/template/runtime/src/lib.rs b/template/runtime/src/lib.rs index 49b877ff..fc65975a 100644 --- a/template/runtime/src/lib.rs +++ b/template/runtime/src/lib.rs @@ -657,7 +657,6 @@ impl pallet_author_inherent::Config for Runtime { type SlotBeacon = cumulus_pallet_parachain_system::RelaychainDataProvider; type AccountLookup = PotentialAuthorSet; type CanAuthor = AuthorFilter; - type WeightInfo = (); } impl pallet_author_slot_filter::Config for Runtime { From 65eac67317b0169ec45f5a0f3130024d34528d00 Mon Sep 17 00:00:00 2001 From: Artur Gontijo Date: Thu, 26 Feb 2026 08:00:23 -0300 Subject: [PATCH 03/10] Remove unused deps --- Cargo.lock | 2 -- pallets/author-inherent/Cargo.toml | 5 +---- pallets/author-inherent/src/lib.rs | 2 -- pallets/author-inherent/src/tests.rs | 16 ++++++++++++++++ 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 96f4d94a..46bf385c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7232,10 +7232,8 @@ dependencies = [ "nimbus-primitives", "parity-scale-codec", "scale-info", - "sp-api", "sp-application-crypto", "sp-core", - "sp-inherents", "sp-io", "sp-runtime", "sp-std", diff --git a/pallets/author-inherent/Cargo.toml b/pallets/author-inherent/Cargo.toml index af41f1dd..adf63465 100644 --- a/pallets/author-inherent/Cargo.toml +++ b/pallets/author-inherent/Cargo.toml @@ -13,10 +13,8 @@ frame-system = { workspace = true } log = { workspace = true } nimbus-primitives = { workspace = true } scale-info = { workspace = true } -sp-api = { workspace = true } sp-application-crypto = { workspace = true } sp-core = { workspace = true } -sp-inherents = { workspace = true } sp-runtime = { workspace = true } sp-std = { workspace = true } @@ -37,9 +35,8 @@ std = [ "nimbus-primitives/std", "parity-scale-codec/std", "scale-info/std", - "sp-api/std", "sp-application-crypto/std", - "sp-inherents/std", + "sp-core/std", "sp-runtime/std", "sp-std/std", ] diff --git a/pallets/author-inherent/src/lib.rs b/pallets/author-inherent/src/lib.rs index 86bb51e4..b20bddc0 100644 --- a/pallets/author-inherent/src/lib.rs +++ b/pallets/author-inherent/src/lib.rs @@ -20,8 +20,6 @@ #![cfg_attr(not(feature = "std"), no_std)] -extern crate alloc; - use frame_support::traits::{FindAuthor, Get}; use nimbus_primitives::{AccountLookup, CanAuthor, NimbusId, SlotBeacon, NIMBUS_ENGINE_ID}; use parity_scale_codec::{Decode, FullCodec}; diff --git a/pallets/author-inherent/src/tests.rs b/pallets/author-inherent/src/tests.rs index f4903324..4918c61f 100644 --- a/pallets/author-inherent/src/tests.rs +++ b/pallets/author-inherent/src/tests.rs @@ -48,3 +48,19 @@ fn test_author_is_extracted_and_stored_from_pre_runtime_digest() { assert_eq!(Some(ALICE), >::get()); }); } + +#[test] +#[should_panic(expected = "Block invalid, missing author in pre-runtime digest")] +fn test_post_inherents_panics_when_author_missing_from_digest() { + new_test_ext().execute_with(|| { + let block_number = 1; + System::initialize( + &block_number, + &H256::default(), + &Digest { logs: vec![] }, + ); + + // post_inherents should panic because there is no pre-runtime digest with the author + AuthorInherent::post_inherents(); + }); +} From 6a9bb33cf3daa1ba5b303b21ec17f6aa4341684c Mon Sep 17 00:00:00 2001 From: Artur Gontijo Date: Thu, 26 Feb 2026 08:01:15 -0300 Subject: [PATCH 04/10] Update pallet description --- pallets/author-inherent/src/lib.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pallets/author-inherent/src/lib.rs b/pallets/author-inherent/src/lib.rs index b20bddc0..1ea23d98 100644 --- a/pallets/author-inherent/src/lib.rs +++ b/pallets/author-inherent/src/lib.rs @@ -14,9 +14,10 @@ // You should have received a copy of the GNU General Public License // along with Moonkit. If not, see . -//! Pallet that allows block authors to include their identity in a block via an inherent. -//! Currently the author does not _prove_ their identity, just states it. So it should not be used, -//! for things like equivocation slashing that require authenticated authorship information. +//! Pallet that extracts the block author identity from the pre-runtime digest and validates +//! eligibility via a `PostInherents` hook. Currently the author does not _prove_ their identity, +//! just states it. So it should not be used for things like equivocation slashing that require +//! authenticated authorship information. #![cfg_attr(not(feature = "std"), no_std)] From 2a7926e4cd5b4419e01534632970f1050c484599 Mon Sep 17 00:00:00 2001 From: Artur Gontijo Date: Thu, 26 Feb 2026 08:05:34 -0300 Subject: [PATCH 05/10] fmt --- pallets/author-inherent/src/mock.rs | 1 - pallets/author-inherent/src/tests.rs | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/pallets/author-inherent/src/mock.rs b/pallets/author-inherent/src/mock.rs index ad9d740e..d4d63155 100644 --- a/pallets/author-inherent/src/mock.rs +++ b/pallets/author-inherent/src/mock.rs @@ -110,7 +110,6 @@ impl pallet_testing::Config for Test { type AccountLookup = MockAccountLookup; type CanAuthor = TestCanAuthor; type SlotBeacon = DummyBeacon; - type WeightInfo = (); } /// Build genesis storage according to the mock runtime. diff --git a/pallets/author-inherent/src/tests.rs b/pallets/author-inherent/src/tests.rs index 4918c61f..d25abc4b 100644 --- a/pallets/author-inherent/src/tests.rs +++ b/pallets/author-inherent/src/tests.rs @@ -54,11 +54,7 @@ fn test_author_is_extracted_and_stored_from_pre_runtime_digest() { fn test_post_inherents_panics_when_author_missing_from_digest() { new_test_ext().execute_with(|| { let block_number = 1; - System::initialize( - &block_number, - &H256::default(), - &Digest { logs: vec![] }, - ); + System::initialize(&block_number, &H256::default(), &Digest { logs: vec![] }); // post_inherents should panic because there is no pre-runtime digest with the author AuthorInherent::post_inherents(); From f9b6b085420854108f498800360e5bd4c209d466 Mon Sep 17 00:00:00 2001 From: Artur Gontijo Date: Thu, 26 Feb 2026 09:09:00 -0300 Subject: [PATCH 06/10] Coderabbit on_initialize() kill --- pallets/author-inherent/src/lib.rs | 8 ++++ pallets/author-inherent/src/tests.rs | 55 +++++++++++++++++++++++++++- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/pallets/author-inherent/src/lib.rs b/pallets/author-inherent/src/lib.rs index 1ea23d98..a016e992 100644 --- a/pallets/author-inherent/src/lib.rs +++ b/pallets/author-inherent/src/lib.rs @@ -88,6 +88,14 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { + fn on_initialize(_n: BlockNumberFor) -> Weight { + // Clear author from previous block so that no pallet reads stale data + // before post_inherents() sets it for this block. + Author::::kill(); + // One storage write (kill clears the value) + T::DbWeight::get().writes(1) + } + fn integrity_test() { // Test that SlotBeacon can be called and returns a valid slot let slot = T::SlotBeacon::slot(); diff --git a/pallets/author-inherent/src/tests.rs b/pallets/author-inherent/src/tests.rs index d25abc4b..299553c4 100644 --- a/pallets/author-inherent/src/tests.rs +++ b/pallets/author-inherent/src/tests.rs @@ -16,7 +16,7 @@ use crate::mock::*; use crate::pallet::Author; -use frame_support::traits::PostInherents; +use frame_support::traits::{Hooks, PostInherents}; use nimbus_primitives::{NimbusId, NIMBUS_ENGINE_ID}; use parity_scale_codec::Encode; use sp_core::{ByteArray, H256}; @@ -60,3 +60,56 @@ fn test_post_inherents_panics_when_author_missing_from_digest() { AuthorInherent::post_inherents(); }); } + +#[test] +fn test_on_initialize_then_post_inherents_lifecycle() { + new_test_ext().execute_with(|| { + // Block 1: full block with author + let block_1 = 1; + System::initialize( + &block_1, + &H256::default(), + &Digest { + logs: vec![DigestItem::PreRuntime( + NIMBUS_ENGINE_ID, + NimbusId::from_slice(&ALICE_NIMBUS).unwrap().encode(), + )], + }, + ); + AuthorInherent::on_initialize(block_1); + assert_eq!( + None, + >::get(), + "Author must be None after on_initialize" + ); + AuthorInherent::post_inherents(); + assert_eq!(Some(ALICE), >::get()); + + System::finalize(); + + // Block 2: on_initialize clears, then post_inherents sets again + let block_2 = 2; + System::initialize( + &block_2, + &H256::default(), + &Digest { + logs: vec![DigestItem::PreRuntime( + NIMBUS_ENGINE_ID, + NimbusId::from_slice(&ALICE_NIMBUS).unwrap().encode(), + )], + }, + ); + AuthorInherent::on_initialize(block_2); + assert_eq!( + None, + >::get(), + "Author must be None after on_initialize" + ); + AuthorInherent::post_inherents(); + assert_eq!( + Some(ALICE), + >::get(), + "Author set again after post_inherents" + ); + }); +} From ab05ba32b155bb10d51e0934db5030c45056a8e4 Mon Sep 17 00:00:00 2001 From: Artur Gontijo Date: Thu, 26 Feb 2026 09:26:29 -0300 Subject: [PATCH 07/10] Coderabbit fix comment --- pallets/author-inherent/src/lib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pallets/author-inherent/src/lib.rs b/pallets/author-inherent/src/lib.rs index a016e992..a7a239ff 100644 --- a/pallets/author-inherent/src/lib.rs +++ b/pallets/author-inherent/src/lib.rs @@ -170,11 +170,10 @@ impl frame_support::traits::PostInherents for Pallet { let digest = >::digest(); let pre_runtime_digests = digest.logs.iter().filter_map(|d| d.as_pre_runtime()); if let Some(author) = Self::find_author(pre_runtime_digests) { - // First check that the slot number is valid (greater than the previous highest) - let new_slot = T::SlotBeacon::slot(); - // Now check that the author is valid in this slot + let slot = T::SlotBeacon::slot(); + // Check that the author is eligible for this slot assert!( - T::CanAuthor::can_author(&author, &new_slot), + T::CanAuthor::can_author(&author, &slot), "Block invalid, supplied author is not eligible." ); >::put(&author); From 836ce30110634a796b9c164f0c462dcbbc707741 Mon Sep 17 00:00:00 2001 From: Artur Gontijo Date: Thu, 26 Feb 2026 09:51:10 -0300 Subject: [PATCH 08/10] Fix zombienet link --- template/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/template/README.md b/template/README.md index e2a19cfd..47e34994 100644 --- a/template/README.md +++ b/template/README.md @@ -55,7 +55,8 @@ docker build . -t polkadot-sdk-parachain-template ### Local Development Chain 🧟 This project uses [Zombienet](https://github.com/paritytech/zombienet) to orchestrate the relaychain and parachain nodes. -You can grab a [released binary](https://github.com/paritytech/zombienet/releases/latest) or use an [npm version](https://www.npmjs.com/package/@zombienet/cli). +You can grab a [released binary](https://github.com/paritytech/zombienet/releases/latest) or install via npm: `npm install -g @zombienet/cli` +(see [Zombienet](https://github.com/paritytech/zombienet) for details). This template produces a parachain node. You still need a relaychain node - you can download the `polkadot` From afe01bb1d6aaffbeb55da452c131bff7a60c133a Mon Sep 17 00:00:00 2001 From: Artur Gontijo Date: Fri, 27 Mar 2026 11:46:05 -0300 Subject: [PATCH 09/10] review: remove integrity_test() + more tests --- pallets/author-inherent/src/lib.rs | 17 --------- pallets/author-inherent/src/mock.rs | 4 ++ pallets/author-inherent/src/tests.rs | 57 ++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 17 deletions(-) diff --git a/pallets/author-inherent/src/lib.rs b/pallets/author-inherent/src/lib.rs index a7a239ff..496b66cd 100644 --- a/pallets/author-inherent/src/lib.rs +++ b/pallets/author-inherent/src/lib.rs @@ -24,7 +24,6 @@ use frame_support::traits::{FindAuthor, Get}; use nimbus_primitives::{AccountLookup, CanAuthor, NimbusId, SlotBeacon, NIMBUS_ENGINE_ID}; use parity_scale_codec::{Decode, FullCodec}; -use sp_core::ByteArray; use sp_runtime::ConsensusEngineId; pub use exec::BlockExecutor; @@ -95,22 +94,6 @@ pub mod pallet { // One storage write (kill clears the value) T::DbWeight::get().writes(1) } - - fn integrity_test() { - // Test that SlotBeacon can be called and returns a valid slot - let slot = T::SlotBeacon::slot(); - // Author storage should be accessible (this is a compile-time check) - assert!( - Author::::get().is_none(), - "Author storage should be none" - ); - - // Test that CanAuthor trait can be called - let _ = Pallet::::can_author( - &NimbusId::from_slice(&[0u8; 32]).expect("Valid NimbusId"), - &slot, - ); - } } impl FindAuthor for Pallet { diff --git a/pallets/author-inherent/src/mock.rs b/pallets/author-inherent/src/mock.rs index d4d63155..e1e217c9 100644 --- a/pallets/author-inherent/src/mock.rs +++ b/pallets/author-inherent/src/mock.rs @@ -85,6 +85,8 @@ impl nimbus_primitives::SlotBeacon for DummyBeacon { pub const ALICE: u64 = 1; pub const ALICE_NIMBUS: [u8; 32] = [1; 32]; +pub const BOB: u64 = 99; +pub const BOB_NIMBUS: [u8; 32] = [2; 32]; pub struct MockAccountLookup; impl AccountLookup for MockAccountLookup { fn lookup_account(nimbus_id: &NimbusId) -> Option { @@ -92,6 +94,8 @@ impl AccountLookup for MockAccountLookup { if nimbus_id_bytes == ALICE_NIMBUS { Some(ALICE) + } else if nimbus_id_bytes == BOB_NIMBUS { + Some(BOB) } else { None } diff --git a/pallets/author-inherent/src/tests.rs b/pallets/author-inherent/src/tests.rs index 299553c4..fde1c0dc 100644 --- a/pallets/author-inherent/src/tests.rs +++ b/pallets/author-inherent/src/tests.rs @@ -113,3 +113,60 @@ fn test_on_initialize_then_post_inherents_lifecycle() { ); }); } + +#[test] +#[should_panic(expected = "No Account Mapped to this NimbusId")] +fn test_post_inherents_panics_when_nimbus_id_is_not_mapped() { + new_test_ext().execute_with(|| { + let block_number = 1; + System::initialize( + &block_number, + &H256::default(), + &Digest { + logs: vec![DigestItem::PreRuntime( + NIMBUS_ENGINE_ID, + NimbusId::from_slice(&[9; 32]).unwrap().encode(), + )], + }, + ); + + AuthorInherent::post_inherents(); + }); +} + +#[test] +#[should_panic(expected = "NimbusId encoded in preruntime digest must be valid")] +fn test_post_inherents_panics_when_nimbus_digest_bytes_are_invalid() { + new_test_ext().execute_with(|| { + let block_number = 1; + System::initialize( + &block_number, + &H256::default(), + &Digest { + logs: vec![DigestItem::PreRuntime(NIMBUS_ENGINE_ID, vec![1, 2, 3])], + }, + ); + + AuthorInherent::post_inherents(); + }); +} + +#[test] +#[should_panic(expected = "Block invalid, supplied author is not eligible.")] +fn test_post_inherents_panics_when_author_is_ineligible() { + new_test_ext().execute_with(|| { + let block_number = 1; + System::initialize( + &block_number, + &H256::default(), + &Digest { + logs: vec![DigestItem::PreRuntime( + NIMBUS_ENGINE_ID, + NimbusId::from_slice(&BOB_NIMBUS).unwrap().encode(), + )], + }, + ); + + AuthorInherent::post_inherents(); + }); +} From 41db2ac65c6257deedef161a47a1bed1fe98202f Mon Sep 17 00:00:00 2001 From: Artur Gontijo Date: Mon, 30 Mar 2026 08:04:58 -0300 Subject: [PATCH 10/10] Add T::DbWeight::get().reads_writes(3, 2) --- pallets/author-inherent/src/lib.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pallets/author-inherent/src/lib.rs b/pallets/author-inherent/src/lib.rs index 496b66cd..d6d88f0e 100644 --- a/pallets/author-inherent/src/lib.rs +++ b/pallets/author-inherent/src/lib.rs @@ -91,8 +91,11 @@ pub mod pallet { // Clear author from previous block so that no pallet reads stale data // before post_inherents() sets it for this block. Author::::kill(); - // One storage write (kill clears the value) - T::DbWeight::get().writes(1) + // Pre-charge for `post_inherents()` which cannot return weight. + // This is mandatory per-block overhead so only DB ops matter: + // reads: AccountLookup + SlotBeacon + CanAuthor (1-3 reads) + // writes: kill + Author::put (2 writes) + T::DbWeight::get().reads_writes(3, 2) } }