From b7ddd2a5d2b38f580df42f5f69c3321cd1505670 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Mon, 13 Jun 2022 21:43:07 +0200 Subject: [PATCH 01/43] Proto,SCORU: Fix bug in rollup node When the queue of heads to be processed is not empty and the rollup node is terminated, the heads in the queue are never processed when the rollup node is restarted. This commit fixes the issue by remembering the processed heads and to include unprocessed heads in the intermediate heads to catch up with the mainnet heads. Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/bin_sc_rollup_node/daemon.ml | 16 +++-- src/proto_alpha/bin_sc_rollup_node/layer1.ml | 71 ++++++++++++++++--- src/proto_alpha/bin_sc_rollup_node/layer1.mli | 10 ++- src/proto_alpha/bin_sc_rollup_node/store.ml | 2 + 4 files changed, 86 insertions(+), 13 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/daemon.ml b/src/proto_alpha/bin_sc_rollup_node/daemon.ml index 7be120b5f4f2..8a4b50cb1ca0 100644 --- a/src/proto_alpha/bin_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/bin_sc_rollup_node/daemon.ml @@ -89,7 +89,12 @@ module Make (PVM : Pvm.S) = struct the current head, but we still need to ensure that the node only published one commitment per block. *) let* () = Components.Commitment.publish_commitment node_ctxt store in - Components.Commitment.cement_commitment_if_possible node_ctxt store head + let* () = + Components.Commitment.cement_commitment_if_possible node_ctxt store head + in + when_ finalized (fun () -> + let*! () = Layer1.mark_processed_head store head in + return ()) (* [on_layer_1_chain_event node_ctxt store chain_event old_heads] processes a list of heads, coming from either a list of [old_heads] or from the current @@ -187,9 +192,12 @@ module Make (PVM : Pvm.S) = struct @@ iter_stream layer_1_chain_events @@ on_layer_1_chain_event node_ctxt store - let install_finalizer store rpc_server = + let install_finalizer store rpc_server heads stopper = let open Lwt_syntax in Lwt_exit.register_clean_up_callback ~loc:__LOC__ @@ fun exit_status -> + stopper () ; + let* () = Lwt_stream.closed heads in + let* () = Layer1.shutdown store in let* () = Components.RPC_server.shutdown rpc_server in let* () = Store.close store in let* () = Event.shutdown_node exit_status in @@ -201,13 +209,13 @@ module Make (PVM : Pvm.S) = struct let* rpc_server = Components.RPC_server.start node_ctxt store configuration in - let* tezos_heads = + let* tezos_heads, stopper = Layer1.start configuration node_ctxt.Node_context.cctxt store in let*! () = Inbox.start () in let*! () = Components.Commitment.start () in - let _ = install_finalizer store rpc_server in + let _ = install_finalizer store rpc_server tezos_heads stopper in let*! () = Event.node_is_ready ~rpc_addr:configuration.rpc_addr diff --git a/src/proto_alpha/bin_sc_rollup_node/layer1.ml b/src/proto_alpha/bin_sc_rollup_node/layer1.ml index 75374bc0ffd8..4b99d823ad6d 100644 --- a/src/proto_alpha/bin_sc_rollup_node/layer1.ml +++ b/src/proto_alpha/bin_sc_rollup_node/layer1.ml @@ -94,17 +94,50 @@ module State = struct let value_encoding = head_encoding end) - end - let already_seen = Store.Blocks.mem + module ProcessedHashes = Store.Make_append_only_map (struct + let path = ["tezos"; "processed_blocks"] + + let keep_last_n_entries_in_memory = reorganization_window_length + + type key = block_hash + + let string_of_key = Block_hash.to_b58check + + type value = unit + + let value_encoding = Data_encoding.unit + end) + + module LastProcessedHead = Store.Make_mutable_value (struct + let path = ["tezos"; "processed_head"] + + type value = head + + let value_encoding = head_encoding + end) + end let last_seen_head = Store.Head.find let set_new_head = Store.Head.set - let store_block = Store.Blocks.add + let store_block store hash block = + let open Lwt_syntax in + let* already_exists = Store.Blocks.mem store hash in + if not already_exists then Store.Blocks.add store hash block else return () let block_of_hash = Store.Blocks.get + + let mark_processed_head store (Head {hash; _} as head) = + let open Lwt_syntax in + let* () = Store.ProcessedHashes.add store hash () in + let* () = Store.LastProcessedHead.set store head in + return () + + let is_processed = Store.ProcessedHashes.mem + + let last_processed_head = Store.LastProcessedHead.find end (** @@ -236,7 +269,7 @@ let catch_up cctxt store chain last_seen_head new_head = (* We have reconnected to the last seen head. *) Lwt.return (ancestor_hash, [same_branch new_head heads]) else - State.already_seen store ancestor_hash >>= function + State.is_processed store ancestor_hash >>= function | true -> (* We have reconnected to a previously known head. [new_head] and [last_seen_head] are not the same branch. *) @@ -274,8 +307,10 @@ let chain_events cctxt store chain = let*! () = List.iter_s (store_chain_event store base) events in Lwt.return events in - let+ heads, _ = Tezos_shell_services.Monitor_services.heads cctxt chain in - Lwt_stream.map_list_s on_head heads + let+ heads, stopper = + Tezos_shell_services.Monitor_services.heads cctxt chain + in + (Lwt_stream.map_list_s on_head heads, stopper) let check_sc_rollup_address_exists sc_rollup_address (cctxt : Protocol_client_context.full) = @@ -324,9 +359,9 @@ let start configuration (cctxt : Protocol_client_context.full) store = let* () = check_sc_rollup_address_exists configuration.sc_rollup_address cctxt in - let* event_stream = chain_events cctxt store `Main in + let* event_stream, stopper = chain_events cctxt store `Main in let* info = gather_info cctxt configuration.sc_rollup_address in - return (discard_pre_origination_blocks info event_stream) + return (discard_pre_origination_blocks info event_stream, stopper) let current_head_hash store = let open Lwt_syntax in @@ -350,3 +385,23 @@ let processed = function | SameBranch {new_head; intermediate_heads} -> List.iter_s processed_head (intermediate_heads @ [new_head]) | Rollback {new_head} -> processed_head new_head + +let mark_processed_head store head = State.mark_processed_head store head + +(* We forget about the last seen heads that are not processed so that + the rollup node can process them when restarted. Notice that this + does prevent skipping heads when the node is interrupted in a bad + way. *) + +(* FIXME: https://gitlab.com/tezos/tezos/-/issues/3205 + + More generally, The rollup node should be able to restart properly + after an abnormal interruption at every point of its process. + Currently, the state is not persistent enough and the processing is + not idempotent enough to achieve that property. *) +let shutdown store = + let open Lwt_syntax in + let* last_processed_head = State.last_processed_head store in + match last_processed_head with + | None -> return_unit + | Some head -> State.set_new_head store head diff --git a/src/proto_alpha/bin_sc_rollup_node/layer1.mli b/src/proto_alpha/bin_sc_rollup_node/layer1.mli index 27100c6a4030..8ea49d73bcde 100644 --- a/src/proto_alpha/bin_sc_rollup_node/layer1.mli +++ b/src/proto_alpha/bin_sc_rollup_node/layer1.mli @@ -54,7 +54,7 @@ val start : Configuration.t -> Protocol_client_context.full -> Store.t -> - chain_event Lwt_stream.t tzresult Lwt.t + (chain_event Lwt_stream.t * RPC_context.stopper) tzresult Lwt.t (** [current_head_hash store] is the current hash of the head of the Tezos chain as far as the smart-contract rollup node knows from the @@ -77,3 +77,11 @@ val genesis_hash : Block_hash.t (** [processed chain_event] emits a log event to officialize the processing of some layer 1 [chain_event]. *) val processed : chain_event -> unit Lwt.t + +(** [mark_process_head store head] remembers that the [head] + is processed. The system should not have to come back to + it. *) +val mark_processed_head : Store.t -> head -> unit Lwt.t + +(** [shutdown store] properly shut the layer 1 down. *) +val shutdown : Store.t -> unit Lwt.t diff --git a/src/proto_alpha/bin_sc_rollup_node/store.ml b/src/proto_alpha/bin_sc_rollup_node/store.ml index e49a8b3d31e5..effda8c917f9 100644 --- a/src/proto_alpha/bin_sc_rollup_node/store.ml +++ b/src/proto_alpha/bin_sc_rollup_node/store.ml @@ -43,6 +43,8 @@ let load configuration = let* repo = IStore.Repo.v (Irmin_pack.config configuration.data_dir) in IStore.main repo +let flush store = IStore.flush (IStore.repo store) + let close store = IStore.Repo.close (IStore.repo store) let info message = -- GitLab From 8327c624cdaab2e3319c537046218a79ead176bd Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 14 Jun 2022 09:06:19 +0200 Subject: [PATCH 02/43] Proto,SCORU: Relax the insertion policy of append-only maps To be robust to interruption in the middle of processes, we accept to redo some work when we restart the node. Hence, it is fine to insert twice the same value for a Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/bin_sc_rollup_node/store.ml | 26 ++++++++++++++------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/store.ml b/src/proto_alpha/bin_sc_rollup_node/store.ml index effda8c917f9..b902beb9bd17 100644 --- a/src/proto_alpha/bin_sc_rollup_node/store.ml +++ b/src/proto_alpha/bin_sc_rollup_node/store.ml @@ -109,15 +109,25 @@ struct let add store key value = let open Lwt_syntax in - let* already_exists = mem store key in + let* existing_value = find store key in let full_path = String.concat "/" (P.path @ [P.string_of_key key]) in - if already_exists then - Stdlib.failwith (Printf.sprintf "Key %s already exists" full_path) ; - let encoded_value = - Data_encoding.Binary.to_bytes_exn P.value_encoding value - in - let info () = info full_path in - IStore.set_exn ~info store (make_key key) encoded_value + let encode v = Data_encoding.Binary.to_bytes_exn P.value_encoding v in + let encoded_value = encode value in + match existing_value with + | None -> + let info () = info full_path in + IStore.set_exn ~info store (make_key key) encoded_value + | Some existing_value -> + (* To be robust to interruption in the middle of processes, + we accept to redo some work when we restart the node. + Hence, it is fine to insert twice the same value for a + given value. *) + if not (Bytes.equal (encode existing_value) encoded_value) then + Stdlib.failwith + (Printf.sprintf + "Key %s already exists with a different value" + full_path) + else return_unit end module Make_mutable_value (P : sig -- GitLab From 590870c6b8f00117266681fdba7a8a4e21fcffdb Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 14 Jun 2022 10:06:42 +0200 Subject: [PATCH 03/43] Proto,SCORU: Reference a follow-up issue Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/bin_sc_rollup_node/commitment.ml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/proto_alpha/bin_sc_rollup_node/commitment.ml b/src/proto_alpha/bin_sc_rollup_node/commitment.ml index a246eb5dd4ff..71c6dafce30c 100644 --- a/src/proto_alpha/bin_sc_rollup_node/commitment.ml +++ b/src/proto_alpha/bin_sc_rollup_node/commitment.ml @@ -51,6 +51,11 @@ module type Mutable_level_store = node, as only finalized heads are processed to build commitments. *) +(* FIXME: #3203 + + Using these global variables is fragile considering chain + reorganizations and interruptions. We should use a more persistent + representations for this piece of information. *) module Mutable_counter = struct module Make () = struct let x = ref Z.zero -- GitLab From 97a75d47f8b97d6ceda63fc5996dda8c1a82c5c7 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 14 Jun 2022 10:07:30 +0200 Subject: [PATCH 04/43] Proto,SCORU: Improve a test report Signed-off-by: Yann Regis-Gianas --- tezt/tests/sc_rollup.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index 4880721beff8..2846d0bb1eda 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -1159,7 +1159,7 @@ let commitment_stored _protocol sc_rollup_node sc_rollup_address _node client = (Check.option Check.int) ~error_msg: "Number of messages processed by commitment is different from the \ - number of messages expected (%L = %R)") ; + number of messages expected (%L expected <> %R processed)") ; let* published_commitment = Sc_rollup_client.last_published_commitment ~hooks sc_rollup_client in -- GitLab From 4b46f94822aa7ead450d756335fdbe3190c8c04d Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 17 May 2022 10:22:52 +0200 Subject: [PATCH 05/43] Proto,SCORU: Hoist tezos node RPC operations retrieval Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/bin_sc_rollup_node/daemon.ml | 18 +++-- src/proto_alpha/bin_sc_rollup_node/inbox.ml | 76 ++++++++------------ src/proto_alpha/bin_sc_rollup_node/inbox.mli | 14 ++-- 3 files changed, 50 insertions(+), 58 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/daemon.ml b/src/proto_alpha/bin_sc_rollup_node/daemon.ml index 8a4b50cb1ca0..1aba1f9585e5 100644 --- a/src/proto_alpha/bin_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/bin_sc_rollup_node/daemon.ml @@ -71,15 +71,23 @@ module Make (PVM : Pvm.S) = struct *) let open Lwt_result_syntax in let {finalized; seen_before; head} = head_state in - let* () = + let cctxt = node_ctxt.Node_context.cctxt in + let* _operations = let*! () = emit_head_processing_event head_state in - if seen_before then return_unit + (* Avoid processing inbox again if it has been processed before for this head *) + if seen_before then return None else - (* Avoid processing inbox again if it has been processed before for this head *) - let* () = Inbox.process_head node_ctxt store head in + let* operations = + let open Layer1_services in + let (Head {level; _}) = head in + Operations.operations cctxt ~chain:`Main ~block:(`Level level) () + in + let*! () = emit_head_processing_event head_state in + let* () = Inbox.process_head node_ctxt store head operations in (* Avoid storing and publishing commitments if the head is not final *) (* Avoid triggering the pvm execution if this has been done before for this head *) - Components.Interpreter.process_head node_ctxt store head + let* () = Components.Interpreter.process_head node_ctxt store head in + return_some operations in let* () = if finalized then Components.Commitment.process_head node_ctxt store head diff --git a/src/proto_alpha/bin_sc_rollup_node/inbox.ml b/src/proto_alpha/bin_sc_rollup_node/inbox.ml index a51e99ba6f72..ca23865772c4 100644 --- a/src/proto_alpha/bin_sc_rollup_node/inbox.ml +++ b/src/proto_alpha/bin_sc_rollup_node/inbox.ml @@ -28,17 +28,9 @@ *) open Protocol open Alpha_context -module Block_services = Block_services.Make (Protocol) (Protocol) let lift = Lwt.map Environment.wrap_tzresult -let head_processing_failure e = - Format.eprintf - "Error during head processing: @[%a@]" - Error_monad.(TzTrace.pp_print_top pp) - e ; - Lwt_exit.exit_and_raise 1 - module State = struct let add_messages = Store.Messages.add @@ -68,12 +60,7 @@ module State = struct let set_message_tree = Store.MessageTrees.set end -let get_messages cctxt head rollup = - let open Lwt_result_syntax in - let open Block_services in - let+ operations = - Operations.operations cctxt ~chain:`Main ~block:(`Level (snd head)) () - in +let get_messages rollup operations = let is_add_message = function | Contents (Manager_operation @@ -82,51 +69,46 @@ let get_messages cctxt head rollup = messages | _ -> [] in - let process_contents {protocol_data = Operation_data {contents; _}; _} = + let process_contents + ({protocol_data = Operation_data {contents; _}; _} : + Layer1_services.operation) = let operations = Operation.to_list (Contents_list contents) in List.concat_map is_add_message operations in - let process_operations operations = - List.concat_map process_contents operations - in + let process_operations = List.concat_map process_contents in List.concat_map process_operations operations -let process_head Node_context.({cctxt; rollup_address; _} as node_ctxt) store - Layer1.(Head {level; hash = head_hash} as head) = +let process_head (Node_context.{rollup_address; _} as node_ctxt) store + Layer1.(Head {level; hash = head_hash} as head) operations = let open Lwt_result_syntax in - let*! res = get_messages cctxt (head_hash, level) rollup_address in - match res with - | Error e -> head_processing_failure e - | Ok messages -> - let*! () = - Inbox_event.get_messages head_hash level (List.length messages) - in - let*! () = State.add_messages store head_hash messages in - (* + let messages = get_messages rollup_address operations in + let*! () = Inbox_event.get_messages head_hash level (List.length messages) in + let*! () = State.add_messages store head_hash messages in + (* We compute the inbox of this block using the inbox of its predecessor. That way, the computation of inboxes is robust to chain reorganization. *) - let*! predecessor = Layer1.predecessor store head in - let*! inbox = State.inbox_of_hash node_ctxt store predecessor in - lift - @@ let*! history = State.history_of_hash store predecessor in - let*! messages_tree = State.get_message_tree store predecessor in - let*? level = Raw_level.of_int32 level in - let* messages_tree, history, inbox = - Store.Inbox.add_external_messages - history - inbox - level - messages - messages_tree - in - let*! () = State.set_message_tree store head_hash messages_tree in - let*! () = State.add_inbox store head_hash inbox in - let*! () = State.add_history store head_hash history in - return_unit + let*! predecessor = Layer1.predecessor store head in + let*! inbox = State.inbox_of_hash node_ctxt store predecessor in + lift + @@ let*! history = State.history_of_hash store predecessor in + let*! messages_tree = State.get_message_tree store predecessor in + let*? level = Raw_level.of_int32 level in + let* messages_tree, history, inbox = + Store.Inbox.add_external_messages + history + inbox + level + messages + messages_tree + in + let*! () = State.set_message_tree store head_hash messages_tree in + let*! () = State.add_inbox store head_hash inbox in + let*! () = State.add_history store head_hash history in + return_unit let inbox_of_hash = State.inbox_of_hash diff --git a/src/proto_alpha/bin_sc_rollup_node/inbox.mli b/src/proto_alpha/bin_sc_rollup_node/inbox.mli index 162ebf02e44f..f0622e64ce0b 100644 --- a/src/proto_alpha/bin_sc_rollup_node/inbox.mli +++ b/src/proto_alpha/bin_sc_rollup_node/inbox.mli @@ -34,13 +34,15 @@ *) open Protocol -(** [process_head node_ctxt store head] changes the state of the inbox to react to - [head]. In particular, this process requests the tezos node - through the context client [node_ctxt.cctxt] to retrieve the messages published - at the level indicated by [head]. *) - +(** [process_head node_ctxt store head operations] changes the state + of the inbox to react to [head]. In particular, this process + filters the provided [operations] of the [head] block. *) val process_head : - Node_context.t -> Store.t -> Layer1.head -> unit tzresult Lwt.t + Node_context.t -> + Store.t -> + Layer1.head -> + Layer1_services.operation list list -> + unit tzresult Lwt.t (** [inbox_of_hash node_ctxt store block_hash] returns the rollup inbox at the end of the given validation of [block_hash]. *) -- GitLab From 2d67a5cd59f3315a1befab5299f1967e9a1b8475 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Wed, 18 May 2022 13:25:58 +0200 Subject: [PATCH 06/43] Proto,SCORU: Force operation metadata to be produced by tezos node No receipt should be ignored by the rollup node even if it is classified as "too large". A rollup node should be running on a fast enough hardware and infrastructure to be able to cope with large metadata. Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/bin_sc_rollup_node/daemon.ml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/daemon.ml b/src/proto_alpha/bin_sc_rollup_node/daemon.ml index 1aba1f9585e5..561517a19f7e 100644 --- a/src/proto_alpha/bin_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/bin_sc_rollup_node/daemon.ml @@ -80,7 +80,16 @@ module Make (PVM : Pvm.S) = struct let* operations = let open Layer1_services in let (Head {level; _}) = head in - Operations.operations cctxt ~chain:`Main ~block:(`Level level) () + (* It would be a security problem to ignore operation + receipt because metadata are too large. For this reason, + we set [force_metadata] to [true] to make sure the + receipts are always accessible to the rollup node. *) + Operations.operations + ~force_metadata:true + cctxt + ~chain:`Main + ~block:(`Level level) + () in let*! () = emit_head_processing_event head_state in let* () = Inbox.process_head node_ctxt store head operations in -- GitLab From d2bc883e2d9dfce76f6659e287b4e09063cf7bee Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Wed, 18 May 2022 18:11:38 +0200 Subject: [PATCH 07/43] Proto,Client: Add sc rollup refutation game operations Signed-off-by: Yann Regis-Gianas --- .../lib_client/client_proto_context.ml | 67 +++++++++++++++++++ .../lib_client/client_proto_context.mli | 54 ++++++++++++++- 2 files changed, 119 insertions(+), 2 deletions(-) diff --git a/src/proto_alpha/lib_client/client_proto_context.ml b/src/proto_alpha/lib_client/client_proto_context.ml index fd8a161c13cd..8ac3b600f08d 100644 --- a/src/proto_alpha/lib_client/client_proto_context.ml +++ b/src/proto_alpha/lib_client/client_proto_context.ml @@ -1346,3 +1346,70 @@ let sc_rollup_recover_bond (cctxt : #full) ~chain ~block ?confirmations ?dry_run match Apply_results.pack_contents_list op result with | Apply_results.Single_and_result ((Manager_operation _ as op), result) -> return (oph, op, result) + +let sc_rollup_refute (cctxt : #full) ~chain ~block ?confirmations ?dry_run + ?verbose_signing ?simulation ?fee ?gas_limit ?storage_limit ?counter ~source + ~rollup ~refutation ~opponent ~src_pk ~src_sk ~fee_parameter () = + let op = + Annotated_manager_operation.Single_manager + (Injection.prepare_manager_operation + ~fee:(Limit.of_option fee) + ~gas_limit:(Limit.of_option gas_limit) + ~storage_limit:(Limit.of_option storage_limit) + (Sc_rollup_refute {rollup; refutation; opponent})) + in + Injection.inject_manager_operation + cctxt + ~chain + ~block + ?confirmations + ?dry_run + ?verbose_signing + ?simulation + ?counter + ~source + ~fee:(Limit.of_option fee) + ~storage_limit:(Limit.of_option storage_limit) + ~gas_limit:(Limit.of_option gas_limit) + ~src_pk + ~src_sk + ~fee_parameter + op + >>=? fun (oph, op, result) -> + match Apply_results.pack_contents_list op result with + | Apply_results.Single_and_result ((Manager_operation _ as op), result) -> + return (oph, op, result) + +let sc_rollup_timeout (cctxt : #full) ~chain ~block ?confirmations ?dry_run + ?verbose_signing ?simulation ?fee ?gas_limit ?storage_limit ?counter ~source + ~rollup ~alice ~bob ~src_pk ~src_sk ~fee_parameter () = + let stakers = (alice, bob) in + let op = + Annotated_manager_operation.Single_manager + (Injection.prepare_manager_operation + ~fee:(Limit.of_option fee) + ~gas_limit:(Limit.of_option gas_limit) + ~storage_limit:(Limit.of_option storage_limit) + (Sc_rollup_timeout {rollup; stakers})) + in + Injection.inject_manager_operation + cctxt + ~chain + ~block + ?confirmations + ?dry_run + ?verbose_signing + ?simulation + ?counter + ~source + ~fee:(Limit.of_option fee) + ~storage_limit:(Limit.of_option storage_limit) + ~gas_limit:(Limit.of_option gas_limit) + ~src_pk + ~src_sk + ~fee_parameter + op + >>=? fun (oph, op, result) -> + match Apply_results.pack_contents_list op result with + | Apply_results.Single_and_result ((Manager_operation _ as op), result) -> + return (oph, op, result) diff --git a/src/proto_alpha/lib_client/client_proto_context.mli b/src/proto_alpha/lib_client/client_proto_context.mli index 2404bb012e2b..0296290720dd 100644 --- a/src/proto_alpha/lib_client/client_proto_context.mli +++ b/src/proto_alpha/lib_client/client_proto_context.mli @@ -815,8 +815,58 @@ val sc_rollup_recover_bond : fee_parameter:Injection.fee_parameter -> sc_rollup:Sc_rollup.t -> unit -> - (Operation_hash.t + Operation_hash.t * Kind.sc_rollup_recover_bond Kind.manager contents - * Kind.sc_rollup_recover_bond Kind.manager Apply_results.contents_result) + * Kind.sc_rollup_recover_bond Kind.manager Apply_results.contents_result + +val sc_rollup_refute : + #Protocol_client_context.full -> + chain:Chain_services.chain -> + block:Block_services.block -> + ?confirmations:int -> + ?dry_run:bool -> + ?verbose_signing:bool -> + ?simulation:bool -> + ?fee:Tez.t -> + ?gas_limit:Gas.Arith.integral -> + ?storage_limit:counter -> + ?counter:counter -> + source:public_key_hash -> + rollup:Alpha_context.Sc_rollup.t -> + refutation:Alpha_context.Sc_rollup.Game.refutation -> + opponent:Alpha_context.Sc_rollup.Staker.t -> + src_pk:public_key -> + src_sk:Client_keys.sk_uri -> + fee_parameter:Injection.fee_parameter -> + unit -> + (Operation_hash.t + * Kind.sc_rollup_refute Kind.manager contents + * Kind.sc_rollup_refute Kind.manager Apply_results.contents_result) + tzresult + Lwt.t + +val sc_rollup_timeout : + #Protocol_client_context.full -> + chain:Chain_services.chain -> + block:Block_services.block -> + ?confirmations:int -> + ?dry_run:bool -> + ?verbose_signing:bool -> + ?simulation:bool -> + ?fee:Tez.t -> + ?gas_limit:Gas.Arith.integral -> + ?storage_limit:counter -> + ?counter:counter -> + source:public_key_hash -> + rollup:Alpha_context.Sc_rollup.t -> + alice:Alpha_context.Sc_rollup.Staker.t -> + bob:Alpha_context.Sc_rollup.Staker.t -> + src_pk:public_key -> + src_sk:Client_keys.sk_uri -> + fee_parameter:Injection.fee_parameter -> + unit -> + (Operation_hash.t + * Kind.sc_rollup_timeout Kind.manager contents + * Kind.sc_rollup_timeout Kind.manager Apply_results.contents_result) tzresult Lwt.t -- GitLab From 6a689be2ce41fd7bcb0e11fe98a12eec8f914d8e Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Wed, 18 May 2022 19:04:11 +0200 Subject: [PATCH 08/43] Proto,SCORU: Make the rollup node only consider applied L1 operations Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/bin_sc_rollup_node/inbox.ml | 27 ++--- .../bin_sc_rollup_node/layer1_services.ml | 102 ++++++++++++++++++ 2 files changed, 116 insertions(+), 13 deletions(-) create mode 100644 src/proto_alpha/bin_sc_rollup_node/layer1_services.ml diff --git a/src/proto_alpha/bin_sc_rollup_node/inbox.ml b/src/proto_alpha/bin_sc_rollup_node/inbox.ml index ca23865772c4..c3e1136762f0 100644 --- a/src/proto_alpha/bin_sc_rollup_node/inbox.ml +++ b/src/proto_alpha/bin_sc_rollup_node/inbox.ml @@ -61,22 +61,23 @@ module State = struct end let get_messages rollup operations = - let is_add_message = function - | Contents - (Manager_operation - {operation = Sc_rollup_add_messages {rollup = rollup'; messages}; _}) + let apply (type kind) accu ~source:_ (operation : kind manager_operation) + _result = + match operation with + | Sc_rollup_add_messages {rollup = rollup'; messages} when Sc_rollup.Address.(rollup' = rollup) -> - messages - | _ -> [] + messages :: accu + | _ -> accu in - let process_contents - ({protocol_data = Operation_data {contents; _}; _} : - Layer1_services.operation) = - let operations = Operation.to_list (Contents_list contents) in - List.concat_map is_add_message operations + let apply_internal (type kind) accu ~source:_ + (_operation : kind Apply_results.internal_manager_operation) _result = + accu in - let process_operations = List.concat_map process_contents in - List.concat_map process_operations operations + Layer1_services.process_applied_operations + [] + operations + {apply; apply_internal} + |> List.rev |> List.flatten let process_head (Node_context.{rollup_address; _} as node_ctxt) store Layer1.(Head {level; hash = head_hash} as head) operations = diff --git a/src/proto_alpha/bin_sc_rollup_node/layer1_services.ml b/src/proto_alpha/bin_sc_rollup_node/layer1_services.ml new file mode 100644 index 000000000000..c1dcdbad2d27 --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/layer1_services.ml @@ -0,0 +1,102 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs, *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +open Protocol +open Alpha_context +open Apply_results +include Block_services.Make (Protocol) (Protocol) + +type 'accu operation_processor = { + apply : + 'kind. + 'accu -> + source:public_key_hash -> + 'kind manager_operation -> + 'kind Apply_results.successful_manager_operation_result -> + 'accu; + apply_internal : + 'kind. + 'accu -> + source:public_key_hash -> + 'kind internal_manager_operation -> + 'kind Apply_results.successful_manager_operation_result -> + 'accu; +} + +let process_applied_operations operations accu f = + let rec on_applied_operation_and_result : + type kind. _ -> kind Apply_results.contents_and_result_list -> _ = + fun accu -> function + | Single_and_result + ( Manager_operation {operation; source; _}, + Manager_operation_result + { + operation_result = Applied operation_result; + internal_operation_results; + _; + } ) -> + let accu = f.apply accu ~source operation operation_result in + on_applied_internal_operations accu source internal_operation_results + | Single_and_result (_, _) -> accu + | Cons_and_result + ( Manager_operation {operation; source; _}, + Manager_operation_result + { + operation_result = Applied operation_result; + internal_operation_results; + _; + }, + rest ) -> + let accu = f.apply accu ~source operation operation_result in + let accu = on_applied_operation_and_result accu rest in + on_applied_internal_operations accu source internal_operation_results + | Cons_and_result (_, _, rest) -> on_applied_operation_and_result accu rest + and on_applied_internal_operations accu source internal_operation_results = + List.fold_left + (fun accu (Internal_manager_operation_result ({operation; _}, result)) -> + match result with + | Applied result -> f.apply_internal accu ~source operation result + | _ -> accu) + accu + internal_operation_results + in + let process_contents accu + ({protocol_data = Operation_data {contents; _}; receipt; _} : operation) = + match receipt with + | Empty | Too_large | Receipt No_operation_metadata -> + (* This should case should not happen between [operations] is supposed + to be retrieved with `force_metadata:true`. *) + assert false + | Receipt (Operation_metadata {contents = results; _}) -> ( + match Apply_results.kind_equal_list contents results with + | Some Eq -> + on_applied_operation_and_result accu + @@ Apply_results.pack_contents_list contents results + | None -> + (* Should not happen *) + assert false) + in + let process_operations = List.fold_left process_contents in + List.fold_left process_operations operations accu -- GitLab From b9bb7b265ce05412867a227d8b6ac58a805ebd7c Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 14 Jun 2022 13:49:24 +0200 Subject: [PATCH 09/43] Proto,SCORU: Add RPC to retrieve the ongoing refutation game Signed-off-by: Yann Regis-Gianas --- .../lib_client/client_proto_context.ml | 6 +- .../lib_client/client_proto_context.mli | 8 +- src/proto_alpha/lib_plugin/RPC.ml | 74 ++++++++++++++++++- .../lib_protocol/alpha_context.mli | 5 ++ .../sc_rollup_refutation_storage.ml | 13 ++++ .../sc_rollup_refutation_storage.mli | 10 ++- 6 files changed, 108 insertions(+), 8 deletions(-) diff --git a/src/proto_alpha/lib_client/client_proto_context.ml b/src/proto_alpha/lib_client/client_proto_context.ml index 8ac3b600f08d..e196148e1625 100644 --- a/src/proto_alpha/lib_client/client_proto_context.ml +++ b/src/proto_alpha/lib_client/client_proto_context.ml @@ -1375,7 +1375,7 @@ let sc_rollup_refute (cctxt : #full) ~chain ~block ?confirmations ?dry_run ~src_sk ~fee_parameter op - >>=? fun (oph, op, result) -> + >>=? fun (oph, _, op, result) -> match Apply_results.pack_contents_list op result with | Apply_results.Single_and_result ((Manager_operation _ as op), result) -> return (oph, op, result) @@ -1383,7 +1383,7 @@ let sc_rollup_refute (cctxt : #full) ~chain ~block ?confirmations ?dry_run let sc_rollup_timeout (cctxt : #full) ~chain ~block ?confirmations ?dry_run ?verbose_signing ?simulation ?fee ?gas_limit ?storage_limit ?counter ~source ~rollup ~alice ~bob ~src_pk ~src_sk ~fee_parameter () = - let stakers = (alice, bob) in + let stakers = Sc_rollup.Game.Index.make alice bob in let op = Annotated_manager_operation.Single_manager (Injection.prepare_manager_operation @@ -1409,7 +1409,7 @@ let sc_rollup_timeout (cctxt : #full) ~chain ~block ?confirmations ?dry_run ~src_sk ~fee_parameter op - >>=? fun (oph, op, result) -> + >>=? fun (oph, _, op, result) -> match Apply_results.pack_contents_list op result with | Apply_results.Single_and_result ((Manager_operation _ as op), result) -> return (oph, op, result) diff --git a/src/proto_alpha/lib_client/client_proto_context.mli b/src/proto_alpha/lib_client/client_proto_context.mli index 0296290720dd..03d4b766ec1a 100644 --- a/src/proto_alpha/lib_client/client_proto_context.mli +++ b/src/proto_alpha/lib_client/client_proto_context.mli @@ -815,9 +815,11 @@ val sc_rollup_recover_bond : fee_parameter:Injection.fee_parameter -> sc_rollup:Sc_rollup.t -> unit -> - Operation_hash.t + (Operation_hash.t * Kind.sc_rollup_recover_bond Kind.manager contents - * Kind.sc_rollup_recover_bond Kind.manager Apply_results.contents_result + * Kind.sc_rollup_recover_bond Kind.manager Apply_results.contents_result) + tzresult + Lwt.t val sc_rollup_refute : #Protocol_client_context.full -> @@ -833,7 +835,7 @@ val sc_rollup_refute : ?counter:counter -> source:public_key_hash -> rollup:Alpha_context.Sc_rollup.t -> - refutation:Alpha_context.Sc_rollup.Game.refutation -> + refutation:Alpha_context.Sc_rollup.Game.refutation option -> opponent:Alpha_context.Sc_rollup.Staker.t -> src_pk:public_key -> src_sk:Client_keys.sk_uri -> diff --git a/src/proto_alpha/lib_plugin/RPC.ml b/src/proto_alpha/lib_plugin/RPC.ml index 03c8f89a1e06..631c94f19da3 100644 --- a/src/proto_alpha/lib_plugin/RPC.ml +++ b/src/proto_alpha/lib_plugin/RPC.ml @@ -1831,6 +1831,41 @@ module Sc_rollup = struct ~query:RPC_query.empty ~output:(Data_encoding.list Sc_rollup.Address.encoding) path + + let ongoing_refutation_game = + let query = + let open RPC_query in + query Sc_rollup.Staker.of_b58check_exn + |+ field "staker" RPC_arg.string "" (fun x -> + Format.asprintf "%a" Sc_rollup.Staker.pp x) + |> seal + in + let output = + Sc_rollup.( + Data_encoding.(option (tup2 Game.encoding Game.Index.encoding))) + in + RPC_service.get_service + ~description:"Ongoing refufation game for a given staker" + ~query + ~output + RPC_path.(path /: Sc_rollup.Address.rpc_arg / "game") + + let conflicts = + let query = + let open RPC_query in + query Sc_rollup.Staker.of_b58check_exn + |+ field "staker" RPC_arg.string "" (fun x -> + Format.asprintf "%a" Sc_rollup.Staker.pp x) + |> seal + in + let output = + Sc_rollup.(Data_encoding.list Refutation_storage.conflict_encoding) + in + RPC_service.get_service + ~description:"List of stakers in conflict with the given staker" + ~query + ~output + RPC_path.(path /: Sc_rollup.Address.rpc_arg / "conflicts") end let kind ctxt block sc_rollup_address = @@ -1890,6 +1925,30 @@ module Sc_rollup = struct Registration.register0 ~chunked:true S.root (fun context () () -> Sc_rollup.list context) + let register_ongoing_refutation_game () = + Registration.register1 + ~chunked:false + S.ongoing_refutation_game + (fun context rollup staker () -> + let open Lwt_tzresult_syntax in + let* game, _ = + Sc_rollup.Refutation_storage.get_ongoing_game_for_staker + context + rollup + staker + in + return game) + + let register_conflicts () = + Registration.register1 + ~chunked:false + S.conflicts + (fun context rollup staker () -> + Sc_rollup.Refutation_storage.conflicting_stakers_uncarbonated + context + rollup + staker) + let register () = register_kind () ; register_inbox () ; @@ -1897,7 +1956,9 @@ module Sc_rollup = struct register_boot_sector () ; register_last_cemented_commitment_hash_with_level () ; register_commitment () ; - register_root () + register_root () ; + register_ongoing_refutation_game () ; + register_conflicts () let list ctxt block = RPC_context.make_call0 S.root ctxt block () () @@ -1915,6 +1976,17 @@ module Sc_rollup = struct let boot_sector ctxt block sc_rollup_address = RPC_context.make_call1 S.boot_sector ctxt block sc_rollup_address () () + + let ongoing_refutation_game ctxt block sc_rollup_address staker = + RPC_context.make_call1 + S.ongoing_refutation_game + ctxt + block + sc_rollup_address + staker + + let conflicts ctxt block sc_rollup_address staker = + RPC_context.make_call1 S.conflicts ctxt block sc_rollup_address staker end module Tx_rollup = struct diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index e246db86a4fd..65f3e7c40c37 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -2953,6 +2953,8 @@ module Sc_rollup : sig val make : Staker.t -> Staker.t -> t end + val encoding : t Data_encoding.t + val opponent : player -> player type step = @@ -3026,6 +3028,9 @@ module Sc_rollup : sig type conflict_point = point * point + val get_ongoing_game_for_staker : + context -> t -> Staker.t -> (Game.t option * context) tzresult Lwt.t + val game_move : context -> t -> diff --git a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml index 9da98f97f764..e3ba389437c8 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml @@ -45,6 +45,19 @@ let timeout_level ctxt = let level = Raw_context.current_level ctxt in Raw_level_repr.add level.level timeout_period_in_blocks +let get_ongoing_game ctxt rollup staker1 staker2 = + let open Lwt_tzresult_syntax in + let stakers = Sc_rollup_game_repr.Index.normalize (staker1, staker2) in + let* ctxt, game = Store.Game.find (ctxt, rollup) stakers in + return (game, ctxt) + +let get_ongoing_game_for_staker ctxt rollup staker = + let open Lwt_tzresult_syntax in + let* ctxt, opponent = Store.Opponent.find (ctxt, rollup) staker in + match opponent with + | Some opponent -> get_ongoing_game ctxt rollup staker opponent + | None -> return (None, ctxt) + (** [goto_inbox_level ctxt rollup inbox_level commit] Follows the predecessors of [commit] until it arrives at the exact [inbox_level]. The result is the commit hash at the given inbox level. *) let goto_inbox_level ctxt rollup inbox_level commit = diff --git a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli index faac56a0eaaf..4c40ea5449f5 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli +++ b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli @@ -33,6 +33,14 @@ type point = { type conflict_point = point * point +(** [get_ongoing_game_for_staker ctxt rollup staker] returns [Some game] if [staker] + is currently playing a refutation game in the [rollup]. *) +val get_ongoing_game_for_staker : + Raw_context.t -> + Sc_rollup_repr.t -> + Sc_rollup_repr.Staker.t -> + (Sc_rollup_game_repr.t option * Raw_context.t) tzresult Lwt.t + (** [game_move ctxt rollup player opponent refutation is_opening_move] handles the storage-side logic for when one of the players makes a move in the game. It initializes the game if [is_opening_move] is @@ -61,7 +69,7 @@ type conflict_point = point * point {li [Sc_rollup_wrong_turn] if a player is trying to move out of turn} } - + The [is_opening_move] argument is included here to make sure that an operation intended to start a refutation game is never mistaken for an operation to play the second move of the game---this may -- GitLab From 4d67b63f61af628c2fbf951e3abf717e1bcd5b9a Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 14 Jun 2022 10:17:31 +0200 Subject: [PATCH 10/43] Proto,SCORU: Use Lwt compatible exit function Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/bin_sc_rollup_node/commitment.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/commitment.ml b/src/proto_alpha/bin_sc_rollup_node/commitment.ml index 71c6dafce30c..5304623e3b7b 100644 --- a/src/proto_alpha/bin_sc_rollup_node/commitment.ml +++ b/src/proto_alpha/bin_sc_rollup_node/commitment.ml @@ -282,7 +282,7 @@ module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM = struct commitment.predecessor lcc_hash in - exit 1 + Lwt_exit.exit_and_raise 1 else Lwt.return () in let* source, src_pk, src_sk = Node_context.get_operator_keys node_ctxt in -- GitLab From 6e3b2ffc0a881d3f3e5dd46dcb15156c3499513f Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 14 Jun 2022 13:19:52 +0200 Subject: [PATCH 11/43] Proto,SCORU: Remove is_opening_move in Sc_rollup_refute It is simpler to separate the initialisation of a game and playing the first move. Hence, we now encode the game initialization through the absence of refutation. Signed-off-by: Yann Regis-Gianas --- .../lib_client/operation_result.ml | 10 ++++----- .../lib_protocol/alpha_context.mli | 4 +--- src/proto_alpha/lib_protocol/apply.ml | 16 +++++++------- .../lib_protocol/operation_repr.ml | 19 ++++++++-------- .../lib_protocol/operation_repr.mli | 3 +-- .../sc_rollup_refutation_storage.ml | 22 +++++++++++-------- .../sc_rollup_refutation_storage.mli | 12 ++++++---- .../lib_protocol/test/helpers/op.ml | 4 ++-- .../lib_protocol/test/helpers/op.mli | 3 +-- 9 files changed, 48 insertions(+), 45 deletions(-) diff --git a/src/proto_alpha/lib_client/operation_result.ml b/src/proto_alpha/lib_client/operation_result.ml index 90d681a1d383..8faabf0be4ec 100644 --- a/src/proto_alpha/lib_client/operation_result.ml +++ b/src/proto_alpha/lib_client/operation_result.ml @@ -282,18 +282,18 @@ let pp_manager_operation_content (type kind) source ppf commitment Sc_rollup.Address.pp rollup - | Sc_rollup_refute {rollup; opponent; refutation; is_opening_move} -> + | Sc_rollup_refute {rollup; opponent; refutation} -> Format.fprintf ppf - "Refute staker %a in the smart contract rollup at address %a using \ - refutation %a %s" + "Refute staker %a in the smart contract rollup at address %a using %a" Sc_rollup.Staker.pp opponent Sc_rollup.Address.pp rollup - Sc_rollup.Game.pp_refutation + (fun fmt -> function + | None -> Format.pp_print_string fmt "opening move of the game" + | Some refutation -> Sc_rollup.Game.pp_refutation fmt refutation) refutation - (if is_opening_move then "(opening move of game)" else "") | Sc_rollup_timeout {rollup; stakers = {alice; bob}} -> Format.fprintf ppf diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 65f3e7c40c37..73e6b2654d65 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -3037,7 +3037,6 @@ module Sc_rollup : sig player:Staker.t -> opponent:Staker.t -> Game.refutation -> - is_opening_move:bool -> (Game.outcome option * context) tzresult Lwt.t val timeout : @@ -3554,8 +3553,7 @@ and _ manager_operation = | Sc_rollup_refute : { rollup : Sc_rollup.t; opponent : Sc_rollup.Staker.t; - refutation : Sc_rollup.Game.refutation; - is_opening_move : bool; + refutation : Sc_rollup.Game.refutation option; } -> Kind.sc_rollup_refute manager_operation | Sc_rollup_timeout : { diff --git a/src/proto_alpha/lib_protocol/apply.ml b/src/proto_alpha/lib_protocol/apply.ml index bd65117ab13d..5da2f2daad72 100644 --- a/src/proto_alpha/lib_protocol/apply.ml +++ b/src/proto_alpha/lib_protocol/apply.ml @@ -1812,14 +1812,14 @@ let apply_external_manager_operation_content : {staked_hash; consumed_gas; published_at_level; balance_updates} in return (ctxt, result, []) - | Sc_rollup_refute {rollup; opponent; refutation; is_opening_move} -> - Sc_rollup.Refutation_storage.game_move - ctxt - rollup - ~player:source - ~opponent - refutation - ~is_opening_move + | Sc_rollup_refute {rollup; opponent; refutation} -> + let open Sc_rollup.Refutation_storage in + let player = source in + (match refutation with + | None -> + start_game ctxt rollup ~player ~opponent >>=? fun ctxt -> + return (None, ctxt) + | Some refutation -> game_move ctxt rollup ~player ~opponent refutation) >>=? fun (outcome, ctxt) -> (match outcome with | None -> return (Sc_rollup.Game.Ongoing, ctxt, []) diff --git a/src/proto_alpha/lib_protocol/operation_repr.ml b/src/proto_alpha/lib_protocol/operation_repr.ml index 4e737928a4de..6c571862b7f2 100644 --- a/src/proto_alpha/lib_protocol/operation_repr.ml +++ b/src/proto_alpha/lib_protocol/operation_repr.ml @@ -410,8 +410,7 @@ and _ manager_operation = | Sc_rollup_refute : { rollup : Sc_rollup_repr.t; opponent : Sc_rollup_repr.Staker.t; - refutation : Sc_rollup_game_repr.refutation; - is_opening_move : bool; + refutation : Sc_rollup_game_repr.refutation option; } -> Kind.sc_rollup_refute manager_operation | Sc_rollup_timeout : { @@ -1055,22 +1054,22 @@ module Encoding = struct tag = sc_rollup_operation_refute_tag; name = "sc_rollup_refute"; encoding = - obj4 + obj3 (req "rollup" Sc_rollup_repr.encoding) (req "opponent" Sc_rollup_repr.Staker.encoding) - (req "refutation" Sc_rollup_game_repr.refutation_encoding) - (req "is_opening_move" bool); + (req + "refutation" + (option Sc_rollup_game_repr.refutation_encoding)); select = (function | Manager (Sc_rollup_refute _ as op) -> Some op | _ -> None); proj = (function - | Sc_rollup_refute {rollup; opponent; refutation; is_opening_move} - -> - (rollup, opponent, refutation, is_opening_move)); + | Sc_rollup_refute {rollup; opponent; refutation} -> + (rollup, opponent, refutation)); inj = - (fun (rollup, opponent, refutation, is_opening_move) -> - Sc_rollup_refute {rollup; opponent; refutation; is_opening_move}); + (fun (rollup, opponent, refutation) -> + Sc_rollup_refute {rollup; opponent; refutation}); } let[@coq_axiom_with_reason "gadt"] sc_rollup_timeout_case = diff --git a/src/proto_alpha/lib_protocol/operation_repr.mli b/src/proto_alpha/lib_protocol/operation_repr.mli index 0277f6fad073..f9501516a2e6 100644 --- a/src/proto_alpha/lib_protocol/operation_repr.mli +++ b/src/proto_alpha/lib_protocol/operation_repr.mli @@ -476,8 +476,7 @@ and _ manager_operation = | Sc_rollup_refute : { rollup : Sc_rollup_repr.t; opponent : Sc_rollup_repr.Staker.t; - refutation : Sc_rollup_game_repr.refutation; - is_opening_move : bool; + refutation : Sc_rollup_game_repr.refutation option; } -> Kind.sc_rollup_refute manager_operation | Sc_rollup_timeout : { diff --git a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml index e3ba389437c8..832421278bd1 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml @@ -243,19 +243,23 @@ let init_game ctxt rollup ~refuter ~defender = let* ctxt, _ = Store.Opponent.init (ctxt, rollup) defender refuter in return (game, ctxt) -let game_move ctxt rollup ~player ~opponent refutation ~is_opening_move = +let start_game ctxt rollup ~player ~opponent = let open Lwt_tzresult_syntax in - let ({alice; bob} as stakers : Sc_rollup_game_repr.Index.t) = - Sc_rollup_game_repr.Index.make player opponent - in - let* game, ctxt = - if is_opening_move then - init_game ctxt rollup ~refuter:player ~defender:opponent - else get_game ctxt rollup stakers + let idx = Sc_rollup_game_repr.Index.make player opponent in + let* game, ctxt = init_game ctxt rollup ~refuter:player ~defender:opponent in + let* ctxt, _ = Store.Game.update (ctxt, rollup) idx game in + let* ctxt, _ = + Store.Game_timeout.update (ctxt, rollup) idx (timeout_level ctxt) in + return ctxt + +let game_move ctxt rollup ~player ~opponent refutation = + let open Lwt_tzresult_syntax in + let idx = Sc_rollup_game_repr.Index.make player opponent in + let* game, ctxt = get_game ctxt rollup idx in let* () = fail_unless - (let turn = match game.turn with Alice -> alice | Bob -> bob in + (let turn = match game.turn with Alice -> idx.alice | Bob -> idx.bob in Sc_rollup_repr.Staker.equal turn player) Sc_rollup_wrong_turn in diff --git a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli index 4c40ea5449f5..d164f1a2bced 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli +++ b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli @@ -40,11 +40,16 @@ val get_ongoing_game_for_staker : Sc_rollup_repr.t -> Sc_rollup_repr.Staker.t -> (Sc_rollup_game_repr.t option * Raw_context.t) tzresult Lwt.t +val start_game : + Raw_context.t -> + Sc_rollup_repr.t -> + player:Signature.public_key_hash -> + opponent:Signature.public_key_hash -> + Raw_context.t tzresult Lwt.t -(** [game_move ctxt rollup player opponent refutation is_opening_move] +(** [game_move ctxt rollup player opponent refutation] handles the storage-side logic for when one of the players makes a - move in the game. It initializes the game if [is_opening_move] is - [true]. Otherwise, it checks the game already exists. Then it checks + move in the game. It checks the game already exists. Then it checks that [player] is the player whose turn it is; if so, it applies [refutation] using the [play] function. @@ -84,7 +89,6 @@ val game_move : player:Sc_rollup_repr.Staker.t -> opponent:Sc_rollup_repr.Staker.t -> Sc_rollup_game_repr.refutation -> - is_opening_move:bool -> (Sc_rollup_game_repr.outcome option * Raw_context.t) tzresult Lwt.t (* TODO: #2902 update reference to timeout period in doc-string *) diff --git a/src/proto_alpha/lib_protocol/test/helpers/op.ml b/src/proto_alpha/lib_protocol/test/helpers/op.ml index 53da6f52100b..5cdf16cb3708 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/op.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/op.ml @@ -861,7 +861,7 @@ let sc_rollup_add_messages ?force_reveal ?counter ?fee ?gas_limit ?storage_limit sign account.sk ctxt to_sign_op let sc_rollup_refute ?force_reveal ?counter ?fee ?gas_limit ?storage_limit ctxt - (src : Contract.t) rollup opponent refutation is_opening_move = + (src : Contract.t) rollup opponent refutation = manager_operation ?force_reveal ?counter @@ -870,7 +870,7 @@ let sc_rollup_refute ?force_reveal ?counter ?fee ?gas_limit ?storage_limit ctxt ?storage_limit ~source:src ctxt - (Sc_rollup_refute {rollup; opponent; refutation; is_opening_move}) + (Sc_rollup_refute {rollup; opponent; refutation}) >>=? fun to_sign_op -> Context.Contract.manager ctxt src >|=? fun account -> sign account.sk ctxt to_sign_op diff --git a/src/proto_alpha/lib_protocol/test/helpers/op.mli b/src/proto_alpha/lib_protocol/test/helpers/op.mli index df18aab209c1..26ebf8a9f642 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/op.mli +++ b/src/proto_alpha/lib_protocol/test/helpers/op.mli @@ -603,8 +603,7 @@ val sc_rollup_refute : Contract.t -> Sc_rollup.t -> public_key_hash -> - Sc_rollup.Game.refutation -> - bool -> + Sc_rollup.Game.refutation option -> Operation.packed tzresult Lwt.t val sc_rollup_timeout : -- GitLab From 925c4c38dcd1a500274113966a95b6c3c4dbb95f Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 14 Jun 2022 13:22:46 +0200 Subject: [PATCH 12/43] Proto,SCORU: Make tick jumping possible Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/lib_protocol/alpha_context.mli | 2 ++ src/proto_alpha/lib_protocol/sc_rollup_tick_repr.ml | 2 ++ src/proto_alpha/lib_protocol/sc_rollup_tick_repr.mli | 4 ++++ 3 files changed, 8 insertions(+) diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 73e6b2654d65..558834397493 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -2485,6 +2485,8 @@ module Sc_rollup : sig val next : t -> t + val jump : t -> Z.t -> t + val distance : t -> t -> Z.t val of_int : int -> t option diff --git a/src/proto_alpha/lib_protocol/sc_rollup_tick_repr.ml b/src/proto_alpha/lib_protocol/sc_rollup_tick_repr.ml index 22156b964fed..8ce3b1c62607 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_tick_repr.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_tick_repr.ml @@ -30,6 +30,8 @@ let initial = zero let next = succ +let jump tick z = max initial (add tick z) + let pp = pp_print let encoding = Data_encoding.n diff --git a/src/proto_alpha/lib_protocol/sc_rollup_tick_repr.mli b/src/proto_alpha/lib_protocol/sc_rollup_tick_repr.mli index feb5c90eb63f..56b6aa33a4f5 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_tick_repr.mli +++ b/src/proto_alpha/lib_protocol/sc_rollup_tick_repr.mli @@ -36,6 +36,10 @@ val initial : t (** [next tick] returns the counter successor of [tick]. No overflow can happen. *) val next : t -> t +(** [jump tick k] moves [tick] by [k] seps. No overflow can happen. + The move stops at [initial] when going back in time. *) +val jump : t -> Z.t -> t + (** [distance t1 t2] is the absolute value of the difference between [t1] and [t2]. *) val distance : t -> t -> Z.t -- GitLab From 1115baf9c73032f45dd203f9d2fa8ce3b3aa39ab Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 14 Jun 2022 13:23:32 +0200 Subject: [PATCH 13/43] Proto,SCORU: Make players equality testable Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/lib_protocol/sc_rollup_game_repr.mli | 8 +++++--- .../lib_protocol/sc_rollup_refutation_storage.ml | 9 +++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/proto_alpha/lib_protocol/sc_rollup_game_repr.mli b/src/proto_alpha/lib_protocol/sc_rollup_game_repr.mli index 2df0a7415e13..44230e35034f 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_game_repr.mli +++ b/src/proto_alpha/lib_protocol/sc_rollup_game_repr.mli @@ -170,6 +170,8 @@ type t = { (** Return the other player *) val opponent : player -> player +val player_equal : player -> player -> bool + val encoding : t Data_encoding.t val pp_dissection : @@ -236,9 +238,9 @@ val initial : (** A [step] in the game is either a new dissection (if there are intermediate ticks remaining to put in it) or a proof. *) -type step = - | Dissection of (State_hash.t option * Sc_rollup_tick_repr.t) list - | Proof of Sc_rollup_proof_repr.t +type step = Dissection of dissection | Proof of Sc_rollup_proof_repr.t + +and dissection = (State_hash.t option * Sc_rollup_tick_repr.t) list (** A [refutation] is a move in the game. [choice] is the final tick in the current dissection at which the two players agree. *) diff --git a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml index 832421278bd1..35d808b5756a 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml @@ -47,9 +47,10 @@ let timeout_level ctxt = let get_ongoing_game ctxt rollup staker1 staker2 = let open Lwt_tzresult_syntax in - let stakers = Sc_rollup_game_repr.Index.normalize (staker1, staker2) in + let stakers = Sc_rollup_game_repr.Index.make staker1 staker2 in let* ctxt, game = Store.Game.find (ctxt, rollup) stakers in - return (game, ctxt) + let answer = Option.map (fun game -> (game, stakers)) game in + return (answer, ctxt) let get_ongoing_game_for_staker ctxt rollup staker = let open Lwt_tzresult_syntax in @@ -269,9 +270,9 @@ let game_move ctxt rollup ~player ~opponent refutation = match move_result with | Either.Left outcome -> return (Some outcome, ctxt) | Either.Right new_game -> - let* ctxt, _ = Store.Game.update (ctxt, rollup) stakers new_game in + let* ctxt, _ = Store.Game.update (ctxt, rollup) idx new_game in let* ctxt, _ = - Store.Game_timeout.update (ctxt, rollup) stakers (timeout_level ctxt) + Store.Game_timeout.update (ctxt, rollup) idx (timeout_level ctxt) in return (None, ctxt) -- GitLab From e6fde237f687b370d114631be59a6ccaf21761e5 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 14 Jun 2022 13:26:46 +0200 Subject: [PATCH 14/43] Proto,SCORU: Expose a function to compute the conflicts of a staker Signed-off-by: Yann Regis-Gianas --- .../lib_protocol/alpha_context.mli | 30 +++++++++++-- .../lib_protocol/sc_rollup_game_repr.ml | 3 ++ .../sc_rollup_refutation_storage.ml | 45 +++++++++++++++++++ .../sc_rollup_refutation_storage.mli | 31 ++++++++++++- src/proto_alpha/lib_protocol/storage.ml | 29 +++++++----- src/proto_alpha/lib_protocol/storage.mli | 7 +++ .../lib_protocol/storage_functors.ml | 2 +- src/proto_alpha/lib_protocol/storage_sigs.ml | 2 +- 8 files changed, 132 insertions(+), 17 deletions(-) diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 558834397493..03b1ac8d52af 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -2933,14 +2933,16 @@ module Sc_rollup : sig val produce : (module PVM_with_context_and_state) -> - Sc_rollup_inbox_repr.t -> - Raw_level_repr.t -> + Inbox.t -> + Raw_level.t -> (t, error) result Lwt.t end module Game : sig type player = Alice | Bob + val player_equal : player -> player -> bool + type t = { turn : player; inbox_snapshot : Inbox.t; @@ -3030,8 +3032,30 @@ module Sc_rollup : sig type conflict_point = point * point + type conflict = { + other : Staker.t; + their_commitment : Commitment.t; + our_commitment : Commitment.t; + parent_commitment : Commitment.Hash.t; + } + + val conflict_encoding : conflict Data_encoding.t + val get_ongoing_game_for_staker : - context -> t -> Staker.t -> (Game.t option * context) tzresult Lwt.t + context -> + t -> + Staker.t -> + ((Game.t * Game.Index.t) option * context) tzresult Lwt.t + + val conflicting_stakers_uncarbonated : + context -> t -> Staker.t -> conflict list tzresult Lwt.t + + val start_game : + context -> + t -> + player:Signature.public_key_hash -> + opponent:Signature.public_key_hash -> + context tzresult Lwt.t val game_move : context -> diff --git a/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml b/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml index 707761721261..94783453a53c 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml @@ -36,6 +36,9 @@ type t = { dissection : (State_hash.t option * Sc_rollup_tick_repr.t) list; } +let player_equal p1 p2 = + match (p1, p2) with Alice, Alice -> true | Bob, Bob -> true | _, _ -> false + let player_encoding = let open Data_encoding in union diff --git a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml index 35d808b5756a..1751ff8c82bf 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml @@ -311,3 +311,48 @@ let apply_outcome ctxt rollup stakers (outcome : Sc_rollup_game_repr.outcome) = module Internal_for_tests = struct let get_conflict_point = get_conflict_point end + +type conflict = { + other : Sc_rollup_repr.Staker.t; + their_commitment : Sc_rollup_commitment_repr.t; + our_commitment : Sc_rollup_commitment_repr.t; + parent_commitment : Sc_rollup_commitment_repr.Hash.t; +} + +let conflict_encoding = + Data_encoding.( + conv + (fun {other; their_commitment; our_commitment; parent_commitment} -> + (other, their_commitment, our_commitment, parent_commitment)) + (fun (other, their_commitment, our_commitment, parent_commitment) -> + {other; their_commitment; our_commitment; parent_commitment}) + (obj4 + (req "other" Sc_rollup_repr.Staker.encoding) + (req "their_commitment" Sc_rollup_commitment_repr.encoding) + (req "our_commitment" Sc_rollup_commitment_repr.encoding) + (req "parent_commitment" Sc_rollup_commitment_repr.Hash.encoding))) + +let make_conflict ctxt rollup other (our_point, their_point) = + let our_hash = our_point.hash and their_hash = their_point.hash in + let open Lwt_tzresult_syntax in + let get = Sc_rollup_commitment_storage.get_commitment_unsafe ctxt rollup in + let* our_commitment, _ = get our_hash in + let* their_commitment, _ = get their_hash in + let parent_commitment = our_commitment.predecessor in + return {other; their_commitment; our_commitment; parent_commitment} + +let conflicting_stakers_uncarbonated ctxt rollup staker = + let open Lwt_tzresult_syntax in + let* stakers = Store.stakers ctxt rollup in + List.fold_left_es + (fun conflicts (other_staker, _) -> + let*! res = get_conflict_point ctxt rollup staker other_staker in + match res with + | Ok (conflict_point, _) -> + let* conflict = + make_conflict ctxt rollup other_staker conflict_point + in + return (conflict :: conflicts) + | Error _ -> return conflicts) + [] + stakers diff --git a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli index d164f1a2bced..9a34b595fbe6 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli +++ b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli @@ -39,7 +39,36 @@ val get_ongoing_game_for_staker : Raw_context.t -> Sc_rollup_repr.t -> Sc_rollup_repr.Staker.t -> - (Sc_rollup_game_repr.t option * Raw_context.t) tzresult Lwt.t + ((Sc_rollup_game_repr.t * Sc_rollup_game_repr.Index.t) option * Raw_context.t) + tzresult + Lwt.t + +(** A conflict between a staker and an [other] staker. The conflict is + about the commitment that follows the [parent_commitment]: + [their_commitment] and [our_commitment] are distinct, hence in + conflict. *) +type conflict = { + other : Sc_rollup_repr.Staker.t; + their_commitment : Sc_rollup_commitment_repr.t; + our_commitment : Sc_rollup_commitment_repr.t; + parent_commitment : Sc_rollup_commitment_repr.Hash.t; +} + +val conflict_encoding : conflict Data_encoding.t + +(** [conflicting_stakers_uncarbonated rollup staker] returns the list + of conflicts with [staker] in [rollup]. + + Notice that this operation can be expensive as it is proportional + to the number of stakers multiplied by the number of commitments in + the staked branches. Fortunately, this operation is only useful as + an RPC for the rollup node to look for a new conflict to solve. *) +val conflicting_stakers_uncarbonated : + Raw_context.t -> + Sc_rollup_repr.t -> + Sc_rollup_repr.Staker.t -> + conflict list tzresult Lwt.t + val start_game : Raw_context.t -> Sc_rollup_repr.t -> diff --git a/src/proto_alpha/lib_protocol/storage.ml b/src/proto_alpha/lib_protocol/storage.ml index f5f916ec2278..b691a7209658 100644 --- a/src/proto_alpha/lib_protocol/storage.ml +++ b/src/proto_alpha/lib_protocol/storage.ml @@ -480,11 +480,7 @@ module Big_map = struct let encoding = Script_repr.expr_encoding end) - module Contents : - Non_iterable_indexed_carbonated_data_storage_with_values - with type key = Script_expr_hash.t - and type value = Script_repr.expr - and type t := key = struct + module Contents = struct module I = Storage_functors.Make_indexed_carbonated_data_storage (Make_subcontext (Registered) (Indexed_context.Raw_context) @@ -518,7 +514,10 @@ module Big_map = struct let add = I.add - let list_values = I.list_values + let list_values ?offset ?length (ctxt, id) = + let open Lwt_tzresult_syntax in + let* ctxt, values = I.list_values ?offset ?length (ctxt, id) in + return (ctxt, List.map snd values) let consume_deserialize_gas ctxt value = Raw_context.consume_gas ctxt (Script_repr.deserialized_cost value) @@ -1600,19 +1599,27 @@ module Sc_rollup = struct let encoding = Sc_rollup_commitment_repr.Hash.encoding end) + module Stakers_subcontext = + Make_subcontext (Registered) (Indexed_context.Raw_context) + (struct + let name = ["stakers"] + end) + module Stakers = Make_indexed_carbonated_data_storage - (Make_subcontext (Registered) (Indexed_context.Raw_context) - (struct - let name = ["stakers"] - end)) - (Public_key_hash_index) + (Stakers_subcontext) + (Public_key_hash_index) (struct type t = Sc_rollup_commitment_repr.Hash.t let encoding = Sc_rollup_commitment_repr.Hash.encoding end) + let stakers (ctxt : Raw_context.t) (rollup : Sc_rollup_repr.t) = + let open Lwt_tzresult_syntax in + let* _, values = Stakers.list_values (ctxt, rollup) in + return values + module Staker_count = Indexed_context.Make_carbonated_map (struct diff --git a/src/proto_alpha/lib_protocol/storage.mli b/src/proto_alpha/lib_protocol/storage.mli index e6aa79faa30a..74d624504208 100644 --- a/src/proto_alpha/lib_protocol/storage.mli +++ b/src/proto_alpha/lib_protocol/storage.mli @@ -722,6 +722,13 @@ module Sc_rollup : sig and type value = Sc_rollup_commitment_repr.Hash.t and type t = Raw_context.t * Sc_rollup_repr.t + val stakers : + Raw_context.t -> + Sc_rollup_repr.t -> + (Signature.Public_key_hash.t * Sc_rollup_commitment_repr.Hash.t) list + tzresult + Lwt.t + (** Cache: This should always be the number of entries in [Stakers]. Combined with {!Commitment_stake_count} (see below), this ensures we can diff --git a/src/proto_alpha/lib_protocol/storage_functors.ml b/src/proto_alpha/lib_protocol/storage_functors.ml index d32d3f00c456..4170b7bb2f55 100644 --- a/src/proto_alpha/lib_protocol/storage_functors.ml +++ b/src/proto_alpha/lib_protocol/storage_functors.ml @@ -500,7 +500,7 @@ module Make_indexed_carbonated_data_storage_INTERNAL | None -> assert false | Some key -> get_unprojected s key >|=? fun (s, value) -> - (s, value :: rev_values, 0, pred length)) + (s, (key, value) :: rev_values, 0, pred length)) | _ -> Lwt.return acc) >|=? fun (s, rev_values, _offset, _length) -> (C.project s, List.rev rev_values) diff --git a/src/proto_alpha/lib_protocol/storage_sigs.ml b/src/proto_alpha/lib_protocol/storage_sigs.ml index ad16d90af855..7871fc33b00b 100644 --- a/src/proto_alpha/lib_protocol/storage_sigs.ml +++ b/src/proto_alpha/lib_protocol/storage_sigs.ml @@ -222,7 +222,7 @@ module type Non_iterable_indexed_carbonated_data_storage_with_values = sig ?offset:int -> ?length:int -> t -> - (Raw_context.t * value list) tzresult Lwt.t + (Raw_context.t * (key * value) list) tzresult Lwt.t end module type Non_iterable_indexed_carbonated_data_storage_INTERNAL = sig -- GitLab From 6bce05d5e12f251fba33100a6afae0c0705b6b38 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 14 Jun 2022 13:28:56 +0200 Subject: [PATCH 15/43] Proto,SCORU: Cosmetics Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/lib_protocol/alpha_context.mli | 12 ++++++++---- src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml | 6 +++--- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 03b1ac8d52af..a2c070aaab08 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -2924,7 +2924,7 @@ module Sc_rollup : sig type t = {pvm_step : wrapped_proof; inbox : Inbox.Proof.t option} module type PVM_with_context_and_state = sig - include Sc_rollups.PVM.S + include PVM.S val context : context @@ -2955,15 +2955,17 @@ module Sc_rollup : sig type t = private {alice : Staker.t; bob : Staker.t} val make : Staker.t -> Staker.t -> t + + val encoding : t Data_encoding.t end val encoding : t Data_encoding.t val opponent : player -> player - type step = - | Dissection of (State_hash.t option * Tick.t) list - | Proof of Proof.t + type step = Dissection of dissection | Proof of Proof.t + + and dissection = (State_hash.t option * Tick.t) list type refutation = {choice : Tick.t; step : step} @@ -2971,6 +2973,8 @@ module Sc_rollup : sig type reason = Conflict_resolved | Invalid_move of string | Timeout + val refutation_encoding : refutation Data_encoding.t + val pp_reason : Format.formatter -> reason -> unit val reason_encoding : reason Data_encoding.t diff --git a/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml b/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml index 94783453a53c..949032bdd705 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml @@ -180,9 +180,9 @@ let initial inbox ~pvm_name ~(parent : Sc_rollup_commitment_repr.t) ]; } -type step = - | Dissection of (State_hash.t option * Sc_rollup_tick_repr.t) list - | Proof of Sc_rollup_proof_repr.t +type step = Dissection of dissection | Proof of Sc_rollup_proof_repr.t + +and dissection = (State_hash.t option * Sc_rollup_tick_repr.t) list let step_encoding = let open Data_encoding in -- GitLab From 292a701e3ce3d4a87ab2b484bd26a7beae334e77 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 23 Jun 2022 11:04:12 +0200 Subject: [PATCH 16/43] Proto,SCORU: Use error the right way Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/lib_protocol/sc_rollup_proof_repr.ml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/proto_alpha/lib_protocol/sc_rollup_proof_repr.ml b/src/proto_alpha/lib_protocol/sc_rollup_proof_repr.ml index 84333262f7ae..4e5a3f8dd01e 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_proof_repr.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_proof_repr.ml @@ -115,8 +115,6 @@ module type PVM_with_context_and_state = sig val state : state end -type error += Proof_cannot_be_wrapped - let produce pvm_and_state inbox commit_level = let open Lwt_result_syntax in let (module P : PVM_with_context_and_state) = pvm_and_state in @@ -144,4 +142,4 @@ let produce pvm_and_state inbox commit_level = end in match Sc_rollups.wrap_proof (module P_with_proof) with | Some pvm_step -> return {pvm_step; inbox} - | None -> fail Proof_cannot_be_wrapped + | None -> proof_error "Could not wrap proof" -- GitLab From 5e431a34f5a54da811e0654697a6da77c13916ea Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 14 Jun 2022 13:32:47 +0200 Subject: [PATCH 17/43] Proto,SCORU: Update rollup node store Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/bin_sc_rollup_node/store.ml | 126 ++++++++++++++++++-- 1 file changed, 118 insertions(+), 8 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/store.ml b/src/proto_alpha/bin_sc_rollup_node/store.ml index b902beb9bd17..ceb1fb698924 100644 --- a/src/proto_alpha/bin_sc_rollup_node/store.ml +++ b/src/proto_alpha/bin_sc_rollup_node/store.ml @@ -65,7 +65,7 @@ module type Mutable_value = sig val find : t -> value option Lwt.t end -module Make_append_only_map (P : sig +module type KeyValue = sig val path : path val keep_last_n_entries_in_memory : int @@ -77,8 +77,9 @@ module Make_append_only_map (P : sig type value val value_encoding : value Data_encoding.t -end) = -struct +end + +module Make_map (P : KeyValue) = struct (* Ignored for now. *) let _ = P.keep_last_n_entries_in_memory @@ -106,6 +107,21 @@ struct let open Lwt_syntax in let* exists = mem store key in if exists then get store key else return (on_default ()) +end + +module Make_updatable_map (P : KeyValue) = struct + include Make_map (P) + + let add store key value = + let full_path = String.concat "/" (P.path @ [P.string_of_key key]) in + let encode v = Data_encoding.Binary.to_bytes_exn P.value_encoding v in + let encoded_value = encode value in + let info () = info full_path in + IStore.set_exn ~info store (make_key key) encoded_value +end + +module Make_append_only_map (P : KeyValue) = struct + include Make_map (P) let add store key value = let open Lwt_syntax in @@ -231,7 +247,11 @@ module MessageTrees = struct message_tree end -type state_info = {num_messages : Z.t; num_ticks : Z.t} +type state_info = { + num_messages : Z.t; + num_ticks : Z.t; + initial_tick : Sc_rollup.Tick.t; +} (** Extraneous state information for the PVM *) module StateInfo = Make_append_only_map (struct @@ -248,13 +268,79 @@ module StateInfo = Make_append_only_map (struct let value_encoding = let open Data_encoding in conv - (fun {num_messages; num_ticks} -> (num_messages, num_ticks)) - (fun (num_messages, num_ticks) -> {num_messages; num_ticks}) - (obj2 + (fun {num_messages; num_ticks; initial_tick} -> + (num_messages, num_ticks, initial_tick)) + (fun (num_messages, num_ticks, initial_tick) -> + {num_messages; num_ticks; initial_tick}) + (obj3 (req "num_messages" Data_encoding.z) - (req "num_ticks" Data_encoding.z)) + (req "num_ticks" Data_encoding.z) + (req "initial_tick" Sc_rollup.Tick.encoding)) end) +module StateHistoryRepr = struct + let path = ["state_history"] + + type event = { + tick : Sc_rollup.Tick.t; + block_hash : Block_hash.t; + predecessor_hash : Block_hash.t; + level : Raw_level.t; + } + + module TickMap = Map.Make (Sc_rollup.Tick) + + type value = event TickMap.t + + let event_encoding = + let open Data_encoding in + conv + (fun {tick; block_hash; predecessor_hash; level} -> + (tick, block_hash, predecessor_hash, level)) + (fun (tick, block_hash, predecessor_hash, level) -> + {tick; block_hash; predecessor_hash; level}) + (obj4 + (req "tick" Sc_rollup.Tick.encoding) + (req "block_hash" Block_hash.encoding) + (req "predecessor_hash" Block_hash.encoding) + (req "level" Raw_level.encoding)) + + let value_encoding = + let open Data_encoding in + conv + TickMap.bindings + (fun bindings -> TickMap.of_seq (List.to_seq bindings)) + (Data_encoding.list (tup2 Sc_rollup.Tick.encoding event_encoding)) +end + +module StateHistory = struct + include Make_mutable_value (StateHistoryRepr) + + let insert store event = + let open Lwt_result_syntax in + let open StateHistoryRepr in + let*! history = find store in + let history = + match history with + | None -> StateHistoryRepr.TickMap.empty + | Some history -> history + in + set store (TickMap.add event.tick event history) + + let event_of_largest_tick_before store tick = + let open Lwt_result_syntax in + let open StateHistoryRepr in + let*! history = find store in + match history with + | None -> return_none + | Some history -> ( + let events_before, opt_value, _ = TickMap.split tick history in + match opt_value with + | Some event -> return (Some event) + | None -> + return @@ Option.map snd @@ TickMap.max_binding_opt events_before) +end + (** Unaggregated messages per block *) module Messages = Make_append_only_map (struct let path = ["messages"] @@ -363,3 +449,27 @@ module Commitments_published_at_level = Make_append_only_map (struct let value_encoding = Raw_level.encoding end) + +module OngoingGameRepr = struct + let path = ["game"] + + type value = { + state : Sc_rollup.Game.t; + starting_level : Raw_level.t; + last_move_level : Raw_level.t; + } + + let value_encoding = + let open Data_encoding in + conv + (fun {state; starting_level; last_move_level} -> + (state, starting_level, last_move_level)) + (fun (state, starting_level, last_move_level) -> + {state; starting_level; last_move_level}) + @@ obj3 + (req "state" Sc_rollup.Game.encoding) + (req "starting_level" Raw_level.encoding) + (req "last_move_level" Raw_level.encoding) +end + +module OngoingGame = Make_mutable_value (OngoingGameRepr) -- GitLab From 6e421456c31adffaa83f8f2e8d0d0dc8f9726446 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 14 Jun 2022 13:33:28 +0200 Subject: [PATCH 18/43] Proto,SCORU: Update layer 1 service of rollup node Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/bin_sc_rollup_node/layer1.ml | 24 ++++++++++++++++++- src/proto_alpha/bin_sc_rollup_node/layer1.mli | 4 ++++ .../bin_sc_rollup_node/layer1_services.ml | 2 +- 3 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/layer1.ml b/src/proto_alpha/bin_sc_rollup_node/layer1.ml index 4b99d823ad6d..1d4d57b324fe 100644 --- a/src/proto_alpha/bin_sc_rollup_node/layer1.ml +++ b/src/proto_alpha/bin_sc_rollup_node/layer1.ml @@ -95,6 +95,20 @@ module State = struct let value_encoding = head_encoding end) + module Levels = Store.Make_updatable_map (struct + let path = ["tezos"; "levels"] + + let keep_last_n_entries_in_memory = reorganization_window_length + + type key = int32 + + let string_of_key = Int32.to_string + + type value = block_hash + + let value_encoding = Block_hash.encoding + end) + module ProcessedHashes = Store.Make_append_only_map (struct let path = ["tezos"; "processed_blocks"] @@ -138,6 +152,10 @@ module State = struct let is_processed = Store.ProcessedHashes.mem let last_processed_head = Store.LastProcessedHead.find + + let hash_of_level = Store.Levels.get + + let set_hash_of_level = Store.Levels.add end (** @@ -189,7 +207,9 @@ let store_chain_event store base = let* () = Layer1_event.setting_new_head hash level in let* () = State.set_new_head store head in blocks_of_heads base (intermediate_heads @ [head]) - |> List.iter_s (fun (hash, block) -> State.store_block store hash block) + |> List.iter_s (fun (hash, (Block {level; _} as block)) -> + let* () = State.store_block store hash block in + State.set_hash_of_level store level hash) | Rollback {new_head = Head {hash; level} as base} -> let* () = Layer1_event.rollback hash level in State.set_new_head store base @@ -373,6 +393,8 @@ let current_level store = let+ head = State.last_seen_head store in Option.map (fun (Head {level; _}) -> level) head +let hash_of_level = State.hash_of_level + let predecessor store (Head {hash; _}) = let open Lwt_syntax in let+ (Block {predecessor; _}) = State.block_of_hash store hash in diff --git a/src/proto_alpha/bin_sc_rollup_node/layer1.mli b/src/proto_alpha/bin_sc_rollup_node/layer1.mli index 8ea49d73bcde..5c52b8cb6202 100644 --- a/src/proto_alpha/bin_sc_rollup_node/layer1.mli +++ b/src/proto_alpha/bin_sc_rollup_node/layer1.mli @@ -68,6 +68,10 @@ val current_head_hash : Store.t -> Block_hash.t option Lwt.t made. *) val current_level : Store.t -> int32 option Lwt.t +(** [hash_of_level level] returns the current block hash for a given + [level]. *) +val hash_of_level : Store.t -> int32 -> Block_hash.t Lwt.t + (** [predecessor store head] returns the hash of the head's predecessor block] *) val predecessor : Store.t -> head -> Block_hash.t Lwt.t diff --git a/src/proto_alpha/bin_sc_rollup_node/layer1_services.ml b/src/proto_alpha/bin_sc_rollup_node/layer1_services.ml index c1dcdbad2d27..acd7a2704364 100644 --- a/src/proto_alpha/bin_sc_rollup_node/layer1_services.ml +++ b/src/proto_alpha/bin_sc_rollup_node/layer1_services.ml @@ -41,7 +41,7 @@ type 'accu operation_processor = { 'accu -> source:public_key_hash -> 'kind internal_manager_operation -> - 'kind Apply_results.successful_manager_operation_result -> + 'kind Apply_results.successful_internal_manager_operation_result -> 'accu; } -- GitLab From d1cb6b0b3df6f0dc1e364f67aeed9d9cbae4b109 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 14 Jun 2022 13:34:06 +0200 Subject: [PATCH 19/43] Proto,SCORU: Add tick sampling in rollup node Signed-off-by: Yann Regis-Gianas SQUASH add tick sampling Signed-off-by: Yann Regis-Gianas --- .../bin_sc_rollup_node/interpreter.ml | 180 +++++++++++++----- 1 file changed, 133 insertions(+), 47 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/interpreter.ml b/src/proto_alpha/bin_sc_rollup_node/interpreter.ml index a1a9cb4a25c5..9408fa2d5715 100644 --- a/src/proto_alpha/bin_sc_rollup_node/interpreter.ml +++ b/src/proto_alpha/bin_sc_rollup_node/interpreter.ml @@ -28,100 +28,150 @@ open Alpha_context module Inbox = Store.Inbox module type S = sig + module PVM : Pvm.S + (** [process_head node_ctxt store head] interprets the messages associated with a [head] from a chain [event]. This requires the inbox to be updated beforehand. *) val process_head : Node_context.t -> Store.t -> Layer1.head -> unit tzresult Lwt.t + + (** [state_of_tick node_ctxt store tick level] returns [Some (state, hash)] + for a given [tick] if this [tick] happened before + [level]. Otherwise, returns [None].*) + val state_of_tick : + Node_context.t -> + Store.t -> + Sc_rollup.Tick.t -> + Raw_level.t -> + (PVM.state * PVM.hash) option tzresult Lwt.t end -module Make (PVM : Pvm.S) : S = struct +module Make (PVM : Pvm.S) : S with module PVM = PVM = struct module PVM = PVM - (** [eval_until_input state] advances a PVM [state] until it wants more inputs. *) - let eval_until_input state = + let consume_fuel = Option.map pred + + let continue_with_fuel fuel state f = + let open Lwt_syntax in + match fuel with + | Some 0 -> return (state, fuel) + | _ -> f (consume_fuel fuel) state + + (** [eval_until_input ?fuel state] advances a PVM [state] until it + wants more inputs or there are no more [fuel] (if [Some fuel] is + specified). *) + let eval_until_input ?fuel state = let open Lwt_syntax in - let rec go state = + let rec go fuel state = let* input_request = PVM.is_input_state state in + continue_with_fuel fuel state @@ fun fuel state -> match input_request with | No_input_required -> let* next_state = PVM.eval state in - go next_state - | _ -> return state + go fuel next_state + | _ -> return (state, fuel) in - go state + go fuel state - (** [feed_input state input] feeds [input] to the PVM in order to advance [state] to the next step - that requires an input. *) - let feed_input state input = + (** [feed_input state input] feeds [input] to the PVM in order to + advance [state] to the next step that requires an input. *) + let feed_input ?fuel state input = let open Lwt_syntax in - let* state = eval_until_input state in + let* state, fuel = eval_until_input ?fuel state in + continue_with_fuel fuel state @@ fun fuel state -> let* state = PVM.set_input input state in let* state = PVM.eval state in - let* state = eval_until_input state in - return state + let* state, fuel = eval_until_input ?fuel state in + return (state, fuel) - (** [transition_pvm node_ctxt store predecessor_hash hash] runs a PVM at the previous state from block - [predecessor_hash] by consuming as many messages as possible from block [hash]. *) - let transition_pvm node_ctxt store predecessor_hash hash = - let open Node_context in + let eval_level ?fuel store hash state = let open Lwt_result_syntax in - (* Retrieve the previous PVM state from store. *) - let*! predecessor_state = Store.PVMState.find store predecessor_hash in - let* predecessor_state = - match predecessor_state with - | None -> - (* The predecessor is before the origination. - Here we use an RPC to get the boot sector instead of doing this - before and packing it into the [node_ctxt] because the bootsector - might be very large and we don't want to keep that in memory for - ever. - *) - let* boot_sector = - Plugin.RPC.Sc_rollup.boot_sector - node_ctxt.cctxt - (node_ctxt.cctxt#chain, node_ctxt.cctxt#block) - node_ctxt.rollup_address - in - let*! initial_state = PVM.initial_state store boot_sector in - return initial_state - | Some predecessor_state -> return predecessor_state - in - (* Obtain inbox and its messages for this block. *) let*! inbox = Store.Inboxes.get store hash in let inbox_level = Inbox.inbox_level inbox in let*! messages = Store.Messages.get store hash in (* Iterate the PVM state with all the messages for this level. *) - let*! state = - List.fold_left_i_s - (fun message_counter state payload -> + let* state, fuel = + List.fold_left_i_es + (fun message_counter (state, fuel) payload -> let input = Sc_rollup. {inbox_level; message_counter = Z.of_int message_counter; payload} in - feed_input state input) - predecessor_state + let*! state, fuel = feed_input ?fuel state input in + return (state, fuel)) + (state, fuel) messages in + return (state, inbox_level, fuel) + + let state_of_hash node_ctxt store hash = + let open Node_context in + let open Lwt_result_syntax in + let*! state = Store.PVMState.find store hash in + match state with + | None -> + (* The predecessor is before the origination. + Here we use an RPC to get the boot sector instead of doing this + before and packing it into the [node_ctxt] because the bootsector + might be very large and we don't want to keep that in memory for + ever. + *) + let* boot_sector = + Plugin.RPC.Sc_rollup.boot_sector + node_ctxt.cctxt + (node_ctxt.cctxt#chain, node_ctxt.cctxt#block) + node_ctxt.rollup_address + in + let*! initial_state = PVM.initial_state store boot_sector in + return initial_state + | Some state -> return state + + (** [transition_pvm node_ctxt store predecessor_hash hash] runs a PVM at the previous state from block + [predecessor_hash] by consuming as many messages as possible from block [hash]. *) + let transition_pvm node_ctxt store predecessor_hash hash = + let open Lwt_result_syntax in + (* Retrieve the previous PVM state from store. *) + let* predecessor_state = state_of_hash node_ctxt store predecessor_hash in + + let* state, inbox_level, _ = eval_level store hash predecessor_state in + let*! messages = Store.Messages.get store hash in (* Write final state to store. *) let*! () = Store.PVMState.set store hash state in (* Compute extra information about the state. *) let*! initial_tick = PVM.get_tick predecessor_state in + + let*! () = + let open Store.StateHistoryRepr in + let event = + { + tick = initial_tick; + block_hash = hash; + predecessor_hash; + level = inbox_level; + } + in + Store.StateHistory.insert store event + in + let*! last_tick = PVM.get_tick state in (* TODO: #2717 - The number of ticks should not be an arbitrarily-sized integer. + The number of ticks should not be an arbitrarily-sized integer or + the difference between two ticks should be made an arbitrarily-sized + integer too. *) let num_ticks = Sc_rollup.Tick.distance initial_tick last_tick in (* TODO: #2717 The length of messages here can potentially overflow the [int] returned from [List.length]. *) let num_messages = Z.of_int (List.length messages) in - let*! () = Store.StateInfo.add store hash {num_messages; num_ticks} in - + let*! () = + Store.StateInfo.add store hash {num_messages; num_ticks; initial_tick} + in (* Produce events. *) let*! () = Interpreter_event.transitioned_pvm state num_messages in @@ -132,4 +182,40 @@ module Make (PVM : Pvm.S) : S = struct let open Lwt_result_syntax in let*! predecessor_hash = Layer1.predecessor store head in transition_pvm node_ctxt store predecessor_hash hash + + (** [run_until_tick tick] *) + let run_until_tick node_ctxt store predecessor_hash hash tick_distance = + let open Lwt_result_syntax in + let* state = state_of_hash node_ctxt store predecessor_hash in + let* state, _, fuel = eval_level ~fuel:tick_distance store hash state in + assert (fuel = Some 0) ; + return state + + (** [state_of_tick node_ctxt store tick level] returns [Some (state, hash)] + for a given [tick] if this [tick] happened before + [level]. Otherwise, returns [None].*) + let state_of_tick node_ctxt store tick level = + let open Lwt_result_syntax in + let* closest_event = + Store.StateHistory.event_of_largest_tick_before store tick + in + match closest_event with + | None -> return None + | Some event -> + if Raw_level.(event.level > level) then return None + else + let tick_distance = + Sc_rollup.Tick.distance tick event.tick |> Z.to_int + in + let hash = event.block_hash in + let* state = + run_until_tick + node_ctxt + store + event.predecessor_hash + hash + tick_distance + in + let*! hash = PVM.state_hash state in + return (Some (state, hash)) end -- GitLab From 8c890756863410209905cdcf7773689611640c26 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 14 Jun 2022 13:35:17 +0200 Subject: [PATCH 20/43] Proto,SCORU: Fix missing field in commitment function Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/bin_sc_rollup_node/commitment.ml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/commitment.ml b/src/proto_alpha/bin_sc_rollup_node/commitment.ml index 5304623e3b7b..03a309b47b93 100644 --- a/src/proto_alpha/bin_sc_rollup_node/commitment.ml +++ b/src/proto_alpha/bin_sc_rollup_node/commitment.ml @@ -230,7 +230,9 @@ module Make (PVM : Pvm.S) : Commitment_sig.S with module PVM = PVM = struct let update_ticks_and_messages store block_hash = let open Lwt_result_syntax in - let*! {num_messages; num_ticks} = Store.StateInfo.get store block_hash in + let*! {num_messages; num_ticks; initial_tick = _} = + Store.StateInfo.get store block_hash + in let () = Number_of_messages.add num_messages in return @@ Number_of_ticks.add num_ticks -- GitLab From b33c06d26c094cc603760e1cc1ce2d7729490d08 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 14 Jun 2022 13:35:49 +0200 Subject: [PATCH 21/43] Proto,SCORU: Add refutation game logic in rollup node Signed-off-by: Yann Regis-Gianas --- .../bin_sc_rollup_node/components.ml | 3 + src/proto_alpha/bin_sc_rollup_node/daemon.ml | 10 +- .../bin_sc_rollup_node/refutation_game.ml | 312 ++++++++++++++++++ .../bin_sc_rollup_node/refutation_game.mli | 33 ++ .../refutation_game_event.ml | 157 +++++++++ 5 files changed, 513 insertions(+), 2 deletions(-) create mode 100644 src/proto_alpha/bin_sc_rollup_node/refutation_game.ml create mode 100644 src/proto_alpha/bin_sc_rollup_node/refutation_game.mli create mode 100644 src/proto_alpha/bin_sc_rollup_node/refutation_game_event.ml diff --git a/src/proto_alpha/bin_sc_rollup_node/components.ml b/src/proto_alpha/bin_sc_rollup_node/components.ml index fd412ff4aef5..79c4e5ea5f76 100644 --- a/src/proto_alpha/bin_sc_rollup_node/components.ml +++ b/src/proto_alpha/bin_sc_rollup_node/components.ml @@ -32,6 +32,8 @@ module type S = sig module Commitment : Commitment_sig.S with module PVM = PVM module RPC_server : RPC_server.S with module PVM = PVM + + module Refutation_game : Refutation_game.S with module PVM = PVM end module Make (PVM : Pvm.S) : S with module PVM = PVM = struct @@ -39,6 +41,7 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct module Interpreter = Interpreter.Make (PVM) module Commitment = Commitment.Make (PVM) module RPC_server = RPC_server.Make (PVM) + module Refutation_game = Refutation_game.Make (PVM) end let pvm_of_kind : Protocol.Alpha_context.Sc_rollup.Kind.t -> (module Pvm.S) = diff --git a/src/proto_alpha/bin_sc_rollup_node/daemon.ml b/src/proto_alpha/bin_sc_rollup_node/daemon.ml index 561517a19f7e..b70e572207bc 100644 --- a/src/proto_alpha/bin_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/bin_sc_rollup_node/daemon.ml @@ -99,8 +99,8 @@ module Make (PVM : Pvm.S) = struct return_some operations in let* () = - if finalized then Components.Commitment.process_head node_ctxt store head - else return_unit + when_ finalized @@ fun () -> + Components.Commitment.process_head node_ctxt store head in (* Publishing a commitment when one is available does not depend on the state of the current head, but we still need to ensure that the node only published @@ -109,6 +109,12 @@ module Make (PVM : Pvm.S) = struct let* () = Components.Commitment.cement_commitment_if_possible node_ctxt store head in + let* () = + (* At each block, there may be some refutation related actions to + be performed. *) + when_ finalized @@ fun () -> + Components.Refutation_game.process head node_ctxt store + in when_ finalized (fun () -> let*! () = Layer1.mark_processed_head store head in return ()) diff --git a/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml b/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml new file mode 100644 index 000000000000..b62ab16be525 --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml @@ -0,0 +1,312 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs, *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +(** This module implements the refutation game logic of the rollup + node. + + When a new L1 block arises, the rollup node asks the L1 node for + the current game it is part of, if any. + + If a game is running and it is the rollup operator turn, the rollup + node injects the next move of the winning strategy. + + If a game is running and it is not the rollup operator turn, the + rollup node asks the L1 node whether the timeout is reached to play + the timeout argument if possible. + + Otherwise, if no game is running, the rollup node asks the L1 node + whether there is a conflict with one of its disputable commitments. If + there is such a conflict with a commitment C', then the rollup node + starts a game to refute C' by starting a game with one of its staker. + +*) +open Protocol + +open Alpha_context + +module type S = sig + module PVM : Pvm.S + + val process : + Layer1.head -> Node_context.t -> PVM.context -> unit tzresult Lwt.t +end + +module Make (PVM : Pvm.S) : S with module PVM = PVM = struct + module PVM = PVM + module Interpreter = Interpreter.Make (PVM) + open Sc_rollup.Game + + let node_role node_ctxt Sc_rollup.Game.Index.{alice; bob} = + let self = node_ctxt.Node_context.operator in + if Sc_rollup.Staker.equal alice self then Alice + else if Sc_rollup.Staker.equal bob self then Bob + else (* By validity of [ongoing_game] RPC. *) + assert false + + type role = Our_turn | Their_turn + + let turn node_ctxt game players = + let Sc_rollup.Game.Index.{alice; bob} = players in + match (node_role node_ctxt players, game.turn) with + | Alice, Alice -> (Our_turn, bob) + | Bob, Bob -> (Our_turn, alice) + | Alice, Bob -> (Their_turn, bob) + | Bob, Alice -> (Their_turn, bob) + + (** [inject_next_move node_ctxt move] submits an L1 operation to + issue the next move in the refutation game. [node_ctxt] provides + the connection to the Tezos node. *) + let inject_next_move node_ctxt (refutation, opponent) = + let open Node_context in + let open Lwt_result_syntax in + let* source, src_pk, src_sk = Node_context.get_operator_keys node_ctxt in + let {rollup_address; cctxt; _} = node_ctxt in + let* _, _, Manager_operation_result {operation_result; _} = + Client_proto_context.sc_rollup_refute + cctxt + ~chain:cctxt#chain + ~block:cctxt#block + ~refutation + ~opponent + ~source + ~rollup:rollup_address + ~src_pk + ~src_sk + ~fee_parameter:Configuration.default_fee_parameter + () + in + let open Apply_results in + let*! () = + match operation_result with + | Applied (Sc_rollup_refute_result _) -> + Refutation_game_event.refutation_published opponent refutation + | Failed (Sc_rollup_refute_manager_kind, _errors) -> + Refutation_game_event.refutation_failed opponent refutation + | Backtracked (Sc_rollup_refute_result _, _errors) -> + Refutation_game_event.refutation_backtracked opponent refutation + | Skipped Sc_rollup_refute_manager_kind -> + Refutation_game_event.refutation_skipped opponent refutation + in + return_unit + + let as_single_tick_dissection dissection = + match (List.hd dissection, List.last_opt dissection) with + | Some (Some start_hash, start_tick), Some (_stop_state, stop_tick) -> + if Sc_rollup.Tick.distance stop_tick start_tick = Z.one then + Some (start_hash, start_tick) + else None + | _ -> + (* By wellformedness of games returned by the [ongoing_game] RPC. *) + assert false + + let generate_proof node_ctxt store game start_state = + let open Lwt_result_syntax in + let module P = struct + include PVM + + let context = store + + let state = start_state + end in + let*! hash = Layer1.hash_of_level store (Raw_level.to_int32 game.level) in + let*! inbox = Inbox.inbox_of_hash node_ctxt store hash in + let*! r = Sc_rollup.Proof.produce (module P) inbox game.level in + match r with + | Ok r -> return r + | Error _err -> failwith "The rollup node cannot produce a proof." + + let new_dissection node_ctxt store last_level ok our_view = + let open Lwt_result_syntax in + let _start_hash, start_tick = ok in + let our_state, stop_tick = our_view in + (* TODO: #3200 + We should not rely on an hard-coded constant here but instead + introduce a protocol constant for the maximum number of sections + in a dissection. + *) + let max_number_of_sections = Z.of_int 32 in + let trace_length = Z.succ (Sc_rollup.Tick.distance stop_tick start_tick) in + let number_of_sections = Z.min max_number_of_sections trace_length in + let section_length = + Z.(max (of_int 1) (div trace_length number_of_sections)) + in + (* [k] is the number of sections in [rev_dissection]. *) + let rec make rev_dissection k tick = + if Z.equal k (Z.pred number_of_sections) then + return @@ List.rev ((our_state, stop_tick) :: rev_dissection) + else + let* r = Interpreter.state_of_tick node_ctxt store tick last_level in + let hash = Option.map snd r in + let next_tick = Sc_rollup.Tick.jump tick section_length in + make ((hash, tick) :: rev_dissection) (Z.succ k) next_tick + in + make [] Z.zero start_tick + + (** [generate_from_dissection node_ctxt store game] + traverses the current [game.dissection] and returns a move which + performs a new dissection of the execution trace or provide a + refutation proof to serve as the next move of the [game]. *) + let generate_next_dissection node_ctxt store game = + let open Lwt_result_syntax in + let rec traverse ok = function + | [] -> + (* The game invariant states that the dissection from the + opponent must contain a tick we disagree with. If the + retrieved game does not respect this, we cannot trust the + Tezos node we are connected to and prefer to stop here. *) + assert false + | (their_hash, tick) :: dissection -> ( + let open Lwt_result_syntax in + let* our = + Interpreter.state_of_tick node_ctxt store tick game.level + in + match (their_hash, our) with + | None, None -> assert false + | Some _, None | None, Some _ -> + return (ok, (Option.map snd our, tick)) + | Some their_hash, Some (_, our_hash) -> + if Sc_rollup.State_hash.equal our_hash their_hash then + traverse (their_hash, tick) dissection + else return (ok, (Some our_hash, tick))) + in + match game.dissection with + | (Some hash, tick) :: dissection -> + let* ok, ko = traverse (hash, tick) dissection in + let choice = snd ok in + let* dissection = new_dissection node_ctxt store game.level ok ko in + return (choice, dissection) + | [] | (None, _) :: _ -> + (* + By wellformedness of dissection. + A dissection always starts with a tick of the form [(Some hash, tick)]. + A dissection always contains strictly more than one element. + *) + assert false + + let next_move node_ctxt store game = + let open Lwt_result_syntax in + let final_move start_tick = + let* start_state = + Interpreter.state_of_tick node_ctxt store start_tick game.level + in + match start_state with + | None -> assert false + | Some (start_state, _start_hash) -> + let* proof = generate_proof node_ctxt store game start_state in + let choice = start_tick in + return {choice; step = Proof proof} + in + match as_single_tick_dissection game.dissection with + | Some (_start_hash, start_tick) -> final_move start_tick + | None -> ( + let* choice, dissection = + generate_next_dissection node_ctxt store game + in + match as_single_tick_dissection dissection with + | Some (_, start_tick) -> final_move start_tick + | None -> return {choice; step = Dissection dissection}) + + let play_next_move node_ctxt store game opponent = + let open Lwt_result_syntax in + let* refutation = next_move node_ctxt store game in + inject_next_move node_ctxt (Some refutation, opponent) + + let try_timeout node_ctxt players = + let Sc_rollup.Game.Index.{alice; bob} = players in + let open Node_context in + let open Lwt_result_syntax in + let* source, src_pk, src_sk = Node_context.get_operator_keys node_ctxt in + let {rollup_address; cctxt; _} = node_ctxt in + let* _, _, Manager_operation_result {operation_result; _} = + Client_proto_context.sc_rollup_timeout + cctxt + ~chain:cctxt#chain + ~block:cctxt#block + ~source + ~alice + ~bob + ~rollup:rollup_address + ~src_pk + ~src_sk + ~fee_parameter:Configuration.default_fee_parameter + () + in + let open Apply_results in + let*! () = + match operation_result with + | Applied (Sc_rollup_timeout_result _) -> + Refutation_game_event.timeout_published players + | Failed (Sc_rollup_timeout_manager_kind, _errors) -> + Refutation_game_event.timeout_failed players + | Backtracked (Sc_rollup_timeout_result _, _errors) -> + Refutation_game_event.timeout_backtracked players + | Skipped Sc_rollup_timeout_manager_kind -> + Refutation_game_event.timeout_skipped players + in + return_unit + + let play node_ctxt store (game, players) = + match turn node_ctxt game players with + | Our_turn, opponent -> play_next_move node_ctxt store game opponent + | Their_turn, _ -> try_timeout node_ctxt players + + let ongoing_game node_ctxt = + let Node_context.{rollup_address; cctxt; operator; _} = node_ctxt in + Plugin.RPC.Sc_rollup.ongoing_refutation_game + cctxt + (cctxt#chain, cctxt#block) + rollup_address + operator + () + + let play_opening_move node_ctxt conflict = + let open Sc_rollup.Refutation_storage in + inject_next_move node_ctxt (None, conflict.other) + + let start_game_if_conflict node_ctxt = + let open Lwt_result_syntax in + let Node_context.{rollup_address; cctxt; operator; _} = node_ctxt in + let* conflicts = + Plugin.RPC.Sc_rollup.conflicts + cctxt + (cctxt#chain, cctxt#block) + rollup_address + operator + () + in + let play_new_game conflicts = + match conflicts with + | [] -> return () + | conflict :: _conflicts -> play_opening_move node_ctxt conflict + in + play_new_game conflicts + + let process _head node_ctxt store = + let open Lwt_result_syntax in + let* game = ongoing_game node_ctxt in + match game with + | Some game -> play node_ctxt store game + | None -> start_game_if_conflict node_ctxt +end diff --git a/src/proto_alpha/bin_sc_rollup_node/refutation_game.mli b/src/proto_alpha/bin_sc_rollup_node/refutation_game.mli new file mode 100644 index 000000000000..62dcfe35f09e --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/refutation_game.mli @@ -0,0 +1,33 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs, *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +module type S = sig + module PVM : Pvm.S + + val process : + Layer1.head -> Node_context.t -> PVM.context -> unit tzresult Lwt.t +end + +module Make (PVM : Pvm.S) : S with module PVM = PVM 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 new file mode 100644 index 000000000000..101484574649 --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/refutation_game_event.ml @@ -0,0 +1,157 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs, *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +open Protocol.Alpha_context + +(* TODO: https://gitlab.com/tezos/tezos/-/issues/2880 + Add corresponding .mli file. *) + +module Simple = struct + include Internal_event.Simple + + let section = ["sc_rollup_node"; "refutation_game"] + + let timeout = + declare_1 + ~section + ~name:"sc_rollup_node_timeout" + ~msg: + "The rollup node has been slashed because of a timeout issued by \ + {address}" + ~level:Notice + ("address", Signature.Public_key_hash.encoding) + + let invalid_move = + declare_0 + ~section + ~name:"sc_rollup_node_invalid_move" + ~msg: + "The rollup node is about to make an invalid move in the refutation \ + game! It is stopped to avoid being slashed. The problem should be \ + reported immediately or the rollup node should be upgraded to have a \ + chance to be back before the timeout is reached." + ~level:Notice + () + + let refutation_published = + declare_2 + ~section + ~name:"sc_rollup_node_refutation_published" + ~msg: + "Refutation was published - opponent: {opponent}, refutation: \ + {refutation}" + ~level:Notice + ("opponent", Sc_rollup.Staker.encoding) + ("refutation", Data_encoding.option Sc_rollup.Game.refutation_encoding) + + let refutation_failed = + declare_2 + ~section + ~name:"sc_rollup_node_refutation_failed" + ~msg: + "Publishing refutation has failed - opponent: {opponent}, refutation: \ + {refutation}" + ~level:Notice + ("opponent", Sc_rollup.Staker.encoding) + ("refutation", Data_encoding.option Sc_rollup.Game.refutation_encoding) + + let refutation_backtracked = + declare_2 + ~section + ~name:"sc_rollup_node_refutation_backtracked" + ~msg: + "Publishing refutation was backtracked - opponent: {opponent}, \ + refutation: {refutation}" + ~level:Notice + ("opponent", Sc_rollup.Staker.encoding) + ("refutation", Data_encoding.option Sc_rollup.Game.refutation_encoding) + + let refutation_skipped = + declare_2 + ~section + ~name:"sc_rollup_node_refutation_skipped" + ~msg: + "Publishing refutation was skipped - opponent: {opponent}, refutation: \ + {refutation}" + ~level:Notice + ("opponent", Sc_rollup.Staker.encoding) + ("refutation", Data_encoding.option Sc_rollup.Game.refutation_encoding) + + let timeout_published = + declare_1 + ~section + ~name:"sc_rollup_node_timeout_published" + ~msg:"Timeout was published - players: {players}" + ~level:Notice + ("players", Sc_rollup.Game.Index.encoding) + + let timeout_failed = + declare_1 + ~section + ~name:"sc_rollup_node_timeout_failed" + ~msg:"Publishing timeout has failed - players: {players}" + ~level:Notice + ("players", Sc_rollup.Game.Index.encoding) + + let timeout_backtracked = + declare_1 + ~section + ~name:"sc_rollup_node_timeout_backtracked" + ~msg:"Publishing timeout was backtracked - players: {players}" + ~level:Notice + ("players", Sc_rollup.Game.Index.encoding) + + let timeout_skipped = + declare_1 + ~section + ~name:"sc_rollup_node_timeout_skipped" + ~msg:"Publishing timeout was skipped - players: {players}" + ~level:Notice + ("players", Sc_rollup.Game.Index.encoding) +end + +let timeout address = Simple.(emit timeout address) + +let invalid_move () = Simple.(emit invalid_move ()) + +let refutation_published opponent refutation = + Simple.(emit refutation_published (opponent, refutation)) + +let refutation_failed opponent refutation = + Simple.(emit refutation_failed (opponent, refutation)) + +let refutation_backtracked opponent refutation = + Simple.(emit refutation_backtracked (opponent, refutation)) + +let refutation_skipped opponent refutation = + Simple.(emit refutation_skipped (opponent, refutation)) + +let timeout_published players = Simple.(emit timeout_published players) + +let timeout_failed players = Simple.(emit timeout_failed players) + +let timeout_backtracked players = Simple.(emit timeout_backtracked players) + +let timeout_skipped players = Simple.(emit timeout_skipped players) -- GitLab From bb71d21a8feb203abcf27764c661870f334f316f Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 14 Jun 2022 14:57:57 +0200 Subject: [PATCH 22/43] Proto,SCORU: Avoid generating very trace in refutation game PBT This is not totally related but this large output makes the CI fail. Signed-off-by: Yann Regis-Gianas --- .../lib_protocol/test/pbt/test_refutation_game.ml | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/src/proto_alpha/lib_protocol/test/pbt/test_refutation_game.ml b/src/proto_alpha/lib_protocol/test/pbt/test_refutation_game.ml index 82b21eb03536..4fb13dc24313 100644 --- a/src/proto_alpha/lib_protocol/test/pbt/test_refutation_game.ml +++ b/src/proto_alpha/lib_protocol/test/pbt/test_refutation_game.ml @@ -574,25 +574,16 @@ module Strategies (PVM : TestPVM with type hash = State_hash.t) = struct in let* outcome = let rec loop game refuter_move = - let player = if refuter_move then "refuter" else "defender" in let* move = if refuter_move then refuter_client.next_move game else defender_client.next_move game in match move with - | None -> - Printf.eprintf "@[No move from %s@]" player ; - return (if refuter_move then Defender_wins else Refuter_wins) + | None -> return (if refuter_move then Defender_wins else Refuter_wins) | Some move -> ( - Format.eprintf - "@[Move from %s is %a@]@." - player - Game.pp_refutation - move ; let* game_result = Game.play game move in match game_result with | Either.Left outcome -> - Format.eprintf "@[%a@]@." Game.pp_outcome outcome ; return (loser_to_outcome_for_tests outcome.loser alice_is_refuter) | Either.Right game -> loop game (not refuter_move)) -- GitLab From 2cffcc14b2892d336bd971da20df0b872d92aafe Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Tue, 14 Jun 2022 16:34:35 +0200 Subject: [PATCH 23/43] Proto,SCORU: Add a cheat mode in the rollup node Signed-off-by: Yann Regis-Gianas --- .../bin_sc_rollup_node/configuration.ml | 20 +++++++++++++++---- .../bin_sc_rollup_node/configuration.mli | 1 + .../main_sc_rollup_node_alpha.ml | 12 ++++++++--- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/configuration.ml b/src/proto_alpha/bin_sc_rollup_node/configuration.ml index af7f25564bbd..6631a18a813e 100644 --- a/src/proto_alpha/bin_sc_rollup_node/configuration.ml +++ b/src/proto_alpha/bin_sc_rollup_node/configuration.ml @@ -33,6 +33,7 @@ type t = { rpc_addr : string; rpc_port : int; fee_parameter : Injection.fee_parameter; + loser_mode : bool; } let default_data_dir = @@ -147,19 +148,22 @@ let encoding : t Data_encoding.t = rpc_addr; rpc_port; fee_parameter; + loser_mode; } -> ( data_dir, sc_rollup_address, sc_rollup_node_operator, rpc_addr, rpc_port, - fee_parameter )) + fee_parameter, + loser_mode )) (fun ( data_dir, sc_rollup_address, sc_rollup_node_operator, rpc_addr, rpc_port, - fee_parameter ) -> + fee_parameter, + loser_mode ) -> { data_dir; sc_rollup_address; @@ -167,8 +171,9 @@ let encoding : t Data_encoding.t = rpc_addr; rpc_port; fee_parameter; + loser_mode; }) - (obj6 + (obj7 (dft "data-dir" ~description:"Location of the data dir" @@ -189,7 +194,14 @@ let encoding : t Data_encoding.t = "fee-parameter" ~description:"The fee parameter used when injecting operations in L1" fee_parameter_encoding - default_fee_parameter)) + default_fee_parameter) + (dft + "loser-mode" + ~description: + "If enabled, the rollup node will issue wrong commitments (for \ + test only!)" + Data_encoding.bool + false)) let save config = let open Lwt_syntax in diff --git a/src/proto_alpha/bin_sc_rollup_node/configuration.mli b/src/proto_alpha/bin_sc_rollup_node/configuration.mli index 23bb4576d681..586bd50264e7 100644 --- a/src/proto_alpha/bin_sc_rollup_node/configuration.mli +++ b/src/proto_alpha/bin_sc_rollup_node/configuration.mli @@ -31,6 +31,7 @@ type t = { rpc_addr : string; rpc_port : int; fee_parameter : Injection.fee_parameter; + loser_mode : bool; } (** [default_data_dir] is the default value for [data_dir]. *) diff --git a/src/proto_alpha/bin_sc_rollup_node/main_sc_rollup_node_alpha.ml b/src/proto_alpha/bin_sc_rollup_node/main_sc_rollup_node_alpha.ml index 20057bc9e0e3..cf2cea0c1237 100644 --- a/src/proto_alpha/bin_sc_rollup_node/main_sc_rollup_node_alpha.ml +++ b/src/proto_alpha/bin_sc_rollup_node/main_sc_rollup_node_alpha.ml @@ -177,6 +177,9 @@ let burn_cap_arg = | Some t -> return t | None -> failwith "Bad burn cap")) +let loser_mode = + Clic.switch ~long:"loser" ~doc:"Set the loser mode (for tests only!)." () + let group = { Clic.name = "sc_rollup.node"; @@ -188,7 +191,7 @@ let config_init_command = command ~group ~desc:"Configure the smart-contract rollup node." - (args9 + (args10 data_dir_arg rpc_addr_arg rpc_port_arg @@ -197,7 +200,8 @@ let config_init_command = minimal_nanotez_per_gas_unit_arg force_low_fee_arg fee_cap_arg - burn_cap_arg) + burn_cap_arg + loser_mode) (prefixes ["config"; "init"; "on"] @@ sc_rollup_address_param @@ prefixes ["with"; "operator"] @@ -210,7 +214,8 @@ let config_init_command = minimal_nanotez_per_gas_unit, force_low_fee, fee_cap, - burn_cap ) + burn_cap, + loser_mode ) sc_rollup_address sc_rollup_node_operator cctxt -> @@ -231,6 +236,7 @@ let config_init_command = fee_cap; burn_cap; }; + loser_mode; } in save config >>=? fun () -> -- GitLab From df2742fabbdd6196dd4ad318a491d4cf78f41cf0 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Wed, 15 Jun 2022 08:10:18 +0200 Subject: [PATCH 24/43] Proto,SCORU: Introduce a loser mode in the rollup node for tests Signed-off-by: Yann Regis-Gianas --- .../bin_sc_rollup_node/configuration.ml | 21 +++++- .../bin_sc_rollup_node/configuration.mli | 2 +- src/proto_alpha/bin_sc_rollup_node/daemon.ml | 10 +-- .../bin_sc_rollup_node/loser_mode.ml | 74 +++++++++++++++++++ .../bin_sc_rollup_node/loser_mode.mli | 46 ++++++++++++ .../main_sc_rollup_node_alpha.ml | 10 ++- .../bin_sc_rollup_node/node_context.ml | 4 +- .../bin_sc_rollup_node/node_context.mli | 4 + tezt/lib_tezos/sc_rollup_node.ml | 39 +++++----- tezt/lib_tezos/sc_rollup_node.mli | 4 +- 10 files changed, 180 insertions(+), 34 deletions(-) create mode 100644 src/proto_alpha/bin_sc_rollup_node/loser_mode.ml create mode 100644 src/proto_alpha/bin_sc_rollup_node/loser_mode.mli diff --git a/src/proto_alpha/bin_sc_rollup_node/configuration.ml b/src/proto_alpha/bin_sc_rollup_node/configuration.ml index 6631a18a813e..a6b674343eed 100644 --- a/src/proto_alpha/bin_sc_rollup_node/configuration.ml +++ b/src/proto_alpha/bin_sc_rollup_node/configuration.ml @@ -33,7 +33,7 @@ type t = { rpc_addr : string; rpc_port : int; fee_parameter : Injection.fee_parameter; - loser_mode : bool; + loser_mode : Loser_mode.t; } let default_data_dir = @@ -200,10 +200,21 @@ let encoding : t Data_encoding.t = ~description: "If enabled, the rollup node will issue wrong commitments (for \ test only!)" - Data_encoding.bool - false)) + Loser_mode.encoding + Loser_mode.no_failures)) + +let loser_warning_message config = + if config.loser_mode <> Loser_mode.no_failures then + Format.printf + {| +************ WARNING ************* +This rollup node is in loser mode. +This should be used for test only! +************ WARNING ************* +|} let save config = + loser_warning_message config ; let open Lwt_syntax in let json = Data_encoding.Json.construct encoding config in let* () = Lwt_utils_unix.create_dir config.data_dir in @@ -212,4 +223,6 @@ let save config = let load ~data_dir = let open Lwt_result_syntax in let+ json = Lwt_utils_unix.Json.read_file (relative_filename data_dir) in - Data_encoding.Json.destruct encoding json + let config = Data_encoding.Json.destruct encoding json in + loser_warning_message config ; + config diff --git a/src/proto_alpha/bin_sc_rollup_node/configuration.mli b/src/proto_alpha/bin_sc_rollup_node/configuration.mli index 586bd50264e7..82fd3d2557eb 100644 --- a/src/proto_alpha/bin_sc_rollup_node/configuration.mli +++ b/src/proto_alpha/bin_sc_rollup_node/configuration.mli @@ -31,7 +31,7 @@ type t = { rpc_addr : string; rpc_port : int; fee_parameter : Injection.fee_parameter; - loser_mode : bool; + loser_mode : Loser_mode.t; } (** [default_data_dir] is the default value for [data_dir]. *) diff --git a/src/proto_alpha/bin_sc_rollup_node/daemon.ml b/src/proto_alpha/bin_sc_rollup_node/daemon.ml index b70e572207bc..561923cafe57 100644 --- a/src/proto_alpha/bin_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/bin_sc_rollup_node/daemon.ml @@ -254,16 +254,14 @@ let run ~data_dir (cctxt : Protocol_client_context.full) = let*! () = Event.starting_node () in let* configuration = Configuration.load ~data_dir in let open Configuration in - let {sc_rollup_address; sc_rollup_node_operator; fee_parameter; _} = - configuration - in let*! store = Store.load configuration in let* node_ctxt = Node_context.init cctxt - sc_rollup_address - sc_rollup_node_operator - fee_parameter + configuration.sc_rollup_address + configuration.sc_rollup_node_operator + configuration.fee_parameter + ~loser_mode:configuration.loser_mode in let* _pkh, _pk, _skh = Node_context.get_operator_keys node_ctxt in (* Check that the public key hash is valid. *) diff --git a/src/proto_alpha/bin_sc_rollup_node/loser_mode.ml b/src/proto_alpha/bin_sc_rollup_node/loser_mode.ml new file mode 100644 index 000000000000..3522f726a960 --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/loser_mode.ml @@ -0,0 +1,74 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs, *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +type failure = {level : int; message_index : int; message_tick : int} + +let failure_encoding = + let open Data_encoding in + conv + (fun {level; message_index; message_tick} -> + (level, message_index, message_tick)) + (fun (level, message_index, message_tick) -> + {level; message_index; message_tick}) + (obj3 + (req "level" int31) + (req "message_index" int31) + (req "message_tick" int31)) + +let compare_failure f1 f2 = + let open Compare.Int in + match compare f1.level f2.level with + | 0 -> ( + match compare f1.message_index f2.message_index with + | 0 -> compare f1.message_tick f2.message_tick + | n -> n) + | n -> n + +type t = failure list + +let encoding = Data_encoding.list failure_encoding + +let no_failures = [] + +let make s = + let tokens = String.split_on_char ' ' s in + let rec chop = function + | [] | [""] -> [] + | level :: message_index :: message_tick :: rest -> + { + level = int_of_string level; + message_index = int_of_string message_index; + message_tick = int_of_string message_tick; + } + :: chop rest + | _ -> raise Not_found + in + try Some (chop tokens |> List.sort compare_failure) with _ -> None + +let is_failure failures ~level ~message_index = + List.filter + (fun f -> Compare.Int.(f.level = level && f.message_index = message_index)) + failures + |> List.map (fun f -> f.message_tick) diff --git a/src/proto_alpha/bin_sc_rollup_node/loser_mode.mli b/src/proto_alpha/bin_sc_rollup_node/loser_mode.mli new file mode 100644 index 000000000000..d86a269ee844 --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/loser_mode.mli @@ -0,0 +1,46 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs, *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +(** A list of failures. *) +type t + +val encoding : t Data_encoding.t + +(** [no_failures] are planned. *) +val no_failures : t + +(** [make s] parses a list of integers separated by spaces that is a + periodic sequence of triple [level message_index message_tick] + representing a failure that the rollup node is supposed to make. + This function returns [None] if the input string is not syntactically + correct. *) +val make : string -> t option + +(** [is_failure failures ~level ~message_index] returns [message_ticks] + where a failure is supposed to happen at the point + of the rollup node processing of a given inbox [level], a given + [message_index] and for all [message_ticks]. Ticks are sorted by + increasing order. *) +val is_failure : t -> level:int -> message_index:int -> int list diff --git a/src/proto_alpha/bin_sc_rollup_node/main_sc_rollup_node_alpha.ml b/src/proto_alpha/bin_sc_rollup_node/main_sc_rollup_node_alpha.ml index cf2cea0c1237..3dec94adb753 100644 --- a/src/proto_alpha/bin_sc_rollup_node/main_sc_rollup_node_alpha.ml +++ b/src/proto_alpha/bin_sc_rollup_node/main_sc_rollup_node_alpha.ml @@ -178,7 +178,15 @@ let burn_cap_arg = | None -> failwith "Bad burn cap")) let loser_mode = - Clic.switch ~long:"loser" ~doc:"Set the loser mode (for tests only!)." () + Clic.default_arg + ~long:"loser-mode" + ~placeholder:"mode" + ~default:"" + ~doc:"Set the rollup node failure points (for test only!)." + (Clic.parameter (fun _ s -> + match Loser_mode.make s with + | Some t -> return t + | None -> failwith "Invalid syntax for failure points")) let group = { 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 d769f4f3082c..d11e41788933 100644 --- a/src/proto_alpha/bin_sc_rollup_node/node_context.ml +++ b/src/proto_alpha/bin_sc_rollup_node/node_context.ml @@ -34,6 +34,7 @@ type t = { block_finality_time : int; kind : Sc_rollup.Kind.t; fee_parameter : Injection.fee_parameter; + loser_mode : Loser_mode.t; } let get_operator_keys node_ctxt = @@ -42,7 +43,7 @@ let get_operator_keys node_ctxt = (node_ctxt.operator, pk, sk) let init (cctxt : Protocol_client_context.full) rollup_address operator - fee_parameter = + fee_parameter ~loser_mode = let open Lwt_result_syntax in let* initial_level = Plugin.RPC.Sc_rollup.initial_level @@ -61,4 +62,5 @@ let init (cctxt : Protocol_client_context.full) rollup_address operator kind; block_finality_time = 2; fee_parameter; + loser_mode; } 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 e51c11150b6c..9333d5d82488 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,9 @@ type t = { kind : Sc_rollup.Kind.t; (** Kind of the smart contract rollup. *) fee_parameter : Injection.fee_parameter; (** Fee parameter to use when injecting operations in layer 1. *) + loser_mode : Loser_mode.t; + (** If [true], the rollup node issues wrong commitments (for + tests). *) } (** [get_operator_keys cctxt] returns a triple [(pkh, pk, sk)] corresponding @@ -62,4 +65,5 @@ val init : Sc_rollup.t -> Signature.Public_key_hash.t -> Injection.fee_parameter -> + loser_mode:Loser_mode.t -> t tzresult Lwt.t diff --git a/tezt/lib_tezos/sc_rollup_node.ml b/tezt/lib_tezos/sc_rollup_node.ml index 8cdac867d207..6fc4628295b4 100644 --- a/tezt/lib_tezos/sc_rollup_node.ml +++ b/tezt/lib_tezos/sc_rollup_node.ml @@ -86,27 +86,28 @@ let layer1_port sc_node = Node.rpc_port sc_node.persistent_state.node let spawn_command sc_node = Process.spawn ~name:sc_node.name ~color:sc_node.color sc_node.path -let spawn_config_init sc_node rollup_address = +let spawn_config_init sc_node ?loser_mode rollup_address = spawn_command sc_node - [ - "config"; - "init"; - "on"; - rollup_address; - "with"; - "operator"; - operator_pkh sc_node; - "--data-dir"; - data_dir sc_node; - "--rpc-addr"; - rpc_host sc_node; - "--rpc-port"; - string_of_int @@ rpc_port sc_node; - ] - -let config_init sc_node rollup_address = - let process = spawn_config_init sc_node rollup_address in + ([ + "config"; + "init"; + "on"; + rollup_address; + "with"; + "operator"; + operator_pkh sc_node; + "--data-dir"; + data_dir sc_node; + "--rpc-addr"; + rpc_host sc_node; + "--rpc-port"; + string_of_int @@ rpc_port sc_node; + ] + @ match loser_mode with None -> [] | Some mode -> ["--loser-mode"; mode]) + +let config_init sc_node ?loser_mode rollup_address = + let process = spawn_config_init sc_node ?loser_mode rollup_address in let* output = Process.check_and_read_stdout process in match output diff --git a/tezt/lib_tezos/sc_rollup_node.mli b/tezt/lib_tezos/sc_rollup_node.mli index e837793e4207..1a27ab14e559 100644 --- a/tezt/lib_tezos/sc_rollup_node.mli +++ b/tezt/lib_tezos/sc_rollup_node.mli @@ -109,9 +109,9 @@ val wait : t -> Unix.process_status Lwt.t a SIGKILL is sent instead of a SIGTERM. *) val terminate : ?kill:bool -> t -> unit Lwt.t -(** Run [tezos-sc-rollup-node-alpha config init rollup_address]. +(** Run [tezos-sc-rollup-node-alpha config init ?loser_mode rollup_address]. Returns the name of the resulting configuration file. *) -val config_init : t -> string -> string Lwt.t +val config_init : t -> ?loser_mode:string -> string -> string Lwt.t module Config_file : sig (** Sc node configuration files. *) -- GitLab From 411e5a71a7ec33e3311c5443d43e37a7a9a22ac5 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 16 Jun 2022 09:06:03 +0200 Subject: [PATCH 25/43] Proto,SCORU: Cosmetics Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/lib_client/operation_result.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/proto_alpha/lib_client/operation_result.ml b/src/proto_alpha/lib_client/operation_result.ml index 8faabf0be4ec..23cc8591e6b0 100644 --- a/src/proto_alpha/lib_client/operation_result.ml +++ b/src/proto_alpha/lib_client/operation_result.ml @@ -712,7 +712,7 @@ let pp_manager_operation_contents_result ppf op_result = | Sc_rollup_cement_result _ -> "smart contract rollup commitment cementing" | Sc_rollup_publish_result _ -> "smart contract rollup commitment publishing" - | Sc_rollup_refute_result _ -> "smart contract rollup refutation start" + | Sc_rollup_refute_result _ -> "smart contract rollup refutation move" | Sc_rollup_timeout_result _ -> "smart contract rollup refutation timeout" | Sc_rollup_execute_outbox_message_result _ -> "smart contract output message execution" -- GitLab From 30c281319c84fb7e1603b87dd6b7eafb30ad196e Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 16 Jun 2022 09:06:25 +0200 Subject: [PATCH 26/43] Proto,SCORU: Expose dissection pretty printer Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/lib_protocol/alpha_context.mli | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index a2c070aaab08..c2a4abf42565 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -2951,6 +2951,8 @@ module Sc_rollup : sig dissection : (State_hash.t option * Tick.t) list; } + val pp : Format.formatter -> t -> unit + module Index : sig type t = private {alice : Staker.t; bob : Staker.t} -- GitLab From e798ec1ab38db97d2fbabd5f69c2a4ea333e7e9e Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 16 Jun 2022 09:06:43 +0200 Subject: [PATCH 27/43] Proto,SCORU: Change default value for stake Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/lib_parameters/default_parameters.ml | 2 +- src/proto_alpha/lib_protocol/raw_context.ml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/proto_alpha/lib_parameters/default_parameters.ml b/src/proto_alpha/lib_parameters/default_parameters.ml index 36ce5101dabd..cc04b1d1da1f 100644 --- a/src/proto_alpha/lib_parameters/default_parameters.ml +++ b/src/proto_alpha/lib_parameters/default_parameters.ml @@ -193,7 +193,7 @@ let constants_mainnet = max_available_messages = 1_000_000; (* TODO: https://gitlab.com/tezos/tezos/-/issues/2756 The following constants need to be refined. *) - stake_amount = Tez.of_mutez_exn 32_000_000L; + stake_amount = Tez.of_mutez_exn 10_000_000_000L; commitment_period_in_blocks = 30; max_lookahead_in_blocks = 30_000l; max_active_outbox_levels = sc_rollup_max_active_outbox_levels; diff --git a/src/proto_alpha/lib_protocol/raw_context.ml b/src/proto_alpha/lib_protocol/raw_context.ml index f14d37096401..212d9006799a 100644 --- a/src/proto_alpha/lib_protocol/raw_context.ml +++ b/src/proto_alpha/lib_protocol/raw_context.ml @@ -996,7 +996,7 @@ let prepare_first_block ~level ~timestamp ctxt = max_available_messages = 1_000_000; (* TODO: https://gitlab.com/tezos/tezos/-/issues/2756 The following constants need to be refined. *) - stake_amount = Tez_repr.of_mutez_exn 32_000_000L; + stake_amount = Tez_repr.of_mutez_exn 10_000_000_000L; commitment_period_in_blocks = 30; max_lookahead_in_blocks = 30_000l; (* Number of active levels kept for executing outbox messages. -- GitLab From 10174b970360a254eddefc1da9b41ce451f0d703 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 16 Jun 2022 09:07:30 +0200 Subject: [PATCH 28/43] Proto,SCORU: Fix invalid encoding The JSON back-end of Data_encoding needs special form of encodings. Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml b/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml index 949032bdd705..65e6cdd7b3a8 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml @@ -47,13 +47,13 @@ let player_encoding = case ~title:"Alice" (Tag 0) - unit + (constant "alice") (function Alice -> Some () | _ -> None) (fun () -> Alice); case ~title:"Bob" (Tag 1) - unit + (constant "bob") (function Bob -> Some () | _ -> None) (fun () -> Bob); ] @@ -260,7 +260,7 @@ let reason_encoding = case ~title:"Conflict_resolved" (Tag 0) - unit + (constant "conflict_resolved") (function Conflict_resolved -> Some () | _ -> None) (fun () -> Conflict_resolved); case @@ -272,7 +272,7 @@ let reason_encoding = case ~title:"Timeout" (Tag 2) - unit + (constant "timeout") (function Timeout -> Some () | _ -> None) (fun () -> Timeout); ] @@ -299,7 +299,7 @@ let status_encoding = case ~title:"Ongoing" (Tag 0) - unit + (constant "ongoing") (function Ongoing -> Some () | _ -> None) (fun () -> Ongoing); case -- GitLab From 752556f03aaf8daeffad05703e2d2a62b96f78fa Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 16 Jun 2022 09:08:28 +0200 Subject: [PATCH 29/43] Proto,SCORU: Fix temporarily the absence of origination commitment commitment to run the refutation tests. Signed-off-by: Yann Regis-Gianas --- .../lib_protocol/sc_rollup_storage.ml | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/proto_alpha/lib_protocol/sc_rollup_storage.ml b/src/proto_alpha/lib_protocol/sc_rollup_storage.ml index 06a0fea1999d..50a3507cba7a 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_storage.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_storage.ml @@ -40,6 +40,31 @@ let originate ctxt ~kind ~boot_sector ~parameters_ty = >>=? fun (ctxt, param_ty_size_diff, _added) -> let inbox = Sc_rollup_inbox_repr.empty address level.level in Store.Inbox.init ctxt address inbox >>=? fun (ctxt, inbox_size_diff) -> + (* FIXME: #3054 + This is a temporary code until proper origination commitment is implemented. + *) + let number_of_messages = + match Sc_rollup_repr.Number_of_messages.of_int32 0l with + | None -> assert false + | Some x -> x + in + let number_of_ticks = + match Sc_rollup_repr.Number_of_ticks.of_int32 0l with + | None -> assert false + | Some x -> x + in + let initial_commitment = + Sc_rollup_commitment_repr. + { + compressed_state = Sc_rollup_repr.State_hash.zero; + inbox_level = level.level; + predecessor = Commitment_hash.zero; + number_of_messages; + number_of_ticks; + } + in + Store.Commitments.init (ctxt, address) Commitment_hash.zero initial_commitment + >>=? fun (ctxt, _size_diff) -> Store.Last_cemented_commitment.init ctxt address Commitment_hash.zero >>=? fun (ctxt, lcc_size_diff) -> Store.Staker_count.init ctxt address 0l >>=? fun (ctxt, stakers_size_diff) -> -- GitLab From dace40eb2bb80cadb57f1e06ecbc5754c0637e00 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 16 Jun 2022 09:10:14 +0200 Subject: [PATCH 30/43] Proto,SCORU: Introduce an event for the failures of the loser node Signed-off-by: Yann Regis-Gianas --- .../bin_sc_rollup_node/interpreter_event.ml | 28 +++++++++++++++---- .../bin_sc_rollup_node/interpreter_event.mli | 19 +++++++++++-- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/interpreter_event.ml b/src/proto_alpha/bin_sc_rollup_node/interpreter_event.ml index d177dd810f3a..2c13a77c8903 100644 --- a/src/proto_alpha/bin_sc_rollup_node/interpreter_event.ml +++ b/src/proto_alpha/bin_sc_rollup_node/interpreter_event.ml @@ -31,21 +31,39 @@ module Simple = struct let section = ["sc_rollup_node"; "interpreter"] let transitioned_pvm = - declare_3 + declare_4 ~section ~name:"sc_rollup_node_interpreter_transitioned_pvm" ~msg: - "Transitioned PVM to {state_hash} at tick {ticks} with {num_messages} \ - messages" + "Transitioned PVM at inbox level {inbox_level} to {state_hash} at tick \ + {ticks} with {num_messages} messages" ~level:Notice + ("inbox_level", Protocol.Alpha_context.Raw_level.encoding) ("state_hash", State_hash.encoding) ("ticks", Tick.encoding) ("num_messages", Data_encoding.z) + + let intended_failure = + declare_4 + ~section + ~name:"sc_rollup_node_interpreter_intended_failure" + ~msg: + "Intended failure at level {level} for message indexed {message_index} \ + and at the tick {message_tick} of message processing (internal = \ + {internal})." + ~level:Notice + ("level", Data_encoding.int31) + ("message_index", Data_encoding.int31) + ("message_tick", Data_encoding.int31) + ("internal", Data_encoding.bool) end -let transitioned_pvm state num_messages = +let transitioned_pvm inbox_level state num_messages = let open Lwt_syntax in (* TODO (#3094): is this code path taken for Wasm_2_0_0_pvm. Do we need to abstract? *) let* hash = Arith_pvm.state_hash state in let* ticks = Arith_pvm.get_tick state in - Simple.(emit transitioned_pvm (hash, ticks, num_messages)) + Simple.(emit transitioned_pvm (inbox_level, hash, ticks, num_messages)) + +let intended_failure ~level ~message_index ~message_tick ~internal = + Simple.(emit intended_failure (level, message_index, message_tick, internal)) diff --git a/src/proto_alpha/bin_sc_rollup_node/interpreter_event.mli b/src/proto_alpha/bin_sc_rollup_node/interpreter_event.mli index 2c7cd5106921..ca3c3ac70099 100644 --- a/src/proto_alpha/bin_sc_rollup_node/interpreter_event.mli +++ b/src/proto_alpha/bin_sc_rollup_node/interpreter_event.mli @@ -26,6 +26,19 @@ (** This module defines functions that emit the events used when running a PVM transition (see {!Interpreter}). *) -(** [transition_pvm hash n] emits the event that a PVM transition is leading to - the state of the given [hash] by processing [n] messages. *) -val transitioned_pvm : Arith_pvm.state -> Z.t -> unit Lwt.t +(** [transition_pvm inbox_level hash n] emits the event that a PVM + transition is leading to the state of the given [hash] by + processing [n] messages. *) +val transitioned_pvm : + Protocol.Alpha_context.Raw_level.t -> Arith_pvm.state -> Z.t -> unit Lwt.t + +(** [intended_failure level message_index message_tick] emits the event that an + intended failure has been injected at some given [level], during the processing + of a given [message_index] and at tick [message_tick] during this message + processing. *) +val intended_failure : + level:int -> + message_index:int -> + message_tick:int -> + internal:bool -> + unit Lwt.t -- GitLab From 8e2d5fee7773e05c26fb8cf5d0cc5703e276ef8b Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 16 Jun 2022 09:11:37 +0200 Subject: [PATCH 31/43] Proto,SCORU: Ignore failures of the timeout move in rollup node Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/bin_sc_rollup_node/refutation_game.ml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml b/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml index b62ab16be525..3b28bc66fd62 100644 --- a/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml +++ b/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml @@ -268,9 +268,12 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct return_unit let play node_ctxt store (game, players) = + let open Lwt_result_syntax in match turn node_ctxt game players with | Our_turn, opponent -> play_next_move node_ctxt store game opponent - | Their_turn, _ -> try_timeout node_ctxt players + | Their_turn, _ -> ( + let*! res = try_timeout node_ctxt players in + match res with Ok _ -> return_unit | Error _ -> return_unit) let ongoing_game node_ctxt = let Node_context.{rollup_address; cctxt; operator; _} = node_ctxt in -- GitLab From 50f77471717af0f064c463bf943d2a9d777cd144 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 16 Jun 2022 09:12:20 +0200 Subject: [PATCH 32/43] Proto,SCORU: Add a rollup node event to witness conflict detection Signed-off-by: Yann Regis-Gianas --- .../bin_sc_rollup_node/refutation_game.ml | 2 ++ .../refutation_game_event.ml | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml b/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml index 3b28bc66fd62..bcc79b6610fd 100644 --- a/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml +++ b/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml @@ -285,7 +285,9 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct () let play_opening_move node_ctxt conflict = + let open Lwt_syntax in let open Sc_rollup.Refutation_storage in + let* () = Refutation_game_event.conflict_detected conflict in inject_next_move node_ctxt (None, conflict.other) let start_game_if_conflict node_ctxt = 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 101484574649..ff8f7e798762 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 @@ -55,6 +55,20 @@ module Simple = struct ~level:Notice () + let conflict_detected = + declare_5 + ~name:"sc_rollup_node_conflict_detected" + ~msg: + "A conflict has been found with our commitment {our_commitment_hash} \ + at level {level} with staker {other} that hash issued commitment \ + {their_commitment_hash} both based on {parent_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) + ("parent_commitment_hash", Sc_rollup.Commitment.Hash.encoding) + let refutation_published = declare_2 ~section @@ -155,3 +169,20 @@ let timeout_failed players = Simple.(emit timeout_failed players) let timeout_backtracked players = Simple.(emit timeout_backtracked players) let timeout_skipped players = Simple.(emit timeout_skipped players) + +let conflict_detected (conflict : Sc_rollup.Refutation_storage.conflict) = + let our_commitment_hash = Sc_rollup.Commitment.hash conflict.our_commitment in + let their_commitment_hash = + Sc_rollup.Commitment.hash conflict.their_commitment + in + let parent_commitment_hash = conflict.parent_commitment in + let other = conflict.other in + let level = conflict.our_commitment.inbox_level in + Simple.( + emit + conflict_detected + ( our_commitment_hash, + level, + other, + their_commitment_hash, + parent_commitment_hash )) -- GitLab From 22051364a0032acb953797f103f0f0bd3cb51b0b Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 16 Jun 2022 09:23:43 +0200 Subject: [PATCH 33/43] Proto,SCORU: Implement a loser interpreter Signed-off-by: Yann Regis-Gianas --- .../bin_sc_rollup_node/interpreter.ml | 111 ++++++++++++++---- 1 file changed, 90 insertions(+), 21 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/interpreter.ml b/src/proto_alpha/bin_sc_rollup_node/interpreter.ml index 9408fa2d5715..374c4b2ec474 100644 --- a/src/proto_alpha/bin_sc_rollup_node/interpreter.ml +++ b/src/proto_alpha/bin_sc_rollup_node/interpreter.ml @@ -58,34 +58,92 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct | Some 0 -> return (state, fuel) | _ -> f (consume_fuel fuel) state - (** [eval_until_input ?fuel state] advances a PVM [state] until it - wants more inputs or there are no more [fuel] (if [Some fuel] is - specified). *) - let eval_until_input ?fuel state = + let loser_noise = + let inbox_level = + match Raw_level.of_int32 0l with Error _ -> assert false | Ok x -> x + in + Sc_rollup.{payload = "0xFA11"; inbox_level; message_counter = Z.of_int 0} + + (** [eval_until_input level message_index ?fuel start_tick + failing_ticks state] advances a PVM [state] until it wants more + inputs or there are no more [fuel] (if [Some fuel] is + specified). The evaluation is running under the processing of + some [message_index] at a given [level] and this is the + [start_tick] of this message processing. If some [failing_ticks] + are planned by the loser mode, they will be made. *) + let eval_until_input level message_index ?fuel start_tick failing_ticks state + = let open Lwt_syntax in - let rec go fuel state = + let eval_tick tick failing_ticks state = + let normal_eval state = + let* state = PVM.eval state in + return (state, failing_ticks) + in + let failure_insertion_eval state failing_ticks' = + let* () = + Interpreter_event.intended_failure + ~level + ~message_index + ~message_tick:tick + ~internal:true + in + let* state = PVM.set_input loser_noise state in + return (state, failing_ticks') + in + match failing_ticks with + | xtick :: failing_ticks' -> + if xtick = tick then failure_insertion_eval state failing_ticks' + else normal_eval state + | _ -> normal_eval state + in + let rec go fuel tick failing_ticks state = let* input_request = PVM.is_input_state state in - continue_with_fuel fuel state @@ fun fuel state -> - match input_request with - | No_input_required -> - let* next_state = PVM.eval state in - go fuel next_state - | _ -> return (state, fuel) + match fuel with + | Some 0 -> return (state, fuel, tick, failing_ticks) + | None | Some _ -> ( + match input_request with + | No_input_required -> + let* next_state, failing_ticks = + eval_tick tick failing_ticks state + in + go (consume_fuel fuel) (tick + 1) failing_ticks next_state + | _ -> return (state, fuel, tick, failing_ticks)) in - go fuel state + go fuel start_tick failing_ticks state + + let mutate input = {input with Sc_rollup.payload = "0xC0C0"} (** [feed_input state input] feeds [input] to the PVM in order to advance [state] to the next step that requires an input. *) - let feed_input ?fuel state input = + let feed_input level message_index ?fuel ~failing_ticks state input = let open Lwt_syntax in - let* state, fuel = eval_until_input ?fuel state in + let* state, fuel, tick, failing_ticks = + eval_until_input level message_index ?fuel 0 failing_ticks state + in continue_with_fuel fuel state @@ fun fuel state -> + let* input, failing_ticks = + match failing_ticks with + | xtick :: failing_ticks' -> + if xtick = tick then + let* () = + Interpreter_event.intended_failure + ~level + ~message_index + ~message_tick:tick + ~internal:false + in + return (mutate input, failing_ticks') + else return (input, failing_ticks) + | _ -> return (input, failing_ticks) + in let* state = PVM.set_input input state in let* state = PVM.eval state in - let* state, fuel = eval_until_input ?fuel state in + let* state, fuel, _, _ = + eval_until_input level message_index ?fuel tick failing_ticks state + in return (state, fuel) - let eval_level ?fuel store hash state = + let eval_level ?fuel failures store hash state = let open Lwt_result_syntax in (* Obtain inbox and its messages for this block. *) let*! inbox = Store.Inboxes.get store hash in @@ -100,7 +158,13 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct Sc_rollup. {inbox_level; message_counter = Z.of_int message_counter; payload} in - let*! state, fuel = feed_input ?fuel state input in + let level = Raw_level.to_int32 inbox_level |> Int32.to_int in + let failing_ticks = + Loser_mode.is_failure failures ~level ~message_index:message_counter + in + let*! state, fuel = + feed_input level message_counter ?fuel ~failing_ticks state input + in return (state, fuel)) (state, fuel) messages @@ -136,7 +200,9 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct (* Retrieve the previous PVM state from store. *) let* predecessor_state = state_of_hash node_ctxt store predecessor_hash in - let* state, inbox_level, _ = eval_level store hash predecessor_state in + let* state, inbox_level, _ = + eval_level node_ctxt.loser_mode store hash predecessor_state + in let*! messages = Store.Messages.get store hash in (* Write final state to store. *) @@ -173,7 +239,9 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct Store.StateInfo.add store hash {num_messages; num_ticks; initial_tick} in (* Produce events. *) - let*! () = Interpreter_event.transitioned_pvm state num_messages in + let*! () = + Interpreter_event.transitioned_pvm inbox_level state num_messages + in return_unit @@ -187,8 +255,9 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct let run_until_tick node_ctxt store predecessor_hash hash tick_distance = let open Lwt_result_syntax in let* state = state_of_hash node_ctxt store predecessor_hash in - let* state, _, fuel = eval_level ~fuel:tick_distance store hash state in - assert (fuel = Some 0) ; + let* state, _, _ = + eval_level node_ctxt.loser_mode ~fuel:tick_distance store hash state + in return state (** [state_of_tick node_ctxt store tick level] returns [Some (state, hash)] -- GitLab From 19d351a1a81fbf3821e5b04862bf03496ad66b9d Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 16 Jun 2022 09:24:03 +0200 Subject: [PATCH 34/43] Proto,SCORU: Add an integration test for the refutation game logic Signed-off-by: Yann Regis-Gianas --- tezt/tests/sc_rollup.ml | 87 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 5 deletions(-) diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index 2846d0bb1eda..eb1513fcf62a 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -484,10 +484,7 @@ let send_messages ?batch_size n sc_rollup_address client = messages let to_text_messages_arg msgs = - let text_messages = - List.map (fun msg -> Hex.of_string msg |> Hex.show) msgs - in - let json = Ezjsonm.list Ezjsonm.string text_messages in + let json = Ezjsonm.list Ezjsonm.string msgs in "text:" ^ Ezjsonm.to_string ~minify:true json let send_text_messages client sc_rollup_address msgs = @@ -1913,6 +1910,85 @@ let test_rollup_client_list_keys = pp maybe_keys) +(* Refutation game scenarios + ------------------------- +*) +let test_refutation_scenario ?commitment_period ?challenge_window variant + (loser_mode, inputs, final_level) = + test_scenario + ?commitment_period + ?challenge_window + { + tags = ["refutation"; "node"]; + variant; + description = "observing the winning strategy of refutation game"; + } + @@ fun _protocol sc_rollup_node sc_rollup_address node client -> + let honest = Constant.bootstrap1.public_key_hash + and dishonest = Constant.bootstrap2.public_key_hash in + + (* Initialize a dishonest rollup node using the provided loser_mode. *) + let sc_rollup_node2 = + Sc_rollup_node.create node client ~operator_pkh:dishonest + in + let* _configuration_filename = + Sc_rollup_node.config_init ~loser_mode sc_rollup_node2 sc_rollup_address + in + + (* + Send messages to the rollup and wait enough time for the conflicting + commitments to be published, and refuted. + *) + let* () = Sc_rollup_node.run sc_rollup_node in + let* () = Sc_rollup_node.run sc_rollup_node2 in + + let block inputs = + let* () = + Lwt_list.iter_s (send_text_messages client sc_rollup_address) inputs + in + Client.bake_for_and_wait client + in + let* () = Lwt_list.iter_s block inputs in + let* () = bake_levels (final_level - List.length inputs) client in + + (* + We verify that the dishonest node has lost its bond while the + honest node has its bond untouched. + *) + let* honest_balances = contract_balances ~pkh:honest client in + let* dishonest_balances = contract_balances ~pkh:dishonest client in + + let stake_value = 10_000_000_000 in + + Check.( + (honest_balances.frozen = stake_value) + int + ~error_msg: + "Frozen bond of honest player is incorrect, expected value = %R, got %L") ; + + Check.( + (dishonest_balances.frozen = 0) + int + ~error_msg: + "Frozen bond of dishonest player is incorrect, expected value = %R, \ + got %L") ; + + return () + +let rec swap i l = + if i <= 0 then l + else match l with [_] | [] -> l | x :: y :: l -> y :: swap (i - 1) (x :: l) + +let valid_inputs_for_nlevels n = + List.init n @@ fun i -> + [swap i ["3 3 +"; "1"; "1 1 x"; "3 7 8 + * y"; "2 2 out"]] + +let test_refutation protocols = + let challenge_window = 50 in + [("failure_in_internal_step", ("5 1 3", valid_inputs_for_nlevels 10, 30))] + |> List.iter (fun (variant, inputs) -> + test_refutation_scenario ~challenge_window variant inputs protocols) + let register ~protocols = test_origination protocols ; test_rollup_node_configuration protocols ; @@ -1960,4 +2036,5 @@ let register ~protocols = test_rollup_node_uses_boot_sector protocols ; test_rollup_client_show_address protocols ; test_rollup_client_generate_keys protocols ; - test_rollup_client_list_keys protocols + test_rollup_client_list_keys protocols ; + test_refutation protocols -- GitLab From 785631c71923c55c615a70b167ee6b9d3db9e67a Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 23 Jun 2022 07:02:05 +0200 Subject: [PATCH 35/43] Proto,SCORU: Refactor ArithPVM Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml | 8 ++++++-- src/proto_alpha/lib_protocol/sc_rollup_arith.ml | 4 +--- src/proto_alpha/lib_protocol/sc_rollup_arith.mli | 2 ++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml b/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml index 2728d785fee1..1e71b6292e26 100644 --- a/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml +++ b/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml @@ -36,6 +36,11 @@ module Arith_proof_format = struct type proof = IStoreProof.Proof.tree IStoreProof.Proof.t + let context_hash_to_state_hash h = + Sc_rollup.State_hash.hash_bytes [Context_hash.to_bytes h] + + let hash_tree tree = context_hash_to_state_hash (IStoreTree.hash tree) + let verify_proof proof step = (* The rollup node is not supposed to verify proof. We keep this part in case this changes in the future. *) @@ -58,8 +63,7 @@ module Arith_proof_format = struct let kinded_hash_to_state_hash : IStoreProof.Proof.kinded_hash -> Sc_rollup.State_hash.t = function - | `Value hash | `Node hash -> - Sc_rollup.State_hash.hash_bytes [Context_hash.to_bytes hash] + | `Value hash | `Node hash -> context_hash_to_state_hash hash let proof_before proof = kinded_hash_to_state_hash proof.IStoreProof.Proof.before diff --git a/src/proto_alpha/lib_protocol/sc_rollup_arith.ml b/src/proto_alpha/lib_protocol/sc_rollup_arith.ml index 2fceb16ac63b..6aec6a4d1fb1 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_arith.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_arith.ml @@ -739,9 +739,7 @@ module Make (Context : P) : let* status = Status.get in match status with | Halted -> return State_hash.zero - | _ -> - Context_hash.to_bytes @@ Tree.hash state |> fun h -> - return @@ State_hash.hash_bytes [h] + | _ -> return @@ Context.hash_tree state in let open Lwt_syntax in let* state = Monad.run m state in diff --git a/src/proto_alpha/lib_protocol/sc_rollup_arith.mli b/src/proto_alpha/lib_protocol/sc_rollup_arith.mli index dc3efc840c8d..3fae2cb0915c 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_arith.mli +++ b/src/proto_alpha/lib_protocol/sc_rollup_arith.mli @@ -133,6 +133,8 @@ module type P = sig type tree = Tree.tree + val hash_tree : tree -> Sc_rollup_repr.State_hash.t + type proof val proof_encoding : proof Data_encoding.t -- GitLab From ae637fdfa7d8a9a7a492ef301b9f7489c87e3632 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 23 Jun 2022 07:03:09 +0200 Subject: [PATCH 36/43] Proto,SCORU: Fix proof production in rollup node Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml | 6 ++++++ src/proto_alpha/bin_sc_rollup_node/store.ml | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml b/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml index 1e71b6292e26..459fb8b16df8 100644 --- a/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml +++ b/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml @@ -55,6 +55,12 @@ module Arith_proof_format = struct let produce_proof context tree step = let open Lwt_syntax in + let time = Time.Protocol.epoch in + let info () = + IStore.Info.v ~author:"Tezos" (Time.Protocol.to_seconds time) ~message:"" + in + let* _ = IStore.set_tree ~info context [] tree in + let* _commit_key = Store.commit ~time context in match IStoreTree.kinded_key tree with | Some k -> let* p = IStoreProof.produce_tree_proof (IStore.repo context) k step in diff --git a/src/proto_alpha/bin_sc_rollup_node/store.ml b/src/proto_alpha/bin_sc_rollup_node/store.ml index ceb1fb698924..d01def0dc9de 100644 --- a/src/proto_alpha/bin_sc_rollup_node/store.ml +++ b/src/proto_alpha/bin_sc_rollup_node/store.ml @@ -51,6 +51,15 @@ let info message = let date = Unix.gettimeofday () |> int_of_float |> Int64.of_int in Irmin.Info.Default.v ~author:"Tezos smart-contract rollup node" ~message date +let commit ~time ?(message = "") context = + let open Lwt_syntax in + let info = + IStore.Info.v ~author:"Tezos" (Time.Protocol.to_seconds time) ~message + in + let* tree = IStore.tree context in + let* commit = IStore.Commit.v (IStore.repo context) ~info ~parents:[] tree in + return @@ IStore.Commit.key commit + module type Mutable_value = sig type value -- GitLab From 426ec0510a3b015cd5cc280e5375629204e1e4ad Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 23 Jun 2022 07:07:33 +0200 Subject: [PATCH 37/43] Proto,SCORU: Add an explicit failure insertion (for tests) in PVM Signed-off-by: Yann Regis-Gianas --- .../bin_sc_rollup_node/interpreter.ml | 8 +------- src/proto_alpha/lib_protocol/alpha_context.mli | 6 ++++++ .../lib_protocol/sc_rollup_PVM_sem.ml | 4 ++++ src/proto_alpha/lib_protocol/sc_rollup_arith.ml | 16 ++++++++++++++-- src/proto_alpha/lib_protocol/sc_rollup_wasm.ml | 8 ++++++++ 5 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/interpreter.ml b/src/proto_alpha/bin_sc_rollup_node/interpreter.ml index 374c4b2ec474..4abb239fcf1c 100644 --- a/src/proto_alpha/bin_sc_rollup_node/interpreter.ml +++ b/src/proto_alpha/bin_sc_rollup_node/interpreter.ml @@ -58,12 +58,6 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct | Some 0 -> return (state, fuel) | _ -> f (consume_fuel fuel) state - let loser_noise = - let inbox_level = - match Raw_level.of_int32 0l with Error _ -> assert false | Ok x -> x - in - Sc_rollup.{payload = "0xFA11"; inbox_level; message_counter = Z.of_int 0} - (** [eval_until_input level message_index ?fuel start_tick failing_ticks state] advances a PVM [state] until it wants more inputs or there are no more [fuel] (if [Some fuel] is @@ -87,7 +81,7 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct ~message_tick:tick ~internal:true in - let* state = PVM.set_input loser_noise state in + let* state = PVM.Internal_for_tests.insert_failure state in return (state, failing_ticks') in match failing_ticks with diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index c2a4abf42565..caaf69e83d77 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -2619,6 +2619,10 @@ module Sc_rollup : sig val produce_output_proof : context -> state -> output -> (output_proof, error) result Lwt.t + + module Internal_for_tests : sig + val insert_failure : state -> state Lwt.t + end end type t = (module S) @@ -2651,6 +2655,8 @@ module Sc_rollup : sig type tree = Tree.tree + val hash_tree : tree -> State_hash.t + type proof val proof_encoding : proof Data_encoding.t diff --git a/src/proto_alpha/lib_protocol/sc_rollup_PVM_sem.ml b/src/proto_alpha/lib_protocol/sc_rollup_PVM_sem.ml index 5d688ddb87e6..90f448093b7f 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_PVM_sem.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_PVM_sem.ml @@ -323,4 +323,8 @@ module type S = sig outbox. *) val produce_output_proof : context -> state -> output -> (output_proof, error) result Lwt.t + + module Internal_for_tests : sig + val insert_failure : state -> state Lwt.t + end end diff --git a/src/proto_alpha/lib_protocol/sc_rollup_arith.ml b/src/proto_alpha/lib_protocol/sc_rollup_arith.ml index 6aec6a4d1fb1..6d7b07333b16 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_arith.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_arith.ml @@ -31,6 +31,8 @@ module type P = sig type tree = Tree.tree + val hash_tree : tree -> State_hash.t + type proof val proof_encoding : proof Data_encoding.t @@ -1110,6 +1112,14 @@ module Make (Context : P) : return {output_proof; output_proof_state; output_proof_output} | Some (_, false) -> fail Arith_invalid_claim_about_outbox | None -> fail Arith_output_proof_production_failed + + module Internal_for_tests = struct + let insert_failure state = + let add n = Tree.add state ["failures"; string_of_int n] Bytes.empty in + let open Lwt_syntax in + let* n = Tree.length state ["failures"] in + add n + end end module ProtocolImplementation = Make (struct @@ -1127,6 +1137,9 @@ module ProtocolImplementation = Make (struct type tree = Context.tree + let hash_tree tree = + State_hash.context_hash_to_state_hash (Context.Tree.hash tree) + type proof = Context.Proof.tree Context.Proof.t let verify_proof p f = @@ -1137,8 +1150,7 @@ module ProtocolImplementation = Make (struct Lwt.return None let kinded_hash_to_state_hash = function - | `Value hash | `Node hash -> - State_hash.hash_bytes [Context_hash.to_bytes hash] + | `Value hash | `Node hash -> State_hash.context_hash_to_state_hash hash let proof_before proof = kinded_hash_to_state_hash proof.Context.Proof.before diff --git a/src/proto_alpha/lib_protocol/sc_rollup_wasm.ml b/src/proto_alpha/lib_protocol/sc_rollup_wasm.ml index 8243b7403fc7..350fbdba2013 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_wasm.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_wasm.ml @@ -463,6 +463,14 @@ module V2_0_0 = struct let open Lwt_result_syntax in let*! output_proof_state = state_hash state in return {output_proof_state; output_proof_output} + + module Internal_for_tests = struct + let insert_failure state = + let add n = Tree.add state ["failures"; string_of_int n] Bytes.empty in + let open Lwt_syntax in + let* n = Tree.length state ["failures"] in + add n + end end module ProtocolImplementation = Make (struct -- GitLab From 2b5f16833c9042da4bb3505577f01c0157c7f98f Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 23 Jun 2022 07:14:56 +0200 Subject: [PATCH 38/43] Proto,SCORU: Show tick in arith PVM pp Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/lib_protocol/sc_rollup_arith.ml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/proto_alpha/lib_protocol/sc_rollup_arith.ml b/src/proto_alpha/lib_protocol/sc_rollup_arith.ml index 6d7b07333b16..93dc0e52e2fc 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_arith.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_arith.ml @@ -673,6 +673,7 @@ module Make (Context : P) : let* vars_pp = Vars.pp in let* output_pp = Output.pp in let* stack = Stack.to_list in + let* current_tick_pp = CurrentTick.pp in return @@ fun fmt () -> Format.fprintf fmt @@ -684,6 +685,7 @@ module Make (Context : P) : %a@;\ %a@;\ %a@;\ + tick : %a@;\ vars : %a@;\ output :%a@;\ stack : %a@;\ @@ -702,6 +704,8 @@ module Make (Context : P) : () evaluation_result_pp () + current_tick_pp + () vars_pp () output_pp -- GitLab From 9896348b206997ed25616cbb29cbeae6229b4b99 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 23 Jun 2022 07:15:21 +0200 Subject: [PATCH 39/43] Proto,SCORU: Show reason of proof production failure Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/bin_sc_rollup_node/refutation_game.ml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml b/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml index bcc79b6610fd..9230c5e03a9b 100644 --- a/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml +++ b/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml @@ -135,7 +135,11 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct let*! r = Sc_rollup.Proof.produce (module P) inbox game.level in match r with | Ok r -> return r - | Error _err -> failwith "The rollup node cannot produce a proof." + | Error err -> + failwith + "The rollup node cannot produce a proof: %a" + Environment.Error_monad.pp + err let new_dissection node_ctxt store last_level ok our_view = let open Lwt_result_syntax in -- GitLab From 9bc948424d85543469f9d5cb8bc78fcf57251188 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 23 Jun 2022 07:16:14 +0200 Subject: [PATCH 40/43] Proto,SCORU: Make arith PVM status update more consistent Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/lib_protocol/sc_rollup_arith.ml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/proto_alpha/lib_protocol/sc_rollup_arith.ml b/src/proto_alpha/lib_protocol/sc_rollup_arith.ml index 93dc0e52e2fc..fc82fc3fea56 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_arith.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_arith.ml @@ -862,15 +862,14 @@ module Make (Context : P) : let* () = ParsingResult.set None in let* () = ParserState.set SkipLayout in let* () = LexerState.set (0, 0) in - let* () = Status.set Parsing in let* () = Code.clear in return () let start_evaluating : unit t = let open Monad.Syntax in + let* () = Status.set Evaluating in let* () = EvaluationResult.set None in let* () = Stack.clear in - let* () = Status.set Evaluating in return () let stop_parsing outcome = -- GitLab From 84849e51f7814388d8194c2536ae93d1bab65572 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 23 Jun 2022 07:17:47 +0200 Subject: [PATCH 41/43] Proto,SCORU: Define conversion from context hash to state hash Signed-off-by: Yann Regis-Gianas --- src/proto_alpha/lib_protocol/sc_rollup_repr.ml | 2 ++ src/proto_alpha/lib_protocol/sc_rollup_repr.mli | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/lib_protocol/sc_rollup_repr.ml b/src/proto_alpha/lib_protocol/sc_rollup_repr.ml index db14462b787e..54d93a6b21b6 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_repr.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_repr.ml @@ -109,6 +109,8 @@ module State_hash = struct let () = Base58.check_encoded_prefix b58check_encoding prefix encoded_size include Path_encoding.Make_hex (H) + + let context_hash_to_state_hash h = hash_bytes [Context_hash.to_bytes h] end type t = Address.t diff --git a/src/proto_alpha/lib_protocol/sc_rollup_repr.mli b/src/proto_alpha/lib_protocol/sc_rollup_repr.mli index e24094de2651..d53e1b94e12b 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_repr.mli +++ b/src/proto_alpha/lib_protocol/sc_rollup_repr.mli @@ -61,7 +61,11 @@ module Internal_for_tests : sig val originated_sc_rollup : Origination_nonce.t -> Address.t end -module State_hash : S.HASH +module State_hash : sig + include S.HASH + + val context_hash_to_state_hash : Context_hash.t -> t +end (** Number of messages consumed by a single commitment. This represents a claim about the shape of the Inbox, which can be disputed as part of a commitment -- GitLab From 0ecf1ce0f5eae4b6979e04b91e236c7b8d625adf Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 23 Jun 2022 07:29:06 +0200 Subject: [PATCH 42/43] Proto,SCORU: Make rollup node perform timeout only when relevant Signed-off-by: Yann Regis-Gianas --- .../bin_sc_rollup_node/refutation_game.ml | 22 ++++++++-- src/proto_alpha/lib_plugin/RPC.ml | 44 +++++++++++++++++++ 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml b/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml index 9230c5e03a9b..0e527413b0b6 100644 --- a/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml +++ b/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml @@ -275,9 +275,25 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct let open Lwt_result_syntax in match turn node_ctxt game players with | Our_turn, opponent -> play_next_move node_ctxt store game opponent - | Their_turn, _ -> ( - let*! res = try_timeout node_ctxt players in - match res with Ok _ -> return_unit | Error _ -> return_unit) + | Their_turn, _ -> + let* is_timeout_reached = + let Node_context.{rollup_address; cctxt; _} = node_ctxt in + Plugin.RPC.Sc_rollup.is_staker_timeout_reached + cctxt + (cctxt#chain, cctxt#block) + rollup_address + players.alice + players.bob + () + in + if is_timeout_reached then + let*! res = try_timeout node_ctxt players in + match res with + | Ok _ -> return_unit + | Error _ -> + let*! () = Refutation_game_event.timeout_failed players in + return_unit + else return_unit let ongoing_game node_ctxt = let Node_context.{rollup_address; cctxt; operator; _} = node_ctxt in diff --git a/src/proto_alpha/lib_plugin/RPC.ml b/src/proto_alpha/lib_plugin/RPC.ml index 631c94f19da3..6d4dfd320cc2 100644 --- a/src/proto_alpha/lib_plugin/RPC.ml +++ b/src/proto_alpha/lib_plugin/RPC.ml @@ -1850,6 +1850,29 @@ module Sc_rollup = struct ~output RPC_path.(path /: Sc_rollup.Address.rpc_arg / "game") + let is_staker_timeout_reached = + let query = + let open RPC_query in + let open Sc_rollup.Game.Index in + query (fun alice bob -> + let alice = Sc_rollup.Staker.of_b58check_exn alice + and bob = Sc_rollup.Staker.of_b58check_exn bob in + make alice bob) + |+ field "alice" RPC_arg.string "" (fun x -> + Format.asprintf "%a" Sc_rollup.Staker.pp x.alice) + |+ field "bob" RPC_arg.string "" (fun x -> + Format.asprintf "%a" Sc_rollup.Staker.pp x.bob) + |> seal + in + let output = Data_encoding.bool in + RPC_service.get_service + ~description: + "Determine whether the timeout of the current player in the game \ + played by the given stakers is reached" + ~query + ~output + RPC_path.(path /: Sc_rollup.Address.rpc_arg / "timeout") + let conflicts = let query = let open RPC_query in @@ -1939,6 +1962,17 @@ module Sc_rollup = struct in return game) + let register_staker_timeout_reached () = + Registration.register1 + ~chunked:false + S.is_staker_timeout_reached + (fun context rollup game_index () -> + let open Lwt_tzresult_syntax in + let*! res = + Sc_rollup.Refutation_storage.timeout context rollup game_index + in + match res with Ok _ -> return true | _ -> return false) + let register_conflicts () = Registration.register1 ~chunked:false @@ -1958,6 +1992,7 @@ module Sc_rollup = struct register_commitment () ; register_root () ; register_ongoing_refutation_game () ; + register_staker_timeout_reached () ; register_conflicts () let list ctxt block = RPC_context.make_call0 S.root ctxt block () () @@ -1985,6 +2020,15 @@ module Sc_rollup = struct sc_rollup_address staker + let is_staker_timeout_reached ctxt block sc_rollup_address alice bob = + let game_index = Sc_rollup.Game.Index.make alice bob in + RPC_context.make_call1 + S.is_staker_timeout_reached + ctxt + block + sc_rollup_address + game_index + let conflicts ctxt block sc_rollup_address staker = RPC_context.make_call1 S.conflicts ctxt block sc_rollup_address staker end -- GitLab From ac50fdecc06c0497f15a43be35b0313cecfb1d45 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Thu, 23 Jun 2022 10:50:44 +0200 Subject: [PATCH 43/43] Proto,SCORU: Add a deterministic delay to start a refutation game When two identical implementations of the rollup node are running, they will start the game simultaneously and one of the two injected operations will fail. To prevent this, we introduce a deterministic strategy where the rollup node starts the game depending on a comparison between the adresses of the two operators in conflict and Signed-off-by: Yann Regis-Gianas --- .../bin_sc_rollup_node/refutation_game.ml | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml b/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml index 0e527413b0b6..68cee4a064de 100644 --- a/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml +++ b/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml @@ -310,7 +310,7 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct let* () = Refutation_game_event.conflict_detected conflict in inject_next_move node_ctxt (None, conflict.other) - let start_game_if_conflict node_ctxt = + let start_game_if_conflict (Layer1.Head {level; _}) node_ctxt = let open Lwt_result_syntax in let Node_context.{rollup_address; cctxt; operator; _} = node_ctxt in let* conflicts = @@ -324,14 +324,34 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct let play_new_game conflicts = match conflicts with | [] -> return () - | conflict :: _conflicts -> play_opening_move node_ctxt conflict + | conflict :: _conflicts -> + (* + + When two identical implementations of the rollup node are + running, they will start the game simultaneously and one + of the two injected operations will fail. To prevent + this, we introduce a deterministic strategy where the + rollup node starts the game depending on a comparison + between the adresses of the two operators in conflict and + the parity of the level. + + *) + let open Sc_rollup.Refutation_storage in + let cmp = + if Signature.Public_key_hash.compare conflict.other operator < 0 + then 0 + else 1 + in + if Int32.to_int level mod 2 = cmp then + play_opening_move node_ctxt conflict + else return_unit in play_new_game conflicts - let process _head node_ctxt store = + let process head node_ctxt store = let open Lwt_result_syntax in let* game = ongoing_game node_ctxt in match game with | Some game -> play node_ctxt store game - | None -> start_game_if_conflict node_ctxt + | None -> start_game_if_conflict head node_ctxt end -- GitLab