From 99bd43f10e61c047432d9811b1169b73773d118b Mon Sep 17 00:00:00 2001 From: paulo Date: Mon, 6 Jan 2025 14:10:09 +0000 Subject: [PATCH 01/12] record bitfields availability from para_inherent.enter extrinsic --- src/onet.rs | 10 ++++--- src/records.rs | 65 +++++++++++++++++++++++++++++++++++++++---- src/runtimes/mod.rs | 4 +-- src/runtimes/paseo.rs | 33 ++++++++++++++++++++-- 4 files changed, 98 insertions(+), 14 deletions(-) diff --git a/src/onet.rs b/src/onet.rs index e016457..ac05d3b 100644 --- a/src/onet.rs +++ b/src/onet.rs @@ -26,7 +26,9 @@ use crate::matrix::{Matrix, UserID, MATRIX_SUBSCRIBERS_FILENAME}; use crate::records::EpochIndex; use crate::report::Network; use crate::runtimes::{ - kusama, paseo, polkadot, + // kusama, + paseo, + // polkadot, support::{ChainPrefix, ChainTokenSymbol, SupportedRuntime}, }; use log::{error, info, warn}; @@ -381,10 +383,10 @@ impl Onet { self.cache_network().await?; match self.runtime { - SupportedRuntime::Polkadot => polkadot::init_and_subscribe_on_chain_events(self).await, - SupportedRuntime::Kusama => kusama::init_and_subscribe_on_chain_events(self).await, + // SupportedRuntime::Polkadot => polkadot::init_and_subscribe_on_chain_events(self).await, + // SupportedRuntime::Kusama => kusama::init_and_subscribe_on_chain_events(self).await, SupportedRuntime::Paseo => paseo::init_and_subscribe_on_chain_events(self).await, - // _ => todo!(), + _ => todo!(), } } // cache methods diff --git a/src/records.rs b/src/records.rs index 41a6517..8fd28fd 100644 --- a/src/records.rs +++ b/src/records.rs @@ -1271,9 +1271,7 @@ impl ParaRecord { pub fn is_para_id_assigned(&self, id: ParaId) -> bool { if let Some(para_id) = self.para_id { - if para_id == id { - return true; - } + return para_id == id; } false } @@ -1384,6 +1382,28 @@ impl ParaRecord { stats.missed_votes += 1; } + pub fn inc_available_bitfields(&mut self) { + if let Some(para_id) = self.para_id { + // increment current available_bitfields + let stats = self + .para_stats + .entry(para_id) + .or_insert(ParaStats::default()); + stats.available_bitfields += 1; + } + } + + pub fn inc_unavailable_bitfields(&mut self) { + if let Some(para_id) = self.para_id { + // increment current unavailable_bitfields + let stats = self + .para_stats + .entry(para_id) + .or_insert(ParaStats::default()); + stats.unavailable_bitfields += 1; + } + } + pub fn total_points(&self) -> Points { self.para_stats.iter().map(|(_, stats)| stats.points).sum() } @@ -1447,6 +1467,20 @@ impl ParaRecord { self.disputes.len().try_into().unwrap() } + pub fn total_available_bitfields(&self) -> Votes { + self.para_stats + .iter() + .map(|(_, stats)| stats.available_bitfields) + .sum() + } + + pub fn total_unavailable_bitfields(&self) -> Votes { + self.para_stats + .iter() + .map(|(_, stats)| stats.unavailable_bitfields) + .sum() + } + pub fn get_para_id_stats(&self, para_id: ParaId) -> Option<&ParaStats> { self.para_stats.get(¶_id) } @@ -1482,6 +1516,10 @@ pub struct ParaStats { pub implicit_votes: u32, #[serde(rename = "mv")] pub missed_votes: u32, + #[serde(rename = "abf")] + pub available_bitfields: u32, + #[serde(rename = "ubf")] + pub unavailable_bitfields: u32, } impl ParaStats { @@ -1520,6 +1558,14 @@ impl ParaStats { pub fn votes_points(&self) -> Points { self.total_votes() * 20 } + + pub fn available_bitfields(&self) -> u32 { + self.available_bitfields + } + + pub fn unavailable_bitfields(&self) -> u32 { + self.unavailable_bitfields + } } impl Validity for ParaStats { @@ -1529,6 +1575,8 @@ impl Validity for ParaStats { && self.authored_blocks() == 0 && self.core_assignments() == 0 && self.points() == 0 + && self.available_bitfields() == 0 + && self.unavailable_bitfields() == 0 } } @@ -1990,10 +2038,15 @@ mod tests { } let dr: DiscoveryRecord = DiscoveryRecord::with_authority_discovery_key([0; 32]); - assert_eq!(dr.authority_discovery_key(), "0000000000000000000000000000000000000000000000000000000000000000"); + assert_eq!( + dr.authority_discovery_key(), + "0000000000000000000000000000000000000000000000000000000000000000" + ); records.set_discovery_record(authority_idx, dr); - assert_eq!(records.get_authority_record(authority_idx, None).is_some(), true); - + assert_eq!( + records.get_authority_record(authority_idx, None).is_some(), + true + ); } } diff --git a/src/runtimes/mod.rs b/src/runtimes/mod.rs index d93f949..8aca3bf 100644 --- a/src/runtimes/mod.rs +++ b/src/runtimes/mod.rs @@ -21,7 +21,7 @@ #![allow(clippy::all)] -pub mod kusama; +// pub mod kusama; pub mod paseo; -pub mod polkadot; +// pub mod polkadot; pub mod support; diff --git a/src/runtimes/paseo.rs b/src/runtimes/paseo.rs index ed00145..2bffb57 100644 --- a/src/runtimes/paseo.rs +++ b/src/runtimes/paseo.rs @@ -78,9 +78,12 @@ use subxt_signer::sr25519::Keypair; mod node_runtime {} use node_runtime::{ + // Event, + para_inherent::calls::types::Enter, runtime_types::{ bounded_collections::bounded_vec::BoundedVec, pallet_nomination_pools::PoolState, - polkadot_parachain_primitives::primitives::Id, polkadot_primitives::v7::DisputeStatement, + polkadot_parachain_primitives::primitives::Id, + polkadot_primitives::v7::AvailabilityBitfield, polkadot_primitives::v7::DisputeStatement, polkadot_primitives::v7::ValidatorIndex, polkadot_primitives::v7::ValidityAttestation, polkadot_runtime_parachains::scheduler::common::Assignment, polkadot_runtime_parachains::scheduler::pallet::CoreOccupied, @@ -88,7 +91,6 @@ use node_runtime::{ sp_consensus_babe::digests::PreDigest, }, session::events::NewSession, - // Event, system::events::ExtrinsicFailed, }; @@ -340,6 +342,7 @@ pub async fn process_finalized_block( // Assign block_metadata to the api api.set_metadata(block_metadata); + // Fetch events let events = api.events().at(block_hash).await?; if let Some(new_session_event) = events.find_first::()? { @@ -471,6 +474,8 @@ pub async fn cache_track_records(onet: &Onet, records: &Records) -> Result<(), O explicit_votes: para_record.total_explicit_votes(), implicit_votes: para_record.total_implicit_votes(), missed_votes: para_record.total_missed_votes(), + available_bitfields: para_record.total_available_bitfields(), + unavailable_bitfields: para_record.total_unavailable_bitfields(), }; let serialized = serde_json::to_string(&summary)?; redis::pipe() @@ -1495,6 +1500,30 @@ pub async fn track_records( } } } + + // *********************************************************************** + // Track Availability data from bitfields + // *********************************************************************** + let extrinsics = api.blocks().at(block_hash).await?.extrinsics().await?; + for res in extrinsics.find::() { + let extrinsic = res?; + for availability_bitfield in extrinsic.value.data.bitfields.iter() { + let ValidatorIndex(auth_idx) = + &availability_bitfield.validator_index; + // Get para_record for the same on chain votes session + if let Some(para_record) = records + .get_mut_para_record(*auth_idx, Some(backing_votes.session)) + { + let AvailabilityBitfield(decoded_bits) = + &availability_bitfield.payload; + if decoded_bits.as_bits().iter().any(|x| x) { + para_record.inc_available_bitfields(); + } else { + para_record.inc_unavailable_bitfields(); + } + } + } + } } else { warn!("None on chain voted recorded."); } From 0bee259f2b80b8d7958737ec850160a79b4c4d23 Mon Sep 17 00:00:00 2001 From: paulo Date: Mon, 6 Jan 2025 18:07:18 +0000 Subject: [PATCH 02/12] change and set bitfields record --- src/records.rs | 121 ++++++++++++++++++++++++++---------------- src/runtimes/paseo.rs | 9 ++-- 2 files changed, 80 insertions(+), 50 deletions(-) diff --git a/src/records.rs b/src/records.rs index 8fd28fd..e3fa62d 100644 --- a/src/records.rs +++ b/src/records.rs @@ -1220,6 +1220,51 @@ impl Validity for DiscoveryRecord { } } +#[derive(Serialize, Deserialize, Debug, Clone, Default)] +pub struct BitfieldsRecord { + availability: u32, + unavailability: u32, + #[serde(skip_serializing_if = "Vec::is_empty")] + unavailable_at: Vec, +} + +impl BitfieldsRecord { + pub fn new() -> Self { + Self { + availability: 0, + unavailability: 0, + unavailable_at: Vec::new(), + } + } + + pub fn availability(&self) -> u32 { + self.availability + } + + pub fn unavailability(&self) -> u32 { + self.unavailability + } + + pub fn unavailable(&self) -> Vec { + self.unavailable_at.to_vec() + } + + pub fn inc_availability(&mut self) { + self.availability += 1; + } + + pub fn push_unavailable_at(&mut self, block_number: BlockNumber) { + self.unavailability += 1; + self.unavailable_at.push(block_number); + } +} + +impl Validity for BitfieldsRecord { + fn is_empty(&self) -> bool { + self.availability() == 0 && self.unavailability() == 0 && self.unavailable_at.is_empty() + } +} + #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct ParaRecord { // index is the position of the authority in paras_shared().active_validator_indices(None) @@ -1234,6 +1279,7 @@ pub struct ParaRecord { disputes: Vec<(BlockNumber, DisputeKind)>, #[serde(skip)] para_stats: BTreeMap, + bitfields: Option, } impl ParaRecord { @@ -1249,6 +1295,7 @@ impl ParaRecord { para_id: None, peers, para_stats: BTreeMap::new(), + bitfields: Some(BitfieldsRecord::default()), ..Default::default() } } @@ -1288,6 +1335,17 @@ impl ParaRecord { self.disputes.push((block_number, msg)); } + pub fn inc_availability(&mut self) { + self.bitfields.as_mut().unwrap().inc_availability(); + } + + pub fn push_unavailable_at(&mut self, block_number: BlockNumber) { + self.bitfields + .as_mut() + .unwrap() + .push_unavailable_at(block_number); + } + pub fn update_scheduled_core(&mut self, para_id: ParaId, core: CoreIndex) { // Assign current scheduled para_id self.para_id = Some(para_id); @@ -1382,28 +1440,6 @@ impl ParaRecord { stats.missed_votes += 1; } - pub fn inc_available_bitfields(&mut self) { - if let Some(para_id) = self.para_id { - // increment current available_bitfields - let stats = self - .para_stats - .entry(para_id) - .or_insert(ParaStats::default()); - stats.available_bitfields += 1; - } - } - - pub fn inc_unavailable_bitfields(&mut self) { - if let Some(para_id) = self.para_id { - // increment current unavailable_bitfields - let stats = self - .para_stats - .entry(para_id) - .or_insert(ParaStats::default()); - stats.unavailable_bitfields += 1; - } - } - pub fn total_points(&self) -> Points { self.para_stats.iter().map(|(_, stats)| stats.points).sum() } @@ -1467,18 +1503,20 @@ impl ParaRecord { self.disputes.len().try_into().unwrap() } - pub fn total_available_bitfields(&self) -> Votes { - self.para_stats - .iter() - .map(|(_, stats)| stats.available_bitfields) - .sum() + pub fn total_availability(&self) -> u32 { + if let Some(bitfields) = &self.bitfields { + bitfields.availability() + } else { + 0 + } } - pub fn total_unavailable_bitfields(&self) -> Votes { - self.para_stats - .iter() - .map(|(_, stats)| stats.unavailable_bitfields) - .sum() + pub fn total_unavailability(&self) -> u32 { + if let Some(bitfields) = &self.bitfields { + bitfields.unavailability() + } else { + 0 + } } pub fn get_para_id_stats(&self, para_id: ParaId) -> Option<&ParaStats> { @@ -1498,6 +1536,7 @@ impl ParaRecord { peers: self.peers.clone(), disputes: self.disputes.clone(), para_stats: BTreeMap::new(), + bitfields: self.bitfields.clone(), } } } @@ -1516,10 +1555,6 @@ pub struct ParaStats { pub implicit_votes: u32, #[serde(rename = "mv")] pub missed_votes: u32, - #[serde(rename = "abf")] - pub available_bitfields: u32, - #[serde(rename = "ubf")] - pub unavailable_bitfields: u32, } impl ParaStats { @@ -1558,14 +1593,6 @@ impl ParaStats { pub fn votes_points(&self) -> Points { self.total_votes() * 20 } - - pub fn available_bitfields(&self) -> u32 { - self.available_bitfields - } - - pub fn unavailable_bitfields(&self) -> u32 { - self.unavailable_bitfields - } } impl Validity for ParaStats { @@ -1575,8 +1602,6 @@ impl Validity for ParaStats { && self.authored_blocks() == 0 && self.core_assignments() == 0 && self.points() == 0 - && self.available_bitfields() == 0 - && self.unavailable_bitfields() == 0 } } @@ -1822,6 +1847,10 @@ pub struct SessionStats { pub missed_votes: u32, #[serde(rename = "di")] pub disputes: u32, + #[serde(rename = "da")] + pub data_availability: u32, + #[serde(rename = "du")] + pub data_unavailability: u32, } impl Validity for SessionStats { diff --git a/src/runtimes/paseo.rs b/src/runtimes/paseo.rs index 2bffb57..fc20b9a 100644 --- a/src/runtimes/paseo.rs +++ b/src/runtimes/paseo.rs @@ -440,6 +440,9 @@ pub async fn cache_track_records(onet: &Onet, records: &Records) -> Result<(), O session_stats.implicit_votes += para_record.total_implicit_votes(); session_stats.missed_votes += para_record.total_missed_votes(); session_stats.disputes += para_record.total_disputes(); + // data availability + session_stats.data_availability += para_record.total_availability(); + session_stats.data_unavailability += para_record.total_unavailability(); // let serialized = serde_json::to_string(¶_record)?; @@ -474,8 +477,6 @@ pub async fn cache_track_records(onet: &Onet, records: &Records) -> Result<(), O explicit_votes: para_record.total_explicit_votes(), implicit_votes: para_record.total_implicit_votes(), missed_votes: para_record.total_missed_votes(), - available_bitfields: para_record.total_available_bitfields(), - unavailable_bitfields: para_record.total_unavailable_bitfields(), }; let serialized = serde_json::to_string(&summary)?; redis::pipe() @@ -1517,9 +1518,9 @@ pub async fn track_records( let AvailabilityBitfield(decoded_bits) = &availability_bitfield.payload; if decoded_bits.as_bits().iter().any(|x| x) { - para_record.inc_available_bitfields(); + para_record.inc_availability(); } else { - para_record.inc_unavailable_bitfields(); + para_record.push_unavailable_at(block_number); } } } From 35ed8becf75c2c80b8e262b24f66aaac9f36624e Mon Sep 17 00:00:00 2001 From: paulo Date: Mon, 6 Jan 2025 23:10:01 +0000 Subject: [PATCH 03/12] fix validator_index and keep track if authorities present in para_inherent.data.bitfields --- src/runtimes/paseo.rs | 44 +++++++++++++++++++++++++++++++++---------- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/src/runtimes/paseo.rs b/src/runtimes/paseo.rs index fc20b9a..d414330 100644 --- a/src/runtimes/paseo.rs +++ b/src/runtimes/paseo.rs @@ -1505,26 +1505,50 @@ pub async fn track_records( // *********************************************************************** // Track Availability data from bitfields // *********************************************************************** + // NOTE: authorities_present vec will contain the authorities present in para_inherent.data.bitfields and it's useful + // to increase unavailability to the authorities not present + let mut authorities_present = Vec::new(); let extrinsics = api.blocks().at(block_hash).await?.extrinsics().await?; for res in extrinsics.find::() { let extrinsic = res?; for availability_bitfield in extrinsic.value.data.bitfields.iter() { - let ValidatorIndex(auth_idx) = + // Note: availability_bitfield.validator_index is the index of the validator in the paras_shared.active_validator_indices + let ValidatorIndex(para_idx) = &availability_bitfield.validator_index; - // Get para_record for the same on chain votes session - if let Some(para_record) = records - .get_mut_para_record(*auth_idx, Some(backing_votes.session)) + + if let Some(ValidatorIndex(auth_idx)) = + active_validator_indices.get(*para_idx as usize) { - let AvailabilityBitfield(decoded_bits) = - &availability_bitfield.payload; - if decoded_bits.as_bits().iter().any(|x| x) { - para_record.inc_availability(); - } else { - para_record.push_unavailable_at(block_number); + // Get para_record for the same on chain votes session + if let Some(para_record) = records + .get_mut_para_record(*auth_idx, Some(backing_votes.session)) + { + let AvailabilityBitfield(decoded_bits) = + &availability_bitfield.payload; + if decoded_bits.as_bits().iter().any(|x| x) { + para_record.inc_availability(); + } else { + para_record.push_unavailable_at(block_number); + } } + // Keep track of the authorities that show up in para_inherent.data.bitfields + authorities_present.push(*auth_idx); } } } + // Also increase unavailability to the authorities that do not show up in para_inherent.data.bitfields + if active_validator_indices.len() != authorities_present.len() { + for ValidatorIndex(auth_idx) in active_validator_indices.iter() { + if !authorities_present.contains(auth_idx) { + if let Some(para_record) = records.get_mut_para_record( + *auth_idx, + Some(backing_votes.session), + ) { + para_record.push_unavailable_at(block_number); + } + } + } + } } else { warn!("None on chain voted recorded."); } From 4fd025c28f9cce7f518101bf3ba3e19a358c153e Mon Sep 17 00:00:00 2001 From: paulo Date: Tue, 7 Jan 2025 13:15:18 +0000 Subject: [PATCH 04/12] support bitfields on kusama and polkadot --- src/onet.rs | 10 +++---- src/runtimes/kusama.rs | 59 ++++++++++++++++++++++++++++++++++++++-- src/runtimes/mod.rs | 4 +-- src/runtimes/paseo.rs | 11 ++++---- src/runtimes/polkadot.rs | 58 +++++++++++++++++++++++++++++++++++++-- 5 files changed, 122 insertions(+), 20 deletions(-) diff --git a/src/onet.rs b/src/onet.rs index ac05d3b..e016457 100644 --- a/src/onet.rs +++ b/src/onet.rs @@ -26,9 +26,7 @@ use crate::matrix::{Matrix, UserID, MATRIX_SUBSCRIBERS_FILENAME}; use crate::records::EpochIndex; use crate::report::Network; use crate::runtimes::{ - // kusama, - paseo, - // polkadot, + kusama, paseo, polkadot, support::{ChainPrefix, ChainTokenSymbol, SupportedRuntime}, }; use log::{error, info, warn}; @@ -383,10 +381,10 @@ impl Onet { self.cache_network().await?; match self.runtime { - // SupportedRuntime::Polkadot => polkadot::init_and_subscribe_on_chain_events(self).await, - // SupportedRuntime::Kusama => kusama::init_and_subscribe_on_chain_events(self).await, + SupportedRuntime::Polkadot => polkadot::init_and_subscribe_on_chain_events(self).await, + SupportedRuntime::Kusama => kusama::init_and_subscribe_on_chain_events(self).await, SupportedRuntime::Paseo => paseo::init_and_subscribe_on_chain_events(self).await, - _ => todo!(), + // _ => todo!(), } } // cache methods diff --git a/src/runtimes/kusama.rs b/src/runtimes/kusama.rs index 648d126..97df272 100644 --- a/src/runtimes/kusama.rs +++ b/src/runtimes/kusama.rs @@ -70,7 +70,6 @@ use subxt::{ tx::TxStatus, utils::{AccountId32, H256}, }; - use subxt_signer::sr25519::Keypair; #[subxt::subxt( @@ -80,9 +79,12 @@ use subxt_signer::sr25519::Keypair; mod node_runtime {} use node_runtime::{ + // Event, + para_inherent::calls::types::Enter, runtime_types::{ bounded_collections::bounded_vec::BoundedVec, pallet_nomination_pools::PoolState, - polkadot_parachain_primitives::primitives::Id, polkadot_primitives::v7::DisputeStatement, + polkadot_parachain_primitives::primitives::Id, + polkadot_primitives::v7::AvailabilityBitfield, polkadot_primitives::v7::DisputeStatement, polkadot_primitives::v7::ValidatorIndex, polkadot_primitives::v7::ValidityAttestation, polkadot_runtime_parachains::scheduler::common::Assignment, polkadot_runtime_parachains::scheduler::pallet::CoreOccupied, @@ -90,7 +92,6 @@ use node_runtime::{ sp_consensus_babe::digests::PreDigest, }, session::events::NewSession, - // Event, system::events::ExtrinsicFailed, }; @@ -263,6 +264,7 @@ pub async fn init_and_subscribe_on_chain_events(onet: &Onet) -> Result<(), OnetE .await?; } }; + // latest_block_number_processed = Some(block_number); } @@ -341,6 +343,7 @@ pub async fn process_finalized_block( // Assign block_metadata to the api api.set_metadata(block_metadata); + // Fetch events let events = api.events().at(block_hash).await?; if let Some(new_session_event) = events.find_first::()? { @@ -438,6 +441,9 @@ pub async fn cache_track_records(onet: &Onet, records: &Records) -> Result<(), O session_stats.implicit_votes += para_record.total_implicit_votes(); session_stats.missed_votes += para_record.total_missed_votes(); session_stats.disputes += para_record.total_disputes(); + // data availability + session_stats.data_availability += para_record.total_availability(); + session_stats.data_unavailability += para_record.total_unavailability(); // let serialized = serde_json::to_string(¶_record)?; @@ -1496,6 +1502,53 @@ pub async fn track_records( } } } + + // *********************************************************************** + // Track Availability data from bitfields + // *********************************************************************** + // NOTE: authorities_present vec will contain the authorities present in para_inherent.data.bitfields and it's useful + // to increase unavailability to the authorities not present + let mut authorities_present = Vec::new(); + let extrinsics = api.blocks().at(block_hash).await?.extrinsics().await?; + for res in extrinsics.find::() { + let extrinsic = res?; + for availability_bitfield in extrinsic.value.data.bitfields.iter() { + // Note: availability_bitfield.validator_index is the index of the validator in the paras_shared.active_validator_indices + let ValidatorIndex(para_idx) = + &availability_bitfield.validator_index; + + if let Some(ValidatorIndex(auth_idx)) = + active_validator_indices.get(*para_idx as usize) + { + // Get para_record for the same on chain votes session + if let Some(para_record) = records + .get_mut_para_record(*auth_idx, Some(backing_votes.session)) + { + let AvailabilityBitfield(decoded_bits) = + &availability_bitfield.payload; + if decoded_bits.as_bits().iter().any(|x| x) { + para_record.inc_availability(); + } else { + para_record.push_unavailable_at(block_number); + } + } + // Keep track of the authorities that show up in para_inherent.data.bitfields + authorities_present.push(*auth_idx); + } + } + } + // Also increase unavailability to the authorities that do not show up in para_inherent.data.bitfields + if active_validator_indices.len() != authorities_present.len() { + for ValidatorIndex(auth_idx) in active_validator_indices.iter() { + if !authorities_present.contains(auth_idx) { + if let Some(para_record) = records + .get_mut_para_record(*auth_idx, Some(backing_votes.session)) + { + para_record.push_unavailable_at(block_number); + } + } + } + } } else { warn!("None on chain voted recorded."); } diff --git a/src/runtimes/mod.rs b/src/runtimes/mod.rs index 8aca3bf..d93f949 100644 --- a/src/runtimes/mod.rs +++ b/src/runtimes/mod.rs @@ -21,7 +21,7 @@ #![allow(clippy::all)] -// pub mod kusama; +pub mod kusama; pub mod paseo; -// pub mod polkadot; +pub mod polkadot; pub mod support; diff --git a/src/runtimes/paseo.rs b/src/runtimes/paseo.rs index d414330..9a6bc93 100644 --- a/src/runtimes/paseo.rs +++ b/src/runtimes/paseo.rs @@ -1505,7 +1505,7 @@ pub async fn track_records( // *********************************************************************** // Track Availability data from bitfields // *********************************************************************** - // NOTE: authorities_present vec will contain the authorities present in para_inherent.data.bitfields and it's useful + // NOTE: authorities_present vec will contain the authorities present in para_inherent.data.bitfields and it's useful // to increase unavailability to the authorities not present let mut authorities_present = Vec::new(); let extrinsics = api.blocks().at(block_hash).await?.extrinsics().await?; @@ -1540,14 +1540,13 @@ pub async fn track_records( if active_validator_indices.len() != authorities_present.len() { for ValidatorIndex(auth_idx) in active_validator_indices.iter() { if !authorities_present.contains(auth_idx) { - if let Some(para_record) = records.get_mut_para_record( - *auth_idx, - Some(backing_votes.session), - ) { + if let Some(para_record) = records + .get_mut_para_record(*auth_idx, Some(backing_votes.session)) + { para_record.push_unavailable_at(block_number); } } - } + } } } else { warn!("None on chain voted recorded."); diff --git a/src/runtimes/polkadot.rs b/src/runtimes/polkadot.rs index bd4962c..1a85d15 100644 --- a/src/runtimes/polkadot.rs +++ b/src/runtimes/polkadot.rs @@ -70,7 +70,6 @@ use subxt::{ tx::TxStatus, utils::{AccountId32, H256}, }; - use subxt_signer::sr25519::Keypair; #[subxt::subxt( @@ -80,9 +79,12 @@ use subxt_signer::sr25519::Keypair; mod node_runtime {} use node_runtime::{ + // Event, + para_inherent::calls::types::Enter, runtime_types::{ bounded_collections::bounded_vec::BoundedVec, pallet_nomination_pools::PoolState, - polkadot_parachain_primitives::primitives::Id, polkadot_primitives::v7::DisputeStatement, + polkadot_parachain_primitives::primitives::Id, + polkadot_primitives::v7::AvailabilityBitfield, polkadot_primitives::v7::DisputeStatement, polkadot_primitives::v7::ValidatorIndex, polkadot_primitives::v7::ValidityAttestation, polkadot_runtime_parachains::scheduler::common::Assignment, polkadot_runtime_parachains::scheduler::pallet::CoreOccupied, @@ -90,7 +92,6 @@ use node_runtime::{ sp_consensus_babe::digests::PreDigest, }, session::events::NewSession, - // Event, system::events::ExtrinsicFailed, }; @@ -342,6 +343,7 @@ pub async fn process_finalized_block( // Assign block_metadata to the api api.set_metadata(block_metadata); + // Fetch events let events = api.events().at(block_hash).await?; if let Some(new_session_event) = events.find_first::()? { @@ -439,6 +441,9 @@ pub async fn cache_track_records(onet: &Onet, records: &Records) -> Result<(), O session_stats.implicit_votes += para_record.total_implicit_votes(); session_stats.missed_votes += para_record.total_missed_votes(); session_stats.disputes += para_record.total_disputes(); + // data availability + session_stats.data_availability += para_record.total_availability(); + session_stats.data_unavailability += para_record.total_unavailability(); // let serialized = serde_json::to_string(¶_record)?; @@ -1497,6 +1502,53 @@ pub async fn track_records( } } } + + // *********************************************************************** + // Track Availability data from bitfields + // *********************************************************************** + // NOTE: authorities_present vec will contain the authorities present in para_inherent.data.bitfields and it's useful + // to increase unavailability to the authorities not present + let mut authorities_present = Vec::new(); + let extrinsics = api.blocks().at(block_hash).await?.extrinsics().await?; + for res in extrinsics.find::() { + let extrinsic = res?; + for availability_bitfield in extrinsic.value.data.bitfields.iter() { + // Note: availability_bitfield.validator_index is the index of the validator in the paras_shared.active_validator_indices + let ValidatorIndex(para_idx) = + &availability_bitfield.validator_index; + + if let Some(ValidatorIndex(auth_idx)) = + active_validator_indices.get(*para_idx as usize) + { + // Get para_record for the same on chain votes session + if let Some(para_record) = records + .get_mut_para_record(*auth_idx, Some(backing_votes.session)) + { + let AvailabilityBitfield(decoded_bits) = + &availability_bitfield.payload; + if decoded_bits.as_bits().iter().any(|x| x) { + para_record.inc_availability(); + } else { + para_record.push_unavailable_at(block_number); + } + } + // Keep track of the authorities that show up in para_inherent.data.bitfields + authorities_present.push(*auth_idx); + } + } + } + // Also increase unavailability to the authorities that do not show up in para_inherent.data.bitfields + if active_validator_indices.len() != authorities_present.len() { + for ValidatorIndex(auth_idx) in active_validator_indices.iter() { + if !authorities_present.contains(auth_idx) { + if let Some(para_record) = records + .get_mut_para_record(*auth_idx, Some(backing_votes.session)) + { + para_record.push_unavailable_at(block_number); + } + } + } + } } else { warn!("None on chain voted recorded."); } From 27bb90d1790696d0ddd1b653853afcec336514fb Mon Sep 17 00:00:00 2001 From: paulo Date: Tue, 7 Jan 2025 23:29:59 +0000 Subject: [PATCH 05/12] serde rename bitfields record attr --- src/records.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/records.rs b/src/records.rs index e3fa62d..b01e4ef 100644 --- a/src/records.rs +++ b/src/records.rs @@ -1222,8 +1222,11 @@ impl Validity for DiscoveryRecord { #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct BitfieldsRecord { + #[serde(rename = "a")] availability: u32, + #[serde(rename = "u")] unavailability: u32, + #[serde(rename = "uat")] #[serde(skip_serializing_if = "Vec::is_empty")] unavailable_at: Vec, } From 6f3f11775e35a199fa12561a4bb188104c668509 Mon Sep 17 00:00:00 2001 From: paulo Date: Tue, 7 Jan 2025 23:38:35 +0000 Subject: [PATCH 06/12] fix serde rename --- src/records.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/records.rs b/src/records.rs index b01e4ef..e482a20 100644 --- a/src/records.rs +++ b/src/records.rs @@ -1222,9 +1222,9 @@ impl Validity for DiscoveryRecord { #[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct BitfieldsRecord { - #[serde(rename = "a")] + #[serde(rename = "ba")] availability: u32, - #[serde(rename = "u")] + #[serde(rename = "bu")] unavailability: u32, #[serde(rename = "uat")] #[serde(skip_serializing_if = "Vec::is_empty")] From 87a2bff36ac5d2a78ccd373811aa9cac569981c9 Mon Sep 17 00:00:00 2001 From: paulo Date: Wed, 8 Jan 2025 13:23:54 +0000 Subject: [PATCH 07/12] rename session stats bitfields attr --- src/records.rs | 8 ++++---- src/runtimes/kusama.rs | 8 +++++--- src/runtimes/paseo.rs | 8 +++++--- src/runtimes/polkadot.rs | 8 +++++--- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/src/records.rs b/src/records.rs index e482a20..6f25ef3 100644 --- a/src/records.rs +++ b/src/records.rs @@ -1850,10 +1850,10 @@ pub struct SessionStats { pub missed_votes: u32, #[serde(rename = "di")] pub disputes: u32, - #[serde(rename = "da")] - pub data_availability: u32, - #[serde(rename = "du")] - pub data_unavailability: u32, + #[serde(rename = "ba")] + pub bitfields_availability: u32, + #[serde(rename = "bu")] + pub bitfields_unavailability: u32, } impl Validity for SessionStats { diff --git a/src/runtimes/kusama.rs b/src/runtimes/kusama.rs index 97df272..e618898 100644 --- a/src/runtimes/kusama.rs +++ b/src/runtimes/kusama.rs @@ -441,9 +441,11 @@ pub async fn cache_track_records(onet: &Onet, records: &Records) -> Result<(), O session_stats.implicit_votes += para_record.total_implicit_votes(); session_stats.missed_votes += para_record.total_missed_votes(); session_stats.disputes += para_record.total_disputes(); - // data availability - session_stats.data_availability += para_record.total_availability(); - session_stats.data_unavailability += para_record.total_unavailability(); + // bitfields availability + session_stats.bitfields_availability += + para_record.total_availability(); + session_stats.bitfields_unavailability += + para_record.total_unavailability(); // let serialized = serde_json::to_string(¶_record)?; diff --git a/src/runtimes/paseo.rs b/src/runtimes/paseo.rs index 9a6bc93..b708b19 100644 --- a/src/runtimes/paseo.rs +++ b/src/runtimes/paseo.rs @@ -440,9 +440,11 @@ pub async fn cache_track_records(onet: &Onet, records: &Records) -> Result<(), O session_stats.implicit_votes += para_record.total_implicit_votes(); session_stats.missed_votes += para_record.total_missed_votes(); session_stats.disputes += para_record.total_disputes(); - // data availability - session_stats.data_availability += para_record.total_availability(); - session_stats.data_unavailability += para_record.total_unavailability(); + // bitfields availability + session_stats.bitfields_availability += + para_record.total_availability(); + session_stats.bitfields_unavailability += + para_record.total_unavailability(); // let serialized = serde_json::to_string(¶_record)?; diff --git a/src/runtimes/polkadot.rs b/src/runtimes/polkadot.rs index 1a85d15..4344db2 100644 --- a/src/runtimes/polkadot.rs +++ b/src/runtimes/polkadot.rs @@ -441,9 +441,11 @@ pub async fn cache_track_records(onet: &Onet, records: &Records) -> Result<(), O session_stats.implicit_votes += para_record.total_implicit_votes(); session_stats.missed_votes += para_record.total_missed_votes(); session_stats.disputes += para_record.total_disputes(); - // data availability - session_stats.data_availability += para_record.total_availability(); - session_stats.data_unavailability += para_record.total_unavailability(); + // bitfields availability + session_stats.bitfields_availability += + para_record.total_availability(); + session_stats.bitfields_unavailability += + para_record.total_unavailability(); // let serialized = serde_json::to_string(¶_record)?; From 93565b8dfa654439db50e32fa34b10fb06ae8862 Mon Sep 17 00:00:00 2001 From: paulo Date: Wed, 8 Jan 2025 16:35:23 +0000 Subject: [PATCH 08/12] include bitfields in validator grade result endpoints --- src/api/handlers/validators.rs | 52 ++++++++++++++++++++++++++++++++-- src/api/responses.rs | 2 ++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/api/handlers/validators.rs b/src/api/handlers/validators.rs index 0b4d1be..2f23b58 100644 --- a/src/api/handlers/validators.rs +++ b/src/api/handlers/validators.rs @@ -32,7 +32,7 @@ use crate::config::CONFIG; use crate::dn::try_fetch_stashes_from_remote_url; use crate::errors::{ApiError, CacheError}; use crate::pools::{PoolId, PoolNominees}; -use crate::records::{grade, EpochIndex, Grade}; +use crate::records::{grade, BitfieldsRecord, EpochIndex, Grade}; use crate::report::Subset; use actix_web::{ web::{Data, Json, Path, Query}, @@ -1183,10 +1183,28 @@ async fn calculate_validator_grade_by_stash( let mvr = mvrs.iter().sum::() / para_epochs as f64; + // calculate bur if para_epochs > 0 + let burs: Vec = data + .iter() + .filter(|v| v.is_para) + .filter_map(|v| v.para.get("bitfields")) + .filter_map(|value| ::deserialize(value).ok()) + .map(|bitfields| { + let partial = bitfields.availability() + bitfields.unavailability(); + if partial > 0 { + bitfields.unavailability() as f64 / partial as f64 + } else { + 0.0_f64 + } + }) + .collect(); + + let bur = burs.iter().sum::() / para_epochs as f64; + if params.show_summary { return Ok(ValidatorGradeResult { address: stash.to_string(), - grade: grade(1.0 - mvr).to_string(), + grade: grade(1.0 - (mvr * 0.6 + bur * 0.4)).to_string(), authority_inclusion: auth_epochs as f64 / params.number_last_sessions as f64, para_authority_inclusion: para_epochs as f64 / params.number_last_sessions as f64, explicit_votes_total: data @@ -1204,6 +1222,20 @@ async fn calculate_validator_grade_by_stash( .filter(|v| v.is_para) .map(|v| v.para_summary.missed_votes) .sum(), + bitfields_availability_total: data + .iter() + .filter(|v| v.is_para) + .filter_map(|v| v.para.get("bitfields")) + .filter_map(|value| serde_json::from_value::(value.clone()).ok()) + .map(|bitfields| bitfields.availability()) + .sum(), + bitfields_unavailability_total: data + .iter() + .filter(|v| v.is_para) + .filter_map(|v| v.para.get("bitfields")) + .filter_map(|value| serde_json::from_value::(value.clone()).ok()) + .map(|bitfields| bitfields.unavailability()) + .sum(), sessions_data: data.into(), ..Default::default() }); @@ -1211,7 +1243,7 @@ async fn calculate_validator_grade_by_stash( return Ok(ValidatorGradeResult { address: stash.to_string(), - grade: grade(1.0 - mvr).to_string(), + grade: grade(1.0 - (mvr * 0.6 + bur * 0.4)).to_string(), authority_inclusion: auth_epochs as f64 / params.number_last_sessions as f64, para_authority_inclusion: para_epochs as f64 / params.number_last_sessions as f64, explicit_votes_total: data @@ -1229,6 +1261,20 @@ async fn calculate_validator_grade_by_stash( .filter(|v| v.is_para) .map(|v| v.para_summary.missed_votes) .sum(), + bitfields_availability_total: data + .iter() + .filter(|v| v.is_para) + .filter_map(|v| v.para.get("bitfields")) + .filter_map(|value| serde_json::from_value::(value.clone()).ok()) + .map(|bitfields| bitfields.availability()) + .sum(), + bitfields_unavailability_total: data + .iter() + .filter(|v| v.is_para) + .filter_map(|v| v.para.get("bitfields")) + .filter_map(|value| serde_json::from_value::(value.clone()).ok()) + .map(|bitfields| bitfields.unavailability()) + .sum(), sessions: data.iter().map(|v| v.session).collect(), ..Default::default() }); diff --git a/src/api/responses.rs b/src/api/responses.rs index 8c743bf..b874aff 100644 --- a/src/api/responses.rs +++ b/src/api/responses.rs @@ -799,6 +799,8 @@ pub struct ValidatorGradeResult { pub explicit_votes_total: u32, pub implicit_votes_total: u32, pub missed_votes_total: u32, + pub bitfields_availability_total: u32, + pub bitfields_unavailability_total: u32, #[serde(skip_serializing_if = "Vec::is_empty")] pub sessions: Vec, #[serde(skip_serializing_if = "Vec::is_empty")] From 1bfc6b0136062f0c80751426eb7d9c57e6f7aacf Mon Sep 17 00:00:00 2001 From: paulo Date: Thu, 9 Jan 2025 13:10:57 +0000 Subject: [PATCH 09/12] fix reports and grades endpoint --- src/api/handlers/validators.rs | 6 +- src/records.rs | 18 +- src/report.rs | 446 +++++++++++++++++++++------------ 3 files changed, 308 insertions(+), 162 deletions(-) diff --git a/src/api/handlers/validators.rs b/src/api/handlers/validators.rs index 2f23b58..108ee16 100644 --- a/src/api/handlers/validators.rs +++ b/src/api/handlers/validators.rs @@ -677,7 +677,7 @@ pub async fn get_validators( // // NOTE: the score is based on 5 key values, which will be aggregated in the following map tupple. - // NOTE: the tupple has a subset, 5 counters plus the final score like: (subset, para_epochs, para_points, explicit_votes, implicit_votes, missed_vote, score) + // NOTE: the tuple has a subset, 5 counters plus the final score like: (subset, para_epochs, para_points, explicit_votes, implicit_votes, missed_vote, score) // let mut aggregator: BTreeMap = BTreeMap::new(); @@ -1204,7 +1204,7 @@ async fn calculate_validator_grade_by_stash( if params.show_summary { return Ok(ValidatorGradeResult { address: stash.to_string(), - grade: grade(1.0 - (mvr * 0.6 + bur * 0.4)).to_string(), + grade: grade(mvr, bur).to_string(), authority_inclusion: auth_epochs as f64 / params.number_last_sessions as f64, para_authority_inclusion: para_epochs as f64 / params.number_last_sessions as f64, explicit_votes_total: data @@ -1243,7 +1243,7 @@ async fn calculate_validator_grade_by_stash( return Ok(ValidatorGradeResult { address: stash.to_string(), - grade: grade(1.0 - (mvr * 0.6 + bur * 0.4)).to_string(), + grade: grade(mvr, bur).to_string(), authority_inclusion: auth_epochs as f64 / params.number_last_sessions as f64, para_authority_inclusion: para_epochs as f64 / params.number_last_sessions as f64, explicit_votes_total: data diff --git a/src/records.rs b/src/records.rs index 6f25ef3..66af007 100644 --- a/src/records.rs +++ b/src/records.rs @@ -205,7 +205,8 @@ impl std::fmt::Display for Grade { } } -pub fn grade(ratio: f64) -> Grade { +pub fn grade(mvr: f64, bur: f64) -> Grade { + let ratio = 1.0_f64 - (mvr * 0.75 + bur * 0.25); let p = (ratio * 10000.0).round() as u32; match p { 9901..=10000 => Grade::Ap, @@ -426,8 +427,11 @@ impl Records { let tv = para_record.total_votes(); let mv = para_record.total_missed_votes(); let mvr = mv as f64 / (tv + mv) as f64; + let ta = para_record.total_availability(); + let tu = para_record.total_unavailability(); + let bur = tu as f64 / (ta + tu) as f64; // Identify failed and exceptional epochs - let grade = grade(1.0 - mvr); + let grade = grade(mvr, bur); if grade == Grade::F { flagged_epochs += 1; } else if grade == Grade::Ap { @@ -1522,6 +1526,16 @@ impl ParaRecord { } } + pub fn bitfields_unavailability_ratio(&self) -> Option { + let total_votes = self.total_availability() + self.total_unavailability(); + if total_votes == 0 { + return None; + } else { + let ratio = self.total_unavailability() as f64 / total_votes as f64; + return Some(ratio); + } + } + pub fn get_para_id_stats(&self, para_id: ParaId) -> Option<&ParaStats> { self.para_stats.get(¶_id) } diff --git a/src/report.rs b/src/report.rs index a40c0ee..668432c 100644 --- a/src/report.rs +++ b/src/report.rs @@ -68,6 +68,9 @@ pub struct Validator { pub missed_votes: u32, pub core_assignments: u32, pub missed_ratio: Option, + pub bitfields_availability: u32, + pub bitfields_unavailability: u32, + pub bitfields_unavailability_ratio: Option, pub score: f64, pub commission_score: f64, pub warnings: Vec, @@ -99,6 +102,9 @@ impl Validator { missed_votes: 0, core_assignments: 0, missed_ratio: None, + bitfields_availability: 0, + bitfields_unavailability: 0, + bitfields_unavailability_ratio: None, score: 0.0_f64, commission_score: 0.0_f64, warnings: Vec::new(), @@ -350,7 +356,7 @@ impl From for Report { report.add_break(); report.add_raw_text(format!( - "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", + "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", "#", "Validator", "Stash", @@ -361,9 +367,12 @@ impl From for Report { "↻", "✓i", "✓e", - "✗", + "✗v", + "✓ba", + "✗bu", "Grade", "MVR", + "BUR", "Avg. PPTS", "Score", "Commission (%)", @@ -373,54 +382,62 @@ impl From for Report { for (i, validator) in validators.iter().enumerate() { if let Some(mvr) = validator.missed_ratio { - report.add_raw_text(format!( - "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", - i + 1, - replace_crln(&validator.name, ""), - validator.stash, - validator.subset.to_string(), - validator.active_epochs, - validator.para_epochs, - validator.authored_blocks, - validator.core_assignments, - validator.implicit_votes, - validator.explicit_votes, - validator.missed_votes, - grade(1.0_f64 - mvr), - (mvr * 10000.0).round() / 10000.0, - validator.avg_para_points, - (validator.score * 10000.0).round() / 10000.0, - (validator.commission * 10000.0).round() / 100.0, - (validator.commission_score * 10000.0).round() / 10000.0, - validator - .pattern - .iter() - .map(|g| g.to_string()) - .collect::() - )); - } else { - report.add_raw_text(format!( - "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", - i + 1, - validator.name, - validator.stash, - "-", - "-", - "-", - "-", - "-", - "-", - "-", - "-", - "-", - "-", - "-", - "-", - "-", - "-", - "-", - )); + if let Some(bur) = validator.bitfields_unavailability_ratio { + report.add_raw_text(format!( + "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", + i + 1, + replace_crln(&validator.name, ""), + validator.stash, + validator.subset.to_string(), + validator.active_epochs, + validator.para_epochs, + validator.authored_blocks, + validator.core_assignments, + validator.implicit_votes, + validator.explicit_votes, + validator.missed_votes, + validator.bitfields_availability, + validator.bitfields_unavailability, + grade(mvr, bur), + (mvr * 10000.0).round() / 10000.0, + (bur * 10000.0).round() / 10000.0, + validator.avg_para_points, + (validator.score * 10000.0).round() / 10000.0, + (validator.commission * 10000.0).round() / 100.0, + (validator.commission_score * 10000.0).round() / 10000.0, + validator + .pattern + .iter() + .map(|g| g.to_string()) + .collect::() + )); + continue; + } } + report.add_raw_text(format!( + "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", + i + 1, + validator.name, + validator.stash, + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-", + )); } report.add_break(); @@ -486,48 +503,59 @@ impl From for Report { for (i, group) in data.groups.iter().enumerate() { clode_block.push_str(&format!( - "{:<24}{:>4}{:>5}{:>5}{:>5}{:>5}{:>5}{:>8}{:>6}{:>6}\n", + "{:<24}{:>4}{:>5}{:>5}{:>5}{:>5}{:>5}{:>5}{:>5}{:>8}{:>8}{:>6}{:>6}\n", format!("{}. VAL_GROUP_{}", i + 1, group.0), "❒", "↻", "✓i", "✓e", - "✗", + "✗v", + "✓ba", + "✗bu", "GRD", "MVR", + "BUR", "PPTS", "TPTS", )); for (authority_record, para_record, val_name) in group.1.iter() { if let Some(mvr) = para_record.missed_votes_ratio() { - clode_block.push_str(&format!( - "{:<24}{:>4}{:>5}{:>5}{:>5}{:>5}{:>5}{:>8}{:>6}{:>6}\n", - slice(&replace_emoji(&val_name, "_"), 24), - authority_record.total_authored_blocks(), - para_record.total_core_assignments(), - para_record.total_implicit_votes(), - para_record.total_explicit_votes(), - para_record.total_missed_votes(), - grade(1.0_f64 - mvr).leading_spaces(3), - (mvr * 10000.0).round() / 10000.0, - authority_record.para_points(), - authority_record.points(), - )); - } else { - clode_block.push_str(&format!( - "{:<24}{:>4}{:>5}{:>5}{:>5}{:>5}{:>5}{:>8}{:>6}{:>6}\n", - slice(&replace_emoji(&val_name, "_"), 24), - "-", - "-", - "-", - "-", - "-", - "-", - "-", - "-", - "-" - )); + if let Some(bur) = para_record.bitfields_unavailability_ratio() { + clode_block.push_str(&format!( + "{:<24}{:>4}{:>5}{:>5}{:>5}{:>5}{:>5}{:>5}{:>5}{:>8}{:>8}{:>6}{:>6}\n", + slice(&replace_emoji(&val_name, "_"), 24), + authority_record.total_authored_blocks(), + para_record.total_core_assignments(), + para_record.total_implicit_votes(), + para_record.total_explicit_votes(), + para_record.total_missed_votes(), + para_record.total_availability(), + para_record.total_unavailability(), + grade(mvr, bur).leading_spaces(3), + (mvr * 10000.0).round() / 10000.0, + (bur * 10000.0).round() / 10000.0, + authority_record.para_points(), + authority_record.points(), + )); + continue; + } } + clode_block.push_str(&format!( + "{:<24}{:>4}{:>5}{:>5}{:>5}{:>5}{:>5}{:>5}{:>5}{:>8}{:>8}{:>6}{:>6}\n", + slice(&replace_emoji(&val_name, "_"), 24), + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-" + )); } clode_block.push_str("\n"); } @@ -694,11 +722,13 @@ impl From for Report { // Print Grade if let Some(mvr) = para_record.missed_votes_ratio() { - report.add_raw_text(format!( - "🎓 Session {} Grade: {}", - data.meta.current_session_index, - grade(1.0_f64 - mvr) - )); + if let Some(bur) = para_record.bitfields_unavailability_ratio() { + report.add_raw_text(format!( + "🎓 Session {} Grade: {}", + data.meta.current_session_index, + grade(mvr, bur) + )); + } } report.add_break(); // Print Rankings @@ -742,38 +772,50 @@ impl From for Report { // val. group validator names clode_block.push_str(&format!( - "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>6}{:>6}\n", + "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>8}{:>6}{:>6}\n", "#", format!("VAL_GROUP_{}", para_record.group().unwrap_or_default()), "❒", "↻", "✓i", "✓e", - "✗", + "✗v", + "✓ba", + "✗bu", "GRD", "MVR", + "BUR", "PPTS", "TPTS", )); if let Some(mvr) = para_record.missed_votes_ratio() { + if let Some(bur) = para_record.bitfields_unavailability_ratio() { + clode_block.push_str(&format!( + "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>8}{:>6}{:>6}\n", + "*", + slice(&replace_emoji(&data.validator.name, "_"), 24), + authority_record.total_authored_blocks(), + para_record.total_core_assignments(), + para_record.total_implicit_votes(), + para_record.total_explicit_votes(), + para_record.total_missed_votes(), + para_record.total_availability(), + para_record.total_unavailability(), + grade(mvr, bur).leading_spaces(2), + (mvr * 10000.0).round() / 10000.0, + (bur * 10000.0).round() / 10000.0, + authority_record.para_points(), + authority_record.points(), + )); + } + } + + if para_record.missed_votes_ratio().is_none() + || para_record.bitfields_unavailability_ratio().is_none() + { clode_block.push_str(&format!( - "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>6}{:>6}\n", - "*", - slice(&replace_emoji(&data.validator.name, "_"), 24), - authority_record.total_authored_blocks(), - para_record.total_core_assignments(), - para_record.total_implicit_votes(), - para_record.total_explicit_votes(), - para_record.total_missed_votes(), - grade(1.0_f64 - mvr).leading_spaces(2), - (mvr * 10000.0).round() / 10000.0, - authority_record.para_points(), - authority_record.points(), - )); - } else { - clode_block.push_str(&format!( - "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>6}{:>6}\n", + "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>8}{:>6}{:>6}\n", "*", slice(&replace_emoji(&data.validator.name, "_"), 24), "-", @@ -784,15 +826,20 @@ impl From for Report { "-", "-", "-", + "-", + "-", + "-", "-" )); } + // Print out peers names let peers_letters = vec!["A", "B", "C", "D", "E", "F", "G", "H"]; for (i, peer) in data.peers.iter().enumerate() { if let Some(mvr) = peer.2.missed_votes_ratio() { - clode_block.push_str(&format!( - "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>6}{:>6}\n", + if let Some(bur) = peer.2.bitfields_unavailability_ratio() { + clode_block.push_str(&format!( + "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>8}{:>6}{:>6}\n", peers_letters[i], slice(&replace_emoji(&peer.0.clone(), "_"), 24), peer.1.total_authored_blocks(), @@ -800,14 +847,19 @@ impl From for Report { peer.2.total_implicit_votes(), peer.2.total_explicit_votes(), peer.2.total_missed_votes(), - grade(1.0_f64 - mvr).leading_spaces(2), + peer.2.total_availability(), + peer.2.total_unavailability(), + grade(mvr, bur).leading_spaces(2), (mvr * 10000.0).round() / 10000.0, + (bur * 10000.0).round() / 10000.0, peer.1.para_points(), peer.1.points() )); - } else { - clode_block.push_str(&format!( - "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>6}{:>6}\n", + continue; + } + } + clode_block.push_str(&format!( + "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>8}{:>6}{:>6}\n", peers_letters[i], slice(&replace_emoji(&peer.0.clone(), "_"), 24), "-", @@ -818,9 +870,11 @@ impl From for Report { "-", "-", "-", + "-", + "-", + "-", "-" )); - } } // NOTE: By default print the full report @@ -1406,11 +1460,23 @@ fn flagged_and_exceptional_validators_report<'a>( .count(); let total_tvp_exceptional = para_validators .iter() - .filter(|v| v.subset == Subset::TVP && grade(1.0 - v.missed_ratio.unwrap()) == Grade::Ap) + .filter(|v| { + v.subset == Subset::TVP + && grade( + v.missed_ratio.unwrap(), + v.bitfields_unavailability_ratio.unwrap(), + ) == Grade::Ap + }) .count(); let total_tvp_flagged = para_validators .iter() - .filter(|v| v.subset == Subset::TVP && grade(1.0 - v.missed_ratio.unwrap()) == Grade::F) + .filter(|v| { + v.subset == Subset::TVP + && grade( + v.missed_ratio.unwrap(), + v.bitfields_unavailability_ratio.unwrap(), + ) == Grade::F + }) .count(); let total_non_tvp = para_validators @@ -1419,11 +1485,23 @@ fn flagged_and_exceptional_validators_report<'a>( .count(); let total_non_tvp_exceptional = para_validators .iter() - .filter(|v| v.subset == Subset::NONTVP && grade(1.0 - v.missed_ratio.unwrap()) == Grade::Ap) + .filter(|v| { + v.subset == Subset::NONTVP + && grade( + v.missed_ratio.unwrap(), + v.bitfields_unavailability_ratio.unwrap(), + ) == Grade::Ap + }) .count(); let total_non_tvp_flagged = para_validators .iter() - .filter(|v| v.subset == Subset::NONTVP && grade(1.0 - v.missed_ratio.unwrap()) == Grade::F) + .filter(|v| { + v.subset == Subset::NONTVP + && grade( + v.missed_ratio.unwrap(), + v.bitfields_unavailability_ratio.unwrap(), + ) == Grade::F + }) .count(); let total_c100 = para_validators @@ -1432,28 +1510,72 @@ fn flagged_and_exceptional_validators_report<'a>( .count(); let total_c100_exceptional = para_validators .iter() - .filter(|v| v.subset == Subset::C100 && grade(1.0 - v.missed_ratio.unwrap()) == Grade::Ap) + .filter(|v| { + v.subset == Subset::C100 + && grade( + v.missed_ratio.unwrap(), + v.bitfields_unavailability_ratio.unwrap(), + ) == Grade::Ap + }) .count(); let total_c100_flagged = para_validators .iter() - .filter(|v| v.subset == Subset::C100 && grade(1.0 - v.missed_ratio.unwrap()) == Grade::F) + .filter(|v| { + v.subset == Subset::C100 + && grade( + v.missed_ratio.unwrap(), + v.bitfields_unavailability_ratio.unwrap(), + ) == Grade::F + }) .count(); let total_flagged = total_c100_flagged + total_non_tvp_flagged + total_tvp_flagged; let total_exceptional = total_c100_exceptional + total_non_tvp_exceptional + total_tvp_exceptional; - let mvr_exceptional: Vec = para_validators - .iter() - .filter(|v| grade(1.0 - v.missed_ratio.unwrap()) == Grade::Ap) - .map(|v| v.missed_ratio.unwrap()) - .collect(); - - let mvr_flagged: Vec = para_validators - .iter() - .filter(|v| grade(1.0 - v.missed_ratio.unwrap()) == Grade::F) - .map(|v| v.missed_ratio.unwrap()) - .collect(); + // let mvr_exceptional: Vec = para_validators + // .iter() + // .filter(|v| { + // grade( + // v.missed_ratio.unwrap(), + // v.bitfields_unavailability_ratio.unwrap(), + // ) == Grade::Ap + // }) + // .map(|v| v.missed_ratio.unwrap()) + // .collect(); + + // let mvr_flagged: Vec = para_validators + // .iter() + // .filter(|v| { + // grade( + // v.missed_ratio.unwrap(), + // v.bitfields_unavailability_ratio.unwrap(), + // ) == Grade::F + // }) + // .map(|v| v.missed_ratio.unwrap()) + // .collect(); + + // let bur_exceptional: Vec = para_validators + // .iter() + // .filter(|v| { + // grade( + // v.missed_ratio.unwrap(), + // v.bitfields_unavailability_ratio.unwrap(), + // ) == Grade::Ap + // }) + // .map(|v| v.bitfields_unavailability_ratio.unwrap()) + // .collect(); + + // let bur_flagged: Vec = para_validators + // .iter() + // .filter(|v| { + // grade( + // v.missed_ratio.unwrap(), + // v.bitfields_unavailability_ratio.unwrap(), + // ) == Grade::F + // }) + // .map(|v| v.bitfields_unavailability_ratio.unwrap()) + // .collect(); if para_validators.len() > 0 { // set a warning flag @@ -1463,26 +1585,37 @@ fn flagged_and_exceptional_validators_report<'a>( "" }; - let avg_mvr_exceptional: String = if mvr_exceptional.len() > 0 { - format!( - "{}", - ((mvr_exceptional.iter().sum::() / mvr_exceptional.len() as f64) * 10000.0) - .round() - / 10000.0 - ) - } else { - "-".to_string() - }; - - let avg_mvr_flagged: String = if mvr_flagged.len() > 0 { - format!( - "{}", - ((mvr_flagged.iter().sum::() / mvr_flagged.len() as f64) * 10000.0).round() - / 10000.0 - ) - } else { - "-".to_string() - }; + // let avg_mvr_exceptional: String = if mvr_exceptional.len() > 0 { + // format!( + // "{}", + // ((mvr_exceptional.iter().sum::() / mvr_exceptional.len() as f64) * 10000.0) + // .round() + // / 10000.0 + // ) + // } else { + // "-".to_string() + // }; + + // let avg_bur_exceptional: String = if bur_exceptional.len() > 0 { + // format!( + // "{}", + // ((bur_exceptional.iter().sum::() / bur_exceptional.len() as f64) * 10000.0) + // .round() + // / 10000.0 + // ) + // } else { + // "-".to_string() + // }; + + // let avg_mvr_flagged: String = if mvr_flagged.len() > 0 { + // format!( + // "{}", + // ((mvr_flagged.iter().sum::() / mvr_flagged.len() as f64) * 10000.0).round() + // / 10000.0 + // ) + // } else { + // "-".to_string() + // }; report.add_raw_text(format!( "In the last {} sessions {} validators were selected to para-validate:", @@ -1492,10 +1625,9 @@ fn flagged_and_exceptional_validators_report<'a>( if total_exceptional > 0 { report.add_raw_text(format!( - "‣ {} consistently had an exceptional performance (A+) with an average missed vote ratio of {}.", - descnd(total_exceptional, para_validators.len()), - avg_mvr_exceptional - )); + "‣ {} consistently had an exceptional performance (A+).", + descnd(total_exceptional, para_validators.len()), + )); // Show subsets if !is_short { report.add_raw_text(format!( @@ -1509,11 +1641,10 @@ fn flagged_and_exceptional_validators_report<'a>( if total_flagged > 0 { report.add_raw_text(format!( - "‣ {}{} consistently had a low performance (F) with an average missed vote ratio of {}.", - warning, - descnd(total_flagged, para_validators.len()), - avg_mvr_flagged - )); + "‣ {}{} consistently had a low performance (F).", + warning, + descnd(total_flagged, para_validators.len()), + )); if !is_short { report.add_raw_text(format!( "‣‣ {} • {} • {}", @@ -1652,10 +1783,11 @@ fn top_performers_report<'a>( for v in &validators[..max] { report.add_raw_text(format!( - "* {} ({:.2}%, {}, {}, {}x)", + "* {} ({:.2}%, {}, {}, {}, {}x)", format!("{}", data.network.name.to_lowercase(), v.stash, v.name), v.score * 100.0, (v.missed_ratio.unwrap() * 10000.0).round() / 10000.0, + (v.bitfields_unavailability_ratio.unwrap() * 10000.0).round() / 10000.0, v.avg_para_points, v.para_epochs, )); @@ -1663,8 +1795,8 @@ fn top_performers_report<'a>( if !is_short { report.add_break(); - report.add_raw_text(format!("Legend: Val. identity (Score, Missed votes ratio, Average p/v points, Number of sessions as p/v)")); - report.add_raw_text(format!("Score: Backing votes ratio (1-MVR) make up 75% of the score, average p/v points make up 18% and number of sessions as p/v the remaining 7%")); + report.add_raw_text(format!("Legend: Val. identity (Score, Missed votes ratio, Bitfields Unavailability Ratio, Average p/v points, Number of sessions as p/v)")); + report.add_raw_text(format!("Score: Backing Votes Ratio (1-MVR) make up 50% of the score, Bitfields Availability Ratio (1-BUR) make up 25%, average p/v points make up 18% and number of sessions as p/v the remaining 7%")); report.add_raw_text(format!( "Sorting: Validators are sorted by Score in descending order" )); From 954405f76baf43ea5cc956b83c453a3a23387fce Mon Sep 17 00:00:00 2001 From: paulo Date: Fri, 10 Jan 2025 12:55:59 +0000 Subject: [PATCH 10/12] fix reports and legends --- LEGENDS.md | 52 ++++++++++-------- SCORES.md | 2 +- src/matrix.rs | 22 +++++--- src/records.rs | 20 +++++++ src/report.rs | 111 ++++++++++++++++++--------------------- src/runtimes/kusama.rs | 12 ++++- src/runtimes/paseo.rs | 12 ++++- src/runtimes/polkadot.rs | 12 ++++- 8 files changed, 147 insertions(+), 96 deletions(-) diff --git a/LEGENDS.md b/LEGENDS.md index 677b74a..6abd047 100644 --- a/LEGENDS.md +++ b/LEGENDS.md @@ -12,19 +12,22 @@ By typing `!legends` in one of the main public channels the following message wi ❒: Total number of authored blocks by the validator. ✓i: Total number of implicit votes by the validator. ✓e: Total number of explicit votes by the validator. -✗: Total number of missed votes by the validator. -MVR: Missed Votes Ratio (MVR) `MVR = (✗) / (✓i + ✓e + ✗)`. - -GRD: Grade reflects the Backing Votes Ratio (BVR) `BVR = 1 - MVR` by the validator: -‣ A+ = BVR > 99% -‣ A = BVR > 95% -‣ B+ = BVR > 90% -‣ B = BVR > 80% -‣ C+ = BVR > 70% -‣ C = BVR > 60% -‣ D+ = BVR > 50% -‣ D = BVR > 40% -‣ F = BVR <= 40% +✗v: Total number of missed votes by the validator. +✓ba: Total number of blocks containing populated bitfields from the validator. +✗bu: Total number of blocks with bitfields unavailable or empty from the validator. +MVR: Missed Votes Ratio (MVR) `MVR = (✗) / (✓i + ✓e + ✗)`. +BAR: Bitfields Availability Ratio `(BAR = (✓ba) / (✓ba + ✗bu))`. + +GRD: Grade is calculated as 75% of the Backing Votes Ratio (BVR = 1-MVR) combined with 25% of the Bitfields Availability Ratio (BAR) by one or more validators `(RATIO = BVR*0.75 + BAR*0.25)`: +‣ A+ = RATIO > 99% +‣ A = RATIO > 95% +‣ B+ = RATIO > 90% +‣ B = RATIO > 80% +‣ C+ = RATIO > 70% +‣ C = RATIO > 60% +‣ D+ = RATIO > 50% +‣ D = RATIO > 40% +‣ F = RATIO <= 40% PPTS: Sum of para-validator points the validator earned. TPTS: Sum of para-validator points + authored blocks points the validator earned. @@ -39,9 +42,12 @@ A, B, C, D: Represents each validator in the same val. group as the subscribed v ❒: Total number of authored blocks. ✓i: Total number of implicit votes. ✓e: Total number of explicit votes. -✗: Total number of missed votes by the validator. +✗v: Total number of missed votes by the validator. +✓ba: Total number of blocks containing populated bitfields from the validator. +✗bu: Total number of blocks with bitfields unavailable or empty from the validator. GRD: Grade reflects the Backing Votes Ratio. -MVR: Missed Votes Ratio. +MVR: Missed Votes Ratio. +BAR: Bitfields Availability Ratio. PPTS: Sum of para-validator points the validator earned. TPTS: Sum of para-validator points + authored blocks points the validator earned. _Note: Val. groups and validators are sorted by para-validator points in descending order._ @@ -54,7 +60,7 @@ _Note: Val. groups and validators are sorted by para-validator points in descend ❒: Total number of authored blocks from all validators when assigned to the parachain. ✓i: Total number of implicit votes from all validators when assigned to the parachain. ✓e: Total number of explicit votes from all validators when assigned to the parachain. -✗: Total number of missed votes from all validators when assigned to the parachain. +✗v: Total number of missed votes from all validators when assigned to the parachain. PPTS: Sum of para-validator points from all validators. TPTS: Sum of para-validator points + authored blocks points from all validators. _Note: Parachains are sorted by para-validator points in descending order._ @@ -63,16 +69,16 @@ _Note: Parachains are sorted by para-validator points in descending order._ `!subscribe insights` -Score: `score = (1 - mvr) * 0.75 + ((avg_pts - min_avg_pts) / (max_avg_pts - min_avg_pts)) * 0.18 + (pv_sessions / total_sessions) * 0.07` +Score: `score = (1 - mvr) * 0.55 + bar * 0.25 + ((avg_pts - min_avg_pts) / (max_avg_pts - min_avg_pts)) * 0.18 + (pv_sessions / total_sessions) * 0.07` Commission Score: `commission_score = score * 0.25 + (1 - commission) * 0.75` Timeline: Graphic performance representation in the last X sessions: -‣ ❚ = BVR >= 90% -‣ ❙ = BVR >= 60% -‣ ❘ = BVR >= 40% -‣ ! = BVR >= 20% -‣ ¿ = BVR < 20% -‣ ? = No-votes +‣ ❚ = RATIO >= 90% +‣ ❙ = RATIO >= 60% +‣ ❘ = RATIO >= 40% +‣ ! = RATIO >= 20% +‣ ¿ = RATIO < 20% +‣ ? = No-show ‣ • = Not P/V ‣ _ = Waiting diff --git a/SCORES.md b/SCORES.md index 4b04d2e..8e9ddab 100644 --- a/SCORES.md +++ b/SCORES.md @@ -2,7 +2,7 @@ ## Performance Score -`performance_score = (1 - mvr) * 0.75 + ((avg_pts - min_avg_pts) / (max_avg_pts - min_avg_pts)) * 0.18 + (pv_sessions / total_sessions) * 0.07` +`performance_score = (1 - mvr) * 0.50 + bar * 0.25 + ((avg_pts - min_avg_pts) / (max_avg_pts - min_avg_pts)) * 0.18 + (pv_sessions / total_sessions) * 0.07` ## Commission Score diff --git a/src/matrix.rs b/src/matrix.rs index a1f2d52..07d5177 100644 --- a/src/matrix.rs +++ b/src/matrix.rs @@ -1317,12 +1317,17 @@ impl Matrix { message.push_str("❒: Total number of authored blocks by the validator.
"); message.push_str("✓i: Total number of implicit votes by the validator.
"); message.push_str("✓e: Total number of explicit votes by the validator.
"); - message.push_str("✗: Total number of missed votes by the validator.
"); - message.push_str("MVR: Missed Votes Ratio (MVR = (✗) / (✓i + ✓e + ✗)).
"); + message.push_str("✗v: Total number of missed votes by the validator.
"); message.push_str( - "GRD: Grade reflects the Backing Votes Ratio (BVR = 1-MVR) by the validator:
", + "✓ba: Total number of blocks containing populated bitfields from the validator.
", ); - message.push_str("‣ A+ = BVR > 99%
"); + message.push_str("✗bu: Total number of blocks with bitfields unavailable or empty from the validator.
"); + message.push_str("MVR: Missed Votes Ratio (MVR = (✗v) / (✓i + ✓e + ✗)).
"); + message.push_str("BAR: Bitfields Availability Ratio (BAR = (✓ba) / (✓ba + ✗bu)).
"); + message.push_str( + "GRD: Grade is calculated as 75% of the Backing Votes Ratio (BVR = 1-MVR) combined with 25% of the Bitfields Availability Ratio (BAR) by the validator (RATIO = BVR*0.75 + BAR*0.25):" + ); + message.push_str("‣ A+ = RATIO > 99%
"); message.push_str("‣ A = BVR > 95%
"); message.push_str("‣ B+ = BVR > 90%
"); message.push_str("‣ B = BVR > 80%
"); @@ -1344,9 +1349,14 @@ impl Matrix { message.push_str("❒: Total number of authored blocks.
"); message.push_str("✓i: Total number of implicit votes.
"); message.push_str("✓e: Total number of explicit votes.
"); - message.push_str("✗: Total number of missed votes by the validator.
"); + message.push_str("✗v: Total number of missed votes by the validator.
"); + message.push_str( + "✓ba: Total number of blocks containing populated bitfields from the validator.
", + ); + message.push_str("✗bu: Total number of blocks with bitfields unavailable or empty from the validator.
"); message.push_str("GRD: Grade reflects the Backing Votes Ratio.
"); message.push_str("MVR: Missed Votes Ratio.
"); + message.push_str("BAR: Bitfields Availability Ratio.
"); message.push_str("PPTS: Sum of para-validator points the validator earned.
"); message.push_str( "TPTS: Sum of para-validator points + authored blocks points the validator earned.
", @@ -1360,7 +1370,7 @@ impl Matrix { message.push_str("❒: Total number of authored blocks from all validators when assigned to the parachain.
"); message.push_str("✓i: Total number of implicit votes from all validators when assigned to the parachain.
"); message.push_str("✓e: Total number of explicit votes from all validators when assigned to the parachain.
"); - message.push_str("✗: Total number of missed votes from all validators when assigned to the parachain.
"); + message.push_str("✗v: Total number of missed votes from all validators when assigned to the parachain.
"); message.push_str("PPTS: Sum of para-validator points from all validators.
"); message.push_str( "TPTS: Sum of para-validator points + authored blocks points from all validators.
", diff --git a/src/records.rs b/src/records.rs index 66af007..f35770c 100644 --- a/src/records.rs +++ b/src/records.rs @@ -74,6 +74,8 @@ pub type ExplicitVotes = u32; pub type ImplicitVotes = u32; pub type MissedVotes = u32; pub type CoreAssignments = u32; +pub type BitfieldsAvailability = u32; +pub type BitfieldsUnavailability = u32; pub type Ratio = f64; pub type ParaEpochs = Vec; pub type Pattern = Vec; @@ -467,6 +469,8 @@ impl Records { ImplicitVotes, MissedVotes, CoreAssignments, + BitfieldsAvailability, + BitfieldsUnavailability, )>, )> { if self.total_full_epochs() == 0 { @@ -480,6 +484,8 @@ impl Records { let mut explicit_votes: Votes = 0; let mut implicit_votes: Votes = 0; let mut missed_votes: Votes = 0; + let mut bitfields_availability: BitfieldsAvailability = 0; + let mut bitfields_unavailability: BitfieldsUnavailability = 0; let mut core_assignments: CoreAssignments = 0; let mut epoch_index = self.current_epoch() - self.total_full_epochs(); @@ -506,6 +512,8 @@ impl Records { explicit_votes += para_record.total_explicit_votes(); implicit_votes += para_record.total_implicit_votes(); missed_votes += para_record.total_missed_votes(); + bitfields_availability += para_record.total_availability(); + bitfields_unavailability += para_record.total_unavailability(); core_assignments += para_record.total_core_assignments(); if let Some(ratio) = para_record.missed_votes_ratio() { @@ -534,6 +542,8 @@ impl Records { implicit_votes, missed_votes, core_assignments, + bitfields_availability, + bitfields_unavailability, )), )) } else { @@ -1536,6 +1546,16 @@ impl ParaRecord { } } + pub fn bitfields_availability_ratio(&self) -> Option { + let total_votes = self.total_availability() + self.total_unavailability(); + if total_votes == 0 { + return None; + } else { + let ratio = self.total_availability() as f64 / total_votes as f64; + return Some(ratio); + } + } + pub fn get_para_id_stats(&self, para_id: ParaId) -> Option<&ParaStats> { self.para_stats.get(¶_id) } diff --git a/src/report.rs b/src/report.rs index 668432c..df1c562 100644 --- a/src/report.rs +++ b/src/report.rs @@ -70,7 +70,7 @@ pub struct Validator { pub missed_ratio: Option, pub bitfields_availability: u32, pub bitfields_unavailability: u32, - pub bitfields_unavailability_ratio: Option, + pub bitfields_availability_ratio: Option, pub score: f64, pub commission_score: f64, pub warnings: Vec, @@ -104,7 +104,7 @@ impl Validator { missed_ratio: None, bitfields_availability: 0, bitfields_unavailability: 0, - bitfields_unavailability_ratio: None, + bitfields_availability_ratio: None, score: 0.0_f64, commission_score: 0.0_f64, warnings: Vec::new(), @@ -366,13 +366,13 @@ impl From for Report { "❒", "↻", "✓i", - "✓e", + "e", "✗v", "✓ba", "✗bu", "Grade", "MVR", - "BUR", + "BAR", "Avg. PPTS", "Score", "Commission (%)", @@ -382,7 +382,7 @@ impl From for Report { for (i, validator) in validators.iter().enumerate() { if let Some(mvr) = validator.missed_ratio { - if let Some(bur) = validator.bitfields_unavailability_ratio { + if let Some(bar) = validator.bitfields_availability_ratio { report.add_raw_text(format!( "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", i + 1, @@ -398,9 +398,9 @@ impl From for Report { validator.missed_votes, validator.bitfields_availability, validator.bitfields_unavailability, - grade(mvr, bur), + grade(mvr, 1.0_f64-bar), (mvr * 10000.0).round() / 10000.0, - (bur * 10000.0).round() / 10000.0, + (bar * 10000.0).round() / 10000.0, validator.avg_para_points, (validator.score * 10000.0).round() / 10000.0, (validator.commission * 10000.0).round() / 100.0, @@ -415,7 +415,7 @@ impl From for Report { } } report.add_raw_text(format!( - "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", + "{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}\t{}", i + 1, validator.name, validator.stash, @@ -436,7 +436,6 @@ impl From for Report { "-", "-", "-", - "-", )); } @@ -503,37 +502,35 @@ impl From for Report { for (i, group) in data.groups.iter().enumerate() { clode_block.push_str(&format!( - "{:<24}{:>4}{:>5}{:>5}{:>5}{:>5}{:>5}{:>5}{:>5}{:>8}{:>8}{:>6}{:>6}\n", + "{:<24}{:>4}{:>5}{:>5}{:>5}{:>5}{:>5}{:>5}{:>8}{:>8}{:>6}{:>6}\n", format!("{}. VAL_GROUP_{}", i + 1, group.0), "❒", "↻", "✓i", "✓e", "✗v", - "✓ba", "✗bu", "GRD", "MVR", - "BUR", + "BAR", "PPTS", "TPTS", )); for (authority_record, para_record, val_name) in group.1.iter() { if let Some(mvr) = para_record.missed_votes_ratio() { - if let Some(bur) = para_record.bitfields_unavailability_ratio() { + if let Some(bar) = para_record.bitfields_availability_ratio() { clode_block.push_str(&format!( - "{:<24}{:>4}{:>5}{:>5}{:>5}{:>5}{:>5}{:>5}{:>5}{:>8}{:>8}{:>6}{:>6}\n", + "{:<24}{:>4}{:>5}{:>5}{:>5}{:>5}{:>5}{:>5}{:>8}{:>8}{:>6}{:>6}\n", slice(&replace_emoji(&val_name, "_"), 24), authority_record.total_authored_blocks(), para_record.total_core_assignments(), para_record.total_implicit_votes(), para_record.total_explicit_votes(), para_record.total_missed_votes(), - para_record.total_availability(), para_record.total_unavailability(), - grade(mvr, bur).leading_spaces(3), + grade(mvr, 1.0f64 - bar).leading_spaces(3), (mvr * 10000.0).round() / 10000.0, - (bur * 10000.0).round() / 10000.0, + (bar * 10000.0).round() / 10000.0, authority_record.para_points(), authority_record.points(), )); @@ -541,7 +538,7 @@ impl From for Report { } } clode_block.push_str(&format!( - "{:<24}{:>4}{:>5}{:>5}{:>5}{:>5}{:>5}{:>5}{:>5}{:>8}{:>8}{:>6}{:>6}\n", + "{:<24}{:>4}{:>5}{:>5}{:>5}{:>5}{:>5}{:>5}{:>8}{:>8}{:>6}{:>6}\n", slice(&replace_emoji(&val_name, "_"), 24), "-", "-", @@ -553,7 +550,6 @@ impl From for Report { "-", "-", "-", - "-", "-" )); } @@ -772,7 +768,7 @@ impl From for Report { // val. group validator names clode_block.push_str(&format!( - "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>8}{:>6}{:>6}\n", + "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>8}{:>6}{:>6}\n", "#", format!("VAL_GROUP_{}", para_record.group().unwrap_or_default()), "❒", @@ -780,19 +776,18 @@ impl From for Report { "✓i", "✓e", "✗v", - "✓ba", "✗bu", "GRD", "MVR", - "BUR", + "BAR", "PPTS", "TPTS", )); if let Some(mvr) = para_record.missed_votes_ratio() { - if let Some(bur) = para_record.bitfields_unavailability_ratio() { + if let Some(bar) = para_record.bitfields_availability_ratio() { clode_block.push_str(&format!( - "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>8}{:>6}{:>6}\n", + "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>8}{:>6}{:>6}\n", "*", slice(&replace_emoji(&data.validator.name, "_"), 24), authority_record.total_authored_blocks(), @@ -800,11 +795,10 @@ impl From for Report { para_record.total_implicit_votes(), para_record.total_explicit_votes(), para_record.total_missed_votes(), - para_record.total_availability(), para_record.total_unavailability(), - grade(mvr, bur).leading_spaces(2), + grade(mvr, 1.0_f64-bar).leading_spaces(2), (mvr * 10000.0).round() / 10000.0, - (bur * 10000.0).round() / 10000.0, + (bar * 10000.0).round() / 10000.0, authority_record.para_points(), authority_record.points(), )); @@ -812,10 +806,10 @@ impl From for Report { } if para_record.missed_votes_ratio().is_none() - || para_record.bitfields_unavailability_ratio().is_none() + || para_record.bitfields_availability_ratio().is_none() { clode_block.push_str(&format!( - "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>8}{:>6}{:>6}\n", + "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>8}{:>6}{:>6}\n", "*", slice(&replace_emoji(&data.validator.name, "_"), 24), "-", @@ -828,7 +822,6 @@ impl From for Report { "-", "-", "-", - "-", "-" )); } @@ -837,9 +830,9 @@ impl From for Report { let peers_letters = vec!["A", "B", "C", "D", "E", "F", "G", "H"]; for (i, peer) in data.peers.iter().enumerate() { if let Some(mvr) = peer.2.missed_votes_ratio() { - if let Some(bur) = peer.2.bitfields_unavailability_ratio() { + if let Some(bar) = peer.2.bitfields_availability_ratio() { clode_block.push_str(&format!( - "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>8}{:>6}{:>6}\n", + "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>8}{:>6}{:>6}\n", peers_letters[i], slice(&replace_emoji(&peer.0.clone(), "_"), 24), peer.1.total_authored_blocks(), @@ -847,11 +840,10 @@ impl From for Report { peer.2.total_implicit_votes(), peer.2.total_explicit_votes(), peer.2.total_missed_votes(), - peer.2.total_availability(), peer.2.total_unavailability(), - grade(mvr, bur).leading_spaces(2), + grade(mvr, 1.0_f64-bar).leading_spaces(2), (mvr * 10000.0).round() / 10000.0, - (bur * 10000.0).round() / 10000.0, + (bar * 10000.0).round() / 10000.0, peer.1.para_points(), peer.1.points() )); @@ -859,22 +851,21 @@ impl From for Report { } } clode_block.push_str(&format!( - "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>8}{:>6}{:>6}\n", - peers_letters[i], - slice(&replace_emoji(&peer.0.clone(), "_"), 24), - "-", - "-", - "-", - "-", - "-", - "-", - "-", - "-", - "-", - "-", - "-", - "-" - )); + "{:<3}{:<24}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>4}{:>8}{:>8}{:>6}{:>6}\n", + peers_letters[i], + slice(&replace_emoji(&peer.0.clone(), "_"), 24), + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-", + "-" + )); } // NOTE: By default print the full report @@ -1464,7 +1455,7 @@ fn flagged_and_exceptional_validators_report<'a>( v.subset == Subset::TVP && grade( v.missed_ratio.unwrap(), - v.bitfields_unavailability_ratio.unwrap(), + 1.0_f64 - v.bitfields_availability_ratio.unwrap(), ) == Grade::Ap }) .count(); @@ -1474,7 +1465,7 @@ fn flagged_and_exceptional_validators_report<'a>( v.subset == Subset::TVP && grade( v.missed_ratio.unwrap(), - v.bitfields_unavailability_ratio.unwrap(), + 1.0_f64 - v.bitfields_availability_ratio.unwrap(), ) == Grade::F }) .count(); @@ -1489,7 +1480,7 @@ fn flagged_and_exceptional_validators_report<'a>( v.subset == Subset::NONTVP && grade( v.missed_ratio.unwrap(), - v.bitfields_unavailability_ratio.unwrap(), + 1.0_f64 - v.bitfields_availability_ratio.unwrap(), ) == Grade::Ap }) .count(); @@ -1499,7 +1490,7 @@ fn flagged_and_exceptional_validators_report<'a>( v.subset == Subset::NONTVP && grade( v.missed_ratio.unwrap(), - v.bitfields_unavailability_ratio.unwrap(), + 1.0_f64 - v.bitfields_availability_ratio.unwrap(), ) == Grade::F }) .count(); @@ -1514,7 +1505,7 @@ fn flagged_and_exceptional_validators_report<'a>( v.subset == Subset::C100 && grade( v.missed_ratio.unwrap(), - v.bitfields_unavailability_ratio.unwrap(), + 1.0_f64 - v.bitfields_availability_ratio.unwrap(), ) == Grade::Ap }) .count(); @@ -1524,7 +1515,7 @@ fn flagged_and_exceptional_validators_report<'a>( v.subset == Subset::C100 && grade( v.missed_ratio.unwrap(), - v.bitfields_unavailability_ratio.unwrap(), + 1.0_f64 - v.bitfields_availability_ratio.unwrap(), ) == Grade::F }) .count(); @@ -1787,7 +1778,7 @@ fn top_performers_report<'a>( format!("{}", data.network.name.to_lowercase(), v.stash, v.name), v.score * 100.0, (v.missed_ratio.unwrap() * 10000.0).round() / 10000.0, - (v.bitfields_unavailability_ratio.unwrap() * 10000.0).round() / 10000.0, + (v.bitfields_availability_ratio.unwrap() * 10000.0).round() / 10000.0, v.avg_para_points, v.para_epochs, )); @@ -1795,8 +1786,8 @@ fn top_performers_report<'a>( if !is_short { report.add_break(); - report.add_raw_text(format!("Legend: Val. identity (Score, Missed votes ratio, Bitfields Unavailability Ratio, Average p/v points, Number of sessions as p/v)")); - report.add_raw_text(format!("Score: Backing Votes Ratio (1-MVR) make up 50% of the score, Bitfields Availability Ratio (1-BUR) make up 25%, average p/v points make up 18% and number of sessions as p/v the remaining 7%")); + report.add_raw_text(format!("Legend: Val. identity (Score, Missed votes ratio, Bitfields Availability Ratio, Average p/v points, Number of sessions as p/v)")); + report.add_raw_text(format!("Score: Backing Votes Ratio (1-MVR) make up 50% of the score, Bitfields Availability Ratio make up 25%, average p/v points make up 18% and number of sessions as p/v the remaining 7%")); report.add_raw_text(format!( "Sorting: Validators are sorted by Score in descending order" )); diff --git a/src/runtimes/kusama.rs b/src/runtimes/kusama.rs index e618898..136bc10 100644 --- a/src/runtimes/kusama.rs +++ b/src/runtimes/kusama.rs @@ -2044,6 +2044,8 @@ pub async fn run_network_report(records: &Records) -> Result<(), OnetError> { implicit_votes, missed_votes, core_assignments, + bitfields_availability, + bitfields_unavailability, )) = para_data { // Note: If Para data exists than get node identity to be visible in the report @@ -2059,6 +2061,11 @@ pub async fn run_network_report(records: &Records) -> Result<(), OnetError> { / (explicit_votes + implicit_votes + missed_votes) as f64; v.missed_ratio = Some(mvr); } + if bitfields_availability + bitfields_unavailability > 0 { + let bar = bitfields_availability as f64 + / (bitfields_availability + bitfields_unavailability) as f64; + v.bitfields_availability_ratio = Some(bar); + } if para_epochs >= 1 { v.avg_para_points = para_points / para_epochs; } @@ -2097,7 +2104,7 @@ pub async fn run_network_report(records: &Records) -> Result<(), OnetError> { } // Calculate a score based on the formula - // SCORE_1 = (1-MVR)*0.75 + ((AVG_PV_POINTS - MIN_AVG_POINTS)/(MAX_AVG_PV_POINTS-MIN_AVG_PV_POINTS))*0.18 + (PV_SESSIONS/TOTAL_SESSIONS)*0.07 + // SCORE_1 = (1-MVR)*0.50 + BAR*0.25 + ((AVG_PV_POINTS - MIN_AVG_POINTS)/(MAX_AVG_PV_POINTS-MIN_AVG_PV_POINTS))*0.18 + (PV_SESSIONS/TOTAL_SESSIONS)*0.07 // SCORE_2 = SCORE*0.25 + (1-COMMISSION)*0.75 // Normalize avg_para_points @@ -2122,7 +2129,8 @@ pub async fn run_network_report(records: &Records) -> Result<(), OnetError> { .filter(|v| v.para_epochs >= 1 && v.missed_ratio.is_some()) .for_each(|v| { let score = if max - min > 0 { - (1.0_f64 - v.missed_ratio.unwrap()) * 0.75_f64 + (1.0_f64 - v.missed_ratio.unwrap_or_default()) * 0.75_f64 + + v.bitfields_availability_ratio.unwrap_or_default() * 0.25_f64 + ((v.avg_para_points as f64 - *min as f64) / (*max as f64 - *min as f64)) * 0.18_f64 + (v.para_epochs as f64 / records.total_full_epochs() as f64) * 0.07_f64 diff --git a/src/runtimes/paseo.rs b/src/runtimes/paseo.rs index b708b19..24eb424 100644 --- a/src/runtimes/paseo.rs +++ b/src/runtimes/paseo.rs @@ -2036,6 +2036,8 @@ pub async fn run_network_report(records: &Records) -> Result<(), OnetError> { implicit_votes, missed_votes, core_assignments, + bitfields_availability, + bitfields_unavailability, )) = para_data { // Note: If Para data exists than get node identity to be visible in the report @@ -2051,6 +2053,11 @@ pub async fn run_network_report(records: &Records) -> Result<(), OnetError> { / (explicit_votes + implicit_votes + missed_votes) as f64; v.missed_ratio = Some(mvr); } + if bitfields_availability + bitfields_unavailability > 0 { + let bar = bitfields_availability as f64 + / (bitfields_availability + bitfields_unavailability) as f64; + v.bitfields_availability_ratio = Some(bar); + } if para_epochs >= 1 { v.avg_para_points = para_points / para_epochs; } @@ -2089,7 +2096,7 @@ pub async fn run_network_report(records: &Records) -> Result<(), OnetError> { } // Calculate a score based on the formula - // SCORE_1 = (1-MVR)*0.75 + ((AVG_PV_POINTS - MIN_AVG_POINTS)/(MAX_AVG_PV_POINTS-MIN_AVG_PV_POINTS))*0.18 + (PV_SESSIONS/TOTAL_SESSIONS)*0.07 + // SCORE_1 = (1-MVR)*0.50 + BAR*0.25 + ((AVG_PV_POINTS - MIN_AVG_POINTS)/(MAX_AVG_PV_POINTS-MIN_AVG_PV_POINTS))*0.18 + (PV_SESSIONS/TOTAL_SESSIONS)*0.07 // SCORE_2 = SCORE*0.25 + (1-COMMISSION)*0.75 // Normalize avg_para_points @@ -2114,7 +2121,8 @@ pub async fn run_network_report(records: &Records) -> Result<(), OnetError> { .filter(|v| v.para_epochs >= 1 && v.missed_ratio.is_some()) .for_each(|v| { let score = if max - min > 0 { - (1.0_f64 - v.missed_ratio.unwrap()) * 0.75_f64 + (1.0_f64 - v.missed_ratio.unwrap_or_default()) * 0.75_f64 + + v.bitfields_availability_ratio.unwrap_or_default() * 0.25_f64 + ((v.avg_para_points as f64 - *min as f64) / (*max as f64 - *min as f64)) * 0.18_f64 + (v.para_epochs as f64 / records.total_full_epochs() as f64) * 0.07_f64 diff --git a/src/runtimes/polkadot.rs b/src/runtimes/polkadot.rs index 4344db2..e29d702 100644 --- a/src/runtimes/polkadot.rs +++ b/src/runtimes/polkadot.rs @@ -2044,6 +2044,8 @@ pub async fn run_network_report(records: &Records) -> Result<(), OnetError> { implicit_votes, missed_votes, core_assignments, + bitfields_availability, + bitfields_unavailability, )) = para_data { // Note: If Para data exists than get node identity to be visible in the report @@ -2059,6 +2061,11 @@ pub async fn run_network_report(records: &Records) -> Result<(), OnetError> { / (explicit_votes + implicit_votes + missed_votes) as f64; v.missed_ratio = Some(mvr); } + if bitfields_availability + bitfields_unavailability > 0 { + let bar = bitfields_availability as f64 + / (bitfields_availability + bitfields_unavailability) as f64; + v.bitfields_availability_ratio = Some(bar); + } if para_epochs >= 1 { v.avg_para_points = para_points / para_epochs; } @@ -2097,7 +2104,7 @@ pub async fn run_network_report(records: &Records) -> Result<(), OnetError> { } // Calculate a score based on the formula - // SCORE_1 = (1-MVR)*0.75 + ((AVG_PV_POINTS - MIN_AVG_POINTS)/(MAX_AVG_PV_POINTS-MIN_AVG_PV_POINTS))*0.18 + (PV_SESSIONS/TOTAL_SESSIONS)*0.07 + // SCORE_1 = (1-MVR)*0.50 + BAR*0.25 + ((AVG_PV_POINTS - MIN_AVG_POINTS)/(MAX_AVG_PV_POINTS-MIN_AVG_PV_POINTS))*0.18 + (PV_SESSIONS/TOTAL_SESSIONS)*0.07 // SCORE_2 = SCORE*0.25 + (1-COMMISSION)*0.75 // Normalize avg_para_points @@ -2122,7 +2129,8 @@ pub async fn run_network_report(records: &Records) -> Result<(), OnetError> { .filter(|v| v.para_epochs >= 1 && v.missed_ratio.is_some()) .for_each(|v| { let score = if max - min > 0 { - (1.0_f64 - v.missed_ratio.unwrap()) * 0.75_f64 + (1.0_f64 - v.missed_ratio.unwrap_or_default()) * 0.75_f64 + + v.bitfields_availability_ratio.unwrap_or_default() * 0.25_f64 + ((v.avg_para_points as f64 - *min as f64) / (*max as f64 - *min as f64)) * 0.18_f64 + (v.para_epochs as f64 / records.total_full_epochs() as f64) * 0.07_f64 From 4d856b9c0fe1c07983130f778db42c2243f4a719 Mon Sep 17 00:00:00 2001 From: paulo Date: Fri, 10 Jan 2025 13:01:36 +0000 Subject: [PATCH 11/12] fix scores --- src/runtimes/kusama.rs | 2 +- src/runtimes/paseo.rs | 2 +- src/runtimes/polkadot.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/runtimes/kusama.rs b/src/runtimes/kusama.rs index 136bc10..4f5a9e6 100644 --- a/src/runtimes/kusama.rs +++ b/src/runtimes/kusama.rs @@ -2129,7 +2129,7 @@ pub async fn run_network_report(records: &Records) -> Result<(), OnetError> { .filter(|v| v.para_epochs >= 1 && v.missed_ratio.is_some()) .for_each(|v| { let score = if max - min > 0 { - (1.0_f64 - v.missed_ratio.unwrap_or_default()) * 0.75_f64 + (1.0_f64 - v.missed_ratio.unwrap_or_default()) * 0.50_f64 + v.bitfields_availability_ratio.unwrap_or_default() * 0.25_f64 + ((v.avg_para_points as f64 - *min as f64) / (*max as f64 - *min as f64)) * 0.18_f64 diff --git a/src/runtimes/paseo.rs b/src/runtimes/paseo.rs index 24eb424..7874a73 100644 --- a/src/runtimes/paseo.rs +++ b/src/runtimes/paseo.rs @@ -2121,7 +2121,7 @@ pub async fn run_network_report(records: &Records) -> Result<(), OnetError> { .filter(|v| v.para_epochs >= 1 && v.missed_ratio.is_some()) .for_each(|v| { let score = if max - min > 0 { - (1.0_f64 - v.missed_ratio.unwrap_or_default()) * 0.75_f64 + (1.0_f64 - v.missed_ratio.unwrap_or_default()) * 0.50_f64 + v.bitfields_availability_ratio.unwrap_or_default() * 0.25_f64 + ((v.avg_para_points as f64 - *min as f64) / (*max as f64 - *min as f64)) * 0.18_f64 diff --git a/src/runtimes/polkadot.rs b/src/runtimes/polkadot.rs index e29d702..9774c0d 100644 --- a/src/runtimes/polkadot.rs +++ b/src/runtimes/polkadot.rs @@ -2129,7 +2129,7 @@ pub async fn run_network_report(records: &Records) -> Result<(), OnetError> { .filter(|v| v.para_epochs >= 1 && v.missed_ratio.is_some()) .for_each(|v| { let score = if max - min > 0 { - (1.0_f64 - v.missed_ratio.unwrap_or_default()) * 0.75_f64 + (1.0_f64 - v.missed_ratio.unwrap_or_default()) * 0.50_f64 + v.bitfields_availability_ratio.unwrap_or_default() * 0.25_f64 + ((v.avg_para_points as f64 - *min as f64) / (*max as f64 - *min as f64)) * 0.18_f64 From 439f576f504a6dc36bf837bf913d8df4a497d302 Mon Sep 17 00:00:00 2001 From: paulo Date: Fri, 10 Jan 2025 13:27:46 +0000 Subject: [PATCH 12/12] fix validators performance ranking --- src/api/handlers/validators.rs | 92 +++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 36 deletions(-) diff --git a/src/api/handlers/validators.rs b/src/api/handlers/validators.rs index 108ee16..05d42b7 100644 --- a/src/api/handlers/validators.rs +++ b/src/api/handlers/validators.rs @@ -676,10 +676,10 @@ pub async fn get_validators( // warn!("__serialized: {:?}", serialized); // - // NOTE: the score is based on 5 key values, which will be aggregated in the following map tupple. - // NOTE: the tuple has a subset, 5 counters plus the final score like: (subset, para_epochs, para_points, explicit_votes, implicit_votes, missed_vote, score) + // NOTE: the score is based on 7 key values, which will be aggregated in the following map tupple. + // NOTE: the tuple has a subset, 7 counters plus the final score like: (subset, para_epochs, para_points, explicit_votes, implicit_votes, missed_votes, bitfields_availability, bitfields_unavailability, score) // - let mut aggregator: BTreeMap = + let mut aggregator: BTreeMap = BTreeMap::new(); let mut validators: BTreeMap = BTreeMap::new(); let mut total_epochs: u32 = 0; @@ -705,18 +705,29 @@ pub async fn get_validators( cache.clone(), ) .await?; - // NOTE: the tupple has a subset, 5 counters plus the final score like: (subset, para_epochs, para_points, explicit_votes, implicit_votes, missed_vote, score) + // NOTE: the tupple has a subset, 5 counters plus the final score like: (subset, para_epochs, para_points, explicit_votes, implicit_votes, missed_vote, bitfields_availability, bitfields_unavailability, score) aggregator .entry(val.address.clone()) - .and_modify(|(subset, para_epochs, para_points, ev, iv, mv, _)| { - *subset = val.profile.subset.clone(); - *para_epochs += 1; - *para_points += val.auth.para_points(); - *ev += val.para_summary.explicit_votes(); - *iv += val.para_summary.implicit_votes(); - *mv += val.para_summary.missed_votes(); - }) - .or_insert((Subset::NotDefined, 0, 0, 0, 0, 0, 0)); + .and_modify( + |(subset, para_epochs, para_points, ev, iv, mv, ba, bu, _)| { + *subset = val.profile.subset.clone(); + *para_epochs += 1; + *para_points += val.auth.para_points(); + *ev += val.para_summary.explicit_votes(); + *iv += val.para_summary.implicit_votes(); + *mv += val.para_summary.missed_votes(); + + if let Some(value) = val.para.get("bitfields") { + if let Ok(bitfields) = + serde_json::from_value::(value.clone()) + { + *ba += bitfields.availability(); + *bu += bitfields.unavailability(); + } + } + }, + ) + .or_insert((Subset::NotDefined, 0, 0, 0, 0, 0, 0, 0, 0)); validators.insert(val.address.clone(), val); } total_epochs += 1; @@ -730,8 +741,10 @@ pub async fn get_validators( // Normalize avg_para_points let avg_para_points: Vec = aggregator_vec .iter() - .filter(|(_, (_, para_epochs, _, _, _, _, _))| *para_epochs >= 1) - .map(|(_, (_, para_epochs, para_points, _, _, _, _))| para_points / para_epochs) + .filter(|(_, (_, para_epochs, _, _, _, _, _, _, _))| *para_epochs >= 1) + .map(|(_, (_, para_epochs, para_points, _, _, _, _, _, _))| { + para_points / para_epochs + }) .collect(); let max = avg_para_points.iter().max().unwrap_or_else(|| &0); let min = avg_para_points.iter().min().unwrap_or_else(|| &0); @@ -741,24 +754,30 @@ pub async fn get_validators( // aggregator_vec .iter_mut() - .filter(|(_, (_, para_epochs, _, _, _, _, _))| *para_epochs >= 1) - .for_each(|(stash, (_, para_epochs, para_points, ev, iv, mv, s))| { - let mvr = *mv as f64 / (*ev + *iv + *mv) as f64; - let avg_para_points = *para_points / *para_epochs; - let score = if max - min > 0 { - (((1.0_f64 - mvr) * 0.75_f64 - + ((avg_para_points - *min) as f64 / (*max - *min) as f64) * 0.18_f64 - + (*para_epochs as f64 / total_epochs as f64) * 0.07_f64) - * 1000000.0_f64) as u32 - } else { - 0 - }; - *s = score; - // Add ranking stats to the validator result - validators.entry(stash.clone()).and_modify(|v| { - v.ranking = RankingStats::with(score, mvr, avg_para_points, *para_epochs); - }); - }); + .filter(|(_, (_, para_epochs, _, _, _, _, _, _, _))| *para_epochs >= 1) + .for_each( + |(stash, (_, para_epochs, para_points, ev, iv, mv, ba, bu, s))| { + let mvr = *mv as f64 / (*ev + *iv + *mv) as f64; + let bar = *ba as f64 / (*ba + *bu) as f64; + let avg_para_points = *para_points / *para_epochs; + let score = if max - min > 0 { + (((1.0_f64 - mvr) * 0.50_f64 + + bar * 0.25_f64 + + ((avg_para_points - *min) as f64 / (*max - *min) as f64) + * 0.18_f64 + + (*para_epochs as f64 / total_epochs as f64) * 0.07_f64) + * 1000000.0_f64) as u32 + } else { + 0 + }; + *s = score; + // Add ranking stats to the validator result + validators.entry(stash.clone()).and_modify(|v| { + v.ranking = + RankingStats::with(score, mvr, avg_para_points, *para_epochs); + }); + }, + ); // Filter by subset and min para epochs // min_para_epochs = 1 if total_full_epochs < 12; @@ -770,7 +789,7 @@ pub async fn get_validators( let mut i = 0; while i < aggregator_vec.len() { - let (_, (subset, para_epochs, _, _, _, _, _)) = &mut aggregator_vec[i]; + let (_, (subset, para_epochs, _, _, _, _, _, _, _)) = &mut aggregator_vec[i]; if (*subset != params.subset && params.subset != Subset::NotDefined) || *para_epochs < min_para_epochs { @@ -781,8 +800,9 @@ pub async fn get_validators( } // Sort ranking validators by score - aggregator_vec - .sort_by(|(_, (_, _, _, _, _, _, a)), (_, (_, _, _, _, _, _, b))| b.cmp(&a)); + aggregator_vec.sort_by( + |(_, (_, _, _, _, _, _, _, _, a)), (_, (_, _, _, _, _, _, _, _, b))| b.cmp(&a), + ); // Truncate aggregator if params.size > 0 {