From 2b85ab863e18995e9943aa47a6d1f202fcd298b0 Mon Sep 17 00:00:00 2001 From: Emma Turner Date: Thu, 25 May 2023 09:33:29 +0100 Subject: [PATCH] Kernel SDK: add external message framing protocol --- src/kernel_sdk/CHANGES.rst | 2 + src/kernel_sdk/encoding/src/inbox.rs | 106 +++++++++++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/src/kernel_sdk/CHANGES.rst b/src/kernel_sdk/CHANGES.rst index ec1be4ab2694..fa609449182e 100644 --- a/src/kernel_sdk/CHANGES.rst +++ b/src/kernel_sdk/CHANGES.rst @@ -52,6 +52,8 @@ SDK - Keep ``PreimageHash`` in ``dac`` module. - Move all other functions/structs move to ``dac::pages`` submodule. Deprecate importing from them ``dac`` directly. - Introduce ``dac::certificate`` submodule for handling serialization and deserialization of DAC certificates. +- Add ``inbox::ExternalMessageFrame`` to ``tezos-smart-rollup-encoding``, to define a shared framing protocol for + Smart Rollup external messages. Installer client ---------------- diff --git a/src/kernel_sdk/encoding/src/inbox.rs b/src/kernel_sdk/encoding/src/inbox.rs index 3787381e728d..60afccf58f40 100644 --- a/src/kernel_sdk/encoding/src/inbox.rs +++ b/src/kernel_sdk/encoding/src/inbox.rs @@ -1,5 +1,6 @@ // SPDX-FileCopyrightText: 2022-2023 TriliTech // SPDX-FileCopyrightText: 2022-2023 Nomadic Labs +// SPDX-FileCopyrightText: 2023 Marigold // // SPDX-License-Identifier: MIT @@ -15,8 +16,13 @@ use crate::public_key_hash::PublicKeyHash; use crate::smart_rollup::SmartRollupAddress; use crate::timestamp::Timestamp; use crypto::hash::{BlockHash, ContractKt1Hash}; +use nom::bytes::complete::tag; use nom::combinator::{map, rest}; +use nom::sequence::pair; +use nom::sequence::preceded; +use nom::Finish; use std::fmt::Display; +use tezos_data_encoding::enc; use tezos_data_encoding::enc::BinWriter; use tezos_data_encoding::encoding::HasEncoding; use tezos_data_encoding::nom::NomReader; @@ -142,12 +148,95 @@ impl Display for InternalInboxMessage { } } +/// External message framing protocol. +/// +/// The rollup inbox is global in nature. Therefore a rollup will recieve +/// messages destined for both itself, and other rollups. +/// +/// For [`InternalInboxMessage::Transfer`]s, the rollup address is included +/// directly. External messages, however, are purely byte sequences. +/// +/// Where a rollup wishes to distinguish which external messages are meant for +/// itself, a framing protocol is suggested. +#[derive(Debug, Eq)] +pub enum ExternalMessageFrame> { + /// A message targetted at a single, specific, rollup. + Targetted { + /// The address of the targetted rollup. + address: SmartRollupAddress, + /// The remaining contents of the message. + contents: T, + }, +} + +impl> ExternalMessageFrame { + const TARGETTED_TAG: u8 = 0; +} + +impl<'a> ExternalMessageFrame<&'a [u8]> { + /// Replacement for `nom_read` for [ExternalMessageFrame]. + /// + /// [NomReader] trait unfortunately does not propagate lifetime of the input bytes, + /// meaning that it is impossible to use it with a type that refers to a section of + /// the input. + /// + /// In our case, we want to avoid copies if possible - which require additional ticks. + pub fn parse(input: &'a [u8]) -> Result { + let (_remaining, message) = map( + preceded( + tag([Self::TARGETTED_TAG]), + pair(SmartRollupAddress::nom_read, rest), + ), + |(address, contents)| Self::Targetted { address, contents }, + )(input) + .finish()?; + + Ok(message) + } +} + +impl, U: AsRef<[u8]>> core::cmp::PartialEq> + for ExternalMessageFrame +{ + fn eq(&self, other: &ExternalMessageFrame) -> bool { + match (self, other) { + ( + Self::Targetted { + address: a1, + contents: c1, + }, + ExternalMessageFrame::Targetted { + address: a2, + contents: c2, + }, + ) => a1 == a2 && c1.as_ref() == c2.as_ref(), + } + } +} + +impl> BinWriter for ExternalMessageFrame { + fn bin_write(&self, output: &mut Vec) -> enc::BinResult { + match self { + Self::Targetted { address, contents } => { + enc::put_byte(&Self::TARGETTED_TAG, output); + address.bin_write(output)?; + enc::put_bytes(contents.as_ref(), output); + } + } + + Ok(()) + } +} + #[cfg(test)] mod test { + use super::ExternalMessageFrame; use super::InboxMessage; use super::InternalInboxMessage; use crate::michelson::Michelson; use crate::michelson::MichelsonUnit; + use crate::smart_rollup::SmartRollupAddress; + use tezos_data_encoding::enc::BinWriter; #[test] fn test_encode_decode_sol() { @@ -231,4 +320,21 @@ mod test { assert!(input_remaining.is_empty()); } + + #[test] + fn test_encode_decode_external_framing_targetted() { + let contents = "Hello, world! (But only in one rollup)"; + let address = + SmartRollupAddress::from_b58check("sr163Lv22CdE8QagCwf48PWDTquk6isQwv57") + .unwrap(); + + let framed = ExternalMessageFrame::Targetted { address, contents }; + + let mut output = Vec::new(); + framed.bin_write(&mut output).unwrap(); + + let parsed = ExternalMessageFrame::parse(&output).unwrap(); + + assert_eq!(framed, parsed); + } } -- GitLab