From b89498b9c439dc68db91386d57949338d1550a69 Mon Sep 17 00:00:00 2001 From: Gabriel Moise Date: Tue, 23 Jul 2024 13:07:27 +0100 Subject: [PATCH 1/3] Store: Upgrade store version to 3.2 --- src/lib_node_config/data_version.ml | 26 +++++++++++++++------ src/lib_store/mocked/store.ml | 2 ++ src/lib_store/store.mli | 2 ++ src/lib_store/unix/cemented_block_store.ml | 2 ++ src/lib_store/unix/cemented_block_store.mli | 2 ++ src/lib_store/unix/store.ml | 7 ++++++ src/lib_store/unix/store.mli | 5 +++- 7 files changed, 38 insertions(+), 8 deletions(-) diff --git a/src/lib_node_config/data_version.ml b/src/lib_node_config/data_version.ml index 866d098b153c..0d7764160d3c 100644 --- a/src/lib_node_config/data_version.ml +++ b/src/lib_node_config/data_version.ml @@ -102,7 +102,8 @@ end * - 2.0 : introduce context GC (upgrade to irmin.3.4) -- v15.0 * - 3.0 : change blocks' context hash semantics and introduce context split (upgrade to irmin.3.5) -- v16.0 - * - 3.1 : change encoding for block store status -- v21.0 *) + * - 3.1 : change encoding for block store status -- v21.0 + * - 3.2 : update offset format for cemented files to 64-bit -- v22.0 *) (* FIXME https://gitlab.com/tezos/tezos/-/issues/2861 We should enable the semantic versioning instead of applying @@ -111,7 +112,9 @@ let v_3_0 = Version.make ~major:3 ~minor:0 let v_3_1 = Version.make ~major:3 ~minor:1 -let current_version = v_3_1 +let v_3_2 = Version.make ~major:3 ~minor:2 + +let current_version = v_3_2 (* List of upgrade functions from each still supported previous version to the current [data_version] above. If this list grows too @@ -125,14 +128,23 @@ let current_version = v_3_1 - we want to deprecate a specific upgrade *) let upgradable_data_version = + let open Lwt_result_syntax in let v_3_1_upgrade ~data_dir genesis = let store_dir = store_dir data_dir in Store.v_3_1_upgrade ~store_dir genesis in + let v_3_2_upgrade ~data_dir genesis = + let store_dir = store_dir data_dir in + Store.v_3_2_upgrade ~store_dir genesis + in [ ( v_3_0, fun ~data_dir genesis ~chain_name:_ ~sandbox_parameters:_ -> - v_3_1_upgrade ~data_dir genesis ); + let* () = v_3_1_upgrade ~data_dir genesis in + v_3_2_upgrade ~data_dir genesis ); + ( v_3_1, + fun ~data_dir genesis ~chain_name:_ ~sandbox_parameters:_ -> + v_3_2_upgrade ~data_dir genesis ); ] type error += Invalid_data_dir_version of Version.t * Version.t @@ -420,10 +432,10 @@ let ensure_data_dir ?(mode = Is_compatible) genesis data_dir = let open Lwt_result_syntax in let* o = ensure_data_dir ~mode data_dir in match o with - | None -> - return_unit - (* Enable automatic upgrade to avoid users to manually upgrade. *) - | Some (version, _) when Version.(equal version v_3_0) -> + | None -> return_unit + | Some (version, _) when false -> + (* Enable automatic upgrade to avoid users to manually upgrade. + This is disabled for the current version. *) let* () = upgrade_data_dir ~data_dir genesis ~chain_name:() ~sandbox_parameters:() in diff --git a/src/lib_store/mocked/store.ml b/src/lib_store/mocked/store.ml index fce76d6df03a..917519bf050f 100644 --- a/src/lib_store/mocked/store.ml +++ b/src/lib_store/mocked/store.ml @@ -2079,3 +2079,5 @@ module Unsafe = struct end let v_3_1_upgrade ~store_dir:_ _genesis = Lwt_result_syntax.return_unit + +let v_3_2_upgrade ~store_dir:_ _genesis = Lwt_result_syntax.return_unit diff --git a/src/lib_store/store.mli b/src/lib_store/store.mli index 16e17136e856..0262de24beea 100644 --- a/src/lib_store/store.mli +++ b/src/lib_store/store.mli @@ -1041,6 +1041,8 @@ end val v_3_1_upgrade : store_dir:string -> Genesis.t -> unit tzresult Lwt.t +val v_3_2_upgrade : store_dir:string -> Genesis.t -> unit tzresult Lwt.t + (**/**) (** Unsafe set of functions intended for internal store manipulation diff --git a/src/lib_store/unix/cemented_block_store.ml b/src/lib_store/unix/cemented_block_store.ml index 1fad8deccebc..90ec3a2679cf 100644 --- a/src/lib_store/unix/cemented_block_store.ml +++ b/src/lib_store/unix/cemented_block_store.ml @@ -1095,3 +1095,5 @@ let check_indexes_consistency ?(post_step = fun () -> Lwt.return_unit) table_list in return_unit + +let v_3_2_upgrade _chain_dir = Lwt_result_syntax.return_unit diff --git a/src/lib_store/unix/cemented_block_store.mli b/src/lib_store/unix/cemented_block_store.mli index 85673e276f79..c5d389b78981 100644 --- a/src/lib_store/unix/cemented_block_store.mli +++ b/src/lib_store/unix/cemented_block_store.mli @@ -309,3 +309,5 @@ val check_indexes_consistency : ?genesis_hash:Block_hash.t -> t -> unit tzresult Lwt.t + +val v_3_2_upgrade : [`Chain_dir] Naming.directory -> unit tzresult Lwt.t diff --git a/src/lib_store/unix/store.ml b/src/lib_store/unix/store.ml index 7ea0f4ad8b39..eaa6c770aeb1 100644 --- a/src/lib_store/unix/store.ml +++ b/src/lib_store/unix/store.ml @@ -3481,6 +3481,13 @@ let v_3_1_upgrade ~store_dir genesis = in Block_store.v_3_1_upgrade chain_dir +let v_3_2_upgrade ~store_dir genesis = + let chain_id = Chain_id.of_block_hash genesis.Genesis.block in + let chain_dir = + Naming.chain_dir (Naming.store_dir ~dir_path:store_dir) chain_id + in + Cemented_block_store.v_3_2_upgrade chain_dir + (************ For testing and internal purposes only **************) module Unsafe = struct let repr_of_block b = b diff --git a/src/lib_store/unix/store.mli b/src/lib_store/unix/store.mli index 67813818417b..93679dd7735b 100644 --- a/src/lib_store/unix/store.mli +++ b/src/lib_store/unix/store.mli @@ -1033,9 +1033,12 @@ module Chain_traversal : sig (Block.t * Block.t list) Lwt.t end -(** Potentially upgrade to v_3 and then upgrade the block_store_status in v_3_1. *) +(** Upgrade the block_store_status in v_3_1. *) val v_3_1_upgrade : store_dir:string -> Genesis.t -> unit tzresult Lwt.t +(** Upgrade the offset format for cemented files in v_3_2. *) +val v_3_2_upgrade : store_dir:string -> Genesis.t -> unit tzresult Lwt.t + (**/**) (** Unsafe set of functions intended for internal store manipulation -- GitLab From b5101ba0e5ac9b6c02d0bdf58f24d7490ab2a8c1 Mon Sep 17 00:00:00 2001 From: Gabriel Moise Date: Thu, 1 Aug 2024 15:00:38 +0100 Subject: [PATCH 2/3] Store: Add v_3_2_upgrade to Cemented_blocks_store module --- src/lib_store/unix/cemented_block_store.ml | 190 +++++++++++++++++--- src/lib_store/unix/cemented_block_store.mli | 4 +- 2 files changed, 164 insertions(+), 30 deletions(-) diff --git a/src/lib_store/unix/cemented_block_store.ml b/src/lib_store/unix/cemented_block_store.ml index 90ec3a2679cf..470cbe5c9caa 100644 --- a/src/lib_store/unix/cemented_block_store.ml +++ b/src/lib_store/unix/cemented_block_store.ml @@ -27,10 +27,10 @@ open Store_errors (* Cemented files overlay: - | x | x | + | x | x | is an absolute offset in the file. - are prefixed by 4 bytes of length + are prefixed by 8 bytes of length *) (* On-disk index of block's hashes to level *) module Cemented_block_level_index = @@ -369,9 +369,7 @@ let close cemented_store = terminated which means potential new reads won't be scheduled. *) Metadata_fd_cache.clear cemented_store.metadata_fd_cache -(* FIXME: https://gitlab.com/tezos/tezos/-/issues/7034 Cemented file - cannot exceed 4Gib. *) -let offset_length = 4 (* file offset *) +let offset_length = 8 (* file offset *) let find_block_file cemented_store block_level = try @@ -584,21 +582,8 @@ let read_block fd block_number = let* () = Lwt_utils_unix.read_bytes ~pos:0 ~len:offset_length fd offset_buffer in - let offset = - let ofs = Bytes.get_int32_be offset_buffer 0 in - (* We interpret the offset, written as an int32, as an unsigned - int32. This is allowed by the encoded scheme and allows one - additional bit to encode the offset. It enables dealing with - files up to 4Gib. *) - match Int32.unsigned_to_int ofs with - | Some v -> v - | None -> - (* It will be [None] on 32-bit machines which is not - supported. We default to [Int32.to_int] instead of [assert - false] *) - Int32.to_int ofs - in - let* _ofs = Lwt_unix.lseek fd offset Unix.SEEK_SET in + let offset = Bytes.get_int64_be offset_buffer 0 in + let* _ofs = Lwt_unix.LargeFile.lseek fd offset Unix.SEEK_SET in (* We move the cursor to the element's position *) let* block, _len = Block_repr_unix.read_next_block_exn fd in Lwt.return block @@ -650,8 +635,6 @@ let get_cemented_block_by_hash ~read_metadata (cemented_store : t) hash = | Some level -> get_cemented_block_by_level ~read_metadata cemented_store level -(* TODO/FIXME: https://gitlab.com/tezos/tezos/-/issues/7035 - Cemented metadata cannot exceed 4Gib *) (* Hypothesis: - The block list is expected to be ordered by increasing level and no blocks are skipped. @@ -781,10 +764,10 @@ let cement_blocks ?(check_consistency = true) (cemented_store : t) Block_repr_unix.prune_raw_block_bytes block_bytes in (* We start by blitting the corresponding offset in the preamble part *) - Bytes.set_int32_be + Bytes.set_int64_be offsets_buffer (i * offset_length) - (Int32.of_int current_offset) ; + (Int64.of_int current_offset) ; (* We write the block in the file *) let*! () = Lwt_utils_unix.write_bytes @@ -957,8 +940,8 @@ let raw_iter_cemented_file f ({file; _} as cemented_blocks_file) = file_path (fun channel -> let nb_blocks = cemented_blocks_file_length cemented_blocks_file in - let* first_block_offset = Lwt_io.BE.read_int channel in - let* () = Lwt_io.set_position channel (Int64.of_int first_block_offset) in + let* first_block_offset = Lwt_io.BE.read_int64 channel in + let* () = Lwt_io.set_position channel first_block_offset in let rec loop n = if n = 0 then Lwt.return_unit else @@ -1037,7 +1020,7 @@ let check_indexes_consistency ?(post_step = fun () -> Lwt.return_unit) let*! () = Lwt_utils_unix.read_bytes ~len:len_offset fd bytes in let offsets = Data_encoding.Binary.of_bytes_exn - Data_encoding.(Variable.array ~max_length:nb_blocks int32) + Data_encoding.(Variable.array ~max_length:nb_blocks int64) bytes in (* Cursor is now after the offset region *) @@ -1047,7 +1030,7 @@ let check_indexes_consistency ?(post_step = fun () -> Lwt.return_unit) let*! cur_offset = Lwt_unix.lseek fd 0 Unix.SEEK_CUR in let* () = fail_unless - Compare.Int32.(Int32.of_int cur_offset = offsets.(n)) + Compare.Int64.(Int64.of_int cur_offset = offsets.(n)) (Inconsistent_cemented_store (Bad_offset {level = n; cycle = Naming.file_path file})) @@ -1096,4 +1079,153 @@ let check_indexes_consistency ?(post_step = fun () -> Lwt.return_unit) in return_unit -let v_3_2_upgrade _chain_dir = Lwt_result_syntax.return_unit +(** [get_and_upgrade_offsets fd nb_blocks] obtains the list of [nb_blocks] offsets from the + beginning of the file given by file descriptor [fd] and upgrades their sizes from 32-bits to + 64-bits, adjusting their values for the following blocks in the cemented blocks file. *) +let get_and_upgrade_offsets fd nb_blocks = + let open Lwt_syntax in + (* Obtain the list of offsets (32-bit format) *) + let preamble_length = 4 * nb_blocks in + let bytes = Bytes.create preamble_length in + let* () = Lwt_utils_unix.read_bytes ~len:preamble_length fd bytes in + let offsets_32_bits = + Data_encoding.Binary.of_bytes_exn + Data_encoding.(Variable.array ~max_length:nb_blocks int32) + bytes + in + (* Transform the 32-bit offsets into 64-bit offsets and add preamble_length to all values. + The reasoning behind this is that we shift the blocks positions in the file with + ( - ) * nb_blocks = (8 - 4) * nb_blocks *) + let offsets_64_bits = + Array.map + (fun offset -> Int64.(add (of_int32 offset) (of_int preamble_length))) + offsets_32_bits + in + Lwt.return + @@ Data_encoding.Binary.to_bytes_exn + Data_encoding.(Variable.array ~max_length:nb_blocks int64) + offsets_64_bits + +(** [is_using_32_bit_offsets fd nb_blocks] checks whether the cemented file + given by [fd] is formatted with 32 bit offsets; the decision + is taken based on whether the first offset points correctly to the first + block in the file or not; + - offset = first 32 bits decoded as an int32 + - first_block_offset = 4 (bytes) * [nb_blocks] (first block offset, given + that the file has 32-bit offsets) + Check whether (offset = first_block_offset) holds. If not, then we cannot + do the upgrade procedure. *) +let is_using_32_bit_offsets fd nb_blocks = + let open Lwt_syntax in + (* For a 32-bit offset the size will be 4 bytes *) + let legacy_offset_length = 4 in + (* Save the current position of the file descriptor *) + let* current_position = Lwt_unix.lseek fd 0 Unix.SEEK_CUR in + (* Obtain the first offset of the file, altering the file descriptor position *) + let* _ofs = Lwt_unix.lseek fd 0 Unix.SEEK_SET in + let offset_buffer = Bytes.create legacy_offset_length in + (* Read the first offset in the offset array *) + let* () = + Lwt_utils_unix.read_bytes ~pos:0 ~len:legacy_offset_length fd offset_buffer + in + let offset = Bytes.get_int32_be offset_buffer 0 in + (* Restore the former position of the file descriptor *) + let* _ofs = Lwt_unix.lseek fd current_position Unix.SEEK_SET in + return Int32.(equal offset (mul (of_int legacy_offset_length) nb_blocks)) + +(* Hypothesis: we expect a directory of cemented files with 32-bit offsets *) +let v_3_2_upgrade chain_dir = + let open Lwt_result_syntax in + let cemented_blocks_dir = Naming.cemented_blocks_dir chain_dir in + let* cemented_blocks_files_opt = load_table cemented_blocks_dir in + match cemented_blocks_files_opt with + | None -> + (* This might be the case for rolling nodes, where no upgrade is needed as + there are not cemented block files. *) + return_unit + | Some cemented_blocks_files -> + let cemented_blocks_files = Array.to_list cemented_blocks_files in + let nb_cemented_files = List.length cemented_blocks_files in + let* () = + Animation.display_progress + ~pp_print_step:(fun fmt i -> + Format.fprintf + fmt + "Upgrading cemented files: %d/%d files" + i + nb_cemented_files) + ~progress_display_mode:Always + (fun notify -> + List.iter_es + (fun cemented_blocks_file -> + (* Original cemented blocks file *) + let file_path = Naming.file_path cemented_blocks_file.file in + let*! fd = + Lwt_unix.openfile file_path [Unix.O_RDONLY; O_CLOEXEC] 0o444 + in + (* Check if the file has 32-bit offsets format *) + let nb_blocks = + cemented_blocks_file_length cemented_blocks_file + in + let*! has_32_bit_offsets = + is_using_32_bit_offsets fd nb_blocks + in + if not has_32_bit_offsets then ( + Format.printf + "File %s does not have 32-bit offsets, skipping upgrade@." + file_path ; + return_unit) + else + (* Upgraded cemented blocks file *) + let upgraded_file_path = file_path ^ "_upgraded" in + let*! upgraded_fd = + Lwt_unix.openfile + upgraded_file_path + [Unix.O_WRONLY; Unix.O_CREAT; Unix.O_TRUNC] + 0o644 + in + (* Total number of blocks stored in the cemented blocks file*) + let nb_blocks = + Int32.to_int + @@ cemented_blocks_file_length cemented_blocks_file + in + Lwt.finalize + (fun () -> + let*! bytes = get_and_upgrade_offsets fd nb_blocks in + (* Write the 64-bit offsets list into the new file *) + let*! () = + Lwt_utils_unix.write_bytes + ~pos:0 + ~len:(8 * nb_blocks) + upgraded_fd + bytes + in + (* Copy the rest of the block file *) + let buffer_size = 4096 * 1024 in + let buffer = Bytes.create buffer_size in + let copy_bytes fd_in fd_out = + let rec loop () = + let*! bytes_read = + Lwt_unix.read fd_in buffer 0 buffer_size + in + if bytes_read = 0 then Lwt.return_unit + else + let*! _bytes_written = + Lwt_unix.write fd_out buffer 0 bytes_read + in + loop () + in + loop () + in + let*! () = copy_bytes fd upgraded_fd in + (* Atomically replace the old cemented file with the upgraded one *) + let*! () = Lwt_unix.rename upgraded_file_path file_path in + return_unit) + (fun () -> + let*! () = Lwt_unix.close fd in + let*! () = Lwt_unix.close upgraded_fd in + let*! () = notify () in + Lwt.return_unit)) + cemented_blocks_files) + in + return_unit diff --git a/src/lib_store/unix/cemented_block_store.mli b/src/lib_store/unix/cemented_block_store.mli index c5d389b78981..47211a4e389b 100644 --- a/src/lib_store/unix/cemented_block_store.mli +++ b/src/lib_store/unix/cemented_block_store.mli @@ -77,7 +77,7 @@ v} | × | × | - where n is (j - i + 1), is a 4 bytes integer representing + where n is (j - i + 1), is a 8 bytes integer representing the absolute offset of a block where the k-th (with 0 <= k < n) offset stands for the absolute offset of the k-th block in the file and with , a {!Block_repr.t} value encoded using @@ -310,4 +310,6 @@ val check_indexes_consistency : t -> unit tzresult Lwt.t +(* Given the chain directory with cemented files, upgrade all of them to have + 64-bit offsets at the beginning, instead of 32-bits. *) val v_3_2_upgrade : [`Chain_dir] Naming.directory -> unit tzresult Lwt.t -- GitLab From c93e9a2763b1fca7749332ae2c9a3dbb8717a3b8 Mon Sep 17 00:00:00 2001 From: Gabriel Moise Date: Mon, 5 Aug 2024 14:06:27 +0100 Subject: [PATCH 3/3] Store: Add event for skipping cemented file upgrade --- src/lib_store/shared/store_events.ml | 9 +++++++++ src/lib_store/unix/cemented_block_store.ml | 10 +++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/lib_store/shared/store_events.ml b/src/lib_store/shared/store_events.ml index b3c7ec347951..37efe592878b 100644 --- a/src/lib_store/shared/store_events.ml +++ b/src/lib_store/shared/store_events.ml @@ -618,3 +618,12 @@ let upgrade_store_started = ~name:"upgrade_store_started" ~msg:"upgrading the store" () + +let upgrade_cemented_file_skip = + declare_1 + ~section + ~level:Info + ~name:"upgrade_cemented_file_skip" + ~msg:"File {path} does not have 32-bit offsets, skipping upgrade" + ~pp1:Format.pp_print_string + ("path", Data_encoding.string) diff --git a/src/lib_store/unix/cemented_block_store.ml b/src/lib_store/unix/cemented_block_store.ml index 470cbe5c9caa..d2db03e8c90a 100644 --- a/src/lib_store/unix/cemented_block_store.ml +++ b/src/lib_store/unix/cemented_block_store.ml @@ -1170,11 +1170,11 @@ let v_3_2_upgrade chain_dir = let*! has_32_bit_offsets = is_using_32_bit_offsets fd nb_blocks in - if not has_32_bit_offsets then ( - Format.printf - "File %s does not have 32-bit offsets, skipping upgrade@." - file_path ; - return_unit) + if not has_32_bit_offsets then + let*! () = + Store_events.(emit upgrade_cemented_file_skip file_path) + in + return_unit else (* Upgraded cemented blocks file *) let upgraded_file_path = file_path ^ "_upgraded" in -- GitLab