Verify Source origin in TargetChainVerifier (#558)

* Make sure to verify sender's origin.

* Make sure to use dispatch verification.

* Add tests.

* cargo fmt --all

* Remove superfluous lifetime.

* Move the check to MessageLanVerifier.

* cargo fmt --all

* Fix docs.
This commit is contained in:
Tomasz Drwięga
2020-12-08 23:29:32 +01:00
committed by Bastian Köcher
parent f57b7e9de0
commit 6f6c8c2417
7 changed files with 154 additions and 67 deletions
+95 -13
View File
@@ -22,7 +22,7 @@
use bp_message_dispatch::MessageDispatch as _; use bp_message_dispatch::MessageDispatch as _;
use bp_message_lane::{ use bp_message_lane::{
source_chain::LaneMessageVerifier, source_chain::{LaneMessageVerifier, Sender},
target_chain::{DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages}, target_chain::{DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages},
InboundLaneData, LaneId, Message, MessageData, MessageKey, MessageNonce, OutboundLaneData, InboundLaneData, LaneId, Message, MessageData, MessageKey, MessageNonce, OutboundLaneData,
}; };
@@ -149,25 +149,34 @@ pub mod source {
#[derive(RuntimeDebug)] #[derive(RuntimeDebug)]
pub struct FromThisChainMessageVerifier<B>(PhantomData<B>); pub struct FromThisChainMessageVerifier<B>(PhantomData<B>);
impl<B: MessageBridge> pub(crate) const BAD_ORIGIN: &str = "Unable to match the source origin to expected target origin.";
LaneMessageVerifier<AccountIdOf<ThisChain<B>>, FromThisChainMessagePayload<B>, BalanceOf<ThisChain<B>>> pub(crate) const TOO_LOW_FEE: &str = "Provided fee is below minimal threshold required by the lane.";
impl<B> LaneMessageVerifier<AccountIdOf<ThisChain<B>>, FromThisChainMessagePayload<B>, BalanceOf<ThisChain<B>>>
for FromThisChainMessageVerifier<B> for FromThisChainMessageVerifier<B>
where
B: MessageBridge,
AccountIdOf<ThisChain<B>>: PartialEq + Clone,
{ {
type Error = &'static str; type Error = &'static str;
fn verify_message( fn verify_message(
_submitter: &AccountIdOf<ThisChain<B>>, submitter: &Sender<AccountIdOf<ThisChain<B>>>,
delivery_and_dispatch_fee: &BalanceOf<ThisChain<B>>, delivery_and_dispatch_fee: &BalanceOf<ThisChain<B>>,
_lane: &LaneId, _lane: &LaneId,
payload: &FromThisChainMessagePayload<B>, payload: &FromThisChainMessagePayload<B>,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
// Do the dispatch-specific check. We assume that the target chain uses
// `CallDispatch`, so we verify the message accordingly.
pallet_bridge_call_dispatch::verify_message_origin(submitter, payload).map_err(|_| BAD_ORIGIN)?;
let minimal_fee_in_bridged_tokens = let minimal_fee_in_bridged_tokens =
estimate_message_dispatch_and_delivery_fee::<B>(payload, B::RELAYER_FEE_PERCENT)?; estimate_message_dispatch_and_delivery_fee::<B>(payload, B::RELAYER_FEE_PERCENT)?;
// compare with actual fee paid // compare with actual fee paid
let actual_fee_in_bridged_tokens = B::this_balance_to_bridged_balance(*delivery_and_dispatch_fee); let actual_fee_in_bridged_tokens = B::this_balance_to_bridged_balance(*delivery_and_dispatch_fee);
if actual_fee_in_bridged_tokens < minimal_fee_in_bridged_tokens { if actual_fee_in_bridged_tokens < minimal_fee_in_bridged_tokens {
return Err("Too low fee paid"); return Err(TOO_LOW_FEE);
} }
Ok(()) Ok(())
@@ -639,7 +648,7 @@ mod tests {
} }
} }
#[derive(Debug, PartialEq, Decode, Encode)] #[derive(Debug, PartialEq, Decode, Encode, Clone)]
struct ThisChainAccountId(u32); struct ThisChainAccountId(u32);
#[derive(Debug, PartialEq, Decode, Encode)] #[derive(Debug, PartialEq, Decode, Encode)]
struct ThisChainSigner(u32); struct ThisChainSigner(u32);
@@ -780,6 +789,8 @@ mod tests {
); );
} }
const TEST_LANE_ID: &LaneId = b"test";
#[test] #[test]
fn message_fee_is_checked_by_verifier() { fn message_fee_is_checked_by_verifier() {
const EXPECTED_MINIMAL_FEE: u32 = 2640; const EXPECTED_MINIMAL_FEE: u32 = 2640;
@@ -802,20 +813,91 @@ mod tests {
); );
// and now check that the verifier checks the fee // and now check that the verifier checks the fee
assert!( assert_eq!(
source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message( source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
&ThisChainAccountId(0), &Sender::Root,
&ThisChainBalance(1), &ThisChainBalance(1),
&*b"test", &TEST_LANE_ID,
&payload, &payload,
) ),
.is_err(), Err(source::TOO_LOW_FEE)
); );
assert!( assert!(
source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message( source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
&ThisChainAccountId(0), &Sender::Root,
&ThisChainBalance(1_000_000), &ThisChainBalance(1_000_000),
&*b"test", &TEST_LANE_ID,
&payload,
)
.is_ok(),
);
}
#[test]
fn should_disallow_root_calls_from_regular_accounts() {
// payload of the This -> Bridged chain message
let payload = source::FromThisChainMessagePayload::<OnThisChainBridge> {
spec_version: 1,
weight: 100,
origin: pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
call: vec![42],
};
// and now check that the verifier checks the fee
assert_eq!(
source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
&Sender::Signed(ThisChainAccountId(0)),
&ThisChainBalance(1_000_000),
&TEST_LANE_ID,
&payload,
),
Err(source::BAD_ORIGIN)
);
assert_eq!(
source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
&Sender::None,
&ThisChainBalance(1_000_000),
&TEST_LANE_ID,
&payload,
),
Err(source::BAD_ORIGIN)
);
assert!(
source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
&Sender::Root,
&ThisChainBalance(1_000_000),
&TEST_LANE_ID,
&payload,
)
.is_ok(),
);
}
#[test]
fn should_verify_source_and_target_origin_matching() {
// payload of the This -> Bridged chain message
let payload = source::FromThisChainMessagePayload::<OnThisChainBridge> {
spec_version: 1,
weight: 100,
origin: pallet_bridge_call_dispatch::CallOrigin::SourceAccount(ThisChainAccountId(1)),
call: vec![42],
};
// and now check that the verifier checks the fee
assert_eq!(
source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
&Sender::Signed(ThisChainAccountId(0)),
&ThisChainBalance(1_000_000),
&TEST_LANE_ID,
&payload,
),
Err(source::BAD_ORIGIN)
);
assert!(
source::FromThisChainMessageVerifier::<OnThisChainBridge>::verify_message(
&Sender::Signed(ThisChainAccountId(1)),
&ThisChainBalance(1_000_000),
&TEST_LANE_ID,
&payload, &payload,
) )
.is_ok(), .is_ok(),
+24 -34
View File
@@ -30,11 +30,12 @@ use codec::{Decode, Encode};
use frame_support::{ use frame_support::{
decl_event, decl_module, decl_storage, decl_event, decl_module, decl_storage,
dispatch::{Dispatchable, Parameter}, dispatch::{Dispatchable, Parameter},
ensure,
traits::Get, traits::Get,
weights::{extract_actual_weight, GetDispatchInfo}, weights::{extract_actual_weight, GetDispatchInfo},
RuntimeDebug, RuntimeDebug,
}; };
use frame_system::{ensure_root, ensure_signed, RawOrigin}; use frame_system::RawOrigin;
use sp_runtime::{ use sp_runtime::{
traits::{BadOrigin, Convert, IdentifyAccount, MaybeDisplay, MaybeSerializeDeserialize, Member, Verify}, traits::{BadOrigin, Convert, IdentifyAccount, MaybeDisplay, MaybeSerializeDeserialize, Member, Verify},
DispatchResult, DispatchResult,
@@ -266,39 +267,31 @@ impl<T: Trait<I>, I: Instance> MessageDispatch<T::MessageId> for Module<T, I> {
/// For example, if a message is sent from a "regular" account on the source chain it will not be /// For example, if a message is sent from a "regular" account on the source chain it will not be
/// allowed to be dispatched as Root on the target chain. This is a useful check to do on the source /// allowed to be dispatched as Root on the target chain. This is a useful check to do on the source
/// chain _before_ sending a message whose dispatch will be rejected on the target chain. /// chain _before_ sending a message whose dispatch will be rejected on the target chain.
pub fn verify_message_origin< pub fn verify_message_origin<SourceChainAccountId, TargetChainAccountPublic, TargetChainSignature, Call>(
SourceChainOuterOrigin, sender_origin: &RawOrigin<SourceChainAccountId>,
SourceChainAccountId,
TargetChainAccountPublic,
TargetChainSignature,
Call,
>(
sender_origin: SourceChainOuterOrigin,
message: &MessagePayload<SourceChainAccountId, TargetChainAccountPublic, TargetChainSignature, Call>, message: &MessagePayload<SourceChainAccountId, TargetChainAccountPublic, TargetChainSignature, Call>,
) -> Result<Option<SourceChainAccountId>, BadOrigin> ) -> Result<Option<SourceChainAccountId>, BadOrigin>
where where
SourceChainOuterOrigin: Into<Result<RawOrigin<SourceChainAccountId>, SourceChainOuterOrigin>>, SourceChainAccountId: PartialEq + Clone,
SourceChainAccountId: PartialEq,
{ {
match message.origin { match message.origin {
CallOrigin::SourceRoot => { CallOrigin::SourceRoot => {
ensure_root(sender_origin)?; ensure!(sender_origin == &RawOrigin::Root, BadOrigin);
Ok(None) Ok(None)
} }
CallOrigin::TargetAccount(ref source_account_id, _, _) => { CallOrigin::TargetAccount(ref source_account_id, _, _) => {
let source_chain_signer = ensure_signed(sender_origin)?; ensure!(
if source_chain_signer != *source_account_id { sender_origin == &RawOrigin::Signed(source_account_id.clone()),
return Err(BadOrigin); BadOrigin
} );
Ok(Some(source_account_id.clone()))
Ok(Some(source_chain_signer))
} }
CallOrigin::SourceAccount(ref source_account_id) => { CallOrigin::SourceAccount(ref source_account_id) => {
let source_chain_signer = ensure_signed(sender_origin)?; ensure!(
if source_chain_signer != *source_account_id { sender_origin == &RawOrigin::Signed(source_account_id.clone()),
return Err(BadOrigin); BadOrigin
} );
Ok(Some(source_chain_signer)) Ok(Some(source_account_id.clone()))
} }
} }
} }
@@ -617,14 +610,11 @@ mod tests {
let message = prepare_root_message(call); let message = prepare_root_message(call);
// When message is sent by Root, CallOrigin::SourceRoot is allowed // When message is sent by Root, CallOrigin::SourceRoot is allowed
assert!(matches!( assert!(matches!(verify_message_origin(&RawOrigin::Root, &message), Ok(None)));
verify_message_origin(Origin::from(RawOrigin::Root), &message),
Ok(None)
));
// when message is sent by some real account, CallOrigin::SourceRoot is not allowed // when message is sent by some real account, CallOrigin::SourceRoot is not allowed
assert!(matches!( assert!(matches!(
verify_message_origin(Origin::from(RawOrigin::Signed(1)), &message), verify_message_origin(&RawOrigin::Signed(1), &message),
Err(BadOrigin) Err(BadOrigin)
)); ));
} }
@@ -636,20 +626,20 @@ mod tests {
// When message is sent by Root, CallOrigin::TargetAccount is not allowed // When message is sent by Root, CallOrigin::TargetAccount is not allowed
assert!(matches!( assert!(matches!(
verify_message_origin(Origin::from(RawOrigin::Root), &message), verify_message_origin(&RawOrigin::Root, &message),
Err(BadOrigin) Err(BadOrigin)
)); ));
// When message is sent by some other account, it is rejected // When message is sent by some other account, it is rejected
assert!(matches!( assert!(matches!(
verify_message_origin(Origin::from(RawOrigin::Signed(2)), &message), verify_message_origin(&RawOrigin::Signed(2), &message),
Err(BadOrigin) Err(BadOrigin)
)); ));
// When message is sent by a real account, it is allowed to have origin // When message is sent by a real account, it is allowed to have origin
// CallOrigin::TargetAccount // CallOrigin::TargetAccount
assert!(matches!( assert!(matches!(
verify_message_origin(Origin::from(RawOrigin::Signed(1)), &message), verify_message_origin(&RawOrigin::Signed(1), &message),
Ok(Some(1)) Ok(Some(1))
)); ));
} }
@@ -661,19 +651,19 @@ mod tests {
// Sending a message from the expected origin account works // Sending a message from the expected origin account works
assert!(matches!( assert!(matches!(
verify_message_origin(Origin::from(RawOrigin::Signed(1)), &message), verify_message_origin(&RawOrigin::Signed(1), &message),
Ok(Some(1)) Ok(Some(1))
)); ));
// If we send a message from a different account, it is rejected // If we send a message from a different account, it is rejected
assert!(matches!( assert!(matches!(
verify_message_origin(Origin::from(RawOrigin::Signed(2)), &message), verify_message_origin(&RawOrigin::Signed(2), &message),
Err(BadOrigin) Err(BadOrigin)
)); ));
// If we try and send the message from Root, it is also rejected // If we try and send the message from Root, it is also rejected
assert!(matches!( assert!(matches!(
verify_message_origin(Origin::from(RawOrigin::Root), &message), verify_message_origin(&RawOrigin::Root, &message),
Err(BadOrigin) Err(BadOrigin)
)); ));
} }
@@ -17,7 +17,7 @@
//! Implementation of `MessageDeliveryAndDispatchPayment` trait on top of `Currency` trait. //! Implementation of `MessageDeliveryAndDispatchPayment` trait on top of `Currency` trait.
//! All payments are instant. //! All payments are instant.
use bp_message_lane::source_chain::MessageDeliveryAndDispatchPayment; use bp_message_lane::source_chain::{MessageDeliveryAndDispatchPayment, Sender};
use codec::Encode; use codec::Encode;
use frame_support::traits::{Currency as CurrencyT, ExistenceRequirement}; use frame_support::traits::{Currency as CurrencyT, ExistenceRequirement};
use sp_std::fmt::Debug; use sp_std::fmt::Debug;
@@ -37,11 +37,20 @@ where
type Error = &'static str; type Error = &'static str;
fn pay_delivery_and_dispatch_fee( fn pay_delivery_and_dispatch_fee(
submitter: &AccountId, submitter: &Sender<AccountId>,
fee: &Currency::Balance, fee: &Currency::Balance,
relayer_fund_account: &AccountId, relayer_fund_account: &AccountId,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
Currency::transfer(submitter, relayer_fund_account, *fee, ExistenceRequirement::AllowDeath).map_err(Into::into) match submitter {
Sender::Signed(submitter) => {
Currency::transfer(submitter, relayer_fund_account, *fee, ExistenceRequirement::AllowDeath)
.map_err(Into::into)
}
Sender::Root | Sender::None => {
// fixme: we might want to add root account id to this struct.
Err("Root and None account is not allowed to send regular messages.")
}
}
} }
fn pay_relayer_reward( fn pay_relayer_reward(
+10 -9
View File
@@ -255,18 +255,19 @@ decl_module! {
delivery_and_dispatch_fee: T::OutboundMessageFee, delivery_and_dispatch_fee: T::OutboundMessageFee,
) -> DispatchResult { ) -> DispatchResult {
ensure_operational::<T, I>()?; ensure_operational::<T, I>()?;
let submitter = ensure_signed(origin)?; let submitter = origin.into().map_err(|_| BadOrigin)?;
// let's first check if message can be delivered to target chain // let's first check if message can be delivered to target chain
T::TargetHeaderChain::verify_message(&payload).map_err(|err| { T::TargetHeaderChain::verify_message(&payload)
frame_support::debug::trace!( .map_err(|err| {
"Message to lane {:?} is rejected by target chain: {:?}", frame_support::debug::trace!(
lane_id, "Message to lane {:?} is rejected by target chain: {:?}",
err, lane_id,
); err,
);
Error::<T, I>::MessageRejectedByChainVerifier Error::<T, I>::MessageRejectedByChainVerifier
})?; })?;
// now let's enforce any additional lane rules // now let's enforce any additional lane rules
T::LaneMessageVerifier::verify_message( T::LaneMessageVerifier::verify_message(
+4 -4
View File
@@ -17,7 +17,7 @@
use crate::Trait; use crate::Trait;
use bp_message_lane::{ use bp_message_lane::{
source_chain::{LaneMessageVerifier, MessageDeliveryAndDispatchPayment, TargetHeaderChain}, source_chain::{LaneMessageVerifier, MessageDeliveryAndDispatchPayment, Sender, TargetHeaderChain},
target_chain::{DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, SourceHeaderChain}, target_chain::{DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages, SourceHeaderChain},
InboundLaneData, LaneId, Message, MessageData, MessageKey, MessageNonce, InboundLaneData, LaneId, Message, MessageData, MessageKey, MessageNonce,
}; };
@@ -209,7 +209,7 @@ impl LaneMessageVerifier<AccountId, TestPayload, TestMessageFee> for TestLaneMes
type Error = &'static str; type Error = &'static str;
fn verify_message( fn verify_message(
_submitter: &AccountId, _submitter: &Sender<AccountId>,
delivery_and_dispatch_fee: &TestMessageFee, delivery_and_dispatch_fee: &TestMessageFee,
_lane: &LaneId, _lane: &LaneId,
_payload: &TestPayload, _payload: &TestPayload,
@@ -234,7 +234,7 @@ impl TestMessageDeliveryAndDispatchPayment {
/// Returns true if given fee has been paid by given submitter. /// Returns true if given fee has been paid by given submitter.
pub fn is_fee_paid(submitter: AccountId, fee: TestMessageFee) -> bool { pub fn is_fee_paid(submitter: AccountId, fee: TestMessageFee) -> bool {
frame_support::storage::unhashed::get(b":message-fee:") == Some((submitter, fee)) frame_support::storage::unhashed::get(b":message-fee:") == Some((Sender::Signed(submitter), fee))
} }
/// Returns true if given relayer has been rewarded with given balance. The reward-paid flag is /// Returns true if given relayer has been rewarded with given balance. The reward-paid flag is
@@ -249,7 +249,7 @@ impl MessageDeliveryAndDispatchPayment<AccountId, TestMessageFee> for TestMessag
type Error = &'static str; type Error = &'static str;
fn pay_delivery_and_dispatch_fee( fn pay_delivery_and_dispatch_fee(
submitter: &AccountId, submitter: &Sender<AccountId>,
fee: &TestMessageFee, fee: &TestMessageFee,
_relayer_fund_account: &AccountId, _relayer_fund_account: &AccountId,
) -> Result<(), Self::Error> { ) -> Result<(), Self::Error> {
@@ -12,6 +12,7 @@ codec = { package = "parity-scale-codec", version = "1.3.1", default-features =
# Substrate Dependencies # Substrate Dependencies
frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false } frame-support = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
frame-system = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false } sp-std = { git = "https://github.com/paritytech/substrate.git", branch = "master" , default-features = false }
[features] [features]
@@ -19,5 +20,6 @@ default = ["std"]
std = [ std = [
"codec/std", "codec/std",
"frame-support/std", "frame-support/std",
"frame-system/std",
"sp-std/std" "sp-std/std"
] ]
@@ -21,12 +21,15 @@ use crate::{InboundLaneData, LaneId};
use frame_support::Parameter; use frame_support::Parameter;
use sp_std::fmt::Debug; use sp_std::fmt::Debug;
/// The sender of the message on the source chain.
pub type Sender<AccountId> = frame_system::RawOrigin<AccountId>;
/// Target chain API. Used by source chain to verify target chain proofs. /// Target chain API. Used by source chain to verify target chain proofs.
/// ///
/// All implementations of this trait should only work with finalized data that /// All implementations of this trait should only work with finalized data that
/// can't change. Wrong implementation may lead to invalid lane states (i.e. lane /// can't change. Wrong implementation may lead to invalid lane states (i.e. lane
/// that's stuck) and/or processing messages without paying fees. /// that's stuck) and/or processing messages without paying fees.
pub trait TargetHeaderChain<Payload, RelayerId> { pub trait TargetHeaderChain<Payload, AccountId> {
/// Error type. /// Error type.
type Error: Debug + Into<&'static str>; type Error: Debug + Into<&'static str>;
@@ -50,7 +53,7 @@ pub trait TargetHeaderChain<Payload, RelayerId> {
/// Verify messages delivery proof and return lane && nonce of the latest recevied message. /// Verify messages delivery proof and return lane && nonce of the latest recevied message.
fn verify_messages_delivery_proof( fn verify_messages_delivery_proof(
proof: Self::MessagesDeliveryProof, proof: Self::MessagesDeliveryProof,
) -> Result<(LaneId, InboundLaneData<RelayerId>), Self::Error>; ) -> Result<(LaneId, InboundLaneData<AccountId>), Self::Error>;
} }
/// Lane message verifier. /// Lane message verifier.
@@ -67,7 +70,7 @@ pub trait LaneMessageVerifier<Submitter, Payload, Fee> {
/// Verify message payload and return Ok(()) if message is valid and allowed to be sent over the lane. /// Verify message payload and return Ok(()) if message is valid and allowed to be sent over the lane.
fn verify_message( fn verify_message(
submitter: &Submitter, submitter: &Sender<Submitter>,
delivery_and_dispatch_fee: &Fee, delivery_and_dispatch_fee: &Fee,
lane: &LaneId, lane: &LaneId,
payload: &Payload, payload: &Payload,
@@ -94,7 +97,7 @@ pub trait MessageDeliveryAndDispatchPayment<AccountId, Balance> {
/// Withhold/write-off delivery_and_dispatch_fee from submitter account to /// Withhold/write-off delivery_and_dispatch_fee from submitter account to
/// some relayers-fund account. /// some relayers-fund account.
fn pay_delivery_and_dispatch_fee( fn pay_delivery_and_dispatch_fee(
submitter: &AccountId, submitter: &Sender<AccountId>,
fee: &Balance, fee: &Balance,
relayer_fund_account: &AccountId, relayer_fund_account: &AccountId,
) -> Result<(), Self::Error>; ) -> Result<(), Self::Error>;