From 2fd6bb4dad903e4dedd9502ff36dabb2225f824d Mon Sep 17 00:00:00 2001 From: FlorianJeandenans Date: Thu, 6 Aug 2026 08:38:05 +0200 Subject: [PATCH 1/3] ci: skip building the WASM runtime blob for check/test steps Test pallets and runtime (~9m18s) and Check node compilation (~18m28s) were together the slowest ~28min of the Blockchain / Substrate CI job (close to its 60min timeout) because both trigger runtime/build.rs's substrate-wasm-builder, which cross-compiles the entire runtime + pallet dependency graph a second time for wasm32-unknown-unknown and runs wasm-opt over it -- for a WASM binary neither step ever uses. WASM_BINARY is only read at runtime, in chain_spec.rs's development chain-spec builder (called when the node actually starts), and the two runtime crate tests only exercise genesis-preset/call-index logic -- neither compile- nor test-time path needs the real blob. Set SKIP_WASM_BUILD=1 for the job so wasm-builder emits a stub instead. Verified locally: 'cargo check -p openinfra-runtime' with SKIP_WASM_BUILD=1 still succeeds; a real wasm build is unaffected outside CI (dev-up, cargo build --release). Co-Authored-By: Claude Sonnet 5 --- .github/workflows/ci.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85e0cfd..fe10178 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,6 +105,15 @@ jobs: name: Blockchain / Substrate runs-on: ubuntu-24.04 timeout-minutes: 60 + env: + # `cargo check`/`cargo test` never run the compiled node, so the real + # WASM runtime blob (a second, cross-compiled build of the whole + # runtime + pallet graph, plus a wasm-opt pass) is pure overhead here. + # WASM_BINARY is only read at runtime in chain_spec.rs, never at + # check/test time. Skipping it cuts both slow steps below + # significantly; a real WASM build still happens for release/dev + # artifacts outside CI. + SKIP_WASM_BUILD: "1" steps: - name: Check out repository uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 From ede17c4858e57accef46542d1369d65499df17d6 Mon Sep 17 00:00:00 2001 From: FlorianJeandenans Date: Thu, 6 Aug 2026 08:50:48 +0200 Subject: [PATCH 2/3] feat(blockchain): add pallet-network-validator (ADR-011 first slice) Identity/stake/lifecycle registry for the Network Validator role defined in ADR-011: register_validator (bonds a real reserved stake via the runtime's existing pallet_balances, not a self-reported number), request_exit/withdraw_unbonded (bounded unbonding period), and root-gated suspend/reinstate for dispute resolution. Exposes NetworkValidatorInspector::is_active for other pallets to check. Deliberately scoped to just identity/stake/lifecycle -- it does not decide committee/challenge assignment, self-assignment exclusion, or evidence aggregation (still-to-be-implemented pieces per ADR-011). availability/reputation origins are untouched in this change; moving them from EnsureRoot to signed+is_active-checked is a follow-up PR, kept separate since it touches two already-shipped pallets' security model. Wired into the runtime at pallet_index(16), Currency = Balances (already configured), SuspensionOrigin = EnsureRoot for the MVP. Verified locally (blockchain/): - cargo fmt --all -- --check - SKIP_WASM_BUILD=1 cargo clippy --workspace --exclude openinfra-node --all-targets -- -D warnings - SKIP_WASM_BUILD=1 cargo test --workspace --exclude openinfra-node (11 new pallet tests + all existing pallet/runtime tests, including the call-index stability test, still pass) openinfra-node itself isn't buildable in this sandbox (no libclang for its rocksdb dependency) -- untouched by this change regardless. Co-Authored-By: Claude Sonnet 5 --- blockchain/Cargo.lock | 14 + blockchain/Cargo.toml | 1 + .../pallets/network-validator/Cargo.toml | 25 ++ .../pallets/network-validator/src/lib.rs | 297 ++++++++++++++++++ .../pallets/network-validator/src/tests.rs | 190 +++++++++++ blockchain/runtime/Cargo.toml | 2 + blockchain/runtime/src/lib.rs | 15 + 7 files changed, 544 insertions(+) create mode 100644 blockchain/pallets/network-validator/Cargo.toml create mode 100644 blockchain/pallets/network-validator/src/lib.rs create mode 100644 blockchain/pallets/network-validator/src/tests.rs diff --git a/blockchain/Cargo.lock b/blockchain/Cargo.lock index 06de355..a6989f1 100644 --- a/blockchain/Cargo.lock +++ b/blockchain/Cargo.lock @@ -8636,6 +8636,7 @@ name = "openinfra-runtime" version = "0.1.0" dependencies = [ "pallet-availability", + "pallet-network-validator", "pallet-openinfra-lease", "pallet-openinfra-rewards", "pallet-provider-registry", @@ -9893,6 +9894,19 @@ dependencies = [ "scale-info", ] +[[package]] +name = "pallet-network-validator" +version = "0.1.0" +dependencies = [ + "frame-support", + "frame-system", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", +] + [[package]] name = "pallet-nft-fractionalization" version = "33.0.0" diff --git a/blockchain/Cargo.toml b/blockchain/Cargo.toml index 288b9a9..d48be04 100644 --- a/blockchain/Cargo.toml +++ b/blockchain/Cargo.toml @@ -3,6 +3,7 @@ members = [ "node", "pallets/availability", "pallets/lease", + "pallets/network-validator", "pallets/provider-registry", "pallets/reputation", "pallets/resource-market", diff --git a/blockchain/pallets/network-validator/Cargo.toml b/blockchain/pallets/network-validator/Cargo.toml new file mode 100644 index 0000000..41b0d4e --- /dev/null +++ b/blockchain/pallets/network-validator/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "pallet-network-validator" +version = "0.1.0" +edition.workspace = true +license.workspace = true + +[dependencies] +codec = { workspace = true } +frame-support = { version = "48.0.0", default-features = false } +frame-system = { version = "48.0.0", default-features = false } +scale-info = { workspace = true } + +[dev-dependencies] +# Pinned to the exact release already resolved elsewhere in this workspace +# (via the runtime's polkadot-sdk umbrella pull) so this crate's test-only +# dependency graph reuses the same frame-support/frame-system 48.0.0 +# instead of resolving a second, incompatible copy. +pallet-balances = { version = "=50.0.0" } +sp-io = { version = "48.0.0" } +sp-runtime = { version = "48.0.0" } + +[features] +default = ["std"] +runtime-benchmarks = [] +std = ["codec/std", "frame-support/std", "frame-system/std", "scale-info/std"] diff --git a/blockchain/pallets/network-validator/src/lib.rs b/blockchain/pallets/network-validator/src/lib.rs new file mode 100644 index 0000000..c3dd7e0 --- /dev/null +++ b/blockchain/pallets/network-validator/src/lib.rs @@ -0,0 +1,297 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +//! Network Validator identity, stake, and lifecycle registry (ADR-011). +//! +//! This pallet only answers "is this account a bonded, active Network +//! Validator". It deliberately does not decide *who gets assigned to +//! challenge which provider* (self-assignment exclusion, committee +//! selection) or how evidence is aggregated into reputation -- those are +//! separate, still-to-be-implemented pieces per ADR-011 that consume +//! [`NetworkValidatorInspector`]. + +use frame_support::{ + pallet_prelude::*, + traits::{Currency, EnsureOrigin, Get, ReservableCurrency}, + weights::Weight, +}; +use frame_system::pallet_prelude::*; +pub use pallet::*; + +/// Narrow interface for pallets that only need to know whether an account is +/// currently an active, bonded Network Validator (e.g. an origin check on +/// availability/reputation submission calls). +pub trait NetworkValidatorInspector { + fn is_active(validator: &AccountId) -> bool; +} + +impl NetworkValidatorInspector for () { + fn is_active(_: &AccountId) -> bool { + false + } +} + +pub trait WeightInfo { + fn register_validator() -> Weight; + fn request_exit() -> Weight; + fn withdraw_unbonded() -> Weight; + fn suspend() -> Weight; + fn reinstate() -> Weight; +} + +impl WeightInfo for () { + fn register_validator() -> Weight { + Weight::from_parts(10_000, 0) + } + fn request_exit() -> Weight { + Weight::from_parts(10_000, 0) + } + fn withdraw_unbonded() -> Weight { + Weight::from_parts(10_000, 0) + } + fn suspend() -> Weight { + Weight::from_parts(10_000, 0) + } + fn reinstate() -> Weight { + Weight::from_parts(10_000, 0) + } +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config>> { + /// Bonds a validator's stake. Reused from the runtime's existing + /// `pallet_balances` -- a real reservation, not a self-reported + /// number, so stake is an actual Sybil deterrent (see ADR-011 §1). + type Currency: ReservableCurrency; + /// Governs forced suspend/reinstate (e.g. dispute resolution). + /// `EnsureRoot` for the MVP; a validator committee/governance origin + /// is future work (ADR-011 §5). + type SuspensionOrigin: EnsureOrigin; + #[pallet::constant] + type MinStake: Get>; + #[pallet::constant] + type UnbondingPeriod: Get>; + type WeightInfo: WeightInfo; + } + + pub type BalanceOf = + <::Currency as Currency<::AccountId>>::Balance; + + #[pallet::pallet] + pub struct Pallet(_); + + #[derive( + Clone, Encode, Decode, DecodeWithMemTracking, Eq, MaxEncodedLen, PartialEq, Debug, TypeInfo, + )] + pub enum ValidatorStatus { + Active, + Suspended, + Exiting { available_at: BlockNumber }, + } + + #[derive( + Clone, Encode, Decode, DecodeWithMemTracking, Eq, MaxEncodedLen, PartialEq, Debug, TypeInfo, + )] + pub struct ValidatorRecord { + pub status: ValidatorStatus, + pub stake: Balance, + pub registered_at: BlockNumber, + } + + #[pallet::storage] + pub type Validators = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + ValidatorRecord, BlockNumberFor>, + OptionQuery, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + ValidatorRegistered { + validator: T::AccountId, + stake: BalanceOf, + }, + ValidatorExitRequested { + validator: T::AccountId, + available_at: BlockNumberFor, + }, + ValidatorExited { + validator: T::AccountId, + stake: BalanceOf, + }, + ValidatorSuspended { + validator: T::AccountId, + }, + ValidatorReinstated { + validator: T::AccountId, + }, + } + + #[pallet::error] + pub enum Error { + AlreadyRegistered, + NotRegistered, + InsufficientStake, + InsufficientFreeBalance, + AlreadyExiting, + NotExiting, + UnbondingNotComplete, + UnbondingPeriodOverflow, + NotActive, + NotSuspended, + } + + #[pallet::call] + impl Pallet { + /// Register the caller as a Network Validator, reserving `stake` + /// from its free balance. Fails below `MinStake` or if already + /// registered -- re-registration after exit requires + /// `withdraw_unbonded` to have cleared the previous record first. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::register_validator())] + pub fn register_validator(origin: OriginFor, stake: BalanceOf) -> DispatchResult { + let who = ensure_signed(origin)?; + ensure!( + !Validators::::contains_key(&who), + Error::::AlreadyRegistered + ); + ensure!(stake >= T::MinStake::get(), Error::::InsufficientStake); + T::Currency::reserve(&who, stake).map_err(|_| Error::::InsufficientFreeBalance)?; + let now = frame_system::Pallet::::block_number(); + Validators::::insert( + &who, + ValidatorRecord { + status: ValidatorStatus::Active, + stake, + registered_at: now, + }, + ); + Self::deposit_event(Event::ValidatorRegistered { + validator: who, + stake, + }); + Ok(()) + } + + /// Begin unbonding. The stake stays reserved (and the validator + /// stays ineligible for new committee assignments -- enforced by + /// callers checking `is_active`, which is false while `Exiting`) + /// until `UnbondingPeriod` has elapsed. + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::request_exit())] + pub fn request_exit(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let available_at = Validators::::try_mutate( + &who, + |maybe_record| -> Result, DispatchError> { + let record = maybe_record.as_mut().ok_or(Error::::NotRegistered)?; + ensure!( + !matches!(record.status, ValidatorStatus::Exiting { .. }), + Error::::AlreadyExiting + ); + let now = frame_system::Pallet::::block_number(); + let available_at = now + .checked_add(&T::UnbondingPeriod::get()) + .ok_or(Error::::UnbondingPeriodOverflow)?; + record.status = ValidatorStatus::Exiting { available_at }; + Ok(available_at) + }, + )?; + Self::deposit_event(Event::ValidatorExitRequested { + validator: who, + available_at, + }); + Ok(()) + } + + /// Release the reserved stake once unbonding has completed and + /// remove the validator record. + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::withdraw_unbonded())] + pub fn withdraw_unbonded(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let record = Validators::::get(&who).ok_or(Error::::NotRegistered)?; + match record.status { + ValidatorStatus::Exiting { available_at } => { + let now = frame_system::Pallet::::block_number(); + ensure!(now >= available_at, Error::::UnbondingNotComplete); + } + _ => return Err(Error::::NotExiting.into()), + } + T::Currency::unreserve(&who, record.stake); + Validators::::remove(&who); + Self::deposit_event(Event::ValidatorExited { + validator: who, + stake: record.stake, + }); + Ok(()) + } + + /// Force-suspend a validator (dispute resolution / detected + /// misbehavior). `SuspensionOrigin`-gated; not self-service. + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::suspend())] + pub fn suspend(origin: OriginFor, validator: T::AccountId) -> DispatchResult { + T::SuspensionOrigin::ensure_origin(origin)?; + Validators::::try_mutate(&validator, |maybe_record| -> DispatchResult { + let record = maybe_record.as_mut().ok_or(Error::::NotRegistered)?; + ensure!( + matches!(record.status, ValidatorStatus::Active), + Error::::NotActive + ); + record.status = ValidatorStatus::Suspended; + Ok(()) + })?; + Self::deposit_event(Event::ValidatorSuspended { validator }); + Ok(()) + } + + /// Reinstate a previously suspended validator. + /// `SuspensionOrigin`-gated. + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::reinstate())] + pub fn reinstate(origin: OriginFor, validator: T::AccountId) -> DispatchResult { + T::SuspensionOrigin::ensure_origin(origin)?; + Validators::::try_mutate(&validator, |maybe_record| -> DispatchResult { + let record = maybe_record.as_mut().ok_or(Error::::NotRegistered)?; + ensure!( + matches!(record.status, ValidatorStatus::Suspended), + Error::::NotSuspended + ); + record.status = ValidatorStatus::Active; + Ok(()) + })?; + Self::deposit_event(Event::ValidatorReinstated { validator }); + Ok(()) + } + } + + impl Pallet { + /// True only for a registered validator whose status is `Active` + /// (not `Suspended`, not `Exiting`). + pub fn is_active(validator: &T::AccountId) -> bool { + matches!( + Validators::::get(validator), + Some(ValidatorRecord { + status: ValidatorStatus::Active, + .. + }) + ) + } + } + + impl super::NetworkValidatorInspector for Pallet { + fn is_active(validator: &T::AccountId) -> bool { + Pallet::::is_active(validator) + } + } +} + +#[cfg(test)] +mod tests; diff --git a/blockchain/pallets/network-validator/src/tests.rs b/blockchain/pallets/network-validator/src/tests.rs new file mode 100644 index 0000000..2b4cdc8 --- /dev/null +++ b/blockchain/pallets/network-validator/src/tests.rs @@ -0,0 +1,190 @@ +use crate as pallet_network_validator; +use frame_support::{assert_noop, assert_ok, derive_impl, parameter_types, traits::ConstU64}; +use sp_runtime::{BuildStorage, DispatchError}; + +type Block = frame_system::mocking::MockBlock; +frame_support::construct_runtime!( + pub enum Test { + System: frame_system, + Balances: pallet_balances, + NetworkValidator: pallet_network_validator, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Test { + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; +} + +parameter_types! { + pub const MinStake: u64 = 100; + pub const UnbondingPeriod: u64 = 5; +} + +impl crate::Config for Test { + type Currency = Balances; + type SuspensionOrigin = frame_system::EnsureRoot; + type MinStake = MinStake; + type UnbondingPeriod = UnbondingPeriod; + type WeightInfo = (); +} + +fn new_test_ext() -> sp_io::TestExternalities { + let mut storage = frame_system::GenesisConfig::::default() + .build_storage() + .unwrap(); + pallet_balances::GenesisConfig:: { + balances: vec![(1, 1_000), (2, 1_000), (3, 50)], + ..Default::default() + } + .assimilate_storage(&mut storage) + .unwrap(); + storage.into() +} + +#[test] +fn register_reserves_stake_and_marks_active() { + new_test_ext().execute_with(|| { + assert_ok!(NetworkValidator::register_validator( + RuntimeOrigin::signed(1), + 100 + )); + assert!(NetworkValidator::is_active(&1)); + assert_eq!(Balances::reserved_balance(1), 100); + assert_eq!(Balances::free_balance(1), 900); + }); +} + +#[test] +fn register_rejects_below_min_stake() { + new_test_ext().execute_with(|| { + assert_noop!( + NetworkValidator::register_validator(RuntimeOrigin::signed(1), 99), + crate::Error::::InsufficientStake + ); + }); +} + +#[test] +fn register_rejects_insufficient_free_balance() { + new_test_ext().execute_with(|| { + // Account 3 only has 50 total; registering 100 must fail on the + // currency reserve, not silently succeed with an unbacked stake. + assert_noop!( + NetworkValidator::register_validator(RuntimeOrigin::signed(3), 100), + crate::Error::::InsufficientFreeBalance + ); + }); +} + +#[test] +fn register_rejects_double_registration() { + new_test_ext().execute_with(|| { + assert_ok!(NetworkValidator::register_validator( + RuntimeOrigin::signed(1), + 100 + )); + assert_noop!( + NetworkValidator::register_validator(RuntimeOrigin::signed(1), 100), + crate::Error::::AlreadyRegistered + ); + }); +} + +#[test] +fn exit_and_withdraw_flow_respects_unbonding_period() { + new_test_ext().execute_with(|| { + System::set_block_number(1); + assert_ok!(NetworkValidator::register_validator( + RuntimeOrigin::signed(1), + 100 + )); + assert_ok!(NetworkValidator::request_exit(RuntimeOrigin::signed(1))); + // No longer active once exiting, even though the record still exists. + assert!(!NetworkValidator::is_active(&1)); + assert_noop!( + NetworkValidator::withdraw_unbonded(RuntimeOrigin::signed(1)), + crate::Error::::UnbondingNotComplete + ); + System::set_block_number(1 + UnbondingPeriod::get()); + assert_ok!(NetworkValidator::withdraw_unbonded(RuntimeOrigin::signed( + 1 + ))); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(Balances::free_balance(1), 1_000); + assert_noop!( + NetworkValidator::withdraw_unbonded(RuntimeOrigin::signed(1)), + crate::Error::::NotRegistered + ); + }); +} + +#[test] +fn request_exit_is_not_idempotent_while_already_exiting() { + new_test_ext().execute_with(|| { + assert_ok!(NetworkValidator::register_validator( + RuntimeOrigin::signed(1), + 100 + )); + assert_ok!(NetworkValidator::request_exit(RuntimeOrigin::signed(1))); + assert_noop!( + NetworkValidator::request_exit(RuntimeOrigin::signed(1)), + crate::Error::::AlreadyExiting + ); + }); +} + +#[test] +fn suspend_and_reinstate_require_the_suspension_origin() { + new_test_ext().execute_with(|| { + assert_ok!(NetworkValidator::register_validator( + RuntimeOrigin::signed(1), + 100 + )); + assert_noop!( + NetworkValidator::suspend(RuntimeOrigin::signed(2), 1), + DispatchError::BadOrigin + ); + assert_ok!(NetworkValidator::suspend(RuntimeOrigin::root(), 1)); + assert!(!NetworkValidator::is_active(&1)); + assert_noop!( + NetworkValidator::suspend(RuntimeOrigin::root(), 1), + crate::Error::::NotActive + ); + assert_noop!( + NetworkValidator::reinstate(RuntimeOrigin::signed(2), 1), + DispatchError::BadOrigin + ); + assert_ok!(NetworkValidator::reinstate(RuntimeOrigin::root(), 1)); + assert!(NetworkValidator::is_active(&1)); + }); +} + +#[test] +fn suspended_validator_cannot_exit_or_withdraw_around_suspension() { + new_test_ext().execute_with(|| { + assert_ok!(NetworkValidator::register_validator( + RuntimeOrigin::signed(1), + 100 + )); + assert_ok!(NetworkValidator::suspend(RuntimeOrigin::root(), 1)); + // Suspended validators can still request exit (to leave the set for + // good) -- suspension blocks new committee work, not unbonding. + assert_ok!(NetworkValidator::request_exit(RuntimeOrigin::signed(1))); + assert!(!NetworkValidator::is_active(&1)); + }); +} + +#[test] +fn unregistered_account_is_never_active() { + new_test_ext().execute_with(|| { + assert!(!NetworkValidator::is_active(&42)); + }); +} diff --git a/blockchain/runtime/Cargo.toml b/blockchain/runtime/Cargo.toml index 590ec3c..e07ff3d 100644 --- a/blockchain/runtime/Cargo.toml +++ b/blockchain/runtime/Cargo.toml @@ -9,6 +9,7 @@ build = "build.rs" codec = { workspace = true } polkadot-sdk = { version = "=2606.0.0", default-features = false, features = ["pallet-aura", "pallet-balances", "pallet-grandpa", "pallet-sudo", "pallet-timestamp", "runtime", "sp-consensus-aura", "sp-consensus-grandpa"] } pallet-availability = { path = "../pallets/availability", default-features = false } +pallet-network-validator = { path = "../pallets/network-validator", default-features = false } pallet-openinfra-lease = { path = "../pallets/lease", default-features = false } pallet-openinfra-rewards = { path = "../pallets/rewards", default-features = false } pallet-provider-registry = { path = "../pallets/provider-registry", default-features = false } @@ -26,6 +27,7 @@ std = [ "codec/std", "polkadot-sdk/std", "pallet-availability/std", + "pallet-network-validator/std", "pallet-openinfra-lease/std", "pallet-openinfra-rewards/std", "pallet-provider-registry/std", diff --git a/blockchain/runtime/src/lib.rs b/blockchain/runtime/src/lib.rs index c4019cd..a863360 100644 --- a/blockchain/runtime/src/lib.rs +++ b/blockchain/runtime/src/lib.rs @@ -87,6 +87,8 @@ mod runtime { pub type Rewards = pallet_openinfra_rewards::Pallet; #[runtime::pallet_index(15)] pub type Availability = pallet_availability::Pallet; + #[runtime::pallet_index(16)] + pub type NetworkValidator = pallet_network_validator::Pallet; } parameter_types! { @@ -101,6 +103,8 @@ parameter_types! { pub const MaxProofAge: u32 = 1_000; pub const MaxRewardResourceUnits: u64 = 1_000_000_000; pub const MaxRewardDuration: u64 = 10_000_000; + pub const MinValidatorStake: u64 = 1_000; + pub const ValidatorUnbondingPeriod: u32 = 14_400; // ~1 day at 6s blocks } #[derive_impl(frame_system::config_preludes::SolochainDefaultConfig)] @@ -209,6 +213,17 @@ impl pallet_availability::Config for Runtime { type WeightInfo = (); } +impl pallet_network_validator::Config for Runtime { + type Currency = Balances; + // Suspend/reinstate is root-gated for the MVP; a validator + // committee/governance origin is ADR-011 §5 follow-up work, not decided + // by this pallet yet. + type SuspensionOrigin = frame_system::EnsureRoot; + type MinStake = MinValidatorStake; + type UnbondingPeriod = ValidatorUnbondingPeriod; + type WeightInfo = (); +} + type Block = frame::runtime::types_common::BlockOf; type RuntimeExecutive = Executive, Runtime, AllPalletsWithSystem>; From 1e7150c7316c8ebc5567225b761cbc57c9c8416c Mon Sep 17 00:00:00 2001 From: FlorianJeandenans Date: Thu, 6 Aug 2026 09:29:33 +0200 Subject: [PATCH 3/3] ci: fix duplicated SKIP_WASM_BUILD block from a squash-merge artifact This branch forked from ci/skip-wasm-build-for-checks before its fix commit, so merging main back in re-added the already-reverted job-level SKIP_WASM_BUILD alongside the correct step-level one -- squash merges collapse history, so main's squash diff couldn't know the job-level block had been added then removed on the source branch. Re-checked out ci.yml from origin/main to match exactly. Co-Authored-By: Claude Sonnet 5 --- .github/workflows/ci.yml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index edd1e53..c65948e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -105,15 +105,6 @@ jobs: name: Blockchain / Substrate runs-on: ubuntu-24.04 timeout-minutes: 60 - env: - # `cargo check`/`cargo test` never run the compiled node, so the real - # WASM runtime blob (a second, cross-compiled build of the whole - # runtime + pallet graph, plus a wasm-opt pass) is pure overhead here. - # WASM_BINARY is only read at runtime in chain_spec.rs, never at - # check/test time. Skipping it cuts both slow steps below - # significantly; a real WASM build still happens for release/dev - # artifacts outside CI. - SKIP_WASM_BUILD: "1" steps: - name: Check out repository uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5