use core::fmt;
use crate::{
BlockNumber, RoundNumber, Validator, ValidatorSet, Signature, AggregateSignature,
SignatureScheme, BlockHash, Block, Blockchain, ValidRound, Data, Message, MessageError,
};
#[cfg(test)]
mod tests;
#[derive(Clone)]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[cfg_attr(feature = "alloc", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
pub(crate) enum EquivocatingData<S: Signature, A: AggregateSignature, H: BlockHash> {
Proposal {
first_valid_round: Option<ValidRound<A>>,
first_proposal: H,
second_valid_round: Option<ValidRound<A>>,
second_proposal: H,
},
Prevote {
first_block: Option<H>,
second_block: Option<H>,
},
Precommit {
first_block_and_precommit_signature: Option<(H, S)>,
second_block_and_precommit_signature: Option<(H, S)>,
},
}
impl<S: Signature, A: AggregateSignature, H: fmt::Debug + BlockHash> fmt::Debug
for EquivocatingData<S, A, H>
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Proposal { first_valid_round, first_proposal, second_valid_round, second_proposal } => {
formatter
.debug_struct("EquivocatingData::Proposal")
.field("first_valid_round", first_valid_round)
.field("first_proposal", first_proposal)
.field("second_valid_round", second_valid_round)
.field("second_proposal", second_proposal)
.finish_non_exhaustive()
}
Self::Prevote { first_block, second_block } => formatter
.debug_struct("EquivocatingData::Prevote")
.field("first_block", first_block)
.field("second_block", second_block)
.finish_non_exhaustive(),
Self::Precommit {
first_block_and_precommit_signature,
second_block_and_precommit_signature,
} => formatter
.debug_struct("EquivocatingData::Precommit")
.field(
"first_block",
&first_block_and_precommit_signature.as_ref().map(|(block, _precommit_signature)| block),
)
.field(
"second_block",
&second_block_and_precommit_signature.as_ref().map(|(block, _precommit_signature)| block),
)
.finish_non_exhaustive(),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
#[cfg_attr(test, derive(Debug))]
pub(crate) struct OpaqueBlockHash<'hash>(&'hash [u8]);
impl AsRef<[u8]> for OpaqueBlockHash<'_> {
fn as_ref(&self) -> &[u8] {
self.0
}
}
#[derive(Clone)]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[repr(transparent)]
pub(crate) struct StubBlock<'hash>(&'hash [u8]);
#[cfg(test)]
impl<'hash> StubBlock<'hash> {
pub(crate) fn from(hash: &'hash [u8]) -> Self {
Self(hash)
}
}
impl<'hash> Block for StubBlock<'hash> {
type Hash = OpaqueBlockHash<'hash>;
fn hash(&self) -> Self::Hash {
OpaqueBlockHash(self.0)
}
}
impl<S: Signature, A: AggregateSignature, H: BlockHash> EquivocatingData<S, A, H> {
fn split(&self) -> (Data<S, A, StubBlock<'_>>, Data<S, A, StubBlock<'_>>) {
match self {
EquivocatingData::Proposal {
first_valid_round,
first_proposal,
second_valid_round,
second_proposal,
} => (
Data::Proposal {
valid_round: first_valid_round.clone(),
proposal: StubBlock(first_proposal.as_ref()),
},
Data::Proposal {
valid_round: second_valid_round.clone(),
proposal: StubBlock(second_proposal.as_ref()),
},
),
EquivocatingData::Prevote { first_block, second_block } => (
Data::Prevote { block: first_block.as_ref().map(|hash| OpaqueBlockHash(hash.as_ref())) },
Data::Prevote { block: second_block.as_ref().map(|hash| OpaqueBlockHash(hash.as_ref())) },
),
EquivocatingData::Precommit {
first_block_and_precommit_signature,
second_block_and_precommit_signature,
} => (
Data::Precommit {
block_and_precommit_signature: first_block_and_precommit_signature
.as_ref()
.map(|(block, signature)| (OpaqueBlockHash(block.as_ref()), signature.clone())),
},
Data::Precommit {
block_and_precommit_signature: second_block_and_precommit_signature
.as_ref()
.map(|(block, signature)| (OpaqueBlockHash(block.as_ref()), signature.clone())),
},
),
}
}
}
#[derive(Clone)]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[cfg_attr(feature = "alloc", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
pub(crate) enum Evidence<S: Signature, A: AggregateSignature, H: BlockHash> {
Equivocation { data: EquivocatingData<S, A, H>, first_signature: S, second_signature: S },
InvalidProposal { valid_round: Option<ValidRound<A>>, proposal: H, signature: S },
InvalidPrecommit { block: H, precommit_signature: S, signature: S },
}
impl<S: Signature, A: AggregateSignature, H: fmt::Debug + BlockHash> fmt::Debug
for Evidence<S, A, H>
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Equivocation { data, first_signature: _, second_signature: _ } => {
formatter.debug_struct("Evidence::Equivocation").field("data", data).finish_non_exhaustive()
}
Self::InvalidProposal { valid_round, proposal, signature: _ } => formatter
.debug_struct("Evidence::InvalidProposal")
.field("valid_round", valid_round)
.field("proposal", proposal)
.finish_non_exhaustive(),
Self::InvalidPrecommit { block, precommit_signature: _, signature: _ } => formatter
.debug_struct("Evidence::InvalidPrecommit")
.field("block", block)
.finish_non_exhaustive(),
}
}
}
#[derive(Clone)]
#[cfg_attr(test, derive(PartialEq, Eq))]
#[cfg_attr(feature = "alloc", derive(borsh::BorshSerialize, borsh::BorshDeserialize))]
pub struct SlashReason<S: Signature, A: AggregateSignature, H: BlockHash> {
pub(crate) block_number: BlockNumber,
pub(crate) round_number: RoundNumber,
pub(crate) evidence: Evidence<S, A, H>,
}
pub type SlashReasonFor<B> = SlashReason<
<<B as Blockchain>::SignatureScheme as SignatureScheme>::Signature,
<<B as Blockchain>::SignatureScheme as SignatureScheme>::AggregateSignature,
<<B as Blockchain>::Block as Block>::Hash,
>;
impl<S: Signature, A: AggregateSignature, H: fmt::Debug + BlockHash> fmt::Debug
for SlashReason<S, A, H>
{
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { block_number, round_number, evidence } = self;
formatter
.debug_struct("SlashReason")
.field("block_number", block_number)
.field("round_number", round_number)
.field("evidence", evidence)
.finish()
}
}
#[derive(Clone, Debug)]
pub struct InvalidReason;
impl<S: Signature, A: AggregateSignature, H: BlockHash> SlashReason<S, A, H> {
pub(crate) fn equivocation<V: Validator, B: Block<Hash = H>>(
first_message: Message<V, S, A, B>,
second_message: Message<V, S, A, B>,
) -> Option<Self> {
Some(Self {
block_number: first_message.block_number,
round_number: first_message.round_number,
evidence: Evidence::Equivocation {
data: match (first_message.data, second_message.data) {
(
Data::Proposal { valid_round: first_valid_round, proposal: first_proposal },
Data::Proposal { valid_round: second_valid_round, proposal: second_proposal },
) => EquivocatingData::Proposal {
first_valid_round,
first_proposal: first_proposal.hash(),
second_valid_round,
second_proposal: second_proposal.hash(),
},
(Data::Prevote { block: first_block }, Data::Prevote { block: second_block }) => {
EquivocatingData::Prevote { first_block, second_block }
}
(
Data::Precommit { block_and_precommit_signature: first_block_and_precommit_signature },
Data::Precommit { block_and_precommit_signature: second_block_and_precommit_signature },
) => EquivocatingData::Precommit {
first_block_and_precommit_signature,
second_block_and_precommit_signature,
},
_ => None?,
},
first_signature: first_message.signature,
second_signature: second_message.signature,
},
})
}
pub fn verify<V: Validator>(
&self,
genesis: impl AsRef<[u8]>,
validator_set: &(impl ?Sized + ValidatorSet<Validator = V>),
signature_scheme: &(impl ?Sized
+ SignatureScheme<Validator = V, Signature = S, AggregateSignature = A>),
validator: V,
) -> Result<(), InvalidReason> {
match &self.evidence {
Evidence::Equivocation { data, first_signature, second_signature } => {
let (data1, data2) = data.split();
if data1 == data2 {
Err(InvalidReason)?;
}
for (data, signature) in [(data1, first_signature), (data2, second_signature)] {
let verify_message = |data, signature| {
if !signature_scheme.verify(
&validator,
Message::<V, S, A, _>::signature_message(
genesis.as_ref(),
self.block_number,
self.round_number,
&data,
),
signature,
) {
Err(InvalidReason)?;
}
Ok(())
};
verify_message(data, signature)?;
}
}
Evidence::InvalidProposal { valid_round, proposal, signature } => {
if !matches!(
(Message {
validator,
block_number: self.block_number,
round_number: self.round_number,
data: Data::Proposal {
valid_round: valid_round.clone(),
proposal: StubBlock(proposal.as_ref()),
},
signature: signature.clone(),
})
.static_verificiation(genesis, validator_set, signature_scheme),
Err(MessageError::Invalid(SlashReason { .. }))
) {
Err(InvalidReason)?;
}
}
Evidence::InvalidPrecommit { block, precommit_signature, signature } => {
if !matches!(
(Message::<_, _, _, StubBlock<'_>> {
validator,
block_number: self.block_number,
round_number: self.round_number,
data: Data::Precommit {
block_and_precommit_signature: Some((
StubBlock(block.as_ref()).hash(),
precommit_signature.clone()
)),
},
signature: signature.clone(),
})
.static_verificiation(genesis, validator_set, signature_scheme),
Err(MessageError::Invalid(SlashReason { .. }))
) {
Err(InvalidReason)?;
}
}
}
Ok(())
}
}