diff --git a/bridges/modules/substrate/Cargo.toml b/bridges/modules/substrate/Cargo.toml
index 2d16493a36..ae70b54af4 100644
--- a/bridges/modules/substrate/Cargo.toml
+++ b/bridges/modules/substrate/Cargo.toml
@@ -9,10 +9,10 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
[dependencies]
bp-header-chain = { path = "../../primitives/header-chain", default-features = false }
-bp-substrate = { path = "../../primitives/substrate", default-features = false }
finality-grandpa = { version = "0.12.3", default-features = false }
hash-db = { version = "0.15.2", default-features = false }
serde = { version = "1.0", optional = true }
+num-traits = { version = "0.2", default-features = false }
[dependencies.codec]
package = "parity-scale-codec"
@@ -89,6 +89,7 @@ std = [
"finality-grandpa/std",
"frame-support/std",
"frame-system/std",
+ "num-traits/std",
"serde",
"sp-finality-grandpa/std",
"sp-runtime/std",
diff --git a/bridges/modules/substrate/src/justification.rs b/bridges/modules/substrate/src/justification.rs
index 4887e18089..100049f810 100644
--- a/bridges/modules/substrate/src/justification.rs
+++ b/bridges/modules/substrate/src/justification.rs
@@ -14,12 +14,11 @@
// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see .
+//! Module for checking Grandpa Finality Proofs.
+//!
//! Adapted copy of substrate/client/finality-grandpa/src/justification.rs. If origin
//! will ever be moved to the sp_finality_grandpa, we should reuse that implementation.
-// TODO: remove on actual use
-#![allow(dead_code)]
-
use codec::Decode;
use finality_grandpa::{voter_set::VoterSet, Chain, Error as GrandpaError};
use frame_support::RuntimeDebug;
@@ -115,16 +114,21 @@ where
Ok(())
}
-/// GRANDPA justification of the bridged chain
+/// A Grandpa Justification is a proof that a given header was finalized
+/// at a certain height and with a certain set of authorities.
+///
+/// This particular proof is used to prove that headers on a bridged chain
+/// (so not our chain) have been finalized correctly.
#[derive(Decode, RuntimeDebug)]
#[cfg_attr(test, derive(codec::Encode))]
-struct GrandpaJustification {
+pub(crate) struct GrandpaJustification {
round: u64,
commit: finality_grandpa::Commit,
votes_ancestries: Vec,
}
/// A utility trait implementing `finality_grandpa::Chain` using a given set of headers.
+#[derive(RuntimeDebug)]
struct AncestryChain {
ancestry: BTreeMap,
}
@@ -170,56 +174,30 @@ where
}
#[cfg(test)]
-mod tests {
+pub(crate) mod tests {
use super::*;
+ use crate::mock::helpers::*;
use codec::Encode;
use sp_core::H256;
+ use sp_finality_grandpa::{AuthorityId, AuthorityWeight};
use sp_keyring::Ed25519Keyring;
- use sp_runtime::traits::BlakeTwo256;
const TEST_GRANDPA_ROUND: u64 = 1;
const TEST_GRANDPA_SET_ID: SetId = 1;
- type TestHeader = sp_runtime::generic::Header;
-
- fn header(index: u8) -> TestHeader {
- TestHeader::new(
- index as _,
- Default::default(),
- Default::default(),
- if index == 0 {
- Default::default()
- } else {
- header(index - 1).hash()
- },
- Default::default(),
- )
- }
-
- fn header_id(index: u8) -> (H256, u64) {
- (header(index).hash(), index as _)
- }
-
- fn authorities_set() -> VoterSet {
- VoterSet::new(vec![
- (Ed25519Keyring::Alice.public().into(), 1),
- (Ed25519Keyring::Bob.public().into(), 1),
- (Ed25519Keyring::Charlie.public().into(), 1),
- ])
- .unwrap()
- }
-
- fn signed_precommit(
+ pub(crate) fn signed_precommit(
signer: Ed25519Keyring,
- target: (H256, u64),
+ target: HeaderId,
+ round: u64,
+ set_id: SetId,
) -> finality_grandpa::SignedPrecommit {
let precommit = finality_grandpa::Precommit {
target_hash: target.0,
target_number: target.1,
};
let encoded = sp_finality_grandpa::localized_payload(
- TEST_GRANDPA_ROUND,
- TEST_GRANDPA_SET_ID,
+ round,
+ set_id,
&finality_grandpa::Message::Precommit(precommit.clone()),
);
let signature = signer.sign(&encoded[..]).into();
@@ -230,26 +208,59 @@ mod tests {
}
}
- fn make_justification_for_header_1() -> GrandpaJustification {
- GrandpaJustification {
- round: TEST_GRANDPA_ROUND,
- commit: finality_grandpa::Commit {
- target_hash: header_id(1).0,
- target_number: header_id(1).1,
- precommits: vec![
- signed_precommit(Ed25519Keyring::Alice, header_id(2)),
- signed_precommit(Ed25519Keyring::Bob, header_id(3)),
- signed_precommit(Ed25519Keyring::Charlie, header_id(4)),
- ],
- },
- votes_ancestries: vec![header(2), header(3), header(4)],
+ pub(crate) fn make_justification_for_header(
+ header: &TestHeader,
+ round: u64,
+ set_id: SetId,
+ authorities: &[(AuthorityId, AuthorityWeight)],
+ ) -> GrandpaJustification {
+ let (target_hash, target_number) = (header.hash(), *header.number());
+ let mut precommits = vec![];
+ let mut votes_ancestries = vec![];
+
+ // We want to make sure that the header included in the vote ancestries
+ // is actually related to our target header
+ let mut precommit_header = test_header(target_number + 1);
+ precommit_header.parent_hash = target_hash;
+
+ // I'm using the same header for all the voters since it doesn't matter as long
+ // as they all vote on blocks _ahead_ of the one we're interested in finalizing
+ for (id, _weight) in authorities.iter() {
+ let signer = extract_keyring(&id);
+ let precommit = signed_precommit(
+ signer,
+ (precommit_header.hash(), *precommit_header.number()),
+ round,
+ set_id,
+ );
+ precommits.push(precommit);
+ votes_ancestries.push(precommit_header.clone());
}
+
+ GrandpaJustification {
+ round,
+ commit: finality_grandpa::Commit {
+ target_hash,
+ target_number,
+ precommits,
+ },
+ votes_ancestries,
+ }
+ }
+
+ pub(crate) fn make_justification_for_header_1() -> GrandpaJustification {
+ make_justification_for_header(
+ &test_header(1),
+ TEST_GRANDPA_ROUND,
+ TEST_GRANDPA_SET_ID,
+ &authority_list(),
+ )
}
#[test]
fn justification_with_invalid_encoding_rejected() {
assert_eq!(
- verify_justification::(header_id(1), TEST_GRANDPA_SET_ID, authorities_set(), &[],),
+ verify_justification::(header_id(1), TEST_GRANDPA_SET_ID, voter_set(), &[],),
Err(Error::JustificationDecode),
);
}
@@ -260,7 +271,7 @@ mod tests {
verify_justification::(
header_id(2),
TEST_GRANDPA_SET_ID,
- authorities_set(),
+ voter_set(),
&make_justification_for_header_1().encode(),
),
Err(Error::InvalidJustificationTarget),
@@ -273,12 +284,7 @@ mod tests {
justification.commit.precommits.clear();
assert_eq!(
- verify_justification::(
- header_id(1),
- TEST_GRANDPA_SET_ID,
- authorities_set(),
- &justification.encode(),
- ),
+ verify_justification::(header_id(1), TEST_GRANDPA_SET_ID, voter_set(), &justification.encode(),),
Err(Error::InvalidJustificationCommit),
);
}
@@ -289,12 +295,7 @@ mod tests {
justification.commit.precommits[0].signature = Default::default();
assert_eq!(
- verify_justification::(
- header_id(1),
- TEST_GRANDPA_SET_ID,
- authorities_set(),
- &justification.encode(),
- ),
+ verify_justification::(header_id(1), TEST_GRANDPA_SET_ID, voter_set(), &justification.encode(),),
Err(Error::InvalidAuthoritySignature),
);
}
@@ -302,15 +303,10 @@ mod tests {
#[test]
fn justification_with_invalid_precommit_ancestry() {
let mut justification = make_justification_for_header_1();
- justification.votes_ancestries.push(header(10));
+ justification.votes_ancestries.push(test_header(10));
assert_eq!(
- verify_justification::(
- header_id(1),
- TEST_GRANDPA_SET_ID,
- authorities_set(),
- &justification.encode(),
- ),
+ verify_justification::(header_id(1), TEST_GRANDPA_SET_ID, voter_set(), &justification.encode(),),
Err(Error::InvalidPrecommitAncestries),
);
}
@@ -321,7 +317,7 @@ mod tests {
verify_justification::(
header_id(1),
TEST_GRANDPA_SET_ID,
- authorities_set(),
+ voter_set(),
&make_justification_for_header_1().encode(),
),
Ok(()),
diff --git a/bridges/modules/substrate/src/lib.rs b/bridges/modules/substrate/src/lib.rs
index 4fb9dbd3d5..dfc3cb1811 100644
--- a/bridges/modules/substrate/src/lib.rs
+++ b/bridges/modules/substrate/src/lib.rs
@@ -31,43 +31,100 @@
// Runtime-generated enums
#![allow(clippy::large_enum_variant)]
-use bp_substrate::{AuthoritySet, ImportedHeader, ScheduledChange};
-use frame_support::{decl_error, decl_module, decl_storage, dispatch};
+use crate::storage::{AuthoritySet, ImportedHeader, ScheduledChange};
+use codec::{Codec, EncodeLike};
+use frame_support::{
+ decl_error, decl_module, decl_storage,
+ dispatch::{DispatchResult, Parameter},
+};
use frame_system::ensure_signed;
-use sp_runtime::traits::Header as HeaderT;
-use sp_std::{marker::PhantomData, prelude::*};
+use num_traits::AsPrimitive;
+use sp_runtime::traits::{
+ AtLeast32BitUnsigned, Hash as HashT, Header as HeaderT, MaybeDisplay, MaybeMallocSizeOf, MaybeSerializeDeserialize,
+ Member, SimpleBitOps,
+};
+use sp_std::{fmt::Debug, marker::PhantomData, prelude::*, str::FromStr};
mod justification;
+mod storage;
mod storage_proof;
mod verifier;
#[cfg(test)]
mod mock;
-type Hash = ::Hash;
-type Number = ::Number;
+pub trait Trait: frame_system::Trait {
+ /// A type that fulfills the abstract idea of what a Substrate header is.
+ // See here for more info:
+ // https://crates.parity.io/sp_runtime/traits/trait.Header.html
+ type BridgedHeader: Parameter + HeaderT;
-pub trait Trait: frame_system::Trait {}
+ /// A type that fulfills the abstract idea of what a Substrate block number is.
+ // Constraits come from the associated Number type of `sp_runtime::traits::Header`
+ // See here for more info:
+ // https://crates.parity.io/sp_runtime/traits/trait.Header.html#associatedtype.Number
+ //
+ // Note that the `AsPrimitive` trait is required by the Grandpa justification
+ // verifier, and is not usually part of a Substrate Header's Number type.
+ type BridgedBlockNumber: Parameter
+ + Member
+ + MaybeSerializeDeserialize
+ + Debug
+ + sp_std::hash::Hash
+ + Copy
+ + MaybeDisplay
+ + AtLeast32BitUnsigned
+ + Codec
+ + FromStr
+ + MaybeMallocSizeOf
+ + AsPrimitive;
+
+ /// A type that fulfills the abstract idea of what a Substrate hash is.
+ // Constraits come from the associated Hash type of `sp_runtime::traits::Header`
+ // See here for more info:
+ // https://crates.parity.io/sp_runtime/traits/trait.Header.html#associatedtype.Hash
+ type BridgedBlockHash: Parameter
+ + Member
+ + MaybeSerializeDeserialize
+ + Debug
+ + sp_std::hash::Hash
+ + Ord
+ + Copy
+ + MaybeDisplay
+ + Default
+ + SimpleBitOps
+ + Codec
+ + AsRef<[u8]>
+ + AsMut<[u8]>
+ + MaybeMallocSizeOf
+ + EncodeLike;
+
+ /// A type that fulfills the abstract idea of what a Substrate hasher (a type
+ /// that produces hashes) is.
+ // Constraits come from the associated Hashing type of `sp_runtime::traits::Header`
+ // See here for more info:
+ // https://crates.parity.io/sp_runtime/traits/trait.Header.html#associatedtype.Hashing
+ type BridgedBlockHasher: HashT