From 8ccc12142d14104200ae3cafb91d2135c59f937c Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Wed, 5 Jun 2024 21:20:47 +0200 Subject: [PATCH 1/4] Rollup node: export function to create commitments (cherry picked from commit 4cf7b13c29fb21a6f145e530162d643ef0587cbe) --- src/lib_smart_rollup_node/publisher.ml | 10 +++++----- src/lib_smart_rollup_node/publisher.mli | 11 +++++++++++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/lib_smart_rollup_node/publisher.ml b/src/lib_smart_rollup_node/publisher.ml index aec6ccdbe44d..281567c54e09 100644 --- a/src/lib_smart_rollup_node/publisher.ml +++ b/src/lib_smart_rollup_node/publisher.ml @@ -208,11 +208,6 @@ let create_commitment_if_necessary plugin (node_ctxt : _ Node_context.t) ~inbox_level:current_level ctxt in - let*! () = - Commitment_event.new_commitment - (Octez_smart_rollup.Commitment.hash commitment) - commitment.inbox_level - in return_some commitment else return_none @@ -231,6 +226,11 @@ let process_head plugin (node_ctxt : _ Node_context.t) ~predecessor match commitment with | None -> return_none | Some commitment -> + let*! () = + Commitment_event.new_commitment + (Octez_smart_rollup.Commitment.hash commitment) + commitment.inbox_level + in let* commitment_hash = Node_context.save_commitment node_ctxt commitment in diff --git a/src/lib_smart_rollup_node/publisher.mli b/src/lib_smart_rollup_node/publisher.mli index e6c89495c3c4..238c53bf4571 100644 --- a/src/lib_smart_rollup_node/publisher.mli +++ b/src/lib_smart_rollup_node/publisher.mli @@ -55,6 +55,17 @@ val process_head : Context.rw -> Commitment.Hash.t option tzresult Lwt.t +(** [create_commitment_if_necessary plugin node_ctxt ~predecessor level ctxt] + returns the commitment for inbox level [level] if there needs to be + one. [ctxt] should be the context checkouted for [level]. *) +val create_commitment_if_necessary : + (module Protocol_plugin_sig.S) -> + 'a Node_context.t -> + predecessor:Block_hash.t -> + int32 -> + 'a Context.t -> + (Commitment.t option, tztrace) result Lwt.t + (** [publish_single_commitment node_ctxt commitment] publishes a single [commitment] if it is missing. This function is meant to be used by the {e accuser} mode to sparingly publish commitments when it detects a -- GitLab From bcf1c6c34d1adc7da83e0d25aef915f770c5bf15 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Wed, 5 Jun 2024 21:22:14 +0200 Subject: [PATCH 2/4] Rollup node: export snapshot helper (cherry picked from commit a40ecda12a88068df0f1a7b755a8185f7b3bee93) --- src/lib_smart_rollup_node/snapshots.ml | 81 ++++++++++++++----------- src/lib_smart_rollup_node/snapshots.mli | 14 +++++ 2 files changed, 59 insertions(+), 36 deletions(-) diff --git a/src/lib_smart_rollup_node/snapshots.ml b/src/lib_smart_rollup_node/snapshots.ml index 36148cc801f4..0d8bf9c3f95e 100644 --- a/src/lib_smart_rollup_node/snapshots.ml +++ b/src/lib_smart_rollup_node/snapshots.ml @@ -455,7 +455,7 @@ let reconstruct_level_context rollup_ctxt ~predecessor return (block, ctxt) let reconstruct_context_from_first_available_level - (node_ctxt : _ Node_context.t) (head : Sc_rollup_block.t) = + (node_ctxt : _ Node_context.t) ~(head : Sc_rollup_block.t) = let open Lwt_result_syntax in let* {first_available_level = first_level; _} = Node_context.get_gc_levels node_ctxt @@ -485,7 +485,8 @@ let reconstruct_context_from_first_available_level in reconstruct_chain_from first_block first_ctxt -let maybe_reconstruct_context cctxt ~data_dir = +let with_modify_data_dir cctxt ~data_dir + ?(skip_condition = fun _ _ ~head:_ -> Lwt_result.return false) f = let open Lwt_result_syntax in let store_dir = Configuration.default_storage_dir data_dir in let context_dir = Configuration.default_context_dir data_dir in @@ -511,44 +512,52 @@ let maybe_reconstruct_context cctxt ~data_dir = let* context = Context.load (module C) ~cache_size:100 Read_write context_dir in - let*! head_ctxt = - Context.checkout + let* skip = skip_condition store context ~head in + unless skip @@ fun () -> + let* current_protocol = + Node_context.protocol_of_level_with_store store head.header.level + in + let*? (module Plugin) = + Protocol_plugins.proto_plugin_for_protocol current_protocol.protocol + in + let* constants = + Plugin.Layer1_helpers.retrieve_constants + cctxt + ~block:(`Level head.header.level) + in + let current_protocol = + { + Node_context.hash = current_protocol.protocol; + proto_level = current_protocol.proto_level; + constants; + } + in + let* node_ctxt = + Node_context_loader.For_snapshots.create_node_context + cctxt + current_protocol + store context - (Smart_rollup_context_hash.to_context_hash head.header.context) + ~data_dir in - match head_ctxt with - | Some _ -> return_unit - | None -> - let* current_protocol = - Node_context.protocol_of_level_with_store store head.header.level - in - let*? (module Plugin) = - Protocol_plugins.proto_plugin_for_protocol current_protocol.protocol - in - let* constants = - Plugin.Layer1_helpers.retrieve_constants - cctxt - ~block:(`Level head.header.level) - in - let current_protocol = - { - Node_context.hash = current_protocol.protocol; - proto_level = current_protocol.proto_level; - constants; - } - in - let* node_ctxt = - Node_context_loader.For_snapshots.create_node_context - cctxt - current_protocol - store + let* () = f node_ctxt ~head in + let*! () = Context.close context in + let* () = Store.close store in + return_unit + +let maybe_reconstruct_context cctxt ~data_dir = + with_modify_data_dir + cctxt + ~data_dir + ~skip_condition:(fun _store context ~head -> + let open Lwt_result_syntax in + let*! head_ctxt = + Context.checkout context - ~data_dir + (Smart_rollup_context_hash.to_context_hash head.header.context) in - let* () = reconstruct_context_from_first_available_level node_ctxt head in - let*! () = Context.close context in - let* () = Store.close store in - return_unit + return (Option.is_some head_ctxt)) + reconstruct_context_from_first_available_level let post_checks ~action ~message snapshot_metadata ~dest = let open Lwt_result_syntax in diff --git a/src/lib_smart_rollup_node/snapshots.mli b/src/lib_smart_rollup_node/snapshots.mli index 4efe9877ea23..2f9e394e4f0e 100644 --- a/src/lib_smart_rollup_node/snapshots.mli +++ b/src/lib_smart_rollup_node/snapshots.mli @@ -62,3 +62,17 @@ val import : val info : snapshot_file:string -> Snapshot_utils.snapshot_metadata * [`Compressed | `Uncompressed] + +(** [with_modify_data_dir cctxt ~data_dir ?skip_condition f] applies [f] in a + read-write context that is created from [data-dir] (and a potential existing + configuration). The node context given to [f] does not follow the L1 chain + and [f] is only supposed to modify the data of the rollup node. It is used + internally by this module to reconstruct contexts from a snapshot but can + also be use by the {!Repair} module to apply fixes off-line. *) +val with_modify_data_dir : + #Client_context.full -> + data_dir:string -> + ?skip_condition: + (Store.rw -> Context.rw -> head:Sc_rollup_block.t -> bool tzresult Lwt.t) -> + (Node_context.rw -> head:Sc_rollup_block.t -> unit tzresult Lwt.t) -> + unit tzresult Lwt.t -- GitLab From ac2ec209b534533717b5e782aad8968f61240ff0 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Wed, 5 Jun 2024 21:30:02 +0200 Subject: [PATCH 3/4] Rollup node: command to repair commitments for a given protocol (cherry picked from commit 7497840ad1e5a50cdad82f75c5afe8847fb45358) --- .../main_smart_rollup_node.ml | 1 + src/lib_smart_rollup_node/cli.ml | 7 + src/lib_smart_rollup_node/repair.ml | 213 ++++++++++++++++++ src/lib_smart_rollup_node/repair.mli | 9 + 4 files changed, 230 insertions(+) create mode 100644 src/lib_smart_rollup_node/repair.ml create mode 100644 src/lib_smart_rollup_node/repair.mli diff --git a/src/bin_smart_rollup_node/main_smart_rollup_node.ml b/src/bin_smart_rollup_node/main_smart_rollup_node.ml index f0b042423823..83df15295195 100644 --- a/src/bin_smart_rollup_node/main_smart_rollup_node.ml +++ b/src/bin_smart_rollup_node/main_smart_rollup_node.ml @@ -531,6 +531,7 @@ let sc_rollup_commands () = snapshot_info; openapi_command; ] + @ Repair.commands let select_commands _ctxt _ = Lwt_result_syntax.return (sc_rollup_commands ()) diff --git a/src/lib_smart_rollup_node/cli.ml b/src/lib_smart_rollup_node/cli.ml index 28fea6602e47..6d6cca995ed0 100644 --- a/src/lib_smart_rollup_node/cli.ml +++ b/src/lib_smart_rollup_node/cli.ml @@ -513,6 +513,13 @@ let protocol_hash_arg = between different versions of the node." protocol_hash_parameter +let protocol_hash_param next = + Tezos_clic.param + ~name:"protocol" + ~desc:"Protocol hash" + protocol_hash_parameter + next + let apply_unsafe_patches_switch : (bool, Client_context.full) Tezos_clic.arg = Tezos_clic.switch ~long:"apply-unsafe-patches" diff --git a/src/lib_smart_rollup_node/repair.ml b/src/lib_smart_rollup_node/repair.ml new file mode 100644 index 000000000000..2b60a2009397 --- /dev/null +++ b/src/lib_smart_rollup_node/repair.ml @@ -0,0 +1,213 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2024 Functori *) +(* *) +(*****************************************************************************) + +let group = + { + Tezos_clic.name = "smart_rollup.node.repair"; + title = "Commands to repair the smart rollup node."; + } + +module Cli = struct + include Cli + + include Binary_dependent_args (struct + let binary_name = "smart rollup node" + end) +end + +type fix_action = + | Nothing + | Remove of Sc_rollup_block.t + | Add of Commitment.t * Sc_rollup_block.t + | Patch of Sc_rollup_block.t + +(** Recompute the commitment for [level] and store it if necessary. Commitments + that were incorrectly computed before are removed. Pre-condition: + commitments for inbox levels before [level] must be correct. *) +let fix_commitment (node_ctxt : Node_context.rw) level = + let open Lwt_result_syntax in + let* l2_block = Node_context.get_l2_block_by_level node_ctxt level in + let stored_commitment_hash = l2_block.header.commitment_hash in + let* plugin = Protocol_plugins.proto_plugin_for_level node_ctxt level in + let (module Plugin) = plugin in + let* constants = + let constants_level = Int32.max level node_ctxt.genesis_info.level in + Protocol_plugins.get_constants_of_protocol + node_ctxt + ~level:constants_level + Plugin.protocol + in + let protocol = + { + Node_context.hash = Plugin.protocol; + proto_level = (Reference.get node_ctxt.current_protocol).proto_level; + (* leave as is because unimportant for the fix *) + constants; + } + in + let* previous_commitment_hash = + if level = node_ctxt.genesis_info.level then + (* Previous commitment for rollup genesis is itself. *) + return node_ctxt.genesis_info.commitment_hash + else + let+ pred = + Node_context.get_l2_block node_ctxt l2_block.header.predecessor + in + Sc_rollup_block.most_recent_commitment pred.header + in + Reference.set node_ctxt.current_protocol protocol ; + let* ctxt = + Node_context.checkout_context node_ctxt l2_block.header.block_hash + in + let* new_commitment = + Publisher.create_commitment_if_necessary + plugin + node_ctxt + ~predecessor:l2_block.header.predecessor + level + ctxt + in + let l2_block, action = + if + Commitment.Hash.( + previous_commitment_hash = l2_block.header.previous_commitment_hash) + then (l2_block, Nothing) + else + let l2_block = + {l2_block with header = {l2_block.header with previous_commitment_hash}} + in + (l2_block, Patch l2_block) + in + let action = + match (new_commitment, stored_commitment_hash) with + | None, None -> action + | Some new_commitment, _ -> ( + let new_commitment_hash = Commitment.hash new_commitment in + match stored_commitment_hash with + | Some stored_commitment_hash + when Commitment.Hash.(new_commitment_hash = stored_commitment_hash) -> + action + | _ -> + let l2_block = + { + l2_block with + header = + { + l2_block.header with + commitment_hash = Some new_commitment_hash; + }; + } + in + Add (new_commitment, l2_block)) + | None, Some _stored_commitment_hash -> + let l2_block = + {l2_block with header = {l2_block.header with commitment_hash = None}} + in + Remove l2_block + in + let* () = + match action with + | Nothing -> return_unit + | Patch l2_block -> Node_context.save_l2_block node_ctxt l2_block + | Remove l2_block -> + Format.printf "Removing incorrect commitment for level %ld@." level ; + Node_context.save_l2_block node_ctxt l2_block + | Add (new_commitment, l2_block) -> + let* new_commitment_hash = + Node_context.save_commitment node_ctxt new_commitment + in + Format.printf + "Registering new commitment %a for level %ld@." + Commitment.Hash.pp + new_commitment_hash + new_commitment.inbox_level ; + Node_context.save_l2_block node_ctxt l2_block + in + return action + +(** Fixes commitments for inbox levels between [first_level] and the current + head. *) +let fix_commitments node_ctxt first_level = + let open Lwt_result_syntax in + Format.printf "Fixing commitments starting at level %ld@." first_level ; + let* l2_head = Node_context.last_processed_head_opt node_ctxt in + let l2_head = WithExceptions.Option.get ~loc:__LOC__ l2_head in + let*? () = + if first_level > l2_head.header.level then + error_with + "First level to fix commitments is %ld but head is at %ld" + first_level + l2_head.header.level + else Ok () + in + let levels = + Stdlib.List.init + (Int32.to_int (Int32.sub l2_head.header.level first_level) + 1) + (fun x -> Int32.add (Int32.of_int x) first_level) + in + let* (removed, added, patched), last = + List.fold_left_es + (fun ((removed, added, patched), _last) level -> + let+ action = fix_commitment node_ctxt level in + let counters = + match action with + | Nothing -> (removed, added, patched) + | Remove _ -> (removed + 1, added, patched) + | Add _ -> (removed, added + 1, patched) + | Patch _ -> (removed, added, patched + 1) + in + (counters, action)) + ((0, 0, 0), Nothing) + levels + in + let* () = + match last with + | (Add (_, b) | Remove b | Patch b) + when b.header.level = l2_head.header.level -> + Node_context.set_l2_head node_ctxt b + | _ -> return_unit + in + Format.printf + "Completed!\n\ + Removed commitments: %3d\n\ + Added commitments: %5d\n\ + Patched blocks: %8d@." + removed + added + patched ; + return_unit + +(** Fixes commitments for [protocol] (and the following ones) up to the current + head. *) +let fix_commitments_for_protocol node_ctxt protocol = + let open Lwt_result_syntax in + Format.printf "Fixing commitments for protocol %a@." Protocol_hash.pp protocol ; + let* act_level = Node_context.protocol_activation_level node_ctxt protocol in + let first_level = + match act_level with + | First_known l -> l + | Activation_level l -> Int32.succ l + in + fix_commitments node_ctxt first_level + +let command_fix_commitments_for_protocol cctxt ~data_dir protocol = + Snapshots.with_modify_data_dir cctxt ~data_dir @@ fun node_ctxt ~head:_ -> + fix_commitments_for_protocol node_ctxt protocol + +let repair_commitments_command = + let open Tezos_clic in + command + ~group + ~desc:"Repair commitments of L2 chain for a given protocol." + (args1 Cli.data_dir_arg) + (prefixes ["repair"; "commitments"; "for"] + @@ Cli.protocol_hash_param @@ stop) + (fun data_dir protocol cctxt -> + command_fix_commitments_for_protocol cctxt ~data_dir protocol) + +(** Commands exported by the [Repair] module. *) +let commands = [repair_commitments_command] diff --git a/src/lib_smart_rollup_node/repair.mli b/src/lib_smart_rollup_node/repair.mli new file mode 100644 index 000000000000..4518f7b0a955 --- /dev/null +++ b/src/lib_smart_rollup_node/repair.mli @@ -0,0 +1,9 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2024 Functori *) +(* *) +(*****************************************************************************) + +(** Repair commands offered by the rollup node. *) +val commands : Client_context.full Tezos_clic.command list -- GitLab From c0e6bdc67005452d7323479e49f6f39744a2b2b0 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Thu, 6 Jun 2024 15:24:10 +0200 Subject: [PATCH 4/4] Doc: changelog (cherry picked from commit 0a897e88621b8cba9c357221d7c86b5427175e3f) --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 586c2b24b05c..522e9798f11c 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -217,6 +217,9 @@ Smart Rollup node - Fix a bug in how commitments are computed after a protocol migration where the the commitment period changes. (MR :gl:`!13588`) +- New command ``repair commitments`` which allows the rollup node to recompute + correct commitments for a protocol upgrade which did not. (MR :gl:`!13615`) + Smart Rollup WASM Debugger -------------------------- -- GitLab