diff --git a/bridges/bin/millau/runtime/Cargo.toml b/bridges/bin/millau/runtime/Cargo.toml index 7cc68340f6..0f23a9442f 100644 --- a/bridges/bin/millau/runtime/Cargo.toml +++ b/bridges/bin/millau/runtime/Cargo.toml @@ -14,12 +14,14 @@ serde = { version = "1.0.121", optional = true, features = ["derive"] } # Bridge dependencies +bp-header-chain = { path = "../../../primitives/header-chain", default-features = false } bp-message-lane = { path = "../../../primitives/message-lane", default-features = false } bp-millau = { path = "../../../primitives/millau", default-features = false } bp-rialto = { path = "../../../primitives/rialto", default-features = false } bp-runtime = { path = "../../../primitives/runtime", default-features = false } bridge-runtime-common = { path = "../../runtime-common", default-features = false } pallet-bridge-call-dispatch = { path = "../../../modules/call-dispatch", default-features = false } +pallet-finality-verifier = { path = "../../../modules/finality-verifier", default-features = false } pallet-message-lane = { path = "../../../modules/message-lane", default-features = false } pallet-shift-session-manager = { path = "../../../modules/shift-session-manager", default-features = false } pallet-substrate-bridge = { path = "../../../modules/substrate", default-features = false } @@ -58,6 +60,7 @@ wasm-builder-runner = { package = "substrate-wasm-builder-runner", version = "2. [features] default = ["std"] std = [ + "bp-header-chain/std", "bp-message-lane/std", "bp-millau/std", "bp-rialto/std", @@ -71,6 +74,7 @@ std = [ "pallet-aura/std", "pallet-balances/std", "pallet-bridge-call-dispatch/std", + "pallet-finality-verifier/std", "pallet-grandpa/std", "pallet-message-lane/std", "pallet-randomness-collective-flip/std", diff --git a/bridges/bin/millau/runtime/src/lib.rs b/bridges/bin/millau/runtime/src/lib.rs index 3164c8a9d8..3bf9309dfb 100644 --- a/bridges/bin/millau/runtime/src/lib.rs +++ b/bridges/bin/millau/runtime/src/lib.rs @@ -132,15 +132,6 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { transaction_version: 1, }; -pub const MILLISECS_PER_BLOCK: u64 = 6000; - -pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; - -// These time units are defined in number of blocks. -pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); -pub const HOURS: BlockNumber = MINUTES * 60; -pub const DAYS: BlockNumber = HOURS * 24; - /// The version information used to identify this runtime when compiled natively. #[cfg(feature = "std")] pub fn native_version() -> NativeVersion { @@ -236,7 +227,7 @@ impl pallet_grandpa::Config for Runtime { } parameter_types! { - pub const MinimumPeriod: u64 = SLOT_DURATION / 2; + pub const MinimumPeriod: u64 = bp_millau::SLOT_DURATION / 2; } impl pallet_timestamp::Config for Runtime { @@ -287,7 +278,7 @@ impl pallet_sudo::Config for Runtime { parameter_types! { /// Authorities are changing every 5 minutes. - pub const Period: BlockNumber = 5 * MINUTES; + pub const Period: BlockNumber = bp_millau::SESSION_LENGTH; pub const Offset: BlockNumber = 0; } @@ -309,6 +300,19 @@ impl pallet_substrate_bridge::Config for Runtime { type BridgedChain = bp_rialto::Rialto; } +parameter_types! { + // We'll use the length of a session on the bridged chain as our bound since GRANDPA is + // guaranteed to produce a justification every session. + pub const MaxHeadersInSingleProof: bp_rialto::BlockNumber = bp_rialto::SESSION_LENGTH; +} + +impl pallet_finality_verifier::Config for Runtime { + type BridgedChain = bp_rialto::Rialto; + type HeaderChain = pallet_substrate_bridge::Module; + type AncestryChecker = bp_header_chain::LinearAncestryChecker; + type MaxHeadersInSingleProof = MaxHeadersInSingleProof; +} + impl pallet_shift_session_manager::Config for Runtime {} parameter_types! { @@ -362,6 +366,7 @@ construct_runtime!( BridgeRialto: pallet_substrate_bridge::{Module, Call, Storage, Config}, BridgeRialtoMessageLane: pallet_message_lane::{Module, Call, Storage, Event}, BridgeCallDispatch: pallet_bridge_call_dispatch::{Module, Event}, + BridgeFinalityVerifier: pallet_finality_verifier::{Module, Call}, System: frame_system::{Module, Call, Config, Storage, Event}, RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Module, Call, Storage}, Timestamp: pallet_timestamp::{Module, Call, Storage, Inherent}, diff --git a/bridges/bin/rialto/runtime/Cargo.toml b/bridges/bin/rialto/runtime/Cargo.toml index dab6b2c6d2..855a1754e7 100644 --- a/bridges/bin/rialto/runtime/Cargo.toml +++ b/bridges/bin/rialto/runtime/Cargo.toml @@ -27,6 +27,7 @@ bridge-runtime-common = { path = "../../runtime-common", default-features = fals pallet-bridge-eth-poa = { path = "../../../modules/ethereum", default-features = false } pallet-bridge-call-dispatch = { path = "../../../modules/call-dispatch", default-features = false } pallet-bridge-currency-exchange = { path = "../../../modules/currency-exchange", default-features = false } +pallet-finality-verifier = { path = "../../../modules/finality-verifier", default-features = false } pallet-substrate-bridge = { path = "../../../modules/substrate", default-features = false } pallet-message-lane = { path = "../../../modules/message-lane", default-features = false } pallet-shift-session-manager = { path = "../../../modules/shift-session-manager", default-features = false } @@ -90,6 +91,7 @@ std = [ "pallet-bridge-eth-poa/std", "pallet-bridge-call-dispatch/std", "pallet-bridge-currency-exchange/std", + "pallet-finality-verifier/std", "pallet-grandpa/std", "pallet-message-lane/std", "pallet-randomness-collective-flip/std", diff --git a/bridges/bin/rialto/runtime/src/lib.rs b/bridges/bin/rialto/runtime/src/lib.rs index 677ca283c4..f7717b1c76 100644 --- a/bridges/bin/rialto/runtime/src/lib.rs +++ b/bridges/bin/rialto/runtime/src/lib.rs @@ -140,15 +140,6 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { transaction_version: 1, }; -pub const MILLISECS_PER_BLOCK: u64 = 6000; - -pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; - -// These time units are defined in number of blocks. -pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); -pub const HOURS: BlockNumber = MINUTES * 60; -pub const DAYS: BlockNumber = HOURS * 24; - /// The version information used to identify this runtime when compiled natively. #[cfg(feature = "std")] pub fn native_version() -> NativeVersion { @@ -344,7 +335,7 @@ impl pallet_grandpa::Config for Runtime { } parameter_types! { - pub const MinimumPeriod: u64 = SLOT_DURATION / 2; + pub const MinimumPeriod: u64 = bp_rialto::SLOT_DURATION / 2; } impl pallet_timestamp::Config for Runtime { @@ -394,7 +385,7 @@ impl pallet_sudo::Config for Runtime { } parameter_types! { - pub const Period: BlockNumber = 4; + pub const Period: BlockNumber = bp_rialto::SESSION_LENGTH; pub const Offset: BlockNumber = 0; } @@ -416,6 +407,19 @@ impl pallet_substrate_bridge::Config for Runtime { type BridgedChain = bp_millau::Millau; } +parameter_types! { + // We'll use the length of a session on the bridged chain as our bound since GRANDPA is + // guaranteed to produce a justification every session. + pub const MaxHeadersInSingleProof: bp_millau::BlockNumber = bp_millau::SESSION_LENGTH; +} + +impl pallet_finality_verifier::Config for Runtime { + type BridgedChain = bp_millau::Millau; + type HeaderChain = pallet_substrate_bridge::Module; + type AncestryChecker = bp_header_chain::LinearAncestryChecker; + type MaxHeadersInSingleProof = MaxHeadersInSingleProof; +} + impl pallet_shift_session_manager::Config for Runtime {} parameter_types! { @@ -471,6 +475,7 @@ construct_runtime!( BridgeRialtoCurrencyExchange: pallet_bridge_currency_exchange::::{Module, Call}, BridgeKovanCurrencyExchange: pallet_bridge_currency_exchange::::{Module, Call}, BridgeMillau: pallet_substrate_bridge::{Module, Call, Storage, Config}, + BridgeFinalityVerifier: pallet_finality_verifier::{Module, Call}, BridgeCallDispatch: pallet_bridge_call_dispatch::{Module, Event}, BridgeMillauMessageLane: pallet_message_lane::{Module, Call, Storage, Event}, System: frame_system::{Module, Call, Config, Storage, Event}, diff --git a/bridges/modules/finality-verifier/Cargo.toml b/bridges/modules/finality-verifier/Cargo.toml index b98f995b61..c1e09c7cf9 100644 --- a/bridges/modules/finality-verifier/Cargo.toml +++ b/bridges/modules/finality-verifier/Cargo.toml @@ -11,6 +11,7 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0" codec = { package = "parity-scale-codec", version = "1.3.1", default-features = false } finality-grandpa = { version = "0.12.3", default-features = false } serde = { version = "1.0", optional = true } +num-traits = { version = "0.2", default-features = false } # Bridge Dependencies @@ -39,6 +40,7 @@ std = [ "finality-grandpa/std", "frame-support/std", "frame-system/std", + "num-traits/std", "serde", "sp-runtime/std", "sp-std/std", diff --git a/bridges/modules/finality-verifier/src/lib.rs b/bridges/modules/finality-verifier/src/lib.rs index bfb5394705..fb708ae294 100644 --- a/bridges/modules/finality-verifier/src/lib.rs +++ b/bridges/modules/finality-verifier/src/lib.rs @@ -37,11 +37,16 @@ use bp_runtime::{Chain, HeaderOf}; use finality_grandpa::voter_set::VoterSet; use frame_support::{dispatch::DispatchError, ensure, traits::Get}; use frame_system::ensure_signed; +use num_traits::AsPrimitive; use sp_runtime::traits::Header as HeaderT; +use sp_std::vec::Vec; #[cfg(test)] mod mock; +// Re-export in crate namespace for `construct_runtime!` +pub use pallet::*; + #[frame_support::pallet] pub mod pallet { use super::*; @@ -69,7 +74,7 @@ pub mod pallet { /// The maximum length of headers we can have in a single ancestry proof. This prevents /// unbounded iteration when verifying proofs. #[pallet::constant] - type MaxHeadersInSingleProof: Get; + type MaxHeadersInSingleProof: Get<::BlockNumber>; } #[pallet::pallet] @@ -100,7 +105,7 @@ pub mod pallet { let _ = ensure_signed(origin)?; ensure!( - ancestry_proof.len() <= T::MaxHeadersInSingleProof::get() as usize, + ancestry_proof.len() <= T::MaxHeadersInSingleProof::get().as_(), >::OversizedAncestryProof ); @@ -149,7 +154,6 @@ pub mod pallet { #[cfg(test)] mod tests { - use super::pallet::*; use super::*; use crate::mock::{run_test, test_header, Origin, TestRuntime}; use bp_test_utils::{authority_list, make_justification_for_header}; diff --git a/bridges/primitives/header-chain/src/lib.rs b/bridges/primitives/header-chain/src/lib.rs index b65325b850..a9623b13dc 100644 --- a/bridges/primitives/header-chain/src/lib.rs +++ b/bridges/primitives/header-chain/src/lib.rs @@ -27,7 +27,9 @@ use core::fmt::Debug; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; use sp_finality_grandpa::{AuthorityList, SetId}; +use sp_runtime::traits::Header as HeaderT; use sp_runtime::RuntimeDebug; +use sp_std::vec::Vec; pub mod justification; @@ -110,3 +112,101 @@ impl AncestryChecker for () { true } } + +/// A simple ancestry checker which verifies ancestry by walking every header between `child` and +/// `ancestor`. +pub struct LinearAncestryChecker; + +impl AncestryChecker> for LinearAncestryChecker { + fn are_ancestors(ancestor: &H, child: &H, proof: &Vec) -> bool { + // You can't be your own parent + if proof.len() < 2 { + return false; + } + + // Let's make sure that the given headers are actually in the proof + match proof.first() { + Some(first) if first == ancestor => {} + _ => return false, + } + + match proof.last() { + Some(last) if last == child => {} + _ => return false, + } + + // Now we actually check the proof + for i in 1..proof.len() { + if &proof[i - 1].hash() != proof[i].parent_hash() { + return false; + } + } + + true + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bp_test_utils::test_header; + use sp_runtime::testing::Header; + + #[test] + fn can_verify_ancestry_correctly() { + let ancestor: Header = test_header(1); + let header2: Header = test_header(2); + let header3: Header = test_header(3); + let child: Header = test_header(4); + + let ancestry_proof = vec![ancestor.clone(), header2, header3, child.clone()]; + + assert!(LinearAncestryChecker::are_ancestors(&ancestor, &child, &ancestry_proof)); + } + + #[test] + fn does_not_verify_invalid_proof() { + let ancestor: Header = test_header(1); + let header2: Header = test_header(2); + let header3: Header = test_header(3); + let child: Header = test_header(4); + + let ancestry_proof = vec![ancestor.clone(), header3, header2, child.clone()]; + + let invalid = !LinearAncestryChecker::are_ancestors(&ancestor, &child, &ancestry_proof); + assert!(invalid); + } + + #[test] + fn header_is_not_allowed_to_be_its_own_ancestor() { + let ancestor: Header = test_header(1); + let child: Header = ancestor.clone(); + let ancestry_proof = vec![ancestor.clone()]; + + let invalid = !LinearAncestryChecker::are_ancestors(&ancestor, &child, &ancestry_proof); + assert!(invalid); + } + + #[test] + fn proof_is_considered_invalid_if_child_and_ancestor_do_not_match() { + let ancestor: Header = test_header(1); + let header2: Header = test_header(2); + let header3: Header = test_header(3); + let child: Header = test_header(4); + + let ancestry_proof = vec![ancestor, header3.clone(), header2.clone(), child]; + + let invalid = !LinearAncestryChecker::are_ancestors(&header2, &header3, &ancestry_proof); + assert!(invalid); + } + + #[test] + fn empty_proof_is_invalid() { + let ancestor: Header = test_header(1); + let child: Header = ancestor.clone(); + let ancestry_proof = vec![]; + + let invalid = !LinearAncestryChecker::are_ancestors(&ancestor, &child, &ancestry_proof); + assert!(invalid); + } +} diff --git a/bridges/primitives/millau/src/lib.rs b/bridges/primitives/millau/src/lib.rs index 5c0febdca3..b6f32582e9 100644 --- a/bridges/primitives/millau/src/lib.rs +++ b/bridges/primitives/millau/src/lib.rs @@ -83,6 +83,24 @@ pub const MAX_SINGLE_MESSAGE_DELIVERY_TX_WEIGHT: Weight = 1_500_000_000; /// runtime upgrades. pub const MAX_SINGLE_MESSAGE_DELIVERY_CONFIRMATION_TX_WEIGHT: Weight = 2_000_000_000; +/// The length of a session (how often authorities change) on Millau measured in of number of blocks. +pub const SESSION_LENGTH: BlockNumber = 5 * time_units::MINUTES; + +/// Re-export `time_units` to make usage easier. +pub use time_units::*; + +/// Human readable time units defined in terms of number of blocks. +pub mod time_units { + use super::BlockNumber; + + pub const MILLISECS_PER_BLOCK: u64 = 6000; + pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; + + pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); + pub const HOURS: BlockNumber = MINUTES * 60; + pub const DAYS: BlockNumber = HOURS * 24; +} + /// Block number type used in Millau. pub type BlockNumber = u64; diff --git a/bridges/primitives/rialto/src/lib.rs b/bridges/primitives/rialto/src/lib.rs index b347ce5ec5..715004efba 100644 --- a/bridges/primitives/rialto/src/lib.rs +++ b/bridges/primitives/rialto/src/lib.rs @@ -74,6 +74,24 @@ pub const MAX_SINGLE_MESSAGE_DELIVERY_TX_WEIGHT: Weight = 1_500_000_000; /// runtime upgrades. pub const MAX_SINGLE_MESSAGE_DELIVERY_CONFIRMATION_TX_WEIGHT: Weight = 2_000_000_000; +/// The length of a session (how often authorities change) on Rialto measured in of number of blocks. +pub const SESSION_LENGTH: BlockNumber = 4; + +/// Re-export `time_units` to make usage easier. +pub use time_units::*; + +/// Human readable time units defined in terms of number of blocks. +pub mod time_units { + use super::BlockNumber; + + pub const MILLISECS_PER_BLOCK: u64 = 6000; + pub const SLOT_DURATION: u64 = MILLISECS_PER_BLOCK; + + pub const MINUTES: BlockNumber = 60_000 / (MILLISECS_PER_BLOCK as BlockNumber); + pub const HOURS: BlockNumber = MINUTES * 60; + pub const DAYS: BlockNumber = HOURS * 24; +} + /// Block number type used in Rialto. pub type BlockNumber = u32;