From bf6abe097768667f8d62cfdcaa264138a8e8ae04 Mon Sep 17 00:00:00 2001 From: FlorianJeandenans Date: Thu, 6 Aug 2026 11:00:09 +0200 Subject: [PATCH] feat(blockchain): add bounded round disputes with reputation rollback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fifth slice of #29/ADR-011 §5, completing the on-chain scoring lifecycle after the registry (#43), gated origins (#46), evidence aggregation (#47) and committee assignment (#48). dispute_round(provider, round, dimension): callable by the scored provider or by any validator that sat on that round's committee, within DisputeWindow blocks of closing. It immediately rolls the dimension back to the value it held before the round applied -- a contested score must not keep influencing scheduling while it is unresolved -- and marks the round Disputed. resolve_dispute(provider, round, dimension, uphold): SuspensionOrigin- gated, since ADR-011 defers full on-chain adjudication. Upholding keeps the rollback (DisputeUpheld); rejecting re-applies the round's aggregate (DisputeRejected). Making the rollback exact rather than approximate required knowing the pre-round value, so close_round now captures it via a new ReputationUpdater::dimension_score reader and stores it on the round as previous_score_bps. pallet-reputation gains dimension_score_bps(), inverting set_dimension_score's scaling; the round trip is exact whenever MaxScore divides 10_000 evenly (it is 1_000 in the runtime, a 10 bps step) and otherwise deterministically truncating -- never node-dependent. RoundResult also gains an explicit RoundStatus (Final/Disputed/DisputeUpheld/DisputeRejected) so a reader can never mistake a contested score for an accepted one. Adds 8 dispute tests: rollback restores the exact pre-round value established by an earlier round; only the provider or a committee member may dispute (an active-but-unassigned validator is rejected); the window is enforced; a round cannot be disputed twice; disputing an unknown round fails; upholding keeps the rollback; rejecting re-applies the aggregate; resolution requires governance and an actual dispute. 33 tests in this pallet, 81 across the workspace. Verified (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. Co-Authored-By: Claude Sonnet 5 --- .../pallets/network-validator/src/lib.rs | 191 ++++++++++- .../pallets/network-validator/src/tests.rs | 297 ++++++++++++++++++ blockchain/pallets/reputation/src/lib.rs | 23 ++ blockchain/runtime/src/lib.rs | 30 +- 4 files changed, 530 insertions(+), 11 deletions(-) diff --git a/blockchain/pallets/network-validator/src/lib.rs b/blockchain/pallets/network-validator/src/lib.rs index 6a9e955..8e6aad5 100644 --- a/blockchain/pallets/network-validator/src/lib.rs +++ b/blockchain/pallets/network-validator/src/lib.rs @@ -9,20 +9,23 @@ //! 1. **Registry**: who is a bonded, active Network Validator //! ([`NetworkValidatorInspector`], consumed by `pallet-availability` //! and `pallet-reputation` to gate their submission origins). -//! 2. **Scoring**: validators submit per-dimension integer evidence for a -//! provider/round; once a quorum is reached the round is closed with a -//! trimmed-mean aggregate that is pushed into `pallet-reputation` -//! through [`ReputationUpdater`]. +//! 2. **Scoring**: a deterministic committee is assigned per +//! (provider, round); its members submit per-dimension integer +//! evidence; once a quorum is reached the round closes with a +//! trimmed-mean aggregate pushed into `pallet-reputation` through +//! [`ReputationUpdater`]. Closed rounds can be contested within a +//! bounded window, which rolls the dimension back pending governance +//! resolution. //! -//! Committee *assignment* (deterministically selecting which validators -//! are expected to challenge which provider in a round) is still open -- -//! today any active validator may submit, bounded by -//! `MaxSubmissionsPerRound` and the self-scoring exclusion below. +//! Still open per ADR-011: validator reward/penalty accrual, and +//! unpredictable (VRF-backed) committee entropy -- see +//! [`Pallet::committee`] for why assignment is currently predictable. extern crate alloc; use frame_support::{ pallet_prelude::*, + sp_runtime::traits::Saturating, traits::{Currency, EnsureOrigin, Get, ReservableCurrency}, weights::Weight, Hashable, @@ -62,12 +65,19 @@ pub trait ReputationUpdater { dimension: ScoreDimension, score_bps: u16, ) -> DispatchResult; + + /// The dimension's current value, in basis points. Captured before a + /// round is applied so an upheld dispute can restore it (ADR-011 §5). + fn dimension_score(provider: &AccountId, dimension: ScoreDimension) -> u16; } impl ReputationUpdater for () { fn record_dimension_score(_: &AccountId, _: ScoreDimension, _: u16) -> DispatchResult { Ok(()) } + fn dimension_score(_: &AccountId, _: ScoreDimension) -> u16 { + 0 + } } /// Narrow interface for pallets that only need to know whether an account is @@ -91,6 +101,8 @@ pub trait WeightInfo { fn reinstate() -> Weight; fn submit_evidence() -> Weight; fn close_round() -> Weight; + fn dispute_round() -> Weight; + fn resolve_dispute() -> Weight; } impl WeightInfo for () { @@ -115,6 +127,12 @@ impl WeightInfo for () { fn close_round() -> Weight { Weight::from_parts(10_000, 0) } + fn dispute_round() -> Weight { + Weight::from_parts(10_000, 0) + } + fn resolve_dispute() -> Weight { + Weight::from_parts(10_000, 0) + } } #[frame_support::pallet] @@ -157,6 +175,9 @@ pub mod pallet { /// selection iterates it, so it must stay bounded. #[pallet::constant] type MaxValidators: Get; + /// How long after a round closes it may still be disputed. + #[pallet::constant] + type DisputeWindow: Get>; type WeightInfo: WeightInfo; } @@ -215,6 +236,31 @@ pub mod pallet { pub payload_hash: [u8; 32], } + /// Lifecycle of a closed round (ADR-011 §5). + #[derive( + Clone, + Copy, + Encode, + Decode, + DecodeWithMemTracking, + Eq, + MaxEncodedLen, + PartialEq, + Debug, + TypeInfo, + )] + pub enum RoundStatus { + /// Closed and applied to reputation. + Final, + /// Contested within the dispute window; the dimension has been + /// rolled back to `previous_score_bps` pending resolution. + Disputed, + /// Governance agreed with the disputer; the rollback stands. + DisputeUpheld, + /// Governance rejected the dispute; the aggregate was re-applied. + DisputeRejected, + } + /// The aggregate committed when a round closes. #[derive( Clone, Encode, Decode, DecodeWithMemTracking, Eq, MaxEncodedLen, PartialEq, Debug, TypeInfo, @@ -222,12 +268,16 @@ pub mod pallet { pub struct RoundResult { /// Trimmed-mean score in basis points. pub score_bps: u16, + /// The dimension's value immediately before this round applied, so + /// an upheld dispute can restore it exactly. + pub previous_score_bps: u16, /// How many independent validators contributed. pub submissions: u32, /// `TargetCommitteeSize` at closing time, so a reader can compute /// confidence without assuming today's configuration. pub committee_target: u32, pub closed_at: BlockNumber, + pub status: RoundStatus, } /// Open submissions, keyed by (provider, round, dimension). @@ -293,6 +343,21 @@ pub mod pallet { submissions: u32, committee_target: u32, }, + RoundDisputed { + provider: T::AccountId, + round: u64, + dimension: ScoreDimension, + disputed_by: T::AccountId, + /// The value reputation was rolled back to. + restored_score_bps: u16, + }, + DisputeResolved { + provider: T::AccountId, + round: u64, + dimension: ScoreDimension, + upheld: bool, + effective_score_bps: u16, + }, } #[pallet::error] @@ -330,6 +395,17 @@ pub mod pallet { NotAssignedToRound, /// `MaxValidators` reached; the active set cannot grow. TooManyValidators, + /// No closed round exists for this (provider, round, dimension). + RoundNotFound, + /// The `DisputeWindow` after closing has elapsed. + DisputeWindowClosed, + /// Only the scored provider or a validator that was on the + /// round's committee may dispute it. + NotEntitledToDispute, + /// The round is already under dispute or already resolved. + AlreadyDisputed, + /// The round is not under dispute, so there is nothing to resolve. + NotDisputed, } #[pallet::call] @@ -558,14 +634,19 @@ pub mod pallet { let score_bps = Self::trimmed_mean(&submissions); let committee_target = T::TargetCommitteeSize::get(); let closed_at = frame_system::Pallet::::block_number(); + // Captured before applying, so an upheld dispute restores the + // exact prior value rather than guessing at it. + let previous_score_bps = T::ReputationUpdater::dimension_score(&provider, dimension); Rounds::::insert( (&provider, round, dimension), RoundResult { score_bps, + previous_score_bps, submissions: count, committee_target, closed_at, + status: RoundStatus::Final, }, ); // Raw submissions are no longer needed once the aggregate is @@ -585,6 +666,100 @@ pub mod pallet { }); Ok(()) } + + /// Contest a closed round within `DisputeWindow` blocks. Callable + /// by the scored provider or by any validator that sat on the + /// round's committee. The dimension is immediately rolled back to + /// the value it held before the round applied -- a contested score + /// must not keep influencing scheduling while it is unresolved -- + /// pending `resolve_dispute`. + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::dispute_round())] + pub fn dispute_round( + origin: OriginFor, + provider: T::AccountId, + round: u64, + dimension: ScoreDimension, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let mut result = + Rounds::::get((&provider, round, dimension)).ok_or(Error::::RoundNotFound)?; + ensure!( + matches!(result.status, RoundStatus::Final), + Error::::AlreadyDisputed + ); + let now = frame_system::Pallet::::block_number(); + let deadline = result.closed_at.saturating_add(T::DisputeWindow::get()); + ensure!(now <= deadline, Error::::DisputeWindowClosed); + ensure!( + who == provider || Self::is_assigned(&provider, round, &who), + Error::::NotEntitledToDispute + ); + + T::ReputationUpdater::record_dimension_score( + &provider, + dimension, + result.previous_score_bps, + )?; + let restored_score_bps = result.previous_score_bps; + result.status = RoundStatus::Disputed; + Rounds::::insert((&provider, round, dimension), result); + + Self::deposit_event(Event::RoundDisputed { + provider, + round, + dimension, + disputed_by: who, + restored_score_bps, + }); + Ok(()) + } + + /// Settle a dispute. `SuspensionOrigin`-gated: full on-chain + /// adjudication is deferred (ADR-011 §5), so for the MVP a + /// governance origin decides. Upholding keeps the rollback; + /// rejecting re-applies the round's aggregate. + #[pallet::call_index(8)] + #[pallet::weight(T::WeightInfo::resolve_dispute())] + pub fn resolve_dispute( + origin: OriginFor, + provider: T::AccountId, + round: u64, + dimension: ScoreDimension, + uphold: bool, + ) -> DispatchResult { + T::SuspensionOrigin::ensure_origin(origin)?; + let mut result = + Rounds::::get((&provider, round, dimension)).ok_or(Error::::RoundNotFound)?; + ensure!( + matches!(result.status, RoundStatus::Disputed), + Error::::NotDisputed + ); + + let effective_score_bps = if uphold { + result.status = RoundStatus::DisputeUpheld; + // Already rolled back by dispute_round; nothing to re-apply. + result.previous_score_bps + } else { + result.status = RoundStatus::DisputeRejected; + T::ReputationUpdater::record_dimension_score( + &provider, + dimension, + result.score_bps, + )?; + result.score_bps + }; + Rounds::::insert((&provider, round, dimension), result); + + Self::deposit_event(Event::DisputeResolved { + provider, + round, + dimension, + upheld: uphold, + effective_score_bps, + }); + Ok(()) + } } impl Pallet { diff --git a/blockchain/pallets/network-validator/src/tests.rs b/blockchain/pallets/network-validator/src/tests.rs index 4166833..24f41c4 100644 --- a/blockchain/pallets/network-validator/src/tests.rs +++ b/blockchain/pallets/network-validator/src/tests.rs @@ -37,6 +37,23 @@ thread_local! { const { std::cell::RefCell::new(Vec::new()) }; } +// Current per-(provider, dimension) score, so the double acts like a +// real reputation store and dispute rollbacks can be asserted. +thread_local! { + static CURRENT: std::cell::RefCell> = + const { std::cell::RefCell::new(std::collections::BTreeMap::new()) }; +} + +fn dimension_key(dimension: ScoreDimension) -> u8 { + match dimension { + ScoreDimension::Compute => 0, + ScoreDimension::Storage => 1, + ScoreDimension::Network => 2, + ScoreDimension::Availability => 3, + ScoreDimension::Reliability => 4, + } +} + pub struct RecordingUpdater; impl crate::ReputationUpdater for RecordingUpdater { fn record_dimension_score( @@ -49,8 +66,27 @@ impl crate::ReputationUpdater for RecordingUpdater { .borrow_mut() .push((*provider, dimension, score_bps)) }); + CURRENT.with(|current| { + current + .borrow_mut() + .insert((*provider, dimension_key(dimension)), score_bps) + }); Ok(()) } + + fn dimension_score(provider: &u64, dimension: ScoreDimension) -> u16 { + CURRENT.with(|current| { + current + .borrow() + .get(&(*provider, dimension_key(dimension))) + .copied() + .unwrap_or(0) + }) + } +} + +fn current_score(provider: u64, dimension: ScoreDimension) -> u16 { + >::dimension_score(&provider, dimension) } fn recorded() -> Vec<(u64, ScoreDimension, u16)> { @@ -59,6 +95,7 @@ fn recorded() -> Vec<(u64, ScoreDimension, u16)> { fn clear_recorded() { RECORDED.with(|recorded| recorded.borrow_mut().clear()); + CURRENT.with(|current| current.borrow_mut().clear()); } parameter_types! { @@ -66,6 +103,7 @@ parameter_types! { pub const MinQuorum: u32 = 3; pub const TargetCommitteeSize: u32 = 5; pub const MaxValidators: u32 = 16; + pub const DisputeWindow: u64 = 20; } impl crate::Config for Test { @@ -78,6 +116,7 @@ impl crate::Config for Test { type MinQuorum = MinQuorum; type TargetCommitteeSize = TargetCommitteeSize; type MaxValidators = MaxValidators; + type DisputeWindow = DisputeWindow; type WeightInfo = (); } @@ -587,3 +626,261 @@ fn the_active_set_is_bounded() { ); }); } + +// --- Disputes (ADR-011 §5) --- + +/// Closes a round at `score`, returning the committee, so dispute tests +/// start from a real Final round. +fn close_round_at(dimension: ScoreDimension, score: u16) -> Vec { + let assigned = committee(); + for member in assigned.iter().take(3) { + assert_ok!(submit(*member, dimension, score)); + } + assert_ok!(NetworkValidator::close_round( + RuntimeOrigin::signed(assigned[0]), + PROVIDER, + ROUND, + dimension + )); + assigned +} + +#[test] +fn a_dispute_rolls_reputation_back_to_the_pre_round_value() { + new_test_ext().execute_with(|| { + clear_recorded(); + System::set_block_number(1); + register_validators(6); + // Establish a prior value via an earlier round -- using that + // round's own committee -- so the rollback target is a real score + // rather than the zero default. + let prior = NetworkValidator::committee(&PROVIDER, ROUND - 1); + for member in prior.iter().take(3) { + assert_ok!(NetworkValidator::submit_evidence( + RuntimeOrigin::signed(*member), + PROVIDER, + ROUND - 1, + ScoreDimension::Compute, + 3_000, + 10, + [1; 32] + )); + } + assert_ok!(NetworkValidator::close_round( + RuntimeOrigin::signed(prior[0]), + PROVIDER, + ROUND - 1, + ScoreDimension::Compute + )); + let before_disputed_round = current_score(PROVIDER, ScoreDimension::Compute); + assert_eq!(before_disputed_round, 3_000); + + close_round_at(ScoreDimension::Compute, 9_000); + assert_eq!(current_score(PROVIDER, ScoreDimension::Compute), 9_000); + + // The provider contests its own score. + assert_ok!(NetworkValidator::dispute_round( + RuntimeOrigin::signed(PROVIDER), + PROVIDER, + ROUND, + ScoreDimension::Compute + )); + assert_eq!( + current_score(PROVIDER, ScoreDimension::Compute), + before_disputed_round, + "a contested score must stop influencing reputation immediately" + ); + let result = + crate::Rounds::::get((PROVIDER, ROUND, ScoreDimension::Compute)).expect("round"); + assert_eq!(result.status, crate::RoundStatus::Disputed); + }); +} + +#[test] +fn only_the_provider_or_a_committee_member_may_dispute() { + new_test_ext().execute_with(|| { + clear_recorded(); + System::set_block_number(1); + let validators = register_validators(8); + let assigned = close_round_at(ScoreDimension::Network, 5_000); + let outsider = validators + .iter() + .find(|candidate| !assigned.contains(candidate)) + .copied() + .expect("some validators are unassigned"); + + assert_noop!( + NetworkValidator::dispute_round( + RuntimeOrigin::signed(outsider), + PROVIDER, + ROUND, + ScoreDimension::Network + ), + crate::Error::::NotEntitledToDispute + ); + // A validator that sat on the committee may dispute. + assert_ok!(NetworkValidator::dispute_round( + RuntimeOrigin::signed(assigned[1]), + PROVIDER, + ROUND, + ScoreDimension::Network + )); + }); +} + +#[test] +fn a_dispute_must_land_inside_the_window() { + new_test_ext().execute_with(|| { + clear_recorded(); + System::set_block_number(1); + register_validators(6); + close_round_at(ScoreDimension::Storage, 5_000); + System::set_block_number(1 + DisputeWindow::get() + 1); + assert_noop!( + NetworkValidator::dispute_round( + RuntimeOrigin::signed(PROVIDER), + PROVIDER, + ROUND, + ScoreDimension::Storage + ), + crate::Error::::DisputeWindowClosed + ); + }); +} + +#[test] +fn a_round_cannot_be_disputed_twice() { + new_test_ext().execute_with(|| { + clear_recorded(); + System::set_block_number(1); + register_validators(6); + close_round_at(ScoreDimension::Reliability, 5_000); + assert_ok!(NetworkValidator::dispute_round( + RuntimeOrigin::signed(PROVIDER), + PROVIDER, + ROUND, + ScoreDimension::Reliability + )); + assert_noop!( + NetworkValidator::dispute_round( + RuntimeOrigin::signed(PROVIDER), + PROVIDER, + ROUND, + ScoreDimension::Reliability + ), + crate::Error::::AlreadyDisputed + ); + }); +} + +#[test] +fn disputing_an_unknown_round_fails() { + new_test_ext().execute_with(|| { + register_validators(6); + assert_noop!( + NetworkValidator::dispute_round( + RuntimeOrigin::signed(PROVIDER), + PROVIDER, + 999, + ScoreDimension::Compute + ), + crate::Error::::RoundNotFound + ); + }); +} + +#[test] +fn upholding_a_dispute_keeps_the_rollback() { + new_test_ext().execute_with(|| { + clear_recorded(); + System::set_block_number(1); + register_validators(6); + close_round_at(ScoreDimension::Compute, 9_000); + assert_ok!(NetworkValidator::dispute_round( + RuntimeOrigin::signed(PROVIDER), + PROVIDER, + ROUND, + ScoreDimension::Compute + )); + assert_ok!(NetworkValidator::resolve_dispute( + RuntimeOrigin::root(), + PROVIDER, + ROUND, + ScoreDimension::Compute, + true + )); + let result = + crate::Rounds::::get((PROVIDER, ROUND, ScoreDimension::Compute)).expect("round"); + assert_eq!(result.status, crate::RoundStatus::DisputeUpheld); + assert_eq!( + current_score(PROVIDER, ScoreDimension::Compute), + result.previous_score_bps + ); + }); +} + +#[test] +fn rejecting_a_dispute_reapplies_the_aggregate() { + new_test_ext().execute_with(|| { + clear_recorded(); + System::set_block_number(1); + register_validators(6); + close_round_at(ScoreDimension::Compute, 9_000); + assert_ok!(NetworkValidator::dispute_round( + RuntimeOrigin::signed(PROVIDER), + PROVIDER, + ROUND, + ScoreDimension::Compute + )); + assert_ne!(current_score(PROVIDER, ScoreDimension::Compute), 9_000); + assert_ok!(NetworkValidator::resolve_dispute( + RuntimeOrigin::root(), + PROVIDER, + ROUND, + ScoreDimension::Compute, + false + )); + let result = + crate::Rounds::::get((PROVIDER, ROUND, ScoreDimension::Compute)).expect("round"); + assert_eq!(result.status, crate::RoundStatus::DisputeRejected); + assert_eq!(current_score(PROVIDER, ScoreDimension::Compute), 9_000); + }); +} + +#[test] +fn resolving_requires_governance_and_an_actual_dispute() { + new_test_ext().execute_with(|| { + clear_recorded(); + System::set_block_number(1); + register_validators(6); + let assigned = close_round_at(ScoreDimension::Compute, 9_000); + // Not disputed yet. + assert_noop!( + NetworkValidator::resolve_dispute( + RuntimeOrigin::root(), + PROVIDER, + ROUND, + ScoreDimension::Compute, + true + ), + crate::Error::::NotDisputed + ); + assert_ok!(NetworkValidator::dispute_round( + RuntimeOrigin::signed(PROVIDER), + PROVIDER, + ROUND, + ScoreDimension::Compute + )); + // A validator cannot settle its own dispute. + assert_noop!( + NetworkValidator::resolve_dispute( + RuntimeOrigin::signed(assigned[0]), + PROVIDER, + ROUND, + ScoreDimension::Compute, + true + ), + DispatchError::BadOrigin + ); + }); +} diff --git a/blockchain/pallets/reputation/src/lib.rs b/blockchain/pallets/reputation/src/lib.rs index 91cec36..9a72596 100644 --- a/blockchain/pallets/reputation/src/lib.rs +++ b/blockchain/pallets/reputation/src/lib.rs @@ -339,6 +339,29 @@ pub mod pallet { Ok(()) } + /// Read one dimension back as basis points, inverting + /// [`Self::set_dimension_score`]'s scaling. + /// + /// The round trip is exact whenever `MaxScore` divides 10_000 + /// evenly (it is 1_000 in the runtime, giving a step of 10 bps); + /// otherwise it is deterministically truncating, never lossy in a + /// way that differs between nodes. + pub fn dimension_score_bps(provider: &T::AccountId, dimension: VectorDimension) -> u16 { + let max_score = T::MaxScore::get(); + if max_score == 0 { + return 0; + } + let vector = ReputationVectors::::get(provider).unwrap_or_else(Self::default_vector); + let score = match dimension { + VectorDimension::Compute => vector.compute, + VectorDimension::Storage => vector.storage, + VectorDimension::Network => vector.network, + VectorDimension::Availability => vector.availability, + VectorDimension::Reliability => vector.reliability, + }; + (u64::from(score).saturating_mul(10_000) / u64::from(max_score)).min(10_000) as u16 + } + fn default_vector() -> ReputationVector { let score = T::DefaultScore::get(); ReputationVector { diff --git a/blockchain/runtime/src/lib.rs b/blockchain/runtime/src/lib.rs index 7bb441e..e41dd51 100644 --- a/blockchain/runtime/src/lib.rs +++ b/blockchain/runtime/src/lib.rs @@ -111,6 +111,8 @@ parameter_types! { pub const ValidatorMinQuorum: u32 = 3; pub const ValidatorTargetCommitteeSize: u32 = 5; pub const MaxNetworkValidators: u32 = 256; + // ~30 minutes at 6s blocks to contest a closed round. + pub const ValidatorDisputeWindow: u32 = 300; } #[derive_impl(frame_system::config_preludes::SolochainDefaultConfig)] @@ -257,16 +259,37 @@ impl pallet_network_validator::ReputationUpdater dimension: pallet_network_validator::ScoreDimension, score_bps: u16, ) -> frame::deps::sp_runtime::DispatchResult { + pallet_reputation::Pallet::::set_dimension_score( + provider, + Self::map_dimension(dimension), + score_bps, + ) + } + + fn dimension_score( + provider: &interface::AccountId, + dimension: pallet_network_validator::ScoreDimension, + ) -> u16 { + pallet_reputation::Pallet::::dimension_score_bps( + provider, + Self::map_dimension(dimension), + ) + } +} + +impl ScoringReputationUpdater { + fn map_dimension( + dimension: pallet_network_validator::ScoreDimension, + ) -> pallet_reputation::pallet::VectorDimension { use pallet_network_validator::ScoreDimension; use pallet_reputation::pallet::VectorDimension; - let mapped = match dimension { + match dimension { ScoreDimension::Compute => VectorDimension::Compute, ScoreDimension::Storage => VectorDimension::Storage, ScoreDimension::Network => VectorDimension::Network, ScoreDimension::Availability => VectorDimension::Availability, ScoreDimension::Reliability => VectorDimension::Reliability, - }; - pallet_reputation::Pallet::::set_dimension_score(provider, mapped, score_bps) + } } } @@ -283,6 +306,7 @@ impl pallet_network_validator::Config for Runtime { type MinQuorum = ValidatorMinQuorum; type TargetCommitteeSize = ValidatorTargetCommitteeSize; type MaxValidators = MaxNetworkValidators; + type DisputeWindow = ValidatorDisputeWindow; type WeightInfo = (); }