diff --git a/client/consensus/beefy/src/communication/fisherman.rs b/client/consensus/beefy/src/communication/fisherman.rs
new file mode 100644
index 0000000000000..2fa91ce783966
--- /dev/null
+++ b/client/consensus/beefy/src/communication/fisherman.rs
@@ -0,0 +1,244 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+use crate::{
+ error::Error,
+ justification::BeefyVersionedFinalityProof,
+ keystore::{BeefyKeystore, BeefySignatureHasher},
+ LOG_TARGET,
+};
+use log::debug;
+use sc_client_api::Backend;
+use sp_api::ProvideRuntimeApi;
+use sp_blockchain::HeaderBackend;
+use sp_consensus_beefy::{
+ check_fork_equivocation_proof,
+ ecdsa_crypto::{AuthorityId, Signature},
+ BeefyApi, ForkEquivocationProof, Payload, PayloadProvider, SignedCommitment, ValidatorSet,
+ VoteMessage,
+};
+use sp_runtime::{
+ generic::BlockId,
+ traits::{Block, Header, NumberFor},
+};
+use std::{marker::PhantomData, sync::Arc};
+
+pub(crate) trait BeefyFisherman: Send + Sync {
+ /// Check `vote` for contained block against expected payload.
+ fn check_vote(
+ &self,
+ vote: VoteMessage, AuthorityId, Signature>,
+ ) -> Result<(), Error>;
+
+ /// Check `signed_commitment` for contained block against expected payload.
+ fn check_signed_commitment(
+ &self,
+ signed_commitment: SignedCommitment, Signature>,
+ ) -> Result<(), Error>;
+
+ /// Check `proof` for contained block against expected payload.
+ fn check_proof(&self, proof: BeefyVersionedFinalityProof) -> Result<(), Error>;
+}
+
+/// Helper wrapper used to check gossiped votes for (historical) equivocations,
+/// and report any such protocol infringements.
+pub(crate) struct Fisherman {
+ pub backend: Arc,
+ pub runtime: Arc,
+ pub key_store: Arc,
+ pub payload_provider: P,
+ pub _phantom: PhantomData,
+}
+
+impl Fisherman
+where
+ B: Block,
+ BE: Backend,
+ P: PayloadProvider,
+ R: ProvideRuntimeApi + Send + Sync,
+ R::Api: BeefyApi,
+{
+ fn expected_header_and_payload(
+ &self,
+ number: NumberFor,
+ ) -> Result<(B::Header, Payload), Error> {
+ // This should be un-ambiguous since `number` is finalized.
+ let hash = self
+ .backend
+ .blockchain()
+ .expect_block_hash_from_id(&BlockId::Number(number))
+ .map_err(|e| Error::Backend(e.to_string()))?;
+ let header = self
+ .backend
+ .blockchain()
+ .expect_header(hash)
+ .map_err(|e| Error::Backend(e.to_string()))?;
+ self.payload_provider
+ .payload(&header)
+ .map(|payload| (header, payload))
+ .ok_or_else(|| Error::Backend("BEEFY Payload not found".into()))
+ }
+
+ fn active_validator_set_at(
+ &self,
+ header: &B::Header,
+ ) -> Result, Error> {
+ self.runtime
+ .runtime_api()
+ .validator_set(header.hash())
+ .map_err(Error::RuntimeApi)?
+ .ok_or_else(|| Error::Backend("could not get BEEFY validator set".into()))
+ }
+
+ pub(crate) fn report_fork_equivocation(
+ &self,
+ proof: ForkEquivocationProof, AuthorityId, Signature, B::Header>,
+ ) -> Result<(), Error> {
+ let validator_set = self.active_validator_set_at(&proof.correct_header)?;
+ let set_id = validator_set.id();
+
+ let expected_header_hash = self
+ .backend
+ .blockchain()
+ .expect_block_hash_from_id(&BlockId::Number(proof.commitment.block_number))
+ .map_err(|e| Error::Backend(e.to_string()))?;
+
+ if proof.commitment.validator_set_id != set_id ||
+ !check_fork_equivocation_proof::<
+ NumberFor,
+ AuthorityId,
+ BeefySignatureHasher,
+ B::Header,
+ >(&proof, &expected_header_hash)
+ {
+ debug!(target: LOG_TARGET, "🥩 Skip report for bad invalid fork proof {:?}", proof);
+ return Ok(())
+ }
+
+ let offender_ids =
+ proof.signatories.iter().cloned().map(|(id, _sig)| id).collect::>();
+ if let Some(local_id) = self.key_store.authority_id(validator_set.validators()) {
+ if offender_ids.contains(&local_id) {
+ debug!(target: LOG_TARGET, "🥩 Skip equivocation report for own equivocation");
+ // TODO: maybe error here instead?
+ return Ok(())
+ }
+ }
+
+ let hash = proof.correct_header.hash();
+ let runtime_api = self.runtime.runtime_api();
+
+ // generate key ownership proof at that block
+ let key_owner_proofs = offender_ids
+ .iter()
+ .filter_map(|id| {
+ match runtime_api.generate_key_ownership_proof(hash, set_id, id.clone()) {
+ Ok(Some(proof)) => Some(Ok(proof)),
+ Ok(None) => {
+ debug!(
+ target: LOG_TARGET,
+ "🥩 Invalid fork vote offender not part of the authority set."
+ );
+ None
+ },
+ Err(e) => Some(Err(Error::RuntimeApi(e))),
+ }
+ })
+ .collect::>()?;
+
+ // submit invalid fork vote report at **best** block
+ let best_block_hash = self.backend.blockchain().info().best_hash;
+ runtime_api
+ .submit_report_fork_equivocation_unsigned_extrinsic(
+ best_block_hash,
+ proof,
+ key_owner_proofs,
+ )
+ .map_err(Error::RuntimeApi)?;
+
+ Ok(())
+ }
+}
+
+impl BeefyFisherman for Fisherman
+where
+ B: Block,
+ BE: Backend,
+ P: PayloadProvider,
+ R: ProvideRuntimeApi + Send + Sync,
+ R::Api: BeefyApi,
+{
+ /// Check `vote` for contained block against expected payload.
+ fn check_vote(
+ &self,
+ vote: VoteMessage, AuthorityId, Signature>,
+ ) -> Result<(), Error> {
+ let number = vote.commitment.block_number;
+ let (correct_header, expected_payload) = self.expected_header_and_payload(number)?;
+ if vote.commitment.payload != expected_payload {
+ let proof = ForkEquivocationProof {
+ commitment: vote.commitment,
+ signatories: vec![(vote.id, vote.signature)],
+ correct_header: correct_header.clone(),
+ };
+ self.report_fork_equivocation(proof)?;
+ }
+ Ok(())
+ }
+
+ /// Check `signed_commitment` for contained block against expected payload.
+ fn check_signed_commitment(
+ &self,
+ signed_commitment: SignedCommitment, Signature>,
+ ) -> Result<(), Error> {
+ let SignedCommitment { commitment, signatures } = signed_commitment;
+ let number = commitment.block_number;
+ let (correct_header, expected_payload) = self.expected_header_and_payload(number)?;
+ if commitment.payload != expected_payload {
+ let validator_set = self.active_validator_set_at(&correct_header)?;
+ if signatures.len() != validator_set.validators().len() {
+ // invalid proof
+ return Ok(())
+ }
+ // report every signer of the bad justification
+ let signatories = validator_set
+ .validators()
+ .iter()
+ .cloned()
+ .zip(signatures.into_iter())
+ .filter_map(|(id, signature)| signature.map(|sig| (id, sig)))
+ .collect();
+
+ let proof = ForkEquivocationProof {
+ commitment,
+ signatories,
+ correct_header: correct_header.clone(),
+ };
+ self.report_fork_equivocation(proof)?;
+ }
+ Ok(())
+ }
+
+ /// Check `proof` for contained block against expected payload.
+ fn check_proof(&self, proof: BeefyVersionedFinalityProof) -> Result<(), Error> {
+ match proof {
+ BeefyVersionedFinalityProof::::V1(signed_commitment) =>
+ self.check_signed_commitment(signed_commitment),
+ }
+ }
+}
diff --git a/client/consensus/beefy/src/communication/gossip.rs b/client/consensus/beefy/src/communication/gossip.rs
index 8c025ca067619..64da631362132 100644
--- a/client/consensus/beefy/src/communication/gossip.rs
+++ b/client/consensus/beefy/src/communication/gossip.rs
@@ -32,6 +32,7 @@ use wasm_timer::Instant;
use crate::{
communication::{
benefit, cost,
+ fisherman::BeefyFisherman,
peers::{KnownPeers, PeerReport},
},
justification::{
@@ -225,26 +226,29 @@ impl Filter {
/// Allows messages for 'rounds >= last concluded' to flow, everything else gets
/// rejected/expired.
///
+/// Messages for active and expired rounds are validated for expected payloads and attempts
+/// to create forks before head of GRANDPA are reported.
+///
///All messaging is handled in a single BEEFY global topic.
-pub(crate) struct GossipValidator
-where
- B: Block,
-{
+pub(crate) struct GossipValidator {
votes_topic: B::Hash,
justifs_topic: B::Hash,
gossip_filter: RwLock>,
next_rebroadcast: Mutex,
known_peers: Arc>>,
report_sender: TracingUnboundedSender,
+ pub(crate) fisherman: F,
}
-impl GossipValidator
+impl GossipValidator
where
B: Block,
+ F: BeefyFisherman,
{
pub(crate) fn new(
known_peers: Arc>>,
- ) -> (GossipValidator, TracingUnboundedReceiver) {
+ fisherman: F,
+ ) -> (GossipValidator, TracingUnboundedReceiver) {
let (tx, rx) = tracing_unbounded("mpsc_beefy_gossip_validator", 10_000);
let val = GossipValidator {
votes_topic: votes_topic::(),
@@ -253,6 +257,7 @@ where
next_rebroadcast: Mutex::new(Instant::now() + REBROADCAST_AFTER),
known_peers,
report_sender: tx,
+ fisherman,
};
(val, rx)
}
@@ -287,9 +292,18 @@ where
let filter = self.gossip_filter.read();
match filter.consider_vote(round, set_id) {
- Consider::RejectPast => return Action::Discard(cost::OUTDATED_MESSAGE),
Consider::RejectFuture => return Action::Discard(cost::FUTURE_MESSAGE),
Consider::RejectOutOfScope => return Action::Discard(cost::OUT_OF_SCOPE_MESSAGE),
+ Consider::RejectPast => {
+ // We know `vote` is for some past (finalized) block. Have fisherman check
+ // for equivocations. Best-effort, ignore errors such as state pruned.
+ let _ = self.fisherman.check_vote(vote);
+ // TODO: maybe raise cost reputation when seeing votes that are intentional
+ // spam: votes that trigger fisherman reports, but don't go through either
+ // because signer is/was not authority or similar reasons.
+ // The idea is to more quickly disconnect neighbors which are attempting DoS.
+ return Action::Discard(cost::OUTDATED_MESSAGE)
+ },
Consider::Accept => {},
}
@@ -331,9 +345,18 @@ where
let guard = self.gossip_filter.read();
// Verify general usefulness of the justification.
match guard.consider_finality_proof(round, set_id) {
- Consider::RejectPast => return Action::Discard(cost::OUTDATED_MESSAGE),
Consider::RejectFuture => return Action::Discard(cost::FUTURE_MESSAGE),
Consider::RejectOutOfScope => return Action::Discard(cost::OUT_OF_SCOPE_MESSAGE),
+ Consider::RejectPast => {
+ // We know `proof` is for some past (finalized) block. Have fisherman check
+ // for equivocations. Best-effort, ignore errors such as state pruned.
+ let _ = self.fisherman.check_proof(proof);
+ // TODO: maybe raise cost reputation when seeing votes that are intentional
+ // spam: votes that trigger fisherman reports, but don't go through either because
+ // signer is/was not authority or similar reasons.
+ // The idea is to more quickly disconnect neighbors which are attempting DoS.
+ return Action::Discard(cost::OUTDATED_MESSAGE)
+ },
Consider::Accept => {},
}
// Verify justification signatures.
@@ -359,9 +382,10 @@ where
}
}
-impl Validator for GossipValidator
+impl Validator for GossipValidator
where
B: Block,
+ F: BeefyFisherman,
{
fn peer_disconnected(&self, _context: &mut dyn ValidatorContext, who: &PeerId) {
self.known_peers.lock().remove(who);
@@ -474,7 +498,7 @@ where
#[cfg(test)]
pub(crate) mod tests {
use super::*;
- use crate::keystore::BeefyKeystore;
+ use crate::{keystore::BeefyKeystore, tests::DummyFisherman};
use sc_network_test::Block;
use sp_application_crypto::key_types::BEEFY as BEEFY_KEY_TYPE;
use sp_consensus_beefy::{
@@ -482,6 +506,7 @@ pub(crate) mod tests {
SignedCommitment, VoteMessage,
};
use sp_keystore::{testing::MemoryKeystore, Keystore};
+ use std::marker::PhantomData;
#[test]
fn known_votes_insert_remove() {
@@ -577,8 +602,9 @@ pub(crate) mod tests {
fn should_validate_messages() {
let keys = vec![Keyring::Alice.public()];
let validator_set = ValidatorSet::::new(keys.clone(), 0).unwrap();
+ let fisherman = DummyFisherman { _phantom: PhantomData:: };
let (gv, mut report_stream) =
- GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new())));
+ GossipValidator::new(Arc::new(Mutex::new(KnownPeers::new())), fisherman);
let sender = PeerId::random();
let mut context = TestContext;
@@ -705,7 +731,8 @@ pub(crate) mod tests {
fn messages_allowed_and_expired() {
let keys = vec![Keyring::Alice.public()];
let validator_set = ValidatorSet::::new(keys.clone(), 0).unwrap();
- let (gv, _) = GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new())));
+ let fisherman = DummyFisherman { _phantom: PhantomData:: };
+ let (gv, _) = GossipValidator::new(Arc::new(Mutex::new(KnownPeers::new())), fisherman);
gv.update_filter(GossipFilterCfg { start: 0, end: 10, validator_set: &validator_set });
let sender = sc_network::PeerId::random();
let topic = Default::default();
@@ -782,7 +809,8 @@ pub(crate) mod tests {
fn messages_rebroadcast() {
let keys = vec![Keyring::Alice.public()];
let validator_set = ValidatorSet::::new(keys.clone(), 0).unwrap();
- let (gv, _) = GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new())));
+ let fisherman = DummyFisherman { _phantom: PhantomData:: };
+ let (gv, _) = GossipValidator::new(Arc::new(Mutex::new(KnownPeers::new())), fisherman);
gv.update_filter(GossipFilterCfg { start: 0, end: 10, validator_set: &validator_set });
let sender = sc_network::PeerId::random();
let topic = Default::default();
diff --git a/client/consensus/beefy/src/communication/mod.rs b/client/consensus/beefy/src/communication/mod.rs
index 7f9535bfc23f1..c31447d08e565 100644
--- a/client/consensus/beefy/src/communication/mod.rs
+++ b/client/consensus/beefy/src/communication/mod.rs
@@ -21,6 +21,7 @@
pub mod notification;
pub mod request_response;
+pub(crate) mod fisherman;
pub(crate) mod gossip;
pub(crate) mod peers;
diff --git a/client/consensus/beefy/src/lib.rs b/client/consensus/beefy/src/lib.rs
index 0b3baa007c1ce..d739a8143b525 100644
--- a/client/consensus/beefy/src/lib.rs
+++ b/client/consensus/beefy/src/lib.rs
@@ -18,6 +18,7 @@
use crate::{
communication::{
+ fisherman::Fisherman,
notification::{
BeefyBestBlockSender, BeefyBestBlockStream, BeefyVersionedFinalityProofSender,
BeefyVersionedFinalityProofStream,
@@ -28,6 +29,7 @@ use crate::{
},
},
import::BeefyBlockImport,
+ keystore::BeefyKeystore,
metrics::register_metrics,
round::Rounds,
worker::PersistedState,
@@ -219,10 +221,10 @@ pub async fn start_beefy_gadget(
beefy_params: BeefyParams,
) where
B: Block,
- BE: Backend,
+ BE: Backend + 'static,
C: Client + BlockBackend,
- P: PayloadProvider + Clone,
- R: ProvideRuntimeApi,
+ P: PayloadProvider,
+ R: ProvideRuntimeApi + Send + Sync + 'static,
R::Api: BeefyApi + MmrApi>,
N: GossipNetwork + NetworkRequest + Send + Sync + 'static,
S: GossipSyncing + SyncOracle + 'static,
@@ -240,6 +242,8 @@ pub async fn start_beefy_gadget(
mut on_demand_justifications_handler,
} = beefy_params;
+ let key_store: Arc = Arc::new(key_store.into());
+
let BeefyNetworkParams {
network,
sync,
@@ -259,10 +263,17 @@ pub async fn start_beefy_gadget(
// select recoverable errors.
loop {
let known_peers = Arc::new(Mutex::new(KnownPeers::new()));
+ let fisherman = Fisherman {
+ backend: backend.clone(),
+ runtime: runtime.clone(),
+ key_store: key_store.clone(),
+ payload_provider: payload_provider.clone(),
+ _phantom: PhantomData,
+ };
// Default votes filter is to discard everything.
// Validator is updated later with correct starting round and set id.
let (gossip_validator, gossip_report_stream) =
- communication::gossip::GossipValidator::new(known_peers.clone());
+ communication::gossip::GossipValidator::new(known_peers.clone(), fisherman);
let gossip_validator = Arc::new(gossip_validator);
let mut gossip_engine = GossipEngine::new(
network.clone(),
diff --git a/client/consensus/beefy/src/round.rs b/client/consensus/beefy/src/round.rs
index 6f400ce47843c..3abe662fc27fd 100644
--- a/client/consensus/beefy/src/round.rs
+++ b/client/consensus/beefy/src/round.rs
@@ -22,7 +22,7 @@ use codec::{Decode, Encode};
use log::debug;
use sp_consensus_beefy::{
ecdsa_crypto::{AuthorityId, Signature},
- Commitment, EquivocationProof, SignedCommitment, ValidatorSet, ValidatorSetId, VoteMessage,
+ Commitment, SignedCommitment, ValidatorSet, ValidatorSetId, VoteEquivocationProof, VoteMessage,
};
use sp_runtime::traits::{Block, NumberFor};
use std::collections::BTreeMap;
@@ -61,7 +61,7 @@ pub fn threshold(authorities: usize) -> usize {
pub enum VoteImportResult {
Ok,
RoundConcluded(SignedCommitment, Signature>),
- Equivocation(EquivocationProof, AuthorityId, Signature>),
+ Equivocation(VoteEquivocationProof, AuthorityId, Signature>),
Invalid,
Stale,
}
@@ -153,7 +153,7 @@ where
target: LOG_TARGET,
"🥩 detected equivocated vote: 1st: {:?}, 2nd: {:?}", previous_vote, vote
);
- return VoteImportResult::Equivocation(EquivocationProof {
+ return VoteImportResult::Equivocation(VoteEquivocationProof {
first: previous_vote.clone(),
second: vote,
})
@@ -203,8 +203,8 @@ mod tests {
use sc_network_test::Block;
use sp_consensus_beefy::{
- known_payloads::MMR_ROOT_ID, Commitment, EquivocationProof, Keyring, Payload,
- SignedCommitment, ValidatorSet, VoteMessage,
+ known_payloads::MMR_ROOT_ID, Commitment, Keyring, Payload, SignedCommitment, ValidatorSet,
+ VoteEquivocationProof, VoteMessage,
};
use super::{threshold, AuthorityId, Block as BlockT, RoundTracker, Rounds};
@@ -485,7 +485,7 @@ mod tests {
let mut alice_vote2 = alice_vote1.clone();
alice_vote2.commitment = commitment2;
- let expected_result = VoteImportResult::Equivocation(EquivocationProof {
+ let expected_result = VoteImportResult::Equivocation(VoteEquivocationProof {
first: alice_vote1.clone(),
second: alice_vote2.clone(),
});
diff --git a/client/consensus/beefy/src/tests.rs b/client/consensus/beefy/src/tests.rs
index 3bb65e9d57f43..5522e782fde86 100644
--- a/client/consensus/beefy/src/tests.rs
+++ b/client/consensus/beefy/src/tests.rs
@@ -22,12 +22,14 @@ use crate::{
aux_schema::{load_persistent, tests::verify_persisted_version},
beefy_block_import_and_links,
communication::{
+ fisherman::BeefyFisherman,
gossip::{
proofs_topic, tests::sign_commitment, votes_topic, GossipFilterCfg, GossipMessage,
GossipValidator,
},
request_response::{on_demand_justifications_protocol_config, BeefyJustifsRequestHandler},
},
+ error::Error,
gossip_protocol_name,
justification::*,
load_or_init_voter_state, wait_for_runtime_pallet, BeefyRPCLinks, BeefyVoterLinks, KnownPeers,
@@ -42,21 +44,21 @@ use sc_consensus::{
};
use sc_network::{config::RequestResponseConfig, ProtocolName};
use sc_network_test::{
- Block, BlockImportAdapter, FullPeerConfig, PassThroughVerifier, Peer, PeersClient,
+ Block, BlockImportAdapter, FullPeerConfig, Header, PassThroughVerifier, Peer, PeersClient,
PeersFullClient, TestNetFactory,
};
use sc_utils::notification::NotificationReceiver;
use serde::{Deserialize, Serialize};
-use sp_api::{ApiRef, ProvideRuntimeApi};
+use sp_api::{ApiRef, BlockT, ProvideRuntimeApi};
use sp_application_crypto::key_types::BEEFY as BEEFY_KEY_TYPE;
use sp_consensus::BlockOrigin;
use sp_consensus_beefy::{
ecdsa_crypto::{AuthorityId, Signature},
known_payloads,
mmr::{find_mmr_root_digest, MmrRootProvider},
- BeefyApi, Commitment, ConsensusLog, EquivocationProof, Keyring as BeefyKeyring, MmrRootHash,
- OpaqueKeyOwnershipProof, Payload, SignedCommitment, ValidatorSet, ValidatorSetId,
- VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID,
+ BeefyApi, Commitment, ConsensusLog, ForkEquivocationProof, Keyring as BeefyKeyring,
+ MmrRootHash, OpaqueKeyOwnershipProof, Payload, SignedCommitment, ValidatorSet, ValidatorSetId,
+ VersionedFinalityProof, VoteEquivocationProof, VoteMessage, BEEFY_ENGINE_ID,
};
use sp_core::H256;
use sp_keystore::{testing::MemoryKeystore, Keystore, KeystorePtr};
@@ -244,13 +246,38 @@ impl TestNetFactory for BeefyTestNet {
}
}
+pub(crate) struct DummyFisherman {
+ pub _phantom: PhantomData,
+}
+
+impl BeefyFisherman for DummyFisherman {
+ fn check_proof(&self, _: BeefyVersionedFinalityProof) -> Result<(), Error> {
+ Ok(())
+ }
+ fn check_signed_commitment(
+ &self,
+ _: SignedCommitment, Signature>,
+ ) -> Result<(), Error> {
+ Ok(())
+ }
+ fn check_vote(
+ &self,
+ _: VoteMessage, AuthorityId, Signature>,
+ ) -> Result<(), Error> {
+ Ok(())
+ }
+}
+
#[derive(Clone)]
pub(crate) struct TestApi {
pub beefy_genesis: u64,
pub validator_set: BeefyValidatorSet,
pub mmr_root_hash: MmrRootHash,
- pub reported_equivocations:
- Option, AuthorityId, Signature>>>>>,
+ pub reported_vote_equivocations:
+ Option, AuthorityId, Signature>>>>>,
+ pub reported_fork_equivocations: Option<
+ Arc, AuthorityId, Signature, Header>>>>,
+ >,
}
impl TestApi {
@@ -263,7 +290,8 @@ impl TestApi {
beefy_genesis,
validator_set: validator_set.clone(),
mmr_root_hash,
- reported_equivocations: None,
+ reported_vote_equivocations: None,
+ reported_fork_equivocations: None,
}
}
@@ -272,12 +300,14 @@ impl TestApi {
beefy_genesis: 1,
validator_set: validator_set.clone(),
mmr_root_hash: GOOD_MMR_ROOT,
- reported_equivocations: None,
+ reported_vote_equivocations: None,
+ reported_fork_equivocations: None,
}
}
pub fn allow_equivocations(&mut self) {
- self.reported_equivocations = Some(Arc::new(Mutex::new(vec![])));
+ self.reported_vote_equivocations = Some(Arc::new(Mutex::new(vec![])));
+ self.reported_fork_equivocations = Some(Arc::new(Mutex::new(vec![])));
}
}
@@ -303,11 +333,23 @@ sp_api::mock_impl_runtime_apis! {
Some(self.inner.validator_set.clone())
}
- fn submit_report_equivocation_unsigned_extrinsic(
- proof: EquivocationProof, AuthorityId, Signature>,
+ fn submit_report_vote_equivocation_unsigned_extrinsic(
+ proof: VoteEquivocationProof, AuthorityId, Signature>,
_dummy: OpaqueKeyOwnershipProof,
) -> Option<()> {
- if let Some(equivocations_buf) = self.inner.reported_equivocations.as_ref() {
+ if let Some(equivocations_buf) = self.inner.reported_vote_equivocations.as_ref() {
+ equivocations_buf.lock().push(proof);
+ None
+ } else {
+ panic!("Equivocations not expected, but following proof was reported: {:?}", proof);
+ }
+ }
+
+ fn submit_report_fork_equivocation_unsigned_extrinsic(
+ proof: ForkEquivocationProof, AuthorityId, Signature, Header>,
+ _dummy: Vec,
+ ) -> Option<()> {
+ if let Some(equivocations_buf) = self.inner.reported_fork_equivocations.as_ref() {
equivocations_buf.lock().push(proof);
None
} else {
@@ -364,8 +406,9 @@ async fn voter_init_setup(
api: &TestApi,
) -> sp_blockchain::Result> {
let backend = net.peer(0).client().as_backend();
+ let fisherman = DummyFisherman { _phantom: PhantomData };
let known_peers = Arc::new(Mutex::new(KnownPeers::new()));
- let (gossip_validator, _) = GossipValidator::new(known_peers);
+ let (gossip_validator, _) = GossipValidator::new(known_peers, fisherman);
let gossip_validator = Arc::new(gossip_validator);
let mut gossip_engine = sc_network_gossip::GossipEngine::new(
net.peer(0).network_service().clone(),
@@ -386,7 +429,7 @@ fn initialize_beefy(
min_block_delta: u32,
) -> impl Future