// This file is part of Substrate.
// Copyright (C) 2018-2022 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 .
//! Tests and test helpers for BEEFY.
use futures::{future, stream::FuturesUnordered, Future, StreamExt};
use parking_lot::Mutex;
use serde::{Deserialize, Serialize};
use std::{sync::Arc, task::Poll};
use tokio::{runtime::Runtime, time::Duration};
use sc_chain_spec::{ChainSpec, GenericChainSpec};
use sc_client_api::HeaderBackend;
use sc_consensus::BoxJustificationImport;
use sc_keystore::LocalKeystore;
use sc_network::{config::ProtocolConfig, NetworkService};
use sc_network_gossip::GossipEngine;
use sc_network_test::{
Block, BlockImportAdapter, FullPeerConfig, PassThroughVerifier, Peer, PeersClient,
PeersFullClient, TestNetFactory,
};
use sc_utils::notification::NotificationReceiver;
use beefy_primitives::{
crypto::AuthorityId, ConsensusLog, MmrRootHash, ValidatorSet, BEEFY_ENGINE_ID,
KEY_TYPE as BeefyKeyType,
};
use sp_consensus::BlockOrigin;
use sp_core::H256;
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
use sp_runtime::{
codec::Encode, generic::BlockId, traits::Header as HeaderT, BuildStorage, DigestItem, Storage,
};
use substrate_test_runtime_client::{runtime::Header, Backend, ClientExt};
use crate::{
beefy_protocol_name,
keystore::tests::Keyring as BeefyKeyring,
notification::*,
worker::{tests::TestModifiers, BeefyWorker},
};
const BEEFY_PROTOCOL_NAME: &'static str = "/beefy/1";
type BeefyValidatorSet = ValidatorSet;
type BeefyPeer = Peer;
#[derive(Debug, Serialize, Deserialize)]
struct Genesis(std::collections::BTreeMap);
impl BuildStorage for Genesis {
fn assimilate_storage(&self, storage: &mut Storage) -> Result<(), String> {
storage
.top
.extend(self.0.iter().map(|(a, b)| (a.clone().into_bytes(), b.clone().into_bytes())));
Ok(())
}
}
#[test]
fn beefy_protocol_name() {
let chain_spec = GenericChainSpec::::from_json_file(std::path::PathBuf::from(
"../chain-spec/res/chain_spec.json",
))
.unwrap()
.cloned_box();
// Create protocol name using random genesis hash.
let genesis_hash = H256::random();
let expected = format!("/{}/beefy/1", hex::encode(genesis_hash));
let proto_name = beefy_protocol_name::standard_name(&genesis_hash, &chain_spec);
assert_eq!(proto_name.to_string(), expected);
// Create protocol name using hardcoded genesis hash. Verify exact representation.
let genesis_hash = [
50, 4, 60, 123, 58, 106, 216, 246, 194, 188, 139, 193, 33, 212, 202, 171, 9, 55, 123, 94,
8, 43, 12, 251, 187, 57, 173, 19, 188, 74, 205, 147,
];
let expected =
"/32043c7b3a6ad8f6c2bc8bc121d4caab09377b5e082b0cfbbb39ad13bc4acd93/beefy/1".to_string();
let proto_name = beefy_protocol_name::standard_name(&genesis_hash, &chain_spec);
assert_eq!(proto_name.to_string(), expected);
}
// TODO: compiler warns us about unused `signed_commitment_stream`, will use in later tests
#[allow(dead_code)]
#[derive(Clone)]
pub(crate) struct BeefyLinkHalf {
signed_commitment_stream: BeefySignedCommitmentStream,
beefy_best_block_stream: BeefyBestBlockStream,
}
#[derive(Default)]
pub(crate) struct PeerData {
pub(crate) beefy_link_half: Mutex>,
pub(crate) test_modifiers: Option,
}
impl PeerData {
pub(crate) fn use_validator_set(&mut self, validator_set: &ValidatorSet) {
if let Some(tm) = self.test_modifiers.as_mut() {
tm.active_validators = validator_set.clone();
} else {
self.test_modifiers = Some(TestModifiers {
active_validators: validator_set.clone(),
corrupt_mmr_roots: false,
});
}
}
}
pub(crate) struct BeefyTestNet {
peers: Vec,
}
impl BeefyTestNet {
pub(crate) fn new(n_authority: usize, n_full: usize) -> Self {
let mut net = BeefyTestNet { peers: Vec::with_capacity(n_authority + n_full) };
for _ in 0..n_authority {
net.add_authority_peer();
}
for _ in 0..n_full {
net.add_full_peer();
}
net
}
pub(crate) fn add_authority_peer(&mut self) {
self.add_full_peer_with_config(FullPeerConfig {
notifications_protocols: vec![BEEFY_PROTOCOL_NAME.into()],
is_authority: true,
..Default::default()
})
}
pub(crate) fn generate_blocks(
&mut self,
count: usize,
session_length: u64,
validator_set: &BeefyValidatorSet,
) {
self.peer(0).generate_blocks(count, BlockOrigin::File, |builder| {
let mut block = builder.build().unwrap().block;
let block_num = *block.header.number();
let num_byte = block_num.to_le_bytes().into_iter().next().unwrap();
let mmr_root = MmrRootHash::repeat_byte(num_byte);
add_mmr_digest(&mut block.header, mmr_root);
if block_num % session_length == 0 {
add_auth_change_digest(&mut block.header, validator_set.clone());
}
block
});
}
}
impl TestNetFactory for BeefyTestNet {
type Verifier = PassThroughVerifier;
type BlockImport = PeersClient;
type PeerData = PeerData;
/// Create new test network with peers and given config.
fn from_config(_config: &ProtocolConfig) -> Self {
BeefyTestNet { peers: Vec::new() }
}
fn make_verifier(
&self,
_client: PeersClient,
_cfg: &ProtocolConfig,
_: &PeerData,
) -> Self::Verifier {
PassThroughVerifier::new(false) // use non-instant finality.
}
fn make_block_import(
&self,
client: PeersClient,
) -> (
BlockImportAdapter,
Option>,
Self::PeerData,
) {
(client.as_block_import(), None, PeerData::default())
}
fn peer(&mut self, i: usize) -> &mut BeefyPeer {
&mut self.peers[i]
}
fn peers(&self) -> &Vec {
&self.peers
}
fn mut_peers)>(&mut self, closure: F) {
closure(&mut self.peers);
}
fn add_full_peer(&mut self) {
self.add_full_peer_with_config(FullPeerConfig {
notifications_protocols: vec![BEEFY_PROTOCOL_NAME.into()],
is_authority: false,
..Default::default()
})
}
}
fn add_mmr_digest(header: &mut Header, mmr_hash: MmrRootHash) {
header.digest_mut().push(DigestItem::Consensus(
BEEFY_ENGINE_ID,
ConsensusLog::::MmrRoot(mmr_hash).encode(),
));
}
fn add_auth_change_digest(header: &mut Header, new_auth_set: BeefyValidatorSet) {
header.digest_mut().push(DigestItem::Consensus(
BEEFY_ENGINE_ID,
ConsensusLog::::AuthoritiesChange(new_auth_set).encode(),
));
}
pub(crate) fn make_beefy_ids(keys: &[BeefyKeyring]) -> Vec {
keys.iter().map(|key| key.clone().public().into()).collect()
}
pub(crate) fn create_beefy_keystore(authority: BeefyKeyring) -> SyncCryptoStorePtr {
let keystore = Arc::new(LocalKeystore::in_memory());
SyncCryptoStore::ecdsa_generate_new(&*keystore, BeefyKeyType, Some(&authority.to_seed()))
.expect("Creates authority key");
keystore
}
pub(crate) fn create_beefy_worker(
peer: &BeefyPeer,
key: &BeefyKeyring,
min_block_delta: u32,
) -> BeefyWorker>> {
let keystore = create_beefy_keystore(*key);
let (signed_commitment_sender, signed_commitment_stream) =
BeefySignedCommitmentStream::::channel();
let (beefy_best_block_sender, beefy_best_block_stream) =
BeefyBestBlockStream::::channel();
let beefy_link_half = BeefyLinkHalf { signed_commitment_stream, beefy_best_block_stream };
*peer.data.beefy_link_half.lock() = Some(beefy_link_half);
let test_modifiers = peer.data.test_modifiers.clone().unwrap();
let network = peer.network_service().clone();
let sync_oracle = network.clone();
let gossip_validator = Arc::new(crate::gossip::GossipValidator::new());
let gossip_engine =
GossipEngine::new(network, BEEFY_PROTOCOL_NAME, gossip_validator.clone(), None);
let worker_params = crate::worker::WorkerParams {
client: peer.client().as_client(),
backend: peer.client().as_backend(),
key_store: Some(keystore).into(),
signed_commitment_sender,
beefy_best_block_sender,
gossip_engine,
gossip_validator,
min_block_delta,
metrics: None,
sync_oracle,
};
BeefyWorker::<_, _, _, _>::new(worker_params, test_modifiers)
}
// Spawns beefy voters. Returns a future to spawn on the runtime.
fn initialize_beefy(
net: &mut BeefyTestNet,
peers: &[BeefyKeyring],
min_block_delta: u32,
) -> impl Future {
let voters = FuturesUnordered::new();
for (peer_id, key) in peers.iter().enumerate() {
let worker = create_beefy_worker(&net.peers[peer_id], key, min_block_delta);
let gadget = worker.run();
fn assert_send(_: &T) {}
assert_send(&gadget);
voters.push(gadget);
}
voters.for_each(|_| async move {})
}
fn block_until(future: impl Future + Unpin, net: &Arc>, runtime: &mut Runtime) {
let drive_to_completion = futures::future::poll_fn(|cx| {
net.lock().poll(cx);
Poll::<()>::Pending
});
runtime.block_on(future::select(future, drive_to_completion));
}
fn run_for(duration: Duration, net: &Arc>, runtime: &mut Runtime) {
let sleep = runtime.spawn(async move { tokio::time::sleep(duration).await });
block_until(sleep, net, runtime);
}
pub(crate) fn get_beefy_streams(
net: &mut BeefyTestNet,
peers: &[BeefyKeyring],
) -> (Vec>, Vec>>) {
let mut best_block_streams = Vec::new();
let mut signed_commitment_streams = Vec::new();
for peer_id in 0..peers.len() {
let beefy_link_half =
net.peer(peer_id).data.beefy_link_half.lock().as_ref().unwrap().clone();
let BeefyLinkHalf { signed_commitment_stream, beefy_best_block_stream } = beefy_link_half;
best_block_streams.push(beefy_best_block_stream.subscribe());
signed_commitment_streams.push(signed_commitment_stream.subscribe());
}
(best_block_streams, signed_commitment_streams)
}
fn wait_for_best_beefy_blocks(
streams: Vec>,
net: &Arc>,
runtime: &mut Runtime,
expected_beefy_blocks: &[u64],
) {
let mut wait_for = Vec::new();
let len = expected_beefy_blocks.len();
streams.into_iter().enumerate().for_each(|(i, stream)| {
let mut expected = expected_beefy_blocks.iter();
wait_for.push(Box::pin(stream.take(len).for_each(move |best_beefy_hash| {
let expected = expected.next();
async move {
let block_id = BlockId::hash(best_beefy_hash);
let header =
net.lock().peer(i).client().as_client().expect_header(block_id).unwrap();
let best_beefy = *header.number();
assert_eq!(expected, Some(best_beefy).as_ref());
}
})));
});
let wait_for = futures::future::join_all(wait_for);
block_until(wait_for, net, runtime);
}
fn wait_for_beefy_signed_commitments(
streams: Vec>>,
net: &Arc>,
runtime: &mut Runtime,
expected_commitment_block_nums: &[u64],
) {
let mut wait_for = Vec::new();
let len = expected_commitment_block_nums.len();
streams.into_iter().for_each(|stream| {
let mut expected = expected_commitment_block_nums.iter();
wait_for.push(Box::pin(stream.take(len).for_each(move |signed_commitment| {
let expected = expected.next();
async move {
let commitment_block_num = signed_commitment.commitment.block_number;
assert_eq!(expected, Some(commitment_block_num).as_ref());
// TODO: also verify commitment payload, validator set id, and signatures.
}
})));
});
let wait_for = futures::future::join_all(wait_for);
block_until(wait_for, net, runtime);
}
fn streams_empty_after_timeout(
streams: Vec>,
net: &Arc>,
runtime: &mut Runtime,
timeout: Option,
) where
T: std::fmt::Debug,
T: std::cmp::PartialEq,
{
if let Some(timeout) = timeout {
run_for(timeout, net, runtime);
}
streams.into_iter().for_each(|mut stream| {
runtime.block_on(future::poll_fn(move |cx| {
assert_eq!(stream.poll_next_unpin(cx), Poll::Pending);
Poll::Ready(())
}));
});
}
fn finalize_block_and_wait_for_beefy(
net: &Arc>,
peers: &[BeefyKeyring],
runtime: &mut Runtime,
finalize_targets: &[u64],
expected_beefy: &[u64],
) {
let (best_blocks, signed_commitments) = get_beefy_streams(&mut *net.lock(), peers);
for block in finalize_targets {
let finalize = BlockId::number(*block);
for i in 0..peers.len() {
net.lock().peer(i).client().as_client().finalize_block(finalize, None).unwrap();
}
}
if expected_beefy.is_empty() {
// run for 1 second then verify no new best beefy block available
let timeout = Some(Duration::from_millis(500));
streams_empty_after_timeout(best_blocks, &net, runtime, timeout);
streams_empty_after_timeout(signed_commitments, &net, runtime, None);
} else {
// run until expected beefy blocks are received
wait_for_best_beefy_blocks(best_blocks, &net, runtime, expected_beefy);
wait_for_beefy_signed_commitments(signed_commitments, &net, runtime, expected_beefy);
}
}
#[test]
fn beefy_finalizing_blocks() {
sp_tracing::try_init_simple();
let mut runtime = Runtime::new().unwrap();
let peers = &[BeefyKeyring::Alice, BeefyKeyring::Bob];
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(2, 0);
for i in 0..peers.len() {
net.peer(i).data.use_validator_set(&validator_set);
}
runtime.spawn(initialize_beefy(&mut net, peers, min_block_delta));
// push 42 blocks including `AuthorityChange` digests every 10 blocks.
net.generate_blocks(42, session_len, &validator_set);
net.block_until_sync();
let net = Arc::new(Mutex::new(net));
// Minimum BEEFY block delta is 4.
// finalize block #5 -> BEEFY should finalize #1 (mandatory) and #5 from diff-power-of-two rule.
finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[5], &[1, 5]);
// GRANDPA finalize #10 -> BEEFY finalize #10 (mandatory)
finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[10], &[10]);
// GRANDPA finalize #18 -> BEEFY finalize #14, then #18 (diff-power-of-two rule)
finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[18], &[14, 18]);
// GRANDPA finalize #20 -> BEEFY finalize #20 (mandatory)
finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[20], &[20]);
// GRANDPA finalize #21 -> BEEFY finalize nothing (yet) because min delta is 4
finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[21], &[]);
}
#[test]
fn lagging_validators() {
sp_tracing::try_init_simple();
let mut runtime = Runtime::new().unwrap();
let peers = &[BeefyKeyring::Charlie, BeefyKeyring::Dave];
let validator_set = ValidatorSet::new(make_beefy_ids(peers), 0).unwrap();
let session_len = 30;
let min_block_delta = 1;
let mut net = BeefyTestNet::new(2, 0);
for i in 0..peers.len() {
net.peer(i).data.use_validator_set(&validator_set);
}
runtime.spawn(initialize_beefy(&mut net, peers, min_block_delta));
// push 42 blocks including `AuthorityChange` digests every 30 blocks.
net.generate_blocks(42, session_len, &validator_set);
net.block_until_sync();
let net = Arc::new(Mutex::new(net));
// finalize block #15 -> BEEFY should finalize #1 (mandatory) and #9, #13, #14, #15 from
// diff-power-of-two rule.
finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[15], &[1, 9, 13, 14, 15]);
// Charlie finalizes #25, Dave lags behind
let finalize = BlockId::number(25);
let (best_blocks, signed_commitments) = get_beefy_streams(&mut *net.lock(), peers);
net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap();
// verify nothing gets finalized by BEEFY
let timeout = Some(Duration::from_millis(500));
streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout);
streams_empty_after_timeout(signed_commitments, &net, &mut runtime, None);
// Dave catches up and also finalizes #25
let (best_blocks, signed_commitments) = get_beefy_streams(&mut *net.lock(), peers);
net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap();
// expected beefy finalizes block #17 from diff-power-of-two
wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[23, 24, 25]);
wait_for_beefy_signed_commitments(signed_commitments, &net, &mut runtime, &[23, 24, 25]);
// Both finalize #30 (mandatory session) and #32 -> BEEFY finalize #30 (mandatory), #31, #32
finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[30, 32], &[30, 31, 32]);
}
#[test]
fn correct_beefy_payload() {
sp_tracing::try_init_simple();
let mut runtime = Runtime::new().unwrap();
let peers =
&[BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie, BeefyKeyring::Dave];
let validator_set = ValidatorSet::new(make_beefy_ids(peers), 0).unwrap();
let session_len = 20;
let min_block_delta = 2;
let mut net = BeefyTestNet::new(4, 0);
for i in 0..peers.len() {
net.peer(i).data.use_validator_set(&validator_set);
}
// Dave will vote on bad mmr roots
net.peer(3).data.test_modifiers.as_mut().map(|tm| tm.corrupt_mmr_roots = true);
runtime.spawn(initialize_beefy(&mut net, peers, min_block_delta));
// push 10 blocks
net.generate_blocks(12, session_len, &validator_set);
net.block_until_sync();
let net = Arc::new(Mutex::new(net));
// with 3 good voters and 1 bad one, consensus should happen and best blocks produced.
finalize_block_and_wait_for_beefy(&net, peers, &mut runtime, &[10], &[1, 9]);
let (best_blocks, signed_commitments) =
get_beefy_streams(&mut *net.lock(), &[BeefyKeyring::Alice]);
// now 2 good validators and 1 bad one are voting
net.lock()
.peer(0)
.client()
.as_client()
.finalize_block(BlockId::number(11), None)
.unwrap();
net.lock()
.peer(1)
.client()
.as_client()
.finalize_block(BlockId::number(11), None)
.unwrap();
net.lock()
.peer(3)
.client()
.as_client()
.finalize_block(BlockId::number(11), None)
.unwrap();
// verify consensus is _not_ reached
let timeout = Some(Duration::from_millis(500));
streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout);
streams_empty_after_timeout(signed_commitments, &net, &mut runtime, None);
// 3rd good validator catches up and votes as well
let (best_blocks, signed_commitments) =
get_beefy_streams(&mut *net.lock(), &[BeefyKeyring::Alice]);
net.lock()
.peer(2)
.client()
.as_client()
.finalize_block(BlockId::number(11), None)
.unwrap();
// verify consensus is reached
wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[11]);
wait_for_beefy_signed_commitments(signed_commitments, &net, &mut runtime, &[11]);
}