use std::fmt::Display;
use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
use sha3::{Digest, Sha3_256};
use utils::TimestampMs;
use utoipa::ToSchema;
use crate::{staking::*, *};
pub type Slot = u64;
pub type View = u64;
#[derive(Clone, Serialize, Deserialize, ToSchema, Debug)]
pub struct ConsensusInfo {
pub slot: Slot,
pub view: View,
pub round_leader: ValidatorPublicKey,
pub last_timestamp: TimestampMs,
pub validators: Vec<ValidatorPublicKey>,
}
#[derive(
Debug,
Serialize,
Deserialize,
Clone,
BorshSerialize,
BorshDeserialize,
PartialEq,
Eq,
Hash,
Ord,
PartialOrd,
)]
pub struct ValidatorCandidacy {
pub peer_address: String,
}
impl Display for SignedByValidator<ValidatorCandidacy> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Pubkey: {}", self.signature.validator)
}
}
#[derive(
Debug,
Clone,
Default,
Serialize,
Deserialize,
BorshSerialize,
BorshDeserialize,
PartialEq,
Eq,
Ord,
PartialOrd,
)]
pub struct ConsensusProposal {
pub slot: Slot,
pub parent_hash: ConsensusProposalHash,
pub cut: Cut,
pub staking_actions: Vec<ConsensusStakingAction>,
pub timestamp: TimestampMs,
}
impl Hashed<ConsensusProposalHash> for ConsensusProposal {
fn hashed(&self) -> ConsensusProposalHash {
let mut hasher = Sha3_256::new();
hasher.update(self.slot.to_le_bytes());
self.cut.iter().for_each(|(lane_id, hash, _, _)| {
lane_id.update_hasher(&mut hasher);
hasher.update(&hash.0);
});
self.staking_actions.iter().for_each(|val| match val {
ConsensusStakingAction::Bond { candidate } => {
hasher.update(&candidate.signature.validator.0)
}
ConsensusStakingAction::PayFeesForDaDi {
lane_id,
cumul_size,
} => {
lane_id.update_hasher(&mut hasher);
hasher.update(cumul_size.0.to_le_bytes())
}
});
hasher.update(self.timestamp.0.to_le_bytes());
hasher.update(&self.parent_hash.0);
ConsensusProposalHash(hasher.finalize().to_vec())
}
}
impl std::hash::Hash for ConsensusProposal {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
Hashed::<ConsensusProposalHash>::hashed(self).hash(state);
}
}
impl Display for ConsensusProposal {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Hash: {}, Parent Hash: {}, Slot: {}, Cut: {}, staking_actions: {:?}",
self.hashed(),
self.parent_hash,
self.slot,
CutDisplay(&self.cut),
self.staking_actions,
)
}
}
impl Display for ConsensusProposalHash {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", hex::encode(&self.0))
}
}
#[derive(
BorshSerialize,
BorshDeserialize,
Debug,
Clone,
Deserialize,
Serialize,
PartialEq,
Eq,
Ord,
PartialOrd,
)]
pub enum ConsensusStakingAction {
Bond {
candidate: Box<SignedByValidator<ValidatorCandidacy>>,
},
PayFeesForDaDi {
lane_id: LaneId,
cumul_size: LaneBytesSize,
},
}
impl From<SignedByValidator<ValidatorCandidacy>> for ConsensusStakingAction {
fn from(val: SignedByValidator<ValidatorCandidacy>) -> Self {
ConsensusStakingAction::Bond {
candidate: Box::new(val),
}
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_consensus_proposal_hash() {
use super::*;
let proposal = ConsensusProposal {
slot: 1,
cut: Cut::default(),
staking_actions: vec![],
timestamp: TimestampMs(1),
parent_hash: b"".into(),
};
let hash = proposal.hashed();
assert_eq!(hash.0.len(), 32);
}
#[test]
fn test_consensus_proposal_hash_all_items() {
use super::*;
let mut a = ConsensusProposal {
slot: 1,
cut: vec![(
LaneId::new(ValidatorPublicKey(vec![1])),
b"propA".into(),
LaneBytesSize(1),
AggregateSignature::default(),
)],
staking_actions: vec![SignedByValidator::<ValidatorCandidacy> {
msg: ValidatorCandidacy {
peer_address: "peer".to_string(),
},
signature: ValidatorSignature {
signature: Signature(vec![1, 2, 3]),
validator: ValidatorPublicKey(vec![1, 2, 3]),
},
}
.into()],
timestamp: TimestampMs(1),
parent_hash: b"parent".into(),
};
let mut b = ConsensusProposal {
slot: 1,
cut: vec![(
LaneId::new(ValidatorPublicKey(vec![1])),
b"propA".into(),
LaneBytesSize(1),
AggregateSignature {
signature: Signature(vec![1, 2, 3]),
validators: vec![ValidatorPublicKey(vec![1, 2, 3])],
},
)],
staking_actions: vec![SignedByValidator::<ValidatorCandidacy> {
msg: ValidatorCandidacy {
peer_address: "different".to_string(),
},
signature: ValidatorSignature {
signature: Signature(vec![3, 4, 5]),
validator: ValidatorPublicKey(vec![3, 4, 5]),
},
}
.into()],
timestamp: TimestampMs(1),
parent_hash: b"parent".into(),
};
assert_ne!(a.hashed(), b.hashed());
if let ConsensusStakingAction::Bond { candidate: a } =
b.staking_actions.first_mut().unwrap()
{
a.msg.peer_address = "peer".to_string()
}
assert_ne!(a.hashed(), b.hashed());
if let ConsensusStakingAction::Bond { candidate: a } =
b.staking_actions.first_mut().unwrap()
{
a.signature = ValidatorSignature {
signature: Signature(vec![1, 2, 3]),
validator: ValidatorPublicKey(vec![1, 2, 3]),
};
}
assert_eq!(a.hashed(), b.hashed());
a.timestamp = TimestampMs(2);
assert_ne!(a.hashed(), b.hashed());
b.timestamp = TimestampMs(2);
assert_eq!(a.hashed(), b.hashed());
a.slot = 2;
assert_ne!(a.hashed(), b.hashed());
b.slot = 2;
assert_eq!(a.hashed(), b.hashed());
a.parent_hash = b"different".into();
assert_ne!(a.hashed(), b.hashed());
b.parent_hash = b"different".into();
assert_eq!(a.hashed(), b.hashed());
}
}