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 e763bbd0b49660caa14a4c88db346c50133812c3..84325edd3fc01c4740a5e4cc8701fbaba92db1e6 100644 --- a/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml +++ b/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml @@ -52,6 +52,7 @@ module Impl : Pvm.S = struct match status with | Halted -> "Halted" | Waiting_for_input_message -> "Waiting for input message" + | Waiting_for_reveal -> "Waiting for reveal" | Parsing -> "Parsing" | Evaluating -> "Evaluating" end diff --git a/src/proto_alpha/bin_sc_rollup_node/daemon.ml b/src/proto_alpha/bin_sc_rollup_node/daemon.ml index 24b860f6a08bf03703463a40c7528316568c7b5a..0391b2ccd844528d0ee0ab8afb996f779f1afc38 100644 --- a/src/proto_alpha/bin_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/bin_sc_rollup_node/daemon.ml @@ -201,7 +201,7 @@ module Make (PVM : Pvm.S) = struct in return_unit - let process_head node_ctxt head_state = + let process_head configuration node_ctxt head_state = let open Lwt_result_syntax in let {finalized; seen_before; head} = head_state in let* () = @@ -232,7 +232,7 @@ module Make (PVM : Pvm.S) = struct (* At each block, there may be some refutation related actions to be performed. *) when_ finalized @@ fun () -> - Components.Refutation_game.process head node_ctxt + Components.Refutation_game.process head configuration node_ctxt in when_ finalized (fun () -> let*! () = Layer1.mark_processed_head node_ctxt.store head in @@ -267,7 +267,7 @@ module Make (PVM : Pvm.S) = struct Because heads that still have not been processed as finalized are persisted to disk, this function is robust against interruptions. *) - let on_layer_1_chain_event node_ctxt chain_event = + let on_layer_1_chain_event configuration node_ctxt chain_event = let open Lwt_result_syntax in let*! old_heads = Layer1.get_heads_not_finalized node_ctxt.Node_context.store @@ -289,7 +289,9 @@ module Make (PVM : Pvm.S) = struct Daemon_event.processing_heads_iteration old_heads new_heads in let head_states = categorise_heads node_ctxt old_heads new_heads in - let* () = List.iter_es (process_head node_ctxt) head_states in + let* () = + List.iter_es (process_head configuration node_ctxt) head_states + in (* Return new_head to be processed as finalized head if the next chain event is of type SameBranch. *) @@ -320,6 +322,7 @@ module Make (PVM : Pvm.S) = struct List.iter_es (fun head -> process_head + configuration node_ctxt {head; finalized = true; seen_before = true}) final_heads @@ -353,7 +356,7 @@ module Make (PVM : Pvm.S) = struct Lwt_stream.iter_s (fun event -> let open Lwt_syntax in - let* res = on_layer_1_chain_event node_ctxt event in + let* res = on_layer_1_chain_event configuration node_ctxt event in match res with | Ok () -> return_unit | Error trace when is_connection_error trace -> @@ -483,6 +486,7 @@ let run ~data_dir (cctxt : Protocol_client_context.full) = Node_context.init cctxt dal_cctxt + ~data_dir l1_ctxt configuration.sc_rollup_address kind diff --git a/src/proto_alpha/bin_sc_rollup_node/interpreter.ml b/src/proto_alpha/bin_sc_rollup_node/interpreter.ml index 76e058a80523e2faac086888d89c810494f24430..4844dee6b44b652c3bb55003177701894577286b 100644 --- a/src/proto_alpha/bin_sc_rollup_node/interpreter.ml +++ b/src/proto_alpha/bin_sc_rollup_node/interpreter.ml @@ -53,7 +53,7 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct let consume_fuel = Option.map pred let continue_with_fuel fuel state f = - let open Lwt_syntax in + let open Lwt_result_syntax in match fuel with | Some 0 -> return (state, fuel) | _ -> f (consume_fuel fuel) state @@ -65,23 +65,23 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct 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 eval_until_input data_dir level message_index ~fuel start_tick + failing_ticks state = + let open Lwt_result_syntax in let eval_tick tick failing_ticks state = let normal_eval state = - let* state = PVM.eval state in + let*! state = PVM.eval state in return (state, failing_ticks) in let failure_insertion_eval state failing_ticks' = - let* () = + let*! () = Interpreter_event.intended_failure ~level ~message_index ~message_tick:tick ~internal:true in - let* state = PVM.Internal_for_tests.insert_failure state in + let*! state = PVM.Internal_for_tests.insert_failure state in return (state, failing_ticks') in match failing_ticks with @@ -90,7 +90,7 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct | _ -> normal_eval state in let rec go fuel tick failing_ticks state = - let* input_request = PVM.is_input_state state in + let*! input_request = PVM.is_input_state state in match fuel with | Some 0 -> return (state, fuel, tick, failing_ticks) | None | Some _ -> ( @@ -100,6 +100,15 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct eval_tick tick failing_ticks state in go (consume_fuel fuel) (tick + 1) failing_ticks next_state + | Needs_reveal (Reveal_raw_data hash) -> ( + match Reveals.get ~data_dir ~pvm_name:PVM.name ~hash with + | None -> + tzfail (Sc_rollup_node_errors.Cannot_retrieve_reveal hash) + | Some data -> + let*! next_state = + PVM.set_input (Reveal (Raw_data data)) state + in + go (consume_fuel fuel) (tick + 1) failing_ticks next_state) | _ -> return (state, fuel, tick, failing_ticks)) in go fuel start_tick failing_ticks state @@ -115,17 +124,17 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct step that requires an input. This function is controlled by some [fuel] and may introduce intended failures at some given [failing_ticks]. *) - let feed_input level message_index ~fuel ~failing_ticks state input = - let open Lwt_syntax in + let feed_input data_dir level message_index ~fuel ~failing_ticks state input = + let open Lwt_result_syntax in let* state, fuel, tick, failing_ticks = - eval_until_input level message_index ~fuel 0 failing_ticks state + eval_until_input data_dir 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* () = + let*! () = Interpreter_event.intended_failure ~level ~message_index @@ -136,13 +145,20 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct else return (input, failing_ticks) | _ -> return (input, failing_ticks) in - let* state = PVM.set_input input state in + let*! state = PVM.set_input (Inbox_message input) state in let* state, fuel, _tick, _failing_ticks = - eval_until_input level message_index ~fuel tick failing_ticks state + eval_until_input + data_dir + level + message_index + ~fuel + tick + failing_ticks + state in return (state, fuel) - let eval_block_inbox ?fuel failures store hash state = + let eval_block_inbox data_dir ?fuel failures store hash state = let open Lwt_result_syntax in (* Obtain inbox and its messages for this block. *) let*! inbox = Store.Inboxes.find store hash in @@ -181,8 +197,9 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct ~level ~message_index:message_counter in - let*! state, fuel = + let* state, fuel = feed_input + data_dir level message_counter ~fuel @@ -226,6 +243,7 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct let transition_pvm node_ctxt ctxt predecessor_hash (Layer1.Head {hash; level}) = let open Lwt_result_syntax in + let data_dir = node_ctxt.Node_context.data_dir in (* Retrieve the previous PVM state from store. *) let pred_level = Int32.pred level |> Raw_level.of_int32_exn in let* ctxt, predecessor_state = @@ -235,6 +253,7 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct in let* state, num_messages, inbox_level, _fuel = eval_block_inbox + data_dir node_ctxt.loser_mode node_ctxt.store hash @@ -303,6 +322,7 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct in let* state, _counter, _level, _fuel = eval_block_inbox + node_ctxt.data_dir node_ctxt.loser_mode ~fuel:tick_distance node_ctxt.store 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 75dfc0383b383c1ac80db319c7ddcdbdb1e28891..acd01d233166c4857b4ba9ae707ad8bedab89ab0 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 @@ -194,6 +194,22 @@ let reconnection_delay_arg = (Clic.parameter (fun _ p -> try return (float_of_string p) with _ -> failwith "Cannot read float")) +let filename_arg = + Clic.default_arg + ~long:"filename" + ~placeholder:"filename" + ~doc:"The path to the file to import." + ~default:"import.in" + Client_proto_args.string_parameter + +let pvm_name_arg = + Clic.default_arg + ~long:"pvm-name" + ~placeholder:"pvm_name" + ~doc:"The name of the PVM." + ~default:"arith" + Client_proto_args.string_parameter + let group = { Clic.name = "sc_rollup.node"; @@ -280,10 +296,22 @@ let run_command = (prefixes ["run"] @@ stop) (fun data_dir cctxt -> Daemon.run ~data_dir cctxt >>=? fun () -> return ()) +let import_command = + let open Clic in + command + ~group + ~desc:"Run the rollup daemon." + (args3 data_dir_arg filename_arg pvm_name_arg) + (prefixes ["import"] @@ stop) + (fun (data_dir, filename, pvm_name) cctxt -> + let hash = Reveals.import ~data_dir ~filename ~pvm_name in + cctxt#message "%a" Protocol.Alpha_context.Sc_rollup.Input_hash.pp hash + >>= return) + let sc_rollup_commands () = List.map (Clic.map_command (new Protocol_client_context.wrap_full)) - [config_init_command; run_command] + [config_init_command; run_command; import_command] let select_commands _ _ = return (sc_rollup_commands () @ Client_helpers_commands.commands ()) 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 15f53f4cb5cf619c8777185073cd3669bed62011..18c6b8cadf48de3927280748da781549b4974df8 100644 --- a/src/proto_alpha/bin_sc_rollup_node/node_context.ml +++ b/src/proto_alpha/bin_sc_rollup_node/node_context.ml @@ -29,6 +29,7 @@ open Alpha_context type t = { cctxt : Protocol_client_context.full; dal_cctxt : Dal_node_client.cctxt; + data_dir : string; l1_ctxt : Layer1.t; rollup_address : Sc_rollup.t; operators : Configuration.operators; @@ -63,13 +64,14 @@ let get_fee_parameter node_ctxt purpose = let retrieve_constants cctxt = Protocol.Constants_services.all cctxt (cctxt#chain, cctxt#block) -let init (cctxt : Protocol_client_context.full) dal_cctxt l1_ctxt rollup_address - kind operators fee_parameters ~loser_mode store context = +let init (cctxt : Protocol_client_context.full) dal_cctxt ~data_dir l1_ctxt + rollup_address kind operators fee_parameters ~loser_mode store context = let open Lwt_result_syntax in let+ protocol_constants = retrieve_constants cctxt in { cctxt; dal_cctxt; + data_dir; l1_ctxt; rollup_address; operators; 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 32ced3509141769d88bcf98c1bd7fbf9486b922e..1402747429c5f2aa750dae9e29969815860ac3c3 100644 --- a/src/proto_alpha/bin_sc_rollup_node/node_context.mli +++ b/src/proto_alpha/bin_sc_rollup_node/node_context.mli @@ -33,6 +33,7 @@ type t = { (** Client context used by the rollup node. *) dal_cctxt : Dal_node_client.cctxt; (** Client context to query the dal node. *) + data_dir : string; (** Node data dir. *) l1_ctxt : Layer1.t; (** Layer 1 context to fetch blocks and monitor heads, etc.*) rollup_address : Sc_rollup.t; @@ -72,7 +73,7 @@ val is_operator : t -> Signature.Public_key_hash.t -> bool *) val get_fee_parameter : t -> Configuration.purpose -> Injection.fee_parameter -(** [init cctxt dal_cctxt l1_ctxt sc_rollup genesis_info kind operators fees +(** [init cctxt dal_cctxt ~data_dir l1_ctxt sc_rollup genesis_info kind operators fees ~loser_mode store context] initialises the rollup representation. The rollup origination level and kind are fetched via an RPC call to the layer1 node that [cctxt] uses for RPC requests. @@ -80,6 +81,7 @@ val get_fee_parameter : t -> Configuration.purpose -> Injection.fee_parameter val init : Protocol_client_context.full -> Dal_node_client.cctxt -> + data_dir:string -> Layer1.t -> Sc_rollup.t -> Protocol.Alpha_context.Sc_rollup.Kind.t -> 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 81e84792e97c051d712b33e60ad74f13fdaa51fc..7468353aa1b207512cd46535a30e82d45ed9e873 100644 --- a/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml +++ b/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml @@ -49,7 +49,8 @@ open Alpha_context module type S = sig module PVM : Pvm.S - val process : Layer1.head -> Node_context.t -> unit tzresult Lwt.t + val process : + Layer1.head -> Configuration.t -> Node_context.t -> unit tzresult Lwt.t end module Make (Interpreter : Interpreter.S) : @@ -83,7 +84,7 @@ module Make (Interpreter : Interpreter.S) : in Injector.add_pending_operation ~source refute_operation - let generate_proof node_ctxt game start_state = + let generate_proof configuration node_ctxt game start_state = let open Lwt_result_syntax in let*! hash = Layer1.hash_of_level @@ -109,6 +110,12 @@ module Make (Interpreter : Interpreter.S) : let state = start_state + let reveal hash = + Reveals.get + ~data_dir:configuration.Configuration.data_dir + ~pvm_name:PVM.name + ~hash + module Inbox_with_history = struct include Context.Inbox @@ -200,7 +207,7 @@ module Make (Interpreter : Interpreter.S) : Sc_rollup_node_errors .Unreliable_tezos_node_returning_inconsistent_game - let next_move node_ctxt game = + let next_move configuration node_ctxt game = let open Lwt_result_syntax in let final_move start_tick = let* start_state = @@ -212,7 +219,9 @@ module Make (Interpreter : Interpreter.S) : Sc_rollup_node_errors .Unreliable_tezos_node_returning_inconsistent_game | Some (start_state, _start_hash) -> - let* proof = generate_proof node_ctxt game start_state in + let* proof = + generate_proof configuration node_ctxt game start_state + in let choice = start_tick in return {choice; step = Proof proof} in @@ -232,9 +241,9 @@ module Make (Interpreter : Interpreter.S) : let choice = agreed_start_chunk.tick in final_move choice - let play_next_move node_ctxt game self opponent = + let play_next_move configuration node_ctxt game self opponent = let open Lwt_result_syntax in - let* refutation = next_move node_ctxt game in + let* refutation = next_move configuration node_ctxt game in inject_next_move node_ctxt self ~refutation:(Some refutation) ~opponent let play_timeout (node_ctxt : Node_context.t) self stakers = @@ -265,12 +274,13 @@ module Make (Interpreter : Interpreter.S) : return (not is_it_me) | _ -> return_false - let play head_block node_ctxt self game staker1 staker2 = + let play head_block configuration node_ctxt self game staker1 staker2 = let open Lwt_result_syntax in let players = (staker1, staker2) in let index = Sc_rollup.Game.Index.make staker1 staker2 in match turn ~self game index with - | Our_turn {opponent} -> play_next_move node_ctxt game self opponent + | Our_turn {opponent} -> + play_next_move configuration node_ctxt game self opponent | Their_turn -> let* timeout_reached = timeout_reached ~self head_block node_ctxt players @@ -318,7 +328,7 @@ module Make (Interpreter : Interpreter.S) : return_unit | Error errs -> Lwt.return (Error errs) - let process (Layer1.Head {hash; _}) node_ctxt = + let process (Layer1.Head {hash; _}) configuration node_ctxt = let head_block = `Hash (hash, 0) in let open Lwt_result_syntax in let refute_signer = Node_context.get_operator node_ctxt Refute in @@ -330,6 +340,6 @@ module Make (Interpreter : Interpreter.S) : let* res = ongoing_game head_block node_ctxt self in match res with | Some (game, staker1, staker2) -> - play head_block node_ctxt self game staker1 staker2 + play head_block configuration node_ctxt self game staker1 staker2 | None -> start_game_if_conflict head_block node_ctxt self) 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 index 9a6b0ef7111a8faf1b78c37a730049e19e526b09..4902f42664d6544cbfe5f68a239ec9703da85f97 100644 --- a/src/proto_alpha/bin_sc_rollup_node/refutation_game.mli +++ b/src/proto_alpha/bin_sc_rollup_node/refutation_game.mli @@ -28,7 +28,10 @@ module type S = sig module PVM : Pvm.S - val process : Layer1.head -> Node_context.t -> unit tzresult Lwt.t + (** [process head config node_ctxt] reacts to any operations of + [head] related to refutation games. *) + val process : + Layer1.head -> Configuration.t -> Node_context.t -> unit tzresult Lwt.t end module Make (Interpreter : Interpreter.S) : S with module PVM = Interpreter.PVM diff --git a/src/proto_alpha/bin_sc_rollup_node/reveals.ml b/src/proto_alpha/bin_sc_rollup_node/reveals.ml new file mode 100644 index 0000000000000000000000000000000000000000..72ace672c7622c9e7403b03627f1c00fb9c51f2c --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/reveals.ml @@ -0,0 +1,145 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +let string_of_file filename = + let cin = open_in filename in + let s = really_input_string cin (in_channel_length cin) in + close_in cin ; + s + +let save_string filename s = + let cout = open_out filename in + output_string cout s ; + close_out cout + +let path data_dir pvm_name hash = + let hash = + Format.asprintf "%a" Protocol.Alpha_context.Sc_rollup.Input_hash.pp hash + in + Filename.(concat (concat data_dir pvm_name) hash) + +let ensure_dir_exists data_dir pvm_name = + let path = Filename.concat data_dir pvm_name in + if Sys.(file_exists path) then ( + if not (Sys.is_directory path) then + Stdlib.failwith (path ^ " should be a directory.")) + else Sys.mkdir path 0o700 + +let get ~data_dir ~pvm_name ~hash = + try Some (string_of_file (path data_dir pvm_name hash)) with _ -> None + +module Arith = struct + let pvm_name = + Protocol.Alpha_context.Sc_rollup.ArithPVM.Protocol_implementation.name + + let rev_chunks_of_file filename = + (* FIXME: https://gitlab.com/tezos/tezos/-/issues/3853 + Can be made more efficient. *) + let get_char cin = try Some (input_char cin) with End_of_file -> None in + let buf = Buffer.create 31 in + let tokens = + let cin = open_in filename in + let rec aux tokens = + match get_char cin with + | None -> + List.rev + @@ + if Buffer.length buf > 0 then + let token = Buffer.contents buf in + token :: tokens + else tokens + | Some ' ' -> + let token = Buffer.contents buf in + Buffer.clear buf ; + aux (token :: tokens) + | Some c -> + Buffer.add_char buf c ; + aux tokens + in + let tokens = aux [] in + close_in cin ; + tokens + in + let limit = + (4 * 1024) - 100 (* We reserve 100 bytes for the continuation hash. *) + in + Buffer.clear buf ; + let make_chunk () = + let chunk = Buffer.contents buf in + Buffer.clear buf ; + chunk + in + let chunks, _ = + List.fold_left + (fun (chunks, size) token -> + let len = String.length token in + if size + len > limit then ( + let chunk = make_chunk () in + Buffer.add_string buf token ; + (chunk :: chunks, len)) + else ( + if Buffer.length buf > 0 then Buffer.add_char buf ' ' ; + Buffer.add_string buf token ; + (chunks, size + len))) + ([], 0) + tokens + in + let chunks = + if Buffer.length buf > 0 then make_chunk () :: chunks else chunks + in + chunks + + let link_rev_chunks rev_chunks = + let rec aux successor_hash linked_chunks = function + | [] -> linked_chunks + | chunk :: rev_chunks -> + let open Protocol.Alpha_context.Sc_rollup in + let cell = + match successor_hash with + | None -> chunk + | Some h -> Format.asprintf "%s hash:%a" chunk Input_hash.pp h + in + let hash = Input_hash.hash_string [cell] in + aux (Some hash) ((cell, hash) :: linked_chunks) rev_chunks + in + aux None [] rev_chunks + + let import data_dir filename = + ensure_dir_exists data_dir pvm_name ; + let rev_chunks = rev_chunks_of_file filename in + let linked_hashed_chunks = link_rev_chunks rev_chunks in + List.iter + (fun (data, hash) -> save_string (path data_dir pvm_name hash) data) + linked_hashed_chunks ; + Stdlib.List.hd linked_hashed_chunks |> snd +end + +let import ~data_dir ~pvm_name ~filename = + if + String.equal + pvm_name + Protocol.Alpha_context.Sc_rollup.ArithPVM.Protocol_implementation.name + then Arith.import data_dir filename + else Stdlib.failwith "Not supported yet" diff --git a/src/proto_alpha/bin_sc_rollup_node/reveals.mli b/src/proto_alpha/bin_sc_rollup_node/reveals.mli new file mode 100644 index 0000000000000000000000000000000000000000..5cfe123ebc29a4dc55fabc0a965a505f20465212 --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/reveals.mli @@ -0,0 +1,69 @@ +(*****************************************************************************) +(* *) +(* 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 provides basic support for reveals. + + The rollup can ask for data being the reveal of some hash. This + allows transferring data directly to the rollup without going + through the L1 inbox. + + Data length must be under 4KB to be refutable in a single L1 + operation. + + Data must be made available by off-chain mechanisms: it is the + responsibility of the rollup kernel to make sure that the reveal + data is available: otherwise, there is a potential safety issue. + + For the moment, the support is basic and mostly manual as the operator + needs to explicitly import a file in the rollup node data directoy to + enable the rollup node to answer reveal requests. + +*) + +(* FIXME:https://gitlab.com/tezos/tezos/-/issues/3854 + + We should probably have a mechanism to let the kernel declare + sources of reveal data so that the rollup node can automatically + download data in advance. *) + +open Protocol.Alpha_context + +(** [get ~data_dir ~pvm_name ~hash] returns [Some data] such that + [Input_hash.hash_string [data] = hash]. If such [data] is known + to the rollup node. Otherwise, returns [None]. *) +val get : + data_dir:string -> + pvm_name:string -> + hash:Sc_rollup.Input_hash.t -> + string option + +(** [import ~data_dir ~pvm_name ~filename] turns the content of ~filename + into a chunk of pages of (at most) 4KB, returning the hash of the first + chunk. *) +val import : + data_dir:string -> + pvm_name:string -> + filename:string -> + Sc_rollup.Input_hash.t diff --git a/src/proto_alpha/bin_sc_rollup_node/sc_rollup_node_errors.ml b/src/proto_alpha/bin_sc_rollup_node/sc_rollup_node_errors.ml index 264913222eb17474a79ab748cf7e1b5a1bb4578c..ca4f4bb931bc4ea0c927d0242603cbb9a615eab0 100644 --- a/src/proto_alpha/bin_sc_rollup_node/sc_rollup_node_errors.ml +++ b/src/proto_alpha/bin_sc_rollup_node/sc_rollup_node_errors.ml @@ -38,10 +38,9 @@ type error += layer1_inbox : Sc_rollup.Inbox.t; inbox : Sc_rollup.Inbox.t; } - -type error += Missing_PVM_state of Block_hash.t * Raw_level.t - -type error += Cannot_checkout_context of Block_hash.t * string option + | Missing_PVM_state of Block_hash.t * Raw_level.t + | Cannot_checkout_context of Block_hash.t * string option + | Cannot_retrieve_reveal of Sc_rollup.Input_hash.t type error += | Lost_game of @@ -237,4 +236,19 @@ let () = (function | Lost_game (loser, reason, slashed) -> Some (loser, reason, slashed) | _ -> None) - (fun (loser, reason, slashed) -> Lost_game (loser, reason, slashed)) + (fun (loser, reason, slashed) -> Lost_game (loser, reason, slashed)) ; + + register_error_kind + `Permanent + ~id:"internal.cannot_retrieve_reveal" + ~title:"Internal error: Cannot retrieve reveal of hash" + ~description:"The rollup node cannot retrieve a reveal asked by the rollup." + ~pp:(fun ppf hash -> + Format.fprintf + ppf + "The node cannot retrieve a reveal for hash %a" + Sc_rollup.Input_hash.pp + hash) + Data_encoding.(obj1 (req "hash" Sc_rollup.Input_hash.encoding)) + (function Cannot_retrieve_reveal hash -> Some hash | _ -> None) + (fun hash -> Cannot_retrieve_reveal hash) diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 5e77ffe58caee56d2ff0eaa137e3a849963c2a9f..0d05acff3f529eb1b580c2dd1076ff7f93cff883 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -2975,20 +2975,29 @@ module Sc_rollup : sig val deserialize : serialized -> t tzresult end - type input = { + type inbox_message = { inbox_level : Raw_level.t; message_counter : Z.t; payload : Inbox_message.serialized; } + type reveal_data = Raw_data of string + + type input = Inbox_message of inbox_message | Reveal of reveal_data + val input_equal : input -> input -> bool val input_encoding : input Data_encoding.t + module Input_hash : S.HASH + + type reveal = Reveal_raw_data of Input_hash.t + type input_request = | No_input_required | Initial | First_after of Raw_level.t * Z.t + | Needs_reveal of reveal val input_request_encoding : input_request Data_encoding.t @@ -3088,14 +3097,14 @@ module Sc_rollup : sig Raw_level.t * Z.t -> history_proof -> proof -> - input option tzresult Lwt.t + inbox_message option tzresult Lwt.t val produce_proof : inbox_context -> History.t -> history_proof -> Raw_level.t * Z.t -> - (proof * input option) tzresult Lwt.t + (proof * inbox_message option) tzresult Lwt.t val empty : inbox_context -> Sc_rollup_repr.t -> Raw_level.t -> t Lwt.t @@ -3324,7 +3333,12 @@ module Sc_rollup : sig val get_tick : state -> Tick.t Lwt.t - type status = Halted | Waiting_for_input_message | Parsing | Evaluating + type status = + | Halted + | Waiting_for_input_message + | Waiting_for_reveal + | Parsing + | Evaluating val get_status : state -> status Lwt.t @@ -3468,13 +3482,17 @@ module Sc_rollup : sig val wrapped_proof_module : wrapped_proof -> (module PVM_with_proof) module Proof : sig - type inbox_proof = { - level : Raw_level.t; - message_counter : Z.t; - proof : Inbox.serialized_proof; - } + type reveal_proof = Raw_data_proof of string + + type input_proof = + | Inbox_proof of { + level : Raw_level.t; + message_counter : Z.t; + proof : Inbox.serialized_proof; + } + | Reveal_proof of reveal_proof - type t = {pvm_step : wrapped_proof; inbox : inbox_proof option} + type t = {pvm_step : wrapped_proof; input_proof : input_proof option} module type PVM_with_context_and_state = sig include PVM.S @@ -3485,6 +3503,8 @@ module Sc_rollup : sig val proof_encoding : proof Data_encoding.t + val reveal : Input_hash.t -> string option + module Inbox_with_history : sig include Inbox.Merkelized_operations with type inbox_context = context diff --git a/src/proto_alpha/lib_protocol/sc_rollup_PVM_sig.ml b/src/proto_alpha/lib_protocol/sc_rollup_PVM_sig.ml index bfca6f3348a468431bdfef42f7ade0cb6386545e..f9c1c944bc13f7e8c88cf851505aa00d529bc563 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_PVM_sig.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_PVM_sig.ml @@ -57,14 +57,18 @@ be put into a variant. *) -type input = { +type inbox_message = { inbox_level : Raw_level_repr.t; message_counter : Z.t; payload : Sc_rollup_inbox_message_repr.serialized; } -(** [input_encoding] encoding value for {!input}. *) -let input_encoding = +type reveal_data = Raw_data of string + +type input = Inbox_message of inbox_message | Reveal of reveal_data + +(** [inbox_message_encoding] encoding value for {!inbox_message}. *) +let inbox_message_encoding = let open Data_encoding in conv (fun {inbox_level; message_counter; payload} -> @@ -77,14 +81,92 @@ let input_encoding = (req "message_counter" n) (req "payload" string)) +let reveal_data_encoding = + let open Data_encoding in + let case_raw_data = + case + ~title:"raw data" + (Tag 0) + (obj2 + (req "reveal_data_kind" (constant "raw_data")) + (req + "raw_data" + (check_size Constants_repr.sc_rollup_message_size_limit bytes))) + (function Raw_data m -> Some ((), Bytes.of_string m)) + (fun ((), m) -> Raw_data (Bytes.to_string m)) + in + union [case_raw_data] + +let input_encoding = + let open Data_encoding in + let case_inbox_message = + case + ~title:"inbox msg" + (Tag 0) + (obj2 + (req "input_kind" (constant "inbox_message")) + (req "inbox_message" inbox_message_encoding)) + (function Inbox_message m -> Some ((), m) | _ -> None) + (fun ((), m) -> Inbox_message m) + and case_reveal_revelation = + case + ~title:"reveal" + (Tag 1) + (obj2 + (req "input_kind" (constant "reveal_revelation")) + (req "reveal_data" reveal_data_encoding)) + (function Reveal d -> Some ((), d) | _ -> None) + (fun ((), d) -> Reveal d) + in + union [case_inbox_message; case_reveal_revelation] + (** [input_equal i1 i2] return whether [i1] and [i2] are equal. *) -let input_equal (a : input) (b : input) : bool = +let inbox_message_equal a b = let {inbox_level; message_counter; payload} = a in (* To be robust to the addition of fields in [input] *) Raw_level_repr.equal inbox_level b.inbox_level && Z.equal message_counter b.message_counter && String.equal (payload :> string) (b.payload :> string) +let reveal_data_equal a b = + match (a, b) with Raw_data a, Raw_data b -> String.equal a b + +let input_equal a b = + match (a, b) with + | Inbox_message a, Inbox_message b -> inbox_message_equal a b + | Reveal a, Reveal b -> reveal_data_equal a b + | Inbox_message _, Reveal _ | Reveal _, Inbox_message _ -> false + +module Input_hash = + Blake2B.Make + (Base58) + (struct + let name = "Sc_rollup_input_hash" + + let title = "A smart contract rollup input hash" + + let b58check_prefix = + "\001\118\125\135" (* "scd1(37)" decoded from base 58. *) + + let size = Some 20 + end) + +type reveal = Reveal_raw_data of Input_hash.t + +let reveal_encoding = + let open Data_encoding in + let case_raw_data = + case + ~title:"RevealRawData" + (Tag 0) + (obj2 + (req "reveal_kind" (constant "reveal_raw_data")) + (req "input_hash" Input_hash.encoding)) + (function Reveal_raw_data s -> Some ((), s)) + (fun ((), s) -> Reveal_raw_data s) + in + union [case_raw_data] + (** The PVM's current input expectations: - [No_input_required] if the machine is busy and has no need for new input. @@ -92,11 +174,16 @@ let input_equal (a : input) (b : input) : bool = first item in the inbox. - [First_after (level, counter)] expects whatever comes next after that - position in the inbox. *) + position in the inbox. + + - [Needs_reveal reveal] if the machine reveals the existence of + some data and needs this data to continue its execution. +*) type input_request = | No_input_required | Initial | First_after of Raw_level_repr.t * Z.t + | Needs_reveal of reveal (** [input_request_encoding] encoding value for {!input_request}. *) let input_request_encoding = @@ -107,28 +194,38 @@ let input_request_encoding = case ~title:"No_input_required" (Tag 0) - (obj1 (req "kind" (constant "no_input_required"))) + (obj1 (req "input_request_kind" (constant "no_input_required"))) (function No_input_required -> Some () | _ -> None) (fun () -> No_input_required); case ~title:"Initial" (Tag 1) - (obj1 (req "kind" (constant "initial"))) + (obj1 (req "input_request_kind" (constant "initial"))) (function Initial -> Some () | _ -> None) (fun () -> Initial); case ~title:"First_after" (Tag 2) (obj3 - (req "kind" (constant "first_after")) + (req "input_request_kind" (constant "first_after")) (req "level" Raw_level_repr.encoding) (req "counter" n)) (function | First_after (level, counter) -> Some ((), level, counter) | _ -> None) (fun ((), level, counter) -> First_after (level, counter)); + case + ~title:"Needs_reveal" + (Tag 3) + (obj2 + (req "input_request_kind" (constant "needs_reveal")) + (req "reveal" reveal_encoding)) + (function Needs_reveal p -> Some ((), p) | _ -> None) + (fun ((), p) -> Needs_reveal p); ] +let pp_reveal fmt (Reveal_raw_data hash) = Input_hash.pp fmt hash + (** [pp_input_request fmt i] pretty prints the given input [i] to the formatter [fmt]. *) let pp_input_request fmt request = @@ -143,6 +240,12 @@ let pp_input_request fmt request = l Z.pp_print n + | Needs_reveal reveal -> + Format.fprintf fmt "Needs reveal of %a" pp_reveal reveal + +let reveal_equal p1 p2 = + match (p1, p2) with + | Reveal_raw_data h1, Reveal_raw_data h2 -> Input_hash.equal h1 h2 (** [input_request_equal i1 i2] return whether [i1] and [i2] are equal. *) let input_request_equal a b = @@ -154,6 +257,8 @@ let input_request_equal a b = | First_after (l, n), First_after (m, o) -> Raw_level_repr.equal l m && Z.equal n o | First_after _, _ -> false + | Needs_reveal p1, Needs_reveal p2 -> reveal_equal p1 p2 + | Needs_reveal _, _ -> false (** Type that describes output values. *) type output = { @@ -296,10 +401,9 @@ module type S = sig has it read so far? *) val is_input_state : state -> input_request Lwt.t - (** [set_input (level, n, msg) state] sets [msg] in [state] as the next - message to be processed. This input message is assumed to be the number - [n] in the inbox messages at the given [level]. The input message must be - the message next to the previous message processed by the rollup. *) + (** [set_input input state] sets [input] in [state] as the next + input to be processed. This must answer the [input_request] + from [is_input_state state]. *) val set_input : input -> state -> state Lwt.t (** [eval s0] returns a state [s1] resulting from the diff --git a/src/proto_alpha/lib_protocol/sc_rollup_arith.ml b/src/proto_alpha/lib_protocol/sc_rollup_arith.ml index 8f59dddc97497ddf0b6befea37cac7cc848d878c..78b967872b8a3f24c53b41fa5d14ea7e73547bcd 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_arith.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_arith.ml @@ -126,7 +126,12 @@ module type S = sig val get_tick : state -> Sc_rollup_tick_repr.t Lwt.t - type status = Halted | Waiting_for_input_message | Parsing | Evaluating + type status = + | Halted + | Waiting_for_input_message + | Waiting_for_reveal + | Parsing + | Evaluating val get_status : state -> status Lwt.t @@ -181,7 +186,12 @@ module Make (Context : P) : type tree = Tree.tree - type status = Halted | Waiting_for_input_message | Parsing | Evaluating + type status = + | Halted + | Waiting_for_input_message + | Waiting_for_reveal + | Parsing + | Evaluating type instruction = | IPush : int -> instruction @@ -570,6 +580,7 @@ module Make (Context : P) : [ ("Halted", Halted); ("Waiting_for_input_message", Waiting_for_input_message); + ("Waiting_for_reveal", Waiting_for_reveal); ("Parsing", Parsing); ("Evaluating", Evaluating); ] @@ -579,12 +590,28 @@ module Make (Context : P) : let string_of_status = function | Halted -> "Halted" | Waiting_for_input_message -> "Waiting for input message" + | Waiting_for_reveal -> "Waiting for reveal" | Parsing -> "Parsing" | Evaluating -> "Evaluating" let pp fmt status = Format.fprintf fmt "%s" (string_of_status status) end) + module Required_reveal = Make_var (struct + type t = PS.Input_hash.t option + + let initial = None + + let encoding = Data_encoding.option PS.Input_hash.encoding + + let name = "required_pre_image_hash" + + let pp fmt v = + match v with + | None -> Format.fprintf fmt "" + | Some h -> PS.Input_hash.pp fmt h + end) + module Current_level = Make_var (struct type t = Raw_level_repr.t @@ -836,6 +863,11 @@ module Make (Context : P) : match counter with | Some n -> return (PS.First_after (level, n)) | None -> return PS.Initial) + | Waiting_for_reveal -> ( + let* h = Required_reveal.get in + match h with + | None -> internal_error "Internal error: Reveal invariant broken" + | Some h -> return (PS.Needs_reveal (Reveal_raw_data h))) | _ -> return PS.No_input_required let is_input_state = @@ -869,7 +901,7 @@ module Make (Context : P) : let* () = Code.clear in return () - let set_input_monadic {PS.inbox_level; message_counter; payload} = + let set_inbox_message_monadic {PS.inbox_level; message_counter; payload} = let open Monad.Syntax in let payload = match Sc_rollup_inbox_message_repr.deserialize payload with @@ -895,12 +927,38 @@ module Make (Context : P) : let* () = Status.set Waiting_for_input_message in return () + let reveal_monadic (PS.Raw_data data) = + (* + + The inbox cursor is unchanged as the message comes from the + outer world. + + We don't have to check that the data hash is the one we + expected as we decided to trust the initial witness. + + It is the responsibility of the rollup node to check it if it + does not want to publish a wrong commitment. + + Notice that a multi-page transmission is possible by embedding + a continuation encoded as an optional hash in [data]. + + *) + let open Monad.Syntax in + let* () = Next_message.set (Some data) in + let* () = start_parsing in + return () + let ticked m = let open Monad.Syntax in let* tick = Current_tick.get in let* () = Current_tick.set (Sc_rollup_tick_repr.next tick) in m + let set_input_monadic input = + match input with + | PS.Inbox_message m -> set_inbox_message_monadic m + | PS.Reveal s -> reveal_monadic s + let set_input input = set_input_monadic input |> ticked |> state_of let next_char = @@ -949,7 +1007,6 @@ module Make (Context : P) : let open Monad.Syntax in let* () = Status.set Evaluating in let* () = Evaluation_result.set None in - let* () = Stack.clear in return () let stop_parsing outcome = @@ -984,9 +1041,10 @@ module Make (Context : P) : let is_letter d = Compare.Char.((d >= 'a' && d <= 'z') || (d >= 'A' && d <= 'Z')) in - let is_identifier_char = function - | '0' .. '9' | 'a' .. 'z' | 'A' .. 'Z' | '%' -> true - | _ -> false + let is_identifier_char d = + is_letter d || is_digit d + || Compare.Char.(d = ':') + || Compare.Char.(d = '%') in let* parser_state = Parser_state.get in match parser_state with @@ -998,7 +1056,7 @@ module Make (Context : P) : let* () = produce_int in let* () = produce_add in return () - | Some ' ' -> + | Some (' ' | '\n') -> let* () = produce_int in let* () = next_char in return () @@ -1014,7 +1072,7 @@ module Make (Context : P) : let* () = produce_var in let* () = produce_add in return () - | Some ' ' -> + | Some (' ' | '\n') -> let* () = produce_var in let* () = next_char in return () @@ -1025,7 +1083,7 @@ module Make (Context : P) : | SkipLayout -> ( let* char = current_char in match char with - | Some ' ' -> next_char + | Some (' ' | '\n') -> next_char | Some '+' -> produce_add | Some d when is_digit d -> let* _ = lexeme in @@ -1088,13 +1146,24 @@ module Make (Context : P) : | None -> stop_evaluating true | Some (IPush x) -> Stack.push x | Some (IStore x) -> ( - let* v = Stack.top in - match v with - | None -> stop_evaluating false - | Some v -> ( - match identifies_target_contract x with - | Some contract_entrypoint -> output contract_entrypoint v - | None -> Vars.set x v)) + let len = String.length x in + if Compare.Int.(len > 5) && Compare.String.(String.sub x 0 5 = "hash:") + then + let hash = String.sub x 5 (len - 5) in + match PS.Input_hash.of_b58check_opt hash with + | None -> stop_evaluating false + | Some hash -> + let* () = Required_reveal.set (Some hash) in + let* () = Status.set Waiting_for_reveal in + return () + else + let* v = Stack.top in + match v with + | None -> stop_evaluating false + | Some v -> ( + match identifies_target_contract x with + | Some contract_entrypoint -> output contract_entrypoint v + | None -> Vars.set x v)) | Some IAdd -> ( let* v = Stack.pop in match v with @@ -1121,12 +1190,10 @@ module Make (Context : P) : let* status = Status.get in match status with | Halted -> boot - | Waiting_for_input_message -> ( + | Waiting_for_input_message | Waiting_for_reveal -> ( let* msg = Next_message.get in match msg with - | None -> - internal_error - "An input state was not provided an input message." + | None -> internal_error "An input state was not provided an input." | Some _ -> start_parsing) | Parsing -> parse | Evaluating -> evaluate) @@ -1139,10 +1206,24 @@ module Make (Context : P) : let* state = match request with | PS.No_input_required -> eval state - | _ -> ( + | PS.Initial | PS.First_after _ -> ( + match input_given with + | Some (PS.Inbox_message _ as input_given) -> + set_input input_given state + | None | Some (PS.Reveal _) -> + state_of + (internal_error + "Invalid set_input: expecting inbox message, got a reveal.") + state) + | PS.Needs_reveal _hash -> ( match input_given with - | Some input -> set_input input state - | None -> return state) + | Some (PS.Reveal _ as input_given) -> set_input input_given state + | None | Some (PS.Inbox_message _) -> + state_of + (internal_error + "Invalid set_input: expecting a reveal, got an inbox \ + message.") + state) in return (state, request) diff --git a/src/proto_alpha/lib_protocol/sc_rollup_arith.mli b/src/proto_alpha/lib_protocol/sc_rollup_arith.mli index 6fcba2a2ed3dcca503bc6f6ce5adec87d2d72d5c..0b9514ea625df22bd82a87984e7e97c9315ef16a 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_arith.mli +++ b/src/proto_alpha/lib_protocol/sc_rollup_arith.mli @@ -79,8 +79,13 @@ module type S = sig (** [get_tick state] returns the current tick of [state]. *) val get_tick : state -> Sc_rollup_tick_repr.t Lwt.t - (** The machine has three possible statuses: *) - type status = Halted | Waiting_for_input_message | Parsing | Evaluating + (** The machine has five possible statuses: *) + type status = + | Halted + | Waiting_for_input_message + | Waiting_for_reveal + | Parsing + | Evaluating (** [get_status state] returns the machine status in [state]. *) val get_status : state -> status Lwt.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 4495d9750210bc1bd9262d54b55f7dc81c404a3b..fae6b283e32bcd9ab8b2a41f10e108f71447e5ea 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml @@ -918,9 +918,16 @@ let check_proof_stop_state ~stop_state input_given (input_request : Sc_rollup_PVM_sig.input_request) proof validate = let stop_proof = match (input_given, input_request) with - | None, No_input_required | Some _, Initial | Some _, First_after _ -> + | None, No_input_required + | Some _, Initial + | Some _, First_after _ + | Some _, Needs_reveal _ -> Some (Sc_rollup_proof_repr.stop proof) - | Some _, No_input_required | None, Initial | None, First_after _ -> None + | Some _, No_input_required + | None, Initial + | None, First_after _ + | None, Needs_reveal _ -> + None in check (let b = Option.equal State_hash.equal stop_state stop_proof in diff --git a/src/proto_alpha/lib_protocol/sc_rollup_inbox_repr.ml b/src/proto_alpha/lib_protocol/sc_rollup_inbox_repr.ml index 43e560b0b357c5acf4581326ea2fd1c006de3fee..f55c481720aca7be8813a8155c53ae4790a4f77f 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_inbox_repr.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_inbox_repr.ml @@ -470,14 +470,14 @@ module type Merkelized_operations = sig Raw_level_repr.t * Z.t -> history_proof -> proof -> - Sc_rollup_PVM_sig.input option tzresult Lwt.t + Sc_rollup_PVM_sig.inbox_message option tzresult Lwt.t val produce_proof : inbox_context -> History.t -> history_proof -> Raw_level_repr.t * Z.t -> - (proof * Sc_rollup_PVM_sig.input option) tzresult Lwt.t + (proof * Sc_rollup_PVM_sig.inbox_message option) tzresult Lwt.t val empty : inbox_context -> Sc_rollup_repr.t -> Raw_level_repr.t -> t Lwt.t diff --git a/src/proto_alpha/lib_protocol/sc_rollup_inbox_repr.mli b/src/proto_alpha/lib_protocol/sc_rollup_inbox_repr.mli index 0caa30b397a74c2d1065990ed910a85711ef39e9..0a78a3e48f1039cb8d7c3e2df3d468e3f33c73a6 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_inbox_repr.mli +++ b/src/proto_alpha/lib_protocol/sc_rollup_inbox_repr.mli @@ -375,7 +375,7 @@ module type Merkelized_operations = sig Raw_level_repr.t * Z.t -> history_proof -> proof -> - Sc_rollup_PVM_sig.input option tzresult Lwt.t + Sc_rollup_PVM_sig.inbox_message option tzresult Lwt.t (** [produce_proof ctxt history inbox (level, counter)] creates an inbox proof proving the first message after the index [counter] at @@ -387,7 +387,7 @@ module type Merkelized_operations = sig History.t -> history_proof -> Raw_level_repr.t * Z.t -> - (proof * Sc_rollup_PVM_sig.input option) tzresult Lwt.t + (proof * Sc_rollup_PVM_sig.inbox_message option) tzresult Lwt.t (** [empty ctxt level] is an inbox started at some given [level] with no message at all. *) 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 8b42b406382a544cb6e5cda56b1497baca64c8e9..292b26e9687e3000813eecda422dd0c3866e864d 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_proof_repr.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_proof_repr.ml @@ -49,32 +49,72 @@ let () = (function Sc_rollup_invalid_serialized_inbox_proof -> Some () | _ -> None) (fun () -> Sc_rollup_invalid_serialized_inbox_proof) -type inbox_proof = { - level : Raw_level_repr.t; - message_counter : Z.t; - proof : Sc_rollup_inbox_repr.serialized_proof; -} +type reveal_proof = Raw_data_proof of string -let inbox_proof_encoding = +let reveal_proof_encoding = let open Data_encoding in - conv - (fun {level; message_counter; proof} -> (level, message_counter, proof)) - (fun (level, message_counter, proof) -> {level; message_counter; proof}) - (obj3 - (req "level" Raw_level_repr.encoding) - (req "message_counter" Data_encoding.n) - (req "proof" Sc_rollup_inbox_repr.serialized_proof_encoding)) + let case_raw_data = + case + ~title:"raw data proof" + (Tag 0) + (obj2 + (req "reveal_proof_kind" (constant "raw_data_proof")) + (req + "raw_data" + (check_size Constants_repr.sc_rollup_message_size_limit bytes))) + (function Raw_data_proof s -> Some ((), Bytes.of_string s)) + (fun ((), s) -> Raw_data_proof (Bytes.to_string s)) + in + union [case_raw_data] + +type input_proof = + | Inbox_proof of { + level : Raw_level_repr.t; + message_counter : Z.t; + proof : Sc_rollup_inbox_repr.serialized_proof; + } + | Reveal_proof of reveal_proof + +let input_proof_encoding = + let open Data_encoding in + let case_inbox_proof = + case + ~title:"inbox proof" + (Tag 0) + (obj4 + (req "input_proof_kind" (constant "inbox_proof")) + (req "level" Raw_level_repr.encoding) + (req "message_counter" Data_encoding.n) + (req "serialized_proof" Sc_rollup_inbox_repr.serialized_proof_encoding)) + (function + | Inbox_proof {level; message_counter; proof} -> + Some ((), level, message_counter, proof) + | _ -> None) + (fun ((), level, message_counter, proof) -> + Inbox_proof {level; message_counter; proof}) + in + let case_reveal_proof = + case + ~title:"reveal proof" + (Tag 1) + (obj2 + (req "input_proof_kind" (constant "reveal_proof")) + (req "reveal_proof" reveal_proof_encoding)) + (function Reveal_proof s -> Some ((), s) | _ -> None) + (fun ((), s) -> Reveal_proof s) + in + union [case_inbox_proof; case_reveal_proof] -type t = {pvm_step : Sc_rollups.wrapped_proof; inbox : inbox_proof option} +type t = {pvm_step : Sc_rollups.wrapped_proof; input_proof : input_proof option} let encoding = let open Data_encoding in conv - (fun {pvm_step; inbox} -> (pvm_step, inbox)) - (fun (pvm_step, inbox) -> {pvm_step; inbox}) + (fun {pvm_step; input_proof} -> (pvm_step, input_proof)) + (fun (pvm_step, input_proof) -> {pvm_step; input_proof}) (obj2 (req "pvm_step" Sc_rollups.wrapped_proof_encoding) - (opt "inbox" inbox_proof_encoding)) + (opt "input_proof" input_proof_encoding)) let pp ppf _ = Format.fprintf ppf "Refutation game proof" @@ -93,9 +133,11 @@ let stop proof = correctly---if the message obtained from the inbox proof is at or above [commit_level] the [input_given] in the PVM proof should be [None]. *) -let cut_at_level level input = - let input_level = Sc_rollup_PVM_sig.(input.inbox_level) in - if Raw_level_repr.(level <= input_level) then None else Some input +let cut_at_level level (input : Sc_rollup_PVM_sig.input) = + match input with + | Inbox_message {inbox_level = input_level; _} -> + if Raw_level_repr.(level <= input_level) then None else Some input + | Reveal _data -> Some input let proof_error reason = let open Lwt_tzresult_syntax in @@ -116,28 +158,41 @@ let valid snapshot commit_level ~pvm_name proof = let (module P) = Sc_rollups.wrapped_proof_module proof.pvm_step in let* () = check (String.equal P.name pvm_name) "Incorrect PVM kind" in let* input = - match proof.inbox with - | None -> return None - | Some {level; message_counter; proof} -> - let+ input = + match proof.input_proof with + | None -> return_none + | Some (Inbox_proof {level; message_counter; proof}) -> + let+ inbox_message = check_inbox_proof snapshot proof (level, Z.succ message_counter) in - Option.bind input (cut_at_level commit_level) + Option.map (fun i -> Sc_rollup_PVM_sig.Inbox_message i) inbox_message + | Some (Reveal_proof (Raw_data_proof data)) -> + return_some (Sc_rollup_PVM_sig.Reveal (Raw_data data)) in + let input = Option.bind input (cut_at_level commit_level) in let* input_requested = P.verify_proof input P.proof in let* () = - match (proof.inbox, input_requested) with + match (proof.input_proof, input_requested) with | None, No_input_required -> return_unit - | Some {level; message_counter; proof = _}, Initial -> + | Some (Inbox_proof {level; message_counter; proof = _}), Initial -> check (Raw_level_repr.(level = root) && Z.(equal message_counter zero)) "Inbox proof is not about the initial input request." - | Some {level; message_counter; proof = _}, First_after (l, n) -> + | Some (Inbox_proof {level; message_counter; proof = _}), First_after (l, n) + -> check (Raw_level_repr.(level = l) && Z.(equal message_counter n)) "Level and index of inbox proof are not equal to the one expected in \ input request." - | Some _, No_input_required | None, Initial | None, First_after _ -> + | ( Some (Reveal_proof (Raw_data_proof data)), + Needs_reveal (Reveal_raw_data expected_hash) ) -> + let data_hash = Sc_rollup_PVM_sig.Input_hash.hash_string [data] in + check + (Sc_rollup_PVM_sig.Input_hash.equal data_hash expected_hash) + "Invalid reveal" + | None, (Initial | First_after _ | Needs_reveal _) + | Some _, No_input_required + | Some (Inbox_proof _), Needs_reveal _ + | Some (Reveal_proof _), (Initial | First_after _) -> proof_error "Inbox proof and input request are dissociated." in return (input, input_requested) @@ -151,6 +206,8 @@ module type PVM_with_context_and_state = sig val proof_encoding : proof Data_encoding.t + val reveal : Sc_rollup_PVM_sig.Input_hash.t -> string option + module Inbox_with_history : sig include Sc_rollup_inbox_repr.Merkelized_operations @@ -169,7 +226,7 @@ let produce pvm_and_state commit_level = let*! (request : Sc_rollup_PVM_sig.input_request) = P.is_input_state P.state in - let* inbox_proof, input_given = + let* input_proof, input_given = match request with | No_input_required -> return (None, None) | Initial -> @@ -179,15 +236,42 @@ let produce pvm_and_state commit_level = Inbox_with_history.( produce_proof context history inbox (level, message_counter)) in - let proof = Inbox_with_history.to_serialized_proof inbox_proof in - return (Some {level; message_counter; proof}, input) + let input = + Option.map (fun msg -> Sc_rollup_PVM_sig.Inbox_message msg) input + in + let inbox_proof = + Inbox_proof + { + level; + message_counter; + proof = Inbox_with_history.to_serialized_proof inbox_proof; + } + in + return (Some inbox_proof, input) | First_after (level, message_counter) -> let* inbox_proof, input = Inbox_with_history.( produce_proof context history inbox (level, Z.succ message_counter)) in - let proof = Inbox_with_history.to_serialized_proof inbox_proof in - return (Some {level; message_counter; proof}, input) + let input = + Option.map (fun msg -> Sc_rollup_PVM_sig.Inbox_message msg) input + in + let inbox_proof = + Inbox_proof + { + level; + message_counter; + proof = Inbox_with_history.to_serialized_proof inbox_proof; + } + in + return (Some inbox_proof, input) + | Needs_reveal (Reveal_raw_data h) -> ( + match reveal h with + | None -> proof_error "No reveal" + | Some data -> + return + ( Some (Reveal_proof (Raw_data_proof data)), + Some (Sc_rollup_PVM_sig.Reveal (Raw_data data)) )) in let input_given = Option.bind input_given (cut_at_level commit_level) in let* pvm_step_proof = P.produce_proof P.context input_given P.state in @@ -197,5 +281,5 @@ let produce pvm_and_state commit_level = let proof = pvm_step_proof end in match Sc_rollups.wrap_proof (module P_with_proof) with - | Some pvm_step -> return {pvm_step; inbox = inbox_proof} + | Some pvm_step -> return {pvm_step; input_proof} | None -> proof_error "Could not wrap proof" diff --git a/src/proto_alpha/lib_protocol/sc_rollup_proof_repr.mli b/src/proto_alpha/lib_protocol/sc_rollup_proof_repr.mli index 145b647f82b6a5b314c0a1f7f555263741171e05..6de550670d48fbc77f4f24c764f230508ab6b940 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_proof_repr.mli +++ b/src/proto_alpha/lib_protocol/sc_rollup_proof_repr.mli @@ -28,30 +28,30 @@ game. This proof is basically a combination of a PVM proof (provided by - each implementation of the PVM signature) and an inbox proof. To + each implementation of the PVM signature) and an input proof. To check the proof we must check each part separately and then also check that they match on the two points where they touch: - the [input_requested] of the PVM proof should match the starting - point of the inbox proof ; + point of the input proof ; - the [input_given] of the PVM proof should match the output - message of the inbox proof. + message of the input proof. It is also often the case that the PVM proof has [No_input_required] for its [input_requested] and [None] for its [input_given]. If this - is the case, we don't need the inbox proof at all and the [inbox] + is the case, we don't need the input proof at all and the [input_proof] parameter in our proof should be [None]. *) open Sc_rollup_repr -type inbox_proof = { - level : Raw_level_repr.t; - message_counter : Z.t; - proof : Sc_rollup_inbox_repr.serialized_proof; -} +(** The proof that a reveal is valid. *) +type reveal_proof = + | Raw_data_proof of string + (** The existence of reveal for a given hash when the + [input_requested] is the [Needs_for_reveal]. *) -(** A PVM proof [pvm_step] is combined with an [inbox] proof to provide +(** A PVM proof [pvm_step] is combined with an [input_proof] to provide the proof necessary to validate a single step in the refutation game. @@ -60,10 +60,24 @@ type inbox_proof = { [No_input_required] and [None] respectively, and in this case [inbox] should also be [None]. - In the case that input is involved, [inbox] is a proof of the next - message available from the inbox after a given location; this must - match up with [pvm_step] to give a valid refutation proof. *) -type t = {pvm_step : Sc_rollups.wrapped_proof; inbox : inbox_proof option} + In the case that input is involved, [input_proof] is either: + + - a proof of the next inbox message available from the inbox + after a given location; this must match up with [pvm_step] + to give a valid refutation proof ; or + + - a proof of a reveal satisfiability. +*) + +type input_proof = + | Inbox_proof of { + level : Raw_level_repr.t; + message_counter : Z.t; + proof : Sc_rollup_inbox_repr.serialized_proof; + } + | Reveal_proof of reveal_proof + +type t = {pvm_step : Sc_rollups.wrapped_proof; input_proof : input_proof option} type error += Sc_rollup_proof_check of string @@ -88,14 +102,14 @@ val stop : t -> State_hash.t This function requires a few bits of data (available from the refutation game record in the storage): - - a snapshot of the inbox, used by the [inbox] proof ; + - a snapshot of the inbox, that may be used by the [input] proof ; - the inbox level of the commitment, used to determine if an - output from the [inbox] proof is too recent to be allowed into the - PVM proof ; + output from the [input] proof is too recent to be allowed into + the PVM proof ; - the [pvm_name], used to check that the proof given has the right - PVM kind. + PVM kind. It also returns the optional input executed during the proof and the input_request for the state at the beginning of the proof. @@ -117,6 +131,8 @@ module type PVM_with_context_and_state = sig val proof_encoding : proof Data_encoding.t + val reveal : Sc_rollup_PVM_sig.Input_hash.t -> string option + module Inbox_with_history : sig include Sc_rollup_inbox_repr.Merkelized_operations @@ -132,7 +148,9 @@ end will construct a full refutation game proof out of the [state] given in [pvm_and_state]. It uses the [inbox] if necessary to provide input in the proof. If the input is above or at [commit_level] it - will block it, and produce a proof that the PVM is blocked. + will block it, and produce a proof that the PVM is blocked. If + the input requested is a reveal the proof production will also + fail. This will fail if any of the [context], [inbox_context] or [inbox_history] given don't have enough data to make the proof. For diff --git a/src/proto_alpha/lib_protocol/sc_rollup_wasm.ml b/src/proto_alpha/lib_protocol/sc_rollup_wasm.ml index af3f60c0b5b2e0f62c0de6838207edd4b26efa2b..221d652ea465c3e5af1ed491551c35cf9f4e8dcd 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_wasm.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_wasm.ml @@ -256,21 +256,31 @@ module V2_0_0 = struct return [] let set_input_state input = - let open PS in - let open Monad.Syntax in - let {inbox_level; message_counter; payload} = input in - let* s = get in - let* s = - lift - (WASM_machine.set_input_step - { - inbox_level = Raw_level_repr.to_int32_non_negative inbox_level; - message_counter; - } - (payload :> string) - s) - in - set s + match input with + | PS.Inbox_message input -> + let open PS in + let open Monad.Syntax in + let {inbox_level; message_counter; payload} = input in + let* s = get in + let* s = + lift + (WASM_machine.set_input_step + { + inbox_level = Raw_level_repr.to_int32_non_negative inbox_level; + message_counter; + } + (payload :> string) + s) + in + set s + | PS.Reveal _ -> + (* TODO: https://gitlab.com/tezos/tezos/-/issues/3754 + + The WASM PVM does not produce [Needs_reveal] input + requests. Thus, no [set_input_state] should transmit a + [Reveal_revelation]. + *) + assert false let set_input input = state_of @@ set_input_state input diff --git a/src/proto_alpha/lib_protocol/test/helpers/sc_rollup_helpers.ml b/src/proto_alpha/lib_protocol/test/helpers/sc_rollup_helpers.ml index e32631392ee27b39e9baacec3ddeafb72da4c1a8..2f3654bb104a214515e5f57751822aa8ec89cacf 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/sc_rollup_helpers.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/sc_rollup_helpers.ml @@ -184,7 +184,7 @@ let make_external_inbox_message_repr str = let make_input_repr ?(inbox_level = Raw_level_repr.root) ?(message_counter = Z.zero) message = - Sc_rollup_PVM_sig. + Sc_rollup_PVM_sig.Inbox_message { inbox_level; message_counter; @@ -198,7 +198,7 @@ let make_external_inbox_message str = let make_input ?(inbox_level = Raw_level.root) ?(message_counter = Z.zero) message = - Sc_rollup. + Sc_rollup.Inbox_message { inbox_level; message_counter; diff --git a/src/proto_alpha/lib_protocol/test/integration/operations/test_sc_rollup.ml b/src/proto_alpha/lib_protocol/test/integration/operations/test_sc_rollup.ml index d0db8bebe78045547cf31fcdf0321db91543348f..0a977a29a2a54b2a7ca25fe594c7b4850ce33eb7 100644 --- a/src/proto_alpha/lib_protocol/test/integration/operations/test_sc_rollup.ml +++ b/src/proto_alpha/lib_protocol/test/integration/operations/test_sc_rollup.ml @@ -1718,13 +1718,15 @@ let dumb_proof ~choice = end) in let inbox_proof = - Sc_rollup.Inbox.Internal_for_tests.serialized_proof_of_string "c4c4" - in - let inbox = - Sc_rollup.Proof. - {level = Raw_level.root; message_counter = Z.zero; proof = inbox_proof} + Sc_rollup.Proof.Inbox_proof + { + level = Raw_level.root; + message_counter = Z.zero; + proof = + Sc_rollup.Inbox.Internal_for_tests.serialized_proof_of_string "c4c4"; + } in - let proof = Sc_rollup.Proof.{pvm_step; inbox = Some inbox} in + let proof = Sc_rollup.Proof.{pvm_step; input_proof = Some inbox_proof} in return Sc_rollup.Game.{choice; step = Proof proof} (** Test that two invalid proofs from the two players lead to a draw diff --git a/src/proto_alpha/lib_protocol/test/integration/test_sc_rollup_wasm.ml b/src/proto_alpha/lib_protocol/test/integration/test_sc_rollup_wasm.ml index fb7626a1cd4cba01189c2ec34a889b5467d17d2c..2ad8c2112d2a59274509dd6d8239c5c97401ee73 100644 --- a/src/proto_alpha/lib_protocol/test/integration/test_sc_rollup_wasm.ml +++ b/src/proto_alpha/lib_protocol/test/integration/test_sc_rollup_wasm.ml @@ -234,7 +234,7 @@ let should_boot_complete_boot_sector boot_sector () = let arbitrary_input i payload = match Sc_rollup.Inbox_message.serialize (External payload) with | Ok payload -> - Sc_rollup. + Sc_rollup.Inbox_message { inbox_level = Raw_level.of_int32_exn 0l; message_counter = Z.of_int i; 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 92aee38c57a5427d08809ccb9d9433e121868d75..730721d8c80f40ae1e4604c04df3499860940900 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 @@ -910,7 +910,8 @@ module Arith_test_pvm = struct let mk_input level message_counter msg = let payload = make_external_inbox_message msg in let level = Int32.of_int level in - {payload; message_counter; inbox_level = Raw_level.of_int32_exn level} + Sc_rollup.Inbox_message + {payload; message_counter; inbox_level = Raw_level.of_int32_exn level} let consume_fuel = Option.map pred @@ -1313,6 +1314,8 @@ let build_proof ~player_client start_tick (game : Game.t) = let state = state + let reveal _ = assert false + module Inbox_with_history = struct include Store_inbox diff --git a/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_arith.ml b/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_arith.ml index 4dbb77662d7748c655a1c68586b00f21ea8eabb7..22eb0bf2bd1949000d4e7bf58b9a3c9110c9e743 100644 --- a/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_arith.ml +++ b/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_arith.ml @@ -129,7 +129,7 @@ let test_boot () = boot "" @@ fun _ctxt state -> is_input_state state >>= function | Initial -> return () - | First_after _ -> + | Needs_reveal _ | First_after _ -> failwith "After booting, the machine should be waiting for the initial input." | No_input_required -> @@ -142,7 +142,7 @@ let test_input_message () = set_input input state >>= fun state -> eval state >>= fun state -> is_input_state state >>= function - | Initial | First_after _ -> + | Initial | Needs_reveal _ | First_after _ -> failwith "After receiving a message, the rollup must not be waiting for input." | No_input_required -> return () diff --git a/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_game.ml b/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_game.ml index 7aa5d60a61f13827e586abda3679dcb633954c60..6e828799992d7b0879b4fe5148966b998cf18387 100644 --- a/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_game.ml +++ b/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_game.ml @@ -340,11 +340,13 @@ let test_invalid_serialized_inbox_proof () = Sc_rollup.Inbox.Internal_for_tests.serialized_proof_of_string "I am the big bad wolf" in - let inbox = - Sc_rollup.Proof. + let inbox_proof = + Sc_rollup.Proof.Inbox_proof {level = Raw_level.root; message_counter = Z.zero; proof = inbox_proof} in - let proof = Sc_rollup.Proof.{pvm_step = wrapped_proof; inbox = Some inbox} in + let proof = + Sc_rollup.Proof.{pvm_step = wrapped_proof; input_proof = Some inbox_proof} + in let*! res = T.lift diff --git a/tezt/lib_tezos/sc_rollup_node.ml b/tezt/lib_tezos/sc_rollup_node.ml index 2c3ff90d456312a514c3984bd91ad59ed46eb59f..6f36a4c3462aef5bdafac279f0307e81e0a89b6a 100644 --- a/tezt/lib_tezos/sc_rollup_node.ml +++ b/tezt/lib_tezos/sc_rollup_node.ml @@ -163,6 +163,22 @@ module Config_file = struct let update sc_node update = read sc_node |> update |> write sc_node end +let spawn_import sc_node ~pvm_name ~filename = + spawn_command sc_node + @@ [ + "import"; + "--data-dir"; + data_dir sc_node; + "--pvm-name"; + pvm_name; + "--filename"; + filename; + ] + +let import sc_node ~pvm_name ~filename = + let process = spawn_import sc_node ~pvm_name ~filename in + Process.check_and_read_stdout process + let trigger_ready sc_node value = let pending = sc_node.persistent_state.pending_ready in sc_node.persistent_state.pending_ready <- [] ; diff --git a/tezt/lib_tezos/sc_rollup_node.mli b/tezt/lib_tezos/sc_rollup_node.mli index 916af785292049da08bb766ad7a7d4bc8f58a19d..ffaedcfa8fc4665ede151377f9b429c9b07bd3a6 100644 --- a/tezt/lib_tezos/sc_rollup_node.mli +++ b/tezt/lib_tezos/sc_rollup_node.mli @@ -150,3 +150,10 @@ val wait_for_ready : t -> unit Lwt.t If [timeout] is provided, stop waiting if [timeout] seconds have passed. *) val wait_for_level : ?timeout:float -> t -> int -> int Lwt.t + +(** [import sc_node ~pvm_name ~filename] makes the contents of + [filename] available as raw data chunks to the rollup assuming + that it runs according to a given [pvm_name]. + Returns the hash of the first of these chunks. + The implementation is PVM-dependent. *) +val import : t -> pvm_name:string -> filename:string -> string Lwt.t diff --git a/tezt/tests/expected/sc_rollup.ml/Alpha- arith - rollup node - correct handling of commitments (arith).out b/tezt/tests/expected/sc_rollup.ml/Alpha- arith - rollup node - correct handling of commitments (arith).out new file mode 100644 index 0000000000000000000000000000000000000000..e844880eea7cb71c4a65bae2dbae30abca4cf4f7 --- /dev/null +++ b/tezt/tests/expected/sc_rollup.ml/Alpha- arith - rollup node - correct handling of commitments (arith).out @@ -0,0 +1,80 @@ + +./tezos-client --wait none originate sc rollup from bootstrap1 of kind arith of type string booting with --burn-cap 9999999 +Node is bootstrapped. +Estimated gas: 3110.449 units (will add 100 for safety) +Estimated storage: 6655 bytes added (will add 20 for safety) +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.00067 + Expected counter: 1 + Gas limit: 3211 + Storage limit: 6675 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.00067 + payload fees(the block proposer) ....... +ꜩ0.00067 + Smart contract rollup origination: + Kind: arith + Parameter type: string + Boot sector Blake2B hash: '0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8' + This smart contract rollup origination was successfully applied + Consumed gas: 3110.449 + Storage size: 6655 bytes + Address: [SC_ROLLUP_HASH] + Genesis commitment hash: [SC_ROLLUP_COMMITMENT_HASH] + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ1.66375 + storage fees ........................... +ꜩ1.66375 + + +./tezos-client rpc get '/chains/main/blocks/head/context/sc_rollup/[SC_ROLLUP_HASH]/genesis_info' +{ "level": 2, + "commitment_hash": "[SC_ROLLUP_COMMITMENT_HASH]" } + +./tezos-client --wait none send sc rollup message 'text:["hash:scd1C1PeCnVMC9jDsDjzEW1Ez9GWS97TLJLxA\n"]' from bootstrap2 to '[SC_ROLLUP_HASH]' +Node is bootstrapped. +Estimated gas: 1877.317 units (will add 100 for safety) +Estimated storage: no bytes added +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000514 + Expected counter: 1 + Gas limit: 1978 + Storage limit: 0 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000514 + payload fees(the block proposer) ....... +ꜩ0.000514 + Smart contract rollup messages submission: + Address: [SC_ROLLUP_HASH] + This smart contract rollup messages submission was successfully applied + Consumed gas: 1877.445 + Resulting inbox state: { rollup = [SC_ROLLUP_HASH] + level = 3 + current messages hash = [SC_ROLLUP_INBOX_HASH] + nb_messages_in_commitment_period = 1 + starting_level_of_current_commitment_period = 2 + message_counter = 1 + old_levels_messages = hash : [SC_ROLLUP_INBOX_HASH] + + content = [SC_ROLLUP_INBOX_HASH] + index = 1 + back_pointers = [SC_ROLLUP_INBOX_HASH] + + } + + +./tezos-sc-rollup-client-alpha get state value for vars/value +"\000\000\128\000" diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index f7d6d30166df9fe27953bab8c8f6801fd2356708..568c7e7a659dfe6de134445461f259fa5a36bc4e 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -2219,6 +2219,69 @@ let test_rollup_node_uses_arith_boot_sector = Lwt.return_unit) +let test_rollup_arith_uses_reveals = + let nadd = 32 * 1024 in + let go_boot client sc_rollup sc_rollup_node = + let filename = + let filename, cout = Filename.open_temp_file "sc_rollup" ".in" in + output_string cout "0 " ; + for _i = 1 to nadd do + output_string cout "1 + " + done ; + output_string cout "value" ; + close_out cout ; + filename + in + let* hash = + Sc_rollup_node.import sc_rollup_node ~pvm_name:"arith" ~filename + in + let* genesis_info = + RPC.Client.call ~hooks client + @@ RPC.get_chain_block_context_sc_rollup_genesis_info sc_rollup + in + let init_level = JSON.(genesis_info |-> "level" |> as_int) in + + let* () = Sc_rollup_node.run sc_rollup_node in + + let sc_rollup_client = Sc_rollup_client.create sc_rollup_node in + let* level = + Sc_rollup_node.wait_for_level ~timeout:120. sc_rollup_node init_level + in + + let* () = send_text_messages client sc_rollup ["hash:" ^ hash] in + let* () = bake_levels 2 client in + let* _ = + Sc_rollup_node.wait_for_level ~timeout:120. sc_rollup_node (level + 1) + in + + let* encoded_value = + Sc_rollup_client.state_value ~hooks sc_rollup_client ~key:"vars/value" + in + let value = + match Data_encoding.(Binary.of_bytes int31) @@ encoded_value with + | Error error -> + failwith + (Format.asprintf + "The arithmetic PVM has an unexpected state: %a" + Data_encoding.Binary.pp_read_error + error) + | Ok x -> x + in + Check.( + (value = nadd) int ~error_msg:"Invalid value in rollup state (%L <> %R)") ; + return () + in + + test_scenario + ~timeout:120 + { + tags = ["reveals"]; + variant = "arith"; + description = "rollup node - correct handling of commitments"; + } + @@ fun _protocol sc_rollup_node sc_rollup _node client -> + go_boot client sc_rollup sc_rollup_node + (* Initializes a client with an existing account being [Constants.tz4_account]. *) let client_with_initial_keys ~protocol ~kind = @@ -3279,6 +3342,8 @@ let register ~protocols = ~kind:"wasm_2_0_0" ~kernel_name:"no_parse_bad_fingerprint" ~internal:false ; + (* DAC tests, not supported yet by the Wasm PVM *) + test_rollup_arith_uses_reveals protocols ~kind:"arith" ; (* Shared tezts - will be executed for both PVMs. *) register ~kind:"wasm_2_0_0" ~protocols ; register ~kind:"arith" ~protocols ;