Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BEEFY: Support compatibility with Warp Sync - Allow Warp Sync for Validators #2689

Merged
merged 12 commits into from
Dec 27, 2023
Merged
19 changes: 2 additions & 17 deletions polkadot/cli/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
use crate::cli::{Cli, Subcommand, NODE_VERSION};
use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory, SUBSTRATE_REFERENCE_HARDWARE};
use futures::future::TryFutureExt;
use log::{info, warn};
use log::info;
use sc_cli::SubstrateCli;
use service::{
self,
Expand Down Expand Up @@ -196,22 +196,7 @@ where
let chain_spec = &runner.config().chain_spec;

// By default, enable BEEFY on all networks, unless explicitly disabled through CLI.
let mut enable_beefy = !cli.run.no_beefy;
// BEEFY doesn't (yet) support warp sync:
// Until we implement https://github.com/paritytech/substrate/issues/14756
// - disallow warp sync for validators,
// - disable BEEFY when warp sync for non-validators.
if enable_beefy && runner.config().network.sync_mode.is_warp() {
if runner.config().role.is_authority() {
return Err(Error::Other(
"Warp sync not supported for validator nodes running BEEFY.".into(),
))
} else {
// disable BEEFY for non-validator nodes that are warp syncing
warn!("🥩 BEEFY not supported when warp syncing. Disabling BEEFY.");
enable_beefy = false;
}
}
let enable_beefy = !cli.run.no_beefy;

set_default_ss58_version(chain_spec);

Expand Down
3 changes: 2 additions & 1 deletion substrate/client/consensus/beefy/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ sp-core = { path = "../../../primitives/core" }
sp-keystore = { path = "../../../primitives/keystore" }
sp-mmr-primitives = { path = "../../../primitives/merkle-mountain-range" }
sp-runtime = { path = "../../../primitives/runtime" }
tokio = "1.22.0"


[dev-dependencies]
serde = "1.0.193"
tempfile = "3.1.0"
tokio = "1.22.0"
sc-block-builder = { path = "../../block-builder" }
sc-network-test = { path = "../../network/test" }
sp-consensus-grandpa = { path = "../../../primitives/consensus/grandpa" }
Expand Down
10 changes: 10 additions & 0 deletions substrate/client/consensus/beefy/src/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,16 @@ where
// Run inner block import.
let inner_import_result = self.inner.import_block(block).await?;

match self.backend.state_at(hash) {
acatangiu marked this conversation as resolved.
Show resolved Hide resolved
Ok(_) => {},
Err(_) => {
// The block is imported as part of some chain sync.
// The voter doesn't need to process it now.
// It will be detected and processed as part of the voter state init.
return Ok(inner_import_result);
},
}

match (beefy_encoded, &inner_import_result) {
(Some(encoded), ImportResult::Imported(_)) => {
match self.decode_and_verify(&encoded, number, hash) {
Expand Down
85 changes: 65 additions & 20 deletions substrate/client/consensus/beefy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ use std::{
collections::{BTreeMap, VecDeque},
marker::PhantomData,
sync::Arc,
time::Duration,
};

mod aux_schema;
Expand All @@ -78,6 +79,8 @@ mod tests;

const LOG_TARGET: &str = "beefy";

const HEADER_SYNC_DELAY: Duration = Duration::from_secs(60);

/// A convenience BEEFY client trait that defines all the type bounds a BEEFY client
/// has to satisfy. Ideally that should actually be a trait alias. Unfortunately as
/// of today, Rust does not allow a type alias to be used as a trait bound. Tracking
Expand Down Expand Up @@ -292,21 +295,29 @@ pub async fn start_beefy_gadget<B, BE, C, N, P, R, S>(
// select recoverable errors.
loop {
// Wait for BEEFY pallet to be active before starting voter.
let persisted_state = match wait_for_runtime_pallet(
let (beefy_genesis, best_grandpa) = match wait_for_runtime_pallet(
&*runtime,
&mut beefy_comms.gossip_engine,
&mut finality_notifications,
)
.await
.and_then(|(beefy_genesis, best_grandpa)| {
load_or_init_voter_state(
&*backend,
&*runtime,
beefy_genesis,
best_grandpa,
min_block_delta,
)
}) {
{
Ok(res) => res,
Err(e) => {
error!(target: LOG_TARGET, "Error: {:?}. Terminating.", e);
return
},
};

let persisted_state = match load_or_init_voter_state(
&*backend,
&*runtime,
beefy_genesis,
best_grandpa,
min_block_delta,
)
.await
{
Ok(state) => state,
Err(e) => {
error!(target: LOG_TARGET, "Error: {:?}. Terminating.", e);
Expand Down Expand Up @@ -357,7 +368,7 @@ pub async fn start_beefy_gadget<B, BE, C, N, P, R, S>(
}
}

fn load_or_init_voter_state<B, BE, R>(
async fn load_or_init_voter_state<B, BE, R>(
backend: &BE,
runtime: &R,
beefy_genesis: NumberFor<B>,
Expand All @@ -371,7 +382,7 @@ where
R::Api: BeefyApi<B, AuthorityId>,
{
// Initialize voter state from AUX DB if compatible.
crate::aux_schema::load_persistent(backend)?
let maybe_persisted_state = crate::aux_schema::load_persistent(backend)?
// Verify state pallet genesis matches runtime.
.filter(|state| state.pallet_genesis() == beefy_genesis)
.and_then(|mut state| {
Expand All @@ -381,18 +392,51 @@ where
state.set_min_block_delta(min_block_delta);
info!(target: LOG_TARGET, "🥩 Loading BEEFY voter state from db: {:?}.", state);
Some(Ok(state))
serban300 marked this conversation as resolved.
Show resolved Hide resolved
})
// No valid voter-state persisted, re-initialize from pallet genesis.
.unwrap_or_else(|| {
initialize_voter_state(backend, runtime, beefy_genesis, best_grandpa, min_block_delta)
})
});
if let Some(persisted_state) = maybe_persisted_state {
serban300 marked this conversation as resolved.
Show resolved Hide resolved
return persisted_state;
}

// No valid voter-state persisted, re-initialize from pallet genesis.
initialize_voter_state(backend, runtime, beefy_genesis, best_grandpa, min_block_delta).await
}

/// Waits until the parent header of `current` is available and returns it.
///
/// When the node uses GRANDPA warp sync it initially downloads only the mandatory GRANDPA headers.
/// The rest of the headers (gap sync) are lazily downloaded later. But the BEEFY voter also needs
/// the headers in range `[beefy_genesis..=best_grandpa]` to be available. This helper method
/// enables us to wait until these headers have been synced.
async fn wait_for_parent_header<B, BC>(
blockchain: &BC,
current: <B as Block>::Header,
delay: Duration,
) -> ClientResult<<B as Block>::Header>
where
B: Block,
BC: BlockchainBackend<B>,
{
loop {
match blockchain.header(*current.parent_hash())? {
Some(parent) => return Ok(parent),
None => {
info!(
target: LOG_TARGET,
"🥩 Parent of header number {} not found. \
BEEFY gadget waiting for header sync to finish ...",
current.number()
);
tokio::time::sleep(delay).await;
},
}
}
}

// If no persisted state present, walk back the chain from first GRANDPA notification to either:
// - latest BEEFY finalized block, or if none found on the way,
// - BEEFY pallet genesis;
// Enqueue any BEEFY mandatory blocks (session boundaries) found on the way, for voter to finalize.
fn initialize_voter_state<B, BE, R>(
async fn initialize_voter_state<B, BE, R>(
backend: &BE,
runtime: &R,
beefy_genesis: NumberFor<B>,
Expand All @@ -405,6 +449,8 @@ where
R: ProvideRuntimeApi<B>,
R::Api: BeefyApi<B, AuthorityId>,
{
let blockchain = backend.blockchain();

let beefy_genesis = runtime
.runtime_api()
.beefy_genesis(best_grandpa.hash())
Expand All @@ -414,7 +460,6 @@ where
.ok_or_else(|| ClientError::Backend("BEEFY pallet expected to be active.".into()))?;
// Walk back the imported blocks and initialize voter either, at the last block with
// a BEEFY justification, or at pallet genesis block; voter will resume from there.
let blockchain = backend.blockchain();
let mut sessions = VecDeque::new();
let mut header = best_grandpa.clone();
let state = loop {
Expand Down Expand Up @@ -481,7 +526,7 @@ where
}

// Move up the chain.
header = blockchain.expect_header(*header.parent_hash())?;
header = wait_for_parent_header(blockchain, header, HEADER_SYNC_DELAY).await?;
};

aux_schema::write_current_version(backend)?;
Expand Down
11 changes: 7 additions & 4 deletions substrate/client/consensus/beefy/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ async fn voter_init_setup(
);
let (beefy_genesis, best_grandpa) =
wait_for_runtime_pallet(api, &mut gossip_engine, finality).await.unwrap();
load_or_init_voter_state(&*backend, api, beefy_genesis, best_grandpa, 1)
load_or_init_voter_state(&*backend, api, beefy_genesis, best_grandpa, 1).await
}

// Spawns beefy voters. Returns a future to spawn on the runtime.
Expand Down Expand Up @@ -1072,8 +1072,9 @@ async fn should_initialize_voter_at_custom_genesis() {
);
let (beefy_genesis, best_grandpa) =
wait_for_runtime_pallet(&api, &mut gossip_engine, &mut finality).await.unwrap();
let persisted_state =
load_or_init_voter_state(&*backend, &api, beefy_genesis, best_grandpa, 1).unwrap();
let persisted_state = load_or_init_voter_state(&*backend, &api, beefy_genesis, best_grandpa, 1)
.await
.unwrap();

// Test initialization at session boundary.
// verify voter initialized with single session starting at block `custom_pallet_genesis` (7)
Expand Down Expand Up @@ -1107,7 +1108,9 @@ async fn should_initialize_voter_at_custom_genesis() {
let (beefy_genesis, best_grandpa) =
wait_for_runtime_pallet(&api, &mut gossip_engine, &mut finality).await.unwrap();
let new_persisted_state =
load_or_init_voter_state(&*backend, &api, beefy_genesis, best_grandpa, 1).unwrap();
load_or_init_voter_state(&*backend, &api, beefy_genesis, best_grandpa, 1)
.await
.unwrap();

// verify voter initialized with single session starting at block `new_pallet_genesis` (10)
let sessions = new_persisted_state.voting_oracle().sessions();
Expand Down
Loading