From 87837e18a5117759e135b0762a545a2da8bc0747 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Mon, 9 Jan 2023 10:10:59 +0100 Subject: [PATCH 1/6] SCORU/Node: accuser mode configuration --- src/proto_alpha/bin_sc_rollup_node/configuration.ml | 8 +++++++- src/proto_alpha/bin_sc_rollup_node/configuration.mli | 2 ++ src/proto_alpha/bin_sc_rollup_node/node_context.ml | 5 +++++ src/proto_alpha/bin_sc_rollup_node/node_context.mli | 6 ++++++ 4 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/configuration.ml b/src/proto_alpha/bin_sc_rollup_node/configuration.ml index a599a2cf6f92..56a722c5556a 100644 --- a/src/proto_alpha/bin_sc_rollup_node/configuration.ml +++ b/src/proto_alpha/bin_sc_rollup_node/configuration.ml @@ -26,7 +26,7 @@ open Protocol.Alpha_context -type mode = Observer | Batcher | Maintenance | Operator | Custom +type mode = Observer | Accuser | Batcher | Maintenance | Operator | Custom type purpose = Publish | Add_messages | Cement | Timeout | Refute @@ -391,6 +391,7 @@ let modes = [Observer; Batcher; Maintenance; Operator; Custom] let string_of_mode = function | Observer -> "observer" + | Accuser -> "accuser" | Batcher -> "batcher" | Maintenance -> "maintenance" | Operator -> "operator" @@ -398,6 +399,7 @@ let string_of_mode = function let mode_of_string = function | "observer" -> Ok Observer + | "accuser" -> Ok Accuser | "batcher" -> Ok Batcher | "maintenance" -> Ok Maintenance | "operator" -> Ok Operator @@ -406,6 +408,8 @@ let mode_of_string = function let description_of_mode = function | Observer -> "Only follows the chain, reconstructs and interprets inboxes" + | Accuser -> + "Only publishes commitments for conflicts and play refutation games" | Batcher -> "Accepts transactions in its queue and batches them on the L1 (TODO)" | Maintenance -> @@ -419,6 +423,7 @@ let mode_encoding = Data_encoding.string_enum [ ("observer", Observer); + ("accuser", Accuser); ("batcher", Batcher); ("maintenance", Maintenance); ("operator", Operator); @@ -606,6 +611,7 @@ let check_mode config = match config.mode with | Observer -> narrow_purposes [] | Batcher -> narrow_purposes [Add_messages] + | Accuser -> narrow_purposes [Publish; Refute] | Maintenance -> narrow_purposes [Publish; Cement; Refute] | Operator -> narrow_purposes [Publish; Cement; Add_messages; Refute] | Custom -> return config diff --git a/src/proto_alpha/bin_sc_rollup_node/configuration.mli b/src/proto_alpha/bin_sc_rollup_node/configuration.mli index 73237ec9044b..906be2d80421 100644 --- a/src/proto_alpha/bin_sc_rollup_node/configuration.mli +++ b/src/proto_alpha/bin_sc_rollup_node/configuration.mli @@ -27,6 +27,8 @@ (** Mode for the rollup node *) type mode = | Observer (** Only follows the chain and reconstructs inboxes *) + | Accuser + (** Only publishes commitments for conflicts and play refutation games *) | Batcher (** Accept transactions in its queue and batches them on the L1 *) | Maintenance (** Follows the chain and publishes commitments *) | Operator (** Equivalent to maintenance + batcher *) diff --git a/src/proto_alpha/bin_sc_rollup_node/node_context.ml b/src/proto_alpha/bin_sc_rollup_node/node_context.ml index c7d2d10a0fac..553e4ae7d5dc 100644 --- a/src/proto_alpha/bin_sc_rollup_node/node_context.ml +++ b/src/proto_alpha/bin_sc_rollup_node/node_context.ml @@ -36,6 +36,7 @@ type 'a t = { data_dir : string; l1_ctxt : Layer1.t; rollup_address : Sc_rollup.t; + mode : Configuration.mode; operators : Configuration.operators; genesis_info : Sc_rollup.Commitment.genesis_info; injector_retention_period : int; @@ -62,6 +63,8 @@ let is_operator node_ctxt pkh = (fun _ operator -> Signature.Public_key_hash.(operator = pkh)) node_ctxt.operators +let is_accuser {mode; _} = mode = Accuser + let get_fee_parameter node_ctxt purpose = Configuration.Operator_purpose_map.find purpose node_ctxt.fee_parameters |> Option.value ~default:(Configuration.default_fee_parameter ~purpose ()) @@ -116,6 +119,7 @@ let init (cctxt : Protocol_client_context.full) dal_cctxt ~data_dir mode { sc_rollup_address = rollup_address; sc_rollup_node_operators = operators; + mode = operating_mode; fee_parameters; loser_mode; _; @@ -141,6 +145,7 @@ let init (cctxt : Protocol_client_context.full) dal_cctxt ~data_dir mode data_dir; l1_ctxt; rollup_address; + mode = operating_mode; operators; genesis_info = l1_ctxt.Layer1.genesis_info; lcc; diff --git a/src/proto_alpha/bin_sc_rollup_node/node_context.mli b/src/proto_alpha/bin_sc_rollup_node/node_context.mli index 4b255f17e7ad..efffb7619227 100644 --- a/src/proto_alpha/bin_sc_rollup_node/node_context.mli +++ b/src/proto_alpha/bin_sc_rollup_node/node_context.mli @@ -42,6 +42,8 @@ type 'a t = { l1_ctxt : Layer1.t; (** Layer 1 context to fetch blocks and monitor heads, etc.*) rollup_address : Sc_rollup.t; (** Smart rollup tracked by the rollup node. *) + mode : Configuration.mode; + (** Mode of the node, see {!Configuration.mode}. *) operators : Configuration.operators; (** Addresses of the rollup node operators by purposes. *) genesis_info : Sc_rollup.Commitment.genesis_info; @@ -84,6 +86,10 @@ val get_operator : operator for the node (for any purpose). *) val is_operator : _ t -> Signature.Public_key_hash.t -> bool +(** [is_accuser node_ctxt] returns [true] if the rollup node runs in accuser + mode. *) +val is_accuser : _ t -> bool + (** [get_fee_parameter cctxt purpose] returns the fee parameter to inject an operation for a given [purpose]. If no specific fee parameters were configured for this purpose, returns the default fee parameter for this -- GitLab From 3aa7abf27c34d83fa0bd703d75964283b1f81c3d Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Mon, 9 Jan 2023 10:49:17 +0100 Subject: [PATCH 2/6] SCORU/Node: Accuser publishes potentially conflicting commitments Participation in refutation games is then automatic. --- .../bin_sc_rollup_node/commitment.ml | 22 +++++- .../bin_sc_rollup_node/commitment_sig.ml | 9 +++ src/proto_alpha/bin_sc_rollup_node/daemon.ml | 77 +++++++++++++------ .../refutation_game_event.ml | 20 +++++ 4 files changed, 103 insertions(+), 25 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/commitment.ml b/src/proto_alpha/bin_sc_rollup_node/commitment.ml index 264d970213bc..c23d02958c07 100644 --- a/src/proto_alpha/bin_sc_rollup_node/commitment.ml +++ b/src/proto_alpha/bin_sc_rollup_node/commitment.ml @@ -233,6 +233,21 @@ module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM = struct return_unit let publish_commitments (node_ctxt : _ Node_context.t) = + let open Lwt_result_syntax in + let operator = Node_context.get_operator node_ctxt Publish in + if Node_context.is_accuser node_ctxt then + (* Accuser does not publish all commitments *) + return_unit + else + match operator with + | None -> + (* Configured to not publish commitments *) + return_unit + | Some source -> + let*! commitments = missing_commitments node_ctxt in + List.iter_es (publish_commitment node_ctxt ~source) commitments + + let publish_single_commitment node_ctxt commitment_hash = let open Lwt_result_syntax in let operator = Node_context.get_operator node_ctxt Publish in match operator with @@ -240,8 +255,11 @@ module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM = struct (* Configured to not publish commitments *) return_unit | Some source -> - let*! commitments = missing_commitments node_ctxt in - List.iter_es (publish_commitment node_ctxt ~source) commitments + let* commitment = + Node_context.get_commitment node_ctxt commitment_hash + in + when_ (commitment.inbox_level > node_ctxt.lcc.level) @@ fun () -> + publish_commitment node_ctxt ~source commitment (* Commitments can only be cemented after [sc_rollup_challenge_window] has passed since they were first published. *) diff --git a/src/proto_alpha/bin_sc_rollup_node/commitment_sig.ml b/src/proto_alpha/bin_sc_rollup_node/commitment_sig.ml index 443930ced36d..3ea1022dc7a2 100644 --- a/src/proto_alpha/bin_sc_rollup_node/commitment_sig.ml +++ b/src/proto_alpha/bin_sc_rollup_node/commitment_sig.ml @@ -59,6 +59,15 @@ module type S = sig cemented commitment. *) val publish_commitments : _ Node_context.t -> unit tzresult Lwt.t + (** [publish_single_commitment node_ctxt commitment_hash] publishes a single + commitment, whose hash is [commitment_hash] if it is missing. This + function is meant to be used by the {e accuser} mode to sparingly publish + commitments when it detects a conflict. *) + val publish_single_commitment : + _ Node_context.t -> + Protocol.Alpha_context.Sc_rollup.Commitment.Hash.t -> + unit tzresult Lwt.t + (** [cement_commitments node_ctxt] cements the commitments that can be cemented, i.e. the commitments that are after the current last cemented commitment and which have [sc_rollup_challenge_period] levels on top of diff --git a/src/proto_alpha/bin_sc_rollup_node/daemon.ml b/src/proto_alpha/bin_sc_rollup_node/daemon.ml index b46e5b40eb66..9acf456b1041 100644 --- a/src/proto_alpha/bin_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/bin_sc_rollup_node/daemon.ml @@ -62,35 +62,66 @@ module Make (PVM : Pvm.S) = struct } in return_unit - | ( Sc_rollup_publish {commitment; _}, - Sc_rollup_publish_result {published_at_level; _} ) -> + | ( Sc_rollup_publish {commitment; rollup}, + Sc_rollup_publish_result + {published_at_level; staked_hash = their_commitment_hash; _} ) -> (* Commitment published by someone else *) - let commitment_hash = - Sc_rollup.Commitment.hash_uncarbonated commitment - in + (* We first register the publication information *) let*! known_commitment = - Node_context.commitment_exists node_ctxt commitment_hash + Node_context.commitment_exists node_ctxt their_commitment_hash in - if not known_commitment then return_unit - else - let*! republication = - Node_context.commitment_was_published - node_ctxt - ~source:Anyone - commitment_hash - in - if republication then return_unit + let* () = + if not known_commitment then return_unit else - let*! () = - Node_context.set_commitment_published_at_level + let*! republication = + Node_context.commitment_was_published node_ctxt - commitment_hash - { - first_published_at_level = published_at_level; - published_at_level = None; - } + ~source:Anyone + their_commitment_hash in - return_unit + if republication then return_unit + else + let*! () = + Node_context.set_commitment_published_at_level + node_ctxt + their_commitment_hash + { + first_published_at_level = published_at_level; + published_at_level = None; + } + in + return_unit + in + if Node_context.is_accuser node_ctxt then + (* We are seeing a commitment from someone else. We check if we agree + with it, otherwise the accuser publishes our commitment in order to + play the refutation game. *) + let* l2_block = + Node_context.get_l2_block_by_level + node_ctxt + (Raw_level.to_int32 commitment.inbox_level) + in + let our_commitment_hash = l2_block.header.commitment_hash in + match our_commitment_hash with + | Some our_commitment_hash + when Sc_rollup.Commitment.Hash.( + their_commitment_hash <> our_commitment_hash) -> + let*! () = + Refutation_game_event.potential_conflict_detected + ~our_commitment_hash + ~their_commitment_hash + ~level:commitment.inbox_level + ~other:source + in + assert (Sc_rollup.Address.(node_ctxt.rollup_address = rollup)) ; + (* TODO: https://gitlab.com/tezos/tezos/-/issues/4628 + Make the accuser node only publish directly refutable + commitments. *) + Components.Commitment.publish_single_commitment + node_ctxt + our_commitment_hash + | _ -> return_unit + else return_unit | Sc_rollup_cement {commitment; _}, Sc_rollup_cement_result {inbox_level; _} -> (* Cemented commitment ---------------------------------------------- *) diff --git a/src/proto_alpha/bin_sc_rollup_node/refutation_game_event.ml b/src/proto_alpha/bin_sc_rollup_node/refutation_game_event.ml index b7b96030d013..50a53811bd7f 100644 --- a/src/proto_alpha/bin_sc_rollup_node/refutation_game_event.ml +++ b/src/proto_alpha/bin_sc_rollup_node/refutation_game_event.ml @@ -68,6 +68,19 @@ module Simple = struct ("other", Sc_rollup.Staker.encoding) ("their_commitment_hash", Sc_rollup.Commitment.Hash.encoding) ("parent_commitment_hash", Sc_rollup.Commitment.Hash.encoding) + + let potential_conflict_detected = + declare_4 + ~name:"sc_rollup_node_potential_conflict_detected" + ~msg: + "A potential conflict has been found with our commitment \ + {our_commitment_hash} at level {level} with staker {other} that hash \ + issued commitment {their_commitment_hash}." + ~level:Notice + ("our_commitment_hash", Sc_rollup.Commitment.Hash.encoding) + ("level", Raw_level.encoding) + ("other", Sc_rollup.Staker.encoding) + ("their_commitment_hash", Sc_rollup.Commitment.Hash.encoding) end let timeout address = Simple.(emit timeout address) @@ -92,3 +105,10 @@ let conflict_detected (conflict : Sc_rollup.Refutation_storage.conflict) = other, their_commitment_hash, parent_commitment_hash )) + +let potential_conflict_detected ~our_commitment_hash ~their_commitment_hash + ~other ~level = + Simple.( + emit + potential_conflict_detected + (our_commitment_hash, level, other, their_commitment_hash)) -- GitLab From 16634f7decf2c38de15476e48a8707e1e18ef905 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Mon, 9 Jan 2023 11:17:14 +0100 Subject: [PATCH 3/6] Tezt/Tezos: accuser mode support for rollup node --- tezt/lib_tezos/sc_rollup_node.ml | 3 ++- tezt/lib_tezos/sc_rollup_node.mli | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tezt/lib_tezos/sc_rollup_node.ml b/tezt/lib_tezos/sc_rollup_node.ml index 6e1d8e0977cc..5abae0d24fa6 100644 --- a/tezt/lib_tezos/sc_rollup_node.ml +++ b/tezt/lib_tezos/sc_rollup_node.ml @@ -25,7 +25,7 @@ type 'a known = Unknown | Known of 'a -type mode = Batcher | Custom | Maintenance | Observer | Operator +type mode = Batcher | Custom | Maintenance | Observer | Operator | Accuser module Parameters = struct type persistent_state = { @@ -58,6 +58,7 @@ let string_of_mode = function | Maintenance -> "maintenance" | Operator -> "operator" | Custom -> "custom" + | Accuser -> "accuser" let check_error ?exit_code ?msg sc_node = match sc_node.status with diff --git a/tezt/lib_tezos/sc_rollup_node.mli b/tezt/lib_tezos/sc_rollup_node.mli index 0b5a08fa1329..743c8d8f383d 100644 --- a/tezt/lib_tezos/sc_rollup_node.mli +++ b/tezt/lib_tezos/sc_rollup_node.mli @@ -31,7 +31,7 @@ (** Smart contract rollup node states. *) type t -type mode = Batcher | Custom | Maintenance | Observer | Operator +type mode = Batcher | Custom | Maintenance | Observer | Operator | Accuser (** Create a smart contract rollup node. -- GitLab From 92686d12d79508c5d3cb33f5c129a7f7183ba6d4 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Mon, 9 Jan 2023 11:17:54 +0100 Subject: [PATCH 4/6] Test: test accuser mode wins refutation games --- tezt/tests/sc_rollup.ml | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index 2ac0d674e642..b6cc0d5ed42d 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -269,8 +269,9 @@ let wait_for_conflict_detected sc_node = A rollup node has a configuration file that must be initialized. *) -let setup_rollup ~protocol ~kind ?boot_sector ?(parameters_ty = "string") - ?(operator = Constant.bootstrap1.alias) tezos_node tezos_client = +let setup_rollup ~protocol ~kind ?(mode = Sc_rollup_node.Operator) ?boot_sector + ?(parameters_ty = "string") ?(operator = Constant.bootstrap1.alias) + tezos_node tezos_client = let* sc_rollup = originate_sc_rollup ~kind @@ -282,7 +283,7 @@ let setup_rollup ~protocol ~kind ?boot_sector ?(parameters_ty = "string") let sc_rollup_node = Sc_rollup_node.create ~protocol - Operator + mode tezos_node tezos_client ~default_operator:operator @@ -314,7 +315,7 @@ let test_l1_scenario ?regression ~kind ?boot_sector ?commitment_period let* sc_rollup = originate_sc_rollup ~kind ?boot_sector ~src tezos_client in scenario sc_rollup tezos_node tezos_client -let test_full_scenario ?regression ~kind ?boot_sector ?commitment_period +let test_full_scenario ?regression ~kind ?mode ?boot_sector ?commitment_period ?(parameters_ty = "string") ?challenge_window ?timeout {variant; tags; description} scenario = let tags = kind :: "rollup_node" :: tags in @@ -339,6 +340,7 @@ let test_full_scenario ?regression ~kind ?boot_sector ?commitment_period ~protocol ~parameters_ty ~kind + ?mode ?boot_sector tezos_node tezos_client @@ -2645,16 +2647,17 @@ let test_consecutive_commitments _protocol _rollup_node _rollup_client sc_rollup its deposit while the honest one has not. *) -let test_refutation_scenario ?commitment_period ?challenge_window ~variant ~kind - (loser_modes, inputs, final_level, empty_levels, stop_loser_at) = +let test_refutation_scenario ?commitment_period ?challenge_window ~variant ~mode + ~kind (loser_modes, inputs, final_level, empty_levels, stop_loser_at) = test_full_scenario ?commitment_period ~kind + ~mode ~timeout:60 ?challenge_window { - tags = ["refutation"]; - variant = Some variant; + tags = (["refutation"] @ if mode = Accuser then ["accuser"] else []); + variant = Some (variant ^ if mode = Accuser then "+accuser" else ""); description = "refutation games winning strategies"; } @@ fun protocol sc_rollup_node sc_client1 sc_rollup_address node client -> @@ -2840,6 +2843,7 @@ let test_refutation protocols ~kind = (fun (variant, inputs) -> test_refutation_scenario ~kind + ~mode:Operator ~challenge_window ~commitment_period ~variant @@ -2847,6 +2851,17 @@ let test_refutation protocols ~kind = protocols) tests +(** Run one of the refutation tests with an accuser instead of a full operator. *) +let test_accuser protocols = + test_refutation_scenario + ~kind:"wasm_2_0_0" + ~mode:Accuser + ~challenge_window:10 + ~commitment_period:10 + ~variant:"pvm_proof_2" + (["7 7 22_000_002_000"], inputs_for 10, 80, [], []) + protocols + (** Helper to check that the operation whose hash is given is successfully included (applied) in the current head block. *) let check_op_included = @@ -4202,6 +4217,7 @@ let register ~protocols = ~kind:"wasm_2_0_0" ~kernel_name:"no_parse_bad_fingerprint" ~internal:false ; + test_accuser protocols ; (* Shared tezts - will be executed for both PVMs. *) register ~kind:"wasm_2_0_0" ~protocols ; register ~kind:"arith" ~protocols -- GitLab From 1d16a0ba610f5914fc0c2879cf03d91342de2f92 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Mon, 9 Jan 2023 11:24:29 +0100 Subject: [PATCH 5/6] Doc/SCORU: document accuser mode --- docs/alpha/smart_rollups.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/alpha/smart_rollups.rst b/docs/alpha/smart_rollups.rst index d019ad433fef..68785c28c38c 100644 --- a/docs/alpha/smart_rollups.rst +++ b/docs/alpha/smart_rollups.rst @@ -490,6 +490,11 @@ In addition, a rollup node can run under different modes: #. ``maintenance`` is the same as the operator mode except that it does not include the message batching service. +#. ``accuser`` follows the layer1-chain and computes commitments but does not + publish them. Only when a conflicting commitment (published by another + staker) is detected will the "accuser node" publish a commitment and + participate in the subsequent refutation game. + The following table summarizes the operation modes, focusing on the L1 operations which are injected by the rollup node in each mode. @@ -504,6 +509,11 @@ operations which are injected by the rollup node in each mode. +-------------+--------------+----------+--------+--------+ | Maintenance | No | Yes | Yes | Yes | +-------------+--------------+----------+--------+--------+ +| Accuser | No | Yes [*]_ | No | Yes | ++-------------+--------------+----------+--------+--------+ + +.. [*] An accuser node will publish commitments only when it detects + conflicts; for such cases it must make a deposit of 10,000 tez. Second, the configured rollup node can be run: -- GitLab From 01845848be0ba9f9a6fee7955353066436aa0ce1 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Mon, 9 Jan 2023 10:10:59 +0100 Subject: [PATCH 6/6] SCORU/Node: backport !7298 (accuser) - SCORU/Node: accuser mode configuration - SCORU/Node: Accuser publishes potentially conflicting commitments - Doc/SCORU: document accuser mode --- docs/mumbai/smart_rollups.rst | 10 +++ .../bin_sc_rollup_node/commitment.ml | 22 +++++- .../bin_sc_rollup_node/commitment_sig.ml | 9 +++ .../bin_sc_rollup_node/configuration.ml | 8 +- .../bin_sc_rollup_node/configuration.mli | 2 + .../bin_sc_rollup_node/daemon.ml | 77 +++++++++++++------ .../bin_sc_rollup_node/node_context.ml | 5 ++ .../bin_sc_rollup_node/node_context.mli | 6 ++ .../refutation_game_event.ml | 20 +++++ 9 files changed, 133 insertions(+), 26 deletions(-) diff --git a/docs/mumbai/smart_rollups.rst b/docs/mumbai/smart_rollups.rst index 8dc965e4251a..f9241f15729d 100644 --- a/docs/mumbai/smart_rollups.rst +++ b/docs/mumbai/smart_rollups.rst @@ -482,6 +482,11 @@ In addition, a rollup node can run under different modes: #. ``maintenance`` is the same as the operator mode except that it does not include the message batching service. +#. ``accuser`` follows the layer1-chain and computes commitments but does not + publish them. Only when a conflicting commitment (published by another + staker) is detected will the "accuser node" publish a commitment and + participate in the subsequent refutation game. + The following table summarizes the operation modes, focusing on the L1 operations which are injected by the rollup node in each mode. @@ -496,6 +501,11 @@ operations which are injected by the rollup node in each mode. +-------------+--------------+----------+--------+--------+ | Maintenance | No | Yes | Yes | Yes | +-------------+--------------+----------+--------+--------+ +| Accuser | No | Yes [*]_ | No | Yes | ++-------------+--------------+----------+--------+--------+ + +.. [*] An accuser node will publish commitments only when it detects + conflicts; for such cases it must make a deposit of 10,000 tez. Second, the configured rollup node can be run: diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment.ml index 264d970213bc..c23d02958c07 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment.ml @@ -233,6 +233,21 @@ module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM = struct return_unit let publish_commitments (node_ctxt : _ Node_context.t) = + let open Lwt_result_syntax in + let operator = Node_context.get_operator node_ctxt Publish in + if Node_context.is_accuser node_ctxt then + (* Accuser does not publish all commitments *) + return_unit + else + match operator with + | None -> + (* Configured to not publish commitments *) + return_unit + | Some source -> + let*! commitments = missing_commitments node_ctxt in + List.iter_es (publish_commitment node_ctxt ~source) commitments + + let publish_single_commitment node_ctxt commitment_hash = let open Lwt_result_syntax in let operator = Node_context.get_operator node_ctxt Publish in match operator with @@ -240,8 +255,11 @@ module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM = struct (* Configured to not publish commitments *) return_unit | Some source -> - let*! commitments = missing_commitments node_ctxt in - List.iter_es (publish_commitment node_ctxt ~source) commitments + let* commitment = + Node_context.get_commitment node_ctxt commitment_hash + in + when_ (commitment.inbox_level > node_ctxt.lcc.level) @@ fun () -> + publish_commitment node_ctxt ~source commitment (* Commitments can only be cemented after [sc_rollup_challenge_window] has passed since they were first published. *) diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment_sig.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment_sig.ml index 443930ced36d..3ea1022dc7a2 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment_sig.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/commitment_sig.ml @@ -59,6 +59,15 @@ module type S = sig cemented commitment. *) val publish_commitments : _ Node_context.t -> unit tzresult Lwt.t + (** [publish_single_commitment node_ctxt commitment_hash] publishes a single + commitment, whose hash is [commitment_hash] if it is missing. This + function is meant to be used by the {e accuser} mode to sparingly publish + commitments when it detects a conflict. *) + val publish_single_commitment : + _ Node_context.t -> + Protocol.Alpha_context.Sc_rollup.Commitment.Hash.t -> + unit tzresult Lwt.t + (** [cement_commitments node_ctxt] cements the commitments that can be cemented, i.e. the commitments that are after the current last cemented commitment and which have [sc_rollup_challenge_period] levels on top of diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/configuration.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/configuration.ml index 92e338776b9c..d966625f7806 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/configuration.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/configuration.ml @@ -26,7 +26,7 @@ open Protocol.Alpha_context -type mode = Observer | Batcher | Maintenance | Operator | Custom +type mode = Observer | Accuser | Batcher | Maintenance | Operator | Custom type purpose = Publish | Add_messages | Cement | Timeout | Refute @@ -392,6 +392,7 @@ let modes = [Observer; Batcher; Maintenance; Operator; Custom] let string_of_mode = function | Observer -> "observer" + | Accuser -> "accuser" | Batcher -> "batcher" | Maintenance -> "maintenance" | Operator -> "operator" @@ -399,6 +400,7 @@ let string_of_mode = function let mode_of_string = function | "observer" -> Ok Observer + | "accuser" -> Ok Accuser | "batcher" -> Ok Batcher | "maintenance" -> Ok Maintenance | "operator" -> Ok Operator @@ -407,6 +409,8 @@ let mode_of_string = function let description_of_mode = function | Observer -> "Only follows the chain, reconstructs and interprets inboxes" + | Accuser -> + "Only publishes commitments for conflicts and play refutation games" | Batcher -> "Accepts transactions in its queue and batches them on the L1 (TODO)" | Maintenance -> @@ -420,6 +424,7 @@ let mode_encoding = Data_encoding.string_enum [ ("observer", Observer); + ("accuser", Accuser); ("batcher", Batcher); ("maintenance", Maintenance); ("operator", Operator); @@ -607,6 +612,7 @@ let check_mode config = match config.mode with | Observer -> narrow_purposes [] | Batcher -> narrow_purposes [Add_messages] + | Accuser -> narrow_purposes [Publish; Refute] | Maintenance -> narrow_purposes [Publish; Cement; Refute] | Operator -> narrow_purposes [Publish; Cement; Add_messages; Refute] | Custom -> return config diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/configuration.mli b/src/proto_016_PtMumbai/bin_sc_rollup_node/configuration.mli index 22058666b750..0248dac64fe3 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/configuration.mli +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/configuration.mli @@ -27,6 +27,8 @@ (** Mode for the rollup node *) type mode = | Observer (** Only follows the chain and reconstructs inboxes *) + | Accuser + (** Only publishes commitments for conflicts and play refutation games *) | Batcher (** Accept transactions in its queue and batches them on the L1 *) | Maintenance (** Follows the chain and publishes commitments *) | Operator (** Equivalent to maintenance + batcher *) diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/daemon.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/daemon.ml index 6e1284961044..dca3913d4b50 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/daemon.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/daemon.ml @@ -62,35 +62,66 @@ module Make (PVM : Pvm.S) = struct } in return_unit - | ( Sc_rollup_publish {commitment; _}, - Sc_rollup_publish_result {published_at_level; _} ) -> + | ( Sc_rollup_publish {commitment; rollup}, + Sc_rollup_publish_result + {published_at_level; staked_hash = their_commitment_hash; _} ) -> (* Commitment published by someone else *) - let commitment_hash = - Sc_rollup.Commitment.hash_uncarbonated commitment - in + (* We first register the publication information *) let*! known_commitment = - Node_context.commitment_exists node_ctxt commitment_hash + Node_context.commitment_exists node_ctxt their_commitment_hash in - if not known_commitment then return_unit - else - let*! republication = - Node_context.commitment_was_published - node_ctxt - ~source:Anyone - commitment_hash - in - if republication then return_unit + let* () = + if not known_commitment then return_unit else - let*! () = - Node_context.set_commitment_published_at_level + let*! republication = + Node_context.commitment_was_published node_ctxt - commitment_hash - { - first_published_at_level = published_at_level; - published_at_level = None; - } + ~source:Anyone + their_commitment_hash in - return_unit + if republication then return_unit + else + let*! () = + Node_context.set_commitment_published_at_level + node_ctxt + their_commitment_hash + { + first_published_at_level = published_at_level; + published_at_level = None; + } + in + return_unit + in + if Node_context.is_accuser node_ctxt then + (* We are seeing a commitment from someone else. We check if we agree + with it, otherwise the accuser publishes our commitment in order to + play the refutation game. *) + let* l2_block = + Node_context.get_l2_block_by_level + node_ctxt + (Raw_level.to_int32 commitment.inbox_level) + in + let our_commitment_hash = l2_block.header.commitment_hash in + match our_commitment_hash with + | Some our_commitment_hash + when Sc_rollup.Commitment.Hash.( + their_commitment_hash <> our_commitment_hash) -> + let*! () = + Refutation_game_event.potential_conflict_detected + ~our_commitment_hash + ~their_commitment_hash + ~level:commitment.inbox_level + ~other:source + in + assert (Sc_rollup.Address.(node_ctxt.rollup_address = rollup)) ; + (* TODO: https://gitlab.com/tezos/tezos/-/issues/4628 + Make the accuser node only publish directly refutable + commitments. *) + Components.Commitment.publish_single_commitment + node_ctxt + our_commitment_hash + | _ -> return_unit + else return_unit | Sc_rollup_cement {commitment; _}, Sc_rollup_cement_result {inbox_level; _} -> (* Cemented commitment ---------------------------------------------- *) diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/node_context.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/node_context.ml index baaa4fd6a1eb..84d819ffdff5 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/node_context.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/node_context.ml @@ -36,6 +36,7 @@ type 'a t = { data_dir : string; l1_ctxt : Layer1.t; rollup_address : Sc_rollup.t; + mode : Configuration.mode; operators : Configuration.operators; genesis_info : Sc_rollup.Commitment.genesis_info; injector_retention_period : int; @@ -62,6 +63,8 @@ let is_operator node_ctxt pkh = (fun _ operator -> Tezos_crypto.Signature.Public_key_hash.(operator = pkh)) node_ctxt.operators +let is_accuser {mode; _} = mode = Accuser + let get_fee_parameter node_ctxt purpose = Configuration.Operator_purpose_map.find purpose node_ctxt.fee_parameters |> Option.value ~default:(Configuration.default_fee_parameter ~purpose ()) @@ -116,6 +119,7 @@ let init (cctxt : Protocol_client_context.full) dal_cctxt ~data_dir mode { sc_rollup_address = rollup_address; sc_rollup_node_operators = operators; + mode = operating_mode; fee_parameters; loser_mode; _; @@ -141,6 +145,7 @@ let init (cctxt : Protocol_client_context.full) dal_cctxt ~data_dir mode data_dir; l1_ctxt; rollup_address; + mode = operating_mode; operators; genesis_info = l1_ctxt.Layer1.genesis_info; lcc; diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/node_context.mli b/src/proto_016_PtMumbai/bin_sc_rollup_node/node_context.mli index 83fdb0420a28..809233e1a55c 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/node_context.mli +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/node_context.mli @@ -42,6 +42,8 @@ type 'a t = { l1_ctxt : Layer1.t; (** Layer 1 context to fetch blocks and monitor heads, etc.*) rollup_address : Sc_rollup.t; (** Smart rollup tracked by the rollup node. *) + mode : Configuration.mode; + (** Mode of the node, see {!Configuration.mode}. *) operators : Configuration.operators; (** Addresses of the rollup node operators by purposes. *) genesis_info : Sc_rollup.Commitment.genesis_info; @@ -86,6 +88,10 @@ val get_operator : operator for the node (for any purpose). *) val is_operator : _ t -> Tezos_crypto.Signature.Public_key_hash.t -> bool +(** [is_accuser node_ctxt] returns [true] if the rollup node runs in accuser + mode. *) +val is_accuser : _ t -> bool + (** [get_fee_parameter cctxt purpose] returns the fee parameter to inject an operation for a given [purpose]. If no specific fee parameters were configured for this purpose, returns the default fee parameter for this diff --git a/src/proto_016_PtMumbai/bin_sc_rollup_node/refutation_game_event.ml b/src/proto_016_PtMumbai/bin_sc_rollup_node/refutation_game_event.ml index 996b63ed080a..a836a38daff8 100644 --- a/src/proto_016_PtMumbai/bin_sc_rollup_node/refutation_game_event.ml +++ b/src/proto_016_PtMumbai/bin_sc_rollup_node/refutation_game_event.ml @@ -68,6 +68,19 @@ module Simple = struct ("other", Sc_rollup.Staker.encoding) ("their_commitment_hash", Sc_rollup.Commitment.Hash.encoding) ("parent_commitment_hash", Sc_rollup.Commitment.Hash.encoding) + + let potential_conflict_detected = + declare_4 + ~name:"sc_rollup_node_potential_conflict_detected" + ~msg: + "A potential conflict has been found with our commitment \ + {our_commitment_hash} at level {level} with staker {other} that hash \ + issued commitment {their_commitment_hash}." + ~level:Notice + ("our_commitment_hash", Sc_rollup.Commitment.Hash.encoding) + ("level", Raw_level.encoding) + ("other", Sc_rollup.Staker.encoding) + ("their_commitment_hash", Sc_rollup.Commitment.Hash.encoding) end let timeout address = Simple.(emit timeout address) @@ -92,3 +105,10 @@ let conflict_detected (conflict : Sc_rollup.Refutation_storage.conflict) = other, their_commitment_hash, parent_commitment_hash )) + +let potential_conflict_detected ~our_commitment_hash ~their_commitment_hash + ~other ~level = + Simple.( + emit + potential_conflict_detected + (our_commitment_hash, level, other, their_commitment_hash)) -- GitLab