Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion bin/millau/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,11 @@ pallet_bridge_grandpa::declare_bridge_reject_obsolete_grandpa_header! {
Call::BridgeWestendGrandpa => WestendGrandpaInstance
}

pallet_bridge_parachains::declare_bridge_reject_obsolete_parachain_header! {
Runtime,
Call::BridgeRialtoParachains => WithRialtoParachainsInstance
}

/// The address format for describing accounts.
pub type Address = AccountId;
/// Block header type as expected by this runtime.
Expand All @@ -586,6 +591,7 @@ pub type SignedExtra = (
frame_system::CheckWeight<Runtime>,
pallet_transaction_payment::ChargeTransactionPayment<Runtime>,
BridgeRejectObsoleteGrandpaHeader,
BridgeRejectObsoleteParachainHeader,
);
/// The payload being signed in transactions.
pub type SignedPayload = generic::SignedPayload<Call, SignedExtra>;
Expand Down Expand Up @@ -994,7 +1000,11 @@ impl_runtime_apis! {
parachains: &[bp_polkadot_core::parachains::ParaId],
parachain_head_size: u32,
proof_size: bp_runtime::StorageProofSize,
) -> (pallet_bridge_parachains::RelayBlockHash, bp_polkadot_core::parachains::ParaHeadsProof) {
) -> (
pallet_bridge_parachains::RelayBlockNumber,
pallet_bridge_parachains::RelayBlockHash,
bp_polkadot_core::parachains::ParaHeadsProof,
) {
bridge_runtime_common::parachains_benchmarking::prepare_parachain_heads_proof::<Runtime, WithRialtoParachainsInstance>(
parachains,
parachain_head_size,
Expand Down
11 changes: 6 additions & 5 deletions bin/runtime-common/src/messages_benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ where
// finally - prepare storage proof and update environment
let (state_root, storage_proof) =
prepare_messages_storage_proof::<B, BHH>(&params, message_payload);
let bridged_header_hash = insert_header_to_grandpa_pallet::<R, FI>(state_root);
let (_, bridged_header_hash) = insert_header_to_grandpa_pallet::<R, FI>(state_root);

(
FromBridgedChainMessagesProof {
Expand Down Expand Up @@ -132,7 +132,7 @@ where
let storage_proof = proof_recorder.drain().into_iter().map(|n| n.data.to_vec()).collect();

// finally insert header with given state root to our storage
let bridged_header_hash = insert_header_to_grandpa_pallet::<R, FI>(root);
let (_, bridged_header_hash) = insert_header_to_grandpa_pallet::<R, FI>(root);

FromBridgedChainMessagesDeliveryProof {
bridged_header_hash: bridged_header_hash.into(),
Expand Down Expand Up @@ -207,22 +207,23 @@ where
/// Insert header to the bridge GRANDPA pallet.
pub(crate) fn insert_header_to_grandpa_pallet<R, GI>(
state_root: bp_runtime::HashOf<R::BridgedChain>,
) -> bp_runtime::HashOf<R::BridgedChain>
) -> (bp_runtime::BlockNumberOf<R::BridgedChain>, bp_runtime::HashOf<R::BridgedChain>)
where
R: pallet_bridge_grandpa::Config<GI>,
GI: 'static,
R::BridgedChain: bp_runtime::Chain,
{
let bridged_block_number = Zero::zero();
let bridged_header = bp_runtime::HeaderOf::<R::BridgedChain>::new(
Zero::zero(),
bridged_block_number,
Default::default(),
state_root,
Default::default(),
Default::default(),
);
let bridged_header_hash = bridged_header.hash();
pallet_bridge_grandpa::initialize_for_benchmarks::<R, GI>(bridged_header);
bridged_header_hash
(bridged_block_number, bridged_header_hash)
}

/// Populate trie with dummy keys+values until trie has at least given size.
Expand Down
10 changes: 5 additions & 5 deletions bin/runtime-common/src/parachains_benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use bp_polkadot_core::parachains::{ParaHead, ParaHeadsProof, ParaId};
use bp_runtime::StorageProofSize;
use codec::Encode;
use frame_support::traits::Get;
use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher};
use pallet_bridge_parachains::{RelayBlockHash, RelayBlockHasher, RelayBlockNumber};
use sp_std::prelude::*;
use sp_trie::{record_all_keys, trie_types::TrieDBMutV1, LayoutV1, MemoryDB, Recorder, TrieMut};

Expand All @@ -37,13 +37,13 @@ pub fn prepare_parachain_heads_proof<R, PI>(
parachains: &[ParaId],
parachain_head_size: u32,
size: StorageProofSize,
) -> (RelayBlockHash, ParaHeadsProof)
) -> (RelayBlockNumber, RelayBlockHash, ParaHeadsProof)
where
R: pallet_bridge_parachains::Config<PI>
+ pallet_bridge_grandpa::Config<R::BridgesGrandpaPalletInstance>,
PI: 'static,
<R as pallet_bridge_grandpa::Config<R::BridgesGrandpaPalletInstance>>::BridgedChain:
bp_runtime::Chain<Hash = RelayBlockHash>,
bp_runtime::Chain<BlockNumber = RelayBlockNumber, Hash = RelayBlockHash>,
{
let parachain_head = ParaHead(vec![0u8; parachain_head_size as usize]);

Expand Down Expand Up @@ -73,8 +73,8 @@ where
.expect("record_all_keys should not fail in benchmarks");
let proof = proof_recorder.drain().into_iter().map(|n| n.data.to_vec()).collect();

let relay_block_hash =
let (relay_block_number, relay_block_hash) =
insert_header_to_grandpa_pallet::<R, R::BridgesGrandpaPalletInstance>(state_root);

(relay_block_hash, ParaHeadsProof(proof))
(relay_block_number, relay_block_hash, ParaHeadsProof(proof))
}
5 changes: 5 additions & 0 deletions modules/grandpa/src/extension.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@
/// Call::BridgeWestendGrandpa => WestendGrandpaInstance,
/// }
/// ```
///
/// The goal of this extension is to avoid "mining" transactions that provide
/// outdated bridged chain headers. Without that extension, even honest relayers
/// may lose their funds if there are multiple relays running and submitting the
/// same information.
#[macro_export]
macro_rules! declare_bridge_reject_obsolete_grandpa_header {
($runtime:ident, $($call:path => $instance:ty),*) => {
Expand Down
19 changes: 10 additions & 9 deletions modules/parachains/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ pub trait Config<I: 'static>: crate::Config<I> {
parachains: &[ParaId],
parachain_head_size: u32,
proof_size: StorageProofSize,
) -> (RelayBlockHash, ParaHeadsProof);
) -> (RelayBlockNumber, RelayBlockHash, ParaHeadsProof);
}

benchmarks_instance_pallet! {
Expand All @@ -57,12 +57,13 @@ benchmarks_instance_pallet! {

let sender = account("sender", 0, 0);
let parachains = (1..=p).map(ParaId).collect::<Vec<_>>();
let (relay_block_hash, parachain_heads_proof) = T::prepare_parachain_heads_proof(
let (relay_block_number, relay_block_hash, parachain_heads_proof) = T::prepare_parachain_heads_proof(
&parachains,
DEFAULT_PARACHAIN_HEAD_SIZE,
StorageProofSize::Minimal(0),
);
}: submit_parachain_heads(RawOrigin::Signed(sender), relay_block_hash, parachains.clone(), parachain_heads_proof)
let at_relay_block = (relay_block_number, relay_block_hash);
}: submit_parachain_heads(RawOrigin::Signed(sender), at_relay_block, parachains.clone(), parachain_heads_proof)
verify {
for parachain in parachains {
assert!(crate::Pallet::<T, I>::best_parachain_head(parachain).is_some());
Expand All @@ -73,13 +74,13 @@ benchmarks_instance_pallet! {
submit_parachain_heads_with_1kb_proof {
let sender = account("sender", 0, 0);
let parachains = vec![ParaId(1)];
let (relay_block_hash, parachain_heads_proof) = T::prepare_parachain_heads_proof(
let (relay_block_number, relay_block_hash, parachain_heads_proof) = T::prepare_parachain_heads_proof(
&parachains,
DEFAULT_PARACHAIN_HEAD_SIZE,
StorageProofSize::HasExtraNodes(1024),
);

}: submit_parachain_heads(RawOrigin::Signed(sender), relay_block_hash, parachains.clone(), parachain_heads_proof)
let at_relay_block = (relay_block_number, relay_block_hash);
}: submit_parachain_heads(RawOrigin::Signed(sender), at_relay_block, parachains.clone(), parachain_heads_proof)
verify {
for parachain in parachains {
assert!(crate::Pallet::<T, I>::best_parachain_head(parachain).is_some());
Expand All @@ -90,13 +91,13 @@ benchmarks_instance_pallet! {
submit_parachain_heads_with_16kb_proof {
let sender = account("sender", 0, 0);
let parachains = vec![ParaId(1)];
let (relay_block_hash, parachain_heads_proof) = T::prepare_parachain_heads_proof(
let (relay_block_number, relay_block_hash, parachain_heads_proof) = T::prepare_parachain_heads_proof(
&parachains,
DEFAULT_PARACHAIN_HEAD_SIZE,
StorageProofSize::HasExtraNodes(16 * 1024),
);

}: submit_parachain_heads(RawOrigin::Signed(sender), relay_block_hash, parachains.clone(), parachain_heads_proof)
let at_relay_block = (relay_block_number, relay_block_hash);
}: submit_parachain_heads(RawOrigin::Signed(sender), at_relay_block, parachains.clone(), parachain_heads_proof)
verify {
for parachain in parachains {
assert!(crate::Pallet::<T, I>::best_parachain_head(parachain).is_some());
Expand Down
195 changes: 195 additions & 0 deletions modules/parachains/src/extension.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
// Copyright 2021 Parity Technologies (UK) Ltd.
// This file is part of Parity Bridges Common.

// Parity Bridges Common is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Parity Bridges Common is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Parity Bridges Common. If not, see <http://www.gnu.org/licenses/>.

/// Declares a runtime-specific `BridgeRejectObsoleteParachainHeader` signed extension.
///
/// ## Example
///
/// ```nocompile
/// pallet_bridge_grandpa::declare_bridge_reject_obsolete_parachain_header!{
/// Runtime,
/// Call::BridgeRialtoParachains => RialtoGrandpaInstance,
/// Call::BridgeWestendParachains => WestendGrandpaInstance,
/// }
/// ```
///
/// The goal of this extension is to avoid "mining" transactions that provide
/// outdated bridged parachain heads. Without that extension, even honest relayers
/// may lose their funds if there are multiple relays running and submitting the
/// same information.
///
/// This extension only works with transactions that are updating single parachain
/// head. We can't use unbounded validation - it may take too long and either break
/// block production, or "eat" significant portion of block production time literally
/// for nothing. In addition, the single-parachain-head-per-transaction is how the
/// pallet will be used in our environment.
#[macro_export]
macro_rules! declare_bridge_reject_obsolete_parachain_header {
($runtime:ident, $($call:path => $instance:ty),*) => {
/// Transaction-with-obsolete-bridged-parachain-header check that will reject transaction if
/// it submits obsolete bridged parachain header.
#[derive(Clone, codec::Decode, codec::Encode, Eq, PartialEq, frame_support::RuntimeDebug, scale_info::TypeInfo)]
pub struct BridgeRejectObsoleteParachainHeader;

impl sp_runtime::traits::SignedExtension for BridgeRejectObsoleteParachainHeader {
const IDENTIFIER: &'static str = "BridgeRejectObsoleteParachainHeader";
type AccountId = <$runtime as frame_system::Config>::AccountId;
type Call = <$runtime as frame_system::Config>::Call;
type AdditionalSigned = ();
type Pre = ();

fn additional_signed(&self) -> sp_std::result::Result<
(),
sp_runtime::transaction_validity::TransactionValidityError,
> {
Ok(())
}

fn validate(
&self,
_who: &Self::AccountId,
call: &Self::Call,
_info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
_len: usize,
) -> sp_runtime::transaction_validity::TransactionValidity {
match *call {
$(
$call($crate::Call::<$runtime, $instance>::submit_parachain_heads {
ref at_relay_block,
ref parachains,
..
}) if parachains.len() == 1 => {
let parachain = parachains.get(0).expect("verified by match condition; qed");

let bundled_relay_block_number = at_relay_block.0;

let best_parachain_head = $crate::BestParaHeads::<$runtime, $instance>::get(parachain);
match best_parachain_head {
Some(best_parachain_head) if best_parachain_head.at_relay_block_number
>= bundled_relay_block_number =>
sp_runtime::transaction_validity::InvalidTransaction::Stale.into(),
_ => Ok(sp_runtime::transaction_validity::ValidTransaction::default()),
}
},
)*
_ => Ok(sp_runtime::transaction_validity::ValidTransaction::default()),
}
}

fn pre_dispatch(
self,
who: &Self::AccountId,
call: &Self::Call,
info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
len: usize,
) -> Result<Self::Pre, sp_runtime::transaction_validity::TransactionValidityError> {
self.validate(who, call, info, len).map(drop)
}

fn post_dispatch(
_maybe_pre: Option<Self::Pre>,
_info: &sp_runtime::traits::DispatchInfoOf<Self::Call>,
_post_info: &sp_runtime::traits::PostDispatchInfoOf<Self::Call>,
_len: usize,
_result: &sp_runtime::DispatchResult,
) -> Result<(), sp_runtime::transaction_validity::TransactionValidityError> {
Ok(())
}
}
};
}

#[cfg(test)]
mod tests {
use crate::{
mock::{run_test, Call, TestRuntime},
BestParaHead, BestParaHeads, RelayBlockNumber,
};
use bp_polkadot_core::parachains::{ParaHeadsProof, ParaId};
use frame_support::weights::{DispatchClass, DispatchInfo, Pays};
use sp_runtime::traits::SignedExtension;

declare_bridge_reject_obsolete_parachain_header! {
TestRuntime,
Call::Parachains => ()
}

fn validate_submit_parachain_heads(num: RelayBlockNumber, parachains: Vec<ParaId>) -> bool {
BridgeRejectObsoleteParachainHeader
.validate(
&42,
&Call::Parachains(crate::Call::<TestRuntime, ()>::submit_parachain_heads {
at_relay_block: (num, Default::default()),
parachains,
parachain_heads_proof: ParaHeadsProof(Vec::new()),
}),
&DispatchInfo { weight: 0, class: DispatchClass::Operational, pays_fee: Pays::Yes },
0,
)
.is_ok()
}

fn sync_to_relay_header_10() {
BestParaHeads::<TestRuntime, ()>::insert(
ParaId(1),
BestParaHead {
at_relay_block_number: 10,
head_hash: Default::default(),
next_imported_hash_position: 0,
},
);
}

#[test]
fn extension_rejects_obsolete_header() {
run_test(|| {
// when current best finalized is #10 and we're trying to import header#5 => tx is
// rejected
sync_to_relay_header_10();
assert!(!validate_submit_parachain_heads(5, vec![ParaId(1)]));
});
}

#[test]
fn extension_rejects_same_header() {
run_test(|| {
// when current best finalized is #10 and we're trying to import header#10 => tx is
// rejected
sync_to_relay_header_10();
assert!(!validate_submit_parachain_heads(10, vec![ParaId(1)]));
});
}

#[test]
fn extension_accepts_new_header() {
run_test(|| {
// when current best finalized is #10 and we're trying to import header#15 => tx is
// accepted
sync_to_relay_header_10();
assert!(validate_submit_parachain_heads(15, vec![ParaId(1)]));
});
}

#[test]
fn extension_accepts_if_more_than_one_parachain_is_submitted() {
run_test(|| {
// when current best finalized is #10 and we're trying to import header#5, but another
// parachain head is also supplied => tx is accepted
sync_to_relay_header_10();
assert!(validate_submit_parachain_heads(5, vec![ParaId(1), ParaId(2)]));
});
}
}
Loading