beefy: initialize voter from genesis and fix initial sync (#11959)

* client/beefy: use backend instead of client where possible

* client/beefy: initialize voter from genesis

Now that we have justifications import, we can drop the "lean beefy"
behaviour and start building justifications chain from Genesis with
containing all past sessions' mandatory blocks justifications.

* client/beefy: walk finality tree_route to catch session changes

* client/beefy: fix block import

During initial block import blocks are not finalized, so trying to
validate and append justifications within block import fails (for
initial network sync imported blocks).

Changes:

- Move justification validation to _after_ `inner.block_import()`,
  so block is imported in backend and runtime api can be called to
  get the BEEFY authorities for said block.
- Move append-to-backend for imported BEEFY justification to voter,
  because it already has the required logic to BEEFY-finalize blocks
  only after GRANDPA finalized them.
- Mark voting rounds as concluded when finalizing through
  imported justifications as well as when finalizing through voting.

* client/beefy: valid justifications are one per block number

The only way we'd get _different_ _validated_ justifications for same
block number is if authorities are double voting, which will be handled
later.

* client/beefy: process incoming justifs during major sync

* client/beefy: correct voter initialization

BEEFY voter should resume voting from either:
  - last BEEFY finalized block,
  - session start,
whichever is closest to head.

* client/beefy: test voter initialization

* client/beefy: impl review suggestions

Signed-off-by: acatangiu <adrian@parity.io>
This commit is contained in:
Adrian Catangiu
2022-09-05 13:47:15 +03:00
committed by GitHub
parent 1fabf067d0
commit 84acfd5f50
4 changed files with 342 additions and 147 deletions
+20 -55
View File
@@ -17,12 +17,11 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use beefy_primitives::{BeefyApi, BEEFY_ENGINE_ID};
use codec::Encode;
use log::error;
use log::debug;
use std::{collections::HashMap, sync::Arc};
use sp_api::{ProvideRuntimeApi, TransactionFor};
use sp_blockchain::{well_known_cache_keys, HeaderBackend};
use sp_blockchain::well_known_cache_keys;
use sp_consensus::Error as ConsensusError;
use sp_runtime::{
generic::BlockId,
@@ -97,29 +96,6 @@ where
decode_and_verify_finality_proof::<Block>(&encoded[..], number, &validator_set)
}
/// Import BEEFY justification: Send it to worker for processing and also append it to backend.
///
/// This function assumes:
/// - `justification` is verified and valid,
/// - the block referred by `justification` has been imported _and_ finalized.
fn import_beefy_justification_unchecked(
&self,
number: NumberFor<Block>,
justification: BeefyVersionedFinalityProof<Block>,
) {
// Append the justification to the block in the backend.
if let Err(e) = self.backend.append_justification(
BlockId::Number(number),
(BEEFY_ENGINE_ID, justification.encode()),
) {
error!(target: "beefy", "🥩 Error {:?} on appending justification: {:?}", e, justification);
}
// Send the justification to the BEEFY voter for processing.
self.justification_sender
.notify(|| Ok::<_, ()>(justification))
.expect("forwards closure result; the closure always returns Ok; qed.");
}
}
#[async_trait::async_trait]
@@ -147,42 +123,31 @@ where
let hash = block.post_hash();
let number = *block.header.number();
let beefy_proof = block
.justifications
.as_mut()
.and_then(|just| {
let decoded = just
.get(BEEFY_ENGINE_ID)
.map(|encoded| self.decode_and_verify(encoded, number, hash));
// Remove BEEFY justification from the list before giving to `inner`;
// we will append it to backend ourselves at the end if all goes well.
just.remove(BEEFY_ENGINE_ID);
decoded
})
.transpose()
.unwrap_or(None);
let beefy_encoded = block.justifications.as_mut().and_then(|just| {
let encoded = just.get(BEEFY_ENGINE_ID).cloned();
// Remove BEEFY justification from the list before giving to `inner`; we send it to the
// voter (beefy-gadget) and it will append it to the backend after block is finalized.
just.remove(BEEFY_ENGINE_ID);
encoded
});
// Run inner block import.
let inner_import_result = self.inner.import_block(block, new_cache).await?;
match (beefy_proof, &inner_import_result) {
(Some(proof), ImportResult::Imported(_)) => {
let status = self.backend.blockchain().info();
if number <= status.finalized_number &&
Some(hash) ==
self.backend
.blockchain()
.hash(number)
.map_err(|e| ConsensusError::ClientImport(e.to_string()))?
{
match (beefy_encoded, &inner_import_result) {
(Some(encoded), ImportResult::Imported(_)) => {
if let Ok(proof) = self.decode_and_verify(&encoded, number, hash) {
// The proof is valid and the block is imported and final, we can import.
self.import_beefy_justification_unchecked(number, proof);
debug!(target: "beefy", "🥩 import justif {:?} for block number {:?}.", proof, number);
// Send the justification to the BEEFY voter for processing.
self.justification_sender
.notify(|| Ok::<_, ()>(proof))
.expect("forwards closure result; the closure always returns Ok; qed.");
} else {
error!(
debug!(
target: "beefy",
"🥩 Cannot import justification: {:?} for, not yet final, block number {:?}",
proof,
number,
"🥩 error decoding justification: {:?} for imported block {:?}",
encoded, number,
);
}
},