use std::sync::Arc;
use log::{trace, warn};
use sp_blockchain::{Backend as BlockchainBackend, Error as ClientError, Result as ClientResult};
use sc_client_api::{
backend::Backend, StorageProof,
light::{FetchChecker, RemoteReadRequest},
StorageProvider, ProofProvider,
};
use parity_scale_codec::{Encode, Decode};
use finality_grandpa::BlockNumberOps;
use sp_runtime::{
Justification, generic::BlockId,
traits::{NumberFor, Block as BlockT, Header as HeaderT, One},
};
use sp_core::storage::StorageKey;
use sc_telemetry::{telemetry, CONSENSUS_INFO};
use sp_finality_grandpa::{AuthorityId, AuthorityList, VersionedAuthorityList, GRANDPA_AUTHORITIES_KEY};
use crate::justification::GrandpaJustification;
use crate::VoterSet;
const MAX_FRAGMENTS_IN_PROOF: usize = 8;
pub trait AuthoritySetForFinalityProver<Block: BlockT>: Send + Sync {
fn authorities(&self, block: &BlockId<Block>) -> ClientResult<AuthorityList>;
fn prove_authorities(&self, block: &BlockId<Block>) -> ClientResult<StorageProof>;
}
pub trait StorageAndProofProvider<Block, BE>: StorageProvider<Block, BE> + ProofProvider<Block> + Send + Sync
where
Block: BlockT,
BE: Backend<Block> + Send + Sync,
{}
impl<Block, BE, P> StorageAndProofProvider<Block, BE> for P
where
Block: BlockT,
BE: Backend<Block> + Send + Sync,
P: StorageProvider<Block, BE> + ProofProvider<Block> + Send + Sync,
{}
impl<BE, Block: BlockT> AuthoritySetForFinalityProver<Block> for Arc<dyn StorageAndProofProvider<Block, BE>>
where
BE: Backend<Block> + Send + Sync + 'static,
{
fn authorities(&self, block: &BlockId<Block>) -> ClientResult<AuthorityList> {
let storage_key = StorageKey(GRANDPA_AUTHORITIES_KEY.to_vec());
self.storage(block, &storage_key)?
.and_then(|encoded| VersionedAuthorityList::decode(&mut encoded.0.as_slice()).ok())
.map(|versioned| versioned.into())
.ok_or(ClientError::InvalidAuthoritiesSet)
}
fn prove_authorities(&self, block: &BlockId<Block>) -> ClientResult<StorageProof> {
self.read_proof(block, &mut std::iter::once(GRANDPA_AUTHORITIES_KEY))
}
}
pub trait AuthoritySetForFinalityChecker<Block: BlockT>: Send + Sync {
fn check_authorities_proof(
&self,
hash: Block::Hash,
header: Block::Header,
proof: StorageProof,
) -> ClientResult<AuthorityList>;
}
impl<Block: BlockT> AuthoritySetForFinalityChecker<Block> for Arc<dyn FetchChecker<Block>> {
fn check_authorities_proof(
&self,
hash: Block::Hash,
header: Block::Header,
proof: StorageProof,
) -> ClientResult<AuthorityList> {
let storage_key = GRANDPA_AUTHORITIES_KEY.to_vec();
let request = RemoteReadRequest {
block: hash,
header,
keys: vec![storage_key.clone()],
retry_count: None,
};
self.check_read_proof(&request, proof)
.and_then(|results| {
let maybe_encoded = results.get(&storage_key)
.expect(
"storage_key is listed in the request keys; \
check_read_proof must return a value for each requested key;
qed"
);
maybe_encoded
.as_ref()
.and_then(|encoded| {
VersionedAuthorityList::decode(&mut encoded.as_slice()).ok()
})
.map(|versioned| versioned.into())
.ok_or(ClientError::InvalidAuthoritiesSet)
})
}
}
pub struct FinalityProofProvider<B, Block: BlockT> {
backend: Arc<B>,
authority_provider: Arc<dyn AuthoritySetForFinalityProver<Block>>,
}
impl<B, Block: BlockT> FinalityProofProvider<B, Block>
where B: Backend<Block> + Send + Sync + 'static
{
pub fn new<P>(
backend: Arc<B>,
authority_provider: P,
) -> Self
where P: AuthoritySetForFinalityProver<Block> + 'static,
{
FinalityProofProvider { backend, authority_provider: Arc::new(authority_provider) }
}
pub fn new_for_service(
backend: Arc<B>,
storage_and_proof_provider: Arc<dyn StorageAndProofProvider<Block, B>>,
) -> Arc<Self> {
Arc::new(Self::new(backend, storage_and_proof_provider))
}
}
impl<B, Block> FinalityProofProvider<B, Block>
where
Block: BlockT,
NumberFor<Block>: BlockNumberOps,
B: Backend<Block> + Send + Sync + 'static,
{
pub fn prove_finality(
&self,
begin: Block::Hash,
end: Block::Hash,
authorities_set_id: u64,
) -> Result<Option<Vec<u8>>, ClientError> {
prove_finality::<_, _, GrandpaJustification<Block>>(
&*self.backend.blockchain(),
&*self.authority_provider,
authorities_set_id,
begin,
end,
)
}
}
impl<B, Block> sc_network::config::FinalityProofProvider<Block> for FinalityProofProvider<B, Block>
where
Block: BlockT,
NumberFor<Block>: BlockNumberOps,
B: Backend<Block> + Send + Sync + 'static,
{
fn prove_finality(
&self,
for_block: Block::Hash,
request: &[u8],
) -> Result<Option<Vec<u8>>, ClientError> {
let request: FinalityProofRequest<Block::Hash> = Decode::decode(&mut &request[..])
.map_err(|e| {
warn!(target: "afg", "Unable to decode finality proof request: {}", e.what());
ClientError::Backend("Invalid finality proof request".to_string())
})?;
match request {
FinalityProofRequest::Original(request) => prove_finality::<_, _, GrandpaJustification<Block>>(
&*self.backend.blockchain(),
&*self.authority_provider,
request.authorities_set_id,
request.last_finalized,
for_block,
),
}
}
}
#[derive(Debug, PartialEq)]
pub struct FinalityEffects<Header: HeaderT> {
pub headers_to_import: Vec<Header>,
pub block: Header::Hash,
pub justification: Vec<u8>,
pub new_set_id: u64,
pub new_authorities: AuthorityList,
}
#[derive(Debug, PartialEq, Encode, Decode, Clone)]
pub struct FinalityProofFragment<Header: HeaderT> {
pub block: Header::Hash,
pub justification: Vec<u8>,
pub unknown_headers: Vec<Header>,
pub authorities_proof: Option<StorageProof>,
}
type FinalityProof<Header> = Vec<FinalityProofFragment<Header>>;
#[derive(Debug, Encode, Decode)]
enum FinalityProofRequest<H: Encode + Decode> {
Original(OriginalFinalityProofRequest<H>),
}
#[derive(Debug, Encode, Decode)]
struct OriginalFinalityProofRequest<H: Encode + Decode> {
pub authorities_set_id: u64,
pub last_finalized: H,
}
pub(crate) fn make_finality_proof_request<H: Encode + Decode>(last_finalized: H, authorities_set_id: u64) -> Vec<u8> {
FinalityProofRequest::Original(OriginalFinalityProofRequest {
authorities_set_id,
last_finalized,
}).encode()
}
pub(crate) fn prove_finality<Block: BlockT, B: BlockchainBackend<Block>, J>(
blockchain: &B,
authorities_provider: &dyn AuthoritySetForFinalityProver<Block>,
authorities_set_id: u64,
begin: Block::Hash,
end: Block::Hash,
) -> ::sp_blockchain::Result<Option<Vec<u8>>>
where
J: ProvableJustification<Block::Header>,
{
let begin_id = BlockId::Hash(begin);
let begin_number = blockchain.expect_block_number_from_id(&begin_id)?;
let info = blockchain.info();
if info.finalized_number <= begin_number {
trace!(
target: "afg",
"Requested finality proof for descendant of #{} while we only have finalized #{}. Returning empty proof.",
begin_number,
info.finalized_number,
);
return Ok(None);
}
let end_number = blockchain.expect_block_number_from_id(&BlockId::Hash(end))?;
if begin_number + One::one() > end_number {
return Err(ClientError::Backend(
format!("Cannot generate finality proof for invalid range: {}..{}", begin_number, end_number),
));
}
let canonical_begin = blockchain.expect_block_hash_from_id(&BlockId::Number(begin_number))?;
if begin != canonical_begin {
return Err(ClientError::Backend(
format!("Cannot generate finality proof for non-canonical block: {}", begin),
));
}
let mut fragment_index = 0;
let mut current_authorities = authorities_provider.authorities(&begin_id)?;
let mut current_number = begin_number + One::one();
let mut finality_proof = Vec::new();
let mut unknown_headers = Vec::new();
let mut latest_proof_fragment = None;
let begin_authorities = current_authorities.clone();
loop {
let current_id = BlockId::Number(current_number);
if current_number > end_number {
let unknown_header = blockchain.expect_header(current_id)?;
unknown_headers.push(unknown_header);
}
if let Some(justification) = blockchain.justification(current_id)? {
let new_authorities = authorities_provider.authorities(¤t_id)?;
let new_authorities_proof = if current_authorities != new_authorities {
current_authorities = new_authorities;
Some(authorities_provider.prove_authorities(¤t_id)?)
} else {
None
};
let current = blockchain.expect_block_hash_from_id(&BlockId::Number(current_number))?;
let proof_fragment = FinalityProofFragment {
block: current,
justification,
unknown_headers: ::std::mem::take(&mut unknown_headers),
authorities_proof: new_authorities_proof,
};
let justifies_end_block = current_number >= end_number;
let justifies_authority_set_change = proof_fragment.authorities_proof.is_some();
if justifies_end_block || justifies_authority_set_change {
if finality_proof.is_empty() {
let justification_check_result = J::decode_and_verify(
&proof_fragment.justification,
authorities_set_id,
&begin_authorities,
);
if justification_check_result.is_err() {
trace!(
target: "afg",
"Can not provide finality proof with requested set id #{}\
(possible forced change?). Returning empty proof.",
authorities_set_id,
);
return Ok(None);
}
}
finality_proof.push(proof_fragment);
latest_proof_fragment = None;
} else {
latest_proof_fragment = Some(proof_fragment);
}
if justifies_end_block {
break;
}
}
if current_number == info.finalized_number {
if let Some(latest_proof_fragment) = latest_proof_fragment.take() {
finality_proof.push(latest_proof_fragment);
fragment_index += 1;
if fragment_index == MAX_FRAGMENTS_IN_PROOF {
break;
}
}
break;
}
current_number += One::one();
}
if finality_proof.is_empty() {
trace!(
target: "afg",
"No justifications found when making finality proof for {}. Returning empty proof.",
end,
);
Ok(None)
} else {
trace!(
target: "afg",
"Built finality proof for {} of {} fragments. Last fragment for {}.",
end,
finality_proof.len(),
finality_proof.last().expect("checked that !finality_proof.is_empty(); qed").block,
);
Ok(Some(finality_proof.encode()))
}
}
pub(crate) fn check_finality_proof<Block: BlockT, B, J>(
blockchain: &B,
current_set_id: u64,
current_authorities: AuthorityList,
authorities_provider: &dyn AuthoritySetForFinalityChecker<Block>,
remote_proof: Vec<u8>,
) -> ClientResult<FinalityEffects<Block::Header>>
where
NumberFor<Block>: BlockNumberOps,
B: BlockchainBackend<Block>,
J: ProvableJustification<Block::Header>,
{
let proof = FinalityProof::<Block::Header>::decode(&mut &remote_proof[..])
.map_err(|_| ClientError::BadJustification("failed to decode finality proof".into()))?;
if proof.is_empty() {
return Err(ClientError::BadJustification("empty proof of finality".into()));
}
let last_fragment_index = proof.len() - 1;
let mut authorities = AuthoritiesOrEffects::Authorities(current_set_id, current_authorities);
for (proof_fragment_index, proof_fragment) in proof.into_iter().enumerate() {
if proof_fragment_index != last_fragment_index {
let has_unknown_headers = !proof_fragment.unknown_headers.is_empty();
let has_new_authorities = proof_fragment.authorities_proof.is_some();
if has_unknown_headers || !has_new_authorities {
return Err(ClientError::BadJustification("redundant proof of finality".into()));
}
}
authorities = check_finality_proof_fragment::<_, _, J>(
blockchain,
authorities,
authorities_provider,
proof_fragment)?;
}
let effects = authorities.extract_effects().expect("at least one loop iteration is guaranteed
because proof is not empty;\
check_finality_proof_fragment is called on every iteration;\
check_finality_proof_fragment always returns FinalityEffects;\
qed");
telemetry!(CONSENSUS_INFO; "afg.finality_proof_ok";
"set_id" => ?effects.new_set_id, "finalized_header_hash" => ?effects.block);
Ok(effects)
}
fn check_finality_proof_fragment<Block: BlockT, B, J>(
blockchain: &B,
authority_set: AuthoritiesOrEffects<Block::Header>,
authorities_provider: &dyn AuthoritySetForFinalityChecker<Block>,
proof_fragment: FinalityProofFragment<Block::Header>,
) -> ClientResult<AuthoritiesOrEffects<Block::Header>>
where
NumberFor<Block>: BlockNumberOps,
B: BlockchainBackend<Block>,
J: Decode + ProvableJustification<Block::Header>,
{
let (mut current_set_id, mut current_authorities) = authority_set.extract_authorities();
let justification: J = Decode::decode(&mut &proof_fragment.justification[..])
.map_err(|_| ClientError::JustificationDecode)?;
justification.verify(current_set_id, ¤t_authorities)?;
if let Some(new_authorities_proof) = proof_fragment.authorities_proof {
let header = match proof_fragment.unknown_headers.iter().rev().next().cloned() {
Some(header) => header,
None => blockchain.expect_header(BlockId::Hash(proof_fragment.block))?,
};
current_authorities = authorities_provider.check_authorities_proof(
proof_fragment.block,
header,
new_authorities_proof,
)?;
current_set_id += 1;
}
Ok(AuthoritiesOrEffects::Effects(FinalityEffects {
headers_to_import: proof_fragment.unknown_headers,
block: proof_fragment.block,
justification: proof_fragment.justification,
new_set_id: current_set_id,
new_authorities: current_authorities,
}))
}
enum AuthoritiesOrEffects<Header: HeaderT> {
Authorities(u64, AuthorityList),
Effects(FinalityEffects<Header>),
}
impl<Header: HeaderT> AuthoritiesOrEffects<Header> {
pub fn extract_authorities(self) -> (u64, AuthorityList) {
match self {
AuthoritiesOrEffects::Authorities(set_id, authorities) => (set_id, authorities),
AuthoritiesOrEffects::Effects(effects) => (effects.new_set_id, effects.new_authorities),
}
}
pub fn extract_effects(self) -> Option<FinalityEffects<Header>> {
match self {
AuthoritiesOrEffects::Authorities(_, _) => None,
AuthoritiesOrEffects::Effects(effects) => Some(effects),
}
}
}
pub(crate) 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)
}
}
#[cfg(test)]
pub(crate) mod tests {
use substrate_test_runtime_client::runtime::{Block, Header, H256};
use sc_client_api::NewBlockState;
use sc_client_api::in_mem::Blockchain as InMemoryBlockchain;
use super::*;
use sp_core::crypto::Public;
pub(crate) type FinalityProof = super::FinalityProof<Header>;
impl<GetAuthorities, ProveAuthorities> AuthoritySetForFinalityProver<Block> for (GetAuthorities, ProveAuthorities)
where
GetAuthorities: Send + Sync + Fn(BlockId<Block>) -> ClientResult<AuthorityList>,
ProveAuthorities: Send + Sync + Fn(BlockId<Block>) -> ClientResult<StorageProof>,
{
fn authorities(&self, block: &BlockId<Block>) -> ClientResult<AuthorityList> {
self.0(*block)
}
fn prove_authorities(&self, block: &BlockId<Block>) -> ClientResult<StorageProof> {
self.1(*block)
}
}
pub(crate) struct ClosureAuthoritySetForFinalityChecker<Closure>(pub Closure);
impl<Closure> AuthoritySetForFinalityChecker<Block> for ClosureAuthoritySetForFinalityChecker<Closure>
where
Closure: Send + Sync + Fn(H256, Header, StorageProof) -> ClientResult<AuthorityList>,
{
fn check_authorities_proof(
&self,
hash: H256,
header: Header,
proof: StorageProof,
) -> ClientResult<AuthorityList> {
self.0(hash, header, proof)
}
}
#[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(())
}
}
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 side_header(number: u64) -> Header {
Header::new(
number,
H256::from_low_u64_be(0),
H256::from_low_u64_be(1),
header(number - 1).hash(),
Default::default(),
)
}
fn second_side_header(number: u64) -> Header {
Header::new(
number,
H256::from_low_u64_be(0),
H256::from_low_u64_be(1),
side_header(number - 1).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_prove_fails_with_invalid_range() {
let blockchain = test_blockchain();
prove_finality::<_, _, TestJustification>(
&blockchain,
&(
|_| unreachable!("should return before calling GetAuthorities"),
|_| unreachable!("should return before calling ProveAuthorities"),
),
0,
header(2).hash(),
header(2).hash(),
).unwrap_err();
}
#[test]
fn finality_proof_is_none_if_no_more_last_finalized_blocks() {
let blockchain = test_blockchain();
blockchain.insert(header(4).hash(), header(4), None, None, NewBlockState::Best).unwrap();
let proof_of_4 = prove_finality::<_, _, TestJustification>(
&blockchain,
&(
|_| unreachable!("should return before calling GetAuthorities"),
|_| unreachable!("should return before calling ProveAuthorities"),
),
0,
header(3).hash(),
header(4).hash(),
).unwrap();
assert_eq!(proof_of_4, None);
}
#[test]
fn finality_proof_fails_for_non_canonical_block() {
let blockchain = test_blockchain();
blockchain.insert(header(4).hash(), header(4), None, None, NewBlockState::Best).unwrap();
blockchain.insert(side_header(4).hash(), side_header(4), None, None, NewBlockState::Best).unwrap();
blockchain.insert(second_side_header(5).hash(), second_side_header(5), None, None, NewBlockState::Best)
.unwrap();
blockchain.insert(header(5).hash(), header(5), Some(vec![5]), None, NewBlockState::Final).unwrap();
prove_finality::<_, _, TestJustification>(
&blockchain,
&(
|_| unreachable!("should return before calling GetAuthorities"),
|_| unreachable!("should return before calling ProveAuthorities"),
),
0,
side_header(4).hash(),
second_side_header(5).hash(),
).unwrap_err();
}
#[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 proof_of_4 = prove_finality::<_, _, TestJustification>(
&blockchain,
&(
|_| Ok(vec![(AuthorityId::from_slice(&[1u8; 32]), 1u64)]),
|_| unreachable!("authorities didn't change => ProveAuthorities won't be called"),
),
0,
header(3).hash(),
header(4).hash(),
).unwrap();
assert_eq!(proof_of_4, None);
}
#[test]
fn finality_proof_works_without_authorities_change() {
let blockchain = test_blockchain();
let authorities = vec![(AuthorityId::from_slice(&[1u8; 32]), 1u64)];
let just4 = TestJustification((0, authorities.clone()), vec![4]).encode();
let just5 = TestJustification((0, authorities.clone()), vec![5]).encode();
blockchain.insert(header(4).hash(), header(4), Some(just4), None, NewBlockState::Final).unwrap();
blockchain.insert(header(5).hash(), header(5), Some(just5.clone()), None, NewBlockState::Final).unwrap();
let proof_of_5: FinalityProof = Decode::decode(&mut &prove_finality::<_, _, TestJustification>(
&blockchain,
&(
|_| Ok(authorities.clone()),
|_| unreachable!("should return before calling ProveAuthorities"),
),
0,
header(3).hash(),
header(5).hash(),
).unwrap().unwrap()[..]).unwrap();
assert_eq!(proof_of_5, vec![FinalityProofFragment {
block: header(5).hash(),
justification: just5,
unknown_headers: Vec::new(),
authorities_proof: None,
}]);
}
#[test]
fn finality_proof_finalized_earlier_block_if_no_justification_for_target_is_known() {
let blockchain = test_blockchain();
blockchain.insert(header(4).hash(), header(4), Some(vec![4]), None, NewBlockState::Final).unwrap();
blockchain.insert(header(5).hash(), header(5), None, None, NewBlockState::Final).unwrap();
let proof_of_5: FinalityProof = Decode::decode(&mut &prove_finality::<_, _, TestJustification>(
&blockchain,
&(
|_| Ok(vec![(AuthorityId::from_slice(&[1u8; 32]), 1u64)]),
|_| unreachable!("should return before calling ProveAuthorities"),
),
0,
header(3).hash(),
header(5).hash(),
).unwrap().unwrap()[..]).unwrap();
assert_eq!(proof_of_5, vec![FinalityProofFragment {
block: header(4).hash(),
justification: vec![4],
unknown_headers: Vec::new(),
authorities_proof: None,
}]);
}
#[test]
fn finality_proof_works_with_authorities_change() {
let blockchain = test_blockchain();
let auth3 = vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)];
let auth5 = vec![(AuthorityId::from_slice(&[5u8; 32]), 1u64)];
let auth7 = vec![(AuthorityId::from_slice(&[7u8; 32]), 1u64)];
let just4 = TestJustification((0, auth3.clone()), vec![4]).encode();
let just5 = TestJustification((0, auth3.clone()), vec![5]).encode();
let just7 = TestJustification((1, auth5.clone()), vec![7]).encode();
blockchain.insert(header(4).hash(), header(4), Some(just4), None, NewBlockState::Final).unwrap();
blockchain.insert(header(5).hash(), header(5), Some(just5.clone()), 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 proof_of_6: FinalityProof = Decode::decode(&mut &prove_finality::<_, _, TestJustification>(
&blockchain,
&(
|block_id| match block_id {
BlockId::Hash(h) if h == header(3).hash() => Ok(auth3.clone()),
BlockId::Number(4) => Ok(auth3.clone()),
BlockId::Number(5) => Ok(auth5.clone()),
BlockId::Number(7) => Ok(auth7.clone()),
_ => unreachable!("no other authorities should be fetched: {:?}", block_id),
},
|block_id| match block_id {
BlockId::Number(5) => Ok(StorageProof::new(vec![vec![50]])),
BlockId::Number(7) => Ok(StorageProof::new(vec![vec![70]])),
_ => unreachable!("no other authorities should be proved: {:?}", block_id),
},
),
0,
header(3).hash(),
header(6).hash(),
).unwrap().unwrap()[..]).unwrap();
assert_eq!(proof_of_6, vec![
FinalityProofFragment {
block: header(5).hash(),
justification: just5,
unknown_headers: Vec::new(),
authorities_proof: Some(StorageProof::new(vec![vec![50]])),
},
FinalityProofFragment {
block: header(7).hash(),
justification: just7.clone(),
unknown_headers: vec![header(7)],
authorities_proof: Some(StorageProof::new(vec![vec![70]])),
},
]);
let blockchain = test_blockchain();
blockchain.insert(header(4).hash(), header(4), None, 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();
let effects = check_finality_proof::<_, _, TestJustification>(
&blockchain,
0,
auth3,
&ClosureAuthoritySetForFinalityChecker(
|hash, _header, proof: StorageProof| match proof.clone().iter_nodes().next().map(|x| x[0]) {
Some(50) => Ok(auth5.clone()),
Some(70) => Ok(auth7.clone()),
_ => unreachable!("no other proofs should be checked: {}", hash),
}
),
proof_of_6.encode(),
).unwrap();
assert_eq!(effects, FinalityEffects {
headers_to_import: vec![header(7)],
block: header(7).hash(),
justification: TestJustification((1, auth5.clone()), vec![7]).encode(),
new_set_id: 2,
new_authorities: auth7,
});
}
#[test]
fn finality_proof_check_fails_when_proof_decode_fails() {
let blockchain = test_blockchain();
check_finality_proof::<_, _, TestJustification>(
&blockchain,
1,
vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)],
&ClosureAuthoritySetForFinalityChecker(|_, _, _| unreachable!("returns before CheckAuthoritiesProof")),
vec![42],
).unwrap_err();
}
#[test]
fn finality_proof_check_fails_when_proof_is_empty() {
let blockchain = test_blockchain();
check_finality_proof::<_, _, TestJustification>(
&blockchain,
1,
vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)],
&ClosureAuthoritySetForFinalityChecker(|_, _, _| unreachable!("returns before CheckAuthoritiesProof")),
Vec::<TestJustification>::new().encode(),
).unwrap_err();
}
#[test]
fn finality_proof_check_fails_when_intermediate_fragment_has_unknown_headers() {
let blockchain = test_blockchain();
let authorities = vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)];
check_finality_proof::<_, _, TestJustification>(
&blockchain,
1,
authorities.clone(),
&ClosureAuthoritySetForFinalityChecker(|_, _, _| unreachable!("returns before CheckAuthoritiesProof")),
vec![FinalityProofFragment {
block: header(4).hash(),
justification: TestJustification((0, authorities.clone()), vec![7]).encode(),
unknown_headers: vec![header(4)],
authorities_proof: Some(StorageProof::new(vec![vec![42]])),
}, FinalityProofFragment {
block: header(5).hash(),
justification: TestJustification((0, authorities), vec![8]).encode(),
unknown_headers: vec![header(5)],
authorities_proof: None,
}].encode(),
).unwrap_err();
}
#[test]
fn finality_proof_check_fails_when_intermediate_fragment_has_no_authorities_proof() {
let blockchain = test_blockchain();
let authorities = vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)];
check_finality_proof::<_, _, TestJustification>(
&blockchain,
1,
authorities.clone(),
&ClosureAuthoritySetForFinalityChecker(|_, _, _| unreachable!("returns before CheckAuthoritiesProof")),
vec![FinalityProofFragment {
block: header(4).hash(),
justification: TestJustification((0, authorities.clone()), vec![7]).encode(),
unknown_headers: Vec::new(),
authorities_proof: None,
}, FinalityProofFragment {
block: header(5).hash(),
justification: TestJustification((0, authorities), vec![8]).encode(),
unknown_headers: vec![header(5)],
authorities_proof: None,
}].encode(),
).unwrap_err();
}
#[test]
fn finality_proof_check_works() {
let blockchain = test_blockchain();
let initial_authorities = vec![(AuthorityId::from_slice(&[3u8; 32]), 1u64)];
let next_authorities = vec![(AuthorityId::from_slice(&[4u8; 32]), 1u64)];
let effects = check_finality_proof::<_, _, TestJustification>(
&blockchain,
1,
initial_authorities.clone(),
&ClosureAuthoritySetForFinalityChecker(|_, _, _| Ok(next_authorities.clone())),
vec![FinalityProofFragment {
block: header(2).hash(),
justification: TestJustification((1, initial_authorities.clone()), vec![7]).encode(),
unknown_headers: Vec::new(),
authorities_proof: Some(StorageProof::new(vec![vec![42]])),
}, FinalityProofFragment {
block: header(4).hash(),
justification: TestJustification((2, next_authorities.clone()), vec![8]).encode(),
unknown_headers: vec![header(4)],
authorities_proof: None,
}].encode(),
).unwrap();
assert_eq!(effects, FinalityEffects {
headers_to_import: vec![header(4)],
block: header(4).hash(),
justification: TestJustification((2, next_authorities.clone()), vec![8]).encode(),
new_set_id: 2,
new_authorities: vec![(AuthorityId::from_slice(&[4u8; 32]), 1u64)],
});
}
#[test]
fn finality_proof_is_none_if_first_justification_is_generated_by_unknown_set() {
let blockchain = test_blockchain();
let just4 = TestJustification((0, vec![(AuthorityId::from_slice(&[42u8; 32]), 1u64)]), vec![4]).encode();
blockchain.insert(header(4).hash(), header(4), Some(just4), None, NewBlockState::Final).unwrap();
let proof_of_4 = prove_finality::<_, _, TestJustification>(
&blockchain,
&(
|_| Ok(vec![(AuthorityId::from_slice(&[1u8; 32]), 1u64)]),
|_| unreachable!("should return before calling ProveAuthorities"),
),
0,
header(3).hash(),
header(4).hash(),
).unwrap();
assert!(proof_of_4.is_none());
}
}