Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Revert approval voting #5438

Merged
merged 11 commits into from
May 11, 2022
13 changes: 13 additions & 0 deletions node/core/approval-voting/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,19 @@ impl ApprovalVotingSubsystem {
metrics,
}
}

/// Revert to the block corresponding to the specified `hash`.
/// The operation is not allowed for blocks older than the last finalized one.
pub fn revert_to(&self, hash: Hash) -> Result<(), SubsystemError> {
let config = approval_db::v1::Config { col_data: self.db_config.col_data };
let mut backend = approval_db::v1::DbBackend::new(self.db.clone(), config);
let mut overlay = OverlayedBackend::new(&backend);

ops::revert_to(&mut overlay, hash)?;

let ops = overlay.into_write_ops();
backend.write(ops)
}
}

impl<Context> overseer::Subsystem<Context, SubsystemError> for ApprovalVotingSubsystem
Expand Down
75 changes: 74 additions & 1 deletion node/core/approval-voting/src/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
//! Middleware interface that leverages low-level database operations
//! to provide a clean API for processing block and candidate imports.

use polkadot_node_subsystem::SubsystemResult;
use polkadot_node_subsystem::{SubsystemError, SubsystemResult};

use bitvec::order::Lsb0 as BitOrderLsb0;
use polkadot_primitives::v2::{BlockNumber, CandidateHash, CandidateReceipt, GroupIndex, Hash};
Expand Down Expand Up @@ -311,3 +311,76 @@ pub fn force_approve(

Ok(approved_hashes)
}

/// Revert to the block corresponding to the specified `hash`.
/// The operation is not allowed for blocks older than the last finalized one.
pub fn revert_to(
overlay: &mut OverlayedBackend<'_, impl Backend>,
hash: Hash,
) -> SubsystemResult<()> {
let (children, height) = match overlay.load_block_entry(&hash)? {
Some(mut entry) => {
let children_height = entry.block_number() + 1;
let children = std::mem::take(&mut entry.children);
// Write revert point block entry without the children.
overlay.write_block_entry(entry);
(children, children_height)
},
None => {
let children_height =
overlay.load_stored_blocks()?.map(|range| range.0).ok_or_else(|| {
SubsystemError::Context(
"no available blocks to infer revert point height".to_string(),
)
})?;

let children = overlay.load_blocks_at_height(&children_height)?;

let child_entry = children
.first()
.and_then(|hash| overlay.load_block_entry(hash).ok())
.flatten()
.ok_or_else(|| {
SubsystemError::Context("lookup failure for first block".to_string())
})?;

// The parent is expected to be the revert point
if child_entry.parent_hash() != hash {
return Err(SubsystemError::Context(
"revert below last finalized block or corrupted storage".to_string(),
))
}

(children, children_height)
},
};

let mut stack: Vec<_> = children.into_iter().map(|h| (h, height)).collect();

while let Some((hash, number)) = stack.pop() {
let mut blocks_at_height = overlay.load_blocks_at_height(&number)?;
blocks_at_height.retain(|h| h != &hash);
overlay.write_blocks_at_height(number, blocks_at_height);

if let Some(entry) = overlay.load_block_entry(&hash)? {
overlay.delete_block_entry(&hash);

// Cleanup the candidate entries by removing any reference to the
// removed block. If for a candidate entry the block block_assignments
// drops to zero then we remove the entry.
for (_, candidate_hash) in entry.candidates() {
if let Some(mut candidate_entry) = overlay.load_candidate_entry(candidate_hash)? {
candidate_entry.block_assignments.remove(&hash);
if candidate_entry.block_assignments.is_empty() {
overlay.delete_candidate_entry(candidate_hash);
} else {
overlay.write_candidate_entry(candidate_entry);
}
}
}

stack.extend(entry.children.into_iter().map(|h| (h, number + 1)));
}
davxy marked this conversation as resolved.
Show resolved Hide resolved
}
Ok(())
}
8 changes: 4 additions & 4 deletions node/core/chain-selection/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,10 @@ impl ChainSelectionSubsystem {
}

/// Revert to the block corresponding to the specified `hash`.
/// The revert is not allowed for blocks older than the last finalized one.
pub fn revert(&self, hash: Hash) -> Result<(), Error> {
let backend_config = db_backend::v1::Config { col_data: self.config.col_data };
let mut backend = db_backend::v1::DbBackend::new(self.db.clone(), backend_config);
/// The operation is not allowed for blocks older than the last finalized one.
pub fn revert_to(&self, hash: Hash) -> Result<(), Error> {
let config = db_backend::v1::Config { col_data: self.config.col_data };
let mut backend = db_backend::v1::DbBackend::new(self.db.clone(), config);

let ops = tree::revert_to(&backend, hash)?.into_write_ops();

Expand Down
103 changes: 73 additions & 30 deletions node/service/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ use {
beefy_gadget::notification::{BeefyBestBlockSender, BeefySignedCommitmentSender},
grandpa::{self, FinalityProofProvider as GrandpaFinalityProofProvider},
gum::info,
polkadot_node_core_approval_voting::Config as ApprovalVotingConfig,
polkadot_node_core_approval_voting::{
self as approval_voting_subsystem, Config as ApprovalVotingConfig,
},
polkadot_node_core_av_store::Config as AvailabilityConfig,
polkadot_node_core_av_store::Error as AvailabilityError,
polkadot_node_core_candidate_validation::Config as CandidateValidationConfig,
Expand Down Expand Up @@ -1403,32 +1405,11 @@ pub fn build_full(
Err(Error::NoRuntime)
}

struct RevertConsensus {
blocks: BlockNumber,
backend: Arc<FullBackend>,
}

impl ExecuteWithClient for RevertConsensus {
type Output = sp_blockchain::Result<()>;

fn execute_with_client<Client, Api, Backend>(self, client: Arc<Client>) -> Self::Output
where
<Api as sp_api::ApiExt<Block>>::StateBackend: sp_api::StateBackend<BlakeTwo256>,
Backend: sc_client_api::Backend<Block> + 'static,
Backend::State: sp_api::StateBackend<BlakeTwo256>,
Api: polkadot_client::RuntimeApiCollection<StateBackend = Backend::State>,
Client: AbstractClient<Block, Backend, Api = Api> + 'static,
{
babe::revert(client.clone(), self.backend, self.blocks)?;
grandpa::revert(client, self.blocks)?;
Ok(())
}
}

/// Reverts the node state down to at most the last finalized block.
///
/// In particular this reverts:
/// - `ChainSelectionSubsystem` data in the parachains-db.
/// - `ApprovalVotingSubsystem` data in the parachains-db;
/// - `ChainSelectionSubsystem` data in the parachains-db;
/// - Low level Babe and Grandpa consensus data.
#[cfg(feature = "full-node")]
pub fn revert_backend(
Expand All @@ -1441,6 +1422,10 @@ pub fn revert_backend(
let finalized = client.info().finalized_number;
let revertible = blocks.min(best_number - finalized);

if revertible == 0 {
return Ok(())
}

let number = best_number - revertible;
let hash = client.block_hash_from_id(&BlockId::Number(number))?.ok_or(
sp_blockchain::Error::Backend(format!(
Expand All @@ -1452,19 +1437,77 @@ pub fn revert_backend(
let parachains_db = open_database(&config.database)
.map_err(|err| sp_blockchain::Error::Backend(err.to_string()))?;

revert_approval_voting(parachains_db.clone(), hash)?;
revert_chain_selection(parachains_db, hash)?;
// Revert Substrate consensus related components
client.execute_with(RevertConsensus { blocks, backend })?;

Ok(())
}

fn revert_chain_selection(db: Arc<dyn Database>, hash: Hash) -> sp_blockchain::Result<()> {
let config = chain_selection_subsystem::Config {
col_data: parachains_db::REAL_COLUMNS.col_chain_selection_data,
stagnant_check_interval: chain_selection_subsystem::StagnantCheckInterval::never(),
};

let chain_selection =
chain_selection_subsystem::ChainSelectionSubsystem::new(config, parachains_db);
let chain_selection = chain_selection_subsystem::ChainSelectionSubsystem::new(config, db);

chain_selection
.revert(hash)
.map_err(|err| sp_blockchain::Error::Backend(err.to_string()))?;
.revert_to(hash)
.map_err(|err| sp_blockchain::Error::Backend(err.to_string()))
}

client.execute_with(RevertConsensus { blocks, backend })?;
fn revert_approval_voting(db: Arc<dyn Database>, hash: Hash) -> sp_blockchain::Result<()> {
let config = approval_voting_subsystem::Config {
col_data: parachains_db::REAL_COLUMNS.col_approval_data,
slot_duration_millis: Default::default(),
};

Ok(())
struct DummyOracle;
impl consensus_common::SyncOracle for DummyOracle {
davxy marked this conversation as resolved.
Show resolved Hide resolved
fn is_major_syncing(&mut self) -> bool {
false
}
fn is_offline(&mut self) -> bool {
false
}
}

// Construct an approval voting subsystem with a dummy sync oracle and keystore.
// This dummy components are not be used by the revert and are only necessary
// for construction.
let approval_voting = approval_voting_subsystem::ApprovalVotingSubsystem::with_config(
config,
db,
Arc::new(sc_keystore::LocalKeystore::in_memory()),
Box::new(DummyOracle),
approval_voting_subsystem::Metrics::default(),
);

approval_voting
.revert_to(hash)
.map_err(|err| sp_blockchain::Error::Backend(err.to_string()))
}

struct RevertConsensus {
blocks: BlockNumber,
backend: Arc<FullBackend>,
}

impl ExecuteWithClient for RevertConsensus {
type Output = sp_blockchain::Result<()>;

fn execute_with_client<Client, Api, Backend>(self, client: Arc<Client>) -> Self::Output
where
<Api as sp_api::ApiExt<Block>>::StateBackend: sp_api::StateBackend<BlakeTwo256>,
Backend: sc_client_api::Backend<Block> + 'static,
Backend::State: sp_api::StateBackend<BlakeTwo256>,
Api: polkadot_client::RuntimeApiCollection<StateBackend = Backend::State>,
Client: AbstractClient<Block, Backend, Api = Api> + 'static,
{
babe::revert(client.clone(), self.backend, self.blocks)?;
grandpa::revert(client, self.blocks)?;
davxy marked this conversation as resolved.
Show resolved Hide resolved
Ok(())
}
}