use log::trace;
use std::sync::Arc;
use finality_grandpa::BlockNumberOps;
use parity_scale_codec::{Encode, Decode};
use sp_blockchain::{Backend as BlockchainBackend, Error as ClientError, Result as ClientResult};
use sp_runtime::{
Justification, generic::BlockId,
traits::{NumberFor, Block as BlockT, Header as HeaderT, Zero, One},
};
use sc_client_api::backend::Backend;
use sp_finality_grandpa::{AuthorityId, AuthorityList};
use crate::authorities::AuthoritySetChanges;
use crate::justification::GrandpaJustification;
use crate::SharedAuthoritySet;
use crate::VoterSet;
const MAX_UNKNOWN_HEADERS: usize = 100_000;
pub struct FinalityProofProvider<BE, Block: BlockT> {
backend: Arc<BE>,
shared_authority_set: Option<SharedAuthoritySet<Block::Hash, NumberFor<Block>>>,
}
impl<B, Block: BlockT> FinalityProofProvider<B, Block>
where
B: Backend<Block> + Send + Sync + 'static,
{
pub fn new(
backend: Arc<B>,
shared_authority_set: Option<SharedAuthoritySet<Block::Hash, NumberFor<Block>>>,
) -> Self {
FinalityProofProvider {
backend,
shared_authority_set,
}
}
pub fn new_for_service(
backend: Arc<B>,
shared_authority_set: Option<SharedAuthoritySet<Block::Hash, NumberFor<Block>>>,
) -> Arc<Self> {
Arc::new(Self::new(backend, shared_authority_set))
}
}
impl<B, Block> FinalityProofProvider<B, Block>
where
Block: BlockT,
NumberFor<Block>: BlockNumberOps,
B: Backend<Block> + Send + Sync + 'static,
{
pub fn prove_finality(
&self,
block: NumberFor<Block>
) -> Result<Option<Vec<u8>>, FinalityProofError> {
let authority_set_changes = if let Some(changes) = self
.shared_authority_set
.as_ref()
.map(SharedAuthoritySet::authority_set_changes)
{
changes
} else {
return Ok(None);
};
prove_finality::<_, _, GrandpaJustification<Block>>(
&*self.backend.blockchain(),
authority_set_changes,
block,
)
}
}
#[derive(Debug, PartialEq, Encode, Decode, Clone)]
pub struct FinalityProof<Header: HeaderT> {
pub block: Header::Hash,
pub justification: Vec<u8>,
pub unknown_headers: Vec<Header>,
}
#[derive(Debug, derive_more::Display, derive_more::From)]
pub enum FinalityProofError {
#[display(fmt = "Block not yet finalized")]
BlockNotYetFinalized,
#[display(fmt = "Block not covered by authority set changes")]
BlockNotInAuthoritySetChanges,
Client(sp_blockchain::Error),
}
#[derive(Debug, PartialEq, Clone, Encode, Decode)]
pub(crate) struct AuthoritySetProofFragment<Header: HeaderT> {
pub header: Header,
pub justification: Vec<u8>,
}
type AuthoritySetProof<Header> = Vec<AuthoritySetProofFragment<Header>>;
fn prove_finality<Block, B, J>(
blockchain: &B,
authority_set_changes: AuthoritySetChanges<NumberFor<Block>>,
block: NumberFor<Block>,
) -> Result<Option<Vec<u8>>, FinalityProofError>
where
Block: BlockT,
B: BlockchainBackend<Block>,
J: ProvableJustification<Block::Header>,
{
let info = blockchain.info();
if info.finalized_number <= block {
let err = format!(
"Requested finality proof for descendant of #{} while we only have finalized #{}.",
block,
info.finalized_number,
);
trace!(target: "afg", "{}", &err);
return Err(FinalityProofError::BlockNotYetFinalized);
}
let (_, last_block_for_set) = if let Some(id) = authority_set_changes.get_set_id(block) {
id
} else {
trace!(
target: "afg",
"AuthoritySetChanges does not cover the requested block #{}. \
Maybe the subscription API is more appropriate.",
block,
);
return Err(FinalityProofError::BlockNotInAuthoritySetChanges);
};
let last_block_for_set_id = BlockId::Number(last_block_for_set);
let justification =
if let Some(justification) = blockchain.justification(last_block_for_set_id)? {
justification
} else {
trace!(
target: "afg",
"No justification found when making finality proof for {}. Returning empty proof.",
block,
);
return Ok(None);
};
let unknown_headers = {
let mut headers = Vec::new();
let mut current = block + One::one();
loop {
if current >= last_block_for_set || headers.len() >= MAX_UNKNOWN_HEADERS {
break;
}
headers.push(blockchain.expect_header(BlockId::Number(current))?);
current += One::one();
}
headers
};
Ok(Some(
FinalityProof {
block: blockchain.expect_block_hash_from_id(&last_block_for_set_id)?,
justification,
unknown_headers,
}
.encode(),
))
}
pub fn prove_warp_sync<Block: BlockT, B: BlockchainBackend<Block>>(
blockchain: &B,
begin: Block::Hash,
max_fragment_limit: Option<usize>,
mut cache: Option<&mut WarpSyncFragmentCache<Block::Header>>,
) -> ::sp_blockchain::Result<Vec<u8>> {
let begin = BlockId::Hash(begin);
let begin_number = blockchain.block_number_from_id(&begin)?
.ok_or_else(|| ClientError::Backend("Missing start block".to_string()))?;
let end = BlockId::Hash(blockchain.last_finalized()?);
let end_number = blockchain.block_number_from_id(&end)?
.ok_or_else(|| ClientError::Backend("Missing last finalized block".to_string()))?;
if begin_number > end_number {
return Err(ClientError::Backend("Unfinalized start for authority proof".to_string()));
}
let mut result = Vec::new();
let mut last_apply = None;
let header = blockchain.expect_header(begin)?;
let mut index = *header.number();
while index > Zero::zero() {
index = index - One::one();
if let Some((fragment, apply_block)) = get_warp_sync_proof_fragment(blockchain, index, &mut cache)? {
if last_apply.map(|next| &next > header.number()).unwrap_or(false) {
result.push(fragment);
last_apply = Some(apply_block);
} else {
break;
}
}
}
let mut index = *header.number();
while index <= end_number {
if max_fragment_limit.map(|limit| result.len() >= limit).unwrap_or(false) {
break;
}
if let Some((fragement, apply_block)) = get_warp_sync_proof_fragment(blockchain, index, &mut cache)? {
if last_apply.map(|next| apply_block < next).unwrap_or(false) {
result.pop();
}
result.push(fragement);
last_apply = Some(apply_block);
}
index = index + One::one();
}
let at_limit = max_fragment_limit.map(|limit| result.len() >= limit).unwrap_or(false);
if !at_limit && result.last().as_ref().map(|head| head.header.number()) != Some(&end_number) {
let header = blockchain.expect_header(end)?;
if let Some(justification) = blockchain.justification(BlockId::Number(end_number.clone()))? {
result.push(AuthoritySetProofFragment {
header: header.clone(),
justification,
});
} else {
}
}
Ok(result.encode())
}
fn get_warp_sync_proof_fragment<Block: BlockT, B: BlockchainBackend<Block>>(
blockchain: &B,
index: NumberFor<Block>,
cache: &mut Option<&mut WarpSyncFragmentCache<Block::Header>>,
) -> sp_blockchain::Result<Option<(AuthoritySetProofFragment<Block::Header>, NumberFor<Block>)>> {
if let Some(cache) = cache.as_mut() {
if let Some(result) = cache.get_item(index) {
return Ok(result);
}
}
let mut result = None;
let header = blockchain.expect_header(BlockId::number(index))?;
if let Some((block_number, sp_finality_grandpa::ScheduledChange {
next_authorities: _,
delay,
})) = crate::import::find_forced_change::<Block>(&header) {
let dest = block_number + delay;
if let Some(justification) = blockchain.justification(BlockId::Number(index.clone()))? {
result = Some((AuthoritySetProofFragment {
header: header.clone(),
justification,
}, dest));
} else {
return Err(ClientError::Backend("Unjustified block with authority set change".to_string()));
}
}
if let Some(sp_finality_grandpa::ScheduledChange {
next_authorities: _,
delay,
}) = crate::import::find_scheduled_change::<Block>(&header) {
let dest = index + delay;
if let Some(justification) = blockchain.justification(BlockId::Number(index.clone()))? {
result = Some((AuthoritySetProofFragment {
header: header.clone(),
justification,
}, dest));
} else {
return Err(ClientError::Backend("Unjustified block with authority set change".to_string()));
}
}
cache.as_mut().map(|cache| cache.new_item(index, result.clone()));
Ok(result)
}
#[allow(unused)]
pub(crate) fn check_warp_sync_proof<Block: BlockT, J>(
current_set_id: u64,
current_authorities: AuthorityList,
remote_proof: Vec<u8>,
) -> ClientResult<(Block::Header, u64, AuthorityList)>
where
NumberFor<Block>: BlockNumberOps,
J: Decode + ProvableJustification<Block::Header> + BlockJustification<Block::Header>,
{
let proof = AuthoritySetProof::<Block::Header>::decode(&mut &remote_proof[..])
.map_err(|_| ClientError::BadJustification("failed to decode authority proof".into()))?;
let last = proof.len() - 1;
let mut result = (current_set_id, current_authorities, NumberFor::<Block>::zero());
for (ix, fragment) in proof.into_iter().enumerate() {
let is_last = ix == last;
result = check_warp_sync_proof_fragment::<Block, J>(
result.0,
&result.1,
&result.2,
is_last,
&fragment,
)?;
if is_last {
return Ok((fragment.header, result.0, result.1))
}
}
return Err(ClientError::BadJustification("empty proof of authority".into()));
}
fn check_warp_sync_proof_fragment<Block: BlockT, J>(
current_set_id: u64,
current_authorities: &AuthorityList,
previous_checked_block: &NumberFor<Block>,
is_last: bool,
authorities_proof: &AuthoritySetProofFragment<Block::Header>,
) -> ClientResult<(u64, AuthorityList, NumberFor<Block>)>
where
NumberFor<Block>: BlockNumberOps,
J: Decode + ProvableJustification<Block::Header> + BlockJustification<Block::Header>,
{
let justification: J = Decode::decode(&mut authorities_proof.justification.as_slice())
.map_err(|_| ClientError::JustificationDecode)?;
justification.verify(current_set_id, ¤t_authorities)?;
if &justification.number() != authorities_proof.header.number()
|| justification.hash().as_ref() != authorities_proof.header.hash().as_ref() {
return Err(ClientError::Backend("Invalid authority warp proof, justification do not match header".to_string()));
}
if authorities_proof.header.number() <= previous_checked_block {
return Err(ClientError::Backend("Invalid authority warp proof".to_string()));
}
let current_block = authorities_proof.header.number();
let mut at_block = None;
if let Some(sp_finality_grandpa::ScheduledChange {
next_authorities,
delay,
}) = crate::import::find_scheduled_change::<Block>(&authorities_proof.header) {
let dest = *current_block + delay;
at_block = Some((dest, next_authorities));
}
if let Some((block_number, sp_finality_grandpa::ScheduledChange {
next_authorities,
delay,
})) = crate::import::find_forced_change::<Block>(&authorities_proof.header) {
let dest = block_number + delay;
at_block = Some((dest, next_authorities));
}
if at_block.is_none() && !is_last {
return Err(ClientError::Backend("Invalid authority warp proof".to_string()));
}
if let Some((at_block, next_authorities)) = at_block {
Ok((current_set_id + 1, next_authorities, at_block))
} else {
Ok((current_set_id, current_authorities.clone(), current_block.clone()))
}
}
pub(crate) trait BlockJustification<Header: HeaderT> {
fn number(&self) -> Header::Number;
fn hash(&self) -> Header::Hash;
}
#[cfg(test)]
fn check_finality_proof<Header: HeaderT, J>(
current_set_id: u64,
current_authorities: AuthorityList,
remote_proof: Vec<u8>,
) -> ClientResult<FinalityProof<Header>>
where
J: ProvableJustification<Header>,
{
let proof = FinalityProof::<Header>::decode(&mut &remote_proof[..])
.map_err(|_| ClientError::BadJustification("failed to decode finality proof".into()))?;
let justification: J = Decode::decode(&mut &proof.justification[..])
.map_err(|_| ClientError::JustificationDecode)?;
justification.verify(current_set_id, ¤t_authorities)?;
use sc_telemetry::{telemetry, CONSENSUS_INFO};
telemetry!(CONSENSUS_INFO; "afg.finality_proof_ok";
"finalized_header_hash" => ?proof.block);
Ok(proof)
}
pub trait ProvableJustification<Header: HeaderT>: Encode + Decode {
fn verify(&self, set_id: u64, authorities: &[(AuthorityId, u64)]) -> ClientResult<()>;
fn decode_and_verify(
justification: &Justification,
set_id: u64,
authorities: &[(AuthorityId, u64)],
) -> ClientResult<Self> {
let justification =
Self::decode(&mut &**justification).map_err(|_| ClientError::JustificationDecode)?;
justification.verify(set_id, authorities)?;
Ok(justification)
}
}
impl<Block: BlockT> ProvableJustification<Block::Header> for GrandpaJustification<Block>
where
NumberFor<Block>: BlockNumberOps,
{
fn verify(&self, set_id: u64, authorities: &[(AuthorityId, u64)]) -> ClientResult<()> {
let authorities = VoterSet::new(authorities.iter().cloned()).ok_or(
ClientError::Consensus(sp_consensus::Error::InvalidAuthoritiesSet),
)?;
GrandpaJustification::verify(self, set_id, &authorities)
}
}
impl<Block: BlockT> BlockJustification<Block::Header> for GrandpaJustification<Block> {
fn number(&self) -> NumberFor<Block> {
self.commit.target_number.clone()
}
fn hash(&self) -> Block::Hash {
self.commit.target_hash.clone()
}
}
pub struct WarpSyncFragmentCache<Header: HeaderT> {
header_has_proof_fragment: std::collections::HashMap<Header::Number, bool>,
cache: linked_hash_map::LinkedHashMap<
Header::Number,
(AuthoritySetProofFragment<Header>, Header::Number),
>,
limit: usize,
}
impl<Header: HeaderT> WarpSyncFragmentCache<Header> {
pub fn new(size: usize) -> Self {
WarpSyncFragmentCache {
header_has_proof_fragment: Default::default(),
cache: Default::default(),
limit: size,
}
}
fn new_item(
&mut self,
at: Header::Number,
item: Option<(AuthoritySetProofFragment<Header>, Header::Number)>,
) {
self.header_has_proof_fragment.insert(at, item.is_some());
if let Some(item) = item {
if self.cache.len() == self.limit {
self.pop_one();
}
self.cache.insert(at, item);
}
}
fn pop_one(&mut self) {
if let Some((header_number, _)) = self.cache.pop_front() {
self.header_has_proof_fragment.remove(&header_number);
}
}
fn get_item(
&mut self,
block: Header::Number,
) -> Option<Option<(AuthoritySetProofFragment<Header>, Header::Number)>> {
match self.header_has_proof_fragment.get(&block) {
Some(true) => Some(self.cache.get_refresh(&block).cloned()),
Some(false) => Some(None),
None => None
}
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::authorities::AuthoritySetChanges;
use sp_core::crypto::Public;
use sp_finality_grandpa::AuthorityList;
use sc_client_api::NewBlockState;
use sc_client_api::in_mem::Blockchain as InMemoryBlockchain;
use substrate_test_runtime_client::runtime::{Block, Header, H256};
pub(crate) type FinalityProof = super::FinalityProof<Header>;
#[derive(Debug, PartialEq, Encode, Decode)]
pub struct TestJustification(pub (u64, AuthorityList), pub Vec<u8>);
impl ProvableJustification<Header> for TestJustification {
fn verify(&self, set_id: u64, authorities: &[(AuthorityId, u64)]) -> ClientResult<()> {
if (self.0).0 != set_id || (self.0).1 != authorities {
return Err(ClientError::BadJustification("test".into()));
}
Ok(())
}
}
#[derive(Debug, PartialEq, Encode, Decode)]
pub struct TestBlockJustification(TestJustification, u64, H256);
impl BlockJustification<Header> for TestBlockJustification {
fn number(&self) -> <Header as HeaderT>::Number {
self.1
}
fn hash(&self) -> <Header as HeaderT>::Hash {
self.2.clone()
}
}
impl ProvableJustification<Header> for TestBlockJustification {
fn verify(&self, set_id: u64, authorities: &[(AuthorityId, u64)]) -> ClientResult<()> {
self.0.verify(set_id, authorities)
}
}
fn header(number: u64) -> Header {
let parent_hash = match number {
0 => Default::default(),
_ => header(number - 1).hash(),
};
Header::new(
number,
H256::from_low_u64_be(0),
H256::from_low_u64_be(0),
parent_hash,
Default::default(),
)
}
fn test_blockchain() -> InMemoryBlockchain<Block> {
let blockchain = InMemoryBlockchain::<Block>::new();
blockchain
.insert(header(0).hash(), header(0), Some(vec![0]), None, NewBlockState::Final)
.unwrap();
blockchain
.insert(header(1).hash(), header(1), Some(vec![1]), None, NewBlockState::Final)
.unwrap();
blockchain
.insert(header(2).hash(), header(2), None, None, NewBlockState::Best)
.unwrap();
blockchain
.insert(header(3).hash(), header(3), Some(vec![3]), None, NewBlockState::Final)
.unwrap();
blockchain
}
#[test]
fn finality_proof_fails_if_no_more_last_finalized_blocks() {
let blockchain = test_blockchain();
blockchain
.insert(header(4).hash(), header(4), Some(vec![1]), None, NewBlockState::Best)
.unwrap();
blockchain
.insert(header(5).hash(), header(5), Some(vec![2]), None, NewBlockState::Best)
.unwrap();
let mut authority_set_changes = AuthoritySetChanges::empty();
authority_set_changes.append(0, 5);
let proof_of_4 = prove_finality::<_, _, TestJustification>(
&blockchain,
authority_set_changes,
*header(4).number(),
);
assert!(matches!(proof_of_4, Err(FinalityProofError::BlockNotYetFinalized)));
}
#[test]
fn finality_proof_is_none_if_no_justification_known() {
let blockchain = test_blockchain();
blockchain
.insert(header(4).hash(), header(4), None, None, NewBlockState::Final)
.unwrap();
let mut authority_set_changes = AuthoritySetChanges::empty();
authority_set_changes.append(0, 4);
let proof_of_3 = prove_finality::<_, _, TestJustification>(
&blockchain,
authority_set_changes,
*header(3).number(),
)
.unwrap();
assert_eq!(proof_of_3, None);
}
#[test]
fn finality_proof_check_fails_when_proof_decode_fails() {
check_finality_proof::<_, TestJustification>(
1,
vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)],
vec![42],
)
.unwrap_err();
}
#[test]
fn finality_proof_check_fails_when_proof_is_empty() {
check_finality_proof::<_, TestJustification>(
1,
vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)],
Vec::<TestJustification>::new().encode(),
)
.unwrap_err();
}
#[test]
fn finality_proof_check_works() {
let auth = vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)];
let finality_proof = FinalityProof {
block: header(2).hash(),
justification: TestJustification((1, auth.clone()), vec![7]).encode(),
unknown_headers: Vec::new(),
};
let proof = check_finality_proof::<_, TestJustification>(
1,
auth.clone(),
finality_proof.encode(),
)
.unwrap();
assert_eq!(proof, finality_proof);
}
#[test]
fn finality_proof_using_authority_set_changes_fails_with_undefined_start() {
let blockchain = test_blockchain();
let auth = vec![(AuthorityId::from_slice(&[1u8; 32]), 1u64)];
let just4 = TestJustification((0, auth.clone()), vec![4]).encode();
let just7 = TestJustification((1, auth.clone()), vec![7]).encode();
blockchain
.insert(header(4).hash(), header(4), Some(just4), None, NewBlockState::Final)
.unwrap();
blockchain
.insert(header(5).hash(), header(5), None, None, NewBlockState::Final)
.unwrap();
blockchain
.insert(header(6).hash(), header(6), None, None, NewBlockState::Final)
.unwrap();
blockchain
.insert(header(7).hash(), header(7), Some(just7.clone()), None, NewBlockState::Final)
.unwrap();
let mut authority_set_changes = AuthoritySetChanges::empty();
authority_set_changes.append(1, 7);
let proof_of_5 = prove_finality::<_, _, TestJustification>(
&blockchain,
authority_set_changes,
*header(5).number(),
);
assert!(matches!(proof_of_5, Err(FinalityProofError::BlockNotInAuthoritySetChanges)));
}
#[test]
fn finality_proof_using_authority_set_changes_works() {
let blockchain = test_blockchain();
let auth = vec![(AuthorityId::from_slice(&[1u8; 32]), 1u64)];
let just4 = TestJustification((0, auth.clone()), vec![4]).encode();
let just7 = TestJustification((1, auth.clone()), vec![7]).encode();
blockchain
.insert(header(4).hash(), header(4), Some(just4), None, NewBlockState::Final)
.unwrap();
blockchain
.insert(header(5).hash(), header(5), None, None, NewBlockState::Final)
.unwrap();
blockchain
.insert(header(6).hash(), header(6), None, None, NewBlockState::Final)
.unwrap();
blockchain
.insert(header(7).hash(), header(7), Some(just7.clone()), None, NewBlockState::Final)
.unwrap();
let mut authority_set_changes = AuthoritySetChanges::empty();
authority_set_changes.append(0, 4);
authority_set_changes.append(1, 7);
let proof_of_5: FinalityProof = Decode::decode(
&mut &prove_finality::<_, _, TestJustification>(
&blockchain,
authority_set_changes,
*header(5).number(),
)
.unwrap()
.unwrap()[..],
)
.unwrap();
assert_eq!(
proof_of_5,
FinalityProof {
block: header(7).hash(),
justification: just7,
unknown_headers: vec![header(6)],
}
);
}
#[test]
fn warp_sync_proof_encoding_decoding() {
fn test_blockchain(
nb_blocks: u64,
mut set_change: &[(u64, Vec<u8>)],
mut justifications: &[(u64, Vec<u8>)],
) -> (InMemoryBlockchain<Block>, Vec<H256>) {
let blockchain = InMemoryBlockchain::<Block>::new();
let mut hashes = Vec::<H256>::new();
let mut set_id = 0;
for i in 0..nb_blocks {
let mut set_id_next = set_id;
let mut header = header(i);
set_change.first()
.map(|j| if i == j.0 {
set_change = &set_change[1..];
let next_authorities: Vec<_> = j.1.iter().map(|i| (AuthorityId::from_slice(&[*i; 32]), 1u64)).collect();
set_id_next += 1;
header.digest_mut().logs.push(
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(),
));
});
if let Some(parent) = hashes.last() {
header.set_parent_hash(parent.clone());
}
let header_hash = header.hash();
let justification = justifications.first()
.and_then(|j| if i == j.0 {
justifications = &justifications[1..];
let authority = j.1.iter().map(|j|
(AuthorityId::from_slice(&[*j; 32]), 1u64)
).collect();
let justification = TestBlockJustification(
TestJustification((set_id, authority), vec![i as u8]),
i,
header_hash,
);
Some(justification.encode())
} else {
None
});
hashes.push(header_hash.clone());
set_id = set_id_next;
blockchain.insert(header_hash, header, justification, None, NewBlockState::Final)
.unwrap();
}
(blockchain, hashes)
}
let (blockchain, hashes) = test_blockchain(
7,
vec![(3, vec![9])].as_slice(),
vec![
(1, vec![1, 2, 3]),
(2, vec![1, 2, 3]),
(3, vec![1, 2, 3]),
(4, vec![9]),
(6, vec![9]),
].as_slice(),
);
let mut cache = WarpSyncFragmentCache::new(5);
let proof_no_cache = prove_warp_sync(&blockchain, hashes[6], None, Some(&mut cache)).unwrap();
let proof = prove_warp_sync(&blockchain, hashes[6], None, Some(&mut cache)).unwrap();
assert_eq!(proof_no_cache, proof);
let initial_authorities: Vec<_> = [1u8, 2, 3].iter().map(|i|
(AuthorityId::from_slice(&[*i; 32]), 1u64)
).collect();
let authorities_next: Vec<_> = [9u8].iter().map(|i|
(AuthorityId::from_slice(&[*i; 32]), 1u64)
).collect();
assert!(check_warp_sync_proof::<Block, TestBlockJustification>(
0,
initial_authorities.clone(),
proof.clone(),
).is_err());
assert!(check_warp_sync_proof::<Block, TestBlockJustification>(
0,
authorities_next.clone(),
proof.clone(),
).is_err());
assert!(check_warp_sync_proof::<Block, TestBlockJustification>(
1,
initial_authorities.clone(),
proof.clone(),
).is_err());
let (
_header,
current_set_id,
current_set,
) = check_warp_sync_proof::<Block, TestBlockJustification>(
1,
authorities_next.clone(),
proof.clone(),
).unwrap();
assert_eq!(current_set_id, 1);
assert_eq!(current_set, authorities_next);
let proof = prove_warp_sync(&blockchain, hashes[1], None, None).unwrap();
let (
_header,
current_set_id,
current_set,
) = check_warp_sync_proof::<Block, TestBlockJustification>(
0,
initial_authorities.clone(),
proof.clone(),
).unwrap();
assert_eq!(current_set_id, 1);
assert_eq!(current_set, authorities_next);
let (blockchain, hashes) = test_blockchain(
13,
vec![(3, vec![7]), (8, vec![9])].as_slice(),
vec![
(1, vec![1, 2, 3]),
(2, vec![1, 2, 3]),
(3, vec![1, 2, 3]),
(4, vec![7]),
(6, vec![7]),
(8, vec![7]),
(10, vec![9]),
].as_slice(),
);
let proof = prove_warp_sync(&blockchain, hashes[1], None, None).unwrap();
let (
_header,
current_set_id,
current_set,
) = check_warp_sync_proof::<Block, TestBlockJustification>(
0,
initial_authorities.clone(),
proof.clone(),
).unwrap();
assert_eq!(current_set_id, 2);
assert_eq!(current_set, authorities_next);
}
}