From 2f1c4c23fc1107a93d21f21d2c9aa89cfd850af7 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Tue, 6 Apr 2021 06:19:23 -0400 Subject: [PATCH] Remove Substrate Pallet (#866) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove `pallet-substrate-bridge` * Fix transfer call encoding test Co-authored-by: Tomasz Drwięga --- bridges/bin/millau/node/src/chain_spec.rs | 9 +- bridges/bin/millau/runtime/Cargo.toml | 2 - bridges/bin/millau/runtime/src/lib.rs | 29 - bridges/bin/rialto/node/src/chain_spec.rs | 9 +- bridges/bin/rialto/runtime/Cargo.toml | 2 - bridges/bin/rialto/runtime/src/lib.rs | 29 - bridges/modules/grandpa/Cargo.toml | 1 - bridges/modules/substrate/Cargo.toml | 55 - bridges/modules/substrate/src/fork_tests.rs | 512 -------- bridges/modules/substrate/src/lib.rs | 1040 ----------------- bridges/modules/substrate/src/mock.rs | 117 -- bridges/modules/substrate/src/storage.rs | 80 -- bridges/modules/substrate/src/verifier.rs | 855 -------------- .../bin-substrate/src/cli/encode_call.rs | 2 +- bridges/relays/client-millau/src/lib.rs | 2 - bridges/relays/client-rialto/src/lib.rs | 2 - 16 files changed, 5 insertions(+), 2741 deletions(-) delete mode 100644 bridges/modules/substrate/Cargo.toml delete mode 100644 bridges/modules/substrate/src/fork_tests.rs delete mode 100644 bridges/modules/substrate/src/lib.rs delete mode 100644 bridges/modules/substrate/src/mock.rs delete mode 100644 bridges/modules/substrate/src/storage.rs delete mode 100644 bridges/modules/substrate/src/verifier.rs diff --git a/bridges/bin/millau/node/src/chain_spec.rs b/bridges/bin/millau/node/src/chain_spec.rs index 64dca2f255..df790c7580 100644 --- a/bridges/bin/millau/node/src/chain_spec.rs +++ b/bridges/bin/millau/node/src/chain_spec.rs @@ -16,8 +16,8 @@ use bp_millau::derive_account_from_rialto_id; use millau_runtime::{ - AccountId, AuraConfig, BalancesConfig, BridgeRialtoConfig, BridgeWestendGrandpaConfig, GenesisConfig, - GrandpaConfig, SessionConfig, SessionKeys, Signature, SudoConfig, SystemConfig, WASM_BINARY, + AccountId, AuraConfig, BalancesConfig, BridgeWestendGrandpaConfig, GenesisConfig, GrandpaConfig, SessionConfig, + SessionKeys, Signature, SudoConfig, SystemConfig, WASM_BINARY, }; use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_core::{sr25519, Pair, Public}; @@ -167,11 +167,6 @@ fn testnet_genesis( pallet_grandpa: Some(GrandpaConfig { 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_session: Some(SessionConfig { keys: initial_authorities diff --git a/bridges/bin/millau/runtime/Cargo.toml b/bridges/bin/millau/runtime/Cargo.toml index a1406d69d8..411835ab6f 100644 --- a/bridges/bin/millau/runtime/Cargo.toml +++ b/bridges/bin/millau/runtime/Cargo.toml @@ -25,7 +25,6 @@ pallet-bridge-dispatch = { path = "../../../modules/dispatch", default-features pallet-bridge-grandpa = { path = "../../../modules/grandpa", 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-substrate-bridge = { path = "../../../modules/substrate", default-features = false } # Substrate Dependencies @@ -83,7 +82,6 @@ std = [ "pallet-randomness-collective-flip/std", "pallet-session/std", "pallet-shift-session-manager/std", - "pallet-substrate-bridge/std", "pallet-sudo/std", "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", diff --git a/bridges/bin/millau/runtime/src/lib.rs b/bridges/bin/millau/runtime/src/lib.rs index ed622e7eea..b627cce404 100644 --- a/bridges/bin/millau/runtime/src/lib.rs +++ b/bridges/bin/millau/runtime/src/lib.rs @@ -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 BridgeGrandpaWestendCall; 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_timestamp::Call as TimestampCall; @@ -301,10 +300,6 @@ impl pallet_session::Config for Runtime { type WeightInfo = (); } -impl pallet_substrate_bridge::Config for Runtime { - type BridgedChain = bp_rialto::Rialto; -} - parameter_types! { // This is a pretty unscientific cap. // @@ -383,7 +378,6 @@ construct_runtime!( NodeBlock = opaque::Block, UncheckedExtrinsic = UncheckedExtrinsic { - BridgeRialto: pallet_substrate_bridge::{Module, Call, Storage, Config}, BridgeRialtoMessages: pallet_bridge_messages::{Module, Call, Storage, Event}, BridgeDispatch: pallet_bridge_dispatch::{Module, Event}, BridgeRialtoGrandpa: pallet_bridge_grandpa::{Module, Call, Storage}, @@ -563,29 +557,6 @@ impl_runtime_apis! { } } - impl bp_rialto::RialtoHeaderApi 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 for Runtime { fn best_finalized() -> (bp_rialto::BlockNumber, bp_rialto::Hash) { let header = BridgeRialtoGrandpa::best_finalized(); diff --git a/bridges/bin/rialto/node/src/chain_spec.rs b/bridges/bin/rialto/node/src/chain_spec.rs index 2794496fc3..14b2c3b84c 100644 --- a/bridges/bin/rialto/node/src/chain_spec.rs +++ b/bridges/bin/rialto/node/src/chain_spec.rs @@ -16,8 +16,8 @@ use bp_rialto::derive_account_from_millau_id; use rialto_runtime::{ - AccountId, AuraConfig, BalancesConfig, BridgeKovanConfig, BridgeMillauConfig, BridgeRialtoPoAConfig, GenesisConfig, - GrandpaConfig, SessionConfig, SessionKeys, Signature, SudoConfig, SystemConfig, WASM_BINARY, + AccountId, AuraConfig, BalancesConfig, BridgeKovanConfig, BridgeRialtoPoAConfig, GenesisConfig, GrandpaConfig, + SessionConfig, SessionKeys, Signature, SudoConfig, SystemConfig, WASM_BINARY, }; use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_core::{sr25519, Pair, Public}; @@ -169,11 +169,6 @@ fn testnet_genesis( pallet_grandpa: Some(GrandpaConfig { 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_session: Some(SessionConfig { keys: initial_authorities diff --git a/bridges/bin/rialto/runtime/Cargo.toml b/bridges/bin/rialto/runtime/Cargo.toml index 74365eec89..ea8c51d0e8 100644 --- a/bridges/bin/rialto/runtime/Cargo.toml +++ b/bridges/bin/rialto/runtime/Cargo.toml @@ -31,7 +31,6 @@ pallet-bridge-eth-poa = { path = "../../../modules/ethereum", default-features = pallet-bridge-grandpa = { path = "../../../modules/grandpa", 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-substrate-bridge = { path = "../../../modules/substrate", default-features = false } # Substrate Dependencies @@ -100,7 +99,6 @@ std = [ "pallet-grandpa/std", "pallet-randomness-collective-flip/std", "pallet-shift-session-manager/std", - "pallet-substrate-bridge/std", "pallet-sudo/std", "pallet-timestamp/std", "pallet-transaction-payment-rpc-runtime-api/std", diff --git a/bridges/bin/rialto/runtime/src/lib.rs b/bridges/bin/rialto/runtime/src/lib.rs index 39693de2c0..5fd10b12de 100644 --- a/bridges/bin/rialto/runtime/src/lib.rs +++ b/bridges/bin/rialto/runtime/src/lib.rs @@ -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_grandpa::Call as BridgeGrandpaMillauCall; 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_timestamp::Call as TimestampCall; @@ -407,10 +406,6 @@ impl pallet_session::Config for Runtime { type WeightInfo = (); } -impl pallet_substrate_bridge::Config for Runtime { - type BridgedChain = bp_millau::Millau; -} - parameter_types! { // This is a pretty unscientific cap. // @@ -480,7 +475,6 @@ construct_runtime!( BridgeKovan: pallet_bridge_eth_poa::::{Module, Call, Config, Storage, ValidateUnsigned}, BridgeRialtoCurrencyExchange: pallet_bridge_currency_exchange::::{Module, Call}, BridgeKovanCurrencyExchange: pallet_bridge_currency_exchange::::{Module, Call}, - BridgeMillau: pallet_substrate_bridge::{Module, Call, Storage, Config}, BridgeMillauGrandpa: pallet_bridge_grandpa::{Module, Call, Storage}, BridgeDispatch: pallet_bridge_dispatch::{Module, Event}, BridgeMillauMessages: pallet_bridge_messages::{Module, Call, Storage, Event}, @@ -619,29 +613,6 @@ impl_runtime_apis! { } } - impl bp_millau::MillauHeaderApi 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 for Runtime { fn best_finalized() -> (bp_millau::BlockNumber, bp_millau::Hash) { let header = BridgeMillauGrandpa::best_finalized(); diff --git a/bridges/modules/grandpa/Cargo.toml b/bridges/modules/grandpa/Cargo.toml index 1191c8c8fc..810dce3dd5 100644 --- a/bridges/modules/grandpa/Cargo.toml +++ b/bridges/modules/grandpa/Cargo.toml @@ -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 } [dev-dependencies] -pallet-substrate-bridge = { path = "../../modules/substrate" } sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" } [features] diff --git a/bridges/modules/substrate/Cargo.toml b/bridges/modules/substrate/Cargo.toml deleted file mode 100644 index bcd9faaede..0000000000 --- a/bridges/modules/substrate/Cargo.toml +++ /dev/null @@ -1,55 +0,0 @@ -[package] -name = "pallet-substrate-bridge" -version = "0.1.0" -authors = ["Parity Technologies "] -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 = [] diff --git a/bridges/modules/substrate/src/fork_tests.rs b/bridges/modules/substrate/src/fork_tests.rs deleted file mode 100644 index 58b9749620..0000000000 --- a/bridges/modules/substrate/src/fork_tests.rs +++ /dev/null @@ -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 . - -//! 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; - -#[derive(Debug)] -enum Type { - Header(TestNumber, ForkId, ForksAt, ScheduledChangeAt), - Finality(TestNumber, ForkId), -} - -// Order: 1, 2, 2', 3, 3'' -// -// / [3''] -// / [2'] -// [1] <- [2] <- [3] -#[test] -fn fork_can_import_headers_on_different_forks() { - run_test(|| { - let mut storage = PalletStorage::::new(); - - let mut chain = vec![ - (Type::Header(1, 1, None, None), Ok(())), - (Type::Header(2, 1, None, None), Ok(())), - (Type::Header(2, 2, Some((1, 1)), None), Ok(())), - (Type::Header(3, 1, None, None), Ok(())), - (Type::Header(3, 3, Some((2, 2)), None), Ok(())), - ]; - - create_chain(&mut storage, &mut chain); - - let best_headers = storage.best_headers(); - assert_eq!(best_headers.len(), 2); - assert_eq!(>::get(), 3); - }) -} - -// Order: 1, 2, 2', F2, F2' -// -// [1] <- [2: F] -// \ [2'] -// -// Not allowed to finalize 2' -#[test] -fn fork_does_not_allow_competing_finality_proofs() { - run_test(|| { - let mut storage = PalletStorage::::new(); - - let mut chain = vec![ - (Type::Header(1, 1, None, None), Ok(())), - (Type::Header(2, 1, None, None), Ok(())), - (Type::Header(2, 2, Some((1, 1)), None), Ok(())), - (Type::Finality(2, 1), Ok(())), - (Type::Finality(2, 2), Err(FinalizationError::OldHeader.into())), - ]; - - create_chain(&mut storage, &mut chain); - }) -} - -// Order: 1, 2, 3, F2, 3 -// -// [1] <- [2: S|0] <- [3] -// -// Not allowed to import 3 until we get F2 -// -// Note: GRANDPA would technically allow 3 to be imported as long as it didn't try and enact an -// authority set change. However, since we expect finality proofs to be imported quickly we've -// decided to simplify our import process and disallow header imports until we get a finality proof. -#[test] -fn fork_waits_for_finality_proof_before_importing_header_past_one_which_enacts_a_change() { - run_test(|| { - let mut storage = PalletStorage::::new(); - - let mut chain = vec![ - (Type::Header(1, 1, None, None), Ok(())), - (Type::Header(2, 1, None, Some(0)), Ok(())), - ( - Type::Header(3, 1, None, None), - Err(ImportError::AwaitingFinalityProof.into()), - ), - (Type::Finality(2, 1), Ok(())), - (Type::Header(3, 1, None, None), Ok(())), - ]; - - create_chain(&mut storage, &mut chain); - }) -} - -// Order: 1, 2, F2, 3 -// -// [1] <- [2: S|1] <- [3: S|0] -// -// GRANDPA can have multiple authority set changes pending on the same fork. However, we've decided -// to introduce a limit of _one_ pending authority set change per fork in order to simplify pallet -// logic and to prevent DoS attacks if GRANDPA finality were to temporarily stall for a long time -// (we'd have to perform a lot of expensive ancestry checks to catch back up). -#[test] -fn fork_does_not_allow_multiple_scheduled_changes_on_the_same_fork() { - run_test(|| { - let mut storage = PalletStorage::::new(); - - let mut chain = vec![ - (Type::Header(1, 1, None, None), Ok(())), - (Type::Header(2, 1, None, Some(1)), Ok(())), - ( - Type::Header(3, 1, None, Some(0)), - Err(ImportError::PendingAuthoritySetChange.into()), - ), - (Type::Finality(2, 1), Ok(())), - (Type::Header(3, 1, None, Some(0)), Ok(())), - ]; - - create_chain(&mut storage, &mut chain); - }) -} - -// Order: 1, 2, 2' -// -// / [2': S|0] -// [1] <- [2: S|0] -// -// Both 2 and 2' should be marked as needing justifications since they enact changes. -#[test] -fn fork_correctly_tracks_which_headers_require_finality_proofs() { - run_test(|| { - let mut storage = PalletStorage::::new(); - - let mut chain = vec![ - (Type::Header(1, 1, None, None), Ok(())), - (Type::Header(2, 1, None, Some(0)), Ok(())), - (Type::Header(2, 2, Some((1, 1)), Some(0)), Ok(())), - ]; - - create_chain(&mut storage, &mut chain); - - let header_ids = storage.missing_justifications(); - assert_eq!(header_ids.len(), 2); - assert!(header_ids[0].hash != header_ids[1].hash); - assert_eq!(header_ids[0].number, 2); - assert_eq!(header_ids[1].number, 2); - }) -} - -// Order: 1, 2, 2', 3', F2, 3, 4' -// -// / [2': S|1] <- [3'] <- [4'] -// [1] <- [2: S|0] <- [3] -// -// -// Not allowed to import 3 or 4' -// Can only import 3 after we get the finality proof for 2 -#[test] -fn fork_does_not_allow_importing_past_header_that_enacts_changes_on_forks() { - run_test(|| { - let mut storage = PalletStorage::::new(); - - let mut chain = vec![ - (Type::Header(1, 1, None, None), Ok(())), - (Type::Header(2, 1, None, Some(0)), Ok(())), - (Type::Header(2, 2, Some((1, 1)), Some(1)), Ok(())), - ( - Type::Header(3, 1, None, None), - Err(ImportError::AwaitingFinalityProof.into()), - ), - (Type::Header(3, 2, None, None), Ok(())), - (Type::Finality(2, 1), Ok(())), - (Type::Header(3, 1, None, None), Ok(())), - ( - Type::Header(4, 2, None, None), - Err(ImportError::AwaitingFinalityProof.into()), - ), - ]; - - create_chain(&mut storage, &mut chain); - - // Since we can't query the map directly to check if we applied the right authority set - // change (we don't know the header hash of 2) we need to get a little clever. - let mut next_change = >::iter(); - let (_, scheduled_change_on_fork) = next_change.next().unwrap(); - assert_eq!(scheduled_change_on_fork.height, 3); - - // Sanity check to make sure we enacted the change on the canonical change - assert_eq!(next_change.next(), None); - }) -} - -// Order: 1, 2, 3, 2', 3' -// -// / [2'] <- [3'] -// [1] <- [2: S|0] <- [3] -// -// Not allowed to import 3 -// Fine to import 2' and 3' -#[test] -fn fork_allows_importing_on_different_fork_while_waiting_for_finality_proof() { - run_test(|| { - let mut storage = PalletStorage::::new(); - - let mut chain = vec![ - (Type::Header(1, 1, None, None), Ok(())), - (Type::Header(2, 1, None, Some(0)), Ok(())), - ( - Type::Header(3, 1, None, None), - Err(ImportError::AwaitingFinalityProof.into()), - ), - (Type::Header(2, 2, Some((1, 1)), None), Ok(())), - (Type::Header(3, 2, None, None), Ok(())), - ]; - - create_chain(&mut storage, &mut chain); - }) -} - -// Order: 1, 2, 2', F2, 3, 3' -// -// / [2'] <- [3'] -// [1] <- [2: F] <- [3] -// -// In our current implementation we're allowed to keep building on fork 2 for as long as our hearts' -// content. However, we'll never be able to finalize anything on that fork. We'd have to check for -// ancestry with `best_finalized` on every import which will get expensive. -// -// I think this is fine as long as we run pruning every so often to clean up these dead forks. -#[test] -fn fork_allows_importing_on_different_fork_past_finalized_header() { - run_test(|| { - let mut storage = PalletStorage::::new(); - - let mut chain = vec![ - (Type::Header(1, 1, None, None), Ok(())), - (Type::Header(2, 1, None, Some(0)), Ok(())), - (Type::Header(2, 2, Some((1, 1)), None), Ok(())), - (Type::Finality(2, 1), Ok(())), - (Type::Header(3, 1, None, None), Ok(())), - (Type::Header(3, 2, None, None), Ok(())), - ]; - - create_chain(&mut storage, &mut chain); - }) -} - -// Order: 1, 2, 3, 4, 3', 4' -// -// / [3': E] <- [4'] -// [1] <- [2: S|1] <- [3: E] <- [4] -// -// Not allowed to import {4|4'} -#[test] -fn fork_can_track_scheduled_changes_across_forks() { - run_test(|| { - let mut storage = PalletStorage::::new(); - - let mut chain = vec![ - (Type::Header(1, 1, None, None), Ok(())), - (Type::Header(2, 1, None, Some(1)), Ok(())), - (Type::Header(3, 1, None, None), Ok(())), - ( - Type::Header(4, 1, None, None), - Err(ImportError::AwaitingFinalityProof.into()), - ), - (Type::Header(3, 2, Some((2, 1)), None), Ok(())), - ( - Type::Header(4, 2, None, None), - Err(ImportError::AwaitingFinalityProof.into()), - ), - ]; - - create_chain(&mut storage, &mut chain); - }) -} - -#[derive(Debug, PartialEq)] -enum TestError { - Import(ImportError), - Finality(FinalizationError), -} - -impl From for TestError { - fn from(e: ImportError) -> Self { - TestError::Import(e) - } -} - -impl From for TestError { - fn from(e: FinalizationError) -> Self { - TestError::Finality(e) - } -} - -// Builds a fork-aware representation of a blockchain given a list of headers. -// -// Takes a list of headers and finality proof operations which will be applied in order. The -// expected outcome for each operation is also required. -// -// The first header in the list will be used as the genesis header and will be manually imported -// into storage. -fn create_chain(storage: &mut S, chain: &mut Vec<(Type, Result<(), TestError>)>) -where - S: BridgeStorage
+ Clone, -{ - let mut map = BTreeMap::new(); - let mut verifier = Verifier { - storage: storage.clone(), - }; - initialize_genesis(storage, &mut map, chain.remove(0).0); - - for h in chain { - match h { - (Type::Header(num, fork_id, does_fork, schedules_change), expected_result) => { - // If we've never seen this fork before - if !map.contains_key(&fork_id) { - // Let's get the info about where to start the fork - if let Some((parent_num, forked_from_id)) = does_fork { - let fork = &*map.get(&forked_from_id).unwrap(); - let parent = fork - .iter() - .find(|h| h.number == *parent_num) - .expect("Trying to fork on a parent which doesn't exist"); - - let mut header = test_header(*num); - header.parent_hash = parent.hash(); - header.state_root = [*fork_id as u8; 32].into(); - - if let Some(delay) = schedules_change { - header.digest = change_log(*delay); - } - - // Try and import into storage - let res = verifier - .import_header(header.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(storage: &mut S, map: &mut BTreeMap>, genesis: Type) -where - S: BridgeStorage
, -{ - if let Type::Header(num, fork_id, None, None) = genesis { - let genesis = test_header(num); - map.insert(fork_id, vec![genesis.clone()]); - - let genesis = ImportedHeader { - header: genesis, - requires_justification: false, - is_finalized: true, - signal_hash: None, - }; - - >::put(genesis.hash()); - storage.write_header(&genesis); - } else { - panic!("Unexpected genesis block format {:#?}", genesis) - } - - let set_id = 1; - let authorities = authority_list(); - let authority_set = AuthoritySet::new(authorities, set_id); - storage.update_current_authority_set(authority_set); -} - -pub(crate) fn change_log(delay: u64) -> Digest { - let consensus_log = ConsensusLog::::ScheduledChange(sp_finality_grandpa::ScheduledChange { - next_authorities: vec![(ALICE.into(), 1), (BOB.into(), 1)], - delay, - }); - - Digest:: { - logs: vec![DigestItem::Consensus(GRANDPA_ENGINE_ID, consensus_log.encode())], - } -} diff --git a/bridges/modules/substrate/src/lib.rs b/bridges/modules/substrate/src/lib.rs deleted file mode 100644 index 20f0833873..0000000000 --- a/bridges/modules/substrate/src/lib.rs +++ /dev/null @@ -1,1040 +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 . - -//! Substrate Bridge Pallet -//! -//! This pallet is an on-chain light client for chains which have a notion of finality. -//! -//! It has a simple interface for achieving this. First it can import headers to the runtime -//! storage. During this it will check the validity of the headers and ensure they don't conflict -//! with any existing headers (e.g they're on a different finalized chain). Secondly it can finalize -//! an already imported header (and its ancestors) given a valid GRANDPA justification. -//! -//! With these two functions the pallet is able to form a "source of truth" for what headers have -//! been finalized on a given Substrate chain. This can be a useful source of info for other -//! higher-level applications. - -#![cfg_attr(not(feature = "std"), no_std)] -// Runtime-generated enums -#![allow(clippy::large_enum_variant)] - -use crate::storage::ImportedHeader; -use bp_header_chain::AuthoritySet; -use bp_runtime::{BlockNumberOf, Chain, HashOf, HasherOf, HeaderOf}; -use frame_support::{ - decl_error, decl_module, decl_storage, dispatch::DispatchResult, ensure, traits::Get, weights::DispatchClass, -}; -use frame_system::{ensure_signed, RawOrigin}; -use sp_runtime::traits::Header as HeaderT; -use sp_runtime::{traits::BadOrigin, RuntimeDebug}; -use sp_std::{marker::PhantomData, prelude::*}; -use sp_trie::StorageProof; - -// Re-export since the node uses these when configuring genesis -pub use storage::{InitializationData, ScheduledChange}; - -mod storage; -mod verifier; - -#[cfg(test)] -mod mock; - -#[cfg(test)] -mod fork_tests; - -/// Block number of the bridged chain. -pub(crate) type BridgedBlockNumber = BlockNumberOf<::BridgedChain>; -/// Block hash of the bridged chain. -pub(crate) type BridgedBlockHash = HashOf<::BridgedChain>; -/// Hasher of the bridged chain. -pub(crate) type BridgedBlockHasher = HasherOf<::BridgedChain>; -/// Header of the bridged chain. -pub(crate) type BridgedHeader = HeaderOf<::BridgedChain>; - -/// A convenience type identifying headers. -#[derive(RuntimeDebug, PartialEq)] -pub struct HeaderId { - /// The block number of the header. - pub number: H::Number, - /// The hash of the header. - pub hash: H::Hash, -} - -pub trait Config: frame_system::Config { - /// Chain that we are bridging here. - type BridgedChain: Chain; -} - -decl_storage! { - trait Store for Module as SubstrateBridge { - /// Hash of the header used to bootstrap the pallet. - InitialHash: BridgedBlockHash; - /// The number of the highest block(s) we know of. - BestHeight: BridgedBlockNumber; - /// Hash of the header at the highest known height. - /// - /// If there are multiple headers at the same "best" height - /// this will contain all of their hashes. - BestHeaders: Vec>; - /// Hash of the best finalized header. - BestFinalized: BridgedBlockHash; - /// The set of header IDs (number, hash) which enact an authority set change and therefore - /// require a GRANDPA justification. - RequiresJustification: map hasher(identity) BridgedBlockHash => BridgedBlockNumber; - /// Headers which have been imported into the pallet. - ImportedHeaders: map hasher(identity) BridgedBlockHash => Option>>; - /// The current GRANDPA Authority set. - CurrentAuthoritySet: AuthoritySet; - /// The next scheduled authority set change for a given fork. - /// - /// The fork is indicated by the header which _signals_ the change (key in the mapping). - /// Note that this is different than a header which _enacts_ a change. - // GRANDPA doesn't require there to always be a pending change. In fact, most of the time - // there will be no pending change available. - NextScheduledChange: map hasher(identity) BridgedBlockHash => Option>>; - /// Optional pallet owner. - /// - /// Pallet owner has a right to halt all pallet operations and then resume it. If it is - /// `None`, then there are no direct ways to halt/resume pallet operations, but other - /// runtime methods may still be used to do that (i.e. democracy::referendum to update halt - /// flag directly or call the `halt_operations`). - ModuleOwner get(fn module_owner): Option; - /// If true, all pallet transactions are failed immediately. - IsHalted get(fn is_halted): bool; - } - add_extra_genesis { - config(owner): Option; - config(init_data): Option>>; - build(|config| { - if let Some(ref owner) = config.owner { - >::put(owner); - } - - if let Some(init_data) = config.init_data.clone() { - initialize_bridge::(init_data); - } else { - // Since the bridge hasn't been initialized we shouldn't allow anyone to perform - // transactions. - IsHalted::put(true); - } - }) - } -} - -decl_error! { - pub enum Error for Module { - /// This header has failed basic verification. - InvalidHeader, - /// This header has not been finalized. - UnfinalizedHeader, - /// The header is unknown. - UnknownHeader, - /// The storage proof doesn't contains storage root. So it is invalid for given header. - StorageRootMismatch, - /// Error when trying to fetch storage value from the proof. - StorageValueUnavailable, - /// All pallet operations are halted. - Halted, - /// The pallet has already been initialized. - AlreadyInitialized, - /// The given header is not a descendant of a particular header. - NotDescendant, - /// The header being imported is on a fork which is incompatible with the current chain. - /// - /// This can happen if we try and import a finalized header at a lower height than our - /// current `best_finalized` header. - ConflictingFork, - } -} - -decl_module! { - pub struct Module for enum Call where origin: T::Origin { - type Error = Error; - - /// Import a signed Substrate header into the runtime. - /// - /// This will perform some basic checks to make sure it is fine to - /// import into the runtime. However, it does not perform any checks - /// related to finality. - // TODO: Update weights [#78] - #[weight = 0] - pub fn import_signed_header( - origin, - header: BridgedHeader, - ) -> DispatchResult { - ensure_operational::()?; - let _ = ensure_signed(origin)?; - let hash = header.hash(); - log::trace!("Going to import header {:?}: {:?}", hash, header); - - let mut verifier = verifier::Verifier { - storage: PalletStorage::::new(), - }; - - let _ = verifier - .import_header(hash, header) - .map_err(|e| { - log::error!("Failed to import header {:?}: {:?}", hash, e); - >::InvalidHeader - })?; - - log::trace!("Successfully imported header: {:?}", hash); - - Ok(()) - } - - /// Import a finalty proof for a particular header. - /// - /// This will take care of finalizing any already imported headers - /// which get finalized when importing this particular proof, as well - /// as updating the current and next validator sets. - // TODO: Update weights [#78] - #[weight = 0] - pub fn finalize_header( - origin, - hash: BridgedBlockHash, - finality_proof: Vec, - ) -> DispatchResult { - ensure_operational::()?; - let _ = ensure_signed(origin)?; - log::trace!("Going to finalize header: {:?}", hash); - - let mut verifier = verifier::Verifier { - storage: PalletStorage::::new(), - }; - - let _ = verifier - .import_finality_proof(hash, finality_proof.into()) - .map_err(|e| { - log::error!("Failed to finalize header {:?}: {:?}", hash, e); - >::UnfinalizedHeader - })?; - - log::trace!("Successfully finalized header: {:?}", hash); - - Ok(()) - } - - /// Bootstrap the bridge pallet with an initial header and authority set from which to sync. - /// - /// The initial configuration provided does not need to be the genesis header of the bridged - /// chain, it can be any arbirary header. You can also provide the next scheduled set change - /// if it is already know. - /// - /// This function is only allowed to be called from a trusted origin and writes to storage - /// with practically no checks in terms of the validity of the data. It is important that - /// you ensure that valid data is being passed in. - //TODO: Update weights [#78] - #[weight = 0] - pub fn initialize( - origin, - init_data: InitializationData>, - ) { - ensure_owner_or_root::(origin)?; - let init_allowed = !>::exists(); - ensure!(init_allowed, >::AlreadyInitialized); - initialize_bridge::(init_data.clone()); - - log::info!( - "Pallet has been initialized with the following parameters: {:?}", init_data - ); - } - - /// Change `ModuleOwner`. - /// - /// May only be called either by root, or by `ModuleOwner`. - #[weight = (T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational)] - pub fn set_owner(origin, new_owner: Option) { - ensure_owner_or_root::(origin)?; - match new_owner { - Some(new_owner) => { - ModuleOwner::::put(&new_owner); - log::info!("Setting pallet Owner to: {:?}", new_owner); - }, - None => { - ModuleOwner::::kill(); - log::info!("Removed Owner of pallet."); - }, - } - } - - /// Halt all pallet operations. Operations may be resumed using `resume_operations` call. - /// - /// May only be called either by root, or by `ModuleOwner`. - #[weight = (T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational)] - pub fn halt_operations(origin) { - ensure_owner_or_root::(origin)?; - IsHalted::put(true); - log::warn!("Stopping pallet operations."); - } - - /// Resume all pallet operations. May be called even if pallet is halted. - /// - /// May only be called either by root, or by `ModuleOwner`. - #[weight = (T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational)] - pub fn resume_operations(origin) { - ensure_owner_or_root::(origin)?; - IsHalted::put(false); - log::info!("Resuming pallet operations."); - } - } -} - -impl Module { - /// Get the highest header(s) that the pallet knows of. - pub fn best_headers() -> Vec<(BridgedBlockNumber, BridgedBlockHash)> { - PalletStorage::::new() - .best_headers() - .iter() - .map(|id| (id.number, id.hash)) - .collect() - } - - /// Get the best finalized header the pallet knows of. - /// - /// Returns a dummy header if there is no best header. This can only happen - /// if the pallet has not been initialized yet. - /// - /// Since this has been finalized correctly a user of the bridge - /// pallet should be confident that any transactions that were - /// included in this or any previous header will not be reverted. - pub fn best_finalized() -> BridgedHeader { - PalletStorage::::new().best_finalized_header().header - } - - /// Check if a particular header is known to the bridge pallet. - pub fn is_known_header(hash: BridgedBlockHash) -> bool { - PalletStorage::::new().header_exists(hash) - } - - /// Check if a particular header is finalized. - /// - /// Will return false if the header is not known to the pallet. - // One thing worth noting here is that this approach won't work well - // once we track forks since there could be an older header on a - // different fork which isn't an ancestor of our best finalized header. - pub fn is_finalized_header(hash: BridgedBlockHash) -> bool { - let storage = PalletStorage::::new(); - if let Some(header) = storage.header_by_hash(hash) { - header.is_finalized - } else { - false - } - } - - /// Returns a list of headers which require finality proofs. - /// - /// These headers require proofs because they enact authority set changes. - pub fn require_justifications() -> Vec<(BridgedBlockNumber, BridgedBlockHash)> { - PalletStorage::::new() - .missing_justifications() - .iter() - .map(|id| (id.number, id.hash)) - .collect() - } - - /// Verify that the passed storage proof is valid, given it is crafted using - /// known finalized header. If the proof is valid, then the `parse` callback - /// is called and the function returns its result. - pub fn parse_finalized_storage_proof( - finalized_header_hash: BridgedBlockHash, - storage_proof: StorageProof, - parse: impl FnOnce(bp_runtime::StorageProofChecker>) -> R, - ) -> Result { - let storage = PalletStorage::::new(); - let header = storage - .header_by_hash(finalized_header_hash) - .ok_or(Error::::UnknownHeader)?; - if !header.is_finalized { - return Err(Error::::UnfinalizedHeader.into()); - } - - let storage_proof_checker = bp_runtime::StorageProofChecker::new(*header.state_root(), storage_proof) - .map_err(|_| Error::::StorageRootMismatch)?; - Ok(parse(storage_proof_checker)) - } -} - -impl bp_header_chain::HeaderChain, sp_runtime::DispatchError> for Module { - fn best_finalized() -> BridgedHeader { - PalletStorage::::new().best_finalized_header().header - } - - fn authority_set() -> AuthoritySet { - PalletStorage::::new().current_authority_set() - } - - fn append_header(header: BridgedHeader) -> Result<(), sp_runtime::DispatchError> { - // We do a quick check here to ensure that our header chain is making progress and isn't - // "travelling back in time" (which would be indicative of something bad, e.g a hard-fork). - let best_finalized = PalletStorage::::new().best_finalized_header().header; - ensure!(best_finalized.number() < header.number(), >::ConflictingFork); - import_header_unchecked::<_, T>(&mut PalletStorage::::new(), header); - - Ok(()) - } -} - -/// Import a finalized header without checking if this is true. -/// -/// This function assumes that all the given header has already been proven to be valid and -/// finalized. Using this assumption it will write them to storage with minimal checks. That -/// means it's of great importance that this function *not* called with any headers whose -/// finality has not been checked, otherwise you risk bricking your bridge. -/// -/// One thing this function does do for you is GRANDPA authority set handoffs. However, since it -/// does not do verification on the incoming header it will assume that the authority set change -/// signals in the digest are well formed. -fn import_header_unchecked(storage: &mut S, header: BridgedHeader) -where - S: BridgeStorage
>, - T: Config, -{ - // Since we want to use the existing storage infrastructure we need to indicate the fork - // that we're on. We will assume that since we are using the unchecked import there are no - // forks, and can indicate that by using the first imported header's "fork". - let dummy_fork_hash = >::get(); - - // If we have a pending change in storage let's check if the current header enacts it. - let enact_change = if let Some(pending_change) = storage.scheduled_set_change(dummy_fork_hash) { - pending_change.height == *header.number() - } else { - // We don't have a scheduled change in storage at the moment. Let's check if the current - // header signals an authority set change. - if let Some(change) = bp_header_chain::find_grandpa_authorities_scheduled_change(&header) { - let next_set = AuthoritySet { - authorities: change.next_authorities, - set_id: storage.current_authority_set().set_id + 1, - }; - - let height = *header.number() + change.delay; - let scheduled_change = ScheduledChange { - authority_set: next_set, - height, - }; - - storage.schedule_next_set_change(dummy_fork_hash, scheduled_change); - - // If the delay is 0 this header will enact the change it signaled - height == *header.number() - } else { - false - } - }; - - if enact_change { - const ENACT_SET_PROOF: &str = "We only set `enact_change` as `true` if we are sure that there is a scheduled - authority set change in storage. Therefore, it must exist."; - - // 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 _ = storage.enact_authority_set(dummy_fork_hash).expect(ENACT_SET_PROOF); - } - - storage.update_best_finalized(header.hash()); - - storage.write_header(&ImportedHeader { - header, - requires_justification: false, - is_finalized: true, - signal_hash: None, - }); -} - -/// Ensure that the origin is either root, or `ModuleOwner`. -fn ensure_owner_or_root(origin: T::Origin) -> Result<(), BadOrigin> { - match origin.into() { - Ok(RawOrigin::Root) => Ok(()), - Ok(RawOrigin::Signed(ref signer)) if Some(signer) == >::module_owner().as_ref() => Ok(()), - _ => Err(BadOrigin), - } -} - -/// Ensure that the pallet is in operational mode (not halted). -fn ensure_operational() -> Result<(), Error> { - if IsHalted::get() { - Err(>::Halted) - } else { - Ok(()) - } -} - -/// (Re)initialize bridge with given header for using it in external benchmarks. -#[cfg(feature = "runtime-benchmarks")] -pub fn initialize_for_benchmarks(header: HeaderOf) { - initialize_bridge::(InitializationData { - header, - authority_list: Vec::new(), // we don't verify any proofs in external benchmarks - set_id: 0, - scheduled_change: None, - is_halted: false, - }); -} - -/// Since this writes to storage with no real checks this should only be used in functions that were -/// called by a trusted origin. -fn initialize_bridge(init_params: InitializationData>) { - let InitializationData { - header, - authority_list, - set_id, - scheduled_change, - is_halted, - } = init_params; - - let initial_hash = header.hash(); - - let mut signal_hash = None; - if let Some(ref change) = scheduled_change { - assert!( - change.height > *header.number(), - "Changes must be scheduled past initial header." - ); - - signal_hash = Some(initial_hash); - >::insert(initial_hash, change); - }; - - >::put(initial_hash); - >::put(header.number()); - >::put(vec![initial_hash]); - >::put(initial_hash); - - let authority_set = AuthoritySet::new(authority_list, set_id); - CurrentAuthoritySet::put(authority_set); - - >::insert( - initial_hash, - ImportedHeader { - header, - requires_justification: false, - is_finalized: true, - signal_hash, - }, - ); - - IsHalted::put(is_halted); -} - -/// Expected interface for interacting with bridge pallet storage. -// TODO: This should be split into its own less-Substrate-dependent crate -pub trait BridgeStorage { - /// The header type being used by the pallet. - type Header: HeaderT; - - /// Write a header to storage. - fn write_header(&mut self, header: &ImportedHeader); - - /// Get the header(s) at the highest known height. - fn best_headers(&self) -> Vec>; - - /// Get the best finalized header the pallet knows of. - /// - /// Returns None if there is no best header. This can only happen if the pallet - /// has not been initialized yet. - fn best_finalized_header(&self) -> ImportedHeader; - - /// Update the best finalized header the pallet knows of. - fn update_best_finalized(&self, hash: ::Hash); - - /// Check if a particular header is known to the pallet. - fn header_exists(&self, hash: ::Hash) -> bool; - - /// Returns a list of headers which require justifications. - /// - /// A header will require a justification if it enacts a new authority set. - fn missing_justifications(&self) -> Vec>; - - /// Get a specific header by its hash. - /// - /// Returns None if it is not known to the pallet. - fn header_by_hash(&self, hash: ::Hash) -> Option>; - - /// Get the current GRANDPA authority set. - fn current_authority_set(&self) -> AuthoritySet; - - /// Update the current GRANDPA authority set. - /// - /// Should only be updated when a scheduled change has been triggered. - fn update_current_authority_set(&self, new_set: AuthoritySet); - - /// Replace the current authority set with the next scheduled set. - /// - /// Returns an error if there is no scheduled authority set to enact. - #[allow(clippy::result_unit_err)] - fn enact_authority_set(&mut self, signal_hash: ::Hash) -> Result<(), ()>; - - /// Get the next scheduled GRANDPA authority set change. - fn scheduled_set_change( - &self, - signal_hash: ::Hash, - ) -> Option::Number>>; - - /// Schedule a GRANDPA authority set change in the future. - /// - /// Takes the hash of the header which scheduled this particular change. - fn schedule_next_set_change( - &mut self, - signal_hash: ::Hash, - next_change: ScheduledChange<::Number>, - ); -} - -/// Used to interact with the pallet storage in a more abstract way. -#[derive(Default, Clone)] -pub struct PalletStorage(PhantomData); - -impl PalletStorage { - fn new() -> Self { - Self(PhantomData::::default()) - } -} - -impl BridgeStorage for PalletStorage { - type Header = BridgedHeader; - - fn write_header(&mut self, header: &ImportedHeader>) { - use core::cmp::Ordering; - - let hash = header.hash(); - let current_height = header.number(); - let best_height = >::get(); - - match current_height.cmp(&best_height) { - Ordering::Equal => { - // Want to avoid duplicates in the case where we're writing a finalized header to - // storage which also happens to be at the best height the best height - let not_duplicate = !>::contains_key(hash); - if not_duplicate { - >::append(hash); - } - } - Ordering::Greater => { - >::kill(); - >::append(hash); - >::put(current_height); - } - Ordering::Less => { - // This is fine. We can still have a valid header, but it might just be on a - // different fork and at a lower height than the "best" overall header. - } - } - - if header.requires_justification { - >::insert(hash, current_height); - } else { - // If the key doesn't exist this is a no-op, so it's fine to call it often - >::remove(hash); - } - - >::insert(hash, header); - } - - fn best_headers(&self) -> Vec>> { - let number = >::get(); - >::get() - .iter() - .map(|hash| HeaderId { number, hash: *hash }) - .collect() - } - - fn best_finalized_header(&self) -> ImportedHeader> { - // We will only construct a dummy header if the pallet is not initialized and someone tries - // to use the public module interface (not dispatchables) to get the best finalized header. - // This is an edge case since this can only really happen when bootstrapping the bridge. - let hash = >::get(); - self.header_by_hash(hash).unwrap_or_else(|| ImportedHeader { - header: >::new( - Default::default(), - Default::default(), - Default::default(), - Default::default(), - Default::default(), - ), - requires_justification: false, - is_finalized: false, - signal_hash: None, - }) - } - - fn update_best_finalized(&self, hash: BridgedBlockHash) { - >::put(hash); - } - - fn header_exists(&self, hash: BridgedBlockHash) -> bool { - >::contains_key(hash) - } - - fn header_by_hash(&self, hash: BridgedBlockHash) -> Option>> { - >::get(hash) - } - - fn missing_justifications(&self) -> Vec>> { - >::iter() - .map(|(hash, number)| HeaderId { number, hash }) - .collect() - } - - fn current_authority_set(&self) -> AuthoritySet { - CurrentAuthoritySet::get() - } - - fn update_current_authority_set(&self, new_set: AuthoritySet) { - CurrentAuthoritySet::put(new_set) - } - - fn enact_authority_set(&mut self, signal_hash: BridgedBlockHash) -> Result<(), ()> { - let new_set = >::take(signal_hash).ok_or(())?.authority_set; - self.update_current_authority_set(new_set); - - Ok(()) - } - - fn scheduled_set_change(&self, signal_hash: BridgedBlockHash) -> Option>> { - >::get(signal_hash) - } - - fn schedule_next_set_change( - &mut self, - signal_hash: BridgedBlockHash, - next_change: ScheduledChange>, - ) { - >::insert(signal_hash, next_change) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::mock::{run_test, test_header, unfinalized_header, Origin, TestHeader, TestRuntime}; - use bp_header_chain::HeaderChain; - use bp_test_utils::{authority_list, ALICE, BOB}; - use frame_support::{assert_err, assert_noop, assert_ok}; - use sp_runtime::DispatchError; - - fn init_with_origin(origin: Origin) -> Result, DispatchError> { - let init_data = InitializationData { - header: test_header(1), - authority_list: authority_list(), - set_id: 1, - scheduled_change: None, - is_halted: false, - }; - - Module::::initialize(origin, init_data.clone()).map(|_| init_data) - } - - #[test] - fn init_root_or_owner_origin_can_initialize_pallet() { - run_test(|| { - assert_noop!(init_with_origin(Origin::signed(1)), DispatchError::BadOrigin); - assert_ok!(init_with_origin(Origin::root())); - - // Reset storage so we can initialize the pallet again - BestFinalized::::kill(); - ModuleOwner::::put(2); - assert_ok!(init_with_origin(Origin::signed(2))); - }) - } - - #[test] - fn init_storage_entries_are_correctly_initialized() { - run_test(|| { - assert!(Module::::best_headers().is_empty()); - assert_eq!(Module::::best_finalized(), test_header(0)); - - let init_data = init_with_origin(Origin::root()).unwrap(); - - let storage = PalletStorage::::new(); - assert!(storage.header_exists(init_data.header.hash())); - assert_eq!( - storage.best_headers()[0], - crate::HeaderId { - number: *init_data.header.number(), - hash: init_data.header.hash() - } - ); - assert_eq!(storage.best_finalized_header().hash(), init_data.header.hash()); - assert_eq!(storage.current_authority_set().authorities, init_data.authority_list); - assert_eq!(IsHalted::get(), false); - }) - } - - #[test] - fn init_can_only_initialize_pallet_once() { - run_test(|| { - assert_ok!(init_with_origin(Origin::root())); - assert_noop!( - init_with_origin(Origin::root()), - >::AlreadyInitialized - ); - }) - } - - #[test] - fn pallet_owner_may_change_owner() { - run_test(|| { - ModuleOwner::::put(2); - - assert_ok!(Module::::set_owner(Origin::root(), Some(1))); - assert_noop!( - Module::::halt_operations(Origin::signed(2)), - DispatchError::BadOrigin, - ); - assert_ok!(Module::::halt_operations(Origin::root())); - - assert_ok!(Module::::set_owner(Origin::signed(1), None)); - assert_noop!( - Module::::resume_operations(Origin::signed(1)), - DispatchError::BadOrigin, - ); - assert_noop!( - Module::::resume_operations(Origin::signed(2)), - DispatchError::BadOrigin, - ); - assert_ok!(Module::::resume_operations(Origin::root())); - }); - } - - #[test] - fn pallet_may_be_halted_by_root() { - run_test(|| { - assert_ok!(Module::::halt_operations(Origin::root())); - assert_ok!(Module::::resume_operations(Origin::root())); - }); - } - - #[test] - fn pallet_may_be_halted_by_owner() { - run_test(|| { - ModuleOwner::::put(2); - - assert_ok!(Module::::halt_operations(Origin::signed(2))); - assert_ok!(Module::::resume_operations(Origin::signed(2))); - - assert_noop!( - Module::::halt_operations(Origin::signed(1)), - DispatchError::BadOrigin, - ); - assert_noop!( - Module::::resume_operations(Origin::signed(1)), - DispatchError::BadOrigin, - ); - - assert_ok!(Module::::halt_operations(Origin::signed(2))); - assert_noop!( - Module::::resume_operations(Origin::signed(1)), - DispatchError::BadOrigin, - ); - }); - } - - #[test] - fn pallet_rejects_transactions_if_halted() { - run_test(|| { - IsHalted::put(true); - - assert_noop!( - Module::::import_signed_header(Origin::signed(1), test_header(1)), - Error::::Halted, - ); - - assert_noop!( - Module::::finalize_header(Origin::signed(1), test_header(1).hash(), vec![]), - Error::::Halted, - ); - }) - } - - #[test] - fn parse_finalized_storage_proof_rejects_proof_on_unknown_header() { - run_test(|| { - assert_noop!( - Module::::parse_finalized_storage_proof( - Default::default(), - StorageProof::new(vec![]), - |_| (), - ), - Error::::UnknownHeader, - ); - }); - } - - #[test] - fn parse_finalized_storage_proof_rejects_proof_on_unfinalized_header() { - run_test(|| { - let mut storage = PalletStorage::::new(); - let header = unfinalized_header(1); - storage.write_header(&header); - - assert_noop!( - Module::::parse_finalized_storage_proof( - header.header.hash(), - StorageProof::new(vec![]), - |_| (), - ), - Error::::UnfinalizedHeader, - ); - }); - } - - #[test] - fn parse_finalized_storage_accepts_valid_proof() { - run_test(|| { - let mut storage = PalletStorage::::new(); - let (state_root, storage_proof) = bp_runtime::craft_valid_storage_proof(); - let mut header = unfinalized_header(1); - header.is_finalized = true; - header.header.set_state_root(state_root); - storage.write_header(&header); - - assert_ok!( - Module::::parse_finalized_storage_proof(header.header.hash(), storage_proof, |_| (),), - (), - ); - }); - } - - #[test] - fn importing_unchecked_headers_works() { - run_test(|| { - init_with_origin(Origin::root()).unwrap(); - let storage = PalletStorage::::new(); - - let header = test_header(2); - assert_ok!(Module::::append_header(header.clone())); - - assert!(storage.header_by_hash(header.hash()).unwrap().is_finalized); - assert_eq!(storage.best_finalized_header().header, header); - assert_eq!(storage.best_headers()[0].hash, header.hash()); - }) - } - - #[test] - fn importing_unchecked_header_ensures_that_chain_is_extended() { - run_test(|| { - init_with_origin(Origin::root()).unwrap(); - - let header = test_header(3); - assert_ok!(Module::::append_header(header)); - - let header = test_header(2); - assert_err!( - Module::::append_header(header), - Error::::ConflictingFork, - ); - - let header = test_header(4); - assert_ok!(Module::::append_header(header)); - }) - } - - #[test] - fn importing_unchecked_headers_enacts_new_authority_set() { - run_test(|| { - init_with_origin(Origin::root()).unwrap(); - let storage = PalletStorage::::new(); - - let next_set_id = 2; - let next_authorities = vec![(ALICE.into(), 1), (BOB.into(), 1)]; - - // Need to update the header digest to indicate that our header signals an authority set - // change. The change will be enacted when we import our header. - let mut header = test_header(2); - header.digest = fork_tests::change_log(0); - - // Let's import our test header - assert_ok!(Module::::append_header(header.clone())); - - // Make sure that our header is the best finalized - assert_eq!(storage.best_finalized_header().header, header); - assert_eq!(storage.best_headers()[0].hash, header.hash()); - - // Make sure that the authority set actually changed upon importing our header - assert_eq!( - storage.current_authority_set(), - AuthoritySet::new(next_authorities, next_set_id), - ); - }) - } - - #[test] - fn importing_unchecked_headers_enacts_new_authority_set_from_old_header() { - run_test(|| { - init_with_origin(Origin::root()).unwrap(); - let storage = PalletStorage::::new(); - - let next_set_id = 2; - let next_authorities = vec![(ALICE.into(), 1), (BOB.into(), 1)]; - - // Need to update the header digest to indicate that our header signals an authority set - // change. However, the change doesn't happen until the next block. - let mut schedules_change = test_header(2); - schedules_change.digest = fork_tests::change_log(1); - let header = test_header(3); - - // Let's import our test headers - assert_ok!(Module::::append_header(schedules_change)); - assert_ok!(Module::::append_header(header.clone())); - - // Make sure that our header is the best finalized - assert_eq!(storage.best_finalized_header().header, header); - assert_eq!(storage.best_headers()[0].hash, header.hash()); - - // Make sure that the authority set actually changed upon importing our header - assert_eq!( - storage.current_authority_set(), - AuthoritySet::new(next_authorities, next_set_id), - ); - }) - } - - #[test] - fn importing_unchecked_header_can_enact_set_change_scheduled_at_genesis() { - run_test(|| { - let storage = PalletStorage::::new(); - - let next_authorities = vec![(ALICE.into(), 1)]; - let next_set_id = 2; - let next_authority_set = AuthoritySet::new(next_authorities.clone(), next_set_id); - - let first_scheduled_change = ScheduledChange { - authority_set: next_authority_set, - height: 2, - }; - - let init_data = InitializationData { - header: test_header(1), - authority_list: authority_list(), - set_id: 1, - scheduled_change: Some(first_scheduled_change), - is_halted: false, - }; - - assert_ok!(Module::::initialize(Origin::root(), init_data)); - - // We are expecting an authority set change at height 2, so this header should enact - // that upon being imported. - assert_ok!(Module::::append_header(test_header(2))); - - // Make sure that the authority set actually changed upon importing our header - assert_eq!( - storage.current_authority_set(), - AuthoritySet::new(next_authorities, next_set_id), - ); - }) - } -} diff --git a/bridges/modules/substrate/src/mock.rs b/bridges/modules/substrate/src/mock.rs deleted file mode 100644 index f2bb422d3d..0000000000 --- a/bridges/modules/substrate/src/mock.rs +++ /dev/null @@ -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 . - -//! 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; -pub type TestNumber = BridgedBlockNumber; -pub type TestHash = BridgedBlockHash; - -type Block = frame_system::mocking::MockBlock; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; - -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}, - 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; - 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 = ::BlockNumber; - type Hash = ::Hash; - type Hasher = ::Hashing; - type Header = ::Header; -} - -pub fn run_test(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 { - crate::storage::ImportedHeader { - header: test_header(num), - requires_justification: false, - is_finalized: false, - signal_hash: None, - } -} diff --git a/bridges/modules/substrate/src/storage.rs b/bridges/modules/substrate/src/storage.rs deleted file mode 100644 index fe730c45d9..0000000000 --- a/bridges/modules/substrate/src/storage.rs +++ /dev/null @@ -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 . - -//! 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 { - /// 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>, - /// 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 { - /// 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 { - /// 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, -} - -impl core::ops::Deref for ImportedHeader { - type Target = H; - - fn deref(&self) -> &H { - &self.header - } -} diff --git a/bridges/modules/substrate/src/verifier.rs b/bridges/modules/substrate/src/verifier.rs deleted file mode 100644 index 1b9e11dc29..0000000000 --- a/bridges/modules/substrate/src/verifier.rs +++ /dev/null @@ -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 . - -//! 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); - -impl From<&[u8]> for FinalityProof { - fn from(proof: &[u8]) -> Self { - Self(proof.to_vec()) - } -} - -impl From> for FinalityProof { - fn from(proof: Vec) -> 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 { - pub storage: S, -} - -impl Verifier -where - S: BridgeStorage
, - 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::::decode(&mut proof.0.as_slice()) - .map_err(|_| FinalizationError::InvalidJustification)?; - - verify_justification::( - (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( - storage: &S, - ancestor: ImportedHeader, - child: ImportedHeader, -) -> Option>> -where - S: BridgeStorage
, - 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, - set_id: SetId, - height: TestNumber, - ) -> ScheduledChange { - 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>( - storage: &mut S, - headers: Vec<(u64, bool, bool)>, - ) -> Vec> { - let mut imported_headers = vec![]; - let genesis = ImportedHeader { - header: test_header(0), - requires_justification: false, - is_finalized: true, - signal_hash: None, - }; - - >::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>( - storage: &mut S, - headers: Vec, - ) -> Vec> { - 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::::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::::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::::new(); - let header = test_header(1); - >::put(header.hash()); - - let imported_header = ImportedHeader { - header: header.clone(), - requires_justification: false, - is_finalized: false, - signal_hash: None, - }; - >::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::::new(); - let parent = test_header(1); - let parent_hash = parent.hash(); - >::put(parent.hash()); - - let imported_header = ImportedHeader { - header: parent, - requires_justification: false, - is_finalized: true, - signal_hash: None, - }; - >::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::::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!(>::get(), 1); - }) - } - - #[test] - fn correctly_updates_the_best_header_given_a_better_header() { - run_test(|| { - let mut storage = PalletStorage::::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!(>::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!(>::get(), 2); - }) - } - - #[test] - fn doesnt_write_best_header_twice_upon_finalization() { - run_test(|| { - let mut storage = PalletStorage::::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::::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::::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::::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::::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::::ScheduledChange(sp_finality_grandpa::ScheduledChange { - next_authorities: vec![(ALICE.into(), u64::MAX), (BOB.into(), u64::MAX)], - delay: 0, - }); - - header.digest = Digest:: { - 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::::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::::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::::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::::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 - >::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 - ); - }); - } -} diff --git a/bridges/relays/bin-substrate/src/cli/encode_call.rs b/bridges/relays/bin-substrate/src/cli/encode_call.rs index 3afe8d43b9..2d97c67800 100644 --- a/bridges/relays/bin-substrate/src/cli/encode_call.rs +++ b/bridges/relays/bin-substrate/src/cli/encode_call.rs @@ -243,7 +243,7 @@ mod tests { // then assert_eq!( format!("{:?}", hex), - "0x0d00d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27de5c0" + "0x0c00d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27de5c0" ); } } diff --git a/bridges/relays/client-millau/src/lib.rs b/bridges/relays/client-millau/src/lib.rs index 5b521012c0..1708a8efa1 100644 --- a/bridges/relays/client-millau/src/lib.rs +++ b/bridges/relays/client-millau/src/lib.rs @@ -22,8 +22,6 @@ use sp_core::{storage::StorageKey, Pair}; use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount}; use std::time::Duration; -pub use millau_runtime::BridgeRialtoCall; - /// Millau header id. pub type HeaderId = relay_utils::HeaderId; diff --git a/bridges/relays/client-rialto/src/lib.rs b/bridges/relays/client-rialto/src/lib.rs index 7061205ec8..0ddc03681d 100644 --- a/bridges/relays/client-rialto/src/lib.rs +++ b/bridges/relays/client-rialto/src/lib.rs @@ -22,8 +22,6 @@ use sp_core::{storage::StorageKey, Pair}; use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount}; use std::time::Duration; -pub use rialto_runtime::BridgeMillauCall; - /// Rialto header id. pub type HeaderId = relay_utils::HeaderId;