BEEFY voter bugfixes (#11335)

* beefy: gadget should always use current validator set

The gadget/client-voter was using previous' session validator set
to sign the 1st block in the new session (to have chained validator
set handoffs).

This is not necessary because:
1. BEEFY piggy-backs on GRANDPA and only works on canonical chain,
   so it need not concern itself with the validity of the block header
   (which contains digest with the new session's validator set). It
   can safely assume header is valid and simply use new validator set.
2. The BEEFY payload itself already contains a merkle root for the
   next validator set keys. So at the BEEFY-payload layer we already
   have a validated/trusted hand-off of authority.

Signed-off-by: acatangiu <adrian@parity.io>

* beefy: buffer votes for not yet finalized blocks

Signed-off-by: acatangiu <adrian@parity.io>

* beefy: add buffered votes regression test
This commit is contained in:
Adrian Catangiu
2022-05-04 12:36:52 +03:00
committed by GitHub
parent 9fd4df9ccf
commit 1e2424ec4b
3 changed files with 112 additions and 73 deletions
+26 -6
View File
@@ -469,8 +469,8 @@ fn finalize_block_and_wait_for_beefy(
}
if expected_beefy.is_empty() {
// run for 1 second then verify no new best beefy block available
let timeout = Some(Duration::from_millis(500));
// 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, runtime, timeout);
streams_empty_after_timeout(signed_commitments, &net, runtime, None);
} else {
@@ -535,8 +535,8 @@ fn lagging_validators() {
let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect();
runtime.spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta));
// push 42 blocks including `AuthorityChange` digests every 30 blocks.
net.generate_blocks(42, session_len, &validator_set, true);
// push 62 blocks including `AuthorityChange` digests every 30 blocks.
net.generate_blocks(62, session_len, &validator_set, true);
net.block_until_sync();
let net = Arc::new(Mutex::new(net));
@@ -550,7 +550,7 @@ fn lagging_validators() {
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));
let timeout = Some(Duration::from_millis(250));
streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout);
streams_empty_after_timeout(signed_commitments, &net, &mut runtime, None);
@@ -563,6 +563,26 @@ fn lagging_validators() {
// 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]);
// 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 finalizes session-boundary mandatory block #60, Bob lags behind
let (best_blocks, signed_commitments) = get_beefy_streams(&mut *net.lock(), peers);
let finalize = BlockId::number(60);
net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap();
// verify nothing gets finalized by BEEFY
let timeout = Some(Duration::from_millis(250));
streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout);
streams_empty_after_timeout(signed_commitments, &net, &mut runtime, None);
// Bob catches up and also finalizes #60 (and should have buffered Alice's vote on #60)
let (best_blocks, signed_commitments) = get_beefy_streams(&mut *net.lock(), peers);
net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap();
// verify beefy skips intermediary votes, and successfully finalizes mandatory block #40
wait_for_best_beefy_blocks(best_blocks, &net, &mut runtime, &[60]);
wait_for_beefy_signed_commitments(signed_commitments, &net, &mut runtime, &[60]);
}
#[test]
@@ -624,7 +644,7 @@ fn correct_beefy_payload() {
.unwrap();
// verify consensus is _not_ reached
let timeout = Some(Duration::from_millis(500));
let timeout = Some(Duration::from_millis(250));
streams_empty_after_timeout(best_blocks, &net, &mut runtime, timeout);
streams_empty_after_timeout(signed_commitments, &net, &mut runtime, None);