diff --git a/.gitlab/ci/jobs/packaging/opam_package.yml b/.gitlab/ci/jobs/packaging/opam_package.yml index 74e8e2610045f6085b896d7264e16110951f459f..eb5078b6b4b5199cb85674b40774670bacbf3d4c 100644 --- a/.gitlab/ci/jobs/packaging/opam_package.yml +++ b/.gitlab/ci/jobs/packaging/opam_package.yml @@ -706,6 +706,8 @@ opam:octez-smart-rollup-wasm-debugger: # Ignoring unreleased package octez-wasmer-test. +# Ignoring unreleased package scoru-sequencer-test. + # Ignoring unreleased package tezos-016-PtMumbai-test-helpers. opam:tezos-017-PtNairob-test-helpers: diff --git a/dune-project b/dune-project index febc03e558c1f6125cedb45630569036ce8868b4..3764355cc79efa7e1aed2ae9166ca2ab07783cde 100644 --- a/dune-project +++ b/dune-project @@ -59,6 +59,7 @@ (package (name octez-srs-extraction)) (package (name octez-testnet-scenarios)) (package (name octez-wasmer-test)(allow_empty)) +(package (name scoru-sequencer-test)(allow_empty)) (package (name tezos-016-PtMumbai-test-helpers)) (package (name tezos-017-PtNairob-test-helpers)) (package (name tezos-018-Proxford-test-helpers)) diff --git a/manifest/main.ml b/manifest/main.ml index 320e0486bbd374f1b387c81c4c925a9b1ee24b02..2796b443c312769a7e56ba082a3bfa09f5d03adb 100644 --- a/manifest/main.ml +++ b/manifest/main.ml @@ -7469,6 +7469,24 @@ let octez_scoru_sequencer = octez_rpc_http_server; ] +let _octez_scoru_sequencer_tests = + tezt + ["test_kernel_message"] + ~path:"src/lib_scoru_sequencer/test" + ~opam:"scoru-sequencer-test" + ~synopsis:"Tests for the scoru sequencer library" + ~with_macos_security_framework:true + ~deps: + [ + octez_base |> open_ ~m:"TzPervasives"; + Protocol.(main alpha) |> open_; + octez_scoru_sequencer; + octez_base_test_helpers |> open_; + octez_test_helpers |> open_; + qcheck_alcotest; + alcotezt; + ] + let _sc_sequencer_node = public_exe "octez-smart-rollup-sequencer-node" diff --git a/opam/scoru-sequencer-test.opam b/opam/scoru-sequencer-test.opam new file mode 100644 index 0000000000000000000000000000000000000000..312f46e304f852c4d57605254242056faf382973 --- /dev/null +++ b/opam/scoru-sequencer-test.opam @@ -0,0 +1,27 @@ +# This file was automatically generated, do not edit. +# Edit file manifest/main.ml instead. +opam-version: "2.0" +maintainer: "contact@tezos.com" +authors: ["Tezos devteam"] +homepage: "https://www.tezos.com/" +bug-reports: "https://gitlab.com/tezos/tezos/issues" +dev-repo: "git+https://gitlab.com/tezos/tezos.git" +license: "MIT" +depends: [ + "dune" { >= "3.0" } + "ocaml" { >= "4.14" } + "tezt" { with-test & >= "3.1.1" } + "tezos-base" {with-test} + "tezos-protocol-alpha" {with-test} + "octez-smart-rollup-sequencer" {with-test} + "tezos-base-test-helpers" {with-test} + "tezos-test-helpers" {with-test} + "qcheck-alcotest" { with-test & >= "0.20" } + "octez-alcotezt" {with-test} +] +build: [ + ["rm" "-r" "vendors" "contrib"] + ["dune" "build" "-p" name "-j" jobs] + ["dune" "runtest" "-p" name "-j" jobs] {with-test} +] +synopsis: "Tests for the scoru sequencer library" diff --git a/src/lib_scoru_sequencer/kernel_message.ml b/src/lib_scoru_sequencer/kernel_message.ml new file mode 100644 index 0000000000000000000000000000000000000000..d7ee9f7f000b48c688ff94d735702d5ed666ea69 --- /dev/null +++ b/src/lib_scoru_sequencer/kernel_message.ml @@ -0,0 +1,185 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022-2023 TriliTech *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +open Protocol +open Alpha_context + +module Framed (Payload : sig + type t + + val encoding : t Data_encoding.t + + val equal : t -> t -> bool +end) = +struct + type t = {rollup_address : Sc_rollup_repr.Address.t; payload : Payload.t} + + let encoding : t Data_encoding.t = + let open Data_encoding in + union + [ + case + (Tag 0) + ~title:"Framed message of version 0" + (obj3 + (req "version" @@ constant "0") + (req "destination" Sc_rollup_repr.Address.encoding) + (req "payload" Payload.encoding)) + (fun {rollup_address; payload} -> Some ((), rollup_address, payload)) + (fun ((), rollup_address, payload) -> {rollup_address; payload}); + ] + + let equal x y = + Sc_rollup_repr.Address.equal x.rollup_address y.rollup_address + && Payload.equal x.payload y.payload +end + +module Signed (Payload : sig + type t + + val encoding : t Data_encoding.t + + val equal : t -> t -> bool +end) = +struct + type t = {unsigned_payload : Payload.t; signature : Signature.V0.t} + + let encoding : t Data_encoding.t = + let open Data_encoding in + conv + (fun {unsigned_payload; signature} -> (unsigned_payload, signature)) + (fun (unsigned_payload, signature) -> {unsigned_payload; signature}) + @@ obj2 + (req "unsigned_payload" Payload.encoding) + (req "signature" Signature.V0.encoding) + + let equal (x : t) (y : t) = + Signature.V0.equal x.signature y.signature + && Payload.equal x.unsigned_payload y.unsigned_payload +end + +type msg_body = + | Sequence of { + nonce : int32; + (* Fix: it should be uint32 *) + delayed_messages_prefix : int32; + (* Fix: it should be uint32 *) + delayed_messages_suffix : int32; + (* Fix: it should be uint32 *) + l2_messages : string list; + } + +module Framed_message = Framed (struct + type t = msg_body + + let encoding : t Data_encoding.t = + let open Data_encoding in + union + [ + case + (Tag 0) + ~title:"Sequence message" + (obj5 + (req "msg_type" @@ constant "sequence") + (req "nonce" int32) + (req "delayed_messages_prefix" int32) + (req "delayed_messages_suffix" int32) + (req "l2_messages" @@ list string)) + (function + | Sequence + { + nonce; + delayed_messages_prefix; + delayed_messages_suffix; + l2_messages; + } -> + Some + ( (), + nonce, + delayed_messages_prefix, + delayed_messages_suffix, + l2_messages )) + (fun ( (), + nonce, + delayed_messages_prefix, + delayed_messages_suffix, + l2_messages ) -> + Sequence + { + nonce; + delayed_messages_prefix; + delayed_messages_suffix; + l2_messages; + }); + ] + + let equal x y = + match (x, y) with + | Sequence x, Sequence y -> + x.nonce = y.nonce + && x.delayed_messages_prefix = y.delayed_messages_prefix + && x.delayed_messages_suffix = y.delayed_messages_suffix + && List.equal String.equal x.l2_messages y.l2_messages +end) + +include Signed (Framed_message) + +(* Signature consisting of zeroes *) +let dummy_signature = + Signature.V0.of_bytes_exn @@ Bytes.make Signature.V0.size @@ Char.chr 0 + +let encode_sequence_message rollup_address ~prefix ~suffix + (l2_messages : Sc_rollup.Inbox_message.serialized list) : string = + (* Fix: actually sign a message *) + let unsigned_payload = + Framed_message. + { + rollup_address; + payload = + Sequence + { + nonce = 0l; + delayed_messages_prefix = prefix; + delayed_messages_suffix = suffix; + l2_messages = + List.map Sc_rollup.Inbox_message.unsafe_to_string l2_messages; + }; + } + in + let signed_framed_sequence = + {unsigned_payload; signature = dummy_signature} + in + Data_encoding.Binary.to_string_exn encoding signed_framed_sequence + +let single_l2_message_overhead = + let dummy_address = + Sc_rollup.Address.of_b58check_exn "sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb" + in + String.length + @@ encode_sequence_message + dummy_address + ~prefix:1000l + ~suffix:1000l + [Sc_rollup.Inbox_message.unsafe_of_string ""] diff --git a/src/lib_scoru_sequencer/seq_batcher.ml b/src/lib_scoru_sequencer/seq_batcher.ml index cdb44abfaa6cda04ed65aef99fdc1f1987773f9a..471804b7940e6407f95bf8b466b8796a09cd4108 100644 --- a/src/lib_scoru_sequencer/seq_batcher.ml +++ b/src/lib_scoru_sequencer/seq_batcher.ml @@ -43,22 +43,6 @@ end module Batched_messages = Hash_queue.Make (L2_message.Hash) (L2_batched_message) -module Sequencer = struct - type msg = Sequence of string - - let msg_content (Sequence cnt) = cnt - - let sequence_message_overhead_size messages_num = - 64 (* 64 bytes for signature *) + 4 - (* 4 bytes for delayed inbox size *) - + (4 * messages_num) - (* each message prepended with its size *) - - let make_sequence_message (_delayed_messages : int) - (_l2_messages : L2_message.t list) : msg = - Sequence "" -end - type state = { node_ctxt : Node_context.ro; signer : Signature.public_key_hash; @@ -70,10 +54,7 @@ type state = { (* Takes sequencer message to inject and L2 messages included into it *) let inject_sequence state (sequencer_message, l2_messages) = let open Lwt_result_syntax in - let operation = - L1_operation.Add_messages - {messages = [Sequencer.msg_content sequencer_message]} - in + let operation = L1_operation.Add_messages {messages = [sequencer_message]} in let+ l1_hash = Injector.add_pending_operation ~source:state.signer operation in @@ -121,18 +102,30 @@ let get_previous_delayed_inbox_size node_ctxt (head : Layer1.head) = let get_batch_sequences state head = let open Lwt_result_syntax in - let+ delayed_inbox_size = + let* delayed_inbox_size = get_previous_delayed_inbox_size state.node_ctxt head in (* Assuming at the moment that all the registered messages fit into a single L2 message. This logic will be extended later. *) let l2_messages = Message_queue.elements state.messages in - ( [ - ( Sequencer.make_sequence_message delayed_inbox_size l2_messages, - l2_messages ); - ], - delayed_inbox_size ) + let*? l2_messages_serialized = + List.map_e + (fun m -> + Sc_rollup.Inbox_message.(serialize (External (L2_message.content m)))) + l2_messages + |> Environment.wrap_tzresult + in + return + ( [ + ( Kernel_message.encode_sequence_message + state.node_ctxt.rollup_address + ~prefix:(Int32.of_int (delayed_inbox_size - 1)) + ~suffix:1l + l2_messages_serialized, + l2_messages ); + ], + delayed_inbox_size ) let produce_batch_sequences state head = let open Lwt_result_syntax in @@ -170,7 +163,7 @@ let simulate node_ctxt simulation_ctxt (messages : L2_message.t list) = *) let max_single_l2_msg_size = Protocol.Constants_repr.sc_rollup_message_size_limit - - Sequencer.sequence_message_overhead_size 1 + - Kernel_message.single_l2_message_overhead - 4 (* each L2 message prepended with it size *) let get_simulation_context state = diff --git a/src/lib_scoru_sequencer/test/dune b/src/lib_scoru_sequencer/test/dune new file mode 100644 index 0000000000000000000000000000000000000000..6727855a379ea19850cbd5c8bf4570c9eff1e8a3 --- /dev/null +++ b/src/lib_scoru_sequencer/test/dune @@ -0,0 +1,47 @@ +; This file was automatically generated, do not edit. +; Edit file manifest/main.ml instead. + +(library + (name src_lib_scoru_sequencer_test_tezt_lib) + (instrumentation (backend bisect_ppx)) + (libraries + tezt.core + tezos-base + tezos-protocol-alpha + octez_smart_rollup_sequencer + tezos-base-test-helpers + tezos-test-helpers + qcheck-alcotest + octez-alcotezt) + (library_flags (:standard -linkall)) + (flags + (:standard) + -open Tezt_core + -open Tezt_core.Base + -open Tezos_base.TzPervasives + -open Tezos_protocol_alpha + -open Tezos_base_test_helpers + -open Tezos_test_helpers + -open Octez_alcotezt) + (modules test_kernel_message)) + +(executable + (name main) + (instrumentation (backend bisect_ppx --bisect-sigterm)) + (libraries + src_lib_scoru_sequencer_test_tezt_lib + tezt) + (link_flags + (:standard) + (:include %{workspace_root}/macos-link-flags.sexp)) + (modules main)) + +(rule + (alias runtest) + (package scoru-sequencer-test) + (enabled_if (<> false %{env:RUNTEZTALIAS=true})) + (action (run %{dep:./main.exe}))) + +(rule + (targets main.ml) + (action (with-stdout-to %{targets} (echo "let () = Tezt.Test.run ()")))) diff --git a/src/lib_scoru_sequencer/test/test_kernel_message.ml b/src/lib_scoru_sequencer/test/test_kernel_message.ml new file mode 100644 index 0000000000000000000000000000000000000000..1d733a8818fa15d8e2271e2821eae3bceaeeb929 --- /dev/null +++ b/src/lib_scoru_sequencer/test/test_kernel_message.ml @@ -0,0 +1,114 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022-2023 TriliTech *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +(** Testing + ------- + Component: lib_scoru_sequencer kernel encodings + Invocation: dune exec src/lib_scoru_sequencer/test/main.exe -- --file test_kernel_message.ml + Subject: Tests kernel message encodings for the octez-smart-rollup-sequencer library +*) + +open Protocol +open Alpha_context +open Tztest +module KM = Octez_smart_rollup_sequencer.Kernel_message + +let test_signed ?loc expected_hex encoded_msg = + let open Lwt_result_syntax in + let result_hex = Hex.show @@ Hex.of_string encoded_msg in + Assert.equal ?loc ~msg:"Encoded hex don't match" expected_hex result_hex ; + return_unit + +let test_empty_suffix_n_prefix () = + let open Lwt_result_syntax in + let rollup_address = + Sc_rollup_repr.Address.of_b58check_exn + "sr1EzLeJYWrvch2Mhvrk1nUVYrnjGQ8A4qdb" + in + + let empty_l2_messages_hex = + "006227a8721213bd7ddb9b56227e3acb01161b1e67000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + in + let* () = + test_signed + ~loc:__LOC__ + empty_l2_messages_hex + (KM.encode_sequence_message rollup_address ~prefix:0l ~suffix:0l []) + in + + let single_empty_l2_message_hex = + "006227a8721213bd7ddb9b56227e3acb01161b1e6700000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + in + let* () = + test_signed + ~loc:__LOC__ + single_empty_l2_message_hex + (KM.encode_sequence_message + rollup_address + ~prefix:0l + ~suffix:0l + [Sc_rollup.Inbox_message.unsafe_of_string ""]) + in + + let hello_l2_message_hex = + "006227a8721213bd7ddb9b56227e3acb01161b1e6700000000000000000000000000000000090000000568656c6c6f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + in + let* () = + test_signed + ~loc:__LOC__ + hello_l2_message_hex + (KM.encode_sequence_message + rollup_address + ~prefix:0l + ~suffix:0l + [Sc_rollup.Inbox_message.unsafe_of_string "hello"]) + in + + let hello_world_l2_message_hex = + "006227a8721213bd7ddb9b56227e3acb01161b1e6700000000000000000000000000000000120000000568656c6c6f00000005776f726c6400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + in + test_signed + ~loc:__LOC__ + hello_world_l2_message_hex + (KM.encode_sequence_message + rollup_address + ~prefix:0l + ~suffix:0l + (List.map Sc_rollup.Inbox_message.unsafe_of_string ["hello"; "world"])) + +let tests = + [ + tztest + "Sequence with empty prefix and suffix" + `Quick + test_empty_suffix_n_prefix; + ] + +let () = + Alcotest_lwt.run + ~__FILE__ + "test lib scoru sequencer message encodings" + [("Encodings", tests)] + |> Lwt_main.run diff --git a/tezt/tests/dune b/tezt/tests/dune index e11a62fff58c05fcd6408b34a2eeb057b09d9db8..99aabde4a7217c5fb85f76edeadea9d189ea24e8 100644 --- a/tezt/tests/dune +++ b/tezt/tests/dune @@ -93,6 +93,7 @@ src_lib_shell_test_tezt_lib src_lib_scoru_wasm_test_tezt_lib src_lib_scoru_wasm_fast_test_tezt_lib + src_lib_scoru_sequencer_test_tezt_lib src_lib_sapling_test_tezt_lib src_lib_rpc_http_test_tezt_lib src_lib_requester_test_tezt_lib