11//! Helpers for fetching and shaping election data shared by CLI commands
22
33use polkadot_sdk:: {
4- frame_election_provider_support:: BoundedSupports ,
4+ frame_election_provider_support:: { BoundedSupports , Get } ,
55 frame_support:: BoundedVec ,
66 pallet_election_provider_multi_block:: {
77 PagedRawSolution ,
@@ -22,8 +22,9 @@ use crate::{
2222 commands:: {
2323 multi_block:: types:: { TargetSnapshotPageOf , Voter , VoterSnapshotPageOf } ,
2424 types:: {
25- ElectionDataSource , NominatorPrediction , NominatorsPrediction , PredictionMetadata ,
26- ValidatorInfo , ValidatorStakeAllocation , ValidatorsPrediction ,
25+ ElectionDataSource , NominatorData , NominatorPrediction , NominatorsPrediction ,
26+ PredictionMetadata , ValidatorData , ValidatorInfo , ValidatorStakeAllocation ,
27+ ValidatorsPrediction ,
2728 } ,
2829 } ,
2930 dynamic:: staking:: { fetch_candidates, fetch_nominators} ,
@@ -46,67 +47,82 @@ pub struct PredictionContext<'a> {
4647 pub data_source : ElectionDataSource ,
4748}
4849
49- /// Merge on-chain nominators with validator self votes to ensure every candidate
50- /// backs itself when generating synthetic election snapshots.
50+ /// Inject validator self-votes to ensure every candidate backs itself when generating
51+ /// synthetic election snapshots.
52+ ///
53+ /// - If an account is a nominator, use their nominator data BUT ensure they vote for themselves
54+ /// - If an account is a validator but NOT a nominator, inject them as voting for themselves
55+ ///
56+ /// We do NOT merge stakes - we respect the nominator data if it exists.
5157pub ( crate ) fn inject_self_votes (
52- candidates : & [ ( AccountId , u128 ) ] ,
53- nominators : Vec < ( AccountId , u64 , Vec < AccountId > ) > ,
54- ) -> Vec < ( AccountId , u64 , Vec < AccountId > ) > {
55- let mut nominator_map: HashMap < AccountId , ( u64 , Vec < AccountId > ) > =
56- HashMap :: with_capacity ( nominators. len ( ) ) ;
57-
58- for ( account, stake, targets) in nominators {
59- nominator_map
60- . entry ( account)
61- . and_modify ( |( existing_stake, existing_targets) | {
62- if stake > * existing_stake {
63- * existing_stake = stake;
64- }
65- existing_targets. extend ( targets. clone ( ) ) ;
66- } )
67- . or_insert ( ( stake, targets) ) ;
68- }
69-
70- let mut combined: Vec < ( AccountId , u64 , Vec < AccountId > ) > =
71- Vec :: with_capacity ( nominator_map. len ( ) + candidates. len ( ) ) ;
72-
73- let mut injected = 0usize ;
74- for ( account, stake) in candidates {
75- let ( stake_u64, truncated) =
76- if * stake > u64:: MAX as u128 { ( u64:: MAX , true ) } else { ( * stake as u64 , false ) } ;
77-
78- if truncated {
79- log:: warn!(
80- target: LOG_TARGET ,
81- "Candidate {:?} stake {} exceeds u64::MAX; truncating to {} for snapshot conversion" ,
82- account,
83- stake,
58+ candidates : & [ ValidatorData ] ,
59+ nominators : Vec < NominatorData > ,
60+ ) -> Vec < NominatorData > {
61+ // Build a map of validator accounts to their stakes for lookup
62+ let validator_stakes: HashMap < AccountId , u64 > = candidates
63+ . iter ( )
64+ . map ( |( account, stake) | {
65+ let stake_u64 = if * stake > u64:: MAX as u128 {
66+ log:: warn!(
67+ target: LOG_TARGET ,
68+ "Validator {:?} stake {} exceeds u64::MAX; truncating to {}" ,
69+ account,
70+ stake,
71+ u64 :: MAX
72+ ) ;
8473 u64:: MAX
85- ) ;
86- }
87-
88- if let Some ( ( existing_stake, mut targets) ) = nominator_map. remove ( account) {
89- if !targets. iter ( ) . any ( |t| t == account) {
74+ } else {
75+ * stake as u64
76+ } ;
77+ ( account. clone ( ) , stake_u64)
78+ } )
79+ . collect ( ) ;
80+
81+ // Build a set of all validator accounts for fast lookup
82+ let validator_accounts: HashSet < AccountId > =
83+ candidates. iter ( ) . map ( |( account, _) | account. clone ( ) ) . collect ( ) ;
84+
85+ let mut combined: Vec < NominatorData > = Vec :: with_capacity ( nominators. len ( ) + candidates. len ( ) ) ;
86+
87+ let mut self_votes_added = 0usize ;
88+
89+ // Process all nominators, ensuring validators vote for themselves
90+ for ( account, stake, mut targets) in nominators {
91+ // If this nominator is also a validator, ensure they vote for themselves
92+ if validator_accounts. contains ( & account) {
93+ // Add self-target if not already present
94+ if !targets. iter ( ) . any ( |t| t == & account) {
9095 targets. push ( account. clone ( ) ) ;
96+ self_votes_added += 1 ;
9197 }
92- let merged_stake = existing_stake. max ( stake_u64) ;
93- combined. push ( ( account. clone ( ) , merged_stake, targets) ) ;
94- } else {
95- combined. push ( ( account. clone ( ) , stake_u64, vec ! [ account. clone( ) ] ) ) ;
96- injected += 1 ;
9798 }
99+ combined. push ( ( account, stake, targets) ) ;
98100 }
99101
100- let remaining = nominator_map. len ( ) ;
101- combined. extend (
102- nominator_map
103- . into_iter ( )
104- . map ( |( account, ( stake, targets) ) | ( account, stake, targets) ) ,
105- ) ;
102+ // Then, for validators that are NOT nominators, inject them as self-voters
103+ let mut injected = 0usize ;
104+ for ( account, _stake) in candidates {
105+ // Skip if this validator is already a nominator (we already processed them above)
106+ if combined. iter ( ) . any ( |( acc, _, _) | acc == account) {
107+ continue ;
108+ }
109+
110+ // Get the validator stake (already converted to u64 in validator_stakes map)
111+ let stake_u64 = validator_stakes
112+ . get ( account)
113+ . copied ( )
114+ . expect ( "Validator stake should exist in map" ) ;
106115
116+ // Add validator as a self-voter (voting only for themselves)
117+ combined. push ( ( account. clone ( ) , stake_u64, vec ! [ account. clone( ) ] ) ) ;
118+ injected += 1 ;
119+ }
107120 log:: info!(
108121 target: LOG_TARGET ,
109- "Prepared nominators for snapshot: {injected} injected self votes, {remaining} existing nominators appended"
122+ "Ensured {} validator self-votes in nominators, injected {} new validator self-voters (total: {} voters)" ,
123+ self_votes_added,
124+ injected,
125+ combined. len( )
110126 ) ;
111127
112128 combined
@@ -118,8 +134,8 @@ pub(crate) fn inject_self_votes(
118134/// dropped). The miner will use all pages when solving; the solution is then trimmed per the
119135/// chain's limits.
120136pub ( crate ) fn convert_staking_data_to_snapshots < T > (
121- candidates : Vec < ( AccountId , u128 ) > ,
122- nominators : Vec < ( AccountId , u64 , Vec < AccountId > ) > ,
137+ candidates : Vec < ValidatorData > ,
138+ nominators : Vec < NominatorData > ,
123139) -> Result < ( TargetSnapshotPageOf < T > , Vec < VoterSnapshotPageOf < T > > ) , Error >
124140where
125141 T : MinerConfig < AccountId = AccountId > ,
@@ -263,6 +279,24 @@ where
263279 let active_set: HashSet < AccountId > =
264280 winners_sorted. iter ( ) . map ( |( validator, _) | validator. clone ( ) ) . collect ( ) ;
265281
282+ // Flatten voters from paged snapshot for nominator perspective.
283+ let all_voters: Vec < Voter < T > > =
284+ voter_snapshot_paged. iter ( ) . flat_map ( |page| page. iter ( ) . cloned ( ) ) . collect ( ) ;
285+
286+ // Identify validators who only have self-votes
287+ let validators_with_only_self_vote: HashSet < AccountId > = all_voters
288+ . iter ( )
289+ . filter ( |( nominator, _, targets) | {
290+ // validator has only self-vote if:
291+ // 1. They are a validator (in active_set)
292+ // 2. Their only target is themselves
293+ // NOTE: Reverted to your original logic as requested, assuming you want strictly this
294+ // behavior.
295+ active_set. contains ( nominator) || ( targets. len ( ) == 1 && targets[ 0 ] == * nominator)
296+ } )
297+ . map ( |( nominator, _, _) | nominator. clone ( ) )
298+ . collect ( ) ;
299+
266300 let mut validator_infos: Vec < ValidatorInfo > = Vec :: new ( ) ;
267301 for ( validator, support) in winners_sorted. iter ( ) {
268302 let self_stake = support
@@ -302,13 +336,15 @@ where
302336
303337 let validators_prediction = ValidatorsPrediction { metadata, results : validator_infos } ;
304338
305- // Flatten voters from paged snapshot for nominator perspective.
306- let all_voters: Vec < Voter < T > > =
307- voter_snapshot_paged. iter ( ) . flat_map ( |page| page. iter ( ) . cloned ( ) ) . collect ( ) ;
308-
339+ // Build nominator predictions, excluding validators who only have self-votes
309340 let mut nominator_predictions: Vec < NominatorPrediction > = Vec :: new ( ) ;
310341
311342 for ( nominator, stake, nominated_targets) in all_voters {
343+ // Skip validators who only have self-votes
344+ if validators_with_only_self_vote. contains ( & nominator) {
345+ continue ;
346+ }
347+
312348 let nominator_encoded = encode_account_id ( & nominator, ctx. ss58_prefix ) ;
313349 let allocations = allocation_map. get ( & nominator) ;
314350
@@ -383,7 +419,9 @@ where
383419 . await
384420 . map_err ( |e| Error :: Other ( format ! ( "Failed to fetch candidates: {e}" ) ) ) ?;
385421
386- let nominators = fetch_nominators ( client)
422+ let voter_limit = ( T :: Pages :: get ( ) * T :: VoterSnapshotPerBlock :: get ( ) ) as usize ;
423+
424+ let nominators = fetch_nominators ( client, voter_limit)
387425 . await
388426 . map_err ( |e| Error :: Other ( format ! ( "Failed to fetch nominators: {e}" ) ) ) ?;
389427
0 commit comments