mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-30 16:21:02 +00:00
Remove Substrate Pallet (#866)
* Remove `pallet-substrate-bridge` * Fix transfer call encoding test Co-authored-by: Tomasz Drwięga <tomasz@parity.io>
This commit is contained in:
committed by
Bastian Köcher
parent
cb90ea0979
commit
2f1c4c23fc
@@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
use bp_millau::derive_account_from_rialto_id;
|
use bp_millau::derive_account_from_rialto_id;
|
||||||
use millau_runtime::{
|
use millau_runtime::{
|
||||||
AccountId, AuraConfig, BalancesConfig, BridgeRialtoConfig, BridgeWestendGrandpaConfig, GenesisConfig,
|
AccountId, AuraConfig, BalancesConfig, BridgeWestendGrandpaConfig, GenesisConfig, GrandpaConfig, SessionConfig,
|
||||||
GrandpaConfig, SessionConfig, SessionKeys, Signature, SudoConfig, SystemConfig, WASM_BINARY,
|
SessionKeys, Signature, SudoConfig, SystemConfig, WASM_BINARY,
|
||||||
};
|
};
|
||||||
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
|
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
|
||||||
use sp_core::{sr25519, Pair, Public};
|
use sp_core::{sr25519, Pair, Public};
|
||||||
@@ -167,11 +167,6 @@ fn testnet_genesis(
|
|||||||
pallet_grandpa: Some(GrandpaConfig {
|
pallet_grandpa: Some(GrandpaConfig {
|
||||||
authorities: Vec::new(),
|
authorities: Vec::new(),
|
||||||
}),
|
}),
|
||||||
pallet_substrate_bridge: Some(BridgeRialtoConfig {
|
|
||||||
// We'll initialize the pallet with a dispatchable instead.
|
|
||||||
init_data: None,
|
|
||||||
owner: Some(root_key.clone()),
|
|
||||||
}),
|
|
||||||
pallet_sudo: Some(SudoConfig { key: root_key }),
|
pallet_sudo: Some(SudoConfig { key: root_key }),
|
||||||
pallet_session: Some(SessionConfig {
|
pallet_session: Some(SessionConfig {
|
||||||
keys: initial_authorities
|
keys: initial_authorities
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ pallet-bridge-dispatch = { path = "../../../modules/dispatch", default-features
|
|||||||
pallet-bridge-grandpa = { path = "../../../modules/grandpa", default-features = false }
|
pallet-bridge-grandpa = { path = "../../../modules/grandpa", default-features = false }
|
||||||
pallet-bridge-messages = { path = "../../../modules/messages", default-features = false }
|
pallet-bridge-messages = { path = "../../../modules/messages", default-features = false }
|
||||||
pallet-shift-session-manager = { path = "../../../modules/shift-session-manager", default-features = false }
|
pallet-shift-session-manager = { path = "../../../modules/shift-session-manager", default-features = false }
|
||||||
pallet-substrate-bridge = { path = "../../../modules/substrate", default-features = false }
|
|
||||||
|
|
||||||
# Substrate Dependencies
|
# Substrate Dependencies
|
||||||
|
|
||||||
@@ -83,7 +82,6 @@ std = [
|
|||||||
"pallet-randomness-collective-flip/std",
|
"pallet-randomness-collective-flip/std",
|
||||||
"pallet-session/std",
|
"pallet-session/std",
|
||||||
"pallet-shift-session-manager/std",
|
"pallet-shift-session-manager/std",
|
||||||
"pallet-substrate-bridge/std",
|
|
||||||
"pallet-sudo/std",
|
"pallet-sudo/std",
|
||||||
"pallet-timestamp/std",
|
"pallet-timestamp/std",
|
||||||
"pallet-transaction-payment-rpc-runtime-api/std",
|
"pallet-transaction-payment-rpc-runtime-api/std",
|
||||||
|
|||||||
@@ -65,7 +65,6 @@ pub use pallet_balances::Call as BalancesCall;
|
|||||||
pub use pallet_bridge_grandpa::Call as BridgeGrandpaRialtoCall;
|
pub use pallet_bridge_grandpa::Call as BridgeGrandpaRialtoCall;
|
||||||
pub use pallet_bridge_grandpa::Call as BridgeGrandpaWestendCall;
|
pub use pallet_bridge_grandpa::Call as BridgeGrandpaWestendCall;
|
||||||
pub use pallet_bridge_messages::Call as MessagesCall;
|
pub use pallet_bridge_messages::Call as MessagesCall;
|
||||||
pub use pallet_substrate_bridge::Call as BridgeRialtoCall;
|
|
||||||
pub use pallet_sudo::Call as SudoCall;
|
pub use pallet_sudo::Call as SudoCall;
|
||||||
pub use pallet_timestamp::Call as TimestampCall;
|
pub use pallet_timestamp::Call as TimestampCall;
|
||||||
|
|
||||||
@@ -301,10 +300,6 @@ impl pallet_session::Config for Runtime {
|
|||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
impl pallet_substrate_bridge::Config for Runtime {
|
|
||||||
type BridgedChain = bp_rialto::Rialto;
|
|
||||||
}
|
|
||||||
|
|
||||||
parameter_types! {
|
parameter_types! {
|
||||||
// This is a pretty unscientific cap.
|
// This is a pretty unscientific cap.
|
||||||
//
|
//
|
||||||
@@ -383,7 +378,6 @@ construct_runtime!(
|
|||||||
NodeBlock = opaque::Block,
|
NodeBlock = opaque::Block,
|
||||||
UncheckedExtrinsic = UncheckedExtrinsic
|
UncheckedExtrinsic = UncheckedExtrinsic
|
||||||
{
|
{
|
||||||
BridgeRialto: pallet_substrate_bridge::{Module, Call, Storage, Config<T>},
|
|
||||||
BridgeRialtoMessages: pallet_bridge_messages::{Module, Call, Storage, Event<T>},
|
BridgeRialtoMessages: pallet_bridge_messages::{Module, Call, Storage, Event<T>},
|
||||||
BridgeDispatch: pallet_bridge_dispatch::{Module, Event<T>},
|
BridgeDispatch: pallet_bridge_dispatch::{Module, Event<T>},
|
||||||
BridgeRialtoGrandpa: pallet_bridge_grandpa::{Module, Call, Storage},
|
BridgeRialtoGrandpa: pallet_bridge_grandpa::{Module, Call, Storage},
|
||||||
@@ -563,29 +557,6 @@ impl_runtime_apis! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl bp_rialto::RialtoHeaderApi<Block> for Runtime {
|
|
||||||
fn best_blocks() -> Vec<(bp_rialto::BlockNumber, bp_rialto::Hash)> {
|
|
||||||
BridgeRialto::best_headers()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finalized_block() -> (bp_rialto::BlockNumber, bp_rialto::Hash) {
|
|
||||||
let header = BridgeRialto::best_finalized();
|
|
||||||
(header.number, header.hash())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn incomplete_headers() -> Vec<(bp_rialto::BlockNumber, bp_rialto::Hash)> {
|
|
||||||
BridgeRialto::require_justifications()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_known_block(hash: bp_rialto::Hash) -> bool {
|
|
||||||
BridgeRialto::is_known_header(hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_finalized_block(hash: bp_rialto::Hash) -> bool {
|
|
||||||
BridgeRialto::is_finalized_header(hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl bp_rialto::RialtoFinalityApi<Block> for Runtime {
|
impl bp_rialto::RialtoFinalityApi<Block> for Runtime {
|
||||||
fn best_finalized() -> (bp_rialto::BlockNumber, bp_rialto::Hash) {
|
fn best_finalized() -> (bp_rialto::BlockNumber, bp_rialto::Hash) {
|
||||||
let header = BridgeRialtoGrandpa::best_finalized();
|
let header = BridgeRialtoGrandpa::best_finalized();
|
||||||
|
|||||||
@@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
use bp_rialto::derive_account_from_millau_id;
|
use bp_rialto::derive_account_from_millau_id;
|
||||||
use rialto_runtime::{
|
use rialto_runtime::{
|
||||||
AccountId, AuraConfig, BalancesConfig, BridgeKovanConfig, BridgeMillauConfig, BridgeRialtoPoAConfig, GenesisConfig,
|
AccountId, AuraConfig, BalancesConfig, BridgeKovanConfig, BridgeRialtoPoAConfig, GenesisConfig, GrandpaConfig,
|
||||||
GrandpaConfig, SessionConfig, SessionKeys, Signature, SudoConfig, SystemConfig, WASM_BINARY,
|
SessionConfig, SessionKeys, Signature, SudoConfig, SystemConfig, WASM_BINARY,
|
||||||
};
|
};
|
||||||
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
|
use sp_consensus_aura::sr25519::AuthorityId as AuraId;
|
||||||
use sp_core::{sr25519, Pair, Public};
|
use sp_core::{sr25519, Pair, Public};
|
||||||
@@ -169,11 +169,6 @@ fn testnet_genesis(
|
|||||||
pallet_grandpa: Some(GrandpaConfig {
|
pallet_grandpa: Some(GrandpaConfig {
|
||||||
authorities: Vec::new(),
|
authorities: Vec::new(),
|
||||||
}),
|
}),
|
||||||
pallet_substrate_bridge: Some(BridgeMillauConfig {
|
|
||||||
// We'll initialize the pallet with a dispatchable instead.
|
|
||||||
init_data: None,
|
|
||||||
owner: Some(root_key.clone()),
|
|
||||||
}),
|
|
||||||
pallet_sudo: Some(SudoConfig { key: root_key }),
|
pallet_sudo: Some(SudoConfig { key: root_key }),
|
||||||
pallet_session: Some(SessionConfig {
|
pallet_session: Some(SessionConfig {
|
||||||
keys: initial_authorities
|
keys: initial_authorities
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ pallet-bridge-eth-poa = { path = "../../../modules/ethereum", default-features =
|
|||||||
pallet-bridge-grandpa = { path = "../../../modules/grandpa", default-features = false }
|
pallet-bridge-grandpa = { path = "../../../modules/grandpa", default-features = false }
|
||||||
pallet-bridge-messages = { path = "../../../modules/messages", default-features = false }
|
pallet-bridge-messages = { path = "../../../modules/messages", default-features = false }
|
||||||
pallet-shift-session-manager = { path = "../../../modules/shift-session-manager", default-features = false }
|
pallet-shift-session-manager = { path = "../../../modules/shift-session-manager", default-features = false }
|
||||||
pallet-substrate-bridge = { path = "../../../modules/substrate", default-features = false }
|
|
||||||
|
|
||||||
# Substrate Dependencies
|
# Substrate Dependencies
|
||||||
|
|
||||||
@@ -100,7 +99,6 @@ std = [
|
|||||||
"pallet-grandpa/std",
|
"pallet-grandpa/std",
|
||||||
"pallet-randomness-collective-flip/std",
|
"pallet-randomness-collective-flip/std",
|
||||||
"pallet-shift-session-manager/std",
|
"pallet-shift-session-manager/std",
|
||||||
"pallet-substrate-bridge/std",
|
|
||||||
"pallet-sudo/std",
|
"pallet-sudo/std",
|
||||||
"pallet-timestamp/std",
|
"pallet-timestamp/std",
|
||||||
"pallet-transaction-payment-rpc-runtime-api/std",
|
"pallet-transaction-payment-rpc-runtime-api/std",
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ pub use pallet_bridge_currency_exchange::Call as BridgeCurrencyExchangeCall;
|
|||||||
pub use pallet_bridge_eth_poa::Call as BridgeEthPoACall;
|
pub use pallet_bridge_eth_poa::Call as BridgeEthPoACall;
|
||||||
pub use pallet_bridge_grandpa::Call as BridgeGrandpaMillauCall;
|
pub use pallet_bridge_grandpa::Call as BridgeGrandpaMillauCall;
|
||||||
pub use pallet_bridge_messages::Call as MessagesCall;
|
pub use pallet_bridge_messages::Call as MessagesCall;
|
||||||
pub use pallet_substrate_bridge::Call as BridgeMillauCall;
|
|
||||||
pub use pallet_sudo::Call as SudoCall;
|
pub use pallet_sudo::Call as SudoCall;
|
||||||
pub use pallet_timestamp::Call as TimestampCall;
|
pub use pallet_timestamp::Call as TimestampCall;
|
||||||
|
|
||||||
@@ -407,10 +406,6 @@ impl pallet_session::Config for Runtime {
|
|||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
impl pallet_substrate_bridge::Config for Runtime {
|
|
||||||
type BridgedChain = bp_millau::Millau;
|
|
||||||
}
|
|
||||||
|
|
||||||
parameter_types! {
|
parameter_types! {
|
||||||
// This is a pretty unscientific cap.
|
// This is a pretty unscientific cap.
|
||||||
//
|
//
|
||||||
@@ -480,7 +475,6 @@ construct_runtime!(
|
|||||||
BridgeKovan: pallet_bridge_eth_poa::<Instance2>::{Module, Call, Config, Storage, ValidateUnsigned},
|
BridgeKovan: pallet_bridge_eth_poa::<Instance2>::{Module, Call, Config, Storage, ValidateUnsigned},
|
||||||
BridgeRialtoCurrencyExchange: pallet_bridge_currency_exchange::<Instance1>::{Module, Call},
|
BridgeRialtoCurrencyExchange: pallet_bridge_currency_exchange::<Instance1>::{Module, Call},
|
||||||
BridgeKovanCurrencyExchange: pallet_bridge_currency_exchange::<Instance2>::{Module, Call},
|
BridgeKovanCurrencyExchange: pallet_bridge_currency_exchange::<Instance2>::{Module, Call},
|
||||||
BridgeMillau: pallet_substrate_bridge::{Module, Call, Storage, Config<T>},
|
|
||||||
BridgeMillauGrandpa: pallet_bridge_grandpa::{Module, Call, Storage},
|
BridgeMillauGrandpa: pallet_bridge_grandpa::{Module, Call, Storage},
|
||||||
BridgeDispatch: pallet_bridge_dispatch::{Module, Event<T>},
|
BridgeDispatch: pallet_bridge_dispatch::{Module, Event<T>},
|
||||||
BridgeMillauMessages: pallet_bridge_messages::{Module, Call, Storage, Event<T>},
|
BridgeMillauMessages: pallet_bridge_messages::{Module, Call, Storage, Event<T>},
|
||||||
@@ -619,29 +613,6 @@ impl_runtime_apis! {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl bp_millau::MillauHeaderApi<Block> for Runtime {
|
|
||||||
fn best_blocks() -> Vec<(bp_millau::BlockNumber, bp_millau::Hash)> {
|
|
||||||
BridgeMillau::best_headers()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finalized_block() -> (bp_millau::BlockNumber, bp_millau::Hash) {
|
|
||||||
let header = BridgeMillau::best_finalized();
|
|
||||||
(header.number, header.hash())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn incomplete_headers() -> Vec<(bp_millau::BlockNumber, bp_millau::Hash)> {
|
|
||||||
BridgeMillau::require_justifications()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_known_block(hash: bp_millau::Hash) -> bool {
|
|
||||||
BridgeMillau::is_known_header(hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_finalized_block(hash: bp_millau::Hash) -> bool {
|
|
||||||
BridgeMillau::is_finalized_header(hash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl bp_millau::MillauFinalityApi<Block> for Runtime {
|
impl bp_millau::MillauFinalityApi<Block> for Runtime {
|
||||||
fn best_finalized() -> (bp_millau::BlockNumber, bp_millau::Hash) {
|
fn best_finalized() -> (bp_millau::BlockNumber, bp_millau::Hash) {
|
||||||
let header = BridgeMillauGrandpa::best_finalized();
|
let header = BridgeMillauGrandpa::best_finalized();
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ bp-test-utils = { path = "../../primitives/test-utils", default-features = false
|
|||||||
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false, optional = true }
|
frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false, optional = true }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pallet-substrate-bridge = { path = "../../modules/substrate" }
|
|
||||||
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
[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 }
|
|
||||||
log = { version = "0.4.14", 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", branch = "master" , default-features = false }
|
|
||||||
frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
|
|
||||||
sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
|
|
||||||
sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
|
|
||||||
sp-std = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
|
|
||||||
sp-trie = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
bp-test-utils = {path = "../../primitives/test-utils" }
|
|
||||||
sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
|
||||||
sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
|
|
||||||
sp-state-machine = { git = "https://github.com/paritytech/substrate", 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",
|
|
||||||
"log/std",
|
|
||||||
"serde",
|
|
||||||
"sp-finality-grandpa/std",
|
|
||||||
"sp-runtime/std",
|
|
||||||
"sp-std/std",
|
|
||||||
"sp-trie/std",
|
|
||||||
]
|
|
||||||
runtime-benchmarks = []
|
|
||||||
@@ -1,512 +0,0 @@
|
|||||||
// Copyright 2020-2021 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::{authority_list, make_default_justification, ALICE, BOB};
|
|
||||||
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 justification = make_default_justification(header).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.into(), 1), (BOB.into(), 1)],
|
|
||||||
delay,
|
|
||||||
});
|
|
||||||
|
|
||||||
Digest::<TestHash> {
|
|
||||||
logs: vec![DigestItem::Consensus(GRANDPA_ENGINE_ID, consensus_log.encode())],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,117 +0,0 @@
|
|||||||
// Copyright 2020-2021 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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
// Copyright 2019-2021 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,855 +0,0 @@
|
|||||||
// Copyright 2020-2021 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::{find_grandpa_authorities_scheduled_change, justification::verify_justification, AuthoritySet};
|
|
||||||
use finality_grandpa::voter_set::VoterSet;
|
|
||||||
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_grandpa_authorities_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> {
|
|
||||||
use codec::Decode;
|
|
||||||
|
|
||||||
// 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.",
|
|
||||||
);
|
|
||||||
|
|
||||||
let justification = bp_header_chain::justification::GrandpaJustification::<H>::decode(&mut proof.0.as_slice())
|
|
||||||
.map_err(|_| FinalizationError::InvalidJustification)?;
|
|
||||||
|
|
||||||
verify_justification::<H>(
|
|
||||||
(hash, *header.number()),
|
|
||||||
current_authority_set.set_id,
|
|
||||||
&voter_set,
|
|
||||||
&justification,
|
|
||||||
)
|
|
||||||
.map_err(|_| FinalizationError::InvalidJustification)?;
|
|
||||||
log::trace!("Received valid justification for {:?}", header);
|
|
||||||
|
|
||||||
log::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)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::mock::*;
|
|
||||||
use crate::{BestFinalized, BestHeight, HeaderId, ImportedHeaders, PalletStorage};
|
|
||||||
use bp_test_utils::{authority_list, make_default_justification, ALICE, BOB};
|
|
||||||
use codec::Encode;
|
|
||||||
use frame_support::{assert_err, assert_ok};
|
|
||||||
use frame_support::{StorageMap, StorageValue};
|
|
||||||
use sp_finality_grandpa::{AuthorityId, ConsensusLog, SetId, GRANDPA_ENGINE_ID};
|
|
||||||
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, 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 justification = make_default_justification(&header).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.into(), u64::MAX), (BOB.into(), 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, set_id);
|
|
||||||
storage.update_current_authority_set(authority_set);
|
|
||||||
|
|
||||||
// We'll need this justification to finalize the header
|
|
||||||
let justification = make_default_justification(&header).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, set_id };
|
|
||||||
storage.update_current_authority_set(authority_set);
|
|
||||||
|
|
||||||
let justification = make_default_justification(&header).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, 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 justification = make_default_justification(&header).encode();
|
|
||||||
|
|
||||||
// Schedule a change at the height of our header
|
|
||||||
let set_id = 2;
|
|
||||||
let height = *header.number();
|
|
||||||
let authorities = vec![ALICE.into()];
|
|
||||||
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
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -243,7 +243,7 @@ mod tests {
|
|||||||
// then
|
// then
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{:?}", hex),
|
format!("{:?}", hex),
|
||||||
"0x0d00d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27de5c0"
|
"0x0c00d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27de5c0"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ use sp_core::{storage::StorageKey, Pair};
|
|||||||
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
|
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub use millau_runtime::BridgeRialtoCall;
|
|
||||||
|
|
||||||
/// Millau header id.
|
/// Millau header id.
|
||||||
pub type HeaderId = relay_utils::HeaderId<millau_runtime::Hash, millau_runtime::BlockNumber>;
|
pub type HeaderId = relay_utils::HeaderId<millau_runtime::Hash, millau_runtime::BlockNumber>;
|
||||||
|
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ use sp_core::{storage::StorageKey, Pair};
|
|||||||
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
|
use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
pub use rialto_runtime::BridgeMillauCall;
|
|
||||||
|
|
||||||
/// Rialto header id.
|
/// Rialto header id.
|
||||||
pub type HeaderId = relay_utils::HeaderId<rialto_runtime::Hash, rialto_runtime::BlockNumber>;
|
pub type HeaderId = relay_utils::HeaderId<rialto_runtime::Hash, rialto_runtime::BlockNumber>;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user