From 4b9da9b2184ba65b12e3601b84830b0822c95c76 Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Mon, 28 Mar 2022 10:47:02 +0200 Subject: [PATCH 01/13] Tx_rollup,Node: prover context --- src/proto_alpha/bin_tx_rollup_node/context.ml | 38 +++++++++++++++++++ .../bin_tx_rollup_node/context.mli | 12 ++++++ 2 files changed, 50 insertions(+) diff --git a/src/proto_alpha/bin_tx_rollup_node/context.ml b/src/proto_alpha/bin_tx_rollup_node/context.ml index 8b5cf5216178..dc7840904414 100644 --- a/src/proto_alpha/bin_tx_rollup_node/context.ml +++ b/src/proto_alpha/bin_tx_rollup_node/context.ml @@ -114,3 +114,41 @@ let checkout_exn index hash = let open Lwt_syntax in let+ context = checkout index hash in match context with None -> raise Not_found | Some context -> context + +(** {2 Prover context} *) + +exception Error of Environment.Error_monad.error + +module Prover_storage : + Protocol.Tx_rollup_l2_storage_sig.STORAGE + with type t = tree + and type 'a m = 'a Lwt.t = struct + type t = tree + + type 'a m = 'a Lwt.t + + module Syntax = struct + include Lwt.Syntax + + let return = Lwt.return + + let fail e = Lwt.fail (Error e) + + let catch (m : 'a m) k h = + Lwt.catch + (fun () -> m >>= k) + (function Error e -> h e | e -> Lwt.fail e) + + let list_fold_left_m = Lwt_list.fold_left_s + end + + let path k = [Bytes.to_string k] + + let get store key = Tree.find store (path key) + + let set store key value = Tree.add store (path key) value + + let remove store key = Tree.remove store (path key) +end + +module Prover_context = Protocol.Tx_rollup_l2_context.Make (Prover_storage) diff --git a/src/proto_alpha/bin_tx_rollup_node/context.mli b/src/proto_alpha/bin_tx_rollup_node/context.mli index c9c8f3863404..ba714333cbf0 100644 --- a/src/proto_alpha/bin_tx_rollup_node/context.mli +++ b/src/proto_alpha/bin_tx_rollup_node/context.mli @@ -77,3 +77,15 @@ val hash : ?message:string -> t -> Protocol.Tx_rollup_l2_context_hash.t additional [message]. *) val commit : ?message:string -> context -> Protocol.Tx_rollup_l2_context_hash.t Lwt.t + +(** {2 Prover Context} *) + +(** The prover context is a subset of the context. It uses the internal + context tree to produce proofs. *) + +type tree + +module Prover_context : + Protocol.Tx_rollup_l2_context_sig.CONTEXT + with type t = tree + and type 'a m = 'a Lwt.t -- GitLab From 8062f7d321b0a565b92e3fb22a8c7624b56f0c3f Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Mon, 28 Mar 2022 10:52:03 +0200 Subject: [PATCH 02/13] Tx_rollup,Node: use not-so empty prover context --- src/proto_alpha/bin_tx_rollup_node/batcher.ml | 3 +- src/proto_alpha/bin_tx_rollup_node/context.ml | 28 +++++++++++++++++++ .../bin_tx_rollup_node/context.mli | 5 ++-- src/proto_alpha/bin_tx_rollup_node/daemon.ml | 9 ++---- src/proto_alpha/bin_tx_rollup_node/l2block.ml | 8 +++--- .../bin_tx_rollup_node/l2block.mli | 2 +- src/proto_alpha/bin_tx_rollup_node/state.ml | 7 +++-- 7 files changed, 45 insertions(+), 17 deletions(-) diff --git a/src/proto_alpha/bin_tx_rollup_node/batcher.ml b/src/proto_alpha/bin_tx_rollup_node/batcher.ml index 16dfaba14a0a..5ecad17118dd 100644 --- a/src/proto_alpha/bin_tx_rollup_node/batcher.ml +++ b/src/proto_alpha/bin_tx_rollup_node/batcher.ml @@ -179,6 +179,7 @@ let async_batch_and_inject ?at_least_one_full_batch state = let init cctxt ~rollup ~signer index parameters = let open Lwt_result_syntax in + let*! incr_context = Context.init_context index in let+ signer = get_signer cctxt signer in Option.map (fun signer -> @@ -188,7 +189,7 @@ let init cctxt ~rollup ~signer index parameters = signer; parameters; transactions = Tx_queue.create 500_000; - incr_context = Context.empty index; + incr_context; lock = Lwt_mutex.create (); }) signer diff --git a/src/proto_alpha/bin_tx_rollup_node/context.ml b/src/proto_alpha/bin_tx_rollup_node/context.ml index dc7840904414..64bcc2e99b88 100644 --- a/src/proto_alpha/bin_tx_rollup_node/context.ml +++ b/src/proto_alpha/bin_tx_rollup_node/context.ml @@ -152,3 +152,31 @@ module Prover_storage : end module Prover_context = Protocol.Tx_rollup_l2_context.Make (Prover_storage) + +let hash_tree = Tree.hash + +let add_tree ctxt tree = + let open Lwt_syntax in + let* ctxt = add_tree ctxt [] tree in + (* Irmin requires that we commit the context before generating the proof. *) + let* ctxt_hash = commit ctxt in + return (ctxt, ctxt_hash) + +(** The initial context must be constructed using the internal empty tree. + This tree however, *needs* to be non-empty. Otherwise, its hash will + be inconsistent. + See {!Protocol.Tx_rollup_commitment_repr.empty_l2_context_hash} for more + context. +*) +let init_context index = + let open Prover_context.Syntax in + let ctxt = empty index in + let tree = Tree.empty ctxt in + let* tree = Prover_context.Address_index.init_counter tree in + let* tree = Prover_context.Ticket_index.init_counter tree in + let tree_hash = hash_tree tree in + assert ( + Context_hash.( + tree_hash = Protocol.Tx_rollup_message_result_repr.empty_l2_context_hash)) ; + let* (ctxt, _) = add_tree ctxt tree in + return ctxt diff --git a/src/proto_alpha/bin_tx_rollup_node/context.mli b/src/proto_alpha/bin_tx_rollup_node/context.mli index ba714333cbf0..f5ff7cd72dc0 100644 --- a/src/proto_alpha/bin_tx_rollup_node/context.mli +++ b/src/proto_alpha/bin_tx_rollup_node/context.mli @@ -44,8 +44,9 @@ val init : string -> index Lwt.t -(** Build an empty context from an index. Don't commit an empty context. *) -val empty : index -> t +(** Initialize an "empty" context from an index. It is not really empty in the + sense that the underlying tree is not empty, it is then committed. *) +val init_context : index -> t Lwt.t (** Close the index. Does not fail when the context is already closed. *) val close : index -> unit Lwt.t diff --git a/src/proto_alpha/bin_tx_rollup_node/daemon.ml b/src/proto_alpha/bin_tx_rollup_node/daemon.ml index 4abd78058fcb..cf2b6ab3eab9 100644 --- a/src/proto_alpha/bin_tx_rollup_node/daemon.ml +++ b/src/proto_alpha/bin_tx_rollup_node/daemon.ml @@ -239,12 +239,9 @@ let extract_messages_from_block block_info rollup_id = let create_genesis_block state tezos_block = let open Lwt_syntax in - let ctxt = Context.empty state.State.context_index in - let genesis_block = - L2block.genesis_block - state.context_index - state.rollup_info.rollup_id - tezos_block + let* ctxt = Context.init_context state.State.context_index in + let* genesis_block = + L2block.genesis_block ctxt state.rollup_info.rollup_id tezos_block in let+ _block_hash = State.save_block state genesis_block in (genesis_block, ctxt) diff --git a/src/proto_alpha/bin_tx_rollup_node/l2block.ml b/src/proto_alpha/bin_tx_rollup_node/l2block.ml index c8c6bd50fff7..0eab0b4b4fdb 100644 --- a/src/proto_alpha/bin_tx_rollup_node/l2block.ml +++ b/src/proto_alpha/bin_tx_rollup_node/l2block.ml @@ -126,9 +126,9 @@ let hash_header h = | Rollup_level _ -> Hash.hash_bytes [Data_encoding.Binary.to_bytes_exn header_encoding h] -let genesis_block index rollup tezos_block = - let ctxt = Context.empty index in - let context_hash = Context.hash ctxt in +let genesis_block ctxt rollup tezos_block = + let open Lwt_syntax in + let* context_hash = Context.commit ctxt in let hash = genesis_hash rollup in let header = { @@ -140,4 +140,4 @@ let genesis_block index rollup tezos_block = } in let inbox : Inbox.t = {contents = []; cumulated_size = 0} in - {hash; header; inbox} + return {hash; header; inbox} diff --git a/src/proto_alpha/bin_tx_rollup_node/l2block.mli b/src/proto_alpha/bin_tx_rollup_node/l2block.mli index 95a011bcb4b1..a5facf988755 100644 --- a/src/proto_alpha/bin_tx_rollup_node/l2block.mli +++ b/src/proto_alpha/bin_tx_rollup_node/l2block.mli @@ -58,7 +58,7 @@ type header = { type t = {hash : hash; header : header; inbox : Inbox.t} (** Build the genesis block *) -val genesis_block : Context.index -> Tx_rollup.t -> Block_hash.t -> t +val genesis_block : Context.t -> Tx_rollup.t -> Block_hash.t -> t Lwt.t (** {2 Encoding} *) diff --git a/src/proto_alpha/bin_tx_rollup_node/state.ml b/src/proto_alpha/bin_tx_rollup_node/state.ml index e02e9e884e31..52f46e0279d7 100644 --- a/src/proto_alpha/bin_tx_rollup_node/state.ml +++ b/src/proto_alpha/bin_tx_rollup_node/state.ml @@ -393,15 +393,16 @@ let init_context ~data_dir = let init_head (stores : Stores.t) context_index rollup rollup_info = let open Lwt_syntax in let* hash = Stores.Head_store.read stores.head in - let+ head = + let* head = match hash with | None -> return_none | Some hash -> get_block_store stores hash in match head with - | Some head -> head + | Some head -> return head | None -> - L2block.genesis_block context_index rollup rollup_info.origination_block + let* ctxt = Context.init_context context_index in + L2block.genesis_block ctxt rollup rollup_info.origination_block let init_parameters cctxt = let open Lwt_result_syntax in -- GitLab From c82ffc7b974700e772a42a93079106431919b380 Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Mon, 28 Mar 2022 11:25:52 +0200 Subject: [PATCH 03/13] Tx_rollup,Node: prover_apply module producing proofs --- src/proto_alpha/bin_tx_rollup_node/context.ml | 23 ++++++++++ .../bin_tx_rollup_node/context.mli | 38 +++++++++++++++ src/proto_alpha/bin_tx_rollup_node/error.ml | 36 +++++++++++++++ src/proto_alpha/bin_tx_rollup_node/error.mli | 6 +++ .../bin_tx_rollup_node/prover_apply.ml | 46 +++++++++++++++++++ .../bin_tx_rollup_node/prover_apply.mli | 45 ++++++++++++++++++ 6 files changed, 194 insertions(+) create mode 100644 src/proto_alpha/bin_tx_rollup_node/prover_apply.ml create mode 100644 src/proto_alpha/bin_tx_rollup_node/prover_apply.mli diff --git a/src/proto_alpha/bin_tx_rollup_node/context.ml b/src/proto_alpha/bin_tx_rollup_node/context.ml index 64bcc2e99b88..8e9ee91277bb 100644 --- a/src/proto_alpha/bin_tx_rollup_node/context.ml +++ b/src/proto_alpha/bin_tx_rollup_node/context.ml @@ -153,6 +153,29 @@ end module Prover_context = Protocol.Tx_rollup_l2_context.Make (Prover_storage) +type 'a produce_proof_result = {tree : tree; result : 'a} + +let produce_proof ctxt f = + let open Lwt_result_syntax in + let index = index ctxt in + let* tree = + let*! tree_opt = find_tree ctxt [] in + match tree_opt with + | Some tree -> return tree + | None -> fail [Error.Tx_rollup_tree_not_found] + in + let* kinded_key = + match Tree.kinded_key tree with + | Some kinded_key -> return kinded_key + | None -> fail [Error.Tx_rollup_tree_kinded_key_not_found] + in + let*! (proof, result) = + produce_stream_proof index kinded_key (fun tree -> + let*! res = f tree in + Lwt.return (res.tree, res)) + in + return (proof, result) + let hash_tree = Tree.hash let add_tree ctxt tree = diff --git a/src/proto_alpha/bin_tx_rollup_node/context.mli b/src/proto_alpha/bin_tx_rollup_node/context.mli index f5ff7cd72dc0..0da34422ab50 100644 --- a/src/proto_alpha/bin_tx_rollup_node/context.mli +++ b/src/proto_alpha/bin_tx_rollup_node/context.mli @@ -90,3 +90,41 @@ module Prover_context : Protocol.Tx_rollup_l2_context_sig.CONTEXT with type t = tree and type 'a m = 'a Lwt.t + +(** ['a produce_proof_result] is the result type needed for the {!produce_proof} + callback function. *) +type 'a produce_proof_result = { + tree : tree; (** the tree modified by the callback function *) + result : 'a; (** the callback result. *) +} + +(** [produce_proof ctxt f] applies [f] in the {!tree} inside [ctxt]. + + It returns a proof that is produced by applying [f], the proof is + constructed using low-levels accesses to the three, that is, it needs the + modified tree to be included in the [f]'s result to calculate the proof. + + Beside the proof production, this function can be used to perform sementical + changes in the {!Prover_context}. Thus, we give the possibility to return a + result in {!'a produce_proof_result} to observe [f]'s results. +*) +val produce_proof : + context -> + (tree -> 'a produce_proof_result Lwt.t) -> + (Protocol.Tx_rollup_l2_proof.t * 'a produce_proof_result) tzresult Lwt.t + +val hash_tree : tree -> Context_hash.t + +(** [add_tree ctxt tree] adds [tree] in the [ctxt]. In order to perform + actions on the tree (e.g. proof production), it needs to be persistent. Thus, + the context is committed on disk after we added the tree, that is, after + every modification on the tree such as a message interpretation. + + FIXME: https://gitlab.com/tezos/tezos/-/issues/2780 + We would like to avoid the commit in this function for performance + matters. +*) +val add_tree : + context -> tree -> (context * Protocol.Tx_rollup_l2_context_hash.t) Lwt.t + +val tree_hash_of_context : context -> Context_hash.t tzresult Lwt.t diff --git a/src/proto_alpha/bin_tx_rollup_node/error.ml b/src/proto_alpha/bin_tx_rollup_node/error.ml index 6cb52b82c344..37e945ed0cd5 100644 --- a/src/proto_alpha/bin_tx_rollup_node/error.ml +++ b/src/proto_alpha/bin_tx_rollup_node/error.ml @@ -275,3 +275,39 @@ let () = Data_encoding.(obj1 (req "block" Block_hash.encoding)) (function Tx_rollup_cannot_fetch_tezos_block b -> Some b | _ -> None) (fun b -> Tx_rollup_cannot_fetch_tezos_block b) + +type error += Tx_rollup_tree_not_found + +let () = + register_error_kind + ~id:"tx_rollup.node.tree_not_found" + ~title:"Tree not found in context" + ~description:"The tree is not found in the context." + ~pp:(fun ppf () -> + Format.fprintf + ppf + "The tree was not found in the context. The merkle proof associated to \ + a message can not be produced, the rollup can not interpret the \ + message.") + `Permanent + Data_encoding.empty + (function Tx_rollup_tree_not_found -> Some () | _ -> None) + (fun () -> Tx_rollup_tree_not_found) + +type error += Tx_rollup_tree_kinded_key_not_found + +let () = + register_error_kind + ~id:"tx_rollup.node.tree_kinded_key_not_found" + ~title:"Kinded key not found in tree" + ~description:"The kinded key is not found in the tree." + ~pp:(fun ppf () -> + Format.fprintf + ppf + "The kinded key was not found in the tree. The merkle proof associated \ + to a message can not be produced, the rollup can not interpret the \ + message.") + `Permanent + Data_encoding.empty + (function Tx_rollup_tree_kinded_key_not_found -> Some () | _ -> None) + (fun () -> Tx_rollup_tree_kinded_key_not_found) diff --git a/src/proto_alpha/bin_tx_rollup_node/error.mli b/src/proto_alpha/bin_tx_rollup_node/error.mli index d5cde5a59a7d..5dfcde03e74a 100644 --- a/src/proto_alpha/bin_tx_rollup_node/error.mli +++ b/src/proto_alpha/bin_tx_rollup_node/error.mli @@ -75,3 +75,9 @@ type error += Tx_rollup_mismatch (** Error when Tezos block cannot be fetched. *) type error += Tx_rollup_cannot_fetch_tezos_block of Block_hash.t + +(** Error when the tree is not found in the context. *) +type error += Tx_rollup_tree_not_found + +(** Error when the kinded key is not found in the tree. *) +type error += Tx_rollup_tree_kinded_key_not_found diff --git a/src/proto_alpha/bin_tx_rollup_node/prover_apply.ml b/src/proto_alpha/bin_tx_rollup_node/prover_apply.ml new file mode 100644 index 000000000000..51e49fdb133b --- /dev/null +++ b/src/proto_alpha/bin_tx_rollup_node/prover_apply.ml @@ -0,0 +1,46 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs, *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +module Prover_apply = Protocol.Tx_rollup_l2_apply.Make (Context.Prover_context) + +type proof = Protocol.Tx_rollup_l2_proof.t + +let proof_size = + Data_encoding.Binary.length Protocol.Tx_rollup_l2_proof.encoding + +let apply_message ctxt parameters message = + let open Lwt_result_syntax in + let f tree = + Context.Prover_context.Syntax.catch + (Prover_apply.apply_message tree parameters message) + (fun (tree, result) -> + Lwt.return Context.{tree; result = Inbox.Interpreted result}) + (fun err -> + Lwt.return + Context. + {tree; result = Inbox.Discarded [Environment.wrap_tzerror err]}) + in + let* (proof, result) = Context.produce_proof ctxt f in + return (proof, result) diff --git a/src/proto_alpha/bin_tx_rollup_node/prover_apply.mli b/src/proto_alpha/bin_tx_rollup_node/prover_apply.mli new file mode 100644 index 000000000000..f951e5cd33a2 --- /dev/null +++ b/src/proto_alpha/bin_tx_rollup_node/prover_apply.mli @@ -0,0 +1,45 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs, *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +type proof = Protocol.Tx_rollup_l2_proof.t + +val proof_size : proof -> int + +(** [apply_message ctxt parameters message] applies [message] on [ctxt] + using [parameters]. + + It uses internally the {!Context.Prover_context} to generate the associated + proof of the application. + + The modified prover context state is returned and can be either dropped or + committed. Note that if the underlying tree is meant to be kept, it + must be committed on disk, no others proofs can be generated on a + non-persistent tree. +*) +val apply_message : + Context.context -> + Protocol.Tx_rollup_l2_apply.parameters -> + Protocol.Alpha_context.Tx_rollup_message.t -> + (proof * Inbox.message_result Context.produce_proof_result) tzresult Lwt.t -- GitLab From 7a063834defffc2a2de8d4a04fef252ffda393f5 Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Fri, 1 Apr 2022 11:27:05 +0200 Subject: [PATCH 04/13] Tx_rollup,Node: l1 constants --- src/proto_alpha/bin_tx_rollup_node/batcher.ml | 15 +++++++++++---- src/proto_alpha/bin_tx_rollup_node/batcher.mli | 2 +- src/proto_alpha/bin_tx_rollup_node/daemon.ml | 9 ++++++++- src/proto_alpha/bin_tx_rollup_node/state.ml | 18 +++++++----------- src/proto_alpha/bin_tx_rollup_node/state.mli | 2 +- 5 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/proto_alpha/bin_tx_rollup_node/batcher.ml b/src/proto_alpha/bin_tx_rollup_node/batcher.ml index 5ecad17118dd..4465143ac978 100644 --- a/src/proto_alpha/bin_tx_rollup_node/batcher.ml +++ b/src/proto_alpha/bin_tx_rollup_node/batcher.ml @@ -31,11 +31,11 @@ module Tx_queue = Hash_queue.Make (L2_transaction.Hash) (L2_transaction) type state = { cctxt : Protocol_client_context.full; rollup : Tx_rollup.t; - parameters : Protocol.Tx_rollup_l2_apply.parameters; signer : signer; transactions : Tx_queue.t; mutable incr_context : Context.t; lock : Lwt_mutex.t; + l1_constants : Protocol.Alpha_context.Constants.parametric; } (* TODO/TORU Change me to correct value and have a configuration option *) @@ -177,7 +177,7 @@ let async_batch_and_inject ?at_least_one_full_batch state = let* _ = batch_and_inject ?at_least_one_full_batch state in return_unit -let init cctxt ~rollup ~signer index parameters = +let init cctxt ~rollup ~signer index l1_constants = let open Lwt_result_syntax in let*! incr_context = Context.init_context index in let+ signer = get_signer cctxt signer in @@ -187,10 +187,10 @@ let init cctxt ~rollup ~signer index parameters = cctxt = batcher_context cctxt; rollup; signer; - parameters; transactions = Tx_queue.create 500_000; incr_context; lock = Lwt_mutex.create (); + l1_constants; }) signer @@ -206,8 +206,15 @@ let register_transaction ?(eager_batch = false) ?(apply = true) state let prev_context = context in let* context = if apply then + let l2_parameters = + Tx_rollup_l2_apply. + { + tx_rollup_max_withdrawals_per_batch = + state.l1_constants.tx_rollup_max_withdrawals_per_batch; + } + in let* (new_context, result, _) = - L2_apply.Batch_V1.apply_batch context state.parameters batch + L2_apply.Batch_V1.apply_batch context l2_parameters batch in let open Tx_rollup_l2_apply.Message_result in let+ context = diff --git a/src/proto_alpha/bin_tx_rollup_node/batcher.mli b/src/proto_alpha/bin_tx_rollup_node/batcher.mli index 162958addbb8..d6ccc4396f8c 100644 --- a/src/proto_alpha/bin_tx_rollup_node/batcher.mli +++ b/src/proto_alpha/bin_tx_rollup_node/batcher.mli @@ -36,7 +36,7 @@ val init : rollup:Tx_rollup.t -> signer:string option -> Context.index -> - Protocol.Tx_rollup_l2_apply.parameters -> + Protocol.Alpha_context.Constants.parametric -> state option tzresult Lwt.t (** Updates the incremental context in the batcher's state. diff --git a/src/proto_alpha/bin_tx_rollup_node/daemon.ml b/src/proto_alpha/bin_tx_rollup_node/daemon.ml index cf2b6ab3eab9..ff66a286b47c 100644 --- a/src/proto_alpha/bin_tx_rollup_node/daemon.ml +++ b/src/proto_alpha/bin_tx_rollup_node/daemon.ml @@ -259,8 +259,15 @@ let process_messages_and_inboxes (state : State.t) ~(predecessor : L2block.t) | None -> checkout_context state predecessor.header.context | Some context -> return context in + let l2_parameters = + Protocol.Tx_rollup_l2_apply. + { + tx_rollup_max_withdrawals_per_batch = + state.l1_constants.tx_rollup_max_withdrawals_per_batch; + } + in let*! (context, inbox) = - interp_messages predecessor_context state.parameters messages cumulated_size + interp_messages predecessor_context l2_parameters messages cumulated_size in match inbox with | None -> diff --git a/src/proto_alpha/bin_tx_rollup_node/state.ml b/src/proto_alpha/bin_tx_rollup_node/state.ml index 52f46e0279d7..ed404efdf28b 100644 --- a/src/proto_alpha/bin_tx_rollup_node/state.ml +++ b/src/proto_alpha/bin_tx_rollup_node/state.ml @@ -48,9 +48,9 @@ type t = { mutable head : L2block.t; rollup_info : rollup_info; tezos_blocks_cache : Alpha_block_services.block_info Tezos_blocks_cache.t; - parameters : Protocol.Tx_rollup_l2_apply.parameters; operator : signer option; batcher_state : Batcher.state option; + l1_constants : Protocol.Alpha_context.Constants.parametric; } type 'block reorg = { @@ -404,16 +404,12 @@ let init_head (stores : Stores.t) context_index rollup rollup_info = let* ctxt = Context.init_context context_index in L2block.genesis_block ctxt rollup rollup_info.origination_block -let init_parameters cctxt = +let init_l1_constants cctxt = let open Lwt_result_syntax in - let* {parametric; _} = + let+ {parametric; _} = Protocol.Constants_services.all cctxt (cctxt#chain, cctxt#block) in - return - { - Protocol.Tx_rollup_l2_apply.tx_rollup_max_withdrawals_per_batch = - parametric.tx_rollup_max_withdrawals_per_batch; - } + parametric let init cctxt ~data_dir ?(readonly = false) ?rollup_genesis ~l2_blocks_cache_size ~operator rollup = @@ -428,9 +424,9 @@ let init cctxt ~data_dir ?(readonly = false) ?rollup_genesis |> lwt_map_error (function [] -> [] | trace :: _ -> trace) in let*! head = init_head stores context_index rollup rollup_info in - let* parameters = init_parameters cctxt in + let* l1_constants = init_l1_constants cctxt in let* batcher_state = - Batcher.init cctxt ~rollup ~signer:operator context_index parameters + Batcher.init cctxt ~rollup ~signer:operator context_index l1_constants in let* operator = get_signer cctxt operator in (* L1 blocks are cached to handle reorganizations efficiently *) @@ -442,7 +438,7 @@ let init cctxt ~data_dir ?(readonly = false) ?rollup_genesis head; rollup_info; tezos_blocks_cache; - parameters; operator; batcher_state; + l1_constants; } diff --git a/src/proto_alpha/bin_tx_rollup_node/state.mli b/src/proto_alpha/bin_tx_rollup_node/state.mli index 21fb0a6b762c..29f9b5d5bbe5 100644 --- a/src/proto_alpha/bin_tx_rollup_node/state.mli +++ b/src/proto_alpha/bin_tx_rollup_node/state.mli @@ -51,9 +51,9 @@ type t = private { mutable head : L2block.t; rollup_info : rollup_info; tezos_blocks_cache : Alpha_block_services.block_info Tezos_blocks_cache.t; - parameters : Protocol.Tx_rollup_l2_apply.parameters; operator : signer option; batcher_state : Batcher.state option; + l1_constants : Protocol.Alpha_context.Constants.parametric; } (** Type of chain reorganizations. *) -- GitLab From 3c99a5fdb83a462057a78b0a780d402c2c764e46 Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Thu, 31 Mar 2022 15:28:28 +0200 Subject: [PATCH 05/13] Tx_rollup,Node: interpreter for inboxes with proofs --- src/proto_alpha/bin_tx_rollup_node/context.ml | 19 ++- src/proto_alpha/bin_tx_rollup_node/daemon.ml | 45 ++------ src/proto_alpha/bin_tx_rollup_node/inbox.ml | 24 +++- src/proto_alpha/bin_tx_rollup_node/inbox.mli | 10 +- .../bin_tx_rollup_node/interpreter.ml | 109 ++++++++++++++++++ .../bin_tx_rollup_node/interpreter.mli | 42 +++++++ 6 files changed, 203 insertions(+), 46 deletions(-) create mode 100644 src/proto_alpha/bin_tx_rollup_node/interpreter.ml create mode 100644 src/proto_alpha/bin_tx_rollup_node/interpreter.mli diff --git a/src/proto_alpha/bin_tx_rollup_node/context.ml b/src/proto_alpha/bin_tx_rollup_node/context.ml index 8e9ee91277bb..1beb8b9ee48a 100644 --- a/src/proto_alpha/bin_tx_rollup_node/context.ml +++ b/src/proto_alpha/bin_tx_rollup_node/context.ml @@ -155,15 +155,17 @@ module Prover_context = Protocol.Tx_rollup_l2_context.Make (Prover_storage) type 'a produce_proof_result = {tree : tree; result : 'a} +let get_tree ctxt = + let open Lwt_result_syntax in + let*! tree_opt = find_tree ctxt [] in + match tree_opt with + | Some tree -> return tree + | None -> fail [Error.Tx_rollup_tree_not_found] + let produce_proof ctxt f = let open Lwt_result_syntax in let index = index ctxt in - let* tree = - let*! tree_opt = find_tree ctxt [] in - match tree_opt with - | Some tree -> return tree - | None -> fail [Error.Tx_rollup_tree_not_found] - in + let* tree = get_tree ctxt in let* kinded_key = match Tree.kinded_key tree with | Some kinded_key -> return kinded_key @@ -178,6 +180,11 @@ let produce_proof ctxt f = let hash_tree = Tree.hash +let tree_hash_of_context ctxt = + let open Lwt_result_syntax in + let+ tree = get_tree ctxt in + hash_tree tree + let add_tree ctxt tree = let open Lwt_syntax in let* ctxt = add_tree ctxt [] tree in diff --git a/src/proto_alpha/bin_tx_rollup_node/daemon.ml b/src/proto_alpha/bin_tx_rollup_node/daemon.ml index ff66a286b47c..1edc9c600332 100644 --- a/src/proto_alpha/bin_tx_rollup_node/daemon.ml +++ b/src/proto_alpha/bin_tx_rollup_node/daemon.ml @@ -38,37 +38,6 @@ let checkout_context (state : State.t) ctxt_hash = let+ context = Context.checkout state.context_index ctxt_hash in Option.to_result ~none:[Tx_rollup_cannot_checkout_context ctxt_hash] context -let interp_messages ctxt parameters messages cumulated_size = - let open Lwt_syntax in - let+ (ctxt, _ctxt_hash, rev_contents) = - List.fold_left_s - (fun (ctxt, ctxt_hash, acc) message -> - let+ apply_res = L2_apply.apply_message ctxt parameters message in - let (ctxt, ctxt_hash, result) = - match apply_res with - | Ok (ctxt, result) -> - (* The message was successfully interpreted but the status in - [result] may indicate that the application failed. The context - may have been modified with e.g. updated counters. *) - (ctxt, Context.hash ctxt, Inbox.Interpreted result) - | Error err -> - (* The message was discarded before attempting to interpret it. The - context is not modified. For instance if a batch is unparsable, - or the BLS signature is incorrect, or a counter is wrong, etc. *) - (ctxt, ctxt_hash, Inbox.Discarded err) - in - let inbox_message = {Inbox.message; result; context_hash = ctxt_hash} in - (ctxt, ctxt_hash, inbox_message :: acc)) - (ctxt, Context.hash ctxt, []) - messages - in - match rev_contents with - | [] -> (ctxt, None) - | _ -> - let contents = List.rev rev_contents in - let inbox = Inbox.{contents; cumulated_size} in - (ctxt, Some inbox) - let parse_tx_rollup_l2_address : Script.node -> Protocol.Tx_rollup_l2_address.Indexable.value tzresult = let open Protocol in @@ -266,14 +235,20 @@ let process_messages_and_inboxes (state : State.t) ~(predecessor : L2block.t) state.l1_constants.tx_rollup_max_withdrawals_per_batch; } in - let*! (context, inbox) = - interp_messages predecessor_context l2_parameters messages cumulated_size + let* (context, contents) = + Interpreter.interpret_messages + predecessor_context + l2_parameters + ~rejection_max_proof_size: + state.l1_constants.tx_rollup_rejection_max_proof_size + messages in - match inbox with + match contents with | None -> (* No inbox at this block *) return (predecessor, predecessor_context) - | Some inbox -> + | Some contents -> + let inbox = Inbox.{contents; cumulated_size} in let*! context_hash = Context.commit context in let level = match predecessor.header.level with diff --git a/src/proto_alpha/bin_tx_rollup_node/inbox.ml b/src/proto_alpha/bin_tx_rollup_node/inbox.ml index 24ac43b07b76..661e86e4cb72 100644 --- a/src/proto_alpha/bin_tx_rollup_node/inbox.ml +++ b/src/proto_alpha/bin_tx_rollup_node/inbox.ml @@ -32,10 +32,15 @@ type message_result = | Interpreted of Tx_rollup_l2_apply.Message_result.t | Discarded of tztrace +type l2_context_hash = { + irmin_hash : Tx_rollup_l2_context_hash.t; + tree_hash : Context_hash.t; +} + type message = { message : Tx_rollup_message.t; result : message_result; - context_hash : Tx_rollup_l2_context_hash.t; + l2_context_hash : l2_context_hash; } type t = {contents : message list; cumulated_size : int} @@ -59,15 +64,26 @@ let message_result_encoding = (fun e -> Discarded e); ] +let l2_context_hash_encoding = + let open Data_encoding in + conv + (fun {irmin_hash; tree_hash} -> (irmin_hash, tree_hash)) + (fun (irmin_hash, tree_hash) -> {irmin_hash; tree_hash}) + (obj2 + (req "irmin_hash" Tx_rollup_l2_context_hash.encoding) + (req "tree_hash" Context_hash.encoding)) + let message_encoding = let open Data_encoding in conv - (fun {message; result; context_hash} -> (message, result, context_hash)) - (fun (message, result, context_hash) -> {message; result; context_hash}) + (fun {message; result; l2_context_hash} -> + (message, result, l2_context_hash)) + (fun (message, result, l2_context_hash) -> + {message; result; l2_context_hash}) (obj3 (req "message" Tx_rollup_message.encoding) (req "result" message_result_encoding) - (req "context_hash" Tx_rollup_l2_context_hash.encoding)) + (req "l2_context_hash" l2_context_hash_encoding)) let encoding = let open Data_encoding in diff --git a/src/proto_alpha/bin_tx_rollup_node/inbox.mli b/src/proto_alpha/bin_tx_rollup_node/inbox.mli index 338aab426ae2..10d70bcd4e77 100644 --- a/src/proto_alpha/bin_tx_rollup_node/inbox.mli +++ b/src/proto_alpha/bin_tx_rollup_node/inbox.mli @@ -38,12 +38,20 @@ type message_result = | Discarded of tztrace (** The message was discarded because it could not be interpreted *) +type l2_context_hash = { + irmin_hash : Tx_rollup_l2_context_hash.t; + (** The context hash of the commited context, used for checkout *) + tree_hash : Context_hash.t; + (** The tree hash is the hash of the underlying tree in the {!Context}, + used to produce proofs *) +} + (** Type of inbox message with the context hash resulting from the application of the message *) type message = { message : Tx_rollup_message.t; result : message_result; - context_hash : Tx_rollup_l2_context_hash.t; + l2_context_hash : l2_context_hash; } (** The type representing an inbox whose contents are the messages and not the diff --git a/src/proto_alpha/bin_tx_rollup_node/interpreter.ml b/src/proto_alpha/bin_tx_rollup_node/interpreter.ml new file mode 100644 index 000000000000..ae4d2f00171e --- /dev/null +++ b/src/proto_alpha/bin_tx_rollup_node/interpreter.ml @@ -0,0 +1,109 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs, *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +type error += Tx_rollup_message_proof_too_large of {limit : int; actual : int} + +let () = + register_error_kind + ~id:"tx_rollup.node.message_proof_too_large" + ~title:"Message's application proof is too large" + ~description: + "The proof associated to the application of the message is too large" + ~pp:(fun ppf (limit, actual) -> + Format.fprintf + ppf + "The message produces a proof of size %d where the protocol limit is \ + %d. It will be rejected by the protocol." + limit + actual) + `Permanent + Data_encoding.(obj2 (req "limit" int31) (req "actual" int31)) + (function + | Tx_rollup_message_proof_too_large {limit; actual} -> Some (limit, actual) + | _ -> None) + (fun (limit, actual) -> Tx_rollup_message_proof_too_large {limit; actual}) + +(** Interpret a message in the context. The function needs to be synchronised + with the [Tx_rollup_l2_verifier] module of the protocol, in particular + the proof size boundaries. *) +let interpret_message ~rejection_max_proof_size ctxt l2_parameters message = + let open Lwt_result_syntax in + let* (proof, res) = Prover_apply.apply_message ctxt l2_parameters message in + let proof_size = Prover_apply.proof_size proof in + let result = + if proof_size > rejection_max_proof_size then + (* The proof is too large, we can not commit this state. The + result is discarded. *) + Inbox.Discarded + [ + Tx_rollup_message_proof_too_large + {limit = rejection_max_proof_size; actual = proof_size}; + ] + else res.Context.result + in + return (res.Context.tree, result) + +let interpret_messages ~rejection_max_proof_size ctxt l2_parameters messages = + let open Lwt_result_syntax in + let ctxt_hash = Context.hash ctxt in + let* tree_hash = Context.tree_hash_of_context ctxt in + let+ (ctxt, _ctxt_hash, _tree_hash, rev_contents) = + List.fold_left_es + (fun (ctxt, ctxt_hash, tree_hash, acc) message -> + let* (tree, result) = + interpret_message ~rejection_max_proof_size ctxt l2_parameters message + in + let* (ctxt, ctxt_hash, tree_hash) = + match result with + | Inbox.Interpreted _ -> + (* The message was successfully interpreted but the status in + [result] may indicate that the application failed. The context + may have been modified with e.g. updated counters. *) + let tree_hash = Context.hash_tree tree in + let*! (ctxt, ctxt_hash) = Context.add_tree ctxt tree in + return (ctxt, ctxt_hash, tree_hash) + | Inbox.Discarded _ -> + (* The message was discarded before attempting to interpret it. The + context is not modified. For instance if a batch is unparsable, + or the BLS signature is incorrect, or a counter is wrong, etc. *) + return (ctxt, ctxt_hash, tree_hash) + in + let inbox_message = + Inbox. + { + message; + result; + l2_context_hash = {irmin_hash = ctxt_hash; tree_hash}; + } + in + return (ctxt, ctxt_hash, tree_hash, inbox_message :: acc)) + (ctxt, ctxt_hash, tree_hash, []) + messages + in + match rev_contents with + | [] -> (ctxt, None) + | _ -> + let contents = List.rev rev_contents in + (ctxt, Some contents) diff --git a/src/proto_alpha/bin_tx_rollup_node/interpreter.mli b/src/proto_alpha/bin_tx_rollup_node/interpreter.mli new file mode 100644 index 000000000000..14c0b5589993 --- /dev/null +++ b/src/proto_alpha/bin_tx_rollup_node/interpreter.mli @@ -0,0 +1,42 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +(** Error result when the message's application produces a too large proof. + It overrides the layer2 apply message result. *) +type error += Tx_rollup_message_proof_too_large of {limit : int; actual : int} + +(** Interpreting the [messages] in the context. + + It uses internally the {!Prover_apply} to produce a proof associated + to the interpretation of each message. In the case where the proof is + larger than the configuration limit, the message's interpretation is + discarded alongside the modified context. +*) +val interpret_messages : + rejection_max_proof_size:int -> + Context.context -> + Protocol.Tx_rollup_l2_apply.parameters -> + Protocol.Alpha_context.Tx_rollup_message.t trace -> + (Context.context * Inbox.message list option) tzresult Lwt.t -- GitLab From 1908dc9b74fa88043de8a9d21480d8c9a0f9fd18 Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Thu, 31 Mar 2022 15:31:52 +0200 Subject: [PATCH 06/13] Tx_rollup,Node: batch transactions until batch is invalid --- src/proto_alpha/bin_tx_rollup_node/batcher.ml | 134 ++++++++++++++---- .../bin_tx_rollup_node/batcher.mli | 10 +- src/proto_alpha/bin_tx_rollup_node/event.ml | 9 +- .../bin_tx_rollup_node/interpreter.ml | 15 ++ .../bin_tx_rollup_node/interpreter.mli | 19 +++ 5 files changed, 148 insertions(+), 39 deletions(-) diff --git a/src/proto_alpha/bin_tx_rollup_node/batcher.ml b/src/proto_alpha/bin_tx_rollup_node/batcher.ml index 4465143ac978..7e1b867c24be 100644 --- a/src/proto_alpha/bin_tx_rollup_node/batcher.ml +++ b/src/proto_alpha/bin_tx_rollup_node/batcher.ml @@ -38,9 +38,6 @@ type state = { l1_constants : Protocol.Alpha_context.Constants.parametric; } -(* TODO/TORU Change me to correct value and have a configuration option *) -let max_batch_transactions = 10 - (* TODO/TORU Change me with bound on size and have a configuration option *) let max_number_of_batches = 10 @@ -130,51 +127,126 @@ let inject_batches state batches = in inject_operations state operations -let get_batches state = - let open Result_syntax in - let transactions = - Tx_queue.peek_at_most - state.transactions - (max_batch_transactions * max_number_of_batches) +(** [is_batch_valid] returns whether the batch is valid or not based on + two criterias: + + The proof produced by the batch interpretation must be smaller than + [l1_constants.tx_rollup_rejection_max_proof_size]. Otherwise, the associated + commitment can be rejected because of the size. + + The batch exceeds the [l1_constants.tx_rollup_hard_size_limit_per_message], + the submit batch operation will fail. +*) +let is_batch_valid ctxt + (l1_constants : Protocol.Alpha_context.Constants.parametric) batch = + let open Lwt_result_syntax in + (* The batch is ok if: + 1. The proof is small enough + 2. The batch is small enough *) + let batch_size_ok = + let size = Data_encoding.Binary.length Tx_rollup_l2_batch.encoding batch in + size <= l1_constants.tx_rollup_hard_size_limit_per_message in - let rec loop acc = function - | [] -> ok (List.rev acc) - | trs -> - let (trs, rest) = List.split_n max_batch_transactions trs in - let* batch = L2_transaction.batch trs in - loop (batch :: acc) rest + if batch_size_ok then + let l2_parameters = + Tx_rollup_l2_apply. + { + tx_rollup_max_withdrawals_per_batch = + l1_constants.tx_rollup_max_withdrawals_per_batch; + } + in + let*! res_interp = + Interpreter.interpret_batch + ctxt + l2_parameters + ~rejection_max_proof_size: + l1_constants.tx_rollup_rejection_max_proof_size + batch + in + let b_proof_size = Result.is_ok res_interp in + return b_proof_size + else return_false + +let get_batches ctxt l1_constants queue = + let open Lwt_result_syntax in + let exception + Batches_finished of { + rev_batches : + (Indexable.unknown, Indexable.unknown) Tx_rollup_l2_batch.t list; + to_remove : L2_transaction.hash list; + } in - let+ batches = loop [] transactions in - (batches, transactions) + try + let* (rev_batches, rev_current_trs, to_remove) = + Tx_queue.fold_es + (fun tr_hash tr (batches, rev_current_trs, to_remove) -> + let new_trs = tr :: rev_current_trs in + let*? batch = L2_transaction.batch (List.rev new_trs) in + let* b = is_batch_valid ctxt l1_constants batch in + if b then return (batches, new_trs, tr_hash :: to_remove) + else + match rev_current_trs with + | [_] -> + (* If only one transaction makes the batch invalid, we remove it + from the current transactions and it'll be removed later. *) + let*! () = Event.(emit Batcher.invalid_transaction) tr in + return (batches, [], tr_hash :: to_remove) + | _ -> + let*? batch = L2_transaction.batch (List.rev rev_current_trs) in + let new_batches = batch :: batches in + if + List.compare_length_with new_batches max_number_of_batches + >= 0 + then + (* We created enough batches, we exit the loop *) + raise + (Batches_finished {rev_batches = new_batches; to_remove}) + else + (* We add the batch to the accumulator and we go on. *) + let*? batch = L2_transaction.batch [tr] in + let* b = is_batch_valid ctxt l1_constants batch in + if b then return (new_batches, [tr], tr_hash :: to_remove) + else + let*! () = Event.(emit Batcher.invalid_transaction) tr in + return (new_batches, [], tr_hash :: to_remove)) + queue + ([], [], []) + in + let*? batches = + let open Result_syntax in + if rev_current_trs <> [] then + let+ last_batch = L2_transaction.batch (List.rev rev_current_trs) in + List.rev (last_batch :: rev_batches) + else return (List.rev rev_batches) + in + return (batches, to_remove) + with Batches_finished {rev_batches; to_remove} -> + return (List.rev rev_batches, to_remove) -let batch_and_inject ?(at_least_one_full_batch = false) state = +let batch_and_inject state = let open Lwt_result_syntax in - let*? (batches, to_remove) = get_batches state in - let*! () = - Event.(emit Batcher.batch) (List.length batches, List.length to_remove) + let* (batches, to_remove) = + get_batches state.incr_context state.l1_constants state.transactions in match batches with | [] -> return_none - | Tx_rollup_l2_batch.(V1 {V1.contents; _}) :: _ - when at_least_one_full_batch - && List.compare_length_with contents max_batch_transactions < 0 -> - (* The first batch is not full, and we requested it to be *) - let*! () = Event.(emit Batcher.no_full_batch) () in - return_none | _ -> + let*! () = + Event.(emit Batcher.batch) (List.length batches, List.length to_remove) + in let*! () = Event.(emit Batcher.inject) () in let* oph = inject_batches state batches in let*! () = Event.(emit Batcher.injection_success) oph in List.iter - (fun tr -> Tx_queue.remove state.transactions (L2_transaction.hash tr)) + (fun tr_hash -> Tx_queue.remove state.transactions tr_hash) to_remove ; return_some oph -let async_batch_and_inject ?at_least_one_full_batch state = +let async_batch_and_inject state = Lwt.async @@ fun () -> let open Lwt_syntax in (* let* _ = Lwt_unix.sleep 2. in *) - let* _ = batch_and_inject ?at_least_one_full_batch state in + let* _ = batch_and_inject state in return_unit let init cctxt ~rollup ~signer index l1_constants = @@ -239,5 +311,5 @@ let register_transaction ?(eager_batch = false) ?(apply = true) state if eager_batch then (* TODO/TORU: find better solution as this reduces throughput when we have a single key to sign/inject. *) - async_batch_and_inject ~at_least_one_full_batch:true state ; + async_batch_and_inject state ; return tr_hash diff --git a/src/proto_alpha/bin_tx_rollup_node/batcher.mli b/src/proto_alpha/bin_tx_rollup_node/batcher.mli index d6ccc4396f8c..9d05ed0e3e64 100644 --- a/src/proto_alpha/bin_tx_rollup_node/batcher.mli +++ b/src/proto_alpha/bin_tx_rollup_node/batcher.mli @@ -68,13 +68,9 @@ val register_transaction : (** Create L2 batches of operations from the queue and pack them in an L1 batch operation. The batch operation is injected on the Tezos node by the signer. If the injection to L1 fails, the transactions are not removed from - the queue. Nothing is injected if [at_least_one_full_batch] is [true] (by - default [false]) and there isn't at least a full batch to inject. *) -val batch_and_inject : - ?at_least_one_full_batch:bool -> - state -> - Operation_hash.t option tzresult Lwt.t + the queue. *) +val batch_and_inject : state -> Operation_hash.t option tzresult Lwt.t (** Same as [batch_and_inject] but asynchronous. In particular, the potential failures are not reported here. *) -val async_batch_and_inject : ?at_least_one_full_batch:bool -> state -> unit +val async_batch_and_inject : state -> unit diff --git a/src/proto_alpha/bin_tx_rollup_node/event.ml b/src/proto_alpha/bin_tx_rollup_node/event.ml index 63d8b0a9cf0a..2c1f0323f8e6 100644 --- a/src/proto_alpha/bin_tx_rollup_node/event.ml +++ b/src/proto_alpha/bin_tx_rollup_node/event.ml @@ -220,7 +220,7 @@ module Batcher = struct ~section ~name:"inject" ~msg:"Injecting batches on Tezos node" - ~level:Info + ~level:Notice () let injection_success = @@ -230,4 +230,11 @@ module Batcher = struct ~msg:"batches were successfully injected in operation {oph}" ~level:Notice ("oph", Operation_hash.encoding) + + let invalid_transaction = + declare_1 + ~section + ~name:"invalid_transaction" + ~msg:"a batch with this only transaction is invalid: {tr}" + ("tr", L2_transaction.encoding) end diff --git a/src/proto_alpha/bin_tx_rollup_node/interpreter.ml b/src/proto_alpha/bin_tx_rollup_node/interpreter.ml index ae4d2f00171e..797da4adff83 100644 --- a/src/proto_alpha/bin_tx_rollup_node/interpreter.ml +++ b/src/proto_alpha/bin_tx_rollup_node/interpreter.ml @@ -107,3 +107,18 @@ let interpret_messages ~rejection_max_proof_size ctxt l2_parameters messages = | _ -> let contents = List.rev rev_contents in (ctxt, Some contents) + +let interpret_batch ~rejection_max_proof_size ctxt l2_parameters batch = + let open Lwt_result_syntax in + let batch_bytes = + Data_encoding.Binary.to_string_exn + Protocol.Tx_rollup_l2_batch.encoding + batch + in + let (message, _) = + Protocol.Alpha_context.Tx_rollup_message.make_batch batch_bytes + in + let* (_tree, result) = + interpret_message ~rejection_max_proof_size ctxt l2_parameters message + in + match result with Inbox.Discarded trace -> fail trace | _ -> return () diff --git a/src/proto_alpha/bin_tx_rollup_node/interpreter.mli b/src/proto_alpha/bin_tx_rollup_node/interpreter.mli index 14c0b5589993..77f38c8287a5 100644 --- a/src/proto_alpha/bin_tx_rollup_node/interpreter.mli +++ b/src/proto_alpha/bin_tx_rollup_node/interpreter.mli @@ -40,3 +40,22 @@ val interpret_messages : Protocol.Tx_rollup_l2_apply.parameters -> Protocol.Alpha_context.Tx_rollup_message.t trace -> (Context.context * Inbox.message list option) tzresult Lwt.t + +(** Interpreting the [batch] in the context. + + Similarly to {!interp_messages}, it uses internally the {!Prover_apply}. + However, the function fails if the interpretation produces a proof + that is larger than the configuration limit. + We here want to check only if the batch is interpretable, the modified + tree is discarded. + + TODO/TORU: maybe we could check the results for each transaction in the batch +*) +val interpret_batch : + rejection_max_proof_size:int -> + Context.context -> + Protocol.Tx_rollup_l2_apply.parameters -> + ( Protocol.Indexable.unknown, + Protocol.Indexable.unknown ) + Protocol.Tx_rollup_l2_batch.t -> + unit tzresult Lwt.t -- GitLab From 697e66e8bfdb24faf63fad7def44eeeb99a5b555 Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Wed, 30 Mar 2022 15:26:55 +0200 Subject: [PATCH 07/13] Plugin,Tezt: RPC message_result_hash and withdraw_list_hash --- src/proto_alpha/lib_plugin/plugin.ml | 40 +++++++++++++++++++++++++++- tezt/lib_tezos/RPC.ml | 36 +++++++++++++++++++++++++ tezt/lib_tezos/RPC.mli | 20 ++++++++++++++ tezt/lib_tezos/rollup.ml | 20 ++++++++++++++ tezt/lib_tezos/rollup.mli | 13 +++++++++ 5 files changed, 128 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/lib_plugin/plugin.ml b/src/proto_alpha/lib_plugin/plugin.ml index f8608e442768..83cf62fc670a 100644 --- a/src/proto_alpha/lib_plugin/plugin.ml +++ b/src/proto_alpha/lib_plugin/plugin.ml @@ -3083,6 +3083,32 @@ module RPC = struct ~output: (obj1 (req "path" Tx_rollup_commitment.Merkle.path_encoding)) RPC_path.(path / "merkle_tree_path") + + let message_result_hash = + RPC_service.post_service + ~description:"Compute the message result hash" + ~query:RPC_query.empty + ~input: + (obj2 + (req "context_hash" Context_hash.encoding) + (req + "withdraw_list_hash" + Tx_rollup_withdraw_list_hash.encoding)) + ~output:(obj1 (req "hash" Tx_rollup_message_result_hash.encoding)) + RPC_path.(path / "message_result_hash") + end + + module Withdraw = struct + let path = RPC_path.(path / "withdraw") + + let withdraw_list_hash = + RPC_service.post_service + ~description:"Compute the hash of a withdraw list" + ~query:RPC_query.empty + ~input: + (obj1 (req "withdraw_list" (list Tx_rollup_withdraw.encoding))) + ~output:(obj1 (req "hash" Tx_rollup_withdraw_list_hash.encoding)) + RPC_path.(path / "withdraw_list_hash") end end end @@ -3146,7 +3172,19 @@ module RPC = struct (fun () (message_result_hashes, position) -> let open Tx_rollup_commitment.Merkle in let tree = List.fold_left snoc nil message_result_hashes in - Lwt.return (compute_path tree position)) + Lwt.return (compute_path tree position)) ; + Registration.register0_noctxt + ~chunked:true + S.Tx_rollup.Commitment.message_result_hash + (fun () (context_hash, withdraw_list_hash) -> + return + (Tx_rollup_message_result_hash.hash_uncarbonated + {context_hash; withdraw_list_hash})) ; + Registration.register0_noctxt + ~chunked:true + S.Tx_rollup.Withdraw.withdraw_list_hash + (fun () withdrawals -> + return (Tx_rollup_withdraw_list_hash.hash_uncarbonated withdrawals)) module Manager = struct let[@coq_axiom_with_reason "cast on e"] operations ctxt block ~branch diff --git a/tezt/lib_tezos/RPC.ml b/tezt/lib_tezos/RPC.ml index 471331d81a21..1a364cd9c6cb 100644 --- a/tezt/lib_tezos/RPC.ml +++ b/tezt/lib_tezos/RPC.ml @@ -684,6 +684,42 @@ module Tx_rollup = struct ] in Client.Spawn.rpc ?endpoint ?hooks ~data POST path client + + let message_result_hash ?endpoint ?hooks ?(chain = "main") + ?(block = "head") ~data client = + let path = + [ + "chains"; + chain; + "blocks"; + block; + "helpers"; + "forge"; + "tx_rollup"; + "commitment"; + "message_result_hash"; + ] + in + Client.Spawn.rpc ?endpoint ?hooks ~data POST path client + end + + module Withdraw = struct + let withdraw_list_hash ?endpoint ?hooks ?(chain = "main") + ?(block = "head") ~data client = + let path = + [ + "chains"; + chain; + "blocks"; + block; + "helpers"; + "forge"; + "tx_rollup"; + "withdraw"; + "withdraw_list_hash"; + ] + in + Client.Spawn.rpc ?endpoint ?hooks ~data POST path client end end end diff --git a/tezt/lib_tezos/RPC.mli b/tezt/lib_tezos/RPC.mli index e7bf6e1f34de..fba25a15791b 100644 --- a/tezt/lib_tezos/RPC.mli +++ b/tezt/lib_tezos/RPC.mli @@ -969,6 +969,26 @@ module Tx_rollup : sig data:JSON.u -> Client.t -> JSON.t Process.runnable + + val message_result_hash : + ?endpoint:Client.endpoint -> + ?hooks:Process.hooks -> + ?chain:string -> + ?block:string -> + data:JSON.u -> + Client.t -> + JSON.t Process.runnable + end + + module Withdraw : sig + val withdraw_list_hash : + ?endpoint:Client.endpoint -> + ?hooks:Process.hooks -> + ?chain:string -> + ?block:string -> + data:JSON.u -> + Client.t -> + JSON.t Process.runnable end end end diff --git a/tezt/lib_tezos/rollup.ml b/tezt/lib_tezos/rollup.ml index 4a7d58511347..dc6e11099189 100644 --- a/tezt/lib_tezos/rollup.ml +++ b/tezt/lib_tezos/rollup.ml @@ -232,6 +232,26 @@ module Tx_rollup = struct in map_runnable parse runnable + let withdraw_list_hash ?hooks ~withdrawals client = + let parse json = JSON.(json |-> "hash" |> as_string) in + let data = + `O [("withdraw_list", `A (List.map (fun x -> `String x) withdrawals))] + in + RPC.Tx_rollup.Forge.Withdraw.withdraw_list_hash ?hooks ~data client + |> map_runnable parse + + let message_result_hash ?hooks ~context_hash ~withdraw_list_hash client = + let parse json = JSON.(json |-> "hash" |> as_string) in + let data = + `O + [ + ("context_hash", `String context_hash); + ("withdraw_list_hash", `String withdraw_list_hash); + ] + in + RPC.Tx_rollup.Forge.Commitment.message_result_hash ?hooks ~data client + |> map_runnable parse + let compute_inbox_from_messages ?hooks messages client = let* message_hashes = Lwt_list.map_p diff --git a/tezt/lib_tezos/rollup.mli b/tezt/lib_tezos/rollup.mli index b7bba1130e46..0965ccdf02f2 100644 --- a/tezt/lib_tezos/rollup.mli +++ b/tezt/lib_tezos/rollup.mli @@ -124,6 +124,19 @@ module Tx_rollup : sig Client.t -> JSON.t Process.runnable + val withdraw_list_hash : + ?hooks:Process.hooks -> + withdrawals:string list -> + Client.t -> + string Process.runnable + + val message_result_hash : + ?hooks:Process.hooks -> + context_hash:string -> + withdraw_list_hash:string -> + Client.t -> + string Process.runnable + val compute_inbox_from_messages : ?hooks:Process.hooks -> message list -> Client.t -> inbox Lwt.t -- GitLab From 86f04e2a69011ecb548518f99e5f661dccfa7a91 Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Wed, 30 Mar 2022 18:54:20 +0200 Subject: [PATCH 08/13] Tx_rollup,Node: RPC for merkle proofs --- src/proto_alpha/bin_tx_rollup_node/RPC.ml | 122 +++++++++++++++---- src/proto_alpha/bin_tx_rollup_node/error.ml | 15 +++ src/proto_alpha/bin_tx_rollup_node/error.mli | 3 + 3 files changed, 116 insertions(+), 24 deletions(-) diff --git a/src/proto_alpha/bin_tx_rollup_node/RPC.ml b/src/proto_alpha/bin_tx_rollup_node/RPC.ml index 3d0d85c702cf..3a1df6bd89f7 100644 --- a/src/proto_alpha/bin_tx_rollup_node/RPC.ml +++ b/src/proto_alpha/bin_tx_rollup_node/RPC.ml @@ -34,6 +34,26 @@ type block_id = type context_id = [block_id | `Context of Tx_rollup_l2_context_hash.t] +let context_of_l2_block state b = + Stores.L2_block_store.context state.State.stores.blocks b + +let context_of_block_id state block_id = + let open Lwt_syntax in + match block_id with + | `L2_block b -> context_of_l2_block state b + | `Tezos_block b -> ( + let* b = State.get_tezos_l2_block_hash state b in + match b with None -> return_none | Some b -> context_of_l2_block state b) + | `Head -> return_some (State.get_head state).header.context + | `Level l -> ( + let* b = State.get_level state l in + match b with None -> return_none | Some b -> context_of_l2_block state b) + +let context_of_id state context_id = + match context_id with + | #block_id as block_id -> context_of_block_id state block_id + | `Context c -> Lwt.return_some c + module Arg = struct let indexable ~kind ~construct ~destruct = let construct i = @@ -182,6 +202,14 @@ module Block = struct | `Head -> return_some (State.get_head state) | `Level l -> State.get_level_l2_block state l + let proof = + RPC_service.get_service + ~description: + "Get the merkle proof for a given message for a given block inbox" + ~query:RPC_query.empty + ~output:Data_encoding.(option Protocol.Tx_rollup_l2_proof.encoding) + RPC_path.(path / "proof" / "message" /: RPC_arg.int) + let () = register block @@ fun (state, block_id) () () -> let*! block = block_of_id state block_id in @@ -206,6 +234,76 @@ module Block = struct return None | _ -> return (Some block.inbox)) + let () = + register proof @@ fun ((state, block_id), message_pos) () () -> + let*! block = block_of_id state block_id in + match block with + | None -> return_none + | Some block -> ( + match block_id with + | `Tezos_block b when Block_hash.(block.header.tezos_block <> b) -> + (* Tezos block has no l2 inbox *) + failwith "The tezos block (%a) has not l2 inbox" Block_hash.pp b + | _ -> + let open Inbox in + let inbox = block.inbox in + let index = state.context_index in + let* (prev_ctxt, message) = + if message_pos = 0 then + (* We must take the block predecessor context *) + let*? message = + match List.nth_opt inbox.contents message_pos with + | Some x -> ok x + | None -> + error + (Error.Tx_rollup_invalid_message_position_in_inbox + message_pos) + in + let pred_block_hash = block.header.predecessor in + let*! pred_block = State.get_block state pred_block_hash in + match pred_block with + | None -> + failwith + "The block (%a) does not have a predecessor" + L2block.Hash.pp + block.hash + | Some block -> ( + let hash = block.hash in + let*! ctxt_hash = context_of_l2_block state hash in + match ctxt_hash with + | Some ctxt_hash -> + let*! ctxt = Context.checkout_exn index ctxt_hash in + return (ctxt, message) + | None -> + failwith + "The block can not be retrieved from the hash %a" + L2block.Hash.pp + hash) + else + let*? (pred_message, message) = + match List.drop_n (message_pos - 1) inbox.contents with + | pred_message :: message :: _ -> ok (pred_message, message) + | _ -> + error + (Error.Tx_rollup_invalid_message_position_in_inbox + message_pos) + in + let ctxt_hash = pred_message.l2_context_hash.irmin_hash in + let*! ctxt = Context.checkout_exn index ctxt_hash in + return (ctxt, message) + in + let l2_parameters = + Protocol.Tx_rollup_l2_apply. + { + tx_rollup_max_withdrawals_per_batch = + state.l1_constants.tx_rollup_max_withdrawals_per_batch; + } + in + let* (proof, _) = + Prover_apply.apply_message prev_ctxt l2_parameters message.message + in + return_some proof) + let build_directory state = !directory |> RPC_directory.map (fun ((), block_id) -> Lwt.return (state, block_id)) @@ -404,30 +502,6 @@ module Context_RPC = struct | None -> return None | Some {public_key; _} -> return (Some public_key)) - let context_of_l2_block state b = - Stores.L2_block_store.context state.State.stores.blocks b - - let context_of_block_id state block_id = - let open Lwt_syntax in - match block_id with - | `L2_block b -> context_of_l2_block state b - | `Tezos_block b -> ( - let* b = State.get_tezos_l2_block_hash state b in - match b with - | None -> return_none - | Some b -> context_of_l2_block state b) - | `Head -> return_some (State.get_head state).header.context - | `Level l -> ( - let* b = State.get_level state l in - match b with - | None -> return_none - | Some b -> context_of_l2_block state b) - - let context_of_id state context_id = - match context_id with - | #block_id as block_id -> context_of_block_id state block_id - | `Context c -> Lwt.return_some c - let build_directory state = !directory |> RPC_directory.map (fun ((), context_id) -> diff --git a/src/proto_alpha/bin_tx_rollup_node/error.ml b/src/proto_alpha/bin_tx_rollup_node/error.ml index 37e945ed0cd5..805925cb8f8c 100644 --- a/src/proto_alpha/bin_tx_rollup_node/error.ml +++ b/src/proto_alpha/bin_tx_rollup_node/error.ml @@ -311,3 +311,18 @@ let () = Data_encoding.empty (function Tx_rollup_tree_kinded_key_not_found -> Some () | _ -> None) (fun () -> Tx_rollup_tree_kinded_key_not_found) + +type error += Tx_rollup_invalid_message_position_in_inbox of int + +let () = + register_error_kind + ~id:"tx_rollup.node.invalid_message_position" + ~title:"Message position invalid in the inbox" + ~description:"The message position is invalid the inbox." + ~pp:(fun ppf i -> + Format.fprintf ppf "The message position %d is invalid in the inbox" i) + `Permanent + Data_encoding.(obj1 (req "message_position" int31)) + (function + | Tx_rollup_invalid_message_position_in_inbox i -> Some i | _ -> None) + (fun i -> Tx_rollup_invalid_message_position_in_inbox i) diff --git a/src/proto_alpha/bin_tx_rollup_node/error.mli b/src/proto_alpha/bin_tx_rollup_node/error.mli index 5dfcde03e74a..d40239e58458 100644 --- a/src/proto_alpha/bin_tx_rollup_node/error.mli +++ b/src/proto_alpha/bin_tx_rollup_node/error.mli @@ -81,3 +81,6 @@ type error += Tx_rollup_tree_not_found (** Error when the kinded key is not found in the tree. *) type error += Tx_rollup_tree_kinded_key_not_found + +(** Error when a message position does not exist in the inbox for the proof RPC *) +type error += Tx_rollup_invalid_message_position_in_inbox of int -- GitLab From ad2f4a46deb279b4b2de2550db475d8416a55f97 Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Fri, 1 Apr 2022 14:58:24 +0200 Subject: [PATCH 09/13] Tx_rollup,Tezt: move rollup_node rpcs to a temporary module --- tezt/lib_tezos/tx_rollup_node.ml | 74 +++++++++++++++++++++++++++++++ tezt/lib_tezos/tx_rollup_node.mli | 35 +++++++++++++++ tezt/tests/tx_rollup_node.ml | 74 +++++++------------------------ 3 files changed, 124 insertions(+), 59 deletions(-) diff --git a/tezt/lib_tezos/tx_rollup_node.ml b/tezt/lib_tezos/tx_rollup_node.ml index 31ff285bac4c..8787020460d3 100644 --- a/tezt/lib_tezos/tx_rollup_node.ml +++ b/tezt/lib_tezos/tx_rollup_node.ml @@ -227,3 +227,77 @@ let run node = "--data-dir"; node.persistent_state.data_dir; ] + +module Inbox = struct + type l2_context_hash = {irmin_hash : string; tree_hash : string} + + type message = { + message : JSON.t; + result : JSON.t; + l2_context_hash : l2_context_hash; + } + + type t = {contents : message list; cumulated_size : int} +end + +module Client = struct + let raw_tx_node_rpc node ~url = + let* rpc = RPC.Curl.get () in + match rpc with + | None -> assert false + | Some curl -> + let url = Printf.sprintf "%s/%s" (rpc_addr node) url in + curl ~url + + let get_inbox ~tx_node ~block = + let parse_l2_context_hash json = + let irmin_hash = JSON.(json |-> "irmin_hash" |> as_string) in + let tree_hash = JSON.(json |-> "tree_hash" |> as_string) in + Inbox.{irmin_hash; tree_hash} + in + let parse_message json = + let message = JSON.(json |-> "message") in + let result = JSON.(json |-> "result") in + let l2_context_hash = + parse_l2_context_hash JSON.(json |-> "l2_context_hash") + in + Inbox.{message; result; l2_context_hash} + in + let parse_json json = + let cumulated_size = JSON.(json |-> "cumulated_size" |> as_int) in + let contents = + JSON.(json |-> "contents" |> as_list) |> List.map parse_message + in + Inbox.{cumulated_size; contents} + in + let* json = raw_tx_node_rpc tx_node ~url:("block/" ^ block ^ "/inbox") in + return (parse_json json) + + let get_balance ~tx_node ~block ~ticket_id ~tz4_address = + let parse_json json = + match JSON.(json |> as_int_opt) with + | Some level -> level + | None -> + Test.fail "Cannot retrieve balance of tz4 address %s" tz4_address + in + let* json = + raw_tx_node_rpc + tx_node + ~url: + ("context/" ^ block ^ "/tickets/" ^ ticket_id ^ "/balance/" + ^ tz4_address) + in + return (parse_json json) + + let get_queue ~tx_node = raw_tx_node_rpc tx_node ~url:"queue" + + let get_transaction_in_queue ~tx_node txh = + raw_tx_node_rpc tx_node ~url:("queue/transaction/" ^ txh) + + let get_block ~tx_node ~block = raw_tx_node_rpc tx_node ~url:("block/" ^ block) + + let get_merkle_proof ~tx_node ~block ~message_pos = + raw_tx_node_rpc + tx_node + ~url:("block/" ^ block ^ "/proof/message/" ^ message_pos) +end diff --git a/tezt/lib_tezos/tx_rollup_node.mli b/tezt/lib_tezos/tx_rollup_node.mli index 01b3f9a51b97..18291d73a183 100644 --- a/tezt/lib_tezos/tx_rollup_node.mli +++ b/tezt/lib_tezos/tx_rollup_node.mli @@ -72,3 +72,38 @@ val terminate : ?kill:bool -> t -> unit Lwt.t (** Get the RPC address given as [--rpc-addr] to a node. *) val rpc_addr : t -> string + +module Inbox : sig + type l2_context_hash = {irmin_hash : string; tree_hash : string} + + type message = { + message : JSON.t; + result : JSON.t; + l2_context_hash : l2_context_hash; + } + + type t = {contents : message list; cumulated_size : int} +end + +(* FIXME/TORU: This is a temporary way of querying the node without + tx_rollup_client. This aims to be replaced as soon as possible by + the dedicated client's RPC. *) +module Client : sig + val get_inbox : tx_node:t -> block:string -> Inbox.t Lwt.t + + val get_balance : + tx_node:t -> + block:string -> + ticket_id:string -> + tz4_address:string -> + int Lwt.t + + val get_queue : tx_node:t -> JSON.t Lwt.t + + val get_transaction_in_queue : tx_node:t -> string -> JSON.t Lwt.t + + val get_block : tx_node:t -> block:string -> JSON.t Lwt.t + + val get_merkle_proof : + tx_node:t -> block:string -> message_pos:string -> JSON.t Lwt.t +end diff --git a/tezt/tests/tx_rollup_node.ml b/tezt/tests/tx_rollup_node.ml index e79518662d58..e6b15aa715a8 100644 --- a/tezt/tests/tx_rollup_node.ml +++ b/tezt/tests/tx_rollup_node.ml @@ -222,17 +222,6 @@ let test_tx_node_store_inbox = Computed %L" ; unit) -(* FIXME/TORU: This is a temporary way of querying the node without - tx_rollup_client. This aims to be replaced as soon as possible by - the dedicated client's RPC. *) -let raw_tx_node_rpc node ~url = - let* rpc = RPC.Curl.get () in - match rpc with - | None -> assert false - | Some curl -> - let url = Printf.sprintf "%s/%s" (Rollup_node.rpc_addr node) url in - curl ~url - let raw_tx_node_rpc_post node ~url data = let* rpc = RPC.Curl.post () in match rpc with @@ -241,19 +230,6 @@ let raw_tx_node_rpc_post node ~url data = let url = Printf.sprintf "%s/%s" (Rollup_node.rpc_addr node) url in curl ~url data -(* FIXME/TORU: Must be replaced by the Tx_client.get_balance command *) -let tx_client_get_balance ~tx_node ~block ~ticket_id ~tz4_address = - let* json = - raw_tx_node_rpc - tx_node - ~url: - ("context/" ^ block ^ "/tickets/" ^ ticket_id ^ "/balance/" - ^ tz4_address) - in - match JSON.(json |> as_int_opt) with - | Some level -> Lwt.return level - | None -> Test.fail "Cannot retrieve balance of tz4 address %s" tz4_address - let tx_client_inject_transaction ~tx_node ?failswith transaction signature = let open Tezos_protocol_alpha.Protocol in let signed_tx_json = @@ -288,18 +264,6 @@ let tx_client_inject_transaction ~tx_node ?failswith transaction signature = (* Dummy value for operation hash *) return "" -let tx_client_get_queue ~tx_node = raw_tx_node_rpc tx_node ~url:"queue" - -let tx_client_get_transaction_in_queue ~tx_node txh = - raw_tx_node_rpc tx_node ~url:("queue/transaction/" ^ txh) - -let tx_client_get_block ~tx_node ~block = - raw_tx_node_rpc tx_node ~url:("block/" ^ block) - -(* FIXME/TORU: Must be replaced by the Tx_client.get_inbox command *) -let tx_client_get_inbox ~tx_node ~block = - raw_tx_node_rpc tx_node ~url:("block/" ^ block ^ "/inbox") - (* Returns the ticket hash, if any, of a given operation. *) let get_ticket_hash_from_op op = let metadata = JSON.(op |-> "contents" |=> 0 |-> "metadata") in @@ -323,8 +287,8 @@ let get_ticket_hash_from_op op = JSON.(result |-> "ticket_hash" |> as_string) | None | Some _ -> Test.fail "The contract origination failed" -let get_ticket_hash_from_deposit d = - JSON.(d |-> "message" |-> "deposit" |-> "ticket_hash" |> as_string) +let get_ticket_hash_from_deposit (d : Rollup_node.Inbox.message) : string = + JSON.(d.message |-> "deposit" |-> "ticket_hash" |> as_string) (* The contract is expecting a parameter of the form: (Pair tx_rollup_txr1_address tx_rollup_tz4_address) *) @@ -366,7 +330,7 @@ let generate_bls_addr ?alias:_ _client = let check_tz4_balance ~tx_node ~block ~ticket_id ~tz4_address ~expected_balance = let* tz4_balance = - tx_client_get_balance ~tx_node ~block ~ticket_id ~tz4_address + Rollup_node.Client.get_balance ~tx_node ~block ~ticket_id ~tz4_address in Check.( ( = ) @@ -432,10 +396,8 @@ let test_ticket_deposit_from_l1_to_l2 = let* _ = Rollup_node.wait_for_tezos_level tx_node 4 in (* Get the operation containing the ticket transfer. We assume that only one operation is issued in this block. *) - let* inbox = tx_client_get_inbox ~tx_node ~block:"head" in - let ticket_id = - get_ticket_hash_from_deposit JSON.(inbox |-> "contents" |=> 0) - in + let* inbox = Rollup_node.Client.get_inbox ~tx_node ~block:"head" in + let ticket_id = get_ticket_hash_from_deposit (List.hd inbox.contents) in Log.info "Ticket %s was successfully emitted" ticket_id ; let* () = check_tz4_balance @@ -557,10 +519,8 @@ let test_l2_to_l2_transaction = let* () = Client.bake_for client in let* _ = Node.wait_for_level node 4 in let* _ = Rollup_node.wait_for_tezos_level tx_node 4 in - let* inbox = tx_client_get_inbox ~tx_node ~block:"head" in - let ticket_id = - get_ticket_hash_from_deposit JSON.(inbox |-> "contents" |=> 0) - in + let* inbox = Rollup_node.Client.get_inbox ~tx_node ~block:"head" in + let ticket_id = get_ticket_hash_from_deposit (List.hd inbox.contents) in Log.info "Ticket %s was successfully emitted" ticket_id ; let* () = check_tz4_balance @@ -702,10 +662,8 @@ let test_batcher = let* () = Client.bake_for client in let* _ = Node.wait_for_level node 4 in let* _ = Rollup_node.wait_for_tezos_level tx_node 4 in - let* inbox = tx_client_get_inbox ~tx_node ~block:"head" in - let ticket_id = - get_ticket_hash_from_deposit JSON.(inbox |-> "contents" |=> 0) - in + let* inbox = Rollup_node.Client.get_inbox ~tx_node ~block:"head" in + let ticket_id = get_ticket_hash_from_deposit (List.hd inbox.contents) in Log.info "Ticket %s was successfully emitted" ticket_id ; let* () = check_tz4_balance @@ -832,12 +790,12 @@ let test_batcher = signature in Log.info "Checking rollup node queue" ; - let* q = tx_client_get_queue ~tx_node in + let* q = Rollup_node.Client.get_queue ~tx_node in let len_q = JSON.(q |> as_list |> List.length) in Check.((len_q = 2) int) ~error_msg:"Queue length is %L but should be %R" ; Log.info "Checking rollup node queue transactions" ; - let* _t1 = tx_client_get_transaction_in_queue ~tx_node txh1 - and* _t2 = tx_client_get_transaction_in_queue ~tx_node txh2 in + let* _t1 = Rollup_node.Client.get_transaction_in_queue ~tx_node txh1 + and* _t2 = Rollup_node.Client.get_transaction_in_queue ~tx_node txh2 in let* () = Client.bake_for client in let* _ = Node.wait_for_level node 6 in let* _ = Rollup_node.wait_for_tezos_level tx_node 6 in @@ -897,7 +855,7 @@ let test_batcher = unit) (List.init nbtxs2 (fun i -> Int64.of_int (i + 2))) in - let* q = tx_client_get_queue ~tx_node in + let* q = Rollup_node.Client.get_queue ~tx_node in let len_q = JSON.(q |> as_list |> List.length) in Check.((len_q = nbtxs1 + nbtxs2) int) ~error_msg:"Queue length is %L but should be %R" ; @@ -988,10 +946,8 @@ let test_reorganization = let* () = Client.bake_for client1 in let* _ = Node.wait_for_level node1 4 in let* _ = Rollup_node.wait_for_tezos_level tx_node 4 in - let* inbox = tx_client_get_inbox ~tx_node ~block:"head" in - let ticket_id = - get_ticket_hash_from_deposit JSON.(inbox |-> "contents" |=> 0) - in + let* inbox = Rollup_node.Client.get_inbox ~tx_node ~block:"head" in + let ticket_id = get_ticket_hash_from_deposit (List.hd inbox.contents) in (* Run the node that will be used to forge an alternative branch *) let* node2 = Node.init nodes_args in let* client2 = Client.init ~endpoint:Client.(Node node2) () in -- GitLab From 9e5513d9a9288c71b567a5a910a129e8b39907e9 Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Mon, 28 Mar 2022 16:30:24 +0200 Subject: [PATCH 10/13] Tx_rollup,Tezt: multiple batches in the same inbox --- tezt/tests/tx_rollup_node.ml | 56 ++++++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/tezt/tests/tx_rollup_node.ml b/tezt/tests/tx_rollup_node.ml index e6b15aa715a8..fa190c5f3a00 100644 --- a/tezt/tests/tx_rollup_node.ml +++ b/tezt/tests/tx_rollup_node.ml @@ -495,12 +495,14 @@ let test_l2_to_l2_transaction = Log.info "The tx_rollup_deposit %s contract was successfully originated" contract_id ; - (* Genarating some identities *) + (* Generating some identities *) let (bls_1_pkh, bls_pk_1, bls_sk_1) = generate_bls_addr client in let bls_pkh_1_str = Bls_public_key_hash.to_b58check bls_1_pkh in (* FIXME/TORU: Use the client *) - let (bls_2_pkh, _, _) = generate_bls_addr client in + let (bls_2_pkh, bls_pk_2, bls_sk_2) = generate_bls_addr client in let bls_pkh_2_str = Bls_public_key_hash.to_b58check bls_2_pkh in + let (bls_3_pkh, _, _) = generate_bls_addr client in + let bls_pkh_3_str = Bls_public_key_hash.to_b58check bls_3_pkh in let arg_1 = make_tx_rollup_deposit_argument tx_rollup_hash bls_pkh_1_str in @@ -558,23 +560,24 @@ let test_l2_to_l2_transaction = in Log.info "Crafting a l2 transaction" ; (* FIXME/TORU: Use the client *) - let tx = + let tx1 = craft_tx ~counter:1L ~signer:(Bls_pk bls_pk_1) ~dest:bls_pkh_2_str ~ticket:ticket_id - 1L + 5L in - Log.info "Crafting a batch" ; - let batch = craft_batch [[tx]] [[bls_sk_1]] in + + Log.info "Crafting a first batch" ; + let batch = craft_batch [[tx1]] [[bls_sk_1]] in let content = Hex.of_string (Data_encoding.Binary.to_string_exn Tezos_protocol_alpha.Protocol.Tx_rollup_l2_batch.encoding (Tezos_protocol_alpha.Protocol.Tx_rollup_l2_batch.V1 batch)) in - Log.info "Submiting a batch" ; + Log.info "Submitting the first batch" ; let*! () = Client.Tx_rollup.submit_batch ~content @@ -582,6 +585,30 @@ let test_l2_to_l2_transaction = ~src:operator client in + Log.info "Crafting a second batch" ; + let tx2 = + craft_tx + ~counter:1L + ~signer:(Bls_pk bls_pk_2) + ~dest:bls_pkh_3_str + ~ticket:ticket_id + 15L + in + let batch = craft_batch [[tx2]] [[bls_sk_2]] in + let content = + Hex.of_string + (Data_encoding.Binary.to_string_exn + Tezos_protocol_alpha.Protocol.Tx_rollup_l2_batch.encoding + (Tezos_protocol_alpha.Protocol.Tx_rollup_l2_batch.V1 batch)) + in + Log.info "Submitting the second batch" ; + let*! () = + Client.Tx_rollup.submit_batch + ~content + ~rollup:tx_rollup_hash + ~src:Constant.bootstrap2.public_key_hash + client + in Log.info "Baking the batch" ; let* () = Client.bake_for client in let* _ = Node.wait_for_level node 6 in @@ -590,20 +617,31 @@ let test_l2_to_l2_transaction = line can be uncommented once it is fixed. let* _node_inbox = get_node_inbox tx_node client in *) + (* Having two batches in the same inbox we can test that: + 1. The batches are applied in the correct order + 2. The apply supports multiple batches + *) let* () = check_tz4_balance ~tx_node ~block:"head" ~ticket_id ~tz4_address:bls_pkh_1_str - ~expected_balance:99_999 + ~expected_balance:99_995 and* () = check_tz4_balance ~tx_node ~block:"head" ~ticket_id ~tz4_address:bls_pkh_2_str - ~expected_balance:100_001 + ~expected_balance:99_990 + and* () = + check_tz4_balance + ~tx_node + ~block:"head" + ~ticket_id + ~tz4_address:bls_pkh_3_str + ~expected_balance:15 in unit) -- GitLab From 3aa23a84f8731ce0a0e58a8e585d08128fe67df9 Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Wed, 30 Mar 2022 18:34:43 +0200 Subject: [PATCH 11/13] Tx_rollup,Tezt: test proof rollup RPC --- tezt/tests/tx_rollup_node.ml | 311 ++++++++++++++++++++++++++++++++++- 1 file changed, 309 insertions(+), 2 deletions(-) diff --git a/tezt/tests/tx_rollup_node.ml b/tezt/tests/tx_rollup_node.ml index fa190c5f3a00..7a00fe5643d6 100644 --- a/tezt/tests/tx_rollup_node.ml +++ b/tezt/tests/tx_rollup_node.ml @@ -568,7 +568,6 @@ let test_l2_to_l2_transaction = ~ticket:ticket_id 5L in - Log.info "Crafting a first batch" ; let batch = craft_batch [[tx1]] [[bls_sk_1]] in let content = @@ -1067,6 +1066,313 @@ let test_reorganization = in unit) +let test_l2_proofs = + Protocol.register_test + ~__FILE__ + ~title:"TX_rollup: proofs l2 transactions" + ~tags:["tx_rollup"; "node"; "proofs"] + (fun protocol -> + let* parameter_file = get_rollup_parameter_file ~protocol in + let* (node, client) = + Client.init_with_protocol ~parameter_file `Client ~protocol () + in + let operator = Constant.bootstrap1.public_key_hash in + let* (tx_rollup_hash, tx_node) = + init_and_run_rollup_node ~operator node client + in + let* contract_id = + Client.originate_contract + ~alias:"rollup_deposit" + ~amount:Tez.zero + ~src:"bootstrap1" + ~prg:"file:./tezt/tests/contracts/proto_alpha/tx_rollup_deposit.tz" + ~init:"Unit" + ~burn_cap:Tez.(of_int 1) + client + in + let* () = Client.bake_for client in + let* _ = Node.wait_for_level node 3 in + Log.info + "The tx_rollup_deposit %s contract was successfully originated" + contract_id ; + (* Generating some identities *) + let (pkh1, pk1, sk1) = generate_bls_addr client in + let pkh1_str = Bls_public_key_hash.to_b58check pkh1 in + let (pkh2, pk2, sk2) = generate_bls_addr client in + let pkh2_str = Bls_public_key_hash.to_b58check pkh2 in + let arg = make_tx_rollup_deposit_argument tx_rollup_hash pkh1_str in + let* () = + Client.transfer + ~gas_limit:100_000 + ~fee:Tez.one + ~amount:Tez.zero + ~burn_cap:Tez.one + ~storage_limit:10_000 + ~giver:"bootstrap1" + ~receiver:contract_id + ~arg + client + in + let* () = Client.bake_for client in + let* _ = Node.wait_for_level node 4 in + let* _ = Rollup_node.wait_for_tezos_level tx_node 4 in + let* inbox = Rollup_node.Client.get_inbox ~tx_node ~block:"head" in + let ticket_id = get_ticket_hash_from_deposit (List.hd inbox.contents) in + Log.info "Ticket %s was successfully emitted" ticket_id ; + (* TODO: commitment here *) + Log.info "Crafting a commitment for the deposit" ; + let*! inbox_opt = + Rollup.get_inbox ~rollup:tx_rollup_hash ~level:0 client + in + let inbox = Option.get inbox_opt in + let inbox_merkle_root = inbox.merkle_root in + let* rollup_inbox = Rollup_node.Client.get_inbox ~tx_node ~block:"head" in + let deposit_context_hash = + match rollup_inbox.contents with + | [x] -> x.Rollup_node.Inbox.l2_context_hash.tree_hash + | _ -> assert false + in + let*! deposit_result_hash = + Rollup.message_result_hash + ~context_hash:deposit_context_hash + ~withdraw_list_hash:Constant.tx_rollup_empty_withdraw_list + client + in + Log.info "Submit a commitment for the deposit" ; + let*! () = + Client.Tx_rollup.submit_commitment + ~level:0 + ~roots:[deposit_result_hash] + ~inbox_merkle_root + ~predecessor:None + ~rollup:tx_rollup_hash + ~src:Constant.bootstrap3.public_key_hash + client + in + (* FIXME/TORU: Use the client *) + let tx1 = + craft_tx + ~counter:1L + ~signer:(Bls_pk pk1) + ~dest:pkh2_str + ~ticket:ticket_id + 5L + in + let batch1 = craft_batch [[tx1]] [[sk1]] in + let content1 = + Hex.of_string + (Data_encoding.Binary.to_string_exn + Tezos_protocol_alpha.Protocol.Tx_rollup_l2_batch.encoding + (Tezos_protocol_alpha.Protocol.Tx_rollup_l2_batch.V1 batch1)) + in + let tx2 = + craft_tx + ~counter:1L + ~signer:(Bls_pk pk2) + ~dest:pkh1_str + ~ticket:ticket_id + 10L + in + let batch2 = craft_batch [[tx2]] [[sk2]] in + let content2 = + Hex.of_string + (Data_encoding.Binary.to_string_exn + Tezos_protocol_alpha.Protocol.Tx_rollup_l2_batch.encoding + (Tezos_protocol_alpha.Protocol.Tx_rollup_l2_batch.V1 batch2)) + in + Log.info "Submiting two batches" ; + let*! () = + Client.Tx_rollup.submit_batch + ~content:content1 + ~rollup:tx_rollup_hash + ~src:operator + client + in + let*! () = + Client.Tx_rollup.submit_batch + ~content:content2 + ~rollup:tx_rollup_hash + ~src:Constant.bootstrap2.public_key_hash + client + in + Log.info "Baking the batches" ; + let* () = Client.bake_for client in + let* _ = Node.wait_for_level node 5 in + let* _ = Rollup_node.wait_for_tezos_level tx_node 5 in + Log.info "Crafting the commitment" ; + let*! inbox_opt = + Rollup.get_inbox ~rollup:tx_rollup_hash ~level:1 client + in + let inbox = Option.get inbox_opt in + let inbox_merkle_root = inbox.merkle_root in + let* rollup_inbox = Rollup_node.Client.get_inbox ~tx_node ~block:"head" in + let context_hashes = + List.map + (fun x -> x.Rollup_node.Inbox.l2_context_hash.tree_hash) + rollup_inbox.contents + in + let* roots = + Lwt_list.map_p + (fun context_hash -> + let*! root = + Rollup.message_result_hash + ~context_hash + ~withdraw_list_hash:Constant.tx_rollup_empty_withdraw_list + client + in + return root) + context_hashes + in + let*! prev_commitment_opt = + Rollup.get_commitment ~rollup:tx_rollup_hash ~level:0 client + in + let prev_commitment = Option.get prev_commitment_opt in + let predecessor = Option.some prev_commitment.commitment_hash in + let*! () = + Client.Tx_rollup.submit_commitment + ~level:1 + ~roots + ~inbox_merkle_root + ~predecessor + ~rollup:tx_rollup_hash + ~src:operator + client + in + let* () = Client.bake_for client in + let* _ = Node.wait_for_level node 6 in + let* _ = Rollup_node.wait_for_tezos_level tx_node 6 in + Log.info "Get the proof for message at position 0" ; + let* proof1 = + Rollup_node.Client.get_merkle_proof + ~tx_node + ~block:"head" + ~message_pos:"0" + in + let proof1_str = JSON.encode proof1 in + let (`Hex content) = content1 in + let message = + Ezjsonm.value_to_string @@ `O [("batch", `String content)] + in + let*! message1_hash = + Rollup.message_hash ~message:(`Batch content1) client + in + let*! message2_hash = + Rollup.message_hash ~message:(`Batch content2) client + in + let message_hashes = [message1_hash; message2_hash] in + Log.info "Trying to reject a valid commitment" ; + let*! message1_path = + Rollup.inbox_merkle_tree_path ~message_hashes ~position:0 client + in + let*! message2_path = + Rollup.inbox_merkle_tree_path ~message_hashes ~position:1 client + in + let message1_path = JSON.encode message1_path in + let message2_path = JSON.encode message2_path in + let message1_context_hash = Stdlib.List.nth context_hashes 0 in + let message2_context_hash = Stdlib.List.nth context_hashes 1 in + let message1_result_hash = Stdlib.List.nth roots 0 in + let message2_result_hash = Stdlib.List.nth roots 1 in + let*! rejected_message1_result_hash = + Rollup.message_result_hash + ~context_hash:message1_context_hash + ~withdraw_list_hash:Constant.tx_rollup_empty_withdraw_list + client + in + let*! rejected_message2_result_hash = + Rollup.message_result_hash + ~context_hash:message2_context_hash + ~withdraw_list_hash:Constant.tx_rollup_empty_withdraw_list + client + in + let*! rejected_message1_result_path = + Rollup.commitment_merkle_tree_path + ~message_result_hashes: + [`Hash message1_result_hash; `Hash message2_result_hash] + ~position:0 + client + in + let*! rejected_message2_result_path = + Rollup.commitment_merkle_tree_path + ~message_result_hashes: + [`Hash message1_result_hash; `Hash message2_result_hash] + ~position:1 + client + in + let agreed_message1_result_hash = deposit_result_hash in + let*! agreed_message1_result_path = + Rollup.commitment_merkle_tree_path + ~message_result_hashes:[`Hash agreed_message1_result_hash] + ~position:0 + client + in + let*! agreed_message2_result_path = + Rollup.commitment_merkle_tree_path + ~message_result_hashes: + [`Hash message1_result_hash; `Hash message2_result_hash] + ~position:0 + client + in + let*? process = + Client.Tx_rollup.submit_rejection + ~src:operator + ~proof:proof1_str + ~rollup:tx_rollup_hash + ~level:1 + ~message + ~position:0 + ~path:message1_path + ~message_result_hash:rejected_message1_result_hash + ~rejected_message_result_path: + (JSON.encode rejected_message1_result_path) + ~context_hash:deposit_context_hash + ~withdraw_list_hash:Constant.tx_rollup_empty_withdraw_list + ~agreed_message_result_path:(JSON.encode agreed_message1_result_path) + client + in + let* () = + Process.check_error + ~msg:(rex "proto.alpha.tx_rollup_proof_produced_rejected_state") + process + in + Log.info "Get the proof for the second message at level 1" ; + let* proof2 = + Rollup_node.Client.get_merkle_proof + ~tx_node + ~block:"head" + ~message_pos:"1" + in + let proof2_str = JSON.encode proof2 in + let (`Hex content) = content2 in + let message = + Ezjsonm.value_to_string @@ `O [("batch", `String content)] + in + Log.info "Trying to reject a valid commitment" ; + + let*? process = + Client.Tx_rollup.submit_rejection + ~src:operator + ~proof:proof2_str + ~rollup:tx_rollup_hash + ~level:1 + ~message + ~position:1 (* OK *) + ~path:message2_path (* OK *) + ~message_result_hash:rejected_message2_result_hash (* OK *) + ~rejected_message_result_path: + (JSON.encode rejected_message2_result_path) (* OK *) + ~context_hash:message1_context_hash (* OK *) + ~withdraw_list_hash:Constant.tx_rollup_empty_withdraw_list + ~agreed_message_result_path:(JSON.encode agreed_message2_result_path) + client + in + let* () = + Process.check_error + ~msg:(rex "proto.alpha.tx_rollup_proof_produced_rejected_state") + process + in + unit) + let register ~protocols = test_node_configuration protocols ; test_tx_node_origination protocols ; @@ -1074,4 +1380,5 @@ let register ~protocols = test_ticket_deposit_from_l1_to_l2 protocols ; test_l2_to_l2_transaction protocols ; test_batcher protocols ; - test_reorganization protocols + test_reorganization protocols ; + test_l2_proofs protocols -- GitLab From 0693b90d75fbf366ae4ed8bccd6ed52d1b956b75 Mon Sep 17 00:00:00 2001 From: Valentin Chaboche Date: Thu, 7 Apr 2022 17:14:32 +0200 Subject: [PATCH 12/13] Proto: fix typo merkle_root to list_hash --- .../lib_client_commands/client_proto_context_commands.ml | 4 ++-- .../lib_protocol/tx_rollup_message_result_hash_repr.mli | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml b/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml index 868aadc3e8cf..4d60f08451c3 100644 --- a/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml +++ b/src/proto_alpha/lib_client_commands/client_proto_context_commands.ml @@ -2631,8 +2631,8 @@ let commands_rw () = Client_proto_args.string_parameter @@ prefixes ["and"; "withdraw"; "list"] @@ Clic.param - ~name:"withdrawals_merkle_root" - ~desc:"the hash of the merkelised withdraw list" + ~name:"withdraw_list_hash" + ~desc:"the hash of the withdraw list" Client_proto_args.string_parameter @@ prefixes ["with"; "path"] @@ Clic.param diff --git a/src/proto_alpha/lib_protocol/tx_rollup_message_result_hash_repr.mli b/src/proto_alpha/lib_protocol/tx_rollup_message_result_hash_repr.mli index 8409c3ed7ca6..7016002dde4c 100644 --- a/src/proto_alpha/lib_protocol/tx_rollup_message_result_hash_repr.mli +++ b/src/proto_alpha/lib_protocol/tx_rollup_message_result_hash_repr.mli @@ -28,7 +28,7 @@ (** The hash of the result of a layer-2 operation: that is, the hash of [(l2_ctxt_hash ^ withdraw_hash)] where [l2_ctxt_hash] is the Merkle tree root of the L2 context after any message (ie. deposit or batch), - and [withdraw_hash] is a [Tx_rollup_withdraw_repr.withdrawals_merkle_root] *) + and [withdraw_hash] is a [Tx_rollup_withdraw_repr.withdraw_list_hash] *) include S.HASH -- GitLab From 6c51a61091a0f57813e7984adfbe88431da9917c Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Tue, 12 Apr 2022 12:25:23 +0200 Subject: [PATCH 13/13] Tezt/tests: Tx rollup batcher test with multiple batches --- tezt/lib_tezos/tx_rollup_node.ml | 28 +++++++++++++ tezt/lib_tezos/tx_rollup_node.mli | 40 ++++++++++++++++++ tezt/tests/tx_rollup_node.ml | 69 +++++++++++++++++++++++++++---- 3 files changed, 128 insertions(+), 9 deletions(-) diff --git a/tezt/lib_tezos/tx_rollup_node.ml b/tezt/lib_tezos/tx_rollup_node.ml index 8787020460d3..1978c6f39096 100644 --- a/tezt/lib_tezos/tx_rollup_node.ml +++ b/tezt/lib_tezos/tx_rollup_node.ml @@ -162,6 +162,34 @@ let wait_for_tezos_level node level = ~where:("level >= " ^ string_of_int level) promise +let wait_for_full ?where node name filter = + let (promise, resolver) = Lwt.task () in + let current_events = + String_map.find_opt name node.one_shot_event_handlers + |> Option.value ~default:[] + in + node.one_shot_event_handlers <- + String_map.add + name + (Event_handler {filter; resolver} :: current_events) + node.one_shot_event_handlers ; + let* result = promise in + match result with + | None -> + raise (Terminated_before_event {daemon = node.name; event = name; where}) + | Some x -> return x + +let event_from_full_event_filter filter json = + let raw = get_event_from_full_event json in + (* If [json] does not match the correct JSON structure, it + will be filtered out, which will result in ignoring + the current event. + @see raw_event_from_event *) + Option.bind raw (fun {value; _} -> filter value) + +let wait_for ?where node name filter = + wait_for_full ?where node name (event_from_full_event_filter filter) + let create ?(path = Constant.tx_rollup_node) ?runner ?data_dir ?(addr = "127.0.0.1") ?(dormant_mode = false) ?color ?event_pipe ?name ~rollup_id ~rollup_genesis ?operator client tezos_node = diff --git a/tezt/lib_tezos/tx_rollup_node.mli b/tezt/lib_tezos/tx_rollup_node.mli index 18291d73a183..e30e75b0122f 100644 --- a/tezt/lib_tezos/tx_rollup_node.mli +++ b/tezt/lib_tezos/tx_rollup_node.mli @@ -60,6 +60,46 @@ val wait_for_ready : t -> unit Lwt.t If such an event already occurred, return immediately. *) val wait_for_tezos_level : t -> int -> int Lwt.t +(** Wait for a custom event to occur. + + Usage: [wait_for_full daemon name filter] + + If an event named [name] occurs, apply [filter] to its + whole json, which is of the form: + {[{ + "fd-sink-item.v0": { + "hostname": "...", + "time_stamp": ..., + "section": [ ... ], + "event": { : ... } + } + }]} + If [filter] returns [None], continue waiting. + If [filter] returns [Some x], return [x]. + + [where] is used as the [where] field of the [Terminated_before_event] exception + if the daemon terminates. It should describe the constraint that [filter] applies, + such as ["field level exists"]. + + It is advised to register such event handlers before starting the daemon, + as if they occur before being registered, they will not trigger your handler. + For instance, you can define a promise with + [let x_event = wait_for daemon "x" (fun x -> Some x)] + and bind it later with [let* x = x_event]. *) +val wait_for_full : + ?where:string -> t -> string -> (JSON.t -> 'a option) -> 'a Lwt.t + +(** Same as [wait_for_full] but ignore metadata from the file descriptor sink. + + More precisely, [filter] is applied to the value of field + ["fd-sink-item.v0"."event".]. + + If the daemon receives a JSON value that does not match the right + JSON structure, it is not given to [filter] and the event is + ignored. See [wait_for_full] to know what the JSON value must + look like. *) +val wait_for : ?where:string -> t -> string -> (JSON.t -> 'a option) -> 'a Lwt.t + (** Connected to a tezos node. Returns the name of the configuration file. *) val config_init : t -> string -> string -> string Lwt.t diff --git a/tezt/tests/tx_rollup_node.ml b/tezt/tests/tx_rollup_node.ml index 7a00fe5643d6..0219bbf2b6d8 100644 --- a/tezt/tests/tx_rollup_node.ml +++ b/tezt/tests/tx_rollup_node.ml @@ -70,7 +70,52 @@ let get_rollup_parameter_file ~protocol = let base = Either.right (protocol, None) in Protocol.write_parameter_file ~base enable_tx_rollup -(* Checks that the configuration is stored and that the required +(* Wait for the [injection_success] event from the rollup node batcher. *) +let wait_for_injection_success_event node = + Rollup_node.wait_for node "injection_success.v0" (fun _ -> Some ()) + +(* Check that all messages in the inbox have been successfully applied. *) +let check_inbox_success (inbox : Rollup_node.Inbox.t) = + let ( |->? ) json field = + let res = JSON.(json |-> field) in + match JSON.unannotate res with `Null -> None | _ -> Some res + in + List.iteri + (fun i msg -> + let result = + (* Pair of result and withdraws *) + JSON.(msg.Rollup_node.Inbox.result |=> 0) + in + match result |->? "deposit_result" with + | None -> + (* Not a deposit, must be a batch *) + let results = + JSON.( + result |->? "batch_v1_result" |> Option.get |-> "results" + |> as_list) + in + List.iteri + (fun j tr_json -> + match JSON.(tr_json |=> 1 |> as_string_opt) with + | Some "transaction_success" -> (* OK *) () + | _ -> + Test.fail + "Transaction at position %d of batch %d failed: %s" + j + i + (JSON.encode tr_json)) + results + | Some result -> ( + match result |->? "deposit_success" with + | Some _ -> (* OK *) () + | None -> + Test.fail + "Deposit at position %d failed: %s" + i + (JSON.encode result))) + inbox.contents + +(* Checks that the configuration is stored and that the required fields are present. *) let test_node_configuration = Protocol.register_test @@ -834,11 +879,10 @@ let test_batcher = let* _t1 = Rollup_node.Client.get_transaction_in_queue ~tx_node txh1 and* _t2 = Rollup_node.Client.get_transaction_in_queue ~tx_node txh2 in let* () = Client.bake_for client in - let* _ = Node.wait_for_level node 6 in - let* _ = Rollup_node.wait_for_tezos_level tx_node 6 in let* () = Client.bake_for client in - let* _ = Node.wait_for_level node 7 in let* _ = Rollup_node.wait_for_tezos_level tx_node 7 in + let* inbox = Rollup_node.Client.get_inbox ~tx_node ~block:"head" in + check_inbox_success inbox ; let* () = check_tz4_balance ~tx_node @@ -862,7 +906,10 @@ let test_batcher = let signature = sign_one_transaction sk [tx] in tx_client_inject_transaction ~tx_node [tx] signature in - let nbtxs1 = 13 in + let nbtxs1 = 70 in + let injection_success_promise = + wait_for_injection_success_event tx_node + in Log.info "Injecting %d transactions to queue" nbtxs1 ; let* () = Lwt_list.iter_s @@ -877,7 +924,7 @@ let test_batcher = unit) (List.init nbtxs1 (fun i -> Int64.of_int (i + 2))) in - let nbtxs2 = 6 in + let nbtxs2 = 30 in Log.info "Injecting %d transactions to queue" nbtxs2 ; let* () = Lwt_list.iter_s @@ -897,23 +944,27 @@ let test_batcher = Check.((len_q = nbtxs1 + nbtxs2) int) ~error_msg:"Queue length is %L but should be %R" ; let* () = Client.bake_for client in - let* _ = Rollup_node.wait_for_tezos_level tx_node 8 in + Log.info "Waiting for injection on L1 to succeed" ; + let* () = injection_success_promise in + Log.info "Injection succeeded" ; let* () = Client.bake_for client in let* _ = Rollup_node.wait_for_tezos_level tx_node 9 in + let* inbox = Rollup_node.Client.get_inbox ~tx_node ~block:"head" in + check_inbox_success inbox ; let* () = check_tz4_balance ~tx_node ~block:"head" ~ticket_id ~tz4_address:bls_pkh_1_str - ~expected_balance:99_997 + ~expected_balance:(100_004 - nbtxs1 + nbtxs2) and* () = check_tz4_balance ~tx_node ~block:"head" ~ticket_id ~tz4_address:bls_pkh_2_str - ~expected_balance:100_003 + ~expected_balance:(99_996 + nbtxs1 - nbtxs2) in unit) -- GitLab