diff --git a/src/kernel_sequencer/Cargo.lock b/src/kernel_sequencer/Cargo.lock index d45b79b312f5fdfee81f6f8dd919bf0fd20826bb..961f7abb77d4ddce3bc79b9e4ac1bcd330ad10ab 100644 --- a/src/kernel_sequencer/Cargo.lock +++ b/src/kernel_sequencer/Cargo.lock @@ -346,6 +346,7 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" name = "kernel-sequencer" version = "0.1.0" dependencies = [ + "hex", "nom", "tezos-smart-rollup-core", "tezos-smart-rollup-debug", diff --git a/src/kernel_sequencer/Cargo.toml b/src/kernel_sequencer/Cargo.toml index 5e8c913cd9a3057b682504c325e785a92e1586a4..c1432364f2f6baa4b9637d995a0e3c2a9de4092e 100644 --- a/src/kernel_sequencer/Cargo.toml +++ b/src/kernel_sequencer/Cargo.toml @@ -50,6 +50,9 @@ version = "0.2.0" version = "0.5.0" default-features = false +[dev-dependencies.hex] +version = "0.4.3" + [features] default = [] diff --git a/src/kernel_sequencer/src/delayed_inbox.rs b/src/kernel_sequencer/src/delayed_inbox.rs index 252fd41cbf975cb0102e7865b6b38353ba60f2c7..29fc85b689db3259bf117631e3a2790f666905c5 100644 --- a/src/kernel_sequencer/src/delayed_inbox.rs +++ b/src/kernel_sequencer/src/delayed_inbox.rs @@ -6,7 +6,6 @@ use tezos_data_encoding::enc::BinWriter; use tezos_data_encoding::nom::NomReader; use tezos_smart_rollup_debug::debug_msg; -use tezos_smart_rollup_encoding::smart_rollup::SmartRollupAddress; use tezos_smart_rollup_host::{ input::Message, metadata::{RollupMetadata, RAW_ROLLUP_ADDRESS_SIZE}, @@ -14,7 +13,7 @@ use tezos_smart_rollup_host::{ }; use crate::{ - message::{Framed, KernelMessage, Sequence, SequencerMsg, SetSequencer}, + message::{Framed, KernelMessage, Sequence, SequencerMsg, SetSequencer, UnverifiedSigned}, queue::Queue, routing::FilterBehavior, state::{update_state, State}, @@ -28,6 +27,15 @@ pub struct UserMessage { pub(crate) payload: Vec, } +/// Message saved in the pending inbox +#[derive(BinWriter, NomReader)] +pub struct PendingUserMessage { + level: u32, + id: u32, + #[encoding(dynamic, list)] + payload: Vec, +} + /// Return a message from the inbox /// /// This function drives the delayed inbox: @@ -39,6 +47,8 @@ pub fn read_input( filter_behavior: FilterBehavior, timeout_window: u32, delayed_inbox_queue: &mut Queue, + pending_inbox_queue: &mut Queue, + pending_inbox_index: &mut u32, ) -> Result, RuntimeError> { let RollupMetadata { raw_rollup_address, .. @@ -46,8 +56,8 @@ pub fn read_input( loop { let msg = host.read_input()?; match msg { - None => return Ok(None), // No more messages to be processed Some(msg) => { + let level = msg.level; let payload = msg.as_ref(); // Verify the state of the delayed inbox on SoL if let [0x00, 0x00, ..] = payload { @@ -61,24 +71,31 @@ pub fn read_input( match message { Err(_) => {} Ok((_, message)) => match message { - KernelMessage::Sequencer(Framed { - destination, - payload: SequencerMsg::Sequence(sequence), - }) => handle_sequence_message( - host, - sequence, - destination, - &raw_rollup_address, - state, - ), - KernelMessage::Sequencer(Framed { - destination, - payload: SequencerMsg::SetSequencer(set_sequence), - }) => handle_set_sequencer_message( - set_sequence, - destination, - &raw_rollup_address, - ), + KernelMessage::Sequencer(sequencer_msg) => { + debug_msg!(host, "Received a sequence message {:?}", &sequencer_msg); + + let Ok(payload) = extract_payload( + sequencer_msg, + &raw_rollup_address, + state, + ) else { continue;}; + + match payload { + SequencerMsg::Sequence(sequence) => { + let _ = handle_sequence_message( + host, + sequence, + delayed_inbox_queue, + pending_inbox_queue, + level, + pending_inbox_index, + ); + } + SequencerMsg::SetSequencer(set_sequencer) => { + handle_set_sequencer_message(set_sequencer) + } + } + } KernelMessage::DelayedMessage(user_message) => { let _ = handle_message( host, @@ -93,41 +110,116 @@ pub fn read_input( }, } } + None => return handle_pending_inbox(host, pending_inbox_queue), } } } -/// Handle Sequence message -fn handle_sequence_message( - host: &impl Runtime, - sequence: Sequence, - destination: SmartRollupAddress, +/// Extracts the payload of the message sent by the sequencer. +/// +/// The destination has to match the current rollup address. +/// The state of the kernel has to be `Sequenced`. +/// The signature has to be valid. +fn extract_payload( + sequencer_msg: UnverifiedSigned>, rollup_address: &[u8; RAW_ROLLUP_ADDRESS_SIZE], state: State, -) { +) -> Result { + // Check if state is sequenced. + let State::Sequenced(sequencer_address) = state else { + return Err(RuntimeError::HostErr( + tezos_smart_rollup_host::Error::GenericInvalidAccess, + )); + }; + + let body = sequencer_msg.body(&sequencer_address)?; + + let Framed { + destination, + payload, + } = body; + + // Verify if the destination is for this rollup. if destination.hash().as_ref() != rollup_address { - return; + return Err(RuntimeError::HostErr( + tezos_smart_rollup_host::Error::GenericInvalidAccess, + )); } - debug_msg!( - host, - "Received a sequence message {:?} targeting our rollup", - sequence - ); + Ok(payload) +} - let State::Sequenced(_) = state else {return;}; +fn handle_sequence_message( + host: &mut impl Runtime, + sequence: Sequence, + delayed_inbox_queue: &mut Queue, + pending_inbox_queue: &mut Queue, + level: u32, + pending_inbox_index: &mut u32, +) -> Result<(), RuntimeError> { + let Sequence { + delayed_messages_prefix, + delayed_messages_suffix, + messages, + .. + } = sequence; - // process the sequence -} + // First pop elements from the delayed inbox indicated by the prefix + for _ in 0..delayed_messages_prefix { + // pop the head of the delayed inbox + let delayed_user_msg: Option = delayed_inbox_queue.pop(host)?; + // break the loop if the delayed inbox is empty + let Some(delayed_user_msg) = delayed_user_msg else {break;}; + // add the payload to the pending inbox + let UserMessage { payload, .. } = delayed_user_msg; + pending_inbox_queue.add( + host, + &PendingUserMessage { + level, + id: *pending_inbox_index, + payload, + }, + )?; + *pending_inbox_index += 1; + } -fn handle_set_sequencer_message( - _set_sequencer: SetSequencer, - destination: SmartRollupAddress, - rollup_address: &[u8; RAW_ROLLUP_ADDRESS_SIZE], -) { - if destination.hash().as_ref() == rollup_address { - // process the set sequencer message + // Then add messages to the pending_inbox_queue + for bytes in messages { + pending_inbox_queue.add( + host, + &PendingUserMessage { + level, + id: *pending_inbox_index, + payload: bytes.inner, + }, + )?; + *pending_inbox_index += 1; + } + + // Finally, pop elements from the delayed inbox indicated by the suffix + for _ in 0..delayed_messages_suffix { + // pop the head of the delayed inbox + let delayed_user_msg: Option = delayed_inbox_queue.pop(host)?; + // break the loop if the delayed inbox is empty + let Some(delayed_user_msg) = delayed_user_msg else {break;}; + // add the payload to the pending inbox + let UserMessage { payload, .. } = delayed_user_msg; + pending_inbox_queue.add( + host, + &PendingUserMessage { + level, + id: *pending_inbox_index, + payload, + }, + )?; + *pending_inbox_index += 1; } + + Ok(()) +} + +fn handle_set_sequencer_message(_set_sequencer: SetSequencer) { + // process the set sequencer message } /// Handle messages @@ -159,3 +251,164 @@ fn handle_message( Ok(()) } + +/// Empty the pending inbox and returns the message +fn handle_pending_inbox( + host: &mut H, + pending_inbox_queue: &mut Queue, +) -> Result, RuntimeError> { + let pending_message = pending_inbox_queue.pop(host)?; + let Some(PendingUserMessage {id, level, payload}) = pending_message else {return Ok(None)}; + + let msg = Message::new(level, id, payload); + Ok(Some(msg)) +} + +#[cfg(test)] +mod tests { + use tezos_crypto_rs::hash::SecretKeyEd25519; + use tezos_crypto_rs::hash::SeedEd25519; + use tezos_crypto_rs::hash::Signature; + use tezos_crypto_rs::hash::SmartRollupHash; + use tezos_data_encoding::enc::BinWriter; + use tezos_smart_rollup_encoding::public_key::PublicKey; + use tezos_smart_rollup_encoding::smart_rollup::SmartRollupAddress; + use tezos_smart_rollup_host::metadata::RollupMetadata; + use tezos_smart_rollup_host::runtime::Runtime; + use tezos_smart_rollup_mock::MockHost; + + use crate::message::SequencerMsg; + use crate::message::Signed; + use crate::state::State; + use crate::storage::write_state; + use crate::{ + message::{Bytes, Framed, Sequence}, + sequencer_runtime::SequencerRuntime, + }; + + #[derive(BinWriter)] + struct Msg { + inner: u32, + } + + impl Msg { + fn new(inner: u32) -> Self { + Self { inner } + } + } + + fn prepare() -> (MockHost, SecretKeyEd25519) { + // create a mock host + let mut mock_host = MockHost::default(); + // generate a secret and public key + let seed = SeedEd25519::from_base58_check( + "edsk31vznjHSSpGExDMHYASz45VZqXN4DPxvsa4hAyY8dHM28cZzp6", + ) + .unwrap(); + let (pk, sk) = seed.keypair().unwrap(); + let pk = PublicKey::Ed25519(pk); + // set the mode of the kernel to Sequenced + write_state(&mut mock_host, State::Sequenced(pk)).unwrap(); + (mock_host, sk) + } + + /// Generate a signed sequence + fn generate_signed_sequence( + secret: SecretKeyEd25519, + destination: SmartRollupAddress, + nonce: u32, + delayed_messages_prefix: u32, + delayed_messages_suffix: u32, + messages: Vec, + ) -> Signed> { + let tmp_sig = Signature::from_base58_check("edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q").unwrap(); + let mut unsigned = Signed { + body: Framed { + destination, + payload: SequencerMsg::Sequence(Sequence { + nonce, + delayed_messages_prefix, + delayed_messages_suffix, + messages, + }), + }, + signature: tmp_sig, + }; + + let hash = unsigned.hash().unwrap(); + + let signature = secret.sign(hash).unwrap(); + unsigned.signature = signature; + unsigned + } + + /// check if the given message is equal to the given byte representation + fn assert_external_eq(message: M, payload: &[u8]) { + let mut bytes = Vec::default(); + message.bin_write(&mut bytes).unwrap(); + + match payload { + [0x01, remaining @ ..] => assert_eq!(bytes, remaining), + _ => panic!(), + } + } + + #[test] + fn test_add_message() { + let (mut mock_host, _) = prepare(); + + mock_host.add_external(Msg::new(0x01)); + + let mut runtime = SequencerRuntime::new(mock_host, crate::FilterBehavior::AllowAll, 1); + let msg = runtime.read_input().unwrap(); + + assert!(msg.is_none()) + } + + #[test] + fn test_add_sequence() { + let (mut mock_host, sk) = prepare(); + let RollupMetadata { + raw_rollup_address, .. + } = mock_host.reveal_metadata(); + + let address = SmartRollupHash::try_from(raw_rollup_address.to_vec()).unwrap(); + let b58 = address.to_base58_check(); + let destination = SmartRollupAddress::from_b58check(&b58).unwrap(); + + mock_host.add_external(Msg::new(0x01)); + mock_host.add_external(Msg::new(0x02)); + mock_host.add_external(Msg::new(0x03)); + mock_host.add_external(generate_signed_sequence(sk, destination, 0, 2, 0, vec![])); + + let mut runtime = SequencerRuntime::new(mock_host, crate::FilterBehavior::AllowAll, 1); + let msg1 = runtime.read_input().unwrap().unwrap(); + let msg2 = runtime.read_input().unwrap().unwrap(); + + assert_external_eq(Msg::new(0x01), msg1.as_ref()); + assert_external_eq(Msg::new(0x02), msg2.as_ref()); + } + + #[test] + fn test_sequence_between_messages() { + let (mut mock_host, sk) = prepare(); + let RollupMetadata { + raw_rollup_address, .. + } = mock_host.reveal_metadata(); + + let address = SmartRollupHash::try_from(raw_rollup_address.to_vec()).unwrap(); + let b58 = address.to_base58_check(); + let destination = SmartRollupAddress::from_b58check(&b58).unwrap(); + + mock_host.add_external(Msg::new(0x01)); + mock_host.add_external(generate_signed_sequence(sk, destination, 0, 1, 0, vec![])); + mock_host.add_external(Msg::new(0x02)); + + let mut runtime = SequencerRuntime::new(mock_host, crate::FilterBehavior::AllowAll, 1); + let msg1 = runtime.read_input().unwrap().unwrap(); + let msg2 = runtime.read_input().unwrap(); + + assert_external_eq(Msg::new(0x01), msg1.as_ref()); + assert!(msg2.is_none()); + } +} diff --git a/src/kernel_sequencer/src/message.rs b/src/kernel_sequencer/src/message.rs index 8f5f85825d9dace34e302f04add0086cd2ec7b59..02d2db65b639adb57ae58a8cc7090932f5acde40 100644 --- a/src/kernel_sequencer/src/message.rs +++ b/src/kernel_sequencer/src/message.rs @@ -8,13 +8,71 @@ use nom::{ combinator::{all_consuming, map}, sequence::preceded, }; -use tezos_crypto_rs::hash::Signature; +#[cfg(test)] +use tezos_crypto_rs::hash::SecretKeyEd25519; +use tezos_crypto_rs::PublicKeySignatureVerifier; +use tezos_crypto_rs::{blake2b, hash::Signature}; use tezos_data_encoding::{ enc::{self, BinResult, BinWriter}, nom::{NomReader, NomResult}, }; use tezos_smart_rollup_encoding::public_key::PublicKey; use tezos_smart_rollup_encoding::smart_rollup::SmartRollupAddress; +use tezos_smart_rollup_host::runtime::RuntimeError; + +#[derive(Debug, Clone, PartialEq, Eq, BinWriter, NomReader)] +pub struct UnverifiedSigned +where + A: NomReader + BinWriter, +{ + body: A, + signature: Signature, +} + +impl UnverifiedSigned +where + A: NomReader + BinWriter, +{ + /// Returns the hash of the body. + fn hash_body(body: &A) -> Result, RuntimeError> { + // Get the bytes of the body. + let mut bytes = Vec::new(); + body.bin_write(&mut bytes) + .map_err(|_| RuntimeError::DecodingError)?; + + // Returns the hash of the body. + blake2b::digest_256(&bytes).map_err(|_| RuntimeError::DecodingError) + } + + /// Returns the hash of the signed message. + /// + /// It's equivalent of the body's hash. + pub fn hash(&self) -> Result, RuntimeError> { + UnverifiedSigned::hash_body(&self.body) + } + + /// Returns the body of the message and verifies the signature. + pub fn body(self, public_key: &PublicKey) -> Result { + let hash = &self.hash()?; + let is_correct = public_key.verify_signature(&self.signature, hash); + match is_correct { + Ok(true) => Ok(self.body), + _ => Err(RuntimeError::DecodingError), + } + } + + /// Return a signed body + #[cfg(test)] + fn sign_ed25519(body: A, secret: &SecretKeyEd25519) -> Result { + use tezos_smart_rollup_host::Error; + + let hash = UnverifiedSigned::hash_body(&body)?; + let signature = secret + .sign(hash) + .map_err(|_| RuntimeError::HostErr(Error::GenericInvalidAccess))?; + Ok(UnverifiedSigned { body, signature }) + } +} /// Framing protocol v0 /// @@ -32,7 +90,7 @@ pub struct Framed

{ #[derive(NomReader, BinWriter, Clone, Debug, PartialEq, Eq)] pub struct Bytes { #[encoding(dynamic, list)] - inner: Vec, + pub inner: Vec, } /// Sequence of messages sent by the sequencer @@ -53,12 +111,11 @@ pub struct Bytes { /// will be processed at the end #[derive(NomReader, BinWriter, Clone, Debug, PartialEq, Eq)] pub struct Sequence { - nonce: u32, - delayed_messages_prefix: u32, - delayed_messages_suffix: u32, + pub nonce: u32, + pub delayed_messages_prefix: u32, + pub delayed_messages_suffix: u32, #[encoding(dynamic, list)] - messages: Vec, - signature: Signature, + pub messages: Vec, } /// Message to set the appropriate sequencer @@ -70,7 +127,6 @@ pub struct SetSequencer { nonce: u32, admin_public_key: PublicKey, sequencer_public_key: PublicKey, - signature: Signature, } #[derive(NomReader, BinWriter, Debug, Clone, Eq, PartialEq)] @@ -81,7 +137,7 @@ pub enum SequencerMsg { #[derive(Debug, Clone, PartialEq, Eq)] pub enum KernelMessage { - Sequencer(Framed), + Sequencer(UnverifiedSigned>), DelayedMessage(Vec), } @@ -128,7 +184,7 @@ impl NomReader for KernelMessage { fn nom_read(input: &[u8]) -> NomResult { all_consuming(alt(( all_consuming(map( - preceded(tag([1]), Framed::::nom_read), + preceded(tag([1]), UnverifiedSigned::>::nom_read), KernelMessage::Sequencer, )), map( @@ -155,11 +211,12 @@ impl BinWriter for KernelMessage { #[cfg(test)] mod tests { - use crate::message::{Framed, SequencerMsg}; + use crate::message::{Bytes, Framed, SequencerMsg, UnverifiedSigned}; use super::{KernelMessage, Sequence}; use crate::message::SetSequencer; - use tezos_crypto_rs::hash::{SecretKeyEd25519, SeedEd25519}; + use tezos_crypto_rs::hash::{PublicKeyEd25519, SecretKeyEd25519, SeedEd25519, Signature}; + use tezos_crypto_rs::PublicKeySignatureVerifier; use tezos_data_encoding::enc::{self, BinWriter}; use tezos_data_encoding::nom::NomReader; use tezos_smart_rollup_encoding::public_key::PublicKey; @@ -181,7 +238,7 @@ mod tests { let (_, secret) = key_pair("edsk3a5SDDdMWw3Q5hPiJwDXUosmZMTuKQkriPqY6UqtSfdLifpZbB"); let signature = secret.sign([0x0]).expect("sign should work"); - let sequence = KernelMessage::Sequencer(Framed { + let body = Framed { destination: SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb") .expect("decoding should work"), payload: SequencerMsg::Sequence(Sequence { @@ -189,9 +246,10 @@ mod tests { delayed_messages_prefix: 0, delayed_messages_suffix: 0, messages: Vec::default(), - signature, }), - }); + }; + + let sequence = KernelMessage::Sequencer(UnverifiedSigned { body, signature }); // Serializing let mut bin: Vec = Vec::new(); @@ -209,16 +267,17 @@ mod tests { key_pair("edsk3a5SDDdMWw3Q5hPiJwDXUosmZMTuKQkriPqY6UqtSfdLifpZbB"); let signature = secret.sign([0x0]).expect("sign should work"); - let sequence = KernelMessage::Sequencer(Framed { + let body = Framed { destination: SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb") .expect("decoding should work"), payload: SequencerMsg::SetSequencer(SetSequencer { nonce: 0, admin_public_key: public_key.clone(), sequencer_public_key: public_key, - signature, }), - }); + }; + + let sequence = KernelMessage::Sequencer(UnverifiedSigned { body, signature }); // Serializing let mut bin: Vec = Vec::new(); @@ -249,17 +308,18 @@ mod tests { let (_, secret) = key_pair("edsk3a5SDDdMWw3Q5hPiJwDXUosmZMTuKQkriPqY6UqtSfdLifpZbB"); let signature = secret.sign([0x0]).expect("sign should work"); - let sequence = KernelMessage::Sequencer(Framed { + let body = Framed { destination: SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb") .expect("decoding should work"), payload: SequencerMsg::Sequence(Sequence { nonce: 0, - signature, delayed_messages_prefix: 5, delayed_messages_suffix: 5, messages: Vec::default(), }), - }); + }; + + let sequence = KernelMessage::Sequencer(UnverifiedSigned { body, signature }); // Serializing let mut bin: Vec = Vec::new(); @@ -273,4 +333,255 @@ mod tests { assert!(remaining.is_empty()); assert_eq!(msg_read, KernelMessage::DelayedMessage(bin)) } + + /// Deserialize a string to a KernelMessage + fn from_string(hex: &str) -> KernelMessage { + let hex = hex::decode(hex).expect("valid hexadecimal"); + let (_, msg) = KernelMessage::nom_read(&hex).expect("deserialization should work"); + msg + } + + #[test] + fn test_deserialization_empty_sequence() { + let kernel_message = from_string("01006227a8721213bd7ddb9b56227e3acb01161b1e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + let rollup_address = + SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb").unwrap(); + let sign = Signature::from_base58_check("edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q").unwrap(); + + match kernel_message { + KernelMessage::Sequencer(UnverifiedSigned { + body: + Framed { + destination, + payload: + SequencerMsg::Sequence(Sequence { + nonce, + delayed_messages_prefix, + delayed_messages_suffix, + messages, + }), + }, + signature, + }) => { + assert_eq!(destination, rollup_address); + assert_eq!(nonce, 0); + assert_eq!(delayed_messages_prefix, 0); + assert_eq!(delayed_messages_suffix, 0); + assert_eq!(messages, vec![]); + assert_eq!(signature, sign) + } + _ => panic!("Wrong message encoding"), + } + } + + #[test] + fn test_deserialization_one_empty_message_sequence() { + let kernel_message = from_string("01006227a8721213bd7ddb9b56227e3acb01161b1e6700000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + let destination = + SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb").unwrap(); + let signature = Signature::from_base58_check("edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q").unwrap(); + + let expected = KernelMessage::Sequencer(UnverifiedSigned { + body: Framed { + destination, + payload: SequencerMsg::Sequence(Sequence { + nonce: 0, + delayed_messages_prefix: 0, + delayed_messages_suffix: 0, + messages: vec![Bytes { + inner: b"".to_vec(), + }], + }), + }, + signature, + }); + + assert_eq!(kernel_message, expected); + } + + #[test] + fn test_deserialization_one_message_sequence() { + let kernel_message = from_string("01006227a8721213bd7ddb9b56227e3acb01161b1e6700000000000000000000000000000000090000000568656c6c6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + let destination = + SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb").unwrap(); + let signature = Signature::from_base58_check("edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q").unwrap(); + + let expected = KernelMessage::Sequencer(UnverifiedSigned { + body: Framed { + destination, + payload: SequencerMsg::Sequence(Sequence { + nonce: 0, + delayed_messages_prefix: 0, + delayed_messages_suffix: 0, + messages: vec![Bytes { + inner: b"hello".to_vec(), + }], + }), + }, + signature, + }); + + assert_eq!(kernel_message, expected); + } + + #[test] + fn test_deserialization_two_messages_sequence() { + let kernel_message = from_string("01006227a8721213bd7ddb9b56227e3acb01161b1e6700000000000000000000000000000000120000000568656c6c6f00000005776f726c6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + let destination = + SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb").unwrap(); + let signature = Signature::from_base58_check("edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q").unwrap(); + + let expected = KernelMessage::Sequencer(UnverifiedSigned { + body: Framed { + destination, + payload: SequencerMsg::Sequence(Sequence { + nonce: 0, + delayed_messages_prefix: 0, + delayed_messages_suffix: 0, + messages: vec![ + Bytes { + inner: b"hello".to_vec(), + }, + Bytes { + inner: b"world".to_vec(), + }, + ], + }), + }, + signature, + }); + + assert_eq!(kernel_message, expected); + } + + #[test] + fn test_incorrect_sequence_signature() { + let destination = + SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb").unwrap(); + let nonce = 0; + let delayed_messages_prefix = 0; + let delayed_messages_suffix = 0; + let messages = vec![]; + let signature = Signature::from_base58_check("edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q").unwrap(); + + let message = UnverifiedSigned { + body: Framed { + destination, + payload: SequencerMsg::Sequence(Sequence { + nonce, + delayed_messages_prefix, + delayed_messages_suffix, + messages, + }), + }, + signature: signature.clone(), + }; + + let hash = message.hash().expect("hash failure"); + + let public_key = PublicKey::Ed25519( + PublicKeyEd25519::from_base58_check( + "edpkuDMUm7Y53wp4gxeLBXuiAhXZrLn8XB1R83ksvvesH8Lp8bmCfK", + ) + .unwrap(), + ); + + let is_correct = public_key.verify_signature(&signature, &hash); + + assert!(matches!(is_correct, Ok(false) | Err(_))); + } + + #[test] + fn test_correct_sequence_signature() { + let destination = + SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb").unwrap(); + + let seed = SeedEd25519::from_base58_check( + "edsk3a5SDDdMWw3Q5hPiJwDXUosmZMTuKQkriPqY6UqtSfdLifpZbB", + ) + .unwrap(); + + let (public_key, secret_key) = seed.keypair().unwrap(); + + let body = Framed { + destination, + payload: SequencerMsg::Sequence(Sequence { + nonce: 0, + delayed_messages_prefix: 0, + delayed_messages_suffix: 0, + messages: vec![], + }), + }; + + let sequence = + UnverifiedSigned::sign_ed25519(body, &secret_key).expect("error when signing body"); + + let hash = sequence.hash().unwrap(); + let is_correct = public_key.verify_signature(&sequence.signature, &hash); + assert!(matches!(is_correct, Ok(true))); + } + + #[test] + fn test_correct_non_empty_sequence_signature() { + let destination = + SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb").unwrap(); + + let seed = SeedEd25519::from_base58_check( + "edsk3a5SDDdMWw3Q5hPiJwDXUosmZMTuKQkriPqY6UqtSfdLifpZbB", + ) + .unwrap(); + + let (public_key, secret_key) = seed.keypair().unwrap(); + + let body = Framed { + destination, + payload: SequencerMsg::Sequence(Sequence { + nonce: 0, + delayed_messages_prefix: 0, + delayed_messages_suffix: 0, + messages: vec![Bytes { + inner: b"hello".to_vec(), + }], + }), + }; + + let sequence = + UnverifiedSigned::sign_ed25519(body, &secret_key).expect("error when signing body"); + + let hash = sequence.hash().unwrap(); + let is_correct = public_key.verify_signature(&sequence.signature, &hash); + assert!(matches!(is_correct, Ok(true))); + } + + #[test] + fn test_correct_signature_encoding() { + let signed_msg = hex::decode("006227a8721213bd7ddb9b56227e3acb01161b1e67000000000000000000000000000000000f0000000b68656c6c6f20776f726c64bc682e5a009f3ee1dc1280d6b538aa53c626c29c5d5c528c8dd92092915aa24d414cc4f7ac81be8a4eb6a6046f9beea09c3c5a54374d68e6bfbad6cb51edc306").unwrap(); + let (_, signed_msg): (&[u8], UnverifiedSigned>) = + UnverifiedSigned::nom_read(&signed_msg).unwrap(); + + let destination = + SmartRollupAddress::from_b58check("sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb").unwrap(); + let seed = SeedEd25519::from_base58_check( + "edsk3a5SDDdMWw3Q5hPiJwDXUosmZMTuKQkriPqY6UqtSfdLifpZbB", + ) + .unwrap(); + let (public_key, _) = seed.keypair().unwrap(); + + let body = Framed { + destination, + payload: SequencerMsg::Sequence(Sequence { + nonce: 0, + delayed_messages_prefix: 0, + delayed_messages_suffix: 0, + messages: vec![Bytes { + inner: b"hello world".to_vec(), + }], + }), + }; + let hash = UnverifiedSigned::hash_body(&body).unwrap(); + + let is_correct = public_key.verify_signature(&signed_msg.signature, &hash); + + assert!(matches!(is_correct, Ok(true))); + } } diff --git a/src/kernel_sequencer/src/queue.rs b/src/kernel_sequencer/src/queue.rs index 2a695975170ec0454393f15541be00f24fcb96a8..e185a05589ac71cdf266a9b466a7a94be66bbf9b 100644 --- a/src/kernel_sequencer/src/queue.rs +++ b/src/kernel_sequencer/src/queue.rs @@ -224,7 +224,6 @@ impl Queue { /// Remove and returns the first element of the queue. /// /// If the queue is empty None is returned - #[allow(dead_code)] pub fn pop(&mut self, host: &mut H) -> Result, RuntimeError> where E: NomReader, diff --git a/src/kernel_sequencer/src/sequencer_runtime.rs b/src/kernel_sequencer/src/sequencer_runtime.rs index 13811f397b845776ac05a673b411d976c3fb6cd5..d521f698838d9fd403b359d35e77014d854f1e83 100644 --- a/src/kernel_sequencer/src/sequencer_runtime.rs +++ b/src/kernel_sequencer/src/sequencer_runtime.rs @@ -12,8 +12,11 @@ use tezos_smart_rollup_host::{ }; use crate::{ - delayed_inbox::read_input, queue::Queue, routing::FilterBehavior, storage::map_user_path, - storage::DELAYED_INBOX_PATH, + delayed_inbox::read_input, + queue::Queue, + routing::FilterBehavior, + storage::map_user_path, + storage::{DELAYED_INBOX_PATH, PENDING_INBOX_PATH}, }; pub struct SequencerRuntime @@ -27,6 +30,16 @@ where timeout_window: u32, /// The delayed inbox queue delayed_inbox_queue: Queue, + /// The pending inbox + /// A buffer that contains messages removed from the delayed inbox + /// Or messages sent by the user + /// This queue is empty at the end of the delayed inbox + pending_inbox_queue: Queue, + /// Index of the pending inbox + /// When a message is added to the pending-inbox + /// Its index is the following field + /// And then the index is incremented + pending_inbox_index: u32, } /// Runtime that handles the delayed inbox and the sequencer protocol. @@ -39,11 +52,14 @@ where { pub fn new(host: R, input_predicate: FilterBehavior, timeout_window: u32) -> Self { let delayed_inbox_queue = Queue::new(&host, &DELAYED_INBOX_PATH).unwrap(); + let pending_inbox_queue = Queue::new(&host, &PENDING_INBOX_PATH).unwrap(); Self { host, input_predicate, timeout_window, delayed_inbox_queue, + pending_inbox_queue, + pending_inbox_index: 0, } } } @@ -66,6 +82,8 @@ where self.input_predicate, self.timeout_window, &mut self.delayed_inbox_queue, + &mut self.pending_inbox_queue, + &mut self.pending_inbox_index, ) } diff --git a/src/kernel_sequencer/src/storage.rs b/src/kernel_sequencer/src/storage.rs index d6da57584a1a988ad6c301d2f0da6a70c43a38c2..651fa9f54ac3d67130e859b2075a4dc1b51a72cc 100644 --- a/src/kernel_sequencer/src/storage.rs +++ b/src/kernel_sequencer/src/storage.rs @@ -15,6 +15,7 @@ const SEQUENCER_PREFIX_PATH: RefPath = RefPath::assert_from(b"/__sequencer"); const USER_PREFIX_PATH: RefPath = RefPath::assert_from(b"/u"); pub const DELAYED_INBOX_PATH: RefPath = RefPath::assert_from(b"/delayed-inbox"); const STATE: RefPath = RefPath::assert_from(b"/state"); +pub const PENDING_INBOX_PATH: RefPath = RefPath::assert_from(b"/pending-inbox"); /// Prefix the given path by `/__sequencer`. /// diff --git a/tezt/tests/sc_sequencer.ml b/tezt/tests/sc_sequencer.ml index e0aaab0f2b4ec6355a780fb5638ac2b3ed61a922..218911aee93528f820348026614af73e8d0df03a 100644 --- a/tezt/tests/sc_sequencer.ml +++ b/tezt/tests/sc_sequencer.ml @@ -201,11 +201,13 @@ let test_delayed_inbox_consumed = let expected_sequences = List.map (fun (delayed_inbox_prefix, delayed_inbox_suffix) -> Format.sprintf - "Received a sequence message Sequence { nonce: 0, \ - delayed_messages_prefix: %d, delayed_messages_suffix: %d, messages: \ - [], signature: \ + "Received a sequence message UnverifiedSigned { body: Framed { \ + destination: SmartRollupAddress { hash: SmartRollupHash(\"%s\") }, \ + payload: Sequence(Sequence { nonce: 0, delayed_messages_prefix: %d, \ + delayed_messages_suffix: %d, messages: [] }) }, signature: \ Signature(\"edsigtXomBKi5CTRf5cjATJWSyaRvhfYNHqSUGrn4SdbYRcGwQrUGjzEfQDTuqHhuA8b2d8NarZjz8TRf65WkpQmo423BtomS8Q\") \ - } targeting our rollup" + }" + (Sc_rollup_repr.Address.to_b58check sc_rollup_address) delayed_inbox_prefix delayed_inbox_suffix) @@ [(4, 1); (7, 1)]