From 98ad3018fd778a3fa61c0aa39167e6ba49e71a0a Mon Sep 17 00:00:00 2001 From: Michael Zaikin Date: Mon, 24 Jun 2024 18:48:03 +0100 Subject: [PATCH] Kernel SDK: allow to enqueue heterogeneous batches of messages to outbox --- etherlink/kernel_evm/Cargo.lock | 1 + src/kernel_dal_echo/Cargo.lock | 7 + src/kernel_sdk/CHANGES.md | 3 + src/kernel_sdk/Cargo.lock | 7 + src/kernel_sdk/encoding/Cargo.toml | 4 + src/kernel_sdk/encoding/src/entrypoint.rs | 2 +- src/kernel_sdk/encoding/src/outbox.rs | 159 ++++++++++++++++++++-- src/kernel_sdk/sdk/src/outbox.rs | 13 +- src/riscv/Cargo.lock | 1 + src/riscv/dummy_kernel/Cargo.lock | 7 + src/riscv/jstz/Cargo.lock | 1 + src/rust_deps/Cargo.lock | 1 + 12 files changed, 186 insertions(+), 20 deletions(-) diff --git a/etherlink/kernel_evm/Cargo.lock b/etherlink/kernel_evm/Cargo.lock index 75fe4e39f0d2..f770afc4c3cf 100644 --- a/etherlink/kernel_evm/Cargo.lock +++ b/etherlink/kernel_evm/Cargo.lock @@ -2253,6 +2253,7 @@ dependencies = [ "nom", "num-bigint 0.3.3", "num-traits", + "paste", "regex", "tezos-smart-rollup-core", "tezos-smart-rollup-host", diff --git a/src/kernel_dal_echo/Cargo.lock b/src/kernel_dal_echo/Cargo.lock index 5eb23650d78e..46295e96600f 100644 --- a/src/kernel_dal_echo/Cargo.lock +++ b/src/kernel_dal_echo/Cargo.lock @@ -854,6 +854,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1320,6 +1326,7 @@ dependencies = [ "nom", "num-bigint", "num-traits", + "paste", "regex", "tezos-smart-rollup-core", "tezos-smart-rollup-host", diff --git a/src/kernel_sdk/CHANGES.md b/src/kernel_sdk/CHANGES.md index 39a3d3a5fdaa..c45972b69e19 100644 --- a/src/kernel_sdk/CHANGES.md +++ b/src/kernel_sdk/CHANGES.md @@ -18,6 +18,9 @@ - Add `entrypoint::main` procedural macro to mark the kernel entrypoint function. - Add `extra` feature flag, for functionalility not-expected to be used by kernels directly. - Remove the `proto-alpha` flag restriction on DAL host functions. +- Refactor `OutboxMessage` to `OutboxMessageFull` enabling different atomic batch backends that implement `AtomicBatch` trait. +- Add `AtomicBatch2` ... `AtomicBatch5` structs implementing `AtomicBatch` and constructed from tuples + of `OutboxMessageTransaction` with potentially different parameter types. ### Installer client/kernel diff --git a/src/kernel_sdk/Cargo.lock b/src/kernel_sdk/Cargo.lock index e110dd2593ea..1e2c36ab1d7f 100644 --- a/src/kernel_sdk/Cargo.lock +++ b/src/kernel_sdk/Cargo.lock @@ -976,6 +976,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1588,6 +1594,7 @@ dependencies = [ "nom", "num-bigint", "num-traits", + "paste", "proptest", "regex", "tezos-smart-rollup-core", diff --git a/src/kernel_sdk/encoding/Cargo.toml b/src/kernel_sdk/encoding/Cargo.toml index 4b5b7d1b3c5a..00542df353df 100644 --- a/src/kernel_sdk/encoding/Cargo.toml +++ b/src/kernel_sdk/encoding/Cargo.toml @@ -33,6 +33,10 @@ proptest = { version = "1.0", optional = true } hex = { version = "0.4.3", optional = true } thiserror = { version = "1.0", optional = true } regex = { version = "1.4.6", optional = true } +paste = "1.0.6" + +[dev-dependencies] +proptest = "1.0" [dependencies.tezos-smart-rollup-core] path = "../core" diff --git a/src/kernel_sdk/encoding/src/entrypoint.rs b/src/kernel_sdk/encoding/src/entrypoint.rs index 7a997a8fa943..fae95259be4a 100644 --- a/src/kernel_sdk/encoding/src/entrypoint.rs +++ b/src/kernel_sdk/encoding/src/entrypoint.rs @@ -119,7 +119,7 @@ impl BinWriter for Entrypoint { } } -#[cfg(feature = "testing")] +#[cfg(any(test, feature = "testing"))] mod testing { use super::*; use proptest::prelude::*; diff --git a/src/kernel_sdk/encoding/src/outbox.rs b/src/kernel_sdk/encoding/src/outbox.rs index c93c9e747f8d..114923049bf7 100644 --- a/src/kernel_sdk/encoding/src/outbox.rs +++ b/src/kernel_sdk/encoding/src/outbox.rs @@ -26,15 +26,20 @@ use crate::public_key_hash::PublicKeyHash; /// /// Encoded as a dynamic list of [OutboxMessageTransaction], with **no** case tag. #[derive(Debug, PartialEq, Eq, HasEncoding, NomReader, BinWriter)] -pub enum OutboxMessage { +pub enum OutboxMessageFull { /// List of outbox transactions that must succeed together. #[encoding(tag = 0)] - AtomicTransactionBatch(OutboxMessageTransactionBatch), + AtomicTransactionBatch(Batch), /// Only keys in the whitelist are allowed to stake and publish a commitment. #[encoding(tag = 2)] WhitelistUpdate(OutboxMessageWhitelistUpdate), } +/// Legacy variant of [OutboxMessageFull] for the backward compatibility. +/// +/// Uses [OutboxMessageTransactionBatch] with generic argument as atomic batch type. +pub type OutboxMessage = OutboxMessageFull>; + /// A batch of [`OutboxMessageTransaction`]. #[derive(Debug, PartialEq, Eq, HasEncoding, BinWriter, NomReader)] pub struct OutboxMessageTransactionBatch { @@ -42,6 +47,14 @@ pub struct OutboxMessageTransactionBatch { batch: Vec>, } +/// This is a marker trait specifying that any type implementing it might be used +/// as the underlying transaction container for the [OutboxMessageFull::AtomicTransactionBatch] variant. +/// +/// This trait is already derived for homogeneous [OutboxMessageTransactionBatch] type +/// and for code-generated structs supporting transactions with different Michelson types +/// [AtomicBatch2] and further up to [AtomicBatch5]. +pub trait AtomicBatch: HasEncoding + BinWriter + NomReader {} + impl OutboxMessageTransactionBatch { /// Returns the number of transactions in the batch. pub fn len(&self) -> usize { @@ -70,18 +83,85 @@ impl From>> } } -impl From> for OutboxMessage { +impl From> + for OutboxMessageFull> +{ fn from(transaction: OutboxMessageTransaction) -> Self { Self::AtomicTransactionBatch(vec![transaction].into()) } } -impl From>> for OutboxMessage { +impl From>> + for OutboxMessageFull> +{ fn from(batch: Vec>) -> Self { Self::AtomicTransactionBatch(batch.into()) } } +impl AtomicBatch for OutboxMessageTransactionBatch {} + +/// This macro defines a new structure AtomicBatchN where N is the number of [OutboxMessageTransaction] elements +/// (with potentially different Michelson parameters) it can hold. +/// +/// It also derives [HasEncoding], [BinWriter], [NomReader], and [AtomicBatch] traits for this struct. +macro_rules! impl_outbox_message_encodable { + ($s:ident, $($idx:tt),+) => { + // Using [paste] here to produce identifiers with suffixes (concat_ident! alternative). + paste::paste! { + /// Atomic batch of outbox message transactions which might have different parameter types. + /// Can be constructed out of a [OutboxMessageTransaction] tuple. + #[derive(Debug)] + pub struct $s<$( []: Michelson ),+>($( pub OutboxMessageTransaction<[]> ),+); + + impl<$( []: Michelson ),+> AtomicBatch for $s<$( [] ),+> {} + + impl<$( []: Michelson ),+> HasEncoding for $s<$( [] ),+> { + fn encoding() -> tezos_data_encoding::encoding::Encoding { + tezos_data_encoding::encoding::Encoding::Custom + } + } + + impl<$( []: Michelson ),+> BinWriter for $s<$( [] ),+> { + fn bin_write(&self, buffer: &mut Vec) -> tezos_data_encoding::enc::BinResult { + use tezos_data_encoding::enc::dynamic; + use tezos_data_encoding::enc::BinResult; + + fn serializer<$( []: Michelson ),+>(batch: &$s<$( [] ),+>, out: &mut Vec) -> BinResult { + $( batch.$idx.bin_write(out)?; )+ + Ok(()) + } + + dynamic(serializer)(&self, buffer) + } + } + + impl<$( []: Michelson ),+> NomReader for $s<$( [] ),+> { + fn nom_read(input: &[u8]) -> tezos_data_encoding::nom::NomResult { + use tezos_data_encoding::nom::dynamic; + use nom::sequence::tuple; + use nom::combinator::map; + + map( + dynamic( + tuple(( + $( OutboxMessageTransaction::<[]>::nom_read ),+ + )) + ), + |( $( [] ),+ )| $s( $( [] ),+ ) + )(input) + } + } + } + }; +} + +// Generate AtomicBatchN structures for tuples of size 2 - 5 +impl_outbox_message_encodable!(AtomicBatch2, 0, 1); +impl_outbox_message_encodable!(AtomicBatch3, 0, 1, 2); +impl_outbox_message_encodable!(AtomicBatch4, 0, 1, 2, 3); +impl_outbox_message_encodable!(AtomicBatch5, 0, 1, 2, 3, 4); + /// Outbox message transaction, part of the outbox message. /// /// Encoded as: @@ -164,7 +244,7 @@ impl TryFrom>> for OutboxMessageWhitelistUpdate { #[cfg(test)] mod test { - use crate::michelson::ticket::StringTicket; + use crate::michelson::{ticket::StringTicket, MichelsonBytes}; use super::*; @@ -212,6 +292,18 @@ mod test { b'a', b'n', b'o', b't', b'h', b'e', b'r', // Entrypoint name ]; + const ENCODED_TRANSACTION_THREE: [u8; 37] = [ + // Single byte + 10, // bytes binary tag + 0, 0, 0, 1, // size of payload + 255, // payload + // Destination + 1, 21, 237, 173, b'\'', 159, b'U', 226, 254, b'@', 17, 222, b'm', b',', b'$', 253, + 245, 27, 242, b'%', 197, 0, // Entrypoint + 0, 0, 0, 5, // Entrypoint size + b't', b'h', b'r', b'e', b'e', // Entrypoint name + ]; + // To display the encoding from OCaml: // Format.asprintf "%a" // Binary_schema.pp @@ -269,9 +361,7 @@ mod test { expected.extend_from_slice(ENCODED_TRANSACTION_ONE.as_slice()); expected.extend_from_slice(ENCODED_TRANSACTION_TWO.as_slice()); - let message = OutboxMessage::AtomicTransactionBatch( - vec![transaction_one(), transaction_two()].into(), - ); + let message = OutboxMessage::from(vec![transaction_one(), transaction_two()]); let mut bin = vec![]; message.bin_write(&mut bin).unwrap(); @@ -299,9 +389,7 @@ mod test { bytes.extend_from_slice(ENCODED_TRANSACTION_TWO.as_slice()); bytes.extend_from_slice(ENCODED_TRANSACTION_ONE.as_slice()); - let expected = OutboxMessage::AtomicTransactionBatch( - vec![transaction_two(), transaction_one()].into(), - ); + let expected = OutboxMessage::from(vec![transaction_two(), transaction_one()]); let (remaining, message) = OutboxMessage::nom_read(bytes.as_slice()).unwrap(); @@ -418,4 +506,53 @@ mod test { (OutboxMessageWhitelistUpdate { whitelist: l4 }) ); } + + fn transaction_three() -> OutboxMessageTransaction { + let parameters = MichelsonBytes::from(vec![255u8; 1]); + let destination = + Contract::from_b58check("KT1AaiUqbT3NmQts2w7ofY4vJviVchztiW4y").unwrap(); + let entrypoint = Entrypoint::try_from("three".to_string()).unwrap(); + + OutboxMessageTransaction { + parameters, + destination, + entrypoint, + } + } + + #[test] + fn serialize_homogeneous_batch_in_two_ways() { + let batch_1 = AtomicBatch2(transaction_two(), transaction_one()); + let batch_2 = OutboxMessageTransactionBatch::from(vec![ + transaction_two(), + transaction_one(), + ]); + + let message_1 = OutboxMessageFull::AtomicTransactionBatch(batch_1); + let message_2 = OutboxMessageFull::AtomicTransactionBatch(batch_2); + + let mut bytes_1 = Vec::new(); + message_1.bin_write(&mut bytes_1).unwrap(); + + let mut bytes_2 = Vec::new(); + message_2.bin_write(&mut bytes_2).unwrap(); + + assert_eq!(bytes_1, bytes_2); + } + + #[test] + fn check_heterogeneous_batch_encoding() { + let batch = AtomicBatch2(transaction_three(), transaction_one()); + + let mut bytes = Vec::new(); + batch.bin_write(&mut bytes).unwrap(); + + let txs_size = + (ENCODED_TRANSACTION_THREE.len() + ENCODED_TRANSACTION_ONE.len()) as u8; + let mut expected = [0, 0, 0, txs_size].to_vec(); + expected.extend_from_slice(ENCODED_TRANSACTION_THREE.as_slice()); + expected.extend_from_slice(ENCODED_TRANSACTION_ONE.as_slice()); + + assert_eq!(expected, bytes); + } } diff --git a/src/kernel_sdk/sdk/src/outbox.rs b/src/kernel_sdk/sdk/src/outbox.rs index 7098466e9f0f..9bf8b5e798d7 100644 --- a/src/kernel_sdk/sdk/src/outbox.rs +++ b/src/kernel_sdk/sdk/src/outbox.rs @@ -71,12 +71,10 @@ //! [outbox queue]: OutboxQueue //! [default]: OUTBOX_QUEUE -#[doc(inline)] -pub use tezos_smart_rollup_encoding::outbox::*; - use tezos_data_encoding::enc::BinWriter; use tezos_smart_rollup_core::MAX_OUTPUT_SIZE; -use tezos_smart_rollup_encoding::michelson::Michelson; +#[doc(inline)] +pub use tezos_smart_rollup_encoding::outbox::*; use tezos_smart_rollup_host::path::{concat, Path, PathError}; use tezos_smart_rollup_host::Error; use tezos_smart_rollup_host::{ @@ -140,10 +138,10 @@ impl<'a, P: Path> OutboxQueue<'a, P> { /// Returns the current length of the queue. /// /// [`flush_queue`]: Self::flush_queue - pub fn queue_message( + pub fn queue_message( &self, host: &mut impl Runtime, - message: impl Into>, + message: impl Into>, ) -> Result { let (start, len) = self.read_meta(host); @@ -153,9 +151,8 @@ impl<'a, P: Path> OutboxQueue<'a, P> { let end = start.wrapping_add(len); - let message: OutboxMessage = message.into(); + let message = message.into(); - // could remove the allocation if bin_write supported encoding into a buffer directly. let mut buffer = Vec::with_capacity(MAX_OUTPUT_SIZE); message .bin_write(&mut buffer) diff --git a/src/riscv/Cargo.lock b/src/riscv/Cargo.lock index 736c8ce29f75..1cc8c8f62e2b 100644 --- a/src/riscv/Cargo.lock +++ b/src/riscv/Cargo.lock @@ -2271,6 +2271,7 @@ dependencies = [ "nom", "num-bigint", "num-traits", + "paste", "regex", "tezos-smart-rollup-core", "tezos-smart-rollup-host", diff --git a/src/riscv/dummy_kernel/Cargo.lock b/src/riscv/dummy_kernel/Cargo.lock index 4e8a01ad3d92..f418e2c82654 100644 --- a/src/riscv/dummy_kernel/Cargo.lock +++ b/src/riscv/dummy_kernel/Cargo.lock @@ -647,6 +647,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -1070,6 +1076,7 @@ dependencies = [ "nom", "num-bigint", "num-traits", + "paste", "regex", "tezos-smart-rollup-core", "tezos-smart-rollup-host", diff --git a/src/riscv/jstz/Cargo.lock b/src/riscv/jstz/Cargo.lock index cfd69cb70502..da68bb1c911c 100644 --- a/src/riscv/jstz/Cargo.lock +++ b/src/riscv/jstz/Cargo.lock @@ -2110,6 +2110,7 @@ dependencies = [ "nom", "num-bigint 0.3.3", "num-traits", + "paste", "regex", "tezos-smart-rollup-core", "tezos-smart-rollup-host", diff --git a/src/rust_deps/Cargo.lock b/src/rust_deps/Cargo.lock index 29ba56a29987..035b32d68753 100644 --- a/src/rust_deps/Cargo.lock +++ b/src/rust_deps/Cargo.lock @@ -3290,6 +3290,7 @@ dependencies = [ "nom 7.1.3", "num-bigint 0.3.3", "num-traits", + "paste", "regex", "tezos-smart-rollup-core", "tezos-smart-rollup-host", -- GitLab