diff --git a/src/lib_smart_rollup/rollup_node_services.ml b/src/lib_smart_rollup/rollup_node_services.ml index 98c54904f5f471d92afdde1075f6020691988648..cf20650ccb64619b98fc99227b1d436af13d39fd 100644 --- a/src/lib_smart_rollup/rollup_node_services.ml +++ b/src/lib_smart_rollup/rollup_node_services.ml @@ -85,7 +85,11 @@ type message_status = published_at_level : int32; } -type gc_info = {last_gc_level : int32; first_available_level : int32} +type gc_info = { + last_gc_level : int32; + first_available_level : int32; + last_context_split_level : int32 option; +} module Encodings = struct open Data_encoding @@ -259,11 +263,14 @@ module Encodings = struct let gc_info : gc_info Data_encoding.t = conv - (fun {last_gc_level; first_available_level} -> - (last_gc_level, first_available_level)) - (fun (last_gc_level, first_available_level) -> - {last_gc_level; first_available_level}) - @@ obj2 (req "last_gc_level" int32) (req "first_available_level" int32) + (fun {last_gc_level; first_available_level; last_context_split_level} -> + (last_gc_level, first_available_level, last_context_split_level)) + (fun (last_gc_level, first_available_level, last_context_split_level) -> + {last_gc_level; first_available_level; last_context_split_level}) + @@ obj3 + (req "last_gc_level" int32) + (req "first_available_level" int32) + (opt "last_context_split_level" int32) end module Arg = struct diff --git a/src/lib_smart_rollup_node/configuration.ml b/src/lib_smart_rollup_node/configuration.ml index 311c711647ad561b7db445ef681535dfbe34d593..744aa06115ffea754f3b852225257acde71eac75 100644 --- a/src/lib_smart_rollup_node/configuration.ml +++ b/src/lib_smart_rollup_node/configuration.ml @@ -43,7 +43,10 @@ type batcher = { type injector = {retention_period : int; attempts : int; injection_ttl : int} -type gc_parameters = {frequency_in_blocks : int32} +type gc_parameters = { + frequency_in_blocks : int32; + context_splitting_period : int option; +} type history_mode = Archive | Full @@ -216,6 +219,7 @@ let default_gc_parameters = (* TODO: https://gitlab.com/tezos/tezos/-/issues/6415 * Refine the default GC frequency parameter *) frequency_in_blocks = 100l; + context_splitting_period = None; } (* TODO: https://gitlab.com/tezos/tezos/-/issues/6576 @@ -377,9 +381,13 @@ let injector_encoding : injector Data_encoding.t = let gc_parameters_encoding : gc_parameters Data_encoding.t = let open Data_encoding in conv - (fun {frequency_in_blocks} -> frequency_in_blocks) - (fun frequency_in_blocks -> {frequency_in_blocks}) - @@ obj1 (dft "frequency" int32 default_gc_parameters.frequency_in_blocks) + (fun {frequency_in_blocks; context_splitting_period} -> + (frequency_in_blocks, context_splitting_period)) + (fun (frequency_in_blocks, context_splitting_period) -> + {frequency_in_blocks; context_splitting_period}) + @@ obj2 + (dft "frequency" int32 default_gc_parameters.frequency_in_blocks) + (opt "context_splitting_period" int31) let history_mode_encoding : history_mode Data_encoding.t = Data_encoding.string_enum [("archive", Archive); ("full", Full)] @@ -712,6 +720,7 @@ module Cli = struct Option.value ~default:default_gc_parameters.frequency_in_blocks gc_frequency; + context_splitting_period = None; }; history_mode; cors = @@ -793,6 +802,8 @@ module Cli = struct Option.value ~default:configuration.gc_parameters.frequency_in_blocks gc_frequency; + context_splitting_period = + configuration.gc_parameters.context_splitting_period; }; history_mode = Option.either history_mode configuration.history_mode; cors = diff --git a/src/lib_smart_rollup_node/configuration.mli b/src/lib_smart_rollup_node/configuration.mli index c933ea0c9c637939d172082b0cb4efb3673c9451..bbf8c96c73366754ed8c8a1a71b7accd0bad9bbc 100644 --- a/src/lib_smart_rollup_node/configuration.mli +++ b/src/lib_smart_rollup_node/configuration.mli @@ -66,7 +66,11 @@ type injector = { never included is retried. *) } -type gc_parameters = {frequency_in_blocks : int32} +type gc_parameters = { + frequency_in_blocks : int32; (** Frequency at which the GC is triggered. *) + context_splitting_period : int option; + (** Number of blocks before splitting the context. *) +} type history_mode = | Archive diff --git a/src/lib_smart_rollup_node/node_context.ml b/src/lib_smart_rollup_node/node_context.ml index 5006d5b635859af071ae0cb34d06036fa8cdd69d..c6009573c3cf2e61b7b468ed5a16c8da7dc369d3 100644 --- a/src/lib_smart_rollup_node/node_context.ml +++ b/src/lib_smart_rollup_node/node_context.ml @@ -913,6 +913,9 @@ let get_gc_levels node_ctxt = first_available_level = node_ctxt.genesis_info.level; } +let get_last_context_split_level node_ctxt = + Store.Last_context_split.read node_ctxt.store.last_context_split_level + let save_gc_info node_ctxt ~at_level ~gc_level = let open Lwt_syntax in (* Note: Setting the `first_available_level` before GC succeeds simplifies the @@ -929,6 +932,9 @@ let save_gc_info node_ctxt ~at_level ~gc_level = | Error _ -> Event.gc_levels_storage_failure () | Ok () -> return_unit +let save_context_split_level node_ctxt level = + Store.Last_context_split.write node_ctxt.store.last_context_split_level level + let get_gc_level node_ctxt = let open Lwt_result_syntax in let* history_mode = Store.History_mode.read node_ctxt.store.history_mode in @@ -950,7 +956,7 @@ let gc node_ctxt ~(level : int32) = called. *) let* gc_level = get_gc_level node_ctxt in let frequency = node_ctxt.config.gc_parameters.frequency_in_blocks in - let* {last_gc_level; first_available_level} = get_gc_levels node_ctxt in + let* {last_gc_level; first_available_level; _} = get_gc_levels node_ctxt in match gc_level with | None -> return_unit | Some gc_level diff --git a/src/lib_smart_rollup_node/node_context.mli b/src/lib_smart_rollup_node/node_context.mli index d941bf9bcc3df98705799cd9cc95ddcd82835f98..c91d0329fc49132397e065d50702c630a3ac1872 100644 --- a/src/lib_smart_rollup_node/node_context.mli +++ b/src/lib_smart_rollup_node/node_context.mli @@ -544,6 +544,10 @@ val get_gc_levels : _ t -> Store.Gc_levels.levels tzresult Lwt.t [level] is before the first non garbage collected level. *) val check_level_available : _ t -> int32 -> unit tzresult Lwt.t +val get_last_context_split_level : _ t -> int32 option tzresult Lwt.t + +val save_context_split_level : rw -> int32 -> unit tzresult Lwt.t + (** {2 Helpers} *) (** [make_kernel_logger event ?log_kernel_debug_file logs_dir] returns two diff --git a/src/lib_smart_rollup_node/rollup_node_daemon.ml b/src/lib_smart_rollup_node/rollup_node_daemon.ml index 346722055b46ab622a3624b8fca36750c5e8bd00..d237709a1c005e83b41fc7c39dd7d82dc8082766 100644 --- a/src/lib_smart_rollup_node/rollup_node_daemon.ml +++ b/src/lib_smart_rollup_node/rollup_node_daemon.ml @@ -82,6 +82,27 @@ let handle_protocol_migration ~catching_up state (head : Layer1.header) = state.node_ctxt.current_protocol <- new_protocol ; return_unit +let maybe_split_context node_ctxt commitment_hash head_level = + let open Lwt_result_syntax in + let* history_mode = Node_context.get_history_mode node_ctxt in + let commit_is_gc_candidate = + history_mode <> Archive && Option.is_some commitment_hash + in + when_ commit_is_gc_candidate @@ fun () -> + let* last = Node_context.get_last_context_split_level node_ctxt in + let last = Option.value last ~default:node_ctxt.genesis_info.level in + let splitting_period = + Option.value + node_ctxt.config.gc_parameters.context_splitting_period + ~default: + node_ctxt.current_protocol.constants.sc_rollup + .challenge_window_in_blocks + in + if Int32.(to_int @@ sub head_level last) >= splitting_period then ( + Context.split node_ctxt.context ; + Node_context.save_context_split_level node_ctxt head_level) + else return_unit + (* Process a L1 that we have never seen and for which we have processed the predecessor. *) let process_unseen_head ({node_ctxt; _} as state) ~catching_up ~predecessor @@ -123,11 +144,7 @@ let process_unseen_head ({node_ctxt; _} as state) ~catching_up ~predecessor head ctxt in - let* history_mode = Node_context.get_history_mode node_ctxt in - let commit_is_gc_candidate = - history_mode <> Archive && Option.is_some commitment_hash - in - if commit_is_gc_candidate then Context.split node_ctxt.context ; + let* () = maybe_split_context node_ctxt commitment_hash head.level in let* () = unless (catching_up && Option.is_none commitment_hash) @@ fun () -> Plugin.Inbox.same_as_layer_1 node_ctxt head.hash inbox diff --git a/src/lib_smart_rollup_node/rpc_directory.ml b/src/lib_smart_rollup_node/rpc_directory.ml index f1b2d0f197c48dadbca5e9d121a0610f1409582e..7863f6513314b4008993e29a9cfd39eaf4fd4b6e 100644 --- a/src/lib_smart_rollup_node/rpc_directory.ml +++ b/src/lib_smart_rollup_node/rpc_directory.ml @@ -176,10 +176,14 @@ let () = Local_directory.register0 Rollup_node_services.Local.gc_info @@ fun node_ctxt () () -> let open Lwt_result_syntax in - let+ {last_gc_level; first_available_level} = + let* {last_gc_level; first_available_level} = Node_context.get_gc_levels node_ctxt + and* last_context_split_level = + Node_context.get_last_context_split_level node_ctxt in - Rollup_node_services.{last_gc_level; first_available_level} + return + Rollup_node_services. + {last_gc_level; first_available_level; last_context_split_level} let () = Local_directory.register0 Rollup_node_services.Local.injection diff --git a/src/lib_smart_rollup_node/store_v2.ml b/src/lib_smart_rollup_node/store_v2.ml index e92cd2753ef3259888a081be0af5592c3f203d47..d1c5f725254fb259a538976c5027dfa1b016e6b2 100644 --- a/src/lib_smart_rollup_node/store_v2.ml +++ b/src/lib_smart_rollup_node/store_v2.ml @@ -311,6 +311,14 @@ module Gc_levels = struct end) end +module Last_context_split = Indexed_store.Make_singleton (struct + type t = int32 + + let name = "last_context_split_level" + + let encoding = Data_encoding.int32 +end) + module History_mode = Indexed_store.Make_singleton (struct type t = Configuration.history_mode @@ -333,6 +341,7 @@ type 'a store = { protocols : 'a Protocols.t; irmin_store : 'a Irmin_store.t; gc_levels : 'a Gc_levels.t; + last_context_split_level : 'a Last_context_split.t; history_mode : 'a History_mode.t; } @@ -357,6 +366,7 @@ let readonly protocols; irmin_store; gc_levels; + last_context_split_level; history_mode; } : _ t) : ro = @@ -375,6 +385,8 @@ let readonly protocols = Protocols.readonly protocols; irmin_store = Irmin_store.readonly irmin_store; gc_levels = Gc_levels.readonly gc_levels; + last_context_split_level = + Last_context_split.readonly last_context_split_level; history_mode = History_mode.readonly history_mode; } @@ -393,6 +405,7 @@ let close protocols = _; irmin_store; gc_levels = _; + last_context_split_level = _; history_mode = _; } : _ t) = @@ -447,6 +460,9 @@ let load (type a) (mode : a mode) ~index_buffer_size ~l2_blocks_cache_size in let* protocols = Protocols.load mode ~path:(path "protocols") in let* gc_levels = Gc_levels.load mode ~path:(path "gc_levels") in + let* last_context_split_level = + Last_context_split.load mode ~path:(path "last_context_split_level") + in let* history_mode = History_mode.load mode ~path:(path "history_mode") in let+ irmin_store = Irmin_store.load mode (path "irmin_store") in { @@ -463,6 +479,7 @@ let load (type a) (mode : a mode) ~index_buffer_size ~l2_blocks_cache_size protocols; irmin_store; gc_levels; + last_context_split_level; history_mode; } @@ -558,6 +575,7 @@ let gc irmin_store = _; protocols = _; gc_levels = _; + last_context_split_level = _; history_mode = _; } : _ t) ~level = @@ -590,6 +608,7 @@ let wait_gc_completion irmin_store = _; protocols = _; gc_levels = _; + last_context_split_level = _; history_mode = _; } : _ t) = @@ -619,6 +638,7 @@ let is_gc_finished irmin_store = _; protocols = _; gc_levels = _; + last_context_split_level = _; history_mode = _; } : _ t) = diff --git a/src/lib_smart_rollup_node/store_v2.mli b/src/lib_smart_rollup_node/store_v2.mli index 9f0ebb9d57af94496314b9291e49bea4c620ddfe..e643f896402bb5a74430b76350cf5827af323558 100644 --- a/src/lib_smart_rollup_node/store_v2.mli +++ b/src/lib_smart_rollup_node/store_v2.mli @@ -124,6 +124,9 @@ module Gc_levels : sig include SINGLETON_STORE with type value = levels end +(** Level at which context was last split. *) +module Last_context_split : SINGLETON_STORE with type value := int32 + (** History mode of the rollup node. *) module History_mode : SINGLETON_STORE with type value := Configuration.history_mode @@ -142,6 +145,7 @@ type +'a store = { protocols : 'a Protocols.t; irmin_store : 'a Irmin_store.t; gc_levels : 'a Gc_levels.t; + last_context_split_level : 'a Last_context_split.t; history_mode : 'a History_mode.t; } diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index dfd311f5c284c3d15263b113f0c5bffcafe2a5b9..e3cbfbd8bd4443043bc3a04db265d7e2ce872b7a 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -992,6 +992,24 @@ let test_gc variant ?(tags = []) ~challenge_window ~commitment_period "Commitment was published but publication info is not available \ anymore." | _ -> ()) ; + let* context_files = + Process.run_and_read_stdout + "ls" + [Sc_rollup_node.data_dir sc_rollup_node ^ "/context/"] + in + let last_suffix = + String.split_on_char '\n' context_files + |> List.filter_map (fun s -> s =~* rex "store\\.(\\d+)\\.suffix") + |> List.rev |> List.hd + in + let nb_suffix = int_of_string last_suffix in + let max_nb_split = + match history_mode with + | Archive -> 0 + | _ -> (level - origination_level + challenge_window - 1) / challenge_window + in + Check.((nb_suffix <= max_nb_split) int) + ~error_msg:"Expected at most %R context suffix files, instead got %L" ; unit (* Testing that snapshots can be exported correctly for a running node, and that