Skip to content

Commit

Permalink
Make LaneId backwards compatible - part 1
Browse files Browse the repository at this point in the history
  • Loading branch information
bkontur committed Jul 26, 2024
1 parent 0a9a336 commit 5aa0d7c
Show file tree
Hide file tree
Showing 7 changed files with 317 additions and 199 deletions.
1 change: 1 addition & 0 deletions bridges/primitives/messages/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ sp-io = { workspace = true }
[dev-dependencies]
hex = { workspace = true, default-features = true }
hex-literal = { workspace = true, default-features = true }
bp-runtime = { features = ["test-helpers"], workspace = true }

[features]
default = ["std"]
Expand Down
258 changes: 258 additions & 0 deletions bridges/primitives/messages/src/lane.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
// Copyright (C) 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/>.

//! Primitives of messages module, that represents lane id.

use codec::{Decode, Encode, Error as CodecError, Input, MaxEncodedLen};
use frame_support::sp_runtime::Either;
use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
use sp_core::{RuntimeDebug, TypeId, H256};
use sp_io::hashing::blake2_256;

/// Bridge lane identifier.
///
/// Lane connects two endpoints at both sides of the bridge. We assume that every endpoint
/// has its own unique identifier. We want lane identifiers to be the same on the both sides
/// of the bridge (and naturally unique across global consensus if endpoints have unique
/// identifiers). So lane id is the hash (`blake2_256`) of **ordered** encoded locations
/// concatenation (separated by some binary data). I.e.:
///
/// ```nocompile
/// let endpoint1 = X2(GlobalConsensus(NetworkId::Rococo), Parachain(42));
/// let endpoint2 = X2(GlobalConsensus(NetworkId::Wococo), Parachain(777));
///
/// let final_lane_key = if endpoint1 < endpoint2 {
/// (endpoint1, VALUES_SEPARATOR, endpoint2)
/// } else {
/// (endpoint2, VALUES_SEPARATOR, endpoint1)
/// }.using_encoded(blake2_256);
/// ```
///
/// Note: For backwards compatibility reasons, we also handle the older format `[u8; 4]`.
#[derive(
Clone,
Copy,
Decode,
Encode,
Eq,
Ord,
PartialOrd,
PartialEq,
TypeInfo,
MaxEncodedLen,
Serialize,
Deserialize,
)]
pub struct LaneId(InnerLaneId);

impl LaneId {
/// Create lane identifier from two locations.
pub fn new<T: Ord + Encode>(endpoint1: T, endpoint2: T) -> Self {
const VALUES_SEPARATOR: [u8; 31] = *b"bridges-lane-id-value-separator";

LaneId(InnerLaneId::Hash(
if endpoint1 < endpoint2 {
(endpoint1, VALUES_SEPARATOR, endpoint2)
} else {
(endpoint2, VALUES_SEPARATOR, endpoint1)
}
.using_encoded(blake2_256)
.into(),
))
}

/// Create lane identifier from given hash.
///
/// There's no `From<H256>` implementation for the `LaneId`, because using this conversion
/// in a wrong way (i.e. computing hash of endpoints manually) may lead to issues. So we
/// want the call to be explicit.
pub const fn from_inner(inner: Either<H256, [u8; 4]>) -> Self {
LaneId(match inner {
Either::Left(hash) => InnerLaneId::Hash(hash),
Either::Right(array) => InnerLaneId::Array(array),
})
}
}

impl core::fmt::Display for LaneId {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Display::fmt(&self.0, f)
}
}

impl core::fmt::Debug for LaneId {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Debug::fmt(&self.0, f)
}
}

impl AsRef<[u8]> for LaneId {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}

impl TypeId for LaneId {
const TYPE_ID: [u8; 4] = *b"blan";
}

#[derive(
Clone, Copy, Eq, Ord, PartialOrd, PartialEq, TypeInfo, MaxEncodedLen, Serialize, Deserialize,
)]
enum InnerLaneId {
/// Old format (for backwards compatibility).
Array([u8; 4]),
/// New format 32-byte hash generated by `blake2_256`.
Hash(H256),
}

impl Encode for InnerLaneId {
fn encode(&self) -> Vec<u8> {
match self {
InnerLaneId::Array(array) => array.encode(),
InnerLaneId::Hash(hash) => hash.encode(),
}
}
}

impl Decode for InnerLaneId {
fn decode<I: Input>(input: &mut I) -> Result<Self, CodecError> {
// check backwards compatibly first
if input.remaining_len() == Ok(Some(4)) {
let array: [u8; 4] = Decode::decode(input)?;
return Ok(InnerLaneId::Array(array))
}

// else check new format
H256::decode(input).map(InnerLaneId::Hash)
}
}

impl core::fmt::Display for InnerLaneId {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
InnerLaneId::Array(array) => write!(f, "Array({:?})", array),
InnerLaneId::Hash(hash) => write!(f, "Hash({:?})", hash),
}
}
}

impl core::fmt::Debug for InnerLaneId {
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
match self {
InnerLaneId::Array(array) => array.fmt(fmt),
InnerLaneId::Hash(hash) => hash.fmt(fmt),
}
}
}

impl AsRef<[u8]> for InnerLaneId {
fn as_ref(&self) -> &[u8] {
match self {
InnerLaneId::Array(array) => array.as_ref(),
InnerLaneId::Hash(hash) => hash.as_ref(),
}
}
}

/// Lane state.
#[derive(Clone, Copy, Decode, Encode, Eq, PartialEq, TypeInfo, MaxEncodedLen, RuntimeDebug)]
pub enum LaneState {
/// Lane is opened and messages may be sent/received over it.
Opened,
/// Lane is closed and all attempts to send/receive messages to/from this lane
/// will fail.
///
/// Keep in mind that the lane has two ends and the state of the same lane at
/// its ends may be different. Those who are controlling/serving the lane
/// and/or sending messages over the lane, have to coordinate their actions on
/// both ends to make sure that lane is operating smoothly on both ends.
Closed,
}

impl LaneState {
/// Returns true if lane state allows sending/receiving messages.
pub fn is_active(&self) -> bool {
matches!(*self, LaneState::Opened)
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn lane_id_debug_format_matches_inner_hash_format() {
assert_eq!(
format!("{:?}", LaneId(InnerLaneId::Hash(H256::from([1u8; 32])))),
format!("{:?}", H256::from([1u8; 32])),
);
assert_eq!(
format!("{:?}", LaneId(InnerLaneId::Array([0, 0, 0, 1]))),
format!("{:?}", [0, 0, 0, 1]),
);
}

#[test]
fn lane_id_as_ref_works() {
assert_eq!(
"0101010101010101010101010101010101010101010101010101010101010101",
hex::encode(LaneId(InnerLaneId::Hash(H256::from([1u8; 32]))).as_ref()),
);
assert_eq!("00000001", hex::encode(LaneId(InnerLaneId::Array([0, 0, 0, 1])).as_ref()),);
}

#[test]
fn lane_id_is_generated_using_ordered_endpoints() {
assert_eq!(LaneId::new(1, 2), LaneId::new(2, 1));
}

#[test]
fn lane_id_is_different_for_different_endpoints() {
assert_ne!(LaneId::new(1, 2), LaneId::new(1, 3));
}

#[test]
fn lane_id_is_different_even_if_arguments_has_partial_matching_encoding() {
/// Some artificial type that generates the same encoding for different values
/// concatenations. I.e. the encoding for `(Either::Two(1, 2), Either::Two(3, 4))`
/// is the same as encoding of `(Either::Three(1, 2, 3), Either::One(4))`.
/// In practice, this type is not useful, because you can't do a proper decoding.
/// But still there may be some collisions even in proper types.
#[derive(Eq, Ord, PartialEq, PartialOrd)]
enum Either {
Three(u64, u64, u64),
Two(u64, u64),
One(u64),
}

impl codec::Encode for Either {
fn encode(&self) -> Vec<u8> {
match *self {
Self::One(a) => a.encode(),
Self::Two(a, b) => (a, b).encode(),
Self::Three(a, b, c) => (a, b, c).encode(),
}
}
}

assert_ne!(
LaneId::new(Either::Two(1, 2), Either::Two(3, 4)),
LaneId::new(Either::Three(1, 2, 3), Either::One(4)),
);
}
}
Loading

0 comments on commit 5aa0d7c

Please sign in to comment.