diff --git a/src/proto_alpha/lib_client/mockup.ml b/src/proto_alpha/lib_client/mockup.ml index f441242e1a365a681fdce83f7ee99a8493287903..0ab6cdc269cd9e16416339fc2b1ad6e472774536 100644 --- a/src/proto_alpha/lib_client/mockup.ml +++ b/src/proto_alpha/lib_client/mockup.ml @@ -94,6 +94,8 @@ module Protocol_constants_overrides = struct sc_rollup_commitment_period_in_blocks : int option; sc_rollup_commitment_storage_size_in_bytes : int option; sc_rollup_max_lookahead_in_blocks : int32 option; + sc_rollup_max_active_outbox_levels : int32 option; + sc_rollup_max_outbox_messages_per_level : int option; (* Additional, "bastard" parameters (they are not protocol constants but partially treated the same way). *) chain_id : Chain_id.t option; timestamp : Time.Protocol.t option; @@ -165,7 +167,9 @@ module Protocol_constants_overrides = struct c.sc_rollup_stake_amount_in_mutez, c.sc_rollup_commitment_period_in_blocks, c.sc_rollup_commitment_storage_size_in_bytes, - c.sc_rollup_max_lookahead_in_blocks ) ) ) ) ) ) )) + c.sc_rollup_max_lookahead_in_blocks, + c.sc_rollup_max_active_outbox_levels, + c.sc_rollup_max_outbox_messages_per_level ) ) ) ) ) ) )) (fun ( ( preserved_cycles, blocks_per_cycle, blocks_per_commitment, @@ -226,7 +230,9 @@ module Protocol_constants_overrides = struct sc_rollup_stake_amount_in_mutez, sc_rollup_commitment_period_in_blocks, sc_rollup_commitment_storage_size_in_bytes, - sc_rollup_max_lookahead_in_blocks ) ) ) ) ) ) ) -> + sc_rollup_max_lookahead_in_blocks, + sc_rollup_max_active_outbox_levels, + sc_rollup_max_outbox_messages_per_level ) ) ) ) ) ) ) -> { preserved_cycles; blocks_per_cycle; @@ -286,6 +292,8 @@ module Protocol_constants_overrides = struct sc_rollup_commitment_period_in_blocks; sc_rollup_commitment_storage_size_in_bytes; sc_rollup_max_lookahead_in_blocks; + sc_rollup_max_active_outbox_levels; + sc_rollup_max_outbox_messages_per_level; chain_id; timestamp; initial_seed; @@ -361,7 +369,7 @@ module Protocol_constants_overrides = struct (opt "tx_rollup_max_ticket_payload_size" int31) (opt "tx_rollup_rejection_max_proof_size" int31) (opt "tx_rollup_sunset_level" int32))) - (obj8 + (obj10 (opt "sc_rollup_enable" bool) (opt "sc_rollup_origination_size" int31) (opt "sc_rollup_challenge_window_in_blocks" int31) @@ -371,7 +379,9 @@ module Protocol_constants_overrides = struct (opt "sc_rollup_commitment_storage_size_in_bytes" int31) - (opt "sc_rollup_max_lookahead_in_blocks" int32)))))))) + (opt "sc_rollup_max_lookahead_in_blocks" int32) + (opt "sc_rollup_max_active_outbox_levels" int32) + (opt "sc_rollup_max_outbox_messages_per_level" int31)))))))) let default_value (cctxt : Tezos_client_base.Client_context.full) : t tzresult Lwt.t = @@ -472,6 +482,10 @@ module Protocol_constants_overrides = struct Some parametric.sc_rollup_commitment_storage_size_in_bytes; sc_rollup_max_lookahead_in_blocks = Some parametric.sc_rollup_max_lookahead_in_blocks; + sc_rollup_max_active_outbox_levels = + Some parametric.sc_rollup_max_active_outbox_levels; + sc_rollup_max_outbox_messages_per_level = + Some parametric.sc_rollup_max_outbox_messages_per_level; (* Bastard additional parameters. *) chain_id = to_chain_id_opt cpctxt#chain; timestamp = Some header.timestamp; @@ -540,6 +554,8 @@ module Protocol_constants_overrides = struct sc_rollup_commitment_period_in_blocks = None; sc_rollup_commitment_storage_size_in_bytes = None; sc_rollup_max_lookahead_in_blocks = None; + sc_rollup_max_active_outbox_levels = None; + sc_rollup_max_outbox_messages_per_level = None; chain_id = None; timestamp = None; initial_seed = None; @@ -1044,6 +1060,14 @@ module Protocol_constants_overrides = struct Option.value ~default:c.sc_rollup_max_lookahead_in_blocks o.sc_rollup_max_lookahead_in_blocks; + sc_rollup_max_active_outbox_levels = + Option.value + ~default:c.sc_rollup_max_active_outbox_levels + o.sc_rollup_max_active_outbox_levels; + sc_rollup_max_outbox_messages_per_level = + Option.value + ~default:c.sc_rollup_max_outbox_messages_per_level + o.sc_rollup_max_outbox_messages_per_level; } : Constants.Parametric.t) end diff --git a/src/proto_alpha/lib_parameters/default_parameters.ml b/src/proto_alpha/lib_parameters/default_parameters.ml index b2aac2aec6527d6791f9d037f24589a8c6ada75c..9ae091ee04f76f3ba89e16b770775e04002d740c 100644 --- a/src/proto_alpha/lib_parameters/default_parameters.ml +++ b/src/proto_alpha/lib_parameters/default_parameters.ml @@ -29,6 +29,25 @@ open Protocol.Alpha_context let tx_rollup_finality_period = 40_000 +(** The challenge window is about a week with 30s block-time (604800s / 30s). + WARNING: changing this value also impacts + [sc_rollup_max_active_outbox_levels]. See below. *) +let sc_rollup_challenge_window_in_blocks = 20_160 + +(** Number of active levels kept for executing outbox messages. + + WARNING: Changing this value impacts the storage charge for + applying messages from the outbox. It also requires migration for + remapping existing active outbox levels to new indices. *) +let sc_rollup_max_active_outbox_levels = + Int32.of_int sc_rollup_challenge_window_in_blocks + +(** Maximum number of outbox messages per level. + + WARNING: changing this value impacts the storage size a rollup has to + pay for at origination time. *) +let sc_rollup_max_outbox_messages_per_level = 100 + let constants_mainnet = let consensus_committee_size = 7000 in let block_time = 30 in @@ -150,8 +169,7 @@ let constants_mainnet = sc_rollup_enable = false; (* The following value is chosen to prevent spam. *) sc_rollup_origination_size = 6_314; - (* The challenge window is about a week with 30s block-time (604800s / 30s). *) - sc_rollup_challenge_window_in_blocks = 20_160; + sc_rollup_challenge_window_in_blocks; (* The following value is chosen to limit the length of inbox refutation proofs. *) (* TODO: https://gitlab.com/tezos/tezos/-/issues/2556 The follow constants need to be refined. *) @@ -162,6 +180,8 @@ let constants_mainnet = sc_rollup_commitment_period_in_blocks = 30; sc_rollup_commitment_storage_size_in_bytes = 84; sc_rollup_max_lookahead_in_blocks = 30_000l; + sc_rollup_max_active_outbox_levels; + sc_rollup_max_outbox_messages_per_level; } let constants_sandbox = diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index b5e56ac56b4fd30aa914ad60c91186cf4a0765c0..631f5b62d7df5f7edb80b4ca26b49b09a263ac2f 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -781,6 +781,8 @@ module Constants : sig sc_rollup_commitment_period_in_blocks : int; sc_rollup_commitment_storage_size_in_bytes : int; sc_rollup_max_lookahead_in_blocks : int32; + sc_rollup_max_active_outbox_levels : int32; + sc_rollup_max_outbox_messages_per_level : int; } val encoding : t Data_encoding.t @@ -2732,6 +2734,15 @@ module Sc_rollup : sig Game.outcome -> (Game.status * context) tzresult Lwt.t + module Outbox : sig + val record_applied_message : + context -> + t -> + Raw_level.t -> + message_index:int -> + (Z.t * context) tzresult Lwt.t + end + module Internal_for_tests : sig val originated_sc_rollup : Origination_nonce.Internal_for_tests.t -> t end diff --git a/src/proto_alpha/lib_protocol/constants_parametric_repr.ml b/src/proto_alpha/lib_protocol/constants_parametric_repr.ml index d2c0b5b0b6c8a6989140dcfd9f5c73c46cedb65a..4742a32eb54546971892a7b6f4268ba14d9c5263 100644 --- a/src/proto_alpha/lib_protocol/constants_parametric_repr.ml +++ b/src/proto_alpha/lib_protocol/constants_parametric_repr.ml @@ -95,6 +95,8 @@ type t = { sc_rollup_commitment_period_in_blocks : int; sc_rollup_commitment_storage_size_in_bytes : int; sc_rollup_max_lookahead_in_blocks : int32; + sc_rollup_max_active_outbox_levels : int32; + sc_rollup_max_outbox_messages_per_level : int; } let encoding = @@ -159,7 +161,9 @@ let encoding = c.sc_rollup_stake_amount_in_mutez, c.sc_rollup_commitment_period_in_blocks, c.sc_rollup_commitment_storage_size_in_bytes, - c.sc_rollup_max_lookahead_in_blocks ) ) ) ) ) ) )) + c.sc_rollup_max_lookahead_in_blocks, + c.sc_rollup_max_active_outbox_levels, + c.sc_rollup_max_outbox_messages_per_level ) ) ) ) ) ) )) (fun ( ( preserved_cycles, blocks_per_cycle, blocks_per_commitment, @@ -218,7 +222,9 @@ let encoding = sc_rollup_stake_amount_in_mutez, sc_rollup_commitment_period_in_blocks, sc_rollup_commitment_storage_size_in_bytes, - sc_rollup_max_lookahead_in_blocks ) ) ) ) ) ) ) -> + sc_rollup_max_lookahead_in_blocks, + sc_rollup_max_active_outbox_levels, + sc_rollup_max_outbox_messages_per_level ) ) ) ) ) ) ) -> { preserved_cycles; blocks_per_cycle; @@ -279,6 +285,8 @@ let encoding = sc_rollup_commitment_period_in_blocks; sc_rollup_commitment_storage_size_in_bytes; sc_rollup_max_lookahead_in_blocks; + sc_rollup_max_active_outbox_levels; + sc_rollup_max_outbox_messages_per_level; }) (merge_objs (obj9 @@ -351,7 +359,7 @@ let encoding = (req "tx_rollup_max_ticket_payload_size" int31) (req "tx_rollup_rejection_max_proof_size" int31) (req "tx_rollup_sunset_level" int32))) - (obj8 + (obj10 (req "sc_rollup_enable" bool) (req "sc_rollup_origination_size" int31) (req "sc_rollup_challenge_window_in_blocks" int31) @@ -361,4 +369,6 @@ let encoding = (req "sc_rollup_commitment_storage_size_in_bytes" int31) - (req "sc_rollup_max_lookahead_in_blocks" int32)))))))) + (req "sc_rollup_max_lookahead_in_blocks" int32) + (req "sc_rollup_max_active_outbox_levels" int32) + (req "sc_rollup_max_outbox_messages_per_level" int31)))))))) diff --git a/src/proto_alpha/lib_protocol/constants_parametric_repr.mli b/src/proto_alpha/lib_protocol/constants_parametric_repr.mli index 4c62c421a9d4726ee7914d78d70d9128f0e4bd4b..ee6722acc7c1fcb652510f59c89d2d935353d8a5 100644 --- a/src/proto_alpha/lib_protocol/constants_parametric_repr.mli +++ b/src/proto_alpha/lib_protocol/constants_parametric_repr.mli @@ -129,6 +129,10 @@ type t = { [sc_rollup_commitment_period_in_blocks] to prevent the cost of a staker's commitments' storage being greater than their deposit. *) sc_rollup_max_lookahead_in_blocks : int32; + (* Maximum number of active outbox levels allowed. An outbox level is active + if it has an associated record of applied messages. *) + sc_rollup_max_active_outbox_levels : int32; + sc_rollup_max_outbox_messages_per_level : int; } val encoding : t Data_encoding.encoding diff --git a/src/proto_alpha/lib_protocol/constants_storage.ml b/src/proto_alpha/lib_protocol/constants_storage.ml index 61226d8be6ff00637adb7f4e573f593318c8415c..a6bb076a484435c31cdabfcca48bdd64e142f343 100644 --- a/src/proto_alpha/lib_protocol/constants_storage.ml +++ b/src/proto_alpha/lib_protocol/constants_storage.ml @@ -241,3 +241,11 @@ let sc_rollup_commitment_storage_size_in_bytes c = let sc_rollup_max_lookahead_in_blocks c = let constants = Raw_context.constants c in constants.sc_rollup_max_lookahead_in_blocks + +let sc_rollup_max_active_outbox_levels c = + let constants = Raw_context.constants c in + constants.sc_rollup_max_active_outbox_levels + +let sc_rollup_max_outbox_messages_per_level c = + let constants = Raw_context.constants c in + constants.sc_rollup_max_outbox_messages_per_level diff --git a/src/proto_alpha/lib_protocol/constants_storage.mli b/src/proto_alpha/lib_protocol/constants_storage.mli index 1349dc4580f1bfdfbb8b90e1b4978008c91a8518..bcabda7717f0e58265fe98c72632b14bc03deecb 100644 --- a/src/proto_alpha/lib_protocol/constants_storage.mli +++ b/src/proto_alpha/lib_protocol/constants_storage.mli @@ -140,3 +140,7 @@ val sc_rollup_commitment_period_in_blocks : Raw_context.t -> int val sc_rollup_commitment_storage_size_in_bytes : Raw_context.t -> int val sc_rollup_max_lookahead_in_blocks : Raw_context.t -> int32 + +val sc_rollup_max_active_outbox_levels : Raw_context.t -> int32 + +val sc_rollup_max_outbox_messages_per_level : Raw_context.t -> int diff --git a/src/proto_alpha/lib_protocol/raw_context.ml b/src/proto_alpha/lib_protocol/raw_context.ml index c97fd34a58d425ccb4807b6b723faca80cf35520..f1341fb2d2d8c3e4b171a42436ac92843ee6ac28 100644 --- a/src/proto_alpha/lib_protocol/raw_context.ml +++ b/src/proto_alpha/lib_protocol/raw_context.ml @@ -950,8 +950,7 @@ let prepare_first_block ~level ~timestamp ctxt = c.tx_rollup_rejection_max_proof_size; tx_rollup_sunset_level = c.tx_rollup_sunset_level; sc_rollup_enable = false; - (* The following value is chosen to prevent spam. *) - sc_rollup_origination_size = 6_314; + sc_rollup_origination_size = c.sc_rollup_origination_size; sc_rollup_challenge_window_in_blocks = 20_160; (* The following value is chosen to limit the maximal length of an inbox refutation proof. *) @@ -967,6 +966,15 @@ let prepare_first_block ~level ~timestamp ctxt = + 0 for Staker_count_update entry *) sc_rollup_commitment_storage_size_in_bytes = 84; sc_rollup_max_lookahead_in_blocks = 30_000l; + (* Number of active levels kept for executing outbox messages. + WARNING: Changing this value impacts the storage charge for + applying messages from the outbox. It also requires migration for + remapping existing active outbox levels to new indices. *) + sc_rollup_max_active_outbox_levels = 20_160l; + (* Maximum number of outbox messages per level. + WARNING: changing this value impacts the storage cost charged + for applying messages from the outbox. *) + sc_rollup_max_outbox_messages_per_level = 100; } in add_constants ctxt constants >>= fun ctxt -> return ctxt) diff --git a/src/proto_alpha/lib_protocol/sc_rollup_storage.ml b/src/proto_alpha/lib_protocol/sc_rollup_storage.ml index e7f5797538a2c4766a1b4098ad6cf5d53c6b6445..18e96f304b1caee93e749cba3a2eca47563e531c 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_storage.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_storage.ml @@ -48,6 +48,9 @@ type error += | (* `Temporary *) Sc_rollup_timeout_level_not_reached | (* `Temporary *) Sc_rollup_max_number_of_messages_reached_for_commitment_period + | (* `Temporary *) Sc_rollup_invalid_outbox_message_index + | (* `Temporary *) Sc_rollup_outbox_level_expired + | (* `Temporary *) Sc_rollup_outbox_message_already_applied let () = register_error_kind @@ -257,6 +260,36 @@ let () = Data_encoding.empty (function Sc_rollup_bad_inbox_level -> Some () | _ -> None) (fun () -> Sc_rollup_bad_inbox_level) ; + let description = "Invalid rollup outbox message index" in + register_error_kind + `Temporary + ~id:"Sc_rollup_invalid_outbox_message_index" + ~title:"Invalid rollup outbox message index" + ~description + ~pp:(fun ppf () -> Format.fprintf ppf "%s" description) + Data_encoding.empty + (function Sc_rollup_invalid_outbox_message_index -> Some () | _ -> None) + (fun () -> Sc_rollup_invalid_outbox_message_index) ; + let description = "Outbox level expired" in + register_error_kind + `Temporary + ~id:"Sc_rollup_outbox_level_expired" + ~title:description + ~description + ~pp:(fun ppf () -> Format.fprintf ppf "%s" description) + Data_encoding.empty + (function Sc_rollup_outbox_level_expired -> Some () | _ -> None) + (fun () -> Sc_rollup_outbox_level_expired) ; + let description = "Outbox message already applied" in + register_error_kind + `Temporary + ~id:"Sc_rollup_outbox_message_already_applied" + ~title:description + ~description + ~pp:(fun ppf () -> Format.fprintf ppf "%s" description) + Data_encoding.empty + (function Sc_rollup_outbox_message_already_applied -> Some () | _ -> None) + (fun () -> Sc_rollup_outbox_message_already_applied) ; () module Store = Storage.Sc_rollup @@ -965,3 +998,62 @@ let apply_outcome ctxt rollup stakers (outcome : Sc_rollup_game_repr.outcome) = let* ctxt, _, _ = Store.Opponent.remove (ctxt, rollup) alice in let* ctxt, _, _ = Store.Opponent.remove (ctxt, rollup) bob in return (Sc_rollup_game_repr.Ended (outcome.reason, losing_staker), ctxt) + +module Outbox = struct + let level_index ctxt level = + let max_active_levels = + Constants_storage.sc_rollup_max_active_outbox_levels ctxt + in + Int32.rem (Raw_level_repr.to_int32 level) max_active_levels + + let record_applied_message ctxt rollup level ~message_index = + let open Lwt_tzresult_syntax in + (* Check that the 0 <= message index < maximum number of outbox messages per + level. *) + let*? () = + let max_outbox_messages_per_level = + Constants_storage.sc_rollup_max_outbox_messages_per_level ctxt + in + error_unless + Compare.Int.( + 0 <= message_index && message_index < max_outbox_messages_per_level) + Sc_rollup_invalid_outbox_message_index + in + let level_index = level_index ctxt level in + let* ctxt, level_and_bitset_opt = + Store.Applied_outbox_messages.find (ctxt, rollup) level_index + in + let*? bitset, ctxt = + let open Tzresult_syntax in + let* bitset, ctxt = + match level_and_bitset_opt with + | Some (existing_level, bitset) + when Raw_level_repr.(existing_level = level) -> + (* The level at the index is the same as requested. Fail if the + message has been applied already. *) + let* already_applied = Bitset.mem bitset message_index in + let* () = + error_when + already_applied + Sc_rollup_outbox_message_already_applied + in + return (bitset, ctxt) + | Some (existing_level, _bitset) + when Raw_level_repr.(level < existing_level) -> + fail Sc_rollup_outbox_level_expired + | Some _ | None -> + (* The old level is outdated or there is no previous bitset at + this index. *) + return (Bitset.empty, ctxt) + in + let* bitset = Bitset.add bitset message_index in + return (bitset, ctxt) + in + let+ ctxt, size_diff, _is_new = + Store.Applied_outbox_messages.add + (ctxt, rollup) + level_index + (level, bitset) + in + (Z.of_int size_diff, ctxt) +end diff --git a/src/proto_alpha/lib_protocol/sc_rollup_storage.mli b/src/proto_alpha/lib_protocol/sc_rollup_storage.mli index e3c43b819319ba3ddf8c9a3644b835aba4d8c773..7a09832d140a6cd1e50e27df4de2d5f52f73668f 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_storage.mli +++ b/src/proto_alpha/lib_protocol/sc_rollup_storage.mli @@ -144,6 +144,9 @@ type error += Sc_rollup_bad_inbox_level | (* `Temporary *) Sc_rollup_max_number_of_messages_reached_for_commitment_period + | (* `Temporary *) Sc_rollup_invalid_outbox_message_index + | (* `Temporary *) Sc_rollup_outbox_level_expired + | (* `Temporary *) Sc_rollup_outbox_message_already_applied (** Module [Internal] implements functions that are used only internally by the [Sc_rollup_storage] module, but need to be exposed in tests or @@ -566,3 +569,25 @@ val apply_outcome : Sc_rollup_repr.Staker.t * Sc_rollup_repr.Staker.t -> Sc_rollup_game_repr.outcome -> (Sc_rollup_game_repr.status * Raw_context.t) tzresult Lwt.t + +(** A module for managing state concerning a rollup's outbox. *) +module Outbox : sig + (** [record_applied_message ctxt rollup level ~message_index] marks the + message in the outbox of rollup [rollup] at level [level] and position + [message_index] as processed. Returns the size diff resulting from + adding an entry. The size diff may be 0 if an entry already exists, or + negative if an index is replaced with a new level. + + An attempt to apply an old level that has already been replaced + fails with an [Sc_rollup_outbox_level_expired] error. + + In case a message has already been applied for the given level and message + index, the function fails with an + [Sc_rollup_outbox_message_already_applied] error. *) + val record_applied_message : + Raw_context.t -> + Sc_rollup_repr.t -> + Raw_level_repr.t -> + message_index:int -> + (Z.t * Raw_context.t) tzresult Lwt.t +end diff --git a/src/proto_alpha/lib_protocol/storage.ml b/src/proto_alpha/lib_protocol/storage.ml index d4b0e00d6e3963d635932054eebf2a46e19d0fbc..b545d2a24b0b34db63607ab26c3054be62abe76b 100644 --- a/src/proto_alpha/lib_protocol/storage.ml +++ b/src/proto_alpha/lib_protocol/storage.ml @@ -1646,4 +1646,65 @@ module Sc_rollup = struct let encoding = Sc_rollup_repr.Staker.encoding end) + + (** An index used for a SCORU's outbox levels. An outbox level is mapped to + the index through: [outbox_level % sc_rollup_max_active_outbox_levels]. + That way we keep a limited number of entries. The current value of an + entry contains the most recently added level that maps to the index. *) + module Level_index = struct + type t = int32 + + let rpc_arg = + let construct = Int32.to_string in + let destruct hash = + Int32.of_string_opt hash + |> Result.of_option ~error:"Cannot parse level index" + in + RPC_arg.make + ~descr:"The level index for applied outbox message records" + ~name:"level_index" + ~construct + ~destruct + () + + let encoding = + Data_encoding.def + "level_index" + ~title:"Level index" + ~description:"The level index for applied outbox message records" + Data_encoding.int32 + + let compare = Compare.Int32.compare + + let path_length = 1 + + let to_path c l = Int32.to_string c :: l + + let of_path = function [c] -> Int32.of_string_opt c | _ -> None + end + + module Level_index_context = + Make_indexed_subcontext + (Make_subcontext (Registered) (Indexed_context.Raw_context) + (struct + let name = ["level_index"] + end)) + (Make_index (Level_index)) + + module Bitset_and_level = struct + type t = Raw_level_repr.t * Bitset.t + + let encoding = + Data_encoding.( + obj2 + (req "level" Raw_level_repr.encoding) + (req "bitset" Bitset.encoding)) + end + + module Applied_outbox_messages = + Level_index_context.Make_carbonated_map + (struct + let name = ["applied_outbox_messages"] + end) + (Bitset_and_level) end diff --git a/src/proto_alpha/lib_protocol/storage.mli b/src/proto_alpha/lib_protocol/storage.mli index 62037ee84e9309259bf43225c52dca7a8e5d10c1..f137b0be6fafc4dbe20ba2b64c4912172d4de7c3 100644 --- a/src/proto_alpha/lib_protocol/storage.mli +++ b/src/proto_alpha/lib_protocol/storage.mli @@ -796,4 +796,24 @@ module Sc_rollup : sig with type key = Signature.Public_key_hash.t and type value = Sc_rollup_repr.Staker.t and type t = Raw_context.t * Sc_rollup_repr.t + + (** A carbonated storage for keeping track of applied outbox messages for a + a SCORU. + + The [key] is an [int32] value that represents the index of a SCORU's + outbox level. An outbox level is mapped to the index through: + + [index = outbox_level % sc_rollup_max_active_outbox_levels] + + The rationale is to keep a limited number of entries. The current value of + an entry contains the most recently added level that maps to the index. + + The [value] is a pair of the actual outbox level and a bitset containing + the set of applied messages. + *) + module Applied_outbox_messages : + Non_iterable_indexed_carbonated_data_storage + with type t = Raw_context.t * Sc_rollup_repr.t + and type key = int32 + and type value = Raw_level_repr.t * Bitset.t end diff --git a/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_storage.ml b/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_storage.ml index 1bdfd07d80abe13190726bfe1ed5c3f84ac208ca..f0405696ab8f07fc85e6928fd42339be455a3387 100644 --- a/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_storage.ml +++ b/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_storage.ml @@ -90,7 +90,7 @@ let assert_fails_with ~loc k msg = k >>= function | Ok _ -> Stdlib.failwith "Expected failure" | Error err -> - let actual_error_msg : string = + let actual_error_msg = Format.asprintf "%a" Environment.Error_monad.pp_trace err in Assert.equal_string ~loc expected_error_msg actual_error_msg @@ -1774,6 +1774,155 @@ let test_limit_on_number_of_messages_during_commitment_period with_gap () = true | _ -> false +let record ctxt rollup level message_index = + Sc_rollup_storage.Outbox.record_applied_message + ctxt + rollup + (Raw_level_repr.of_int32_exn @@ Int32.of_int level) + ~message_index + +(* Recreating the indexing logic to make sure messages are applied. *) +let assert_is_already_applied ~loc ctxt rollup level message_index = + let level = Raw_level_repr.of_int32_exn (Int32.of_int level) in + let level_index = + let max_active_levels = + Constants_storage.sc_rollup_max_active_outbox_levels ctxt + in + Int32.rem (Raw_level_repr.to_int32 level) max_active_levels + in + let* _ctxt, level_and_bitset_opt = + lift + @@ Storage.Sc_rollup.Applied_outbox_messages.find (ctxt, rollup) level_index + in + match level_and_bitset_opt with + | Some (existing_level, bitset) when Raw_level_repr.(existing_level = level) + -> + let*? is_set = + Environment.wrap_tzresult @@ Bitset.mem bitset message_index + in + Assert.equal_bool ~loc is_set true + | _ -> Stdlib.failwith "Expected a bitset and a matching level." + +(** Test outbox for applied messages. *) +let test_storage_outbox () = + let* ctxt = new_context () in + let* rollup1, ctxt = lift @@ new_sc_rollup ctxt in + let level1 = 100 in + (* Test that is-applied is false for non-recorded messages. *) + let* _size_diff, ctxt = lift @@ record ctxt rollup1 level1 1 in + let* () = assert_is_already_applied ~loc:__LOC__ ctxt rollup1 level1 1 in + (* Record the same level and message twice should fail. *) + let* () = + assert_fails_with + ~loc:__LOC__ + (record ctxt rollup1 level1 1) + "Outbox message already applied" + in + let* _size_diff, ctxt = lift @@ record ctxt rollup1 level1 2 in + let* () = assert_is_already_applied ~loc:__LOC__ ctxt rollup1 level1 2 in + (* Record messages for new level. *) + let level2 = level1 + 3 in + let* _size_diff, ctxt = lift @@ record ctxt rollup1 level2 47 in + let* () = assert_is_already_applied ~loc:__LOC__ ctxt rollup1 level2 47 in + let* () = assert_is_already_applied ~loc:__LOC__ ctxt rollup1 level1 1 in + (* Record for a new rollup. *) + let* rollup2, ctxt = lift @@ new_sc_rollup ctxt in + let* _size_diff, ctxt = lift @@ record ctxt rollup2 level1 1 in + let* _size_diff, ctxt = lift @@ record ctxt rollup2 level1 3 in + let* () = assert_is_already_applied ~loc:__LOC__ ctxt rollup2 level1 1 in + let* () = assert_is_already_applied ~loc:__LOC__ ctxt rollup2 level1 3 in + assert_is_already_applied ~loc:__LOC__ ctxt rollup1 level1 1 + +(** Test limits for applied outbox messages. *) +let test_storage_outbox_exceed_limits () = + let level = 1234 in + let* ctxt = new_context () in + let* rollup, ctxt = lift @@ new_sc_rollup ctxt in + (* Assert that recording a message index that exceeds max outbox messages per + level fails. *) + let* () = + let max_message_index = + Constants_storage.sc_rollup_max_outbox_messages_per_level ctxt + in + assert_fails_with + ~loc:__LOC__ + (record ctxt rollup level max_message_index) + "Invalid rollup outbox message index" + in + let* () = + assert_fails_with + ~loc:__LOC__ + (record ctxt rollup level (-1)) + "Invalid rollup outbox message index" + in + let max_active_levels = + Int32.to_int @@ Constants_storage.sc_rollup_max_active_outbox_levels ctxt + in + (* Record message 42 at level 15 *) + let* _size_diff, ctxt = lift @@ record ctxt rollup 15 42 in + let* () = assert_is_already_applied ~loc:__LOC__ ctxt rollup 15 42 in + (* Record message 42 at level [max_active_levels + 15] *) + let* _size_diff, ctxt = + lift @@ record ctxt rollup (max_active_levels + 15) 42 + in + (* Record message 42 at level 15 again should fail as it's expired. *) + let* () = + assert_fails_with + ~loc:__LOC__ + (record ctxt rollup 15 42) + "Outbox level expired" + in + return () + +(** Test storage outbox size diff. Note that these tests depend on the constant. + [sc_rollup_max_outbox_messages_per_level] which controls the maximum size + of bitsets required to store applied messages per level. + + Here's a breakdown of the size for applied-outbox-messages storage: + - [size_of_level = 4] + - [max_size_per_level < (2 * (sc_rollup_max_outbox_messages_per_level / 8))] + - [max_size_per_level < size_of_level + size_of_bitset] + - [total_size < sc_rollup_max_active_outbox_levels * max_size_per_level] + *) +let test_storage_outbox_size_diff () = + (* This is the maximum additional storage space required to store one message. + It depends on [sc_rollup_max_outbox_messages_per_level]. *) + let max_size_diff = 19 in + let* ctxt = new_context () in + let* rollup, ctxt = lift @@ new_sc_rollup ctxt in + let level = 15 in + let max_message_index = + Constants_storage.sc_rollup_max_outbox_messages_per_level ctxt - 1 + in + let max_active_levels = + Int32.to_int @@ Constants_storage.sc_rollup_max_active_outbox_levels ctxt + in + (* Record a new message. *) + let* size_diff, ctxt = lift @@ record ctxt rollup level 1 in + (* Size diff is 11 bytes. 4 bytes for level and 7 bytes for a new Z.t *) + let* () = Assert.equal_int ~loc:__LOC__ (Z.to_int size_diff) 5 in + let* size_diff, ctxt = lift @@ record ctxt rollup level 2 in + (* Recording a new message in the bitset at a lower index does not occupy + any additional space. *) + let* () = Assert.equal_int ~loc:__LOC__ (Z.to_int size_diff) 0 in + (* Record a new message at the highest index at an existing level. This + expands the bitset but does not charge for the level. *) + let* size_diff, ctxt = lift @@ record ctxt rollup level max_message_index in + let* () = Assert.equal_int ~loc:__LOC__ (Z.to_int size_diff) 14 in + (* Record a new message at the highest index at a new level. This charges for + space for level and maximum bitset. *) + let* size_diff, ctxt = + lift @@ record ctxt rollup (level + 1) max_message_index + in + let* () = Assert.equal_int ~loc:__LOC__ (Z.to_int size_diff) max_size_diff in + (* Record a new message for a level that resets an index. This replaces the + bitset with a smaller one. Hence we get a negative size diff. *) + let* size_diff, _ctxt = + lift @@ record ctxt rollup (level + max_active_levels) 0 + in + let* () = Assert.equal_int ~loc:__LOC__ (Z.to_int size_diff) (-14) in + return () + let tests = [ Tztest.tztest @@ -1986,6 +2135,15 @@ let tests = limit (with gap)" `Quick (test_limit_on_number_of_messages_during_commitment_period true); + Tztest.tztest "Record messages in storage outbox" `Quick test_storage_outbox; + Tztest.tztest + "Record messages in storage outbox limits" + `Quick + test_storage_outbox_exceed_limits; + Tztest.tztest + "Record messages size diffs" + `Quick + test_storage_outbox_size_diff; ] (* FIXME: https://gitlab.com/tezos/tezos/-/issues/2460 diff --git a/tests_python/tests_alpha/test_mockup.py b/tests_python/tests_alpha/test_mockup.py index 1aa0bb01d55a6e57229c890483f774df1a62dada..246b27c5fdda42e467d0ee928fb7c4956270984b 100644 --- a/tests_python/tests_alpha/test_mockup.py +++ b/tests_python/tests_alpha/test_mockup.py @@ -678,6 +678,8 @@ def _test_create_mockup_init_show_roundtrip( "sc_rollup_commitment_period_in_blocks": 40, "sc_rollup_commitment_storage_size_in_bytes": 84, "sc_rollup_max_lookahead_in_blocks": 10_000, + "sc_rollup_max_active_outbox_levels": 20_160, + "sc_rollup_max_outbox_messages_per_level": 100, } ), ], diff --git a/tezt/_regressions/rpc/alpha.client.others.out b/tezt/_regressions/rpc/alpha.client.others.out index 3d2956a12ecfe2e015c92fae220e6388304a87e1..6e692cbcd10a16375c21d940a6944349d2e26d52 100644 --- a/tezt/_regressions/rpc/alpha.client.others.out +++ b/tezt/_regressions/rpc/alpha.client.others.out @@ -48,7 +48,9 @@ rpc/alpha.client.others.out "sc_rollup_stake_amount_in_mutez": 32000000, "sc_rollup_commitment_period_in_blocks": 30, "sc_rollup_commitment_storage_size_in_bytes": 84, - "sc_rollup_max_lookahead_in_blocks": 30000 } + "sc_rollup_max_lookahead_in_blocks": 30000, + "sc_rollup_max_active_outbox_levels": 20160, + "sc_rollup_max_outbox_messages_per_level": 100 } ./tezos-client rpc get /chains/main/blocks/head/helpers/baking_rights [ { "level": 2, "delegate": "[PUBLIC_KEY_HASH]", diff --git a/tezt/_regressions/rpc/alpha.light.others.out b/tezt/_regressions/rpc/alpha.light.others.out index 07046aa916720b5fcd19a96600268250073b6068..61402074c195e6db67c4ffb4bf65995a06a244b0 100644 --- a/tezt/_regressions/rpc/alpha.light.others.out +++ b/tezt/_regressions/rpc/alpha.light.others.out @@ -48,7 +48,9 @@ rpc/alpha.light.others.out "sc_rollup_stake_amount_in_mutez": 32000000, "sc_rollup_commitment_period_in_blocks": 30, "sc_rollup_commitment_storage_size_in_bytes": 84, - "sc_rollup_max_lookahead_in_blocks": 30000 } + "sc_rollup_max_lookahead_in_blocks": 30000, + "sc_rollup_max_active_outbox_levels": 20160, + "sc_rollup_max_outbox_messages_per_level": 100 } protocol of light mode unspecified, using the node's protocol: ProtoGenesisGenesisGenesisGenesisGenesisGenesk612im ./tezos-client --mode light rpc get /chains/main/blocks/head/helpers/baking_rights diff --git a/tezt/_regressions/rpc/alpha.proxy.others.out b/tezt/_regressions/rpc/alpha.proxy.others.out index 794d61e123176a08c54df84d35768c94ebbb7d85..7d26b7089f0333e42aff2ce512021352aa1e1df3 100644 --- a/tezt/_regressions/rpc/alpha.proxy.others.out +++ b/tezt/_regressions/rpc/alpha.proxy.others.out @@ -48,7 +48,9 @@ rpc/alpha.proxy.others.out "sc_rollup_stake_amount_in_mutez": 32000000, "sc_rollup_commitment_period_in_blocks": 30, "sc_rollup_commitment_storage_size_in_bytes": 84, - "sc_rollup_max_lookahead_in_blocks": 30000 } + "sc_rollup_max_lookahead_in_blocks": 30000, + "sc_rollup_max_active_outbox_levels": 20160, + "sc_rollup_max_outbox_messages_per_level": 100 } protocol of proxy unspecified, using the node's protocol: ProtoGenesisGenesisGenesisGenesisGenesisGenesk612im ./tezos-client --mode proxy rpc get /chains/main/blocks/head/helpers/baking_rights diff --git a/tezt/_regressions/rpc/alpha.proxy_server_data_dir.others.out b/tezt/_regressions/rpc/alpha.proxy_server_data_dir.others.out index 0769042d20df22b55c7688cc277c5f842324fcdd..12f77d24e987a0276887b20f63a96845cc3ed808 100644 --- a/tezt/_regressions/rpc/alpha.proxy_server_data_dir.others.out +++ b/tezt/_regressions/rpc/alpha.proxy_server_data_dir.others.out @@ -48,7 +48,9 @@ rpc/alpha.proxy_server_data_dir.others.out "sc_rollup_stake_amount_in_mutez": 32000000, "sc_rollup_commitment_period_in_blocks": 30, "sc_rollup_commitment_storage_size_in_bytes": 84, - "sc_rollup_max_lookahead_in_blocks": 30000 } + "sc_rollup_max_lookahead_in_blocks": 30000, + "sc_rollup_max_active_outbox_levels": 20160, + "sc_rollup_max_outbox_messages_per_level": 100 } ./tezos-client rpc get /chains/main/blocks/head/helpers/baking_rights [ { "level": 3, "delegate": "[PUBLIC_KEY_HASH]", diff --git a/tezt/_regressions/rpc/alpha.proxy_server_rpc.others.out b/tezt/_regressions/rpc/alpha.proxy_server_rpc.others.out index 1e14cff54521a722305d9aa6d026e375f8e43e8b..869343b796cc776736144cf20de72f00ee66be84 100644 --- a/tezt/_regressions/rpc/alpha.proxy_server_rpc.others.out +++ b/tezt/_regressions/rpc/alpha.proxy_server_rpc.others.out @@ -48,7 +48,9 @@ rpc/alpha.proxy_server_rpc.others.out "sc_rollup_stake_amount_in_mutez": 32000000, "sc_rollup_commitment_period_in_blocks": 30, "sc_rollup_commitment_storage_size_in_bytes": 84, - "sc_rollup_max_lookahead_in_blocks": 30000 } + "sc_rollup_max_lookahead_in_blocks": 30000, + "sc_rollup_max_active_outbox_levels": 20160, + "sc_rollup_max_outbox_messages_per_level": 100 } ./tezos-client rpc get /chains/main/blocks/head/helpers/baking_rights [ { "level": 3, "delegate": "[PUBLIC_KEY_HASH]", diff --git a/tezt/_regressions/sc_rollup_commitment_of_rollup_node_commitment_is_stored.out b/tezt/_regressions/sc_rollup_commitment_of_rollup_node_commitment_is_stored.out index 2f778b421479d7ce859da2be2ebab0ee8a9f113d..d58a65a2301040bda453d4402bf05f8510ae8608 100644 --- a/tezt/_regressions/sc_rollup_commitment_of_rollup_node_commitment_is_stored.out +++ b/tezt/_regressions/sc_rollup_commitment_of_rollup_node_commitment_is_stored.out @@ -81,7 +81,9 @@ This sequence of operations was run: "sc_rollup_stake_amount_in_mutez": 32000000, "sc_rollup_commitment_period_in_blocks": 30, "sc_rollup_commitment_storage_size_in_bytes": 84, - "sc_rollup_max_lookahead_in_blocks": 30000 } + "sc_rollup_max_lookahead_in_blocks": 30000, + "sc_rollup_max_active_outbox_levels": 20160, + "sc_rollup_max_outbox_messages_per_level": 100 } ./tezos-client --wait none send sc rollup message 'text:["CAFEBABE"]' from bootstrap1 to '[SC_ROLLUP_HASH]' Node is bootstrapped. diff --git a/tezt/_regressions/sc_rollup_commitment_of_rollup_node_first_published_at_level_global.out b/tezt/_regressions/sc_rollup_commitment_of_rollup_node_first_published_at_level_global.out index cd160e73a2d915690ccc98c1fb4d0e4b63675c63..7b0e5c3efb31ad62bd4fa506009b738a01b6bef7 100644 --- a/tezt/_regressions/sc_rollup_commitment_of_rollup_node_first_published_at_level_global.out +++ b/tezt/_regressions/sc_rollup_commitment_of_rollup_node_first_published_at_level_global.out @@ -81,7 +81,9 @@ This sequence of operations was run: "sc_rollup_stake_amount_in_mutez": 32000000, "sc_rollup_commitment_period_in_blocks": 30, "sc_rollup_commitment_storage_size_in_bytes": 84, - "sc_rollup_max_lookahead_in_blocks": 30000 } + "sc_rollup_max_lookahead_in_blocks": 30000, + "sc_rollup_max_active_outbox_levels": 20160, + "sc_rollup_max_outbox_messages_per_level": 100 } ./tezos-sc-rollup-client-alpha rpc get /last_published_commitment { "commitment": diff --git a/tezt/_regressions/sc_rollup_commitment_of_rollup_node_handles_chain_reorgs.out b/tezt/_regressions/sc_rollup_commitment_of_rollup_node_handles_chain_reorgs.out index 3cd8de211fad014bf4dc6014d5653f0392e78847..acc8c5b327611596a4683be0ce2fdf42be3d27a3 100644 --- a/tezt/_regressions/sc_rollup_commitment_of_rollup_node_handles_chain_reorgs.out +++ b/tezt/_regressions/sc_rollup_commitment_of_rollup_node_handles_chain_reorgs.out @@ -81,7 +81,9 @@ This sequence of operations was run: "sc_rollup_stake_amount_in_mutez": 32000000, "sc_rollup_commitment_period_in_blocks": 30, "sc_rollup_commitment_storage_size_in_bytes": 84, - "sc_rollup_max_lookahead_in_blocks": 30000 } + "sc_rollup_max_lookahead_in_blocks": 30000, + "sc_rollup_max_active_outbox_levels": 20160, + "sc_rollup_max_outbox_messages_per_level": 100 } ./tezos-client --wait none send sc rollup message 'text:["CAFEBABE"]' from bootstrap1 to '[SC_ROLLUP_HASH]' Node is bootstrapped. diff --git a/tezt/_regressions/sc_rollup_commitment_of_rollup_node_messages_reset.out b/tezt/_regressions/sc_rollup_commitment_of_rollup_node_messages_reset.out index e0064304b4619f1cf6e3e16c4cdf2c109581f322..2182fd0ede427aa091e342b1fd728b2a14190dff 100644 --- a/tezt/_regressions/sc_rollup_commitment_of_rollup_node_messages_reset.out +++ b/tezt/_regressions/sc_rollup_commitment_of_rollup_node_messages_reset.out @@ -81,7 +81,9 @@ This sequence of operations was run: "sc_rollup_stake_amount_in_mutez": 32000000, "sc_rollup_commitment_period_in_blocks": 30, "sc_rollup_commitment_storage_size_in_bytes": 84, - "sc_rollup_max_lookahead_in_blocks": 30000 } + "sc_rollup_max_lookahead_in_blocks": 30000, + "sc_rollup_max_active_outbox_levels": 20160, + "sc_rollup_max_outbox_messages_per_level": 100 } ./tezos-client --wait none send sc rollup message 'text:["CAFEBABE"]' from bootstrap1 to '[SC_ROLLUP_HASH]' Node is bootstrapped. diff --git a/tezt/_regressions/sc_rollup_commitment_of_rollup_node_non_final_level.out b/tezt/_regressions/sc_rollup_commitment_of_rollup_node_non_final_level.out index 71124fe9b4cb584915ef99dd21f54996dea22113..eae2bc2deef15789be9bb2cc8d45816d18beba4a 100644 --- a/tezt/_regressions/sc_rollup_commitment_of_rollup_node_non_final_level.out +++ b/tezt/_regressions/sc_rollup_commitment_of_rollup_node_non_final_level.out @@ -81,7 +81,9 @@ This sequence of operations was run: "sc_rollup_stake_amount_in_mutez": 32000000, "sc_rollup_commitment_period_in_blocks": 30, "sc_rollup_commitment_storage_size_in_bytes": 84, - "sc_rollup_max_lookahead_in_blocks": 30000 } + "sc_rollup_max_lookahead_in_blocks": 30000, + "sc_rollup_max_active_outbox_levels": 20160, + "sc_rollup_max_outbox_messages_per_level": 100 } ./tezos-client --wait none send sc rollup message 'text:["CAFEBABE"]' from bootstrap1 to '[SC_ROLLUP_HASH]' Node is bootstrapped.