mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 22:47:56 +00:00
80616f6d03
[litep2p](https://github.com/altonen/litep2p) is a libp2p-compatible P2P networking library. It supports all of the features of `rust-libp2p` that are currently being utilized by Polkadot SDK. Compared to `rust-libp2p`, `litep2p` has a quite different architecture which is why the new `litep2p` network backend is only able to use a little of the existing code in `sc-network`. The design has been mainly influenced by how we'd wish to structure our networking-related code in Polkadot SDK: independent higher-levels protocols directly communicating with the network over links that support bidirectional backpressure. A good example would be `NotificationHandle`/`RequestResponseHandle` abstractions which allow, e.g., `SyncingEngine` to directly communicate with peers to announce/request blocks. I've tried running `polkadot --network-backend litep2p` with a few different peer configurations and there is a noticeable reduction in networking CPU usage. For high load (`--out-peers 200`), networking CPU usage goes down from ~110% to ~30% (80 pp) and for normal load (`--out-peers 40`), the usage goes down from ~55% to ~18% (37 pp). These should not be taken as final numbers because: a) there are still some low-hanging optimization fruits, such as enabling [receive window auto-tuning](https://github.com/libp2p/rust-yamux/pull/176), integrating `Peerset` more closely with `litep2p` or improving memory usage of the WebSocket transport b) fixing bugs/instabilities that incorrectly cause `litep2p` to do less work will increase the networking CPU usage c) verification in a more diverse set of tests/conditions is needed Nevertheless, these numbers should give an early estimate for CPU usage of the new networking backend. This PR consists of three separate changes: * introduce a generic `PeerId` (wrapper around `Multihash`) so that we don't have use `NetworkService::PeerId` in every part of the code that uses a `PeerId` * introduce `NetworkBackend` trait, implement it for the libp2p network stack and make Polkadot SDK generic over `NetworkBackend` * implement `NetworkBackend` for litep2p The new library should be considered experimental which is why `rust-libp2p` will remain as the default option for the time being. This PR currently depends on the master branch of `litep2p` but I'll cut a new release for the library once all review comments have been addresses. --------- Signed-off-by: Alexandru Vasile <alexandru.vasile@parity.io> Co-authored-by: Dmitry Markin <dmitry@markin.tech> Co-authored-by: Alexandru Vasile <60601340+lexnv@users.noreply.github.com> Co-authored-by: Alexandru Vasile <alexandru.vasile@parity.io>
1574 lines
60 KiB
Rust
1574 lines
60 KiB
Rust
// 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 <https://www.gnu.org/licenses/>.
|
|
|
|
//! Tests and test helpers for BEEFY.
|
|
|
|
use crate::{
|
|
aux_schema::{load_persistent, tests::verify_persisted_version},
|
|
beefy_block_import_and_links,
|
|
communication::{
|
|
gossip::{
|
|
proofs_topic,
|
|
tests::{sign_commitment, TestNetwork},
|
|
votes_topic, GossipFilterCfg, GossipMessage, GossipValidator,
|
|
},
|
|
request_response::{on_demand_justifications_protocol_config, BeefyJustifsRequestHandler},
|
|
},
|
|
error::Error,
|
|
gossip_protocol_name,
|
|
justification::*,
|
|
wait_for_runtime_pallet,
|
|
worker::PersistedState,
|
|
BeefyRPCLinks, BeefyVoterLinks, BeefyWorkerBuilder, KnownPeers,
|
|
};
|
|
use futures::{future, stream::FuturesUnordered, Future, FutureExt, StreamExt};
|
|
use parking_lot::Mutex;
|
|
use sc_block_builder::BlockBuilderBuilder;
|
|
use sc_client_api::{Backend as BackendT, BlockchainEvents, FinalityNotifications, HeaderBackend};
|
|
use sc_consensus::{
|
|
BlockImport, BlockImportParams, BoxJustificationImport, ForkChoiceStrategy, ImportResult,
|
|
ImportedAux,
|
|
};
|
|
use sc_network::{config::RequestResponseConfig, ProtocolName};
|
|
use sc_network_test::{
|
|
Block, BlockImportAdapter, FullPeerConfig, PassThroughVerifier, Peer, PeersClient,
|
|
PeersFullClient, TestNetFactory,
|
|
};
|
|
use sc_utils::notification::NotificationReceiver;
|
|
use serde::{Deserialize, Serialize};
|
|
use sp_api::{ApiRef, 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},
|
|
test_utils::Keyring as BeefyKeyring,
|
|
BeefyApi, Commitment, ConsensusLog, EquivocationProof, MmrRootHash, OpaqueKeyOwnershipProof,
|
|
Payload, SignedCommitment, ValidatorSet, ValidatorSetId, VersionedFinalityProof, VoteMessage,
|
|
BEEFY_ENGINE_ID,
|
|
};
|
|
use sp_core::H256;
|
|
use sp_keystore::{testing::MemoryKeystore, Keystore, KeystorePtr};
|
|
use sp_mmr_primitives::{Error as MmrError, MmrApi};
|
|
use sp_runtime::{
|
|
codec::{Decode, Encode},
|
|
traits::{Header as HeaderT, NumberFor},
|
|
BuildStorage, DigestItem, EncodedJustification, Justifications, Storage,
|
|
};
|
|
use std::{marker::PhantomData, sync::Arc, task::Poll};
|
|
use substrate_test_runtime_client::{BlockBuilderExt, ClientExt};
|
|
use tokio::time::Duration;
|
|
|
|
const GENESIS_HASH: H256 = H256::zero();
|
|
pub(crate) fn beefy_gossip_proto_name() -> ProtocolName {
|
|
gossip_protocol_name(GENESIS_HASH, None)
|
|
}
|
|
|
|
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,
|
|
substrate_test_runtime_client::Backend,
|
|
TestApi,
|
|
BlockImportAdapter<PeersClient>,
|
|
>;
|
|
|
|
pub(crate) type BeefyValidatorSet = ValidatorSet<AuthorityId>;
|
|
pub(crate) type BeefyPeer = Peer<PeerData, BeefyBlockImport>;
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
struct Genesis(std::collections::BTreeMap<String, String>);
|
|
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(())
|
|
}
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub(crate) struct PeerData {
|
|
pub(crate) beefy_rpc_links: Mutex<Option<BeefyRPCLinks<Block>>>,
|
|
pub(crate) beefy_voter_links: Mutex<Option<BeefyVoterLinks<Block>>>,
|
|
pub(crate) beefy_justif_req_handler:
|
|
Mutex<Option<BeefyJustifsRequestHandler<Block, PeersFullClient>>>,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub(crate) struct BeefyTestNet {
|
|
peers: Vec<BeefyPeer>,
|
|
pub beefy_genesis: NumberFor<Block>,
|
|
}
|
|
|
|
impl BeefyTestNet {
|
|
pub(crate) fn new(n_authority: usize) -> Self {
|
|
let beefy_genesis = 1;
|
|
let mut net = BeefyTestNet { peers: Vec::with_capacity(n_authority), beefy_genesis };
|
|
|
|
for i in 0..n_authority {
|
|
let (rx, cfg) = on_demand_justifications_protocol_config::<
|
|
_,
|
|
Block,
|
|
sc_network::NetworkWorker<_, _>,
|
|
>(GENESIS_HASH, None);
|
|
let justif_protocol_name = cfg.name.clone();
|
|
|
|
net.add_authority_peer(vec![cfg]);
|
|
|
|
let client = net.peers[i].client().as_client();
|
|
let justif_handler = BeefyJustifsRequestHandler {
|
|
request_receiver: rx,
|
|
justif_protocol_name,
|
|
client,
|
|
_block: PhantomData,
|
|
metrics: None,
|
|
};
|
|
*net.peers[i].data.beefy_justif_req_handler.lock() = Some(justif_handler);
|
|
}
|
|
net
|
|
}
|
|
|
|
pub(crate) fn add_authority_peer(&mut self, req_resp_cfgs: Vec<RequestResponseConfig>) {
|
|
self.add_full_peer_with_config(FullPeerConfig {
|
|
notifications_protocols: vec![beefy_gossip_proto_name()],
|
|
request_response_protocols: req_resp_cfgs,
|
|
is_authority: true,
|
|
..Default::default()
|
|
});
|
|
}
|
|
|
|
/// Builds the blocks and returns the vector of built block hashes.
|
|
/// Returned vector contains the genesis hash which allows for easy indexing (block number is
|
|
/// equal to index)
|
|
pub(crate) async fn generate_blocks_and_sync(
|
|
&mut self,
|
|
count: usize,
|
|
session_length: u64,
|
|
validator_set: &BeefyValidatorSet,
|
|
include_mmr_digest: bool,
|
|
) -> Vec<H256> {
|
|
let mut all_hashes = Vec::with_capacity(count + 1);
|
|
|
|
// make sure genesis is the only block in network, so we can insert genesis at the beginning
|
|
// of hashes, otherwise indexing would be broken
|
|
assert!(self.peer(0).client().as_backend().blockchain().hash(1).unwrap().is_none());
|
|
|
|
// push genesis to make indexing human readable (index equals to block number)
|
|
all_hashes.push(self.peer(0).client().info().genesis_hash);
|
|
|
|
let mut block_num: NumberFor<Block> = self.peer(0).client().info().best_number;
|
|
let built_hashes = self.peer(0).generate_blocks(count, BlockOrigin::File, |mut builder| {
|
|
block_num = block_num.saturating_add(1).try_into().unwrap();
|
|
if include_mmr_digest {
|
|
let num_byte = block_num.to_le_bytes().into_iter().next().unwrap();
|
|
let mmr_root = MmrRootHash::repeat_byte(num_byte);
|
|
add_mmr_digest(&mut builder, mmr_root);
|
|
}
|
|
|
|
if block_num % session_length == 0 {
|
|
add_auth_change_digest(&mut builder, validator_set.clone());
|
|
}
|
|
|
|
let block = builder.build().unwrap().block;
|
|
assert_eq!(block.header.number, block_num);
|
|
|
|
block
|
|
});
|
|
all_hashes.extend(built_hashes);
|
|
self.run_until_sync().await;
|
|
|
|
all_hashes
|
|
}
|
|
}
|
|
|
|
impl TestNetFactory for BeefyTestNet {
|
|
type Verifier = PassThroughVerifier;
|
|
type BlockImport = BeefyBlockImport;
|
|
type PeerData = PeerData;
|
|
|
|
fn make_verifier(&self, _client: PeersClient, _: &PeerData) -> Self::Verifier {
|
|
PassThroughVerifier::new(false) // use non-instant finality.
|
|
}
|
|
|
|
fn make_block_import(
|
|
&self,
|
|
client: PeersClient,
|
|
) -> (
|
|
BlockImportAdapter<Self::BlockImport>,
|
|
Option<BoxJustificationImport<Block>>,
|
|
Self::PeerData,
|
|
) {
|
|
let keys = &[BeefyKeyring::Alice, BeefyKeyring::Bob];
|
|
let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap();
|
|
let api = Arc::new(TestApi::new(self.beefy_genesis, &validator_set, GOOD_MMR_ROOT));
|
|
let inner = BlockImportAdapter::new(client.clone());
|
|
let (block_import, voter_links, rpc_links) =
|
|
beefy_block_import_and_links(inner, client.as_backend(), api, None);
|
|
let peer_data = PeerData {
|
|
beefy_rpc_links: Mutex::new(Some(rpc_links)),
|
|
beefy_voter_links: Mutex::new(Some(voter_links)),
|
|
..Default::default()
|
|
};
|
|
(BlockImportAdapter::new(block_import), None, peer_data)
|
|
}
|
|
|
|
fn peer(&mut self, i: usize) -> &mut BeefyPeer {
|
|
&mut self.peers[i]
|
|
}
|
|
|
|
fn peers(&self) -> &Vec<BeefyPeer> {
|
|
&self.peers
|
|
}
|
|
|
|
fn peers_mut(&mut self) -> &mut Vec<BeefyPeer> {
|
|
&mut self.peers
|
|
}
|
|
|
|
fn mut_peers<F: FnOnce(&mut Vec<BeefyPeer>)>(&mut self, closure: F) {
|
|
closure(&mut self.peers);
|
|
}
|
|
|
|
fn add_full_peer(&mut self) {
|
|
// `add_authority_peer()` used instead.
|
|
unimplemented!()
|
|
}
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub(crate) struct TestApi {
|
|
pub beefy_genesis: u64,
|
|
pub validator_set: Option<BeefyValidatorSet>,
|
|
pub mmr_root_hash: MmrRootHash,
|
|
pub reported_equivocations:
|
|
Option<Arc<Mutex<Vec<EquivocationProof<NumberFor<Block>, AuthorityId, Signature>>>>>,
|
|
}
|
|
|
|
impl TestApi {
|
|
pub fn new(
|
|
beefy_genesis: u64,
|
|
validator_set: &BeefyValidatorSet,
|
|
mmr_root_hash: MmrRootHash,
|
|
) -> Self {
|
|
TestApi {
|
|
beefy_genesis,
|
|
validator_set: Some(validator_set.clone()),
|
|
mmr_root_hash,
|
|
reported_equivocations: None,
|
|
}
|
|
}
|
|
|
|
pub fn with_validator_set(validator_set: &BeefyValidatorSet) -> Self {
|
|
TestApi {
|
|
beefy_genesis: 1,
|
|
validator_set: Some(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
|
|
#[allow(dead_code)]
|
|
pub(crate) struct RuntimeApi {
|
|
inner: TestApi,
|
|
}
|
|
|
|
impl ProvideRuntimeApi<Block> for TestApi {
|
|
type Api = RuntimeApi;
|
|
fn runtime_api(&self) -> ApiRef<Self::Api> {
|
|
RuntimeApi { inner: self.clone() }.into()
|
|
}
|
|
}
|
|
sp_api::mock_impl_runtime_apis! {
|
|
impl BeefyApi<Block, AuthorityId> for RuntimeApi {
|
|
fn beefy_genesis() -> Option<NumberFor<Block>> {
|
|
Some(self.inner.beefy_genesis)
|
|
}
|
|
|
|
fn validator_set() -> Option<BeefyValidatorSet> {
|
|
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 add_mmr_digest(builder: &mut impl BlockBuilderExt, mmr_hash: MmrRootHash) {
|
|
builder
|
|
.push_deposit_log_digest_item(DigestItem::Consensus(
|
|
BEEFY_ENGINE_ID,
|
|
ConsensusLog::<AuthorityId>::MmrRoot(mmr_hash).encode(),
|
|
))
|
|
.unwrap();
|
|
}
|
|
|
|
fn add_auth_change_digest(builder: &mut impl BlockBuilderExt, new_auth_set: BeefyValidatorSet) {
|
|
builder
|
|
.push_deposit_log_digest_item(DigestItem::Consensus(
|
|
BEEFY_ENGINE_ID,
|
|
ConsensusLog::<AuthorityId>::AuthoritiesChange(new_auth_set).encode(),
|
|
))
|
|
.unwrap();
|
|
}
|
|
|
|
pub(crate) fn make_beefy_ids(keys: &[BeefyKeyring<AuthorityId>]) -> Vec<AuthorityId> {
|
|
keys.iter().map(|key| key.public().into()).collect()
|
|
}
|
|
|
|
pub(crate) fn create_beefy_keystore(authority: &BeefyKeyring<AuthorityId>) -> KeystorePtr {
|
|
let keystore = MemoryKeystore::new();
|
|
keystore
|
|
.ecdsa_generate_new(BEEFY_KEY_TYPE, Some(&authority.to_seed()))
|
|
.expect("Creates authority key");
|
|
keystore.into()
|
|
}
|
|
|
|
async fn voter_init_setup(
|
|
net: &mut BeefyTestNet,
|
|
finality: &mut futures::stream::Fuse<FinalityNotifications<Block>>,
|
|
api: &TestApi,
|
|
) -> Result<PersistedState<Block>, Error> {
|
|
let backend = net.peer(0).client().as_backend();
|
|
let (beefy_genesis, best_grandpa) = wait_for_runtime_pallet(api, finality).await.unwrap();
|
|
let key_store = None.into();
|
|
let metrics = None;
|
|
BeefyWorkerBuilder::load_or_init_state(
|
|
beefy_genesis,
|
|
best_grandpa,
|
|
1,
|
|
backend,
|
|
Arc::new(api.clone()),
|
|
&key_store,
|
|
&metrics,
|
|
true,
|
|
)
|
|
.await
|
|
}
|
|
|
|
// Spawns beefy voters. Returns a future to spawn on the runtime.
|
|
fn initialize_beefy<API>(
|
|
net: &mut BeefyTestNet,
|
|
peers: Vec<(usize, &BeefyKeyring<AuthorityId>, Arc<API>)>,
|
|
min_block_delta: u32,
|
|
) -> impl Future<Output = ()>
|
|
where
|
|
API: ProvideRuntimeApi<Block> + Sync + Send,
|
|
API::Api: BeefyApi<Block, AuthorityId> + MmrApi<Block, MmrRootHash, NumberFor<Block>>,
|
|
{
|
|
let tasks = FuturesUnordered::new();
|
|
|
|
let mut notification_services = peers
|
|
.iter()
|
|
.map(|(peer_id, _, _)| {
|
|
let peer = &mut net.peers[*peer_id];
|
|
(*peer_id, peer.take_notification_service(&beefy_gossip_proto_name()).unwrap())
|
|
})
|
|
.collect::<std::collections::HashMap<_, _>>();
|
|
|
|
for (peer_id, key, api) in peers.into_iter() {
|
|
let peer = &net.peers[peer_id];
|
|
|
|
let keystore = create_beefy_keystore(key);
|
|
|
|
let (_, _, peer_data) = net.make_block_import(peer.client().clone());
|
|
let PeerData { beefy_rpc_links, beefy_voter_links, .. } = peer_data;
|
|
|
|
let beefy_voter_links = beefy_voter_links.lock().take();
|
|
*peer.data.beefy_rpc_links.lock() = beefy_rpc_links.lock().take();
|
|
*peer.data.beefy_voter_links.lock() = beefy_voter_links.clone();
|
|
|
|
let on_demand_justif_handler = peer.data.beefy_justif_req_handler.lock().take().unwrap();
|
|
|
|
let network_params = crate::BeefyNetworkParams {
|
|
network: peer.network_service().clone(),
|
|
sync: peer.sync_service().clone(),
|
|
notification_service: notification_services.remove(&peer_id).unwrap(),
|
|
gossip_protocol_name: beefy_gossip_proto_name(),
|
|
justifications_protocol_name: on_demand_justif_handler.protocol_name(),
|
|
_phantom: PhantomData,
|
|
};
|
|
let payload_provider = MmrRootProvider::new(api.clone());
|
|
|
|
let beefy_params = crate::BeefyParams {
|
|
client: peer.client().as_client(),
|
|
backend: peer.client().as_backend(),
|
|
payload_provider,
|
|
runtime: api.clone(),
|
|
key_store: Some(keystore),
|
|
network_params,
|
|
links: beefy_voter_links.unwrap(),
|
|
min_block_delta,
|
|
prometheus_registry: None,
|
|
on_demand_justifications_handler: on_demand_justif_handler,
|
|
is_authority: true,
|
|
};
|
|
let task = crate::start_beefy_gadget::<_, _, _, _, _, _, _>(beefy_params);
|
|
|
|
fn assert_send<T: Send>(_: &T) {}
|
|
assert_send(&task);
|
|
tasks.push(task);
|
|
}
|
|
|
|
tasks.for_each(|_| async move {})
|
|
}
|
|
|
|
async fn run_until(future: impl Future + Unpin, net: &Arc<Mutex<BeefyTestNet>>) {
|
|
let drive_to_completion = futures::future::poll_fn(|cx| {
|
|
net.lock().poll(cx);
|
|
Poll::<()>::Pending
|
|
});
|
|
let _ = future::select(future, drive_to_completion).await;
|
|
}
|
|
|
|
async fn run_for(duration: Duration, net: &Arc<Mutex<BeefyTestNet>>) {
|
|
run_until(Box::pin(tokio::time::sleep(duration)), net).await;
|
|
}
|
|
|
|
pub(crate) fn get_beefy_streams(
|
|
net: &mut BeefyTestNet,
|
|
// peer index and key
|
|
peers: impl Iterator<Item = (usize, BeefyKeyring<AuthorityId>)>,
|
|
) -> (Vec<NotificationReceiver<H256>>, Vec<NotificationReceiver<BeefyVersionedFinalityProof<Block>>>)
|
|
{
|
|
let mut best_block_streams = Vec::new();
|
|
let mut versioned_finality_proof_streams = Vec::new();
|
|
peers.for_each(|(index, _)| {
|
|
let beefy_rpc_links = net.peer(index).data.beefy_rpc_links.lock().clone().unwrap();
|
|
let BeefyRPCLinks { from_voter_justif_stream, from_voter_best_beefy_stream } =
|
|
beefy_rpc_links;
|
|
best_block_streams.push(from_voter_best_beefy_stream.subscribe(100_000));
|
|
versioned_finality_proof_streams.push(from_voter_justif_stream.subscribe(100_000));
|
|
});
|
|
(best_block_streams, versioned_finality_proof_streams)
|
|
}
|
|
|
|
async fn wait_for_best_beefy_blocks(
|
|
streams: Vec<NotificationReceiver<H256>>,
|
|
net: &Arc<Mutex<BeefyTestNet>>,
|
|
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 header =
|
|
net.lock().peer(i).client().as_client().expect_header(best_beefy_hash).unwrap();
|
|
let best_beefy = *header.number();
|
|
|
|
assert_eq!(expected, Some(best_beefy).as_ref());
|
|
}
|
|
})));
|
|
});
|
|
let wait_for = futures::future::join_all(wait_for);
|
|
run_until(wait_for, net).await;
|
|
}
|
|
|
|
async fn wait_for_beefy_signed_commitments(
|
|
streams: Vec<NotificationReceiver<BeefyVersionedFinalityProof<Block>>>,
|
|
net: &Arc<Mutex<BeefyTestNet>>,
|
|
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 |versioned_finality_proof| {
|
|
let expected = expected.next();
|
|
async move {
|
|
let signed_commitment = match versioned_finality_proof {
|
|
sp_consensus_beefy::VersionedFinalityProof::V1(sc) => sc,
|
|
};
|
|
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);
|
|
run_until(wait_for, net).await;
|
|
}
|
|
|
|
async fn streams_empty_after_future<T>(
|
|
streams: Vec<NotificationReceiver<T>>,
|
|
future: Option<impl Future + Unpin>,
|
|
) where
|
|
T: std::fmt::Debug,
|
|
T: std::cmp::PartialEq,
|
|
{
|
|
if let Some(future) = future {
|
|
future.await;
|
|
}
|
|
for mut stream in streams.into_iter() {
|
|
future::poll_fn(move |cx| {
|
|
assert_eq!(stream.poll_next_unpin(cx), Poll::Pending);
|
|
Poll::Ready(())
|
|
})
|
|
.await;
|
|
}
|
|
}
|
|
|
|
async fn streams_empty_after_timeout<T>(
|
|
streams: Vec<NotificationReceiver<T>>,
|
|
net: &Arc<Mutex<BeefyTestNet>>,
|
|
timeout: Option<Duration>,
|
|
) where
|
|
T: std::fmt::Debug,
|
|
T: std::cmp::PartialEq,
|
|
{
|
|
let timeout = timeout.map(|timeout| Box::pin(run_for(timeout, net)));
|
|
streams_empty_after_future(streams, timeout).await;
|
|
}
|
|
|
|
async fn finalize_block_and_wait_for_beefy(
|
|
net: &Arc<Mutex<BeefyTestNet>>,
|
|
// peer index and key
|
|
peers: impl Iterator<Item = (usize, BeefyKeyring<AuthorityId>)> + Clone,
|
|
finalize_target: &H256,
|
|
expected_beefy: &[u64],
|
|
) {
|
|
let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone());
|
|
|
|
peers.clone().for_each(|(index, _)| {
|
|
let client = net.lock().peer(index).client().as_client();
|
|
client.finalize_block(*finalize_target, None).unwrap();
|
|
});
|
|
|
|
if expected_beefy.is_empty() {
|
|
// run for quarter second then verify no new best beefy block available
|
|
let timeout = Some(Duration::from_millis(250));
|
|
streams_empty_after_timeout(best_blocks, &net, timeout).await;
|
|
streams_empty_after_timeout(versioned_finality_proof, &net, None).await;
|
|
} else {
|
|
// run until expected beefy blocks are received
|
|
wait_for_best_beefy_blocks(best_blocks, &net, expected_beefy).await;
|
|
wait_for_beefy_signed_commitments(versioned_finality_proof, &net, expected_beefy).await;
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn beefy_finalizing_blocks() {
|
|
sp_tracing::try_init_simple();
|
|
|
|
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);
|
|
|
|
let api = Arc::new(TestApi::with_validator_set(&validator_set));
|
|
let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect();
|
|
tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta));
|
|
|
|
// push 42 blocks including `AuthorityChange` digests every 10 blocks.
|
|
let hashes = net.generate_blocks_and_sync(42, session_len, &validator_set, true).await;
|
|
|
|
let net = Arc::new(Mutex::new(net));
|
|
|
|
// Minimum BEEFY block delta is 4.
|
|
|
|
let peers = peers.into_iter().enumerate();
|
|
// finalize block #5 -> BEEFY should finalize #1 (mandatory) and #5 from diff-power-of-two rule.
|
|
finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[1], &[1]).await;
|
|
finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[5], &[5]).await;
|
|
|
|
// GRANDPA finalize #10 -> BEEFY finalize #10 (mandatory)
|
|
finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[10], &[10]).await;
|
|
|
|
// GRANDPA finalize #18 -> BEEFY finalize #14, then #18 (diff-power-of-two rule)
|
|
finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[18], &[14, 18]).await;
|
|
|
|
// GRANDPA finalize #20 -> BEEFY finalize #20 (mandatory)
|
|
finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[20], &[20]).await;
|
|
|
|
// GRANDPA finalize #21 -> BEEFY finalize nothing (yet) because min delta is 4
|
|
finalize_block_and_wait_for_beefy(&net, peers, &hashes[21], &[]).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn lagging_validators() {
|
|
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 = 30;
|
|
let min_block_delta = 1;
|
|
|
|
let mut net = BeefyTestNet::new(3);
|
|
let api = Arc::new(TestApi::with_validator_set(&validator_set));
|
|
let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect();
|
|
tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta));
|
|
|
|
// push 62 blocks including `AuthorityChange` digests every 30 blocks.
|
|
let hashes = net.generate_blocks_and_sync(62, session_len, &validator_set, true).await;
|
|
|
|
let net = Arc::new(Mutex::new(net));
|
|
|
|
let peers = peers.into_iter().enumerate();
|
|
// 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.clone(), &hashes[1], &[1]).await;
|
|
finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[15], &[9, 13, 14, 15]).await;
|
|
|
|
// Alice and Bob finalize #25, Charlie lags behind
|
|
let finalize = hashes[25];
|
|
let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone());
|
|
net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap();
|
|
net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap();
|
|
// verify nothing gets finalized by BEEFY
|
|
let timeout = Some(Duration::from_millis(100));
|
|
streams_empty_after_timeout(best_blocks, &net, timeout).await;
|
|
streams_empty_after_timeout(versioned_finality_proof, &net, None).await;
|
|
|
|
// Charlie catches up and also finalizes #25
|
|
let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone());
|
|
net.lock().peer(2).client().as_client().finalize_block(finalize, None).unwrap();
|
|
// expected beefy finalizes blocks 23, 24, 25 from diff-power-of-two
|
|
wait_for_best_beefy_blocks(best_blocks, &net, &[23, 24, 25]).await;
|
|
wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &[23, 24, 25]).await;
|
|
|
|
// Both finalize #30 (mandatory session) and #32 -> BEEFY finalize #30 (mandatory), #31, #32
|
|
finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[30], &[30]).await;
|
|
finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[32], &[31, 32]).await;
|
|
|
|
// Verify that session-boundary votes get buffered by client and only processed once
|
|
// session-boundary block is GRANDPA-finalized (this guarantees authenticity for the new session
|
|
// validator set).
|
|
|
|
// Alice and Bob finalize session-boundary mandatory block #60, Charlie lags behind
|
|
let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone());
|
|
let finalize = hashes[60];
|
|
net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap();
|
|
net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap();
|
|
// verify nothing gets finalized by BEEFY
|
|
let timeout = Some(Duration::from_millis(100));
|
|
streams_empty_after_timeout(best_blocks, &net, timeout).await;
|
|
streams_empty_after_timeout(versioned_finality_proof, &net, None).await;
|
|
|
|
// Charlie catches up and also finalizes #60 (and should have buffered Alice's vote on #60)
|
|
let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers);
|
|
net.lock().peer(2).client().as_client().finalize_block(finalize, None).unwrap();
|
|
// verify beefy skips intermediary votes, and successfully finalizes mandatory block #60
|
|
wait_for_best_beefy_blocks(best_blocks, &net, &[60]).await;
|
|
wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &[60]).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn correct_beefy_payload() {
|
|
sp_tracing::try_init_simple();
|
|
|
|
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);
|
|
|
|
// Alice, Bob, Charlie will vote on good payloads
|
|
let good_api = Arc::new(TestApi::new(1, &validator_set, GOOD_MMR_ROOT));
|
|
let good_peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie]
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(id, key)| (id, key, good_api.clone()))
|
|
.collect();
|
|
tokio::spawn(initialize_beefy(&mut net, good_peers, min_block_delta));
|
|
|
|
// Dave will vote on bad mmr roots
|
|
let bad_api = Arc::new(TestApi::new(1, &validator_set, BAD_MMR_ROOT));
|
|
let bad_peers = vec![(3, &BeefyKeyring::Dave, bad_api)];
|
|
tokio::spawn(initialize_beefy(&mut net, bad_peers, min_block_delta));
|
|
|
|
// push 12 blocks
|
|
let hashes = net.generate_blocks_and_sync(12, session_len, &validator_set, false).await;
|
|
|
|
let net = Arc::new(Mutex::new(net));
|
|
let peers = peers.into_iter().enumerate();
|
|
// with 3 good voters and 1 bad one, consensus should happen and best blocks produced.
|
|
finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[1], &[1]).await;
|
|
finalize_block_and_wait_for_beefy(&net, peers, &hashes[10], &[9]).await;
|
|
|
|
let (best_blocks, versioned_finality_proof) =
|
|
get_beefy_streams(&mut net.lock(), [(0, BeefyKeyring::Alice)].into_iter());
|
|
|
|
// now 2 good validators and 1 bad one are voting
|
|
let hashof11 = hashes[11];
|
|
net.lock().peer(0).client().as_client().finalize_block(hashof11, None).unwrap();
|
|
net.lock().peer(1).client().as_client().finalize_block(hashof11, None).unwrap();
|
|
net.lock().peer(3).client().as_client().finalize_block(hashof11, None).unwrap();
|
|
|
|
// verify consensus is _not_ reached
|
|
let timeout = Some(Duration::from_millis(100));
|
|
streams_empty_after_timeout(best_blocks, &net, timeout).await;
|
|
streams_empty_after_timeout(versioned_finality_proof, &net, None).await;
|
|
|
|
// 3rd good validator catches up and votes as well
|
|
let (best_blocks, versioned_finality_proof) =
|
|
get_beefy_streams(&mut net.lock(), [(0, BeefyKeyring::Alice)].into_iter());
|
|
net.lock().peer(2).client().as_client().finalize_block(hashof11, None).unwrap();
|
|
|
|
// verify consensus is reached
|
|
wait_for_best_beefy_blocks(best_blocks, &net, &[11]).await;
|
|
wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &[11]).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn beefy_importing_justifications() {
|
|
use futures::{future::poll_fn, task::Poll};
|
|
use sc_client_api::BlockBackend;
|
|
|
|
sp_tracing::try_init_simple();
|
|
|
|
let mut net = BeefyTestNet::new(2);
|
|
let keys = &[BeefyKeyring::Alice, BeefyKeyring::Bob];
|
|
let good_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap();
|
|
// Set BEEFY genesis to block 3.
|
|
net.beefy_genesis = 3;
|
|
|
|
let client = net.peer(0).client().clone();
|
|
let full_client = client.as_client();
|
|
let (mut block_import, _, peer_data) = net.make_block_import(client.clone());
|
|
let PeerData { beefy_voter_links, .. } = peer_data;
|
|
let justif_stream = beefy_voter_links.lock().take().unwrap().from_block_import_justif_stream;
|
|
let mut justif_recv = justif_stream.subscribe(100_000);
|
|
|
|
let params = |block: Block, justifications: Option<Justifications>| {
|
|
let mut import = BlockImportParams::new(BlockOrigin::File, block.header);
|
|
import.justifications = justifications;
|
|
import.body = Some(block.extrinsics);
|
|
import.finalized = true;
|
|
import.fork_choice = Some(ForkChoiceStrategy::LongestChain);
|
|
import
|
|
};
|
|
let backend_justif_for = |block_hash: H256| -> Option<EncodedJustification> {
|
|
full_client
|
|
.justifications(block_hash)
|
|
.unwrap()
|
|
.and_then(|j| j.get(BEEFY_ENGINE_ID).cloned())
|
|
};
|
|
|
|
let builder = BlockBuilderBuilder::new(&*full_client)
|
|
.on_parent_block(full_client.genesis_hash())
|
|
.with_parent_block_number(0)
|
|
.build()
|
|
.unwrap();
|
|
let block = builder.build().unwrap().block;
|
|
let hashof1 = block.header.hash();
|
|
|
|
// Import block 1 without justifications.
|
|
assert_eq!(
|
|
block_import.import_block(params(block.clone(), None)).await.unwrap(),
|
|
ImportResult::Imported(ImportedAux { is_new_best: true, ..Default::default() }),
|
|
);
|
|
assert_eq!(
|
|
block_import.import_block(params(block, None)).await.unwrap(),
|
|
ImportResult::AlreadyInChain,
|
|
);
|
|
|
|
// Import block 2 with "valid" justification (beefy pallet genesis block not yet reached).
|
|
let block_num = 2;
|
|
let builder = BlockBuilderBuilder::new(&*full_client)
|
|
.on_parent_block(hashof1)
|
|
.with_parent_block_number(1)
|
|
.build()
|
|
.unwrap();
|
|
let block = builder.build().unwrap().block;
|
|
let hashof2 = block.header.hash();
|
|
|
|
let proof = crate::justification::tests::new_finality_proof(block_num, &good_set, keys);
|
|
let versioned_proof: VersionedFinalityProof<NumberFor<Block>, Signature> = proof.into();
|
|
let encoded = versioned_proof.encode();
|
|
let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded)));
|
|
assert_eq!(
|
|
block_import.import_block(params(block, justif)).await.unwrap(),
|
|
ImportResult::Imported(ImportedAux {
|
|
bad_justification: false,
|
|
is_new_best: true,
|
|
..Default::default()
|
|
}),
|
|
);
|
|
|
|
// Verify no BEEFY justifications present (for either block 1 or 2):
|
|
{
|
|
// none in backend,
|
|
assert_eq!(backend_justif_for(hashof1), None);
|
|
assert_eq!(backend_justif_for(hashof2), None);
|
|
// and none sent to BEEFY worker.
|
|
poll_fn(move |cx| {
|
|
assert_eq!(justif_recv.poll_next_unpin(cx), Poll::Pending);
|
|
Poll::Ready(())
|
|
})
|
|
.await;
|
|
}
|
|
|
|
// Import block 3 with valid justification.
|
|
let block_num = 3;
|
|
let builder = BlockBuilderBuilder::new(&*full_client)
|
|
.on_parent_block(hashof2)
|
|
.with_parent_block_number(2)
|
|
.build()
|
|
.unwrap();
|
|
let block = builder.build().unwrap().block;
|
|
let hashof3 = block.header.hash();
|
|
let proof = crate::justification::tests::new_finality_proof(block_num, &good_set, keys);
|
|
let versioned_proof: VersionedFinalityProof<NumberFor<Block>, Signature> = proof.into();
|
|
let encoded = versioned_proof.encode();
|
|
let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded)));
|
|
let mut justif_recv = justif_stream.subscribe(100_000);
|
|
assert_eq!(
|
|
block_import.import_block(params(block, justif)).await.unwrap(),
|
|
ImportResult::Imported(ImportedAux {
|
|
bad_justification: false,
|
|
is_new_best: true,
|
|
..Default::default()
|
|
}),
|
|
);
|
|
// Verify BEEFY justification successfully imported:
|
|
{
|
|
// still not in backend (worker is responsible for appending to backend),
|
|
assert_eq!(backend_justif_for(hashof3), None);
|
|
// but sent to BEEFY worker
|
|
// (worker will append it to backend when all previous mandatory justifs are there as well).
|
|
poll_fn(move |cx| {
|
|
match justif_recv.poll_next_unpin(cx) {
|
|
Poll::Ready(Some(_justification)) => (),
|
|
v => panic!("unexpected value: {:?}", v),
|
|
}
|
|
Poll::Ready(())
|
|
})
|
|
.await;
|
|
}
|
|
|
|
// Import block 4 with invalid justification (incorrect validator set).
|
|
let block_num = 4;
|
|
let builder = BlockBuilderBuilder::new(&*full_client)
|
|
.on_parent_block(hashof3)
|
|
.with_parent_block_number(3)
|
|
.build()
|
|
.unwrap();
|
|
let block = builder.build().unwrap().block;
|
|
let hashof4 = block.header.hash();
|
|
let keys = &[BeefyKeyring::Alice];
|
|
let bad_set = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap();
|
|
let proof = crate::justification::tests::new_finality_proof(block_num, &bad_set, keys);
|
|
let versioned_proof: VersionedFinalityProof<NumberFor<Block>, Signature> = proof.into();
|
|
let encoded = versioned_proof.encode();
|
|
let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded)));
|
|
let mut justif_recv = justif_stream.subscribe(100_000);
|
|
assert_eq!(
|
|
block_import.import_block(params(block, justif)).await.unwrap(),
|
|
ImportResult::Imported(ImportedAux {
|
|
// Still `false` because we don't want to fail import on bad BEEFY justifications.
|
|
bad_justification: false,
|
|
is_new_best: true,
|
|
..Default::default()
|
|
}),
|
|
);
|
|
// Verify bad BEEFY justifications was not imported:
|
|
{
|
|
// none in backend,
|
|
assert_eq!(backend_justif_for(hashof4), None);
|
|
// and none sent to BEEFY worker.
|
|
poll_fn(move |cx| {
|
|
assert_eq!(justif_recv.poll_next_unpin(cx), Poll::Pending);
|
|
Poll::Ready(())
|
|
})
|
|
.await;
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn on_demand_beefy_justification_sync() {
|
|
sp_tracing::try_init_simple();
|
|
|
|
let all_peers =
|
|
[BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie, BeefyKeyring::Dave];
|
|
let validator_set = ValidatorSet::new(make_beefy_ids(&all_peers), 0).unwrap();
|
|
let session_len = 5;
|
|
let min_block_delta = 4;
|
|
|
|
let mut net = BeefyTestNet::new(4);
|
|
|
|
// Alice, Bob, Charlie start first and make progress through voting.
|
|
let api = Arc::new(TestApi::with_validator_set(&validator_set));
|
|
let fast_peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie];
|
|
let voting_peers =
|
|
fast_peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect();
|
|
tokio::spawn(initialize_beefy(&mut net, voting_peers, min_block_delta));
|
|
|
|
// Dave will start late and have to catch up using on-demand justification requests (since
|
|
// in this test there is no block import queue to automatically import justifications).
|
|
let dave = vec![(3, &BeefyKeyring::Dave, api)];
|
|
// Instantiate but don't run Dave, yet.
|
|
let dave_task = initialize_beefy(&mut net, dave, min_block_delta);
|
|
let dave_index = 3;
|
|
|
|
// push 30 blocks
|
|
let mut hashes = net.generate_blocks_and_sync(30, session_len, &validator_set, false).await;
|
|
|
|
let fast_peers = fast_peers.into_iter().enumerate();
|
|
let net = Arc::new(Mutex::new(net));
|
|
// With 3 active voters and one inactive, consensus should happen and blocks BEEFY-finalized.
|
|
// Need to finalize at least one block in each session, choose randomly.
|
|
finalize_block_and_wait_for_beefy(&net, fast_peers.clone(), &hashes[1], &[1]).await;
|
|
finalize_block_and_wait_for_beefy(&net, fast_peers.clone(), &hashes[6], &[5]).await;
|
|
finalize_block_and_wait_for_beefy(&net, fast_peers.clone(), &hashes[10], &[10]).await;
|
|
finalize_block_and_wait_for_beefy(&net, fast_peers.clone(), &hashes[17], &[15]).await;
|
|
finalize_block_and_wait_for_beefy(&net, fast_peers.clone(), &hashes[24], &[20]).await;
|
|
|
|
// Spawn Dave, they are now way behind voting and can only catch up through on-demand justif
|
|
// sync.
|
|
tokio::spawn(dave_task);
|
|
// Dave pushes and syncs 4 more blocks just to make sure he gets included in gossip.
|
|
{
|
|
let mut net_guard = net.lock();
|
|
let built_hashes =
|
|
net_guard
|
|
.peer(dave_index)
|
|
.generate_blocks(4, BlockOrigin::File, |builder| builder.build().unwrap().block);
|
|
hashes.extend(built_hashes);
|
|
net_guard.run_until_sync().await;
|
|
}
|
|
|
|
let (dave_best_blocks, _) =
|
|
get_beefy_streams(&mut net.lock(), [(dave_index, BeefyKeyring::Dave)].into_iter());
|
|
let client = net.lock().peer(dave_index).client().as_client();
|
|
client.finalize_block(hashes[1], None).unwrap();
|
|
// Give Dave task some cpu cycles to process the finality notification,
|
|
run_for(Duration::from_millis(100), &net).await;
|
|
// freshly spun up Dave now needs to listen for gossip to figure out the state of their peers.
|
|
|
|
// Have the other peers do some gossip so Dave finds out about their progress.
|
|
finalize_block_and_wait_for_beefy(&net, fast_peers.clone(), &hashes[25], &[25]).await;
|
|
finalize_block_and_wait_for_beefy(&net, fast_peers, &hashes[29], &[29]).await;
|
|
|
|
// Kick Dave's async loop by finalizing another block.
|
|
client.finalize_block(hashes[2], None).unwrap();
|
|
|
|
// And verify Dave successfully finalized #1 (through on-demand justification request).
|
|
wait_for_best_beefy_blocks(dave_best_blocks, &net, &[1]).await;
|
|
|
|
// Give all tasks some cpu cycles to burn through their events queues,
|
|
run_for(Duration::from_millis(100), &net).await;
|
|
// then verify Dave catches up through on-demand justification requests.
|
|
let (dave_best_blocks, _) =
|
|
get_beefy_streams(&mut net.lock(), [(dave_index, BeefyKeyring::Dave)].into_iter());
|
|
client.finalize_block(hashes[6], None).unwrap();
|
|
client.finalize_block(hashes[10], None).unwrap();
|
|
client.finalize_block(hashes[17], None).unwrap();
|
|
client.finalize_block(hashes[24], None).unwrap();
|
|
client.finalize_block(hashes[26], None).unwrap();
|
|
wait_for_best_beefy_blocks(dave_best_blocks, &net, &[5, 10, 15, 20, 25]).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn should_initialize_voter_at_genesis() {
|
|
let keys = &[BeefyKeyring::Alice];
|
|
let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap();
|
|
let mut net = BeefyTestNet::new(1);
|
|
let backend = net.peer(0).client().as_backend();
|
|
|
|
// push 15 blocks with `AuthorityChange` digests every 10 blocks
|
|
let hashes = net.generate_blocks_and_sync(15, 10, &validator_set, false).await;
|
|
let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse();
|
|
// finalize 13 without justifications
|
|
net.peer(0).client().as_client().finalize_block(hashes[13], None).unwrap();
|
|
|
|
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).await.unwrap();
|
|
|
|
// Test initialization at session boundary.
|
|
// verify voter initialized with two sessions starting at blocks 1 and 10
|
|
let sessions = persisted_state.voting_oracle().sessions();
|
|
assert_eq!(sessions.len(), 2);
|
|
assert_eq!(sessions[0].session_start(), 1);
|
|
assert_eq!(sessions[1].session_start(), 10);
|
|
let rounds = persisted_state.active_round().unwrap();
|
|
assert_eq!(rounds.session_start(), 1);
|
|
assert_eq!(rounds.validator_set_id(), validator_set.id());
|
|
|
|
// verify next vote target is mandatory block 1
|
|
assert_eq!(persisted_state.best_beefy(), 0);
|
|
assert_eq!(persisted_state.best_grandpa_number(), 13);
|
|
assert_eq!(persisted_state.voting_oracle().voting_target(), Some(1));
|
|
|
|
// verify state also saved to db
|
|
assert!(verify_persisted_version(&*backend));
|
|
let state = load_persistent(&*backend).unwrap().unwrap();
|
|
assert_eq!(state, persisted_state);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn should_initialize_voter_at_custom_genesis() {
|
|
let keys = &[BeefyKeyring::Alice];
|
|
let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap();
|
|
let mut net = BeefyTestNet::new(1);
|
|
let backend = net.peer(0).client().as_backend();
|
|
// custom pallet genesis is block number 7
|
|
let custom_pallet_genesis = 7;
|
|
let api = TestApi::new(custom_pallet_genesis, &validator_set, GOOD_MMR_ROOT);
|
|
|
|
// push 15 blocks with `AuthorityChange` digests every 15 blocks
|
|
let hashes = net.generate_blocks_and_sync(15, 15, &validator_set, false).await;
|
|
let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse();
|
|
// finalize 3, 5, 8 without justifications
|
|
net.peer(0).client().as_client().finalize_block(hashes[3], None).unwrap();
|
|
net.peer(0).client().as_client().finalize_block(hashes[5], None).unwrap();
|
|
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).await.unwrap();
|
|
|
|
// Test initialization at session boundary.
|
|
// verify voter initialized with single session starting at block `custom_pallet_genesis` (7)
|
|
let sessions = persisted_state.voting_oracle().sessions();
|
|
assert_eq!(sessions.len(), 1);
|
|
assert_eq!(sessions[0].session_start(), custom_pallet_genesis);
|
|
let rounds = persisted_state.active_round().unwrap();
|
|
assert_eq!(rounds.session_start(), custom_pallet_genesis);
|
|
assert_eq!(rounds.validator_set_id(), validator_set.id());
|
|
|
|
// verify next vote target is mandatory block 7
|
|
assert_eq!(persisted_state.best_beefy(), 0);
|
|
assert_eq!(persisted_state.best_grandpa_number(), 8);
|
|
assert_eq!(persisted_state.voting_oracle().voting_target(), Some(custom_pallet_genesis));
|
|
|
|
// verify state also saved to db
|
|
assert!(verify_persisted_version(&*backend));
|
|
let state = load_persistent(&*backend).unwrap().unwrap();
|
|
assert_eq!(state, persisted_state);
|
|
|
|
// now re-init after genesis changes
|
|
|
|
// should ignore existing aux db state and reinit at new genesis
|
|
let new_validator_set = ValidatorSet::new(make_beefy_ids(keys), 42).unwrap();
|
|
let new_pallet_genesis = 10;
|
|
let api = TestApi::new(new_pallet_genesis, &new_validator_set, GOOD_MMR_ROOT);
|
|
|
|
net.peer(0).client().as_client().finalize_block(hashes[10], None).unwrap();
|
|
// load persistent state - state preset in DB, but with different pallet genesis
|
|
let new_persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap();
|
|
|
|
// verify voter initialized with single session starting at block `new_pallet_genesis` (10)
|
|
let sessions = new_persisted_state.voting_oracle().sessions();
|
|
assert_eq!(sessions.len(), 1);
|
|
assert_eq!(sessions[0].session_start(), new_pallet_genesis);
|
|
let rounds = new_persisted_state.active_round().unwrap();
|
|
assert_eq!(rounds.session_start(), new_pallet_genesis);
|
|
assert_eq!(rounds.validator_set_id(), new_validator_set.id());
|
|
|
|
// verify next vote target is mandatory block 10
|
|
assert_eq!(new_persisted_state.best_beefy(), 0);
|
|
assert_eq!(new_persisted_state.best_grandpa_number(), 10);
|
|
assert_eq!(new_persisted_state.voting_oracle().voting_target(), Some(new_pallet_genesis));
|
|
|
|
// verify state also saved to db
|
|
assert!(verify_persisted_version(&*backend));
|
|
let state = load_persistent(&*backend).unwrap().unwrap();
|
|
assert_eq!(state, new_persisted_state);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn should_initialize_voter_when_last_final_is_session_boundary() {
|
|
let keys = &[BeefyKeyring::Alice];
|
|
let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap();
|
|
let mut net = BeefyTestNet::new(1);
|
|
let backend = net.peer(0).client().as_backend();
|
|
|
|
// push 15 blocks with `AuthorityChange` digests every 10 blocks
|
|
let hashes = net.generate_blocks_and_sync(15, 10, &validator_set, false).await;
|
|
|
|
let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse();
|
|
|
|
// finalize 13 without justifications
|
|
net.peer(0).client().as_client().finalize_block(hashes[13], None).unwrap();
|
|
|
|
// import/append BEEFY justification for session boundary block 10
|
|
let commitment = Commitment {
|
|
payload: Payload::from_single_entry(known_payloads::MMR_ROOT_ID, vec![]),
|
|
block_number: 10,
|
|
validator_set_id: validator_set.id(),
|
|
};
|
|
let justif = VersionedFinalityProof::<_, Signature>::V1(SignedCommitment {
|
|
commitment,
|
|
signatures: vec![None],
|
|
});
|
|
backend
|
|
.append_justification(hashes[10], (BEEFY_ENGINE_ID, justif.encode()))
|
|
.unwrap();
|
|
|
|
// Test corner-case where session boundary == last beefy finalized,
|
|
// expect rounds initialized at last beefy finalized 10.
|
|
|
|
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).await.unwrap();
|
|
|
|
// verify voter initialized with single session starting at block 10
|
|
assert_eq!(persisted_state.voting_oracle().sessions().len(), 1);
|
|
let rounds = persisted_state.active_round().unwrap();
|
|
assert_eq!(rounds.session_start(), 10);
|
|
assert_eq!(rounds.validator_set_id(), validator_set.id());
|
|
|
|
// verify block 10 is correctly marked as finalized
|
|
assert_eq!(persisted_state.best_beefy(), 10);
|
|
assert_eq!(persisted_state.best_grandpa_number(), 13);
|
|
// verify next vote target is diff-power-of-two block 12
|
|
assert_eq!(persisted_state.voting_oracle().voting_target(), Some(12));
|
|
|
|
// verify state also saved to db
|
|
assert!(verify_persisted_version(&*backend));
|
|
let state = load_persistent(&*backend).unwrap().unwrap();
|
|
assert_eq!(state, persisted_state);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn should_initialize_voter_at_latest_finalized() {
|
|
let keys = &[BeefyKeyring::Alice];
|
|
let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap();
|
|
let mut net = BeefyTestNet::new(1);
|
|
let backend = net.peer(0).client().as_backend();
|
|
|
|
// push 15 blocks with `AuthorityChange` digests every 10 blocks
|
|
let hashes = net.generate_blocks_and_sync(15, 10, &validator_set, false).await;
|
|
|
|
let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse();
|
|
|
|
// finalize 13 without justifications
|
|
net.peer(0).client().as_client().finalize_block(hashes[13], None).unwrap();
|
|
|
|
// import/append BEEFY justification for block 12
|
|
let commitment = Commitment {
|
|
payload: Payload::from_single_entry(known_payloads::MMR_ROOT_ID, vec![]),
|
|
block_number: 12,
|
|
validator_set_id: validator_set.id(),
|
|
};
|
|
let justif = VersionedFinalityProof::<_, Signature>::V1(SignedCommitment {
|
|
commitment,
|
|
signatures: vec![None],
|
|
});
|
|
backend
|
|
.append_justification(hashes[12], (BEEFY_ENGINE_ID, justif.encode()))
|
|
.unwrap();
|
|
|
|
// Test initialization at last BEEFY 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).await.unwrap();
|
|
|
|
// verify voter initialized with single session starting at block 12
|
|
assert_eq!(persisted_state.voting_oracle().sessions().len(), 1);
|
|
let rounds = persisted_state.active_round().unwrap();
|
|
assert_eq!(rounds.session_start(), 12);
|
|
assert_eq!(rounds.validator_set_id(), validator_set.id());
|
|
|
|
// verify next vote target is 13
|
|
assert_eq!(persisted_state.best_beefy(), 12);
|
|
assert_eq!(persisted_state.best_grandpa_number(), 13);
|
|
assert_eq!(persisted_state.voting_oracle().voting_target(), Some(13));
|
|
|
|
// verify state also saved to db
|
|
assert!(verify_persisted_version(&*backend));
|
|
let state = load_persistent(&*backend).unwrap().unwrap();
|
|
assert_eq!(state, persisted_state);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn should_initialize_voter_at_custom_genesis_when_state_unavailable() {
|
|
let keys = &[BeefyKeyring::Alice];
|
|
let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap();
|
|
let mut net = BeefyTestNet::new(1);
|
|
let backend = net.peer(0).client().as_backend();
|
|
// custom pallet genesis is block number 7
|
|
let custom_pallet_genesis = 7;
|
|
let mut api = TestApi::new(custom_pallet_genesis, &validator_set, GOOD_MMR_ROOT);
|
|
// remove validator set from `TestApi`, practically simulating unavailable/pruned runtime state
|
|
api.validator_set = None;
|
|
|
|
// push 30 blocks with `AuthorityChange` digests every 5 blocks
|
|
let hashes = net.generate_blocks_and_sync(30, 5, &validator_set, false).await;
|
|
let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse();
|
|
// finalize 30 without justifications
|
|
net.peer(0).client().as_client().finalize_block(hashes[30], None).unwrap();
|
|
|
|
// load persistent state - nothing in DB, should init at genesis
|
|
let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap();
|
|
|
|
// Test initialization at session boundary.
|
|
// verify voter initialized with all sessions pending, first one starting at block 5 (start of
|
|
// session containing `custom_pallet_genesis`).
|
|
let sessions = persisted_state.voting_oracle().sessions();
|
|
// should have enqueued 6 sessions (every 5 blocks from 5 to 30)
|
|
assert_eq!(sessions.len(), 6);
|
|
assert_eq!(sessions[0].session_start(), 7);
|
|
assert_eq!(sessions[1].session_start(), 10);
|
|
assert_eq!(sessions[2].session_start(), 15);
|
|
assert_eq!(sessions[3].session_start(), 20);
|
|
assert_eq!(sessions[4].session_start(), 25);
|
|
assert_eq!(sessions[5].session_start(), 30);
|
|
let rounds = persisted_state.active_round().unwrap();
|
|
assert_eq!(rounds.session_start(), custom_pallet_genesis);
|
|
assert_eq!(rounds.validator_set_id(), validator_set.id());
|
|
|
|
// verify next vote target is mandatory block 7 (genesis)
|
|
assert_eq!(persisted_state.best_beefy(), 0);
|
|
assert_eq!(persisted_state.best_grandpa_number(), 30);
|
|
assert_eq!(persisted_state.voting_oracle().voting_target(), Some(custom_pallet_genesis));
|
|
|
|
// verify state also saved to db
|
|
assert!(verify_persisted_version(&*backend));
|
|
let state = load_persistent(&*backend).unwrap().unwrap();
|
|
assert_eq!(state, persisted_state);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn should_catch_up_when_loading_saved_voter_state() {
|
|
let keys = &[BeefyKeyring::Alice];
|
|
let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap();
|
|
let mut net = BeefyTestNet::new(1);
|
|
let backend = net.peer(0).client().as_backend();
|
|
|
|
// push 30 blocks with `AuthorityChange` digests every 10 blocks
|
|
let hashes = net.generate_blocks_and_sync(30, 10, &validator_set, false).await;
|
|
let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse();
|
|
// finalize 13 without justifications
|
|
net.peer(0).client().as_client().finalize_block(hashes[13], None).unwrap();
|
|
|
|
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).await.unwrap();
|
|
|
|
// Test initialization at session boundary.
|
|
// verify voter initialized with two sessions starting at blocks 1 and 10
|
|
let sessions = persisted_state.voting_oracle().sessions();
|
|
assert_eq!(sessions.len(), 2);
|
|
assert_eq!(sessions[0].session_start(), 1);
|
|
assert_eq!(sessions[1].session_start(), 10);
|
|
let rounds = persisted_state.active_round().unwrap();
|
|
assert_eq!(rounds.session_start(), 1);
|
|
assert_eq!(rounds.validator_set_id(), validator_set.id());
|
|
|
|
// verify next vote target is mandatory block 1
|
|
assert_eq!(persisted_state.best_beefy(), 0);
|
|
assert_eq!(persisted_state.best_grandpa_number(), 13);
|
|
assert_eq!(persisted_state.voting_oracle().voting_target(), Some(1));
|
|
|
|
// verify state also saved to db
|
|
assert!(verify_persisted_version(&*backend));
|
|
let state = load_persistent(&*backend).unwrap().unwrap();
|
|
assert_eq!(state, persisted_state);
|
|
|
|
// now let's consider that the node goes offline, and then it restarts after a while
|
|
|
|
// finalize 25 without justifications
|
|
net.peer(0).client().as_client().finalize_block(hashes[25], None).unwrap();
|
|
// load persistent state - state preset in DB
|
|
let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap();
|
|
|
|
// Verify voter initialized with old sessions plus a new one starting at block 20.
|
|
// There shouldn't be any duplicates.
|
|
let sessions = persisted_state.voting_oracle().sessions();
|
|
assert_eq!(sessions.len(), 3);
|
|
assert_eq!(sessions[0].session_start(), 1);
|
|
assert_eq!(sessions[1].session_start(), 10);
|
|
assert_eq!(sessions[2].session_start(), 20);
|
|
let rounds = persisted_state.active_round().unwrap();
|
|
assert_eq!(rounds.session_start(), 1);
|
|
assert_eq!(rounds.validator_set_id(), validator_set.id());
|
|
|
|
// verify next vote target is mandatory block 1
|
|
assert_eq!(persisted_state.best_beefy(), 0);
|
|
assert_eq!(persisted_state.best_grandpa_number(), 25);
|
|
assert_eq!(persisted_state.voting_oracle().voting_target(), Some(1));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn beefy_finalizing_after_pallet_genesis() {
|
|
sp_tracing::try_init_simple();
|
|
|
|
let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob];
|
|
let validator_set = ValidatorSet::new(make_beefy_ids(&peers), 14).unwrap();
|
|
let session_len = 10;
|
|
let min_block_delta = 1;
|
|
let pallet_genesis = 15;
|
|
|
|
let mut net = BeefyTestNet::new(2);
|
|
|
|
let api = Arc::new(TestApi::new(pallet_genesis, &validator_set, GOOD_MMR_ROOT));
|
|
let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect();
|
|
tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta));
|
|
|
|
// push 42 blocks including `AuthorityChange` digests every 10 blocks.
|
|
let hashes = net.generate_blocks_and_sync(42, session_len, &validator_set, true).await;
|
|
|
|
let net = Arc::new(Mutex::new(net));
|
|
let peers = peers.into_iter().enumerate();
|
|
|
|
// Minimum BEEFY block delta is 1.
|
|
|
|
// GRANDPA finalize blocks leading up to BEEFY pallet genesis -> BEEFY should finalize nothing.
|
|
finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[14], &[]).await;
|
|
|
|
// GRANDPA finalize block #16 -> BEEFY should finalize #15 (genesis mandatory) and #16.
|
|
finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[16], &[15, 16]).await;
|
|
|
|
// 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, &BeefyKeyring::Alice, 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, &BeefyKeyring::Bob, 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).
|
|
let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone());
|
|
peers.clone().for_each(|(index, _)| {
|
|
let client = net.lock().peer(index).client().as_client();
|
|
client.finalize_block(hashes[1], None).unwrap();
|
|
});
|
|
|
|
// run for up to 5 seconds waiting for Alice's report of Bob/Bob_Prime equivocation.
|
|
for wait_ms in [250, 500, 1250, 3000] {
|
|
run_for(Duration::from_millis(wait_ms), &net).await;
|
|
if !api_alice.reported_equivocations.as_ref().unwrap().lock().is_empty() {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Verify expected 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);
|
|
|
|
// 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());
|
|
|
|
// sanity verify no new blocks have been finalized by BEEFY
|
|
streams_empty_after_timeout(best_blocks, &net, None).await;
|
|
streams_empty_after_timeout(versioned_finality_proof, &net, None).await;
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn gossipped_finality_proofs() {
|
|
sp_tracing::try_init_simple();
|
|
|
|
let validators = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie];
|
|
// Only Alice and Bob are running the voter -> finality threshold not reached
|
|
let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob];
|
|
let validator_set = ValidatorSet::new(make_beefy_ids(&validators), 0).unwrap();
|
|
let session_len = 10;
|
|
let min_block_delta = 1;
|
|
|
|
let mut net = BeefyTestNet::new(3);
|
|
let api = Arc::new(TestApi::with_validator_set(&validator_set));
|
|
let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect();
|
|
|
|
let charlie = &mut net.peers[2];
|
|
let known_peers = Arc::new(Mutex::new(KnownPeers::<Block>::new()));
|
|
// Charlie will run just the gossip engine and not the full voter.
|
|
let gossip_validator = GossipValidator::new(known_peers, Arc::new(TestNetwork::new().0));
|
|
let charlie_gossip_validator = Arc::new(gossip_validator);
|
|
charlie_gossip_validator.update_filter(GossipFilterCfg::<Block> {
|
|
start: 1,
|
|
end: 10,
|
|
validator_set: &validator_set,
|
|
});
|
|
let mut charlie_gossip_engine = sc_network_gossip::GossipEngine::new(
|
|
charlie.network_service().clone(),
|
|
charlie.sync_service().clone(),
|
|
charlie.take_notification_service(&beefy_gossip_proto_name()).unwrap(),
|
|
beefy_gossip_proto_name(),
|
|
charlie_gossip_validator.clone(),
|
|
None,
|
|
);
|
|
|
|
// Alice and Bob run full voter.
|
|
tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta));
|
|
|
|
let net = Arc::new(Mutex::new(net));
|
|
|
|
// push 42 blocks
|
|
let hashes = net.lock().generate_blocks_and_sync(42, session_len, &validator_set, true).await;
|
|
|
|
let peers = peers.into_iter().enumerate();
|
|
|
|
// Alice, Bob and Charlie finalize #1, Alice and Bob vote on it, but not Charlie.
|
|
let finalize = hashes[1];
|
|
let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone());
|
|
net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap();
|
|
net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap();
|
|
net.lock().peer(2).client().as_client().finalize_block(finalize, None).unwrap();
|
|
// verify nothing gets finalized by BEEFY
|
|
let timeout = Box::pin(tokio::time::sleep(Duration::from_millis(100)));
|
|
let pump_net = futures::future::poll_fn(|cx| {
|
|
net.lock().poll(cx);
|
|
Poll::<()>::Pending
|
|
});
|
|
let pump_gossip = &mut charlie_gossip_engine;
|
|
let pump_with_timeout = future::select(pump_gossip, future::select(pump_net, timeout));
|
|
streams_empty_after_future(best_blocks, Some(pump_with_timeout)).await;
|
|
streams_empty_after_timeout(versioned_finality_proof, &net, None).await;
|
|
|
|
let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone());
|
|
// Charlie gossips finality proof for #1 -> Alice and Bob also finalize.
|
|
let proof = crate::communication::gossip::tests::dummy_proof(1, &validator_set);
|
|
let gossip_proof = GossipMessage::<Block>::FinalityProof(proof);
|
|
let encoded_proof = gossip_proof.encode();
|
|
charlie_gossip_engine.gossip_message(proofs_topic::<Block>(), encoded_proof, true);
|
|
// Expect #1 is finalized.
|
|
wait_for_best_beefy_blocks(best_blocks, &net, &[1]).await;
|
|
wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &[1]).await;
|
|
|
|
// Code above verifies gossipped finality proofs are correctly imported and consumed by voters.
|
|
// Next, let's verify finality proofs are correctly generated and gossipped by voters.
|
|
|
|
// Everyone finalizes #2
|
|
let block_number = 2u64;
|
|
let finalize = hashes[block_number as usize];
|
|
let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone());
|
|
net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap();
|
|
net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap();
|
|
net.lock().peer(2).client().as_client().finalize_block(finalize, None).unwrap();
|
|
|
|
// Simulate Charlie vote on #2
|
|
let header = net.lock().peer(2).client().as_client().expect_header(finalize).unwrap();
|
|
let mmr_root = find_mmr_root_digest::<Block>(&header).unwrap();
|
|
let payload = Payload::from_single_entry(known_payloads::MMR_ROOT_ID, mmr_root.encode());
|
|
let commitment = Commitment { payload, block_number, validator_set_id: validator_set.id() };
|
|
let signature = sign_commitment(&BeefyKeyring::Charlie, &commitment);
|
|
let vote_message = VoteMessage { commitment, id: BeefyKeyring::Charlie.public(), signature };
|
|
let encoded_vote = GossipMessage::<Block>::Vote(vote_message).encode();
|
|
charlie_gossip_engine.gossip_message(votes_topic::<Block>(), encoded_vote, true);
|
|
|
|
// Expect #2 is finalized.
|
|
wait_for_best_beefy_blocks(best_blocks, &net, &[2]).await;
|
|
wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &[2]).await;
|
|
|
|
// Now verify Charlie also sees the gossipped proof generated by either Alice or Bob.
|
|
let mut charlie_gossip_proofs = Box::pin(
|
|
charlie_gossip_engine
|
|
.messages_for(proofs_topic::<Block>())
|
|
.filter_map(|notification| async move {
|
|
GossipMessage::<Block>::decode(&mut ¬ification.message[..]).ok().and_then(
|
|
|message| match message {
|
|
GossipMessage::<Block>::Vote(_) => unreachable!(),
|
|
GossipMessage::<Block>::FinalityProof(proof) => Some(proof),
|
|
},
|
|
)
|
|
})
|
|
.fuse(),
|
|
);
|
|
loop {
|
|
let pump_net = futures::future::poll_fn(|cx| {
|
|
net.lock().poll(cx);
|
|
Poll::<()>::Pending
|
|
});
|
|
let mut gossip_engine = &mut charlie_gossip_engine;
|
|
futures::select! {
|
|
// pump gossip engine
|
|
_ = gossip_engine => unreachable!(),
|
|
// pump network
|
|
_ = pump_net.fuse() => unreachable!(),
|
|
// verify finality proof has been gossipped
|
|
proof = charlie_gossip_proofs.next() => {
|
|
let proof = proof.unwrap();
|
|
let (round, _) = proof_block_num_and_set_id::<Block>(&proof);
|
|
match round {
|
|
1 => continue, // finality proof generated by Charlie in the previous round
|
|
2 => break, // finality proof generated by Alice or Bob and gossiped to Charlie
|
|
_ => panic!("Charlie got unexpected finality proof"),
|
|
}
|
|
},
|
|
}
|
|
}
|
|
}
|