use std::{collections::BTreeSet, fmt::Debug};
use datasize::DataSize;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use casper_types::Timestamp;
use crate::components::consensus::{
highway_core::{
endorsement::SignedEndorsement,
highway::{PingError, VertexError},
state::Panorama,
},
traits::{Context, ValidatorSecret},
utils::{ValidatorIndex, Validators},
};
#[allow(clippy::arithmetic_side_effects)]
mod relaxed {
use casper_types::Timestamp;
use datasize::DataSize;
use serde::{Deserialize, Serialize};
use strum::EnumDiscriminants;
use crate::components::consensus::{
highway_core::evidence::Evidence, traits::Context, utils::ValidatorIndex,
};
use super::{Endorsements, Ping, SignedWireUnit};
#[derive(
DataSize,
Clone,
Debug,
Eq,
PartialEq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
EnumDiscriminants,
)]
#[serde(bound(
serialize = "C::Hash: Serialize",
deserialize = "C::Hash: Deserialize<'de>",
))]
#[strum_discriminants(derive(strum::EnumIter))]
pub enum Dependency<C>
where
C: Context,
{
Unit(C::Hash),
Evidence(ValidatorIndex),
Endorsement(C::Hash),
Ping(ValidatorIndex, Timestamp),
}
#[derive(
DataSize, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash, EnumDiscriminants,
)]
#[serde(bound(
serialize = "C::Hash: Serialize",
deserialize = "C::Hash: Deserialize<'de>",
))]
#[strum_discriminants(derive(strum::EnumIter))]
pub enum Vertex<C>
where
C: Context,
{
Unit(SignedWireUnit<C>),
Evidence(Evidence<C>),
Endorsements(Endorsements<C>),
Ping(Ping<C>),
}
}
pub use relaxed::{Dependency, DependencyDiscriminants, Vertex, VertexDiscriminants};
impl<C: Context> Dependency<C> {
pub fn is_unit(&self) -> bool {
matches!(self, Dependency::Unit(_))
}
}
impl<C: Context> Vertex<C> {
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,
}
}
pub fn unit_hash(&self) -> Option<C::Hash> {
match self {
Vertex::Unit(swunit) => Some(swunit.hash()),
Vertex::Evidence(_) | Vertex::Endorsements(_) | Vertex::Ping(_) => None,
}
}
pub fn unit_seq_number(&self) -> Option<u64> {
match self {
Vertex::Unit(swunit) => Some(swunit.wire_unit().seq_number),
_ => None,
}
}
pub fn is_evidence(&self) -> bool {
matches!(self, Vertex::Evidence(_))
}
pub fn timestamp(&self) -> Option<Timestamp> {
match self {
Vertex::Unit(signed_wire_unit) => Some(signed_wire_unit.wire_unit().timestamp),
Vertex::Ping(ping) => Some(ping.timestamp()),
Vertex::Evidence(_) | Vertex::Endorsements(_) => None,
}
}
pub fn creator(&self) -> Option<ValidatorIndex> {
match self {
Vertex::Unit(signed_wire_unit) => Some(signed_wire_unit.wire_unit().creator),
Vertex::Ping(ping) => Some(ping.creator),
Vertex::Evidence(_) | Vertex::Endorsements(_) => None,
}
}
pub fn id(&self) -> Dependency<C> {
match self {
Vertex::Unit(signed_wire_unit) => Dependency::Unit(signed_wire_unit.hash()),
Vertex::Evidence(evidence) => Dependency::Evidence(evidence.perpetrator()),
Vertex::Endorsements(endorsement) => Dependency::Endorsement(endorsement.unit),
Vertex::Ping(ping) => Dependency::Ping(ping.creator(), ping.timestamp()),
}
}
pub fn unit(&self) -> Option<&SignedWireUnit<C>> {
match self {
Vertex::Unit(signed_wire_unit) => Some(signed_wire_unit),
_ => None,
}
}
pub fn is_proposal(&self) -> bool {
self.value().is_some()
}
}
mod specimen_support {
use super::{
Dependency, DependencyDiscriminants, Endorsements, HashedWireUnit, Ping, SignedEndorsement,
SignedWireUnit, Vertex, VertexDiscriminants, WireUnit,
};
use crate::{
components::consensus::ClContext,
utils::specimen::{
btree_set_distinct_from_prop, largest_variant, vec_prop_specimen, Cache,
LargestSpecimen, SizeEstimator,
},
};
impl LargestSpecimen for Vertex<ClContext> {
fn largest_specimen<E: SizeEstimator>(estimator: &E, cache: &mut Cache) -> Self {
largest_variant::<Self, VertexDiscriminants, _, _>(estimator, |variant| match variant {
VertexDiscriminants::Unit => {
Vertex::Unit(LargestSpecimen::largest_specimen(estimator, cache))
}
VertexDiscriminants::Evidence => {
Vertex::Evidence(LargestSpecimen::largest_specimen(estimator, cache))
}
VertexDiscriminants::Endorsements => {
if estimator.parameter_bool("endorsements_enabled") {
Vertex::Endorsements(LargestSpecimen::largest_specimen(estimator, cache))
} else {
Vertex::Ping(LargestSpecimen::largest_specimen(estimator, cache))
}
}
VertexDiscriminants::Ping => {
Vertex::Ping(LargestSpecimen::largest_specimen(estimator, cache))
}
})
}
}
impl LargestSpecimen for Dependency<ClContext> {
fn largest_specimen<E: SizeEstimator>(estimator: &E, cache: &mut Cache) -> Self {
largest_variant::<Self, DependencyDiscriminants, _, _>(estimator, |variant| {
match variant {
DependencyDiscriminants::Unit => {
Dependency::Unit(LargestSpecimen::largest_specimen(estimator, cache))
}
DependencyDiscriminants::Evidence => {
Dependency::Evidence(LargestSpecimen::largest_specimen(estimator, cache))
}
DependencyDiscriminants::Endorsement => {
Dependency::Endorsement(LargestSpecimen::largest_specimen(estimator, cache))
}
DependencyDiscriminants::Ping => Dependency::Ping(
LargestSpecimen::largest_specimen(estimator, cache),
LargestSpecimen::largest_specimen(estimator, cache),
),
}
})
}
}
impl LargestSpecimen for SignedWireUnit<ClContext> {
fn largest_specimen<E: SizeEstimator>(estimator: &E, cache: &mut Cache) -> Self {
SignedWireUnit {
hashed_wire_unit: LargestSpecimen::largest_specimen(estimator, cache),
signature: LargestSpecimen::largest_specimen(estimator, cache),
}
}
}
impl LargestSpecimen for Endorsements<ClContext> {
fn largest_specimen<E: SizeEstimator>(estimator: &E, cache: &mut Cache) -> Self {
Endorsements {
unit: LargestSpecimen::largest_specimen(estimator, cache),
endorsers: if estimator.parameter_bool("endorsements_enabled") {
vec_prop_specimen(estimator, "validator_count", cache)
} else {
Vec::new()
},
}
}
}
impl LargestSpecimen for SignedEndorsement<ClContext> {
fn largest_specimen<E: SizeEstimator>(estimator: &E, cache: &mut Cache) -> Self {
SignedEndorsement::new(
LargestSpecimen::largest_specimen(estimator, cache),
LargestSpecimen::largest_specimen(estimator, cache),
)
}
}
impl LargestSpecimen for Ping<ClContext> {
fn largest_specimen<E: SizeEstimator>(estimator: &E, cache: &mut Cache) -> Self {
Ping {
creator: LargestSpecimen::largest_specimen(estimator, cache),
timestamp: LargestSpecimen::largest_specimen(estimator, cache),
instance_id: LargestSpecimen::largest_specimen(estimator, cache),
signature: LargestSpecimen::largest_specimen(estimator, cache),
}
}
}
impl LargestSpecimen for HashedWireUnit<ClContext> {
fn largest_specimen<E: SizeEstimator>(estimator: &E, cache: &mut Cache) -> Self {
if let Some(item) = cache.get::<Self>() {
return item.clone();
}
let hash = LargestSpecimen::largest_specimen(estimator, cache);
let wire_unit = LargestSpecimen::largest_specimen(estimator, cache);
cache.set(HashedWireUnit { hash, wire_unit }).clone()
}
}
impl LargestSpecimen for WireUnit<ClContext> {
fn largest_specimen<E: SizeEstimator>(estimator: &E, cache: &mut Cache) -> Self {
WireUnit {
panorama: LargestSpecimen::largest_specimen(estimator, cache),
creator: LargestSpecimen::largest_specimen(estimator, cache),
instance_id: LargestSpecimen::largest_specimen(estimator, cache),
value: LargestSpecimen::largest_specimen(estimator, cache),
seq_number: LargestSpecimen::largest_specimen(estimator, cache),
timestamp: LargestSpecimen::largest_specimen(estimator, cache),
round_exp: LargestSpecimen::largest_specimen(estimator, cache),
endorsed: btree_set_distinct_from_prop(estimator, "validator_count", cache),
}
}
}
}
#[derive(DataSize, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
#[serde(bound(
serialize = "C::Hash: Serialize",
deserialize = "C::Hash: Deserialize<'de>",
))]
pub struct SignedWireUnit<C>
where
C: Context,
{
pub(crate) hashed_wire_unit: HashedWireUnit<C>,
pub(crate) signature: C::Signature,
}
impl<C: Context> SignedWireUnit<C> {
pub(crate) fn new(
hashed_wire_unit: HashedWireUnit<C>,
secret_key: &C::ValidatorSecret,
) -> Self {
let signature = secret_key.sign(&hashed_wire_unit.hash);
SignedWireUnit {
hashed_wire_unit,
signature,
}
}
pub fn wire_unit(&self) -> &WireUnit<C> {
self.hashed_wire_unit.wire_unit()
}
pub fn hash(&self) -> C::Hash {
self.hashed_wire_unit.hash()
}
}
#[derive(Clone, DataSize, Debug, Eq, PartialEq, Hash)]
pub struct HashedWireUnit<C>
where
C: Context,
{
hash: C::Hash,
wire_unit: WireUnit<C>,
}
impl<C> HashedWireUnit<C>
where
C: Context,
{
pub(crate) fn new(wire_unit: WireUnit<C>) -> Self {
let hash = wire_unit.compute_hash();
Self::new_with_hash(wire_unit, hash)
}
pub fn into_inner(self) -> WireUnit<C> {
self.wire_unit
}
pub fn wire_unit(&self) -> &WireUnit<C> {
&self.wire_unit
}
pub fn hash(&self) -> C::Hash {
self.hash
}
pub(crate) fn new_with_hash(wire_unit: WireUnit<C>, hash: C::Hash) -> Self {
HashedWireUnit { hash, wire_unit }
}
}
impl<C: Context> Serialize for HashedWireUnit<C> {
fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
self.wire_unit.serialize(serializer)
}
}
impl<'de, C: Context> Deserialize<'de> for HashedWireUnit<C> {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
Ok(HashedWireUnit::new(<_>::deserialize(deserializer)?))
}
}
#[derive(DataSize, Clone, Eq, PartialEq, Serialize, Deserialize, Hash)]
#[serde(bound(
serialize = "C::Hash: Serialize",
deserialize = "C::Hash: Deserialize<'de>",
))]
pub struct WireUnit<C>
where
C: Context,
{
pub panorama: Panorama<C>,
pub creator: ValidatorIndex,
pub instance_id: C::InstanceId,
pub value: Option<C::ConsensusValue>,
pub seq_number: u64,
pub timestamp: Timestamp,
pub round_exp: u8,
pub endorsed: BTreeSet<C::Hash>,
}
impl<C: Context> Debug for WireUnit<C> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
struct Ellipsis;
impl Debug for Ellipsis {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "..")
}
}
f.debug_struct("WireUnit")
.field("value", &self.value.as_ref().map(|_| Ellipsis))
.field("creator.0", &self.creator.0)
.field("instance_id", &self.instance_id)
.field("seq_number", &self.seq_number)
.field("timestamp", &self.timestamp.millis())
.field("panorama", self.panorama.as_ref())
.field("round_exp", &self.round_exp)
.field("endorsed", &self.endorsed)
.finish()
}
}
impl<C: Context> WireUnit<C> {
pub(crate) fn into_hashed(self) -> HashedWireUnit<C> {
HashedWireUnit::new(self)
}
pub fn previous(&self) -> Option<&C::Hash> {
self.panorama[self.creator].correct()
}
fn compute_hash(&self) -> C::Hash {
<C as Context>::hash(&bincode::serialize(self).expect("serialize WireUnit"))
}
}
#[derive(Clone, DataSize, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
#[serde(bound(
serialize = "C::Hash: Serialize",
deserialize = "C::Hash: Deserialize<'de>",
))]
pub struct Endorsements<C>
where
C: Context,
{
pub unit: C::Hash,
pub endorsers: Vec<(ValidatorIndex, C::Signature)>,
}
impl<C: Context> Endorsements<C> {
pub fn unit(&self) -> &C::Hash {
&self.unit
}
pub fn validator_ids(&self) -> impl Iterator<Item = ValidatorIndex> + '_ {
self.endorsers.iter().map(|(v, _)| *v)
}
}
impl<C: Context> From<SignedEndorsement<C>> for Endorsements<C> {
fn from(signed_e: SignedEndorsement<C>) -> Self {
Endorsements {
unit: *signed_e.unit(),
endorsers: vec![(signed_e.validator_idx(), *signed_e.signature())],
}
}
}
#[derive(Clone, DataSize, Debug, Eq, PartialEq, Serialize, Deserialize, Hash)]
#[serde(bound(
serialize = "C::Hash: Serialize",
deserialize = "C::Hash: Deserialize<'de>",
))]
pub struct Ping<C>
where
C: Context,
{
creator: ValidatorIndex,
timestamp: Timestamp,
instance_id: C::InstanceId,
signature: C::Signature,
}
impl<C: Context> Ping<C> {
pub(crate) fn new(
creator: ValidatorIndex,
timestamp: Timestamp,
instance_id: C::InstanceId,
sk: &C::ValidatorSecret,
) -> Self {
let signature = sk.sign(&Self::hash(creator, timestamp, instance_id));
Ping {
creator,
timestamp,
instance_id,
signature,
}
}
pub fn creator(&self) -> ValidatorIndex {
self.creator
}
pub fn timestamp(&self) -> Timestamp {
self.timestamp
}
pub(crate) fn validate(
&self,
validators: &Validators<C::ValidatorId>,
our_instance_id: &C::InstanceId,
) -> Result<(), VertexError> {
let Ping {
creator,
timestamp,
instance_id,
signature,
} = self;
if instance_id != our_instance_id {
return Err(PingError::InstanceId.into());
}
let v_id = validators.id(self.creator).ok_or(PingError::Creator)?;
let hash = Self::hash(*creator, *timestamp, *instance_id);
if !C::verify_signature(&hash, v_id, signature) {
return Err(PingError::Signature.into());
}
Ok(())
}
fn hash(creator: ValidatorIndex, timestamp: Timestamp, instance_id: C::InstanceId) -> C::Hash {
let bytes = bincode::serialize(&(creator, timestamp, instance_id)).expect("serialize Ping");
<C as Context>::hash(&bytes)
}
}