1#![warn(missing_docs)]
37#![cfg_attr(not(feature = "std"), no_std)]
38
39pub use inbound_lane::{InboundLane, InboundLaneStorage, StoredInboundLaneData};
40pub use lanes_manager::{
41 LanesManager, LanesManagerError, RuntimeInboundLaneStorage, RuntimeOutboundLaneStorage,
42};
43pub use outbound_lane::{
44 OutboundLane, OutboundLaneStorage, ReceptionConfirmationError, StoredMessagePayload,
45};
46pub use weights::WeightInfo;
47pub use weights_ext::{
48 ensure_able_to_receive_confirmation, ensure_able_to_receive_message,
49 ensure_maximal_message_dispatch, ensure_weights_are_correct, WeightInfoExt,
50 EXPECTED_DEFAULT_MESSAGE_LENGTH, EXTRA_STORAGE_PROOF_SIZE,
51};
52
53use bp_header_chain::HeaderChain;
54use bp_messages::{
55 source_chain::{
56 DeliveryConfirmationPayments, FromBridgedChainMessagesDeliveryProof, OnMessagesDelivered,
57 SendMessageArtifacts,
58 },
59 target_chain::{
60 DeliveryPayments, DispatchMessage, FromBridgedChainMessagesProof, MessageDispatch,
61 ProvedLaneMessages, ProvedMessages,
62 },
63 ChainWithMessages, DeliveredMessages, InboundLaneData, InboundMessageDetails, MessageKey,
64 MessageNonce, MessagePayload, MessagesOperatingMode, OutboundLaneData, OutboundMessageDetails,
65 UnrewardedRelayersState, VerificationError,
66};
67use bp_runtime::{
68 AccountIdOf, BasicOperatingMode, HashOf, OwnedBridgeModule, PreComputedSize, RangeInclusiveExt,
69 Size,
70};
71use codec::{Decode, Encode};
72use frame_support::{dispatch::PostDispatchInfo, ensure, fail, traits::Get, DefaultNoBound};
73use sp_std::{marker::PhantomData, prelude::*};
74
75mod call_ext;
76mod inbound_lane;
77mod lanes_manager;
78mod outbound_lane;
79mod proofs;
80mod tests;
81mod weights_ext;
82
83pub mod weights;
84
85#[cfg(feature = "runtime-benchmarks")]
86pub mod benchmarking;
87pub mod migration;
88
89pub use call_ext::*;
90pub use pallet::*;
91#[cfg(feature = "test-helpers")]
92pub use tests::*;
93
94pub const LOG_TARGET: &str = "runtime::bridge-messages";
96
97#[frame_support::pallet]
98pub mod pallet {
99 use super::*;
100 use bp_messages::{LaneIdType, ReceivedMessages, ReceptionResult};
101 use bp_runtime::RangeInclusiveExt;
102 use frame_support::pallet_prelude::*;
103 use frame_system::pallet_prelude::*;
104
105 #[pallet::config]
106 pub trait Config<I: 'static = ()>: frame_system::Config {
107 #[allow(deprecated)]
111 type RuntimeEvent: From<Event<Self, I>>
112 + IsType<<Self as frame_system::Config>::RuntimeEvent>;
113 type WeightInfo: WeightInfoExt;
115
116 type ThisChain: ChainWithMessages;
118 type BridgedChain: ChainWithMessages;
120 type BridgedHeaderChain: HeaderChain<Self::BridgedChain>;
122
123 type OutboundPayload: Parameter + Size;
125 type InboundPayload: Decode;
127 type LaneId: LaneIdType;
129
130 type DeliveryPayments: DeliveryPayments<Self::AccountId>;
132 type DeliveryConfirmationPayments: DeliveryConfirmationPayments<
135 Self::AccountId,
136 Self::LaneId,
137 >;
138 type OnMessagesDelivered: OnMessagesDelivered<Self::LaneId>;
140
141 type MessageDispatch: MessageDispatch<
143 DispatchPayload = Self::InboundPayload,
144 LaneId = Self::LaneId,
145 >;
146 }
147
148 pub type ThisChainOf<T, I> = <T as Config<I>>::ThisChain;
150 pub type BridgedChainOf<T, I> = <T as Config<I>>::BridgedChain;
152 pub type BridgedHeaderChainOf<T, I> = <T as Config<I>>::BridgedHeaderChain;
154 pub type LaneIdOf<T, I> = <T as Config<I>>::LaneId;
156
157 #[pallet::pallet]
158 #[pallet::storage_version(migration::STORAGE_VERSION)]
159 pub struct Pallet<T, I = ()>(PhantomData<(T, I)>);
160
161 impl<T: Config<I>, I: 'static> OwnedBridgeModule<T> for Pallet<T, I> {
162 const LOG_TARGET: &'static str = LOG_TARGET;
163 type OwnerStorage = PalletOwner<T, I>;
164 type OperatingMode = MessagesOperatingMode;
165 type OperatingModeStorage = PalletOperatingMode<T, I>;
166 }
167
168 #[pallet::call]
169 impl<T: Config<I>, I: 'static> Pallet<T, I> {
170 #[pallet::call_index(0)]
174 #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
175 pub fn set_owner(origin: OriginFor<T>, new_owner: Option<T::AccountId>) -> DispatchResult {
176 <Self as OwnedBridgeModule<_>>::set_owner(origin, new_owner)
177 }
178
179 #[pallet::call_index(1)]
183 #[pallet::weight((T::DbWeight::get().reads_writes(1, 1), DispatchClass::Operational))]
184 pub fn set_operating_mode(
185 origin: OriginFor<T>,
186 operating_mode: MessagesOperatingMode,
187 ) -> DispatchResult {
188 <Self as OwnedBridgeModule<_>>::set_operating_mode(origin, operating_mode)
189 }
190
191 #[pallet::call_index(2)]
213 #[pallet::weight(T::WeightInfo::receive_messages_proof_weight(&**proof, *messages_count, *dispatch_weight))]
214 pub fn receive_messages_proof(
215 origin: OriginFor<T>,
216 relayer_id_at_bridged_chain: AccountIdOf<BridgedChainOf<T, I>>,
217 proof: Box<FromBridgedChainMessagesProof<HashOf<BridgedChainOf<T, I>>, T::LaneId>>,
218 messages_count: u32,
219 dispatch_weight: Weight,
220 ) -> DispatchResultWithPostInfo {
221 Self::ensure_not_halted().map_err(Error::<T, I>::BridgeModule)?;
222 let relayer_id_at_this_chain = ensure_signed(origin)?;
223
224 ensure!(
226 MessageNonce::from(messages_count) <=
227 BridgedChainOf::<T, I>::MAX_UNCONFIRMED_MESSAGES_IN_CONFIRMATION_TX,
228 Error::<T, I>::TooManyMessagesInTheProof
229 );
230
231 let declared_weight = T::WeightInfo::receive_messages_proof_weight(
242 &*proof,
243 messages_count,
244 dispatch_weight,
245 );
246 let mut actual_weight = declared_weight;
247
248 let (lane_id, lane_data) =
250 verify_and_decode_messages_proof::<T, I>(*proof, messages_count).map_err(
251 |err| {
252 tracing::trace!(target: LOG_TARGET, error=?err, "Rejecting invalid messages proof");
253
254 Error::<T, I>::InvalidMessagesProof
255 },
256 )?;
257
258 let mut total_messages = 0;
260 let mut valid_messages = 0;
261 let mut dispatch_weight_left = dispatch_weight;
262 let mut lane = active_inbound_lane::<T, I>(lane_id)?;
263
264 let lane_extra_proof_size_bytes = lane.storage().extra_proof_size_bytes();
267 actual_weight = actual_weight.set_proof_size(
268 actual_weight.proof_size().saturating_sub(lane_extra_proof_size_bytes),
269 );
270
271 if let Some(lane_state) = lane_data.lane_state {
272 let updated_latest_confirmed_nonce = lane.receive_state_update(lane_state);
273 if let Some(updated_latest_confirmed_nonce) = updated_latest_confirmed_nonce {
274 tracing::trace!(
275 target: LOG_TARGET,
276 ?lane_id,
277 latest_confirmed_nonce=%updated_latest_confirmed_nonce,
278 unrewarded_relayers=?UnrewardedRelayersState::from(&lane.storage().data()),
279 "Received state update"
280 );
281 }
282 }
283
284 let mut messages_received_status =
285 ReceivedMessages::new(lane_id, Vec::with_capacity(lane_data.messages.len()));
286 for mut message in lane_data.messages {
287 debug_assert_eq!(message.key.lane_id, lane_id);
288 total_messages += 1;
289
290 let message_dispatch_weight = T::MessageDispatch::dispatch_weight(&mut message);
294 if message_dispatch_weight.any_gt(dispatch_weight_left) {
295 tracing::trace!(
296 target: LOG_TARGET,
297 ?lane_id,
298 declared=%message_dispatch_weight,
299 left=%dispatch_weight_left,
300 "Cannot dispatch any more messages"
301 );
302
303 fail!(Error::<T, I>::InsufficientDispatchWeight);
304 }
305
306 let receival_result = lane.receive_message::<T::MessageDispatch>(
307 &relayer_id_at_bridged_chain,
308 message.key.nonce,
309 message.data,
310 );
311
312 let unspent_weight = match &receival_result {
319 ReceptionResult::Dispatched(dispatch_result) => {
320 valid_messages += 1;
321 dispatch_result.unspent_weight
322 },
323 ReceptionResult::InvalidNonce |
324 ReceptionResult::TooManyUnrewardedRelayers |
325 ReceptionResult::TooManyUnconfirmedMessages => message_dispatch_weight,
326 };
327 messages_received_status.push(message.key.nonce, receival_result);
328
329 let unspent_weight = unspent_weight.min(message_dispatch_weight);
330 dispatch_weight_left -= message_dispatch_weight - unspent_weight;
331 actual_weight = actual_weight.saturating_sub(unspent_weight);
332 }
333
334 T::DeliveryPayments::pay_reward(
336 relayer_id_at_this_chain,
337 total_messages,
338 valid_messages,
339 actual_weight,
340 );
341
342 tracing::debug!(
343 target: LOG_TARGET,
344 total=%total_messages,
345 valid=%valid_messages,
346 %actual_weight,
347 %declared_weight,
348 "Received messages."
349 );
350
351 Self::deposit_event(Event::MessagesReceived(messages_received_status));
352
353 Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes })
354 }
355
356 #[pallet::call_index(3)]
358 #[pallet::weight(T::WeightInfo::receive_messages_delivery_proof_weight(
359 proof,
360 relayers_state,
361 ))]
362 pub fn receive_messages_delivery_proof(
363 origin: OriginFor<T>,
364 proof: FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChainOf<T, I>>, T::LaneId>,
365 mut relayers_state: UnrewardedRelayersState,
366 ) -> DispatchResultWithPostInfo {
367 Self::ensure_not_halted().map_err(Error::<T, I>::BridgeModule)?;
368
369 let proof_size = proof.size();
370 let confirmation_relayer = ensure_signed(origin)?;
371 let (lane_id, lane_data) = proofs::verify_messages_delivery_proof::<T, I>(proof)
372 .map_err(|err| {
373 tracing::trace!(
374 target: LOG_TARGET,
375 error=?err,
376 "Rejecting invalid messages delivery proof"
377 );
378
379 Error::<T, I>::InvalidMessagesDeliveryProof
380 })?;
381 ensure!(
382 relayers_state.is_valid(&lane_data),
383 Error::<T, I>::InvalidUnrewardedRelayersState
384 );
385
386 let mut lane = any_state_outbound_lane::<T, I>(lane_id)?;
388 let last_delivered_nonce = lane_data.last_delivered_nonce();
389 let confirmed_messages = lane
390 .confirm_delivery(
391 relayers_state.total_messages,
392 last_delivered_nonce,
393 &lane_data.relayers,
394 )
395 .map_err(Error::<T, I>::ReceptionConfirmation)?;
396
397 if let Some(confirmed_messages) = confirmed_messages {
398 let received_range = confirmed_messages.begin..=confirmed_messages.end;
400 Self::deposit_event(Event::MessagesDelivered {
401 lane_id: lane_id.into(),
402 messages: confirmed_messages,
403 });
404
405 let actually_rewarded_relayers = T::DeliveryConfirmationPayments::pay_reward(
407 lane_id,
408 lane_data.relayers,
409 &confirmation_relayer,
410 &received_range,
411 );
412
413 relayers_state.unrewarded_relayer_entries = sp_std::cmp::min(
415 relayers_state.unrewarded_relayer_entries,
416 actually_rewarded_relayers,
417 );
418 relayers_state.total_messages = sp_std::cmp::min(
419 relayers_state.total_messages,
420 received_range.checked_len().unwrap_or(MessageNonce::MAX),
421 );
422 };
423
424 tracing::trace!(
425 target: LOG_TARGET,
426 ?lane_id,
427 %last_delivered_nonce,
428 "Received messages delivery proof up to (and including)"
429 );
430
431 T::OnMessagesDelivered::on_messages_delivered(
433 lane_id,
434 lane.data().queued_messages().saturating_len(),
435 );
436
437 let actual_weight = T::WeightInfo::receive_messages_delivery_proof_weight(
441 &PreComputedSize(proof_size as usize),
442 &relayers_state,
443 );
444
445 Ok(PostDispatchInfo { actual_weight: Some(actual_weight), pays_fee: Pays::Yes })
446 }
447 }
448
449 #[pallet::event]
450 #[pallet::generate_deposit(pub(super) fn deposit_event)]
451 pub enum Event<T: Config<I>, I: 'static = ()> {
452 MessageAccepted {
454 lane_id: T::LaneId,
456 nonce: MessageNonce,
458 },
459 MessagesReceived(
461 ReceivedMessages<
463 <T::MessageDispatch as MessageDispatch>::DispatchLevelResult,
464 T::LaneId,
465 >,
466 ),
467 MessagesDelivered {
469 lane_id: T::LaneId,
471 messages: DeliveredMessages,
473 },
474 }
475
476 #[pallet::error]
477 #[derive(PartialEq, Eq)]
478 pub enum Error<T, I = ()> {
479 NotOperatingNormally,
481 LanesManager(LanesManagerError),
483 MessageRejectedByPallet(VerificationError),
485 TooManyMessagesInTheProof,
487 InvalidMessagesProof,
489 InvalidMessagesDeliveryProof,
491 InvalidUnrewardedRelayersState,
494 InsufficientDispatchWeight,
497 ReceptionConfirmation(ReceptionConfirmationError),
499 BridgeModule(bp_runtime::OwnedBridgeModuleError),
501 }
502
503 #[pallet::storage]
510 pub type PalletOwner<T: Config<I>, I: 'static = ()> = StorageValue<_, T::AccountId>;
511
512 #[pallet::storage]
516 pub type PalletOperatingMode<T: Config<I>, I: 'static = ()> =
517 StorageValue<_, MessagesOperatingMode, ValueQuery>;
518
519 #[pallet::storage]
524 pub type InboundLanes<T: Config<I>, I: 'static = ()> =
525 StorageMap<_, Blake2_128Concat, T::LaneId, StoredInboundLaneData<T, I>, OptionQuery>;
526
527 #[pallet::storage]
529 pub type OutboundLanes<T: Config<I>, I: 'static = ()> = StorageMap<
530 Hasher = Blake2_128Concat,
531 Key = T::LaneId,
532 Value = OutboundLaneData,
533 QueryKind = OptionQuery,
534 >;
535
536 #[pallet::storage]
538 pub type OutboundMessages<T: Config<I>, I: 'static = ()> =
539 StorageMap<_, Blake2_128Concat, MessageKey<T::LaneId>, StoredMessagePayload<T, I>>;
540
541 #[pallet::genesis_config]
542 #[derive(DefaultNoBound)]
543 pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
544 pub operating_mode: MessagesOperatingMode,
546 pub owner: Option<T::AccountId>,
548 pub opened_lanes: Vec<T::LaneId>,
550 #[serde(skip)]
552 pub _phantom: sp_std::marker::PhantomData<I>,
553 }
554
555 #[pallet::genesis_build]
556 impl<T: Config<I>, I: 'static> BuildGenesisConfig for GenesisConfig<T, I> {
557 fn build(&self) {
558 PalletOperatingMode::<T, I>::put(self.operating_mode);
559 if let Some(ref owner) = self.owner {
560 PalletOwner::<T, I>::put(owner);
561 }
562
563 for lane_id in &self.opened_lanes {
564 InboundLanes::<T, I>::insert(lane_id, InboundLaneData::opened());
565 OutboundLanes::<T, I>::insert(lane_id, OutboundLaneData::opened());
566 }
567 }
568 }
569
570 #[pallet::hooks]
571 impl<T: Config<I>, I: 'static> Hooks<BlockNumberFor<T>> for Pallet<T, I> {
572 #[cfg(feature = "try-runtime")]
573 fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
574 Self::do_try_state()
575 }
576 }
577
578 impl<T: Config<I>, I: 'static> Pallet<T, I> {
579 pub fn outbound_message_data(
581 lane: T::LaneId,
582 nonce: MessageNonce,
583 ) -> Option<MessagePayload> {
584 OutboundMessages::<T, I>::get(MessageKey { lane_id: lane, nonce }).map(Into::into)
585 }
586
587 pub fn inbound_message_data(
589 lane: T::LaneId,
590 payload: MessagePayload,
591 outbound_details: OutboundMessageDetails,
592 ) -> InboundMessageDetails {
593 let mut dispatch_message = DispatchMessage {
594 key: MessageKey { lane_id: lane, nonce: outbound_details.nonce },
595 data: payload.into(),
596 };
597 InboundMessageDetails {
598 dispatch_weight: T::MessageDispatch::dispatch_weight(&mut dispatch_message),
599 }
600 }
601
602 pub fn outbound_lane_data(lane: T::LaneId) -> Option<OutboundLaneData> {
604 OutboundLanes::<T, I>::get(lane)
605 }
606
607 pub fn inbound_lane_data(
609 lane: T::LaneId,
610 ) -> Option<InboundLaneData<AccountIdOf<BridgedChainOf<T, I>>>> {
611 InboundLanes::<T, I>::get(lane).map(|lane| lane.0)
612 }
613 }
614
615 #[cfg(any(feature = "try-runtime", test))]
616 impl<T: Config<I>, I: 'static> Pallet<T, I> {
617 pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
619 Self::do_try_state_for_outbound_lanes()
620 }
621
622 pub fn do_try_state_for_outbound_lanes() -> Result<(), sp_runtime::TryRuntimeError> {
624 use sp_runtime::traits::One;
625 use sp_std::vec::Vec;
626
627 let mut unpruned_lanes = Vec::new();
629 for (lane_id, lane_data) in OutboundLanes::<T, I>::iter() {
630 let Some(expected_last_prunned_nonce) =
631 lane_data.oldest_unpruned_nonce.checked_sub(One::one())
632 else {
633 continue;
634 };
635
636 let mut unpruned_message_nonces = Vec::new();
638 const MAX_MESSAGES_ITERATION: u64 = 16;
639 let start_nonce =
640 expected_last_prunned_nonce.checked_sub(MAX_MESSAGES_ITERATION).unwrap_or(0);
641 for current_nonce in start_nonce..=expected_last_prunned_nonce {
642 if OutboundMessages::<T, I>::contains_key(MessageKey {
644 lane_id,
645 nonce: current_nonce,
646 }) {
647 unpruned_message_nonces.push(current_nonce);
648 }
649 }
650
651 if !unpruned_message_nonces.is_empty() {
652 tracing::warn!(
653 target: LOG_TARGET,
654 ?lane_id,
655 ?lane_data,
656 ?unpruned_message_nonces,
657 "do_try_state_for_outbound_lanes found",
658 );
659 unpruned_lanes.push((lane_id, lane_data, unpruned_message_nonces));
660 }
661 }
662
663 ensure!(unpruned_lanes.is_empty(), "Found unpruned lanes!");
665
666 Ok(())
667 }
668 }
669}
670
671#[derive(Debug, PartialEq, Eq)]
674pub struct SendMessageArgs<T: Config<I>, I: 'static> {
675 lane_id: T::LaneId,
676 lane: OutboundLane<RuntimeOutboundLaneStorage<T, I>>,
677 payload: StoredMessagePayload<T, I>,
678}
679
680impl<T, I> bp_messages::source_chain::MessagesBridge<T::OutboundPayload, T::LaneId> for Pallet<T, I>
681where
682 T: Config<I>,
683 I: 'static,
684{
685 type Error = Error<T, I>;
686 type SendMessageArgs = SendMessageArgs<T, I>;
687
688 fn validate_message(
689 lane_id: T::LaneId,
690 message: &T::OutboundPayload,
691 ) -> Result<SendMessageArgs<T, I>, Self::Error> {
692 ensure_normal_operating_mode::<T, I>()?;
694
695 let lane = active_outbound_lane::<T, I>(lane_id)?;
697
698 Ok(SendMessageArgs {
699 lane_id,
700 lane,
701 payload: StoredMessagePayload::<T, I>::try_from(message.encode()).map_err(|_| {
702 Error::<T, I>::MessageRejectedByPallet(VerificationError::MessageTooLarge)
703 })?,
704 })
705 }
706
707 fn send_message(args: SendMessageArgs<T, I>) -> SendMessageArtifacts {
708 let mut lane = args.lane;
710 let message_len = args.payload.len();
711 let nonce = lane.send_message(args.payload);
712
713 let enqueued_messages = lane.data().queued_messages().saturating_len();
715
716 tracing::trace!(
717 target: LOG_TARGET,
718 lane_id=?args.lane_id,
719 %nonce,
720 message_size=?message_len,
721 "Accepted message"
722 );
723
724 Pallet::<T, I>::deposit_event(Event::MessageAccepted {
725 lane_id: args.lane_id.into(),
726 nonce,
727 });
728
729 SendMessageArtifacts { nonce, enqueued_messages }
730 }
731}
732
733fn ensure_normal_operating_mode<T: Config<I>, I: 'static>() -> Result<(), Error<T, I>> {
735 if PalletOperatingMode::<T, I>::get() ==
736 MessagesOperatingMode::Basic(BasicOperatingMode::Normal)
737 {
738 return Ok(());
739 }
740
741 Err(Error::<T, I>::NotOperatingNormally)
742}
743
744fn active_inbound_lane<T: Config<I>, I: 'static>(
746 lane_id: T::LaneId,
747) -> Result<InboundLane<RuntimeInboundLaneStorage<T, I>>, Error<T, I>> {
748 LanesManager::<T, I>::new()
749 .active_inbound_lane(lane_id)
750 .map_err(Error::LanesManager)
751}
752
753fn active_outbound_lane<T: Config<I>, I: 'static>(
755 lane_id: T::LaneId,
756) -> Result<OutboundLane<RuntimeOutboundLaneStorage<T, I>>, Error<T, I>> {
757 LanesManager::<T, I>::new()
758 .active_outbound_lane(lane_id)
759 .map_err(Error::LanesManager)
760}
761
762fn any_state_outbound_lane<T: Config<I>, I: 'static>(
764 lane_id: T::LaneId,
765) -> Result<OutboundLane<RuntimeOutboundLaneStorage<T, I>>, Error<T, I>> {
766 LanesManager::<T, I>::new()
767 .any_state_outbound_lane(lane_id)
768 .map_err(Error::LanesManager)
769}
770
771fn verify_and_decode_messages_proof<T: Config<I>, I: 'static>(
773 proof: FromBridgedChainMessagesProof<HashOf<BridgedChainOf<T, I>>, T::LaneId>,
774 messages_count: u32,
775) -> Result<
776 ProvedMessages<T::LaneId, DispatchMessage<T::InboundPayload, T::LaneId>>,
777 VerificationError,
778> {
779 proofs::verify_messages_proof::<T, I>(proof, messages_count).map(|(lane, lane_data)| {
783 (
784 lane,
785 ProvedLaneMessages {
786 lane_state: lane_data.lane_state,
787 messages: lane_data.messages.into_iter().map(Into::into).collect(),
788 },
789 )
790 })
791}