mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 19:11:02 +00:00
BEEFY: client support for detecting equivocated votes (#13285)
* client/beefy: detect equivocated votes * client/beefy: make sure to persist state after voting * client/beefy: drop never-used aux-schema v2 migration * impl review suggestion --------- Signed-off-by: Adrian Catangiu <adrian@parity.io>
This commit is contained in:
Generated
+2
-1
@@ -512,7 +512,6 @@ dependencies = [
|
|||||||
"sp-mmr-primitives",
|
"sp-mmr-primitives",
|
||||||
"sp-runtime",
|
"sp-runtime",
|
||||||
"sp-tracing",
|
"sp-tracing",
|
||||||
"strum",
|
|
||||||
"substrate-prometheus-endpoint",
|
"substrate-prometheus-endpoint",
|
||||||
"substrate-test-runtime-client",
|
"substrate-test-runtime-client",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
@@ -9734,6 +9733,7 @@ name = "sp-beefy"
|
|||||||
version = "4.0.0-dev"
|
version = "4.0.0-dev"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"array-bytes",
|
"array-bytes",
|
||||||
|
"lazy_static",
|
||||||
"parity-scale-codec",
|
"parity-scale-codec",
|
||||||
"scale-info",
|
"scale-info",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -9745,6 +9745,7 @@ dependencies = [
|
|||||||
"sp-mmr-primitives",
|
"sp-mmr-primitives",
|
||||||
"sp-runtime",
|
"sp-runtime",
|
||||||
"sp-std",
|
"sp-std",
|
||||||
|
"strum",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" }
|
|||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
serde = "1.0.136"
|
serde = "1.0.136"
|
||||||
strum = { version = "0.24.1", features = ["derive"] }
|
|
||||||
tempfile = "3.1.0"
|
tempfile = "3.1.0"
|
||||||
tokio = "1.22.0"
|
tokio = "1.22.0"
|
||||||
sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" }
|
sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" }
|
||||||
|
|||||||
@@ -26,25 +26,25 @@ use sp_blockchain::{Error as ClientError, Result as ClientResult};
|
|||||||
use sp_runtime::traits::Block as BlockT;
|
use sp_runtime::traits::Block as BlockT;
|
||||||
|
|
||||||
const VERSION_KEY: &[u8] = b"beefy_auxschema_version";
|
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 = 2;
|
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);
|
info!(target: LOG_TARGET, "🥩 write aux schema version {:?}", CURRENT_VERSION);
|
||||||
AuxStore::insert_aux(backend, &[(VERSION_KEY, CURRENT_VERSION.encode().as_slice())], &[])
|
AuxStore::insert_aux(backend, &[(VERSION_KEY, CURRENT_VERSION.encode().as_slice())], &[])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write voter state.
|
/// Write voter state.
|
||||||
pub(crate) fn write_voter_state<Block: BlockT, B: AuxStore>(
|
pub(crate) fn write_voter_state<B: BlockT, BE: AuxStore>(
|
||||||
backend: &B,
|
backend: &BE,
|
||||||
state: &PersistedState<Block>,
|
state: &PersistedState<B>,
|
||||||
) -> ClientResult<()> {
|
) -> ClientResult<()> {
|
||||||
trace!(target: LOG_TARGET, "🥩 persisting {:?}", state);
|
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)? {
|
match backend.get_aux(key)? {
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
Some(t) => T::decode(&mut &t[..])
|
Some(t) => T::decode(&mut &t[..])
|
||||||
@@ -64,7 +64,7 @@ where
|
|||||||
match version {
|
match version {
|
||||||
None => (),
|
None => (),
|
||||||
Some(1) => (), // version 1 is totally obsolete and should be simply ignored
|
Some(1) => (), // version 1 is totally obsolete and should be simply ignored
|
||||||
Some(2) => return load_decode::<_, PersistedState<B>>(backend, WORKER_STATE),
|
Some(2) => return load_decode::<_, PersistedState<B>>(backend, WORKER_STATE_KEY),
|
||||||
other =>
|
other =>
|
||||||
return Err(ClientError::Backend(format!("Unsupported BEEFY DB version: {:?}", other))),
|
return Err(ClientError::Backend(format!("Unsupported BEEFY DB version: {:?}", other))),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ where
|
|||||||
|
|
||||||
/// Note a voting round.
|
/// 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>) {
|
pub(crate) fn note_round(&self, round: NumberFor<B>) {
|
||||||
debug!(target: LOG_TARGET, "🥩 About to note gossip round #{}", round);
|
debug!(target: LOG_TARGET, "🥩 About to note gossip round #{}", round);
|
||||||
self.known_votes.write().insert(round);
|
self.known_votes.write().insert(round);
|
||||||
@@ -242,9 +242,10 @@ mod tests {
|
|||||||
use sc_network_test::Block;
|
use sc_network_test::Block;
|
||||||
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
|
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
|
||||||
|
|
||||||
use crate::keystore::{tests::Keyring, BeefyKeystore};
|
use crate::keystore::BeefyKeystore;
|
||||||
use beefy_primitives::{
|
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::*;
|
use super::*;
|
||||||
|
|||||||
@@ -81,12 +81,13 @@ fn verify_with_validator_set<Block: BlockT>(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod tests {
|
pub(crate) mod tests {
|
||||||
use beefy_primitives::{
|
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 substrate_test_runtime_client::runtime::Block;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{keystore::tests::Keyring, tests::make_beefy_ids};
|
use crate::tests::make_beefy_ids;
|
||||||
|
|
||||||
pub(crate) fn new_finality_proof(
|
pub(crate) fn new_finality_proof(
|
||||||
block_num: NumberFor<Block>,
|
block_num: NumberFor<Block>,
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
use sp_application_crypto::RuntimeAppPublic;
|
use sp_application_crypto::RuntimeAppPublic;
|
||||||
use sp_core::keccak_256;
|
use sp_core::keccak_256;
|
||||||
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
|
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
|
||||||
use sp_runtime::traits::Keccak256;
|
|
||||||
|
|
||||||
use log::warn;
|
use log::warn;
|
||||||
|
|
||||||
@@ -30,6 +29,9 @@ use beefy_primitives::{
|
|||||||
|
|
||||||
use crate::{error, LOG_TARGET};
|
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
|
/// A BEEFY specific keystore implemented as a `Newtype`. This is basically a
|
||||||
/// wrapper around [`sp_keystore::SyncCryptoStore`] and allows to customize
|
/// wrapper around [`sp_keystore::SyncCryptoStore`] and allows to customize
|
||||||
/// common cryptographic functionality.
|
/// common cryptographic functionality.
|
||||||
@@ -104,7 +106,7 @@ impl BeefyKeystore {
|
|||||||
///
|
///
|
||||||
/// Return `true` if the signature is authentic, `false` otherwise.
|
/// Return `true` if the signature is authentic, `false` otherwise.
|
||||||
pub fn verify(public: &Public, sig: &Signature, message: &[u8]) -> bool {
|
pub fn verify(public: &Public, sig: &Signature, message: &[u8]) -> bool {
|
||||||
BeefyAuthorityId::<Keccak256>::verify(public, sig, message)
|
BeefyAuthorityId::<BeefySignatureHasher>::verify(public, sig, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,63 +121,13 @@ pub mod tests {
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use sc_keystore::LocalKeystore;
|
use sc_keystore::LocalKeystore;
|
||||||
use sp_core::{ecdsa, keccak_256, Pair};
|
use sp_core::{ecdsa, Pair};
|
||||||
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
|
|
||||||
|
|
||||||
use beefy_primitives::{crypto, KEY_TYPE};
|
use beefy_primitives::{crypto, keyring::Keyring};
|
||||||
|
|
||||||
use super::BeefyKeystore;
|
use super::*;
|
||||||
use crate::error::Error;
|
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 {
|
fn keystore() -> SyncCryptoStorePtr {
|
||||||
Arc::new(LocalKeystore::in_memory())
|
Arc::new(LocalKeystore::in_memory())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ pub(crate) struct Metrics {
|
|||||||
pub beefy_round_concluded: Gauge<U64>,
|
pub beefy_round_concluded: Gauge<U64>,
|
||||||
/// Best block finalized by BEEFY
|
/// Best block finalized by BEEFY
|
||||||
pub beefy_best_block: Gauge<U64>,
|
pub beefy_best_block: Gauge<U64>,
|
||||||
|
/// Best block BEEFY voted on
|
||||||
|
pub beefy_best_voted: Gauge<U64>,
|
||||||
/// Next block BEEFY should vote on
|
/// Next block BEEFY should vote on
|
||||||
pub beefy_should_vote_on: Gauge<U64>,
|
pub beefy_should_vote_on: Gauge<U64>,
|
||||||
/// Number of sessions with lagging signed commitment on mandatory block
|
/// Number of sessions with lagging signed commitment on mandatory block
|
||||||
@@ -61,6 +63,10 @@ impl Metrics {
|
|||||||
Gauge::new("substrate_beefy_best_block", "Best block finalized by BEEFY")?,
|
Gauge::new("substrate_beefy_best_block", "Best block finalized by BEEFY")?,
|
||||||
registry,
|
registry,
|
||||||
)?,
|
)?,
|
||||||
|
beefy_best_voted: register(
|
||||||
|
Gauge::new("substrate_beefy_best_voted", "Best block voted on by BEEFY")?,
|
||||||
|
registry,
|
||||||
|
)?,
|
||||||
beefy_should_vote_on: register(
|
beefy_should_vote_on: register(
|
||||||
Gauge::new("substrate_beefy_should_vote_on", "Next block, BEEFY should vote on")?,
|
Gauge::new("substrate_beefy_should_vote_on", "Next block, BEEFY should vote on")?,
|
||||||
registry,
|
registry,
|
||||||
|
|||||||
+200
-201
@@ -19,39 +19,33 @@
|
|||||||
use crate::LOG_TARGET;
|
use crate::LOG_TARGET;
|
||||||
|
|
||||||
use beefy_primitives::{
|
use beefy_primitives::{
|
||||||
crypto::{Public, Signature},
|
crypto::{AuthorityId, Public, Signature},
|
||||||
ValidatorSet, ValidatorSetId,
|
Commitment, SignedCommitment, ValidatorSet, ValidatorSetId, VoteMessage,
|
||||||
};
|
};
|
||||||
use codec::{Decode, Encode};
|
use codec::{Decode, Encode};
|
||||||
use log::{debug, trace};
|
use log::debug;
|
||||||
use sp_runtime::traits::{Block, NumberFor};
|
use sp_runtime::traits::{Block, NumberFor};
|
||||||
use std::{collections::BTreeMap, hash::Hash};
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
/// Tracks for each round which validators have voted/signed and
|
/// Tracks for each round which validators have voted/signed and
|
||||||
/// whether the local `self` validator has voted/signed.
|
/// whether the local `self` validator has voted/signed.
|
||||||
///
|
///
|
||||||
/// Does not do any validation on votes or signatures, layers above need to handle that (gossip).
|
/// Does not do any validation on votes or signatures, layers above need to handle that (gossip).
|
||||||
#[derive(Debug, Decode, Default, Encode, PartialEq)]
|
#[derive(Debug, Decode, Default, Encode, PartialEq)]
|
||||||
struct RoundTracker {
|
pub(crate) struct RoundTracker {
|
||||||
self_vote: bool,
|
|
||||||
votes: BTreeMap<Public, Signature>,
|
votes: BTreeMap<Public, Signature>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RoundTracker {
|
impl RoundTracker {
|
||||||
fn add_vote(&mut self, vote: (Public, Signature), self_vote: bool) -> bool {
|
fn add_vote(&mut self, vote: (Public, Signature)) -> bool {
|
||||||
if self.votes.contains_key(&vote.0) {
|
if self.votes.contains_key(&vote.0) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
self.self_vote = self.self_vote || self_vote;
|
|
||||||
self.votes.insert(vote.0, vote.1);
|
self.votes.insert(vote.0, vote.1);
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn has_self_vote(&self) -> bool {
|
|
||||||
self.self_vote
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_done(&self, threshold: usize) -> bool {
|
fn is_done(&self, threshold: usize) -> bool {
|
||||||
self.votes.len() >= threshold
|
self.votes.len() >= threshold
|
||||||
}
|
}
|
||||||
@@ -63,27 +57,37 @@ pub fn threshold(authorities: usize) -> usize {
|
|||||||
authorities - faulty
|
authorities - faulty
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum VoteImportResult<B: Block> {
|
||||||
|
Ok,
|
||||||
|
RoundConcluded(SignedCommitment<NumberFor<B>, Signature>),
|
||||||
|
Equivocation, /* TODO: (EquivocationProof<NumberFor<B>, Public, Signature>) */
|
||||||
|
Invalid,
|
||||||
|
Stale,
|
||||||
|
}
|
||||||
|
|
||||||
/// Keeps track of all voting rounds (block numbers) within a session.
|
/// Keeps track of all voting rounds (block numbers) within a session.
|
||||||
/// Only round numbers > `best_done` are of interest, all others are considered stale.
|
/// 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).
|
/// 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, PartialEq)]
|
||||||
pub(crate) struct Rounds<Payload, B: Block> {
|
pub(crate) struct Rounds<B: Block> {
|
||||||
rounds: BTreeMap<(Payload, NumberFor<B>), RoundTracker>,
|
rounds: BTreeMap<Commitment<NumberFor<B>>, RoundTracker>,
|
||||||
|
previous_votes: BTreeMap<(Public, NumberFor<B>), VoteMessage<NumberFor<B>, Public, Signature>>,
|
||||||
session_start: NumberFor<B>,
|
session_start: NumberFor<B>,
|
||||||
validator_set: ValidatorSet<Public>,
|
validator_set: ValidatorSet<Public>,
|
||||||
mandatory_done: bool,
|
mandatory_done: bool,
|
||||||
best_done: Option<NumberFor<B>>,
|
best_done: Option<NumberFor<B>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P, B> Rounds<P, B>
|
impl<B> Rounds<B>
|
||||||
where
|
where
|
||||||
P: Ord + Hash + Clone,
|
|
||||||
B: Block,
|
B: Block,
|
||||||
{
|
{
|
||||||
pub(crate) fn new(session_start: NumberFor<B>, validator_set: ValidatorSet<Public>) -> Self {
|
pub(crate) fn new(session_start: NumberFor<B>, validator_set: ValidatorSet<Public>) -> Self {
|
||||||
Rounds {
|
Rounds {
|
||||||
rounds: BTreeMap::new(),
|
rounds: BTreeMap::new(),
|
||||||
|
previous_votes: BTreeMap::new(),
|
||||||
session_start,
|
session_start,
|
||||||
validator_set,
|
validator_set,
|
||||||
mandatory_done: false,
|
mandatory_done: false,
|
||||||
@@ -111,60 +115,77 @@ where
|
|||||||
self.mandatory_done
|
self.mandatory_done
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn should_self_vote(&self, round: &(P, NumberFor<B>)) -> bool {
|
|
||||||
Some(round.1) > self.best_done &&
|
|
||||||
self.rounds.get(round).map(|tracker| !tracker.has_self_vote()).unwrap_or(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn add_vote(
|
pub(crate) fn add_vote(
|
||||||
&mut self,
|
&mut self,
|
||||||
round: &(P, NumberFor<B>),
|
vote: VoteMessage<NumberFor<B>, AuthorityId, Signature>,
|
||||||
vote: (Public, Signature),
|
) -> VoteImportResult<B> {
|
||||||
self_vote: bool,
|
let num = vote.commitment.block_number;
|
||||||
) -> bool {
|
let vote_key = (vote.id.clone(), num);
|
||||||
let num = round.1;
|
|
||||||
if num < self.session_start || Some(num) <= self.best_done {
|
if num < self.session_start || Some(num) <= self.best_done {
|
||||||
debug!(target: LOG_TARGET, "🥩 received vote for old stale round {:?}, ignoring", num);
|
debug!(target: LOG_TARGET, "🥩 received vote for old stale round {:?}, ignoring", num);
|
||||||
false
|
return VoteImportResult::Stale
|
||||||
} else if !self.validators().iter().any(|id| vote.0 == *id) {
|
} else if vote.commitment.validator_set_id != self.validator_set_id() {
|
||||||
|
debug!(
|
||||||
|
target: LOG_TARGET,
|
||||||
|
"🥩 expected set_id {:?}, ignoring vote {:?}.",
|
||||||
|
self.validator_set_id(),
|
||||||
|
vote,
|
||||||
|
);
|
||||||
|
return VoteImportResult::Invalid
|
||||||
|
} else if !self.validators().iter().any(|id| &vote.id == id) {
|
||||||
debug!(
|
debug!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
"🥩 received vote {:?} from validator that is not in the validator set, ignoring",
|
"🥩 received vote {:?} from validator that is not in the validator set, ignoring",
|
||||||
vote
|
vote
|
||||||
);
|
);
|
||||||
false
|
return VoteImportResult::Invalid
|
||||||
} else {
|
|
||||||
self.rounds.entry(round.clone()).or_default().add_vote(vote, self_vote)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(previous_vote) = self.previous_votes.get(&vote_key) {
|
||||||
|
// is the same public key voting for a different payload?
|
||||||
|
if previous_vote.commitment.payload != vote.commitment.payload {
|
||||||
|
debug!(
|
||||||
|
target: LOG_TARGET,
|
||||||
|
"🥩 detected equivocated vote: 1st: {:?}, 2nd: {:?}", previous_vote, vote
|
||||||
|
);
|
||||||
|
// TODO: build `EquivocationProof` and return it here.
|
||||||
|
return VoteImportResult::Equivocation
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// this is the first vote sent by `id` for `num`, all good
|
||||||
|
self.previous_votes.insert(vote_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_entry(&vote.commitment) {
|
||||||
|
return VoteImportResult::RoundConcluded(self.signed_commitment(round))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VoteImportResult::Ok
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn should_conclude(
|
fn signed_commitment(
|
||||||
&mut self,
|
&mut self,
|
||||||
round: &(P, NumberFor<B>),
|
round: (Commitment<NumberFor<B>>, RoundTracker),
|
||||||
) -> Option<Vec<Option<Signature>>> {
|
) -> SignedCommitment<NumberFor<B>, Signature> {
|
||||||
let done = self
|
let votes = round.1.votes;
|
||||||
.rounds
|
let signatures = self
|
||||||
.get(round)
|
.validators()
|
||||||
.map(|tracker| tracker.is_done(threshold(self.validator_set.len())))
|
.iter()
|
||||||
.unwrap_or(false);
|
.map(|authority_id| votes.get(authority_id).cloned())
|
||||||
trace!(target: LOG_TARGET, "🥩 Round #{} done: {}", round.1, done);
|
.collect();
|
||||||
|
SignedCommitment { commitment: round.0, signatures }
|
||||||
if done {
|
|
||||||
let signatures = self.rounds.remove(round)?.votes;
|
|
||||||
Some(
|
|
||||||
self.validators()
|
|
||||||
.iter()
|
|
||||||
.map(|authority_id| signatures.get(authority_id).cloned())
|
|
||||||
.collect(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn conclude(&mut self, round_num: NumberFor<B>) {
|
pub(crate) fn conclude(&mut self, round_num: NumberFor<B>) {
|
||||||
// Remove this and older (now stale) rounds.
|
// Remove this and older (now stale) rounds.
|
||||||
self.rounds.retain(|&(_, number), _| number > round_num);
|
self.rounds.retain(|commitment, _| commitment.block_number > round_num);
|
||||||
|
self.previous_votes.retain(|&(_, number), _| number > round_num);
|
||||||
self.mandatory_done = self.mandatory_done || round_num == self.session_start;
|
self.mandatory_done = self.mandatory_done || round_num == self.session_start;
|
||||||
self.best_done = self.best_done.max(Some(round_num));
|
self.best_done = self.best_done.max(Some(round_num));
|
||||||
debug!(target: LOG_TARGET, "🥩 Concluded round #{}", round_num);
|
debug!(target: LOG_TARGET, "🥩 Concluded round #{}", round_num);
|
||||||
@@ -174,16 +195,17 @@ where
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use sc_network_test::Block;
|
use sc_network_test::Block;
|
||||||
use sp_core::H256;
|
|
||||||
|
|
||||||
use beefy_primitives::{crypto::Public, ValidatorSet};
|
use beefy_primitives::{
|
||||||
|
crypto::Public, keyring::Keyring, known_payloads::MMR_ROOT_ID, Commitment, Payload,
|
||||||
|
SignedCommitment, ValidatorSet, VoteMessage,
|
||||||
|
};
|
||||||
|
|
||||||
use super::{threshold, Block as BlockT, Hash, RoundTracker, Rounds};
|
use super::{threshold, Block as BlockT, RoundTracker, Rounds};
|
||||||
use crate::keystore::tests::Keyring;
|
use crate::round::VoteImportResult;
|
||||||
|
|
||||||
impl<P, B> Rounds<P, B>
|
impl<B> Rounds<B>
|
||||||
where
|
where
|
||||||
P: Ord + Hash + Clone,
|
|
||||||
B: BlockT,
|
B: BlockT,
|
||||||
{
|
{
|
||||||
pub(crate) fn test_set_mandatory_done(&mut self, done: bool) {
|
pub(crate) fn test_set_mandatory_done(&mut self, done: bool) {
|
||||||
@@ -197,26 +219,18 @@ mod tests {
|
|||||||
let bob_vote = (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed"));
|
let bob_vote = (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed"));
|
||||||
let threshold = 2;
|
let threshold = 2;
|
||||||
|
|
||||||
// self vote not added yet
|
|
||||||
assert!(!rt.has_self_vote());
|
|
||||||
|
|
||||||
// adding new vote allowed
|
// adding new vote allowed
|
||||||
assert!(rt.add_vote(bob_vote.clone(), false));
|
assert!(rt.add_vote(bob_vote.clone()));
|
||||||
// adding existing vote not allowed
|
// adding existing vote not allowed
|
||||||
assert!(!rt.add_vote(bob_vote, false));
|
assert!(!rt.add_vote(bob_vote));
|
||||||
|
|
||||||
// self vote still not added yet
|
|
||||||
assert!(!rt.has_self_vote());
|
|
||||||
|
|
||||||
// vote is not done
|
// vote is not done
|
||||||
assert!(!rt.is_done(threshold));
|
assert!(!rt.is_done(threshold));
|
||||||
|
|
||||||
let alice_vote = (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed"));
|
let alice_vote = (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed"));
|
||||||
// adding new vote (self vote this time) allowed
|
// adding new vote (self vote this time) allowed
|
||||||
assert!(rt.add_vote(alice_vote, true));
|
assert!(rt.add_vote(alice_vote));
|
||||||
|
|
||||||
// self vote registered
|
|
||||||
assert!(rt.has_self_vote());
|
|
||||||
// vote is now done
|
// vote is now done
|
||||||
assert!(rt.is_done(threshold));
|
assert!(rt.is_done(threshold));
|
||||||
}
|
}
|
||||||
@@ -242,7 +256,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let session_start = 1u64.into();
|
let session_start = 1u64.into();
|
||||||
let rounds = Rounds::<H256, Block>::new(session_start, validators);
|
let rounds = Rounds::<Block>::new(session_start, validators);
|
||||||
|
|
||||||
assert_eq!(42, rounds.validator_set_id());
|
assert_eq!(42, rounds.validator_set_id());
|
||||||
assert_eq!(1, rounds.session_start());
|
assert_eq!(1, rounds.session_start());
|
||||||
@@ -266,65 +280,56 @@ mod tests {
|
|||||||
Default::default(),
|
Default::default(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let round = (H256::from_low_u64_le(1), 1);
|
let validator_set_id = validators.id();
|
||||||
|
|
||||||
let session_start = 1u64.into();
|
let session_start = 1u64.into();
|
||||||
let mut rounds = Rounds::<H256, Block>::new(session_start, validators);
|
let mut rounds = Rounds::<Block>::new(session_start, validators);
|
||||||
|
|
||||||
// no self vote yet, should self vote
|
|
||||||
assert!(rounds.should_self_vote(&round));
|
|
||||||
|
|
||||||
|
let payload = Payload::from_single_entry(MMR_ROOT_ID, vec![]);
|
||||||
|
let block_number = 1;
|
||||||
|
let commitment = Commitment { block_number, payload, validator_set_id };
|
||||||
|
let mut vote = VoteMessage {
|
||||||
|
id: Keyring::Alice.public(),
|
||||||
|
commitment: commitment.clone(),
|
||||||
|
signature: Keyring::Alice.sign(b"I am committed"),
|
||||||
|
};
|
||||||
// add 1st good vote
|
// add 1st good vote
|
||||||
assert!(rounds.add_vote(
|
assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok);
|
||||||
&round,
|
|
||||||
(Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")),
|
|
||||||
true
|
|
||||||
));
|
|
||||||
// round not concluded
|
|
||||||
assert!(rounds.should_conclude(&round).is_none());
|
|
||||||
// self vote already present, should not self vote
|
|
||||||
assert!(!rounds.should_self_vote(&round));
|
|
||||||
|
|
||||||
// double voting not allowed
|
// double voting (same vote), ok, no effect
|
||||||
assert!(!rounds.add_vote(
|
assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok);
|
||||||
&round,
|
|
||||||
(Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")),
|
|
||||||
true
|
|
||||||
));
|
|
||||||
|
|
||||||
|
vote.id = Keyring::Dave.public();
|
||||||
|
vote.signature = Keyring::Dave.sign(b"I am committed");
|
||||||
// invalid vote (Dave is not a validator)
|
// invalid vote (Dave is not a validator)
|
||||||
assert!(!rounds.add_vote(
|
assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Invalid);
|
||||||
&round,
|
|
||||||
(Keyring::Dave.public(), Keyring::Dave.sign(b"I am committed")),
|
|
||||||
false
|
|
||||||
));
|
|
||||||
assert!(rounds.should_conclude(&round).is_none());
|
|
||||||
|
|
||||||
|
vote.id = Keyring::Bob.public();
|
||||||
|
vote.signature = Keyring::Bob.sign(b"I am committed");
|
||||||
// add 2nd good vote
|
// add 2nd good vote
|
||||||
assert!(rounds.add_vote(
|
assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok);
|
||||||
&round,
|
|
||||||
(Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")),
|
|
||||||
false
|
|
||||||
));
|
|
||||||
// round not concluded
|
|
||||||
assert!(rounds.should_conclude(&round).is_none());
|
|
||||||
|
|
||||||
// add 3rd good vote
|
vote.id = Keyring::Charlie.public();
|
||||||
assert!(rounds.add_vote(
|
vote.signature = Keyring::Charlie.sign(b"I am committed");
|
||||||
&round,
|
// add 3rd good vote -> round concluded -> signatures present
|
||||||
(Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am committed")),
|
assert_eq!(
|
||||||
false
|
rounds.add_vote(vote.clone()),
|
||||||
));
|
VoteImportResult::RoundConcluded(SignedCommitment {
|
||||||
// round concluded
|
commitment,
|
||||||
assert!(rounds.should_conclude(&round).is_some());
|
signatures: vec![
|
||||||
rounds.conclude(round.1);
|
Some(Keyring::Alice.sign(b"I am committed")),
|
||||||
|
Some(Keyring::Bob.sign(b"I am committed")),
|
||||||
|
Some(Keyring::Charlie.sign(b"I am committed")),
|
||||||
|
None,
|
||||||
|
]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
rounds.conclude(block_number);
|
||||||
|
|
||||||
|
vote.id = Keyring::Eve.public();
|
||||||
|
vote.signature = Keyring::Eve.sign(b"I am committed");
|
||||||
// Eve is a validator, but round was concluded, adding vote disallowed
|
// Eve is a validator, but round was concluded, adding vote disallowed
|
||||||
assert!(!rounds.add_vote(
|
assert_eq!(rounds.add_vote(vote), VoteImportResult::Stale);
|
||||||
&round,
|
|
||||||
(Keyring::Eve.public(), Keyring::Eve.sign(b"I am committed")),
|
|
||||||
false
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -336,30 +341,39 @@ mod tests {
|
|||||||
42,
|
42,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let alice = (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed"));
|
let validator_set_id = validators.id();
|
||||||
|
|
||||||
|
// active rounds starts at block 10
|
||||||
let session_start = 10u64.into();
|
let session_start = 10u64.into();
|
||||||
let mut rounds = Rounds::<H256, Block>::new(session_start, validators);
|
let mut rounds = Rounds::<Block>::new(session_start, validators);
|
||||||
|
|
||||||
let mut vote = (H256::from_low_u64_le(1), 9);
|
// vote on round 9
|
||||||
|
let block_number = 9;
|
||||||
|
let payload = Payload::from_single_entry(MMR_ROOT_ID, vec![]);
|
||||||
|
let commitment = Commitment { block_number, payload, validator_set_id };
|
||||||
|
let mut vote = VoteMessage {
|
||||||
|
id: Keyring::Alice.public(),
|
||||||
|
commitment,
|
||||||
|
signature: Keyring::Alice.sign(b"I am committed"),
|
||||||
|
};
|
||||||
// add vote for previous session, should fail
|
// add vote for previous session, should fail
|
||||||
assert!(!rounds.add_vote(&vote, alice.clone(), true));
|
assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Stale);
|
||||||
// no votes present
|
// no votes present
|
||||||
assert!(rounds.rounds.is_empty());
|
assert!(rounds.rounds.is_empty());
|
||||||
|
|
||||||
// simulate 11 was concluded
|
// simulate 11 was concluded
|
||||||
rounds.best_done = Some(11);
|
rounds.best_done = Some(11);
|
||||||
// add votes for current session, but already concluded rounds, should fail
|
// add votes for current session, but already concluded rounds, should fail
|
||||||
vote.1 = 10;
|
vote.commitment.block_number = 10;
|
||||||
assert!(!rounds.add_vote(&vote, alice.clone(), true));
|
assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Stale);
|
||||||
vote.1 = 11;
|
vote.commitment.block_number = 11;
|
||||||
assert!(!rounds.add_vote(&vote, alice.clone(), true));
|
assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Stale);
|
||||||
// no votes present
|
// no votes present
|
||||||
assert!(rounds.rounds.is_empty());
|
assert!(rounds.rounds.is_empty());
|
||||||
|
|
||||||
// add good vote
|
// add vote for active round 12
|
||||||
vote.1 = 12;
|
vote.commitment.block_number = 12;
|
||||||
assert!(rounds.add_vote(&vote, alice, true));
|
assert_eq!(rounds.add_vote(vote), VoteImportResult::Ok);
|
||||||
// good vote present
|
// good vote present
|
||||||
assert_eq!(rounds.rounds.len(), 1);
|
assert_eq!(rounds.rounds.len(), 1);
|
||||||
}
|
}
|
||||||
@@ -369,88 +383,73 @@ mod tests {
|
|||||||
sp_tracing::try_init_simple();
|
sp_tracing::try_init_simple();
|
||||||
|
|
||||||
let validators = ValidatorSet::<Public>::new(
|
let validators = ValidatorSet::<Public>::new(
|
||||||
vec![
|
vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()],
|
||||||
Keyring::Alice.public(),
|
|
||||||
Keyring::Bob.public(),
|
|
||||||
Keyring::Charlie.public(),
|
|
||||||
Keyring::Dave.public(),
|
|
||||||
],
|
|
||||||
Default::default(),
|
Default::default(),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
let validator_set_id = validators.id();
|
||||||
|
|
||||||
let session_start = 1u64.into();
|
let session_start = 1u64.into();
|
||||||
let mut rounds = Rounds::<H256, Block>::new(session_start, validators);
|
let mut rounds = Rounds::<Block>::new(session_start, validators);
|
||||||
|
|
||||||
// round 1
|
let payload = Payload::from_single_entry(MMR_ROOT_ID, vec![]);
|
||||||
assert!(rounds.add_vote(
|
let commitment = Commitment { block_number: 1, payload, validator_set_id };
|
||||||
&(H256::from_low_u64_le(1), 1),
|
let mut alice_vote = VoteMessage {
|
||||||
(Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")),
|
id: Keyring::Alice.public(),
|
||||||
true,
|
commitment: commitment.clone(),
|
||||||
));
|
signature: Keyring::Alice.sign(b"I am committed"),
|
||||||
assert!(rounds.add_vote(
|
};
|
||||||
&(H256::from_low_u64_le(1), 1),
|
let mut bob_vote = VoteMessage {
|
||||||
(Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")),
|
id: Keyring::Bob.public(),
|
||||||
false,
|
commitment: commitment.clone(),
|
||||||
));
|
signature: Keyring::Bob.sign(b"I am committed"),
|
||||||
assert!(rounds.add_vote(
|
};
|
||||||
&(H256::from_low_u64_le(1), 1),
|
let mut charlie_vote = VoteMessage {
|
||||||
(Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am committed")),
|
id: Keyring::Charlie.public(),
|
||||||
false,
|
commitment,
|
||||||
));
|
signature: Keyring::Charlie.sign(b"I am committed"),
|
||||||
|
};
|
||||||
|
let expected_signatures = vec![
|
||||||
|
Some(Keyring::Alice.sign(b"I am committed")),
|
||||||
|
Some(Keyring::Bob.sign(b"I am committed")),
|
||||||
|
Some(Keyring::Charlie.sign(b"I am committed")),
|
||||||
|
];
|
||||||
|
|
||||||
// round 2
|
// round 1 - only 2 out of 3 vote
|
||||||
assert!(rounds.add_vote(
|
assert_eq!(rounds.add_vote(alice_vote.clone()), VoteImportResult::Ok);
|
||||||
&(H256::from_low_u64_le(2), 2),
|
assert_eq!(rounds.add_vote(charlie_vote.clone()), VoteImportResult::Ok);
|
||||||
(Keyring::Alice.public(), Keyring::Alice.sign(b"I am again committed")),
|
// should be 1 active round
|
||||||
true,
|
|
||||||
));
|
|
||||||
assert!(rounds.add_vote(
|
|
||||||
&(H256::from_low_u64_le(2), 2),
|
|
||||||
(Keyring::Bob.public(), Keyring::Bob.sign(b"I am again committed")),
|
|
||||||
false,
|
|
||||||
));
|
|
||||||
assert!(rounds.add_vote(
|
|
||||||
&(H256::from_low_u64_le(2), 2),
|
|
||||||
(Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am again committed")),
|
|
||||||
false,
|
|
||||||
));
|
|
||||||
|
|
||||||
// round 3
|
|
||||||
assert!(rounds.add_vote(
|
|
||||||
&(H256::from_low_u64_le(3), 3),
|
|
||||||
(Keyring::Alice.public(), Keyring::Alice.sign(b"I am still committed")),
|
|
||||||
true,
|
|
||||||
));
|
|
||||||
assert!(rounds.add_vote(
|
|
||||||
&(H256::from_low_u64_le(3), 3),
|
|
||||||
(Keyring::Bob.public(), Keyring::Bob.sign(b"I am still committed")),
|
|
||||||
false,
|
|
||||||
));
|
|
||||||
assert!(rounds.add_vote(
|
|
||||||
&(H256::from_low_u64_le(3), 3),
|
|
||||||
(Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am still committed")),
|
|
||||||
false,
|
|
||||||
));
|
|
||||||
assert_eq!(3, rounds.rounds.len());
|
|
||||||
|
|
||||||
// conclude unknown round
|
|
||||||
assert!(rounds.should_conclude(&(H256::from_low_u64_le(5), 5)).is_none());
|
|
||||||
assert_eq!(3, rounds.rounds.len());
|
|
||||||
|
|
||||||
// conclude round 2
|
|
||||||
let signatures = rounds.should_conclude(&(H256::from_low_u64_le(2), 2)).unwrap();
|
|
||||||
rounds.conclude(2);
|
|
||||||
assert_eq!(1, rounds.rounds.len());
|
assert_eq!(1, rounds.rounds.len());
|
||||||
|
|
||||||
|
// round 2 - only Charlie votes
|
||||||
|
charlie_vote.commitment.block_number = 2;
|
||||||
|
assert_eq!(rounds.add_vote(charlie_vote.clone()), VoteImportResult::Ok);
|
||||||
|
// should be 2 active rounds
|
||||||
|
assert_eq!(2, rounds.rounds.len());
|
||||||
|
|
||||||
|
// round 3 - all validators vote -> round is concluded
|
||||||
|
alice_vote.commitment.block_number = 3;
|
||||||
|
bob_vote.commitment.block_number = 3;
|
||||||
|
charlie_vote.commitment.block_number = 3;
|
||||||
|
assert_eq!(rounds.add_vote(alice_vote.clone()), VoteImportResult::Ok);
|
||||||
|
assert_eq!(rounds.add_vote(bob_vote.clone()), VoteImportResult::Ok);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
signatures,
|
rounds.add_vote(charlie_vote.clone()),
|
||||||
vec![
|
VoteImportResult::RoundConcluded(SignedCommitment {
|
||||||
Some(Keyring::Alice.sign(b"I am again committed")),
|
commitment: charlie_vote.commitment,
|
||||||
Some(Keyring::Bob.sign(b"I am again committed")),
|
signatures: expected_signatures
|
||||||
Some(Keyring::Charlie.sign(b"I am again committed")),
|
})
|
||||||
None
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
// should be only 2 active since this one auto-concluded
|
||||||
|
assert_eq!(2, rounds.rounds.len());
|
||||||
|
|
||||||
|
// conclude round 2
|
||||||
|
rounds.conclude(2);
|
||||||
|
// should be no more active rounds since 2 was officially concluded and round "1" is stale
|
||||||
|
assert!(rounds.rounds.is_empty());
|
||||||
|
|
||||||
|
// conclude round 3
|
||||||
|
rounds.conclude(3);
|
||||||
|
assert!(rounds.previous_votes.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ use crate::{
|
|||||||
},
|
},
|
||||||
gossip_protocol_name,
|
gossip_protocol_name,
|
||||||
justification::*,
|
justification::*,
|
||||||
keystore::tests::Keyring as BeefyKeyring,
|
|
||||||
load_or_init_voter_state, wait_for_runtime_pallet, BeefyRPCLinks, BeefyVoterLinks, KnownPeers,
|
load_or_init_voter_state, wait_for_runtime_pallet, BeefyRPCLinks, BeefyVoterLinks, KnownPeers,
|
||||||
PersistedState,
|
PersistedState,
|
||||||
};
|
};
|
||||||
use beefy_primitives::{
|
use beefy_primitives::{
|
||||||
crypto::{AuthorityId, Signature},
|
crypto::{AuthorityId, Signature},
|
||||||
|
keyring::Keyring as BeefyKeyring,
|
||||||
known_payloads,
|
known_payloads,
|
||||||
mmr::MmrRootProvider,
|
mmr::MmrRootProvider,
|
||||||
BeefyApi, Commitment, ConsensusLog, MmrRootHash, Payload, SignedCommitment, ValidatorSet,
|
BeefyApi, Commitment, ConsensusLog, MmrRootHash, Payload, SignedCommitment, ValidatorSet,
|
||||||
|
|||||||
@@ -26,13 +26,13 @@ use crate::{
|
|||||||
keystore::BeefyKeystore,
|
keystore::BeefyKeystore,
|
||||||
metric_inc, metric_set,
|
metric_inc, metric_set,
|
||||||
metrics::Metrics,
|
metrics::Metrics,
|
||||||
round::Rounds,
|
round::{Rounds, VoteImportResult},
|
||||||
BeefyVoterLinks, LOG_TARGET,
|
BeefyVoterLinks, LOG_TARGET,
|
||||||
};
|
};
|
||||||
use beefy_primitives::{
|
use beefy_primitives::{
|
||||||
crypto::{AuthorityId, Signature},
|
crypto::{AuthorityId, Signature},
|
||||||
Commitment, ConsensusLog, Payload, PayloadProvider, SignedCommitment, ValidatorSet,
|
Commitment, ConsensusLog, PayloadProvider, ValidatorSet, VersionedFinalityProof, VoteMessage,
|
||||||
VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID,
|
BEEFY_ENGINE_ID,
|
||||||
};
|
};
|
||||||
use codec::{Codec, Decode, Encode};
|
use codec::{Codec, Decode, Encode};
|
||||||
use futures::{stream::Fuse, FutureExt, StreamExt};
|
use futures::{stream::Fuse, FutureExt, StreamExt};
|
||||||
@@ -55,6 +55,7 @@ use std::{
|
|||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Bound for the number of buffered future voting rounds.
|
/// Bound for the number of buffered future voting rounds.
|
||||||
const MAX_BUFFERED_VOTE_ROUNDS: usize = 600;
|
const MAX_BUFFERED_VOTE_ROUNDS: usize = 600;
|
||||||
/// Bound for the number of buffered votes per round number.
|
/// Bound for the number of buffered votes per round number.
|
||||||
@@ -83,17 +84,14 @@ pub(crate) struct VoterOracle<B: Block> {
|
|||||||
/// 3. lagging behind GRANDPA: queue has [1, N] elements, where all `mandatory_done == false`.
|
/// 3. lagging behind GRANDPA: queue has [1, N] elements, where all `mandatory_done == false`.
|
||||||
/// In this state, everytime a session gets its mandatory block BEEFY finalized, it's
|
/// In this state, everytime a session gets its mandatory block BEEFY finalized, it's
|
||||||
/// popped off the queue, eventually getting to state `2. up-to-date`.
|
/// popped off the queue, eventually getting to state `2. up-to-date`.
|
||||||
sessions: VecDeque<Rounds<Payload, B>>,
|
sessions: VecDeque<Rounds<B>>,
|
||||||
/// Min delta in block numbers between two blocks, BEEFY should vote on.
|
/// Min delta in block numbers between two blocks, BEEFY should vote on.
|
||||||
min_block_delta: u32,
|
min_block_delta: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<B: Block> VoterOracle<B> {
|
impl<B: Block> VoterOracle<B> {
|
||||||
/// Verify provided `sessions` satisfies requirements, then build `VoterOracle`.
|
/// Verify provided `sessions` satisfies requirements, then build `VoterOracle`.
|
||||||
pub fn checked_new(
|
pub fn checked_new(sessions: VecDeque<Rounds<B>>, min_block_delta: u32) -> Option<Self> {
|
||||||
sessions: VecDeque<Rounds<Payload, B>>,
|
|
||||||
min_block_delta: u32,
|
|
||||||
) -> Option<Self> {
|
|
||||||
let mut prev_start = Zero::zero();
|
let mut prev_start = Zero::zero();
|
||||||
let mut prev_validator_id = None;
|
let mut prev_validator_id = None;
|
||||||
// verifies the
|
// verifies the
|
||||||
@@ -136,13 +134,13 @@ impl<B: Block> VoterOracle<B> {
|
|||||||
|
|
||||||
// Return reference to rounds pertaining to first session in the queue.
|
// Return reference to rounds pertaining to first session in the queue.
|
||||||
// Voting will always happen at the head of the queue.
|
// Voting will always happen at the head of the queue.
|
||||||
fn active_rounds(&self) -> Option<&Rounds<Payload, B>> {
|
fn active_rounds(&self) -> Option<&Rounds<B>> {
|
||||||
self.sessions.front()
|
self.sessions.front()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return mutable reference to rounds pertaining to first session in the queue.
|
// Return mutable reference to rounds pertaining to first session in the queue.
|
||||||
// Voting will always happen at the head of the queue.
|
// Voting will always happen at the head of the queue.
|
||||||
fn active_rounds_mut(&mut self) -> Option<&mut Rounds<Payload, B>> {
|
fn active_rounds_mut(&mut self) -> Option<&mut Rounds<B>> {
|
||||||
self.sessions.front_mut()
|
self.sessions.front_mut()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +155,7 @@ impl<B: Block> VoterOracle<B> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add new observed session to the Oracle.
|
/// Add new observed session to the Oracle.
|
||||||
pub fn add_session(&mut self, rounds: Rounds<Payload, B>) {
|
pub fn add_session(&mut self, rounds: Rounds<B>) {
|
||||||
self.sessions.push_back(rounds);
|
self.sessions.push_back(rounds);
|
||||||
// Once we add a new session we can drop/prune previous session if it's been finalized.
|
// Once we add a new session we can drop/prune previous session if it's been finalized.
|
||||||
self.try_prune();
|
self.try_prune();
|
||||||
@@ -264,6 +262,8 @@ pub(crate) struct PersistedState<B: Block> {
|
|||||||
best_grandpa_block_header: <B as Block>::Header,
|
best_grandpa_block_header: <B as Block>::Header,
|
||||||
/// Best block a BEEFY voting round has been concluded for.
|
/// Best block a BEEFY voting round has been concluded for.
|
||||||
best_beefy_block: NumberFor<B>,
|
best_beefy_block: NumberFor<B>,
|
||||||
|
/// Best block we voted on.
|
||||||
|
best_voted: NumberFor<B>,
|
||||||
/// Chooses which incoming votes to accept and which votes to generate.
|
/// Chooses which incoming votes to accept and which votes to generate.
|
||||||
/// Keeps track of voting seen for current and future rounds.
|
/// Keeps track of voting seen for current and future rounds.
|
||||||
voting_oracle: VoterOracle<B>,
|
voting_oracle: VoterOracle<B>,
|
||||||
@@ -273,12 +273,13 @@ impl<B: Block> PersistedState<B> {
|
|||||||
pub fn checked_new(
|
pub fn checked_new(
|
||||||
grandpa_header: <B as Block>::Header,
|
grandpa_header: <B as Block>::Header,
|
||||||
best_beefy: NumberFor<B>,
|
best_beefy: NumberFor<B>,
|
||||||
sessions: VecDeque<Rounds<Payload, B>>,
|
sessions: VecDeque<Rounds<B>>,
|
||||||
min_block_delta: u32,
|
min_block_delta: u32,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
VoterOracle::checked_new(sessions, min_block_delta).map(|voting_oracle| PersistedState {
|
VoterOracle::checked_new(sessions, min_block_delta).map(|voting_oracle| PersistedState {
|
||||||
best_grandpa_block_header: grandpa_header,
|
best_grandpa_block_header: grandpa_header,
|
||||||
best_beefy_block: best_beefy,
|
best_beefy_block: best_beefy,
|
||||||
|
best_voted: Zero::zero(),
|
||||||
voting_oracle,
|
voting_oracle,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -381,7 +382,7 @@ where
|
|||||||
&self.persisted_state.voting_oracle
|
&self.persisted_state.voting_oracle
|
||||||
}
|
}
|
||||||
|
|
||||||
fn active_rounds(&mut self) -> Option<&Rounds<Payload, B>> {
|
fn active_rounds(&mut self) -> Option<&Rounds<B>> {
|
||||||
self.persisted_state.voting_oracle.active_rounds()
|
self.persisted_state.voting_oracle.active_rounds()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,12 +487,9 @@ where
|
|||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let block_num = vote.commitment.block_number;
|
let block_num = vote.commitment.block_number;
|
||||||
let best_grandpa = self.best_grandpa_block();
|
let best_grandpa = self.best_grandpa_block();
|
||||||
|
self.gossip_validator.note_round(block_num);
|
||||||
match self.voting_oracle().triage_round(block_num, best_grandpa)? {
|
match self.voting_oracle().triage_round(block_num, best_grandpa)? {
|
||||||
RoundAction::Process => self.handle_vote(
|
RoundAction::Process => self.handle_vote(vote)?,
|
||||||
(vote.commitment.payload, vote.commitment.block_number),
|
|
||||||
(vote.id, vote.signature),
|
|
||||||
false,
|
|
||||||
)?,
|
|
||||||
RoundAction::Enqueue => {
|
RoundAction::Enqueue => {
|
||||||
debug!(target: LOG_TARGET, "🥩 Buffer vote for round: {:?}.", block_num);
|
debug!(target: LOG_TARGET, "🥩 Buffer vote for round: {:?}.", block_num);
|
||||||
if self.pending_votes.len() < MAX_BUFFERED_VOTE_ROUNDS {
|
if self.pending_votes.len() < MAX_BUFFERED_VOTE_ROUNDS {
|
||||||
@@ -546,57 +544,47 @@ where
|
|||||||
|
|
||||||
fn handle_vote(
|
fn handle_vote(
|
||||||
&mut self,
|
&mut self,
|
||||||
round: (Payload, NumberFor<B>),
|
vote: VoteMessage<NumberFor<B>, AuthorityId, Signature>,
|
||||||
vote: (AuthorityId, Signature),
|
|
||||||
self_vote: bool,
|
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
self.gossip_validator.note_round(round.1);
|
|
||||||
|
|
||||||
let rounds = self
|
let rounds = self
|
||||||
.persisted_state
|
.persisted_state
|
||||||
.voting_oracle
|
.voting_oracle
|
||||||
.active_rounds_mut()
|
.active_rounds_mut()
|
||||||
.ok_or(Error::UninitSession)?;
|
.ok_or(Error::UninitSession)?;
|
||||||
|
|
||||||
if rounds.add_vote(&round, vote, self_vote) {
|
let block_number = vote.commitment.block_number;
|
||||||
if let Some(signatures) = rounds.should_conclude(&round) {
|
match rounds.add_vote(vote) {
|
||||||
self.gossip_validator.conclude_round(round.1);
|
VoteImportResult::RoundConcluded(signed_commitment) => {
|
||||||
|
self.gossip_validator.conclude_round(block_number);
|
||||||
let block_num = round.1;
|
metric_set!(self, beefy_round_concluded, block_number);
|
||||||
let commitment = Commitment {
|
|
||||||
payload: round.0,
|
|
||||||
block_number: block_num,
|
|
||||||
validator_set_id: rounds.validator_set_id(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let finality_proof =
|
|
||||||
VersionedFinalityProof::V1(SignedCommitment { commitment, signatures });
|
|
||||||
|
|
||||||
metric_set!(self, beefy_round_concluded, block_num);
|
|
||||||
|
|
||||||
|
let finality_proof = VersionedFinalityProof::V1(signed_commitment);
|
||||||
info!(
|
info!(
|
||||||
target: LOG_TARGET,
|
target: LOG_TARGET,
|
||||||
"🥩 Round #{} concluded, finality_proof: {:?}.", round.1, finality_proof
|
"🥩 Round #{} concluded, finality_proof: {:?}.", block_number, finality_proof
|
||||||
);
|
);
|
||||||
|
|
||||||
// We created the `finality_proof` and know to be valid.
|
// We created the `finality_proof` and know to be valid.
|
||||||
// New state is persisted after finalization.
|
// New state is persisted after finalization.
|
||||||
self.finalize(finality_proof)?;
|
self.finalize(finality_proof)?;
|
||||||
} else {
|
},
|
||||||
let mandatory_round = self
|
VoteImportResult::Ok => {
|
||||||
|
// Persist state after handling mandatory block vote.
|
||||||
|
if self
|
||||||
.voting_oracle()
|
.voting_oracle()
|
||||||
.mandatory_pending()
|
.mandatory_pending()
|
||||||
.map(|p| p.0 == round.1)
|
.map(|(mandatory_num, _)| mandatory_num == block_number)
|
||||||
.unwrap_or(false);
|
.unwrap_or(false)
|
||||||
// Persist state after handling self vote to avoid double voting in case
|
{
|
||||||
// of voter restarts.
|
|
||||||
// Also persist state after handling mandatory block vote.
|
|
||||||
if self_vote || mandatory_round {
|
|
||||||
crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state)
|
crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state)
|
||||||
.map_err(|e| Error::Backend(e.to_string()))?;
|
.map_err(|e| Error::Backend(e.to_string()))?;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
VoteImportResult::Equivocation => {
|
||||||
|
// TODO: report returned `EquivocationProof` to chain through `pallet-beefy`.
|
||||||
|
()
|
||||||
|
},
|
||||||
|
VoteImportResult::Invalid | VoteImportResult::Stale => (),
|
||||||
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,11 +683,7 @@ where
|
|||||||
for (num, votes) in votes_to_handle.into_iter() {
|
for (num, votes) in votes_to_handle.into_iter() {
|
||||||
debug!(target: LOG_TARGET, "🥩 Handle buffered votes for: {:?}.", num);
|
debug!(target: LOG_TARGET, "🥩 Handle buffered votes for: {:?}.", num);
|
||||||
for v in votes.into_iter() {
|
for v in votes.into_iter() {
|
||||||
if let Err(err) = self.handle_vote(
|
if let Err(err) = self.handle_vote(v) {
|
||||||
(v.commitment.payload, v.commitment.block_number),
|
|
||||||
(v.id, v.signature),
|
|
||||||
false,
|
|
||||||
) {
|
|
||||||
error!(target: LOG_TARGET, "🥩 Error handling buffered vote: {}", err);
|
error!(target: LOG_TARGET, "🥩 Error handling buffered vote: {}", err);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -716,7 +700,9 @@ where
|
|||||||
.voting_target(self.best_beefy_block(), self.best_grandpa_block())
|
.voting_target(self.best_beefy_block(), self.best_grandpa_block())
|
||||||
{
|
{
|
||||||
metric_set!(self, beefy_should_vote_on, target);
|
metric_set!(self, beefy_should_vote_on, target);
|
||||||
self.do_vote(target)?;
|
if target > self.persisted_state.best_voted {
|
||||||
|
self.do_vote(target)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -766,13 +752,6 @@ where
|
|||||||
.voting_oracle
|
.voting_oracle
|
||||||
.active_rounds_mut()
|
.active_rounds_mut()
|
||||||
.ok_or(Error::UninitSession)?;
|
.ok_or(Error::UninitSession)?;
|
||||||
if !rounds.should_self_vote(&(payload.clone(), target_number)) {
|
|
||||||
debug!(
|
|
||||||
target: LOG_TARGET,
|
|
||||||
"🥩 Don't double vote for block number: {:?}", target_number
|
|
||||||
);
|
|
||||||
return Ok(())
|
|
||||||
}
|
|
||||||
let (validators, validator_set_id) = (rounds.validators(), rounds.validator_set_id());
|
let (validators, validator_set_id) = (rounds.validators(), rounds.validator_set_id());
|
||||||
|
|
||||||
let authority_id = if let Some(id) = self.key_store.authority_id(validators) {
|
let authority_id = if let Some(id) = self.key_store.authority_id(validators) {
|
||||||
@@ -812,17 +791,17 @@ where
|
|||||||
|
|
||||||
debug!(target: LOG_TARGET, "🥩 Sent vote message: {:?}", message);
|
debug!(target: LOG_TARGET, "🥩 Sent vote message: {:?}", message);
|
||||||
|
|
||||||
if let Err(err) = self.handle_vote(
|
if let Err(err) = self.handle_vote(message) {
|
||||||
(message.commitment.payload, message.commitment.block_number),
|
|
||||||
(message.id, message.signature),
|
|
||||||
true,
|
|
||||||
) {
|
|
||||||
error!(target: LOG_TARGET, "🥩 Error handling self vote: {}", err);
|
error!(target: LOG_TARGET, "🥩 Error handling self vote: {}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.gossip_engine.gossip_message(topic::<B>(), encoded_message, false);
|
self.gossip_engine.gossip_message(topic::<B>(), encoded_message, false);
|
||||||
|
|
||||||
Ok(())
|
// Persist state after vote to avoid double voting in case of voter restarts.
|
||||||
|
self.persisted_state.best_voted = target_number;
|
||||||
|
metric_set!(self, beefy_best_voted, target_number);
|
||||||
|
crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state)
|
||||||
|
.map_err(|e| Error::Backend(e.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_new_state(&mut self) {
|
fn process_new_state(&mut self) {
|
||||||
@@ -848,8 +827,7 @@ where
|
|||||||
|
|
||||||
/// Main loop for BEEFY worker.
|
/// Main loop for BEEFY worker.
|
||||||
///
|
///
|
||||||
/// Wait for BEEFY runtime pallet to be available, then start the main async loop
|
/// Run the main async loop which is driven by finality notifications and gossiped votes.
|
||||||
/// which is driven by finality notifications and gossiped votes.
|
|
||||||
pub(crate) async fn run(
|
pub(crate) async fn run(
|
||||||
mut self,
|
mut self,
|
||||||
mut block_import_justif: Fuse<NotificationReceiver<BeefyVersionedFinalityProof<B>>>,
|
mut block_import_justif: Fuse<NotificationReceiver<BeefyVersionedFinalityProof<B>>>,
|
||||||
@@ -992,14 +970,15 @@ pub(crate) mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{
|
use crate::{
|
||||||
communication::notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofStream},
|
communication::notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofStream},
|
||||||
keystore::tests::Keyring,
|
|
||||||
tests::{
|
tests::{
|
||||||
create_beefy_keystore, get_beefy_streams, make_beefy_ids, BeefyPeer, BeefyTestNet,
|
create_beefy_keystore, get_beefy_streams, make_beefy_ids, BeefyPeer, BeefyTestNet,
|
||||||
TestApi,
|
TestApi,
|
||||||
},
|
},
|
||||||
BeefyRPCLinks, KnownPeers,
|
BeefyRPCLinks, KnownPeers,
|
||||||
};
|
};
|
||||||
use beefy_primitives::{known_payloads, mmr::MmrRootProvider};
|
use beefy_primitives::{
|
||||||
|
keyring::Keyring, known_payloads, mmr::MmrRootProvider, Payload, SignedCommitment,
|
||||||
|
};
|
||||||
use futures::{future::poll_fn, task::Poll};
|
use futures::{future::poll_fn, task::Poll};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use sc_client_api::{Backend as BackendT, HeaderBackend};
|
use sc_client_api::{Backend as BackendT, HeaderBackend};
|
||||||
@@ -1007,7 +986,7 @@ pub(crate) mod tests {
|
|||||||
use sc_network_test::TestNetFactory;
|
use sc_network_test::TestNetFactory;
|
||||||
use sp_api::HeaderT;
|
use sp_api::HeaderT;
|
||||||
use sp_blockchain::Backend as BlockchainBackendT;
|
use sp_blockchain::Backend as BlockchainBackendT;
|
||||||
use sp_runtime::traits::{One, Zero};
|
use sp_runtime::traits::One;
|
||||||
use substrate_test_runtime_client::{
|
use substrate_test_runtime_client::{
|
||||||
runtime::{Block, Digest, DigestItem, Header, H256},
|
runtime::{Block, Digest, DigestItem, Header, H256},
|
||||||
Backend,
|
Backend,
|
||||||
@@ -1018,7 +997,7 @@ pub(crate) mod tests {
|
|||||||
&self.voting_oracle
|
&self.voting_oracle
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn active_round(&self) -> Option<&Rounds<Payload, B>> {
|
pub fn active_round(&self) -> Option<&Rounds<B>> {
|
||||||
self.voting_oracle.active_rounds()
|
self.voting_oracle.active_rounds()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1032,7 +1011,7 @@ pub(crate) mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<B: super::Block> VoterOracle<B> {
|
impl<B: super::Block> VoterOracle<B> {
|
||||||
pub fn sessions(&self) -> &VecDeque<Rounds<Payload, B>> {
|
pub fn sessions(&self) -> &VecDeque<Rounds<B>> {
|
||||||
&self.sessions
|
&self.sessions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1090,7 +1069,7 @@ pub(crate) mod tests {
|
|||||||
min_block_delta,
|
min_block_delta,
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let payload_provider = MmrRootProvider::new(api);
|
let payload_provider = MmrRootProvider::new(api.clone());
|
||||||
let worker_params = crate::worker::WorkerParams {
|
let worker_params = crate::worker::WorkerParams {
|
||||||
backend,
|
backend,
|
||||||
payload_provider,
|
payload_provider,
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ sp-io = { version = "7.0.0", default-features = false, path = "../io" }
|
|||||||
sp-mmr-primitives = { version = "4.0.0-dev", default-features = false, path = "../merkle-mountain-range" }
|
sp-mmr-primitives = { version = "4.0.0-dev", default-features = false, path = "../merkle-mountain-range" }
|
||||||
sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" }
|
sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" }
|
||||||
sp-std = { version = "5.0.0", default-features = false, path = "../std" }
|
sp-std = { version = "5.0.0", default-features = false, path = "../std" }
|
||||||
|
strum = { version = "0.24.1", features = ["derive"] }
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
array-bytes = "4.1"
|
array-bytes = "4.1"
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ where
|
|||||||
self.validator_set_id
|
self.validator_set_id
|
||||||
.cmp(&other.validator_set_id)
|
.cmp(&other.validator_set_id)
|
||||||
.then_with(|| self.block_number.cmp(&other.block_number))
|
.then_with(|| self.block_number.cmp(&other.block_number))
|
||||||
|
.then_with(|| self.payload.cmp(&other.payload))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ pub enum ConsensusLog<AuthorityId: Codec> {
|
|||||||
///
|
///
|
||||||
/// A vote message is a direct vote created by a BEEFY node on every voting round
|
/// A vote message is a direct vote created by a BEEFY node on every voting round
|
||||||
/// and is gossiped to its peers.
|
/// and is gossiped to its peers.
|
||||||
#[derive(Debug, Decode, Encode, TypeInfo)]
|
#[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)]
|
||||||
pub struct VoteMessage<Number, Id, Signature> {
|
pub struct VoteMessage<Number, Id, Signature> {
|
||||||
/// Commit to information extracted from a finalized block
|
/// Commit to information extracted from a finalized block
|
||||||
pub commitment: Commitment<Number>,
|
pub commitment: Commitment<Number>,
|
||||||
@@ -209,6 +209,83 @@ sp_api::decl_runtime_apis! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "std")]
|
||||||
|
/// Test accounts using [`crate::crypto`] types.
|
||||||
|
pub mod keyring {
|
||||||
|
use super::*;
|
||||||
|
use sp_core::{ecdsa, keccak_256, Pair};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
/// Set of test accounts using [`crate::crypto`] types.
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display, strum::EnumIter)]
|
||||||
|
pub enum Keyring {
|
||||||
|
Alice,
|
||||||
|
Bob,
|
||||||
|
Charlie,
|
||||||
|
Dave,
|
||||||
|
Eve,
|
||||||
|
Ferdie,
|
||||||
|
One,
|
||||||
|
Two,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Keyring {
|
||||||
|
/// Sign `msg`.
|
||||||
|
pub fn sign(self, msg: &[u8]) -> crypto::Signature {
|
||||||
|
// todo: use custom signature hashing type
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get Keyring from public key.
|
||||||
|
pub fn from_public(who: &crypto::Public) -> Option<Keyring> {
|
||||||
|
Self::iter().find(|&k| &crypto::Public::from(k) == who)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lazy_static::lazy_static! {
|
||||||
|
static ref PRIVATE_KEYS: HashMap<Keyring, crypto::Pair> =
|
||||||
|
Keyring::iter().map(|i| (i, i.pair())).collect();
|
||||||
|
static ref PUBLIC_KEYS: HashMap<Keyring, crypto::Public> =
|
||||||
|
PRIVATE_KEYS.iter().map(|(&name, pair)| (name, pair.public())).collect();
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Keyring> for crypto::Public {
|
||||||
|
fn from(k: Keyring) -> Self {
|
||||||
|
(*PUBLIC_KEYS).get(&k).cloned().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
Reference in New Issue
Block a user