use crate::{
gossip::{EventHash, PackedEvent},
hash::Hash,
id::{PublicId, SecretId},
key_gen::message::DkgMessage,
network_event::NetworkEvent,
peer_list::{Peer, PeerIndex, PeerList},
serialise, DkgResultWrapper,
};
use serde::{de::Visitor, Deserialize, Deserializer, Serialize, Serializer};
use std::{
cmp::Ordering,
collections::{BTreeMap, BTreeSet},
error::Error,
fmt::{self, Debug, Formatter},
hash::{Hash as StdHash, Hasher},
};
#[serde(bound = "")]
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
pub enum Observation<T: NetworkEvent, P: PublicId> {
Genesis {
group: BTreeSet<P>,
related_info: Vec<u8>,
},
Add {
peer_id: P,
related_info: Vec<u8>,
},
Remove {
peer_id: P,
related_info: Vec<u8>,
},
Accusation {
offender: P,
malice: Malice<T, P>,
},
OpaquePayload(T),
StartDkg(BTreeSet<P>),
DkgResult {
participants: BTreeSet<P>,
dkg_result: DkgResultWrapper,
},
DkgMessage(DkgMessage),
}
impl<T: NetworkEvent, P: PublicId> Observation<T, P> {
pub fn is_opaque(&self) -> bool {
if let Observation::OpaquePayload(_) = *self {
true
} else {
false
}
}
pub fn is_dkg_message(&self) -> bool {
match *self {
Observation::DkgMessage(_) => true,
_ => false,
}
}
pub fn is_internal(&self) -> bool {
match *self {
Observation::DkgMessage(_) | Observation::StartDkg(_) => true,
_ => false,
}
}
pub fn is_dkg_result(&self) -> bool {
match *self {
Observation::DkgResult { .. } => true,
_ => false,
}
}
}
impl<T: NetworkEvent, P: PublicId> Debug for Observation<T, P> {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
match self {
Observation::Genesis { group, .. } => write!(formatter, "Genesis({:?})", group),
Observation::Add { peer_id, .. } => write!(formatter, "Add({:?})", peer_id),
Observation::Remove { peer_id, .. } => write!(formatter, "Remove({:?})", peer_id),
Observation::Accusation { offender, malice } => {
write!(formatter, "Accusation {{ {:?}, {:?} }}", offender, malice)
}
Observation::StartDkg(result) => write!(formatter, "StartDkg({:?})", result),
Observation::DkgResult {
participants,
dkg_result,
} => write!(formatter, "({:?}, {:?})", participants, dkg_result),
Observation::DkgMessage(msg) => write!(formatter, "{:?}", msg),
Observation::OpaquePayload(payload) => {
write!(formatter, "OpaquePayload({:?})", payload)
}
}
}
}
#[serde(bound = "")]
#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize, Debug)]
pub enum Malice<T: NetworkEvent, P: PublicId> {
UnexpectedGenesis(EventHash),
DuplicateVote(EventHash, EventHash),
MissingGenesis(EventHash),
IncorrectGenesis(Box<PackedEvent<T, P>>),
Fork(EventHash),
InvalidAccusation(EventHash),
OtherParentBySameCreator(Box<PackedEvent<T, P>>),
SelfParentByDifferentCreator(Box<PackedEvent<T, P>>),
InvalidRequest(Box<PackedEvent<T, P>>),
InvalidResponse(Box<PackedEvent<T, P>>),
Unprovable(UnprovableMalice),
Accomplice(EventHash, Box<Malice<T, P>>),
}
#[cfg(any(all(test, feature = "mock"), feature = "testing"))]
#[derive(Debug)]
pub(crate) enum MaliceInput {
Fork(String),
InvalidAccusation(String),
}
#[cfg(feature = "malice-detection")]
impl<T: NetworkEvent, P: PublicId> Malice<T, P> {
pub(crate) fn is_provable(&self) -> bool {
match *self {
Malice::Unprovable(_) => false,
_ => true,
}
}
pub(crate) fn single_hash(&self) -> Option<&EventHash> {
match self {
Malice::UnexpectedGenesis(hash)
| Malice::MissingGenesis(hash)
| Malice::Fork(hash)
| Malice::InvalidAccusation(hash)
| Malice::Accomplice(hash, _) => Some(hash),
Malice::DuplicateVote(_, _)
| Malice::IncorrectGenesis(_)
| Malice::OtherParentBySameCreator(_)
| Malice::SelfParentByDifferentCreator(_)
| Malice::InvalidRequest(_)
| Malice::InvalidResponse(_)
| Malice::Unprovable(_) => None,
}
}
pub(crate) fn accused_events_in_graph(&self) -> Vec<&EventHash> {
match self {
Malice::UnexpectedGenesis(hash)
| Malice::MissingGenesis(hash)
| Malice::Fork(hash)
| Malice::InvalidAccusation(hash)
| Malice::Accomplice(hash, _) => vec![hash],
Malice::DuplicateVote(first, second) => vec![first, second],
Malice::IncorrectGenesis(_)
| Malice::OtherParentBySameCreator(_)
| Malice::SelfParentByDifferentCreator(_)
| Malice::InvalidRequest(_)
| Malice::InvalidResponse(_)
| Malice::Unprovable(_) => vec![],
}
}
}
#[derive(Clone, Debug)]
pub enum UnprovableMalice {
Spam,
Unspecified,
}
impl PartialEq for UnprovableMalice {
fn eq(&self, _: &Self) -> bool {
true
}
}
impl Eq for UnprovableMalice {}
impl StdHash for UnprovableMalice {
fn hash<H: Hasher>(&self, _state: &mut H) {}
}
impl PartialOrd for UnprovableMalice {
fn partial_cmp(&self, _: &Self) -> Option<Ordering> {
Some(Ordering::Equal)
}
}
impl Ord for UnprovableMalice {
fn cmp(&self, _: &Self) -> Ordering {
Ordering::Equal
}
}
impl Serialize for UnprovableMalice {
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
s.serialize_unit()
}
}
impl<'a> Deserialize<'a> for UnprovableMalice {
fn deserialize<D: Deserializer<'a>>(deserializer: D) -> Result<Self, D::Error> {
deserializer.deserialize_unit(UnprovableMaliceVisitor)
}
}
struct UnprovableMaliceVisitor;
impl<'a> Visitor<'a> for UnprovableMaliceVisitor {
type Value = UnprovableMalice;
fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
write!(formatter, "UnprovableMalice")
}
fn visit_unit<E: Error>(self) -> Result<Self::Value, E> {
Ok(UnprovableMalice::Unspecified)
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub(crate) struct ObservationHash(pub(crate) Hash);
impl<'a, T: NetworkEvent, P: PublicId> From<&'a Observation<T, P>> for ObservationHash {
fn from(observation: &'a Observation<T, P>) -> Self {
ObservationHash(Hash::from(serialise(observation).as_slice()))
}
}
impl Debug for ObservationHash {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
write!(formatter, "{:?}", self.0)
}
}
#[derive(Debug)]
pub(crate) struct ObservationInfo<T: NetworkEvent, P: PublicId> {
pub(crate) observation: Observation<T, P>,
pub(crate) consensused: bool,
pub(crate) created_by_us: bool,
}
impl<T: NetworkEvent, P: PublicId> ObservationInfo<T, P> {
pub fn new(observation: Observation<T, P>) -> Self {
Self {
observation,
consensused: false,
created_by_us: false,
}
}
}
pub(crate) type ObservationStore<T, P> = BTreeMap<ObservationKey, ObservationInfo<T, P>>;
pub(crate) type ObservationForStore<T, P> = Option<(ObservationKey, ObservationInfo<T, P>)>;
#[derive(Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
pub(crate) enum ObservationKey {
Single(ObservationHash, PeerIndex),
Supermajority(ObservationHash),
}
impl ObservationKey {
pub fn new(hash: ObservationHash, creator: PeerIndex, consensus_mode: ConsensusMode) -> Self {
match consensus_mode {
ConsensusMode::Single => ObservationKey::Single(hash, creator),
ConsensusMode::Supermajority => ObservationKey::Supermajority(hash),
}
}
pub fn hash(&self) -> &ObservationHash {
match *self {
ObservationKey::Single(ref hash, _) => hash,
ObservationKey::Supermajority(ref hash) => hash,
}
}
pub fn consensus_mode(&self) -> ConsensusMode {
match *self {
ObservationKey::Single(..) => ConsensusMode::Single,
ObservationKey::Supermajority(..) => ConsensusMode::Supermajority,
}
}
pub fn peer_index(&self) -> Option<PeerIndex> {
match *self {
ObservationKey::Single(_, peer_index) => Some(peer_index),
ObservationKey::Supermajority(_) => None,
}
}
pub fn consistent_cmp<S: SecretId>(&self, other: &Self, peer_list: &PeerList<S>) -> Ordering {
self.hash().cmp(other.hash()).then_with(|| {
let lhs_peer_id = self
.peer_index()
.and_then(|index| peer_list.get(index))
.map(Peer::id);
let rhs_peer_id = other
.peer_index()
.and_then(|index| peer_list.get(index))
.map(Peer::id);
lhs_peer_id.cmp(&rhs_peer_id)
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ConsensusMode {
Single,
Supermajority,
}
impl ConsensusMode {
pub(crate) fn of<T: NetworkEvent, P: PublicId>(self, observation: &Observation<T, P>) -> Self {
if observation.is_opaque() {
self
} else if observation.is_dkg_message() {
ConsensusMode::Single
} else {
ConsensusMode::Supermajority
}
}
}
pub fn is_more_than_two_thirds(small: usize, large: usize) -> bool {
3 * small > 2 * large
}
#[cfg(any(all(test, feature = "mock"), feature = "dump-graphs"))]
pub(crate) mod snapshot {
use super::*;
use crate::{id::SecretId, peer_list::PeerList};
#[serde(bound = "")]
#[derive(Eq, PartialEq, Debug, Serialize, Deserialize)]
pub(crate) enum ObservationKeySnapshot<P: PublicId> {
Supermajority(ObservationHash),
Single(ObservationHash, P),
}
impl<P: PublicId> ObservationKeySnapshot<P> {
pub fn new<S>(key: &ObservationKey, peer_list: &PeerList<S>) -> Option<Self>
where
S: SecretId<PublicId = P>,
{
match *key {
ObservationKey::Supermajority(hash) => {
Some(ObservationKeySnapshot::Supermajority(hash))
}
ObservationKey::Single(hash, peer_index) => peer_list
.get(peer_index)
.map(|peer| peer.id().clone())
.map(|peer_id| ObservationKeySnapshot::Single(hash, peer_id)),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::{PeerId, Transaction};
#[test]
fn malice_comparison_and_hashing_ignores_unprovable_value() {
let malice1 = Malice::Unprovable::<Transaction, PeerId>(UnprovableMalice::Spam);
let malice2 = Malice::Unprovable::<Transaction, PeerId>(UnprovableMalice::Unspecified);
assert!(malice1 == malice2);
assert!(!(malice1 < malice2));
assert!(!(malice1 > malice2));
assert_eq!(
Hash::from(serialise(&malice1).as_slice()),
Hash::from(serialise(&malice2).as_slice())
);
}
#[test]
fn unprovable_malice_is_deserialisable() {
let before = Malice::Unprovable::<Transaction, PeerId>(UnprovableMalice::Spam);
let serialised = serialise(&before);
let _: Malice<Transaction, PeerId> = bincode::deserialize(&serialised).unwrap();
}
}