(*****************************************************************************) (* *) (* Open Source License *) (* Copyright (c) 2021 Nomadic Labs, *) (* Copyright (c) 2023 Functori, *) (* *) (* 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. *) (* *) (*****************************************************************************) include Store_sigs include Store_utils include Store_v1 let version = Store_version.V2 module Make_hash_index_key (H : Tezos_crypto.Intfs.HASH) = Indexed_store.Make_index_key (struct include Indexed_store.Make_fixed_encodable (H) let equal = H.equal end) (** Unaggregated messages per block *) module Messages = Indexed_store.Make_indexed_file (struct let name = "messages" end) (Make_hash_index_key (Merkelized_payload_hashes_hash)) (struct type t = string list let name = "messages_list" let encoding = Data_encoding.(list @@ dynamic_size (Variable.string' Hex)) module Header = struct type t = Block_hash.t let name = "messages_block" let encoding = Block_hash.encoding let fixed_size = WithExceptions.Option.get ~loc:__LOC__ @@ Data_encoding.Binary.fixed_length encoding end end) module Empty_header = struct type t = unit let name = "empty" let encoding = Data_encoding.unit let fixed_size = 0 end module Add_empty_header = struct module Header = Empty_header let header _ = () end (** Versioned inboxes *) module Inboxes = Indexed_store.Make_simple_indexed_file (struct let name = "inboxes" end) (Make_hash_index_key (Octez_smart_rollup.Inbox.Hash)) (struct type t = Octez_smart_rollup.Inbox.t let encoding = Data_encoding.conv Octez_smart_rollup.Inbox.to_versioned Octez_smart_rollup.Inbox.of_versioned Octez_smart_rollup.Inbox.versioned_encoding let name = "inbox" include Add_empty_header end) (** Versioned commitments *) module Commitments = Indexed_store.Make_simple_indexed_file (struct let name = "commitments" end) (Make_hash_index_key (Octez_smart_rollup.Commitment.Hash)) (struct type t = Octez_smart_rollup.Commitment.t let encoding = Data_encoding.conv Octez_smart_rollup.Commitment.to_versioned Octez_smart_rollup.Commitment.of_versioned Octez_smart_rollup.Commitment.versioned_encoding let name = "commitment" include Add_empty_header end) (** Single commitment for LCC. *) module Lcc = struct type lcc = {commitment : Commitment.Hash.t; level : int32} include Indexed_store.Make_singleton (struct type t = lcc let name = "lcc" let encoding = let open Data_encoding in conv (fun {commitment; level} -> (commitment, level)) (fun (commitment, level) -> {commitment; level}) @@ obj2 (req "commitment" Octez_smart_rollup.Commitment.Hash.encoding) (req "level" int32) end) end (** Single commitment for LPC. *) module Lpc = Indexed_store.Make_singleton (struct type t = Octez_smart_rollup.Commitment.t let encoding = Data_encoding.conv Octez_smart_rollup.Commitment.to_versioned Octez_smart_rollup.Commitment.of_versioned Octez_smart_rollup.Commitment.versioned_encoding let name = "lpc" end) (** Versioned slot headers *) module Dal_slots_headers = Irmin_store.Make_nested_map (struct let path = ["dal"; "slot_headers"] end) (struct type key = Block_hash.t let to_path_representation = Block_hash.to_b58check end) (struct type key = Octez_smart_rollup.Dal.Slot_index.t let encoding = Octez_smart_rollup.Dal.Slot_index.encoding let compare = Compare.Int.compare let name = "slot_index" end) (struct type value = Octez_smart_rollup.Dal.Slot_header.t let name = "slot_header" let encoding = Data_encoding.conv Octez_smart_rollup.Dal.Slot_header.to_versioned Octez_smart_rollup.Dal.Slot_header.of_versioned Octez_smart_rollup.Dal.Slot_header.versioned_encoding end) (** Versioned Confirmed DAL slots history *) module Dal_confirmed_slots_history = Irmin_store.Make_append_only_map (struct let path = ["dal"; "confirmed_slots_history"] end) (struct type key = Block_hash.t let to_path_representation = Block_hash.to_b58check end) (struct type value = Octez_smart_rollup.Dal.Slot_history.t let name = "dal_slot_histories" let encoding = Data_encoding.conv Octez_smart_rollup.Dal.Slot_history.to_versioned Octez_smart_rollup.Dal.Slot_history.of_versioned Octez_smart_rollup.Dal.Slot_history.versioned_encoding end) (** Versioned Confirmed DAL slots histories cache. *) module Dal_confirmed_slots_histories = (* TODO: https://gitlab.com/tezos/tezos/-/issues/4390 Store single history points in map instead of whole history. *) Irmin_store.Make_append_only_map (struct let path = ["dal"; "confirmed_slots_histories_cache"] end) (struct type key = Block_hash.t let to_path_representation = Block_hash.to_b58check end) (struct type value = Octez_smart_rollup.Dal.Slot_history_cache.t let name = "dal_slot_histories" let encoding = Data_encoding.conv Octez_smart_rollup.Dal.Slot_history_cache.to_versioned Octez_smart_rollup.Dal.Slot_history_cache.of_versioned Octez_smart_rollup.Dal.Slot_history_cache.versioned_encoding end) module Protocols = struct type level = First_known of int32 | Activation_level of int32 type proto_info = { level : level; proto_level : int; protocol : Protocol_hash.t; } type value = proto_info list let level_encoding = let open Data_encoding in conv (function First_known l -> (l, false) | Activation_level l -> (l, true)) (function l, false -> First_known l | l, true -> Activation_level l) @@ obj2 (req "level" int32) (req "activates" bool) let proto_info_encoding = let open Data_encoding in conv (fun {level; proto_level; protocol} -> (level, proto_level, protocol)) (fun (level, proto_level, protocol) -> {level; proto_level; protocol}) @@ obj3 (req "level" level_encoding) (req "proto_level" int31) (req "protocol" Protocol_hash.encoding) include Indexed_store.Make_singleton (struct type t = value let name = "protocols" let level_encoding = let open Data_encoding in conv (function | First_known l -> (l, false) | Activation_level l -> (l, true)) (function l, false -> First_known l | l, true -> Activation_level l) @@ obj2 (req "level" int32) (req "activates" bool) let proto_info_encoding = let open Data_encoding in conv (fun {level; proto_level; protocol} -> (level, proto_level, protocol)) (fun (level, proto_level, protocol) -> {level; proto_level; protocol}) @@ obj3 (req "level" level_encoding) (req "proto_level" int31) (req "protocol" Protocol_hash.encoding) let encoding = Data_encoding.list proto_info_encoding end) end module Gc_levels = struct type levels = {last_gc_level : int32; first_available_level : int32} type value = levels include Indexed_store.Make_singleton (struct type t = levels let name = "gc_levels" let encoding : t Data_encoding.t = let open Data_encoding in 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) 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 let name = "history_mode" let encoding = Configuration.history_mode_encoding end) type 'a store = { l2_blocks : 'a L2_blocks.t; messages : 'a Messages.t; inboxes : 'a Inboxes.t; commitments : 'a Commitments.t; commitments_published_at_level : 'a Commitments_published_at_level.t; l2_head : 'a L2_head.t; last_finalized_level : 'a Last_finalized_level.t; lcc : 'a Lcc.t; lpc : 'a Lpc.t; levels_to_hashes : 'a Levels_to_hashes.t; 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; } type 'a t = ([< `Read | `Write > `Read] as 'a) store type rw = Store_sigs.rw t type ro = Store_sigs.ro t let readonly ({ l2_blocks; messages; inboxes; commitments; commitments_published_at_level; l2_head; last_finalized_level; lcc; lpc; levels_to_hashes; protocols; irmin_store; gc_levels; last_context_split_level; history_mode; } : _ t) : ro = { l2_blocks = L2_blocks.readonly l2_blocks; messages = Messages.readonly messages; inboxes = Inboxes.readonly inboxes; commitments = Commitments.readonly commitments; commitments_published_at_level = Commitments_published_at_level.readonly commitments_published_at_level; l2_head = L2_head.readonly l2_head; last_finalized_level = Last_finalized_level.readonly last_finalized_level; lcc = Lcc.readonly lcc; lpc = Lpc.readonly lpc; levels_to_hashes = Levels_to_hashes.readonly levels_to_hashes; 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; } let close ({ l2_blocks; messages; inboxes; commitments; commitments_published_at_level; l2_head = _; last_finalized_level = _; lcc = _; lpc = _; levels_to_hashes; protocols = _; irmin_store; gc_levels = _; last_context_split_level = _; history_mode = _; } : _ t) = let open Lwt_result_syntax in let+ () = L2_blocks.close l2_blocks and+ () = Messages.close messages and+ () = Inboxes.close inboxes and+ () = Commitments.close commitments and+ () = Commitments_published_at_level.close commitments_published_at_level and+ () = Levels_to_hashes.close levels_to_hashes and+ () = Irmin_store.close irmin_store in () let load (type a) (mode : a mode) ~index_buffer_size ~l2_blocks_cache_size data_dir : a store tzresult Lwt.t = let open Lwt_result_syntax in let path name = Filename.concat data_dir name in let cache_size = l2_blocks_cache_size in let* l2_blocks = L2_blocks.load mode ~index_buffer_size ~path:(path "l2_blocks") ~cache_size in let* messages = Messages.load mode ~index_buffer_size ~path:(path "messages") ~cache_size in let* inboxes = Inboxes.load mode ~index_buffer_size ~path:(path "inboxes") ~cache_size in let* commitments = Commitments.load mode ~index_buffer_size ~path:(path "commitments") ~cache_size in let* commitments_published_at_level = Commitments_published_at_level.load ~index_buffer_size mode ~path:(path "commitments_published_at_level") in let* l2_head = L2_head.load mode ~path:(path "l2_head") in let* last_finalized_level = Last_finalized_level.load mode ~path:(path "last_finalized_level") in let* lcc = Lcc.load mode ~path:(path "lcc") in let* lpc = Lpc.load mode ~path:(path "lpc") in let* levels_to_hashes = Levels_to_hashes.load mode ~index_buffer_size ~path:(path "levels_to_hashes") 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 { l2_blocks; messages; inboxes; commitments; commitments_published_at_level; l2_head; last_finalized_level; lcc; lpc; levels_to_hashes; protocols; irmin_store; gc_levels; last_context_split_level; history_mode; } let first_available_level metadata store = let open Lwt_result_syntax in let* gc_levels = Gc_levels.read store.gc_levels in match gc_levels with | Some {first_available_level; _} -> return first_available_level | None -> return metadata.Metadata.genesis_info.level let iter_l2_blocks ?progress metadata ({l2_blocks; l2_head; _} as store) f = let open Lwt_result_syntax in let* head = L2_head.read l2_head in match head with | None -> (* No reachable head, nothing to do *) return_unit | Some head -> let* track_progress = match progress with | None -> return (fun f -> f (fun _ -> Lwt.return_unit)) | Some message -> let+ first_level = first_available_level metadata store in let progress_bar = let total = Int32.sub head.header.level first_level |> Int32.to_int in Progress_bar.progress_bar ~counter:`Int ~message total in fun f -> Progress_bar.Lwt.with_reporter progress_bar f in track_progress @@ fun count_progress -> let rec loop hash = let* block = L2_blocks.read l2_blocks hash in match block with | None -> (* The block does not exist, the known chain stops here, so do we. *) return_unit | Some (block, header) -> let* () = f {block with header} in let*! () = count_progress 1 in loop header.predecessor in loop head.header.block_hash let gc_l2_blocks l2_blocks ~level = L2_blocks.gc l2_blocks (fun _hash header _content -> Lwt_result.return (header.Sc_rollup_block.level >= level)) let gc_commitments commitments ~level = Commitments.gc commitments (fun _hash () commitment -> Lwt_result.return (commitment.Commitment.inbox_level >= level)) let gc_levels_to_hashes levels_to_hashes ~level = Levels_to_hashes.gc levels_to_hashes (fun block_level _block_hash -> Lwt_result.return (block_level >= level)) let gc_messages messages l2_blocks ~level = Messages.gc messages (fun _witness predecessor _msgs -> let open Lwt_result_syntax in let+ pred = L2_blocks.header l2_blocks predecessor in match pred with | Some {level = pred_level; _} -> pred_level >= Int32.pred level | None -> false) let gc_commitments_published_at_level commitments_published_at_level commitments ~level = Commitments_published_at_level.gc commitments_published_at_level (fun commitment_hash _ -> let open Lwt_result_syntax in let* commitment = Commitments.read commitments commitment_hash in match commitment with | None -> return_false | Some ({inbox_level; _}, ()) -> return (inbox_level >= level)) let gc_inboxes inboxes ~level = Inboxes.gc inboxes (fun _inbox_hash () inbox -> Lwt_result.return (inbox.level >= level)) let gc ({ l2_blocks; messages; inboxes; commitments; commitments_published_at_level; l2_head = _; last_finalized_level = _; lcc = _; lpc = _; levels_to_hashes; irmin_store = _; protocols = _; gc_levels = _; last_context_split_level = _; history_mode = _; } : _ t) ~level = let open Lwt_result_syntax in tzjoin [ gc_l2_blocks l2_blocks ~level; gc_commitments commitments ~level; gc_levels_to_hashes levels_to_hashes ~level; gc_messages messages l2_blocks ~level; gc_commitments_published_at_level commitments_published_at_level commitments ~level; gc_inboxes inboxes ~level; ] let wait_gc_completion ({ l2_blocks; messages; inboxes; commitments; commitments_published_at_level; l2_head = _; last_finalized_level = _; lcc = _; lpc = _; levels_to_hashes; irmin_store = _; protocols = _; gc_levels = _; last_context_split_level = _; history_mode = _; } : _ t) = let open Lwt_syntax in let* () = L2_blocks.wait_gc_completion l2_blocks and* () = Messages.wait_gc_completion messages and* () = Inboxes.wait_gc_completion inboxes and* () = Commitments.wait_gc_completion commitments and* () = Commitments_published_at_level.wait_gc_completion commitments_published_at_level and* () = Levels_to_hashes.wait_gc_completion levels_to_hashes in return_unit let is_gc_finished ({ l2_blocks; messages; inboxes; commitments; commitments_published_at_level; l2_head = _; last_finalized_level = _; lcc = _; lpc = _; levels_to_hashes; irmin_store = _; protocols = _; gc_levels = _; last_context_split_level = _; history_mode = _; } : _ t) = L2_blocks.is_gc_finished l2_blocks && Messages.is_gc_finished messages && Inboxes.is_gc_finished inboxes && Commitments.is_gc_finished commitments && Commitments_published_at_level.is_gc_finished commitments_published_at_level && Levels_to_hashes.is_gc_finished levels_to_hashes