From 0e6f5d9234034fe5fe707a3393e46b798be192ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kami=C5=84ski?= Date: Fri, 15 Sep 2023 14:50:46 +0200 Subject: [PATCH 1/7] Make some consensus types public --- node/src/components.rs | 2 +- node/src/components/consensus.rs | 10 +- node/src/components/consensus/cl_context.rs | 2 +- node/src/components/consensus/highway_core.rs | 6 +- .../consensus/highway_core/endorsement.rs | 19 ++-- .../consensus/highway_core/evidence.rs | 10 +- .../highway_core/finality_detector.rs | 8 +- .../highway_core/finality_detector/rewards.rs | 8 +- .../consensus/highway_core/highway.rs | 4 +- .../consensus/highway_core/highway/vertex.rs | 100 +++++++++++------ .../consensus/highway_core/state.rs | 106 ++++++++---------- .../consensus/highway_core/state/block.rs | 14 +-- .../consensus/highway_core/state/panorama.rs | 38 +++---- .../consensus/highway_core/state/params.rs | 26 ++--- .../consensus/highway_core/state/unit.rs | 34 +++--- .../components/consensus/leader_sequence.rs | 4 +- node/src/components/consensus/protocols.rs | 4 +- .../components/consensus/protocols/common.rs | 4 +- node/src/components/consensus/utils.rs | 6 +- .../components/consensus/utils/validators.rs | 69 +++++++----- node/src/components/consensus/utils/weight.rs | 5 +- node/src/lib.rs | 2 +- node/src/types/block.rs | 2 +- 23 files changed, 270 insertions(+), 213 deletions(-) diff --git a/node/src/components.rs b/node/src/components.rs index 17c0fbf08a..af24afea45 100644 --- a/node/src/components.rs +++ b/node/src/components.rs @@ -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; diff --git a/node/src/components/consensus.rs b/node/src/components/consensus.rs index 220d640b9e..d9aecaa31f 100644 --- a/node/src/components/consensus.rs +++ b/node/src/components/consensus.rs @@ -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::{ @@ -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}; @@ -123,6 +123,7 @@ 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, @@ -130,6 +131,7 @@ pub struct NewBlockPayload { pub(crate) block_context: BlockContext, } +/// The result of validation of a ProposedBlock. #[derive(DataSize, Debug, From)] pub struct ResolveValidity { era_id: EraId, diff --git a/node/src/components/consensus/cl_context.rs b/node/src/components/consensus/cl_context.rs index 7d8225010f..251f1022dd 100644 --- a/node/src/components/consensus/cl_context.rs +++ b/node/src/components/consensus/cl_context.rs @@ -56,7 +56,7 @@ impl ConsensusValueT for Arc { /// 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; diff --git a/node/src/components/consensus/highway_core.rs b/node/src/components/consensus/highway_core.rs index 311f91b10a..c0cd2524e7 100644 --- a/node/src/components/consensus/highway_core.rs +++ b/node/src/components/consensus/highway_core.rs @@ -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; @@ -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; diff --git a/node/src/components/consensus/highway_core/endorsement.rs b/node/src/components/consensus/highway_core/endorsement.rs index c20bcaadf9..99be3dbcee 100644 --- a/node/src/components/consensus/highway_core/endorsement.rs +++ b/node/src/components/consensus/highway_core/endorsement.rs @@ -24,7 +24,7 @@ pub(crate) enum EndorsementError { serialize = "C::Hash: Serialize", deserialize = "C::Hash: Deserialize<'de>", ))] -pub(crate) struct Endorsement +pub struct Endorsement where C: Context, { @@ -42,7 +42,8 @@ impl Endorsement { } } - pub(crate) fn hash(&self) -> C::Hash { + /// Returns the hash of the endorsement. + pub fn hash(&self) -> C::Hash { ::hash( &bincode::serialize(&(self.unit, self.creator)).expect("serialize endorsement"), ) @@ -74,7 +75,7 @@ mod specimen_support { serialize = "C::Signature: Serialize", deserialize = "C::Signature: Deserialize<'de>", ))] -pub(crate) struct SignedEndorsement +pub struct SignedEndorsement where C: Context, { @@ -92,19 +93,23 @@ impl SignedEndorsement { } } - 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() } } diff --git a/node/src/components/consensus/highway_core/evidence.rs b/node/src/components/consensus/highway_core/evidence.rs index 5667edde00..85fe61bdf2 100644 --- a/node/src/components/consensus/highway_core/evidence.rs +++ b/node/src/components/consensus/highway_core/evidence.rs @@ -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.")] @@ -57,7 +57,7 @@ pub mod relaxed { deserialize = "C::Hash: Deserialize<'de>", ))] #[strum_discriminants(derive(strum::EnumIter))] - pub(crate) enum Evidence + pub enum Evidence where C: Context, { @@ -80,11 +80,11 @@ pub mod relaxed { }, } } -pub(crate) use relaxed::{Evidence, EvidenceDiscriminants}; +pub use relaxed::{Evidence, EvidenceDiscriminants}; impl Evidence { /// 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(), @@ -96,7 +96,7 @@ impl Evidence { /// /// - 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, instance_id: &C::InstanceId, diff --git a/node/src/components/consensus/highway_core/finality_detector.rs b/node/src/components/consensus/highway_core/finality_detector.rs index 6e72b63472..32e4563a29 100644 --- a/node/src/components/consensus/highway_core/finality_detector.rs +++ b/node/src/components/consensus/highway_core/finality_detector.rs @@ -1,3 +1,5 @@ +//! Functions for detecting finality of proposed blocks and calculating rewards. + mod horizon; mod rewards; @@ -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)] @@ -179,7 +185,7 @@ impl FinalityDetector { 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(); diff --git a/node/src/components/consensus/highway_core/finality_detector/rewards.rs b/node/src/components/consensus/highway_core/finality_detector/rewards.rs index 4f2528b2fd..50a592c443 100644 --- a/node/src/components/consensus/highway_core/finality_detector/rewards.rs +++ b/node/src/components/consensus/highway_core/finality_detector/rewards.rs @@ -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(state: &State, bhash: &C::Hash) -> ValidatorMap { +pub fn compute_rewards(state: &State, bhash: &C::Hash) -> ValidatorMap { // 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 @@ -39,7 +39,7 @@ pub(crate) fn compute_rewards(state: &State, bhash: &C::Hash) -> } /// Returns the rewards for finalizing the block with hash `proposal_h`. -fn compute_rewards_for( +pub fn compute_rewards_for( state: &State, panorama: &Panorama, proposal_h: &C::Hash, @@ -113,7 +113,7 @@ fn compute_rewards_for( /// 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. @@ -123,7 +123,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, obs: &'a Observation, r_id: Timestamp, diff --git a/node/src/components/consensus/highway_core/highway.rs b/node/src/components/consensus/highway_core/highway.rs index 75f77397c8..682485463d 100644 --- a/node/src/components/consensus/highway_core/highway.rs +++ b/node/src/components/consensus/highway_core/highway.rs @@ -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, }; diff --git a/node/src/components/consensus/highway_core/highway/vertex.rs b/node/src/components/consensus/highway_core/highway/vertex.rs index c8f38611fd..1be85d8636 100644 --- a/node/src/components/consensus/highway_core/highway/vertex.rs +++ b/node/src/components/consensus/highway_core/highway/vertex.rs @@ -50,13 +50,17 @@ mod relaxed { deserialize = "C::Hash: Deserialize<'de>", ))] #[strum_discriminants(derive(strum::EnumIter))] - pub(crate) enum Dependency + pub enum Dependency where C: Context, { + /// The hash of a unit. Unit(C::Hash), + /// The index of the validator against which evidence is needed. Evidence(ValidatorIndex), + /// The hash of the unit to be endorsed. Endorsement(C::Hash), + /// The ping by a particular validator for a particular timestamp. Ping(ValidatorIndex, Timestamp), } @@ -71,21 +75,25 @@ mod relaxed { deserialize = "C::Hash: Deserialize<'de>", ))] #[strum_discriminants(derive(strum::EnumIter))] - pub(crate) enum Vertex + pub enum Vertex where C: Context, { + /// A signed unit of the consensus DAG. Unit(SignedWireUnit), + /// Evidence of a validator's transgression. Evidence(Evidence), + /// Endorsements for a unit. Endorsements(Endorsements), + /// A ping conveying the activity of its creator. Ping(Ping), } } -pub(crate) use relaxed::{Dependency, DependencyDiscriminants, Vertex, VertexDiscriminants}; +pub use relaxed::{Dependency, DependencyDiscriminants, Vertex, VertexDiscriminants}; impl Dependency { /// Returns whether this identifies a unit, as opposed to other types of vertices. - pub(crate) fn is_unit(&self) -> bool { + pub fn is_unit(&self) -> bool { matches!(self, Dependency::Unit(_)) } } @@ -97,7 +105,7 @@ impl Vertex { /// `C::ConsensusValue` is a transaction, it should be validated first (correct signature, /// structure, gas limit, etc.). If it is a hash of a transaction, the transaction should be /// obtained _and_ validated. Only after that, the vertex can be considered valid. - pub(crate) fn value(&self) -> Option<&C::ConsensusValue> { + pub fn value(&self) -> Option<&C::ConsensusValue> { match self { Vertex::Unit(swunit) => swunit.wire_unit().value.as_ref(), Vertex::Evidence(_) | Vertex::Endorsements(_) | Vertex::Ping(_) => None, @@ -105,7 +113,7 @@ impl Vertex { } /// Returns the unit hash of this vertex (if it is a unit). - pub(crate) fn unit_hash(&self) -> Option { + pub fn unit_hash(&self) -> Option { match self { Vertex::Unit(swunit) => Some(swunit.hash()), Vertex::Evidence(_) | Vertex::Endorsements(_) | Vertex::Ping(_) => None, @@ -113,7 +121,7 @@ impl Vertex { } /// Returns the seq number of this vertex (if it is a unit). - pub(crate) fn unit_seq_number(&self) -> Option { + pub fn unit_seq_number(&self) -> Option { match self { Vertex::Unit(swunit) => Some(swunit.wire_unit().seq_number), _ => None, @@ -121,12 +129,12 @@ impl Vertex { } /// Returns whether this is evidence, as opposed to other types of vertices. - pub(crate) fn is_evidence(&self) -> bool { + pub fn is_evidence(&self) -> bool { matches!(self, Vertex::Evidence(_)) } /// Returns a `Timestamp` provided the vertex is a `Vertex::Unit` or `Vertex::Ping`. - pub(crate) fn timestamp(&self) -> Option { + pub fn timestamp(&self) -> Option { match self { Vertex::Unit(signed_wire_unit) => Some(signed_wire_unit.wire_unit().timestamp), Vertex::Ping(ping) => Some(ping.timestamp()), @@ -134,7 +142,8 @@ impl Vertex { } } - pub(crate) fn creator(&self) -> Option { + /// Returns the creator of this vertex, if one is defined. + pub fn creator(&self) -> Option { match self { Vertex::Unit(signed_wire_unit) => Some(signed_wire_unit.wire_unit().creator), Vertex::Ping(ping) => Some(ping.creator), @@ -142,7 +151,8 @@ impl Vertex { } } - pub(crate) fn id(&self) -> Dependency { + /// Returns the ID of this vertex. + pub fn id(&self) -> Dependency { match self { Vertex::Unit(signed_wire_unit) => Dependency::Unit(signed_wire_unit.hash()), Vertex::Evidence(evidence) => Dependency::Evidence(evidence.perpetrator()), @@ -152,7 +162,7 @@ impl Vertex { } /// Returns a reference to the unit, or `None` if this is not a unit. - pub(crate) fn unit(&self) -> Option<&SignedWireUnit> { + pub fn unit(&self) -> Option<&SignedWireUnit> { match self { Vertex::Unit(signed_wire_unit) => Some(signed_wire_unit), _ => None, @@ -160,7 +170,7 @@ impl Vertex { } /// Returns true whether unit is a proposal. - pub(crate) fn is_proposal(&self) -> bool { + pub fn is_proposal(&self) -> bool { self.value().is_some() } } @@ -293,12 +303,13 @@ mod specimen_support { } } +/// A `WireUnit` together with its hash and a cryptographic signature by its creator. #[derive(DataSize, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] #[serde(bound( serialize = "C::Hash: Serialize", deserialize = "C::Hash: Deserialize<'de>", ))] -pub(crate) struct SignedWireUnit +pub struct SignedWireUnit where C: Context, { @@ -318,17 +329,20 @@ impl SignedWireUnit { } } - pub(crate) fn wire_unit(&self) -> &WireUnit { + /// Returns the inner `WireUnit`. + pub fn wire_unit(&self) -> &WireUnit { self.hashed_wire_unit.wire_unit() } - pub(crate) fn hash(&self) -> C::Hash { + /// Returns this unit's hash. + pub fn hash(&self) -> C::Hash { self.hashed_wire_unit.hash() } } +/// A `WireUnit` together with its hash. #[derive(Clone, DataSize, Debug, Eq, PartialEq, Hash)] -pub(crate) struct HashedWireUnit +pub struct HashedWireUnit where C: Context, { @@ -346,15 +360,18 @@ where Self::new_with_hash(wire_unit, hash) } - pub(crate) fn into_inner(self) -> WireUnit { + /// Returns the inner `WireUnit`. + pub fn into_inner(self) -> WireUnit { self.wire_unit } - pub(crate) fn wire_unit(&self) -> &WireUnit { + /// Returns a reference to the inner `WireUnit`. + pub fn wire_unit(&self) -> &WireUnit { &self.wire_unit } - pub(crate) fn hash(&self) -> C::Hash { + /// Returns this unit's hash. + pub fn hash(&self) -> C::Hash { self.hash } @@ -383,18 +400,26 @@ impl<'de, C: Context> Deserialize<'de> for HashedWireUnit { serialize = "C::Hash: Serialize", deserialize = "C::Hash: Deserialize<'de>", ))] -pub(crate) struct WireUnit +pub struct WireUnit where C: Context, { - pub(crate) panorama: Panorama, - pub(crate) creator: ValidatorIndex, - pub(crate) instance_id: C::InstanceId, - pub(crate) value: Option, - pub(crate) seq_number: u64, - pub(crate) timestamp: Timestamp, - pub(crate) round_exp: u8, - pub(crate) endorsed: BTreeSet, + /// The panorama of cited units. + pub panorama: Panorama, + /// The index of the creator of this unit. + pub creator: ValidatorIndex, + /// The consensus instance ID for which this unit was created. + pub instance_id: C::InstanceId, + /// The consensus value included in the unit, if any. + pub value: Option, + /// The sequence number of this unit in the creator's swimlane. + pub seq_number: u64, + /// Timestamp of when the unit was created. + pub timestamp: Timestamp, + /// The current round exponent of the unit's creator. + pub round_exp: u8, + /// The units this unit endorses. + pub endorsed: BTreeSet, } impl Debug for WireUnit { @@ -427,7 +452,7 @@ impl WireUnit { } /// Returns the creator's previous unit. - pub(crate) fn previous(&self) -> Option<&C::Hash> { + pub fn previous(&self) -> Option<&C::Hash> { self.panorama[self.creator].correct() } @@ -438,17 +463,20 @@ impl WireUnit { } } +/// A set of endorsements for a unit. #[derive(Clone, DataSize, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)] #[serde(bound( serialize = "C::Hash: Serialize", deserialize = "C::Hash: Deserialize<'de>", ))] -pub(crate) struct Endorsements +pub struct Endorsements where C: Context, { - pub(crate) unit: C::Hash, - pub(crate) endorsers: Vec<(ValidatorIndex, C::Signature)>, + /// The endorsed unit. + pub unit: C::Hash, + /// The endorsements for the unit. + pub endorsers: Vec<(ValidatorIndex, C::Signature)>, } impl Endorsements { @@ -479,7 +507,7 @@ impl From> for Endorsements { serialize = "C::Hash: Serialize", deserialize = "C::Hash: Deserialize<'de>", ))] -pub(crate) struct Ping +pub struct Ping where C: Context, { @@ -507,12 +535,12 @@ impl Ping { } /// The creator who signals that it is online. - pub(crate) fn creator(&self) -> ValidatorIndex { + pub fn creator(&self) -> ValidatorIndex { self.creator } /// The timestamp when the ping was created. - pub(crate) fn timestamp(&self) -> Timestamp { + pub fn timestamp(&self) -> Timestamp { self.timestamp } diff --git a/node/src/components/consensus/highway_core/state.rs b/node/src/components/consensus/highway_core/state.rs index 7a54833bc0..6847546b2e 100644 --- a/node/src/components/consensus/highway_core/state.rs +++ b/node/src/components/consensus/highway_core/state.rs @@ -13,7 +13,7 @@ use quanta::Clock; use serde::{Deserialize, Serialize}; pub(crate) use index_panorama::{IndexObservation, IndexPanorama}; -pub(crate) use panorama::{Observation, Panorama}; +pub use panorama::{Observation, Panorama}; pub(super) use unit::Unit; use std::{ @@ -111,7 +111,7 @@ pub(crate) enum UnitError { /// be replaced with `Direct` evidence, which has the same effect but doesn't rely on information /// from other consensus protocol instances. #[derive(Clone, DataSize, Debug, Deserialize, Eq, PartialEq, Hash, Serialize)] -pub(crate) enum Fault +pub enum Fault where C: Context, { @@ -125,7 +125,8 @@ where } impl Fault { - pub(crate) fn evidence(&self) -> Option<&Evidence> { + /// Returns the evidence included in this `Fault`. + pub fn evidence(&self) -> Option<&Evidence> { match self { Fault::Banned | Fault::Indirect => None, Fault::Direct(ev) => Some(ev), @@ -138,8 +139,8 @@ impl Fault { /// Both observers and active validators must instantiate this, pass in all incoming vertices from /// peers, and use a [FinalityDetector](../finality_detector/struct.FinalityDetector.html) to /// determine the outcome of the consensus process. -#[derive(Debug, Clone, DataSize, Serialize)] -pub(crate) struct State +#[derive(Debug, Clone, DataSize, Serialize, Deserialize)] +pub struct State where C: Context, { @@ -198,7 +199,7 @@ impl State { { let weights = ValidatorMap::from(weights.into_iter().map(|w| *w.borrow()).collect_vec()); assert!( - weights.len() > 0, + !weights.is_empty(), "cannot initialize Highway with no validators" ); let mut panorama = Panorama::new(weights.len()); @@ -238,27 +239,27 @@ impl State { } /// Returns the fixed parameters. - pub(crate) fn params(&self) -> &Params { + pub fn params(&self) -> &Params { &self.params } /// Returns the number of validators. - pub(crate) fn validator_count(&self) -> usize { + pub fn validator_count(&self) -> usize { self.weights.len() } /// Returns the `idx`th validator's voting weight. - pub(crate) fn weight(&self, idx: ValidatorIndex) -> Weight { + pub fn weight(&self, idx: ValidatorIndex) -> Weight { self.weights[idx] } /// Returns the map of validator weights. - pub(crate) fn weights(&self) -> &ValidatorMap { + pub fn weights(&self) -> &ValidatorMap { &self.weights } /// Returns the total weight of all validators marked faulty in this panorama. - pub(crate) fn faulty_weight_in(&self, panorama: &Panorama) -> Weight { + pub fn faulty_weight_in(&self, panorama: &Panorama) -> Weight { panorama .iter() .zip(&self.weights) @@ -268,22 +269,22 @@ impl State { } /// Returns the total weight of all known-faulty validators. - pub(crate) fn faulty_weight(&self) -> Weight { + pub fn faulty_weight(&self) -> Weight { self.faulty_weight_in(self.panorama()) } /// Returns the sum of all validators' voting weights. - pub(crate) fn total_weight(&self) -> Weight { + pub fn total_weight(&self) -> Weight { self.leader_sequence.total_weight() } /// Returns evidence against validator nr. `idx`, if present. - pub(crate) fn maybe_evidence(&self, idx: ValidatorIndex) -> Option<&Evidence> { + pub fn maybe_evidence(&self, idx: ValidatorIndex) -> Option<&Evidence> { self.maybe_fault(idx).and_then(Fault::evidence) } /// Returns endorsements for `unit`, if any. - pub(crate) fn maybe_endorsements(&self, unit: &C::Hash) -> Option> { + pub fn maybe_endorsements(&self, unit: &C::Hash) -> Option> { self.endorsements.get(unit).map(|signatures| Endorsements { unit: *unit, endorsers: signatures.iter_some().map(|(i, sig)| (i, *sig)).collect(), @@ -291,12 +292,12 @@ impl State { } /// Returns whether evidence against validator nr. `idx` is known. - pub(crate) fn has_evidence(&self, idx: ValidatorIndex) -> bool { + pub fn has_evidence(&self, idx: ValidatorIndex) -> bool { self.maybe_evidence(idx).is_some() } /// Returns whether we have all endorsements for `unit`. - pub(crate) fn has_all_endorsements>( + pub fn has_all_endorsements>( &self, unit: &C::Hash, v_ids: I, @@ -312,12 +313,12 @@ impl State { /// Returns whether we have seen enough endorsements for the unit. /// Unit is endorsed when it has endorsements from more than 50% of the validators (by weight). - pub(crate) fn is_endorsed(&self, hash: &C::Hash) -> bool { + pub fn is_endorsed(&self, hash: &C::Hash) -> bool { self.endorsements.contains_key(hash) } /// Returns hash of unit that needs to be endorsed. - pub(crate) fn needs_endorsements(&self, unit: &SignedWireUnit) -> Option { + pub fn needs_endorsements(&self, unit: &SignedWireUnit) -> Option { unit.wire_unit() .endorsed .iter() @@ -327,63 +328,63 @@ impl State { /// Returns the timestamp of the last ping or unit received from the validator, or the start /// timestamp if we haven't received anything yet. - pub(crate) fn last_seen(&self, idx: ValidatorIndex) -> Timestamp { + pub fn last_seen(&self, idx: ValidatorIndex) -> Timestamp { self.pings[idx] } /// Marks the given validator as faulty, unless it is already banned or we have direct evidence. - pub(crate) fn mark_faulty(&mut self, idx: ValidatorIndex) { + pub fn mark_faulty(&mut self, idx: ValidatorIndex) { self.panorama[idx] = Observation::Faulty; self.faults.entry(idx).or_insert(Fault::Indirect); } /// Returns the fault type of validator nr. `idx`, if it is known to be faulty. - pub(crate) fn maybe_fault(&self, idx: ValidatorIndex) -> Option<&Fault> { + pub fn maybe_fault(&self, idx: ValidatorIndex) -> Option<&Fault> { self.faults.get(&idx) } /// Returns whether validator nr. `idx` is known to be faulty. - pub(crate) fn is_faulty(&self, idx: ValidatorIndex) -> bool { + pub fn is_faulty(&self, idx: ValidatorIndex) -> bool { self.faults.contains_key(&idx) } /// Returns an iterator over all faulty validators. - pub(crate) fn faulty_validators(&self) -> impl Iterator + '_ { + pub fn faulty_validators(&self) -> impl Iterator + '_ { self.faults.keys().cloned() } /// Returns an iterator over latest unit hashes from honest validators. - pub(crate) fn iter_correct_hashes(&self) -> impl Iterator { + pub fn iter_correct_hashes(&self) -> impl Iterator { self.panorama.iter_correct_hashes() } /// Returns the unit with the given hash, if present. - pub(crate) fn maybe_unit(&self, hash: &C::Hash) -> Option<&Unit> { + pub fn maybe_unit(&self, hash: &C::Hash) -> Option<&Unit> { self.units.get(hash) } /// Returns whether the unit with the given hash is known. - pub(crate) fn has_unit(&self, hash: &C::Hash) -> bool { + pub fn has_unit(&self, hash: &C::Hash) -> bool { self.units.contains_key(hash) } /// Returns the unit with the given hash. Panics if not found. - pub(crate) fn unit(&self, hash: &C::Hash) -> &Unit { + pub fn unit(&self, hash: &C::Hash) -> &Unit { self.maybe_unit(hash).expect("unit hash must exist") } /// Returns the block contained in the unit with the given hash, if present. - pub(crate) fn maybe_block(&self, hash: &C::Hash) -> Option<&Block> { + pub fn maybe_block(&self, hash: &C::Hash) -> Option<&Block> { self.blocks.get(hash) } /// Returns the block contained in the unit with the given hash. Panics if not found. - pub(crate) fn block(&self, hash: &C::Hash) -> &Block { + pub fn block(&self, hash: &C::Hash) -> &Block { self.maybe_block(hash).expect("block hash must exist") } /// Returns the complete protocol state's latest panorama. - pub(crate) fn panorama(&self) -> &Panorama { + pub fn panorama(&self) -> &Panorama { &self.panorama } @@ -394,7 +395,7 @@ impl State { /// validators excluded. This ensures that once the validator set has been decided, correct /// validators' slots never get reassigned to someone else, even if after the fact someone is /// excluded as a leader. - pub(crate) fn leader(&self, timestamp: Timestamp) -> ValidatorIndex { + pub fn leader(&self, timestamp: Timestamp) -> ValidatorIndex { self.leader_sequence.leader(timestamp.millis()) } @@ -497,7 +498,7 @@ impl State { } /// Returns whether this state already includes an endorsement of `uhash` by `vidx`. - pub(crate) fn has_endorsement(&self, uhash: &C::Hash, vidx: ValidatorIndex) -> bool { + pub fn has_endorsement(&self, uhash: &C::Hash, vidx: ValidatorIndex) -> bool { self.endorsements .get(uhash) .map(|vmap| vmap[vidx].is_some()) @@ -515,7 +516,7 @@ impl State { } /// Returns `true` if the latest timestamp we have is older than the given timestamp. - pub(crate) fn has_ping(&self, creator: ValidatorIndex, timestamp: Timestamp) -> bool { + pub fn has_ping(&self, creator: ValidatorIndex, timestamp: Timestamp) -> bool { self.pings .get(creator) .map_or(false, |ping_time| *ping_time >= timestamp) @@ -533,7 +534,7 @@ impl State { /// /// Endorsements must be validated before calling this: The endorsers must exist, the /// signatures must be valid and the endorsed unit must be present in `self.units`. - pub(crate) fn find_conflicting_endorsements( + pub fn find_conflicting_endorsements( &self, endorsements: &Endorsements, instance_id: &C::InstanceId, @@ -616,7 +617,7 @@ impl State { } /// Returns the `SignedWireUnit` with the given hash, if it is present in the state. - pub(crate) fn wire_unit( + pub fn wire_unit( &self, hash: &C::Hash, instance_id: C::InstanceId, @@ -649,7 +650,7 @@ impl State { /// all of its ancestors. At each level the block with the highest score is selected from the /// children of the previously selected block (or from all blocks at height 0), until a block /// is reached that has no children with any votes. - pub(crate) fn fork_choice<'a>(&'a self, pan: &Panorama) -> Option<&'a C::Hash> { + pub fn fork_choice<'a>(&'a self, pan: &Panorama) -> Option<&'a C::Hash> { let start = self.clock.start(); // Collect all correct votes in a `Tallies` map, sorted by height. let to_entry = |(obs, w): (&Observation, &Weight)| { @@ -675,7 +676,7 @@ impl State { /// Returns the ancestor of the block with the given `hash`, on the specified `height`, or /// `None` if the block's height is lower than that. /// NOTE: Panics if used on non-proposal hashes. For those use [`find_ancestor_unit`]. - pub(crate) fn find_ancestor_proposal<'a>( + pub fn find_ancestor_proposal<'a>( &'a self, hash: &'a C::Hash, height: u64, @@ -827,7 +828,7 @@ impl State { /// Returns the hash of the message with the given sequence number from the creator of `hash`, /// or `None` if the sequence number is higher than that of the unit with `hash`. - pub(crate) fn find_in_swimlane<'a>( + pub fn find_in_swimlane<'a>( &'a self, hash: &'a C::Hash, seq_number: u64, @@ -850,7 +851,7 @@ impl State { /// Returns an iterator over units (with hashes) by the same creator, in reverse chronological /// order, starting with the specified unit. Panics if no unit with `uhash` exists. - pub(crate) fn swimlane<'a>( + pub fn swimlane<'a>( &'a self, uhash: &'a C::Hash, ) -> impl Iterator)> { @@ -865,10 +866,7 @@ impl State { /// Returns an iterator over all hashes of ancestors of the block `bhash`, excluding `bhash` /// itself. Panics if `bhash` is not the hash of a known block. - pub(crate) fn ancestor_hashes<'a>( - &'a self, - bhash: &'a C::Hash, - ) -> impl Iterator { + pub fn ancestor_hashes<'a>(&'a self, bhash: &'a C::Hash) -> impl Iterator { let mut next = self.block(bhash).parent(); iter::from_fn(move || { let current = next?; @@ -884,7 +882,7 @@ impl State { } /// Returns the set of units (by hash) that are endorsed and seen from the panorama. - pub(crate) fn seen_endorsed(&self, pan: &Panorama) -> BTreeSet { + pub fn seen_endorsed(&self, pan: &Panorama) -> BTreeSet { if !ENABLE_ENDORSEMENTS { return Default::default(); }; @@ -1052,12 +1050,12 @@ impl State { /// Returns whether the unit with `hash0` sees the one with `hash1` (i.e. `hash0 ≥ hash1`), /// and sees `hash1`'s creator as correct. - pub(crate) fn sees_correct(&self, hash0: &C::Hash, hash1: &C::Hash) -> bool { + pub fn sees_correct(&self, hash0: &C::Hash, hash1: &C::Hash) -> bool { hash0 == hash1 || self.unit(hash0).panorama.sees_correct(self, hash1) } /// Returns whether the unit with `hash0` sees the one with `hash1` (i.e. `hash0 ≥ hash1`). - pub(crate) fn sees(&self, hash0: &C::Hash, hash1: &C::Hash) -> bool { + pub fn sees(&self, hash0: &C::Hash, hash1: &C::Hash) -> bool { hash0 == hash1 || self.unit(hash0).panorama.sees(self, hash1) } @@ -1069,11 +1067,7 @@ impl State { } /// Returns the panorama of the confirmation for the leader unit `uhash`. - pub(crate) fn confirmation_panorama( - &self, - creator: ValidatorIndex, - uhash: &C::Hash, - ) -> Panorama { + pub fn confirmation_panorama(&self, creator: ValidatorIndex, uhash: &C::Hash) -> Panorama { self.valid_panorama(creator, self.inclusive_panorama(uhash)) } @@ -1081,11 +1075,7 @@ impl State { /// to the given one. It is only modified if necessary for validity: /// * Cite `creator`'s previous unit, i.e. don't equivocate. /// * Satisfy the LNC, i.e. don't add new naively cited forks. - pub(crate) fn valid_panorama( - &self, - creator: ValidatorIndex, - mut pan: Panorama, - ) -> Panorama { + pub fn valid_panorama(&self, creator: ValidatorIndex, mut pan: Panorama) -> Panorama { // Make sure the panorama sees the creator's own previous unit. let maybe_prev_uhash = self.panorama()[creator].correct(); if let Some(prev_uhash) = maybe_prev_uhash { @@ -1116,7 +1106,7 @@ impl State { } /// Returns panorama of a unit where latest entry of the creator is that unit's hash. - pub(crate) fn inclusive_panorama(&self, uhash: &C::Hash) -> Panorama { + pub fn inclusive_panorama(&self, uhash: &C::Hash) -> Panorama { let unit = self.unit(uhash); let mut pan = unit.panorama.clone(); pan[unit.creator] = Observation::Correct(*uhash); diff --git a/node/src/components/consensus/highway_core/state/block.rs b/node/src/components/consensus/highway_core/state/block.rs index 67e9d06736..8e58fedd4f 100644 --- a/node/src/components/consensus/highway_core/state/block.rs +++ b/node/src/components/consensus/highway_core/state/block.rs @@ -1,24 +1,24 @@ use datasize::DataSize; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use super::State; use crate::components::consensus::traits::Context; /// A block: Chains of blocks are the consensus values in the CBC Casper sense. -#[derive(Clone, DataSize, Debug, Eq, PartialEq, Serialize)] -pub(crate) struct Block +#[derive(Clone, DataSize, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct Block where C: Context, { /// The total number of ancestors, i.e. the height in the blockchain. - pub(crate) height: u64, + pub height: u64, /// The payload, e.g. a list of transactions. - pub(crate) value: C::ConsensusValue, + pub value: C::ConsensusValue, /// A skip list index of the block's ancestors. /// /// For every `p = 1 << i` that divides `height`, this contains an `i`-th entry pointing to the /// ancestor with `height - p`. - pub(crate) skip_idx: Vec, + pub skip_idx: Vec, } impl Block { @@ -47,7 +47,7 @@ impl Block { } /// Returns the block's parent, or `None` if it has height 0. - pub(crate) fn parent(&self) -> Option<&C::Hash> { + pub fn parent(&self) -> Option<&C::Hash> { self.skip_idx.first() } diff --git a/node/src/components/consensus/highway_core/state/panorama.rs b/node/src/components/consensus/highway_core/state/panorama.rs index b1dd31b37c..9d4e7a0277 100644 --- a/node/src/components/consensus/highway_core/state/panorama.rs +++ b/node/src/components/consensus/highway_core/state/panorama.rs @@ -31,7 +31,7 @@ mod relaxed { deserialize = "C::Hash: Deserialize<'de>", ))] #[strum_discriminants(derive(strum::EnumIter))] - pub(crate) enum Observation + pub enum Observation where C: Context, { @@ -43,7 +43,7 @@ mod relaxed { Faulty, } } -pub(crate) use relaxed::{Observation, ObservationDiscriminants}; +pub use relaxed::{Observation, ObservationDiscriminants}; impl Debug for Observation where @@ -60,28 +60,31 @@ where impl Observation { /// Returns the unit hash, if this is a correct observation. - pub(crate) fn correct(&self) -> Option<&C::Hash> { + pub fn correct(&self) -> Option<&C::Hash> { match self { Self::None | Self::Faulty => None, Self::Correct(hash) => Some(hash), } } - pub(crate) fn is_correct(&self) -> bool { + /// Returns `true` if this `Observation` is an `Observation::Correct`. + pub fn is_correct(&self) -> bool { match self { Self::None | Self::Faulty => false, Self::Correct(_) => true, } } - pub(crate) fn is_faulty(&self) -> bool { + /// Returns `true` if this `Observation` is an `Observation::Faulty`. + pub fn is_faulty(&self) -> bool { match self { Self::Faulty => true, Self::None | Self::Correct(_) => false, } } - pub(crate) fn is_none(&self) -> bool { + /// Returns `true` if this `Observation` is an `Observation::None`. + pub fn is_none(&self) -> bool { match self { Self::None => true, Self::Faulty | Self::Correct(_) => false, @@ -110,7 +113,7 @@ impl Observation { } /// The observed behavior of all validators at some point in time. -pub(crate) type Panorama = ValidatorMap>; +pub type Panorama = ValidatorMap>; impl Panorama { /// Creates a new, empty panorama. @@ -119,33 +122,30 @@ impl Panorama { } /// Returns `true` if there is at least one correct observation. - pub(crate) fn has_correct(&self) -> bool { + pub fn has_correct(&self) -> bool { self.iter().any(Observation::is_correct) } /// Returns an iterator over all honest validators' latest units. - pub(crate) fn iter_correct<'a>( - &'a self, - state: &'a State, - ) -> impl Iterator> { + pub fn iter_correct<'a>(&'a self, state: &'a State) -> impl Iterator> { let to_unit = move |vh: &C::Hash| state.unit(vh); self.iter_correct_hashes().map(to_unit) } /// Returns an iterator over all honest validators' latest units' hashes. - pub(crate) fn iter_correct_hashes(&self) -> impl Iterator { + pub fn iter_correct_hashes(&self) -> impl Iterator { self.iter().filter_map(Observation::correct) } /// Returns an iterator over all faulty validators' indices. - pub(crate) fn iter_faulty(&self) -> impl Iterator + '_ { + pub fn iter_faulty(&self) -> impl Iterator + '_ { self.enumerate() .filter(|(_, obs)| obs.is_faulty()) .map(|(i, _)| i) } /// Returns an iterator over all faulty validators' indices. - pub(crate) fn iter_none(&self) -> impl Iterator + '_ { + pub fn iter_none(&self) -> impl Iterator + '_ { self.enumerate() .filter(|(_, obs)| obs.is_none()) .map(|(i, _)| i) @@ -160,7 +160,7 @@ impl Panorama { } /// Returns `true` if `self` sees the creator of `hash` as correct, and sees that unit. - pub(crate) fn sees_correct(&self, state: &State, hash: &C::Hash) -> bool { + pub fn sees_correct(&self, state: &State, hash: &C::Hash) -> bool { let unit = state.unit(hash); let can_see = |latest_hash: &C::Hash| { Some(hash) == state.find_in_swimlane(latest_hash, unit.seq_number) @@ -171,7 +171,7 @@ impl Panorama { } /// Returns `true` if `self` sees the unit with the specified `hash`. - pub(crate) fn sees(&self, state: &State, hash_to_be_found: &C::Hash) -> bool { + pub fn sees(&self, state: &State, hash_to_be_found: &C::Hash) -> bool { let unit_to_be_found = state.unit(hash_to_be_found); let mut visited = HashSet::new(); let mut to_visit: Vec<_> = self.iter_correct_hashes().collect(); @@ -208,7 +208,7 @@ impl Panorama { /// Returns the panorama seeing all units seen by `self` with a timestamp no later than /// `timestamp`. Accusations are preserved regardless of the evidence's timestamp. - pub(crate) fn cutoff(&self, state: &State, timestamp: Timestamp) -> Panorama { + pub fn cutoff(&self, state: &State, timestamp: Timestamp) -> Panorama { let obs_cutoff = |obs: &Observation| match obs { Observation::Correct(vhash) => state .swimlane(vhash) @@ -228,7 +228,7 @@ impl Panorama { /// Returns whether `self` can possibly come later in time than `other`, i.e. it can see /// every honest message and every fault seen by `other`. - pub(super) fn geq(&self, state: &State, other: &Panorama) -> bool { + pub fn geq(&self, state: &State, other: &Panorama) -> bool { let mut pairs_iter = self.iter().zip(other); pairs_iter.all(|(obs_self, obs_other)| obs_self.geq(state, obs_other)) } diff --git a/node/src/components/consensus/highway_core/state/params.rs b/node/src/components/consensus/highway_core/state/params.rs index 8a222c4195..a37beb1e51 100644 --- a/node/src/components/consensus/highway_core/state/params.rs +++ b/node/src/components/consensus/highway_core/state/params.rs @@ -1,11 +1,11 @@ use datasize::DataSize; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use super::{TimeDiff, Timestamp}; /// Protocol parameters for Highway. -#[derive(Debug, DataSize, Clone, Serialize)] -pub(crate) struct Params { +#[derive(Debug, DataSize, Clone, Serialize, Deserialize)] +pub struct Params { seed: u64, block_reward: u64, reduced_block_reward: u64, @@ -68,55 +68,55 @@ impl Params { } /// Returns the random seed. - pub(crate) fn seed(&self) -> u64 { + pub fn seed(&self) -> u64 { self.seed } /// Returns the total reward for a finalized block. - pub(crate) fn block_reward(&self) -> u64 { + pub fn block_reward(&self) -> u64 { self.block_reward } /// Returns the reduced block reward that is paid out even if the heaviest summit does not /// exceed half the total weight. This is at most `block_reward`. - pub(crate) fn reduced_block_reward(&self) -> u64 { + pub fn reduced_block_reward(&self) -> u64 { self.reduced_block_reward } /// Returns the minimum round length. This is always greater than 0. - pub(crate) fn min_round_length(&self) -> TimeDiff { + pub fn min_round_length(&self) -> TimeDiff { self.min_round_len } /// Returns the maximum round length. - pub(crate) fn max_round_length(&self) -> TimeDiff { + pub fn max_round_length(&self) -> TimeDiff { self.max_round_len } /// Returns the initial round length. - pub(crate) fn init_round_len(&self) -> TimeDiff { + pub fn init_round_len(&self) -> TimeDiff { self.init_round_len } /// Returns the minimum height of the last block. - pub(crate) fn end_height(&self) -> u64 { + pub fn end_height(&self) -> u64 { self.end_height } /// Returns the start timestamp of the era. - pub(crate) fn start_timestamp(&self) -> Timestamp { + pub fn start_timestamp(&self) -> Timestamp { self.start_timestamp } /// Returns the minimum timestamp of the last block. - pub(crate) fn end_timestamp(&self) -> Timestamp { + pub fn end_timestamp(&self) -> Timestamp { self.end_timestamp } /// Returns the maximum number of additional units included in evidence for conflicting /// endorsements. If you endorse two conflicting forks at sequence numbers that differ by more /// than this, you get away with it and are not marked faulty. - pub(crate) fn endorsement_evidence_limit(&self) -> u64 { + pub fn endorsement_evidence_limit(&self) -> u64 { self.endorsement_evidence_limit } } diff --git a/node/src/components/consensus/highway_core/state/unit.rs b/node/src/components/consensus/highway_core/state/unit.rs index 2dd0e05bd4..775c5cc4c4 100644 --- a/node/src/components/consensus/highway_core/state/unit.rs +++ b/node/src/components/consensus/highway_core/state/unit.rs @@ -1,7 +1,7 @@ use std::collections::BTreeSet; use datasize::DataSize; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use casper_types::{TimeDiff, Timestamp}; @@ -17,39 +17,39 @@ use crate::components::consensus::{ /// A unit sent to or received from the network. /// /// This is only instantiated when it gets added to a `State`, and only once it has been validated. -#[derive(Clone, DataSize, Debug, Eq, PartialEq, Serialize)] -pub(crate) struct Unit +#[derive(Clone, DataSize, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub struct Unit where C: Context, { /// The list of latest units and faults observed by the creator of this message. /// The panorama must be valid, and this unit's creator must not be marked as faulty. - pub(crate) panorama: Panorama, + pub panorama: Panorama, /// The number of earlier messages by the same creator. /// This must be `0` if the creator's entry in the panorama is `None`. Otherwise it must be /// the previous unit's sequence number plus one. - pub(crate) seq_number: u64, + pub seq_number: u64, /// The validator who created and sent this unit. - pub(crate) creator: ValidatorIndex, + pub creator: ValidatorIndex, /// The block this unit votes for. Either it or its parent must be the fork choice. - pub(crate) block: C::Hash, + pub block: C::Hash, /// A skip list index of the creator's swimlane, i.e. the previous unit by the same creator. /// /// For every `p = 1 << i` that divides `seq_number`, this contains an `i`-th entry pointing to /// the older unit with `seq_number - p`. - pub(crate) skip_idx: Vec, + pub skip_idx: Vec, /// This unit's timestamp, in milliseconds since the epoch. This must not be earlier than the /// timestamp of any unit cited in the panorama. - pub(crate) timestamp: Timestamp, + pub timestamp: Timestamp, /// Original signature of the `SignedWireUnit`. - pub(crate) signature: C::Signature, + pub signature: C::Signature, /// The length of the current round, that this message belongs to. /// /// All cited units by `creator` in the same round must have the same round length. - pub(crate) round_len: TimeDiff, + pub round_len: TimeDiff, /// Units that this one claims are endorsed. /// All of these must be cited (directly or indirectly) by the panorama. - pub(crate) endorsed: BTreeSet, + pub endorsed: BTreeSet, } impl Unit { @@ -101,17 +101,17 @@ impl Unit { } /// Returns the creator's previous unit. - pub(crate) fn previous(&self) -> Option<&C::Hash> { + pub fn previous(&self) -> Option<&C::Hash> { self.skip_idx.first() } /// Returns the time at which the round containing this unit began. - pub(crate) fn round_id(&self) -> Timestamp { + pub fn round_id(&self) -> Timestamp { state::round_id(self.timestamp, self.round_len) } /// Returns the length of the round containing this unit. - pub(crate) fn round_len(&self) -> TimeDiff { + pub fn round_len(&self) -> TimeDiff { self.round_len } @@ -120,7 +120,7 @@ impl Unit { /// /// NOTE: Returns `false` if `vidx` is faulty or hasn't produced any units according to the /// creator of `vhash`. - pub(crate) fn new_hash_obs(&self, state: &State, vidx: ValidatorIndex) -> bool { + pub fn new_hash_obs(&self, state: &State, vidx: ValidatorIndex) -> bool { let latest_obs = self.panorama[vidx].correct(); let penultimate_obs = self .previous() @@ -132,7 +132,7 @@ impl Unit { } /// Returns an iterator over units this one claims are endorsed. - pub(crate) fn claims_endorsed(&self) -> impl Iterator { + pub fn claims_endorsed(&self) -> impl Iterator { self.endorsed.iter() } } diff --git a/node/src/components/consensus/leader_sequence.rs b/node/src/components/consensus/leader_sequence.rs index 1b6f29df6b..c45b2bffb2 100644 --- a/node/src/components/consensus/leader_sequence.rs +++ b/node/src/components/consensus/leader_sequence.rs @@ -1,13 +1,13 @@ use datasize::DataSize; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha8Rng; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use tracing::error; use crate::components::consensus::utils::{ValidatorIndex, ValidatorMap, Weight}; /// A pseudorandom sequence of validator indices, distributed by weight. -#[derive(Debug, Clone, DataSize, Serialize)] +#[derive(Debug, Clone, DataSize, Serialize, Deserialize)] pub(crate) struct LeaderSequence { /// Cumulative validator weights: Entry `i` contains the sum of the weights of validators `0` /// through `i`. diff --git a/node/src/components/consensus/protocols.rs b/node/src/components/consensus/protocols.rs index 1f47740d17..dfbf9a6498 100644 --- a/node/src/components/consensus/protocols.rs +++ b/node/src/components/consensus/protocols.rs @@ -1,3 +1,5 @@ -pub(crate) mod common; +//! Implementations of consensus protocols. + +pub mod common; pub(crate) mod highway; pub(crate) mod zug; diff --git a/node/src/components/consensus/protocols/common.rs b/node/src/components/consensus/protocols/common.rs index 4c8e597151..95f7f13e97 100644 --- a/node/src/components/consensus/protocols/common.rs +++ b/node/src/components/consensus/protocols/common.rs @@ -1,3 +1,5 @@ +//! Utilities common to different consensus algorithms. + use itertools::Itertools; use num_rational::Ratio; use std::collections::{BTreeMap, HashSet}; @@ -12,7 +14,7 @@ use casper_types::U512; /// Computes the validator set given the stakes and the faulty and inactive /// reports from the previous eras. -pub(crate) fn validators( +pub fn validators( faulty: &HashSet, inactive: &HashSet, validator_stakes: BTreeMap, diff --git a/node/src/components/consensus/utils.rs b/node/src/components/consensus/utils.rs index a202e259dc..1e71d7833f 100644 --- a/node/src/components/consensus/utils.rs +++ b/node/src/components/consensus/utils.rs @@ -1,5 +1,7 @@ +//! Various utilities relevant to consensus. + mod validators; mod weight; -pub(crate) use validators::{Validator, ValidatorIndex, ValidatorMap, Validators}; -pub(crate) use weight::Weight; +pub use validators::{Validator, ValidatorIndex, ValidatorMap, Validators}; +pub use weight::Weight; diff --git a/node/src/components/consensus/utils/validators.rs b/node/src/components/consensus/utils/validators.rs index 20debb0bdd..50b4175fcf 100644 --- a/node/src/components/consensus/utils/validators.rs +++ b/node/src/components/consensus/utils/validators.rs @@ -20,7 +20,7 @@ use crate::utils::ds; #[derive( Copy, Clone, DataSize, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Serialize, Deserialize, )] -pub(crate) struct ValidatorIndex(pub(crate) u32); +pub struct ValidatorIndex(pub u32); impl From for ValidatorIndex { fn from(idx: u32) -> Self { @@ -30,7 +30,7 @@ impl From for ValidatorIndex { /// Information about a validator: their ID and weight. #[derive(Clone, DataSize, Debug, Eq, PartialEq)] -pub(crate) struct Validator { +pub struct Validator { weight: Weight, id: VID, banned: bool, @@ -49,18 +49,20 @@ impl> From<(VID, W)> for Validator { } impl Validator { - pub(crate) fn id(&self) -> &VID { + /// Returns the validator's ID. + pub fn id(&self) -> &VID { &self.id } - pub(crate) fn weight(&self) -> Weight { + /// Returns the validator's weight. + pub fn weight(&self) -> Weight { self.weight } } /// The validator IDs and weight map. #[derive(Debug, DataSize, Clone)] -pub(crate) struct Validators +pub struct Validators where VID: Eq + Hash, { @@ -70,39 +72,47 @@ where } impl Validators { - pub(crate) fn total_weight(&self) -> Weight { + /// Returns the total weight of the set of validators. + pub fn total_weight(&self) -> Weight { self.total_weight } /// Returns the weight of the validator with the given index. /// /// *Panics* if the validator index does not exist. - pub(crate) fn weight(&self, idx: ValidatorIndex) -> Weight { + pub fn weight(&self, idx: ValidatorIndex) -> Weight { self.validators[idx.0 as usize].weight } + /// Returns `true` if the map is empty. + pub fn is_empty(&self) -> bool { + self.validators.is_empty() + } + /// Returns the number of validators. - pub(crate) fn len(&self) -> usize { + pub fn len(&self) -> usize { self.validators.len() } - pub(crate) fn get_index(&self, id: &VID) -> Option { + /// Gets the index of a validator with the given ID. Returns `None` if no such validator is in + /// the set. + pub fn get_index(&self, id: &VID) -> Option { self.index_by_id.get(id).cloned() } /// Returns validator ID by index, or `None` if it doesn't exist. - pub(crate) fn id(&self, idx: ValidatorIndex) -> Option<&VID> { + pub fn id(&self, idx: ValidatorIndex) -> Option<&VID> { self.validators.get(idx.0 as usize).map(Validator::id) } /// Returns an iterator over all validators, sorted by ID. - pub(crate) fn iter(&self) -> impl Iterator> { + pub fn iter(&self) -> impl Iterator> { self.validators.iter() } /// Marks the validator with that ID as banned, if it exists, and excludes it from the leader /// sequence. - pub(crate) fn ban(&mut self, vid: &VID) { + pub fn ban(&mut self, vid: &VID) { if let Some(idx) = self.get_index(vid) { self.validators[idx.0 as usize].banned = true; self.validators[idx.0 as usize].can_propose = false; @@ -110,14 +120,14 @@ impl Validators { } /// Marks the validator as excluded from the leader sequence. - pub(crate) fn set_cannot_propose(&mut self, vid: &VID) { + pub fn set_cannot_propose(&mut self, vid: &VID) { if let Some(idx) = self.get_index(vid) { self.validators[idx.0 as usize].can_propose = false; } } /// Returns an iterator of all indices of banned validators. - pub(crate) fn iter_banned_idx(&self) -> impl Iterator + '_ { + pub fn iter_banned_idx(&self) -> impl Iterator + '_ { self.iter() .enumerate() .filter(|(_, v)| v.banned) @@ -125,14 +135,15 @@ impl Validators { } /// Returns an iterator of all indices of validators that are not allowed to propose values. - pub(crate) fn iter_cannot_propose_idx(&self) -> impl Iterator + '_ { + pub fn iter_cannot_propose_idx(&self) -> impl Iterator + '_ { self.iter() .enumerate() .filter(|(_, v)| !v.can_propose) .map(|(idx, _)| ValidatorIndex::from(idx as u32)) } - pub(crate) fn enumerate_ids<'a>(&'a self) -> impl Iterator { + /// Returns an iterator of pairs (validator index, validator ID). + pub fn enumerate_ids<'a>(&'a self) -> impl Iterator { let to_idx = |(idx, v): (usize, &'a Validator)| (ValidatorIndex::from(idx as u32), v.id()); self.iter().enumerate().map(to_idx) @@ -191,8 +202,9 @@ impl fmt::Display for Validators { } } +/// A map from the set of validators to some values. #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, AsRef, From, Hash)] -pub(crate) struct ValidatorMap(Vec); +pub struct ValidatorMap(Vec); impl fmt::Display for ValidatorMap> where @@ -227,39 +239,44 @@ where impl ValidatorMap { /// Returns the value for the given validator, or `None` if the index is out of range. - pub(crate) fn get(&self, idx: ValidatorIndex) -> Option<&T> { + pub fn get(&self, idx: ValidatorIndex) -> Option<&T> { self.0.get(idx.0 as usize) } /// Returns the number of values. This must equal the number of validators. - pub(crate) fn len(&self) -> usize { + pub fn len(&self) -> usize { self.0.len() } + /// Returns `true` if this ValidatorMap is empty. + pub fn is_empty(&self) -> bool { + self.0.is_empty() + } + /// Returns an iterator over all values. - pub(crate) fn iter(&self) -> impl Iterator { + pub fn iter(&self) -> impl Iterator { self.0.iter() } /// Returns an iterator over mutable references to all values. - pub(crate) fn iter_mut(&mut self) -> impl Iterator { + pub fn iter_mut(&mut self) -> impl Iterator { self.0.iter_mut() } /// Returns an iterator over all values, by validator index. - pub(crate) fn enumerate(&self) -> impl Iterator { + pub fn enumerate(&self) -> impl Iterator { self.iter() .enumerate() .map(|(idx, value)| (ValidatorIndex(idx as u32), value)) } /// Returns `true` if `self` has an entry for validator number `idx`. - pub(crate) fn has(&self, idx: ValidatorIndex) -> bool { + pub fn has(&self, idx: ValidatorIndex) -> bool { self.0.len() > idx.0 as usize } /// Returns an iterator over all validator indices. - pub(crate) fn keys(&self) -> impl Iterator { + pub fn keys(&self) -> impl Iterator { (0..self.len()).map(|idx| ValidatorIndex(idx as u32)) } @@ -339,12 +356,12 @@ impl> Add> for ValidatorMa impl ValidatorMap> { /// Returns the keys of all validators whose value is `Some`. - pub(crate) fn keys_some(&self) -> impl Iterator + '_ { + pub fn keys_some(&self) -> impl Iterator + '_ { self.iter_some().map(|(vidx, _)| vidx) } /// Returns an iterator over all values that are present, together with their index. - pub(crate) fn iter_some(&self) -> impl Iterator + '_ { + pub fn iter_some(&self) -> impl Iterator + '_ { self.enumerate() .filter_map(|(vidx, opt)| opt.as_ref().map(|val| (vidx, val))) } diff --git a/node/src/components/consensus/utils/weight.rs b/node/src/components/consensus/utils/weight.rs index eb938d9816..6850c9941f 100644 --- a/node/src/components/consensus/utils/weight.rs +++ b/node/src/components/consensus/utils/weight.rs @@ -5,7 +5,7 @@ use std::{ use datasize::DataSize; use derive_more::{Add, AddAssign, From, Sub, SubAssign, Sum}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; /// A vote weight. #[derive( @@ -20,13 +20,14 @@ use serde::Serialize; Ord, Add, Serialize, + Deserialize, Sub, AddAssign, SubAssign, Sum, From, )] -pub(crate) struct Weight(pub(crate) u64); +pub struct Weight(pub u64); impl Weight { /// Checked addition. Returns `None` if overflow occurred. diff --git a/node/src/lib.rs b/node/src/lib.rs index d7938250d1..b4a5e7b21b 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -64,7 +64,7 @@ pub(crate) use components::{ upgrade_watcher::Config as UpgradeWatcherConfig, }; pub use components::{ - contract_runtime, + consensus, contract_runtime, rpc_server::rpcs, storage::{self, Config as StorageConfig}, }; diff --git a/node/src/types/block.rs b/node/src/types/block.rs index c570fe36e0..0c89f354f3 100644 --- a/node/src/types/block.rs +++ b/node/src/types/block.rs @@ -226,7 +226,7 @@ impl From for Error { #[derive( Clone, DataSize, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize, Default, )] -pub(crate) struct BlockPayload { +pub struct BlockPayload { deploys: Vec, transfers: Vec, accusations: Vec, From 9b3f67dcfc5da661d6a315df790f92a4c8a7b369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kami=C5=84ski?= Date: Fri, 6 Oct 2023 13:22:51 +0200 Subject: [PATCH 2/7] Refactor the rewards calculation --- .../highway_core/finality_detector/rewards.rs | 54 +++++++++++++------ 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/node/src/components/consensus/highway_core/finality_detector/rewards.rs b/node/src/components/consensus/highway_core/finality_detector/rewards.rs index 50a592c443..c8939866fe 100644 --- a/node/src/components/consensus/highway_core/finality_detector/rewards.rs +++ b/node/src/components/consensus/highway_core/finality_detector/rewards.rs @@ -38,17 +38,15 @@ pub fn compute_rewards(state: &State, bhash: &C::Hash) -> Validat rewards } -/// Returns the rewards for finalizing the block with hash `proposal_h`. -pub fn compute_rewards_for( - state: &State, - panorama: &Panorama, - proposal_h: &C::Hash, -) -> ValidatorMap { - 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, + panorama: &'c Panorama, + r_id: Timestamp, +) -> (Weight, ValidatorMap>) { + // 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() { @@ -59,13 +57,17 @@ pub fn compute_rewards_for( } 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( + state: &State, + proposal_h: &C::Hash, + latest: &ValidatorMap>, +) -> ValidatorMap { // 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) { @@ -77,6 +79,26 @@ pub fn compute_rewards_for( max_quorum[vidx] = quorum; } } + max_quorum +} + +/// Returns the rewards for finalizing the block with hash `proposal_h`. +pub fn compute_rewards_for( + state: &State, + panorama: &Panorama, + proposal_h: &C::Hash, +) -> ValidatorMap { + 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(); From a43f12f412bec0b3e02e7b88527407f5e1a24565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kami=C5=84ski?= Date: Fri, 6 Oct 2023 15:06:15 +0200 Subject: [PATCH 3/7] Add a tool printing skipped rounds --- Cargo.lock | 14 +++ Cargo.toml | 2 + utils/highway-rewards-analysis/Cargo.toml | 15 +++ utils/highway-rewards-analysis/src/main.rs | 136 +++++++++++++++++++++ 4 files changed, 167 insertions(+) create mode 100644 utils/highway-rewards-analysis/Cargo.toml create mode 100644 utils/highway-rewards-analysis/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index da39047115..8b99c99728 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -827,6 +827,7 @@ checksum = "34d21f9bf1b425d2968943631ec91202fe5e837264063503708b83013f8fc938" dependencies = [ "clap_builder", "clap_derive 4.2.0", + "once_cell", ] [[package]] @@ -2955,6 +2956,19 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" +[[package]] +name = "highway-rewards-analysis" +version = "0.1.0" +dependencies = [ + "bincode", + "casper-hashing", + "casper-node", + "casper-types", + "clap 4.2.7", + "flate2", + "serde", +] + [[package]] name = "hmac" version = "0.12.1" diff --git a/Cargo.toml b/Cargo.toml index 3c4773e543..95eaa0d63d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ members = [ "types", "utils/global-state-update-gen", "utils/validation", + "utils/highway-rewards-analysis", ] default-members = [ @@ -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"] diff --git a/utils/highway-rewards-analysis/Cargo.toml b/utils/highway-rewards-analysis/Cargo.toml new file mode 100644 index 0000000000..91d252b13a --- /dev/null +++ b/utils/highway-rewards-analysis/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "highway-rewards-analysis" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bincode = "1" +clap = { version = "4", features = ["derive"] } +casper-hashing = { path = "../../hashing" } +casper-node = { path = "../../node" } +casper-types = { path = "../../types" } +flate2 = "1" +serde = "1" diff --git a/utils/highway-rewards-analysis/src/main.rs b/utils/highway-rewards-analysis/src/main.rs new file mode 100644 index 0000000000..a9d0d6cebe --- /dev/null +++ b/utils/highway-rewards-analysis/src/main.rs @@ -0,0 +1,136 @@ +use std::{ + collections::{BTreeMap, HashSet}, + fs::File, + io::Read, +}; + +use clap::Parser; +use flate2::read::GzDecoder; +use serde::{Deserialize, Serialize}; + +use casper_node::consensus::{ + highway_core::{ + finality_detector::{round_participation, RoundParticipation}, + State, + }, + protocols::common::validators, + utils::Validators, + ClContext, +}; +use casper_types::{EraId, PublicKey, Timestamp, U512}; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + filename: String, + #[arg(short, long)] + verbose: bool, +} + +/// Debug dump of era used for serialization. +#[derive(Debug, Serialize, Deserialize)] +pub(crate) struct EraDump { + /// The era that is being dumped. + pub id: EraId, + + /// The scheduled starting time of this era. + pub start_time: Timestamp, + /// The height of this era's first block. + pub start_height: u64, + + // omitted: pending blocks + /// Validators that have been faulty in any of the recent BONDED_ERAS switch blocks. This + /// includes `new_faulty`. + pub faulty: HashSet, + /// Validators that are excluded from proposing new blocks. + pub cannot_propose: HashSet, + /// Accusations collected in this era so far. + pub accusations: HashSet, + /// The validator weights. + pub validators: BTreeMap, + + /// The state of the highway instance associated with the era. + pub highway_state: State, +} + +fn main() { + let args = Args::parse(); + + let mut data = vec![]; + let mut file = File::open(&args.filename).unwrap(); + + if args.filename.ends_with(".gz") { + let mut gz = GzDecoder::new(file); + gz.read_to_end(&mut data).unwrap(); + } else { + file.read_to_end(&mut data).unwrap(); + } + + let dump: EraDump = bincode::deserialize(&data).unwrap(); + + let validators = + validators::(&dump.faulty, &dump.cannot_propose, dump.validators.clone()); + + find_skipped_rounds(&validators, &dump, args.verbose); +} + +const TOP_TO_PRINT: usize = 10; + +fn round_num(dump: &EraDump, round_id: Timestamp) -> u64 { + let min_round_length = dump.highway_state.params().min_round_length(); + (round_id.millis() - dump.start_time.millis()) / min_round_length.millis() +} + +fn find_skipped_rounds(validators: &Validators, dump: &EraDump, verbose: bool) { + let state = &dump.highway_state; + let highest_block = state.fork_choice(state.panorama()).unwrap(); + let all_blocks = std::iter::once(highest_block).chain(state.ancestor_hashes(highest_block)); + let mut skipped_rounds = vec![vec![]; validators.len()]; + + for block_hash in all_blocks { + let block_unit = state.unit(block_hash); + let round_id = block_unit.timestamp; + for i in 0..validators.len() as u32 { + let observation = state.panorama().get(i.into()).unwrap(); + let round_participation = round_participation(state, observation, round_id); + if matches!(round_participation, RoundParticipation::No) { + skipped_rounds[i as usize].push(round_id); + } + } + } + + for rounds in skipped_rounds.iter_mut() { + rounds.sort(); + } + + let mut num_skipped_rounds: Vec<_> = skipped_rounds + .iter() + .enumerate() + .map(|(i, skipped)| (i as u32, skipped.len())) + .collect(); + num_skipped_rounds.sort_by_key(|(_, len)| *len); + + println!("{} validators who skipped the most rounds:", TOP_TO_PRINT); + for (vid, len) in num_skipped_rounds.iter().rev().take(TOP_TO_PRINT) { + println!( + "{}: skipped {} rounds", + validators.id((*vid).into()).unwrap(), + len + ); + } + + if verbose { + println!(); + for (vid, _) in num_skipped_rounds.iter().rev().take(TOP_TO_PRINT) { + let skipped_rounds: Vec<_> = skipped_rounds[*vid as usize] + .iter() + .map(|rid| format!("{}", round_num(dump, *rid))) + .collect(); + println!( + "{} skipped rounds: {}", + validators.id((*vid).into()).unwrap(), + skipped_rounds.join(", ") + ); + } + } +} From e80e4cfe24128a175e56704dc2c7016d15a1a7f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kami=C5=84ski?= Date: Fri, 6 Oct 2023 15:24:02 +0200 Subject: [PATCH 4/7] Add printing average max quora --- utils/highway-rewards-analysis/src/main.rs | 54 +++++++++++++++++++++- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/utils/highway-rewards-analysis/src/main.rs b/utils/highway-rewards-analysis/src/main.rs index a9d0d6cebe..2a2b962720 100644 --- a/utils/highway-rewards-analysis/src/main.rs +++ b/utils/highway-rewards-analysis/src/main.rs @@ -10,7 +10,10 @@ use serde::{Deserialize, Serialize}; use casper_node::consensus::{ highway_core::{ - finality_detector::{round_participation, RoundParticipation}, + finality_detector::{ + assigned_weight_and_latest_unit, find_max_quora, round_participation, + RoundParticipation, + }, State, }, protocols::common::validators, @@ -72,6 +75,8 @@ fn main() { validators::(&dump.faulty, &dump.cannot_propose, dump.validators.clone()); find_skipped_rounds(&validators, &dump, args.verbose); + + find_lowest_quorum_participation(&validators, &dump); } const TOP_TO_PRINT: usize = 10; @@ -89,7 +94,7 @@ fn find_skipped_rounds(validators: &Validators, dump: &EraDump, verbo for block_hash in all_blocks { let block_unit = state.unit(block_hash); - let round_id = block_unit.timestamp; + let round_id = block_unit.round_id(); for i in 0..validators.len() as u32 { let observation = state.panorama().get(i.into()).unwrap(); let round_participation = round_participation(state, observation, round_id); @@ -133,4 +138,49 @@ fn find_skipped_rounds(validators: &Validators, dump: &EraDump, verbo ); } } + + println!(); +} + +fn find_lowest_quorum_participation(validators: &Validators, dump: &EraDump) { + let state = &dump.highway_state; + let highest_block = state.fork_choice(state.panorama()).unwrap(); + let mut quora_sum = vec![0.0; validators.len()]; + let mut num_rounds = 0; + + let hb_unit = state.unit(highest_block); + for bhash in state.ancestor_hashes(highest_block) { + let proposal_unit = state.unit(bhash); + let r_id = proposal_unit.round_id(); + + let (assigned_weight, latest) = + assigned_weight_and_latest_unit(state, &hb_unit.panorama, r_id); + + let max_quora = find_max_quora(state, bhash, &latest); + let max_quora: Vec = max_quora + .into_iter() + .map(|quorum_w| quorum_w.0 as f32 / assigned_weight.0 as f32 * 100.0) + .collect(); + + for (q_sum, max_q) in quora_sum.iter_mut().zip(&max_quora) { + *q_sum += max_q; + } + num_rounds += 1; + } + + let mut quora_avg: Vec<_> = quora_sum + .into_iter() + .enumerate() + .map(|(vid, q_sum)| (vid as u32, q_sum / num_rounds as f32)) + .collect(); + quora_avg.sort_by(|(_, q_avg1), (_, q_avg2)| q_avg1.partial_cmp(q_avg2).unwrap()); + + println!("{} validators with lowest average max quora:", TOP_TO_PRINT); + for (vid, q_avg) in quora_avg.iter().take(TOP_TO_PRINT) { + println!( + "{}: average max quorum {:3.1}%", + validators.id((*vid).into()).unwrap(), + q_avg + ); + } } From 7b2f7829152149169ba6f4372de51fffb1adb547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kami=C5=84ski?= Date: Fri, 6 Oct 2023 15:42:15 +0200 Subject: [PATCH 5/7] Print faults --- utils/highway-rewards-analysis/src/main.rs | 23 ++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/utils/highway-rewards-analysis/src/main.rs b/utils/highway-rewards-analysis/src/main.rs index 2a2b962720..541c422d2e 100644 --- a/utils/highway-rewards-analysis/src/main.rs +++ b/utils/highway-rewards-analysis/src/main.rs @@ -74,9 +74,24 @@ fn main() { let validators = validators::(&dump.faulty, &dump.cannot_propose, dump.validators.clone()); - find_skipped_rounds(&validators, &dump, args.verbose); + print_faults(&validators, &dump.highway_state); - find_lowest_quorum_participation(&validators, &dump); + print_skipped_rounds(&validators, &dump, args.verbose); + + print_lowest_quorum_participation(&validators, &dump); +} + +fn print_faults(validators: &Validators, state: &State) { + if state.faulty_validators().count() == 0 { + return; + } + + println!("Faults:"); + for vid in state.faulty_validators() { + let fault = state.maybe_fault(vid).unwrap(); + println!("{}: {:?}", validators.id(vid).unwrap(), fault); + } + println!(); } const TOP_TO_PRINT: usize = 10; @@ -86,7 +101,7 @@ fn round_num(dump: &EraDump, round_id: Timestamp) -> u64 { (round_id.millis() - dump.start_time.millis()) / min_round_length.millis() } -fn find_skipped_rounds(validators: &Validators, dump: &EraDump, verbose: bool) { +fn print_skipped_rounds(validators: &Validators, dump: &EraDump, verbose: bool) { let state = &dump.highway_state; let highest_block = state.fork_choice(state.panorama()).unwrap(); let all_blocks = std::iter::once(highest_block).chain(state.ancestor_hashes(highest_block)); @@ -142,7 +157,7 @@ fn find_skipped_rounds(validators: &Validators, dump: &EraDump, verbo println!(); } -fn find_lowest_quorum_participation(validators: &Validators, dump: &EraDump) { +fn print_lowest_quorum_participation(validators: &Validators, dump: &EraDump) { let state = &dump.highway_state; let highest_block = state.fork_choice(state.panorama()).unwrap(); let mut quora_sum = vec![0.0; validators.len()]; From 751619816b9ad361126645d94e53e51d816b3a87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kami=C5=84ski?= Date: Tue, 10 Oct 2023 16:01:38 +0200 Subject: [PATCH 6/7] Add a readme --- utils/highway-rewards-analysis/README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 utils/highway-rewards-analysis/README.md diff --git a/utils/highway-rewards-analysis/README.md b/utils/highway-rewards-analysis/README.md new file mode 100644 index 0000000000..2f95e074df --- /dev/null +++ b/utils/highway-rewards-analysis/README.md @@ -0,0 +1,11 @@ +# Highway State Analyzer + +This tool analyzes a Highway protocol state dump and prints some information that may be helpful in explaining decreased reward amounts. + +Usage: `highway-rewards-analysis [-v] FILE` + +`FILE` should contain a Highway protocol state dump in the Bincode format, either plain or gzip-compressed. The `-v` flag causes the tool to print some additional information (see below). + +The tool prints out 10 nodes that missed the most rounds in the era contained in the dump, as well as 10 nodes with the lowest average maximum level-1 summit quorum. The reward for a given block for a node is proportional to the maximum quorum of a level-1 summit containing that node in the round in which it was proposed - such quora for all the rounds in the era are what is averaged in the latter statistic. + +If the `-v` flag is set, in addition to printing the 10 nodes with the most rounds missed, the tool also prints which rounds were missed by those nodes. From a05115c1b0743d7f66d9e1a4105569da75bd5294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Kami=C5=84ski?= Date: Tue, 10 Oct 2023 17:44:31 +0200 Subject: [PATCH 7/7] Fix a doc issue --- node/src/components/consensus/highway_core/state.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/src/components/consensus/highway_core/state.rs b/node/src/components/consensus/highway_core/state.rs index 6847546b2e..a250140209 100644 --- a/node/src/components/consensus/highway_core/state.rs +++ b/node/src/components/consensus/highway_core/state.rs @@ -675,7 +675,7 @@ impl State { /// Returns the ancestor of the block with the given `hash`, on the specified `height`, or /// `None` if the block's height is lower than that. - /// NOTE: Panics if used on non-proposal hashes. For those use [`find_ancestor_unit`]. + /// NOTE: Panics if used on non-proposal hashes. pub fn find_ancestor_proposal<'a>( &'a self, hash: &'a C::Hash,