mirror of
https://github.com/datahaven-xyz/datahaven
synced 2026-05-24 01:38:32 +00:00
fix: Use block authorship as liveness indicator for validator rewards (#367)
## Summary
Use block authorship as direct proof of liveness for the 30% liveness
component of validator rewards. Validators who author at least one block
in a session are considered online and receive the full liveness bonus.
## Problem
The rewards pallet was checking validator liveness via ImOnline
**after** the session had rotated - at which point ImOnline had already
cleared its `AuthoredBlocks` storage. This caused all validators to
appear offline, resulting in only ~70% of expected rewards being
allocated (missing the 30% liveness bonus).
## Solution
Use **block authorship as the proxy for liveness**:
- A validator who authored at least one block is definitively online
- Liveness is determined directly in `award_session_performance_points`
via `blocks_authored > 0`
- No dependency on external liveness checks (ImOnline)
### Rewards Formula
- **60%** Block authorship (proportional to blocks produced)
- **30%** Liveness (full bonus if authored ≥1 block, zero otherwise)
- **10%** Base reward (for being in the validator set)
### Files Changed
- `pallets/external-validators-rewards/src/lib.rs` - Core logic changes
- `pallets/external-validators-rewards/src/mock.rs` - Test mock updates
- `pallets/external-validators-rewards/src/tests.rs` - Test updates
- `runtime/{mainnet,testnet,stagenet}/src/configs/mod.rs` - Config
updates
## Testing
- All 76 pallet tests pass
- Local testing should show correct points per session (e.g., 3200
points for 2 validators with 10 blocks)
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Ahmad Kaouk <56095276+ahmadkaouk@users.noreply.github.com>
This commit is contained in:
parent
aee282613f
commit
9bca5c467b
10 changed files with 126 additions and 334 deletions
3
operator/Cargo.lock
generated
3
operator/Cargo.lock
generated
|
|
@ -8977,7 +8977,6 @@ dependencies = [
|
|||
name = "pallet-external-validators-rewards"
|
||||
version = "0.12.0"
|
||||
dependencies = [
|
||||
"cumulus-primitives-core",
|
||||
"frame-benchmarking",
|
||||
"frame-support",
|
||||
"frame-system",
|
||||
|
|
@ -8988,8 +8987,6 @@ dependencies = [
|
|||
"pallet-session",
|
||||
"pallet-timestamp",
|
||||
"parity-scale-codec",
|
||||
"polkadot-primitives",
|
||||
"polkadot-runtime-parachains",
|
||||
"scale-info",
|
||||
"snowbridge-core 0.12.0",
|
||||
"snowbridge-merkle-tree",
|
||||
|
|
|
|||
|
|
@ -156,7 +156,6 @@ parachains-common = { git = "https://github.com/paritytech/polkadot-sdk.git", ta
|
|||
polkadot-parachain-primitives = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
|
||||
polkadot-primitives = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
|
||||
polkadot-runtime-common = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
|
||||
runtime-parachains = { package = "polkadot-runtime-parachains", git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
|
||||
sc-basic-authorship = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
|
||||
sc-cli = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
|
||||
sc-client-api = { git = "https://github.com/paritytech/polkadot-sdk", tag = "polkadot-stable2412-6", default-features = false }
|
||||
|
|
|
|||
|
|
@ -30,13 +30,10 @@ pallet-authorship = { workspace = true }
|
|||
pallet-balances = { workspace = true, optional = true }
|
||||
pallet-external-validators = { workspace = true }
|
||||
pallet-session = { workspace = true, features = [ "historical" ] }
|
||||
runtime-parachains = { workspace = true }
|
||||
|
||||
snowbridge-core = { workspace = true }
|
||||
snowbridge-merkle-tree = { workspace = true }
|
||||
snowbridge-outbound-queue-primitives = { workspace = true }
|
||||
cumulus-primitives-core = { workspace = true }
|
||||
polkadot-primitives = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pallet-timestamp = { workspace = true }
|
||||
|
|
@ -45,7 +42,6 @@ sp-io = { workspace = true }
|
|||
[features]
|
||||
default = [ "std" ]
|
||||
std = [
|
||||
"cumulus-primitives-core/std",
|
||||
"frame-benchmarking/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
|
|
@ -56,8 +52,6 @@ std = [
|
|||
"pallet-session/std",
|
||||
"pallet-timestamp/std",
|
||||
"parity-scale-codec/std",
|
||||
"polkadot-primitives/std",
|
||||
"runtime-parachains/std",
|
||||
"scale-info/std",
|
||||
"snowbridge-core/std",
|
||||
"snowbridge-merkle-tree/std",
|
||||
|
|
@ -69,15 +63,12 @@ std = [
|
|||
"sp-std/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"cumulus-primitives-core/runtime-benchmarks",
|
||||
"frame-benchmarking/runtime-benchmarks",
|
||||
"frame-support/runtime-benchmarks",
|
||||
"frame-system/runtime-benchmarks",
|
||||
"pallet-balances/runtime-benchmarks",
|
||||
"pallet-external-validators/runtime-benchmarks",
|
||||
"pallet-timestamp/runtime-benchmarks",
|
||||
"polkadot-primitives/runtime-benchmarks",
|
||||
"runtime-parachains/runtime-benchmarks",
|
||||
"snowbridge-core/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
"sp-staking/runtime-benchmarks",
|
||||
|
|
@ -91,6 +82,5 @@ try-runtime = [
|
|||
"pallet-external-validators/try-runtime",
|
||||
"pallet-session/try-runtime",
|
||||
"pallet-timestamp/try-runtime",
|
||||
"runtime-parachains/try-runtime",
|
||||
"sp-runtime/try-runtime",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -35,11 +35,9 @@ pub use pallet::*;
|
|||
|
||||
use {
|
||||
crate::types::{EraRewardsUtils, HandleInflation, SendMessage},
|
||||
frame_support::traits::{Contains, Defensive, Get, ValidatorSet},
|
||||
frame_support::traits::{Get, ValidatorSet},
|
||||
pallet_external_validators::traits::{ExternalIndexProvider, OnEraEnd, OnEraStart},
|
||||
parity_scale_codec::Encode,
|
||||
polkadot_primitives::ValidatorIndex,
|
||||
runtime_parachains::session_info,
|
||||
parity_scale_codec::{Decode, Encode},
|
||||
snowbridge_merkle_tree::{merkle_proof, merkle_root, verify_proof, MerkleProof},
|
||||
sp_core::{H160, H256},
|
||||
sp_runtime::{
|
||||
|
|
@ -47,7 +45,7 @@ use {
|
|||
Perbill,
|
||||
},
|
||||
sp_staking::SessionIndex,
|
||||
sp_std::{collections::btree_set::BTreeSet, vec::Vec},
|
||||
sp_std::vec::Vec,
|
||||
};
|
||||
|
||||
/// Trait for checking if a validator has been slashed in a given era
|
||||
|
|
@ -92,14 +90,6 @@ pub mod pallet {
|
|||
#[pallet::constant]
|
||||
type HistoryDepth: Get<EraIndex>;
|
||||
|
||||
/// The amount of era points given by backing a candidate that is included.
|
||||
#[pallet::constant]
|
||||
type BackingPoints: Get<u32>;
|
||||
|
||||
/// The amount of era points given by dispute voting on a candidate.
|
||||
#[pallet::constant]
|
||||
type DisputeStatementPoints: Get<u32>;
|
||||
|
||||
/// Provider to know how may tokens were inflated (added) in a specific era.
|
||||
type EraInflationProvider: Get<u128>;
|
||||
|
||||
|
|
@ -108,11 +98,12 @@ pub mod pallet {
|
|||
|
||||
type GetWhitelistedValidators: Get<Vec<Self::AccountId>>;
|
||||
|
||||
/// Validator set provider for performance tracking
|
||||
type ValidatorSet: frame_support::traits::ValidatorSet<Self::AccountId>;
|
||||
|
||||
/// Provider to check if validators are online (sent heartbeat this session)
|
||||
type LivenessCheck: frame_support::traits::Contains<Self::AccountId>;
|
||||
/// Validator set provider for performance tracking.
|
||||
/// Requires ValidatorId = AccountId so we can use validators() directly.
|
||||
type ValidatorSet: frame_support::traits::ValidatorSet<
|
||||
Self::AccountId,
|
||||
ValidatorId = Self::AccountId,
|
||||
>;
|
||||
|
||||
/// Check if a validator has been slashed in a given era
|
||||
type SlashingCheck: SlashingCheck<Self::AccountId>;
|
||||
|
|
@ -134,7 +125,7 @@ pub mod pallet {
|
|||
/// The remainder (100% - block - liveness) is the unconditional base reward.
|
||||
type BlockAuthoringWeight: Get<Perbill>;
|
||||
|
||||
/// Weight of liveness (heartbeat/block authorship) in the rewards formula.
|
||||
/// Weight of liveness (block authorship) in the rewards formula.
|
||||
/// Combined with BlockAuthoringWeight, the sum should not exceed 100%.
|
||||
/// The remainder (100% - block - liveness) is the unconditional base reward.
|
||||
type LivenessWeight: Get<Perbill>;
|
||||
|
|
@ -468,9 +459,9 @@ pub mod pallet {
|
|||
///
|
||||
/// # Liveness Scoring
|
||||
///
|
||||
/// Based on ImOnline's is_online() which considers a validator online if:
|
||||
/// - They sent a heartbeat in the current session, OR
|
||||
/// - They authored at least one block in the current session
|
||||
/// Uses block authorship as proof of liveness. A validator is considered online if
|
||||
/// they authored at least one block in the current session. This is simpler and more
|
||||
/// reliable than ImOnline heartbeats, which have timing issues with session rotation.
|
||||
///
|
||||
/// # Weight Validation
|
||||
///
|
||||
|
|
@ -602,9 +593,11 @@ pub mod pallet {
|
|||
// credited_blocks = min(blocks_authored, max_credited_blocks)
|
||||
let credited_blocks = blocks_authored.min(max_credited_blocks);
|
||||
|
||||
// Liveness score: based on ImOnline's is_online() which considers
|
||||
// heartbeats OR block authorship
|
||||
let is_online = T::LivenessCheck::contains(validator);
|
||||
// Liveness score: Use block authorship as proof of liveness.
|
||||
// A validator who authored at least one block is definitively online.
|
||||
// This is simpler and more reliable than trying to cache ImOnline state
|
||||
// which has timing issues with session rotation.
|
||||
let is_online = blocks_authored > 0;
|
||||
let liveness_score = if is_online {
|
||||
Perbill::one()
|
||||
} else {
|
||||
|
|
@ -750,105 +743,11 @@ pub mod pallet {
|
|||
}
|
||||
}
|
||||
|
||||
/// Rewards validators for participating in parachains with era points in pallet-staking.
|
||||
pub struct RewardValidatorsWithEraPoints<C>(core::marker::PhantomData<C>);
|
||||
|
||||
impl<C> RewardValidatorsWithEraPoints<C>
|
||||
where
|
||||
C: pallet::Config
|
||||
+ session_info::Config<
|
||||
ValidatorSet: frame_support::traits::ValidatorSet<
|
||||
C::AccountId,
|
||||
ValidatorId = C::AccountId,
|
||||
>,
|
||||
>,
|
||||
<C as pallet::Config>::ValidatorSet:
|
||||
frame_support::traits::ValidatorSet<C::AccountId, ValidatorId = C::AccountId>,
|
||||
C::AccountId: Ord,
|
||||
{
|
||||
/// Reward validators in session with points, but only if they are in the active set.
|
||||
fn reward_only_active(
|
||||
session_index: SessionIndex,
|
||||
indices: impl IntoIterator<Item = ValidatorIndex>,
|
||||
points: u32,
|
||||
) {
|
||||
let validators = session_info::AccountKeys::<C>::get(&session_index);
|
||||
let validators = match validators
|
||||
.defensive_proof("account_keys are present for dispute_period sessions")
|
||||
{
|
||||
Some(validators) => validators,
|
||||
None => return,
|
||||
};
|
||||
// limit rewards to the active validator set
|
||||
let mut active_set: BTreeSet<C::AccountId> =
|
||||
<C as pallet::Config>::ValidatorSet::validators()
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
// Remove whitelisted validators, we don't want to reward them
|
||||
let whitelisted_validators = C::GetWhitelistedValidators::get();
|
||||
for validator in whitelisted_validators {
|
||||
active_set.remove(&validator);
|
||||
}
|
||||
|
||||
let rewards = indices
|
||||
.into_iter()
|
||||
.filter_map(|i| validators.get(i.0 as usize).cloned())
|
||||
.filter(|v| active_set.contains(v))
|
||||
.map(|v| (v, points));
|
||||
|
||||
pallet::Pallet::<C>::reward_by_ids(rewards);
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> runtime_parachains::inclusion::RewardValidators for RewardValidatorsWithEraPoints<C>
|
||||
where
|
||||
C: pallet::Config
|
||||
+ runtime_parachains::shared::Config
|
||||
+ session_info::Config<
|
||||
ValidatorSet: frame_support::traits::ValidatorSet<
|
||||
C::AccountId,
|
||||
ValidatorId = C::AccountId,
|
||||
>,
|
||||
>,
|
||||
<C as pallet::Config>::ValidatorSet:
|
||||
frame_support::traits::ValidatorSet<C::AccountId, ValidatorId = C::AccountId>,
|
||||
C::AccountId: Ord,
|
||||
{
|
||||
fn reward_backing(indices: impl IntoIterator<Item = ValidatorIndex>) {
|
||||
let session_index = runtime_parachains::shared::CurrentSessionIndex::<C>::get();
|
||||
Self::reward_only_active(session_index, indices, C::BackingPoints::get());
|
||||
}
|
||||
|
||||
fn reward_bitfields(_validators: impl IntoIterator<Item = ValidatorIndex>) {}
|
||||
}
|
||||
|
||||
impl<C> runtime_parachains::disputes::RewardValidators for RewardValidatorsWithEraPoints<C>
|
||||
where
|
||||
C: pallet::Config
|
||||
+ session_info::Config<
|
||||
ValidatorSet: frame_support::traits::ValidatorSet<
|
||||
C::AccountId,
|
||||
ValidatorId = C::AccountId,
|
||||
>,
|
||||
>,
|
||||
<C as pallet::Config>::ValidatorSet:
|
||||
frame_support::traits::ValidatorSet<C::AccountId, ValidatorId = C::AccountId>,
|
||||
C::AccountId: Ord,
|
||||
{
|
||||
fn reward_dispute_statement(
|
||||
session: SessionIndex,
|
||||
validators: impl IntoIterator<Item = ValidatorIndex>,
|
||||
) {
|
||||
Self::reward_only_active(session, validators, C::DisputeStatementPoints::get());
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper for pallet_session::SessionManager that awards performance-based points at session end.
|
||||
///
|
||||
/// This implements the 60/30/10 performance formula for solochain validators:
|
||||
/// - 60% weight: Block production (BABE participation)
|
||||
/// - 30% weight: Heartbeat/liveness (ImOnline participation)
|
||||
/// - 60% weight: Block production (credited blocks vs fair share)
|
||||
/// - 30% weight: Liveness (1.0 if authored at least one block, 0.0 otherwise)
|
||||
/// - 10% weight: Base guarantee (always awarded)
|
||||
///
|
||||
/// Wraps an inner SessionManager (typically `NoteHistoricalRoot<ExternalValidators>`) and calls
|
||||
|
|
|
|||
|
|
@ -191,26 +191,6 @@ impl frame_support::traits::ValidatorSet<H160> for MockValidatorSet {
|
|||
}
|
||||
}
|
||||
|
||||
/// Configurable liveness check that mirrors ImOnline behavior.
|
||||
/// A validator is considered online if:
|
||||
/// 1. They are NOT in the offline_validators list, OR
|
||||
/// 2. They have authored at least one block in the current session
|
||||
///
|
||||
/// This matches the real ImOnline pallet which considers block authorship
|
||||
/// as proof of liveness (no heartbeat needed if you authored a block).
|
||||
pub struct MockLivenessCheck;
|
||||
impl frame_support::traits::Contains<H160> for MockLivenessCheck {
|
||||
fn contains(validator: &H160) -> bool {
|
||||
// Check if validator authored any blocks this session
|
||||
let authored_blocks = crate::BlocksAuthoredInSession::<Test>::get(validator);
|
||||
|
||||
// Validator is online if:
|
||||
// 1. They authored blocks (proves they're online), OR
|
||||
// 2. They're not in the offline list (sent heartbeat)
|
||||
authored_blocks > 0 || !Mock::mock().offline_validators.contains(validator)
|
||||
}
|
||||
}
|
||||
|
||||
/// Configurable slashing check that reads slashed validators from mock data.
|
||||
/// Validators in the slashed_validators list (for the given era) are considered slashed.
|
||||
pub struct MockSlashingCheck;
|
||||
|
|
@ -226,13 +206,10 @@ impl pallet_external_validators_rewards::Config for Test {
|
|||
type RuntimeEvent = RuntimeEvent;
|
||||
type EraIndexProvider = mock_data::Pallet<Test>;
|
||||
type HistoryDepth = ConstU32<10>;
|
||||
type BackingPoints = ConstU32<20>;
|
||||
type DisputeStatementPoints = ConstU32<20>;
|
||||
type EraInflationProvider = EraInflationProvider;
|
||||
type ExternalIndexProvider = TimestampProvider;
|
||||
type GetWhitelistedValidators = ();
|
||||
type ValidatorSet = MockValidatorSet;
|
||||
type LivenessCheck = MockLivenessCheck;
|
||||
type SlashingCheck = MockSlashingCheck;
|
||||
type BasePointsPerBlock = BasePointsPerBlock;
|
||||
type BlockAuthoringWeight = BlockAuthoringWeight;
|
||||
|
|
@ -389,3 +366,12 @@ pub fn run_to_block(n: u64) {
|
|||
Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function for tests to award session performance points.
|
||||
pub fn end_session(session_index: u32, validators: Vec<H160>, whitelisted: Vec<H160>) {
|
||||
ExternalValidatorsRewards::award_session_performance_points(
|
||||
session_index,
|
||||
validators,
|
||||
whitelisted,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1462,7 +1462,7 @@ fn test_session_performance_60_30_10_formula() {
|
|||
// MockIsOnline always returns true, so all validators are considered online
|
||||
|
||||
// Award session performance points
|
||||
ExternalValidatorsRewards::award_session_performance_points(
|
||||
end_session(
|
||||
1, // session_index
|
||||
validators.clone(),
|
||||
vec![], // no whitelisted validators
|
||||
|
|
@ -1473,21 +1473,23 @@ fn test_session_performance_60_30_10_formula() {
|
|||
// fair_share = 10/4 = 2, max_credited = 2 + 50%×2 = 3
|
||||
// effective_total_for_other = max(10, 4) = 10
|
||||
//
|
||||
// Liveness is determined by block authorship (blocks_authored > 0)
|
||||
// New formula per validator (with BasePointsPerBlock = 320):
|
||||
// block_contribution = 60% × credited × 320
|
||||
// liveness_base_contribution = 40% × 10 × 320 / 4 = 320
|
||||
// For online validators (authored blocks): liveness_base = 40% × 10 × 320 / 4 = 320
|
||||
// For offline validators (no blocks): liveness_base = 10% × 10 × 320 / 4 = 80
|
||||
//
|
||||
// - Validator 1: 4 blocks → credited=3, block=576, other=320, total=896
|
||||
// - Validator 2: 4 blocks → credited=3, block=576, other=320, total=896
|
||||
// - Validator 3: 2 blocks → credited=2, block=384, other=320, total=704
|
||||
// - Validator 4: 0 blocks → credited=0, block=0, other=320, total=320
|
||||
// - Validator 1: 4 blocks → online, credited=3, block=576, other=320, total=896
|
||||
// - Validator 2: 4 blocks → online, credited=3, block=576, other=320, total=896
|
||||
// - Validator 3: 2 blocks → online, credited=2, block=384, other=320, total=704
|
||||
// - Validator 4: 0 blocks → offline, credited=0, block=0, other=80, total=80
|
||||
|
||||
// Check total points for the active era (era 1)
|
||||
let era_rewards = pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1);
|
||||
assert_eq!(
|
||||
era_rewards.total,
|
||||
2816, // 896 + 896 + 704 + 320
|
||||
"Total points should be 2816"
|
||||
2576, // 896 + 896 + 704 + 80
|
||||
"Total points should be 2576"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
|
@ -1519,7 +1521,7 @@ fn test_session_performance_whitelisted_validators_excluded() {
|
|||
}
|
||||
|
||||
// Award session performance points
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, whitelisted);
|
||||
end_session(1, validators, whitelisted);
|
||||
|
||||
// Fair share and liveness/base both use total validator count:
|
||||
// 9 blocks total, 3 validators, 2 non-whitelisted
|
||||
|
|
@ -1577,7 +1579,7 @@ fn test_session_performance_whitelisted_fair_share_calculation() {
|
|||
}
|
||||
|
||||
// Award session performance points
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, whitelisted);
|
||||
end_session(1, validators, whitelisted);
|
||||
|
||||
// Fair share and liveness/base both use total validator count:
|
||||
// fair_share = 12 total blocks / 4 total validators = 3 blocks
|
||||
|
|
@ -1696,23 +1698,23 @@ fn test_session_performance_zero_total_blocks() {
|
|||
H160::from_low_u64_be(3),
|
||||
];
|
||||
|
||||
// No blocks authored by anyone
|
||||
// No blocks authored by anyone - all validators are considered offline
|
||||
|
||||
// Award session performance points
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, vec![]);
|
||||
end_session(1, validators, vec![]);
|
||||
|
||||
// With 0 total blocks, fair_share defaults to 1 (via .max(1))
|
||||
// effective_total_for_other = max(0, 3) = 3
|
||||
// Each validator: 0 blocks
|
||||
// Each validator: 0 blocks → offline (no liveness bonus)
|
||||
// - block_contribution = 60% × 0 × 320 = 0
|
||||
// - liveness_base_contribution = 40% × 3 × 320 / 3 = 128
|
||||
// - total = 128 points
|
||||
// Total: 3 validators × 128 points = 384 points
|
||||
// - liveness_base_contribution = 10% × 3 × 320 / 3 = 32 (only base, no liveness)
|
||||
// - total = 32 points
|
||||
// Total: 3 validators × 32 points = 96 points
|
||||
|
||||
assert_eq!(
|
||||
pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1).total,
|
||||
384,
|
||||
"Should award liveness + base points even with zero blocks"
|
||||
96,
|
||||
"Should award only base points when no blocks authored (all validators offline)"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
|
@ -1745,7 +1747,7 @@ fn test_session_performance_fair_share_capping() {
|
|||
// effective_total_for_other = max(15, 2) = 15
|
||||
|
||||
// Award session performance points
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, vec![]);
|
||||
end_session(1, validators, vec![]);
|
||||
|
||||
// New formula (with BasePointsPerBlock = 320):
|
||||
// block_contribution = 60% × credited × 320
|
||||
|
|
@ -1785,7 +1787,7 @@ fn test_session_performance_single_validator() {
|
|||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(1));
|
||||
}
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, vec![]);
|
||||
end_session(1, validators, vec![]);
|
||||
|
||||
// Fair share: 10 / 1 = 10 blocks
|
||||
// max_credited = 10 + 50%×10 = 15
|
||||
|
|
@ -1818,7 +1820,7 @@ fn test_session_performance_no_active_validators() {
|
|||
let validators = vec![];
|
||||
|
||||
// Award session performance points with empty validator set
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, vec![]);
|
||||
end_session(1, validators, vec![]);
|
||||
|
||||
// Should handle gracefully without panicking
|
||||
assert_eq!(
|
||||
|
|
@ -1848,19 +1850,19 @@ fn test_session_performance_checked_math_division() {
|
|||
H160::from_low_u64_be(3),
|
||||
];
|
||||
|
||||
// Session 1: No blocks produced
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators.clone(), vec![]);
|
||||
// Session 1: No blocks produced - all validators offline
|
||||
end_session(1, validators.clone(), vec![]);
|
||||
|
||||
// Should not panic, checked_div returns Some or defaults to 1 via .max(1)
|
||||
// With 0 blocks, effective_total_for_other = max(0, 3) = 3
|
||||
// Each validator: block_contribution = 0
|
||||
// liveness_base_contribution = 40% × 3 × 320 / 3 = 128 per validator
|
||||
// Total for 3 validators = 384 points
|
||||
// Each validator: block_contribution = 0, offline (no blocks authored)
|
||||
// liveness_base_contribution = 10% × 3 × 320 / 3 = 32 per validator
|
||||
// Total for 3 validators = 96 points
|
||||
let points_after_session1 =
|
||||
pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1).total;
|
||||
assert_eq!(
|
||||
points_after_session1, 384,
|
||||
"Should award 384 points (128 per validator) with zero blocks"
|
||||
points_after_session1, 96,
|
||||
"Should award 96 points (32 per validator) with zero blocks (all offline)"
|
||||
);
|
||||
|
||||
// Session 2: Author blocks equally among all validators
|
||||
|
|
@ -1870,23 +1872,23 @@ fn test_session_performance_checked_math_division() {
|
|||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(3));
|
||||
}
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(2, validators, vec![]);
|
||||
end_session(2, validators, vec![]);
|
||||
|
||||
// With 18 blocks (6 per validator):
|
||||
// fair_share = 18 / 3 = 6, max_credited = 6 + 50%×6 = 9
|
||||
// effective_total_for_other = max(18, 3) = 18
|
||||
//
|
||||
// Each validator: 6 blocks → credited 6
|
||||
// Each validator: 6 blocks → online, credited 6
|
||||
// block_contribution = 60% × 6 × 320 = 1152
|
||||
// liveness_base_contribution = 40% × 18 × 320 / 3 = 768
|
||||
// Total per validator = 1920
|
||||
// Total for 3 validators = 5760 points
|
||||
// Cumulative total = 384 + 5760 = 6144 points
|
||||
// Cumulative total = 96 + 5760 = 5856 points
|
||||
let points_after_session2 =
|
||||
pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1).total;
|
||||
assert_eq!(
|
||||
points_after_session2, 6144,
|
||||
"Should have 6144 total points (384 from session 1 + 5760 from session 2)"
|
||||
points_after_session2, 5856,
|
||||
"Should have 5856 total points (96 from session 1 + 5760 from session 2)"
|
||||
);
|
||||
})
|
||||
}
|
||||
|
|
@ -1911,7 +1913,7 @@ fn test_session_performance_multiple_sessions_cumulative() {
|
|||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(2));
|
||||
}
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators.clone(), vec![]);
|
||||
end_session(1, validators.clone(), vec![]);
|
||||
|
||||
let points_after_session1 =
|
||||
pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1).total;
|
||||
|
|
@ -1928,7 +1930,7 @@ fn test_session_performance_multiple_sessions_cumulative() {
|
|||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(2));
|
||||
}
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(2, validators, vec![]);
|
||||
end_session(2, validators, vec![]);
|
||||
|
||||
let points_after_session2 =
|
||||
pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1).total;
|
||||
|
|
@ -1960,7 +1962,7 @@ fn test_session_performance_base_reward_points_config() {
|
|||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(1));
|
||||
}
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, vec![]);
|
||||
end_session(1, validators, vec![]);
|
||||
|
||||
// BasePointsPerBlock is 320 (points per block)
|
||||
// fair_share = 5 blocks, effective_total_for_other = max(5, 1) = 5
|
||||
|
|
@ -2556,7 +2558,7 @@ fn test_session_performance_offline_validator_gets_reduced_points() {
|
|||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(3));
|
||||
}
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, vec![]);
|
||||
end_session(1, validators, vec![]);
|
||||
|
||||
// With 12 blocks total, fair_share = 12 / 3 = 4
|
||||
// max_credited = 4 + 50%×4 = 6
|
||||
|
|
@ -2622,7 +2624,7 @@ fn test_session_performance_all_validators_offline() {
|
|||
|
||||
// No validators author blocks - they are all truly offline
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, vec![]);
|
||||
end_session(1, validators, vec![]);
|
||||
|
||||
// With 0 blocks total, fair_share = max(0/3, 1) = 1
|
||||
// effective_total_for_other = max(0, 3) = 3
|
||||
|
|
@ -2672,7 +2674,7 @@ fn test_session_performance_offline_but_authored_blocks() {
|
|||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(3));
|
||||
}
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, vec![]);
|
||||
end_session(1, validators, vec![]);
|
||||
|
||||
// With 18 blocks total, fair_share = 6
|
||||
// max_credited = 6 + 50%×6 = 9
|
||||
|
|
@ -2722,7 +2724,7 @@ fn test_session_performance_offline_validator_zero_blocks() {
|
|||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(3));
|
||||
}
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, vec![]);
|
||||
end_session(1, validators, vec![]);
|
||||
|
||||
// With 10 blocks total, fair_share = 10 / 3 = 3
|
||||
// max_credited = 3 + 50%×3 = 4
|
||||
|
|
@ -2775,7 +2777,7 @@ fn test_session_performance_weight_overflow_handled() {
|
|||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(1));
|
||||
}
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, vec![]);
|
||||
end_session(1, validators, vec![]);
|
||||
|
||||
// Verify the formula works with current weights
|
||||
// fair_share = 10, effective_total_for_other = max(10, 1) = 10
|
||||
|
|
@ -2853,7 +2855,7 @@ fn test_session_performance_slashed_validator_still_gets_points_when_disabled()
|
|||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(2));
|
||||
}
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, vec![]);
|
||||
end_session(1, validators, vec![]);
|
||||
|
||||
// With slashing DISABLED, validator 2 still gets points
|
||||
// fair_share = 10 / 2 = 5
|
||||
|
|
@ -2910,27 +2912,29 @@ fn test_fair_share_non_integer_division_rounding() {
|
|||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(1));
|
||||
}
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, vec![]);
|
||||
end_session(1, validators, vec![]);
|
||||
|
||||
// New formula with 10 blocks, 3 validators:
|
||||
// fair_share = 10/3 = 3, max_credited = 3 + 50%×3 = 4
|
||||
// effective_total_for_other = max(10, 3) = 10
|
||||
//
|
||||
// block_contribution = 60% × credited × 320
|
||||
// liveness_base_contribution = 40% × 10 × 320 / 3 = 1280 / 3 = 426
|
||||
// Liveness is determined by block authorship (blocks_authored > 0)
|
||||
//
|
||||
// Validator 1 (10 blocks): credited=4, block=768, other=426, total=1194
|
||||
// Validators 2, 3 (0 blocks): block=0, other=426, total=426 each
|
||||
// Validator 1 (10 blocks): online, credited=4
|
||||
// block_contribution = 60% × 4 × 320 = 768
|
||||
// liveness_base_contribution = 40% × 10 × 320 / 3 = 426
|
||||
// total = 1194
|
||||
//
|
||||
// Total = 1194 + 426 + 426 = 2046
|
||||
// Validators 2, 3 (0 blocks): offline
|
||||
// block_contribution = 0
|
||||
// liveness_base_contribution = 10% × 10 × 320 / 3 = 106 (only base, no liveness)
|
||||
// total = 106 each
|
||||
//
|
||||
// This demonstrates the fix for:
|
||||
// 1. Perbill capping - validator 1 now gets proper over-performance bonus (credited 4 > fair_share 3)
|
||||
// 2. Fair share truncation - using total_blocks (10) for liveness/base pool, not fair_share×count (9)
|
||||
// Total = 1194 + 106 + 106 = 1406
|
||||
|
||||
let era_rewards = pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1);
|
||||
assert_eq!(
|
||||
era_rewards.total, 2046,
|
||||
era_rewards.total, 1406,
|
||||
"Non-integer division should not lose points"
|
||||
);
|
||||
})
|
||||
|
|
@ -2966,7 +2970,7 @@ fn test_all_validators_whitelisted_no_panic() {
|
|||
}
|
||||
|
||||
// Should not panic, just skip awarding points
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, whitelisted);
|
||||
end_session(1, validators, whitelisted);
|
||||
|
||||
let era_rewards = pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1);
|
||||
assert_eq!(
|
||||
|
|
@ -3001,27 +3005,29 @@ fn test_blocks_less_than_validators() {
|
|||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(1));
|
||||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(1));
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, vec![]);
|
||||
end_session(1, validators, vec![]);
|
||||
|
||||
// fair_share = 2 / 5 = 0, but .max(1) ensures minimum of 1
|
||||
// max_credited = 1 + 50%×1 = 1
|
||||
// effective_total_for_other = max(2, 5) = 5
|
||||
//
|
||||
// Liveness is determined by block authorship (blocks_authored > 0)
|
||||
|
||||
// Validator 1: 2 blocks, credited = min(2, 1) = 1
|
||||
// Validator 1: 2 blocks → online, credited = min(2, 1) = 1
|
||||
// block_contribution = 60% × 1 × 320 = 192
|
||||
// liveness_base_contribution = 40% × 5 × 320 / 5 = 128
|
||||
// total = 320
|
||||
|
||||
// Validators 2-5: 0 blocks
|
||||
// Validators 2-5: 0 blocks → offline
|
||||
// block_contribution = 0
|
||||
// liveness_base_contribution = 128
|
||||
// total = 128
|
||||
// liveness_base_contribution = 10% × 5 × 320 / 5 = 32 (only base)
|
||||
// total = 32
|
||||
|
||||
// Total = 320 + 128×4 = 832
|
||||
// Total = 320 + 32×4 = 448
|
||||
|
||||
let era_rewards = pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1);
|
||||
assert_eq!(
|
||||
era_rewards.total, 832,
|
||||
era_rewards.total, 448,
|
||||
"Should handle fewer blocks than validators"
|
||||
);
|
||||
})
|
||||
|
|
@ -3056,26 +3062,28 @@ fn test_single_block_many_validators() {
|
|||
// Only 1 block for 10 validators
|
||||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(1));
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, vec![]);
|
||||
end_session(1, validators, vec![]);
|
||||
|
||||
// fair_share = 1 / 10 = 0, but .max(1) ensures minimum of 1
|
||||
// effective_total_for_other = max(1, 10) = 10
|
||||
//
|
||||
// Liveness is determined by block authorship (blocks_authored > 0)
|
||||
|
||||
// Validator 1: 1 block, credited = min(1, 1) = 1
|
||||
// Validator 1: 1 block → online, credited = min(1, 1) = 1
|
||||
// block_contribution = 60% × 1 × 320 = 192
|
||||
// liveness_base_contribution = 40% × 10 × 320 / 10 = 128
|
||||
// total = 320
|
||||
|
||||
// Validators 2-10: 0 blocks
|
||||
// Validators 2-10: 0 blocks → offline
|
||||
// block_contribution = 0
|
||||
// liveness_base_contribution = 128
|
||||
// total = 128 each
|
||||
// liveness_base_contribution = 10% × 10 × 320 / 10 = 32 (only base)
|
||||
// total = 32 each
|
||||
|
||||
// Total = 320 + 128×9 = 1472
|
||||
// Total = 320 + 32×9 = 608
|
||||
|
||||
let era_rewards = pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1);
|
||||
assert_eq!(
|
||||
era_rewards.total, 1472,
|
||||
era_rewards.total, 608,
|
||||
"Should handle 1 block for many validators"
|
||||
);
|
||||
})
|
||||
|
|
@ -3116,11 +3124,7 @@ fn test_perbill_precision_many_sessions() {
|
|||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(3));
|
||||
}
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(
|
||||
session,
|
||||
validators.clone(),
|
||||
vec![],
|
||||
);
|
||||
end_session(session, validators.clone(), vec![]);
|
||||
}
|
||||
|
||||
// Verify total points accumulated without overflow or significant precision loss
|
||||
|
|
@ -3231,7 +3235,7 @@ fn test_total_points_sum_equals_expected_pool() {
|
|||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(4));
|
||||
}
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, vec![]);
|
||||
end_session(1, validators, vec![]);
|
||||
|
||||
let era_rewards = pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1);
|
||||
|
||||
|
|
@ -3284,7 +3288,7 @@ fn test_total_points_with_uneven_distribution() {
|
|||
}
|
||||
// Validator 3 authors no blocks
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, vec![]);
|
||||
end_session(1, validators, vec![]);
|
||||
|
||||
let era_rewards = pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1);
|
||||
|
||||
|
|
@ -3299,22 +3303,22 @@ fn test_total_points_with_uneven_distribution() {
|
|||
// fair_share = 5, max_credited = 7
|
||||
// effective_total_for_other = max(15, 3) = 15
|
||||
//
|
||||
// Validator 1: 10 blocks → credited = 7 (capped)
|
||||
// Validator 1: 10 blocks → online, credited = 7 (capped)
|
||||
// block = 60% × 7 × 320 = 1344
|
||||
// other = 40% × 15 × 320 / 3 = 640
|
||||
// total = 1984
|
||||
//
|
||||
// Validator 2: 5 blocks → credited = 5
|
||||
// Validator 2: 5 blocks → online, credited = 5
|
||||
// block = 60% × 5 × 320 = 960
|
||||
// other = 640
|
||||
// total = 1600
|
||||
//
|
||||
// Validator 3: 0 blocks
|
||||
// Validator 3: 0 blocks → offline
|
||||
// block = 0
|
||||
// other = 640
|
||||
// total = 640
|
||||
// other = 10% × 15 × 320 / 3 = 160 (only base, no liveness)
|
||||
// total = 160
|
||||
//
|
||||
// Total = 1984 + 1600 + 640 = 4224
|
||||
// Total = 1984 + 1600 + 160 = 3744
|
||||
|
||||
assert_eq!(
|
||||
era_rewards.individual.get(&H160::from_low_u64_be(1)),
|
||||
|
|
@ -3328,10 +3332,10 @@ fn test_total_points_with_uneven_distribution() {
|
|||
);
|
||||
assert_eq!(
|
||||
era_rewards.individual.get(&H160::from_low_u64_be(3)),
|
||||
Some(&640),
|
||||
"Validator 3 should have 640 points"
|
||||
Some(&160),
|
||||
"Validator 3 should have 160 points (offline, only base)"
|
||||
);
|
||||
assert_eq!(era_rewards.total, 4224, "Total should be 4224 points");
|
||||
assert_eq!(era_rewards.total, 3744, "Total should be 3744 points");
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -3368,7 +3372,7 @@ fn test_whitelisted_overproducer_does_not_affect_nonwhitelisted() {
|
|||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(4));
|
||||
}
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, whitelisted);
|
||||
end_session(1, validators, whitelisted);
|
||||
|
||||
// 47 blocks total, 4 validators (1 non-whitelisted)
|
||||
// fair_share = 47 / 4 = 11
|
||||
|
|
@ -3423,11 +3427,7 @@ fn test_whitelisted_majority_fair_share_calculation() {
|
|||
}
|
||||
}
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(
|
||||
1,
|
||||
validators.clone(),
|
||||
whitelisted,
|
||||
);
|
||||
end_session(1, validators.clone(), whitelisted);
|
||||
|
||||
// 30 blocks total, 10 validators
|
||||
// fair_share = 30 / 10 = 3
|
||||
|
|
@ -3499,7 +3499,7 @@ fn test_large_block_count_no_overflow() {
|
|||
large_block_count,
|
||||
);
|
||||
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, vec![]);
|
||||
end_session(1, validators, vec![]);
|
||||
|
||||
// Should not panic
|
||||
let era_rewards = pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1);
|
||||
|
|
@ -3553,7 +3553,7 @@ fn test_saturating_arithmetic_protection() {
|
|||
);
|
||||
|
||||
// Should not panic due to saturating arithmetic
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators, vec![]);
|
||||
end_session(1, validators, vec![]);
|
||||
|
||||
let era_rewards = pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1);
|
||||
assert!(
|
||||
|
|
@ -3589,7 +3589,7 @@ fn test_multiple_sessions_accumulate_to_era_correctly() {
|
|||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(1));
|
||||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(2));
|
||||
}
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators.clone(), vec![]);
|
||||
end_session(1, validators.clone(), vec![]);
|
||||
let points_after_s1 =
|
||||
pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1).total;
|
||||
|
||||
|
|
@ -3604,7 +3604,7 @@ fn test_multiple_sessions_accumulate_to_era_correctly() {
|
|||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(1));
|
||||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(2));
|
||||
}
|
||||
ExternalValidatorsRewards::award_session_performance_points(2, validators.clone(), vec![]);
|
||||
end_session(2, validators.clone(), vec![]);
|
||||
let points_after_s2 =
|
||||
pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1).total;
|
||||
|
||||
|
|
@ -3621,7 +3621,7 @@ fn test_multiple_sessions_accumulate_to_era_correctly() {
|
|||
for _ in 0..20 {
|
||||
ExternalValidatorsRewards::note_block_author(H160::from_low_u64_be(2));
|
||||
}
|
||||
ExternalValidatorsRewards::award_session_performance_points(3, validators.clone(), vec![]);
|
||||
end_session(3, validators.clone(), vec![]);
|
||||
let points_after_s3 =
|
||||
pallet_external_validators_rewards::RewardPointsForEra::<Test>::get(1).total;
|
||||
|
||||
|
|
@ -3684,7 +3684,7 @@ fn test_era_end_uses_correct_era_blocks_not_session() {
|
|||
}
|
||||
|
||||
// Award session points
|
||||
ExternalValidatorsRewards::award_session_performance_points(1, validators.clone(), vec![]);
|
||||
end_session(1, validators.clone(), vec![]);
|
||||
|
||||
// Clear session storage (simulating session end)
|
||||
// This should NOT affect era inflation calculation
|
||||
|
|
|
|||
|
|
@ -379,7 +379,6 @@ impl pallet_session::Config for Runtime {
|
|||
type ValidatorIdOf = ConvertInto;
|
||||
type ShouldEndSession = Babe;
|
||||
type NextSessionRotation = Babe;
|
||||
// Wrap the session manager with performance tracking to implement 50/30/20 formula
|
||||
type SessionManager = pallet_external_validators_rewards::SessionPerformanceManager<
|
||||
Runtime,
|
||||
pallet_session::historical::NoteHistoricalRoot<Self, ExternalValidators>,
|
||||
|
|
@ -1507,24 +1506,6 @@ impl datahaven_runtime_common::rewards_adapter::RewardsSubmissionConfig for Main
|
|||
pub type RewardsSendAdapter =
|
||||
datahaven_runtime_common::rewards_adapter::RewardsSubmissionAdapter<MainnetRewardsConfig>;
|
||||
|
||||
/// Wrapper to check if a validator is online in the current session.
|
||||
/// Uses ImOnline's is_online() which considers a validator online if:
|
||||
/// - They sent a heartbeat in the current session, OR
|
||||
/// - They authored at least one block in the current session
|
||||
pub struct ValidatorIsOnline;
|
||||
impl frame_support::traits::Contains<AccountId> for ValidatorIsOnline {
|
||||
fn contains(account: &AccountId) -> bool {
|
||||
let validators = Session::validators();
|
||||
if let Some(index) = validators.iter().position(|v| v == account) {
|
||||
// Check if validator is online (heartbeat OR block authorship)
|
||||
ImOnline::is_online(index as u32)
|
||||
} else {
|
||||
// Not a validator in current session, consider offline
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper to check if a validator has been slashed in a given era
|
||||
pub struct ValidatorSlashChecker;
|
||||
impl pallet_external_validators_rewards::SlashingCheck<AccountId> for ValidatorSlashChecker {
|
||||
|
|
@ -1552,18 +1533,10 @@ impl pallet_external_validators_rewards::Config for Runtime {
|
|||
type RuntimeEvent = RuntimeEvent;
|
||||
type EraIndexProvider = ExternalValidators;
|
||||
type HistoryDepth = ConstU32<64>;
|
||||
|
||||
// NOT USED: DataHaven is a solochain with BABE+GRANDPA consensus, not a parachain.
|
||||
// Backing and dispute points are only relevant for parachain validation.
|
||||
// These are set to 0 to make it explicit they're unused.
|
||||
type BackingPoints = ConstU32<0>;
|
||||
type DisputeStatementPoints = ConstU32<0>;
|
||||
|
||||
type EraInflationProvider = ExternalRewardsEraInflationProvider;
|
||||
type ExternalIndexProvider = ExternalValidators;
|
||||
type GetWhitelistedValidators = GetWhitelistedValidators;
|
||||
type ValidatorSet = Session;
|
||||
type LivenessCheck = ValidatorIsOnline;
|
||||
type SlashingCheck = ValidatorSlashChecker;
|
||||
type BasePointsPerBlock = ConstU32<320>;
|
||||
type BlockAuthoringWeight =
|
||||
|
|
|
|||
|
|
@ -1502,24 +1502,6 @@ impl datahaven_runtime_common::rewards_adapter::RewardsSubmissionConfig for Stag
|
|||
pub type RewardsSendAdapter =
|
||||
datahaven_runtime_common::rewards_adapter::RewardsSubmissionAdapter<StagenetRewardsConfig>;
|
||||
|
||||
/// Wrapper to check if a validator is online in the current session.
|
||||
/// Uses ImOnline's is_online() which considers a validator online if:
|
||||
/// - They sent a heartbeat in the current session, OR
|
||||
/// - They authored at least one block in the current session
|
||||
pub struct ValidatorIsOnline;
|
||||
impl frame_support::traits::Contains<AccountId> for ValidatorIsOnline {
|
||||
fn contains(account: &AccountId) -> bool {
|
||||
let validators = Session::validators();
|
||||
if let Some(index) = validators.iter().position(|v| v == account) {
|
||||
// Check if validator is online (heartbeat OR block authorship)
|
||||
ImOnline::is_online(index as u32)
|
||||
} else {
|
||||
// Not a validator in current session, consider offline
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper to check if a validator has been slashed in a given era
|
||||
pub struct ValidatorSlashChecker;
|
||||
impl pallet_external_validators_rewards::SlashingCheck<AccountId> for ValidatorSlashChecker {
|
||||
|
|
@ -1547,18 +1529,10 @@ impl pallet_external_validators_rewards::Config for Runtime {
|
|||
type RuntimeEvent = RuntimeEvent;
|
||||
type EraIndexProvider = ExternalValidators;
|
||||
type HistoryDepth = ConstU32<64>;
|
||||
|
||||
// NOT USED: DataHaven is a solochain with BABE+GRANDPA consensus, not a parachain.
|
||||
// Backing and dispute points are only relevant for parachain validation.
|
||||
// These are set to 0 to make it explicit they're unused.
|
||||
type BackingPoints = ConstU32<0>;
|
||||
type DisputeStatementPoints = ConstU32<0>;
|
||||
|
||||
type EraInflationProvider = ExternalRewardsEraInflationProvider;
|
||||
type ExternalIndexProvider = ExternalValidators;
|
||||
type GetWhitelistedValidators = GetWhitelistedValidators;
|
||||
type ValidatorSet = Session;
|
||||
type LivenessCheck = ValidatorIsOnline;
|
||||
type SlashingCheck = ValidatorSlashChecker;
|
||||
type BasePointsPerBlock = ConstU32<320>;
|
||||
type BlockAuthoringWeight =
|
||||
|
|
@ -1572,9 +1546,9 @@ impl pallet_external_validators_rewards::Config for Runtime {
|
|||
type Hashing = Keccak256;
|
||||
type Currency = Balances;
|
||||
type RewardsEthereumSovereignAccount = ExternalValidatorRewardsAccount;
|
||||
type WeightInfo = stagenet_weights::pallet_external_validators_rewards::WeightInfo<Runtime>;
|
||||
type SendMessage = RewardsSendAdapter;
|
||||
type HandleInflation = ExternalRewardsInflationHandler;
|
||||
type WeightInfo = stagenet_weights::pallet_external_validators_rewards::WeightInfo<Runtime>;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1506,24 +1506,6 @@ impl datahaven_runtime_common::rewards_adapter::RewardsSubmissionConfig for Test
|
|||
pub type RewardsSendAdapter =
|
||||
datahaven_runtime_common::rewards_adapter::RewardsSubmissionAdapter<TestnetRewardsConfig>;
|
||||
|
||||
/// Wrapper to check if a validator is online in the current session.
|
||||
/// Uses ImOnline's is_online() which considers a validator online if:
|
||||
/// - They sent a heartbeat in the current session, OR
|
||||
/// - They authored at least one block in the current session
|
||||
pub struct ValidatorIsOnline;
|
||||
impl frame_support::traits::Contains<AccountId> for ValidatorIsOnline {
|
||||
fn contains(account: &AccountId) -> bool {
|
||||
let validators = Session::validators();
|
||||
if let Some(index) = validators.iter().position(|v| v == account) {
|
||||
// Check if validator is online (heartbeat OR block authorship)
|
||||
ImOnline::is_online(index as u32)
|
||||
} else {
|
||||
// Not a validator in current session, consider offline
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper to check if a validator has been slashed in a given era
|
||||
pub struct ValidatorSlashChecker;
|
||||
impl pallet_external_validators_rewards::SlashingCheck<AccountId> for ValidatorSlashChecker {
|
||||
|
|
@ -1551,18 +1533,10 @@ impl pallet_external_validators_rewards::Config for Runtime {
|
|||
type RuntimeEvent = RuntimeEvent;
|
||||
type EraIndexProvider = ExternalValidators;
|
||||
type HistoryDepth = ConstU32<64>;
|
||||
|
||||
// NOT USED: DataHaven is a solochain with BABE+GRANDPA consensus, not a parachain.
|
||||
// Backing and dispute points are only relevant for parachain validation.
|
||||
// These are set to 0 to make it explicit they're unused.
|
||||
type BackingPoints = ConstU32<0>;
|
||||
type DisputeStatementPoints = ConstU32<0>;
|
||||
|
||||
type EraInflationProvider = ExternalRewardsEraInflationProvider;
|
||||
type ExternalIndexProvider = ExternalValidators;
|
||||
type GetWhitelistedValidators = GetWhitelistedValidators;
|
||||
type ValidatorSet = Session;
|
||||
type LivenessCheck = ValidatorIsOnline;
|
||||
type SlashingCheck = ValidatorSlashChecker;
|
||||
type BasePointsPerBlock = ConstU32<320>;
|
||||
type BlockAuthoringWeight =
|
||||
|
|
@ -1576,9 +1550,9 @@ impl pallet_external_validators_rewards::Config for Runtime {
|
|||
type Hashing = Keccak256;
|
||||
type Currency = Balances;
|
||||
type RewardsEthereumSovereignAccount = ExternalValidatorRewardsAccount;
|
||||
type WeightInfo = testnet_weights::pallet_external_validators_rewards::WeightInfo<Runtime>;
|
||||
type SendMessage = RewardsSendAdapter;
|
||||
type HandleInflation = ExternalRewardsInflationHandler;
|
||||
type WeightInfo = testnet_weights::pallet_external_validators_rewards::WeightInfo<Runtime>;
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type BenchmarkHelper = ();
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
Loading…
Reference in a new issue