From d8167723f865adbb816d51f14e84b2c44aa80e26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Kr=C3=BCger?= Date: Tue, 29 Mar 2022 16:22:28 +0100 Subject: [PATCH 1/3] SCORU: Extend client for testing needs --- .../bin_sc_rollup_client/commands.ml | 55 ++++++++++++++++++- tezt/lib_tezos/sc_rollup_client.ml | 27 +++++++++ tezt/lib_tezos/sc_rollup_client.mli | 15 +++++ 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/bin_sc_rollup_client/commands.ml b/src/proto_alpha/bin_sc_rollup_client/commands.ml index a1b63e5bad98..769407d091be 100644 --- a/src/proto_alpha/bin_sc_rollup_client/commands.ml +++ b/src/proto_alpha/bin_sc_rollup_client/commands.ml @@ -36,4 +36,57 @@ let get_sc_rollup_addresses_command () = RPC.get_sc_rollup_addresses_command cctxt >>=? fun addr -> cctxt#message "@[%a@]" Sc_rollup.Address.pp addr >>= fun () -> return_unit) -let all () = [get_sc_rollup_addresses_command ()] +(** [display_answer cctxt answer] will print an RPC answer to the screen. *) +let display_answer (cctxt : #Configuration.sc_client_context) : + RPC_context.generic_call_result -> unit Lwt.t = function + | `Json (`Ok json) -> cctxt#answer "%a" Json_repr.(pp (module Ezjsonm)) json + | `Binary (`Ok binary) -> cctxt#answer "%a" Hex.pp (Hex.of_string binary) + | `Json (`Error (Some error)) -> + cctxt#error + "@[Command failed: @[%a@]@]@." + (Format.pp_print_list Error_monad.pp) + (Data_encoding.Json.destruct + (Data_encoding.list Error_monad.error_encoding) + error) + | `Binary (`Error (Some error)) -> ( + match Data_encoding.Binary.of_string Error_monad.trace_encoding error with + | Ok trace -> + cctxt#error + "@[Command failed: @[%a@]@]@." + Error_monad.pp_print_trace + trace + | Error msg -> + cctxt#error + "@[Error whilst decoding the server response: @[%a@]@]@." + Data_encoding.Binary.pp_read_error + msg) + | `Json (`Not_found _) | `Binary (`Not_found _) | `Other (_, `Not_found _) -> + cctxt#error "No service found at this URL\n%!" + | `Json (`Gone _) | `Binary (`Gone _) | `Other (_, `Gone _) -> + cctxt#error + "Requested data concerns a pruned block and target resource is no \ + longer available\n\ + %!" + | `Json (`Unauthorized _) + | `Binary (`Unauthorized _) + | `Other (_, `Unauthorized _) -> + cctxt#error "@[[HTTP 403] Access denied to: %a@]@." Uri.pp cctxt#base + | _ -> cctxt#error "Unexpected server answer\n%!" + +(** [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 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*! () = display_answer cctxt answer in + return_unit + +let rpc_get = + command + ~desc:"Call an RPC with the GET method." + no_options + (prefixes ["rpc"; "get"] @@ string ~name:"url" ~desc:"the RPC URL" @@ stop) + (fun () url cctxt -> call_get cctxt url) + +let all () = [get_sc_rollup_addresses_command (); rpc_get] diff --git a/tezt/lib_tezos/sc_rollup_client.ml b/tezt/lib_tezos/sc_rollup_client.ml index d207ce81d688..5106868e4900 100644 --- a/tezt/lib_tezos/sc_rollup_client.ml +++ b/tezt/lib_tezos/sc_rollup_client.ml @@ -69,3 +69,30 @@ let sc_rollup_address sc_client = |> Process.check_and_read_stdout in return (String.trim out) + +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) + +let ticks ?hooks sc_client = + let open Lwt.Syntax in + let+ res = rpc_get ?hooks sc_client ["ticks"] in + JSON.as_int res + +let total_ticks ?hooks sc_client = + let open Lwt.Syntax in + let+ res = rpc_get ?hooks sc_client ["total_ticks"] in + JSON.as_int res + +let state_hash ?hooks sc_client = + let open Lwt.Syntax in + let+ res = rpc_get ?hooks sc_client ["state_hash"] in + JSON.as_string res + +let status ?hooks sc_client = + let open Lwt.Syntax in + let+ res = rpc_get ?hooks sc_client ["status"] in + JSON.as_string res diff --git a/tezt/lib_tezos/sc_rollup_client.mli b/tezt/lib_tezos/sc_rollup_client.mli index 82fc69210541..f274a9275e59 100644 --- a/tezt/lib_tezos/sc_rollup_client.mli +++ b/tezt/lib_tezos/sc_rollup_client.mli @@ -41,3 +41,18 @@ val create : (** [sc_rollup_address client] returns the smart contract rollup address of the node associated to the [client]. *) val sc_rollup_address : t -> string Lwt.t + +(** [rpc_get client path] issues a GET request for [path]. *) +val rpc_get : ?hooks:Process.hooks -> t -> Client.path -> JSON.t Lwt.t + +(** [total_ticks client] gets the total number of ticks for the PVM. *) +val total_ticks : ?hooks:Process.hooks -> t -> int Lwt.t + +(** [ticks client] gets the number of ticks for the PVM for the current head. *) +val ticks : ?hooks:Process.hooks -> t -> int Lwt.t + +(** [state_hash client] gets the corresponding PVM state hash for the current head block. *) +val state_hash : ?hooks:Process.hooks -> t -> string Lwt.t + +(** [status client] gets the corresponding PVM status for the current head block. *) +val status : ?hooks:Process.hooks -> t -> string Lwt.t -- GitLab From 162e57c0d0e7ba9de67b80dd04882a1743fdfcb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Kr=C3=BCger?= Date: Wed, 23 Mar 2022 11:23:19 +0000 Subject: [PATCH 2/3] Proto: Expose things from the protocol that are needed by SCORU nodes --- src/proto_alpha/lib_protocol/alpha_context.mli | 2 ++ src/proto_alpha/lib_protocol/sc_rollup_arith.mli | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 51b05993ce52..ae57df3823b1 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -2493,6 +2493,8 @@ module Sc_rollup : sig val empty : Address.t -> Raw_level.t -> t + val inbox_level : t -> Raw_level.t + val number_of_available_messages : t -> Z.t val consume_n_messages : int -> t -> t option tzresult diff --git a/src/proto_alpha/lib_protocol/sc_rollup_arith.mli b/src/proto_alpha/lib_protocol/sc_rollup_arith.mli index cb3fc4bfbdea..ec7706492a39 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_arith.mli +++ b/src/proto_alpha/lib_protocol/sc_rollup_arith.mli @@ -138,4 +138,5 @@ module type P = sig Lwt.t end -module Make (Context : P) : S with type context = Context.Tree.t +module Make (Context : P) : + S with type context = Context.Tree.t and type state = Context.tree -- GitLab From 771e44e24f53bff6f154091143bdb5be6f47dd99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Kr=C3=BCger?= Date: Wed, 30 Mar 2022 10:52:55 +0100 Subject: [PATCH 3/3] SCORU: Add interpreter for arithmetic PVM --- .../bin_sc_rollup_client/commands.ml | 6 +- .../bin_sc_rollup_node/RPC_server.ml | 184 ++++-- .../bin_sc_rollup_node/arith_pvm.ml | 74 +++ src/proto_alpha/bin_sc_rollup_node/daemon.ml | 6 +- .../bin_sc_rollup_node/interpreter.ml | 157 +++++ .../bin_sc_rollup_node/interpreter_event.ml | 50 ++ src/proto_alpha/bin_sc_rollup_node/pvm.ml | 45 ++ src/proto_alpha/bin_sc_rollup_node/store.ml | 94 ++- .../lib_sc_rollup/sc_rollup_services.ml | 35 ++ .../sc_rollup_node_advances_pvm_state.out | 543 ++++++++++++++++++ ...c_rollup_node_boots_into_initial_state.out | 40 ++ tezt/tests/sc_rollup.ml | 152 ++++- 12 files changed, 1304 insertions(+), 82 deletions(-) create mode 100644 src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml create mode 100644 src/proto_alpha/bin_sc_rollup_node/interpreter.ml create mode 100644 src/proto_alpha/bin_sc_rollup_node/interpreter_event.ml create mode 100644 src/proto_alpha/bin_sc_rollup_node/pvm.ml create mode 100644 tezt/_regressions/sc_rollup_node_advances_pvm_state.out create mode 100644 tezt/_regressions/sc_rollup_node_boots_into_initial_state.out diff --git a/src/proto_alpha/bin_sc_rollup_client/commands.ml b/src/proto_alpha/bin_sc_rollup_client/commands.ml index 769407d091be..3f8b05d243e8 100644 --- a/src/proto_alpha/bin_sc_rollup_client/commands.ml +++ b/src/proto_alpha/bin_sc_rollup_client/commands.ml @@ -36,7 +36,7 @@ let get_sc_rollup_addresses_command () = RPC.get_sc_rollup_addresses_command cctxt >>=? fun addr -> cctxt#message "@[%a@]" Sc_rollup.Address.pp addr >>= fun () -> return_unit) -(** [display_answer cctxt answer] will print an RPC answer to the screen. *) +(** [display_answer cctxt answer] prints an RPC answer. *) let display_answer (cctxt : #Configuration.sc_client_context) : RPC_context.generic_call_result -> unit Lwt.t = function | `Json (`Ok json) -> cctxt#answer "%a" Json_repr.(pp (module Ezjsonm)) json @@ -82,11 +82,11 @@ let call_get (cctxt : #Configuration.sc_client_context) raw_url = let*! () = display_answer cctxt answer in return_unit -let rpc_get = +let rpc_get_command = command ~desc:"Call an RPC with the GET method." no_options (prefixes ["rpc"; "get"] @@ string ~name:"url" ~desc:"the RPC URL" @@ stop) (fun () url cctxt -> call_get cctxt url) -let all () = [get_sc_rollup_addresses_command (); rpc_get] +let all () = [get_sc_rollup_addresses_command (); rpc_get_command] 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 2c5c881e2b20..d22c873edb53 100644 --- a/src/proto_alpha/bin_sc_rollup_node/RPC_server.ml +++ b/src/proto_alpha/bin_sc_rollup_node/RPC_server.ml @@ -27,55 +27,135 @@ open Tezos_rpc open Tezos_rpc_http open Tezos_rpc_http_server -let register_sc_rollup_address configuration dir = - RPC_directory.register0 - dir - (Sc_rollup_services.sc_rollup_address ()) - (fun () () -> return @@ configuration.Configuration.sc_rollup_address) - -let register_current_tezos_head store dir = - RPC_directory.register0 - dir - (Sc_rollup_services.current_tezos_head ()) - (fun () () -> Layer1.current_head_hash store >>= return) - -let register_current_tezos_level store dir = - RPC_directory.register0 - dir - (Sc_rollup_services.current_tezos_level ()) - (fun () () -> Layer1.current_level store >>= return) - -let register_current_inbox store dir = - RPC_directory.opt_register0 - dir - (Sc_rollup_services.current_inbox ()) - (fun () () -> - Layer1.current_head_hash store >>= function - | Some head_hash -> Inbox.inbox_of_hash store head_hash >>= return_some - | None -> return None) - -let register store configuration = - RPC_directory.empty - |> register_sc_rollup_address configuration - |> register_current_tezos_head store - |> register_current_inbox store - -let start store configuration = - let Configuration.{rpc_addr; rpc_port; _} = configuration in - let rpc_addr = P2p_addr.of_string_exn rpc_addr in - let host = Ipaddr.V6.to_string rpc_addr in - let dir = register store configuration in - let node = `TCP (`Port rpc_port) in - let acl = RPC_server.Acl.default rpc_addr in - Lwt.catch - (fun () -> - RPC_server.launch - ~media_types:Media_type.all_media_types - ~host - ~acl - node - dir - >>= return) - fail_with_exn - -let shutdown = RPC_server.shutdown +let get_head_exn store = + let open Lwt_tzresult_syntax in + let*! head = Layer1.current_head_hash store in + match head with None -> failwith "No head" | Some head -> return head + +let get_state_exn store = + let open Lwt_tzresult_syntax in + let* head = get_head_exn store in + let*! state = Store.PVMState.find store head in + match state with None -> failwith "No state" | Some state -> return state + +let get_state_info_exn store = + let open Lwt_tzresult_syntax in + let* head = get_head_exn store in + let*! state = Store.StateInfo.get store head in + return state + +module Common = struct + let register_current_num_messages store dir = + RPC_directory.register0 + dir + (Sc_rollup_services.current_num_messages ()) + (fun () () -> + let open Lwt_tzresult_syntax in + let* state_info = get_state_info_exn store in + return state_info.num_messages) + + let register_sc_rollup_address configuration dir = + RPC_directory.register0 + dir + (Sc_rollup_services.sc_rollup_address ()) + (fun () () -> return @@ configuration.Configuration.sc_rollup_address) + + let register_current_tezos_head store dir = + RPC_directory.register0 + dir + (Sc_rollup_services.current_tezos_head ()) + (fun () () -> Layer1.current_head_hash store >>= return) + + let register_current_tezos_level store dir = + RPC_directory.register0 + dir + (Sc_rollup_services.current_tezos_level ()) + (fun () () -> Layer1.current_level store >>= return) + + let register_current_inbox store dir = + RPC_directory.opt_register0 + dir + (Sc_rollup_services.current_inbox ()) + (fun () () -> + Layer1.current_head_hash store >>= function + | Some head_hash -> Inbox.inbox_of_hash store head_hash >>= return_some + | None -> return None) + + let register_current_ticks store dir = + RPC_directory.register0 + dir + (Sc_rollup_services.current_ticks ()) + (fun () () -> + let open Lwt_tzresult_syntax in + let* state = get_state_info_exn store in + return state.num_ticks) + + let start configuration dir = + let Configuration.{rpc_addr; rpc_port; _} = configuration in + let rpc_addr = P2p_addr.of_string_exn rpc_addr in + let host = Ipaddr.V6.to_string rpc_addr in + let node = `TCP (`Port rpc_port) in + let acl = RPC_server.Acl.default rpc_addr in + Lwt.catch + (fun () -> + RPC_server.launch + ~media_types:Media_type.all_media_types + ~host + ~acl + node + dir + >>= return) + fail_with_exn + + let shutdown = RPC_server.shutdown +end + +module Make (PVM : Pvm.S) = struct + include Common + + let register_current_total_ticks store dir = + RPC_directory.register0 + dir + (Sc_rollup_services.current_total_ticks ()) + (fun () () -> + let open Lwt_tzresult_syntax in + let* state = get_state_exn store in + let*! tick = PVM.get_tick state in + return tick) + + let register_current_state_hash store dir = + RPC_directory.register0 + dir + (Sc_rollup_services.current_state_hash ()) + (fun () () -> + let open Lwt_tzresult_syntax in + let* state = get_state_exn store in + let*! hash = PVM.state_hash state in + return hash) + + let register_current_status store dir = + RPC_directory.register0 + dir + (Sc_rollup_services.current_status ()) + (fun () () -> + let open Lwt_tzresult_syntax in + let* state = get_state_exn store in + let*! status = PVM.get_status state in + return (PVM.string_of_status status)) + + let register store configuration = + RPC_directory.empty + |> register_sc_rollup_address configuration + |> register_current_tezos_head store + |> register_current_inbox store + |> register_current_ticks store + |> register_current_total_ticks store + |> register_current_num_messages store + |> register_current_state_hash store + |> register_current_status store + + let start store configuration = + Common.start configuration (register store configuration) +end + +module Arith = Make (Arith_pvm) diff --git a/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml b/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml new file mode 100644 index 000000000000..154567fce399 --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/arith_pvm.ml @@ -0,0 +1,74 @@ +(*****************************************************************************) +(* *) +(* 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 +open Alpha_context + +(** This module manifests the proof format used by the Arith PVM as defined by + the Layer 1 implementation for it. + + It is imperative that this is aligned with the protocol's implementation. +*) +module Arith_proof_format = struct + open Store + + type proof = IStoreProof.Proof.tree IStoreProof.Proof.t + + let verify_proof = IStoreProof.verify_tree_proof + + let kinded_hash_to_state_hash : + IStoreProof.Proof.kinded_hash -> Sc_rollup.State_hash.t = function + | `Value hash | `Node hash -> + Sc_rollup.State_hash.hash_bytes [Context_hash.to_bytes hash] + + let proof_start_state proof = + kinded_hash_to_state_hash proof.IStoreProof.Proof.before + + let proof_stop_state proof = + kinded_hash_to_state_hash proof.IStoreProof.Proof.after + + let proof_encoding = + Tezos_context_helpers.Merkle_proof_encoding.V2.Tree32.tree_proof_encoding +end + +module Impl : Pvm.S = struct + include Sc_rollup_arith.Make (struct + open Store + module Tree = IStoreTree + + type tree = IStoreTree.tree + + include Arith_proof_format + end) + + let string_of_status status = + match status with + | Halted -> "Halted" + | WaitingForInputMessage -> "WaitingForInputMessage" + | Parsing -> "Parsing" + | Evaluating -> "Evaluating" +end + +include Impl diff --git a/src/proto_alpha/bin_sc_rollup_node/daemon.ml b/src/proto_alpha/bin_sc_rollup_node/daemon.ml index 2d018efae927..f455b85853da 100644 --- a/src/proto_alpha/bin_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/bin_sc_rollup_node/daemon.ml @@ -26,6 +26,7 @@ let on_layer_1_chain_event cctxt store chain_event = let open Lwt_tzresult_syntax in let* () = Inbox.update cctxt store chain_event in + let* () = Interpreter.Arith.update store chain_event in let*! () = Layer1.processed chain_event in return () @@ -46,7 +47,7 @@ let daemonize cctxt store layer_1_chain_events = let install_finalizer store rpc_server = let open Lwt_syntax in Lwt_exit.register_clean_up_callback ~loc:__LOC__ @@ fun exit_status -> - let* () = RPC_server.shutdown rpc_server in + let* () = RPC_server.Arith.shutdown rpc_server in let* () = Store.close store in let* () = Event.shutdown_node exit_status in Tezos_base_unix.Internal_event_unix.close () @@ -61,7 +62,8 @@ let run ~data_dir (cctxt : Protocol_client_context.full) = let*! store = Store.load configuration in let* tezos_heads = Layer1.start configuration cctxt store in let*! () = Inbox.start store sc_rollup_address in - let* rpc_server = RPC_server.start store configuration in + let* () = Interpreter.Arith.start store in + let* rpc_server = RPC_server.Arith.start store configuration in let _ = install_finalizer store rpc_server in let*! () = Event.node_is_ready ~rpc_addr ~rpc_port in daemonize cctxt store tezos_heads diff --git a/src/proto_alpha/bin_sc_rollup_node/interpreter.ml b/src/proto_alpha/bin_sc_rollup_node/interpreter.ml new file mode 100644 index 000000000000..c8acc3d7ad8d --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/interpreter.ml @@ -0,0 +1,157 @@ +(*****************************************************************************) +(* *) +(* 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 +open Alpha_context +module Inbox = Store.Inbox + +module type S = sig + (** [update store event] interprets the messages associated with a chain [event]. + This requires the inbox to be updated beforehand. *) + val update : Store.t -> Layer1.chain_event -> unit tzresult Lwt.t + + (** [start store] sets up the initial state for the PVM interpreter to work. *) + val start : Store.t -> unit tzresult Lwt.t +end + +module Make (PVM : Pvm.S) : S = struct + module PVM = PVM + + (** [eval_until_input state] advances a PVM [state] until it wants more inputs. *) + let eval_until_input state = + let open Lwt_syntax in + let rec go state = + let* input_request = PVM.is_input_state state in + match input_request with + | Some _ -> return state + | None -> + let* next_state = PVM.eval state in + go next_state + in + go state + + (** [feed_input state input] feeds [input] to the PVM in order to advance [state] to the next step + that requires an input. *) + let feed_input state input = + let open Lwt_syntax in + let* state = eval_until_input state in + let* state = PVM.set_input input state in + let* state = PVM.eval state in + let* state = eval_until_input state in + return state + + (** [transition_pvm store predecessor_hash hash] runs a PVM at the previous state from block + [predecessor_hash] by consuming as many messages as possible from block [hash]. *) + let transition_pvm store predecessor_hash hash = + let open Lwt_tzresult_syntax in + (* Retrieve the previous PVM state from store. *) + let*! predecessor_state = Store.PVMState.find store predecessor_hash in + let* predecessor_state = + match predecessor_state with + | None -> + failwith + "Missing PVM state for %s" + (Block_hash.to_b58check predecessor_hash) + | Some predecessor_state -> return predecessor_state + in + + (* Obtain inbox and its messages for this block. *) + let*! inbox = Store.Inboxes.get store hash in + let inbox_level = Inbox.inbox_level inbox in + let*! messages = Store.Messages.get store hash in + + (* Iterate the PVM state with all the messages for this level. *) + let*! state = + List.fold_left_i_s + (fun message_counter state payload -> + let input = + Sc_rollup_PVM_sem. + {inbox_level; message_counter = Z.of_int message_counter; payload} + in + feed_input state input) + predecessor_state + messages + in + + (* Write final state to store. *) + let*! () = Store.PVMState.set store hash state in + + (* Compute extra information about the state. *) + let*! initial_tick = PVM.get_tick predecessor_state in + let*! last_tick = PVM.get_tick state in + (* TODO: #2717 + The number of ticks should not be an arbitrarily-sized integer. + *) + let num_ticks = Sc_rollup.Tick.distance initial_tick last_tick in + (* TODO: #2717 + The length of messages here can potentially overflow the [int] returned from [List.length]. + *) + let num_messages = Z.of_int (List.length messages) in + let*! () = Store.StateInfo.add store hash {num_messages; num_ticks} in + + (* Produce events. *) + let*! () = Interpreter_event.transitioned_pvm state num_messages in + + return_unit + + (** [process_head store head] runs the PVM for the given head. *) + let process_head store (Layer1.Head {hash; _} as head) = + let open Lwt_tzresult_syntax in + let*! predecessor_hash = Layer1.predecessor store head in + transition_pvm store predecessor_hash hash + + (** [update store chain_event] reacts to an event on the chain. *) + let update store chain_event = + let open Lwt_tzresult_syntax in + match chain_event with + | Layer1.SameBranch {intermediate_heads; new_head} -> + let* () = List.iter_es (process_head store) intermediate_heads in + process_head store new_head + | Layer1.Rollback _new_head -> return_unit + + (** [start store] initializes the [store] with the needed state. *) + let start store = + let open Lwt_tzresult_syntax in + let*! () = + Store.PVMState.init_s store Layer1.genesis_hash (fun () -> + PVM.initial_state + store + (* TODO: #2722 + This initial state is a stub. We must figure out the bootsector (part of the + origination message for a SC rollup) first - only then can the initial state be + constructed. + *) + "") + in + let*! () = + Store.StateInfo.set + store + Layer1.genesis_hash + {num_ticks = Z.zero; num_messages = Z.zero} + in + return_unit +end + +module Arith = Make (Arith_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 new file mode 100644 index 000000000000..eecc453fd03e --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/interpreter_event.ml @@ -0,0 +1,50 @@ +(*****************************************************************************) +(* *) +(* 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.Sc_rollup + +module Simple = struct + include Internal_event.Simple + + let section = ["sc_rollup_node"; "interpreter"] + + let transitioned_pvm = + declare_3 + ~section + ~name:"sc_rollup_node_interpreter_transitioned_pvm" + ~msg: + "Transitioned PVM to {state_hash} at tick {ticks} with {num_messages} \ + messages" + ~level:Notice + ("state_hash", State_hash.encoding) + ("ticks", Tick.encoding) + ("num_messages", Data_encoding.z) +end + +let transitioned_pvm state num_messages = + let open Lwt_syntax in + let* hash = Arith_pvm.state_hash state in + let* ticks = Arith_pvm.get_tick state in + Simple.(emit transitioned_pvm (hash, ticks, num_messages)) diff --git a/src/proto_alpha/bin_sc_rollup_node/pvm.ml b/src/proto_alpha/bin_sc_rollup_node/pvm.ml new file mode 100644 index 000000000000..6a7360c020d6 --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/pvm.ml @@ -0,0 +1,45 @@ +(*****************************************************************************) +(* *) +(* 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 +open Alpha_context + +(** Desired module type of a PVM from the L2 node's perspective *) +module type S = sig + include + Sc_rollup_PVM_sem.S with type context = Store.t and type state = Store.tree + + (** [get_tick state] gets the total tick counter for the given PVM state. *) + val get_tick : state -> Sc_rollup.Tick.t Lwt.t + + (** PVM status *) + type status + + (** [get_status state] gives you the current execution status for the PVM. *) + val get_status : state -> status Lwt.t + + (** [string_of_status status] returns a string representation of [status]. *) + val string_of_status : status -> string +end diff --git a/src/proto_alpha/bin_sc_rollup_node/store.ml b/src/proto_alpha/bin_sc_rollup_node/store.ml index a528546ee8c3..e137af0dd231 100644 --- a/src/proto_alpha/bin_sc_rollup_node/store.ml +++ b/src/proto_alpha/bin_sc_rollup_node/store.ml @@ -25,10 +25,16 @@ open Protocol.Alpha_context module Maker = Irmin_pack_unix.Maker (Tezos_context_encoding.Context.Conf) -module IStore = Maker.Make (Tezos_context_encoding.Context.Schema) + +module IStore = struct + include Maker.Make (Tezos_context_encoding.Context.Schema) + module Schema = Tezos_context_encoding.Context.Schema +end type t = IStore.t +type tree = IStore.tree + type path = string list let load configuration = @@ -72,15 +78,21 @@ struct let get store key = IStore.get store (make_key key) >>= decode_value - let add store key value = - let open Lwt_syntax in - let* already_exists = mem store key in - assert (not already_exists) ; + let set store key value = let encoded_value = Data_encoding.Binary.to_bytes_exn P.value_encoding value in - let info () = info (String.concat "/" P.path ^ P.string_of_key key) in + let full_path = String.concat "/" (P.path @ [P.string_of_key key]) in + let info () = info full_path in IStore.set_exn ~info store (make_key key) encoded_value + + let add store key value = + let open Lwt_syntax in + let* already_exists = mem store key in + let full_path = String.concat "/" (P.path @ [P.string_of_key key]) in + if already_exists then + Stdlib.failwith (Printf.sprintf "Key %s already exists" full_path) ; + set store key value end module Make_mutable_value (P : sig @@ -116,10 +128,7 @@ module IStoreTree = struct include Tezos_context_helpers.Context.Make_tree (Tezos_context_encoding.Context.Conf) - (struct - include IStore - module Schema = Tezos_context_encoding.Context.Schema - end) + (IStore) type t = IStore.t @@ -130,10 +139,44 @@ module IStoreTree = struct type value = bytes end -module Inbox = Sc_rollup.Inbox.MakeHashingScheme (IStoreTree) +module IStoreProof = + Tezos_context_helpers.Context.Make_proof + (IStore) + (Tezos_context_encoding.Context.Conf) + +module Inbox = struct + include Sc_rollup.Inbox + include Sc_rollup.Inbox.MakeHashingScheme (IStoreTree) +end + +(** State of the PVM that this rollup node deals with *) +module PVMState = struct + let[@inline] key block_hash = ["pvm_state"; Block_hash.to_b58check block_hash] + + let find store block_hash = IStore.find_tree store (key block_hash) + + let exists store block_hash = IStore.mem store (key block_hash) + + let set store block_hash state = + IStore.set_tree_exn + ~info:(fun () -> info "Update PVM state") + store + (key block_hash) + state + + let init_s store block_hash make_state = + let open Lwt_syntax in + let* exists = exists store block_hash in + if exists then return_unit + else + let* state = make_state () in + set store block_hash state +end +(** Aggregated collection of messages from the L1 inbox *) module MessageTrees = struct - let key block_hash = ["message_tree"; Block_hash.to_b58check block_hash] + let[@inline] key block_hash = + ["message_tree"; Block_hash.to_b58check block_hash] (** [get store block_hash] retrieves the message tree for [block_hash]. If it is not present, an empty tree is returned. *) @@ -151,6 +194,31 @@ module MessageTrees = struct message_tree end +type state_info = {num_messages : Z.t; num_ticks : Z.t} + +(** Extraneous state information for the PVM *) +module StateInfo = Make_append_only_map (struct + let path = ["state_info"] + + let keep_last_n_entries_in_memory = 6000 + + type key = Block_hash.t + + let string_of_key = Block_hash.to_b58check + + type value = state_info + + let value_encoding = + let open Data_encoding in + conv + (fun {num_messages; num_ticks} -> (num_messages, num_ticks)) + (fun (num_messages, num_ticks) -> {num_messages; num_ticks}) + (obj2 + (req "num_messages" Data_encoding.z) + (req "num_ticks" Data_encoding.z)) +end) + +(** Unaggregated messages per block *) module Messages = Make_append_only_map (struct let path = ["messages"] @@ -165,6 +233,7 @@ module Messages = Make_append_only_map (struct let value_encoding = Data_encoding.(list string) end) +(** Inbox state for each block *) module Inboxes = Make_append_only_map (struct let path = ["inboxes"] @@ -179,6 +248,7 @@ module Inboxes = Make_append_only_map (struct let value_encoding = Sc_rollup.Inbox.encoding end) +(** Message history for the inbox at a given block *) module Histories = Make_append_only_map (struct let path = ["histories"] 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 0a64d5f4e905..e3888340130c 100644 --- a/src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml +++ b/src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml @@ -54,3 +54,38 @@ let current_inbox () = ~query:RPC_query.empty ~output:Sc_rollup.Inbox.encoding RPC_path.(open_root / "inbox") + +let current_ticks () = + RPC_service.get_service + ~description:"Current number of ticks for current level" + ~query:RPC_query.empty + ~output:Data_encoding.z + RPC_path.(open_root / "ticks") + +let current_total_ticks () = + RPC_service.get_service + ~description:"Current total number of ticks" + ~query:RPC_query.empty + ~output:Sc_rollup.Tick.encoding + RPC_path.(open_root / "total_ticks") + +let current_num_messages () = + RPC_service.get_service + ~description:"Current number of messages" + ~query:RPC_query.empty + ~output:Data_encoding.z + RPC_path.(open_root / "current_num_messages") + +let current_state_hash () = + RPC_service.get_service + ~description:"Current state hash" + ~query:RPC_query.empty + ~output:Sc_rollup.State_hash.encoding + RPC_path.(open_root / "state_hash") + +let current_status () = + RPC_service.get_service + ~description:"Current PVM status" + ~query:RPC_query.empty + ~output:Data_encoding.string + RPC_path.(open_root / "status") diff --git a/tezt/_regressions/sc_rollup_node_advances_pvm_state.out b/tezt/_regressions/sc_rollup_node_advances_pvm_state.out new file mode 100644 index 000000000000..30eff9d8105b --- /dev/null +++ b/tezt/_regressions/sc_rollup_node_advances_pvm_state.out @@ -0,0 +1,543 @@ +sc_rollup_node_advances_pvm_state.out + +./tezos-client --wait none originate sc rollup from '[PUBLIC_KEY_HASH]' of kind arith booting with --burn-cap 9999999 +Node is bootstrapped. +Estimated gas: 1600.648 units (will add 100 for safety) +Estimated storage: 6522 bytes added (will add 20 for safety) +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000402 + Expected counter: 1 + Gas limit: 1701 + Storage limit: 6542 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000402 + payload fees(the block proposer) ....... +ꜩ0.000402 + Originate smart contract rollup of kind arith with boot sector '' + This smart contract rollup origination was successfully applied + Consumed gas: 1600.648 + Storage size: 6522 bytes + Address: [SC_ROLLUP_HASH] + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ1.6305 + storage fees ........................... +ꜩ1.6305 + + +./tezos-client rpc get '/chains/main/blocks/head/context/sc_rollup/[SC_ROLLUP_HASH]/initial_level' +2 + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"0" + +./tezos-client --wait none send sc rollup message 'text:["31","36","2b"]' from bootstrap1 to '[SC_ROLLUP_HASH]' +Node is bootstrapped. +Estimated gas: 1650.756 units (will add 100 for safety) +Estimated storage: no bytes added +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.00046 + Expected counter: 2 + Gas limit: 1751 + Storage limit: 0 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.00046 + payload fees(the block proposer) ....... +ꜩ0.00046 + Add a message to the inbox of the smart contract rollup at address [SC_ROLLUP_HASH] + This operation sending a message to a smart contract rollup was successfully applied + Consumed gas: 1650.884 + Resulting inbox state: + rollup = [SC_ROLLUP_HASH] + level = 3 + current messages hash = CoVPEhS88nFx6gQMi77JKyFeHrqPG6eaSZTQMpET4WUupibBapF6 + nb_available_messages = 3 + message_counter = 3 + old_levels_messages = + content = CoUkdBQ53N7FWav8LuTvrcp3jyoxnpqk3xnEo3gSCgNwia4fq44j + index = 1 + back_pointers = CoVawGHT9AxoKnd7hDBCii5PEcM2U3WbtL4L5HGD6PC9BWcLnzqD + + + + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"15" + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"15" + +./tezos-client --wait none send sc rollup message 'text:["32","38","2b"]' from bootstrap1 to '[SC_ROLLUP_HASH]' +Node is bootstrapped. +Estimated gas: 1650.948 units (will add 100 for safety) +Estimated storage: no bytes added +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.00046 + Expected counter: 3 + Gas limit: 1751 + Storage limit: 0 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.00046 + payload fees(the block proposer) ....... +ꜩ0.00046 + Add a message to the inbox of the smart contract rollup at address [SC_ROLLUP_HASH] + This operation sending a message to a smart contract rollup was successfully applied + Consumed gas: 1651.076 + Resulting inbox state: + rollup = [SC_ROLLUP_HASH] + level = 4 + current messages hash = CoVQyPJJesQSn6SqpU6HC8VaW8oYuC8cE9jWVFLiDPPqv2NVFJ5w + nb_available_messages = 6 + message_counter = 3 + old_levels_messages = + content = CoVPEhS88nFx6gQMi77JKyFeHrqPG6eaSZTQMpET4WUupibBapF6 + index = 2 + back_pointers = CoUmDifn9cHq3g1wRc8ft64oMz7Jha8f4mcUWZd2YRseVae6MQAN + CoUmDifn9cHq3g1wRc8ft64oMz7Jha8f4mcUWZd2YRseVae6MQAN + + + + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"29" + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"29" + +./tezos-client --wait none send sc rollup message 'text:["33","3130","2b"]' from bootstrap1 to '[SC_ROLLUP_HASH]' +Node is bootstrapped. +Estimated gas: 1651.140 units (will add 100 for safety) +Estimated storage: no bytes added +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000461 + Expected counter: 4 + Gas limit: 1752 + Storage limit: 0 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000461 + payload fees(the block proposer) ....... +ꜩ0.000461 + Add a message to the inbox of the smart contract rollup at address [SC_ROLLUP_HASH] + This operation sending a message to a smart contract rollup was successfully applied + Consumed gas: 1651.140 + Resulting inbox state: + rollup = [SC_ROLLUP_HASH] + level = 5 + current messages hash = CoWCzzH2BbhbVPir9nwjs1Cq7VhsFrKUfsjyKCLDqNqF6Jv2XZ4H + nb_available_messages = 9 + message_counter = 3 + old_levels_messages = + content = CoVQyPJJesQSn6SqpU6HC8VaW8oYuC8cE9jWVFLiDPPqv2NVFJ5w + index = 3 + back_pointers = CoVBrPGWST4qmXjRooFd1zDYiE1KRz6aS4YVx3tM49c43j9xnYXK + CoUmDifn9cHq3g1wRc8ft64oMz7Jha8f4mcUWZd2YRseVae6MQAN + + + + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"44" + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"44" + +./tezos-client --wait none send sc rollup message 'text:["34","3132","2b"]' from bootstrap1 to '[SC_ROLLUP_HASH]' +Node is bootstrapped. +Estimated gas: 1651.140 units (will add 100 for safety) +Estimated storage: no bytes added +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000461 + Expected counter: 5 + Gas limit: 1752 + Storage limit: 0 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000461 + payload fees(the block proposer) ....... +ꜩ0.000461 + Add a message to the inbox of the smart contract rollup at address [SC_ROLLUP_HASH] + This operation sending a message to a smart contract rollup was successfully applied + Consumed gas: 1651.268 + Resulting inbox state: + rollup = [SC_ROLLUP_HASH] + level = 6 + current messages hash = CoVLyVysnSxXAmWcLNAXGxsdCXmk3YbXz5rDMaT2n6nJfDhHY2z2 + nb_available_messages = 12 + message_counter = 3 + old_levels_messages = + content = CoWCzzH2BbhbVPir9nwjs1Cq7VhsFrKUfsjyKCLDqNqF6Jv2XZ4H + index = 4 + back_pointers = CoUhTjeU6Ay7tuTPQbMdmSm9dK6QbULqf2hLNmmsbp6v31zifwGi + CoUhTjeU6Ay7tuTPQbMdmSm9dK6QbULqf2hLNmmsbp6v31zifwGi + CoUhTjeU6Ay7tuTPQbMdmSm9dK6QbULqf2hLNmmsbp6v31zifwGi + + + + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"59" + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"59" + +./tezos-client --wait none send sc rollup message 'text:["35","3134","2b"]' from bootstrap1 to '[SC_ROLLUP_HASH]' +Node is bootstrapped. +Estimated gas: 1651.332 units (will add 100 for safety) +Estimated storage: no bytes added +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000461 + Expected counter: 6 + Gas limit: 1752 + Storage limit: 0 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000461 + payload fees(the block proposer) ....... +ꜩ0.000461 + Add a message to the inbox of the smart contract rollup at address [SC_ROLLUP_HASH] + This operation sending a message to a smart contract rollup was successfully applied + Consumed gas: 1651.332 + Resulting inbox state: + rollup = [SC_ROLLUP_HASH] + level = 7 + current messages hash = CoVyFCKqAUuwhYrZzg57MsdSKVZBLZVqTmYzq7Ryt4DHNraUnziN + nb_available_messages = 15 + message_counter = 3 + old_levels_messages = + content = CoVLyVysnSxXAmWcLNAXGxsdCXmk3YbXz5rDMaT2n6nJfDhHY2z2 + index = 5 + back_pointers = CoW8kWoyV6gL4BwFcW9fZDLcf5F8YATMBUGptvigdwVP3d3Mz1dG + CoUhTjeU6Ay7tuTPQbMdmSm9dK6QbULqf2hLNmmsbp6v31zifwGi + CoUhTjeU6Ay7tuTPQbMdmSm9dK6QbULqf2hLNmmsbp6v31zifwGi + + + + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"74" + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"74" + +./tezos-client --wait none send sc rollup message 'text:["36","3136","2b"]' from bootstrap1 to '[SC_ROLLUP_HASH]' +Node is bootstrapped. +Estimated gas: 1651.332 units (will add 100 for safety) +Estimated storage: no bytes added +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000461 + Expected counter: 7 + Gas limit: 1752 + Storage limit: 0 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000461 + payload fees(the block proposer) ....... +ꜩ0.000461 + Add a message to the inbox of the smart contract rollup at address [SC_ROLLUP_HASH] + This operation sending a message to a smart contract rollup was successfully applied + Consumed gas: 1651.332 + Resulting inbox state: + rollup = [SC_ROLLUP_HASH] + level = 8 + current messages hash = CoWXC3SoWWgnKoTp3aAFop8Ac7bDWQzifGPFkDiScQegLoKhxQ8u + nb_available_messages = 18 + message_counter = 3 + old_levels_messages = + content = CoVyFCKqAUuwhYrZzg57MsdSKVZBLZVqTmYzq7Ryt4DHNraUnziN + index = 6 + back_pointers = CoVsF3bFcbZcXPRBsyCsXkCPvWKth38Z2zDXrKdnYgiyiB2KSmSX + CoVsF3bFcbZcXPRBsyCsXkCPvWKth38Z2zDXrKdnYgiyiB2KSmSX + CoUhTjeU6Ay7tuTPQbMdmSm9dK6QbULqf2hLNmmsbp6v31zifwGi + + + + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"89" + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"89" + +./tezos-client --wait none send sc rollup message 'text:["37","3138","2b"]' from bootstrap1 to '[SC_ROLLUP_HASH]' +Node is bootstrapped. +Estimated gas: 1651.332 units (will add 100 for safety) +Estimated storage: no bytes added +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000461 + Expected counter: 8 + Gas limit: 1752 + Storage limit: 0 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000461 + payload fees(the block proposer) ....... +ꜩ0.000461 + Add a message to the inbox of the smart contract rollup at address [SC_ROLLUP_HASH] + This operation sending a message to a smart contract rollup was successfully applied + Consumed gas: 1651.332 + Resulting inbox state: + rollup = [SC_ROLLUP_HASH] + level = 9 + current messages hash = CoVzoJeMcZYFGJMM9vrQXc3pYvBoTpBpyUSP6q3VmD5AquCMn9Fi + nb_available_messages = 21 + message_counter = 3 + old_levels_messages = + content = CoWXC3SoWWgnKoTp3aAFop8Ac7bDWQzifGPFkDiScQegLoKhxQ8u + index = 7 + back_pointers = CoVLPSjmrA6YC1PkwhmG6aGgSXex2h4khieKN86sPiyNeDxQzH9k + CoVsF3bFcbZcXPRBsyCsXkCPvWKth38Z2zDXrKdnYgiyiB2KSmSX + CoUhTjeU6Ay7tuTPQbMdmSm9dK6QbULqf2hLNmmsbp6v31zifwGi + + + + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"104" + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"104" + +./tezos-client --wait none send sc rollup message 'text:["38","3230","2b"]' from bootstrap1 to '[SC_ROLLUP_HASH]' +Node is bootstrapped. +Estimated gas: 1651.332 units (will add 100 for safety) +Estimated storage: no bytes added +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000461 + Expected counter: 9 + Gas limit: 1752 + Storage limit: 0 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000461 + payload fees(the block proposer) ....... +ꜩ0.000461 + Add a message to the inbox of the smart contract rollup at address [SC_ROLLUP_HASH] + This operation sending a message to a smart contract rollup was successfully applied + Consumed gas: 1651.460 + Resulting inbox state: + rollup = [SC_ROLLUP_HASH] + level = 10 + current messages hash = CoVc3ajtZyT9cmcXNx6dnUNPFrnTJ7Y8jAi2FpPq7ad9GUAGJ1dh + nb_available_messages = 24 + message_counter = 3 + old_levels_messages = + content = CoVzoJeMcZYFGJMM9vrQXc3pYvBoTpBpyUSP6q3VmD5AquCMn9Fi + index = 8 + back_pointers = CoUeSPWh7QbJ1tB4ZKXUiL1yLXZL3ietZJPXEuG5yvuXfNRWG7BA + CoUeSPWh7QbJ1tB4ZKXUiL1yLXZL3ietZJPXEuG5yvuXfNRWG7BA + CoUeSPWh7QbJ1tB4ZKXUiL1yLXZL3ietZJPXEuG5yvuXfNRWG7BA + CoUeSPWh7QbJ1tB4ZKXUiL1yLXZL3ietZJPXEuG5yvuXfNRWG7BA + + + + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"119" + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"119" + +./tezos-client --wait none send sc rollup message 'text:["39","3232","2b"]' from bootstrap1 to '[SC_ROLLUP_HASH]' +Node is bootstrapped. +Estimated gas: 1651.524 units (will add 100 for safety) +Estimated storage: no bytes added +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000461 + Expected counter: 10 + Gas limit: 1752 + Storage limit: 0 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000461 + payload fees(the block proposer) ....... +ꜩ0.000461 + Add a message to the inbox of the smart contract rollup at address [SC_ROLLUP_HASH] + This operation sending a message to a smart contract rollup was successfully applied + Consumed gas: 1651.524 + Resulting inbox state: + rollup = [SC_ROLLUP_HASH] + level = 11 + current messages hash = CoUoBsXuAj3AVYNC9MbiV9pB1Jemhewhum4onFV7rn5dqe6iiAjS + nb_available_messages = 27 + message_counter = 3 + old_levels_messages = + content = CoVc3ajtZyT9cmcXNx6dnUNPFrnTJ7Y8jAi2FpPq7ad9GUAGJ1dh + index = 9 + back_pointers = CoUgC9LmZ18AHEjT2ZKwmpz6dxSWnS8sCDcBsNsvN1MWFMgetweS + CoUeSPWh7QbJ1tB4ZKXUiL1yLXZL3ietZJPXEuG5yvuXfNRWG7BA + CoUeSPWh7QbJ1tB4ZKXUiL1yLXZL3ietZJPXEuG5yvuXfNRWG7BA + CoUeSPWh7QbJ1tB4ZKXUiL1yLXZL3ietZJPXEuG5yvuXfNRWG7BA + + + + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"134" + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"134" + +./tezos-client --wait none send sc rollup message 'text:["3130","3234","2b"]' from bootstrap1 to '[SC_ROLLUP_HASH]' +Node is bootstrapped. +Estimated gas: 1651.524 units (will add 100 for safety) +Estimated storage: no bytes added +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000462 + Expected counter: 11 + Gas limit: 1752 + Storage limit: 0 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000462 + payload fees(the block proposer) ....... +ꜩ0.000462 + Add a message to the inbox of the smart contract rollup at address [SC_ROLLUP_HASH] + This operation sending a message to a smart contract rollup was successfully applied + Consumed gas: 1651.524 + Resulting inbox state: + rollup = [SC_ROLLUP_HASH] + level = 12 + current messages hash = CoUsw4TxpTwo9Xu6GE5iXVtyhXbLBHNrNopTiaVCVa7NfLkqPKJF + nb_available_messages = 30 + message_counter = 3 + old_levels_messages = + content = CoUoBsXuAj3AVYNC9MbiV9pB1Jemhewhum4onFV7rn5dqe6iiAjS + index = 10 + back_pointers = CoVaFiUPin2nwRUWLD9heruCr3nCpvXFVd8G1NPz9a77zJxaBwS7 + CoVaFiUPin2nwRUWLD9heruCr3nCpvXFVd8G1NPz9a77zJxaBwS7 + CoUeSPWh7QbJ1tB4ZKXUiL1yLXZL3ietZJPXEuG5yvuXfNRWG7BA + CoUeSPWh7QbJ1tB4ZKXUiL1yLXZL3ietZJPXEuG5yvuXfNRWG7BA + + + + +./tezos-sc-rollup-client-alpha rpc get /state_hash +"[SC_ROLLUP_STATE_HASH]" + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"150" diff --git a/tezt/_regressions/sc_rollup_node_boots_into_initial_state.out b/tezt/_regressions/sc_rollup_node_boots_into_initial_state.out new file mode 100644 index 000000000000..13b5f0449dc0 --- /dev/null +++ b/tezt/_regressions/sc_rollup_node_boots_into_initial_state.out @@ -0,0 +1,40 @@ +sc_rollup_node_boots_into_initial_state.out + +./tezos-client --wait none originate sc rollup from '[PUBLIC_KEY_HASH]' of kind arith booting with --burn-cap 9999999 +Node is bootstrapped. +Estimated gas: 1600.648 units (will add 100 for safety) +Estimated storage: 6522 bytes added (will add 20 for safety) +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000402 + Expected counter: 1 + Gas limit: 1701 + Storage limit: 6542 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000402 + payload fees(the block proposer) ....... +ꜩ0.000402 + Originate smart contract rollup of kind arith with boot sector '' + This smart contract rollup origination was successfully applied + Consumed gas: 1600.648 + Storage size: 6522 bytes + Address: [SC_ROLLUP_HASH] + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ1.6305 + storage fees ........................... +ꜩ1.6305 + + +./tezos-client rpc get '/chains/main/blocks/head/context/sc_rollup/[SC_ROLLUP_HASH]/initial_level' +2 + +./tezos-sc-rollup-client-alpha rpc get /total_ticks +"0" + +./tezos-sc-rollup-client-alpha rpc get /status +"Halted" diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index f6082b4e9834..b91f2ce366a6 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -283,18 +283,18 @@ let test_rollup_get_initial_level = the Tezos node. Then we can observe that the messages are included in the inbox. *) -let send_messages n sc_rollup_address client = - let send msg = - let* () = - Client.Sc_rollup.send_message - ~hooks - ~src:"bootstrap1" - ~dst:sc_rollup_address - ~msg - client - in - Client.bake_for client +let send_message client sc_rollup_address msg = + let* () = + Client.Sc_rollup.send_message + ~hooks + ~src:"bootstrap1" + ~dst:sc_rollup_address + ~msg + client in + Client.bake_for client + +let send_messages n sc_rollup_address client = let messages = range 1 n |> fun is -> List.map @@ -303,7 +303,19 @@ let send_messages n sc_rollup_address client = @@ List.map (fun _ -> Printf.sprintf "\"CAFEBABE\"") (range 1 i)) is in - Lwt_list.iter_s send messages + Lwt_list.iter_s + (fun msg -> send_message client sc_rollup_address msg) + messages + +let to_text_messages_arg msgs = + let text_messages = + List.map (fun msg -> Hex.of_string msg |> Hex.show) msgs + in + let json = Ezjsonm.list Ezjsonm.string text_messages in + "text:" ^ Ezjsonm.to_string ~minify:true json + +let send_text_messages client sc_rollup_address msgs = + send_message client sc_rollup_address (to_text_messages_arg msgs) let parse_inbox json = let go () = @@ -506,6 +518,118 @@ let test_rollup_list = "list originated rollups" (fun protocol -> setup ~protocol go) +(* Make sure the rollup node boots into the initial state. + ------------------------------------------------------- + + When a rollup node starts, we want to make sure that in the absence of + messages it will boot into the initial state. + +*) +let test_rollup_node_boots_into_initial_state = + let go client sc_rollup_address sc_rollup_node = + let* init_level = + RPC.Sc_rollup.get_initial_level ~hooks ~sc_rollup_address client + in + let init_level = init_level |> JSON.as_int in + + let* () = Sc_rollup_node.run sc_rollup_node in + let sc_rollup_client = Sc_rollup_client.create sc_rollup_node in + + let* level = Sc_rollup_node.wait_for_level sc_rollup_node init_level in + Check.(level = init_level) + 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 + Check.(ticks = 0) + Check.int + ~error_msg:"Unexpected initial tick count (%L = %R)" ; + + let* status = Sc_rollup_client.status ~hooks sc_rollup_client in + Check.(status = "Halted") + Check.string + ~error_msg:"Unexpected PVM status (%L = %R)" ; + + Lwt.return_unit + in + + let output_file _ = "sc_rollup_node_boots_into_initial_state" in + test + ~__FILE__ + ~output_file + ~tags:["run"; "node"] + "node boots into the initial state" + (fun protocol -> + setup ~protocol @@ fun node client -> + with_fresh_rollup + (fun sc_rollup_address sc_rollup_node _filename -> + go client sc_rollup_address sc_rollup_node) + node + client) + +(* Ensure the PVM is transitioning upon incoming messages. + ------------------------------------------------------- + + When the rollup node receives messages, we like to see evidence that the PVM + has advanced. + +*) +let test_rollup_node_advances_pvm_state = + let go client sc_rollup_address sc_rollup_node = + let* init_level = + RPC.Sc_rollup.get_initial_level ~hooks ~sc_rollup_address client + in + let init_level = init_level |> JSON.as_int in + + let* () = Sc_rollup_node.run sc_rollup_node in + let sc_rollup_client = Sc_rollup_client.create sc_rollup_node in + + let* level = Sc_rollup_node.wait_for_level sc_rollup_node init_level in + Check.(level = init_level) + Check.int + ~error_msg:"Current level has moved past origination level (%L = %R)" ; + + let test_message i = + 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 x = Int.to_string i in + let y = Int.to_string ((i + 2) * 2) in + let* () = send_text_messages client sc_rollup_address [x; y; "+"] in + let* _ = Sc_rollup_node.wait_for_level sc_rollup_node (level + i) 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 + Check.(ticks >= prev_ticks) + Check.int + ~error_msg:"Tick counter did not advance (%L >= %R)" ; + Lwt.return_unit + in + let* () = Lwt_list.iter_s test_message (range 1 10) in + + Lwt.return_unit + in + + let output_file _ = "sc_rollup_node_advances_pvm_state" in + test + ~__FILE__ + ~output_file + ~tags:["run"; "node"] + "node advances PVM state with messages" + (fun protocol -> + setup ~protocol @@ fun node client -> + with_fresh_rollup + (fun sc_rollup_address sc_rollup_node _filename -> + go client sc_rollup_address sc_rollup_node) + node + client) + let register ~protocols = test_origination protocols ; test_rollup_node_configuration protocols ; @@ -522,4 +646,6 @@ let register ~protocols = test_rollup_inbox_of_rollup_node "handles_chain_reorg" sc_rollup_node_handles_chain_reorg - protocols + protocols ; + test_rollup_node_boots_into_initial_state protocols ; + test_rollup_node_advances_pvm_state protocols -- GitLab