mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 12:51:02 +00:00
Warp sync part I (#9227)
* Started warp sync * BABE & GRANDPA recovery * Warp sync protocol * Sync warp proofs first * Added basic documentation * Apply suggestions from code review Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> * Style changes * Apply suggestions from code review Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> * fmt * Apply suggestions from code review Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> * Fixed chage trie pruning wrt missing blocks * Restore parent finalization * fmt * fmt * Revert pwasm-utils bump * Change error type & check API version * Apply suggestions from code review Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Build fix * Fixed target block check * Formatting Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com> Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>
This commit is contained in:
@@ -22,7 +22,7 @@ futures = "0.3.9"
|
||||
futures-timer = "3.0.1"
|
||||
log = "0.4.8"
|
||||
parking_lot = "0.11.1"
|
||||
rand = "0.7.2"
|
||||
rand = "0.8.4"
|
||||
parity-scale-codec = { version = "2.0.0", features = ["derive"] }
|
||||
sp-application-crypto = { version = "4.0.0-dev", path = "../../primitives/application-crypto" }
|
||||
sp-arithmetic = { version = "4.0.0-dev", path = "../../primitives/arithmetic" }
|
||||
|
||||
@@ -1087,7 +1087,7 @@ where
|
||||
|
||||
// random between `[0, 2 * gossip_duration]` seconds.
|
||||
let delay: u64 =
|
||||
thread_rng().gen_range(0, 2 * self.config.gossip_duration.as_millis() as u64);
|
||||
thread_rng().gen_range(0..2 * self.config.gossip_duration.as_millis() as u64);
|
||||
Box::pin(Delay::new(Duration::from_millis(delay)).map(Ok))
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
use std::{collections::HashMap, marker::PhantomData, sync::Arc};
|
||||
|
||||
use log::debug;
|
||||
use parity_scale_codec::Encode;
|
||||
use parity_scale_codec::{Decode, Encode};
|
||||
|
||||
use sc_client_api::{backend::Backend, utils::is_descendent_of};
|
||||
use sc_consensus::{
|
||||
@@ -27,10 +27,11 @@ use sc_consensus::{
|
||||
BlockCheckParams, BlockImport, BlockImportParams, ImportResult, JustificationImport,
|
||||
};
|
||||
use sc_telemetry::TelemetryHandle;
|
||||
use sp_api::TransactionFor;
|
||||
use sp_api::{Core, RuntimeApiInfo, TransactionFor};
|
||||
use sp_blockchain::{well_known_cache_keys, BlockStatus};
|
||||
use sp_consensus::{BlockOrigin, Error as ConsensusError, SelectChain};
|
||||
use sp_finality_grandpa::{ConsensusLog, ScheduledChange, SetId, GRANDPA_ENGINE_ID};
|
||||
use sp_core::hashing::twox_128;
|
||||
use sp_finality_grandpa::{ConsensusLog, GrandpaApi, ScheduledChange, SetId, GRANDPA_ENGINE_ID};
|
||||
use sp_runtime::{
|
||||
generic::{BlockId, OpaqueDigestItemId},
|
||||
traits::{Block as BlockT, DigestFor, Header as HeaderT, NumberFor, Zero},
|
||||
@@ -43,7 +44,7 @@ use crate::{
|
||||
environment::finalize_block,
|
||||
justification::GrandpaJustification,
|
||||
notification::GrandpaJustificationSender,
|
||||
ClientForGrandpa, CommandOrError, Error, NewAuthoritySet, VoterCommand,
|
||||
AuthoritySetChanges, ClientForGrandpa, CommandOrError, Error, NewAuthoritySet, VoterCommand,
|
||||
};
|
||||
|
||||
/// A block-import handler for GRANDPA.
|
||||
@@ -230,6 +231,10 @@ where
|
||||
DigestFor<Block>: Encode,
|
||||
BE: Backend<Block>,
|
||||
Client: ClientForGrandpa<Block, BE>,
|
||||
Client::Api: GrandpaApi<Block>,
|
||||
for<'a> &'a Client:
|
||||
BlockImport<Block, Error = ConsensusError, Transaction = TransactionFor<Client, Block>>,
|
||||
TransactionFor<Client, Block>: 'static,
|
||||
{
|
||||
// check for a new authority set change.
|
||||
fn check_new_change(
|
||||
@@ -418,6 +423,91 @@ where
|
||||
|
||||
Ok(PendingSetChanges { just_in_case, applied_changes, do_pause })
|
||||
}
|
||||
|
||||
/// Read current set id form a given state.
|
||||
fn current_set_id(&self, id: &BlockId<Block>) -> Result<SetId, ConsensusError> {
|
||||
let runtime_version = self.inner.runtime_api().version(id).map_err(|e| {
|
||||
ConsensusError::ClientImport(format!(
|
||||
"Unable to retrieve current runtime version. {}",
|
||||
e
|
||||
))
|
||||
})?;
|
||||
if runtime_version
|
||||
.api_version(&<dyn GrandpaApi<Block>>::ID)
|
||||
.map_or(false, |v| v < 3)
|
||||
{
|
||||
// The new API is not supported in this runtime. Try reading directly from storage.
|
||||
// This code may be removed once warp sync to an old runtime is no longer needed.
|
||||
for prefix in ["GrandpaFinality", "Grandpa"] {
|
||||
let k = [twox_128(prefix.as_bytes()), twox_128(b"CurrentSetId")].concat();
|
||||
if let Ok(Some(id)) =
|
||||
self.inner.storage(&id, &sc_client_api::StorageKey(k.to_vec()))
|
||||
{
|
||||
if let Ok(id) = SetId::decode(&mut id.0.as_ref()) {
|
||||
return Ok(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(ConsensusError::ClientImport("Unable to retrieve current set id.".into()))
|
||||
} else {
|
||||
self.inner
|
||||
.runtime_api()
|
||||
.current_set_id(&id)
|
||||
.map_err(|e| ConsensusError::ClientImport(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Import whole new state and reset authority set.
|
||||
async fn import_state(
|
||||
&mut self,
|
||||
mut block: BlockImportParams<Block, TransactionFor<Client, Block>>,
|
||||
new_cache: HashMap<well_known_cache_keys::Id, Vec<u8>>,
|
||||
) -> Result<ImportResult, ConsensusError> {
|
||||
let hash = block.post_hash();
|
||||
let number = *block.header.number();
|
||||
// Force imported state finality.
|
||||
block.finalized = true;
|
||||
let import_result = (&*self.inner).import_block(block, new_cache).await;
|
||||
match import_result {
|
||||
Ok(ImportResult::Imported(aux)) => {
|
||||
// We've just imported a new state. We trust the sync module has verified
|
||||
// finality proofs and that the state is correct and final.
|
||||
// So we can read the authority list and set id from the state.
|
||||
self.authority_set_hard_forks.clear();
|
||||
let block_id = BlockId::hash(hash);
|
||||
let authorities = self
|
||||
.inner
|
||||
.runtime_api()
|
||||
.grandpa_authorities(&block_id)
|
||||
.map_err(|e| ConsensusError::ClientImport(e.to_string()))?;
|
||||
let set_id = self.current_set_id(&block_id)?;
|
||||
let authority_set = AuthoritySet::new(
|
||||
authorities.clone(),
|
||||
set_id,
|
||||
fork_tree::ForkTree::new(),
|
||||
Vec::new(),
|
||||
AuthoritySetChanges::empty(),
|
||||
)
|
||||
.ok_or_else(|| ConsensusError::ClientImport("Invalid authority list".into()))?;
|
||||
*self.authority_set.inner_locked() = authority_set.clone();
|
||||
|
||||
crate::aux_schema::update_authority_set::<Block, _, _>(
|
||||
&authority_set,
|
||||
None,
|
||||
|insert| self.inner.insert_aux(insert, []),
|
||||
)
|
||||
.map_err(|e| ConsensusError::ClientImport(e.to_string()))?;
|
||||
let new_set =
|
||||
NewAuthoritySet { canon_number: number, canon_hash: hash, set_id, authorities };
|
||||
let _ = self
|
||||
.send_voter_commands
|
||||
.unbounded_send(VoterCommand::ChangeAuthorities(new_set));
|
||||
Ok(ImportResult::Imported(aux))
|
||||
},
|
||||
Ok(r) => Ok(r),
|
||||
Err(e) => Err(ConsensusError::ClientImport(e.to_string())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -427,6 +517,7 @@ where
|
||||
DigestFor<Block>: Encode,
|
||||
BE: Backend<Block>,
|
||||
Client: ClientForGrandpa<Block, BE>,
|
||||
Client::Api: GrandpaApi<Block>,
|
||||
for<'a> &'a Client:
|
||||
BlockImport<Block, Error = ConsensusError, Transaction = TransactionFor<Client, Block>>,
|
||||
TransactionFor<Client, Block>: 'static,
|
||||
@@ -455,6 +546,10 @@ where
|
||||
Err(e) => return Err(ConsensusError::ClientImport(e.to_string())),
|
||||
}
|
||||
|
||||
if block.with_state() {
|
||||
return self.import_state(block, new_cache).await
|
||||
}
|
||||
|
||||
// on initial sync we will restrict logging under info to avoid spam.
|
||||
let initial_sync = block.origin == BlockOrigin::NetworkInitialSync;
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ use prometheus_endpoint::{PrometheusError, Registry};
|
||||
use sc_client_api::{
|
||||
backend::{AuxStore, Backend},
|
||||
BlockchainEvents, CallExecutor, ExecutionStrategy, ExecutorProvider, Finalizer, LockImportRun,
|
||||
TransactionFor,
|
||||
StorageProvider, TransactionFor,
|
||||
};
|
||||
use sc_consensus::BlockImport;
|
||||
use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_INFO};
|
||||
@@ -119,6 +119,7 @@ mod notification;
|
||||
mod observer;
|
||||
mod until_imported;
|
||||
mod voting_rule;
|
||||
pub mod warp_proof;
|
||||
|
||||
pub use authorities::{AuthoritySet, AuthoritySetChanges, SharedAuthoritySet};
|
||||
pub use aux_schema::best_justification;
|
||||
@@ -335,6 +336,7 @@ pub trait ClientForGrandpa<Block, BE>:
|
||||
+ ProvideRuntimeApi<Block>
|
||||
+ ExecutorProvider<Block>
|
||||
+ BlockImport<Block, Transaction = TransactionFor<BE, Block>, Error = sp_consensus::Error>
|
||||
+ StorageProvider<Block, BE>
|
||||
where
|
||||
BE: Backend<Block>,
|
||||
Block: BlockT,
|
||||
@@ -353,7 +355,8 @@ where
|
||||
+ BlockchainEvents<Block>
|
||||
+ ProvideRuntimeApi<Block>
|
||||
+ ExecutorProvider<Block>
|
||||
+ BlockImport<Block, Transaction = TransactionFor<BE, Block>, Error = sp_consensus::Error>,
|
||||
+ BlockImport<Block, Transaction = TransactionFor<BE, Block>, Error = sp_consensus::Error>
|
||||
+ StorageProvider<Block, BE>,
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
@@ -177,12 +177,11 @@ where
|
||||
{
|
||||
let LinkHalf {
|
||||
client,
|
||||
select_chain: _,
|
||||
persistent_data,
|
||||
voter_commands_rx,
|
||||
justification_sender,
|
||||
justification_stream: _,
|
||||
telemetry,
|
||||
..
|
||||
} = link;
|
||||
|
||||
let network = NetworkBridge::new(
|
||||
|
||||
@@ -203,6 +203,10 @@ sp_api::mock_impl_runtime_apis! {
|
||||
self.inner.genesis_authorities.clone()
|
||||
}
|
||||
|
||||
fn current_set_id(&self) -> SetId {
|
||||
0
|
||||
}
|
||||
|
||||
fn submit_report_equivocation_unsigned_extrinsic(
|
||||
_equivocation_proof: EquivocationProof<Hash, BlockNumber>,
|
||||
_key_owner_proof: OpaqueKeyOwnershipProof,
|
||||
|
||||
@@ -0,0 +1,430 @@
|
||||
// Copyright 2021 Parity Technologies (UK) Ltd.
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Substrate is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Substrate is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Utilities for generating and verifying GRANDPA warp sync proofs.
|
||||
|
||||
use sp_runtime::codec::{self, Decode, Encode};
|
||||
|
||||
use crate::{
|
||||
best_justification, find_scheduled_change, AuthoritySetChanges, BlockNumberOps,
|
||||
GrandpaJustification, SharedAuthoritySet,
|
||||
};
|
||||
use sc_client_api::Backend as ClientBackend;
|
||||
use sc_network::warp_request_handler::{EncodedProof, VerificationResult, WarpSyncProvider};
|
||||
use sp_blockchain::{Backend as BlockchainBackend, HeaderBackend};
|
||||
use sp_finality_grandpa::{AuthorityList, SetId, GRANDPA_ENGINE_ID};
|
||||
use sp_runtime::{
|
||||
generic::BlockId,
|
||||
traits::{Block as BlockT, Header as HeaderT, NumberFor, One},
|
||||
};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Warp proof processing error.
|
||||
#[derive(Debug, derive_more::Display, derive_more::From)]
|
||||
pub enum Error {
|
||||
/// Decoding error.
|
||||
#[display(fmt = "Failed to decode block hash: {}.", _0)]
|
||||
DecodeScale(codec::Error),
|
||||
/// Client backend error.
|
||||
Client(sp_blockchain::Error),
|
||||
/// Invalid request data.
|
||||
#[from(ignore)]
|
||||
InvalidRequest(String),
|
||||
/// Invalid warp proof.
|
||||
#[from(ignore)]
|
||||
InvalidProof(String),
|
||||
/// Missing header or authority set change data.
|
||||
#[display(fmt = "Missing required data to be able to answer request.")]
|
||||
MissingData,
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
|
||||
/// The maximum size in bytes of the `WarpSyncProof`.
|
||||
pub(super) const MAX_WARP_SYNC_PROOF_SIZE: usize = 8 * 1024 * 1024;
|
||||
|
||||
/// A proof of an authority set change.
|
||||
#[derive(Decode, Encode, Debug)]
|
||||
pub struct WarpSyncFragment<Block: BlockT> {
|
||||
/// The last block that the given authority set finalized. This block should contain a digest
|
||||
/// signaling an authority set change from which we can fetch the next authority set.
|
||||
pub header: Block::Header,
|
||||
/// A justification for the header above which proves its finality. In order to validate it the
|
||||
/// verifier must be aware of the authorities and set id for which the justification refers to.
|
||||
pub justification: GrandpaJustification<Block>,
|
||||
}
|
||||
|
||||
/// An accumulated proof of multiple authority set changes.
|
||||
#[derive(Decode, Encode)]
|
||||
pub struct WarpSyncProof<Block: BlockT> {
|
||||
proofs: Vec<WarpSyncFragment<Block>>,
|
||||
is_finished: bool,
|
||||
}
|
||||
|
||||
impl<Block: BlockT> WarpSyncProof<Block> {
|
||||
/// Generates a warp sync proof starting at the given block. It will generate authority set
|
||||
/// change proofs for all changes that happened from `begin` until the current authority set
|
||||
/// (capped by MAX_WARP_SYNC_PROOF_SIZE).
|
||||
fn generate<Backend>(
|
||||
backend: &Backend,
|
||||
begin: Block::Hash,
|
||||
set_changes: &AuthoritySetChanges<NumberFor<Block>>,
|
||||
) -> Result<WarpSyncProof<Block>, Error>
|
||||
where
|
||||
Backend: ClientBackend<Block>,
|
||||
{
|
||||
// TODO: cache best response (i.e. the one with lowest begin_number)
|
||||
let blockchain = backend.blockchain();
|
||||
|
||||
let begin_number = blockchain
|
||||
.block_number_from_id(&BlockId::Hash(begin))?
|
||||
.ok_or_else(|| Error::InvalidRequest("Missing start block".to_string()))?;
|
||||
|
||||
if begin_number > blockchain.info().finalized_number {
|
||||
return Err(Error::InvalidRequest("Start block is not finalized".to_string()))
|
||||
}
|
||||
|
||||
let canon_hash = blockchain.hash(begin_number)?.expect(
|
||||
"begin number is lower than finalized number; \
|
||||
all blocks below finalized number must have been imported; \
|
||||
qed.",
|
||||
);
|
||||
|
||||
if canon_hash != begin {
|
||||
return Err(Error::InvalidRequest(
|
||||
"Start block is not in the finalized chain".to_string(),
|
||||
))
|
||||
}
|
||||
|
||||
let mut proofs = Vec::new();
|
||||
let mut proofs_encoded_len = 0;
|
||||
let mut proof_limit_reached = false;
|
||||
|
||||
let set_changes = set_changes.iter_from(begin_number).ok_or(Error::MissingData)?;
|
||||
|
||||
for (_, last_block) in set_changes {
|
||||
let header = blockchain.header(BlockId::Number(*last_block))?.expect(
|
||||
"header number comes from previously applied set changes; must exist in db; qed.",
|
||||
);
|
||||
|
||||
// the last block in a set is the one that triggers a change to the next set,
|
||||
// therefore the block must have a digest that signals the authority set change
|
||||
if find_scheduled_change::<Block>(&header).is_none() {
|
||||
// if it doesn't contain a signal for standard change then the set must have changed
|
||||
// through a forced changed, in which case we stop collecting proofs as the chain of
|
||||
// trust in authority handoffs was broken.
|
||||
break
|
||||
}
|
||||
|
||||
let justification = blockchain
|
||||
.justifications(BlockId::Number(*last_block))?
|
||||
.and_then(|just| just.into_justification(GRANDPA_ENGINE_ID))
|
||||
.expect(
|
||||
"header is last in set and contains standard change signal; \
|
||||
must have justification; \
|
||||
qed.",
|
||||
);
|
||||
|
||||
let justification = GrandpaJustification::<Block>::decode(&mut &justification[..])?;
|
||||
|
||||
let proof = WarpSyncFragment { header: header.clone(), justification };
|
||||
let proof_size = proof.encoded_size();
|
||||
|
||||
// Check for the limit. We remove some bytes from the maximum size, because we're only
|
||||
// counting the size of the `WarpSyncFragment`s. The extra margin is here to leave
|
||||
// room for rest of the data (the size of the `Vec` and the boolean).
|
||||
if proofs_encoded_len + proof_size >= MAX_WARP_SYNC_PROOF_SIZE - 50 {
|
||||
proof_limit_reached = true;
|
||||
break
|
||||
}
|
||||
|
||||
proofs_encoded_len += proof_size;
|
||||
proofs.push(proof);
|
||||
}
|
||||
|
||||
let is_finished = if proof_limit_reached {
|
||||
false
|
||||
} else {
|
||||
let latest_justification = best_justification(backend)?.filter(|justification| {
|
||||
// the existing best justification must be for a block higher than the
|
||||
// last authority set change. if we didn't prove any authority set
|
||||
// change then we fallback to make sure it's higher or equal to the
|
||||
// initial warp sync block.
|
||||
let limit = proofs
|
||||
.last()
|
||||
.map(|proof| proof.justification.target().0 + One::one())
|
||||
.unwrap_or(begin_number);
|
||||
|
||||
justification.target().0 >= limit
|
||||
});
|
||||
|
||||
if let Some(latest_justification) = latest_justification {
|
||||
let header = blockchain.header(BlockId::Hash(latest_justification.target().1))?
|
||||
.expect("header hash corresponds to a justification in db; must exist in db as well; qed.");
|
||||
|
||||
proofs.push(WarpSyncFragment { header, justification: latest_justification })
|
||||
}
|
||||
|
||||
true
|
||||
};
|
||||
|
||||
let final_outcome = WarpSyncProof { proofs, is_finished };
|
||||
debug_assert!(final_outcome.encoded_size() <= MAX_WARP_SYNC_PROOF_SIZE);
|
||||
Ok(final_outcome)
|
||||
}
|
||||
|
||||
/// Verifies the warp sync proof starting at the given set id and with the given authorities.
|
||||
/// Verification stops when either the proof is exhausted or finality for the target header can be proven.
|
||||
/// If the proof is valid the new set id and authorities is returned.
|
||||
fn verify(
|
||||
&self,
|
||||
set_id: SetId,
|
||||
authorities: AuthorityList,
|
||||
) -> Result<(SetId, AuthorityList), Error>
|
||||
where
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
{
|
||||
let mut current_set_id = set_id;
|
||||
let mut current_authorities = authorities;
|
||||
|
||||
for (fragment_num, proof) in self.proofs.iter().enumerate() {
|
||||
proof
|
||||
.justification
|
||||
.verify(current_set_id, ¤t_authorities)
|
||||
.map_err(|err| Error::InvalidProof(err.to_string()))?;
|
||||
|
||||
if proof.justification.target().1 != proof.header.hash() {
|
||||
return Err(Error::InvalidProof(
|
||||
"Mismatch between header and justification".to_owned(),
|
||||
))
|
||||
}
|
||||
|
||||
if let Some(scheduled_change) = find_scheduled_change::<Block>(&proof.header) {
|
||||
current_authorities = scheduled_change.next_authorities;
|
||||
current_set_id += 1;
|
||||
} else if fragment_num != self.proofs.len() - 1 || !self.is_finished {
|
||||
// Only the last fragment of the last proof message is allowed to be missing
|
||||
// the authority set change.
|
||||
return Err(Error::InvalidProof(
|
||||
"Header is missing authority set change digest".to_string(),
|
||||
))
|
||||
}
|
||||
}
|
||||
Ok((current_set_id, current_authorities))
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements network API for warp sync.
|
||||
pub struct NetworkProvider<Block: BlockT, Backend: ClientBackend<Block>>
|
||||
where
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
{
|
||||
backend: Arc<Backend>,
|
||||
authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
|
||||
}
|
||||
|
||||
impl<Block: BlockT, Backend: ClientBackend<Block>> NetworkProvider<Block, Backend>
|
||||
where
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
{
|
||||
/// Create a new istance for a given backend and authority set.
|
||||
pub fn new(
|
||||
backend: Arc<Backend>,
|
||||
authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
|
||||
) -> Self {
|
||||
NetworkProvider { backend, authority_set }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Block: BlockT, Backend: ClientBackend<Block>> WarpSyncProvider<Block>
|
||||
for NetworkProvider<Block, Backend>
|
||||
where
|
||||
NumberFor<Block>: BlockNumberOps,
|
||||
{
|
||||
fn generate(
|
||||
&self,
|
||||
start: Block::Hash,
|
||||
) -> Result<EncodedProof, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let proof = WarpSyncProof::<Block>::generate(
|
||||
&*self.backend,
|
||||
start,
|
||||
&self.authority_set.authority_set_changes(),
|
||||
)
|
||||
.map_err(Box::new)?;
|
||||
Ok(EncodedProof(proof.encode()))
|
||||
}
|
||||
|
||||
fn verify(
|
||||
&self,
|
||||
proof: &EncodedProof,
|
||||
set_id: SetId,
|
||||
authorities: AuthorityList,
|
||||
) -> Result<VerificationResult<Block>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let EncodedProof(proof) = proof;
|
||||
let proof = WarpSyncProof::<Block>::decode(&mut proof.as_slice())
|
||||
.map_err(|e| format!("Proof decoding error: {:?}", e))?;
|
||||
let last_header = proof
|
||||
.proofs
|
||||
.last()
|
||||
.map(|p| p.header.clone())
|
||||
.ok_or_else(|| "Empty proof".to_string())?;
|
||||
let (next_set_id, next_authorities) =
|
||||
proof.verify(set_id, authorities).map_err(Box::new)?;
|
||||
if proof.is_finished {
|
||||
Ok(VerificationResult::<Block>::Complete(next_set_id, next_authorities, last_header))
|
||||
} else {
|
||||
Ok(VerificationResult::<Block>::Partial(
|
||||
next_set_id,
|
||||
next_authorities,
|
||||
last_header.hash(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn current_authorities(&self) -> AuthorityList {
|
||||
self.authority_set.inner().current_authorities.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{codec::Encode, WarpSyncProof};
|
||||
use crate::{AuthoritySetChanges, GrandpaJustification};
|
||||
use rand::prelude::*;
|
||||
use sc_block_builder::BlockBuilderProvider;
|
||||
use sp_blockchain::HeaderBackend;
|
||||
use sp_consensus::BlockOrigin;
|
||||
use sp_finality_grandpa::GRANDPA_ENGINE_ID;
|
||||
use sp_keyring::Ed25519Keyring;
|
||||
use sp_runtime::{generic::BlockId, traits::Header as _};
|
||||
use std::sync::Arc;
|
||||
use substrate_test_runtime_client::{
|
||||
ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt, TestClientBuilder,
|
||||
TestClientBuilderExt,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn warp_sync_proof_generate_verify() {
|
||||
let mut rng = rand::rngs::StdRng::from_seed([0; 32]);
|
||||
let builder = TestClientBuilder::new();
|
||||
let backend = builder.backend();
|
||||
let mut client = Arc::new(builder.build());
|
||||
|
||||
let available_authorities = Ed25519Keyring::iter().collect::<Vec<_>>();
|
||||
let genesis_authorities = vec![(Ed25519Keyring::Alice.public().into(), 1)];
|
||||
|
||||
let mut current_authorities = vec![Ed25519Keyring::Alice];
|
||||
let mut current_set_id = 0;
|
||||
let mut authority_set_changes = Vec::new();
|
||||
|
||||
for n in 1..=100 {
|
||||
let mut block = client.new_block(Default::default()).unwrap().build().unwrap().block;
|
||||
|
||||
let mut new_authorities = None;
|
||||
|
||||
// we will trigger an authority set change every 10 blocks
|
||||
if n != 0 && n % 10 == 0 {
|
||||
// pick next authorities and add digest for the set change
|
||||
let n_authorities = rng.gen_range(1..available_authorities.len());
|
||||
let next_authorities = available_authorities
|
||||
.choose_multiple(&mut rng, n_authorities)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
new_authorities = Some(next_authorities.clone());
|
||||
|
||||
let next_authorities = next_authorities
|
||||
.iter()
|
||||
.map(|keyring| (keyring.public().into(), 1))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let digest = sp_runtime::generic::DigestItem::Consensus(
|
||||
sp_finality_grandpa::GRANDPA_ENGINE_ID,
|
||||
sp_finality_grandpa::ConsensusLog::ScheduledChange(
|
||||
sp_finality_grandpa::ScheduledChange { delay: 0u64, next_authorities },
|
||||
)
|
||||
.encode(),
|
||||
);
|
||||
|
||||
block.header.digest_mut().logs.push(digest);
|
||||
}
|
||||
|
||||
futures::executor::block_on(client.import(BlockOrigin::Own, block)).unwrap();
|
||||
|
||||
if let Some(new_authorities) = new_authorities {
|
||||
// generate a justification for this block, finalize it and note the authority set
|
||||
// change
|
||||
let (target_hash, target_number) = {
|
||||
let info = client.info();
|
||||
(info.best_hash, info.best_number)
|
||||
};
|
||||
|
||||
let mut precommits = Vec::new();
|
||||
for keyring in ¤t_authorities {
|
||||
let precommit = finality_grandpa::Precommit { target_hash, target_number };
|
||||
|
||||
let msg = finality_grandpa::Message::Precommit(precommit.clone());
|
||||
let encoded = sp_finality_grandpa::localized_payload(42, current_set_id, &msg);
|
||||
let signature = keyring.sign(&encoded[..]).into();
|
||||
|
||||
let precommit = finality_grandpa::SignedPrecommit {
|
||||
precommit,
|
||||
signature,
|
||||
id: keyring.public().into(),
|
||||
};
|
||||
|
||||
precommits.push(precommit);
|
||||
}
|
||||
|
||||
let commit = finality_grandpa::Commit { target_hash, target_number, precommits };
|
||||
|
||||
let justification = GrandpaJustification::from_commit(&client, 42, commit).unwrap();
|
||||
|
||||
client
|
||||
.finalize_block(
|
||||
BlockId::Hash(target_hash),
|
||||
Some((GRANDPA_ENGINE_ID, justification.encode())),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
authority_set_changes.push((current_set_id, n));
|
||||
|
||||
current_set_id += 1;
|
||||
current_authorities = new_authorities;
|
||||
}
|
||||
}
|
||||
|
||||
let authority_set_changes = AuthoritySetChanges::from(authority_set_changes);
|
||||
|
||||
// generate a warp sync proof
|
||||
let genesis_hash = client.hash(0).unwrap().unwrap();
|
||||
|
||||
let warp_sync_proof =
|
||||
WarpSyncProof::generate(&*backend, genesis_hash, &authority_set_changes).unwrap();
|
||||
|
||||
// verifying the proof should yield the last set id and authorities
|
||||
let (new_set_id, new_authorities) = warp_sync_proof.verify(0, genesis_authorities).unwrap();
|
||||
|
||||
let expected_authorities = current_authorities
|
||||
.iter()
|
||||
.map(|keyring| (keyring.public().into(), 1))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
assert_eq!(new_set_id, current_set_id);
|
||||
assert_eq!(new_authorities, expected_authorities);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user