Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

BEEFY: client support for detecting equivocated votes #13285

Merged
merged 6 commits into from
Feb 6, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
3 changes: 2 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion client/beefy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" }

[dev-dependencies]
serde = "1.0.136"
strum = { version = "0.24.1", features = ["derive"] }
tempfile = "3.1.0"
tokio = "1.22.0"
sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" }
Expand Down
131 changes: 122 additions & 9 deletions client/beefy/src/aux_schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,25 +26,25 @@ use sp_blockchain::{Error as ClientError, Result as ClientResult};
use sp_runtime::traits::Block as BlockT;

const VERSION_KEY: &[u8] = b"beefy_auxschema_version";
const WORKER_STATE: &[u8] = b"beefy_voter_state";
const WORKER_STATE_KEY: &[u8] = b"beefy_voter_state";

const CURRENT_VERSION: u32 = 1;
const CURRENT_VERSION: u32 = 2;

pub(crate) fn write_current_version<B: AuxStore>(backend: &B) -> ClientResult<()> {
pub(crate) fn write_current_version<BE: AuxStore>(backend: &BE) -> ClientResult<()> {
info!(target: LOG_TARGET, "🥩 write aux schema version {:?}", CURRENT_VERSION);
AuxStore::insert_aux(backend, &[(VERSION_KEY, CURRENT_VERSION.encode().as_slice())], &[])
}

/// Write voter state.
pub(crate) fn write_voter_state<Block: BlockT, B: AuxStore>(
backend: &B,
state: &PersistedState<Block>,
pub(crate) fn write_voter_state<B: BlockT, BE: AuxStore>(
backend: &BE,
state: &PersistedState<B>,
) -> ClientResult<()> {
trace!(target: LOG_TARGET, "🥩 persisting {:?}", state);
backend.insert_aux(&[(WORKER_STATE, state.encode().as_slice())], &[])
AuxStore::insert_aux(backend, &[(WORKER_STATE_KEY, state.encode().as_slice())], &[])
}

fn load_decode<B: AuxStore, T: Decode>(backend: &B, key: &[u8]) -> ClientResult<Option<T>> {
fn load_decode<BE: AuxStore, T: Decode>(backend: &BE, key: &[u8]) -> ClientResult<Option<T>> {
match backend.get_aux(key)? {
None => Ok(None),
Some(t) => T::decode(&mut &t[..])
Expand All @@ -63,7 +63,8 @@ where

match version {
None => (),
Some(1) => return load_decode::<_, PersistedState<B>>(backend, WORKER_STATE),
Some(1) => return v1::migrate_from_version1::<B, _>(backend),
Some(2) => return load_decode::<_, PersistedState<B>>(backend, WORKER_STATE_KEY),
other =>
return Err(ClientError::Backend(format!("Unsupported BEEFY DB version: {:?}", other))),
}
Expand All @@ -72,6 +73,118 @@ where
Ok(None)
}

mod v1 {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can drop this whole migration if we merge the PR in the same release cycle as #13215

use super::*;
use crate::{round::RoundTracker, worker::PersistedState, Rounds};
use beefy_primitives::{
crypto::{Public, Signature},
Commitment, Payload, ValidatorSet,
};
use sp_runtime::traits::NumberFor;
use std::collections::{BTreeMap, VecDeque};

#[derive(Decode)]
struct V1RoundTracker {
self_vote: bool,
votes: BTreeMap<Public, Signature>,
}

impl Into<RoundTracker> for V1RoundTracker {
fn into(self) -> RoundTracker {
// make the compiler happy by using this deprecated field
let _ = self.self_vote;
RoundTracker::new(self.votes)
}
}

#[derive(Decode)]
struct V1Rounds<B: BlockT> {
rounds: BTreeMap<(Payload, NumberFor<B>), V1RoundTracker>,
session_start: NumberFor<B>,
validator_set: ValidatorSet<Public>,
mandatory_done: bool,
best_done: Option<NumberFor<B>>,
}

impl<B> Into<Rounds<B>> for V1Rounds<B>
where
B: BlockT,
{
fn into(self) -> Rounds<B> {
let validator_set_id = self.validator_set.id();
let rounds = self
.rounds
.into_iter()
.map(|((payload, block_number), v1_tracker)| {
(Commitment { payload, block_number, validator_set_id }, v1_tracker.into())
})
.collect();
Rounds::<B>::new_manual(
rounds,
BTreeMap::new(),
self.session_start,
self.validator_set,
self.mandatory_done,
self.best_done,
)
}
}

#[derive(Decode)]
pub(crate) struct V1VoterOracle<B: BlockT> {
sessions: VecDeque<V1Rounds<B>>,
min_block_delta: u32,
}

#[derive(Decode)]
pub(crate) struct V1PersistedState<B: BlockT> {
/// Best block we received a GRANDPA finality for.
best_grandpa_block_header: <B as BlockT>::Header,
/// Best block a BEEFY voting round has been concluded for.
best_beefy_block: NumberFor<B>,
/// Chooses which incoming votes to accept and which votes to generate.
/// Keeps track of voting seen for current and future rounds.
voting_oracle: V1VoterOracle<B>,
}

impl<B> TryInto<PersistedState<B>> for V1PersistedState<B>
where
B: BlockT,
{
type Error = ();
fn try_into(self) -> Result<PersistedState<B>, Self::Error> {
let Self { best_grandpa_block_header, best_beefy_block, voting_oracle } = self;
let V1VoterOracle { sessions, min_block_delta } = voting_oracle;
let sessions =
sessions.into_iter().map(<V1Rounds<B> as Into<Rounds<B>>>::into).collect();
PersistedState::checked_new(
best_grandpa_block_header,
best_beefy_block,
sessions,
min_block_delta,
)
.ok_or(())
}
}

pub(super) fn migrate_from_version1<B: BlockT, BE>(
backend: &BE,
) -> ClientResult<Option<PersistedState<B>>>
where
B: BlockT,
BE: Backend<B>,
{
write_current_version(backend)?;
if let Some(new_state) = load_decode::<_, V1PersistedState<B>>(backend, WORKER_STATE_KEY)?
.and_then(|old_state| old_state.try_into().ok())
{
write_voter_state(backend, &new_state)?;
return Ok(Some(new_state))
}
Ok(None)
}
}

#[cfg(test)]
pub(crate) mod tests {
use super::*;
Expand Down
7 changes: 4 additions & 3 deletions client/beefy/src/communication/gossip.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ where

/// Note a voting round.
///
/// Noting round will start a live `round`.
/// Noting round will track gossiped votes for `round`.
pub(crate) fn note_round(&self, round: NumberFor<B>) {
debug!(target: LOG_TARGET, "🥩 About to note gossip round #{}", round);
self.known_votes.write().insert(round);
Expand Down Expand Up @@ -242,9 +242,10 @@ mod tests {
use sc_network_test::Block;
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};

use crate::keystore::{tests::Keyring, BeefyKeystore};
use crate::keystore::BeefyKeystore;
use beefy_primitives::{
crypto::Signature, known_payloads, Commitment, MmrRootHash, Payload, VoteMessage, KEY_TYPE,
crypto::Signature, keyring::Keyring, known_payloads, Commitment, MmrRootHash, Payload,
VoteMessage, KEY_TYPE,
};

use super::*;
Expand Down
5 changes: 3 additions & 2 deletions client/beefy/src/justification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,12 +81,13 @@ fn verify_with_validator_set<Block: BlockT>(
#[cfg(test)]
pub(crate) mod tests {
use beefy_primitives::{
known_payloads, Commitment, Payload, SignedCommitment, VersionedFinalityProof,
keyring::Keyring, known_payloads, Commitment, Payload, SignedCommitment,
VersionedFinalityProof,
};
use substrate_test_runtime_client::runtime::Block;

use super::*;
use crate::{keystore::tests::Keyring, tests::make_beefy_ids};
use crate::tests::make_beefy_ids;

pub(crate) fn new_finality_proof(
block_num: NumberFor<Block>,
Expand Down
62 changes: 7 additions & 55 deletions client/beefy/src/keystore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
use sp_application_crypto::RuntimeAppPublic;
use sp_core::keccak_256;
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
use sp_runtime::traits::Keccak256;

use log::warn;

Expand All @@ -30,6 +29,9 @@ use beefy_primitives::{

use crate::{error, LOG_TARGET};

/// Hasher used for BEEFY signatures.
pub(crate) type BeefySignatureHasher = sp_runtime::traits::Keccak256;

/// A BEEFY specific keystore implemented as a `Newtype`. This is basically a
/// wrapper around [`sp_keystore::SyncCryptoStore`] and allows to customize
/// common cryptographic functionality.
Expand Down Expand Up @@ -104,7 +106,7 @@ impl BeefyKeystore {
///
/// Return `true` if the signature is authentic, `false` otherwise.
pub fn verify(public: &Public, sig: &Signature, message: &[u8]) -> bool {
BeefyAuthorityId::<Keccak256>::verify(public, sig, message)
BeefyAuthorityId::<BeefySignatureHasher>::verify(public, sig, message)
}
}

Expand All @@ -119,63 +121,13 @@ pub mod tests {
use std::sync::Arc;

use sc_keystore::LocalKeystore;
use sp_core::{ecdsa, keccak_256, Pair};
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
use sp_core::{ecdsa, Pair};

use beefy_primitives::{crypto, KEY_TYPE};
use beefy_primitives::{crypto, keyring::Keyring};

use super::BeefyKeystore;
use super::*;
use crate::error::Error;

/// Set of test accounts using [`beefy_primitives::crypto`] types.
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display, strum::EnumIter)]
pub(crate) enum Keyring {
Alice,
Bob,
Charlie,
Dave,
Eve,
Ferdie,
One,
Two,
}

impl Keyring {
/// Sign `msg`.
pub fn sign(self, msg: &[u8]) -> crypto::Signature {
let msg = keccak_256(msg);
ecdsa::Pair::from(self).sign_prehashed(&msg).into()
}

/// Return key pair.
pub fn pair(self) -> crypto::Pair {
ecdsa::Pair::from_string(self.to_seed().as_str(), None).unwrap().into()
}

/// Return public key.
pub fn public(self) -> crypto::Public {
self.pair().public()
}

/// Return seed string.
pub fn to_seed(self) -> String {
format!("//{}", self)
}
}

impl From<Keyring> for crypto::Pair {
fn from(k: Keyring) -> Self {
k.pair()
}
}

impl From<Keyring> for ecdsa::Pair {
fn from(k: Keyring) -> Self {
k.pair().into()
}
}

fn keystore() -> SyncCryptoStorePtr {
Arc::new(LocalKeystore::in_memory())
}
Expand Down
6 changes: 6 additions & 0 deletions client/beefy/src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub(crate) struct Metrics {
pub beefy_round_concluded: Gauge<U64>,
/// Best block finalized by BEEFY
pub beefy_best_block: Gauge<U64>,
/// Best block BEEFY voted on
pub beefy_best_voted: Gauge<U64>,
/// Next block BEEFY should vote on
pub beefy_should_vote_on: Gauge<U64>,
/// Number of sessions with lagging signed commitment on mandatory block
Expand Down Expand Up @@ -61,6 +63,10 @@ impl Metrics {
Gauge::new("substrate_beefy_best_block", "Best block finalized by BEEFY")?,
registry,
)?,
beefy_best_voted: register(
Gauge::new("substrate_beefy_best_voted", "Best block voted on by BEEFY")?,
registry,
)?,
beefy_should_vote_on: register(
Gauge::new("substrate_beefy_should_vote_on", "Next block, BEEFY should vote on")?,
registry,
Expand Down
Loading