diff --git a/src/lib_injector/injector_operation.ml b/src/lib_injector/injector_operation.ml index 99525259b40d91f08fc55b8040a430128c2e1e92..61a28c2ccc8336f3c9f87d75bcfeefd0d25ec7f9 100644 --- a/src/lib_injector/injector_operation.ml +++ b/src/lib_injector/injector_operation.ml @@ -51,13 +51,26 @@ module Make (O : PARAM_OPERATION) : type t = {hash : hash; operation : O.t; mutable errors : errors} - let hash_inner_operation op = - Hash.hash_bytes [Data_encoding.Binary.to_bytes_exn O.encoding op] + let hash_inner_operation nonce op = + Hash.hash_string + [ + Option.fold ~none:"" ~some:Z.to_bits nonce; + Data_encoding.Binary.to_string_exn O.encoding op; + ] + + let counter = ref Z.zero let no_errors = {count = 0; last_error = None} let make operation = - let hash = hash_inner_operation operation in + let nonce = + if O.unique operation then ( + let c = !counter in + counter := Z.succ !counter ; + Some c) + else None + in + let hash = hash_inner_operation nonce operation in {hash; operation; errors = no_errors} let errors_encoding = diff --git a/src/lib_injector/injector_sigs.ml b/src/lib_injector/injector_sigs.ml index f36dd6d3ae29ea381c114fb3b80facef89f3f8da..a3caa29d14621f203a9c48f50ae501b67247fa0f 100644 --- a/src/lib_injector/injector_sigs.ml +++ b/src/lib_injector/injector_sigs.ml @@ -97,6 +97,11 @@ module type PARAM_OPERATION = sig (** Pretty-printing injector's operations *) val pp : Format.formatter -> t -> unit + + (** If [unique op = true], then the injector's queue will only contain at most + one operation [op]. Otherwise, it will allow duplicate such operations to + appear in the queue. *) + val unique : t -> bool end (** Internal representation of injector operations. *) diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_layer2/l1_operation.ml b/src/proto_016_PtMumbai/lib_sc_rollup_layer2/l1_operation.ml index 1b3e165b794ff98f676d154ef7162e9703531525..686d7927381163e753afcbafee3af4459633533b 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_layer2/l1_operation.ml +++ b/src/proto_016_PtMumbai/lib_sc_rollup_layer2/l1_operation.ml @@ -176,3 +176,7 @@ let of_manager_operation : type kind. kind manager_operation -> t option = Some (Refute {rollup; opponent; refutation}) | Sc_rollup_timeout {rollup; stakers} -> Some (Timeout {rollup; stakers}) | _ -> None + +let unique = function + | Add_messages _ -> false + | Cement _ | Publish _ | Refute _ | Timeout _ -> true diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_layer2/l1_operation.mli b/src/proto_016_PtMumbai/lib_sc_rollup_layer2/l1_operation.mli index d6bd71285e0f47e35d01ce3f0f11fcbbb32dbfcb..953cd6f7fb5ee45675058819e90596711672bc56 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_layer2/l1_operation.mli +++ b/src/proto_016_PtMumbai/lib_sc_rollup_layer2/l1_operation.mli @@ -48,3 +48,6 @@ val of_manager_operation : 'a manager_operation -> t option (** Pretty printer (human readable) for L1 operations. *) val pp : Format.formatter -> t -> unit + +(** [false] if the injector will accept duplicate such operations. *) +val unique : t -> bool diff --git a/src/proto_017_PtNairob/lib_sc_rollup_layer2/l1_operation.ml b/src/proto_017_PtNairob/lib_sc_rollup_layer2/l1_operation.ml index 1b3e165b794ff98f676d154ef7162e9703531525..686d7927381163e753afcbafee3af4459633533b 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_layer2/l1_operation.ml +++ b/src/proto_017_PtNairob/lib_sc_rollup_layer2/l1_operation.ml @@ -176,3 +176,7 @@ let of_manager_operation : type kind. kind manager_operation -> t option = Some (Refute {rollup; opponent; refutation}) | Sc_rollup_timeout {rollup; stakers} -> Some (Timeout {rollup; stakers}) | _ -> None + +let unique = function + | Add_messages _ -> false + | Cement _ | Publish _ | Refute _ | Timeout _ -> true diff --git a/src/proto_017_PtNairob/lib_sc_rollup_layer2/l1_operation.mli b/src/proto_017_PtNairob/lib_sc_rollup_layer2/l1_operation.mli index d6bd71285e0f47e35d01ce3f0f11fcbbb32dbfcb..953cd6f7fb5ee45675058819e90596711672bc56 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_layer2/l1_operation.mli +++ b/src/proto_017_PtNairob/lib_sc_rollup_layer2/l1_operation.mli @@ -48,3 +48,6 @@ val of_manager_operation : 'a manager_operation -> t option (** Pretty printer (human readable) for L1 operations. *) val pp : Format.formatter -> t -> unit + +(** [false] if the injector will accept duplicate such operations. *) +val unique : t -> bool diff --git a/src/proto_alpha/lib_sc_rollup_layer2/l1_operation.ml b/src/proto_alpha/lib_sc_rollup_layer2/l1_operation.ml index 1b3e165b794ff98f676d154ef7162e9703531525..686d7927381163e753afcbafee3af4459633533b 100644 --- a/src/proto_alpha/lib_sc_rollup_layer2/l1_operation.ml +++ b/src/proto_alpha/lib_sc_rollup_layer2/l1_operation.ml @@ -176,3 +176,7 @@ let of_manager_operation : type kind. kind manager_operation -> t option = Some (Refute {rollup; opponent; refutation}) | Sc_rollup_timeout {rollup; stakers} -> Some (Timeout {rollup; stakers}) | _ -> None + +let unique = function + | Add_messages _ -> false + | Cement _ | Publish _ | Refute _ | Timeout _ -> true diff --git a/src/proto_alpha/lib_sc_rollup_layer2/l1_operation.mli b/src/proto_alpha/lib_sc_rollup_layer2/l1_operation.mli index d6bd71285e0f47e35d01ce3f0f11fcbbb32dbfcb..953cd6f7fb5ee45675058819e90596711672bc56 100644 --- a/src/proto_alpha/lib_sc_rollup_layer2/l1_operation.mli +++ b/src/proto_alpha/lib_sc_rollup_layer2/l1_operation.mli @@ -48,3 +48,6 @@ val of_manager_operation : 'a manager_operation -> t option (** Pretty printer (human readable) for L1 operations. *) val pp : Format.formatter -> t -> unit + +(** [false] if the injector will accept duplicate such operations. *) +val unique : t -> bool diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index 5a862f4a71497edd8e392a799da4cedee7ae4f66..87ddd7a03d80f2ff41cf7686c66b8361cbf88172 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -1068,6 +1068,10 @@ let bake_until_lpc_updated ?hook ?(at_least = 0) ?(timeout = 15.) client let* _ = Sc_rollup_node.wait_sync sc_rollup_node ~timeout:3. in return updated_level +let check_batcher_message_status response status = + Check.((JSON.(response |-> "status" |> as_string) = status) string) + ~error_msg:"Status of message is %L but expected %R." + (* Rollup node batcher *) let sc_rollup_node_batcher sc_rollup_node sc_rollup_client sc_rollup node client = @@ -1081,11 +1085,8 @@ let sc_rollup_node_batcher sc_rollup_node sc_rollup_client sc_rollup node client let*! retrieved_msg1, status_msg1 = Sc_rollup_client.get_batcher_msg sc_rollup_client msg1_hash in - let check_status response status = - Check.((JSON.(response |-> "status" |> as_string) = status) string) - ~error_msg:"Status of message is %L but expected %R." - in - check_status status_msg1 "pending_batch" ; + + check_batcher_message_status status_msg1 "pending_batch" ; Check.((retrieved_msg1 = msg1) string) ~error_msg:"Message in queue is %L but injected %R." ; let*! queue = Sc_rollup_client.batcher_queue sc_rollup_client in @@ -1100,13 +1101,13 @@ let sc_rollup_node_batcher sc_rollup_node sc_rollup_client sc_rollup node client let*! _msg1, status_msg1 = Sc_rollup_client.get_batcher_msg sc_rollup_client msg1_hash in - check_status status_msg1 "injected" ; + check_batcher_message_status status_msg1 "injected" ; (* We bake so that msg1 is included. *) let* () = Client.bake_for_and_wait client in let*! _msg1, status_msg1 = Sc_rollup_client.get_batcher_msg sc_rollup_client msg1_hash in - check_status status_msg1 "included" ; + check_batcher_message_status status_msg1 "included" ; let* _ = Sc_rollup_node.wait_for_level ~timeout:3. @@ -1184,7 +1185,7 @@ let sc_rollup_node_batcher sc_rollup_node sc_rollup_client sc_rollup node client let*! _msg1, status_msg1 = Sc_rollup_client.get_batcher_msg sc_rollup_client msg1_hash in - check_status status_msg1 "committed" ; + check_batcher_message_status status_msg1 "committed" ; unit (* One can retrieve the list of originated SCORUs. @@ -5237,6 +5238,48 @@ let test_bootstrap_smart_rollup_originated = ~error_msg:"Expected %R bootstrapped smart rollups, got %L") ; unit +let test_inject_identital_messages ~kind = + test_full_scenario + ~kind + { + tags = ["injector"; "inject"; "identical"]; + variant = None; + description = "The injector can inject identical messages"; + } + @@ fun _protocol sc_rollup_node sc_rollup_client sc_rollup _node client -> + let* () = + Sc_rollup_node.run ~event_level:`Debug sc_rollup_node sc_rollup [] + in + let check_status response status = + Check.((JSON.(response |-> "status" |> as_string) = status) string) + ~error_msg:"Status of message is %L but expected %R." + in + (* Produce artificially what two large [Sc_rollup_add_messages] would look like. + As the batches are the same, if we don't allow identical operations + in the injector, only the first hashes will be included, as the two + manager operations are the same. + *) + let s = String.make 4090 'a' in + let msgs = List.init 7 (fun _ -> s) in + let*! hashes1 = Sc_rollup_client.inject sc_rollup_client msgs in + let*! hashes2 = Sc_rollup_client.inject sc_rollup_client msgs in + let hash1 = Stdlib.List.hd hashes1 in + let hash2 = Stdlib.List.hd hashes2 in + let injected = + wait_for_injecting_event ~tags:["add_messages"] sc_rollup_node + in + let* () = Client.bake_for_and_wait client in + let* _ = injected in + let*! _msg1, status_msg1 = + Sc_rollup_client.get_batcher_msg sc_rollup_client hash1 + in + check_status status_msg1 "injected" ; + let*! _msg1, status_msg2 = + Sc_rollup_client.get_batcher_msg sc_rollup_client hash2 + in + check_status status_msg2 "injected" ; + unit + let register ~kind ~protocols = test_origination ~kind protocols ; test_rollup_node_running ~kind protocols ; @@ -5376,6 +5419,7 @@ let register ~protocols = test_valid_dispute_dissection ~kind:"arith" protocols ; test_refutation_reward_and_punishment protocols ~kind:"arith" ; test_timeout ~kind:"arith" protocols ; + test_inject_identital_messages ~kind:"arith" protocols ; test_no_cementation_if_parent_not_lcc_or_if_disputed_commit ~kind:"arith" protocols ;