diff --git a/src/proto_alpha/bin_sc_rollup_client/commands.ml b/src/proto_alpha/bin_sc_rollup_client/commands.ml index 4d77f875aece371fe679fb63f7daa276c893e12c..cd492698bddecf221397a95f0ac0719953ce8d0c 100644 --- a/src/proto_alpha/bin_sc_rollup_client/commands.ml +++ b/src/proto_alpha/bin_sc_rollup_client/commands.ml @@ -197,23 +197,82 @@ let get_output_message_encoding () = | Error _ -> cctxt#message "Error while encoding outbox message.") >>= fun () -> return_unit) -(** [call_get cctxt raw_url] executes a GET RPC call against the [raw_url]. *) -let call_get (cctxt : #Configuration.sc_client_context) raw_url = +let call ?body meth raw_url (cctxt : #Configuration.sc_client_context) = let open Lwt_result_syntax in - let meth = `GET in let uri = Uri.of_string raw_url in - let* answer = cctxt#generic_media_type_call meth uri in + let body = + (* This code is similar to a piece of code in [fill_in] + function. An RPC is declared as POST, PATCH or PUT, but the + body is not given. In that case, the body should be an empty + JSON object. *) + match (meth, body) with + | _, Some _ -> body + | `DELETE, None | `GET, None -> None + | `PATCH, None | `PUT, None | `POST, None -> Some (`O []) + in + let* answer = cctxt#generic_media_type_call ?body meth uri in let*! () = display_answer cctxt answer in return_unit -let rpc_get_command = - Tezos_clic.command - ~desc:"Call an RPC with the GET method." - Tezos_clic.no_options - (Tezos_clic.prefixes ["rpc"; "get"] - @@ Tezos_clic.string ~name:"url" ~desc:"the RPC URL" - @@ Tezos_clic.stop) - (fun () url cctxt -> call_get cctxt url) +let call_with_json meth raw_url json (cctxt : #Configuration.sc_client_context) + = + match Ezjsonm.value_from_string_result json with + | Error err -> + cctxt#error + "Failed to parse the provided json: %s\n%!" + (Ezjsonm.read_error_description err) + | Ok body -> call meth ~body raw_url cctxt + +let call_with_file_or_json meth url maybe_file + (cctxt : #Configuration.sc_client_context) = + let open Lwt_result_syntax in + let* json = + match TzString.split ':' ~limit:1 maybe_file with + | ["file"; filename] -> + Lwt.catch + (fun () -> + Lwt_result.ok @@ Lwt_io.(with_file ~mode:Input filename read)) + (fun exn -> failwith "cannot read file (%s)" (Printexc.to_string exn)) + | _ -> return maybe_file + in + call_with_json meth url json cctxt + +let rpc_commands () = + let open Tezos_clic in + let group = {name = "rpc"; title = "Commands for the low level RPC layer"} in + [ + command + ~group + ~desc:"Call an RPC with the GET method." + no_options + (prefixes ["rpc"; "get"] @@ string ~name:"url" ~desc:"the RPC URL" @@ stop) + (fun () -> call `GET); + command + ~group + ~desc:"Call an RPC with the POST method." + no_options + (prefixes ["rpc"; "post"] + @@ string ~name:"url" ~desc:"the RPC URL" + @@ stop) + (fun () -> call `POST); + command + ~group + ~desc: + "Call an RPC with the POST method, providing input data via the \ + command line." + no_options + (prefixes ["rpc"; "post"] + @@ string ~name:"url" ~desc:"the RPC URL" + @@ prefix "with" + @@ string + ~name:"input" + ~desc: + "the raw JSON input to the RPC\n\ + For instance, use `{}` to send the empty document.\n\ + Alternatively, use `file:path` to read the JSON data from a file." + @@ stop) + (fun () -> call_with_file_or_json `POST); + ] module Keys = struct open Tezos_client_base.Client_keys @@ -272,9 +331,9 @@ let all () = get_state_value_command (); get_output_proof (); get_output_message_encoding (); - rpc_get_command; Keys.generate_keys (); Keys.list_keys (); Keys.show_address (); Keys.import_secret_key (); ] + @ rpc_commands () diff --git a/src/proto_alpha/bin_sc_rollup_node/RPC_server.ml b/src/proto_alpha/bin_sc_rollup_node/RPC_server.ml index 4ad0a4e9a253e82500dfa2335f6334bf9c86a819..004e24add74c386bdb67d7bee8912a3d98c679dd 100644 --- a/src/proto_alpha/bin_sc_rollup_node/RPC_server.ml +++ b/src/proto_alpha/bin_sc_rollup_node/RPC_server.ml @@ -229,9 +229,11 @@ module Common = struct state.num_ticks end -module Make (PVM : Pvm.S) = struct - module PVM = PVM +module Make (Interpreter : Interpreter.S) = struct + module PVM = Interpreter.PVM module Outbox = Outbox.Make (PVM) + module Free_pvm = Interpreter.Free_pvm + module Simulation = Simulation.Make (Interpreter) let get_state (node_ctxt : _ Node_context.t) block_hash = let open Lwt_result_syntax in @@ -239,6 +241,53 @@ module Make (PVM : Pvm.S) = struct let*! state = PVM.State.find ctxt in match state with None -> failwith "No state" | Some state -> return state + let simulate_messages (node_ctxt : Node_context.ro) block ~reveal_pages + messages = + let open Lwt_result_syntax in + let open Alpha_context in + let reveal_map = + match reveal_pages with + | Some pages -> + let map = + List.fold_left + (fun map page -> + let hash = Sc_rollup.Reveal_hash.hash_string [page] in + Sc_rollup.Reveal_hash.Map.add hash page map) + Sc_rollup.Reveal_hash.Map.empty + pages + in + Some map + | None -> None + in + let* level = State.level_of_hash node_ctxt.store block in + let* sim = + Simulation.start_simulation + node_ctxt + ~reveal_map + Layer1.{hash = block; level} + in + let messages = + List.map (fun m -> Sc_rollup.Inbox_message.External m) messages + in + let* sim, num_ticks_0 = + Simulation.simulate_messages node_ctxt sim messages + in + let* {state; inbox_level; _}, num_ticks_end = + Simulation.end_simulation node_ctxt sim + in + let num_ticks = Z.(num_ticks_0 + num_ticks_end) in + let*! outbox = PVM.get_outbox state in + let output = + List.filter + (fun Sc_rollup.{outbox_level; _} -> outbox_level = inbox_level) + outbox + in + let*! state_hash = PVM.state_hash state in + let*! status = PVM.get_status state in + let status = PVM.string_of_status status in + return + Sc_rollup_services.{state_hash; status; output; inbox_level; num_ticks} + let () = Block_directory.register0 Sc_rollup_services.Global.Block.total_ticks @@ fun (node_ctxt, block) () () -> @@ -343,6 +392,11 @@ module Make (PVM : Pvm.S) = struct Sc_rollup_services.Global.Helpers.outbox_proof @@ fun node_ctxt output () -> Outbox.proof_of_output node_ctxt output + let () = + Block_directory.register0 Sc_rollup_services.Global.Block.simulate + @@ fun (node_ctxt, block) () {messages; reveal_pages} -> + simulate_messages node_ctxt block ~reveal_pages messages + let register node_ctxt = List.fold_left (fun dir f -> Tezos_rpc.Directory.merge dir (f node_ctxt)) diff --git a/src/proto_alpha/bin_sc_rollup_node/RPC_server.mli b/src/proto_alpha/bin_sc_rollup_node/RPC_server.mli index 2bb1a448b1ec896c1453947dd377292cc3378584..9ba5b19c2dbf01eaa3192c69b9967e33a3138fc9 100644 --- a/src/proto_alpha/bin_sc_rollup_node/RPC_server.mli +++ b/src/proto_alpha/bin_sc_rollup_node/RPC_server.mli @@ -25,9 +25,8 @@ open Tezos_rpc_http_server -(** Functor to construct an RPC server for a given PVM, i.e. the PVM for the - rollup of the current node. *) -module Make (PVM : Pvm.S) : sig +(** Functor to construct an RPC server for a given PVM with interpreter. *) +module Make (Interpreter : Interpreter.S) : sig (** [start node_ctxt config] starts an RPC server listening for requests on the port [config.rpc_port] and address [config.rpc_addr]. *) val start : 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 946a347b06dabe1a2af607a010cb2ccb4e055c28..881417dcc1d31c778a3fe4e945eb363d04bd1baa 100644 --- a/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml +++ b/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml @@ -45,7 +45,11 @@ module Arith_proof_format = end) module Impl : Pvm.S = struct - include Sc_rollup.ArithPVM.Make (Arith_proof_format) + module PVM = Sc_rollup.ArithPVM.Make (Arith_proof_format) + include PVM + + let kind = Sc_rollup.Kind.of_pvm (module PVM) + module State = Context.PVMState let string_of_status status = diff --git a/src/proto_alpha/bin_sc_rollup_node/components.ml b/src/proto_alpha/bin_sc_rollup_node/components.ml index 394b62ed64138761d6b51eec5bc01944914579fd..6612ae8c384991b4913bae4d2b6cfb7381dd9906 100644 --- a/src/proto_alpha/bin_sc_rollup_node/components.ml +++ b/src/proto_alpha/bin_sc_rollup_node/components.ml @@ -28,7 +28,7 @@ module Make (PVM : Pvm.S) = struct module PVM = PVM module Interpreter = Interpreter.Make (PVM) module Commitment = Commitment.Make (PVM) - module RPC_server = RPC_server.Make (PVM) + module RPC_server = RPC_server.Make (Interpreter) module Refutation_game = Refutation_game.Make (Interpreter) end diff --git a/src/proto_alpha/bin_sc_rollup_node/fuel.ml b/src/proto_alpha/bin_sc_rollup_node/fuel.ml index dd662dac1d1eed30688ca01badbf0d7a5cc426b2..73a6f6c0dc9e002cfd92f41ba54736ba8809ce79 100644 --- a/src/proto_alpha/bin_sc_rollup_node/fuel.ml +++ b/src/proto_alpha/bin_sc_rollup_node/fuel.ml @@ -26,34 +26,35 @@ module type S = sig type t - (** [consume consumption fuel] consumes the [consumption] amount from the - original [fuel] - It returns - None when the [consumption] is greater than the original [fuel] or - Some remaining fuel - *) + (** [consume consumption fuel] consumes the [consumption] amount from the + original [fuel]. It returns [None] when the [consumption] is greater + than the original [fuel] or [Some remaining_fuel]. *) val consume : t -> t -> t option (** The amount of fuel required to run one PVM tick. - - one_tick_consumption = of_ticks 1L + {[ + one_tick_consumption = of_ticks 1L + ]} *) val one_tick_consumption : t - (** [of_ticks ticks] gives the amount of fuel required to execute the amount of [ticks] - *) + (** [of_ticks ticks] gives the amount of fuel required to execute the amount + of [ticks]. *) val of_ticks : int64 -> t val is_empty : t -> bool - (** The maximum number of ticks that can be executed with the given amount of fuel - - max_ticks . of_ticks = id - of_ticks . max_ticks = id - *) + (** The maximum number of ticks that can be executed with the given amount of + fuel. + {[ + max_ticks ∘ of_ticks = Fun.id + of_ticks ∘ max_ticks = Fun.id + ]} + *) val max_ticks : t -> int64 end +(** Free fuel where consumption has no effect. *) module Free : S = struct type t = Free @@ -68,6 +69,7 @@ module Free : S = struct let max_ticks _ = Int64.max_int end +(** Accounted fuel where each tick consumes one unit of fuel. *) module Accounted : S = struct type t = int64 diff --git a/src/proto_alpha/bin_sc_rollup_node/fueled_pvm.ml b/src/proto_alpha/bin_sc_rollup_node/fueled_pvm.ml index a39c5144b496532978c3675d61d462b657555d10..284ea35f6ab0c8c093676f5b4756add2489291af 100644 --- a/src/proto_alpha/bin_sc_rollup_node/fueled_pvm.ml +++ b/src/proto_alpha/bin_sc_rollup_node/fueled_pvm.ml @@ -30,262 +30,340 @@ open Protocol open Alpha_context module type S = sig - type state + module PVM : Pvm.S type fuel + type eval_result = {state : PVM.state; remaining_fuel : fuel; num_ticks : Z.t} + + (** [eval_block_inbox ~fuel node_ctxt block_hash state] evaluates the + [messages] for the inbox of block [block_hash] in the given [state] of the + PVM and returns the evaluation results containing the new state, the + number of messages, the inbox level and the remaining fuel. *) val eval_block_inbox : - metadata:Sc_rollup.Metadata.t -> - dal_attestation_lag:int -> fuel:fuel -> _ Node_context.t -> Tezos_crypto.Block_hash.t -> - state -> - (state * Z.t * Raw_level.t * fuel) Node_context.delayed_write tzresult Lwt.t + PVM.state -> + (PVM.state * Z.t * Raw_level.t * fuel) Node_context.delayed_write tzresult + Lwt.t + + (** [eval_messages ?reveal_map ~fuel node_ctxt ~message_counter_offset state + inbox_level messages] evaluates the [messages] for inbox level + [inbox_level] in the given [state] of the PVM and returns the evaluation + results containing the new state, the remaining fuel, and the number of + ticks for the evaluation of these messages. [message_counter_offset] is + used when we evaluate partial inboxes, such as during simulation. When + [reveal_map] is provided, it is used as an additional source of data for + revelation ticks. *) + val eval_messages : + ?reveal_map:string Sc_rollup.Reveal_hash.Map.t -> + fuel:fuel -> + _ Node_context.t -> + message_counter_offset:int -> + PVM.state -> + Raw_level.t -> + Sc_rollup.Inbox_message.t list -> + eval_result Node_context.delayed_write tzresult Lwt.t end -module Make - (PVM : Pvm.S) - (Interpreter_event : Interpreter_event.S with type state := PVM.state) - (F : Fuel.S) : S with type state = PVM.state and type fuel = F.t = struct - type state = PVM.state +module Make (PVM : Pvm.S) = struct + module Make_fueled (F : Fuel.S) : + S with module PVM = PVM and type fuel = F.t = struct + module PVM = PVM - type fuel = F.t + type fuel = F.t - let continue_with_fuel consumption initial_fuel state f = - let open Delayed_write_monad.Lwt_result_syntax in - match F.consume consumption initial_fuel with - | None -> return (state, 0L) - | Some fuel_left -> f fuel_left state + type eval_result = { + state : PVM.state; + remaining_fuel : fuel; + num_ticks : Z.t; + } - exception Error_wrapper of tztrace + let get_reveal ~data_dir reveal_map hash = + let found_in_map = + match reveal_map with + | None -> None + | Some map -> Sc_rollup.Reveal_hash.Map.find_opt hash map + in + match found_in_map with + | Some data -> return data + | None -> Reveals.get ~data_dir ~pvm_name:PVM.name ~hash - (** [eval_until_input ~metadata 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 ~metadata ~dal_attestation_lag data_dir store level - message_index ~fuel start_tick failing_ticks state = - let open Lwt_result_syntax in - let open Delayed_write_monad.Lwt_result_syntax in - let module Builtins = struct - let reveal_preimage hash = - let hash = - (* The 32-byte payload represents the encoded [Reveal_hash.t]. We must - decode it properly, instead of converting it byte-for-byte. *) - Tezos_webassembly_interpreter.Reveal.reveal_hash_to_string hash - |> Data_encoding.Binary.of_string_exn Sc_rollup.Reveal_hash.encoding - in - let*! data = Reveals.get ~data_dir ~pvm_name:PVM.name ~hash in - match data with - | Error error -> - (* The [Error_wrapper] must be caught upstream and converted into a - tzresult. *) - Lwt.fail (Error_wrapper error) - | Ok data -> Lwt.return data + let continue_with_fuel consumption initial_fuel state f = + let open Delayed_write_monad.Lwt_result_syntax in + match F.consume consumption initial_fuel with + | None -> return (state, 0L) + | Some fuel_left -> f fuel_left state - let reveal_metadata () = - Lwt.return - (Data_encoding.Binary.to_string_exn - Sc_rollup.Metadata.encoding - metadata) - end in - let builtins = (module Builtins : Tezos_scoru_wasm.Builtins.S) in - let eval_tick fuel tick failing_ticks state = - let max_steps = F.max_ticks fuel in - let normal_eval state = - Lwt.catch - (fun () -> - let*! state, executed_ticks = - PVM.eval_many ~builtins ~max_steps state - in - return (state, executed_ticks, failing_ticks)) - (function - | Error_wrapper error -> Lwt.return (Error error) | exn -> raise exn) + exception Error_wrapper of tztrace + + (** [eval_until_input node_ctxt reveal_map 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 node_ctxt reveal_map level message_index ~fuel + start_tick failing_ticks state = + let open Lwt_result_syntax in + let open Delayed_write_monad.Lwt_result_syntax in + let metadata = Node_context.metadata node_ctxt in + let dal_attestation_lag = + node_ctxt.protocol_constants.parametric.dal.attestation_lag in - let failure_insertion_eval state failing_ticks' = - let*! () = - Interpreter_event.intended_failure - ~level - ~message_index - ~message_tick:tick - ~internal:true + let module Builtins = struct + let reveal_preimage hash = + let hash = + (* The 32-byte payload represents the encoded [Reveal_hash.t]. We must + decode it properly, instead of converting it byte-for-byte. *) + Tezos_webassembly_interpreter.Reveal.reveal_hash_to_string hash + |> Data_encoding.Binary.of_string_exn Sc_rollup.Reveal_hash.encoding + in + let*! data = + get_reveal ~data_dir:node_ctxt.data_dir reveal_map hash + in + match data with + | Error error -> + (* The [Error_wrapper] must be caught upstream and converted into a + tzresult. *) + Lwt.fail (Error_wrapper error) + | Ok data -> Lwt.return data + + let reveal_metadata () = + Lwt.return + (Data_encoding.Binary.to_string_exn + Sc_rollup.Metadata.encoding + metadata) + end in + let builtins = (module Builtins : Tezos_scoru_wasm.Builtins.S) in + let eval_tick fuel tick failing_ticks state = + let max_steps = F.max_ticks fuel in + let normal_eval state = + Lwt.catch + (fun () -> + let*! state, executed_ticks = + PVM.eval_many ~builtins ~max_steps state + in + return (state, executed_ticks, failing_ticks)) + (function + | Error_wrapper error -> Lwt.return (Error error) + | exn -> raise exn) in - let*! state = PVM.Internal_for_tests.insert_failure state in - return (state, 1L, failing_ticks') + let failure_insertion_eval state failing_ticks' = + let*! () = + Interpreter_event.intended_failure + ~level + ~message_index + ~message_tick:tick + ~internal:true + in + let*! state = PVM.Internal_for_tests.insert_failure state in + return (state, 1L, failing_ticks') + in + match failing_ticks with + | xtick :: failing_ticks' when xtick = tick -> + failure_insertion_eval state failing_ticks' + | _ -> normal_eval state in - match failing_ticks with - | xtick :: failing_ticks' when xtick = tick -> - failure_insertion_eval state failing_ticks' - | _ -> normal_eval state - in - let rec go (fuel : fuel) current_tick failing_ticks state = - let*! input_request = PVM.is_input_state state in - if F.is_empty fuel then return (state, fuel, current_tick, failing_ticks) - else - match input_request with - | No_input_required -> - let>* next_state, executed_ticks, failing_ticks = - eval_tick fuel current_tick failing_ticks state - in - go - fuel - (Int64.add current_tick executed_ticks) - failing_ticks - next_state - | Needs_reveal (Reveal_raw_data hash) -> ( - let* data = Reveals.get ~data_dir ~pvm_name:PVM.name ~hash in - let*! next_state = PVM.set_input (Reveal (Raw_data data)) state in - match F.consume F.one_tick_consumption fuel with - | None -> return (state, fuel, current_tick, failing_ticks) - | Some fuel -> - go fuel (Int64.succ current_tick) failing_ticks next_state) - | Needs_reveal Reveal_metadata -> ( - let*! next_state = - PVM.set_input (Reveal (Metadata metadata)) state - in - match F.consume F.one_tick_consumption fuel with - | None -> return (state, fuel, current_tick, failing_ticks) - | Some fuel -> - go fuel (Int64.succ current_tick) failing_ticks next_state) - | Needs_reveal (Request_dal_page page_id) -> ( - let>* content_opt = - Dal_pages_request.page_content ~dal_attestation_lag store page_id - in - let*! next_state = - PVM.set_input (Reveal (Dal_page content_opt)) state - in - match F.consume F.one_tick_consumption fuel with - | None -> return (state, fuel, current_tick, failing_ticks) - | Some fuel -> - go fuel (Int64.succ current_tick) failing_ticks next_state) - | Initial | First_after _ -> - return (state, fuel, current_tick, failing_ticks) - in - go fuel start_tick failing_ticks state - - (** [mutate input] corrupts the payload of [input] for testing purposes. *) - let mutate input = - let payload = Sc_rollup.Inbox_message.unsafe_of_string "0xC4C4" in - {input with Sc_rollup.payload} + let rec go (fuel : fuel) current_tick failing_ticks state = + let*! input_request = PVM.is_input_state state in + if F.is_empty fuel then return (state, fuel, current_tick, failing_ticks) + else + match input_request with + | No_input_required -> + let>* next_state, executed_ticks, failing_ticks = + eval_tick fuel current_tick failing_ticks state + in + go + fuel + (Int64.add current_tick executed_ticks) + failing_ticks + next_state + | Needs_reveal (Reveal_raw_data hash) -> ( + let* data = + get_reveal ~data_dir:node_ctxt.data_dir reveal_map hash + in + let*! next_state = PVM.set_input (Reveal (Raw_data data)) state in + match F.consume F.one_tick_consumption fuel with + | None -> return (state, fuel, current_tick, failing_ticks) + | Some fuel -> + go fuel (Int64.succ current_tick) failing_ticks next_state) + | Needs_reveal Reveal_metadata -> ( + let*! next_state = + PVM.set_input (Reveal (Metadata metadata)) state + in + match F.consume F.one_tick_consumption fuel with + | None -> return (state, fuel, current_tick, failing_ticks) + | Some fuel -> + go fuel (Int64.succ current_tick) failing_ticks next_state) + | Needs_reveal (Request_dal_page page_id) -> ( + let>* content_opt = + Dal_pages_request.page_content + ~dal_attestation_lag + node_ctxt + page_id + in + let*! next_state = + PVM.set_input (Reveal (Dal_page content_opt)) state + in + match F.consume F.one_tick_consumption fuel with + | None -> return (state, fuel, current_tick, failing_ticks) + | Some fuel -> + go fuel (Int64.succ current_tick) failing_ticks next_state) + | Initial | First_after _ -> + return (state, fuel, current_tick, failing_ticks) + in + go fuel start_tick failing_ticks state - (** [feed_input ~metadata level message_index ~fuel ~failing_ticks state - input] feeds [input] (that has a given [message_index] in inbox - of [level]) to the PVM in order to advance [state] to the next - 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 ~metadata ~dal_attestation_lag data_dir store level - message_index ~fuel ~failing_ticks state input = - let open Lwt_result_syntax in - let open Delayed_write_monad.Lwt_result_syntax in - let>* state, fuel, tick, failing_ticks = - eval_until_input - ~metadata - ~dal_attestation_lag - data_dir - store - level - message_index - ~fuel - 0L - failing_ticks - state - in - let consumption = F.of_ticks tick in - continue_with_fuel consumption 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 (Inbox_message input) state in - let>* state, _fuel, tick, _failing_ticks = - eval_until_input - ~metadata - ~dal_attestation_lag - data_dir - store - level - message_index - ~fuel - tick - failing_ticks - state - in - return (state, tick) + (** [mutate input] corrupts the payload of [input] for testing purposes. *) + let mutate input = + let payload = Sc_rollup.Inbox_message.unsafe_of_string "0xC4C4" in + {input with Sc_rollup.payload} - let eval_block_inbox ~metadata ~dal_attestation_lag ~fuel - (Node_context.{data_dir; store; loser_mode; _} as node_context) hash - (state : state) : - (state * Z.t * Raw_level.t * fuel) Node_context.delayed_write tzresult - Lwt.t = - let open Lwt_result_syntax in - let open Delayed_write_monad.Lwt_result_syntax in - (* Obtain inbox and its messages for this block. *) - let*! inbox = Store.Inboxes.find store hash in - match inbox with - | None -> - (* A level with no messages for use. Skip it. *) - let* level = State.level_of_hash store hash in - return (state, Z.zero, Raw_level.of_int32_exn level, fuel) - | Some inbox -> - let inbox_level = Inbox.inbox_level inbox in - let*! messages = Store.Messages.get store hash in - (* TODO: #2717 - The length of messages here can potentially overflow the [int] returned from [List.length]. - *) - let num_messages = List.length messages |> Z.of_int in + (** [feed_input node_ctxt reveal_map level message_index ~fuel + ~failing_ticks state input] feeds [input] (that has a given + [message_index] in inbox of [level]) to the PVM in order to advance + [state] to the next 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 node_ctxt reveal_map level message_index ~fuel ~failing_ticks + state input = + let open Lwt_result_syntax in + let open Delayed_write_monad.Lwt_result_syntax in + let>* state, fuel, tick, failing_ticks = + eval_until_input + node_ctxt + reveal_map + level + message_index + ~fuel + 0L + failing_ticks + state + in + let consumption = F.of_ticks tick in + continue_with_fuel consumption 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 (Inbox_message input) state in + let>* state, _fuel, tick, _failing_ticks = + eval_until_input + node_ctxt + reveal_map + level + message_index + ~fuel + tick + failing_ticks + state + in + return (state, tick) - let feed_message (message_counter : int) (state, fuel) - (message : Sc_rollup.Inbox_message.t) = + let eval_messages ~reveal_map ~fuel node_ctxt ~message_counter_offset state + inbox_level messages = + let open Lwt_result_syntax in + let open Delayed_write_monad.Lwt_result_syntax in + let level = Raw_level.to_int32 inbox_level |> Int32.to_int in + (* Iterate the PVM state with all the messages. *) + list_fold_left_i_es + (fun message_counter (state, fuel) message -> let*? payload = Sc_rollup.Inbox_message.( message |> serialize |> Environment.wrap_tzresult) in - let input = - Sc_rollup. - {inbox_level; message_counter = Z.of_int message_counter; payload} - in - let level = Raw_level.to_int32 inbox_level |> Int32.to_int in - + let message_index = message_counter_offset + message_counter in + let message_counter = Z.of_int message_index in + let input = Sc_rollup.{inbox_level; message_counter; payload} in let failing_ticks = Loser_mode.is_failure - loser_mode + node_ctxt.Node_context.loser_mode ~level - ~message_index:message_counter + ~message_index in let>* state, executed_ticks = feed_input - ~metadata - ~dal_attestation_lag - data_dir - node_context + node_ctxt + reveal_map level - message_counter + message_index ~fuel ~failing_ticks state input in - return (state, F.of_ticks executed_ticks) - in - (* Iterate the PVM state with all the messages for this level. *) - let>* state, fuel = - list_fold_left_i_es feed_message (state, fuel) messages - in - return (state, num_messages, inbox_level, fuel) + return (state, F.of_ticks executed_ticks)) + (state, fuel) + messages + + let eval_block_inbox ~fuel (Node_context.{store; _} as node_ctxt) hash + (state : PVM.state) : + (PVM.state * Z.t * Raw_level.t * fuel) Node_context.delayed_write + tzresult + Lwt.t = + let open Lwt_result_syntax in + let open Delayed_write_monad.Lwt_result_syntax in + (* Obtain inbox and its messages for this block. *) + let*! inbox = Store.Inboxes.find store hash in + match inbox with + | None -> + (* A level with no messages for use. Skip it. *) + let* level = State.level_of_hash store hash in + return (state, Z.zero, Raw_level.of_int32_exn level, fuel) + | Some inbox -> + let inbox_level = Inbox.inbox_level inbox in + let*! messages = Store.Messages.get store hash in + (* TODO: #2717 + The length of messages here can potentially overflow the [int] returned from [List.length]. + *) + let num_messages = List.length messages |> Z.of_int in + (* Evaluate all the messages for this level. *) + let>* state, fuel = + eval_messages + ~reveal_map:None + ~fuel + node_ctxt + ~message_counter_offset:0 + state + inbox_level + messages + in + return (state, num_messages, inbox_level, fuel) + + let eval_messages ?reveal_map ~fuel node_ctxt ~message_counter_offset state + inbox_level messages = + let open Lwt_result_syntax in + let open Delayed_write_monad.Lwt_result_syntax in + let*! initial_tick = PVM.get_tick state in + let>* state, remaining_fuel = + eval_messages + ~reveal_map + ~fuel + node_ctxt + ~message_counter_offset + state + inbox_level + messages + in + let*! final_tick = PVM.get_tick state in + let num_ticks = Sc_rollup.Tick.distance initial_tick final_tick in + return {state; remaining_fuel; num_ticks} + end + + module Free = Make_fueled (Fuel.Free) + module Accounted = Make_fueled (Fuel.Accounted) end diff --git a/src/proto_alpha/bin_sc_rollup_node/inbox.ml b/src/proto_alpha/bin_sc_rollup_node/inbox.ml index 45beefd267d9b2ae99567bb2850eb27d69f4659b..278f776f450e866799443bb1fbbe7de78335a1c7 100644 --- a/src/proto_alpha/bin_sc_rollup_node/inbox.ml +++ b/src/proto_alpha/bin_sc_rollup_node/inbox.ml @@ -29,7 +29,7 @@ open Protocol open Alpha_context -let lift = Lwt.map Environment.wrap_tzresult +let lift promise = Lwt.map Environment.wrap_tzresult promise module State = struct let add_messages = Store.Messages.add @@ -159,6 +159,39 @@ let same_inbox_as_layer_1 node_ctxt head_hash inbox = (Sc_rollup.Inbox.equal layer1_inbox inbox) (Sc_rollup_node_errors.Inconsistent_inbox {layer1_inbox; inbox}) +let add_messages (node_ctxt : _ Node_context.t) ctxt level inbox history + messages = + let open Lwt_result_syntax in + let*! messages_tree = Context.MessageTrees.find ctxt in + lift + @@ + if messages = [] then return (history, inbox, ctxt) + else + let*? messages = List.map_e Sc_rollup.Inbox_message.serialize messages in + (* TODO: https://gitlab.com/tezos/tezos/-/issues/3978 + The number of messages during commitment period is broken with the + unique inbox. *) + (* + let commitment_period = + node_ctxt.protocol_constants.parametric.sc_rollup + .commitment_period_in_blocks |> Int32.of_int + in + let inbox = + Sc_rollup.Inbox.refresh_commitment_period ~commitment_period ~level inbox + in + *) + let* messages_tree, history, inbox = + Context.Inbox.add_messages + node_ctxt.context + history + inbox + level + messages + messages_tree + in + let*! ctxt = Context.MessageTrees.set ctxt messages_tree in + return (history, inbox, ctxt) + let process_head (node_ctxt : _ Node_context.t) Layer1.({level; hash = head_hash} as head) = let open Lwt_result_syntax in @@ -181,49 +214,16 @@ let process_head (node_ctxt : _ Node_context.t) let* predecessor = Layer1.get_predecessor node_ctxt.l1_ctxt head in let* inbox = State.inbox_of_head node_ctxt predecessor in let* history = State.history_of_head node_ctxt predecessor in + let*? level = Environment.wrap_tzresult @@ Raw_level.of_int32 level in let* ctxt = - if level <= Raw_level.to_int32 node_ctxt.Node_context.genesis_info.level - then + if Raw_level.(level <= node_ctxt.Node_context.genesis_info.level) then (* This is before we have interpreted the boot sector, so we start with an empty context in genesis *) return (Context.empty node_ctxt.context) else Node_context.checkout_context node_ctxt predecessor.hash in - let*! messages_tree = Context.MessageTrees.find ctxt in let* history, inbox, ctxt = - lift - @@ let*? level = Raw_level.of_int32 level in - let*? messages = - List.map_e Sc_rollup.Inbox_message.serialize messages - in - if messages = [] then return (history, inbox, ctxt) - else - (* TODO: https://gitlab.com/tezos/tezos/-/issues/3978 - - The number of messages during commitment period is broken with the - unique inbox. *) - (* let commitment_period = - * node_ctxt.protocol_constants.parametric.sc_rollup - * .commitment_period_in_blocks |> Int32.of_int - * in - * let inbox = - * Sc_rollup.Inbox.refresh_commitment_period - * ~commitment_period - * ~level - * inbox - * in *) - let* messages_tree, history, inbox = - Context.Inbox.add_messages - node_ctxt.context - history - inbox - level - messages - messages_tree - in - - let*! ctxt = Context.MessageTrees.set ctxt messages_tree in - return (history, inbox, ctxt) + add_messages node_ctxt ctxt level inbox history messages in let* () = same_inbox_as_layer_1 node_ctxt head_hash inbox in let*! () = State.add_inbox node_ctxt.store head_hash inbox in @@ -241,4 +241,8 @@ let history_of_hash node_ctxt hash = let* level = State.level_of_hash node_ctxt.Node_context.store hash in State.history_of_head node_ctxt {hash; level} +let inbox_of_head = State.inbox_of_head + +let history_of_head = State.history_of_head + let start () = Inbox_event.starting () diff --git a/src/proto_alpha/bin_sc_rollup_node/inbox.mli b/src/proto_alpha/bin_sc_rollup_node/inbox.mli index 24457aadba25e7f51e15ff1fa7c1598fcf968e9b..12873db7bc7b28ba014055563f2dac0b4a3883ad 100644 --- a/src/proto_alpha/bin_sc_rollup_node/inbox.mli +++ b/src/proto_alpha/bin_sc_rollup_node/inbox.mli @@ -54,5 +54,28 @@ val history_of_hash : Tezos_crypto.Block_hash.t -> Sc_rollup.Inbox.History.t tzresult Lwt.t +(** [inbox_of_head node_ctxt block_head] returns the rollup inbox at the end of + the given validation of [block_head]. *) +val inbox_of_head : + _ Node_context.t -> Layer1.head -> Sc_rollup.Inbox.t tzresult Lwt.t + +(** [history_of_head node_ctxt block_head] returns the rollup inbox history at + the end of the given validation of [block_head]. *) +val history_of_head : + _ Node_context.t -> Layer1.head -> Sc_rollup.Inbox.History.t tzresult Lwt.t + (** [start ()] initializes the inbox to track the messages being published. *) val start : unit -> unit Lwt.t + +(** [add_messages node_ctxt ctxt inbox_level inbox history messages] adds + [messages] to the [inbox] whose history is [history]. The new inbox level is + given as [inbox_level]. It takes a context [ctxt] and returns a new + [history], [inbox] and context [ctxt] with an updated message tree. *) +val add_messages : + Node_context.rw -> + 'a Context.t -> + Raw_level.t -> + Sc_rollup.Inbox.t -> + Sc_rollup.Inbox.History.t -> + Sc_rollup.Inbox_message.t list -> + (Sc_rollup.Inbox.History.t * Sc_rollup.Inbox.t * 'a Context.t) tzresult Lwt.t diff --git a/src/proto_alpha/bin_sc_rollup_node/interpreter.ml b/src/proto_alpha/bin_sc_rollup_node/interpreter.ml index 2421f68454b0a3805306d69b67d6995fb8dbec67..711bf576cdddc3f7043e20a5235840b5817d2668 100644 --- a/src/proto_alpha/bin_sc_rollup_node/interpreter.ml +++ b/src/proto_alpha/bin_sc_rollup_node/interpreter.ml @@ -29,40 +29,33 @@ open Alpha_context module type S = sig module PVM : Pvm.S - val metadata : _ Node_context.t -> Sc_rollup.Metadata.t + module Accounted_pvm : + Fueled_pvm.S with module PVM = PVM and type fuel = Fuel.Accounted.t + + module Free_pvm : + Fueled_pvm.S with module PVM = PVM and type fuel = Fuel.Free.t - (** [process_head node_ctxt 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.rw -> Context.rw -> Layer1.head -> unit tzresult Lwt.t - (** [state_of_tick node_ctxt 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 -> Sc_rollup.Tick.t -> Raw_level.t -> (PVM.state * PVM.hash) option tzresult Lwt.t + + val state_of_head : + 'a Node_context.t -> + 'a Context.t -> + Layer1.head -> + ('a Context.t * PVM.state) tzresult Lwt.t end module Make (PVM : Pvm.S) : S with module PVM = PVM = struct module PVM = PVM - - module Interpreter_event : Interpreter_event.S with type state := PVM.state = - Interpreter_event.Make (PVM) - - module Accounted_pvm = - Fueled_pvm.Make (PVM) (Interpreter_event) (Fuel.Accounted) - module Free_pvm = Fueled_pvm.Make (PVM) (Interpreter_event) (Fuel.Free) - - (** [metadata node_ctxt] creates a {Sc_rollup.Metadata.t} using the information - stored in [node_ctxt]. *) - let metadata (node_ctxt : _ Node_context.t) = - let address = node_ctxt.rollup_address in - let origination_level = node_ctxt.genesis_info.Sc_rollup.Commitment.level in - Sc_rollup.Metadata.{address; origination_level} + module Fueled_pvm = Fueled_pvm.Make (PVM) + module Accounted_pvm = Fueled_pvm.Accounted + module Free_pvm = Fueled_pvm.Free (** [get_boot_sector block_hash node_ctxt] fetches the operations in the [block_hash] and looks for the bootsector used to originate the rollup @@ -137,14 +130,8 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct let open Lwt_result_syntax in (* Retrieve the previous PVM state from store. *) let* ctxt, predecessor_state = state_of_head node_ctxt ctxt predecessor in - let metadata = metadata node_ctxt in - let dal_attestation_lag = - node_ctxt.protocol_constants.parametric.dal.attestation_lag - in let* eval_result = Free_pvm.eval_block_inbox - ~metadata - ~dal_attestation_lag ~fuel:(Fuel.Free.of_ticks 0L) node_ctxt hash @@ -189,8 +176,13 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct {num_messages; num_ticks; initial_tick} in (* Produce events. *) + let*! state_hash = PVM.state_hash state in let*! () = - Interpreter_event.transitioned_pvm inbox_level state num_messages + Interpreter_event.transitioned_pvm + inbox_level + state_hash + last_tick + num_messages in return_unit @@ -240,14 +232,8 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct ctxt Layer1.{hash = predecessor_hash; level = pred_level} in - let metadata = metadata node_ctxt in - let dal_attestation_lag = - node_ctxt.protocol_constants.parametric.dal.attestation_lag - in let>* state, _counter, _level, _fuel = Accounted_pvm.eval_block_inbox - ~metadata - ~dal_attestation_lag ~fuel:(Fuel.Accounted.of_ticks tick_distance) node_ctxt hash diff --git a/src/proto_alpha/bin_sc_rollup_node/interpreter.mli b/src/proto_alpha/bin_sc_rollup_node/interpreter.mli new file mode 100644 index 0000000000000000000000000000000000000000..9f3fbc27871ee964a84fb4f3e215294c62d7d988 --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/interpreter.mli @@ -0,0 +1,63 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 TriliTech *) +(* *) +(* 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 + +module type S = sig + module PVM : Pvm.S + + module Accounted_pvm : + Fueled_pvm.S with module PVM = PVM and type fuel = Fuel.Accounted.t + + module Free_pvm : + Fueled_pvm.S with module PVM = PVM and type fuel = Fuel.Free.t + + (** [process_head node_ctxt 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.rw -> Context.rw -> Layer1.head -> unit tzresult Lwt.t + + (** [state_of_tick node_ctxt 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 -> + Sc_rollup.Tick.t -> + Raw_level.t -> + (PVM.state * PVM.hash) option tzresult Lwt.t + + (** [state_of_head node_ctxt ctxt head] returns the state corresponding to the + block [head], or the state at rollup genesis if the block is before the + rollup origination. *) + val state_of_head : + 'a Node_context.t -> + 'a Context.t -> + Layer1.head -> + ('a Context.t * PVM.state) tzresult Lwt.t +end + +(** Functor to construct an interpreter for a given PVM. *) +module Make (PVM : Pvm.S) : S with module PVM = PVM 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 4ebe754da6a30e523692e3558d23edf32fb049a6..49152eed397aa73e17c7549827d6696fa223717d 100644 --- a/src/proto_alpha/bin_sc_rollup_node/interpreter_event.ml +++ b/src/proto_alpha/bin_sc_rollup_node/interpreter_event.ml @@ -25,71 +25,51 @@ open Protocol.Alpha_context.Sc_rollup -module type S = sig - type state +module Simple = struct + include Internal_event.Simple - (** [transition_pvm inbox_level hash n] emits the event that a PVM + let section = ["sc_rollup_node"; "interpreter"] + + let transitioned_pvm = + declare_4 + ~section + ~name:"sc_rollup_node_interpreter_transitioned_pvm" + ~msg: + "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.int64) + ("internal", Data_encoding.bool) +end + +(** [transition_pvm inbox_level hash tick 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 -> state -> Z.t -> unit Lwt.t + processing [n] messages at [tick]. *) +let transitioned_pvm inbox_level hash tick num_messages = + Simple.(emit transitioned_pvm (inbox_level, hash, tick, num_messages)) - (** [intended_failure level message_index message_tick internal] emits +(** [intended_failure level message_index message_tick internal] 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. [internal] is [true] if the failure is injected in a PVM internal step. [internal] is [false] if the failure is injected in the input to the PVM. *) - val intended_failure : - level:int -> - message_index:int -> - message_tick:int64 -> - internal:bool -> - unit Lwt.t -end - -module Make (PVM : Pvm.S) : S with type state := PVM.state = struct - module Simple = struct - include Internal_event.Simple - - let section = ["sc_rollup_node"; PVM.name; "interpreter"] - - let transitioned_pvm = - declare_4 - ~section - ~name:"sc_rollup_node_interpreter_transitioned_pvm" - ~msg: - "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.int64) - ("internal", Data_encoding.bool) - end - - let transitioned_pvm inbox_level state num_messages = - let open Lwt_syntax in - let* hash = PVM.state_hash state in - let* ticks = PVM.get_tick state in - 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)) -end +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/main_sc_rollup_node_alpha.ml b/src/proto_alpha/bin_sc_rollup_node/main_sc_rollup_node_alpha.ml index f91d067aff6004832bf66a060d23a9ad0d36b762..7341a72bf374b4fe994162b0c1b469334bd1f224 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 @@ -205,6 +205,12 @@ let pvm_name_arg = ~default:"arith" Client_proto_args.string_parameter +let pvm_kind_arg = + Tezos_clic.map_arg pvm_name_arg ~f:(fun _ name -> + match Protocol.Alpha_context.Sc_rollup.Kind.of_name name with + | None -> failwith "Invalid PVM name %s" name + | Some kind -> return kind) + let group = { Tezos_clic.name = "sc_rollup.node"; @@ -331,13 +337,16 @@ let import_command = let open Tezos_clic in command ~group - ~desc:"Run the rollup daemon." - (args3 data_dir_arg filename_arg pvm_name_arg) + ~desc:"Import data to be used in reveal ticks." + (args3 data_dir_arg filename_arg pvm_kind_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.Reveal_hash.pp hash - >>= return) + (fun (data_dir, filename, pvm_kind) cctxt -> + let open Lwt_result_syntax in + let*? hash = Reveals.import ~data_dir pvm_kind ~filename in + let*! () = + cctxt#message "%a" Protocol.Alpha_context.Sc_rollup.Reveal_hash.pp hash + in + return_unit) let sc_rollup_commands () = List.map 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 d666f9a2a93faa34445927ff578b191567614656..062ba1365261667145cc025d26ba2ac2554369c7 100644 --- a/src/proto_alpha/bin_sc_rollup_node/node_context.ml +++ b/src/proto_alpha/bin_sc_rollup_node/node_context.ml @@ -106,6 +106,11 @@ let checkout_context node_ctxt block_hash = (block_hash, Some (Context.hash_to_raw_string context_hash))) | Some ctxt -> return ctxt +let metadata node_ctxt = + let address = node_ctxt.rollup_address in + let origination_level = node_ctxt.genesis_info.Sc_rollup.Commitment.level in + Sc_rollup.Metadata.{address; origination_level} + let readonly (node_ctxt : _ t) = { node_ctxt with 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 6ff286da6e4bab3853459aab5979f040d3be0132..2d04267b78f84b37fd60e15a44014346cce0a067 100644 --- a/src/proto_alpha/bin_sc_rollup_node/node_context.mli +++ b/src/proto_alpha/bin_sc_rollup_node/node_context.mli @@ -105,6 +105,10 @@ val init : val checkout_context : 'a t -> Tezos_crypto.Block_hash.t -> 'a Context.t tzresult Lwt.t +(** [metadata node_ctxt] creates a {Sc_rollup.Metadata.t} using the information + stored in [node_ctxt]. *) +val metadata : _ t -> Sc_rollup.Metadata.t + (** [readonly node_ctxt] returns a read only version of the node context [node_ctxt]. *) val readonly : _ t -> ro diff --git a/src/proto_alpha/bin_sc_rollup_node/outbox.ml b/src/proto_alpha/bin_sc_rollup_node/outbox.ml index 9f2420f9b12e5bfbb254ed5bcda41de6ab412cc5..27c1f166d986097312c2472d77c1e6e34b80b7c9 100644 --- a/src/proto_alpha/bin_sc_rollup_node/outbox.ml +++ b/src/proto_alpha/bin_sc_rollup_node/outbox.ml @@ -23,8 +23,6 @@ (* *) (*****************************************************************************) -(** This module provides helper to interact with PVM outboxes. *) - open Node_context open Protocol.Alpha_context diff --git a/src/proto_alpha/bin_sc_rollup_node/outbox.mli b/src/proto_alpha/bin_sc_rollup_node/outbox.mli new file mode 100644 index 0000000000000000000000000000000000000000..d061ca3397c13813b49287698d443e3b2ec44472 --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/outbox.mli @@ -0,0 +1,37 @@ +(*****************************************************************************) +(* *) +(* 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 helper to interact with PVM outboxes. *) + +open Protocol.Alpha_context + +module Make (PVM : Pvm.S) : sig + (** [proof_of_output node_ctxt output] returns the last cemented commitment + hash and the proof of the output in the LCC. *) + val proof_of_output : + Node_context.rw -> + Sc_rollup.output -> + (Store.Last_cemented_commitment_hash.value * string) tzresult Lwt.t +end diff --git a/src/proto_alpha/bin_sc_rollup_node/pvm.ml b/src/proto_alpha/bin_sc_rollup_node/pvm.ml index f47b30948aa55b2ca2aa990ecff4bd96b79921ca..37a10fcf32292e5d64a7e0501119a573b6bbf1f2 100644 --- a/src/proto_alpha/bin_sc_rollup_node/pvm.ml +++ b/src/proto_alpha/bin_sc_rollup_node/pvm.ml @@ -34,6 +34,9 @@ module type S = sig with type context = Context.rw_index and type hash = Sc_rollup.State_hash.t + (** Kind of the PVM (same as {!name}). *) + val kind : Sc_rollup.Kind.t + (** [get_tick state] gets the total tick counter for the given PVM state. *) val get_tick : state -> Sc_rollup.Tick.t Lwt.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 1a3836d4abedd0bfe57163882ec168023e7c6418..2975aa1ee54267faa27ef402ee83be2e2b8aa0a6 100644 --- a/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml +++ b/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml @@ -243,7 +243,7 @@ module Make (Interpreter : Interpreter.S) : let page_info = page_info end end in - let metadata = Interpreter.metadata node_ctxt in + let metadata = Node_context.metadata node_ctxt in let* proof = trace (Sc_rollup_node_errors.Cannot_produce_proof diff --git a/src/proto_alpha/bin_sc_rollup_node/reveals.ml b/src/proto_alpha/bin_sc_rollup_node/reveals.ml index 4f0523afefa31cc1193224f8171dff2e1b8fc5ec..8642a5da81542af2ebbde089eb100e0c32aa4440 100644 --- a/src/proto_alpha/bin_sc_rollup_node/reveals.ml +++ b/src/proto_alpha/bin_sc_rollup_node/reveals.ml @@ -28,6 +28,8 @@ module Reveal_hash = Protocol.Alpha_context.Sc_rollup.Reveal_hash type error += | Wrong_hash of {found : Reveal_hash.t; expected : Reveal_hash.t} | Could_not_open_preimage_file of String.t + | Empty_reveal_data + | PVM_not_supported of Protocol.Alpha_context.Sc_rollup.Kind.t let () = register_error_kind @@ -63,7 +65,33 @@ let () = Data_encoding.(obj1 (req "hash" string)) (function | Could_not_open_preimage_file filename -> Some filename | _ -> None) - (fun filename -> Could_not_open_preimage_file filename) + (fun filename -> Could_not_open_preimage_file filename) ; + register_error_kind + `Permanent + ~id:"sc_rollup_node_empty_reveal_data" + ~title:"Empty revelation data" + ~description:"Empty revelation data." + ~pp:(fun ppf () -> + Format.pp_print_string ppf "Tried to import or use empty revelation data.") + Data_encoding.unit + (function Empty_reveal_data -> Some () | _ -> None) + (fun () -> Empty_reveal_data) ; + register_error_kind + `Permanent + ~id:"sc_rollup_node_reveal_data_not_supported" + ~title:"Revelation data not supported for PVM" + ~description:"Revelation data not supported for PVM." + ~pp:(fun ppf kind -> + Format.fprintf + ppf + "Revelation data not supported for PVM %s" + (Protocol.Alpha_context.Sc_rollup.Kind.name_of kind)) + Data_encoding.( + obj1 (req "pvm_kind" Protocol.Alpha_context.Sc_rollup.Kind.encoding)) + (function PVM_not_supported k -> Some k | _ -> None) + (fun k -> PVM_not_supported k) + +type source = String of string | File of string let file_contents filename = let open Lwt_result_syntax in @@ -105,15 +133,27 @@ module Arith = struct let pvm_name = Protocol.Alpha_context.Sc_rollup.ArithPVM.Protocol_implementation.name - let rev_chunks_of_file filename = + type input = + | String of {s : string; mutable pos : int; len : int} + | Input of in_channel + + let rev_chunks_of_input input = (* 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 get_char () = + match input with + | Input cin -> ( try Some (input_char cin) with End_of_file -> None) + | String ({s; pos; len} as sin) -> + if pos >= len then None + else ( + sin.pos <- pos + 1 ; + Some s.[pos]) + in let buf = Buffer.create 31 in let tokens = - let cin = open_in filename in + (* let cin = open_in filename in *) let rec aux tokens = - match get_char cin with + match get_char () with | None -> List.rev @@ @@ -130,7 +170,6 @@ module Arith = struct aux tokens in let tokens = aux [] in - close_in cin ; tokens in let limit = @@ -150,10 +189,15 @@ module Arith = struct let chunk = make_chunk () in Buffer.add_string buf token ; (chunk :: chunks, len)) - else ( - if Buffer.length buf > 0 then Buffer.add_char buf ' ' ; + else + let len = + if Buffer.length buf > 0 then ( + Buffer.add_char buf ' ' ; + len + 1) + else len + in Buffer.add_string buf token ; - (chunks, size + len))) + (chunks, size + len)) ([], 0) tokens in @@ -172,24 +216,56 @@ module Arith = struct | Some h -> Format.asprintf "%s hash:%a" chunk Reveal_hash.pp h in let hash = Reveal_hash.hash_string [cell] in - aux (Some hash) ((cell, hash) :: linked_chunks) rev_chunks + aux (Some hash) ((hash, cell) :: 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 input_of_source = function + | File filename -> Input (open_in filename) + | String s -> String {s; pos = 0; len = String.length s} + + let close_input = function String _ -> () | Input cin -> close_in cin + + let chunkify source = + let input = input_of_source source in + let rev_chunks = rev_chunks_of_input input in let linked_hashed_chunks = link_rev_chunks rev_chunks in + close_input input ; + linked_hashed_chunks + + let first_hash = function + | (hash, _) :: _ -> ok hash + | [] -> error Empty_reveal_data + + let import data_dir source = + ensure_dir_exists data_dir pvm_name ; + let linked_hashed_chunks = chunkify source in List.iter - (fun (data, hash) -> save_string (path data_dir pvm_name hash) data) + (fun (hash, data) -> save_string (path data_dir pvm_name hash) data) linked_hashed_chunks ; - Stdlib.List.hd linked_hashed_chunks |> snd + first_hash linked_hashed_chunks + + let chunkify source = + let open Result_syntax in + let linked_hashed_chunks = chunkify source in + let chunks_map = + linked_hashed_chunks |> List.to_seq + |> Protocol.Alpha_context.Sc_rollup.Reveal_hash.Map.of_seq + in + let+ hash = first_hash linked_hashed_chunks in + (chunks_map, hash) 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" +let import ~data_dir (pvm_kind : Protocol.Alpha_context.Sc_rollup.Kind.t) + ~filename = + match pvm_kind with + | Example_arith -> Arith.import data_dir (File filename) + | Wasm_2_0_0 -> + (* TODO: https://gitlab.com/tezos/tezos/-/issues/4067 + Add support for multiple revelation data serialization schemes *) + error (PVM_not_supported pvm_kind) + +let chunkify (pvm_kind : Protocol.Alpha_context.Sc_rollup.Kind.t) source = + match pvm_kind with + | Example_arith -> Arith.chunkify source + | Wasm_2_0_0 -> error (PVM_not_supported pvm_kind) diff --git a/src/proto_alpha/bin_sc_rollup_node/reveals.mli b/src/proto_alpha/bin_sc_rollup_node/reveals.mli index 10abf2d3d7f0bac262e6c6085cfd52f0a2a7a621..bdffe3bc9618a032506621e207f955757587046c 100644 --- a/src/proto_alpha/bin_sc_rollup_node/reveals.mli +++ b/src/proto_alpha/bin_sc_rollup_node/reveals.mli @@ -50,6 +50,12 @@ open Protocol.Alpha_context +(** Source of data *) +type source = + | String of string (** A string containing the whole data *) + | File of string + (** A file name whose associated file contains the whole data *) + (** [get ~data_dir ~pvm_name ~hash] retrieves the data associated with the reveal hash [hash] from disk. May fail with: {ul @@ -66,11 +72,19 @@ val get : hash:Sc_rollup.Reveal_hash.t -> string tzresult Lwt.t -(** [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. *) +(** [import ~data_dir pvm_kind ~filename] turns the content of ~filename into a + chunk of pages of (at most) 4KB and stores them on disk in [data_dir]. It + returns the hash of the first chunk. *) val import : data_dir:string -> - pvm_name:string -> + Sc_rollup.Kind.t -> filename:string -> - Sc_rollup.Reveal_hash.t + Sc_rollup.Reveal_hash.t tzresult + +(** [chunkify pvm_kind source] turns the content of ~filename into a chunk of + pages of (at most) 4KB. It returns the map of chunks and the hash of the + first chunk. *) +val chunkify : + Sc_rollup.Kind.t -> + source -> + (string Sc_rollup.Reveal_hash.Map.t * Sc_rollup.Reveal_hash.t) tzresult diff --git a/src/proto_alpha/bin_sc_rollup_node/simulation.ml b/src/proto_alpha/bin_sc_rollup_node/simulation.ml new file mode 100644 index 0000000000000000000000000000000000000000..e092917bb390d3cf882350993324210d0e3201ec --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/simulation.ml @@ -0,0 +1,196 @@ +(*****************************************************************************) +(* *) +(* 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 + +module type S = sig + module Interpreter : Interpreter.S + + module PVM = Interpreter.PVM + module Fueled_pvm = Interpreter.Free_pvm + + type level_position = Start | Middle | End + + type t = { + ctxt : Context.ro; + inbox_level : Raw_level.t; + state : PVM.state; + reveal_map : string Sc_rollup.Reveal_hash.Map.t option; + nb_messages_period : int64; + nb_messages_inbox : int; + level_position : level_position; + } + + val start_simulation : + Node_context.ro -> + reveal_map:string Sc_rollup.Reveal_hash.Map.t option -> + Layer1.head -> + t tzresult Lwt.t + + val simulate_messages : + Node_context.ro -> + t -> + Sc_rollup.Inbox_message.t list -> + (t * Z.t) tzresult Lwt.t + + val end_simulation : Node_context.ro -> t -> (t * Z.t) tzresult Lwt.t +end + +module Make (Interpreter : Interpreter.S) : + S with module Interpreter = Interpreter = struct + module Interpreter = Interpreter + module PVM = Interpreter.PVM + module Fueled_pvm = Interpreter.Free_pvm + + type level_position = Start | Middle | End + + type t = { + ctxt : Context.ro; + inbox_level : Raw_level.t; + state : PVM.state; + reveal_map : string Sc_rollup.Reveal_hash.Map.t option; + nb_messages_period : int64; + nb_messages_inbox : int; + level_position : level_position; + } + + let start_simulation node_ctxt ~reveal_map (Layer1.{hash; level} as head) = + let open Lwt_result_syntax in + let*? level = Environment.wrap_tzresult @@ Raw_level.of_int32 level in + let*? () = + error_unless + Raw_level.(level >= node_ctxt.Node_context.genesis_info.level) + (Exn (Failure "Cannot simulate before origination level")) + in + let first_inbox_level = Raw_level.succ node_ctxt.genesis_info.level in + let* ctxt = + if Raw_level.(level < first_inbox_level) then + (* This is before we have interpreted the boot sector, so we start + with an empty context in genesis *) + return (Context.empty node_ctxt.context) + else Node_context.checkout_context node_ctxt hash + in + let* inbox = Inbox.inbox_of_head node_ctxt head in + let+ ctxt, state = Interpreter.state_of_head node_ctxt ctxt head in + let nb_messages_period = + Sc_rollup.Inbox.number_of_messages_during_commitment_period inbox + in + let inbox_level = Raw_level.succ level in + { + ctxt; + state; + inbox_level; + reveal_map; + nb_messages_period; + nb_messages_inbox = 0; + level_position = Start; + } + + let simulate_messages_no_checks (node_ctxt : Node_context.ro) + ({ + ctxt; + state; + inbox_level; + reveal_map; + nb_messages_period; + nb_messages_inbox; + level_position = _; + } as sim) messages = + let open Lwt_result_syntax in + (* Build new state *) + let* eval_result = + Fueled_pvm.eval_messages + ?reveal_map + ~fuel:(Fuel.Free.of_ticks 0L) + node_ctxt + ~message_counter_offset:nb_messages_inbox + state + inbox_level + messages + in + let Fueled_pvm.{state; num_ticks; _} = + Delayed_write_monad.ignore eval_result + in + let*! ctxt = PVM.State.set ctxt state in + let nb_messages = List.length messages in + let nb_messages_inbox = nb_messages_inbox + nb_messages in + let nb_messages_period = + Int64.add nb_messages_period (Int64.of_int nb_messages) + in + return + ({sim with ctxt; state; nb_messages_period; nb_messages_inbox}, num_ticks) + + let simulate_messages (node_ctxt : Node_context.ro) sim messages = + let open Lwt_result_syntax in + (* Build new inbox *) + let*? () = + error_when + (sim.level_position = End) + (Exn (Failure "Level for simulation is ended")) + in + let*? () = + error_when + (messages = []) + (Environment.wrap_tzerror Sc_rollup_errors.Sc_rollup_add_zero_messages) + in + let messages = + if sim.level_position = Start then + Sc_rollup.Inbox_message.Internal Start_of_level :: messages + else messages + in + let max_messages = + node_ctxt.protocol_constants.parametric.sc_rollup + .max_number_of_messages_per_commitment_period |> Int64.of_int + |> Int64.pred (* To account for End_of_level message *) + in + let nb_messages_period = + Int64.add sim.nb_messages_period (Int64.of_int (List.length messages)) + in + let*? () = + error_when + Compare.Int64.(nb_messages_period > max_messages) + (Environment.wrap_tzerror + Sc_rollup_errors + .Sc_rollup_max_number_of_messages_reached_for_commitment_period) + in + let+ sim, num_ticks = simulate_messages_no_checks node_ctxt sim messages in + ({sim with level_position = Middle}, num_ticks) + + let end_simulation node_ctxt sim = + let open Lwt_result_syntax in + let*? () = + error_when + (sim.level_position = End) + (Exn (Failure "Level for simulation is ended")) + in + let+ sim, num_ticks = + simulate_messages_no_checks + node_ctxt + sim + [Sc_rollup.Inbox_message.Internal End_of_level] + in + ({sim with level_position = End}, num_ticks) +end diff --git a/src/proto_alpha/bin_sc_rollup_node/simulation.mli b/src/proto_alpha/bin_sc_rollup_node/simulation.mli new file mode 100644 index 0000000000000000000000000000000000000000..9d25bceb4ebd352856a3e5eab88f2e613de3e216 --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/simulation.mli @@ -0,0 +1,72 @@ +(*****************************************************************************) +(* *) +(* 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 + +module type S = sig + module Interpreter : Interpreter.S + + module PVM = Interpreter.PVM + module Fueled_pvm = Interpreter.Free_pvm + + type level_position = Start | Middle | End + + (** Type of the state for a simulation. *) + type t = { + ctxt : Context.ro; + inbox_level : Raw_level.t; + state : PVM.state; + reveal_map : string Sc_rollup.Reveal_hash.Map.t option; + nb_messages_period : int64; + nb_messages_inbox : int; + level_position : level_position; + } + + (** [start_simulation node_ctxt reveal_source block] starts a new simulation + {e on top} of [block], i.e. for an hypothetical new inbox (level). *) + val start_simulation : + Node_context.ro -> + reveal_map:string Sc_rollup.Reveal_hash.Map.t option -> + Layer1.head -> + t tzresult Lwt.t + + (** [simulate_messages node_ctxt sim messages] runs a simulation of new + [messages] in the given simulation (state) [sim] and returns a new + simulation state, the remaining fuel (when [?fuel] is provided) and the + number of ticks that happened. *) + val simulate_messages : + Node_context.ro -> + t -> + Sc_rollup.Inbox_message.t list -> + (t * Z.t) tzresult Lwt.t + + (** [end_simulation node_ctxt sim] adds and [End_of_level] message and marks + the simulation as ended. *) + val end_simulation : Node_context.ro -> t -> (t * Z.t) tzresult Lwt.t +end + +(** Functor to construct a simulator for a given PVM with interpreter. *) +module Make (Interpreter : Interpreter.S) : + S with module Interpreter = Interpreter diff --git a/src/proto_alpha/bin_sc_rollup_node/wasm_2_0_0_pvm.ml b/src/proto_alpha/bin_sc_rollup_node/wasm_2_0_0_pvm.ml index 92dde90354fc96501c2217d4e7cba6c78d99f63d..45fc6c234f82f3cf48e4ce0c2021a5a98d0963e3 100644 --- a/src/proto_alpha/bin_sc_rollup_node/wasm_2_0_0_pvm.ml +++ b/src/proto_alpha/bin_sc_rollup_node/wasm_2_0_0_pvm.ml @@ -64,7 +64,12 @@ module Make_backend (Tree : TreeS) = struct end module Impl : Pvm.S = struct - include Sc_rollup.Wasm_2_0_0PVM.Make (Make_backend) (Wasm_2_0_0_proof_format) + module PVM = + Sc_rollup.Wasm_2_0_0PVM.Make (Make_backend) (Wasm_2_0_0_proof_format) + include PVM + + let kind = Sc_rollup.Kind.of_pvm (module PVM) + module State = Context.PVMState let string_of_status : status -> string = function diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 40733c2cbc483320a86f40cfa3055e8c7bedf29f..79968e2a575d9a11e8f73f3290e42904d3416fd4 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -3290,6 +3290,8 @@ module Sc_rollup : sig val inbox_level : t -> Raw_level.t + val number_of_messages_during_commitment_period : t -> int64 + type history_proof val equal_history_proof : history_proof -> history_proof -> bool diff --git a/src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml b/src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml index 0dbbca4a226aa1247b553e9ef405de14876f03d9..f19b52b5f2bdb483379dc7bbd0d79460c049b8a3 100644 --- a/src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml +++ b/src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml @@ -48,6 +48,19 @@ open Alpha_context See below for a more detailed explanation. *) +type eval_result = { + state_hash : Sc_rollup.State_hash.t; + status : string; + output : Sc_rollup.output list; + inbox_level : Raw_level.t; + num_ticks : Z.t; +} + +type simulate_input = { + messages : string list; + reveal_pages : string list option; +} + module Encodings = struct open Data_encoding @@ -58,6 +71,47 @@ module Encodings = struct (opt "published_at_level" Raw_level.encoding) let hex_string = conv Bytes.of_string Bytes.to_string bytes + + let eval_result = + conv + (fun {state_hash; status; output; inbox_level; num_ticks} -> + (state_hash, status, output, inbox_level, num_ticks)) + (fun (state_hash, status, output, inbox_level, num_ticks) -> + {state_hash; status; output; inbox_level; num_ticks}) + @@ obj5 + (req + "state_hash" + Sc_rollup.State_hash.encoding + ~description: + "Hash of the state after execution of the PVM on the input \ + messages") + (req "status" string ~description:"Status of the PVM after evaluation") + (req + "output" + (list Sc_rollup.output_encoding) + ~description:"Output produced by evaluation of the messages") + (req + "inbox_level" + Raw_level.encoding + ~description:"Level of the inbox that would contain these messages") + (req + "num_ticks" + z + ~description:"Ticks taken by the PVM for evaluating the messages") + + let simulate_input = + conv + (fun {messages; reveal_pages} -> (messages, reveal_pages)) + (fun (messages, reveal_pages) -> {messages; reveal_pages}) + @@ obj2 + (req + "messages" + (list hex_string) + ~description:"Input messages for simulation") + (opt + "reveal_pages" + (list hex_string) + ~description:"Pages (at most 4kB) to be used for revelation ticks") end module Arg = struct @@ -303,6 +357,14 @@ module Global = struct ~output:Data_encoding.(list Sc_rollup.output_encoding) (path / "outbox") + let simulate = + Tezos_rpc.Service.post_service + ~description:"Simulate messages evaluation by the PVM" + ~query:Tezos_rpc.Query.empty + ~input:Encodings.simulate_input + ~output:Encodings.eval_result + (path / "simulate") + let dal_slots = Tezos_rpc.Service.get_service ~description:"Availability slots for a given block" diff --git a/tezt/lib_tezos/sc_rollup_client.ml b/tezt/lib_tezos/sc_rollup_client.ml index 7c76ef36a5cfce6a582b22c4fc65e1249273560e..b6d73983ef528168e527303566a7914415a671bf 100644 --- a/tezt/lib_tezos/sc_rollup_client.ml +++ b/tezt/lib_tezos/sc_rollup_client.ml @@ -23,6 +23,8 @@ (* *) (*****************************************************************************) +open Runnable.Syntax + type t = { name : string; path : string; @@ -40,6 +42,14 @@ type commitment = { type slot_header = {level : int; commitment : string; index : int} +type simulation_result = { + state_hash : string; + status : string; + output : JSON.t; + inbox_level : int; + num_ticks : int; +} + let commitment_from_json json = if JSON.is_null json then None else @@ -87,29 +97,27 @@ let endpoint_arg sc_client = ["--endpoint"; Sc_rollup_node.endpoint sc_client.sc_node] let spawn_command ?hooks sc_client command = - Process.spawn - ~name:sc_client.name - ~color:sc_client.color - ?hooks - sc_client.path - (base_dir_arg sc_client @ endpoint_arg sc_client @ command) + let process = + Process.spawn + ~name:sc_client.name + ~color:sc_client.color + ?hooks + sc_client.path + (base_dir_arg sc_client @ endpoint_arg sc_client @ command) + in + Runnable.{value = process; run = Process.check_and_read_stdout} let sc_rollup_address ?hooks sc_client = - let* out = - spawn_command ?hooks sc_client ["get"; "sc"; "rollup"; "address"] - |> Process.check_and_read_stdout - in - return (String.trim out) + spawn_command ?hooks sc_client ["get"; "sc"; "rollup"; "address"] + |> Runnable.map String.trim let state_value ?hooks ?(block = "head") sc_client ~key = - let* out = - spawn_command - ?hooks - sc_client - ["get"; "state"; "value"; "for"; key; "--block"; block] - |> Process.check_and_read_stdout - in - return (Scanf.sscanf (String.trim out) "%S" (fun s -> s) |> String.to_bytes) + spawn_command + ?hooks + sc_client + ["get"; "state"; "value"; "for"; key; "--block"; block] + |> Runnable.map @@ fun out -> + Scanf.sscanf (String.trim out) "%S" (fun s -> s) |> String.to_bytes type transaction = { destination : string; @@ -133,7 +141,7 @@ type outbox_proof = {commitment_hash : string; proof : string} let outbox_proof_batch ?hooks ?expected_error sc_client ~message_index ~outbox_level batch = - let process = + let*? process = spawn_command ?hooks sc_client @@ -175,82 +183,101 @@ let outbox_proof_single ?hooks ?expected_error ?entrypoint sc_client [{destination; entrypoint; parameters}] let rpc_get ?hooks sc_client path = - let process = - spawn_command ?hooks sc_client ["rpc"; "get"; Client.string_of_path path] - in - let* output = Process.check_and_read_stdout process in - return (JSON.parse ~origin:(Client.string_of_path path ^ " response") output) + spawn_command ?hooks sc_client ["rpc"; "get"; Client.string_of_path path] + |> Runnable.map @@ fun output -> + JSON.parse ~origin:(Client.string_of_path path ^ " response") output + +let rpc_post ?hooks sc_client path data = + spawn_command + ?hooks + sc_client + ["rpc"; "post"; Client.string_of_path path; "with"; JSON.encode data] + |> Runnable.map @@ fun output -> + JSON.parse ~origin:(Client.string_of_path path ^ " response") output let ticks ?hooks ?(block = "head") sc_client = - let open Lwt.Syntax in - let+ res = rpc_get ?hooks sc_client ["global"; "block"; block; "ticks"] in - JSON.as_int res + let res = rpc_get ?hooks sc_client ["global"; "block"; block; "ticks"] in + Runnable.map JSON.as_int res let total_ticks ?hooks ?(block = "head") sc_client = - let open Lwt.Syntax in - let+ res = - rpc_get ?hooks sc_client ["global"; "block"; block; "total_ticks"] - in - JSON.as_int res + rpc_get ?hooks sc_client ["global"; "block"; block; "total_ticks"] + |> Runnable.map JSON.as_int let state_hash ?hooks ?(block = "head") sc_client = - let open Lwt.Syntax in - let+ res = - rpc_get ?hooks sc_client ["global"; "block"; block; "state_hash"] - in - JSON.as_string res + rpc_get ?hooks sc_client ["global"; "block"; block; "state_hash"] + |> Runnable.map JSON.as_string let status ?hooks ?(block = "head") sc_client = - let open Lwt.Syntax in - let+ res = rpc_get ?hooks sc_client ["global"; "block"; block; "status"] in - JSON.as_string res + rpc_get ?hooks sc_client ["global"; "block"; block; "status"] + |> Runnable.map JSON.as_string let outbox ?hooks ?(block = "cemented") sc_client = - let open Lwt.Syntax in - let+ res = rpc_get ?hooks sc_client ["global"; "block"; block; "outbox"] in - JSON.encode res + rpc_get ?hooks sc_client ["global"; "block"; block; "outbox"] let last_stored_commitment ?hooks sc_client = - let open Lwt.Syntax in - let+ json = rpc_get ?hooks sc_client ["global"; "last_stored_commitment"] in - commitment_with_hash_and_level_from_json json + rpc_get ?hooks sc_client ["global"; "last_stored_commitment"] + |> Runnable.map commitment_with_hash_and_level_from_json let last_published_commitment ?hooks sc_client = - let open Lwt.Syntax in - let+ json = rpc_get ?hooks sc_client ["local"; "last_published_commitment"] in - commitment_with_hash_and_level_from_json json + rpc_get ?hooks sc_client ["local"; "last_published_commitment"] + |> Runnable.map commitment_with_hash_and_level_from_json let dal_slot_headers ?hooks ?(block = "head") sc_client = - let open Lwt.Syntax in - let+ json = - rpc_get ?hooks sc_client ["global"; "block"; block; "dal"; "slot_headers"] - in - JSON.( - as_list json - |> List.map (fun obj -> - { - level = obj |> get "level" |> as_int; - commitment = obj |> get "commitment" |> as_string; - index = obj |> get "index" |> as_int; - })) + rpc_get ?hooks sc_client ["global"; "block"; block; "dal"; "slot_headers"] + |> Runnable.map (fun json -> + JSON.( + as_list json + |> List.map (fun obj -> + { + level = obj |> get "level" |> as_int; + commitment = obj |> get "commitment" |> as_string; + index = obj |> get "index" |> as_int; + }))) let dal_downloaded_confirmed_slot_pages ?hooks ?(block = "head") sc_client = - let open Lwt.Syntax in - let+ json = - rpc_get - ?hooks - sc_client - ["global"; "block"; block; "dal"; "confirmed_slot_pages"] + rpc_get + ?hooks + sc_client + ["global"; "block"; block; "dal"; "confirmed_slot_pages"] + |> Runnable.map (fun json -> + JSON.as_list json + |> List.map (fun obj -> + let index = obj |> JSON.get "index" |> JSON.as_int in + let contents = + obj |> JSON.get "contents" |> JSON.as_list + |> List.map (fun page -> + page |> JSON.as_string |> fun s -> + Hex.to_string (`Hex s)) + in + (index, contents))) + +let simulate ?hooks ?(block = "head") sc_client ?(reveal_pages = []) messages = + let messages_json = + `A (List.map (fun s -> `String Hex.(of_string s |> show)) messages) + in + let reveal_json = + match reveal_pages with + | [] -> [] + | pages -> + [ + ( "reveal_pages", + `A (List.map (fun s -> `String Hex.(of_string s |> show)) pages) ); + ] in - JSON.as_list json - |> List.map (fun obj -> - let index = obj |> JSON.get "index" |> JSON.as_int in - let contents = - obj |> JSON.get "contents" |> JSON.as_list - |> List.map (fun page -> - page |> JSON.as_string |> fun s -> Hex.to_string (`Hex s)) - in - (index, contents)) + let data = + `O (("messages", messages_json) :: reveal_json) + |> JSON.annotate ~origin:"simulation data" + in + rpc_post ?hooks sc_client ["global"; "block"; block; "simulate"] data + |> Runnable.map (fun obj -> + JSON. + { + state_hash = obj |> get "state_hash" |> as_string; + status = obj |> get "status" |> as_string; + output = obj |> get "output"; + inbox_level = obj |> get "inbox_level" |> as_int; + num_ticks = obj |> get "num_ticks" |> as_string |> int_of_string; + }) let spawn_generate_keys ?hooks ?(force = false) ~alias sc_client = spawn_command @@ -259,7 +286,8 @@ let spawn_generate_keys ?hooks ?(force = false) ~alias sc_client = (["gen"; "unencrypted"; "keys"; alias] @ if force then ["--force"] else []) let generate_keys ?hooks ?force ~alias sc_client = - spawn_generate_keys ?hooks ?force ~alias sc_client |> Process.check + let*? process = spawn_generate_keys ?hooks ?force ~alias sc_client in + Process.check process let spawn_list_keys ?hooks sc_client = spawn_command ?hooks sc_client ["list"; "keys"] @@ -282,18 +310,14 @@ let parse_list_keys output = | Some l -> l let list_keys ?hooks sc_client = - let* out = - spawn_list_keys ?hooks sc_client |> Process.check_and_read_stdout - in + let*! out = spawn_list_keys ?hooks sc_client in return (parse_list_keys out) let spawn_show_address ?hooks ~alias sc_client = spawn_command ?hooks sc_client ["show"; "address"; alias] let show_address ?hooks ~alias sc_client = - let* out = - spawn_show_address ?hooks ~alias sc_client |> Process.check_and_read_stdout - in + let*! out = spawn_show_address ?hooks ~alias sc_client in return (Account.parse_client_output_aggregate ~alias ~client_output:out) let spawn_import_secret_key ?hooks ?(force = false) @@ -309,4 +333,5 @@ let spawn_import_secret_key ?hooks ?(force = false) @ if force then ["--force"] else []) let import_secret_key ?hooks ?force key sc_client = - spawn_import_secret_key ?hooks ?force key sc_client |> Process.check + let*? process = spawn_import_secret_key ?hooks ?force key sc_client in + Process.check process diff --git a/tezt/lib_tezos/sc_rollup_client.mli b/tezt/lib_tezos/sc_rollup_client.mli index eb234144053ca0255cc6be8c0a5e52a9a874955b..8d0e005dbd542707611fc6afd6b48319382f176d 100644 --- a/tezt/lib_tezos/sc_rollup_client.mli +++ b/tezt/lib_tezos/sc_rollup_client.mli @@ -35,6 +35,14 @@ type commitment = { type slot_header = {level : int; commitment : string; index : int} +type simulation_result = { + state_hash : string; + status : string; + output : JSON.t; + inbox_level : int; + num_ticks : int; +} + (** [create ?name ?path ?base_dir ?path node] returns a fresh client identified by a specified [name], logging in [color], executing the program at [path], storing local information in [base_dir], and @@ -49,35 +57,48 @@ val create : (** [sc_rollup_address client] returns the smart contract rollup address of the node associated to the [client]. *) -val sc_rollup_address : ?hooks:Process.hooks -> t -> string Lwt.t +val sc_rollup_address : ?hooks:Process.hooks -> t -> string Runnable.process (** [rpc_get client path] issues a GET request for [path]. *) -val rpc_get : ?hooks:Process.hooks -> t -> Client.path -> JSON.t Lwt.t +val rpc_get : + ?hooks:Process.hooks -> t -> Client.path -> JSON.t Runnable.process + +(** [rpc_post client path data] issues a POST request for [path] with [data]. *) +val rpc_post : + ?hooks:Process.hooks -> t -> Client.path -> JSON.t -> JSON.t Runnable.process (** [total_ticks ?block client] gets the total number of ticks for the PVM. *) -val total_ticks : ?hooks:Process.hooks -> ?block:string -> t -> int Lwt.t +val total_ticks : + ?hooks:Process.hooks -> ?block:string -> t -> int Runnable.process (** [ticks ?block client] gets the number of ticks for the PVM for the [block] (default ["head"]). *) -val ticks : ?hooks:Process.hooks -> ?block:string -> t -> int Lwt.t +val ticks : ?hooks:Process.hooks -> ?block:string -> t -> int Runnable.process (** [state_hash ?block client] gets the corresponding PVM state hash for the [block] (default ["head"]). *) -val state_hash : ?hooks:Process.hooks -> ?block:string -> t -> string Lwt.t +val state_hash : + ?hooks:Process.hooks -> ?block:string -> t -> string Runnable.process (** [state_value ?block client key] gets the corresponding PVM state value mapped to [key] for the [block] (default ["head"]). *) val state_value : - ?hooks:Process.hooks -> ?block:string -> t -> key:string -> bytes Lwt.t + ?hooks:Process.hooks -> + ?block:string -> + t -> + key:string -> + bytes Runnable.process (** [status ?block client] gets the corresponding PVM status for the [block] (default ["head"]). *) -val status : ?hooks:Process.hooks -> ?block:string -> t -> string Lwt.t +val status : + ?hooks:Process.hooks -> ?block:string -> t -> string Runnable.process (** [outbox ?block client] gets the rollup outbox for the [block] (default ["cemented"] which is the block corresponding to the last cemented level). *) -val outbox : ?hooks:Process.hooks -> ?block:string -> t -> string Lwt.t +val outbox : + ?hooks:Process.hooks -> ?block:string -> t -> JSON.t Runnable.process type outbox_proof = {commitment_hash : string; proof : string} @@ -125,22 +146,44 @@ val commitment_with_hash_and_level_from_json : (** [last_stored_commitment client] gets the last commitment with its hash stored by the rollup node. *) val last_stored_commitment : - ?hooks:Process.hooks -> t -> (string * commitment * int option) option Lwt.t + ?hooks:Process.hooks -> + t -> + (string * commitment * int option) option Runnable.process (** [last_published_commitment client] gets the last commitment published by the rollup node, with its hash and level when the commitment was first published. *) val last_published_commitment : - ?hooks:Process.hooks -> t -> (string * commitment * int option) option Lwt.t + ?hooks:Process.hooks -> + t -> + (string * commitment * int option) option Runnable.process (** [dal_slot_headers ?block client] returns the dal slot headers of the [block] (default ["head"]). *) val dal_slot_headers : - ?hooks:Process.hooks -> ?block:string -> t -> slot_header list Lwt.t + ?hooks:Process.hooks -> + ?block:string -> + t -> + slot_header list Runnable.process (** [dal_downloaded_confirmed_slot_pages ?block client] returns the confirmed slots downloaded after processing the [block] (default ["head"]). *) val dal_downloaded_confirmed_slot_pages : - ?hooks:Process.hooks -> ?block:string -> t -> (int * string list) list Lwt.t + ?hooks:Process.hooks -> + ?block:string -> + t -> + (int * string list) list Runnable.process + +(** [simulate ?block client ?reveal_pages messages] simulates the evaluation of + input [messages] for the rollup PVM at [block] (default + ["head"]). [reveal_pages] can be used to provide data to be used for the + revelation ticks. *) +val simulate : + ?hooks:Process_hooks.t -> + ?block:string -> + t -> + ?reveal_pages:string list -> + string list -> + simulation_result Runnable.process (** [generate_keys ~alias client] generates new unencrypted keys for [alias]. *) val generate_keys : diff --git a/tezt/long_tests/sc_rollup.ml b/tezt/long_tests/sc_rollup.ml index 1a89c281bd9fa7222b052eb83f45339b2e459fda..5a619152e60a7ce6650afc9e2ffea782f70d909f 100644 --- a/tezt/long_tests/sc_rollup.ml +++ b/tezt/long_tests/sc_rollup.ml @@ -192,10 +192,10 @@ let test_rollup_node_advances_pvm_state protocols ~test_name ~boot_sector in (* Called with monotonically increasing [i] *) let test_message i = - let* prev_state_hash = + let*! prev_state_hash = Sc_rollup_client.state_hash ~hooks sc_rollup_client in - let* prev_ticks = Sc_rollup_client.total_ticks ~hooks sc_rollup_client in + let*! prev_ticks = Sc_rollup_client.total_ticks ~hooks sc_rollup_client in let message = sf "%d %d + value" i ((i + 2) * 2) in let* () = match forwarder with @@ -220,7 +220,7 @@ let test_rollup_node_advances_pvm_state protocols ~test_name ~boot_sector let* () = match kind with | "arith" -> - let* encoded_value = + let*! encoded_value = Sc_rollup_client.state_value ~hooks sc_rollup_client @@ -254,12 +254,12 @@ let test_rollup_node_advances_pvm_state protocols ~test_name ~boot_sector | _otherwise -> raise (Invalid_argument kind) in - let* state_hash = Sc_rollup_client.state_hash ~hooks sc_rollup_client in + let*! state_hash = Sc_rollup_client.state_hash ~hooks sc_rollup_client in Check.(state_hash <> prev_state_hash) Check.string ~error_msg:"State hash has not changed (%L <> %R)" ; - let* ticks = Sc_rollup_client.total_ticks ~hooks sc_rollup_client in + let*! ticks = Sc_rollup_client.total_ticks ~hooks sc_rollup_client in Check.(ticks >= prev_ticks) Check.int ~error_msg:"Tick counter did not advance (%L >= %R)" ; diff --git a/tezt/tests/dal.ml b/tezt/tests/dal.ml index ad20b9c3da5f3b93946e2849405c16ae1b691be8..847eb04695ecfc2d47ebf8b8978f7c344dcc2bd6 100644 --- a/tezt/tests/dal.ml +++ b/tezt/tests/dal.ml @@ -977,7 +977,7 @@ let rollup_node_stores_dal_slots ?expand_test _protocol dal_node sc_rollup_node let* slots_published_level = Sc_rollup_node.wait_for_level sc_rollup_node (init_level + 1) in - let* slots_headers = + let*! slots_headers = Sc_rollup_client.dal_slot_headers ~hooks sc_rollup_client in let commitments = @@ -1016,7 +1016,7 @@ let rollup_node_stores_dal_slots ?expand_test _protocol dal_node sc_rollup_node "Current level has moved past slot attestation level (current = %L, \ expected = %R)" ; (* 7. Check that no slots have been downloaded *) - let* downloaded_confirmed_slots = + let*! downloaded_confirmed_slots = Sc_rollup_client.dal_downloaded_confirmed_slot_pages ~hooks sc_rollup_client in let expected_number_of_downloaded_or_unconfirmed_slots = 0 in @@ -1047,7 +1047,7 @@ let rollup_node_stores_dal_slots ?expand_test _protocol dal_node sc_rollup_node expected = %R)" ; (* 9. Wait for the rollup node to download the attested slots. *) let confirmed_level_as_string = Int.to_string slot_confirmed_level in - let* downloaded_confirmed_slots = + let*! downloaded_confirmed_slots = Sc_rollup_client.dal_downloaded_confirmed_slot_pages ~block:confirmed_level_as_string sc_rollup_client @@ -1145,7 +1145,7 @@ let rollup_node_interprets_dal_pages client sc_rollup sc_rollup_node = let* _lvl = Sc_rollup_node.wait_for_level ~timeout:120. sc_rollup_node (level + 1) in - let* encoded_value = + let*! encoded_value = Sc_rollup_client.state_value ~hooks sc_rollup_client ~key:"vars/value" in match Data_encoding.(Binary.of_bytes int31) @@ encoded_value with diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index 2bf5f087b20631788ee7c8ef3eb10d4beac35b20..1333be547b2347f0835c2a3e53d4dcfbd8c25864 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -156,10 +156,14 @@ let register_test ?(regression = false) ~__FILE__ ~tags ~title f = if regression then Protocol.register_regression_test ~__FILE__ ~title ~tags f else Protocol.register_test ~__FILE__ ~title ~tags f -let setup_l1 ?commitment_period ?challenge_window ?timeout protocol = +let setup_l1 ?commitment_period ?challenge_window + ?max_number_of_messages_per_commitment_period ?timeout protocol = let parameters = make_parameter "sc_rollup_commitment_period_in_blocks" commitment_period @ make_parameter "sc_rollup_challenge_window_in_blocks" challenge_window + @ make_parameter + "sc_rollup_max_number_of_messages_per_commitment_period" + max_number_of_messages_per_commitment_period @ make_parameter "sc_rollup_timeout_period_in_blocks" timeout @ [(["sc_rollup_enable"], `Bool true)] in @@ -250,7 +254,8 @@ let test_l1_scenario ?regression ~kind ?boot_sector ?commitment_period scenario sc_rollup tezos_node tezos_client let test_full_scenario ?regression ~kind ?boot_sector ?commitment_period - ?challenge_window ?timeout {variant; tags; description} scenario = + ?challenge_window ?timeout ?max_number_of_messages_per_commitment_period + {variant; tags; description} scenario = let tags = kind :: "rollup_node" :: tags in register_test ?regression @@ -266,7 +271,12 @@ let test_full_scenario ?regression ~kind ?boot_sector ?commitment_period | None -> "")) @@ fun protocol -> let* tezos_node, tezos_client = - setup_l1 ?commitment_period ?challenge_window ?timeout protocol + setup_l1 + ?commitment_period + ?challenge_window + ?timeout + ?max_number_of_messages_per_commitment_period + protocol in let* rollup_node, rollup_client, sc_rollup = setup_rollup ~kind ?boot_sector tezos_node tezos_client @@ -421,7 +431,7 @@ let test_rollup_node_running ~kind = failwith "Please install curl" | Some sc_rollup_from_rpc -> JSON.as_string sc_rollup_from_rpc in - let* sc_rollup_from_client = + let*! sc_rollup_from_client = Sc_rollup_client.sc_rollup_address ~hooks rollup_client in if sc_rollup_from_rpc <> sc_rollup then @@ -656,11 +666,14 @@ let fetch_messages_from_block client = tree which must have the same root hash as the one stored by the protocol in the context. *) -let test_rollup_inbox_of_rollup_node ~variant scenario ~kind = +let test_rollup_inbox_of_rollup_node + ?max_number_of_messages_per_commitment_period ?(extra_tags = []) ~variant + scenario ~kind = test_full_scenario + ?max_number_of_messages_per_commitment_period { variant = Some variant; - tags = ["inbox"]; + tags = ["inbox"] @ extra_tags; description = "maintenance of inbox in the rollup node"; } ~kind @@ -787,6 +800,47 @@ let sc_rollup_node_handles_chain_reorg sc_rollup_node _rollup_client _sc_rollup let* _ = Sc_rollup_node.wait_for_level ~timeout:3. sc_rollup_node 5 in unit +(* Simulation of messages *) +let sc_rollup_node_simulate sc_rollup_node sc_rollup_client _sc_rollup node + client = + let level = Node.get_level node in + let* () = Sc_rollup_node.run sc_rollup_node [] in + let msg1 = "3 3 + out" in + let*! sim_result = + Sc_rollup_client.simulate + sc_rollup_client + ~reveal_pages: + (List.init 12 (fun j -> + String.init 1024 (fun i -> Char.chr (i * j mod 256)))) + [msg1] + in + let* () = send_message client (to_text_messages_arg [msg1]) in + let* _ = + Sc_rollup_node.wait_for_level ~timeout:3. sc_rollup_node (level + 1) + in + let*! real_state_hash = Sc_rollup_client.state_hash sc_rollup_client in + let*! real_outbox = Sc_rollup_client.outbox sc_rollup_client ~block:"head" in + Check.((sim_result.state_hash = real_state_hash) string) + ~error_msg:"Simulated resulting state hash is %L but should have been %R" ; + Check.((JSON.encode sim_result.output = JSON.encode real_outbox) string) + ~error_msg:"Simulated resulting outbox is %L but should have been %R" ; + let*? sim_result = Sc_rollup_client.simulate sc_rollup_client [] in + let* () = + Process.check_error ~msg:(rex "Tried to add zero messages") sim_result + in + let* constants = get_sc_rollup_constants client in + let too_many_msgs = + List.init + constants.max_number_of_messages_per_commitment_period + (Fun.const "") + in + (* In the context of "head", there is already one message in the inbox, so if + we add max messages, we'll be over the limit. *) + let*? sim_result = Sc_rollup_client.simulate sc_rollup_client too_many_msgs in + Process.check_error + ~msg:(rex "Maximum number of messages reached for commitment period") + sim_result + (* One can retrieve the list of originated SCORUs. ----------------------------------------------- *) @@ -850,12 +904,12 @@ let test_rollup_node_boots_into_initial_state ~kind = Check.int ~error_msg:"Current level has moved past origination level (%L = %R)" ; - let* ticks = Sc_rollup_client.total_ticks ~hooks sc_rollup_client in + let*! ticks = Sc_rollup_client.total_ticks ~hooks sc_rollup_client in Check.(ticks = 0) Check.int ~error_msg:"Unexpected initial tick count (%L = %R)" ; - let* status = Sc_rollup_client.status ~hooks sc_rollup_client in + let*! status = Sc_rollup_client.status ~hooks sc_rollup_client in let expected_status = match kind with | "arith" -> "Halted" @@ -913,10 +967,10 @@ let test_rollup_node_advances_pvm_state ?regression ~title ?boot_sector in (* Called with monotonically increasing [i] *) let test_message i = - let* prev_state_hash = + let*! prev_state_hash = Sc_rollup_client.state_hash ~hooks sc_rollup_client in - let* prev_ticks = Sc_rollup_client.total_ticks ~hooks sc_rollup_client in + let*! prev_ticks = Sc_rollup_client.total_ticks ~hooks sc_rollup_client in let message = sf "%d %d + value" i ((i + 2) * 2) in let* () = match forwarder with @@ -943,7 +997,7 @@ let test_rollup_node_advances_pvm_state ?regression ~title ?boot_sector let* () = match kind with | "arith" -> - let* encoded_value = + let*! encoded_value = Sc_rollup_client.state_value ~hooks sc_rollup_client @@ -977,12 +1031,12 @@ let test_rollup_node_advances_pvm_state ?regression ~title ?boot_sector | _otherwise -> raise (Invalid_argument kind) in - let* state_hash = Sc_rollup_client.state_hash ~hooks sc_rollup_client in + let*! state_hash = Sc_rollup_client.state_hash ~hooks sc_rollup_client in Check.(state_hash <> prev_state_hash) Check.string ~error_msg:"State hash has not changed (%L <> %R)" ; - let* ticks = Sc_rollup_client.total_ticks ~hooks sc_rollup_client in + let*! ticks = Sc_rollup_client.total_ticks ~hooks sc_rollup_client in Check.(ticks >= prev_ticks) Check.int ~error_msg:"Tick counter did not advance (%L >= %R)" ; @@ -1161,7 +1215,7 @@ let commitment_stored sc_rollup_node sc_rollup_client sc_rollup _node client = sc_rollup_node store_commitment_level in - let* stored_commitment = + let*! stored_commitment = Sc_rollup_client.last_stored_commitment ~hooks sc_rollup_client in let stored_inbox_level = Option.map inbox_level stored_commitment in @@ -1171,7 +1225,7 @@ let commitment_stored sc_rollup_node sc_rollup_client sc_rollup _node client = "Commitment has been stored at a level different than expected (%L = %R)" ; (* Bake one level for commitment to be included *) let* () = Client.bake_for_and_wait client in - let* published_commitment = + let*! published_commitment = Sc_rollup_client.last_published_commitment ~hooks sc_rollup_client in check_commitment_eq @@ -1228,17 +1282,19 @@ let mode_publish mode publishes sc_rollup_node sc_rollup_client sc_rollup node let* _ = Sc_rollup_node.wait_for_level sc_rollup_node level and* _ = Sc_rollup_node.wait_for_level sc_rollup_other_node level in Log.info "Both rollup nodes have reached level %d." level ; - let* state_hash = Sc_rollup_client.state_hash ~hooks sc_rollup_client - and* state_hash_other = + let state_hash = Sc_rollup_client.state_hash ~hooks sc_rollup_client + and state_hash_other = Sc_rollup_client.state_hash ~hooks sc_rollup_other_client in + let*! state_hash = state_hash in + let*! state_hash_other = state_hash_other in Check.((state_hash = state_hash_other) string) ~error_msg: "State hash of other rollup node is %R but the first rollup node has %L" ; - let* published_commitment = + let*! published_commitment = Sc_rollup_client.last_published_commitment ~hooks sc_rollup_client in - let* other_published_commitment = + let*! other_published_commitment = Sc_rollup_client.last_published_commitment ~hooks sc_rollup_other_client in if published_commitment = None then @@ -1294,7 +1350,7 @@ let commitment_not_stored_if_non_final sc_rollup_node sc_rollup_client sc_rollup sc_rollup_node (store_commitment_level + levels_to_finalize) in - let* commitment = + let*! commitment = Sc_rollup_client.last_stored_commitment ~hooks sc_rollup_client in let stored_inbox_level = Option.map inbox_level commitment in @@ -1302,7 +1358,7 @@ let commitment_not_stored_if_non_final sc_rollup_node sc_rollup_client sc_rollup (Check.option Check.int) ~error_msg: "Commitment has been stored at a level different than expected (%L = %R)" ; - let* commitment = + let*! commitment = Sc_rollup_client.last_published_commitment ~hooks sc_rollup_client in let published_inbox_level = Option.map inbox_level commitment in @@ -1363,7 +1419,7 @@ let commitments_messages_reset kind sc_rollup_node sc_rollup_client sc_rollup sc_rollup_node (init_level + (2 * levels_to_commitment) + block_finality_time) in - let* stored_commitment = + let*! stored_commitment = Sc_rollup_client.last_stored_commitment ~hooks sc_rollup_client in let stored_inbox_level = Option.map inbox_level stored_commitment in @@ -1386,7 +1442,7 @@ let commitments_messages_reset kind sc_rollup_node sc_rollup_client sc_rollup ~error_msg: "Number of ticks processed by commitment is different from the number \ of ticks expected (%L = %R)") ; - let* published_commitment = + let*! published_commitment = Sc_rollup_client.last_published_commitment ~hooks sc_rollup_client in check_commitment_eq @@ -1468,10 +1524,10 @@ let commitment_stored_robust_to_failures sc_rollup_node sc_rollup_client sc_rollup_node' level_commitment_is_stored in - let* stored_commitment = + let*! stored_commitment = Sc_rollup_client.last_stored_commitment ~hooks sc_rollup_client in - let* stored_commitment' = + let*! stored_commitment' = Sc_rollup_client.last_stored_commitment ~hooks sc_rollup_client' in check_commitment_eq @@ -1578,7 +1634,7 @@ let commitments_reorgs ~kind sc_rollup_node sc_rollup_client sc_rollup node sc_rollup_node (init_level + levels_to_commitment + block_finality_time) in - let* stored_commitment = + let*! stored_commitment = Sc_rollup_client.last_stored_commitment ~hooks sc_rollup_client in let stored_inbox_level = Option.map inbox_level stored_commitment in @@ -1609,7 +1665,7 @@ let commitments_reorgs ~kind sc_rollup_node sc_rollup_client sc_rollup node ~error_msg: "Number of ticks processed by commitment is different from the number \ of ticks expected (%L = %R)") ; - let* published_commitment = + let*! published_commitment = Sc_rollup_client.last_published_commitment ~hooks sc_rollup_client in check_commitment_eq @@ -1715,10 +1771,10 @@ let commitment_before_lcc_not_published sc_rollup_node sc_rollup_client sc_rollup_node (commitment_inbox_level + block_finality_time) in - let* rollup_node1_stored_commitment = + let*! rollup_node1_stored_commitment = Sc_rollup_client.last_stored_commitment ~hooks sc_rollup_client in - let* rollup_node1_published_commitment = + let*! rollup_node1_published_commitment = Sc_rollup_client.last_published_commitment ~hooks sc_rollup_client in let () = @@ -1795,7 +1851,7 @@ let commitment_before_lcc_not_published sc_rollup_node sc_rollup_client Check.int ~error_msg:"Current level has moved past cementation inbox level (%L = %R)" ; (* Check that no commitment was published. *) - let* rollup_node2_last_published_commitment = + let*! rollup_node2_last_published_commitment = Sc_rollup_client.last_published_commitment ~hooks sc_rollup_client' in let rollup_node2_last_published_commitment_inbox_level = @@ -1810,7 +1866,7 @@ let commitment_before_lcc_not_published sc_rollup_node sc_rollup_client in (* Check that the commitment stored by the second rollup node is the same commmitment stored by the first rollup node. *) - let* rollup_node2_stored_commitment = + let*! rollup_node2_stored_commitment = Sc_rollup_client.last_stored_commitment ~hooks sc_rollup_client' in let () = @@ -1832,7 +1888,7 @@ let commitment_before_lcc_not_published sc_rollup_node sc_rollup_client sc_rollup_node' (level_after_cementation + commitment_period) in - let* rollup_node2_last_published_commitment = + let*! rollup_node2_last_published_commitment = Sc_rollup_client.last_published_commitment ~hooks sc_rollup_client' in let rollup_node2_last_published_commitment_inbox_level = @@ -1893,7 +1949,7 @@ let first_published_level_is_global sc_rollup_node sc_rollup_client sc_rollup sc_rollup_node (commitment_inbox_level + block_finality_time) in - let* rollup_node1_published_commitment = + let*! rollup_node1_published_commitment = Sc_rollup_client.last_published_commitment ~hooks sc_rollup_client in Check.( @@ -1908,7 +1964,7 @@ let first_published_level_is_global sc_rollup_node sc_rollup_client sc_rollup let* commitment_publish_level = Sc_rollup_node.wait_for_level sc_rollup_node (commitment_finalized_level + 1) in - let* rollup_node1_published_commitment = + let*! rollup_node1_published_commitment = Sc_rollup_client.last_published_commitment ~hooks sc_rollup_client in Check.( @@ -1941,7 +1997,7 @@ let first_published_level_is_global sc_rollup_node sc_rollup_client sc_rollup Check.int ~error_msg:"Current level has moved past cementation inbox level (%L = %R)" ; (* Check that no commitment was published. *) - let* rollup_node2_published_commitment = + let*! rollup_node2_published_commitment = Sc_rollup_client.last_published_commitment ~hooks sc_rollup_client' in check_commitment_eq @@ -2077,7 +2133,7 @@ let test_rollup_origination_boot_sector ~boot_sector ~kind = let init_hash = JSON.(init_commitment |-> "compressed_state" |> as_string) in let* () = Sc_rollup_node.run rollup_node [] in let* _ = Sc_rollup_node.wait_for_level ~timeout:3. rollup_node init_level in - let* node_state_hash = Sc_rollup_client.state_hash ~hooks rollup_client in + let*! node_state_hash = Sc_rollup_client.state_hash ~hooks rollup_client in Check.( (init_hash = node_state_hash) string @@ -2179,7 +2235,7 @@ let test_rollup_arith_uses_reveals ~kind = Sc_rollup_node.wait_for_level ~timeout:120. sc_rollup_node (level + 1) in - let* encoded_value = + let*! encoded_value = Sc_rollup_client.state_value ~hooks sc_rollup_client ~key:"vars/value" in let value = @@ -2443,7 +2499,7 @@ let test_refutation_scenario ?commitment_period ?challenge_window ~variant ~kind ~error_msg:"expecting loss for dishonest participant = %R, got %L") ; Log.info "Checking that we can still retrieve state from rollup node" ; (* This is a way to make sure the rollup node did not crash *) - let* _value = Sc_rollup_client.state_hash ~hooks sc_client1 in + let*! _value = Sc_rollup_client.state_hash ~hooks sc_client1 in unit let rec swap i l = @@ -3050,8 +3106,8 @@ let test_outbox_message_generic ?regression ?expected_error ~skip ~earliness repeat blocks_to_wait @@ fun () -> Client.bake_for client in let trigger_outbox_message_execution address = - let* outbox = Sc_rollup_client.outbox sc_client in - Log.info "Outbox is %s" outbox ; + let*! outbox = Sc_rollup_client.outbox sc_client in + Log.info "Outbox is %s" (JSON.encode outbox) ; let* answer = let message_index = 0 in let outbox_level = 4 in @@ -3137,7 +3193,7 @@ let test_rpcs ~kind = } @@ fun sc_rollup_node sc_client sc_rollup node client -> let* () = Sc_rollup_node.run sc_rollup_node [] in - let* sc_rollup_address = + let*! sc_rollup_address = Sc_rollup_client.rpc_get ~hooks sc_client ["global"; "sc_rollup_address"] in let sc_rollup_address = JSON.as_string sc_rollup_address in @@ -3151,7 +3207,7 @@ let test_rpcs ~kind = Sc_rollup_node.wait_for_level ~timeout:3.0 sc_rollup_node (level + n) in let* l1_block_hash = RPC.Client.call client @@ RPC.get_chain_block_hash () in - let* l2_block_hash = + let*! l2_block_hash = Sc_rollup_client.rpc_get ~hooks sc_client @@ -3163,13 +3219,13 @@ let test_rpcs ~kind = let* l1_block_hash = RPC.Client.call client @@ RPC.get_chain_block_hash ~block:"5" () in - let* l2_block_hash = + let*! l2_block_hash = Sc_rollup_client.rpc_get ~hooks sc_client ["global"; "block"; "5"; "hash"] in let l2_block_hash = JSON.as_string l2_block_hash in Check.((l1_block_hash = l2_block_hash) string) ~error_msg:"Block 5 on L1 is %L where as on L2 it is %R" ; - let* l2_finalied_block_level = + let*! l2_finalied_block_level = Sc_rollup_client.rpc_get ~hooks sc_client @@ -3178,7 +3234,7 @@ let test_rpcs ~kind = let l2_finalied_block_level = JSON.as_int l2_finalied_block_level in Check.((l2_finalied_block_level = level + n - 2) int) ~error_msg:"Finalized block is %L but should be %R" ; - let* l2_num_messages = + let*! l2_num_messages = Sc_rollup_client.rpc_get ~hooks sc_client @@ -3187,34 +3243,34 @@ let test_rpcs ~kind = let l2_num_messages = JSON.as_int l2_num_messages in Check.((l2_num_messages = batch_size + 2) int) ~error_msg:"Number of messages of head is %L but should be %R" ; - let* _status = + let*! _status = Sc_rollup_client.rpc_get ~hooks sc_client ["global"; "block"; "head"; "status"] in - let* _ticks = + let*! _ticks = Sc_rollup_client.rpc_get ~hooks sc_client ["global"; "block"; "head"; "ticks"] in - let* _state_hash = + let*! _state_hash = Sc_rollup_client.rpc_get ~hooks sc_client ["global"; "block"; "head"; "state_hash"] in - let* _outbox = + let*! _outbox = Sc_rollup_client.rpc_get ~hooks sc_client ["global"; "block"; "head"; "outbox"] in - let* _head = + let*! _head = Sc_rollup_client.rpc_get ~hooks sc_client ["global"; "tezos_head"] in - let* _level = + let*! _level = Sc_rollup_client.rpc_get ~hooks sc_client ["global"; "tezos_level"] in unit @@ -3382,6 +3438,13 @@ let register ~protocols = ~boot_sector2:"31" ~kind:"arith" protocols ; + test_rollup_inbox_of_rollup_node + ~kind:"arith" + ~variant:"simulation" + ~extra_tags:["simulation"] + ~max_number_of_messages_per_commitment_period:300 + sc_rollup_node_simulate + protocols ; (* Specific Wasm PVM tezts *) test_rollup_node_run_with_kernel protocols