mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-20 07:01:02 +00:00
Grandpa revert procedure (#11162)
* Grandpa revert procedure * Trigger ci pipeline * Test rename * Update client/finality-grandpa/src/authorities.rs Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
This commit is contained in:
@@ -95,7 +95,11 @@ pub fn run() -> sc_cli::Result<()> {
|
|||||||
runner.async_run(|config| {
|
runner.async_run(|config| {
|
||||||
let PartialComponents { client, task_manager, backend, .. } =
|
let PartialComponents { client, task_manager, backend, .. } =
|
||||||
service::new_partial(&config)?;
|
service::new_partial(&config)?;
|
||||||
Ok((cmd.run(client, backend, None), task_manager))
|
let aux_revert = Box::new(move |client, _, blocks| {
|
||||||
|
sc_finality_grandpa::revert(client, blocks)?;
|
||||||
|
Ok(())
|
||||||
|
});
|
||||||
|
Ok((cmd.run(client, backend, Some(aux_revert)), task_manager))
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
Some(Subcommand::Benchmark(cmd)) =>
|
Some(Subcommand::Benchmark(cmd)) =>
|
||||||
|
|||||||
@@ -16,7 +16,11 @@
|
|||||||
// You should have received a copy of the GNU General Public License
|
// You should have received a copy of the GNU General Public License
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
use crate::{chain_spec, service, service::new_partial, Cli, Subcommand};
|
use crate::{
|
||||||
|
chain_spec, service,
|
||||||
|
service::{new_partial, FullClient},
|
||||||
|
Cli, Subcommand,
|
||||||
|
};
|
||||||
use node_executor::ExecutorDispatch;
|
use node_executor::ExecutorDispatch;
|
||||||
use node_primitives::Block;
|
use node_primitives::Block;
|
||||||
use node_runtime::RuntimeApi;
|
use node_runtime::RuntimeApi;
|
||||||
@@ -175,13 +179,12 @@ pub fn run() -> Result<()> {
|
|||||||
let runner = cli.create_runner(cmd)?;
|
let runner = cli.create_runner(cmd)?;
|
||||||
runner.async_run(|config| {
|
runner.async_run(|config| {
|
||||||
let PartialComponents { client, task_manager, backend, .. } = new_partial(&config)?;
|
let PartialComponents { client, task_manager, backend, .. } = new_partial(&config)?;
|
||||||
let revert_aux = Box::new(|client, backend, blocks| {
|
let aux_revert = Box::new(move |client: Arc<FullClient>, backend, blocks| {
|
||||||
sc_consensus_babe::revert(client, backend, blocks)?;
|
sc_consensus_babe::revert(client.clone(), backend, blocks)?;
|
||||||
// TODO: grandpa revert
|
grandpa::revert(client, blocks)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
Ok((cmd.run(client, backend, Some(aux_revert)), task_manager))
|
||||||
Ok((cmd.run(client, backend, Some(revert_aux)), task_manager))
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
#[cfg(feature = "try-runtime")]
|
#[cfg(feature = "try-runtime")]
|
||||||
|
|||||||
@@ -1832,7 +1832,9 @@ where
|
|||||||
Ok(BasicQueue::new(verifier, Box::new(block_import), justification_import, spawner, registry))
|
Ok(BasicQueue::new(verifier, Box::new(block_import), justification_import, spawner, registry))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reverts aux data.
|
/// Reverts protocol aux data to at most the last finalized block.
|
||||||
|
/// In particular, epoch-changes and block weights announced after the revert
|
||||||
|
/// point are removed.
|
||||||
pub fn revert<Block, Client, Backend>(
|
pub fn revert<Block, Client, Backend>(
|
||||||
client: Arc<Client>,
|
client: Arc<Client>,
|
||||||
backend: Arc<Backend>,
|
backend: Arc<Backend>,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
use std::{cmp::Ord, fmt::Debug, ops::Add};
|
use std::{cmp::Ord, fmt::Debug, ops::Add};
|
||||||
|
|
||||||
use finality_grandpa::voter_set::VoterSet;
|
use finality_grandpa::voter_set::VoterSet;
|
||||||
use fork_tree::ForkTree;
|
use fork_tree::{FilterAction, ForkTree};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use parity_scale_codec::{Decode, Encode};
|
use parity_scale_codec::{Decode, Encode};
|
||||||
use parking_lot::MappedMutexGuard;
|
use parking_lot::MappedMutexGuard;
|
||||||
@@ -220,6 +220,37 @@ where
|
|||||||
pub(crate) fn current(&self) -> (u64, &[(AuthorityId, u64)]) {
|
pub(crate) fn current(&self) -> (u64, &[(AuthorityId, u64)]) {
|
||||||
(self.set_id, &self.current_authorities[..])
|
(self.set_id, &self.current_authorities[..])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Revert to a specified block given its `hash` and `number`.
|
||||||
|
/// This removes all the authority set changes that were announced after
|
||||||
|
/// the revert point.
|
||||||
|
/// Revert point is identified by `number` and `hash`.
|
||||||
|
pub(crate) fn revert<F, E>(&mut self, hash: H, number: N, is_descendent_of: &F)
|
||||||
|
where
|
||||||
|
F: Fn(&H, &H) -> Result<bool, E>,
|
||||||
|
{
|
||||||
|
let mut filter = |node_hash: &H, node_num: &N, _: &PendingChange<H, N>| {
|
||||||
|
if number >= *node_num &&
|
||||||
|
(is_descendent_of(node_hash, &hash).unwrap_or_default() || *node_hash == hash)
|
||||||
|
{
|
||||||
|
// Continue the search in this subtree.
|
||||||
|
FilterAction::KeepNode
|
||||||
|
} else if number < *node_num && is_descendent_of(&hash, node_hash).unwrap_or_default() {
|
||||||
|
// Found a node to be removed.
|
||||||
|
FilterAction::Remove
|
||||||
|
} else {
|
||||||
|
// Not a parent or child of the one we're looking for, stop processing this branch.
|
||||||
|
FilterAction::KeepTree
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Remove standard changes.
|
||||||
|
let _ = self.pending_standard_changes.drain_filter(&mut filter);
|
||||||
|
|
||||||
|
// Remove forced changes.
|
||||||
|
self.pending_forced_changes
|
||||||
|
.retain(|change| !is_descendent_of(&hash, &change.canon_hash).unwrap_or_default());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<H: Eq, N> AuthoritySet<H, N>
|
impl<H: Eq, N> AuthoritySet<H, N>
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ use parking_lot::RwLock;
|
|||||||
use prometheus_endpoint::{PrometheusError, Registry};
|
use prometheus_endpoint::{PrometheusError, Registry};
|
||||||
use sc_client_api::{
|
use sc_client_api::{
|
||||||
backend::{AuxStore, Backend},
|
backend::{AuxStore, Backend},
|
||||||
|
utils::is_descendent_of,
|
||||||
BlockchainEvents, CallExecutor, ExecutionStrategy, ExecutorProvider, Finalizer, LockImportRun,
|
BlockchainEvents, CallExecutor, ExecutionStrategy, ExecutorProvider, Finalizer, LockImportRun,
|
||||||
StorageProvider, TransactionFor,
|
StorageProvider, TransactionFor,
|
||||||
};
|
};
|
||||||
@@ -71,7 +72,7 @@ use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_INFO};
|
|||||||
use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver};
|
use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver};
|
||||||
use sp_api::ProvideRuntimeApi;
|
use sp_api::ProvideRuntimeApi;
|
||||||
use sp_application_crypto::AppKey;
|
use sp_application_crypto::AppKey;
|
||||||
use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata};
|
use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata, Result as ClientResult};
|
||||||
use sp_consensus::SelectChain;
|
use sp_consensus::SelectChain;
|
||||||
use sp_core::crypto::ByteArray;
|
use sp_core::crypto::ByteArray;
|
||||||
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
|
use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr};
|
||||||
@@ -1162,3 +1163,49 @@ fn local_authority_id(
|
|||||||
.map(|(p, _)| p.clone())
|
.map(|(p, _)| p.clone())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Reverts protocol aux data to at most the last finalized block.
|
||||||
|
/// In particular, standard and forced authority set changes announced after the
|
||||||
|
/// revert point are removed.
|
||||||
|
pub fn revert<Block, Client>(client: Arc<Client>, blocks: NumberFor<Block>) -> ClientResult<()>
|
||||||
|
where
|
||||||
|
Block: BlockT,
|
||||||
|
Client: AuxStore
|
||||||
|
+ HeaderMetadata<Block, Error = sp_blockchain::Error>
|
||||||
|
+ HeaderBackend<Block>
|
||||||
|
+ ProvideRuntimeApi<Block>,
|
||||||
|
{
|
||||||
|
let best_number = client.info().best_number;
|
||||||
|
let finalized = client.info().finalized_number;
|
||||||
|
let revertible = blocks.min(best_number - finalized);
|
||||||
|
|
||||||
|
let number = best_number - revertible;
|
||||||
|
let hash = client
|
||||||
|
.block_hash_from_id(&BlockId::Number(number))?
|
||||||
|
.ok_or(ClientError::Backend(format!(
|
||||||
|
"Unexpected hash lookup failure for block number: {}",
|
||||||
|
number
|
||||||
|
)))?;
|
||||||
|
|
||||||
|
let info = client.info();
|
||||||
|
let persistent_data: PersistentData<Block> =
|
||||||
|
aux_schema::load_persistent(&*client, info.genesis_hash, Zero::zero(), || unreachable!())?;
|
||||||
|
|
||||||
|
let shared_authority_set = persistent_data.authority_set;
|
||||||
|
let mut authority_set = shared_authority_set.inner();
|
||||||
|
|
||||||
|
let is_descendent_of = is_descendent_of(&*client, None);
|
||||||
|
authority_set.revert(hash, number, &is_descendent_of);
|
||||||
|
|
||||||
|
// The following has the side effect to properly reset the current voter state.
|
||||||
|
let (set_id, set_ref) = authority_set.current();
|
||||||
|
let new_set = Some(NewAuthoritySet {
|
||||||
|
canon_hash: info.finalized_hash,
|
||||||
|
canon_number: info.finalized_number,
|
||||||
|
set_id,
|
||||||
|
authorities: set_ref.to_vec(),
|
||||||
|
});
|
||||||
|
aux_schema::update_authority_set::<Block, _, _>(&authority_set, new_set.as_ref(), |values| {
|
||||||
|
client.insert_aux(values, None)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ use tokio::runtime::{Handle, Runtime};
|
|||||||
|
|
||||||
use authorities::AuthoritySet;
|
use authorities::AuthoritySet;
|
||||||
use communication::grandpa_protocol_name;
|
use communication::grandpa_protocol_name;
|
||||||
use sc_block_builder::BlockBuilderProvider;
|
use sc_block_builder::{BlockBuilder, BlockBuilderProvider};
|
||||||
use sc_consensus::LongestChain;
|
use sc_consensus::LongestChain;
|
||||||
use sc_keystore::LocalKeystore;
|
use sc_keystore::LocalKeystore;
|
||||||
use sp_application_crypto::key_types::GRANDPA;
|
use sp_application_crypto::key_types::GRANDPA;
|
||||||
@@ -1685,3 +1685,128 @@ fn grandpa_environment_doesnt_send_equivocation_reports_for_itself() {
|
|||||||
let equivocation_proof = sp_finality_grandpa::Equivocation::Prevote(equivocation);
|
let equivocation_proof = sp_finality_grandpa::Equivocation::Prevote(equivocation);
|
||||||
assert!(environment.report_equivocation(equivocation_proof).is_ok());
|
assert!(environment.report_equivocation(equivocation_proof).is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn revert_prunes_authority_changes() {
|
||||||
|
sp_tracing::try_init_simple();
|
||||||
|
let runtime = Runtime::new().unwrap();
|
||||||
|
|
||||||
|
let peers = &[Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie];
|
||||||
|
|
||||||
|
type TestBlockBuilder<'a> =
|
||||||
|
BlockBuilder<'a, Block, PeersFullClient, substrate_test_runtime_client::Backend>;
|
||||||
|
let edit_block = |builder: TestBlockBuilder| {
|
||||||
|
let mut block = builder.build().unwrap().block;
|
||||||
|
add_scheduled_change(
|
||||||
|
&mut block,
|
||||||
|
ScheduledChange { next_authorities: make_ids(peers), delay: 0 },
|
||||||
|
);
|
||||||
|
block
|
||||||
|
};
|
||||||
|
|
||||||
|
let api = TestApi::new(make_ids(peers));
|
||||||
|
let mut net = GrandpaTestNet::new(api, 3, 0);
|
||||||
|
runtime.spawn(initialize_grandpa(&mut net, peers));
|
||||||
|
|
||||||
|
let peer = net.peer(0);
|
||||||
|
let client = peer.client().as_client();
|
||||||
|
|
||||||
|
// Test scenario: (X) = auth-change, 24 = revert-point
|
||||||
|
//
|
||||||
|
// +---------(27)
|
||||||
|
// /
|
||||||
|
// 0---(21)---23---24---25---(28)---30
|
||||||
|
// ^ \
|
||||||
|
// revert-point +------(29)
|
||||||
|
|
||||||
|
// Construct canonical chain
|
||||||
|
|
||||||
|
// add 20 blocks
|
||||||
|
peer.push_blocks(20, false);
|
||||||
|
// at block 21 we add an authority transition
|
||||||
|
peer.generate_blocks(1, BlockOrigin::File, edit_block);
|
||||||
|
// add more blocks on top of it (until we have 24)
|
||||||
|
peer.push_blocks(3, false);
|
||||||
|
// add more blocks on top of it (until we have 27)
|
||||||
|
peer.push_blocks(3, false);
|
||||||
|
// at block 28 we add an authority transition
|
||||||
|
peer.generate_blocks(1, BlockOrigin::File, edit_block);
|
||||||
|
// add more blocks on top of it (until we have 30)
|
||||||
|
peer.push_blocks(2, false);
|
||||||
|
|
||||||
|
// Fork before revert point
|
||||||
|
|
||||||
|
// add more blocks on top of block 23 (until we have 26)
|
||||||
|
let hash = peer.generate_blocks_at(
|
||||||
|
BlockId::Number(23),
|
||||||
|
3,
|
||||||
|
BlockOrigin::File,
|
||||||
|
|builder| {
|
||||||
|
let mut block = builder.build().unwrap().block;
|
||||||
|
block.header.digest_mut().push(DigestItem::Other(vec![1]));
|
||||||
|
block
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
ForkChoiceStrategy::LongestChain,
|
||||||
|
);
|
||||||
|
// at block 27 of the fork add an authority transition
|
||||||
|
peer.generate_blocks_at(
|
||||||
|
BlockId::Hash(hash),
|
||||||
|
1,
|
||||||
|
BlockOrigin::File,
|
||||||
|
edit_block,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
ForkChoiceStrategy::LongestChain,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Fork after revert point
|
||||||
|
|
||||||
|
// add more block on top of block 25 (until we have 28)
|
||||||
|
let hash = peer.generate_blocks_at(
|
||||||
|
BlockId::Number(25),
|
||||||
|
3,
|
||||||
|
BlockOrigin::File,
|
||||||
|
|builder| {
|
||||||
|
let mut block = builder.build().unwrap().block;
|
||||||
|
block.header.digest_mut().push(DigestItem::Other(vec![2]));
|
||||||
|
block
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
ForkChoiceStrategy::LongestChain,
|
||||||
|
);
|
||||||
|
// at block 29 of the fork add an authority transition
|
||||||
|
peer.generate_blocks_at(
|
||||||
|
BlockId::Hash(hash),
|
||||||
|
1,
|
||||||
|
BlockOrigin::File,
|
||||||
|
edit_block,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
ForkChoiceStrategy::LongestChain,
|
||||||
|
);
|
||||||
|
|
||||||
|
revert(client.clone(), 6).unwrap();
|
||||||
|
|
||||||
|
let persistent_data: PersistentData<Block> = aux_schema::load_persistent(
|
||||||
|
&*client,
|
||||||
|
client.info().genesis_hash,
|
||||||
|
Zero::zero(),
|
||||||
|
|| unreachable!(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
let changes_num: Vec<_> = persistent_data
|
||||||
|
.authority_set
|
||||||
|
.inner()
|
||||||
|
.pending_standard_changes
|
||||||
|
.iter()
|
||||||
|
.map(|(_, n, _)| *n)
|
||||||
|
.collect();
|
||||||
|
assert_eq!(changes_num, [21, 27]);
|
||||||
|
}
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ where
|
|||||||
|
|
||||||
/// Add blocks to the peer -- edit the block before adding. The chain will
|
/// Add blocks to the peer -- edit the block before adding. The chain will
|
||||||
/// start at the given block iD.
|
/// start at the given block iD.
|
||||||
fn generate_blocks_at<F>(
|
pub fn generate_blocks_at<F>(
|
||||||
&mut self,
|
&mut self,
|
||||||
at: BlockId<Block>,
|
at: BlockId<Block>,
|
||||||
count: usize,
|
count: usize,
|
||||||
|
|||||||
Reference in New Issue
Block a user