Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
7d74264
client/beefy: simplify self_vote logic
acatangiu Dec 21, 2022
c366330
client/beefy: migrate to new state version
acatangiu Dec 21, 2022
a9f067e
client/beefy: detect equivocated votes
acatangiu Dec 22, 2022
4becc37
fix typos
acatangiu Jan 4, 2023
6db1632
sp-beefy: add equivocation primitives
acatangiu Jan 4, 2023
e8f50ac
client/beefy: refactor vote processing
acatangiu Jan 4, 2023
fc35fd5
fix version migration for new rounds struct
acatangiu Jan 5, 2023
800c1d9
client/beefy: track equivocations and create proofs
acatangiu Jan 5, 2023
bbc786a
client/beefy: adjust tests for new voting logic
acatangiu Jan 5, 2023
d6f6c18
sp-beefy: fix commitment ordering and equality
acatangiu Jan 5, 2023
4958928
client/beefy: simplify handle_vote() a bit
acatangiu Jan 5, 2023
acfcb1f
client/beefy: add simple equivocation test
acatangiu Jan 6, 2023
8c0d67b
client/beefy: submit equivocation proof - WIP
acatangiu Jan 9, 2023
7114074
frame/beefy: add equivocation report runtime api - part 1
acatangiu Jan 9, 2023
3219c2f
frame/beefy: report equivocation logic - part 2
acatangiu Jan 10, 2023
9278bf0
frame/beefy: add pluggable Equivocation handler - part 3
acatangiu Jan 10, 2023
db11585
frame/beefy: impl ValidateUnsigned for equivocations reporting
acatangiu Jan 10, 2023
57d6897
client/beefy: submit report equivocation unsigned extrinsic
acatangiu Jan 10, 2023
8a344cc
primitives/beefy: fix tests
acatangiu Jan 10, 2023
f6aa2e0
frame/beefy: add default weights
acatangiu Jan 10, 2023
5cf2560
frame/beefy: fix tests
acatangiu Jan 10, 2023
2c2c5d6
client/beefy: fix tests
acatangiu Jan 10, 2023
93ba33d
Merge branch 'master' of github.com:paritytech/substrate into beefy-e…
acatangiu Jan 16, 2023
9349350
frame/beefy-mmr: fix tests
acatangiu Jan 16, 2023
b5e2c21
frame/beefy: cross-check session index with equivocation report
acatangiu Jan 16, 2023
1eca5b2
sp-beefy: make test Keyring useable in pallet
acatangiu Jan 17, 2023
fccf5c7
frame/beefy: add basic equivocation test
acatangiu Jan 17, 2023
9d68c1d
frame/beefy: test verify equivocation results in slashing
acatangiu Jan 17, 2023
a08b319
frame/beefy: test report_equivocation_old_set
acatangiu Jan 17, 2023
64114b4
frame/beefy: add more equivocation tests
acatangiu Jan 18, 2023
5634805
sp-beefy: fix docs
acatangiu Jan 18, 2023
d8898a5
beefy: simplify equivocations and fix tests
acatangiu Jan 19, 2023
7388c55
client/beefy: address review comments
acatangiu Jan 19, 2023
1f52f4c
Merge remote-tracking branch 'origin/master' into beefy-equivocations
Jan 19, 2023
6ff53e5
frame/beefy: add ValidateUnsigned to test/mock runtime
acatangiu Jan 20, 2023
8f29cdb
Merge branch 'master' of github.com:paritytech/substrate into beefy-e…
acatangiu Jan 24, 2023
ab11674
client/beefy: fixes after merge master
acatangiu Jan 24, 2023
cb2bb8b
Merge branch 'master' of github.com:paritytech/substrate into beefy-e…
acatangiu Feb 6, 2023
711832b
fix missed merge damage
acatangiu Feb 6, 2023
7d62d22
Merge branch 'master' of github.com:paritytech/substrate into beefy-e…
acatangiu Feb 13, 2023
ab975f4
client/beefy: add test for reporting equivocations
acatangiu Feb 13, 2023
f6bfc81
sp-beefy: move test utils to their own file
acatangiu Feb 13, 2023
02e166e
client/beefy: add negative test for equivocation reports
acatangiu Feb 13, 2023
42ef521
sp-beefy: move back MmrRootProvider - used in polkadot-service
acatangiu Feb 13, 2023
282d18f
impl review suggestions
acatangiu Feb 16, 2023
900547f
Merge branch 'master' of github.com:paritytech/substrate into beefy-e…
acatangiu Feb 16, 2023
f070f63
client/beefy: add equivocation metrics
acatangiu Feb 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
client/beefy: track equivocations and create proofs
  • Loading branch information
acatangiu committed Jan 5, 2023
commit 800c1d93e4ec3446f1f7a213231dd6d9268a5681
91 changes: 52 additions & 39 deletions client/beefy/src/round.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

use beefy_primitives::{
crypto::{AuthorityId, Public, Signature},
Commitment, Payload, ValidatorSet, ValidatorSetId, VoteMessage,
Commitment, Equivocation, EquivocationProof, ValidatorSet, ValidatorSetId, VoteMessage,
};
use codec::{Decode, Encode};
use log::debug;
Expand Down Expand Up @@ -59,23 +59,23 @@ pub fn threshold(authorities: usize) -> usize {
authorities - faulty
}

#[derive(Debug, PartialEq)]
pub enum VoteImportResult {
#[derive(Debug)]
pub enum VoteImportResult<B: Block> {
Ok,
RoundConcluded(Vec<Option<Signature>>),
Equivocation(EquivocationProof<NumberFor<B>, Public, Signature>),
Invalid,
Stale,
Equivocation,
OkRoundInProgress,
OkRoundConcluded(Vec<Option<Signature>>),
}

/// Keeps track of all voting rounds (block numbers) within a session.
/// Only round numbers > `best_done` are of interest, all others are considered stale.
///
/// Does not do any validation on votes or signatures, layers above need to handle that (gossip).
#[derive(Debug, Decode, Encode, PartialEq)]
#[derive(Debug, Decode, Encode)]
pub(crate) struct Rounds<B: Block> {
rounds: BTreeMap<Commitment<NumberFor<B>>, RoundTracker>,
equivocations: BTreeMap<(Public, NumberFor<B>), Payload>,
equivocations: BTreeMap<(Public, NumberFor<B>), VoteMessage<NumberFor<B>, Public, Signature>>,
session_start: NumberFor<B>,
validator_set: ValidatorSet<Public>,
mandatory_done: bool,
Expand All @@ -99,7 +99,10 @@ where

pub(crate) fn new_manual(
rounds: BTreeMap<Commitment<NumberFor<B>>, RoundTracker>,
equivocations: BTreeMap<(Public, NumberFor<B>), Payload>,
equivocations: BTreeMap<
(Public, NumberFor<B>),
VoteMessage<NumberFor<B>, Public, Signature>,
>,
session_start: NumberFor<B>,
validator_set: ValidatorSet<Public>,
mandatory_done: bool,
Expand Down Expand Up @@ -131,55 +134,65 @@ where
pub(crate) fn add_vote(
&mut self,
vote: VoteMessage<NumberFor<B>, AuthorityId, Signature>,
) -> VoteImportResult {
) -> VoteImportResult<B> {
let num = vote.commitment.block_number;
let equivocation_key = (vote.id.clone(), num);

if num < self.session_start || Some(num) <= self.best_done {
debug!(target: "beefy", "🥩 received vote for old stale round {:?}, ignoring", num);
VoteImportResult::Stale
return VoteImportResult::Stale
} else if vote.commitment.validator_set_id != self.validator_set_id() {
debug!(
target: "beefy", "🥩 expected set_id {:?}, ignoring vote {:?}.",
self.validator_set_id(), vote,
);
VoteImportResult::Invalid
return VoteImportResult::Invalid
} else if !self.validators().iter().any(|id| &vote.id == id) {
debug!(
target: "beefy",
"🥩 received vote {:?} from validator that is not in the validator set, ignoring",
vote
);
VoteImportResult::Invalid
} else if self
.equivocations
return VoteImportResult::Invalid
}

if let Some(existing_vote) = self.equivocations.get(&equivocation_key) {
// is the same public key voting for a different payload?
.get(&equivocation_key)
.map(|payload| payload != &vote.commitment.payload)
.unwrap_or_else(|| {
// this is the first vote done by `id` for `num`
self.equivocations.insert(equivocation_key, vote.commitment.payload.clone());
// no equivocation
false
}) {
debug!(target: "beefy", "🥩 detected equivocated vote {:?}", vote);
VoteImportResult::Equivocation
if existing_vote.commitment.payload != vote.commitment.payload {
debug!(
target: "beefy", "🥩 detected equivocated vote: 1st: {:?}, 2nd: {:?}",
existing_vote, vote
);
let equivocation = Equivocation {
round_number: equivocation_key.1,
id: equivocation_key.0,
first: existing_vote.clone(),
second: vote,
};
let set_id = self.validator_set_id();
return VoteImportResult::Equivocation(EquivocationProof { set_id, equivocation })
}
} else {
let round = self.rounds.entry(vote.commitment.clone()).or_default();
if round.add_vote((vote.id, vote.signature)) &&
round.is_done(threshold(self.validator_set.len()))
{
if let Some(round) = self.rounds.remove(&vote.commitment) {
let votes = round.votes;
let signatures = self
.validators()
.iter()
.map(|authority_id| votes.get(authority_id).cloned())
.collect();
return VoteImportResult::OkRoundConcluded(signatures)
}
// this is the first vote sent by `id` for `num`, all good
self.equivocations.insert(equivocation_key, vote.clone());
}

// add valid vote
let round = self.rounds.entry(vote.commitment.clone()).or_default();
if round.add_vote((vote.id, vote.signature)) &&
round.is_done(threshold(self.validator_set.len()))
{
if let Some(round) = self.rounds.remove(&vote.commitment) {
let votes = round.votes;
let signatures = self
.validators()
.iter()
.map(|authority_id| votes.get(authority_id).cloned())
.collect();
return VoteImportResult::RoundConcluded(signatures)
}
VoteImportResult::OkRoundInProgress
}
VoteImportResult::Ok
}

pub(crate) fn conclude(&mut self, round_num: NumberFor<B>) {
Expand Down
26 changes: 20 additions & 6 deletions client/beefy/src/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use crate::{
};
use beefy_primitives::{
crypto::{AuthorityId, Signature},
Commitment, ConsensusLog, PayloadProvider, SignedCommitment, ValidatorSet,
Commitment, ConsensusLog, EquivocationProof, PayloadProvider, SignedCommitment, ValidatorSet,
VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID,
};
use codec::{Codec, Decode, Encode};
Expand Down Expand Up @@ -73,7 +73,7 @@ pub(crate) enum RoundAction {
/// Responsible for the voting strategy.
/// It chooses which incoming votes to accept and which votes to generate.
/// Keeps track of voting seen for current and future rounds.
#[derive(Debug, Decode, Encode, PartialEq)]
#[derive(Debug, Decode, Encode)]
pub(crate) struct VoterOracle<B: Block> {
/// Queue of known sessions. Keeps track of voting rounds (block numbers) within each session.
///
Expand Down Expand Up @@ -256,7 +256,7 @@ pub(crate) struct WorkerParams<B: Block, BE, P, N> {
pub persisted_state: PersistedState<B>,
}

#[derive(Debug, Decode, Encode, PartialEq)]
#[derive(Debug, Decode, Encode)]
pub(crate) struct PersistedState<B: Block> {
/// Best block we received a GRANDPA finality for.
best_grandpa_block_header: <B as Block>::Header,
Expand Down Expand Up @@ -549,7 +549,7 @@ where
let round = (vote.commitment.payload.clone(), number);

match rounds.add_vote(vote) {
VoteImportResult::OkRoundConcluded(signatures) => {
VoteImportResult::RoundConcluded(signatures) => {
self.gossip_validator.conclude_round(number);

let commitment = Commitment {
Expand All @@ -568,7 +568,7 @@ where
// New state is persisted after finalization.
self.finalize(finality_proof)?;
},
VoteImportResult::OkRoundInProgress => {
VoteImportResult::Ok => {
let mandatory_round = self
.voting_oracle()
.mandatory_pending()
Expand All @@ -582,7 +582,9 @@ where
.map_err(|e| Error::Backend(e.to_string()))?;
}
},
VoteImportResult::Equivocation => unimplemented!(),
VoteImportResult::Equivocation(proof) => {
self.report_equivocation(proof)?;
},
VoteImportResult::Invalid | VoteImportResult::Stale => (),
};
Ok(())
Expand Down Expand Up @@ -901,6 +903,18 @@ where
}
}
}

/// Report the given equivocation to the BEEFY runtime module. This method
/// generates a session membership proof of the offender and then submits an
/// extrinsic to report the equivocation. In particular, the session membership
/// proof must be generated at the block at which the given set was active which
/// isn't necessarily the best block if there are pending authority set changes.
pub(crate) fn report_equivocation(
&self,
_proof: EquivocationProof<NumberFor<B>, AuthorityId, Signature>,
) -> Result<(), Error> {
todo!()
}
}

/// Scan the `header` digest log for a BEEFY validator set change. Return either the new
Expand Down
8 changes: 5 additions & 3 deletions primitives/beefy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ pub enum ConsensusLog<AuthorityId: Codec> {
///
/// A vote message is a direct vote created by a BEEFY node on every voting round
/// and is gossiped to its peers.
#[derive(Debug, Decode, Encode, TypeInfo)]
#[derive(Clone, Debug, Decode, Encode, TypeInfo)]
pub struct VoteMessage<Number, Id, Signature> {
/// Commit to information extracted from a finalized block
pub commitment: Commitment<Number>,
Expand Down Expand Up @@ -211,8 +211,10 @@ pub struct Equivocation<Number, Id, Signature> {
/// Proving is achieved by collecting the signed commitments of conflicting votes.
#[derive(Debug, Decode, Encode, TypeInfo)]
pub struct EquivocationProof<Number, Id, Signature> {
set_id: ValidatorSetId,
equivocation: Equivocation<Number, Id, Signature>,
/// BEEFY validator set id active during this equivocation.
pub set_id: ValidatorSetId,
/// Equivocation details including the conflicting votes.
pub equivocation: Equivocation<Number, Id, Signature>,
}

/// Check a commitment signature by encoding the commitment and
Expand Down