// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.
// Parity Bridges Common 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.
// Parity Bridges Common 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 Parity Bridges Common. If not, see .
//! Tests for checking that behaviour of importing headers and finality proofs works correctly.
//!
//! The tests are built around the idea that we will be importing headers on different forks and we
//! should be able to check that we're correctly importing headers, scheduling changes, and
//! finalizing headers across different forks.
//!
//! Each test is depicted using beautiful ASCII art. The symbols used in the tests are the
//! following:
//!
//! - S|N: Schedules change in N blocks
//! - E: Enacts change
//! - F: Finalized
//! - FN: Finality proof imported for header N
//!
//! Each diagram also comes with an import order. This is important since we expect things to fail
//! when headers or proofs are imported in a certain order.
//!
//! Tests can be read as follows:
//!
//! ## Example Import 1
//!
//! (Type::Header(2, 1, None, None), Ok(()))
//!
//! Import header 2 on fork 1. This does not create a fork, or schedule an authority set change. We
//! expect this header import to be succesful.
//!
//! ## Example Import 2
//!
//! (Type::Header(4, 2, Some((3, 1)), Some(0)), Ok(()))
//!
//! Import header 4 on fork 2. This header starts a new fork from header 3 on fork 1. It also
//! schedules a change with a delay of 0 blocks. It should be succesfully imported.
//!
//! ## Example Import 3
//!
//! (Type::Finality(2, 1), Err(FinalizationError::OldHeader.into()))
//!
//! Import a finality proof for header 2 on fork 1. This finalty proof should fail to be imported
//! because the header is an old header.
use crate::justification::tests::*;
use crate::mock::{helpers::*, *};
use crate::storage::{AuthoritySet, ImportedHeader};
use crate::verifier::*;
use crate::{BestFinalized, BestHeight, BridgeStorage, NextScheduledChange, PalletStorage};
use codec::Encode;
use frame_support::{IterableStorageMap, StorageValue};
use sp_finality_grandpa::{ConsensusLog, GRANDPA_ENGINE_ID};
use sp_runtime::{Digest, DigestItem};
use std::collections::BTreeMap;
type ForkId = u64;
type Delay = u64;
// Indicates when to start a new fork. The first item in the tuple
// will be the parent header of the header starting this fork.
type ForksAt = Option<(TestNumber, ForkId)>;
type ScheduledChangeAt = Option;
#[derive(Debug)]
enum Type {
Header(TestNumber, ForkId, ForksAt, ScheduledChangeAt),
Finality(TestNumber, ForkId),
}
// Order: 1, 2, 2', 3, 3''
//
// / [3'']
// / [2']
// [1] <- [2] <- [3]
#[test]
fn fork_can_import_headers_on_different_forks() {
run_test(|| {
let mut storage = PalletStorage::::new();
let mut chain = vec![
(Type::Header(1, 1, None, None), Ok(())),
(Type::Header(2, 1, None, None), Ok(())),
(Type::Header(2, 2, Some((1, 1)), None), Ok(())),
(Type::Header(3, 1, None, None), Ok(())),
(Type::Header(3, 3, Some((2, 2)), None), Ok(())),
];
create_chain(&mut storage, &mut chain);
let best_headers = storage.best_headers();
assert_eq!(best_headers.len(), 2);
assert_eq!(>::get(), 3);
})
}
// Order: 1, 2, 2', F2, F2'
//
// [1] <- [2: F]
// \ [2']
//
// Not allowed to finalize 2'
#[test]
fn fork_does_not_allow_competing_finality_proofs() {
run_test(|| {
let mut storage = PalletStorage::::new();
let mut chain = vec![
(Type::Header(1, 1, None, None), Ok(())),
(Type::Header(2, 1, None, None), Ok(())),
(Type::Header(2, 2, Some((1, 1)), None), Ok(())),
(Type::Finality(2, 1), Ok(())),
(Type::Finality(2, 2), Err(FinalizationError::OldHeader.into())),
];
create_chain(&mut storage, &mut chain);
})
}
// Order: 1, 2, 3, F2, 3
//
// [1] <- [2: S|0] <- [3]
//
// Not allowed to import 3 until we get F2
//
// Note: Grandpa would technically allow 3 to be imported as long as it didn't try and enact an
// authority set change. However, since we expect finality proofs to be imported quickly we've
// decided to simplify our import process and disallow header imports until we get a finality proof.
#[test]
fn fork_waits_for_finality_proof_before_importing_header_past_one_which_enacts_a_change() {
run_test(|| {
let mut storage = PalletStorage::::new();
let mut chain = vec![
(Type::Header(1, 1, None, None), Ok(())),
(Type::Header(2, 1, None, Some(0)), Ok(())),
(
Type::Header(3, 1, None, None),
Err(ImportError::AwaitingFinalityProof.into()),
),
(Type::Finality(2, 1), Ok(())),
(Type::Header(3, 1, None, None), Ok(())),
];
create_chain(&mut storage, &mut chain);
})
}
// Order: 1, 2, F2, 3
//
// [1] <- [2: S|1] <- [3: S|0]
//
// Grandpa can have multiple authority set changes pending on the same fork. However, we've decided
// to introduce a limit of _one_ pending authority set change per fork in order to simplify pallet
// logic and to prevent DoS attacks if Grandpa finality were to temporarily stall for a long time
// (we'd have to perform a lot of expensive ancestry checks to catch back up).
#[test]
fn fork_does_not_allow_multiple_scheduled_changes_on_the_same_fork() {
run_test(|| {
let mut storage = PalletStorage::::new();
let mut chain = vec![
(Type::Header(1, 1, None, None), Ok(())),
(Type::Header(2, 1, None, Some(1)), Ok(())),
(
Type::Header(3, 1, None, Some(0)),
Err(ImportError::PendingAuthoritySetChange.into()),
),
(Type::Finality(2, 1), Ok(())),
(Type::Header(3, 1, None, Some(0)), Ok(())),
];
create_chain(&mut storage, &mut chain);
})
}
// Order: 1, 2, 2'
//
// / [2': S|0]
// [1] <- [2: S|0]
//
// Both 2 and 2' should be marked as needing justifications since they enact changes.
#[test]
fn fork_correctly_tracks_which_headers_require_finality_proofs() {
run_test(|| {
let mut storage = PalletStorage::::new();
let mut chain = vec![
(Type::Header(1, 1, None, None), Ok(())),
(Type::Header(2, 1, None, Some(0)), Ok(())),
(Type::Header(2, 2, Some((1, 1)), Some(0)), Ok(())),
];
create_chain(&mut storage, &mut chain);
let header_ids = storage.missing_justifications();
assert_eq!(header_ids.len(), 2);
assert!(header_ids[0].hash != header_ids[1].hash);
assert_eq!(header_ids[0].number, 2);
assert_eq!(header_ids[1].number, 2);
})
}
// Order: 1, 2, 2', 3', F2, 3, 4'
//
// / [2': S|1] <- [3'] <- [4']
// [1] <- [2: S|0] <- [3]
//
//
// Not allowed to import 3 or 4'
// Can only import 3 after we get the finality proof for 2
#[test]
fn fork_does_not_allow_importing_past_header_that_enacts_changes_on_forks() {
run_test(|| {
let mut storage = PalletStorage::::new();
let mut chain = vec![
(Type::Header(1, 1, None, None), Ok(())),
(Type::Header(2, 1, None, Some(0)), Ok(())),
(Type::Header(2, 2, Some((1, 1)), Some(1)), Ok(())),
(
Type::Header(3, 1, None, None),
Err(ImportError::AwaitingFinalityProof.into()),
),
(Type::Header(3, 2, None, None), Ok(())),
(Type::Finality(2, 1), Ok(())),
(Type::Header(3, 1, None, None), Ok(())),
(
Type::Header(4, 2, None, None),
Err(ImportError::AwaitingFinalityProof.into()),
),
];
create_chain(&mut storage, &mut chain);
// Since we can't query the map directly to check if we applied the right authority set
// change (we don't know the header hash of 2) we need to get a little clever.
let mut next_change = >::iter();
let (_, scheduled_change_on_fork) = next_change.next().unwrap();
assert_eq!(scheduled_change_on_fork.height, 3);
// Sanity check to make sure we enacted the change on the canonical change
assert_eq!(next_change.next(), None);
})
}
// Order: 1, 2, 3, 2', 3'
//
// / [2'] <- [3']
// [1] <- [2: S|0] <- [3]
//
// Not allowed to import 3
// Fine to import 2' and 3'
#[test]
fn fork_allows_importing_on_different_fork_while_waiting_for_finality_proof() {
run_test(|| {
let mut storage = PalletStorage::::new();
let mut chain = vec![
(Type::Header(1, 1, None, None), Ok(())),
(Type::Header(2, 1, None, Some(0)), Ok(())),
(
Type::Header(3, 1, None, None),
Err(ImportError::AwaitingFinalityProof.into()),
),
(Type::Header(2, 2, Some((1, 1)), None), Ok(())),
(Type::Header(3, 2, None, None), Ok(())),
];
create_chain(&mut storage, &mut chain);
})
}
// Order: 1, 2, 2', F2, 3, 3'
//
// / [2'] <- [3']
// [1] <- [2: F] <- [3]
//
// In our current implementation we're allowed to keep building on fork 2 for as long as our hearts'
// content. However, we'll never be able to finalize anything on that fork. We'd have to check for
// ancestry with `best_finalized` on every import which will get expensive.
//
// I think this is fine as long as we run pruning every so often to clean up these dead forks.
#[test]
fn fork_allows_importing_on_different_fork_past_finalized_header() {
run_test(|| {
let mut storage = PalletStorage::::new();
let mut chain = vec![
(Type::Header(1, 1, None, None), Ok(())),
(Type::Header(2, 1, None, Some(0)), Ok(())),
(Type::Header(2, 2, Some((1, 1)), None), Ok(())),
(Type::Finality(2, 1), Ok(())),
(Type::Header(3, 1, None, None), Ok(())),
(Type::Header(3, 2, None, None), Ok(())),
];
create_chain(&mut storage, &mut chain);
})
}
// Order: 1, 2, 3, 4, 3', 4'
//
// / [3': E] <- [4']
// [1] <- [2: S|1] <- [3: E] <- [4]
//
// Not allowed to import {4|4'}
#[test]
fn fork_can_track_scheduled_changes_across_forks() {
run_test(|| {
let mut storage = PalletStorage::::new();
let mut chain = vec![
(Type::Header(1, 1, None, None), Ok(())),
(Type::Header(2, 1, None, Some(1)), Ok(())),
(Type::Header(3, 1, None, None), Ok(())),
(
Type::Header(4, 1, None, None),
Err(ImportError::AwaitingFinalityProof.into()),
),
(Type::Header(3, 2, Some((2, 1)), None), Ok(())),
(
Type::Header(4, 2, None, None),
Err(ImportError::AwaitingFinalityProof.into()),
),
];
create_chain(&mut storage, &mut chain);
})
}
#[derive(Debug, PartialEq)]
enum TestError {
Import(ImportError),
Finality(FinalizationError),
}
impl From for TestError {
fn from(e: ImportError) -> Self {
TestError::Import(e)
}
}
impl From for TestError {
fn from(e: FinalizationError) -> Self {
TestError::Finality(e)
}
}
// Builds a fork-aware representation of a blockchain given a list of headers.
//
// Takes a list of headers and finality proof operations which will be applied in order. The
// expected outcome for each operation is also required.
//
// The first header in the list will be used as the genesis header and will be manually imported
// into storage.
fn create_chain(storage: &mut S, chain: &mut Vec<(Type, Result<(), TestError>)>)
where
S: BridgeStorage + Clone,
{
let mut map = BTreeMap::new();
let mut verifier = Verifier {
storage: storage.clone(),
};
initialize_genesis(storage, &mut map, chain.remove(0).0);
for h in chain {
match h {
(Type::Header(num, fork_id, does_fork, schedules_change), expected_result) => {
// If we've never seen this fork before
if !map.contains_key(&fork_id) {
// Let's get the info about where to start the fork
if let Some((parent_num, forked_from_id)) = does_fork {
let fork = &*map.get(&forked_from_id).unwrap();
let parent = fork
.iter()
.find(|h| h.number == *parent_num)
.expect("Trying to fork on a parent which doesn't exist");
let mut header = test_header(*num);
header.parent_hash = parent.hash();
header.state_root = [*fork_id as u8; 32].into();
if let Some(delay) = schedules_change {
header.digest = change_log(*delay);
}
// Try and import into storage
let res = verifier.import_header(header.clone()).map_err(TestError::Import);
assert_eq!(
res, *expected_result,
"Expected {:?} while importing header ({}, {}), got {:?}",
*expected_result, *num, *fork_id, res,
);
// Let's mark the header down in a new fork
if res.is_ok() {
map.insert(*fork_id, vec![header]);
}
}
} else {
// We've seen this fork before so let's append our new header to it
let parent_hash = {
let fork = &*map.get(&fork_id).unwrap();
fork.last().unwrap().hash()
};
let mut header = test_header(*num);
header.parent_hash = parent_hash;
// Doing this to make sure headers at the same height but on
// different forks have different hashes
header.state_root = [*fork_id as u8; 32].into();
if let Some(delay) = schedules_change {
header.digest = change_log(*delay);
}
let res = verifier.import_header(header.clone()).map_err(TestError::Import);
assert_eq!(
res, *expected_result,
"Expected {:?} while importing header ({}, {}), got {:?}",
*expected_result, *num, *fork_id, res,
);
if res.is_ok() {
map.get_mut(&fork_id).unwrap().push(header);
}
}
}
(Type::Finality(num, fork_id), expected_result) => {
let header = map[fork_id]
.iter()
.find(|h| h.number == *num)
.expect("Trying to finalize block that doesn't exist");
// This is technically equivocating (accepting the same justification on the same
// `grandpa_round`).
//
// See for more: https://github.com/paritytech/parity-bridges-common/issues/430
let grandpa_round = 1;
let set_id = 1;
let authorities = authority_list();
let justification =
make_justification_for_header(&header, grandpa_round, set_id, &authorities).encode();
let res = verifier
.import_finality_proof(header.hash(), justification.into())
.map_err(TestError::Finality);
assert_eq!(
res, *expected_result,
"Expected {:?} while importing finality proof for header ({}, {}), got {:?}",
*expected_result, *num, *fork_id, res,
);
}
}
}
for (key, value) in map.iter() {
println!("{}: {:#?}", key, value);
}
}
fn initialize_genesis(storage: &mut S, map: &mut BTreeMap>, genesis: Type)
where
S: BridgeStorage,
{
if let Type::Header(num, fork_id, None, None) = genesis {
let genesis = test_header(num);
map.insert(fork_id, vec![genesis.clone()]);
let genesis = ImportedHeader {
header: genesis,
requires_justification: false,
is_finalized: true,
signal_hash: None,
};
>::put(genesis.hash());
storage.write_header(&genesis);
} else {
panic!("Unexpected genesis block format {:#?}", genesis)
}
let set_id = 1;
let authorities = authority_list();
let authority_set = AuthoritySet::new(authorities, set_id);
storage.update_current_authority_set(authority_set);
}
fn change_log(delay: u64) -> Digest {
let consensus_log = ConsensusLog::::ScheduledChange(sp_finality_grandpa::ScheduledChange {
next_authorities: vec![(alice(), 1), (bob(), 1)],
delay,
});
Digest:: {
logs: vec![DigestItem::Consensus(GRANDPA_ENGINE_ID, consensus_log.encode())],
}
}