use std::cmp::{Ord, Ordering, PartialOrd};
use std::convert::TryFrom;
use bytes::Bytes;
use derive_more::Display;
use serde::{Deserialize, Serialize};
use crate::error::ConsensusError;
use crate::smr::smr_types::{SMRStatus, Step, TriggerType};
use crate::{Codec, DurationConfig};
pub type Address = Bytes;
pub type Hash = Bytes;
pub type Signature = Bytes;
#[derive(Serialize, Deserialize, Clone, Debug, Display, PartialEq, Eq, Hash)]
pub enum VoteType {
#[display(fmt = "Prevote")]
Prevote,
#[display(fmt = "Precommit")]
Precommit,
}
impl From<VoteType> for u8 {
fn from(v: VoteType) -> u8 {
match v {
VoteType::Prevote => 1,
VoteType::Precommit => 2,
}
}
}
impl From<VoteType> for TriggerType {
fn from(v: VoteType) -> TriggerType {
match v {
VoteType::Prevote => TriggerType::PrevoteQC,
VoteType::Precommit => TriggerType::PrecommitQC,
}
}
}
impl From<VoteType> for Step {
fn from(v: VoteType) -> Step {
match v {
VoteType::Prevote => Step::Prevote,
VoteType::Precommit => Step::Precommit,
}
}
}
impl TryFrom<u8> for VoteType {
type Error = ConsensusError;
fn try_from(s: u8) -> Result<Self, Self::Error> {
match s {
1 => Ok(VoteType::Prevote),
2 => Ok(VoteType::Precommit),
_ => Err(ConsensusError::Other("".to_string())),
}
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Display, PartialEq, Eq)]
pub enum OverlordMsg<T: Codec> {
#[display(fmt = "Signed Proposal")]
SignedProposal(SignedProposal<T>),
#[display(fmt = "Signed Vote")]
SignedVote(SignedVote),
#[display(fmt = "Aggregated Vote")]
AggregatedVote(AggregatedVote),
#[display(fmt = "Rich Status")]
RichStatus(Status),
#[display(fmt = "Choke Message")]
SignedChoke(SignedChoke),
#[display(fmt = "Stop Overlord")]
Stop,
#[cfg(test)]
Commit(Commit<T>),
}
impl<T: Codec> OverlordMsg<T> {
pub(crate) fn is_rich_status(&self) -> bool {
matches!(self, OverlordMsg::RichStatus(_))
}
pub(crate) fn get_height(&self) -> u64 {
match self {
OverlordMsg::SignedProposal(sp) => sp.proposal.height,
OverlordMsg::SignedVote(sv) => sv.get_height(),
OverlordMsg::AggregatedVote(av) => av.get_height(),
OverlordMsg::RichStatus(s) => s.height,
OverlordMsg::SignedChoke(sc) => sc.choke.height,
_ => unreachable!(),
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
pub enum UpdateFrom {
PrevoteQC(AggregatedVote),
PrecommitQC(AggregatedVote),
ChokeQC(AggregatedChoke),
}
#[derive(Serialize, Deserialize, Clone, Debug, Display)]
pub enum ViewChangeReason {
#[display(fmt = "Do not receive proposal from network")]
NoProposalFromNetwork,
#[display(fmt = "Do not receive Prevote QC from network")]
NoPrevoteQCFromNetwork,
#[display(fmt = "Do not receive precommit QC from network")]
NoPrecommitQCFromNetwork,
#[display(fmt = "Check the block not pass")]
CheckBlockNotPass,
#[display(fmt = "Update from a higher round prevote QC from {} to {}", _0, _1)]
UpdateFromHigherPrevoteQC(u64, u64),
#[display(fmt = "Update from a higher round precommit QC from {} to {}", _0, _1)]
UpdateFromHigherPrecommitQC(u64, u64),
#[display(fmt = "Update from a higher round choke QC from {} to {}", _0, _1)]
UpdateFromHigherChokeQC(u64, u64),
#[display(fmt = "{:?} votes count is below threshold", _0)]
LeaderReceivedVoteBelowThreshold(VoteType),
#[display(fmt = "other reasons")]
Others,
}
#[derive(Clone, Debug, Display, PartialEq, Eq)]
#[display(fmt = "Signed Proposal {:?}", proposal)]
pub struct SignedProposal<T: Codec> {
pub signature: Bytes,
pub proposal: Proposal<T>,
}
#[derive(Clone, Debug, Display, PartialEq, Eq)]
#[display(fmt = "Proposal height {}, round {}", height, round)]
pub struct Proposal<T: Codec> {
pub height: u64,
pub round: u64,
pub content: T,
pub block_hash: Hash,
pub lock: Option<PoLC>,
pub proposer: Address,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PoLC {
pub lock_round: u64,
pub lock_votes: AggregatedVote,
}
#[derive(Clone, Debug, Display, PartialEq, Eq, Hash)]
#[display(fmt = "Signed vote {:?}", vote)]
pub struct SignedVote {
pub signature: Bytes,
pub vote: Vote,
pub voter: Address,
}
impl PartialOrd for SignedVote {
fn partial_cmp(&self, other: &SignedVote) -> Option<Ordering> {
Some(self.voter.cmp(&other.voter))
}
}
impl Ord for SignedVote {
fn cmp(&self, other: &SignedVote) -> Ordering {
self.voter.cmp(&other.voter)
}
}
impl SignedVote {
pub fn get_height(&self) -> u64 {
self.vote.height
}
pub fn get_round(&self) -> u64 {
self.vote.round
}
pub fn get_hash(&self) -> Hash {
self.vote.block_hash.clone()
}
pub fn is_prevote(&self) -> bool {
self.vote.vote_type == VoteType::Prevote
}
}
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
pub struct AggregatedSignature {
#[serde(with = "super::serde_hex")]
pub signature: Signature,
#[serde(with = "super::serde_hex")]
pub address_bitmap: Bytes,
}
#[derive(Serialize, Deserialize, Clone, Debug, Display, PartialEq, Eq, Hash)]
#[rustfmt::skip]
#[display(fmt = "{:?} aggregated vote height {}, round {}", vote_type, height, round)]
pub struct AggregatedVote {
pub signature: AggregatedSignature,
pub vote_type: VoteType,
pub height: u64,
pub round: u64,
#[serde(with = "super::serde_hex")]
pub block_hash: Hash,
#[serde(with = "super::serde_hex")]
pub leader: Address,
}
impl AggregatedVote {
pub fn get_height(&self) -> u64 {
self.height
}
pub fn get_round(&self) -> u64 {
self.round
}
pub fn is_prevote_qc(&self) -> bool {
self.vote_type == VoteType::Prevote
}
pub fn to_vote(&self) -> Vote {
Vote {
height: self.height,
round: self.round,
vote_type: self.vote_type.clone(),
block_hash: self.block_hash.clone(),
}
}
}
#[derive(Clone, Debug, Display, PartialEq, Eq, Hash)]
#[display(fmt = "{:?} vote height {}, round {}", vote_type, height, round)]
pub struct Vote {
pub height: u64,
pub round: u64,
pub vote_type: VoteType,
pub block_hash: Hash,
}
#[derive(Clone, Debug, Display, PartialEq, Eq)]
#[display(fmt = "Commit height {}", height)]
pub struct Commit<T: Codec> {
pub height: u64,
pub content: T,
pub proof: Proof,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Proof {
pub height: u64,
pub round: u64,
pub block_hash: Hash,
pub signature: AggregatedSignature,
}
#[derive(Clone, Debug, Display, PartialEq, Eq)]
#[display(fmt = "Rich status height {}", height)]
pub struct Status {
pub height: u64,
pub interval: Option<u64>,
pub timer_config: Option<DurationConfig>,
pub authority_list: Vec<Node>,
}
impl From<Status> for SMRStatus {
fn from(s: Status) -> SMRStatus {
SMRStatus {
height: s.height,
new_interval: s.interval,
new_config: s.timer_config,
}
}
}
impl Status {
pub(crate) fn is_consensus_node(&self, address: &Address) -> bool {
self.authority_list
.iter()
.any(|node| node.address == address)
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
pub struct Node {
#[serde(with = "super::serde_hex")]
pub address: Address,
pub propose_weight: u32,
pub vote_weight: u32,
}
impl PartialOrd for Node {
fn partial_cmp(&self, other: &Node) -> Option<Ordering> {
Some(self.address.cmp(&other.address))
}
}
impl Ord for Node {
fn cmp(&self, other: &Node) -> Ordering {
self.address.cmp(&other.address)
}
}
impl Node {
pub fn new(addr: Address) -> Self {
Node {
address: addr,
propose_weight: 1u32,
vote_weight: 1u32,
}
}
pub fn set_propose_weight(&mut self, propose_weight: u32) {
self.propose_weight = propose_weight;
}
pub fn set_vote_weight(&mut self, vote_weight: u32) {
self.vote_weight = vote_weight;
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct VerifyResp {
pub(crate) height: u64,
pub(crate) round: u64,
pub(crate) block_hash: Hash,
pub(crate) is_pass: bool,
}
#[derive(Serialize, Deserialize, Clone, Debug, Hash, PartialEq, Eq)]
pub struct AggregatedChoke {
pub height: u64,
pub round: u64,
#[serde(with = "super::serde_hex")]
pub signature: Signature,
#[serde(with = "super::serde_multi_hex")]
pub voters: Vec<Address>,
}
#[allow(clippy::len_without_is_empty)]
impl AggregatedChoke {
pub(crate) fn len(&self) -> usize {
self.voters.len()
}
pub(crate) fn to_hash(&self) -> HashChoke {
HashChoke {
height: self.height,
round: self.round,
}
}
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct SignedChoke {
pub signature: Signature,
pub choke: Choke,
pub address: Address,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct Choke {
pub height: u64,
pub round: u64,
pub from: UpdateFrom,
}
impl Choke {
pub(crate) fn to_hash(&self) -> HashChoke {
HashChoke {
height: self.height,
round: self.round,
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct HashChoke {
pub(crate) height: u64,
pub(crate) round: u64,
}
#[cfg(test)]
mod test {
use super::*;
use rand::random;
fn gen_address() -> Address {
Address::from((0..32).map(|_| random::<u8>()).collect::<Vec<_>>())
}
fn mock_node() -> Node {
Node::new(gen_address())
}
fn mock_status() -> Status {
Status {
height: random::<u64>(),
interval: None,
timer_config: None,
authority_list: vec![mock_node(), mock_node()],
}
}
#[test]
fn test_consensus_power() {
let status = mock_status();
let consensus_node = status.authority_list[0].address.clone();
let sync_node = gen_address();
assert!(status.is_consensus_node(&consensus_node));
assert!(!status.is_consensus_node(&sync_node));
}
}