mavryk_smart_rollup_encoding/
inbox.rs

1// SPDX-FileCopyrightText: 2022-2023 TriliTech <contact@trili.tech>
2// SPDX-FileCopyrightText: 2022-2023 Nomadic Labs <contact@nomadic-labs.com>
3// SPDX-FileCopyrightText: 2023 Marigold <contact@marigold.dev>
4//
5// SPDX-License-Identifier: MIT
6
7//! Types & encodings for the *inbox-half* of the *L1/L2 communication protocol*
8//!
9//! In *general*, this module is a re-implementation of the mavryk-protocol
10//! [inbox message repr].
11//!
12//! [inbox message repr]: <https://gitlab.com/tezos/tezos/-/blob/9028b797894a5d9db38bc61a20abb793c3778316/src/proto_alpha/lib_protocol/sc_rollup_inbox_message_repr.mli>
13
14use crate::michelson::Michelson;
15use crate::public_key_hash::PublicKeyHash;
16use crate::smart_rollup::SmartRollupAddress;
17use crate::timestamp::Timestamp;
18use crypto::hash::{BlockHash, ContractKt1Hash};
19use mavryk_data_encoding::enc;
20use mavryk_data_encoding::enc::BinWriter;
21use mavryk_data_encoding::encoding::HasEncoding;
22use mavryk_data_encoding::nom::NomReader;
23use nom::bytes::complete::tag;
24use nom::combinator::{map, rest};
25use nom::sequence::pair;
26use nom::sequence::preceded;
27use nom::Finish;
28use std::fmt::Display;
29
30#[derive(Debug, PartialEq, Eq, NomReader, HasEncoding, BinWriter)]
31enum InboxMessageRepr<Expr: Michelson> {
32    #[encoding(tag = 0)]
33    Internal(InternalInboxMessage<Expr>),
34    #[encoding(tag = 1)]
35    External,
36}
37
38/// Inbox message, received by the kernel as mavryk-encoded bytes.
39#[derive(Debug, PartialEq, Eq)]
40pub enum InboxMessage<'a, Expr: Michelson> {
41    /// Message sent from an L1 smart-contract.
42    Internal(InternalInboxMessage<Expr>),
43    /// Message of arbitrary bytes, in a format specific to the kernel.
44    ///
45    /// The containing operation will be sent by an implicit account - but will
46    /// deserialize to a structure representing *transactions* & *withdrawals* between
47    /// and from **Layer2Mv4* addresses respectively.
48    External(&'a [u8]),
49}
50
51impl<'a, Expr: Michelson> InboxMessage<'a, Expr> {
52    /// Replacement for `nom_read` for [InboxMessage].
53    ///
54    /// [NomReader] trait unfortunately does not propagate lifetime of the input bytes,
55    /// meaning that it is impossible to use it with a type that refers to a section of
56    /// the input.
57    ///
58    /// In our case, we want to avoid copies if possible - which require additional ticks.
59    pub fn parse(input: &'a [u8]) -> mavryk_data_encoding::nom::NomResult<Self> {
60        let (remaining, repr): (&'a [u8], _) = InboxMessageRepr::nom_read(input)?;
61
62        match repr {
63            InboxMessageRepr::Internal(i) => Ok((remaining, InboxMessage::Internal(i))),
64            InboxMessageRepr::External => map(rest, InboxMessage::External)(remaining),
65        }
66    }
67
68    /// Replacement for `bin_write` for [InboxMessage].
69    ///
70    /// [BinWriter] does not allow consumption of the input.
71    pub fn serialize(self, output: &mut Vec<u8>) -> mavryk_data_encoding::enc::BinResult {
72        match self {
73            InboxMessage::Internal(m) => {
74                InboxMessageRepr::Internal(m).bin_write(output)?;
75            }
76            InboxMessage::External(m) => {
77                InboxMessageRepr::<Expr>::External.bin_write(output)?;
78                output.extend_from_slice(m);
79            }
80        }
81        Ok(())
82    }
83}
84
85#[derive(Debug, PartialEq, Eq, NomReader, HasEncoding, BinWriter)]
86/// Transfer sent by an L1 smart-contract.
87pub struct Transfer<Expr: Michelson> {
88    /// Micheline-encoded payload, sent by the calling contract.
89    pub payload: Expr,
90    /// The calling smart-contract.
91    pub sender: ContractKt1Hash,
92    /// The originator of the transaction.
93    pub source: PublicKeyHash,
94    /// The destination of the message.
95    pub destination: SmartRollupAddress,
96}
97
98impl<E: Michelson> Display for Transfer<E> {
99    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
100        write!(
101            f,
102            "Transfer {{sender: {}, source: {}, dest: {}}}",
103            self.sender, self.source, self.destination
104        )
105    }
106}
107
108/// Metainformation per inbox level, usually goes after StartOfLevel message
109#[derive(Debug, PartialEq, Eq, NomReader, HasEncoding, BinWriter, Clone)]
110pub struct InfoPerLevel {
111    /// Timestamp of predecessor block
112    pub predecessor_timestamp: Timestamp,
113    /// Hash of predecessor block
114    pub predecessor: BlockHash,
115}
116
117impl Display for InfoPerLevel {
118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119        write!(
120            f,
121            "InfoPerLevel {{predecessor_timestamp: {}, predecessor: {}}}",
122            self.predecessor_timestamp, self.predecessor
123        )
124    }
125}
126
127/// Internal inbox message - known to be sent by the protocol
128#[derive(Debug, PartialEq, Eq, NomReader, HasEncoding, BinWriter)]
129pub enum InternalInboxMessage<Expr: Michelson> {
130    /// Transfer message
131    Transfer(Transfer<Expr>),
132    /// Start of level message, pushed at the beginning of an inbox level.
133    StartOfLevel,
134    /// End of level message, pushed at the end of an inbox level.
135    EndOfLevel,
136    /// Info per level, goes after StartOfLevel
137    InfoPerLevel(InfoPerLevel),
138}
139
140impl<Expr: Michelson> Display for InternalInboxMessage<Expr> {
141    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
142        match self {
143            Self::Transfer(tr) => write!(f, "{}", tr),
144            Self::StartOfLevel => write!(f, "StartOfLevel"),
145            Self::EndOfLevel => write!(f, "EndOfLevel"),
146            Self::InfoPerLevel(ipl) => write!(f, "{}", ipl),
147        }
148    }
149}
150
151/// External message framing protocol.
152///
153/// The rollup inbox is global in nature. Therefore a rollup will recieve
154/// messages destined for both itself, and other rollups.
155///
156/// For [`InternalInboxMessage::Transfer`]s, the rollup address is included
157/// directly. External messages, however, are purely byte sequences.
158///
159/// Where a rollup wishes to distinguish which external messages are meant for
160/// itself, a framing protocol is suggested.
161#[derive(Debug, Eq)]
162pub enum ExternalMessageFrame<T: AsRef<[u8]>> {
163    /// A message targetted at a single, specific, rollup.
164    Targetted {
165        /// The address of the targetted rollup.
166        address: SmartRollupAddress,
167        /// The remaining contents of the message.
168        contents: T,
169    },
170}
171
172impl<T: AsRef<[u8]>> ExternalMessageFrame<T> {
173    const TARGETTED_TAG: u8 = 0;
174}
175
176impl<'a> ExternalMessageFrame<&'a [u8]> {
177    /// Replacement for `nom_read` for [ExternalMessageFrame].
178    ///
179    /// [NomReader] trait unfortunately does not propagate lifetime of the input bytes,
180    /// meaning that it is impossible to use it with a type that refers to a section of
181    /// the input.
182    ///
183    /// In our case, we want to avoid copies if possible - which require additional ticks.
184    pub fn parse(input: &'a [u8]) -> Result<Self, mavryk_data_encoding::nom::NomError> {
185        let (_remaining, message) = map(
186            preceded(
187                tag([Self::TARGETTED_TAG]),
188                pair(SmartRollupAddress::nom_read, rest),
189            ),
190            |(address, contents)| Self::Targetted { address, contents },
191        )(input)
192        .finish()?;
193
194        Ok(message)
195    }
196}
197
198impl<T: AsRef<[u8]>, U: AsRef<[u8]>> core::cmp::PartialEq<ExternalMessageFrame<U>>
199    for ExternalMessageFrame<T>
200{
201    fn eq(&self, other: &ExternalMessageFrame<U>) -> bool {
202        match (self, other) {
203            (
204                Self::Targetted {
205                    address: a1,
206                    contents: c1,
207                },
208                ExternalMessageFrame::Targetted {
209                    address: a2,
210                    contents: c2,
211                },
212            ) => a1 == a2 && c1.as_ref() == c2.as_ref(),
213        }
214    }
215}
216
217impl<T: AsRef<[u8]>> BinWriter for ExternalMessageFrame<T> {
218    fn bin_write(&self, output: &mut Vec<u8>) -> enc::BinResult {
219        match self {
220            Self::Targetted { address, contents } => {
221                enc::put_byte(&Self::TARGETTED_TAG, output);
222                address.bin_write(output)?;
223                enc::put_bytes(contents.as_ref(), output);
224            }
225        }
226
227        Ok(())
228    }
229}
230
231#[cfg(test)]
232mod test {
233    use super::ExternalMessageFrame;
234    use super::InboxMessage;
235    use super::InternalInboxMessage;
236    use crate::michelson::Michelson;
237    use crate::michelson::MichelsonUnit;
238    use crate::smart_rollup::SmartRollupAddress;
239    use mavryk_data_encoding::enc::BinWriter;
240
241    #[test]
242    fn test_encode_decode_sol() {
243        // binary encoding produced by lightly-modified (to print encoded data) protocol test
244        let expected_bytes = vec![
245            // Inbox message start
246            0, // Internal tag
247            1, // Start of level tag
248        ];
249
250        let inbox_message = InboxMessage::Internal(InternalInboxMessage::StartOfLevel);
251
252        test_encode_decode::<MichelsonUnit>(expected_bytes, inbox_message)
253    }
254
255    #[test]
256    fn test_encode_decode_eol() {
257        // binary encoding produced by lightly-modified (to print encoded data) protocol test
258        let expected_bytes = vec![
259            // Inbox message start
260            0, // Internal tag
261            2, // End of level tag
262        ];
263
264        let inbox_message = InboxMessage::Internal(InternalInboxMessage::EndOfLevel);
265
266        test_encode_decode::<MichelsonUnit>(expected_bytes, inbox_message)
267    }
268
269    #[test]
270    fn test_encode_decode_external_inbox_message() {
271        let assert_enc = |message: Vec<u8>| {
272            let inbox_message =
273                InboxMessage::<MichelsonUnit>::External(message.as_slice());
274
275            assert_encode_decode_inbox_message(inbox_message);
276        };
277
278        assert_enc(vec![]);
279        assert_enc(vec![b'A']);
280        assert_enc("0123456789".as_bytes().to_vec());
281        assert_enc(vec![b'B'; 256]);
282        assert_enc(vec![b'\n'; 1234567]);
283        assert_enc(vec![5; 1234567]);
284    }
285
286    fn assert_encode_decode_inbox_message<Expr: Michelson>(message: InboxMessage<Expr>) {
287        let mut encoded = Vec::new();
288        message
289            .serialize(&mut encoded)
290            .expect("encoding should work");
291
292        let decoded = InboxMessage::<Expr>::parse(encoded.as_slice())
293            .expect("Deserialization failed")
294            .1;
295
296        let mut encoded_twice = Vec::new();
297        decoded
298            .serialize(&mut encoded_twice)
299            .expect("encoding should work");
300
301        assert_eq!(encoded, encoded_twice);
302    }
303
304    fn test_encode_decode<Expr: Michelson>(
305        expected_bytes: Vec<u8>,
306        inbox_message: InboxMessage<Expr>,
307    ) {
308        // Encoding
309        let mut bin = Vec::new();
310        inbox_message
311            .serialize(&mut bin)
312            .expect("encoding should work");
313
314        assert_eq!(expected_bytes, bin, "error in serialization");
315
316        // Decoding
317        let (input_remaining, _parsed_message) =
318            InboxMessage::<Expr>::parse(bin.as_slice())
319                .expect("deserialization should work");
320
321        assert!(input_remaining.is_empty());
322    }
323
324    #[test]
325    fn test_encode_decode_external_framing_targetted() {
326        let contents = "Hello, world! (But only in one rollup)";
327        let address =
328            SmartRollupAddress::from_b58check("sr163Lv22CdE8QagCwf48PWDTquk6isQwv57")
329                .unwrap();
330
331        let framed = ExternalMessageFrame::Targetted { address, contents };
332
333        let mut output = Vec::new();
334        framed.bin_write(&mut output).unwrap();
335
336        let parsed = ExternalMessageFrame::parse(&output).unwrap();
337
338        assert_eq!(framed, parsed);
339    }
340}