mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-31 20:21:03 +00:00
BEEFY: implement equivocations detection, reporting and slashing (#13121)
* client/beefy: simplify self_vote logic * client/beefy: migrate to new state version * client/beefy: detect equivocated votes * fix typos * sp-beefy: add equivocation primitives * client/beefy: refactor vote processing * fix version migration for new rounds struct * client/beefy: track equivocations and create proofs * client/beefy: adjust tests for new voting logic * sp-beefy: fix commitment ordering and equality * client/beefy: simplify handle_vote() a bit * client/beefy: add simple equivocation test * client/beefy: submit equivocation proof - WIP * frame/beefy: add equivocation report runtime api - part 1 * frame/beefy: report equivocation logic - part 2 * frame/beefy: add pluggable Equivocation handler - part 3 * frame/beefy: impl ValidateUnsigned for equivocations reporting * client/beefy: submit report equivocation unsigned extrinsic * primitives/beefy: fix tests * frame/beefy: add default weights * frame/beefy: fix tests * client/beefy: fix tests * frame/beefy-mmr: fix tests * frame/beefy: cross-check session index with equivocation report * sp-beefy: make test Keyring useable in pallet * frame/beefy: add basic equivocation test * frame/beefy: test verify equivocation results in slashing * frame/beefy: test report_equivocation_old_set * frame/beefy: add more equivocation tests * sp-beefy: fix docs * beefy: simplify equivocations and fix tests * client/beefy: address review comments * frame/beefy: add ValidateUnsigned to test/mock runtime * client/beefy: fixes after merge master * fix missed merge damage * client/beefy: add test for reporting equivocations Also validated there's no unexpected equivocations reported in the other tests. Signed-off-by: acatangiu <adrian@parity.io> * sp-beefy: move test utils to their own file * client/beefy: add negative test for equivocation reports * sp-beefy: move back MmrRootProvider - used in polkadot-service * impl review suggestions * client/beefy: add equivocation metrics --------- Signed-off-by: acatangiu <adrian@parity.io> Co-authored-by: parity-processbot <>
This commit is contained in:
@@ -31,10 +31,10 @@ use crate::{
|
||||
};
|
||||
use beefy_primitives::{
|
||||
crypto::{AuthorityId, Signature},
|
||||
keyring::Keyring as BeefyKeyring,
|
||||
known_payloads,
|
||||
mmr::MmrRootProvider,
|
||||
BeefyApi, Commitment, ConsensusLog, MmrRootHash, Payload, SignedCommitment, ValidatorSet,
|
||||
BeefyApi, Commitment, ConsensusLog, EquivocationProof, Keyring as BeefyKeyring, MmrRootHash,
|
||||
OpaqueKeyOwnershipProof, Payload, SignedCommitment, ValidatorSet, ValidatorSetId,
|
||||
VersionedFinalityProof, BEEFY_ENGINE_ID, KEY_TYPE as BeefyKeyType,
|
||||
};
|
||||
use futures::{future, stream::FuturesUnordered, Future, StreamExt};
|
||||
@@ -55,7 +55,7 @@ use sp_api::{ApiRef, ProvideRuntimeApi};
|
||||
use sp_consensus::BlockOrigin;
|
||||
use sp_core::H256;
|
||||
use sp_keystore::{testing::KeyStore as TestKeystore, SyncCryptoStore, SyncCryptoStorePtr};
|
||||
use sp_mmr_primitives::{EncodableOpaqueLeaf, Error as MmrError, MmrApi, Proof};
|
||||
use sp_mmr_primitives::{Error as MmrError, MmrApi};
|
||||
use sp_runtime::{
|
||||
codec::Encode,
|
||||
generic::BlockId,
|
||||
@@ -73,6 +73,7 @@ fn beefy_gossip_proto_name() -> ProtocolName {
|
||||
|
||||
const GOOD_MMR_ROOT: MmrRootHash = MmrRootHash::repeat_byte(0xbf);
|
||||
const BAD_MMR_ROOT: MmrRootHash = MmrRootHash::repeat_byte(0x42);
|
||||
const ALTERNATE_BAD_MMR_ROOT: MmrRootHash = MmrRootHash::repeat_byte(0x13);
|
||||
|
||||
type BeefyBlockImport = crate::BeefyBlockImport<
|
||||
Block,
|
||||
@@ -236,6 +237,8 @@ pub(crate) struct TestApi {
|
||||
pub beefy_genesis: u64,
|
||||
pub validator_set: BeefyValidatorSet,
|
||||
pub mmr_root_hash: MmrRootHash,
|
||||
pub reported_equivocations:
|
||||
Option<Arc<Mutex<Vec<EquivocationProof<NumberFor<Block>, AuthorityId, Signature>>>>>,
|
||||
}
|
||||
|
||||
impl TestApi {
|
||||
@@ -244,7 +247,12 @@ impl TestApi {
|
||||
validator_set: &BeefyValidatorSet,
|
||||
mmr_root_hash: MmrRootHash,
|
||||
) -> Self {
|
||||
TestApi { beefy_genesis, validator_set: validator_set.clone(), mmr_root_hash }
|
||||
TestApi {
|
||||
beefy_genesis,
|
||||
validator_set: validator_set.clone(),
|
||||
mmr_root_hash,
|
||||
reported_equivocations: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_validator_set(validator_set: &BeefyValidatorSet) -> Self {
|
||||
@@ -252,8 +260,13 @@ impl TestApi {
|
||||
beefy_genesis: 1,
|
||||
validator_set: validator_set.clone(),
|
||||
mmr_root_hash: GOOD_MMR_ROOT,
|
||||
reported_equivocations: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn allow_equivocations(&mut self) {
|
||||
self.reported_equivocations = Some(Arc::new(Mutex::new(vec![])));
|
||||
}
|
||||
}
|
||||
|
||||
// compiler gets confused and warns us about unused inner
|
||||
@@ -277,31 +290,29 @@ sp_api::mock_impl_runtime_apis! {
|
||||
fn validator_set() -> Option<BeefyValidatorSet> {
|
||||
Some(self.inner.validator_set.clone())
|
||||
}
|
||||
|
||||
fn submit_report_equivocation_unsigned_extrinsic(
|
||||
proof: EquivocationProof<NumberFor<Block>, AuthorityId, Signature>,
|
||||
_dummy: OpaqueKeyOwnershipProof,
|
||||
) -> Option<()> {
|
||||
if let Some(equivocations_buf) = self.inner.reported_equivocations.as_ref() {
|
||||
equivocations_buf.lock().push(proof);
|
||||
None
|
||||
} else {
|
||||
panic!("Equivocations not expected, but following proof was reported: {:?}", proof);
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_key_ownership_proof(
|
||||
_dummy1: ValidatorSetId,
|
||||
_dummy2: AuthorityId,
|
||||
) -> Option<OpaqueKeyOwnershipProof> { Some(OpaqueKeyOwnershipProof::new(vec![])) }
|
||||
}
|
||||
|
||||
impl MmrApi<Block, MmrRootHash, NumberFor<Block>> for RuntimeApi {
|
||||
fn mmr_root() -> Result<MmrRootHash, MmrError> {
|
||||
Ok(self.inner.mmr_root_hash)
|
||||
}
|
||||
|
||||
fn generate_proof(
|
||||
_block_numbers: Vec<u64>,
|
||||
_best_known_block_number: Option<u64>
|
||||
) -> Result<(Vec<EncodableOpaqueLeaf>, Proof<MmrRootHash>), MmrError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn verify_proof(_leaves: Vec<EncodableOpaqueLeaf>, _proof: Proof<MmrRootHash>) -> Result<(), MmrError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn verify_proof_stateless(
|
||||
_root: MmrRootHash,
|
||||
_leaves: Vec<EncodableOpaqueLeaf>,
|
||||
_proof: Proof<MmrRootHash>
|
||||
) -> Result<(), MmrError> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,7 +341,7 @@ pub(crate) fn create_beefy_keystore(authority: BeefyKeyring) -> SyncCryptoStoreP
|
||||
keystore
|
||||
}
|
||||
|
||||
fn voter_init_setup(
|
||||
async fn voter_init_setup(
|
||||
net: &mut BeefyTestNet,
|
||||
finality: &mut futures::stream::Fuse<FinalityNotifications<Block>>,
|
||||
api: &TestApi,
|
||||
@@ -345,9 +356,7 @@ fn voter_init_setup(
|
||||
gossip_validator,
|
||||
None,
|
||||
);
|
||||
let best_grandpa =
|
||||
futures::executor::block_on(wait_for_runtime_pallet(api, &mut gossip_engine, finality))
|
||||
.unwrap();
|
||||
let best_grandpa = wait_for_runtime_pallet(api, &mut gossip_engine, finality).await.unwrap();
|
||||
load_or_init_voter_state(&*backend, api, best_grandpa, 1)
|
||||
}
|
||||
|
||||
@@ -980,7 +989,7 @@ async fn should_initialize_voter_at_genesis() {
|
||||
|
||||
let api = TestApi::with_validator_set(&validator_set);
|
||||
// load persistent state - nothing in DB, should init at genesis
|
||||
let persisted_state = voter_init_setup(&mut net, &mut finality, &api).unwrap();
|
||||
let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap();
|
||||
|
||||
// Test initialization at session boundary.
|
||||
// verify voter initialized with two sessions starting at blocks 1 and 10
|
||||
@@ -1029,7 +1038,7 @@ async fn should_initialize_voter_at_custom_genesis() {
|
||||
net.peer(0).client().as_client().finalize_block(hashes[8], None).unwrap();
|
||||
|
||||
// load persistent state - nothing in DB, should init at genesis
|
||||
let persisted_state = voter_init_setup(&mut net, &mut finality, &api).unwrap();
|
||||
let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap();
|
||||
|
||||
// Test initialization at session boundary.
|
||||
// verify voter initialized with single session starting at block `custom_pallet_genesis` (7)
|
||||
@@ -1090,7 +1099,7 @@ async fn should_initialize_voter_when_last_final_is_session_boundary() {
|
||||
|
||||
let api = TestApi::with_validator_set(&validator_set);
|
||||
// load persistent state - nothing in DB, should init at session boundary
|
||||
let persisted_state = voter_init_setup(&mut net, &mut finality, &api).unwrap();
|
||||
let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap();
|
||||
|
||||
// verify voter initialized with single session starting at block 10
|
||||
assert_eq!(persisted_state.voting_oracle().sessions().len(), 1);
|
||||
@@ -1148,7 +1157,7 @@ async fn should_initialize_voter_at_latest_finalized() {
|
||||
|
||||
let api = TestApi::with_validator_set(&validator_set);
|
||||
// load persistent state - nothing in DB, should init at last BEEFY finalized
|
||||
let persisted_state = voter_init_setup(&mut net, &mut finality, &api).unwrap();
|
||||
let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap();
|
||||
|
||||
// verify voter initialized with single session starting at block 12
|
||||
assert_eq!(persisted_state.voting_oracle().sessions().len(), 1);
|
||||
@@ -1205,3 +1214,59 @@ async fn beefy_finalizing_after_pallet_genesis() {
|
||||
// GRANDPA finalize #21 -> BEEFY finalize #20 (mandatory) and #21
|
||||
finalize_block_and_wait_for_beefy(&net, peers.clone(), &[hashes[21]], &[20, 21]).await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn beefy_reports_equivocations() {
|
||||
sp_tracing::try_init_simple();
|
||||
|
||||
let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie];
|
||||
let validator_set = ValidatorSet::new(make_beefy_ids(&peers), 0).unwrap();
|
||||
let session_len = 10;
|
||||
let min_block_delta = 4;
|
||||
|
||||
let mut net = BeefyTestNet::new(3);
|
||||
|
||||
// Alice votes on good MMR roots, equivocations are allowed/expected.
|
||||
let mut api_alice = TestApi::with_validator_set(&validator_set);
|
||||
api_alice.allow_equivocations();
|
||||
let api_alice = Arc::new(api_alice);
|
||||
let alice = (0, &peers[0], api_alice.clone());
|
||||
tokio::spawn(initialize_beefy(&mut net, vec![alice], min_block_delta));
|
||||
|
||||
// Bob votes on bad MMR roots, equivocations are allowed/expected.
|
||||
let mut api_bob = TestApi::new(1, &validator_set, BAD_MMR_ROOT);
|
||||
api_bob.allow_equivocations();
|
||||
let api_bob = Arc::new(api_bob);
|
||||
let bob = (1, &peers[1], api_bob.clone());
|
||||
tokio::spawn(initialize_beefy(&mut net, vec![bob], min_block_delta));
|
||||
|
||||
// We spawn another node voting with Bob key, on alternate bad MMR roots (equivocating).
|
||||
// Equivocations are allowed/expected.
|
||||
let mut api_bob_prime = TestApi::new(1, &validator_set, ALTERNATE_BAD_MMR_ROOT);
|
||||
api_bob_prime.allow_equivocations();
|
||||
let api_bob_prime = Arc::new(api_bob_prime);
|
||||
let bob_prime = (2, &BeefyKeyring::Bob, api_bob_prime.clone());
|
||||
tokio::spawn(initialize_beefy(&mut net, vec![bob_prime], min_block_delta));
|
||||
|
||||
// push 42 blocks including `AuthorityChange` digests every 10 blocks.
|
||||
let hashes = net.generate_blocks_and_sync(42, session_len, &validator_set, false).await;
|
||||
|
||||
let net = Arc::new(Mutex::new(net));
|
||||
|
||||
// Minimum BEEFY block delta is 4.
|
||||
|
||||
let peers = peers.into_iter().enumerate();
|
||||
// finalize block #1 -> BEEFY should not finalize anything (each node votes on different MMR).
|
||||
finalize_block_and_wait_for_beefy(&net, peers, &[hashes[1]], &[]).await;
|
||||
|
||||
// Verify neither Bob or Bob_Prime report themselves as equivocating.
|
||||
assert!(api_bob.reported_equivocations.as_ref().unwrap().lock().is_empty());
|
||||
assert!(api_bob_prime.reported_equivocations.as_ref().unwrap().lock().is_empty());
|
||||
|
||||
// Verify Alice reports Bob/Bob_Prime equivocation.
|
||||
let alice_reported_equivocations = api_alice.reported_equivocations.as_ref().unwrap().lock();
|
||||
assert_eq!(alice_reported_equivocations.len(), 1);
|
||||
let equivocation_proof = alice_reported_equivocations.get(0).unwrap();
|
||||
assert_eq!(equivocation_proof.first.id, BeefyKeyring::Bob.public());
|
||||
assert_eq!(equivocation_proof.first.commitment.block_number, 1);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user