Skip to content

Commit

Permalink
Merge #4327
Browse files Browse the repository at this point in the history
4327: Highway analysis tool r=AlexanderLimonov a=fizyk20

This adds a draft of a tool for analyzing the consensus state. The tool prints faulty validators, validators with the most skipped rounds, and validators with lowest average max quorum (max quorum being a value calculated per round, per validator, depending on how quickly the validator participates in the round, which multiplies the reward earned by that validator).

Closes #4325

Co-authored-by: Bartłomiej Kamiński <bart@casperlabs.io>
  • Loading branch information
casperlabs-bors-ng[bot] and fizyk20 committed Oct 12, 2023
2 parents d9b978b + a05115c commit b997ca2
Show file tree
Hide file tree
Showing 28 changed files with 551 additions and 229 deletions.
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ members = [
"types",
"utils/global-state-update-gen",
"utils/validation",
"utils/highway-rewards-analysis",
]

default-members = [
Expand All @@ -25,6 +26,7 @@ default-members = [
"types",
"utils/global-state-update-gen",
"utils/validation",
"utils/highway-rewards-analysis",
]

exclude = ["utils/nctl/remotes/casper-client-rs"]
Expand Down
2 changes: 1 addition & 1 deletion node/src/components.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@
pub(crate) mod block_accumulator;
pub(crate) mod block_synchronizer;
pub(crate) mod block_validator;
pub(crate) mod consensus;
pub mod consensus;
pub mod contract_runtime;
pub(crate) mod deploy_acceptor;
pub(crate) mod deploy_buffer;
Expand Down
10 changes: 6 additions & 4 deletions node/src/components/consensus.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ mod config;
mod consensus_protocol;
mod era_supervisor;
#[macro_use]
mod highway_core;
pub mod highway_core;
pub(crate) mod error;
mod leader_sequence;
mod metrics;
mod protocols;
pub mod protocols;
#[cfg(test)]
pub(crate) mod tests;
mod traits;
pub(crate) mod utils;
pub mod utils;
mod validator_change;

use std::{
Expand Down Expand Up @@ -56,7 +56,7 @@ use crate::{
use protocols::{highway::HighwayProtocol, zug::Zug};
use traits::Context;

pub(crate) use cl_context::ClContext;
pub use cl_context::ClContext;
pub(crate) use config::{ChainspecConsensusExt, Config};
pub(crate) use consensus_protocol::{BlockContext, EraReport, ProposedBlock};
pub(crate) use era_supervisor::{debug::EraDump, EraSupervisor, SerializedMessage};
Expand Down Expand Up @@ -123,13 +123,15 @@ pub struct TimerId(pub u8);
#[derive(DataSize, Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct ActionId(pub u8);

/// Payload for a block to be proposed.
#[derive(DataSize, Debug, From)]
pub struct NewBlockPayload {
pub(crate) era_id: EraId,
pub(crate) block_payload: Arc<BlockPayload>,
pub(crate) block_context: BlockContext<ClContext>,
}

/// The result of validation of a ProposedBlock.
#[derive(DataSize, Debug, From)]
pub struct ResolveValidity {
era_id: EraId,
Expand Down
2 changes: 1 addition & 1 deletion node/src/components/consensus/cl_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ impl ConsensusValueT for Arc<BlockPayload> {

/// The collection of types used for cryptography, IDs and blocks in the Casper node.
#[derive(Clone, DataSize, Debug, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize)]
pub(crate) struct ClContext;
pub struct ClContext;

impl Context for ClContext {
type ConsensusValue = Arc<BlockPayload>;
Expand Down
6 changes: 3 additions & 3 deletions node/src/components/consensus/highway_core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
mod test_macros;

pub(crate) mod active_validator;
pub(crate) mod finality_detector;
pub(crate) mod highway;
pub mod finality_detector;
pub mod highway;
pub(crate) mod state;
pub(super) mod synchronizer;

Expand All @@ -43,7 +43,7 @@ mod evidence;
#[cfg(test)]
pub(crate) mod highway_testing;

pub(crate) use state::State;
pub use state::{Observation, Panorama, State};

// Enables the endorsement mechanism.
const ENABLE_ENDORSEMENTS: bool = false;
19 changes: 12 additions & 7 deletions node/src/components/consensus/highway_core/endorsement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pub(crate) enum EndorsementError {
serialize = "C::Hash: Serialize",
deserialize = "C::Hash: Deserialize<'de>",
))]
pub(crate) struct Endorsement<C>
pub struct Endorsement<C>
where
C: Context,
{
Expand All @@ -42,7 +42,8 @@ impl<C: Context> Endorsement<C> {
}
}

pub(crate) fn hash(&self) -> C::Hash {
/// Returns the hash of the endorsement.
pub fn hash(&self) -> C::Hash {
<C as Context>::hash(
&bincode::serialize(&(self.unit, self.creator)).expect("serialize endorsement"),
)
Expand Down Expand Up @@ -74,7 +75,7 @@ mod specimen_support {
serialize = "C::Signature: Serialize",
deserialize = "C::Signature: Deserialize<'de>",
))]
pub(crate) struct SignedEndorsement<C>
pub struct SignedEndorsement<C>
where
C: Context,
{
Expand All @@ -92,19 +93,23 @@ impl<C: Context> SignedEndorsement<C> {
}
}

pub(crate) fn unit(&self) -> &C::Hash {
/// Returns the unit being endorsed.
pub fn unit(&self) -> &C::Hash {
&self.endorsement.unit
}

pub(crate) fn validator_idx(&self) -> ValidatorIndex {
/// Returns the creator of the endorsement.
pub fn validator_idx(&self) -> ValidatorIndex {
self.endorsement.creator
}

pub(crate) fn signature(&self) -> &C::Signature {
/// Returns the signature of the endorsement.
pub fn signature(&self) -> &C::Signature {
&self.signature
}

pub(crate) fn hash(&self) -> C::Hash {
/// Returns the hash of the endorsement.
pub fn hash(&self) -> C::Hash {
self.endorsement.hash()
}
}
10 changes: 5 additions & 5 deletions node/src/components/consensus/highway_core/evidence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::components::consensus::{

/// An error due to invalid evidence.
#[derive(Debug, Error, Eq, PartialEq)]
pub(crate) enum EvidenceError {
pub enum EvidenceError {
#[error("The sequence numbers in the equivocating units are different.")]
EquivocationDifferentSeqNumbers,
#[error("The creators in the equivocating units are different.")]
Expand Down Expand Up @@ -57,7 +57,7 @@ pub mod relaxed {
deserialize = "C::Hash: Deserialize<'de>",
))]
#[strum_discriminants(derive(strum::EnumIter))]
pub(crate) enum Evidence<C>
pub enum Evidence<C>
where
C: Context,
{
Expand All @@ -80,11 +80,11 @@ pub mod relaxed {
},
}
}
pub(crate) use relaxed::{Evidence, EvidenceDiscriminants};
pub use relaxed::{Evidence, EvidenceDiscriminants};

impl<C: Context> Evidence<C> {
/// Returns the ID of the faulty validator.
pub(crate) fn perpetrator(&self) -> ValidatorIndex {
pub fn perpetrator(&self) -> ValidatorIndex {
match self {
Evidence::Equivocation(unit1, _) => unit1.wire_unit().creator,
Evidence::Endorsements { endorsement1, .. } => endorsement1.validator_idx(),
Expand All @@ -96,7 +96,7 @@ impl<C: Context> Evidence<C> {
///
/// - For an equivocation, it checks whether the creators, sequence numbers and instance IDs of
/// the two units are the same.
pub(crate) fn validate(
pub fn validate(
&self,
validators: &Validators<C::ValidatorId>,
instance_id: &C::InstanceId,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//! Functions for detecting finality of proposed blocks and calculating rewards.

mod horizon;
mod rewards;

Expand All @@ -18,6 +20,10 @@ use crate::components::consensus::{
utils::{ValidatorIndex, Weight},
};
use horizon::Horizon;
pub use rewards::{
assigned_weight_and_latest_unit, compute_rewards, compute_rewards_for, find_max_quora,
round_participation, RoundParticipation,
};

/// An error returned if the configured fault tolerance has been exceeded.
#[derive(Debug)]
Expand Down Expand Up @@ -179,7 +185,7 @@ impl<C: Context> FinalityDetector<C> {
let state = highway.state();

// Compute the rewards, and replace each validator index with the validator ID.
let rewards = rewards::compute_rewards(state, bhash);
let rewards = compute_rewards(state, bhash);
let rewards_iter = rewards.enumerate();
let rewards = rewards_iter.map(|(vidx, r)| (to_id(vidx), *r)).collect();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use crate::components::consensus::{
/// Returns the map of rewards to be paid out when the block `bhash` gets finalized.
///
/// This is the sum of all rewards for finalization of ancestors of `bhash`, as seen from `bhash`.
pub(crate) fn compute_rewards<C: Context>(state: &State<C>, bhash: &C::Hash) -> ValidatorMap<u64> {
pub fn compute_rewards<C: Context>(state: &State<C>, bhash: &C::Hash) -> ValidatorMap<u64> {
// The unit that introduced the payout block.
let payout_unit = state.unit(bhash);
// The panorama of the payout block: Rewards must only use this panorama, since it defines
Expand All @@ -38,17 +38,15 @@ pub(crate) fn compute_rewards<C: Context>(state: &State<C>, bhash: &C::Hash) ->
rewards
}

/// Returns the rewards for finalizing the block with hash `proposal_h`.
fn compute_rewards_for<C: Context>(
state: &State<C>,
panorama: &Panorama<C>,
proposal_h: &C::Hash,
) -> ValidatorMap<u64> {
let proposal_unit = state.unit(proposal_h);
let r_id = proposal_unit.round_id();

// Only consider messages in round `r_id` for the summit. To compute the assigned weight, we
// also include validators who didn't send a message in that round, but were supposed to.
/// Finds the total assigned weight of a particular round and the latest unit of each validator in
/// that round.
pub fn assigned_weight_and_latest_unit<'a, 'b: 'a, 'c: 'a, C: Context>(
state: &'b State<C>,
panorama: &'c Panorama<C>,
r_id: Timestamp,
) -> (Weight, ValidatorMap<Option<&'a C::Hash>>) {
// To compute the assigned weight, we also include validators who didn't send a message in that
// round, but were supposed to.
let mut assigned_weight = Weight(0);
let mut latest = ValidatorMap::from(vec![None; panorama.len()]);
for (idx, obs) in panorama.enumerate() {
Expand All @@ -59,13 +57,17 @@ fn compute_rewards_for<C: Context>(
}
assigned_weight += state.weight(idx);
}
(assigned_weight, latest)
}

if assigned_weight.is_zero() {
return ValidatorMap::from(vec![0; latest.len()]);
}

/// Finds the maximum quora of level-1 summits in which different validators participated.
pub fn find_max_quora<C: Context>(
state: &State<C>,
proposal_h: &C::Hash,
latest: &ValidatorMap<Option<&C::Hash>>,
) -> ValidatorMap<Weight> {
// Find all level-1 summits. For each validator, store the highest quorum it is a part of.
let horizon = Horizon::level0(proposal_h, state, &latest);
let horizon = Horizon::level0(proposal_h, state, latest);
let (mut committee, _) = horizon.prune_committee(Weight(1), latest.keys_some().collect());
let mut max_quorum = ValidatorMap::from(vec![Weight(0); latest.len()]);
while let Some(quorum) = horizon.committee_quorum(&committee) {
Expand All @@ -77,6 +79,26 @@ fn compute_rewards_for<C: Context>(
max_quorum[vidx] = quorum;
}
}
max_quorum
}

/// Returns the rewards for finalizing the block with hash `proposal_h`.
pub fn compute_rewards_for<C: Context>(
state: &State<C>,
panorama: &Panorama<C>,
proposal_h: &C::Hash,
) -> ValidatorMap<u64> {
let proposal_unit = state.unit(proposal_h);
let r_id = proposal_unit.round_id();

// Only consider messages in round `r_id` for the summit.
let (assigned_weight, latest) = assigned_weight_and_latest_unit(state, panorama, r_id);

if assigned_weight.is_zero() {
return ValidatorMap::from(vec![0; latest.len()]);
}

let max_quorum = find_max_quora(state, proposal_h, &latest);

let faulty_w: Weight = panorama.iter_faulty().map(|vidx| state.weight(vidx)).sum();

Expand Down Expand Up @@ -113,7 +135,7 @@ fn compute_rewards_for<C: Context>(

/// Information about how a validator participated in a particular round.
#[derive(Debug, Eq, PartialEq)]
enum RoundParticipation<'a, C: Context> {
pub enum RoundParticipation<'a, C: Context> {
/// The validator was not assigned: The round ID was not the beginning of one of their rounds.
Unassigned,
/// The validator was assigned but did not create any messages in that round.
Expand All @@ -123,7 +145,7 @@ enum RoundParticipation<'a, C: Context> {
}

/// Returns information about the participation of a validator with `obs` in round `r_id`.
fn round_participation<'a, C: Context>(
pub fn round_participation<'a, C: Context>(
state: &'a State<C>,
obs: &'a Observation<C>,
r_id: Timestamp,
Expand Down
4 changes: 3 additions & 1 deletion node/src/components/consensus/highway_core/highway.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! The implementation of the Highway consensus protocol.

mod vertex;

pub(crate) use crate::components::consensus::highway_core::state::Params;
pub(crate) use vertex::{
pub use vertex::{
Dependency, Endorsements, HashedWireUnit, Ping, SignedWireUnit, Vertex, WireUnit,
};

Expand Down
Loading

0 comments on commit b997ca2

Please sign in to comment.