diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69492170c..a1e0cb0a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,6 +28,7 @@ jobs: - bp-pangoro - bp-rococo - bp-darwinia-core + - bridge-runtime-common steps: - uses: actions/checkout@v2 - name: Check ${{ matrix.package }} diff --git a/Cargo.lock b/Cargo.lock index c3979593f..45f3ff61f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -507,6 +507,7 @@ dependencies = [ "pallet-bridge-grandpa", "pallet-bridge-messages", "pallet-bridge-parachains", + "pallet-fee-market", "pallet-transaction-payment", "parity-scale-codec", "scale-info", diff --git a/bin/runtime-common/Cargo.toml b/bin/runtime-common/Cargo.toml index 2788412fb..6cf0f75fa 100644 --- a/bin/runtime-common/Cargo.toml +++ b/bin/runtime-common/Cargo.toml @@ -23,6 +23,7 @@ pallet-bridge-dispatch = { path = "../../modules/dispatch", default-features = f pallet-bridge-grandpa = { path = "../../modules/grandpa", default-features = false } pallet-bridge-messages = { path = "../../modules/messages", default-features = false } pallet-bridge-parachains = { path = "../../modules/parachains", default-features = false } +pallet-fee-market = { path = "../../modules/fee-market", default-features = false } # Substrate dependencies @@ -53,6 +54,7 @@ std = [ "pallet-bridge-grandpa/std", "pallet-bridge-messages/std", "pallet-bridge-parachains/std", + "pallet-fee-market/std", "pallet-transaction-payment/std", "scale-info/std", "sp-api/std", diff --git a/bin/runtime-common/src/messages.rs b/bin/runtime-common/src/messages.rs index 7bd2757e5..9f3504108 100644 --- a/bin/runtime-common/src/messages.rs +++ b/bin/runtime-common/src/messages.rs @@ -22,14 +22,12 @@ use bp_message_dispatch::MessageDispatch as _; use bp_messages::{ + source_chain::LaneMessageVerifier, target_chain::{DispatchMessage, MessageDispatch, ProvedLaneMessages, ProvedMessages}, InboundLaneData, LaneId, Message, MessageData, MessageKey, MessageNonce, OutboundLaneData, }; use bp_polkadot_core::parachains::{ParaHash, ParaHasher, ParaId}; -use bp_runtime::{ - messages::{ MessageDispatchResult}, - ChainId, Size, StorageProofChecker, -}; +use bp_runtime::{messages::MessageDispatchResult, ChainId, Size, StorageProofChecker}; use codec::{Decode, DecodeLimit, Encode}; use frame_support::{ traits::{Currency, ExistenceRequirement}, @@ -247,6 +245,24 @@ pub mod source { pub type ParsedMessagesDeliveryProofFromBridgedChain = (LaneId, InboundLaneData>>); + /// Message verifier that is doing all basic checks. + /// + /// This verifier assumes following: + /// + /// - all message lanes are equivalent, so all checks are the same; + /// - messages are being dispatched using `pallet-bridge-dispatch` pallet on the target chain. + /// + /// Following checks are made: + /// + /// - message is rejected if its lane is currently blocked; + /// - message is rejected if there are too many pending (undelivered) messages at the outbound + /// lane; + /// - check that the sender has rights to dispatch the call on target chain using provided + /// dispatch origin; + /// - check that the sender has paid enough funds for both message delivery and dispatch. + #[derive(RuntimeDebug)] + pub struct FromThisChainMessageVerifier(PhantomData<(B, F, I)>); + /// The error message returned from LaneMessageVerifier when outbound lane is disabled. pub const MESSAGE_REJECTED_BY_OUTBOUND_LANE: &str = "The outbound message lane has rejected the message."; @@ -258,6 +274,97 @@ pub mod source { /// The error message returned from LaneMessageVerifier when the message fee is too low. pub const TOO_LOW_FEE: &str = "Provided fee is below minimal threshold required by the lane."; + impl + LaneMessageVerifier< + OriginOf>, + AccountIdOf>, + FromThisChainMessagePayload, + BalanceOf>, + > for FromThisChainMessageVerifier + where + B: MessageBridge, + F: pallet_fee_market::Config, + I: 'static, + // matches requirements from the `frame_system::Config::Origin` + OriginOf>: Clone + + Into>>, OriginOf>>>, + AccountIdOf>: PartialEq + Clone, + pallet_fee_market::BalanceOf: From>>, + { + type Error = &'static str; + + #[allow(clippy::single_match)] + #[cfg(not(feature = "runtime-benchmarks"))] + fn verify_message( + submitter: &OriginOf>, + delivery_and_dispatch_fee: &BalanceOf>, + lane: &LaneId, + lane_outbound_data: &OutboundLaneData, + payload: &FromThisChainMessagePayload, + ) -> Result<(), Self::Error> { + // reject message if lane is blocked + if !ThisChain::::is_message_accepted(submitter, lane) { + return Err(MESSAGE_REJECTED_BY_OUTBOUND_LANE); + } + + // reject message if there are too many pending messages at this lane + let max_pending_messages = ThisChain::::maximal_pending_messages_at_outbound_lane(); + let pending_messages = lane_outbound_data + .latest_generated_nonce + .saturating_sub(lane_outbound_data.latest_received_nonce); + if pending_messages > max_pending_messages { + return Err(TOO_MANY_PENDING_MESSAGES); + } + + // Do the dispatch-specific check. We assume that the target chain uses + // `Dispatch`, so we verify the message accordingly. + let raw_origin_or_err: Result< + frame_system::RawOrigin>>, + OriginOf>, + > = submitter.clone().into(); + if let Ok(raw_origin) = raw_origin_or_err { + pallet_bridge_dispatch::verify_message_origin(&raw_origin, payload) + .map(drop) + .map_err(|_| BAD_ORIGIN)?; + } else { + // so what it means that we've failed to convert origin to the + // `frame_system::RawOrigin`? now it means that the custom pallet origin has + // been used to send the message. Do we need to verify it? The answer is no, + // because pallet may craft any origin (e.g. root) && we can't verify whether it + // is valid, or not. + }; + + // Do the delivery_and_dispatch_fee. We assume that the delivery and dispatch fee always + // greater than the fee market provided fee. + if let Some(market_fee) = pallet_fee_market::Pallet::::market_fee() { + let message_fee: pallet_fee_market::BalanceOf = + (*delivery_and_dispatch_fee).into(); + + // compare with actual fee paid + if message_fee < market_fee { + return Err(TOO_LOW_FEE); + } + } else { + const NO_MARKET_FEE: &str = "The fee market are not ready for accepting messages."; + + return Err(NO_MARKET_FEE); + } + + Ok(()) + } + + #[cfg(feature = "runtime-benchmarks")] + fn verify_message( + _submitter: &OriginOf>, + _delivery_and_dispatch_fee: &BalanceOf>, + _lane: &LaneId, + _lane_outbound_data: &OutboundLaneData, + _payload: &FromThisChainMessagePayload, + ) -> Result<(), Self::Error> { + Ok(()) + } + } + /// Return maximal message size of This -> Bridged chain message. pub fn maximal_message_size() -> u32 { super::target::maximal_incoming_message_size(BridgedChain::::maximal_extrinsic_size()) @@ -530,7 +637,7 @@ pub mod target { fn from(encoded_call: FromBridgedChainEncodedMessageCall) -> Self { DecodedCall::decode_with_depth_limit( sp_api::MAX_EXTRINSIC_DEPTH, - &mut &encoded_call.encoded_call[..], + &mut &encoded_call.encoded_call[..], ) .map_err(drop) } @@ -771,15 +878,14 @@ pub mod target { #[cfg(test)] mod tests { use super::*; + use bp_runtime::messages::DispatchFeePayment; use codec::{Decode, Encode}; use frame_support::weights::Weight; use std::ops::RangeInclusive; const DELIVERY_TRANSACTION_WEIGHT: Weight = 100; - const DELIVERY_CONFIRMATION_TRANSACTION_WEIGHT: Weight = 100; const THIS_CHAIN_WEIGHT_TO_BALANCE_RATE: Weight = 2; const BRIDGED_CHAIN_WEIGHT_TO_BALANCE_RATE: Weight = 4; - const BRIDGED_CHAIN_TO_THIS_CHAIN_BALANCE_RATE: u32 = 6; const BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT: Weight = 2048; const BRIDGED_CHAIN_MAX_EXTRINSIC_SIZE: u32 = 1024; @@ -1037,10 +1143,6 @@ mod tests { } } - fn test_lane_outbound_data() -> OutboundLaneData { - OutboundLaneData::default() - } - #[test] fn message_from_bridged_chain_is_decoded() { // the message is encoded on the bridged chain @@ -1078,180 +1180,6 @@ mod tests { const TEST_LANE_ID: &LaneId = b"test"; const MAXIMAL_PENDING_MESSAGES_AT_TEST_LANE: MessageNonce = 32; - fn regular_outbound_message_payload() -> source::FromThisChainMessagePayload - { - source::FromThisChainMessagePayload:: { - spec_version: 1, - weight: 100, - origin: bp_message_dispatch::CallOrigin::SourceRoot, - dispatch_fee_payment: DispatchFeePayment::AtSourceChain, - call: vec![42], - } - } - - #[test] - fn message_fee_is_checked_by_verifier() { - const EXPECTED_MINIMAL_FEE: u32 = 5500; - - // payload of the This -> Bridged chain message - let payload = regular_outbound_message_payload(); - - // let's check if estimation matching hardcoded value - assert_eq!( - source::estimate_message_dispatch_and_delivery_fee::( - &payload, - OnThisChainBridge::RELAYER_FEE_PERCENT, - None, - ), - Ok(ThisChainBalance(EXPECTED_MINIMAL_FEE)), - ); - - // let's check if estimation is less than hardcoded, if dispatch is paid at target chain - let mut payload_with_pay_on_target = regular_outbound_message_payload(); - payload_with_pay_on_target.dispatch_fee_payment = DispatchFeePayment::AtTargetChain; - let fee_at_source = - source::estimate_message_dispatch_and_delivery_fee::( - &payload_with_pay_on_target, - OnThisChainBridge::RELAYER_FEE_PERCENT, - None, - ) - .expect( - "estimate_message_dispatch_and_delivery_fee failed for pay-at-target-chain message", - ); - assert!( - fee_at_source < EXPECTED_MINIMAL_FEE.into(), - "Computed fee {:?} without prepaid dispatch must be less than the fee with prepaid dispatch {}", - fee_at_source, - EXPECTED_MINIMAL_FEE, - ); - - // and now check that the verifier checks the fee - assert_eq!( - source::FromThisChainMessageVerifier::::verify_message( - &ThisChainOrigin(Ok(frame_system::RawOrigin::Root)), - &ThisChainBalance(1), - TEST_LANE_ID, - &test_lane_outbound_data(), - &payload, - ), - Err(source::TOO_LOW_FEE) - ); - assert!(source::FromThisChainMessageVerifier::::verify_message( - &ThisChainOrigin(Ok(frame_system::RawOrigin::Root)), - &ThisChainBalance(1_000_000), - TEST_LANE_ID, - &test_lane_outbound_data(), - &payload, - ) - .is_ok(),); - } - - #[test] - fn should_disallow_root_calls_from_regular_accounts() { - // payload of the This -> Bridged chain message - let payload = source::FromThisChainMessagePayload:: { - spec_version: 1, - weight: 100, - origin: bp_message_dispatch::CallOrigin::SourceRoot, - dispatch_fee_payment: DispatchFeePayment::AtSourceChain, - call: vec![42], - }; - - // and now check that the verifier checks the fee - assert_eq!( - source::FromThisChainMessageVerifier::::verify_message( - &ThisChainOrigin(Ok(frame_system::RawOrigin::Signed(ThisChainAccountId(0)))), - &ThisChainBalance(1_000_000), - TEST_LANE_ID, - &test_lane_outbound_data(), - &payload, - ), - Err(source::BAD_ORIGIN) - ); - assert_eq!( - source::FromThisChainMessageVerifier::::verify_message( - &ThisChainOrigin(Ok(frame_system::RawOrigin::None)), - &ThisChainBalance(1_000_000), - TEST_LANE_ID, - &test_lane_outbound_data(), - &payload, - ), - Err(source::BAD_ORIGIN) - ); - assert!(source::FromThisChainMessageVerifier::::verify_message( - &ThisChainOrigin(Ok(frame_system::RawOrigin::Root)), - &ThisChainBalance(1_000_000), - TEST_LANE_ID, - &test_lane_outbound_data(), - &payload, - ) - .is_ok(),); - } - - #[test] - fn should_verify_source_and_target_origin_matching() { - // payload of the This -> Bridged chain message - let payload = source::FromThisChainMessagePayload:: { - spec_version: 1, - weight: 100, - origin: bp_message_dispatch::CallOrigin::SourceAccount(ThisChainAccountId(1)), - dispatch_fee_payment: DispatchFeePayment::AtSourceChain, - call: vec![42], - }; - - // and now check that the verifier checks the fee - assert_eq!( - source::FromThisChainMessageVerifier::::verify_message( - &ThisChainOrigin(Ok(frame_system::RawOrigin::Signed(ThisChainAccountId(0)))), - &ThisChainBalance(1_000_000), - TEST_LANE_ID, - &test_lane_outbound_data(), - &payload, - ), - Err(source::BAD_ORIGIN) - ); - assert!(source::FromThisChainMessageVerifier::::verify_message( - &ThisChainOrigin(Ok(frame_system::RawOrigin::Signed(ThisChainAccountId(1)))), - &ThisChainBalance(1_000_000), - TEST_LANE_ID, - &test_lane_outbound_data(), - &payload, - ) - .is_ok(),); - } - - #[test] - fn message_is_rejected_when_sent_using_disabled_lane() { - assert_eq!( - source::FromThisChainMessageVerifier::::verify_message( - &ThisChainOrigin(Ok(frame_system::RawOrigin::Root)), - &ThisChainBalance(1_000_000), - b"dsbl", - &test_lane_outbound_data(), - ®ular_outbound_message_payload(), - ), - Err(source::MESSAGE_REJECTED_BY_OUTBOUND_LANE) - ); - } - - #[test] - fn message_is_rejected_when_there_are_too_many_pending_messages_at_outbound_lane() { - assert_eq!( - source::FromThisChainMessageVerifier::::verify_message( - &ThisChainOrigin(Ok(frame_system::RawOrigin::Root)), - &ThisChainBalance(1_000_000), - TEST_LANE_ID, - &OutboundLaneData { - latest_received_nonce: 100, - latest_generated_nonce: 100 + MAXIMAL_PENDING_MESSAGES_AT_TEST_LANE + 1, - ..Default::default() - }, - ®ular_outbound_message_payload(), - ), - Err(source::TOO_MANY_PENDING_MESSAGES) - ); - } - #[test] fn verify_chain_message_rejects_message_with_too_small_declared_weight() { assert!(source::verify_chain_message::( @@ -1580,21 +1508,4 @@ mod tests { 100 + 50 * 10 + 777, ); } - - #[test] - fn conversion_rate_override_works() { - let payload = regular_outbound_message_payload(); - let regular_fee = source::estimate_message_dispatch_and_delivery_fee::( - &payload, - OnThisChainBridge::RELAYER_FEE_PERCENT, - None, - ); - let overrided_fee = source::estimate_message_dispatch_and_delivery_fee::( - &payload, - OnThisChainBridge::RELAYER_FEE_PERCENT, - Some(FixedU128::from_float((BRIDGED_CHAIN_TO_THIS_CHAIN_BALANCE_RATE * 2) as f64)), - ); - - assert!(regular_fee < overrided_fee); - } } diff --git a/primitives/parachains/src/lib.rs b/primitives/parachains/src/lib.rs index 086eb6a72..4bcada2da 100644 --- a/primitives/parachains/src/lib.rs +++ b/primitives/parachains/src/lib.rs @@ -18,14 +18,10 @@ #![cfg_attr(not(feature = "std"), no_std)] -use bp_polkadot_core::{ - parachains::{ParaHash, ParaHead, ParaId}, - BlockNumber as RelayBlockNumber, -}; +use bp_polkadot_core::parachains::ParaId; // use bp_runtime::{StorageDoubleMapKeyProvider, StorageMapKeyProvider}; -use codec::{Decode, Encode}; -use frame_support::{Blake2_128Concat, RuntimeDebug, Twox64Concat}; -use scale_info::TypeInfo; +use codec::Encode; +use frame_support::Twox64Concat; use sp_core::storage::StorageKey; // /// Best known parachain head hash.