BEEFY: gossip finality proofs (#13727)

* sc-consensus-beefy: add justifications to gossip protocol

* sc-consensus-beefy: voter gossips finality proofs

* sc-consensus-beefy: add finality proof gossip test

* sc-consensus-beefy: always gossip finality proof

Gossip finality proof in _both_ cases of reaching finality threshold
through votes:
1. threshold reached through self vote,
2. threshold reached through incoming vote.

* address comments
This commit is contained in:
Adrian Catangiu
2023-03-30 17:23:36 +03:00
committed by GitHub
parent 25a616ce08
commit 92c1229e24
7 changed files with 637 additions and 201 deletions
+165 -11
View File
@@ -21,15 +21,18 @@
use crate::{
aux_schema::{load_persistent, tests::verify_persisted_version},
beefy_block_import_and_links,
communication::request_response::{
on_demand_justifications_protocol_config, BeefyJustifsRequestHandler,
communication::{
gossip::{
proofs_topic, tests::sign_commitment, votes_topic, GossipFilterCfg, GossipMessage,
},
request_response::{on_demand_justifications_protocol_config, BeefyJustifsRequestHandler},
},
gossip_protocol_name,
justification::*,
load_or_init_voter_state, wait_for_runtime_pallet, BeefyRPCLinks, BeefyVoterLinks, KnownPeers,
PersistedState,
};
use futures::{future, stream::FuturesUnordered, Future, StreamExt};
use futures::{future, stream::FuturesUnordered, Future, FutureExt, StreamExt};
use parking_lot::Mutex;
use sc_client_api::{Backend as BackendT, BlockchainEvents, FinalityNotifications, HeaderBackend};
use sc_consensus::{
@@ -48,16 +51,16 @@ use sp_consensus::BlockOrigin;
use sp_consensus_beefy::{
crypto::{AuthorityId, Signature},
known_payloads,
mmr::MmrRootProvider,
mmr::{find_mmr_root_digest, MmrRootProvider},
BeefyApi, Commitment, ConsensusLog, EquivocationProof, Keyring as BeefyKeyring, MmrRootHash,
OpaqueKeyOwnershipProof, Payload, SignedCommitment, ValidatorSet, ValidatorSetId,
VersionedFinalityProof, BEEFY_ENGINE_ID, KEY_TYPE as BeefyKeyType,
VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, KEY_TYPE as BeefyKeyType,
};
use sp_core::H256;
use sp_keystore::{testing::MemoryKeystore, Keystore, KeystorePtr};
use sp_mmr_primitives::{Error as MmrError, MmrApi};
use sp_runtime::{
codec::Encode,
codec::{Decode, Encode},
traits::{Header as HeaderT, NumberFor},
BuildStorage, DigestItem, EncodedJustification, Justifications, Storage,
};
@@ -503,16 +506,15 @@ async fn wait_for_beefy_signed_commitments(
run_until(wait_for, net).await;
}
async fn streams_empty_after_timeout<T>(
async fn streams_empty_after_future<T>(
streams: Vec<NotificationReceiver<T>>,
net: &Arc<Mutex<BeefyTestNet>>,
timeout: Option<Duration>,
future: Option<impl Future + Unpin>,
) where
T: std::fmt::Debug,
T: std::cmp::PartialEq,
{
if let Some(timeout) = timeout {
run_for(timeout, net).await;
if let Some(future) = future {
future.await;
}
for mut stream in streams.into_iter() {
future::poll_fn(move |cx| {
@@ -523,6 +525,18 @@ async fn streams_empty_after_timeout<T>(
}
}
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
@@ -1229,3 +1243,143 @@ async fn beefy_reports_equivocations() {
assert_eq!(equivocation_proof.first.id, BeefyKeyring::Bob.public());
assert_eq!(equivocation_proof.first.commitment.block_number, 1);
}
#[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 = 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();
let charlie = &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 charlie_gossip_validator =
Arc::new(crate::communication::gossip::GossipValidator::new(known_peers));
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(),
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));
// Pump net + Charlie gossip to see peers.
let timeout = Box::pin(tokio::time::sleep(Duration::from_millis(200)));
let gossip_engine_pump = &mut charlie_gossip_engine;
let pump_with_timeout = future::select(gossip_engine_pump, timeout);
run_until(pump_with_timeout, &net).await;
// push 10 blocks
let hashes = net.lock().generate_blocks_and_sync(10, 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 &notification.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"),
}
},
}
}
}