From 4cf7b13c29fb21a6f145e530162d643ef0587cbe 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 --- 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 02268186d5f6..d51a404ee07c 100644 --- a/src/lib_smart_rollup_node/publisher.ml +++ b/src/lib_smart_rollup_node/publisher.ml @@ -209,11 +209,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 @@ -232,6 +227,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 4c1aa371c2ed..fe20a7c22183 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 a40ecda12a88068df0f1a7b755a8185f7b3bee93 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 --- src/lib_smart_rollup_node/snapshots.mli | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/lib_smart_rollup_node/snapshots.mli b/src/lib_smart_rollup_node/snapshots.mli index 3a10139ac526..0c87de553055 100644 --- a/src/lib_smart_rollup_node/snapshots.mli +++ b/src/lib_smart_rollup_node/snapshots.mli @@ -65,3 +65,19 @@ val import : val info : snapshot_file:string -> Snapshot_utils.snapshot_metadata * [`Compressed | `Uncompressed] + +(** [with_modify_data_dir cctxt ~data_dir ~apply_unsafe_patches ?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 -> + apply_unsafe_patches:bool -> + ?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 7497840ad1e5a50cdad82f75c5afe8847fb45358 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 --- .../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 aca9c73bc74c..3a0f6de845ce 100644 --- a/src/bin_smart_rollup_node/main_smart_rollup_node.ml +++ b/src/bin_smart_rollup_node/main_smart_rollup_node.ml @@ -541,6 +541,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..d5fae7b2b7d2 --- /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 ~apply_unsafe_patches:false + @@ 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 0a897e88621b8cba9c357221d7c86b5427175e3f 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 --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index fb1c91670a99..bcdbb639b5f4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -129,6 +129,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