pub use bp_runtime::{RangeInclusiveExt, UnderlyingChainOf, UnderlyingChainProvider};
use bp_header_chain::HeaderChain;
use bp_messages::{
source_chain::{LaneMessageVerifier, TargetHeaderChain},
target_chain::{ProvedLaneMessages, ProvedMessages, SourceHeaderChain},
InboundLaneData, LaneId, Message, MessageKey, MessageNonce, MessagePayload, OutboundLaneData,
VerificationError,
};
use bp_runtime::{Chain, RawStorageProof, Size, StorageProofChecker};
use codec::{Decode, Encode};
use frame_support::{traits::Get, weights::Weight, RuntimeDebug};
use hash_db::Hasher;
use scale_info::TypeInfo;
use sp_std::{convert::TryFrom, marker::PhantomData, vec::Vec};
pub trait MessageBridge {
const BRIDGED_MESSAGES_PALLET_NAME: &'static str;
type ThisChain: ThisChainWithMessages;
type BridgedChain: BridgedChainWithMessages;
type BridgedHeaderChain: HeaderChain<UnderlyingChainOf<Self::BridgedChain>>;
}
pub trait ThisChainWithMessages: UnderlyingChainProvider {
type RuntimeOrigin;
}
pub trait BridgedChainWithMessages: UnderlyingChainProvider {}
pub type ThisChain<B> = <B as MessageBridge>::ThisChain;
pub type BridgedChain<B> = <B as MessageBridge>::BridgedChain;
pub type HashOf<C> = bp_runtime::HashOf<<C as UnderlyingChainProvider>::Chain>;
pub type HasherOf<C> = bp_runtime::HasherOf<UnderlyingChainOf<C>>;
pub type AccountIdOf<C> = bp_runtime::AccountIdOf<UnderlyingChainOf<C>>;
pub type BalanceOf<C> = bp_runtime::BalanceOf<UnderlyingChainOf<C>>;
pub type OriginOf<C> = <C as ThisChainWithMessages>::RuntimeOrigin;
pub mod source {
use super::*;
pub type FromThisChainMessagePayload = crate::messages_xcm_extension::XcmAsPlainPayload;
pub struct FromThisChainMaximalOutboundPayloadSize<B>(PhantomData<B>);
impl<B: MessageBridge> Get<u32> for FromThisChainMaximalOutboundPayloadSize<B> {
fn get() -> u32 {
maximal_message_size::<B>()
}
}
#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub struct FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash> {
pub bridged_header_hash: BridgedHeaderHash,
pub storage_proof: RawStorageProof,
pub lane: LaneId,
}
impl<BridgedHeaderHash> Size for FromBridgedChainMessagesDeliveryProof<BridgedHeaderHash> {
fn size(&self) -> u32 {
u32::try_from(
self.storage_proof
.iter()
.fold(0usize, |sum, node| sum.saturating_add(node.len())),
)
.unwrap_or(u32::MAX)
}
}
pub type ParsedMessagesDeliveryProofFromBridgedChain<B> =
(LaneId, InboundLaneData<AccountIdOf<ThisChain<B>>>);
#[derive(RuntimeDebug)]
pub struct FromThisChainMessageVerifier<B>(PhantomData<B>);
impl<B> LaneMessageVerifier<OriginOf<ThisChain<B>>, FromThisChainMessagePayload>
for FromThisChainMessageVerifier<B>
where
B: MessageBridge,
OriginOf<ThisChain<B>>: Clone
+ Into<Result<frame_system::RawOrigin<AccountIdOf<ThisChain<B>>>, OriginOf<ThisChain<B>>>>,
AccountIdOf<ThisChain<B>>: PartialEq + Clone,
{
fn verify_message(
_submitter: &OriginOf<ThisChain<B>>,
_lane: &LaneId,
_lane_outbound_data: &OutboundLaneData,
_payload: &FromThisChainMessagePayload,
) -> Result<(), VerificationError> {
Ok(())
}
}
pub fn maximal_message_size<B: MessageBridge>() -> u32 {
super::target::maximal_incoming_message_size(
UnderlyingChainOf::<BridgedChain<B>>::max_extrinsic_size(),
)
}
pub struct TargetHeaderChainAdapter<B>(PhantomData<B>);
impl<B: MessageBridge> TargetHeaderChain<FromThisChainMessagePayload, AccountIdOf<ThisChain<B>>>
for TargetHeaderChainAdapter<B>
{
type MessagesDeliveryProof = FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChain<B>>>;
fn verify_message(payload: &FromThisChainMessagePayload) -> Result<(), VerificationError> {
verify_chain_message::<B>(payload)
}
fn verify_messages_delivery_proof(
proof: Self::MessagesDeliveryProof,
) -> Result<(LaneId, InboundLaneData<AccountIdOf<ThisChain<B>>>), VerificationError> {
verify_messages_delivery_proof::<B>(proof)
}
}
pub fn verify_chain_message<B: MessageBridge>(
payload: &FromThisChainMessagePayload,
) -> Result<(), VerificationError> {
if payload.len() > maximal_message_size::<B>() as usize {
return Err(VerificationError::MessageTooLarge)
}
Ok(())
}
pub fn verify_messages_delivery_proof<B: MessageBridge>(
proof: FromBridgedChainMessagesDeliveryProof<HashOf<BridgedChain<B>>>,
) -> Result<ParsedMessagesDeliveryProofFromBridgedChain<B>, VerificationError> {
let FromBridgedChainMessagesDeliveryProof { bridged_header_hash, storage_proof, lane } =
proof;
let mut storage =
B::BridgedHeaderChain::storage_proof_checker(bridged_header_hash, storage_proof)
.map_err(VerificationError::HeaderChain)?;
let storage_inbound_lane_data_key = bp_messages::storage_keys::inbound_lane_data_key(
B::BRIDGED_MESSAGES_PALLET_NAME,
&lane,
);
let inbound_lane_data = storage
.read_and_decode_mandatory_value(storage_inbound_lane_data_key.0.as_ref())
.map_err(VerificationError::InboundLaneStorage)?;
storage.ensure_no_unused_nodes().map_err(VerificationError::StorageProof)?;
Ok((lane, inbound_lane_data))
}
}
pub mod target {
use super::*;
pub type FromBridgedChainMessagePayload = crate::messages_xcm_extension::XcmAsPlainPayload;
#[derive(Clone, Decode, Encode, Eq, PartialEq, RuntimeDebug, TypeInfo)]
pub struct FromBridgedChainMessagesProof<BridgedHeaderHash> {
pub bridged_header_hash: BridgedHeaderHash,
pub storage_proof: RawStorageProof,
pub lane: LaneId,
pub nonces_start: MessageNonce,
pub nonces_end: MessageNonce,
}
impl<BridgedHeaderHash> Size for FromBridgedChainMessagesProof<BridgedHeaderHash> {
fn size(&self) -> u32 {
u32::try_from(
self.storage_proof
.iter()
.fold(0usize, |sum, node| sum.saturating_add(node.len())),
)
.unwrap_or(u32::MAX)
}
}
pub fn maximal_incoming_message_dispatch_weight(maximal_extrinsic_weight: Weight) -> Weight {
maximal_extrinsic_weight / 2
}
pub fn maximal_incoming_message_size(maximal_extrinsic_size: u32) -> u32 {
maximal_extrinsic_size / 3 * 2
}
pub struct SourceHeaderChainAdapter<B>(PhantomData<B>);
impl<B: MessageBridge> SourceHeaderChain for SourceHeaderChainAdapter<B> {
type MessagesProof = FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>;
fn verify_messages_proof(
proof: Self::MessagesProof,
messages_count: u32,
) -> Result<ProvedMessages<Message>, VerificationError> {
verify_messages_proof::<B>(proof, messages_count)
}
}
pub fn verify_messages_proof<B: MessageBridge>(
proof: FromBridgedChainMessagesProof<HashOf<BridgedChain<B>>>,
messages_count: u32,
) -> Result<ProvedMessages<Message>, VerificationError> {
let FromBridgedChainMessagesProof {
bridged_header_hash,
storage_proof,
lane,
nonces_start,
nonces_end,
} = proof;
let storage =
B::BridgedHeaderChain::storage_proof_checker(bridged_header_hash, storage_proof)
.map_err(VerificationError::HeaderChain)?;
let mut parser = StorageProofCheckerAdapter::<_, B> { storage, _dummy: Default::default() };
let nonces_range = nonces_start..=nonces_end;
let messages_in_the_proof = nonces_range.checked_len().unwrap_or(0);
if messages_in_the_proof != MessageNonce::from(messages_count) {
return Err(VerificationError::MessagesCountMismatch)
}
let mut messages = Vec::with_capacity(messages_in_the_proof as _);
for nonce in nonces_range {
let message_key = MessageKey { lane_id: lane, nonce };
let message_payload = parser.read_and_decode_message_payload(&message_key)?;
messages.push(Message { key: message_key, payload: message_payload });
}
let proved_lane_messages = ProvedLaneMessages {
lane_state: parser.read_and_decode_outbound_lane_data(&lane)?,
messages,
};
if proved_lane_messages.lane_state.is_none() && proved_lane_messages.messages.is_empty() {
return Err(VerificationError::EmptyMessageProof)
}
parser
.storage
.ensure_no_unused_nodes()
.map_err(VerificationError::StorageProof)?;
let mut proved_messages = ProvedMessages::new();
proved_messages.insert(lane, proved_lane_messages);
Ok(proved_messages)
}
struct StorageProofCheckerAdapter<H: Hasher, B> {
storage: StorageProofChecker<H>,
_dummy: sp_std::marker::PhantomData<B>,
}
impl<H: Hasher, B: MessageBridge> StorageProofCheckerAdapter<H, B> {
fn read_and_decode_outbound_lane_data(
&mut self,
lane_id: &LaneId,
) -> Result<Option<OutboundLaneData>, VerificationError> {
let storage_outbound_lane_data_key = bp_messages::storage_keys::outbound_lane_data_key(
B::BRIDGED_MESSAGES_PALLET_NAME,
lane_id,
);
self.storage
.read_and_decode_opt_value(storage_outbound_lane_data_key.0.as_ref())
.map_err(VerificationError::OutboundLaneStorage)
}
fn read_and_decode_message_payload(
&mut self,
message_key: &MessageKey,
) -> Result<MessagePayload, VerificationError> {
let storage_message_key = bp_messages::storage_keys::message_key(
B::BRIDGED_MESSAGES_PALLET_NAME,
&message_key.lane_id,
message_key.nonce,
);
self.storage
.read_and_decode_mandatory_value(storage_message_key.0.as_ref())
.map_err(VerificationError::MessageStorage)
}
}
}
pub type BridgeMessagesCallOf<C> = bp_messages::BridgeMessagesCall<
bp_runtime::AccountIdOf<C>,
target::FromBridgedChainMessagesProof<bp_runtime::HashOf<C>>,
source::FromBridgedChainMessagesDeliveryProof<bp_runtime::HashOf<C>>,
>;
#[cfg(test)]
mod tests {
use super::*;
use crate::{
messages_generation::{
encode_all_messages, encode_lane_data, prepare_messages_storage_proof,
},
mock::*,
};
use bp_header_chain::{HeaderChainError, StoredHeaderDataBuilder};
use bp_runtime::{HeaderId, StorageProofError};
use codec::Encode;
use sp_core::H256;
use sp_runtime::traits::Header as _;
#[test]
fn verify_chain_message_rejects_message_with_too_large_declared_weight() {
assert!(source::verify_chain_message::<OnThisChainBridge>(&vec![
42;
BRIDGED_CHAIN_MAX_EXTRINSIC_WEIGHT -
1
])
.is_err());
}
#[test]
fn verify_chain_message_rejects_message_too_large_message() {
assert!(source::verify_chain_message::<OnThisChainBridge>(&vec![
0;
source::maximal_message_size::<OnThisChainBridge>()
as usize + 1
],)
.is_err());
}
#[test]
fn verify_chain_message_accepts_maximal_message() {
assert_eq!(
source::verify_chain_message::<OnThisChainBridge>(&vec![
0;
source::maximal_message_size::<OnThisChainBridge>()
as _
],),
Ok(()),
);
}
fn using_messages_proof<R>(
nonces_end: MessageNonce,
outbound_lane_data: Option<OutboundLaneData>,
encode_message: impl Fn(MessageNonce, &MessagePayload) -> Option<Vec<u8>>,
encode_outbound_lane_data: impl Fn(&OutboundLaneData) -> Vec<u8>,
test: impl Fn(target::FromBridgedChainMessagesProof<H256>) -> R,
) -> R {
let (state_root, storage_proof) = prepare_messages_storage_proof::<OnThisChainBridge>(
TEST_LANE_ID,
1..=nonces_end,
outbound_lane_data,
bp_runtime::StorageProofSize::Minimal(0),
vec![42],
encode_message,
encode_outbound_lane_data,
);
sp_io::TestExternalities::new(Default::default()).execute_with(move || {
let bridged_header = BridgedChainHeader::new(
0,
Default::default(),
state_root,
Default::default(),
Default::default(),
);
let bridged_header_hash = bridged_header.hash();
pallet_bridge_grandpa::BestFinalized::<TestRuntime>::put(HeaderId(
0,
bridged_header_hash,
));
pallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
bridged_header_hash,
bridged_header.build(),
);
test(target::FromBridgedChainMessagesProof {
bridged_header_hash,
storage_proof,
lane: TEST_LANE_ID,
nonces_start: 1,
nonces_end,
})
})
}
#[test]
fn messages_proof_is_rejected_if_declared_less_than_actual_number_of_messages() {
assert_eq!(
using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| {
target::verify_messages_proof::<OnThisChainBridge>(proof, 5)
}),
Err(VerificationError::MessagesCountMismatch),
);
}
#[test]
fn messages_proof_is_rejected_if_declared_more_than_actual_number_of_messages() {
assert_eq!(
using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| {
target::verify_messages_proof::<OnThisChainBridge>(proof, 15)
}),
Err(VerificationError::MessagesCountMismatch),
);
}
#[test]
fn message_proof_is_rejected_if_header_is_missing_from_the_chain() {
assert_eq!(
using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| {
let bridged_header_hash =
pallet_bridge_grandpa::BestFinalized::<TestRuntime>::get().unwrap().1;
pallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::remove(bridged_header_hash);
target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
}),
Err(VerificationError::HeaderChain(HeaderChainError::UnknownHeader)),
);
}
#[test]
fn message_proof_is_rejected_if_header_state_root_mismatches() {
assert_eq!(
using_messages_proof(10, None, encode_all_messages, encode_lane_data, |proof| {
let bridged_header_hash =
pallet_bridge_grandpa::BestFinalized::<TestRuntime>::get().unwrap().1;
pallet_bridge_grandpa::ImportedHeaders::<TestRuntime>::insert(
bridged_header_hash,
BridgedChainHeader::new(
0,
Default::default(),
Default::default(),
Default::default(),
Default::default(),
)
.build(),
);
target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
}),
Err(VerificationError::HeaderChain(HeaderChainError::StorageProof(
StorageProofError::StorageRootMismatch
))),
);
}
#[test]
fn message_proof_is_rejected_if_it_has_duplicate_trie_nodes() {
assert_eq!(
using_messages_proof(10, None, encode_all_messages, encode_lane_data, |mut proof| {
let node = proof.storage_proof.pop().unwrap();
proof.storage_proof.push(node.clone());
proof.storage_proof.push(node);
target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
},),
Err(VerificationError::HeaderChain(HeaderChainError::StorageProof(
StorageProofError::DuplicateNodesInProof
))),
);
}
#[test]
fn message_proof_is_rejected_if_it_has_unused_trie_nodes() {
assert_eq!(
using_messages_proof(10, None, encode_all_messages, encode_lane_data, |mut proof| {
proof.storage_proof.push(vec![42]);
target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
},),
Err(VerificationError::StorageProof(StorageProofError::UnusedNodesInTheProof)),
);
}
#[test]
fn message_proof_is_rejected_if_required_message_is_missing() {
matches!(
using_messages_proof(
10,
None,
|n, m| if n != 5 { Some(m.encode()) } else { None },
encode_lane_data,
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 10)
),
Err(VerificationError::MessageStorage(StorageProofError::StorageValueEmpty)),
);
}
#[test]
fn message_proof_is_rejected_if_message_decode_fails() {
matches!(
using_messages_proof(
10,
None,
|n, m| {
let mut m = m.encode();
if n == 5 {
m = vec![42]
}
Some(m)
},
encode_lane_data,
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 10),
),
Err(VerificationError::MessageStorage(StorageProofError::StorageValueDecodeFailed(_))),
);
}
#[test]
fn message_proof_is_rejected_if_outbound_lane_state_decode_fails() {
matches!(
using_messages_proof(
10,
Some(OutboundLaneData {
oldest_unpruned_nonce: 1,
latest_received_nonce: 1,
latest_generated_nonce: 1,
}),
encode_all_messages,
|d| {
let mut d = d.encode();
d.truncate(1);
d
},
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 10),
),
Err(VerificationError::OutboundLaneStorage(
StorageProofError::StorageValueDecodeFailed(_)
)),
);
}
#[test]
fn message_proof_is_rejected_if_it_is_empty() {
assert_eq!(
using_messages_proof(0, None, encode_all_messages, encode_lane_data, |proof| {
target::verify_messages_proof::<OnThisChainBridge>(proof, 0)
},),
Err(VerificationError::EmptyMessageProof),
);
}
#[test]
fn non_empty_message_proof_without_messages_is_accepted() {
assert_eq!(
using_messages_proof(
0,
Some(OutboundLaneData {
oldest_unpruned_nonce: 1,
latest_received_nonce: 1,
latest_generated_nonce: 1,
}),
encode_all_messages,
encode_lane_data,
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 0),
),
Ok(vec![(
TEST_LANE_ID,
ProvedLaneMessages {
lane_state: Some(OutboundLaneData {
oldest_unpruned_nonce: 1,
latest_received_nonce: 1,
latest_generated_nonce: 1,
}),
messages: Vec::new(),
},
)]
.into_iter()
.collect()),
);
}
#[test]
fn non_empty_message_proof_is_accepted() {
assert_eq!(
using_messages_proof(
1,
Some(OutboundLaneData {
oldest_unpruned_nonce: 1,
latest_received_nonce: 1,
latest_generated_nonce: 1,
}),
encode_all_messages,
encode_lane_data,
|proof| target::verify_messages_proof::<OnThisChainBridge>(proof, 1),
),
Ok(vec![(
TEST_LANE_ID,
ProvedLaneMessages {
lane_state: Some(OutboundLaneData {
oldest_unpruned_nonce: 1,
latest_received_nonce: 1,
latest_generated_nonce: 1,
}),
messages: vec![Message {
key: MessageKey { lane_id: TEST_LANE_ID, nonce: 1 },
payload: vec![42],
}],
},
)]
.into_iter()
.collect()),
);
}
#[test]
fn verify_messages_proof_does_not_panic_if_messages_count_mismatches() {
assert_eq!(
using_messages_proof(1, None, encode_all_messages, encode_lane_data, |mut proof| {
proof.nonces_end = u64::MAX;
target::verify_messages_proof::<OnThisChainBridge>(proof, u32::MAX)
},),
Err(VerificationError::MessagesCountMismatch),
);
}
}