mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 19:51:02 +00:00
Adding Bridges code as git subtree. (#2515)
* Add instructions. * Squashed 'bridges/' content from commit 345e84a21 git-subtree-dir: bridges git-subtree-split: 345e84a2146b56628e9888c9f5e129cb40e868a9 * Remove bridges workspace file to avoid confusing Cargo. * Add some bridges primitives to Polkadot workspace. * Improve docs.
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
[package]
|
||||
name = "pallet-substrate-bridge"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false }
|
||||
finality-grandpa = { version = "0.14.0", default-features = false }
|
||||
hash-db = { version = "0.15.2", default-features = false }
|
||||
serde = { version = "1.0", optional = true }
|
||||
|
||||
# Bridge Dependencies
|
||||
|
||||
bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
|
||||
bp-runtime = { path = "../../primitives/runtime", default-features = false }
|
||||
|
||||
# Substrate Dependencies
|
||||
|
||||
frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
sp-finality-grandpa = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
sp-trie = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
bp-test-utils = {path = "../../primitives/test-utils" }
|
||||
sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
sp-io = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
sp-state-machine = { git = "https://github.com/paritytech/substrate.git", branch = "master" }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"bp-header-chain/std",
|
||||
"bp-runtime/std",
|
||||
"bp-header-chain/std",
|
||||
"codec/std",
|
||||
"finality-grandpa/std",
|
||||
"frame-support/std",
|
||||
"frame-system/std",
|
||||
"hash-db/std",
|
||||
"serde",
|
||||
"sp-finality-grandpa/std",
|
||||
"sp-runtime/std",
|
||||
"sp-std/std",
|
||||
"sp-trie/std",
|
||||
]
|
||||
runtime-benchmarks = []
|
||||
@@ -0,0 +1,515 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! 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::mock::*;
|
||||
use crate::storage::ImportedHeader;
|
||||
use crate::verifier::*;
|
||||
use crate::{BestFinalized, BestHeight, BridgeStorage, NextScheduledChange, PalletStorage};
|
||||
use bp_header_chain::AuthoritySet;
|
||||
use bp_test_utils::{alice, authority_list, bob, make_justification_for_header};
|
||||
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<Delay>;
|
||||
|
||||
#[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::<TestRuntime>::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!(<BestHeight<TestRuntime>>::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::<TestRuntime>::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::<TestRuntime>::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::<TestRuntime>::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::<TestRuntime>::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::<TestRuntime>::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 = <NextScheduledChange<TestRuntime>>::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::<TestRuntime>::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::<TestRuntime>::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::<TestRuntime>::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<ImportError> for TestError {
|
||||
fn from(e: ImportError) -> Self {
|
||||
TestError::Import(e)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<FinalizationError> 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<S>(storage: &mut S, chain: &mut Vec<(Type, Result<(), TestError>)>)
|
||||
where
|
||||
S: BridgeStorage<Header = TestHeader> + 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.hash(), 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.hash(), 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<S>(storage: &mut S, map: &mut BTreeMap<TestNumber, Vec<TestHeader>>, genesis: Type)
|
||||
where
|
||||
S: BridgeStorage<Header = TestHeader>,
|
||||
{
|
||||
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,
|
||||
};
|
||||
|
||||
<BestFinalized<TestRuntime>>::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);
|
||||
}
|
||||
|
||||
pub(crate) fn change_log(delay: u64) -> Digest<TestHash> {
|
||||
let consensus_log = ConsensusLog::<TestNumber>::ScheduledChange(sp_finality_grandpa::ScheduledChange {
|
||||
next_authorities: vec![(alice(), 1), (bob(), 1)],
|
||||
delay,
|
||||
});
|
||||
|
||||
Digest::<TestHash> {
|
||||
logs: vec![DigestItem::Consensus(GRANDPA_ENGINE_ID, consensus_log.encode())],
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,117 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Mock Runtime for Substrate Pallet Testing.
|
||||
//!
|
||||
//! Includes some useful testing types and functions.
|
||||
|
||||
#![cfg(test)]
|
||||
// From construct_runtime macro
|
||||
#![allow(clippy::from_over_into)]
|
||||
|
||||
use crate::{BridgedBlockHash, BridgedBlockNumber, BridgedHeader, Config};
|
||||
use bp_runtime::Chain;
|
||||
use frame_support::{parameter_types, weights::Weight};
|
||||
use sp_runtime::{
|
||||
testing::{Header, H256},
|
||||
traits::{BlakeTwo256, IdentityLookup},
|
||||
Perbill,
|
||||
};
|
||||
|
||||
pub type AccountId = u64;
|
||||
pub type TestHeader = BridgedHeader<TestRuntime>;
|
||||
pub type TestNumber = BridgedBlockNumber<TestRuntime>;
|
||||
pub type TestHash = BridgedBlockHash<TestRuntime>;
|
||||
|
||||
type Block = frame_system::mocking::MockBlock<TestRuntime>;
|
||||
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
|
||||
|
||||
use crate as pallet_substrate;
|
||||
|
||||
frame_support::construct_runtime! {
|
||||
pub enum TestRuntime where
|
||||
Block = Block,
|
||||
NodeBlock = Block,
|
||||
UncheckedExtrinsic = UncheckedExtrinsic,
|
||||
{
|
||||
System: frame_system::{Module, Call, Config, Storage, Event<T>},
|
||||
Substrate: pallet_substrate::{Module, Call},
|
||||
}
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const BlockHashCount: u64 = 250;
|
||||
pub const MaximumBlockWeight: Weight = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
pub const AvailableBlockRatio: Perbill = Perbill::one();
|
||||
}
|
||||
|
||||
impl frame_system::Config for TestRuntime {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type Call = Call;
|
||||
type BlockNumber = u64;
|
||||
type Hash = H256;
|
||||
type Hashing = BlakeTwo256;
|
||||
type AccountId = AccountId;
|
||||
type Lookup = IdentityLookup<Self::AccountId>;
|
||||
type Header = Header;
|
||||
type Event = ();
|
||||
type BlockHashCount = BlockHashCount;
|
||||
type Version = ();
|
||||
type PalletInfo = PalletInfo;
|
||||
type AccountData = ();
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
type BaseCallFilter = ();
|
||||
type SystemWeightInfo = ();
|
||||
type DbWeight = ();
|
||||
type BlockWeights = ();
|
||||
type BlockLength = ();
|
||||
type SS58Prefix = ();
|
||||
}
|
||||
|
||||
impl Config for TestRuntime {
|
||||
type BridgedChain = TestBridgedChain;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestBridgedChain;
|
||||
|
||||
impl Chain for TestBridgedChain {
|
||||
type BlockNumber = <TestRuntime as frame_system::Config>::BlockNumber;
|
||||
type Hash = <TestRuntime as frame_system::Config>::Hash;
|
||||
type Hasher = <TestRuntime as frame_system::Config>::Hashing;
|
||||
type Header = <TestRuntime as frame_system::Config>::Header;
|
||||
}
|
||||
|
||||
pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
|
||||
sp_io::TestExternalities::new(Default::default()).execute_with(test)
|
||||
}
|
||||
|
||||
pub fn test_header(num: TestNumber) -> TestHeader {
|
||||
// We wrap the call to avoid explicit type annotations in our tests
|
||||
bp_test_utils::test_header(num)
|
||||
}
|
||||
|
||||
pub fn unfinalized_header(num: u64) -> crate::storage::ImportedHeader<TestHeader> {
|
||||
crate::storage::ImportedHeader {
|
||||
header: test_header(num),
|
||||
requires_justification: false,
|
||||
is_finalized: false,
|
||||
signal_hash: None,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
// Copyright 2019-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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Storage primitives for the Substrate light client (a.k.a bridge) pallet.
|
||||
|
||||
use bp_header_chain::AuthoritySet;
|
||||
use codec::{Decode, Encode};
|
||||
use core::default::Default;
|
||||
#[cfg(feature = "std")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp_finality_grandpa::{AuthorityList, SetId};
|
||||
use sp_runtime::traits::Header as HeaderT;
|
||||
use sp_runtime::RuntimeDebug;
|
||||
|
||||
/// Data required for initializing the bridge pallet.
|
||||
///
|
||||
/// The bridge needs to know where to start its sync from, and this provides that initial context.
|
||||
#[derive(Default, Encode, Decode, RuntimeDebug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub struct InitializationData<H: HeaderT> {
|
||||
/// The header from which we should start syncing.
|
||||
pub header: H,
|
||||
/// The initial authorities of the pallet.
|
||||
pub authority_list: AuthorityList,
|
||||
/// The ID of the initial authority set.
|
||||
pub set_id: SetId,
|
||||
/// The first scheduled authority set change of the pallet.
|
||||
pub scheduled_change: Option<ScheduledChange<H::Number>>,
|
||||
/// Should the pallet block transaction immediately after initialization.
|
||||
pub is_halted: bool,
|
||||
}
|
||||
|
||||
/// Keeps track of when the next GRANDPA authority set change will occur.
|
||||
#[derive(Default, Encode, Decode, RuntimeDebug, PartialEq, Clone)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub struct ScheduledChange<N> {
|
||||
/// The authority set that will be used once this change is enacted.
|
||||
pub authority_set: AuthoritySet,
|
||||
/// The block height at which the authority set should be enacted.
|
||||
///
|
||||
/// Note: It will only be enacted once a header at this height is finalized.
|
||||
pub height: N,
|
||||
}
|
||||
|
||||
/// A more useful representation of a header for storage purposes.
|
||||
#[derive(Default, Encode, Decode, Clone, RuntimeDebug, PartialEq)]
|
||||
pub struct ImportedHeader<H: HeaderT> {
|
||||
/// A plain Substrate header.
|
||||
pub header: H,
|
||||
/// Does this header enact a new authority set change. If it does
|
||||
/// then it will require a justification.
|
||||
pub requires_justification: bool,
|
||||
/// Has this header been finalized, either explicitly via a justification,
|
||||
/// or implicitly via one of its children getting finalized.
|
||||
pub is_finalized: bool,
|
||||
/// The hash of the header which scheduled a change on this fork. If there are currently
|
||||
/// not pending changes on this fork this will be empty.
|
||||
pub signal_hash: Option<H::Hash>,
|
||||
}
|
||||
|
||||
impl<H: HeaderT> core::ops::Deref for ImportedHeader<H> {
|
||||
type Target = H;
|
||||
|
||||
fn deref(&self) -> &H {
|
||||
&self.header
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
// Copyright 2019-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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
// TODO: remove on actual use
|
||||
#![allow(dead_code)]
|
||||
|
||||
//! Logic for checking Substrate storage proofs.
|
||||
|
||||
use hash_db::{HashDB, Hasher, EMPTY_PREFIX};
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::vec::Vec;
|
||||
use sp_trie::{read_trie_value, Layout, MemoryDB, StorageProof};
|
||||
|
||||
/// This struct is used to read storage values from a subset of a Merklized database. The "proof"
|
||||
/// is a subset of the nodes in the Merkle structure of the database, so that it provides
|
||||
/// authentication against a known Merkle root as well as the values in the database themselves.
|
||||
pub struct StorageProofChecker<H>
|
||||
where
|
||||
H: Hasher,
|
||||
{
|
||||
root: H::Out,
|
||||
db: MemoryDB<H>,
|
||||
}
|
||||
|
||||
impl<H> StorageProofChecker<H>
|
||||
where
|
||||
H: Hasher,
|
||||
{
|
||||
/// Constructs a new storage proof checker.
|
||||
///
|
||||
/// This returns an error if the given proof is invalid with respect to the given root.
|
||||
pub fn new(root: H::Out, proof: StorageProof) -> Result<Self, Error> {
|
||||
let db = proof.into_memory_db();
|
||||
if !db.contains(&root, EMPTY_PREFIX) {
|
||||
return Err(Error::StorageRootMismatch);
|
||||
}
|
||||
|
||||
let checker = StorageProofChecker { root, db };
|
||||
Ok(checker)
|
||||
}
|
||||
|
||||
/// Reads a value from the available subset of storage. If the value cannot be read due to an
|
||||
/// incomplete or otherwise invalid proof, this returns an error.
|
||||
pub fn read_value(&self, key: &[u8]) -> Result<Option<Vec<u8>>, Error> {
|
||||
read_trie_value::<Layout<H>, _>(&self.db, &self.root, key).map_err(|_| Error::StorageValueUnavailable)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(RuntimeDebug, PartialEq)]
|
||||
pub enum Error {
|
||||
StorageRootMismatch,
|
||||
StorageValueUnavailable,
|
||||
}
|
||||
|
||||
impl<T: crate::Config> From<Error> for crate::Error<T> {
|
||||
fn from(error: Error) -> Self {
|
||||
match error {
|
||||
Error::StorageRootMismatch => crate::Error::StorageRootMismatch,
|
||||
Error::StorageValueUnavailable => crate::Error::StorageValueUnavailable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
|
||||
use sp_core::{Blake2Hasher, H256};
|
||||
use sp_state_machine::{backend::Backend, prove_read, InMemoryBackend};
|
||||
|
||||
/// Return valid storage proof and state root.
|
||||
pub fn craft_valid_storage_proof() -> (H256, StorageProof) {
|
||||
// construct storage proof
|
||||
let backend = <InMemoryBackend<Blake2Hasher>>::from(vec![
|
||||
(None, vec![(b"key1".to_vec(), Some(b"value1".to_vec()))]),
|
||||
(None, vec![(b"key2".to_vec(), Some(b"value2".to_vec()))]),
|
||||
(None, vec![(b"key3".to_vec(), Some(b"value3".to_vec()))]),
|
||||
// Value is too big to fit in a branch node
|
||||
(None, vec![(b"key11".to_vec(), Some(vec![0u8; 32]))]),
|
||||
]);
|
||||
let root = backend.storage_root(std::iter::empty()).0;
|
||||
let proof = StorageProof::new(
|
||||
prove_read(backend, &[&b"key1"[..], &b"key2"[..], &b"key22"[..]])
|
||||
.unwrap()
|
||||
.iter_nodes()
|
||||
.collect(),
|
||||
);
|
||||
|
||||
(root, proof)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn storage_proof_check() {
|
||||
let (root, proof) = craft_valid_storage_proof();
|
||||
|
||||
// check proof in runtime
|
||||
let checker = <StorageProofChecker<Blake2Hasher>>::new(root, proof.clone()).unwrap();
|
||||
assert_eq!(checker.read_value(b"key1"), Ok(Some(b"value1".to_vec())));
|
||||
assert_eq!(checker.read_value(b"key2"), Ok(Some(b"value2".to_vec())));
|
||||
assert_eq!(checker.read_value(b"key11111"), Err(Error::StorageValueUnavailable));
|
||||
assert_eq!(checker.read_value(b"key22"), Ok(None));
|
||||
|
||||
// checking proof against invalid commitment fails
|
||||
assert_eq!(
|
||||
<StorageProofChecker<Blake2Hasher>>::new(H256::random(), proof).err(),
|
||||
Some(Error::StorageRootMismatch)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,871 @@
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! The verifier's role is to check the validity of headers being imported, and also determine if
|
||||
//! they can be finalized.
|
||||
//!
|
||||
//! When importing headers it performs checks to ensure that no invariants are broken (like
|
||||
//! importing the same header twice). When it imports finality proofs it will ensure that the proof
|
||||
//! has been signed off by the correct GRANDPA authorities, and also enact any authority set changes
|
||||
//! if required.
|
||||
|
||||
use crate::storage::{ImportedHeader, ScheduledChange};
|
||||
use crate::BridgeStorage;
|
||||
|
||||
use bp_header_chain::{justification::verify_justification, AuthoritySet};
|
||||
use finality_grandpa::voter_set::VoterSet;
|
||||
use sp_finality_grandpa::{ConsensusLog, GRANDPA_ENGINE_ID};
|
||||
use sp_runtime::generic::OpaqueDigestItemId;
|
||||
use sp_runtime::traits::{CheckedAdd, Header as HeaderT, One};
|
||||
use sp_runtime::RuntimeDebug;
|
||||
use sp_std::{prelude::Vec, vec};
|
||||
|
||||
/// The finality proof used by the pallet.
|
||||
///
|
||||
/// For a Substrate based chain using GRANDPA this will
|
||||
/// be an encoded GRANDPA Justification.
|
||||
#[derive(RuntimeDebug)]
|
||||
pub struct FinalityProof(Vec<u8>);
|
||||
|
||||
impl From<&[u8]> for FinalityProof {
|
||||
fn from(proof: &[u8]) -> Self {
|
||||
Self(proof.to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for FinalityProof {
|
||||
fn from(proof: Vec<u8>) -> Self {
|
||||
Self(proof)
|
||||
}
|
||||
}
|
||||
|
||||
/// Errors which can happen while importing a header.
|
||||
#[derive(RuntimeDebug, PartialEq)]
|
||||
pub enum ImportError {
|
||||
/// This header is at the same height or older than our latest finalized block, thus not useful.
|
||||
OldHeader,
|
||||
/// This header has already been imported by the pallet.
|
||||
HeaderAlreadyExists,
|
||||
/// We're missing a parent for this header.
|
||||
MissingParent,
|
||||
/// The number of the header does not follow its parent's number.
|
||||
InvalidChildNumber,
|
||||
/// The height of the next authority set change overflowed.
|
||||
ScheduledHeightOverflow,
|
||||
/// Received an authority set which was invalid in some way, such as
|
||||
/// the authority weights being empty or overflowing the `AuthorityWeight`
|
||||
/// type.
|
||||
InvalidAuthoritySet,
|
||||
/// This header is not allowed to be imported since an ancestor requires a finality proof.
|
||||
///
|
||||
/// This can happen if an ancestor is supposed to enact an authority set change.
|
||||
AwaitingFinalityProof,
|
||||
/// This header schedules an authority set change even though we're still waiting
|
||||
/// for an old authority set change to be enacted on this fork.
|
||||
PendingAuthoritySetChange,
|
||||
}
|
||||
|
||||
/// Errors which can happen while verifying a headers finality.
|
||||
#[derive(RuntimeDebug, PartialEq)]
|
||||
pub enum FinalizationError {
|
||||
/// This header has never been imported by the pallet.
|
||||
UnknownHeader,
|
||||
/// Trying to prematurely import a justification
|
||||
PrematureJustification,
|
||||
/// We failed to verify this header's ancestry.
|
||||
AncestryCheckFailed,
|
||||
/// This header is at the same height or older than our latest finalized block, thus not useful.
|
||||
OldHeader,
|
||||
/// The given justification was not able to finalize the given header.
|
||||
///
|
||||
/// There are several reasons why this might happen, such as the justification being
|
||||
/// signed by the wrong authority set, being given alongside an unexpected header,
|
||||
/// or failing ancestry checks.
|
||||
InvalidJustification,
|
||||
}
|
||||
|
||||
/// Used to verify imported headers and their finality status.
|
||||
#[derive(RuntimeDebug)]
|
||||
pub struct Verifier<S> {
|
||||
pub storage: S,
|
||||
}
|
||||
|
||||
impl<S, H> Verifier<S>
|
||||
where
|
||||
S: BridgeStorage<Header = H>,
|
||||
H: HeaderT,
|
||||
H::Number: finality_grandpa::BlockNumberOps,
|
||||
{
|
||||
/// Import a header to the pallet.
|
||||
///
|
||||
/// Will perform some basic checks to make sure that this header doesn't break any assumptions
|
||||
/// such as being on a different finalized fork.
|
||||
pub fn import_header(&mut self, hash: H::Hash, header: H) -> Result<(), ImportError> {
|
||||
let best_finalized = self.storage.best_finalized_header();
|
||||
|
||||
if header.number() <= best_finalized.number() {
|
||||
return Err(ImportError::OldHeader);
|
||||
}
|
||||
|
||||
if self.storage.header_exists(hash) {
|
||||
return Err(ImportError::HeaderAlreadyExists);
|
||||
}
|
||||
|
||||
let parent_header = self
|
||||
.storage
|
||||
.header_by_hash(*header.parent_hash())
|
||||
.ok_or(ImportError::MissingParent)?;
|
||||
|
||||
let parent_number = *parent_header.number();
|
||||
if parent_number + One::one() != *header.number() {
|
||||
return Err(ImportError::InvalidChildNumber);
|
||||
}
|
||||
|
||||
// A header requires a justification if it enacts an authority set change. We don't
|
||||
// need to act on it right away (we'll update the set once the header gets finalized), but
|
||||
// we need to make a note of it.
|
||||
//
|
||||
// Note: This assumes that we can only have one authority set change pending per fork at a
|
||||
// time. While this is not strictly true of GRANDPA (it can have multiple pending changes,
|
||||
// even across forks), this assumption simplifies our tracking of authority set changes.
|
||||
let mut signal_hash = parent_header.signal_hash;
|
||||
let scheduled_change = find_scheduled_change(&header);
|
||||
|
||||
// Check if our fork is expecting an authority set change
|
||||
let requires_justification = if let Some(hash) = signal_hash {
|
||||
const PROOF: &str = "If the header has a signal hash it means there's an accompanying set
|
||||
change in storage, therefore this must always be valid.";
|
||||
let pending_change = self.storage.scheduled_set_change(hash).expect(PROOF);
|
||||
|
||||
if scheduled_change.is_some() {
|
||||
return Err(ImportError::PendingAuthoritySetChange);
|
||||
}
|
||||
|
||||
if *header.number() > pending_change.height {
|
||||
return Err(ImportError::AwaitingFinalityProof);
|
||||
}
|
||||
|
||||
pending_change.height == *header.number()
|
||||
} else {
|
||||
// Since we don't currently have a pending authority set change let's check if the header
|
||||
// contains a log indicating when the next change should be.
|
||||
if let Some(change) = scheduled_change {
|
||||
let mut total_weight = 0u64;
|
||||
|
||||
for (_id, weight) in &change.next_authorities {
|
||||
total_weight = total_weight
|
||||
.checked_add(*weight)
|
||||
.ok_or(ImportError::InvalidAuthoritySet)?;
|
||||
}
|
||||
|
||||
// If none of the authorities have a weight associated with them the
|
||||
// set is essentially empty. We don't want that.
|
||||
if total_weight == 0 {
|
||||
return Err(ImportError::InvalidAuthoritySet);
|
||||
}
|
||||
|
||||
let next_set = AuthoritySet {
|
||||
authorities: change.next_authorities,
|
||||
set_id: self.storage.current_authority_set().set_id + 1,
|
||||
};
|
||||
|
||||
let height = (*header.number())
|
||||
.checked_add(&change.delay)
|
||||
.ok_or(ImportError::ScheduledHeightOverflow)?;
|
||||
|
||||
let scheduled_change = ScheduledChange {
|
||||
authority_set: next_set,
|
||||
height,
|
||||
};
|
||||
|
||||
// Note: It's important that the signal hash is updated if a header schedules a
|
||||
// change or else we end up with inconsistencies in other places.
|
||||
signal_hash = Some(hash);
|
||||
self.storage.schedule_next_set_change(hash, scheduled_change);
|
||||
|
||||
// If the delay is 0 this header will enact the change it signaled
|
||||
height == *header.number()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
};
|
||||
|
||||
self.storage.write_header(&ImportedHeader {
|
||||
header,
|
||||
requires_justification,
|
||||
is_finalized: false,
|
||||
signal_hash,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Verify that a previously imported header can be finalized with the given GRANDPA finality
|
||||
/// proof. If the header enacts an authority set change the change will be applied once the
|
||||
/// header has been finalized.
|
||||
pub fn import_finality_proof(&mut self, hash: H::Hash, proof: FinalityProof) -> Result<(), FinalizationError> {
|
||||
// Make sure that we've previously imported this header
|
||||
let header = self
|
||||
.storage
|
||||
.header_by_hash(hash)
|
||||
.ok_or(FinalizationError::UnknownHeader)?;
|
||||
|
||||
// We don't want to finalize an ancestor of an already finalized
|
||||
// header, this would be inconsistent
|
||||
let last_finalized = self.storage.best_finalized_header();
|
||||
if header.number() <= last_finalized.number() {
|
||||
return Err(FinalizationError::OldHeader);
|
||||
}
|
||||
|
||||
let current_authority_set = self.storage.current_authority_set();
|
||||
let voter_set = VoterSet::new(current_authority_set.authorities).expect(
|
||||
"We verified the correctness of the authority list during header import,
|
||||
before writing them to storage. This must always be valid.",
|
||||
);
|
||||
verify_justification::<H>(
|
||||
(hash, *header.number()),
|
||||
current_authority_set.set_id,
|
||||
voter_set,
|
||||
&proof.0,
|
||||
)
|
||||
.map_err(|_| FinalizationError::InvalidJustification)?;
|
||||
frame_support::debug::trace!("Received valid justification for {:?}", header);
|
||||
|
||||
frame_support::debug::trace!(
|
||||
"Checking ancestry for headers between {:?} and {:?}",
|
||||
last_finalized,
|
||||
header
|
||||
);
|
||||
let mut finalized_headers =
|
||||
if let Some(ancestors) = headers_between(&self.storage, last_finalized, header.clone()) {
|
||||
// Since we only try and finalize headers with a height strictly greater
|
||||
// than `best_finalized` if `headers_between` returns Some we must have
|
||||
// at least one element. If we don't something's gone wrong, so best
|
||||
// to die before we write to storage.
|
||||
assert_eq!(
|
||||
ancestors.is_empty(),
|
||||
false,
|
||||
"Empty ancestry list returned from `headers_between()`",
|
||||
);
|
||||
|
||||
// Check if any of our ancestors `requires_justification` a.k.a schedule authority
|
||||
// set changes. If they're still waiting to be finalized we must reject this
|
||||
// justification. We don't include our current header in this check.
|
||||
//
|
||||
// We do this because it is important to to import justifications _in order_,
|
||||
// otherwise we risk finalizing headers on competing chains.
|
||||
let requires_justification = ancestors.iter().skip(1).find(|h| h.requires_justification);
|
||||
if requires_justification.is_some() {
|
||||
return Err(FinalizationError::PrematureJustification);
|
||||
}
|
||||
|
||||
ancestors
|
||||
} else {
|
||||
return Err(FinalizationError::AncestryCheckFailed);
|
||||
};
|
||||
|
||||
// If the current header was marked as `requires_justification` it means that it enacts a
|
||||
// new authority set change. When we finalize the header we need to update the current
|
||||
// authority set.
|
||||
if header.requires_justification {
|
||||
const SIGNAL_HASH_PROOF: &str = "When we import a header we only mark it as
|
||||
`requires_justification` if we have checked that it contains a signal hash. Therefore
|
||||
this must always be valid.";
|
||||
|
||||
const ENACT_SET_PROOF: &str =
|
||||
"Headers must only be marked as `requires_justification` if there's a scheduled change in storage.";
|
||||
|
||||
// If we are unable to enact an authority set it means our storage entry for scheduled
|
||||
// changes is missing. Best to crash since this is likely a bug.
|
||||
let _ = self
|
||||
.storage
|
||||
.enact_authority_set(header.signal_hash.expect(SIGNAL_HASH_PROOF))
|
||||
.expect(ENACT_SET_PROOF);
|
||||
}
|
||||
|
||||
for header in finalized_headers.iter_mut() {
|
||||
header.is_finalized = true;
|
||||
header.requires_justification = false;
|
||||
header.signal_hash = None;
|
||||
self.storage.write_header(header);
|
||||
}
|
||||
|
||||
self.storage.update_best_finalized(hash);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the lineage of headers between [child, ancestor)
|
||||
fn headers_between<S, H>(
|
||||
storage: &S,
|
||||
ancestor: ImportedHeader<H>,
|
||||
child: ImportedHeader<H>,
|
||||
) -> Option<Vec<ImportedHeader<H>>>
|
||||
where
|
||||
S: BridgeStorage<Header = H>,
|
||||
H: HeaderT,
|
||||
{
|
||||
let mut ancestors = vec![];
|
||||
let mut current_header = child;
|
||||
|
||||
while ancestor.hash() != current_header.hash() {
|
||||
// We've gotten to the same height and we're not related
|
||||
if ancestor.number() >= current_header.number() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let parent = storage.header_by_hash(*current_header.parent_hash());
|
||||
ancestors.push(current_header);
|
||||
current_header = match parent {
|
||||
Some(h) => h,
|
||||
None => return None,
|
||||
}
|
||||
}
|
||||
|
||||
Some(ancestors)
|
||||
}
|
||||
|
||||
pub(crate) fn find_scheduled_change<H: HeaderT>(header: &H) -> Option<sp_finality_grandpa::ScheduledChange<H::Number>> {
|
||||
let id = OpaqueDigestItemId::Consensus(&GRANDPA_ENGINE_ID);
|
||||
|
||||
let filter_log = |log: ConsensusLog<H::Number>| match log {
|
||||
ConsensusLog::ScheduledChange(change) => Some(change),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// find the first consensus digest with the right ID which converts to
|
||||
// the right kind of consensus log.
|
||||
header.digest().convert_first(|l| l.try_to(id).and_then(filter_log))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mock::*;
|
||||
use crate::{BestFinalized, BestHeight, HeaderId, ImportedHeaders, PalletStorage};
|
||||
use bp_test_utils::{alice, authority_list, bob, make_justification_for_header};
|
||||
use codec::Encode;
|
||||
use frame_support::{assert_err, assert_ok};
|
||||
use frame_support::{StorageMap, StorageValue};
|
||||
use sp_finality_grandpa::{AuthorityId, SetId};
|
||||
use sp_runtime::{Digest, DigestItem};
|
||||
|
||||
fn schedule_next_change(
|
||||
authorities: Vec<AuthorityId>,
|
||||
set_id: SetId,
|
||||
height: TestNumber,
|
||||
) -> ScheduledChange<TestNumber> {
|
||||
let authorities = authorities.into_iter().map(|id| (id, 1u64)).collect();
|
||||
let authority_set = AuthoritySet::new(authorities, set_id);
|
||||
ScheduledChange { authority_set, height }
|
||||
}
|
||||
|
||||
// Useful for quickly writing a chain of headers to storage
|
||||
// Input is expected in the form: vec![(num, requires_justification, is_finalized)]
|
||||
fn write_headers<S: BridgeStorage<Header = TestHeader>>(
|
||||
storage: &mut S,
|
||||
headers: Vec<(u64, bool, bool)>,
|
||||
) -> Vec<ImportedHeader<TestHeader>> {
|
||||
let mut imported_headers = vec![];
|
||||
let genesis = ImportedHeader {
|
||||
header: test_header(0),
|
||||
requires_justification: false,
|
||||
is_finalized: true,
|
||||
signal_hash: None,
|
||||
};
|
||||
|
||||
<BestFinalized<TestRuntime>>::put(genesis.hash());
|
||||
storage.write_header(&genesis);
|
||||
imported_headers.push(genesis);
|
||||
|
||||
for (num, requires_justification, is_finalized) in headers {
|
||||
let header = ImportedHeader {
|
||||
header: test_header(num),
|
||||
requires_justification,
|
||||
is_finalized,
|
||||
signal_hash: None,
|
||||
};
|
||||
|
||||
storage.write_header(&header);
|
||||
imported_headers.push(header);
|
||||
}
|
||||
|
||||
imported_headers
|
||||
}
|
||||
|
||||
// Given a block number will generate a chain of headers which don't require justification and
|
||||
// are not considered to be finalized.
|
||||
fn write_default_headers<S: BridgeStorage<Header = TestHeader>>(
|
||||
storage: &mut S,
|
||||
headers: Vec<u64>,
|
||||
) -> Vec<ImportedHeader<TestHeader>> {
|
||||
let headers = headers.iter().map(|num| (*num, false, false)).collect();
|
||||
write_headers(storage, headers)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_import_old_header() {
|
||||
run_test(|| {
|
||||
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||
let parent = unfinalized_header(5);
|
||||
storage.write_header(&parent);
|
||||
storage.update_best_finalized(parent.hash());
|
||||
|
||||
let header = test_header(1);
|
||||
let mut verifier = Verifier { storage };
|
||||
assert_err!(verifier.import_header(header.hash(), header), ImportError::OldHeader);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_import_header_without_parent() {
|
||||
run_test(|| {
|
||||
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||
let parent = unfinalized_header(1);
|
||||
storage.write_header(&parent);
|
||||
storage.update_best_finalized(parent.hash());
|
||||
|
||||
// By default the parent is `0x00`
|
||||
let header = TestHeader::new_from_number(2);
|
||||
|
||||
let mut verifier = Verifier { storage };
|
||||
assert_err!(
|
||||
verifier.import_header(header.hash(), header),
|
||||
ImportError::MissingParent
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_import_header_twice() {
|
||||
run_test(|| {
|
||||
let storage = PalletStorage::<TestRuntime>::new();
|
||||
let header = test_header(1);
|
||||
<BestFinalized<TestRuntime>>::put(header.hash());
|
||||
|
||||
let imported_header = ImportedHeader {
|
||||
header: header.clone(),
|
||||
requires_justification: false,
|
||||
is_finalized: false,
|
||||
signal_hash: None,
|
||||
};
|
||||
<ImportedHeaders<TestRuntime>>::insert(header.hash(), &imported_header);
|
||||
|
||||
let mut verifier = Verifier { storage };
|
||||
assert_err!(verifier.import_header(header.hash(), header), ImportError::OldHeader);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn succesfully_imports_valid_but_unfinalized_header() {
|
||||
run_test(|| {
|
||||
let storage = PalletStorage::<TestRuntime>::new();
|
||||
let parent = test_header(1);
|
||||
let parent_hash = parent.hash();
|
||||
<BestFinalized<TestRuntime>>::put(parent.hash());
|
||||
|
||||
let imported_header = ImportedHeader {
|
||||
header: parent,
|
||||
requires_justification: false,
|
||||
is_finalized: true,
|
||||
signal_hash: None,
|
||||
};
|
||||
<ImportedHeaders<TestRuntime>>::insert(parent_hash, &imported_header);
|
||||
|
||||
let header = test_header(2);
|
||||
let mut verifier = Verifier {
|
||||
storage: storage.clone(),
|
||||
};
|
||||
assert_ok!(verifier.import_header(header.hash(), header.clone()));
|
||||
|
||||
let stored_header = storage
|
||||
.header_by_hash(header.hash())
|
||||
.expect("Should have been imported successfully");
|
||||
assert_eq!(stored_header.is_finalized, false);
|
||||
assert_eq!(stored_header.hash(), storage.best_headers()[0].hash);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn successfully_imports_two_different_headers_at_same_height() {
|
||||
run_test(|| {
|
||||
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||
|
||||
// We want to write the genesis header to storage
|
||||
let _ = write_headers(&mut storage, vec![]);
|
||||
|
||||
// Both of these headers have the genesis header as their parent
|
||||
let header_on_fork1 = test_header(1);
|
||||
let mut header_on_fork2 = test_header(1);
|
||||
|
||||
// We need to change _something_ to make it a different header
|
||||
header_on_fork2.state_root = [1; 32].into();
|
||||
|
||||
let mut verifier = Verifier {
|
||||
storage: storage.clone(),
|
||||
};
|
||||
|
||||
// It should be fine to import both
|
||||
assert_ok!(verifier.import_header(header_on_fork1.hash(), header_on_fork1.clone()));
|
||||
assert_ok!(verifier.import_header(header_on_fork2.hash(), header_on_fork2.clone()));
|
||||
|
||||
// We should have two headers marked as being the best since they're
|
||||
// both at the same height
|
||||
let best_headers = storage.best_headers();
|
||||
assert_eq!(best_headers.len(), 2);
|
||||
assert_eq!(
|
||||
best_headers[0],
|
||||
HeaderId {
|
||||
number: *header_on_fork1.number(),
|
||||
hash: header_on_fork1.hash()
|
||||
}
|
||||
);
|
||||
assert_eq!(
|
||||
best_headers[1],
|
||||
HeaderId {
|
||||
number: *header_on_fork2.number(),
|
||||
hash: header_on_fork2.hash()
|
||||
}
|
||||
);
|
||||
assert_eq!(<BestHeight<TestRuntime>>::get(), 1);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correctly_updates_the_best_header_given_a_better_header() {
|
||||
run_test(|| {
|
||||
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||
|
||||
// We want to write the genesis header to storage
|
||||
let _ = write_headers(&mut storage, vec![]);
|
||||
|
||||
// Write two headers at the same height to storage.
|
||||
let best_header = test_header(1);
|
||||
let mut also_best_header = test_header(1);
|
||||
|
||||
// We need to change _something_ to make it a different header
|
||||
also_best_header.state_root = [1; 32].into();
|
||||
|
||||
let mut verifier = Verifier {
|
||||
storage: storage.clone(),
|
||||
};
|
||||
|
||||
// It should be fine to import both
|
||||
assert_ok!(verifier.import_header(best_header.hash(), best_header.clone()));
|
||||
assert_ok!(verifier.import_header(also_best_header.hash(), also_best_header));
|
||||
|
||||
// The headers we manually imported should have been marked as the best
|
||||
// upon writing to storage. Let's confirm that.
|
||||
assert_eq!(storage.best_headers().len(), 2);
|
||||
assert_eq!(<BestHeight<TestRuntime>>::get(), 1);
|
||||
|
||||
// Now let's build something at a better height.
|
||||
let mut better_header = test_header(2);
|
||||
better_header.parent_hash = best_header.hash();
|
||||
|
||||
assert_ok!(verifier.import_header(better_header.hash(), better_header.clone()));
|
||||
|
||||
// Since `better_header` is the only one at height = 2 we should only have
|
||||
// a single "best header" now.
|
||||
let best_headers = storage.best_headers();
|
||||
assert_eq!(best_headers.len(), 1);
|
||||
assert_eq!(
|
||||
best_headers[0],
|
||||
HeaderId {
|
||||
number: *better_header.number(),
|
||||
hash: better_header.hash()
|
||||
}
|
||||
);
|
||||
assert_eq!(<BestHeight<TestRuntime>>::get(), 2);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doesnt_write_best_header_twice_upon_finalization() {
|
||||
run_test(|| {
|
||||
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||
let _imported_headers = write_default_headers(&mut storage, vec![1]);
|
||||
|
||||
let set_id = 1;
|
||||
let authorities = authority_list();
|
||||
let initial_authority_set = AuthoritySet::new(authorities.clone(), set_id);
|
||||
storage.update_current_authority_set(initial_authority_set);
|
||||
|
||||
// Let's import our header
|
||||
let header = test_header(2);
|
||||
let mut verifier = Verifier {
|
||||
storage: storage.clone(),
|
||||
};
|
||||
assert_ok!(verifier.import_header(header.hash(), header.clone()));
|
||||
|
||||
// Our header should be the only best header we have
|
||||
assert_eq!(storage.best_headers()[0].hash, header.hash());
|
||||
assert_eq!(storage.best_headers().len(), 1);
|
||||
|
||||
// Now lets finalize our best header
|
||||
let grandpa_round = 1;
|
||||
let justification = make_justification_for_header(&header, grandpa_round, set_id, &authorities).encode();
|
||||
assert_ok!(verifier.import_finality_proof(header.hash(), justification.into()));
|
||||
|
||||
// Our best header should only appear once in the list of best headers
|
||||
assert_eq!(storage.best_headers()[0].hash, header.hash());
|
||||
assert_eq!(storage.best_headers().len(), 1);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn related_headers_are_ancestors() {
|
||||
run_test(|| {
|
||||
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||
let mut imported_headers = write_default_headers(&mut storage, vec![1, 2, 3]);
|
||||
|
||||
for header in imported_headers.iter() {
|
||||
assert!(storage.header_exists(header.hash()));
|
||||
}
|
||||
|
||||
let ancestor = imported_headers.remove(0);
|
||||
let child = imported_headers.pop().unwrap();
|
||||
let ancestors = headers_between(&storage, ancestor, child);
|
||||
|
||||
assert!(ancestors.is_some());
|
||||
assert_eq!(ancestors.unwrap().len(), 3);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unrelated_headers_are_not_ancestors() {
|
||||
run_test(|| {
|
||||
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||
|
||||
let mut imported_headers = write_default_headers(&mut storage, vec![1, 2, 3]);
|
||||
for header in imported_headers.iter() {
|
||||
assert!(storage.header_exists(header.hash()));
|
||||
}
|
||||
|
||||
// Need to give it a different parent_hash or else it'll be
|
||||
// related to our test genesis header
|
||||
let mut bad_ancestor = test_header(0);
|
||||
bad_ancestor.parent_hash = [1u8; 32].into();
|
||||
let bad_ancestor = ImportedHeader {
|
||||
header: bad_ancestor,
|
||||
requires_justification: false,
|
||||
is_finalized: false,
|
||||
signal_hash: None,
|
||||
};
|
||||
|
||||
let child = imported_headers.pop().unwrap();
|
||||
let ancestors = headers_between(&storage, bad_ancestor, child);
|
||||
assert!(ancestors.is_none());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ancestor_newer_than_child_is_not_related() {
|
||||
run_test(|| {
|
||||
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||
|
||||
let mut imported_headers = write_default_headers(&mut storage, vec![1, 2, 3]);
|
||||
for header in imported_headers.iter() {
|
||||
assert!(storage.header_exists(header.hash()));
|
||||
}
|
||||
|
||||
// What if we have an "ancestor" that's newer than child?
|
||||
let new_ancestor = test_header(5);
|
||||
let new_ancestor = ImportedHeader {
|
||||
header: new_ancestor,
|
||||
requires_justification: false,
|
||||
is_finalized: false,
|
||||
signal_hash: None,
|
||||
};
|
||||
|
||||
let child = imported_headers.pop().unwrap();
|
||||
let ancestors = headers_between(&storage, new_ancestor, child);
|
||||
assert!(ancestors.is_none());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doesnt_import_header_which_schedules_change_with_invalid_authority_set() {
|
||||
run_test(|| {
|
||||
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||
let _imported_headers = write_default_headers(&mut storage, vec![1]);
|
||||
let mut header = test_header(2);
|
||||
|
||||
// This is an *invalid* authority set because the combined weight of the
|
||||
// authorities is greater than `u64::MAX`
|
||||
let consensus_log = ConsensusLog::<TestNumber>::ScheduledChange(sp_finality_grandpa::ScheduledChange {
|
||||
next_authorities: vec![(alice(), u64::MAX), (bob(), u64::MAX)],
|
||||
delay: 0,
|
||||
});
|
||||
|
||||
header.digest = Digest::<TestHash> {
|
||||
logs: vec![DigestItem::Consensus(GRANDPA_ENGINE_ID, consensus_log.encode())],
|
||||
};
|
||||
|
||||
let mut verifier = Verifier { storage };
|
||||
|
||||
assert_eq!(
|
||||
verifier.import_header(header.hash(), header).unwrap_err(),
|
||||
ImportError::InvalidAuthoritySet
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn finalizes_header_which_doesnt_enact_or_schedule_a_new_authority_set() {
|
||||
run_test(|| {
|
||||
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||
let _imported_headers = write_default_headers(&mut storage, vec![1]);
|
||||
|
||||
// Nothing special about this header, yet GRANDPA may have created a justification
|
||||
// for it since it does that periodically
|
||||
let header = test_header(2);
|
||||
|
||||
let set_id = 1;
|
||||
let authorities = authority_list();
|
||||
let authority_set = AuthoritySet::new(authorities.clone(), set_id);
|
||||
storage.update_current_authority_set(authority_set);
|
||||
|
||||
// We'll need this justification to finalize the header
|
||||
let grandpa_round = 1;
|
||||
let justification = make_justification_for_header(&header, grandpa_round, set_id, &authorities).encode();
|
||||
|
||||
let mut verifier = Verifier {
|
||||
storage: storage.clone(),
|
||||
};
|
||||
|
||||
assert_ok!(verifier.import_header(header.hash(), header.clone()));
|
||||
assert_ok!(verifier.import_finality_proof(header.hash(), justification.into()));
|
||||
assert_eq!(storage.best_finalized_header().header, header);
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correctly_verifies_and_finalizes_chain_of_headers() {
|
||||
run_test(|| {
|
||||
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||
let imported_headers = write_default_headers(&mut storage, vec![1, 2]);
|
||||
let header = test_header(3);
|
||||
|
||||
let set_id = 1;
|
||||
let authorities = authority_list();
|
||||
let authority_set = AuthoritySet {
|
||||
authorities: authorities.clone(),
|
||||
set_id,
|
||||
};
|
||||
storage.update_current_authority_set(authority_set);
|
||||
|
||||
let grandpa_round = 1;
|
||||
let justification = make_justification_for_header(&header, grandpa_round, set_id, &authorities).encode();
|
||||
|
||||
let mut verifier = Verifier {
|
||||
storage: storage.clone(),
|
||||
};
|
||||
assert!(verifier.import_header(header.hash(), header.clone()).is_ok());
|
||||
assert!(verifier
|
||||
.import_finality_proof(header.hash(), justification.into())
|
||||
.is_ok());
|
||||
|
||||
// Make sure we marked the our headers as finalized
|
||||
assert!(storage.header_by_hash(imported_headers[1].hash()).unwrap().is_finalized);
|
||||
assert!(storage.header_by_hash(imported_headers[2].hash()).unwrap().is_finalized);
|
||||
assert!(storage.header_by_hash(header.hash()).unwrap().is_finalized);
|
||||
|
||||
// Make sure the header at the highest height is the best finalized
|
||||
assert_eq!(storage.best_finalized_header().header, header);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn updates_authority_set_upon_finalizing_header_which_enacts_change() {
|
||||
run_test(|| {
|
||||
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||
let genesis_hash = write_headers(&mut storage, vec![])[0].hash();
|
||||
|
||||
// We want this header to indicate that there's an upcoming set change on this fork
|
||||
let parent = ImportedHeader {
|
||||
header: test_header(1),
|
||||
requires_justification: false,
|
||||
is_finalized: false,
|
||||
signal_hash: Some(genesis_hash),
|
||||
};
|
||||
storage.write_header(&parent);
|
||||
|
||||
let set_id = 1;
|
||||
let authorities = authority_list();
|
||||
let initial_authority_set = AuthoritySet::new(authorities.clone(), set_id);
|
||||
storage.update_current_authority_set(initial_authority_set);
|
||||
|
||||
// This header enacts an authority set change upon finalization
|
||||
let header = test_header(2);
|
||||
|
||||
let grandpa_round = 1;
|
||||
let justification = make_justification_for_header(&header, grandpa_round, set_id, &authorities).encode();
|
||||
|
||||
// Schedule a change at the height of our header
|
||||
let set_id = 2;
|
||||
let height = *header.number();
|
||||
let authorities = vec![alice()];
|
||||
let change = schedule_next_change(authorities, set_id, height);
|
||||
storage.schedule_next_set_change(genesis_hash, change.clone());
|
||||
|
||||
let mut verifier = Verifier {
|
||||
storage: storage.clone(),
|
||||
};
|
||||
|
||||
assert_ok!(verifier.import_header(header.hash(), header.clone()));
|
||||
assert_eq!(storage.missing_justifications().len(), 1);
|
||||
assert_eq!(storage.missing_justifications()[0].hash, header.hash());
|
||||
|
||||
assert_ok!(verifier.import_finality_proof(header.hash(), justification.into()));
|
||||
assert_eq!(storage.best_finalized_header().header, header);
|
||||
|
||||
// Make sure that we have updated the set now that we've finalized our header
|
||||
assert_eq!(storage.current_authority_set(), change.authority_set);
|
||||
assert!(storage.missing_justifications().is_empty());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn importing_finality_proof_for_already_finalized_header_doesnt_work() {
|
||||
run_test(|| {
|
||||
let mut storage = PalletStorage::<TestRuntime>::new();
|
||||
let genesis = test_header(0);
|
||||
|
||||
let genesis = ImportedHeader {
|
||||
header: genesis,
|
||||
requires_justification: false,
|
||||
is_finalized: true,
|
||||
signal_hash: None,
|
||||
};
|
||||
|
||||
// Make sure that genesis is the best finalized header
|
||||
<BestFinalized<TestRuntime>>::put(genesis.hash());
|
||||
storage.write_header(&genesis);
|
||||
|
||||
let mut verifier = Verifier { storage };
|
||||
|
||||
// Now we want to try and import it again to see what happens
|
||||
assert_eq!(
|
||||
verifier
|
||||
.import_finality_proof(genesis.hash(), vec![4, 2].into())
|
||||
.unwrap_err(),
|
||||
FinalizationError::OldHeader
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user