From a955aeb3709b2a780cd4ca9e6bf72a735597b97a Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Fri, 29 Apr 2022 09:56:48 +0200 Subject: [PATCH 01/22] Tx_rollup,Node: make L1 operation type private Enforce invariant with hash. --- src/proto_alpha/lib_tx_rollup/accuser.ml | 6 ++--- src/proto_alpha/lib_tx_rollup/batcher.ml | 22 +++++++------------ src/proto_alpha/lib_tx_rollup/committer.ml | 9 +++----- src/proto_alpha/lib_tx_rollup/daemon.ml | 5 +---- src/proto_alpha/lib_tx_rollup/dispatcher.ml | 6 ++--- src/proto_alpha/lib_tx_rollup/l1_operation.ml | 7 +++--- .../lib_tx_rollup/l1_operation.mli | 10 ++++----- 7 files changed, 25 insertions(+), 40 deletions(-) diff --git a/src/proto_alpha/lib_tx_rollup/accuser.ml b/src/proto_alpha/lib_tx_rollup/accuser.ml index bd54b01c1379..6b33162f7dd4 100644 --- a/src/proto_alpha/lib_tx_rollup/accuser.ml +++ b/src/proto_alpha/lib_tx_rollup/accuser.ml @@ -250,7 +250,5 @@ let reject_bad_commitment ~source (state : State.t) proof; } in - let manager_operation = Manager rejection_operation in - let hash = L1_operation.hash_manager_operation manager_operation in - Injector.add_pending_operation - {L1_operation.hash; source; manager_operation} + let manager_operation = L1_operation.make ~source rejection_operation in + Injector.add_pending_operation manager_operation diff --git a/src/proto_alpha/lib_tx_rollup/batcher.ml b/src/proto_alpha/lib_tx_rollup/batcher.ml index f622994f9934..ddb2e597642b 100644 --- a/src/proto_alpha/lib_tx_rollup/batcher.ml +++ b/src/proto_alpha/lib_tx_rollup/batcher.ml @@ -52,21 +52,15 @@ let inject_batches state batches = (fun batch -> let open Result_syntax in let+ batch_content = encode_batch batch in - let manager_operation = - Manager - (Tx_rollup_submit_batch - { - tx_rollup = state.rollup; - content = batch_content; - burn_limit = None; - }) + let batch_operation = + Tx_rollup_submit_batch + { + tx_rollup = state.rollup; + content = batch_content; + burn_limit = None; + } in - { - L1_operation.hash = - L1_operation.hash_manager_operation manager_operation; - source = state.signer; - manager_operation; - }) + L1_operation.make ~source:state.signer batch_operation) batches in Injector.add_pending_operations operations diff --git a/src/proto_alpha/lib_tx_rollup/committer.ml b/src/proto_alpha/lib_tx_rollup/committer.ml index ba31a9f5ec2c..edbdca6d4686 100644 --- a/src/proto_alpha/lib_tx_rollup/committer.ml +++ b/src/proto_alpha/lib_tx_rollup/committer.ml @@ -40,9 +40,6 @@ let commit_block ~operator tx_rollup block = match block.L2block.commitment with | None -> return_unit | Some commitment -> - let manager_operation = - Manager (Tx_rollup_commit {tx_rollup; commitment}) - in - let hash = L1_operation.hash_manager_operation manager_operation in - Injector.add_pending_operation - {L1_operation.hash; source = operator; manager_operation} + let commit_operation = Tx_rollup_commit {tx_rollup; commitment} in + let l1_operation = L1_operation.make ~source:operator commit_operation in + Injector.add_pending_operation l1_operation diff --git a/src/proto_alpha/lib_tx_rollup/daemon.ml b/src/proto_alpha/lib_tx_rollup/daemon.ml index 30601446d653..096c15980cbd 100644 --- a/src/proto_alpha/lib_tx_rollup/daemon.ml +++ b/src/proto_alpha/lib_tx_rollup/daemon.ml @@ -438,10 +438,7 @@ let queue_gc_operations state = let open Lwt_result_syntax in let tx_rollup = state.State.rollup_info.rollup_id in let inject source op = - let manager_operation = Manager op in - let hash = L1_operation.hash_manager_operation manager_operation in - Injector.add_pending_operation - {L1_operation.hash; source; manager_operation} + Injector.add_pending_operation (L1_operation.make ~source op) in let queue_finalize_commitment state = match state.State.signers.finalize_commitment with diff --git a/src/proto_alpha/lib_tx_rollup/dispatcher.ml b/src/proto_alpha/lib_tx_rollup/dispatcher.ml index daef32bbd59b..c560316deed5 100644 --- a/src/proto_alpha/lib_tx_rollup/dispatcher.ml +++ b/src/proto_alpha/lib_tx_rollup/dispatcher.ml @@ -118,8 +118,6 @@ let dispatch_withdrawals ~source state block = let* operations = dispatch_operations_of_block state block in List.iter_es (fun dispatch_op -> - let manager_operation = Manager dispatch_op in - let hash = L1_operation.hash_manager_operation manager_operation in - Injector.add_pending_operation - {L1_operation.hash; source; manager_operation}) + let l1_operation = L1_operation.make ~source dispatch_op in + Injector.add_pending_operation l1_operation) operations diff --git a/src/proto_alpha/lib_tx_rollup/l1_operation.ml b/src/proto_alpha/lib_tx_rollup/l1_operation.ml index 178bafdd1c26..75d516f34baf 100644 --- a/src/proto_alpha/lib_tx_rollup/l1_operation.ml +++ b/src/proto_alpha/lib_tx_rollup/l1_operation.ml @@ -172,9 +172,10 @@ let hash_manager_operation op = Hash.hash_bytes [Data_encoding.Binary.to_bytes_exn Manager_operation.encoding op] -let hash op = - (* Hashing only manager operation *) - hash_manager_operation op.manager_operation +let make ~source manager_operation = + let manager_operation = Manager manager_operation in + let hash = hash_manager_operation manager_operation in + {hash; source; manager_operation} let encoding = let open Data_encoding in diff --git a/src/proto_alpha/lib_tx_rollup/l1_operation.mli b/src/proto_alpha/lib_tx_rollup/l1_operation.mli index a335e2b9d266..de5df94a3abe 100644 --- a/src/proto_alpha/lib_tx_rollup/l1_operation.mli +++ b/src/proto_alpha/lib_tx_rollup/l1_operation.mli @@ -32,7 +32,7 @@ module Hash : S.HASH type hash = Hash.t (** The type of L1 operations that are injected on Tezos by the rollup node *) -type t = { +type t = private { hash : hash; (** The hash of the L1 manager operation (without the source) *) source : public_key_hash; (** The source of the operation, i.e., the key that will sign the @@ -41,13 +41,13 @@ type t = { manager_operation : packed_manager_operation; (** The manager operation *) } +(** [make ~source op] returns an L1 operation with the corresponding hash and + whose source is set to [source]. *) +val make : source:public_key_hash -> 'a manager_operation -> t + (** Hash a manager operation *) val hash_manager_operation : packed_manager_operation -> hash -(** Hash an L1 operation. This is the same as hashing the corresponding manager - operation. *) -val hash : t -> hash - (** Encoding for L1 operations *) val encoding : t Data_encoding.t -- GitLab From 44099f10f9e8d3ba8128b55942ef613e03386ede Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Fri, 29 Apr 2022 10:05:25 +0200 Subject: [PATCH 02/22] Tx_rollup,Node: injector builds L1 operation --- src/proto_alpha/lib_tx_rollup/accuser.ml | 3 +-- src/proto_alpha/lib_tx_rollup/batcher.ml | 26 +++++++-------------- src/proto_alpha/lib_tx_rollup/committer.ml | 3 +-- src/proto_alpha/lib_tx_rollup/daemon.ml | 13 +++++++---- src/proto_alpha/lib_tx_rollup/dispatcher.ml | 6 +---- src/proto_alpha/lib_tx_rollup/injector.ml | 9 ++++--- src/proto_alpha/lib_tx_rollup/injector.mli | 6 ++--- 7 files changed, 26 insertions(+), 40 deletions(-) diff --git a/src/proto_alpha/lib_tx_rollup/accuser.ml b/src/proto_alpha/lib_tx_rollup/accuser.ml index 6b33162f7dd4..9e255f001c7c 100644 --- a/src/proto_alpha/lib_tx_rollup/accuser.ml +++ b/src/proto_alpha/lib_tx_rollup/accuser.ml @@ -250,5 +250,4 @@ let reject_bad_commitment ~source (state : State.t) proof; } in - let manager_operation = L1_operation.make ~source rejection_operation in - Injector.add_pending_operation manager_operation + Injector.add_pending_operation ~source rejection_operation diff --git a/src/proto_alpha/lib_tx_rollup/batcher.ml b/src/proto_alpha/lib_tx_rollup/batcher.ml index ddb2e597642b..33d007ca5d08 100644 --- a/src/proto_alpha/lib_tx_rollup/batcher.ml +++ b/src/proto_alpha/lib_tx_rollup/batcher.ml @@ -47,23 +47,15 @@ let encode_batch batch = let inject_batches state batches = let open Lwt_result_syntax in - let*? operations = - List.map_e - (fun batch -> - let open Result_syntax in - let+ batch_content = encode_batch batch in - let batch_operation = - Tx_rollup_submit_batch - { - tx_rollup = state.rollup; - content = batch_content; - burn_limit = None; - } - in - L1_operation.make ~source:state.signer batch_operation) - batches - in - Injector.add_pending_operations operations + List.iter_es + (fun batch -> + let*? batch_content = encode_batch batch in + let batch_operation = + Tx_rollup_submit_batch + {tx_rollup = state.rollup; content = batch_content; burn_limit = None} + in + Injector.add_pending_operation ~source:state.signer batch_operation) + batches (** [is_batch_valid] returns whether the batch is valid or not based on two criteria: diff --git a/src/proto_alpha/lib_tx_rollup/committer.ml b/src/proto_alpha/lib_tx_rollup/committer.ml index edbdca6d4686..cd817ea2f3bf 100644 --- a/src/proto_alpha/lib_tx_rollup/committer.ml +++ b/src/proto_alpha/lib_tx_rollup/committer.ml @@ -41,5 +41,4 @@ let commit_block ~operator tx_rollup block = | None -> return_unit | Some commitment -> let commit_operation = Tx_rollup_commit {tx_rollup; commitment} in - let l1_operation = L1_operation.make ~source:operator commit_operation in - Injector.add_pending_operation l1_operation + Injector.add_pending_operation ~source:operator commit_operation diff --git a/src/proto_alpha/lib_tx_rollup/daemon.ml b/src/proto_alpha/lib_tx_rollup/daemon.ml index 096c15980cbd..9f28c39f0971 100644 --- a/src/proto_alpha/lib_tx_rollup/daemon.ml +++ b/src/proto_alpha/lib_tx_rollup/daemon.ml @@ -437,18 +437,21 @@ let notify_head state head reorg = let queue_gc_operations state = let open Lwt_result_syntax in let tx_rollup = state.State.rollup_info.rollup_id in - let inject source op = - Injector.add_pending_operation (L1_operation.make ~source op) - in let queue_finalize_commitment state = match state.State.signers.finalize_commitment with | None -> return_unit - | Some source -> inject source (Tx_rollup_finalize_commitment {tx_rollup}) + | Some source -> + Injector.add_pending_operation + ~source + (Tx_rollup_finalize_commitment {tx_rollup}) in let queue_remove_commitment state = match state.State.signers.remove_commitment with | None -> return_unit - | Some source -> inject source (Tx_rollup_remove_commitment {tx_rollup}) + | Some source -> + Injector.add_pending_operation + ~source + (Tx_rollup_remove_commitment {tx_rollup}) in let* () = queue_finalize_commitment state in queue_remove_commitment state diff --git a/src/proto_alpha/lib_tx_rollup/dispatcher.ml b/src/proto_alpha/lib_tx_rollup/dispatcher.ml index c560316deed5..354907d21f79 100644 --- a/src/proto_alpha/lib_tx_rollup/dispatcher.ml +++ b/src/proto_alpha/lib_tx_rollup/dispatcher.ml @@ -116,8 +116,4 @@ let dispatch_operations_of_block (state : State.t) (block : L2block.t) = let dispatch_withdrawals ~source state block = let open Lwt_result_syntax in let* operations = dispatch_operations_of_block state block in - List.iter_es - (fun dispatch_op -> - let l1_operation = L1_operation.make ~source dispatch_op in - Injector.add_pending_operation l1_operation) - operations + List.iter_es (Injector.add_pending_operation ~source) operations diff --git a/src/proto_alpha/lib_tx_rollup/injector.ml b/src/proto_alpha/lib_tx_rollup/injector.ml index 7ba248252e70..b07f98185151 100644 --- a/src/proto_alpha/lib_tx_rollup/injector.ml +++ b/src/proto_alpha/lib_tx_rollup/injector.ml @@ -785,14 +785,13 @@ let worker_of_signer signer_pkh = error (Error.No_worker_for_source signer_pkh) | Some worker -> ok worker -let add_pending_operation op = +let add_pending_operation ~source op = let open Lwt_result_syntax in - let*? w = worker_of_signer op.L1_operation.source in - let*! () = Worker.Queue.push_request w (Request.Add_pending op) in + let*? w = worker_of_signer source in + let l1_operation = L1_operation.make ~source op in + let*! () = Worker.Queue.push_request w (Request.Add_pending l1_operation) in return_unit -let add_pending_operations ops = List.iter_es add_pending_operation ops - let new_tezos_head h reorg = let workers = Worker.list table in List.iter_p diff --git a/src/proto_alpha/lib_tx_rollup/injector.mli b/src/proto_alpha/lib_tx_rollup/injector.mli index 7b78bda92d23..3ec5800c6fac 100644 --- a/src/proto_alpha/lib_tx_rollup/injector.mli +++ b/src/proto_alpha/lib_tx_rollup/injector.mli @@ -45,10 +45,8 @@ val init : unit tzresult Lwt.t (** Add an operation as pending injection in the injector. *) -val add_pending_operation : L1_operation.t -> unit tzresult Lwt.t - -(** Add multiple operations as pending injection in the injector. *) -val add_pending_operations : L1_operation.t trace -> unit tzresult Lwt.t +val add_pending_operation : + source:public_key_hash -> 'a manager_operation -> unit tzresult Lwt.t (** Notify the injector of a new Tezos head. The injector marks the operations appropriately (for instance reverted operations that are part of a -- GitLab From 0b570f66ca2bc7b887c9f0bc3a2d134457703e63 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Tue, 3 May 2022 16:48:42 +0200 Subject: [PATCH 03/22] Tx_rollup,Node: remove source from L1 operations --- src/proto_alpha/lib_tx_rollup/injector.ml | 4 ++-- .../lib_tx_rollup/injector_worker_types.ml | 4 +--- src/proto_alpha/lib_tx_rollup/l1_operation.ml | 23 ++++++++----------- .../lib_tx_rollup/l1_operation.mli | 12 ++-------- 4 files changed, 14 insertions(+), 29 deletions(-) diff --git a/src/proto_alpha/lib_tx_rollup/injector.ml b/src/proto_alpha/lib_tx_rollup/injector.ml index b07f98185151..4bbeee25fbd7 100644 --- a/src/proto_alpha/lib_tx_rollup/injector.ml +++ b/src/proto_alpha/lib_tx_rollup/injector.ml @@ -427,7 +427,7 @@ let size_l1_batch signer rev_ops = let contents = Manager_operation { - source = op.source; + source = signer.pkh; operation; (* Below are dummy values that are only used to approximate the size. It is thus important that they remain above the real @@ -788,7 +788,7 @@ let worker_of_signer signer_pkh = let add_pending_operation ~source op = let open Lwt_result_syntax in let*? w = worker_of_signer source in - let l1_operation = L1_operation.make ~source op in + let l1_operation = L1_operation.make op in let*! () = Worker.Queue.push_request w (Request.Add_pending l1_operation) in return_unit diff --git a/src/proto_alpha/lib_tx_rollup/injector_worker_types.ml b/src/proto_alpha/lib_tx_rollup/injector_worker_types.ml index 43e8449785de..47b35175f196 100644 --- a/src/proto_alpha/lib_tx_rollup/injector_worker_types.ml +++ b/src/proto_alpha/lib_tx_rollup/injector_worker_types.ml @@ -126,11 +126,9 @@ module Request = struct | Add_pending op -> Format.fprintf ppf - "request add %a by %a to pending queue" + "request add %a to pending queue" L1_operation.Hash.pp op.hash - Signature.Public_key_hash.pp - op.source | New_tezos_head (b, r) -> Format.fprintf ppf diff --git a/src/proto_alpha/lib_tx_rollup/l1_operation.ml b/src/proto_alpha/lib_tx_rollup/l1_operation.ml index 75d516f34baf..1b765034554b 100644 --- a/src/proto_alpha/lib_tx_rollup/l1_operation.ml +++ b/src/proto_alpha/lib_tx_rollup/l1_operation.ml @@ -162,36 +162,31 @@ let () = Base58.check_encoded_prefix Hash.b58check_encoding "mop" 53 type hash = Hash.t -type t = { - hash : hash; - source : public_key_hash; - manager_operation : packed_manager_operation; -} +type t = {hash : hash; manager_operation : packed_manager_operation} let hash_manager_operation op = Hash.hash_bytes [Data_encoding.Binary.to_bytes_exn Manager_operation.encoding op] -let make ~source manager_operation = +let make manager_operation = let manager_operation = Manager manager_operation in let hash = hash_manager_operation manager_operation in - {hash; source; manager_operation} + {hash; manager_operation} let encoding = let open Data_encoding in conv - (fun {hash; source; manager_operation} -> (hash, source, manager_operation)) - (fun (hash, source, manager_operation) -> {hash; source; manager_operation}) - @@ obj3 + (fun {hash; manager_operation} -> (hash, manager_operation)) + (fun (hash, manager_operation) -> {hash; manager_operation}) + @@ obj2 (req "hash" Hash.encoding) - (req "source" Signature.Public_key_hash.encoding) (req "manager_operation" Manager_operation.encoding) -let pp ppf op = +let pp ppf {hash; manager_operation} = Format.fprintf ppf "%a (%a)" Manager_operation.pp - op.manager_operation + manager_operation Hash.pp - op.hash + hash diff --git a/src/proto_alpha/lib_tx_rollup/l1_operation.mli b/src/proto_alpha/lib_tx_rollup/l1_operation.mli index de5df94a3abe..73f1886ed737 100644 --- a/src/proto_alpha/lib_tx_rollup/l1_operation.mli +++ b/src/proto_alpha/lib_tx_rollup/l1_operation.mli @@ -34,19 +34,11 @@ type hash = Hash.t (** The type of L1 operations that are injected on Tezos by the rollup node *) type t = private { hash : hash; (** The hash of the L1 manager operation (without the source) *) - source : public_key_hash; - (** The source of the operation, i.e., the key that will sign the - operation for injection. Note: the source is decided when the - operation is queued in the injector at the moment. *) manager_operation : packed_manager_operation; (** The manager operation *) } -(** [make ~source op] returns an L1 operation with the corresponding hash and - whose source is set to [source]. *) -val make : source:public_key_hash -> 'a manager_operation -> t - -(** Hash a manager operation *) -val hash_manager_operation : packed_manager_operation -> hash +(** [make op] returns an L1 operation with the corresponding hash. *) +val make : 'a manager_operation -> t (** Encoding for L1 operations *) val encoding : t Data_encoding.t -- GitLab From b3491963a03bc3d144c2efa516c1c60e34cf2a9d Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Fri, 29 Apr 2022 10:34:49 +0200 Subject: [PATCH 04/22] Tx_rollup,Node: move injector events in separate module --- src/proto_alpha/lib_tx_rollup/daemon.ml | 2 +- src/proto_alpha/lib_tx_rollup/event.ml | 203 +---------------- src/proto_alpha/lib_tx_rollup/injector.ml | 38 ++-- .../lib_tx_rollup/injector_events.ml | 206 ++++++++++++++++++ 4 files changed, 229 insertions(+), 220 deletions(-) create mode 100644 src/proto_alpha/lib_tx_rollup/injector_events.ml diff --git a/src/proto_alpha/lib_tx_rollup/daemon.ml b/src/proto_alpha/lib_tx_rollup/daemon.ml index 9f28c39f0971..5d18e9e727f9 100644 --- a/src/proto_alpha/lib_tx_rollup/daemon.ml +++ b/src/proto_alpha/lib_tx_rollup/daemon.ml @@ -497,7 +497,7 @@ let trigger_injection state header = let* () = if delay <= 0. then return_unit else - let* () = Event.(emit Injector.wait) delay in + let* () = Event.(emit inject_wait) delay in Lwt_unix.sleep delay in Injector.inject ~strategy:Injector.Delay_block () diff --git a/src/proto_alpha/lib_tx_rollup/event.ml b/src/proto_alpha/lib_tx_rollup/event.ml index 0e319edd580d..ae25cc369689 100644 --- a/src/proto_alpha/lib_tx_rollup/event.ml +++ b/src/proto_alpha/lib_tx_rollup/event.ml @@ -195,6 +195,14 @@ let new_tezos_head = ~level:Notice ("tezos_head", Block_hash.encoding) +let inject_wait = + declare_1 + ~section + ~name:"inject_wait" + ~msg:"Waiting {delay} seconds to trigger injection" + ~level:Notice + ("delay", Data_encoding.float) + module Batcher = struct let section = section @ ["batcher"] @@ -280,201 +288,6 @@ module Batcher = struct end end -module Injector = struct - open Injector_worker_types - - let section = section @ ["injector"] - - let wait = - declare_1 - ~section - ~name:"wait" - ~msg:"Waiting {delay} seconds to trigger injection" - ~level:Notice - ("delay", Data_encoding.float) - - let declare_1 ~name ~msg ~level ?pp1 enc1 = - declare_3 - ~section - ~name - ~msg:("[{signer}: {tags}] " ^ msg) - ~level - ("signer", Signature.Public_key_hash.encoding) - ("tags", Injector_worker_types.tags_encoding) - enc1 - ~pp1:Signature.Public_key_hash.pp_short - ~pp2:Injector_worker_types.pp_tags - ?pp3:pp1 - - let declare_2 ~name ~msg ~level ?pp1 ?pp2 enc1 enc2 = - declare_4 - ~section - ~name - ~msg:("[{signer}: {tags}] " ^ msg) - ~level - ("signer", Signature.Public_key_hash.encoding) - ("tags", Injector_worker_types.tags_encoding) - enc1 - enc2 - ~pp1:Signature.Public_key_hash.pp_short - ~pp2:Injector_worker_types.pp_tags - ?pp3:pp1 - ?pp4:pp2 - - let declare_3 ~name ~msg ~level ?pp1 ?pp2 ?pp3 enc1 enc2 enc3 = - declare_5 - ~section - ~name - ~msg:("[{signer}: {tags}] " ^ msg) - ~level - ("signer", Signature.Public_key_hash.encoding) - ("tags", Injector_worker_types.tags_encoding) - enc1 - enc2 - enc3 - ~pp1:Signature.Public_key_hash.pp_short - ~pp2:Injector_worker_types.pp_tags - ?pp3:pp1 - ?pp4:pp2 - ?pp5:pp3 - - let request_failed = - declare_3 - ~name:"request_failed" - ~msg:"request {view} failed ({worker_status}): {errors}" - ~level:Warning - ("view", Request.encoding) - ~pp1:Request.pp - ("worker_status", Worker_types.request_status_encoding) - ~pp2:Worker_types.pp_status - ("errors", Error_monad.trace_encoding) - ~pp3:Error_monad.pp_print_trace - - let request_completed_notice = - declare_2 - ~name:"request_completed_notice" - ~msg:"{view} {worker_status}" - ~level:Notice - ("view", Request.encoding) - ("worker_status", Worker_types.request_status_encoding) - ~pp1:Request.pp - ~pp2:Worker_types.pp_status - - let request_completed_debug = - declare_2 - ~name:"request_completed_debug" - ~msg:"{view} {worker_status}" - ~level:Debug - ("view", Request.encoding) - ("worker_status", Worker_types.request_status_encoding) - ~pp1:Request.pp - ~pp2:Worker_types.pp_status - - let new_tezos_head = - declare_1 - ~name:"new_tezos_head" - ~msg:"processing new Tezos head {head}" - ~level:Debug - ("head", Block_hash.encoding) - - let injecting_pending = - declare_1 - ~name:"injecting_pending" - ~msg:"Injecting {count} pending operations" - ~level:Notice - ("count", Data_encoding.int31) - - let pp_operations_list ppf operations = - Format.fprintf - ppf - "@[%a@]" - (Format.pp_print_list L1_operation.pp) - operations - - let pp_operations_hash_list ppf operations = - Format.fprintf - ppf - "@[%a@]" - (Format.pp_print_list L1_operation.Hash.pp) - operations - - let injecting_operations = - declare_1 - ~name:"injecting_operations" - ~msg:"Injecting operations: {operations}" - ~level:Notice - ("operations", Data_encoding.list L1_operation.encoding) - ~pp1:pp_operations_list - - let simulating_operations = - declare_2 - ~name:"simulating_operations" - ~msg:"Simulating operations (force = {force}): {operations}" - ~level:Debug - ("operations", Data_encoding.list L1_operation.encoding) - ("force", Data_encoding.bool) - ~pp1:pp_operations_list - - let dropping_operation = - declare_2 - ~name:"dropping_operation" - ~msg:"Dropping operation {operation} failing with {error}" - ~level:Notice - ("operation", L1_operation.encoding) - ~pp1:L1_operation.pp - ("error", Environment.Error_monad.trace_encoding) - ~pp2:Environment.Error_monad.pp_trace - - let injected = - declare_1 - ~name:"injected" - ~msg:"Injected in {oph}" - ~level:Notice - ("oph", Operation_hash.encoding) - - let add_pending = - declare_1 - ~name:"add_pending" - ~msg:"Add {operation} to pending" - ~level:Notice - ("operation", L1_operation.encoding) - ~pp1:L1_operation.pp - - let included = - declare_3 - ~name:"included" - ~msg:"Included operations of {block} at level {level}: {operations}" - ~level:Notice - ("block", Block_hash.encoding) - ("level", Data_encoding.int32) - ("operations", Data_encoding.list L1_operation.Hash.encoding) - ~pp3:pp_operations_hash_list - - let revert_operations = - declare_1 - ~name:"revert_operations" - ~msg:"Reverting operations: {operations}" - ~level:Notice - ("operations", Data_encoding.list L1_operation.Hash.encoding) - ~pp1:pp_operations_hash_list - - let confirmed_level = - declare_1 - ~name:"confirmed_level" - ~msg:"Confirmed Tezos level {level}" - ~level:Notice - ("level", Data_encoding.int32) - - let confirmed_operations = - declare_2 - ~name:"confirmed_operations" - ~msg:"Confirmed operations of level {level}: {operations}" - ~level:Notice - ("level", Data_encoding.int32) - ("operations", Data_encoding.list L1_operation.Hash.encoding) - ~pp2:pp_operations_hash_list -end - module Accuser = struct let section = section @ ["accuser"] diff --git a/src/proto_alpha/lib_tx_rollup/injector.ml b/src/proto_alpha/lib_tx_rollup/injector.ml index 4bbeee25fbd7..169f05cddcc2 100644 --- a/src/proto_alpha/lib_tx_rollup/injector.ml +++ b/src/proto_alpha/lib_tx_rollup/injector.ml @@ -158,7 +158,7 @@ let init_injector rollup_node_state ~signer strategy tags = } module Event = struct - include Event + include Injector_events let emit1 e state x = Event.emit e (state.signer.pkh, state.tags, x) @@ -171,7 +171,7 @@ end operation. *) let add_pending_operation state op = let open Lwt_syntax in - let+ () = Event.(emit1 Injector.add_pending) state op in + let+ () = Event.(emit1 add_pending) state op in Op_queue.replace state.queue op.L1_operation.hash op (** Mark operations as injected (in [oph]). *) @@ -193,7 +193,7 @@ let add_injected_operations state oph operations = let add_included_operations state oph l1_block l1_level operations = let open Lwt_syntax in let+ () = - Event.(emit3 Injector.included) + Event.(emit3 included) state l1_block l1_level @@ -277,9 +277,7 @@ let simulate_operations ~must_succeed state signer succeed *) match must_succeed with `All -> false | `At_least_one -> true) in - let*! () = - Event.(emit2 Injector.simulating_operations) state operations force - in + let*! () = Event.(emit2 simulating_operations) state operations force in let operations = List.map (fun {L1_operation.manager_operation = Manager operation; _} -> @@ -359,7 +357,7 @@ let inject_on_node state packed_contents = ~chain:state.cctxt#chain op_bytes >>=? fun oph -> - let*! () = Event.(emit1 Injector.injected) state oph in + let*! () = Event.(emit1 injected) state oph in return oph (** Inject the given [operations] in an L1 batch. If [must_succeed] is [`All] @@ -391,9 +389,7 @@ let rec inject_operations ~must_succeed state (operations : L1_operation.t list) match result with | Apply_results.Manager_operation_result {operation_result = Failed (_, error); _} -> - let*! () = - Event.(emit2 Injector.dropping_operation) state op error - in + let*! () = Event.(emit2 dropping_operation) state op error in failure := true ; Lwt.return acc | Apply_results.Manager_operation_result @@ -516,9 +512,7 @@ let inject_pending_operations | [] -> return_unit | _ -> ( let*! () = - Event.(emit1 Injector.injecting_pending) - state - (List.length operations_to_inject) + Event.(emit1 injecting_pending) state (List.length operations_to_inject) in (* TODO: https://gitlab.com/tezos/tezos/-/issues/2813 Decide if some operations must all succeed *) @@ -604,7 +598,7 @@ let revert_included_operations state block = let open Lwt_syntax in let included_infos = remove_included_operation state block in let* () = - Event.(emit1 Injector.revert_operations) + Event.(emit1 revert_operations) state (List.map (fun o -> o.op.hash) included_infos) in @@ -624,14 +618,13 @@ let revert_included_operations state block = let register_confirmed_level state confirmed_level = let open Lwt_syntax in let* () = - Event.(emit Injector.confirmed_level) - (state.signer.pkh, state.tags, confirmed_level) + Event.(emit confirmed_level) (state.signer.pkh, state.tags, confirmed_level) in Block_hash.Table.iter_s (fun block (level, _operations) -> if level <= confirmed_level then let confirmed_ops = remove_included_operation state block in - Event.(emit2 Injector.confirmed_operations) + Event.(emit2 confirmed_operations) state level (List.map (fun o -> o.op.hash) confirmed_ops) @@ -646,7 +639,7 @@ let register_confirmed_level state confirmed_level = let on_new_tezos_head state (head : Alpha_block_services.block_info) (reorg : Alpha_block_services.block_info reorg) = let open Lwt_result_syntax in - let*! () = Event.(emit1 Injector.new_tezos_head) state head.hash in + let*! () = Event.(emit1 new_tezos_head) state head.hash in let*! () = List.iter_s (fun removed_block -> @@ -717,19 +710,16 @@ module Handlers = struct let open Lwt_result_syntax in let state = Worker.state w in (* Errors do not stop the worker but emit an entry in the log. *) - let*! () = Event.(emit3 Injector.request_failed) state r st errs in + let*! () = Event.(emit3 request_failed) state r st errs in return_unit let on_completion w r _ st = let state = Worker.state w in match Request.view r with | Request.View (Add_pending _ | New_tezos_head _) -> - Event.(emit2 Injector.request_completed_debug) state (Request.view r) st + Event.(emit2 request_completed_debug) state (Request.view r) st | View Inject -> - Event.(emit2 Injector.request_completed_notice) - state - (Request.view r) - st + Event.(emit2 request_completed_notice) state (Request.view r) st let on_no_request _ = return_unit diff --git a/src/proto_alpha/lib_tx_rollup/injector_events.ml b/src/proto_alpha/lib_tx_rollup/injector_events.ml new file mode 100644 index 000000000000..2101cd29ac0b --- /dev/null +++ b/src/proto_alpha/lib_tx_rollup/injector_events.ml @@ -0,0 +1,206 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +include Internal_event.Simple +open Injector_worker_types + +let section = ["tx_rollup_node"; "injector"] + +let declare_1 ~name ~msg ~level ?pp1 enc1 = + declare_3 + ~section + ~name + ~msg:("[{signer}: {tags}] " ^ msg) + ~level + ("signer", Signature.Public_key_hash.encoding) + ("tags", Injector_worker_types.tags_encoding) + enc1 + ~pp1:Signature.Public_key_hash.pp_short + ~pp2:Injector_worker_types.pp_tags + ?pp3:pp1 + +let declare_2 ~name ~msg ~level ?pp1 ?pp2 enc1 enc2 = + declare_4 + ~section + ~name + ~msg:("[{signer}: {tags}] " ^ msg) + ~level + ("signer", Signature.Public_key_hash.encoding) + ("tags", Injector_worker_types.tags_encoding) + enc1 + enc2 + ~pp1:Signature.Public_key_hash.pp_short + ~pp2:Injector_worker_types.pp_tags + ?pp3:pp1 + ?pp4:pp2 + +let declare_3 ~name ~msg ~level ?pp1 ?pp2 ?pp3 enc1 enc2 enc3 = + declare_5 + ~section + ~name + ~msg:("[{signer}: {tags}] " ^ msg) + ~level + ("signer", Signature.Public_key_hash.encoding) + ("tags", Injector_worker_types.tags_encoding) + enc1 + enc2 + enc3 + ~pp1:Signature.Public_key_hash.pp_short + ~pp2:Injector_worker_types.pp_tags + ?pp3:pp1 + ?pp4:pp2 + ?pp5:pp3 + +let request_failed = + declare_3 + ~name:"request_failed" + ~msg:"request {view} failed ({worker_status}): {errors}" + ~level:Warning + ("view", Request.encoding) + ~pp1:Request.pp + ("worker_status", Worker_types.request_status_encoding) + ~pp2:Worker_types.pp_status + ("errors", Error_monad.trace_encoding) + ~pp3:Error_monad.pp_print_trace + +let request_completed_notice = + declare_2 + ~name:"request_completed_notice" + ~msg:"{view} {worker_status}" + ~level:Notice + ("view", Request.encoding) + ("worker_status", Worker_types.request_status_encoding) + ~pp1:Request.pp + ~pp2:Worker_types.pp_status + +let request_completed_debug = + declare_2 + ~name:"request_completed_debug" + ~msg:"{view} {worker_status}" + ~level:Debug + ("view", Request.encoding) + ("worker_status", Worker_types.request_status_encoding) + ~pp1:Request.pp + ~pp2:Worker_types.pp_status + +let new_tezos_head = + declare_1 + ~name:"new_tezos_head" + ~msg:"processing new Tezos head {head}" + ~level:Debug + ("head", Block_hash.encoding) + +let injecting_pending = + declare_1 + ~name:"injecting_pending" + ~msg:"Injecting {count} pending operations" + ~level:Notice + ("count", Data_encoding.int31) + +let pp_operations_list ppf operations = + Format.fprintf ppf "@[%a@]" (Format.pp_print_list L1_operation.pp) operations + +let pp_operations_hash_list ppf operations = + Format.fprintf + ppf + "@[%a@]" + (Format.pp_print_list L1_operation.Hash.pp) + operations + +let injecting_operations = + declare_1 + ~name:"injecting_operations" + ~msg:"Injecting operations: {operations}" + ~level:Notice + ("operations", Data_encoding.list L1_operation.encoding) + ~pp1:pp_operations_list + +let simulating_operations = + declare_2 + ~name:"simulating_operations" + ~msg:"Simulating operations (force = {force}): {operations}" + ~level:Debug + ("operations", Data_encoding.list L1_operation.encoding) + ("force", Data_encoding.bool) + ~pp1:pp_operations_list + +let dropping_operation = + declare_2 + ~name:"dropping_operation" + ~msg:"Dropping operation {operation} failing with {error}" + ~level:Notice + ("operation", L1_operation.encoding) + ~pp1:L1_operation.pp + ("error", Environment.Error_monad.trace_encoding) + ~pp2:Environment.Error_monad.pp_trace + +let injected = + declare_1 + ~name:"injected" + ~msg:"Injected in {oph}" + ~level:Notice + ("oph", Operation_hash.encoding) + +let add_pending = + declare_1 + ~name:"add_pending" + ~msg:"Add {operation} to pending" + ~level:Notice + ("operation", L1_operation.encoding) + ~pp1:L1_operation.pp + +let included = + declare_3 + ~name:"included" + ~msg:"Included operations of {block} at level {level}: {operations}" + ~level:Notice + ("block", Block_hash.encoding) + ("level", Data_encoding.int32) + ("operations", Data_encoding.list L1_operation.Hash.encoding) + ~pp3:pp_operations_hash_list + +let revert_operations = + declare_1 + ~name:"revert_operations" + ~msg:"Reverting operations: {operations}" + ~level:Notice + ("operations", Data_encoding.list L1_operation.Hash.encoding) + ~pp1:pp_operations_hash_list + +let confirmed_level = + declare_1 + ~name:"confirmed_level" + ~msg:"Confirmed Tezos level {level}" + ~level:Notice + ("level", Data_encoding.int32) + +let confirmed_operations = + declare_2 + ~name:"confirmed_operations" + ~msg:"Confirmed operations of level {level}: {operations}" + ~level:Notice + ("level", Data_encoding.int32) + ("operations", Data_encoding.list L1_operation.Hash.encoding) + ~pp2:pp_operations_hash_list -- GitLab From 56c6c306bc4c99ac7478f8ef7649547af5a45f1d Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Fri, 29 Apr 2022 12:49:52 +0200 Subject: [PATCH 05/22] Tx_rollup,Node: Functorize injector --- src/proto_alpha/lib_tx_rollup/daemon.ml | 17 +- src/proto_alpha/lib_tx_rollup/injector.ml | 888 +++--------------- src/proto_alpha/lib_tx_rollup/injector.mli | 51 +- .../lib_tx_rollup/injector_events.ml | 366 ++++---- .../lib_tx_rollup/injector_functor.ml | 834 ++++++++++++++++ .../lib_tx_rollup/injector_functor.mli | 29 + .../lib_tx_rollup/injector_sigs.ml | 141 +++ .../lib_tx_rollup/injector_tags.ml | 39 + .../lib_tx_rollup/injector_tags.mli | 35 + .../lib_tx_rollup/injector_worker_types.ml | 49 - .../lib_tx_rollup/injector_worker_types.mli | 16 - 11 files changed, 1395 insertions(+), 1070 deletions(-) create mode 100644 src/proto_alpha/lib_tx_rollup/injector_functor.ml create mode 100644 src/proto_alpha/lib_tx_rollup/injector_functor.mli create mode 100644 src/proto_alpha/lib_tx_rollup/injector_sigs.ml create mode 100644 src/proto_alpha/lib_tx_rollup/injector_tags.ml create mode 100644 src/proto_alpha/lib_tx_rollup/injector_tags.mli diff --git a/src/proto_alpha/lib_tx_rollup/daemon.ml b/src/proto_alpha/lib_tx_rollup/daemon.ml index 5d18e9e727f9..11118dfed68e 100644 --- a/src/proto_alpha/lib_tx_rollup/daemon.ml +++ b/src/proto_alpha/lib_tx_rollup/daemon.ml @@ -500,11 +500,11 @@ let trigger_injection state header = let* () = Event.(emit inject_wait) delay in Lwt_unix.sleep delay in - Injector.inject ~strategy:Injector.Delay_block () + Injector.inject ~strategy:`Delay_block () in ignore promise ; (* Queue request for injection of operation that must be injected each block *) - Injector.inject ~strategy:Injector.Each_block () + Injector.inject ~strategy:`Each_block () let dispatch_withdrawals_on_l1 state level = let open Lwt_result_syntax in @@ -791,6 +791,7 @@ let run configuration cctxt = in let* () = Injector.init + state.cctxt state ~signers: (List.filter_map @@ -798,15 +799,15 @@ let run configuration cctxt = | (None, _, _) -> None | (Some x, strategy, tags) -> Some (x, strategy, tags)) [ - (operator, Injector.Each_block, [`Commitment]); + (operator, `Each_block, [Injector.Commitment]); (* Batches of L2 operations are submitted with a delay after each block, to allow for more operations to arrive and be included in the following block. *) - (signers.submit_batch, Delay_block, [`Submit_batch]); - (signers.finalize_commitment, Each_block, [`Finalize_commitment]); - (signers.remove_commitment, Each_block, [`Remove_commitment]); - (signers.rejection, Each_block, [`Rejection]); - (signers.dispatch_withdrawals, Each_block, [`Dispatch_withdrawals]); + (signers.submit_batch, `Delay_block, [Submit_batch]); + (signers.finalize_commitment, `Each_block, [Finalize_commitment]); + (signers.remove_commitment, `Each_block, [Remove_commitment]); + (signers.rejection, `Each_block, [Rejection]); + (signers.dispatch_withdrawals, `Each_block, [Dispatch_withdrawals]); ]) in let* () = diff --git a/src/proto_alpha/lib_tx_rollup/injector.ml b/src/proto_alpha/lib_tx_rollup/injector.ml index 169f05cddcc2..8a6bff438f00 100644 --- a/src/proto_alpha/lib_tx_rollup/injector.ml +++ b/src/proto_alpha/lib_tx_rollup/injector.ml @@ -23,793 +23,129 @@ (* *) (*****************************************************************************) -open Protocol_client_context -open Protocol -open Alpha_context -open Common -open Injector_worker_types - -(* This is the Tenderbake finality for blocks. *) -(* TODO: https://gitlab.com/tezos/tezos/-/issues/2815 - Centralize this and maybe make it configurable. *) -let confirmations = 2 - -module Op_queue = Hash_queue.Make (L1_operation.Hash) (L1_operation) - -type injection_strategy = Each_block | Delay_block - -(** Information stored about an L1 operation that was injected on a Tezos - node. *) -type injected_info = { - op : L1_operation.t; (** The L1 manager operation. *) - oph : Operation_hash.t; - (** The hash of the operation which contains [op] (this can be an L1 batch of - several manager operations). *) -} - -(** The part of the state which gathers information about injected - operations (but not included). *) -type injected_state = { - injected_operations : injected_info L1_operation.Hash.Table.t; - (** A table mapping L1 manager operation hashes to the injection info for that - operation. *) - injected_ophs : L1_operation.Hash.t list Operation_hash.Table.t; - (** A mapping of all L1 manager operations contained in a L1 batch (i.e. an L1 - operation). *) -} - -(** Information stored about an L1 operation that was included in a Tezos - block. *) -type included_info = { - op : L1_operation.t; (** The L1 manager operation. *) - oph : Operation_hash.t; - (** The hash of the operation which contains [op] (this can be an L1 batch of - several manager operations). *) - l1_block : Block_hash.t; - (** The hash of the L1 block in which the operation was included. *) - l1_level : int32; (** The level of [l1_block]. *) -} - -(** The part of the state which gathers information about - operations which are included in the L1 chain (but not confirmed). *) -type included_state = { - included_operations : included_info L1_operation.Hash.Table.t; - included_in_blocks : (int32 * L1_operation.Hash.t list) Block_hash.Table.t; -} - -(* TODO/TORU: https://gitlab.com/tezos/tezos/-/issues/2755 - Persist injector data on disk *) - -(** The internal state of each injector worker. *) -type state = { - cctxt : Protocol_client_context.full; - (** The client context which is used to perform the injections. *) - signer : signer; (** The signer for this worker. *) - tags : tags; - (** The tags of this worker, for both informative and identification - purposes. *) - strategy : injection_strategy; - (** The strategy of this worker for injecting the pending operations. *) - queue : Op_queue.t; (** The queue of pending operations for this injector. *) - injected : injected_state; (** The information about injected operations. *) - included : included_state; - (** The information about included operations. {b Note}: Operations which - are confirmed are simply removed from the state and do not appear - anymore. *) - rollup_node_state : State.t; - (** The state of the rollup node (essentially to access the stores). *) -} - -(** Builds a client context from another client context but uses logging instead - of printing on stdout directly. This client context cannot make the injector - exit. *) -let injector_context (cctxt : #Client_context.full) = - let log _channel msg = Logs_lwt.info (fun m -> m "%s" msg) in - object - inherit - Protocol_client_context.wrap_full - (new Client_context.proxy_context (cctxt :> Client_context.full)) - - inherit! Client_context.simple_printer log - - method! exit code = - Format.ksprintf Stdlib.failwith "Injector client wants to exit %d" code +open Protocol.Alpha_context +open Injector_sigs + +type tag = + | Commitment + | Submit_batch + | Finalize_commitment + | Remove_commitment + | Rejection + | Dispatch_withdrawals + +module Parameters = struct + type rollup_node_state = State.t + + let events_section = ["tx_rollup_node"; "injector"] + + module Tag = struct + type t = tag + + let compare = Stdlib.compare + + let string_of_tag : t -> string = function + | Submit_batch -> "submit_batch" + | Commitment -> "commitment" + | Finalize_commitment -> "finalize_commitment" + | Remove_commitment -> "remove_commitment" + | Rejection -> "rejection" + | Dispatch_withdrawals -> "dispatch_withdrawals" + + let pp ppf t = Format.pp_print_string ppf (string_of_tag t) + + let encoding : t Data_encoding.t = + let open Data_encoding in + string_enum + (List.map + (fun t -> (string_of_tag t, t)) + [ + Submit_batch; + Commitment; + Finalize_commitment; + Remove_commitment; + Rejection; + Dispatch_withdrawals; + ]) end -let init_injector rollup_node_state ~signer strategy tags = - let open Lwt_result_syntax in - let+ signer = get_signer rollup_node_state.State.cctxt signer in - let queue = Op_queue.create 50_000 in (* Very coarse approximation for the number of operation we expect for each block *) - let n = - Tags.fold - (fun t acc -> - let n = - match t with - | `Commitment -> 3 - | `Submit_batch -> 509 - | `Finalize_commitment -> 3 - | `Remove_commitment -> 3 - | `Rejection -> 3 - | `Dispatch_withdrawals -> 89 - in - acc + n) - tags - 0 - in - { - cctxt = injector_context rollup_node_state.State.cctxt; - signer; - tags; - strategy; - queue; - injected = - { - injected_operations = L1_operation.Hash.Table.create n; - injected_ophs = Operation_hash.Table.create n; - }; - included = + let table_estimated_size = function + | Commitment -> 3 + | Submit_batch -> 509 + | Finalize_commitment -> 3 + | Remove_commitment -> 3 + | Rejection -> 3 + | Dispatch_withdrawals -> 89 + + let fee_parameter _ = + Injection. { - included_operations = L1_operation.Hash.Table.create (confirmations * n); - included_in_blocks = Block_hash.Table.create (confirmations * n); - }; - rollup_node_state; - } - -module Event = struct - include Injector_events - - let emit1 e state x = Event.emit e (state.signer.pkh, state.tags, x) - - let emit2 e state x y = Event.emit e (state.signer.pkh, state.tags, x, y) - - let emit3 e state x y z = Event.emit e (state.signer.pkh, state.tags, x, y, z) -end - -(** Add an operation to the pending queue corresponding to the signer for this - operation. *) -let add_pending_operation state op = - let open Lwt_syntax in - let+ () = Event.(emit1 add_pending) state op in - Op_queue.replace state.queue op.L1_operation.hash op - -(** Mark operations as injected (in [oph]). *) -let add_injected_operations state oph operations = - let infos = - List.map (fun op -> (op.L1_operation.hash, {op; oph})) operations - in - L1_operation.Hash.Table.replace_seq - state.injected.injected_operations - (List.to_seq infos) ; - Operation_hash.Table.replace - state.injected.injected_ophs - oph - (List.map fst infos) - -(** [add_included_operations state oph l1_block l1_level operations] marks the - [operations] as included (in the L1 batch [oph]) in the Tezos block - [l1_block] of level [l1_level]. *) -let add_included_operations state oph l1_block l1_level operations = - let open Lwt_syntax in - let+ () = - Event.(emit3 included) - state - l1_block - l1_level - (List.map (fun o -> o.L1_operation.hash) operations) - in - let infos = - List.map - (fun op -> (op.L1_operation.hash, {op; oph; l1_block; l1_level})) - operations - in - L1_operation.Hash.Table.replace_seq - state.included.included_operations - (List.to_seq infos) ; - Block_hash.Table.replace - state.included.included_in_blocks - l1_block - (l1_level, List.map fst infos) - -(** [remove state oph] removes the operations that correspond to the L1 batch - [oph] from the injected operations in the injector state. This function is - used to move operations from injected to included. *) -let remove_injected_operation state oph = - match Operation_hash.Table.find state.injected.injected_ophs oph with - | None -> - (* Nothing removed *) - [] - | Some mophs -> - Operation_hash.Table.remove state.injected.injected_ophs oph ; - List.fold_left - (fun removed moph -> - match - L1_operation.Hash.Table.find state.injected.injected_operations moph - with - | None -> removed - | Some info -> - L1_operation.Hash.Table.remove - state.injected.injected_operations - moph ; - info :: removed) - [] - mophs - -(** [remove state block] removes the included operations that correspond to all - the L1 batches included in [block]. This function is used when [block] is on - an alternative chain in the case of a reorganization. *) -let remove_included_operation state block = - match Block_hash.Table.find state.included.included_in_blocks block with - | None -> - (* Nothing removed *) - [] - | Some (_level, mophs) -> - Block_hash.Table.remove state.included.included_in_blocks block ; - List.fold_left - (fun removed moph -> - match - L1_operation.Hash.Table.find state.included.included_operations moph - with - | None -> removed - | Some info -> - L1_operation.Hash.Table.remove - state.included.included_operations - moph ; - info :: removed) - [] - mophs - -(** Simulate the injection of [operations]. See {!inject_operations} for the - specification of [must_succeed]. *) -let simulate_operations ~must_succeed state signer - (operations : L1_operation.t list) = - let open Lwt_result_syntax in - let open Annotated_manager_operation in - let force = - match operations with - | [] -> assert false - | [_] -> - (* If there is only one operation, fail when simulation fails *) - false - | _ -> ( - (* We want to see which operation failed in the batch if not all must - succeed *) - match must_succeed with `All -> false | `At_least_one -> true) - in - let*! () = Event.(emit2 simulating_operations) state operations force in - let operations = - List.map - (fun {L1_operation.manager_operation = Manager operation; _} -> - Annotated_manager_operation - (Injection.prepare_manager_operation - ~fee:Limit.unknown - ~gas_limit:Limit.unknown - ~storage_limit:Limit.unknown - operation)) - operations - in - let (Manager_list annot_op) = - Annotated_manager_operation.manager_of_list operations - in - let* (oph, op, result) = - Injection.inject_manager_operation - state.cctxt - ~simulation:true (* Only simulation here *) - ~force - ~chain:state.cctxt#chain - ~block:(`Head 0) - ~source:signer.pkh - ~src_pk:signer.pk - ~src_sk:signer.sk - ~successor_level: - true (* Needed to simulate tx_rollup operations in the next block *) - ~fee:Limit.unknown - ~gas_limit:Limit.unknown - ~storage_limit:Limit.unknown - ~fee_parameter: - { - minimal_fees = Tez.of_mutez_exn 100L; - minimal_nanotez_per_byte = Q.of_int 1000; - minimal_nanotez_per_gas_unit = Q.of_int 100; - force_low_fee = false; - (* TODO: https://gitlab.com/tezos/tezos/-/issues/2811 - Use acceptable values wrt operations to inject *) - fee_cap = Tez.one; - burn_cap = Tez.one; - } - annot_op - in - return (oph, Contents_list op, Apply_results.Contents_result_list result) - -let inject_on_node state packed_contents = - let open Lwt_result_syntax in - (* TODO: https://gitlab.com/tezos/tezos/-/issues/2815 *) - (* Branch to head - 2 for tenderbake *) - let* branch = - Tezos_shell_services.Shell_services.Blocks.hash - state.cctxt - ~chain:state.cctxt#chain - ~block:(`Head 2) - () - in - let unsigned_op_bytes = - Data_encoding.Binary.to_bytes_exn - Operation.unsigned_encoding - ({branch}, packed_contents) - in - let* signature = - Client_keys.sign - state.cctxt - ~watermark:Signature.Generic_operation - state.signer.sk - unsigned_op_bytes - in - let (Contents_list contents) = packed_contents in - let op : _ Operation.t = - {shell = {branch}; protocol_data = {contents; signature = Some signature}} - in - let op_bytes = - Data_encoding.Binary.to_bytes_exn Operation.encoding (Operation.pack op) - in - Tezos_shell_services.Shell_services.Injection.operation - state.cctxt - ~chain:state.cctxt#chain - op_bytes - >>=? fun oph -> - let*! () = Event.(emit1 injected) state oph in - return oph - -(** Inject the given [operations] in an L1 batch. If [must_succeed] is [`All] - then all the operations must succeed in the simulation of injection. If - [must_succeed] is [`At_least_one] at least one operation in the list - [operations] must be successful in the simulation. In any case, only - operations which are known as successful will be included in the injected L1 - batch. {b Note}: [must_succeed = `At_least_one] allows to incrementally build - "or-batches" by iteratively removing operations that fail from the desired - batch. *) -let rec inject_operations ~must_succeed state (operations : L1_operation.t list) - = - let open Lwt_result_syntax in - let* (_oph, packed_contents, result) = - simulate_operations ~must_succeed state state.signer operations - in - let results = Apply_results.to_list result in - let failure = ref false in - let* rev_non_failing_operations = - List.fold_left2_s - ~when_different_lengths: - [ - Exn - (Failure - "Unexpected error: length of operations and result differ in \ - simulation"); - ] - (fun acc op (Apply_results.Contents_result result) -> - match result with - | Apply_results.Manager_operation_result - {operation_result = Failed (_, error); _} -> - let*! () = Event.(emit2 dropping_operation) state op error in - failure := true ; - Lwt.return acc - | Apply_results.Manager_operation_result - {operation_result = Applied _ | Backtracked _ | Skipped _; _} -> - (* Not known to be failing *) - Lwt.return (op :: acc) - | _ -> - (* Only manager operations *) - assert false) - [] - operations - results - in - if !failure then - (* Invariant: must_succeed = `At_least_one, otherwise the simulation would have - returned an error. We try to inject without the failing operation. *) - let operations = List.rev rev_non_failing_operations in - inject_operations ~must_succeed state operations - else - (* Inject on node for real *) - let+ oph = inject_on_node state packed_contents in - (oph, operations) - -(** Returns the (upper bound on) the size of an L1 batch of operations composed - of the manager operations [rev_ops]. *) -let size_l1_batch signer rev_ops = - let contents_list = - List.map - (fun (op : L1_operation.t) -> - let (Manager operation) = op.manager_operation in - let contents = - Manager_operation - { - source = signer.pkh; - operation; - (* Below are dummy values that are only used to approximate the - size. It is thus important that they remain above the real - values if we want the computed size to be an over_approximation - (without having to do a simulation first). *) - (* TODO: https://gitlab.com/tezos/tezos/-/issues/2812 - check the size, or compute them wrt operation kind *) - fee = Tez.of_mutez_exn 3_000_000L; - counter = Z.of_int 500_000; - gas_limit = Gas.Arith.integral_of_int_exn 500_000; - storage_limit = Z.of_int 500_000; - } - in - Contents contents) - rev_ops - in - let (Contents_list contents) = - match Operation.of_list contents_list with - | Error _ -> - (* Cannot happen: rev_ops is non empty and contains only manager - operations *) - assert false - | Ok packed_contents_list -> packed_contents_list - in - let signature = - match signer.pkh with - | Signature.Ed25519 _ -> Signature.of_ed25519 Ed25519.zero - | Secp256k1 _ -> Signature.of_secp256k1 Secp256k1.zero - | P256 _ -> Signature.of_p256 P256.zero - in - let branch = Block_hash.zero in - let operation = + minimal_fees = Tez.of_mutez_exn 100L; + minimal_nanotez_per_byte = Q.of_int 1000; + minimal_nanotez_per_gas_unit = Q.of_int 100; + force_low_fee = false; + (* TODO: https://gitlab.com/tezos/tezos/-/issues/2811 + Use acceptable values wrt operations to inject *) + fee_cap = Tez.one; + burn_cap = Tez.one; + } + + (* Below are dummy values that are only used to approximate the + size. It is thus important that they remain above the real + values if we want the computed size to be an over_approximation + (without having to do a simulation first). *) + (* TODO: https://gitlab.com/tezos/tezos/-/issues/2812 + check the size, or compute them wrt operation kind *) + let approximate_fee_bound _ = { - shell = {branch}; - protocol_data = Operation_data {contents; signature = Some signature}; + fee = Tez.of_mutez_exn 3_000_000L; + counter = Z.of_int 500_000; + gas_limit = Gas.Arith.integral_of_int_exn 500_000; + storage_limit = Z.of_int 500_000; } - in - Data_encoding.Binary.length Operation.encoding operation -(** Retrieve as many operations from the queue while remaining below the size - limit. *) -let get_operations_from_queue ~size_limit state = - let exception Reached_limit of L1_operation.t list in - let rev_ops = - try - Op_queue.fold - (fun _oph op ops -> - let new_ops = op :: ops in - let new_size = size_l1_batch state.signer new_ops in - if new_size > size_limit then raise (Reached_limit ops) ; - new_ops) - state.queue - [] - with Reached_limit ops -> ops - in - List.rev rev_ops + (* TODO: https://gitlab.com/tezos/tezos/-/issues/2813 + Decide if some operations must all succeed *) + let batch_must_succeed _ = `At_least_one -(* Ignore the failures of finalize and remove commitment operations. These - operations fail when there are either no commitment to finalize or to remove - (which can happen when there are no inbox for instance). *) -let ignore_failing_gc_operations operations = function - | Ok res -> Ok (`Injected res) - | Error _ as res -> - let only_gc_operations = - List.for_all - (fun op -> - let (Manager op) = op.L1_operation.manager_operation in - match op with - | Tx_rollup_finalize_commitment _ | Tx_rollup_remove_commitment _ -> - true - | _ -> false) - operations - in - if only_gc_operations then Ok `Ignored else res + let ignore_failing_operation : type kind. kind manager_operation -> _ = + function + | Tx_rollup_remove_commitment _ | Tx_rollup_finalize_commitment _ -> + (* We can keep these operations as there will be at most one of them in + the queue at any given time. *) + `Ignore_keep + | _ -> `Don't_ignore -(** [inject_pending_operations_for ~size_limit state pending] injects - operations from the pending queue [pending], whose total size does - not exceed [size_limit]. Upon successful injection, the - operations are removed from the queue and marked as injected. *) -let inject_pending_operations - ?(size_limit = Constants.max_operation_data_length) state = - let open Lwt_result_syntax in - (* Retrieve and remove operations from pending *) - let operations_to_inject = get_operations_from_queue ~size_limit state in - match operations_to_inject with - | [] -> return_unit - | _ -> ( - let*! () = - Event.(emit1 injecting_pending) state (List.length operations_to_inject) - in - (* TODO: https://gitlab.com/tezos/tezos/-/issues/2813 - Decide if some operations must all succeed *) - let*! res = - inject_operations ~must_succeed:`At_least_one state operations_to_inject - in - let*? res = ignore_failing_gc_operations operations_to_inject res in - match res with - | `Injected (oph, injected_operations) -> - (* Injection succeeded, remove from pending and add to injected *) - List.iter - (fun op -> Op_queue.remove state.queue op.L1_operation.hash) - injected_operations ; - add_injected_operations state oph operations_to_inject ; - return_unit - | `Ignored -> - (* Injection failed but we ignore the failure. We can leave the GC - operations in the queue as their can be only one unique. *) - return_unit) - -(** [register_included_operation state block level oph] marks the manager - operations contained in the L1 batch [oph] as being included in the [block] - of level [level], by moving them from the "injected" state to the "included" - state. *) -let register_included_operation state block level oph = - match remove_injected_operation state oph with - | [] -> Lwt.return_unit - | injected_infos -> - let included_mops = - List.map (fun (i : injected_info) -> i.op) injected_infos - in - add_included_operations state oph block level included_mops - -(** [register_included_operations state block level oph] marks the known (by - this injector) manager operations contained in [block] as being included. *) -let register_included_operations state (block : Alpha_block_services.block_info) - = - List.iter_s - (List.iter_s (fun (op : Alpha_block_services.operation) -> - register_included_operation - state - block.hash - block.header.shell.level - op.hash - (* TODO/TORU: Handle operations for rollup_id here with - callback *))) - block.Alpha_block_services.operations - -(** Returns [true] if an included operation should be re-queued for injection + (** Returns [true] if an included operation should be re-queued for injection when the block in which it is included is reverted (due to a reorganization). *) -let requeue_reverted_operation state op = - let open Lwt_syntax in - let (Manager operation) = op.L1_operation.manager_operation in - match operation with - | Tx_rollup_rejection _ -> - (* TODO: check if rejected commitment in still in main chain *) - return_true - | Tx_rollup_commit {commitment; _} -> ( - let level = L2block.Rollup_level commitment.level in - let* l2_block = State.get_level_l2_block state.rollup_node_state level in - match l2_block with - | None -> - (* We don't know this L2 block, should not happen *) - let+ () = Debug_events.(emit should_not_happen) __LOC__ in - false - | Some l2_block -> ( - match l2_block.L2block.header.commitment with - | None -> return_false - | Some c -> - let commit_hash = - Tx_rollup_commitment.(Compact.hash (Full.compact commitment)) - in - (* Do not re-queue if commitment for this level has changed *) - return Tx_rollup_commitment_hash.(c = commit_hash))) - | _ -> return_true - -(** [revert_included_operations state block] marks the known (by this injector) - manager operations contained in [block] as not being included any more, - typically in the case of a reorganization where [block] is on an alternative - chain. The operations are put back in the pending queue. *) -let revert_included_operations state block = - let open Lwt_syntax in - let included_infos = remove_included_operation state block in - let* () = - Event.(emit1 revert_operations) - state - (List.map (fun o -> o.op.hash) included_infos) - in - (* TODO/TORU: https://gitlab.com/tezos/tezos/-/issues/2814 - maybe put at the front of the queue for re-injection. *) - List.iter_s - (fun {op; _} -> - let* requeue = requeue_reverted_operation state op in - if requeue then add_pending_operation state op else return_unit) - included_infos - -(** [register_confirmed_level state confirmed_level] is called when the level - [confirmed_level] is known as confirmed. In this case, the operations of - block which are below this level are also considered as confirmed and are - removed from the "included" state. These operations cannot be part of a - reorganization so there will be no need to re-inject them anymore. *) -let register_confirmed_level state confirmed_level = - let open Lwt_syntax in - let* () = - Event.(emit confirmed_level) (state.signer.pkh, state.tags, confirmed_level) - in - Block_hash.Table.iter_s - (fun block (level, _operations) -> - if level <= confirmed_level then - let confirmed_ops = remove_included_operation state block in - Event.(emit2 confirmed_operations) - state - level - (List.map (fun o -> o.op.hash) confirmed_ops) - else Lwt.return_unit) - state.included.included_in_blocks - -(** [on_new_tezos_head state head reorg] is called when there is a new Tezos - head (with a potential reorganization [reorg]). It first reverts any blocks - that are in the alternative branch of the reorganization and then registers - the effect of the new branch (the newly included operation and confirmed - operations). *) -let on_new_tezos_head state (head : Alpha_block_services.block_info) - (reorg : Alpha_block_services.block_info reorg) = - let open Lwt_result_syntax in - let*! () = Event.(emit1 new_tezos_head) state head.hash in - let*! () = - List.iter_s - (fun removed_block -> - revert_included_operations state removed_block.Alpha_block_services.hash) - (List.rev reorg.old_chain) - in - let*! () = - List.iter_s - (fun added_block -> register_included_operations state added_block) - reorg.new_chain - in - (* Head is already included in the reorganization, so no need to process it - separately. *) - let confirmed_level = - Int32.sub - head.Alpha_block_services.header.shell.level - (Int32.of_int confirmations) - in - let*! () = - if confirmed_level >= 0l then register_confirmed_level state confirmed_level - else Lwt.return_unit - in - return_unit - -(* The request {Request.Inject} triggers an injection of the operations - the pending queue. *) -let on_inject state = inject_pending_operations state - -module Types = struct - type nonrec state = state - - type parameters = { - rollup_node_state : State.t; - strategy : injection_strategy; - tags : Tags.t; - } + let requeue_reverted_operation (type kind) state + (operation : kind manager_operation) = + let open Lwt_syntax in + match operation with + | Tx_rollup_rejection _ -> + (* TODO: check if rejected commitment in still in main chain *) + return_true + | Tx_rollup_commit {commitment; _} -> ( + let level = L2block.Rollup_level commitment.level in + let* l2_block = State.get_level_l2_block state level in + match l2_block with + | None -> + (* We don't know this L2 block, should not happen *) + let+ () = Debug_events.(emit should_not_happen) __LOC__ in + false + | Some l2_block -> ( + match l2_block.L2block.header.commitment with + | None -> return_false + | Some c -> + let commit_hash = + Tx_rollup_commitment.(Compact.hash (Full.compact commitment)) + in + (* Do not re-queue if commitment for this level has changed *) + return Tx_rollup_commitment_hash.(c = commit_hash))) + | _ -> return_true end -(* The worker for the injector. *) -module Worker = Worker.Make (Name) (Dummy_event) (Request) (Types) (Logger) - -(* The queue for the requests to the injector worker is infinite. *) -type worker = Worker.infinite Worker.queue Worker.t - -module Handlers = struct - type self = worker - - let on_request : type r. worker -> r Request.t -> r tzresult Lwt.t = - fun w request -> - let open Lwt_result_syntax in - let state = Worker.state w in - match request with - | Request.Add_pending op -> - let*! () = add_pending_operation state op in - return_unit - | Request.New_tezos_head (head, reorg) -> on_new_tezos_head state head reorg - | Request.Inject -> on_inject state - - let on_request w r = - (* The execution of the request handler is protected to avoid stopping the - worker in case of an exception. *) - protect @@ fun () -> on_request w r - - let on_launch _w signer Types.{rollup_node_state; strategy; tags} = - init_injector rollup_node_state ~signer strategy tags - - let on_error w r st errs = - let open Lwt_result_syntax in - let state = Worker.state w in - (* Errors do not stop the worker but emit an entry in the log. *) - let*! () = Event.(emit3 request_failed) state r st errs in - return_unit - - let on_completion w r _ st = - let state = Worker.state w in - match Request.view r with - | Request.View (Add_pending _ | New_tezos_head _) -> - Event.(emit2 request_completed_debug) state (Request.view r) st - | View Inject -> - Event.(emit2 request_completed_notice) state (Request.view r) st - - let on_no_request _ = return_unit - - let on_close _w = Lwt.return_unit -end - -let table = Worker.create_table Queue - -(* TODO/TORU: https://gitlab.com/tezos/tezos/-/issues/2754 - Injector worker in a separate process *) -let init rollup_node_state ~signers = - let open Lwt_result_syntax in - let signers_map = - List.fold_left - (fun acc (signer, strategy, tags) -> - let tags = Tags.of_list tags in - let (strategy, tags) = - match Signature.Public_key_hash.Map.find_opt signer acc with - | None -> (strategy, tags) - | Some (other_strategy, other_tags) -> - let strategy = - match (strategy, other_strategy) with - | (Each_block, Each_block) -> Each_block - | (Delay_block, _) | (_, Delay_block) -> - (* Delay_block strategy takes over because we can always wait a - little bit more to inject operation which are to be injected - "each block". *) - Delay_block - in - (strategy, Tags.union other_tags tags) - in - Signature.Public_key_hash.Map.add signer (strategy, tags) acc) - Signature.Public_key_hash.Map.empty - signers - in - Signature.Public_key_hash.Map.iter_es - (fun signer (strategy, tags) -> - let+ worker = - Worker.launch - table - signer - {rollup_node_state; strategy; tags} - (module Handlers) - in - ignore worker) - signers_map - -let worker_of_signer signer_pkh = - match Worker.find_opt table signer_pkh with - | None -> - (* TODO: https://gitlab.com/tezos/tezos/-/issues/2818 - maybe lazily start worker here *) - error (Error.No_worker_for_source signer_pkh) - | Some worker -> ok worker - -let add_pending_operation ~source op = - let open Lwt_result_syntax in - let*? w = worker_of_signer source in - let l1_operation = L1_operation.make op in - let*! () = Worker.Queue.push_request w (Request.Add_pending l1_operation) in - return_unit - -let new_tezos_head h reorg = - let workers = Worker.list table in - List.iter_p - (fun (_signer, w) -> - Worker.Queue.push_request w (Request.New_tezos_head (h, reorg))) - workers - -let has_tag_in ~tags state = - match tags with - | None -> - (* Not filtering on tags *) - true - | Some tags -> not (Tags.disjoint state.tags tags) - -let has_strategy ~strategy state = - match strategy with - | None -> - (* Not filtering on strategy *) - true - | Some strategy -> state.strategy = strategy - -let inject ?tags ?strategy () = - let workers = Worker.list table in - let tags = Option.map Tags.of_list tags in - List.iter_p - (fun (_signer, w) -> - let worker_state = Worker.state w in - if has_tag_in ~tags worker_state && has_strategy ~strategy worker_state - then Worker.Queue.push_request w Request.Inject - else Lwt.return_unit) - workers +include Injector_functor.Make (Parameters) diff --git a/src/proto_alpha/lib_tx_rollup/injector.mli b/src/proto_alpha/lib_tx_rollup/injector.mli index 3ec5800c6fac..f23eda69a9a4 100644 --- a/src/proto_alpha/lib_tx_rollup/injector.mli +++ b/src/proto_alpha/lib_tx_rollup/injector.mli @@ -23,46 +23,13 @@ (* *) (*****************************************************************************) -open Protocol_client_context -open Protocol -open Alpha_context -open Common +type tag = + | Commitment + | Submit_batch + | Finalize_commitment + | Remove_commitment + | Rejection + | Dispatch_withdrawals -type injection_strategy = - | Each_block (** Inject pending operations after each new L1 block *) - | Delay_block - (** Wait for some time after the L1 block is produced to inject pending - operations. This strategy allows for maximizing the number of the same - kind of operations to include in a block. *) - -(** Initializes the injector with the rollup node state, for a list of - signers. Each signer has its own worker with a queue of operations to - inject. *) -val init : - State.t -> - signers: - (public_key_hash * injection_strategy * Injector_worker_types.tag list) list -> - unit tzresult Lwt.t - -(** Add an operation as pending injection in the injector. *) -val add_pending_operation : - source:public_key_hash -> 'a manager_operation -> unit tzresult Lwt.t - -(** Notify the injector of a new Tezos head. The injector marks the operations - appropriately (for instance reverted operations that are part of a - reorganization are put back in the pending queue). When an operation is - considered as {e confirmed}, it disappears from the injector. *) -val new_tezos_head : - Alpha_block_services.block_info -> - Alpha_block_services.block_info reorg -> - unit Lwt.t - -(** Trigger an injection of the pending operations for all workers. If [tags] - is given, only the workers which have a tag in [tags] inject their pending - operations. If [strategy] is given, only workers which have this strategy - inject their pending operations. *) -val inject : - ?tags:Injector_worker_types.tag list -> - ?strategy:injection_strategy -> - unit -> - unit Lwt.t +include + Injector_sigs.S with type rollup_node_state := State.t and type tag := tag diff --git a/src/proto_alpha/lib_tx_rollup/injector_events.ml b/src/proto_alpha/lib_tx_rollup/injector_events.ml index 2101cd29ac0b..a1f2682fe7c9 100644 --- a/src/proto_alpha/lib_tx_rollup/injector_events.ml +++ b/src/proto_alpha/lib_tx_rollup/injector_events.ml @@ -23,184 +23,192 @@ (* *) (*****************************************************************************) -include Internal_event.Simple open Injector_worker_types -let section = ["tx_rollup_node"; "injector"] - -let declare_1 ~name ~msg ~level ?pp1 enc1 = - declare_3 - ~section - ~name - ~msg:("[{signer}: {tags}] " ^ msg) - ~level - ("signer", Signature.Public_key_hash.encoding) - ("tags", Injector_worker_types.tags_encoding) - enc1 - ~pp1:Signature.Public_key_hash.pp_short - ~pp2:Injector_worker_types.pp_tags - ?pp3:pp1 - -let declare_2 ~name ~msg ~level ?pp1 ?pp2 enc1 enc2 = - declare_4 - ~section - ~name - ~msg:("[{signer}: {tags}] " ^ msg) - ~level - ("signer", Signature.Public_key_hash.encoding) - ("tags", Injector_worker_types.tags_encoding) - enc1 - enc2 - ~pp1:Signature.Public_key_hash.pp_short - ~pp2:Injector_worker_types.pp_tags - ?pp3:pp1 - ?pp4:pp2 - -let declare_3 ~name ~msg ~level ?pp1 ?pp2 ?pp3 enc1 enc2 enc3 = - declare_5 - ~section - ~name - ~msg:("[{signer}: {tags}] " ^ msg) - ~level - ("signer", Signature.Public_key_hash.encoding) - ("tags", Injector_worker_types.tags_encoding) - enc1 - enc2 - enc3 - ~pp1:Signature.Public_key_hash.pp_short - ~pp2:Injector_worker_types.pp_tags - ?pp3:pp1 - ?pp4:pp2 - ?pp5:pp3 - -let request_failed = - declare_3 - ~name:"request_failed" - ~msg:"request {view} failed ({worker_status}): {errors}" - ~level:Warning - ("view", Request.encoding) - ~pp1:Request.pp - ("worker_status", Worker_types.request_status_encoding) - ~pp2:Worker_types.pp_status - ("errors", Error_monad.trace_encoding) - ~pp3:Error_monad.pp_print_trace - -let request_completed_notice = - declare_2 - ~name:"request_completed_notice" - ~msg:"{view} {worker_status}" - ~level:Notice - ("view", Request.encoding) - ("worker_status", Worker_types.request_status_encoding) - ~pp1:Request.pp - ~pp2:Worker_types.pp_status - -let request_completed_debug = - declare_2 - ~name:"request_completed_debug" - ~msg:"{view} {worker_status}" - ~level:Debug - ("view", Request.encoding) - ("worker_status", Worker_types.request_status_encoding) - ~pp1:Request.pp - ~pp2:Worker_types.pp_status - -let new_tezos_head = - declare_1 - ~name:"new_tezos_head" - ~msg:"processing new Tezos head {head}" - ~level:Debug - ("head", Block_hash.encoding) - -let injecting_pending = - declare_1 - ~name:"injecting_pending" - ~msg:"Injecting {count} pending operations" - ~level:Notice - ("count", Data_encoding.int31) - -let pp_operations_list ppf operations = - Format.fprintf ppf "@[%a@]" (Format.pp_print_list L1_operation.pp) operations - -let pp_operations_hash_list ppf operations = - Format.fprintf - ppf - "@[%a@]" - (Format.pp_print_list L1_operation.Hash.pp) - operations - -let injecting_operations = - declare_1 - ~name:"injecting_operations" - ~msg:"Injecting operations: {operations}" - ~level:Notice - ("operations", Data_encoding.list L1_operation.encoding) - ~pp1:pp_operations_list - -let simulating_operations = - declare_2 - ~name:"simulating_operations" - ~msg:"Simulating operations (force = {force}): {operations}" - ~level:Debug - ("operations", Data_encoding.list L1_operation.encoding) - ("force", Data_encoding.bool) - ~pp1:pp_operations_list - -let dropping_operation = - declare_2 - ~name:"dropping_operation" - ~msg:"Dropping operation {operation} failing with {error}" - ~level:Notice - ("operation", L1_operation.encoding) - ~pp1:L1_operation.pp - ("error", Environment.Error_monad.trace_encoding) - ~pp2:Environment.Error_monad.pp_trace - -let injected = - declare_1 - ~name:"injected" - ~msg:"Injected in {oph}" - ~level:Notice - ("oph", Operation_hash.encoding) - -let add_pending = - declare_1 - ~name:"add_pending" - ~msg:"Add {operation} to pending" - ~level:Notice - ("operation", L1_operation.encoding) - ~pp1:L1_operation.pp - -let included = - declare_3 - ~name:"included" - ~msg:"Included operations of {block} at level {level}: {operations}" - ~level:Notice - ("block", Block_hash.encoding) - ("level", Data_encoding.int32) - ("operations", Data_encoding.list L1_operation.Hash.encoding) - ~pp3:pp_operations_hash_list - -let revert_operations = - declare_1 - ~name:"revert_operations" - ~msg:"Reverting operations: {operations}" - ~level:Notice - ("operations", Data_encoding.list L1_operation.Hash.encoding) - ~pp1:pp_operations_hash_list - -let confirmed_level = - declare_1 - ~name:"confirmed_level" - ~msg:"Confirmed Tezos level {level}" - ~level:Notice - ("level", Data_encoding.int32) - -let confirmed_operations = - declare_2 - ~name:"confirmed_operations" - ~msg:"Confirmed operations of level {level}: {operations}" - ~level:Notice - ("level", Data_encoding.int32) - ("operations", Data_encoding.list L1_operation.Hash.encoding) - ~pp2:pp_operations_hash_list +module Make (Rollup : Injector_sigs.PARAMETERS) = struct + module Tags = Injector_tags.Make (Rollup.Tag) + include Internal_event.Simple + + let section = Rollup.events_section + + let declare_1 ~name ~msg ~level ?pp1 enc1 = + declare_3 + ~section + ~name + ~msg:("[{signer}: {tags}] " ^ msg) + ~level + ("signer", Signature.Public_key_hash.encoding) + ("tags", Tags.encoding) + enc1 + ~pp1:Signature.Public_key_hash.pp_short + ~pp2:Tags.pp + ?pp3:pp1 + + let declare_2 ~name ~msg ~level ?pp1 ?pp2 enc1 enc2 = + declare_4 + ~section + ~name + ~msg:("[{signer}: {tags}] " ^ msg) + ~level + ("signer", Signature.Public_key_hash.encoding) + ("tags", Tags.encoding) + enc1 + enc2 + ~pp1:Signature.Public_key_hash.pp_short + ~pp2:Tags.pp + ?pp3:pp1 + ?pp4:pp2 + + let declare_3 ~name ~msg ~level ?pp1 ?pp2 ?pp3 enc1 enc2 enc3 = + declare_5 + ~section + ~name + ~msg:("[{signer}: {tags}] " ^ msg) + ~level + ("signer", Signature.Public_key_hash.encoding) + ("tags", Tags.encoding) + enc1 + enc2 + enc3 + ~pp1:Signature.Public_key_hash.pp_short + ~pp2:Tags.pp + ?pp3:pp1 + ?pp4:pp2 + ?pp5:pp3 + + let request_failed = + declare_3 + ~name:"request_failed" + ~msg:"request {view} failed ({worker_status}): {errors}" + ~level:Warning + ("view", Request.encoding) + ~pp1:Request.pp + ("worker_status", Worker_types.request_status_encoding) + ~pp2:Worker_types.pp_status + ("errors", Error_monad.trace_encoding) + ~pp3:Error_monad.pp_print_trace + + let request_completed_notice = + declare_2 + ~name:"request_completed_notice" + ~msg:"{view} {worker_status}" + ~level:Notice + ("view", Request.encoding) + ("worker_status", Worker_types.request_status_encoding) + ~pp1:Request.pp + ~pp2:Worker_types.pp_status + + let request_completed_debug = + declare_2 + ~name:"request_completed_debug" + ~msg:"{view} {worker_status}" + ~level:Debug + ("view", Request.encoding) + ("worker_status", Worker_types.request_status_encoding) + ~pp1:Request.pp + ~pp2:Worker_types.pp_status + + let new_tezos_head = + declare_1 + ~name:"new_tezos_head" + ~msg:"processing new Tezos head {head}" + ~level:Debug + ("head", Block_hash.encoding) + + let injecting_pending = + declare_1 + ~name:"injecting_pending" + ~msg:"Injecting {count} pending operations" + ~level:Notice + ("count", Data_encoding.int31) + + let pp_operations_list ppf operations = + Format.fprintf + ppf + "@[%a@]" + (Format.pp_print_list L1_operation.pp) + operations + + let pp_operations_hash_list ppf operations = + Format.fprintf + ppf + "@[%a@]" + (Format.pp_print_list L1_operation.Hash.pp) + operations + + let injecting_operations = + declare_1 + ~name:"injecting_operations" + ~msg:"Injecting operations: {operations}" + ~level:Notice + ("operations", Data_encoding.list L1_operation.encoding) + ~pp1:pp_operations_list + + let simulating_operations = + declare_2 + ~name:"simulating_operations" + ~msg:"Simulating operations (force = {force}): {operations}" + ~level:Debug + ("operations", Data_encoding.list L1_operation.encoding) + ("force", Data_encoding.bool) + ~pp1:pp_operations_list + + let dropping_operation = + declare_2 + ~name:"dropping_operation" + ~msg:"Dropping operation {operation} failing with {error}" + ~level:Notice + ("operation", L1_operation.encoding) + ~pp1:L1_operation.pp + ("error", Environment.Error_monad.trace_encoding) + ~pp2:Environment.Error_monad.pp_trace + + let injected = + declare_1 + ~name:"injected" + ~msg:"Injected in {oph}" + ~level:Notice + ("oph", Operation_hash.encoding) + + let add_pending = + declare_1 + ~name:"add_pending" + ~msg:"Add {operation} to pending" + ~level:Notice + ("operation", L1_operation.encoding) + ~pp1:L1_operation.pp + + let included = + declare_3 + ~name:"included" + ~msg:"Included operations of {block} at level {level}: {operations}" + ~level:Notice + ("block", Block_hash.encoding) + ("level", Data_encoding.int32) + ("operations", Data_encoding.list L1_operation.Hash.encoding) + ~pp3:pp_operations_hash_list + + let revert_operations = + declare_1 + ~name:"revert_operations" + ~msg:"Reverting operations: {operations}" + ~level:Notice + ("operations", Data_encoding.list L1_operation.Hash.encoding) + ~pp1:pp_operations_hash_list + + let confirmed_level = + declare_1 + ~name:"confirmed_level" + ~msg:"Confirmed Tezos level {level}" + ~level:Notice + ("level", Data_encoding.int32) + + let confirmed_operations = + declare_2 + ~name:"confirmed_operations" + ~msg:"Confirmed operations of level {level}: {operations}" + ~level:Notice + ("level", Data_encoding.int32) + ("operations", Data_encoding.list L1_operation.Hash.encoding) + ~pp2:pp_operations_hash_list +end diff --git a/src/proto_alpha/lib_tx_rollup/injector_functor.ml b/src/proto_alpha/lib_tx_rollup/injector_functor.ml new file mode 100644 index 000000000000..3f342ec57ce9 --- /dev/null +++ b/src/proto_alpha/lib_tx_rollup/injector_functor.ml @@ -0,0 +1,834 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs, *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +open Protocol_client_context +open Protocol +open Alpha_context +open Common +open Injector_worker_types +open Injector_sigs + +(* This is the Tenderbake finality for blocks. *) +(* TODO: https://gitlab.com/tezos/tezos/-/issues/2815 + Centralize this and maybe make it configurable. *) +let confirmations = 2 + +module Op_queue = Hash_queue.Make (L1_operation.Hash) (L1_operation) + +type injection_strategy = [`Each_block | `Delay_block] + +(** Information stored about an L1 operation that was injected on a Tezos + node. *) +type injected_info = { + op : L1_operation.t; (** The L1 manager operation. *) + oph : Operation_hash.t; + (** The hash of the operation which contains [op] (this can be an L1 batch of + several manager operations). *) +} + +(** The part of the state which gathers information about injected + operations (but not included). *) +type injected_state = { + injected_operations : injected_info L1_operation.Hash.Table.t; + (** A table mapping L1 manager operation hashes to the injection info for that + operation. *) + injected_ophs : L1_operation.Hash.t list Operation_hash.Table.t; + (** A mapping of all L1 manager operations contained in a L1 batch (i.e. an L1 + operation). *) +} + +(** Information stored about an L1 operation that was included in a Tezos + block. *) +type included_info = { + op : L1_operation.t; (** The L1 manager operation. *) + oph : Operation_hash.t; + (** The hash of the operation which contains [op] (this can be an L1 batch of + several manager operations). *) + l1_block : Block_hash.t; + (** The hash of the L1 block in which the operation was included. *) + l1_level : int32; (** The level of [l1_block]. *) +} + +(** The part of the state which gathers information about + operations which are included in the L1 chain (but not confirmed). *) +type included_state = { + included_operations : included_info L1_operation.Hash.Table.t; + included_in_blocks : (int32 * L1_operation.Hash.t list) Block_hash.Table.t; +} + +(* TODO/TORU: https://gitlab.com/tezos/tezos/-/issues/2755 + Persist injector data on disk *) + +(** Builds a client context from another client context but uses logging instead + of printing on stdout directly. This client context cannot make the injector + exit. *) +let injector_context (cctxt : #Protocol_client_context.full) = + let log _channel msg = Logs_lwt.info (fun m -> m "%s" msg) in + object + inherit + Protocol_client_context.wrap_full + (new Client_context.proxy_context (cctxt :> Client_context.full)) + + inherit! Client_context.simple_printer log + + method! exit code = + Format.ksprintf Stdlib.failwith "Injector client wants to exit %d" code + end + +module Make (Rollup : PARAMETERS) = struct + module Tags = Injector_tags.Make (Rollup.Tag) + + (** The internal state of each injector worker. *) + type state = { + cctxt : Protocol_client_context.full; + (** The client context which is used to perform the injections. *) + signer : signer; (** The signer for this worker. *) + tags : Tags.t; + (** The tags of this worker, for both informative and identification + purposes. *) + strategy : injection_strategy; + (** The strategy of this worker for injecting the pending operations. *) + queue : Op_queue.t; + (** The queue of pending operations for this injector. *) + injected : injected_state; + (** The information about injected operations. *) + included : included_state; + (** The information about included operations. {b Note}: Operations which + are confirmed are simply removed from the state and do not appear + anymore. *) + rollup_node_state : Rollup.rollup_node_state; + (** The state of the rollup node. *) + } + + let init_injector cctxt rollup_node_state ~signer strategy tags = + let open Lwt_result_syntax in + let+ signer = get_signer cctxt signer in + let queue = Op_queue.create 50_000 in + (* Very coarse approximation for the number of operation we expect for each + block *) + let n = + Tags.fold (fun t acc -> acc + Rollup.table_estimated_size t) tags 0 + in + { + cctxt = injector_context (cctxt :> #Protocol_client_context.full); + signer; + tags; + strategy; + queue; + injected = + { + injected_operations = L1_operation.Hash.Table.create n; + injected_ophs = Operation_hash.Table.create n; + }; + included = + { + included_operations = + L1_operation.Hash.Table.create (confirmations * n); + included_in_blocks = Block_hash.Table.create (confirmations * n); + }; + rollup_node_state; + } + + module Event = struct + include Injector_events.Make (Rollup) + + let emit1 e state x = Event.emit e (state.signer.pkh, state.tags, x) + + let emit2 e state x y = Event.emit e (state.signer.pkh, state.tags, x, y) + + let emit3 e state x y z = + Event.emit e (state.signer.pkh, state.tags, x, y, z) + end + + (** Add an operation to the pending queue corresponding to the signer for this + operation. *) + let add_pending_operation state op = + let open Lwt_syntax in + let+ () = Event.(emit1 add_pending) state op in + Op_queue.replace state.queue op.L1_operation.hash op + + (** Mark operations as injected (in [oph]). *) + let add_injected_operations state oph operations = + let infos = + List.map (fun op -> (op.L1_operation.hash, {op; oph})) operations + in + L1_operation.Hash.Table.replace_seq + state.injected.injected_operations + (List.to_seq infos) ; + Operation_hash.Table.replace + state.injected.injected_ophs + oph + (List.map fst infos) + + (** [add_included_operations state oph l1_block l1_level operations] marks the + [operations] as included (in the L1 batch [oph]) in the Tezos block + [l1_block] of level [l1_level]. *) + let add_included_operations state oph l1_block l1_level operations = + let open Lwt_syntax in + let+ () = + Event.(emit3 included) + state + l1_block + l1_level + (List.map (fun o -> o.L1_operation.hash) operations) + in + let infos = + List.map + (fun op -> (op.L1_operation.hash, {op; oph; l1_block; l1_level})) + operations + in + L1_operation.Hash.Table.replace_seq + state.included.included_operations + (List.to_seq infos) ; + Block_hash.Table.replace + state.included.included_in_blocks + l1_block + (l1_level, List.map fst infos) + + (** [remove state oph] removes the operations that correspond to the L1 batch + [oph] from the injected operations in the injector state. This function is + used to move operations from injected to included. *) + let remove_injected_operation state oph = + match Operation_hash.Table.find state.injected.injected_ophs oph with + | None -> + (* Nothing removed *) + [] + | Some mophs -> + Operation_hash.Table.remove state.injected.injected_ophs oph ; + List.fold_left + (fun removed moph -> + match + L1_operation.Hash.Table.find + state.injected.injected_operations + moph + with + | None -> removed + | Some info -> + L1_operation.Hash.Table.remove + state.injected.injected_operations + moph ; + info :: removed) + [] + mophs + + (** [remove state block] removes the included operations that correspond to all + the L1 batches included in [block]. This function is used when [block] is on + an alternative chain in the case of a reorganization. *) + let remove_included_operation state block = + match Block_hash.Table.find state.included.included_in_blocks block with + | None -> + (* Nothing removed *) + [] + | Some (_level, mophs) -> + Block_hash.Table.remove state.included.included_in_blocks block ; + List.fold_left + (fun removed moph -> + match + L1_operation.Hash.Table.find + state.included.included_operations + moph + with + | None -> removed + | Some info -> + L1_operation.Hash.Table.remove + state.included.included_operations + moph ; + info :: removed) + [] + mophs + + let fee_parameter_of_operations ops = + List.fold_left + (fun acc {L1_operation.manager_operation = Manager op; _} -> + let param = Rollup.fee_parameter op in + Injection. + { + minimal_fees = Tez.max acc.minimal_fees param.minimal_fees; + minimal_nanotez_per_byte = + Q.max acc.minimal_nanotez_per_byte param.minimal_nanotez_per_byte; + minimal_nanotez_per_gas_unit = + Q.max + acc.minimal_nanotez_per_gas_unit + param.minimal_nanotez_per_gas_unit; + force_low_fee = acc.force_low_fee || param.force_low_fee; + fee_cap = + WithExceptions.Result.get_ok + ~loc:__LOC__ + Tez.(acc.fee_cap +? param.fee_cap); + burn_cap = + WithExceptions.Result.get_ok + ~loc:__LOC__ + Tez.(acc.burn_cap +? param.burn_cap); + }) + Injection. + { + minimal_fees = Tez.zero; + minimal_nanotez_per_byte = Q.zero; + minimal_nanotez_per_gas_unit = Q.zero; + force_low_fee = false; + fee_cap = Tez.zero; + burn_cap = Tez.zero; + } + ops + + (** Simulate the injection of [operations]. See {!inject_operations} for the + specification of [must_succeed]. *) + let simulate_operations ~must_succeed state signer + (operations : L1_operation.t list) = + let open Lwt_result_syntax in + let open Annotated_manager_operation in + let force = + match operations with + | [] -> assert false + | [_] -> + (* If there is only one operation, fail when simulation fails *) + false + | _ -> ( + (* We want to see which operation failed in the batch if not all must + succeed *) + match must_succeed with `All -> false | `At_least_one -> true) + in + let*! () = Event.(emit2 simulating_operations) state operations force in + let fee_parameter = fee_parameter_of_operations operations in + let operations = + List.map + (fun {L1_operation.manager_operation = Manager operation; _} -> + Annotated_manager_operation + (Injection.prepare_manager_operation + ~fee:Limit.unknown + ~gas_limit:Limit.unknown + ~storage_limit:Limit.unknown + operation)) + operations + in + let (Manager_list annot_op) = + Annotated_manager_operation.manager_of_list operations + in + let* (oph, op, result) = + Injection.inject_manager_operation + state.cctxt + ~simulation:true (* Only simulation here *) + ~force + ~chain:state.cctxt#chain + ~block:(`Head 0) + ~source:signer.pkh + ~src_pk:signer.pk + ~src_sk:signer.sk + ~successor_level: + true (* Needed to simulate tx_rollup operations in the next block *) + ~fee:Limit.unknown + ~gas_limit:Limit.unknown + ~storage_limit:Limit.unknown + ~fee_parameter + annot_op + in + return (oph, Contents_list op, Apply_results.Contents_result_list result) + + let inject_on_node state packed_contents = + let open Lwt_result_syntax in + (* TODO: https://gitlab.com/tezos/tezos/-/issues/2815 *) + (* Branch to head - 2 for tenderbake *) + let* branch = + Tezos_shell_services.Shell_services.Blocks.hash + state.cctxt + ~chain:state.cctxt#chain + ~block:(`Head 2) + () + in + let unsigned_op_bytes = + Data_encoding.Binary.to_bytes_exn + Operation.unsigned_encoding + ({branch}, packed_contents) + in + let* signature = + Client_keys.sign + state.cctxt + ~watermark:Signature.Generic_operation + state.signer.sk + unsigned_op_bytes + in + let (Contents_list contents) = packed_contents in + let op : _ Operation.t = + {shell = {branch}; protocol_data = {contents; signature = Some signature}} + in + let op_bytes = + Data_encoding.Binary.to_bytes_exn Operation.encoding (Operation.pack op) + in + Tezos_shell_services.Shell_services.Injection.operation + state.cctxt + ~chain:state.cctxt#chain + op_bytes + >>=? fun oph -> + let*! () = Event.(emit1 injected) state oph in + return oph + + (** Inject the given [operations] in an L1 batch. If [must_succeed] is [`All] + then all the operations must succeed in the simulation of injection. If + [must_succeed] is [`At_least_one] at least one operation in the list + [operations] must be successful in the simulation. In any case, only + operations which are known as successful will be included in the injected L1 + batch. {b Note}: [must_succeed = `At_least_one] allows to incrementally build + "or-batches" by iteratively removing operations that fail from the desired + batch. *) + let rec inject_operations ~must_succeed state + (operations : L1_operation.t list) = + let open Lwt_result_syntax in + let* (_oph, packed_contents, result) = + simulate_operations ~must_succeed state state.signer operations + in + let results = Apply_results.to_list result in + let failure = ref false in + let* rev_non_failing_operations = + List.fold_left2_s + ~when_different_lengths: + [ + Exn + (Failure + "Unexpected error: length of operations and result differ in \ + simulation"); + ] + (fun acc op (Apply_results.Contents_result result) -> + match result with + | Apply_results.Manager_operation_result + {operation_result = Failed (_, error); _} -> + let*! () = Event.(emit2 dropping_operation) state op error in + failure := true ; + Lwt.return acc + | Apply_results.Manager_operation_result + {operation_result = Applied _ | Backtracked _ | Skipped _; _} -> + (* Not known to be failing *) + Lwt.return (op :: acc) + | _ -> + (* Only manager operations *) + assert false) + [] + operations + results + in + if !failure then + (* Invariant: must_succeed = `At_least_one, otherwise the simulation would have + returned an error. We try to inject without the failing operation. *) + let operations = List.rev rev_non_failing_operations in + inject_operations ~must_succeed state operations + else + (* Inject on node for real *) + let+ oph = inject_on_node state packed_contents in + (oph, operations) + + (** Returns the (upper bound on) the size of an L1 batch of operations composed + of the manager operations [rev_ops]. *) + let size_l1_batch signer rev_ops = + let contents_list = + List.map + (fun (op : L1_operation.t) -> + let (Manager operation) = op.manager_operation in + let {fee; counter; gas_limit; storage_limit} = + Rollup.approximate_fee_bound operation + in + let contents = + Manager_operation + { + source = signer.pkh; + operation; + fee; + counter; + gas_limit; + storage_limit; + } + in + Contents contents) + rev_ops + in + let (Contents_list contents) = + match Operation.of_list contents_list with + | Error _ -> + (* Cannot happen: rev_ops is non empty and contains only manager + operations *) + assert false + | Ok packed_contents_list -> packed_contents_list + in + let signature = + match signer.pkh with + | Signature.Ed25519 _ -> Signature.of_ed25519 Ed25519.zero + | Secp256k1 _ -> Signature.of_secp256k1 Secp256k1.zero + | P256 _ -> Signature.of_p256 P256.zero + in + let branch = Block_hash.zero in + let operation = + { + shell = {branch}; + protocol_data = Operation_data {contents; signature = Some signature}; + } + in + Data_encoding.Binary.length Operation.encoding operation + + (** Retrieve as many operations from the queue while remaining below the size + limit. *) + let get_operations_from_queue ~size_limit state = + let exception Reached_limit of L1_operation.t list in + let rev_ops = + try + Op_queue.fold + (fun _oph op ops -> + let new_ops = op :: ops in + let new_size = size_l1_batch state.signer new_ops in + if new_size > size_limit then raise (Reached_limit ops) ; + new_ops) + state.queue + [] + with Reached_limit ops -> ops + in + List.rev rev_ops + + (* Ignore the failures of finalize and remove commitment operations. These + operations fail when there are either no commitment to finalize or to remove + (which can happen when there are no inbox for instance). *) + let ignore_ignorable_failing_operations operations = function + | Ok res -> Ok (`Injected res) + | Error _ as res -> + let open Result_syntax in + let+ operations_to_drop = + List.fold_left_e + (fun to_drop op -> + let (Manager operation) = op.L1_operation.manager_operation in + match Rollup.ignore_failing_operation operation with + | `Don't_ignore -> res + | `Ignore_keep -> Ok to_drop + | `Ignore_drop -> Ok (op :: to_drop)) + [] + operations + in + `Ignored operations_to_drop + + (** [inject_pending_operations_for ~size_limit state pending] injects + operations from the pending queue [pending], whose total size does + not exceed [size_limit]. Upon successful injection, the + operations are removed from the queue and marked as injected. *) + let inject_pending_operations + ?(size_limit = Constants.max_operation_data_length) state = + let open Lwt_result_syntax in + (* Retrieve and remove operations from pending *) + let operations_to_inject = get_operations_from_queue ~size_limit state in + match operations_to_inject with + | [] -> return_unit + | _ -> ( + let*! () = + Event.(emit1 injecting_pending) + state + (List.length operations_to_inject) + in + let must_succeed = + Rollup.batch_must_succeed + @@ List.map + (fun op -> op.L1_operation.manager_operation) + operations_to_inject + in + let*! res = + inject_operations ~must_succeed state operations_to_inject + in + let*? res = + ignore_ignorable_failing_operations operations_to_inject res + in + match res with + | `Injected (oph, injected_operations) -> + (* Injection succeeded, remove from pending and add to injected *) + List.iter + (fun op -> Op_queue.remove state.queue op.L1_operation.hash) + injected_operations ; + add_injected_operations state oph operations_to_inject ; + return_unit + | `Ignored operations_to_drop -> + (* Injection failed but we ignore the failure. *) + List.iter + (fun op -> Op_queue.remove state.queue op.L1_operation.hash) + operations_to_drop ; + return_unit) + + (** [register_included_operation state block level oph] marks the manager + operations contained in the L1 batch [oph] as being included in the [block] + of level [level], by moving them from the "injected" state to the "included" + state. *) + let register_included_operation state block level oph = + match remove_injected_operation state oph with + | [] -> Lwt.return_unit + | injected_infos -> + let included_mops = + List.map (fun (i : injected_info) -> i.op) injected_infos + in + add_included_operations state oph block level included_mops + + (** [register_included_operations state block level oph] marks the known (by + this injector) manager operations contained in [block] as being included. *) + let register_included_operations state + (block : Alpha_block_services.block_info) = + List.iter_s + (List.iter_s (fun (op : Alpha_block_services.operation) -> + register_included_operation + state + block.hash + block.header.shell.level + op.hash + (* TODO/TORU: Handle operations for rollup_id here with + callback *))) + block.Alpha_block_services.operations + + (** [revert_included_operations state block] marks the known (by this injector) + manager operations contained in [block] as not being included any more, + typically in the case of a reorganization where [block] is on an alternative + chain. The operations are put back in the pending queue. *) + let revert_included_operations state block = + let open Lwt_syntax in + let included_infos = remove_included_operation state block in + let* () = + Event.(emit1 revert_operations) + state + (List.map (fun o -> o.op.hash) included_infos) + in + (* TODO/TORU: https://gitlab.com/tezos/tezos/-/issues/2814 + maybe put at the front of the queue for re-injection. *) + List.iter_s + (fun {op; _} -> + let {L1_operation.manager_operation = Manager mop; _} = op in + let* requeue = + Rollup.requeue_reverted_operation state.rollup_node_state mop + in + if requeue then add_pending_operation state op else return_unit) + included_infos + + (** [register_confirmed_level state confirmed_level] is called when the level + [confirmed_level] is known as confirmed. In this case, the operations of + block which are below this level are also considered as confirmed and are + removed from the "included" state. These operations cannot be part of a + reorganization so there will be no need to re-inject them anymore. *) + let register_confirmed_level state confirmed_level = + let open Lwt_syntax in + let* () = + Event.(emit confirmed_level) + (state.signer.pkh, state.tags, confirmed_level) + in + Block_hash.Table.iter_s + (fun block (level, _operations) -> + if level <= confirmed_level then + let confirmed_ops = remove_included_operation state block in + Event.(emit2 confirmed_operations) + state + level + (List.map (fun o -> o.op.hash) confirmed_ops) + else Lwt.return_unit) + state.included.included_in_blocks + + (** [on_new_tezos_head state head reorg] is called when there is a new Tezos + head (with a potential reorganization [reorg]). It first reverts any blocks + that are in the alternative branch of the reorganization and then registers + the effect of the new branch (the newly included operation and confirmed + operations). *) + let on_new_tezos_head state (head : Alpha_block_services.block_info) + (reorg : Alpha_block_services.block_info reorg) = + let open Lwt_result_syntax in + let*! () = Event.(emit1 new_tezos_head) state head.hash in + let*! () = + List.iter_s + (fun removed_block -> + revert_included_operations + state + removed_block.Alpha_block_services.hash) + (List.rev reorg.old_chain) + in + let*! () = + List.iter_s + (fun added_block -> register_included_operations state added_block) + reorg.new_chain + in + (* Head is already included in the reorganization, so no need to process it + separately. *) + let confirmed_level = + Int32.sub + head.Alpha_block_services.header.shell.level + (Int32.of_int confirmations) + in + let*! () = + if confirmed_level >= 0l then + register_confirmed_level state confirmed_level + else Lwt.return_unit + in + return_unit + + (* The request {Request.Inject} triggers an injection of the operations + the pending queue. *) + let on_inject state = inject_pending_operations state + + module Types = struct + type nonrec state = state + + type parameters = { + cctxt : Protocol_client_context.full; + rollup_node_state : Rollup.rollup_node_state; + strategy : injection_strategy; + tags : Tags.t; + } + end + + (* The worker for the injector. *) + module Worker = Worker.Make (Name) (Dummy_event) (Request) (Types) (Logger) + + (* The queue for the requests to the injector worker is infinite. *) + type worker = Worker.infinite Worker.queue Worker.t + + module Handlers = struct + type self = worker + + let on_request : type r. worker -> r Request.t -> r tzresult Lwt.t = + fun w request -> + let open Lwt_result_syntax in + let state = Worker.state w in + match request with + | Request.Add_pending op -> + let*! () = add_pending_operation state op in + return_unit + | Request.New_tezos_head (head, reorg) -> + on_new_tezos_head state head reorg + | Request.Inject -> on_inject state + + let on_request w r = + (* The execution of the request handler is protected to avoid stopping the + worker in case of an exception. *) + protect @@ fun () -> on_request w r + + let on_launch _w signer Types.{cctxt; rollup_node_state; strategy; tags} = + init_injector cctxt rollup_node_state ~signer strategy tags + + let on_error w r st errs = + let open Lwt_result_syntax in + let state = Worker.state w in + (* Errors do not stop the worker but emit an entry in the log. *) + let*! () = Event.(emit3 request_failed) state r st errs in + return_unit + + let on_completion w r _ st = + let state = Worker.state w in + match Request.view r with + | Request.View (Add_pending _ | New_tezos_head _) -> + Event.(emit2 request_completed_debug) state (Request.view r) st + | View Inject -> + Event.(emit2 request_completed_notice) state (Request.view r) st + + let on_no_request _ = return_unit + + let on_close _w = Lwt.return_unit + end + + let table = Worker.create_table Queue + + (* TODO/TORU: https://gitlab.com/tezos/tezos/-/issues/2754 + Injector worker in a separate process *) + let init (cctxt : #Protocol_client_context.full) rollup_node_state ~signers = + let open Lwt_result_syntax in + let signers_map = + List.fold_left + (fun acc (signer, strategy, tags) -> + let tags = Tags.of_list tags in + let (strategy, tags) = + match Signature.Public_key_hash.Map.find_opt signer acc with + | None -> (strategy, tags) + | Some (other_strategy, other_tags) -> + let strategy = + match (strategy, other_strategy) with + | (`Each_block, `Each_block) -> `Each_block + | (`Delay_block, _) | (_, `Delay_block) -> + (* Delay_block strategy takes over because we can always wait a + little bit more to inject operation which are to be injected + "each block". *) + `Delay_block + in + (strategy, Tags.union other_tags tags) + in + Signature.Public_key_hash.Map.add signer (strategy, tags) acc) + Signature.Public_key_hash.Map.empty + signers + in + Signature.Public_key_hash.Map.iter_es + (fun signer (strategy, tags) -> + let+ worker = + Worker.launch + table + signer + { + cctxt = (cctxt :> Protocol_client_context.full); + rollup_node_state; + strategy; + tags; + } + (module Handlers) + in + ignore worker) + signers_map + + let worker_of_signer signer_pkh = + match Worker.find_opt table signer_pkh with + | None -> + (* TODO: https://gitlab.com/tezos/tezos/-/issues/2818 + maybe lazily start worker here *) + error (Error.No_worker_for_source signer_pkh) + | Some worker -> ok worker + + let add_pending_operation ~source op = + let open Lwt_result_syntax in + let*? w = worker_of_signer source in + let l1_operation = L1_operation.make op in + let*! () = Worker.Queue.push_request w (Request.Add_pending l1_operation) in + return_unit + + let new_tezos_head h reorg = + let workers = Worker.list table in + List.iter_p + (fun (_signer, w) -> + Worker.Queue.push_request w (Request.New_tezos_head (h, reorg))) + workers + + let has_tag_in ~tags state = + match tags with + | None -> + (* Not filtering on tags *) + true + | Some tags -> not (Tags.disjoint state.tags tags) + + let has_strategy ~strategy state = + match strategy with + | None -> + (* Not filtering on strategy *) + true + | Some strategy -> state.strategy = strategy + + let inject ?tags ?strategy () = + let workers = Worker.list table in + let tags = Option.map Tags.of_list tags in + List.iter_p + (fun (_signer, w) -> + let worker_state = Worker.state w in + if has_tag_in ~tags worker_state && has_strategy ~strategy worker_state + then Worker.Queue.push_request w Request.Inject + else Lwt.return_unit) + workers +end diff --git a/src/proto_alpha/lib_tx_rollup/injector_functor.mli b/src/proto_alpha/lib_tx_rollup/injector_functor.mli new file mode 100644 index 000000000000..183066b02a9a --- /dev/null +++ b/src/proto_alpha/lib_tx_rollup/injector_functor.mli @@ -0,0 +1,29 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs, *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +open Injector_sigs + +module Make (P : PARAMETERS) : + S with type rollup_node_state := P.rollup_node_state and type tag := P.Tag.t diff --git a/src/proto_alpha/lib_tx_rollup/injector_sigs.ml b/src/proto_alpha/lib_tx_rollup/injector_sigs.ml new file mode 100644 index 000000000000..3457e814ea36 --- /dev/null +++ b/src/proto_alpha/lib_tx_rollup/injector_sigs.ml @@ -0,0 +1,141 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs, *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +open Protocol.Alpha_context + +(** Type to represent {e appoximate upper-bounds} for the fee and limits, used + to compute an upper bound on the size (in bytes) of an operation. *) +type approximate_fee_bound = { + fee : Tez.t; + counter : Z.t; + gas_limit : Gas.Arith.integral; + storage_limit : Z.t; +} + +type injection_strategy = + [ `Each_block (** Inject pending operations after each new L1 block *) + | `Delay_block + (** Wait for some time after the L1 block is produced to inject pending + operations. This strategy allows for maximizing the number of the same + kind of operations to include in a block. *) + ] + +module type TAG = sig + include Stdlib.Set.OrderedType + + val pp : Format.formatter -> t -> unit + + val encoding : t Data_encoding.t +end + +(** Module type for parameter of functor {!Injector_functor.Make}. *) +module type PARAMETERS = sig + (** The type of the state for the rollup node that the injector can access *) + type rollup_node_state + + (** A module which contains the different tags for the injector *) + module Tag : TAG + + (** Where to put the events for this injector *) + val events_section : string list + + (** Returns an estimation of the size of operations of this tag injected at + each block *) + val table_estimated_size : Tag.t -> int + + (** [requeue_reverted_operation state op] should return [true] if an included + operation should be re-queued for injection when the block in which it is + included is reverted (due to a reorganization). *) + val requeue_reverted_operation : + rollup_node_state -> 'a manager_operation -> bool Lwt.t + + (** [ignore_failing_operation op] specifies if the injector should + ignore this operation when its simulation fails when trying to inject. + Returns: + - [`Ignore_keep] if the operation should be ignored but kept from the + pending queue, + - [`Ignore_drop] if the operation should be ignored and dropped from the + pending queue, + - [`Don't_ignore] if the failing operation should not be ignored and the + failure reported. + *) + val ignore_failing_operation : + 'a manager_operation -> [`Ignore_keep | `Ignore_drop | `Don't_ignore] + + (** Returns the {e appoximate upper-bounds} for the fee and limits of an operation, used + to compute an upper bound on the size (in bytes) for this operation. *) + val approximate_fee_bound : 'a manager_operation -> approximate_fee_bound + + (** Returns the fee_parameter (to compute fee w.r.t. gas, size, etc.) and the + caps of fee and burn for each operation. *) + val fee_parameter : 'a manager_operation -> Injection.fee_parameter + + (** When injecting the given [operations] in an L1 batch, if + [batch_must_succeed operations] returns [`All] then all the operations must + succeed in the simulation of injection. If it returns [`At_least_one] at + least one operation in the list [operations] must be successful in the + simulation. In any case, only operations which are known as successful will + be included in the injected L1 batch. {b Note}: Returning [`At_least_one] + allows to incrementally build "or-batches" by iteratively removing + operations that fail from the desired batch. *) + val batch_must_succeed : + packed_manager_operation list -> [`All | `At_least_one] +end + +(** Output signature for functor {!Injector_functor.Make}. *) +module type S = sig + type rollup_node_state + + type tag + + (** Initializes the injector with the rollup node state, for a list of + signers. Each signer has its own worker with a queue of operations to + inject. *) + val init : + #Protocol_client_context.full -> + rollup_node_state -> + signers:(public_key_hash * injection_strategy * tag list) list -> + unit tzresult Lwt.t + + (** Add an operation as pending injection in the injector. *) + val add_pending_operation : + source:public_key_hash -> 'a manager_operation -> unit tzresult Lwt.t + + (** Notify the injector of a new Tezos head. The injector marks the operations + appropriately (for instance reverted operations that are part of a + reorganization are put back in the pending queue). When an operation is + considered as {e confirmed}, it disappears from the injector. *) + val new_tezos_head : + Protocol_client_context.Alpha_block_services.block_info -> + Protocol_client_context.Alpha_block_services.block_info Common.reorg -> + unit Lwt.t + + (** Trigger an injection of the pending operations for all workers. If [tags] + is given, only the workers which have a tag in [tags] inject their pending + operations. If [strategy] is given, only workers which have this strategy + inject their pending operations. *) + val inject : + ?tags:tag list -> ?strategy:injection_strategy -> unit -> unit Lwt.t +end diff --git a/src/proto_alpha/lib_tx_rollup/injector_tags.ml b/src/proto_alpha/lib_tx_rollup/injector_tags.ml new file mode 100644 index 000000000000..1e92ff5de2cb --- /dev/null +++ b/src/proto_alpha/lib_tx_rollup/injector_tags.ml @@ -0,0 +1,39 @@ +(*****************************************************************************) +(* *) +(* 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 Make (Tag : Injector_sigs.TAG) = struct + include Set.Make (Tag) + + let pp ppf tags = + Format.pp_print_list + ~pp_sep:(fun ppf () -> Format.fprintf ppf ",@ ") + Tag.pp + ppf + (elements tags) + + let encoding = + let open Data_encoding in + conv elements of_list (list Tag.encoding) +end diff --git a/src/proto_alpha/lib_tx_rollup/injector_tags.mli b/src/proto_alpha/lib_tx_rollup/injector_tags.mli new file mode 100644 index 000000000000..9efa6842376a --- /dev/null +++ b/src/proto_alpha/lib_tx_rollup/injector_tags.mli @@ -0,0 +1,35 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +(** Make a set of tags given a module for tags. *) +module Make (Tag : Injector_sigs.TAG) : sig + include Set.S with type elt = Tag.t + + (** Pretty print a set of tags *) + val pp : Format.formatter -> t -> unit + + (** Encoding for sets of tags *) + val encoding : t Data_encoding.t +end diff --git a/src/proto_alpha/lib_tx_rollup/injector_worker_types.ml b/src/proto_alpha/lib_tx_rollup/injector_worker_types.ml index 47b35175f196..2396da7a9702 100644 --- a/src/proto_alpha/lib_tx_rollup/injector_worker_types.ml +++ b/src/proto_alpha/lib_tx_rollup/injector_worker_types.ml @@ -28,55 +28,6 @@ open Protocol open Alpha_context open Common -type tag = - [ `Commitment - | `Submit_batch - | `Finalize_commitment - | `Remove_commitment - | `Rejection - | `Dispatch_withdrawals ] - -module Tags = Set.Make (struct - type t = tag - - let compare = Stdlib.compare -end) - -type tags = Tags.t - -let string_of_tag : tag -> string = function - | `Submit_batch -> "submit_batch" - | `Commitment -> "commitment" - | `Finalize_commitment -> "finalize_commitment" - | `Remove_commitment -> "remove_commitment" - | `Rejection -> "rejection" - | `Dispatch_withdrawals -> "dispatch_withdrawals" - -let pp_tags ppf tags = - Format.pp_print_list - ~pp_sep:(fun ppf () -> Format.fprintf ppf ",@ ") - (fun ppf t -> Format.pp_print_string ppf (string_of_tag t)) - ppf - (Tags.elements tags) - -let tag_encoding : tag Data_encoding.t = - let open Data_encoding in - string_enum - (List.map - (fun t -> (string_of_tag t, t)) - [ - `Submit_batch; - `Commitment; - `Finalize_commitment; - `Remove_commitment; - `Rejection; - `Dispatch_withdrawals; - ]) - -let tags_encoding : tags Data_encoding.t = - let open Data_encoding in - conv Tags.elements Tags.of_list (list tag_encoding) - module Request = struct type 'a t = | Add_pending : L1_operation.t -> unit t diff --git a/src/proto_alpha/lib_tx_rollup/injector_worker_types.mli b/src/proto_alpha/lib_tx_rollup/injector_worker_types.mli index d7aa1fb2f378..ddca3d714f54 100644 --- a/src/proto_alpha/lib_tx_rollup/injector_worker_types.mli +++ b/src/proto_alpha/lib_tx_rollup/injector_worker_types.mli @@ -28,22 +28,6 @@ open Protocol open Alpha_context open Common -type tag = - [ `Commitment - | `Submit_batch - | `Finalize_commitment - | `Remove_commitment - | `Rejection - | `Dispatch_withdrawals ] - -module Tags : Set.S with type elt = tag - -type tags = Tags.t - -val tags_encoding : tags Data_encoding.t - -val pp_tags : Format.formatter -> tags -> unit - module Request : sig type 'a t = | Add_pending : L1_operation.t -> unit t -- GitLab From 97f7cc861ed42f133056903e3604cee85c81ab3e Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Fri, 29 Apr 2022 12:50:31 +0200 Subject: [PATCH 06/22] Test/Tezt: use baker_for_and_wait in tx rollup node tests --- tezt/tests/tx_rollup_node.ml | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/tezt/tests/tx_rollup_node.ml b/tezt/tests/tx_rollup_node.ml index 383aebf79800..353172973da5 100644 --- a/tezt/tests/tx_rollup_node.ml +++ b/tezt/tests/tx_rollup_node.ml @@ -335,7 +335,6 @@ let test_tx_node_store_inbox = let operator = Constant.bootstrap1.public_key_hash in let*! rollup = Client.Tx_rollup.originate ~src:operator client in let* () = Client.bake_for_and_wait client in - let* _ = Node.wait_for_level node 2 in let* block_hash = RPC.get_block_hash client in let tx_node = Rollup_node.create @@ -360,7 +359,6 @@ let test_tx_node_store_inbox = client in let* () = Client.bake_for_and_wait client in - let* _ = Node.wait_for_level node 3 in let* _ = Rollup_node.wait_for_tezos_level tx_node 3 in let* tx_node_inbox_1 = tx_client_get_inbox ~tx_client ~tezos_client:client ~block:"0" @@ -390,7 +388,6 @@ let test_tx_node_store_inbox = client in let* () = Client.bake_for_and_wait client in - let* _ = Node.wait_for_level node 4 in let* _ = Rollup_node.wait_for_tezos_level tx_node 4 in let* tx_node_inbox_2 = tx_client_get_inbox ~tx_client ~tezos_client:client ~block:"1" @@ -657,7 +654,6 @@ let test_ticket_deposit_from_l1_to_l2 = client in let* () = Client.bake_for_and_wait client in - let* _ = Node.wait_for_level node 3 in Log.info "The tx_rollup_deposit %s contract was successfully originated" contract_id ; @@ -822,7 +818,6 @@ let test_l2_to_l2_transaction = client in let* () = Client.bake_for_and_wait client in - let* _ = Node.wait_for_level node 3 in Log.info "The tx_rollup_deposit %s contract was successfully originated" contract_id ; @@ -851,7 +846,6 @@ let test_l2_to_l2_transaction = client in let* () = Client.bake_for_and_wait 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_as_json ~tx_client ~block:"head" in let ticket_id = get_ticket_hash_from_deposit_json inbox in @@ -886,7 +880,6 @@ let test_l2_to_l2_transaction = client in let* () = Client.bake_for_and_wait client in - let* _ = Node.wait_for_level node 5 in let* _ = Rollup_node.wait_for_tezos_level tx_node 5 in let* () = check_tz4_balance @@ -915,7 +908,6 @@ let test_l2_to_l2_transaction = in Log.info "Baking the batch" ; let* () = Client.bake_for_and_wait client in - let* _ = Node.wait_for_level node 6 in let* _ = Rollup_node.wait_for_tezos_level tx_node 6 in (* The decoding fails because of the buggy JSON encoding. This line can be uncommented once it is fixed.*) @@ -1506,7 +1498,6 @@ let test_l2_proof_rpc_position = in Log.info "Baking the batches" ; let* () = Client.bake_for_and_wait client in - let* _ = Node.wait_for_level node 5 in let* _ = Rollup_node.wait_for_tezos_level tx_node 5 in Log.info "Commitment for rollup level: 1" ; let* ({ @@ -1668,7 +1659,7 @@ let test_reject_bad_commitment = ~src:Constant.bootstrap3.public_key_hash client in - let* () = Client.bake_for client in + let* () = Client.bake_for_and_wait client in let* _ = Rollup_node.wait_for_tezos_level tx_node 4 in let* { proof; @@ -1757,11 +1748,11 @@ let test_committer = Log.info "Sending some L2 transactions" ; let* _ = inject_tx ~from:bls_key_1 ~dest:bls_pkh_2 ~amount:1000L () in let* _ = inject_tx ~from:bls_key_2 ~dest:bls_pkh_1 ~amount:2L () in - let* () = Client.bake_for client in + let* () = Client.bake_for_and_wait client in let* tzlevel = Rollup_node.wait_for_tezos_level tx_node (tzlevel + 1) in let* () = check_commitments_inclusion ~tx_node [("0", true)] in let* () = - check_injection tx_node "commitment" @@ Client.bake_for client + check_injection tx_node "commitment" @@ Client.bake_for_and_wait client in let* tzlevel = Rollup_node.wait_for_tezos_level tx_node (tzlevel + 1) in let* block = Rollup_node.Client.get_block ~tx_node ~block:"head" in @@ -1772,7 +1763,7 @@ let test_committer = Log.info "Sending some more L2 transactions" ; let* _ = inject_tx ~from:bls_key_1 ~dest:bls_pkh_2 ~amount:3L () in let* _ = inject_tx ~from:bls_key_2 ~dest:bls_pkh_1 ~amount:4L () in - let* () = Client.bake_for client in + let* () = Client.bake_for_and_wait client in let* tzlevel = Rollup_node.wait_for_tezos_level tx_node (tzlevel + 1) in let* block = Rollup_node.Client.get_block ~tx_node ~block:"head" in check_l2_level block 1 ; @@ -1783,7 +1774,7 @@ let test_committer = let* _ = inject_tx ~from:bls_key_1 ~dest:bls_pkh_2 ~amount:5L () in let* _ = inject_tx ~from:bls_key_2 ~dest:bls_pkh_1 ~amount:6L () in let* () = - check_injection tx_node "commitment" @@ Client.bake_for client + check_injection tx_node "commitment" @@ Client.bake_for_and_wait client in let* tzlevel = Rollup_node.wait_for_tezos_level tx_node (tzlevel + 1) in let* block = Rollup_node.Client.get_block ~tx_node ~block:"head" in @@ -1794,7 +1785,7 @@ let test_committer = [("0", true); ("1", true); ("2", false)] in let* () = - check_injection tx_node "commitment" @@ Client.bake_for client + check_injection tx_node "commitment" @@ Client.bake_for_and_wait client in let* _tzlevel = Rollup_node.wait_for_tezos_level tx_node (tzlevel + 1) in let* block = Rollup_node.Client.get_block ~tx_node ~block:"head" in @@ -1891,8 +1882,8 @@ let test_tickets_context = ~qty:5L in Log.info "Waiting for new L2 block" ; - let* () = Client.bake_for client in - let* () = Client.bake_for client in + let* () = Client.bake_for_and_wait client in + let* () = Client.bake_for_and_wait client in let* _ = Rollup_node.wait_for_tezos_level tx_node 6 in let* inbox = Rollup_node.Client.get_inbox ~tx_node ~block:"head" in check_inbox_success inbox ; -- GitLab From 086523ae99472a4262a5a4a466296c02c1d734ea Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Fri, 29 Apr 2022 15:27:00 +0200 Subject: [PATCH 07/22] Tx_rollup,Node: remove useless ancestor from reorg --- src/proto_alpha/lib_tx_rollup/common.ml | 15 +++++---------- src/proto_alpha/lib_tx_rollup/common.mli | 3 --- src/proto_alpha/lib_tx_rollup/state.ml | 22 ++++------------------ 3 files changed, 9 insertions(+), 31 deletions(-) diff --git a/src/proto_alpha/lib_tx_rollup/common.ml b/src/proto_alpha/lib_tx_rollup/common.ml index 491478b2c1c7..67d558f77569 100644 --- a/src/proto_alpha/lib_tx_rollup/common.ml +++ b/src/proto_alpha/lib_tx_rollup/common.ml @@ -35,20 +35,15 @@ let get_signer cctxt pkh = let* (alias, pk, sk) = Client_keys.get_key cctxt pkh in return {alias; pkh; pk; sk} -type 'block reorg = { - ancestor : 'block option; - old_chain : 'block list; - new_chain : 'block list; -} +type 'block reorg = {old_chain : 'block list; new_chain : 'block list} -let no_reorg = {ancestor = None; old_chain = []; new_chain = []} +let no_reorg = {old_chain = []; new_chain = []} let reorg_encoding block_encoding = let open Data_encoding in conv - (fun {ancestor; old_chain; new_chain} -> (ancestor, old_chain, new_chain)) - (fun (ancestor, old_chain, new_chain) -> {ancestor; old_chain; new_chain}) - @@ obj3 - (opt "ancestor" block_encoding) + (fun {old_chain; new_chain} -> (old_chain, new_chain)) + (fun (old_chain, new_chain) -> {old_chain; new_chain}) + @@ obj2 (req "old_chain" (list block_encoding)) (req "new_chain" (list block_encoding)) diff --git a/src/proto_alpha/lib_tx_rollup/common.mli b/src/proto_alpha/lib_tx_rollup/common.mli index 8be676d7ccde..9ffa3da6ad54 100644 --- a/src/proto_alpha/lib_tx_rollup/common.mli +++ b/src/proto_alpha/lib_tx_rollup/common.mli @@ -33,9 +33,6 @@ type signer = { (** Type of chain reorganizations. *) type 'block reorg = { - ancestor : 'block option; - (** The common ancestor of the two chains. Can be [None] if the chains have no - common ancestor, in which case all the blocks are changed *) old_chain : 'block list; (** The blocks that were in the old chain and which are not in the new one. *) new_chain : 'block list; diff --git a/src/proto_alpha/lib_tx_rollup/state.ml b/src/proto_alpha/lib_tx_rollup/state.ml index eb715fc15b93..b26cdc863ae4 100644 --- a/src/proto_alpha/lib_tx_rollup/state.ml +++ b/src/proto_alpha/lib_tx_rollup/state.ml @@ -83,12 +83,7 @@ let tezos_reorg state ~old_head_hash ~new_head_hash = let open Lwt_result_syntax in let rec loop old_chain new_chain old_head_hash new_head_hash = if Block_hash.(old_head_hash = new_head_hash) then - let+ ancestor = fetch_tezos_block state old_head_hash in - { - ancestor = Some ancestor; - old_chain = List.rev old_chain; - new_chain = List.rev new_chain; - } + return {old_chain = List.rev old_chain; new_chain = List.rev new_chain} else let* new_head = fetch_tezos_block state new_head_hash in let* old_head = fetch_tezos_block state old_head_hash in @@ -127,7 +122,7 @@ let set_tezos_head state new_head_hash = (* No known tezos head, consider the new head as being on top of a previous tezos block. *) let+ new_head = fetch_tezos_block state new_head_hash in - {ancestor = None; old_chain = []; new_chain = [new_head]} + {old_chain = []; new_chain = [new_head]} | Some old_head_hash -> tezos_reorg state ~old_head_hash ~new_head_hash in let* () = @@ -208,20 +203,11 @@ let rollup_reorg state ~old_head ~new_head = let rec loop old_chain new_chain old_head new_head = match (old_head, new_head) with | (None, _) | (_, None) -> - return - { - ancestor = None; - old_chain = List.rev old_chain; - new_chain = List.rev new_chain; - } + return {old_chain = List.rev old_chain; new_chain = List.rev new_chain} | (Some old_head, Some new_head) -> if L2block.Hash.(old_head.L2block.hash = new_head.L2block.hash) then return - { - ancestor = Some old_head; - old_chain = List.rev old_chain; - new_chain = List.rev new_chain; - } + {old_chain = List.rev old_chain; new_chain = List.rev new_chain} else let diff = distance_l2_levels -- GitLab From 2629385c3689050dcd4096ca5785626be7d566ce Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Fri, 29 Apr 2022 15:50:28 +0200 Subject: [PATCH 08/22] Rollups,Node: Move injector in separate library --- manifest/main.ml | 24 +++++++++++ .../lib_rollups/.ocamlformat | 17 ++++++++ src/proto_013_PtJakart/lib_rollups/dune | 32 ++++++++++++++ .../lib_rollups/dune-project | 4 ++ .../tezos-rollups-013-PtJakart.opam | 26 +++++++++++ src/proto_013_PtJakart/lib_tx_rollup/dune | 6 ++- .../tezos-tx-rollup-013-PtJakart.opam | 1 + src/proto_alpha/lib_rollups/.ocamlformat | 17 ++++++++ .../{lib_tx_rollup => lib_rollups}/common.ml | 0 .../{lib_tx_rollup => lib_rollups}/common.mli | 2 +- src/proto_alpha/lib_rollups/dune | 32 ++++++++++++++ src/proto_alpha/lib_rollups/dune-project | 4 ++ .../lib_rollups/injector_errors.ml | 43 +++++++++++++++++++ .../lib_rollups/injector_errors.mli | 28 ++++++++++++ .../injector_events.ml | 0 .../injector_functor.ml | 10 ++--- .../injector_functor.mli | 0 .../injector_sigs.ml | 20 +++++---- .../injector_tags.ml | 0 .../injector_tags.mli | 0 .../injector_worker_types.ml | 0 .../injector_worker_types.mli | 0 .../l1_operation.ml | 0 .../l1_operation.mli | 0 .../lib_rollups/tezos-rollups-alpha.opam | 26 +++++++++++ src/proto_alpha/lib_tx_rollup/dune | 6 ++- src/proto_alpha/lib_tx_rollup/error.ml | 19 -------- src/proto_alpha/lib_tx_rollup/error.mli | 4 -- .../lib_tx_rollup/tezos-tx-rollup-alpha.opam | 1 + 29 files changed, 280 insertions(+), 42 deletions(-) create mode 100644 src/proto_013_PtJakart/lib_rollups/.ocamlformat create mode 100644 src/proto_013_PtJakart/lib_rollups/dune create mode 100644 src/proto_013_PtJakart/lib_rollups/dune-project create mode 100644 src/proto_013_PtJakart/lib_rollups/tezos-rollups-013-PtJakart.opam create mode 100644 src/proto_alpha/lib_rollups/.ocamlformat rename src/proto_alpha/{lib_tx_rollup => lib_rollups}/common.ml (100%) rename src/proto_alpha/{lib_tx_rollup => lib_rollups}/common.mli (97%) create mode 100644 src/proto_alpha/lib_rollups/dune create mode 100644 src/proto_alpha/lib_rollups/dune-project create mode 100644 src/proto_alpha/lib_rollups/injector_errors.ml create mode 100644 src/proto_alpha/lib_rollups/injector_errors.mli rename src/proto_alpha/{lib_tx_rollup => lib_rollups}/injector_events.ml (100%) rename src/proto_alpha/{lib_tx_rollup => lib_rollups}/injector_functor.ml (99%) rename src/proto_alpha/{lib_tx_rollup => lib_rollups}/injector_functor.mli (100%) rename src/proto_alpha/{lib_tx_rollup => lib_rollups}/injector_sigs.ml (92%) rename src/proto_alpha/{lib_tx_rollup => lib_rollups}/injector_tags.ml (100%) rename src/proto_alpha/{lib_tx_rollup => lib_rollups}/injector_tags.mli (100%) rename src/proto_alpha/{lib_tx_rollup => lib_rollups}/injector_worker_types.ml (100%) rename src/proto_alpha/{lib_tx_rollup => lib_rollups}/injector_worker_types.mli (100%) rename src/proto_alpha/{lib_tx_rollup => lib_rollups}/l1_operation.ml (100%) rename src/proto_alpha/{lib_tx_rollup => lib_rollups}/l1_operation.mli (100%) create mode 100644 src/proto_alpha/lib_rollups/tezos-rollups-alpha.opam diff --git a/manifest/main.ml b/manifest/main.ml index 45520a3f6ef5..ad2895712beb 100644 --- a/manifest/main.ml +++ b/manifest/main.ml @@ -3394,6 +3394,29 @@ end = struct let _baker = daemon "baker" in let _accuser = daemon "accuser" in let _endorser = some_if N.(number <= 011) @@ fun () -> daemon "endorser" in + let rollups = + some_if N.(number >= 013) @@ fun () -> + public_lib + (sf "tezos-rollups-%s" name_dash) + ~path:(sf "src/proto_%s/lib_rollups" name_underscore) + ~synopsis:"Tezos/Protocol: protocol specific library for rollups" + ~deps: + [ + tezos_base |> open_ ~m:"TzPervasives" + |> open_ ~m:"TzPervasives.Error_monad.Legacy_monad_globals" + |> open_; + tezos_crypto |> open_; + main |> open_; + environment |> open_; + tezos_micheline |> open_; + client |> if_some |> open_; + tezos_client_base |> open_; + tezos_workers |> open_; + tezos_shell; + ] + ~inline_tests:ppx_inline_test + ~linkall:true + in let sc_rollup = some_if N.(number >= 013) @@ fun () -> public_lib @@ -3499,6 +3522,7 @@ end = struct tezos_shell; tezos_store; tezos_workers |> open_; + rollups |> if_some |> open_; ] ~inline_tests:ppx_inline_test ~linkall:true diff --git a/src/proto_013_PtJakart/lib_rollups/.ocamlformat b/src/proto_013_PtJakart/lib_rollups/.ocamlformat new file mode 100644 index 000000000000..5e1158919e85 --- /dev/null +++ b/src/proto_013_PtJakart/lib_rollups/.ocamlformat @@ -0,0 +1,17 @@ +version=0.18.0 +wrap-fun-args=false +let-binding-spacing=compact +field-space=loose +break-separators=after +space-around-arrays=false +space-around-lists=false +space-around-records=false +space-around-variants=false +dock-collection-brackets=true +space-around-records=false +sequence-style=separator +doc-comments=before +margin=80 +module-item-spacing=sparse +parens-tuple=always +parens-tuple-patterns=always diff --git a/src/proto_013_PtJakart/lib_rollups/dune b/src/proto_013_PtJakart/lib_rollups/dune new file mode 100644 index 000000000000..f178914b7f80 --- /dev/null +++ b/src/proto_013_PtJakart/lib_rollups/dune @@ -0,0 +1,32 @@ +; This file was automatically generated, do not edit. +; Edit file manifest/main.ml instead. + +(library + (name tezos_rollups_013_PtJakart) + (public_name tezos-rollups-013-PtJakart) + (instrumentation (backend bisect_ppx)) + (libraries + tezos-base + tezos-crypto + tezos-protocol-013-PtJakart + tezos-protocol-013-PtJakart.environment + tezos-micheline + tezos-client-013-PtJakart + tezos-client-base + tezos-workers + tezos-shell) + (inline_tests (flags -verbose) (modes native)) + (preprocess (pps ppx_inline_test)) + (library_flags (:standard -linkall)) + (flags + (:standard + -open Tezos_base.TzPervasives + -open Tezos_base.TzPervasives.Error_monad.Legacy_monad_globals + -open Tezos_base + -open Tezos_crypto + -open Tezos_protocol_013_PtJakart + -open Tezos_protocol_environment_013_PtJakart + -open Tezos_micheline + -open Tezos_client_013_PtJakart + -open Tezos_client_base + -open Tezos_workers))) diff --git a/src/proto_013_PtJakart/lib_rollups/dune-project b/src/proto_013_PtJakart/lib_rollups/dune-project new file mode 100644 index 000000000000..d895be4582e6 --- /dev/null +++ b/src/proto_013_PtJakart/lib_rollups/dune-project @@ -0,0 +1,4 @@ +(lang dune 2.9) +(formatting (enabled_for ocaml)) +; This file was automatically generated, do not edit. +; Edit file manifest/manifest.ml instead. diff --git a/src/proto_013_PtJakart/lib_rollups/tezos-rollups-013-PtJakart.opam b/src/proto_013_PtJakart/lib_rollups/tezos-rollups-013-PtJakart.opam new file mode 100644 index 000000000000..29ca1902d25c --- /dev/null +++ b/src/proto_013_PtJakart/lib_rollups/tezos-rollups-013-PtJakart.opam @@ -0,0 +1,26 @@ +# This file was automatically generated, do not edit. +# Edit file manifest/main.ml instead. +opam-version: "2.0" +maintainer: "contact@tezos.com" +authors: ["Tezos devteam"] +homepage: "https://www.tezos.com/" +bug-reports: "https://gitlab.com/tezos/tezos/issues" +dev-repo: "git+https://gitlab.com/tezos/tezos.git" +license: "MIT" +depends: [ + "dune" { >= "2.9" } + "ppx_inline_test" + "tezos-base" + "tezos-crypto" + "tezos-protocol-013-PtJakart" + "tezos-micheline" + "tezos-client-013-PtJakart" + "tezos-client-base" + "tezos-workers" + "tezos-shell" +] +build: [ + ["dune" "build" "-p" name "-j" jobs] + ["dune" "runtest" "-p" name "-j" jobs] {with-test} +] +synopsis: "Tezos/Protocol: protocol specific library for rollups" diff --git a/src/proto_013_PtJakart/lib_tx_rollup/dune b/src/proto_013_PtJakart/lib_tx_rollup/dune index b4a4148d3a36..2a7ad72c88c7 100644 --- a/src/proto_013_PtJakart/lib_tx_rollup/dune +++ b/src/proto_013_PtJakart/lib_tx_rollup/dune @@ -25,7 +25,8 @@ tezos-client-base-unix tezos-shell tezos-store - tezos-workers) + tezos-workers + tezos-rollups-013-PtJakart) (inline_tests (flags -verbose) (modes native)) (preprocess (pps ppx_inline_test)) (library_flags (:standard -linkall)) @@ -48,4 +49,5 @@ -open Tezos_micheline -open Tezos_client_base -open Tezos_client_base_unix - -open Tezos_workers))) + -open Tezos_workers + -open Tezos_rollups_013_PtJakart))) diff --git a/src/proto_013_PtJakart/lib_tx_rollup/tezos-tx-rollup-013-PtJakart.opam b/src/proto_013_PtJakart/lib_tx_rollup/tezos-tx-rollup-013-PtJakart.opam index c6aae8b32aaa..803765aa3646 100644 --- a/src/proto_013_PtJakart/lib_tx_rollup/tezos-tx-rollup-013-PtJakart.opam +++ b/src/proto_013_PtJakart/lib_tx_rollup/tezos-tx-rollup-013-PtJakart.opam @@ -29,6 +29,7 @@ depends: [ "tezos-shell" "tezos-store" "tezos-workers" + "tezos-rollups-013-PtJakart" ] build: [ ["dune" "build" "-p" name "-j" jobs] diff --git a/src/proto_alpha/lib_rollups/.ocamlformat b/src/proto_alpha/lib_rollups/.ocamlformat new file mode 100644 index 000000000000..5e1158919e85 --- /dev/null +++ b/src/proto_alpha/lib_rollups/.ocamlformat @@ -0,0 +1,17 @@ +version=0.18.0 +wrap-fun-args=false +let-binding-spacing=compact +field-space=loose +break-separators=after +space-around-arrays=false +space-around-lists=false +space-around-records=false +space-around-variants=false +dock-collection-brackets=true +space-around-records=false +sequence-style=separator +doc-comments=before +margin=80 +module-item-spacing=sparse +parens-tuple=always +parens-tuple-patterns=always diff --git a/src/proto_alpha/lib_tx_rollup/common.ml b/src/proto_alpha/lib_rollups/common.ml similarity index 100% rename from src/proto_alpha/lib_tx_rollup/common.ml rename to src/proto_alpha/lib_rollups/common.ml diff --git a/src/proto_alpha/lib_tx_rollup/common.mli b/src/proto_alpha/lib_rollups/common.mli similarity index 97% rename from src/proto_alpha/lib_tx_rollup/common.mli rename to src/proto_alpha/lib_rollups/common.mli index 9ffa3da6ad54..a329f09343f0 100644 --- a/src/proto_alpha/lib_tx_rollup/common.mli +++ b/src/proto_alpha/lib_rollups/common.mli @@ -23,7 +23,7 @@ (* *) (*****************************************************************************) -(** The type of signers for operations injected by the Tx rollup node *) +(** The type of signers for operations injected by the injector *) type signer = { alias : string; pkh : Signature.public_key_hash; diff --git a/src/proto_alpha/lib_rollups/dune b/src/proto_alpha/lib_rollups/dune new file mode 100644 index 000000000000..170d24ffe6a4 --- /dev/null +++ b/src/proto_alpha/lib_rollups/dune @@ -0,0 +1,32 @@ +; This file was automatically generated, do not edit. +; Edit file manifest/main.ml instead. + +(library + (name tezos_rollups_alpha) + (public_name tezos-rollups-alpha) + (instrumentation (backend bisect_ppx)) + (libraries + tezos-base + tezos-crypto + tezos-protocol-alpha + tezos-protocol-alpha.environment + tezos-micheline + tezos-client-alpha + tezos-client-base + tezos-workers + tezos-shell) + (inline_tests (flags -verbose) (modes native)) + (preprocess (pps ppx_inline_test)) + (library_flags (:standard -linkall)) + (flags + (:standard + -open Tezos_base.TzPervasives + -open Tezos_base.TzPervasives.Error_monad.Legacy_monad_globals + -open Tezos_base + -open Tezos_crypto + -open Tezos_protocol_alpha + -open Tezos_protocol_environment_alpha + -open Tezos_micheline + -open Tezos_client_alpha + -open Tezos_client_base + -open Tezos_workers))) diff --git a/src/proto_alpha/lib_rollups/dune-project b/src/proto_alpha/lib_rollups/dune-project new file mode 100644 index 000000000000..d895be4582e6 --- /dev/null +++ b/src/proto_alpha/lib_rollups/dune-project @@ -0,0 +1,4 @@ +(lang dune 2.9) +(formatting (enabled_for ocaml)) +; This file was automatically generated, do not edit. +; Edit file manifest/manifest.ml instead. diff --git a/src/proto_alpha/lib_rollups/injector_errors.ml b/src/proto_alpha/lib_rollups/injector_errors.ml new file mode 100644 index 000000000000..ce3f8318ad04 --- /dev/null +++ b/src/proto_alpha/lib_rollups/injector_errors.ml @@ -0,0 +1,43 @@ +(*****************************************************************************) +(* *) +(* 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 += No_worker_for_source of Signature.Public_key_hash.t + +let () = + register_error_kind + ~id:"tx_rollup.node.no_worker_for_source" + ~title:"No injecting queue for source" + ~description: + "An L1 operation could not be queued because its source has no worker." + ~pp:(fun ppf s -> + Format.fprintf + ppf + "No worker for source %a" + Signature.Public_key_hash.pp + s) + `Permanent + Data_encoding.(obj1 (req "source" Signature.Public_key_hash.encoding)) + (function No_worker_for_source s -> Some s | _ -> None) + (fun s -> No_worker_for_source s) diff --git a/src/proto_alpha/lib_rollups/injector_errors.mli b/src/proto_alpha/lib_rollups/injector_errors.mli new file mode 100644 index 000000000000..86bde783fb9c --- /dev/null +++ b/src/proto_alpha/lib_rollups/injector_errors.mli @@ -0,0 +1,28 @@ +(*****************************************************************************) +(* *) +(* 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 when the injector has no worker for the source which must inject an + operation. *) +type error += No_worker_for_source of Signature.Public_key_hash.t diff --git a/src/proto_alpha/lib_tx_rollup/injector_events.ml b/src/proto_alpha/lib_rollups/injector_events.ml similarity index 100% rename from src/proto_alpha/lib_tx_rollup/injector_events.ml rename to src/proto_alpha/lib_rollups/injector_events.ml diff --git a/src/proto_alpha/lib_tx_rollup/injector_functor.ml b/src/proto_alpha/lib_rollups/injector_functor.ml similarity index 99% rename from src/proto_alpha/lib_tx_rollup/injector_functor.ml rename to src/proto_alpha/lib_rollups/injector_functor.ml index 3f342ec57ce9..bf6d38a741b0 100644 --- a/src/proto_alpha/lib_tx_rollup/injector_functor.ml +++ b/src/proto_alpha/lib_rollups/injector_functor.ml @@ -29,6 +29,7 @@ open Alpha_context open Common open Injector_worker_types open Injector_sigs +open Injector_errors (* This is the Tenderbake finality for blocks. *) (* TODO: https://gitlab.com/tezos/tezos/-/issues/2815 @@ -154,12 +155,11 @@ module Make (Rollup : PARAMETERS) = struct module Event = struct include Injector_events.Make (Rollup) - let emit1 e state x = Event.emit e (state.signer.pkh, state.tags, x) + let emit1 e state x = emit e (state.signer.pkh, state.tags, x) - let emit2 e state x y = Event.emit e (state.signer.pkh, state.tags, x, y) + let emit2 e state x y = emit e (state.signer.pkh, state.tags, x, y) - let emit3 e state x y z = - Event.emit e (state.signer.pkh, state.tags, x, y, z) + let emit3 e state x y z = emit e (state.signer.pkh, state.tags, x, y, z) end (** Add an operation to the pending queue corresponding to the signer for this @@ -790,7 +790,7 @@ module Make (Rollup : PARAMETERS) = struct | None -> (* TODO: https://gitlab.com/tezos/tezos/-/issues/2818 maybe lazily start worker here *) - error (Error.No_worker_for_source signer_pkh) + error (No_worker_for_source signer_pkh) | Some worker -> ok worker let add_pending_operation ~source op = diff --git a/src/proto_alpha/lib_tx_rollup/injector_functor.mli b/src/proto_alpha/lib_rollups/injector_functor.mli similarity index 100% rename from src/proto_alpha/lib_tx_rollup/injector_functor.mli rename to src/proto_alpha/lib_rollups/injector_functor.mli diff --git a/src/proto_alpha/lib_tx_rollup/injector_sigs.ml b/src/proto_alpha/lib_rollups/injector_sigs.ml similarity index 92% rename from src/proto_alpha/lib_tx_rollup/injector_sigs.ml rename to src/proto_alpha/lib_rollups/injector_sigs.ml index 3457e814ea36..7fb3adf8a361 100644 --- a/src/proto_alpha/lib_tx_rollup/injector_sigs.ml +++ b/src/proto_alpha/lib_rollups/injector_sigs.ml @@ -26,7 +26,7 @@ open Protocol.Alpha_context (** Type to represent {e appoximate upper-bounds} for the fee and limits, used - to compute an upper bound on the size (in bytes) of an operation. *) + to compute an upper bound on the size (in bytes) of an operation. *) type approximate_fee_bound = { fee : Tez.t; counter : Z.t; @@ -42,6 +42,7 @@ type injection_strategy = kind of operations to include in a block. *) ] +(** Signature for tags used in injector *) module type TAG = sig include Stdlib.Set.OrderedType @@ -61,8 +62,8 @@ module type PARAMETERS = sig (** Where to put the events for this injector *) val events_section : string list - (** Returns an estimation of the size of operations of this tag injected at - each block *) + (** Coarse approximation for the number of operation of each tag we expect to + inject for each block. *) val table_estimated_size : Tag.t -> int (** [requeue_reverted_operation state op] should return [true] if an included @@ -74,7 +75,7 @@ module type PARAMETERS = sig (** [ignore_failing_operation op] specifies if the injector should ignore this operation when its simulation fails when trying to inject. Returns: - - [`Ignore_keep] if the operation should be ignored but kept from the + - [`Ignore_keep] if the operation should be ignored but kept in the pending queue, - [`Ignore_drop] if the operation should be ignored and dropped from the pending queue, @@ -84,8 +85,9 @@ module type PARAMETERS = sig val ignore_failing_operation : 'a manager_operation -> [`Ignore_keep | `Ignore_drop | `Don't_ignore] - (** Returns the {e appoximate upper-bounds} for the fee and limits of an operation, used - to compute an upper bound on the size (in bytes) for this operation. *) + (** Returns the {e appoximate upper-bounds} for the fee and limits of an + operation, used to compute an upper bound on the size (in bytes) for this + operation. *) val approximate_fee_bound : 'a manager_operation -> approximate_fee_bound (** Returns the fee_parameter (to compute fee w.r.t. gas, size, etc.) and the @@ -94,7 +96,7 @@ module type PARAMETERS = sig (** When injecting the given [operations] in an L1 batch, if [batch_must_succeed operations] returns [`All] then all the operations must - succeed in the simulation of injection. If it returns [`At_least_one] at + succeed in the simulation of injection. If it returns [`At_least_one], at least one operation in the list [operations] must be successful in the simulation. In any case, only operations which are known as successful will be included in the injected L1 batch. {b Note}: Returning [`At_least_one] @@ -111,8 +113,8 @@ module type S = sig type tag (** Initializes the injector with the rollup node state, for a list of - signers. Each signer has its own worker with a queue of operations to - inject. *) + signers, and start the workers. Each signer has its own worker with a + queue of operations to inject. *) val init : #Protocol_client_context.full -> rollup_node_state -> diff --git a/src/proto_alpha/lib_tx_rollup/injector_tags.ml b/src/proto_alpha/lib_rollups/injector_tags.ml similarity index 100% rename from src/proto_alpha/lib_tx_rollup/injector_tags.ml rename to src/proto_alpha/lib_rollups/injector_tags.ml diff --git a/src/proto_alpha/lib_tx_rollup/injector_tags.mli b/src/proto_alpha/lib_rollups/injector_tags.mli similarity index 100% rename from src/proto_alpha/lib_tx_rollup/injector_tags.mli rename to src/proto_alpha/lib_rollups/injector_tags.mli diff --git a/src/proto_alpha/lib_tx_rollup/injector_worker_types.ml b/src/proto_alpha/lib_rollups/injector_worker_types.ml similarity index 100% rename from src/proto_alpha/lib_tx_rollup/injector_worker_types.ml rename to src/proto_alpha/lib_rollups/injector_worker_types.ml diff --git a/src/proto_alpha/lib_tx_rollup/injector_worker_types.mli b/src/proto_alpha/lib_rollups/injector_worker_types.mli similarity index 100% rename from src/proto_alpha/lib_tx_rollup/injector_worker_types.mli rename to src/proto_alpha/lib_rollups/injector_worker_types.mli diff --git a/src/proto_alpha/lib_tx_rollup/l1_operation.ml b/src/proto_alpha/lib_rollups/l1_operation.ml similarity index 100% rename from src/proto_alpha/lib_tx_rollup/l1_operation.ml rename to src/proto_alpha/lib_rollups/l1_operation.ml diff --git a/src/proto_alpha/lib_tx_rollup/l1_operation.mli b/src/proto_alpha/lib_rollups/l1_operation.mli similarity index 100% rename from src/proto_alpha/lib_tx_rollup/l1_operation.mli rename to src/proto_alpha/lib_rollups/l1_operation.mli diff --git a/src/proto_alpha/lib_rollups/tezos-rollups-alpha.opam b/src/proto_alpha/lib_rollups/tezos-rollups-alpha.opam new file mode 100644 index 000000000000..4f7711a2f66a --- /dev/null +++ b/src/proto_alpha/lib_rollups/tezos-rollups-alpha.opam @@ -0,0 +1,26 @@ +# This file was automatically generated, do not edit. +# Edit file manifest/main.ml instead. +opam-version: "2.0" +maintainer: "contact@tezos.com" +authors: ["Tezos devteam"] +homepage: "https://www.tezos.com/" +bug-reports: "https://gitlab.com/tezos/tezos/issues" +dev-repo: "git+https://gitlab.com/tezos/tezos.git" +license: "MIT" +depends: [ + "dune" { >= "2.9" } + "ppx_inline_test" + "tezos-base" + "tezos-crypto" + "tezos-protocol-alpha" + "tezos-micheline" + "tezos-client-alpha" + "tezos-client-base" + "tezos-workers" + "tezos-shell" +] +build: [ + ["dune" "build" "-p" name "-j" jobs] + ["dune" "runtest" "-p" name "-j" jobs] {with-test} +] +synopsis: "Tezos/Protocol: protocol specific library for rollups" diff --git a/src/proto_alpha/lib_tx_rollup/dune b/src/proto_alpha/lib_tx_rollup/dune index ccc0f8755ed1..da23ecc44d53 100644 --- a/src/proto_alpha/lib_tx_rollup/dune +++ b/src/proto_alpha/lib_tx_rollup/dune @@ -25,7 +25,8 @@ tezos-client-base-unix tezos-shell tezos-store - tezos-workers) + tezos-workers + tezos-rollups-alpha) (inline_tests (flags -verbose) (modes native)) (preprocess (pps ppx_inline_test)) (library_flags (:standard -linkall)) @@ -48,4 +49,5 @@ -open Tezos_micheline -open Tezos_client_base -open Tezos_client_base_unix - -open Tezos_workers))) + -open Tezos_workers + -open Tezos_rollups_alpha))) diff --git a/src/proto_alpha/lib_tx_rollup/error.ml b/src/proto_alpha/lib_tx_rollup/error.ml index 07fcf66de704..10a6c3fcd12b 100644 --- a/src/proto_alpha/lib_tx_rollup/error.ml +++ b/src/proto_alpha/lib_tx_rollup/error.ml @@ -358,25 +358,6 @@ let () = | Tx_rollup_invalid_message_position_in_inbox i -> Some i | _ -> None) (fun i -> Tx_rollup_invalid_message_position_in_inbox i) -type error += No_worker_for_source of Signature.Public_key_hash.t - -let () = - register_error_kind - ~id:"tx_rollup.node.no_worker_for_source" - ~title:"No injecting queue for source" - ~description: - "An L1 operation could not be queued because its source has no worker." - ~pp:(fun ppf s -> - Format.fprintf - ppf - "No worker for source %a" - Signature.Public_key_hash.pp - s) - `Permanent - Data_encoding.(obj1 (req "source" Signature.Public_key_hash.encoding)) - (function No_worker_for_source s -> Some s | _ -> None) - (fun s -> No_worker_for_source s) - type error += No_batcher let () = diff --git a/src/proto_alpha/lib_tx_rollup/error.mli b/src/proto_alpha/lib_tx_rollup/error.mli index 81097dcb006a..7959486a25a2 100644 --- a/src/proto_alpha/lib_tx_rollup/error.mli +++ b/src/proto_alpha/lib_tx_rollup/error.mli @@ -95,10 +95,6 @@ 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 -(** Error when the injector has no worker for the source which must inject an - operation. *) -type error += No_worker_for_source of Signature.Public_key_hash.t - (** Error when we want to interact with the batcher but it was not started. *) type error += No_batcher diff --git a/src/proto_alpha/lib_tx_rollup/tezos-tx-rollup-alpha.opam b/src/proto_alpha/lib_tx_rollup/tezos-tx-rollup-alpha.opam index 2e34c83ae963..d37a20087bf2 100644 --- a/src/proto_alpha/lib_tx_rollup/tezos-tx-rollup-alpha.opam +++ b/src/proto_alpha/lib_tx_rollup/tezos-tx-rollup-alpha.opam @@ -29,6 +29,7 @@ depends: [ "tezos-shell" "tezos-store" "tezos-workers" + "tezos-rollups-alpha" ] build: [ ["dune" "build" "-p" name "-j" jobs] -- GitLab From 1ebe1b29eb834423f6323c78e93507b24e640a35 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Tue, 3 May 2022 09:12:55 +0200 Subject: [PATCH 09/22] Rollups: move tezos_reorg computation and fetch to lib_rollups --- src/proto_alpha/lib_rollups/common.ml | 54 +++++++++++++++++++++++++ src/proto_alpha/lib_rollups/common.mli | 25 ++++++++++++ src/proto_alpha/lib_tx_rollup/state.ml | 56 ++++---------------------- 3 files changed, 87 insertions(+), 48 deletions(-) diff --git a/src/proto_alpha/lib_rollups/common.ml b/src/proto_alpha/lib_rollups/common.ml index 67d558f77569..05ad6be16e46 100644 --- a/src/proto_alpha/lib_rollups/common.ml +++ b/src/proto_alpha/lib_rollups/common.ml @@ -23,6 +23,8 @@ (* *) (*****************************************************************************) +open Protocol_client_context + type signer = { alias : string; pkh : Signature.public_key_hash; @@ -47,3 +49,55 @@ let reorg_encoding block_encoding = @@ obj2 (req "old_chain" (list block_encoding)) (req "new_chain" (list block_encoding)) + +let fetch_tezos_block ~find_in_cache (cctxt : #full) hash : + (Alpha_block_services.block_info, error trace) result Lwt.t = + let open Lwt_result_syntax in + let fetch hash = + let*! block = + Alpha_block_services.info + cctxt + ~chain:cctxt#chain + ~block:(`Hash (hash, 0)) + () + in + Lwt.return @@ Result.to_option block + in + find_in_cache hash fetch + +(* Compute the reorganization of L1 blocks from the chain whose head is + [old_head_hash] and the chain whose head [new_head_hash]. *) +let tezos_reorg fetch_tezos_block ~old_head_hash ~new_head_hash = + let open Alpha_block_services in + let open Lwt_result_syntax in + let rec loop old_chain new_chain old_head_hash new_head_hash = + if Block_hash.(old_head_hash = new_head_hash) then + return {old_chain = List.rev old_chain; new_chain = List.rev new_chain} + else + let* new_head = fetch_tezos_block new_head_hash in + let* old_head = fetch_tezos_block old_head_hash in + let old_level = old_head.header.shell.level in + let new_level = new_head.header.shell.level in + let diff = Int32.sub new_level old_level in + let (old_chain, new_chain, old, new_) = + if diff = 0l then + (* Heads at same level *) + let new_chain = new_head :: new_chain in + let old_chain = old_head :: old_chain in + let new_head_hash = new_head.header.shell.predecessor in + let old_head_hash = old_head.header.shell.predecessor in + (old_chain, new_chain, old_head_hash, new_head_hash) + else if diff > 0l then + (* New chain is longer *) + let new_chain = new_head :: new_chain in + let new_head_hash = new_head.header.shell.predecessor in + (old_chain, new_chain, old_head_hash, new_head_hash) + else + (* Old chain was longer *) + let old_chain = old_head :: old_chain in + let old_head_hash = old_head.header.shell.predecessor in + (old_chain, new_chain, old_head_hash, new_head_hash) + in + loop old_chain new_chain old new_ + in + loop [] [] old_head_hash new_head_hash diff --git a/src/proto_alpha/lib_rollups/common.mli b/src/proto_alpha/lib_rollups/common.mli index a329f09343f0..14807a47f170 100644 --- a/src/proto_alpha/lib_rollups/common.mli +++ b/src/proto_alpha/lib_rollups/common.mli @@ -23,6 +23,8 @@ (* *) (*****************************************************************************) +open Protocol_client_context + (** The type of signers for operations injected by the injector *) type signer = { alias : string; @@ -47,3 +49,26 @@ val get_signer : val no_reorg : 'a reorg val reorg_encoding : 'a Data_encoding.t -> 'a reorg Data_encoding.t + +type block_info := Alpha_block_services.block_info + +(** [fetch_tezos_block ~find_in_cache cctxt hash] returns a block info given a + block hash. Looks for the block using [find_in_cache] first, and fetches + it from the L1 node otherwise. *) +val fetch_tezos_block : + find_in_cache: + (Block_hash.t -> + (Block_hash.t -> block_info option Lwt.t) -> + block_info tzresult Lwt.t) -> + #full -> + Block_hash.t -> + block_info tzresult Lwt.t + +(** [tezos_reorg fetch ~old_head_hash ~new_head_hash] computes the + reorganization of L1 blocks from the chain whose head is [old_head_hash] and + the chain whose head [new_head_hash]. *) +val tezos_reorg : + (Block_hash.t -> block_info tzresult Lwt.t) -> + old_head_hash:Block_hash.t -> + new_head_hash:Block_hash.t -> + block_info reorg tzresult Lwt.t diff --git a/src/proto_alpha/lib_tx_rollup/state.ml b/src/proto_alpha/lib_tx_rollup/state.ml index b26cdc863ae4..87a89c8dd8bb 100644 --- a/src/proto_alpha/lib_tx_rollup/state.ml +++ b/src/proto_alpha/lib_tx_rollup/state.ml @@ -62,56 +62,15 @@ let get_head state = state.head let fetch_tezos_block state hash = let open Lwt_syntax in - let fetch hash = + let find_in_cache hash fetch = let+ block = - Alpha_block_services.info - state.cctxt - ~chain:state.cctxt#chain - ~block:(`Hash (hash, 0)) - () + Tezos_blocks_cache.find_or_replace state.tezos_blocks_cache hash fetch in - Result.to_option block + Result.of_option + ~error:[Error.Tx_rollup_cannot_fetch_tezos_block hash] + block in - let+ block = - Tezos_blocks_cache.find_or_replace state.tezos_blocks_cache hash fetch - in - Result.of_option ~error:[Error.Tx_rollup_cannot_fetch_tezos_block hash] block - -(* Compute the reorganization of L1 blocks from the chain whose head is - [old_head_hash] and the chain whose head [new_head_hash]. *) -let tezos_reorg state ~old_head_hash ~new_head_hash = - let open Lwt_result_syntax in - let rec loop old_chain new_chain old_head_hash new_head_hash = - if Block_hash.(old_head_hash = new_head_hash) then - return {old_chain = List.rev old_chain; new_chain = List.rev new_chain} - else - let* new_head = fetch_tezos_block state new_head_hash in - let* old_head = fetch_tezos_block state old_head_hash in - let old_level = old_head.header.shell.level in - let new_level = new_head.header.shell.level in - let diff = Int32.sub new_level old_level in - let (old_chain, new_chain, old, new_) = - if diff = 0l then - (* Heads at same level *) - let new_chain = new_head :: new_chain in - let old_chain = old_head :: old_chain in - let new_head_hash = new_head.header.shell.predecessor in - let old_head_hash = old_head.header.shell.predecessor in - (old_chain, new_chain, old_head_hash, new_head_hash) - else if diff > 0l then - (* New chain is longer *) - let new_chain = new_head :: new_chain in - let new_head_hash = new_head.header.shell.predecessor in - (old_chain, new_chain, old_head_hash, new_head_hash) - else - (* Old chain was longer *) - let old_chain = old_head :: old_chain in - let old_head_hash = old_head.header.shell.predecessor in - (old_chain, new_chain, old_head_hash, new_head_hash) - in - loop old_chain new_chain old new_ - in - loop [] [] old_head_hash new_head_hash + fetch_tezos_block ~find_in_cache state.cctxt hash let set_tezos_head state new_head_hash = let open Lwt_result_syntax in @@ -123,7 +82,8 @@ let set_tezos_head state new_head_hash = tezos block. *) let+ new_head = fetch_tezos_block state new_head_hash in {old_chain = []; new_chain = [new_head]} - | Some old_head_hash -> tezos_reorg state ~old_head_hash ~new_head_hash + | Some old_head_hash -> + tezos_reorg (fetch_tezos_block state) ~old_head_hash ~new_head_hash in let* () = Stores.Tezos_head_store.write state.stores.tezos_head new_head_hash -- GitLab From 69ff5e2b5b4a2879646da750bed7029cc34b8bf1 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Wed, 4 May 2022 17:13:56 +0200 Subject: [PATCH 10/22] Rollups/Injector: use rollup_node_state for fee related functions --- .../lib_rollups/injector_functor.ml | 30 ++++++++++--------- src/proto_alpha/lib_rollups/injector_sigs.ml | 6 ++-- src/proto_alpha/lib_tx_rollup/injector.ml | 4 +-- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/proto_alpha/lib_rollups/injector_functor.ml b/src/proto_alpha/lib_rollups/injector_functor.ml index bf6d38a741b0..c37f4fd42d1b 100644 --- a/src/proto_alpha/lib_rollups/injector_functor.ml +++ b/src/proto_alpha/lib_rollups/injector_functor.ml @@ -259,10 +259,10 @@ module Make (Rollup : PARAMETERS) = struct [] mophs - let fee_parameter_of_operations ops = + let fee_parameter_of_operations state ops = List.fold_left (fun acc {L1_operation.manager_operation = Manager op; _} -> - let param = Rollup.fee_parameter op in + let param = Rollup.fee_parameter state op in Injection. { minimal_fees = Tez.max acc.minimal_fees param.minimal_fees; @@ -295,8 +295,8 @@ module Make (Rollup : PARAMETERS) = struct (** Simulate the injection of [operations]. See {!inject_operations} for the specification of [must_succeed]. *) - let simulate_operations ~must_succeed state signer - (operations : L1_operation.t list) = + let simulate_operations ~must_succeed state (operations : L1_operation.t list) + = let open Lwt_result_syntax in let open Annotated_manager_operation in let force = @@ -311,7 +311,9 @@ module Make (Rollup : PARAMETERS) = struct match must_succeed with `All -> false | `At_least_one -> true) in let*! () = Event.(emit2 simulating_operations) state operations force in - let fee_parameter = fee_parameter_of_operations operations in + let fee_parameter = + fee_parameter_of_operations state.rollup_node_state operations + in let operations = List.map (fun {L1_operation.manager_operation = Manager operation; _} -> @@ -333,9 +335,9 @@ module Make (Rollup : PARAMETERS) = struct ~force ~chain:state.cctxt#chain ~block:(`Head 0) - ~source:signer.pkh - ~src_pk:signer.pk - ~src_sk:signer.sk + ~source:state.signer.pkh + ~src_pk:state.signer.pk + ~src_sk:state.signer.sk ~successor_level: true (* Needed to simulate tx_rollup operations in the next block *) ~fee:Limit.unknown @@ -396,7 +398,7 @@ module Make (Rollup : PARAMETERS) = struct (operations : L1_operation.t list) = let open Lwt_result_syntax in let* (_oph, packed_contents, result) = - simulate_operations ~must_succeed state state.signer operations + simulate_operations ~must_succeed state operations in let results = Apply_results.to_list result in let failure = ref false in @@ -439,18 +441,18 @@ module Make (Rollup : PARAMETERS) = struct (** Returns the (upper bound on) the size of an L1 batch of operations composed of the manager operations [rev_ops]. *) - let size_l1_batch signer rev_ops = + let size_l1_batch state rev_ops = let contents_list = List.map (fun (op : L1_operation.t) -> let (Manager operation) = op.manager_operation in let {fee; counter; gas_limit; storage_limit} = - Rollup.approximate_fee_bound operation + Rollup.approximate_fee_bound state.rollup_node_state operation in let contents = Manager_operation { - source = signer.pkh; + source = state.signer.pkh; operation; fee; counter; @@ -470,7 +472,7 @@ module Make (Rollup : PARAMETERS) = struct | Ok packed_contents_list -> packed_contents_list in let signature = - match signer.pkh with + match state.signer.pkh with | Signature.Ed25519 _ -> Signature.of_ed25519 Ed25519.zero | Secp256k1 _ -> Signature.of_secp256k1 Secp256k1.zero | P256 _ -> Signature.of_p256 P256.zero @@ -493,7 +495,7 @@ module Make (Rollup : PARAMETERS) = struct Op_queue.fold (fun _oph op ops -> let new_ops = op :: ops in - let new_size = size_l1_batch state.signer new_ops in + let new_size = size_l1_batch state new_ops in if new_size > size_limit then raise (Reached_limit ops) ; new_ops) state.queue diff --git a/src/proto_alpha/lib_rollups/injector_sigs.ml b/src/proto_alpha/lib_rollups/injector_sigs.ml index 7fb3adf8a361..1bee0bd72170 100644 --- a/src/proto_alpha/lib_rollups/injector_sigs.ml +++ b/src/proto_alpha/lib_rollups/injector_sigs.ml @@ -88,11 +88,13 @@ module type PARAMETERS = sig (** Returns the {e appoximate upper-bounds} for the fee and limits of an operation, used to compute an upper bound on the size (in bytes) for this operation. *) - val approximate_fee_bound : 'a manager_operation -> approximate_fee_bound + val approximate_fee_bound : + rollup_node_state -> 'a manager_operation -> approximate_fee_bound (** Returns the fee_parameter (to compute fee w.r.t. gas, size, etc.) and the caps of fee and burn for each operation. *) - val fee_parameter : 'a manager_operation -> Injection.fee_parameter + val fee_parameter : + rollup_node_state -> 'a manager_operation -> Injection.fee_parameter (** When injecting the given [operations] in an L1 batch, if [batch_must_succeed operations] returns [`All] then all the operations must diff --git a/src/proto_alpha/lib_tx_rollup/injector.ml b/src/proto_alpha/lib_tx_rollup/injector.ml index 8a6bff438f00..c69747ae3d0a 100644 --- a/src/proto_alpha/lib_tx_rollup/injector.ml +++ b/src/proto_alpha/lib_tx_rollup/injector.ml @@ -79,7 +79,7 @@ module Parameters = struct | Rejection -> 3 | Dispatch_withdrawals -> 89 - let fee_parameter _ = + let fee_parameter _ _ = Injection. { minimal_fees = Tez.of_mutez_exn 100L; @@ -98,7 +98,7 @@ module Parameters = struct (without having to do a simulation first). *) (* TODO: https://gitlab.com/tezos/tezos/-/issues/2812 check the size, or compute them wrt operation kind *) - let approximate_fee_bound _ = + let approximate_fee_bound _ _ = { fee = Tez.of_mutez_exn 3_000_000L; counter = Z.of_int 500_000; -- GitLab From 647555dc497fc98122127051cc4bbc1165b5ade1 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Fri, 6 May 2022 15:09:56 +0200 Subject: [PATCH 11/22] Tx_rollup,Node: keep actual error on fetch block failure --- src/proto_alpha/lib_rollups/common.ml | 14 +++++--------- src/proto_alpha/lib_rollups/common.mli | 2 +- src/proto_alpha/lib_tx_rollup/state.ml | 14 +++++++++++--- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/proto_alpha/lib_rollups/common.ml b/src/proto_alpha/lib_rollups/common.ml index 05ad6be16e46..72be7960b004 100644 --- a/src/proto_alpha/lib_rollups/common.ml +++ b/src/proto_alpha/lib_rollups/common.ml @@ -52,16 +52,12 @@ let reorg_encoding block_encoding = let fetch_tezos_block ~find_in_cache (cctxt : #full) hash : (Alpha_block_services.block_info, error trace) result Lwt.t = - let open Lwt_result_syntax in let fetch hash = - let*! block = - Alpha_block_services.info - cctxt - ~chain:cctxt#chain - ~block:(`Hash (hash, 0)) - () - in - Lwt.return @@ Result.to_option block + Alpha_block_services.info + cctxt + ~chain:cctxt#chain + ~block:(`Hash (hash, 0)) + () in find_in_cache hash fetch diff --git a/src/proto_alpha/lib_rollups/common.mli b/src/proto_alpha/lib_rollups/common.mli index 14807a47f170..a1f4a55e2c7d 100644 --- a/src/proto_alpha/lib_rollups/common.mli +++ b/src/proto_alpha/lib_rollups/common.mli @@ -58,7 +58,7 @@ type block_info := Alpha_block_services.block_info val fetch_tezos_block : find_in_cache: (Block_hash.t -> - (Block_hash.t -> block_info option Lwt.t) -> + (Block_hash.t -> block_info tzresult Lwt.t) -> block_info tzresult Lwt.t) -> #full -> Block_hash.t -> diff --git a/src/proto_alpha/lib_tx_rollup/state.ml b/src/proto_alpha/lib_tx_rollup/state.ml index 87a89c8dd8bb..648a3ad33b04 100644 --- a/src/proto_alpha/lib_tx_rollup/state.ml +++ b/src/proto_alpha/lib_tx_rollup/state.ml @@ -62,13 +62,21 @@ let get_head state = state.head let fetch_tezos_block state hash = let open Lwt_syntax in + let errors = ref [] in let find_in_cache hash fetch = + let fetch hash = + let* block = fetch hash in + match block with + | Error errs -> + errors := errs ; + return_none + | Ok block -> return_some block + in let+ block = Tezos_blocks_cache.find_or_replace state.tezos_blocks_cache hash fetch in - Result.of_option - ~error:[Error.Tx_rollup_cannot_fetch_tezos_block hash] - block + Result.of_option ~error:!errors block + |> record_trace (Error.Tx_rollup_cannot_fetch_tezos_block hash) in fetch_tezos_block ~find_in_cache state.cctxt hash -- GitLab From 2cff2ebe552672b001d59b17c7ec37d45fd0c364 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Mon, 2 May 2022 13:21:47 +0200 Subject: [PATCH 12/22] SCORU/Node: add dependency to lib_rollups --- manifest/main.ml | 1 + src/proto_013_PtJakart/bin_sc_rollup_node/dune | 6 ++++-- .../tezos-sc-rollup-node-013-PtJakart.opam | 1 + src/proto_alpha/bin_sc_rollup_node/dune | 6 ++++-- .../bin_sc_rollup_node/tezos-sc-rollup-node-alpha.opam | 1 + 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/manifest/main.ml b/manifest/main.ml index ad2895712beb..d941b91a104e 100644 --- a/manifest/main.ml +++ b/manifest/main.ml @@ -3489,6 +3489,7 @@ end = struct irmin; ringo; ringo_lwt; + rollups |> if_some |> open_; ] in let tx_rollup = diff --git a/src/proto_013_PtJakart/bin_sc_rollup_node/dune b/src/proto_013_PtJakart/bin_sc_rollup_node/dune index 955c99a2de2e..504a8dd66a99 100644 --- a/src/proto_013_PtJakart/bin_sc_rollup_node/dune +++ b/src/proto_013_PtJakart/bin_sc_rollup_node/dune @@ -30,7 +30,8 @@ irmin-pack.unix irmin ringo - ringo-lwt) + ringo-lwt + tezos-rollups-013-PtJakart) (flags (:standard -open Tezos_base.TzPervasives @@ -45,4 +46,5 @@ -open Tezos_protocol_013_PtJakart_parameters -open Tezos_rpc -open Tezos_shell_services - -open Tezos_sc_rollup_013_PtJakart))) + -open Tezos_sc_rollup_013_PtJakart + -open Tezos_rollups_013_PtJakart))) diff --git a/src/proto_013_PtJakart/bin_sc_rollup_node/tezos-sc-rollup-node-013-PtJakart.opam b/src/proto_013_PtJakart/bin_sc_rollup_node/tezos-sc-rollup-node-013-PtJakart.opam index b346c36f2561..e38a5bce6f40 100644 --- a/src/proto_013_PtJakart/bin_sc_rollup_node/tezos-sc-rollup-node-013-PtJakart.opam +++ b/src/proto_013_PtJakart/bin_sc_rollup_node/tezos-sc-rollup-node-013-PtJakart.opam @@ -29,6 +29,7 @@ depends: [ "irmin" { >= "3.2.0" & < "3.3.0" } "ringo" { = "0.8" } "ringo-lwt" { = "0.8" } + "tezos-rollups-013-PtJakart" ] build: [ ["dune" "build" "-p" name "-j" jobs] diff --git a/src/proto_alpha/bin_sc_rollup_node/dune b/src/proto_alpha/bin_sc_rollup_node/dune index 9c76ba42e6b6..501b78e992d8 100644 --- a/src/proto_alpha/bin_sc_rollup_node/dune +++ b/src/proto_alpha/bin_sc_rollup_node/dune @@ -30,7 +30,8 @@ irmin-pack.unix irmin ringo - ringo-lwt) + ringo-lwt + tezos-rollups-alpha) (flags (:standard -open Tezos_base.TzPervasives @@ -45,4 +46,5 @@ -open Tezos_protocol_alpha_parameters -open Tezos_rpc -open Tezos_shell_services - -open Tezos_sc_rollup_alpha))) + -open Tezos_sc_rollup_alpha + -open Tezos_rollups_alpha))) diff --git a/src/proto_alpha/bin_sc_rollup_node/tezos-sc-rollup-node-alpha.opam b/src/proto_alpha/bin_sc_rollup_node/tezos-sc-rollup-node-alpha.opam index e0bf8c7b7447..b7a955219bcf 100644 --- a/src/proto_alpha/bin_sc_rollup_node/tezos-sc-rollup-node-alpha.opam +++ b/src/proto_alpha/bin_sc_rollup_node/tezos-sc-rollup-node-alpha.opam @@ -29,6 +29,7 @@ depends: [ "irmin" { >= "3.2.0" & < "3.3.0" } "ringo" { = "0.8" } "ringo-lwt" { = "0.8" } + "tezos-rollups-alpha" ] build: [ ["dune" "build" "-p" name "-j" jobs] -- GitLab From e47f218a1f54ee43e19942b64912476662a0ba72 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Mon, 2 May 2022 13:24:47 +0200 Subject: [PATCH 13/22] SCORU/Node: implement injector --- .../bin_sc_rollup_node/injector.ml | 113 ++++++++++++++++++ src/proto_alpha/lib_rollups/l1_operation.ml | 24 ++++ 2 files changed, 137 insertions(+) create mode 100644 src/proto_alpha/bin_sc_rollup_node/injector.ml diff --git a/src/proto_alpha/bin_sc_rollup_node/injector.ml b/src/proto_alpha/bin_sc_rollup_node/injector.ml new file mode 100644 index 000000000000..6da20a5bfe82 --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/injector.ml @@ -0,0 +1,113 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs, *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +open Protocol.Alpha_context +open Injector_sigs + +type tag = Publish | Add_messages | Cement + +module Parameters : + PARAMETERS with type rollup_node_state = Node_context.t and type Tag.t = tag = +struct + type rollup_node_state = Node_context.t + + let events_section = ["sc_rollup"] + + module Tag : TAG with type t = tag = struct + type t = tag + + let compare = Stdlib.compare + + let string_of_tag = function + | Publish -> "publish" + | Add_messages -> "add_messages" + | Cement -> "cement" + + let pp ppf t = Format.pp_print_string ppf (string_of_tag t) + + let encoding : t Data_encoding.t = + let open Data_encoding in + string_enum + (List.map + (fun t -> (string_of_tag t, t)) + [Publish; Add_messages; Cement]) + end + + (* TODO: to determine + Very coarse approximation for the number of operation we + expect for each block *) + let table_estimated_size : Tag.t -> int = function + | Publish -> 1 + | Add_messages -> 100 + | Cement -> 1 + + let fee_parameter {Node_context.fee_parameter; _} _ = fee_parameter + + (* Below are dummy values that are only used to approximate the + size. It is thus important that they remain above the real + values if we want the computed size to be an over_approximation + (without having to do a simulation first). *) + (* TODO: + See TORU issue: https://gitlab.com/tezos/tezos/-/issues/2812 + check the size, or compute them wrt operation kind *) + let approximate_fee_bound _ _ = + { + fee = Tez.of_mutez_exn 3_000_000L; + counter = Z.of_int 500_000; + gas_limit = Gas.Arith.integral_of_int_exn 500_000; + storage_limit = Z.of_int 500_000; + } + + (* TODO: Decide if some batches must have all the operation to succeed. See + {!Injector_sigs.Parameter.batch_must_succeed}. *) + let batch_must_succeed _ = `At_least_one + + let ignore_failing_operation : + type kind. + kind manager_operation -> [`Ignore_keep | `Ignore_drop | `Don't_ignore] = + function + | _ -> `Don't_ignore + + (** Returns [true] if an included operation should be re-queued for injection + when the block in which it is included is reverted (due to a + reorganization). *) + let requeue_reverted_operation (type kind) _ + (operation : kind manager_operation) = + let open Lwt_syntax in + match operation with + | Sc_rollup_publish _ -> + (* Commitments are always produced on finalized blocks. They don't need + to be recomputed. *) + return_true + | Sc_rollup_cement _ -> + (* TODO *) + return_true + | Sc_rollup_add_messages _ -> + (* TODO *) + return_true + | _ -> return_true +end + +include Injector_functor.Make (Parameters) diff --git a/src/proto_alpha/lib_rollups/l1_operation.ml b/src/proto_alpha/lib_rollups/l1_operation.ml index 1b765034554b..7e9bca11247d 100644 --- a/src/proto_alpha/lib_rollups/l1_operation.ml +++ b/src/proto_alpha/lib_rollups/l1_operation.ml @@ -142,6 +142,30 @@ module Manager_operation = struct level (Format.pp_print_list pp_rollup_reveal) tickets_info + | Sc_rollup_add_messages {rollup; messages} -> + Format.fprintf + ppf + "publishing %d messages to rollup %a inbox" + (List.length messages) + Sc_rollup.Address.pp + rollup + | Sc_rollup_cement {rollup; commitment} -> + Format.fprintf + ppf + "cementing commitment %a of rollup %a" + Sc_rollup.Commitment_hash.pp + commitment + Sc_rollup.Address.pp + rollup + | Sc_rollup_publish + {rollup; commitment = Sc_rollup.Commitment.{inbox_level; _}} -> + Format.fprintf + ppf + "publish commitment for level %a of rollup %a" + Raw_level.pp + inbox_level + Sc_rollup.Address.pp + rollup | _ -> pp_kind ppf op end -- GitLab From a1f7bfdef65b4e6bd750df482e4023904ee32752 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Tue, 3 May 2022 13:13:41 +0200 Subject: [PATCH 14/22] SCORU/Node: add cache for recent blocks to compute reorgs --- src/proto_alpha/bin_sc_rollup_node/daemon.ml | 11 +-- src/proto_alpha/bin_sc_rollup_node/layer1.ml | 82 +++++++++++++++++-- src/proto_alpha/bin_sc_rollup_node/layer1.mli | 42 ++++++++-- 3 files changed, 114 insertions(+), 21 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/daemon.ml b/src/proto_alpha/bin_sc_rollup_node/daemon.ml index cb55f4b33aae..4b50b840f3a4 100644 --- a/src/proto_alpha/bin_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/bin_sc_rollup_node/daemon.ml @@ -168,9 +168,8 @@ module Make (PVM : Pvm.S) = struct in go [] - let daemonize node_ctxt store layer_1_chain_events = - Lwt.no_cancel - @@ iter_stream layer_1_chain_events + let daemonize node_ctxt store (l1_ctxt : Layer1.t) = + Lwt.no_cancel @@ iter_stream l1_ctxt.events @@ on_layer_1_chain_event node_ctxt store let install_finalizer store rpc_server = @@ -187,9 +186,7 @@ module Make (PVM : Pvm.S) = struct let* rpc_server = Components.RPC_server.start node_ctxt store configuration in - let* tezos_heads = - Layer1.start configuration node_ctxt.Node_context.cctxt store - in + let* l1_ctxt = Layer1.start configuration node_ctxt store in let*! () = Inbox.start () in let*! () = Components.Commitment.start () in @@ -199,7 +196,7 @@ module Make (PVM : Pvm.S) = struct ~rpc_addr:configuration.rpc_addr ~rpc_port:configuration.rpc_port in - daemonize node_ctxt store tezos_heads + daemonize node_ctxt store l1_ctxt in start () end diff --git a/src/proto_alpha/bin_sc_rollup_node/layer1.ml b/src/proto_alpha/bin_sc_rollup_node/layer1.ml index 4431df47a204..bc9cf5195519 100644 --- a/src/proto_alpha/bin_sc_rollup_node/layer1.ml +++ b/src/proto_alpha/bin_sc_rollup_node/layer1.ml @@ -26,6 +26,7 @@ open Configuration open Protocol.Alpha_context open Plugin +open Common (** @@ -40,6 +41,24 @@ let synchronization_failure e = e ; Lwt_exit.exit_and_raise 1 +type error += Cannot_find_block of Block_hash.t + +let () = + register_error_kind + ~id:"sc_rollup.node.cannot_find_block" + ~title:"Cannot find block from L1" + ~description:"A block couldn't be found from the L1 node" + ~pp:(fun ppf hash -> + Format.fprintf + ppf + "Block with hash %a was not found on the L1 node." + Block_hash.pp + hash) + `Temporary + Data_encoding.(obj1 (req "hash" Block_hash.encoding)) + (function Cannot_find_block hash -> Some hash | _ -> None) + (fun hash -> Cannot_find_block hash) + (** State @@ -105,8 +124,17 @@ module State = struct let store_block = Store.Blocks.add let block_of_hash = Store.Blocks.get + + module Blocks_cache = + Ringo_lwt.Functors.Make_opt + ((val Ringo.( + map_maker ~replacement:LRU ~overflow:Strong ~accounting:Precise)) + (Block_hash)) end +type blocks_cache = + Protocol_client_context.Alpha_block_services.block_info State.Blocks_cache.t + (** Chain events @@ -123,6 +151,12 @@ let same_branch new_head intermediate_heads = let rollback new_head = Rollback {new_head} +type t = { + blocks_cache : blocks_cache; + events : chain_event Lwt_stream.t; + node_ctxt : Node_context.t; +} + (** Helpers @@ -134,6 +168,11 @@ let genesis_hash = Block_hash.of_b58check_exn "BLockGenesisGenesisGenesisGenesisGenesisf79b5d1CoW2" +let chain_event_head_hash = function + | SameBranch {new_head = Head {hash; _}; _} + | Rollback {new_head = Head {hash; _}} -> + hash + (** [blocks_of_heads base heads] given a list of successive heads connected to [base], returns an associative list mapping block hash to block. This list is only used for traversal, not lookup. The @@ -323,15 +362,18 @@ let discard_pre_origination_blocks info chain_events = in Lwt_stream.filter_map at_or_after_origination chain_events -let start configuration (cctxt : Protocol_client_context.full) store = +let start configuration (node_ctxt : Node_context.t) store = let open Lwt_result_syntax in let*! () = Layer1_event.starting () in let* () = - check_sc_rollup_address_exists configuration.sc_rollup_address cctxt + check_sc_rollup_address_exists + configuration.sc_rollup_address + node_ctxt.cctxt in - let* event_stream = chain_events cctxt store `Main in - let* info = gather_info cctxt configuration.sc_rollup_address in - return (discard_pre_origination_blocks info event_stream) + let* event_stream = chain_events node_ctxt.cctxt store `Main in + let+ info = gather_info node_ctxt.cctxt configuration.sc_rollup_address in + let events = discard_pre_origination_blocks info event_stream in + {node_ctxt; events; blocks_cache = State.Blocks_cache.create 32} let current_head_hash store = let open Lwt_syntax in @@ -355,3 +397,33 @@ let processed = function | SameBranch {new_head; intermediate_heads} -> List.iter_s processed_head (intermediate_heads @ [new_head]) | Rollback {new_head} -> processed_head new_head + +(** [fetch_tezos_block l1_ctxt hash] returns a block info given a block + hash. Looks for the block in the blocks cache first, and fetches it from the + L1 node otherwise. *) +let fetch_tezos_block l1_ctxt hash = + let open Lwt_result_syntax in + let find_in_cache hash fetch = + let fetch hash = + let*! block = fetch hash in + Lwt.return @@ Result.to_option block + in + let*! block = + State.Blocks_cache.find_or_replace l1_ctxt.blocks_cache hash fetch + in + match block with Some b -> return b | _ -> tzfail (Cannot_find_block hash) + in + fetch_tezos_block ~find_in_cache l1_ctxt.node_ctxt.cctxt hash + +(** Returns the reorganization of L1 blocks (if any) for [new_head]. *) +let get_tezos_reorg_for_new_head l1_state store new_head_hash = + let open Lwt_result_syntax in + let*! old_head_hash = current_head_hash store in + match old_head_hash with + | None -> + (* No known tezos head, consider the new head as being on top of a previous + tezos block. *) + let+ new_head = fetch_tezos_block l1_state new_head_hash in + {old_chain = []; new_chain = [new_head]} + | Some old_head_hash -> + tezos_reorg (fetch_tezos_block l1_state) ~old_head_hash ~new_head_hash diff --git a/src/proto_alpha/bin_sc_rollup_node/layer1.mli b/src/proto_alpha/bin_sc_rollup_node/layer1.mli index 27100c6a4030..e953c25e0a83 100644 --- a/src/proto_alpha/bin_sc_rollup_node/layer1.mli +++ b/src/proto_alpha/bin_sc_rollup_node/layer1.mli @@ -46,15 +46,22 @@ type chain_event = (** A chain reorganization occurred since the previous synchronization. The rollback set [new_head] to an old block. *) -(** [start configuration cctxt store] returns a stream of [chain_event] - obtained from the monitoring of the Tezos node set up by the client - [cctxt]. The layer 1 state is stored in the data directory declared - in [configuration]. *) -val start : - Configuration.t -> - Protocol_client_context.full -> - Store.t -> - chain_event Lwt_stream.t tzresult Lwt.t +(** Type of cache holding the last 32 blocks, with their operations. *) +type blocks_cache + +type t = private { + blocks_cache : blocks_cache; + events : chain_event Lwt_stream.t; + node_ctxt : Node_context.t; +} + +val chain_event_head_hash : chain_event -> Block_hash.t + +(** [start configuration node_ctxt store] returns a stream of [chain_event] + obtained from the monitoring of the Tezos node set up by the client + [node_ctxt.cctxt]. The layer 1 state is stored in the data directory + declared in [configuration]. *) +val start : Configuration.t -> Node_context.t -> Store.t -> t tzresult Lwt.t (** [current_head_hash store] is the current hash of the head of the Tezos chain as far as the smart-contract rollup node knows from the @@ -77,3 +84,20 @@ val genesis_hash : Block_hash.t (** [processed chain_event] emits a log event to officialize the processing of some layer 1 [chain_event]. *) val processed : chain_event -> unit Lwt.t + +(** [fetch_tezos_block l1_ctxt hash] returns a block info given a block hash. + Looks for the block in the blocks cache first, and fetches it from the L1 + node otherwise. *) +val fetch_tezos_block : + t -> + Block_hash.t -> + Protocol_client_context.Alpha_block_services.block_info tzresult Lwt.t + +(** [get_tezos_reorg_for_new_head l1_ctxt store hash] returns the reorganization + of L1 blocks (if any) for [new_head]. *) +val get_tezos_reorg_for_new_head : + t -> + Store.t -> + Block_hash.t -> + Protocol_client_context.Alpha_block_services.block_info Common.reorg tzresult + Lwt.t -- GitLab From 61c031ffdf0119bca38861e4a0f8964d2d621949 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Tue, 3 May 2022 15:13:20 +0200 Subject: [PATCH 15/22] SCORU/Node: use injector for commitment --- .../bin_sc_rollup_node/commitment.ml | 69 ++++++------------- src/proto_alpha/bin_sc_rollup_node/daemon.ml | 24 ++++++- 2 files changed, 41 insertions(+), 52 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/commitment.ml b/src/proto_alpha/bin_sc_rollup_node/commitment.ml index 66b093103e89..5cd6fab27c90 100644 --- a/src/proto_alpha/bin_sc_rollup_node/commitment.ml +++ b/src/proto_alpha/bin_sc_rollup_node/commitment.ml @@ -23,19 +23,19 @@ (* *) (*****************************************************************************) -(** The rollup node stores and publishes commitments for the PVM +(** The rollup node stores and publishes commitments for the PVM every 20 levels. - Every time a finalized block is processed by the rollup node, - the latter determines whether the last commitment that the node - has produced referred to 20 blocks earlier. In this case, it - computes and stores a new commitment in a level-indexed map. + Every time a finalized block is processed by the rollup node, + the latter determines whether the last commitment that the node + has produced referred to 20 blocks earlier. In this case, it + computes and stores a new commitment in a level-indexed map. - Stored commitments are signed by the rollup node operator - and published on the layer1 chain. To ensure that commitments - produced by the rollup node are eventually published, - storing and publishing commitments are decoupled. Every time - a new head is processed, the node tries to publish the oldest + Stored commitments are signed by the rollup node operator + and published on the layer1 chain. To ensure that commitments + produced by the rollup node are eventually published, + storing and publishing commitments are decoupled. Every time + a new head is processed, the node tries to publish the oldest commitment that was not published already. *) @@ -45,9 +45,9 @@ open Alpha_context module type Mutable_level_store = Store.Mutable_value with type value = Raw_level.t -(* We keep the number of messages and ticks to be included in the - next commitment in memory. Note that we do not risk to increase - these counters when the wrong branch is tracked by the rollup +(* We keep the number of messages and ticks to be included in the + next commitment in memory. Note that we do not risk to increase + these counters when the wrong branch is tracked by the rollup node, as only finalized heads are processed to build commitments. *) @@ -240,8 +240,6 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct let* () = update_ticks_and_messages store hash in store_commitment_if_necessary ~origination_level store current_level hash - (* TODO: https://gitlab.com/tezos/tezos/-/issues/2869 - use the Injector to publish commitments. *) let publish_commitment (node_ctxt : Node_context.t) store = let origination_level = node_ctxt.initial_level in let open Lwt_result_syntax in @@ -257,42 +255,15 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct in if is_commitment_available then let*! commitment = Store.Commitments.get store next_level_to_publish in - let cctxt = node_ctxt.cctxt in - let sc_rollup_address = node_ctxt.rollup_address in - let fee_parameter = node_ctxt.fee_parameter in - let* (source, src_pk, src_sk) = - Node_context.get_operator_keys node_ctxt + let rollup = node_ctxt.rollup_address in + let publish_operation = Sc_rollup_publish {rollup; commitment} in + let* () = + Injector.add_pending_operation + ~source:node_ctxt.operator + publish_operation in - let* (_, _, Manager_operation_result {operation_result; _}) = - Client_proto_context.sc_rollup_publish - cctxt - ~chain:cctxt#chain - ~block:cctxt#block - ~commitment - ~source - ~rollup:sc_rollup_address - ~src_pk - ~src_sk - ~fee_parameter - () - in - let open Apply_results in let*! () = - match operation_result with - | Applied (Sc_rollup_publish_result _) -> - let open Lwt_syntax in - let* () = - Store.Last_published_commitment_level.set - store - commitment.inbox_level - in - Commitment_event.commitment_published commitment - | Failed (Sc_rollup_publish_manager_kind, _errors) -> - Commitment_event.commitment_failed commitment - | Backtracked (Sc_rollup_publish_result _, _errors) -> - Commitment_event.commitment_backtracked commitment - | Skipped Sc_rollup_publish_manager_kind -> - Commitment_event.commitment_skipped commitment + Store.Last_published_commitment_level.set store commitment.inbox_level in return_unit else return_unit diff --git a/src/proto_alpha/bin_sc_rollup_node/daemon.ml b/src/proto_alpha/bin_sc_rollup_node/daemon.ml index 4b50b840f3a4..7b2ec24195c4 100644 --- a/src/proto_alpha/bin_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/bin_sc_rollup_node/daemon.ml @@ -88,7 +88,18 @@ module Make (PVM : Pvm.S) = struct (* Publishing a commitment when one is available does not depend on the state of the current head, but we still need to ensure that the node only published one commitment per block. *) - Components.Commitment.publish_commitment node_ctxt store + let* () = Components.Commitment.publish_commitment node_ctxt store in + let*! () = Injector.inject () in + return_unit + + let notify_injector l1_ctxt store chain_event = + let open Lwt_result_syntax in + let open Layer1 in + let hash = chain_event_head_hash chain_event in + let* head = fetch_tezos_block l1_ctxt hash in + let* reorg = get_tezos_reorg_for_new_head l1_ctxt store hash in + let*! () = Injector.new_tezos_head head reorg in + return_unit (* [on_layer_1_chain_event node_ctxt store chain_event old_heads] processes a list of heads, coming from either a list of [old_heads] or from the current @@ -104,9 +115,10 @@ module Make (PVM : Pvm.S) = struct needs to be returned to be included as the rollup node started tracking a new branch. *) - let on_layer_1_chain_event node_ctxt store chain_event old_heads = + let on_layer_1_chain_event l1_ctxt node_ctxt store chain_event old_heads = let open Lwt_result_syntax in let open Layer1 in + let* () = notify_injector l1_ctxt store chain_event in let* non_final_heads = match chain_event with | SameBranch {new_head; intermediate_heads} -> @@ -170,7 +182,7 @@ module Make (PVM : Pvm.S) = struct let daemonize node_ctxt store (l1_ctxt : Layer1.t) = Lwt.no_cancel @@ iter_stream l1_ctxt.events - @@ on_layer_1_chain_event node_ctxt store + @@ on_layer_1_chain_event l1_ctxt node_ctxt store let install_finalizer store rpc_server = let open Lwt_syntax in @@ -189,6 +201,12 @@ module Make (PVM : Pvm.S) = struct let* l1_ctxt = Layer1.start configuration node_ctxt store in let*! () = Inbox.start () in let*! () = Components.Commitment.start () in + let* () = + Injector.init + node_ctxt.cctxt + node_ctxt + ~signers:[(node_ctxt.operator, `Each_block, [Injector.Publish])] + in let _ = install_finalizer store rpc_server in let*! () = -- GitLab From ba6d77d0308484454aebbf20e05b6271c9794cf9 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Thu, 21 Apr 2022 14:44:30 +0200 Subject: [PATCH 16/22] SCORU/Node: add messages simulation --- .../bin_sc_rollup_node/interpreter.ml | 68 +++++++++++++++---- src/proto_alpha/bin_sc_rollup_node/layer1.ml | 31 ++++++++- src/proto_alpha/bin_sc_rollup_node/layer1.mli | 13 ++++ 3 files changed, 94 insertions(+), 18 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/interpreter.ml b/src/proto_alpha/bin_sc_rollup_node/interpreter.ml index ba769393f1af..d6ff62a042fa 100644 --- a/src/proto_alpha/bin_sc_rollup_node/interpreter.ml +++ b/src/proto_alpha/bin_sc_rollup_node/interpreter.ml @@ -28,6 +28,19 @@ open Alpha_context module Inbox = Store.Inbox module type S = sig + module PVM : Pvm.S + + type simulation_result = { + state_hash : PVM.hash; + status : PVM.status; + ticks_diff : Z.t; + } + + (** [simulate node_ctxt store messages] interprets a list of messages and + checks its applicability. *) + val simulate : + Node_context.t -> Store.t -> string list -> simulation_result tzresult Lwt.t + (** [process_head node_ctxt store head] interprets the messages associated with a [head] from a chain [event]. This requires the inbox to be updated beforehand. *) @@ -35,9 +48,15 @@ module type S = sig Node_context.t -> Store.t -> Layer1.head -> unit tzresult Lwt.t end -module Make (PVM : Pvm.S) : S = struct +module Make (PVM : Pvm.S) : S with module PVM = PVM = struct module PVM = PVM + type simulation_result = { + state_hash : PVM.hash; + status : PVM.status; + ticks_diff : Z.t; + } + (** [eval_until_input state] advances a PVM [state] until it wants more inputs. *) let eval_until_input state = let open Lwt_syntax in @@ -61,13 +80,11 @@ module Make (PVM : Pvm.S) : S = struct let* state = eval_until_input state in return state - (** [transition_pvm node_ctxt store predecessor_hash hash] runs a PVM at the previous state from block - [predecessor_hash] by consuming as many messages as possible from block [hash]. *) - let transition_pvm node_ctxt store predecessor_hash hash = - let open Node_context in + let evaluate (node_ctxt : Node_context.t) store current_hash inbox_level + messages = let open Lwt_result_syntax in (* Retrieve the previous PVM state from store. *) - let*! predecessor_state = Store.PVMState.find store predecessor_hash in + let*! predecessor_state = Store.PVMState.find store current_hash in let* predecessor_state = match predecessor_state with | None -> @@ -88,11 +105,6 @@ module Make (PVM : Pvm.S) : S = struct | Some predecessor_state -> return predecessor_state in - (* Obtain inbox and its messages for this block. *) - let*! inbox = Store.Inboxes.get store hash in - let inbox_level = Inbox.inbox_level inbox in - let*! messages = Store.Messages.get store hash in - (* Iterate the PVM state with all the messages for this level. *) let*! state = List.fold_left_i_s @@ -105,10 +117,6 @@ module Make (PVM : Pvm.S) : S = struct predecessor_state messages in - - (* Write final state to store. *) - let*! () = Store.PVMState.set store hash state in - (* Compute extra information about the state. *) let*! initial_tick = PVM.get_tick predecessor_state in let*! last_tick = PVM.get_tick state in @@ -116,6 +124,25 @@ module Make (PVM : Pvm.S) : S = struct The number of ticks should not be an arbitrarily-sized integer. *) let num_ticks = Sc_rollup.Tick.distance initial_tick last_tick in + Lwt.return_ok (state, num_ticks) + + (** [transition_pvm node_ctxt store predecessor_hash hash] runs a PVM at the + previous state from block [predecessor_hash] by consuming as many messages + as possible from block [hash]. *) + let transition_pvm node_ctxt store predecessor_hash hash = + let open Lwt_result_syntax in + (* Obtain inbox and its messages for this block. *) + let*! inbox = Store.Inboxes.get store hash in + let inbox_level = Inbox.inbox_level inbox in + let*! messages = Store.Messages.get store hash in + + let* (state, num_ticks) = + evaluate node_ctxt store predecessor_hash inbox_level messages + in + + (* Write final state to store. *) + let*! () = Store.PVMState.set store hash state in + (* TODO: #2717 The length of messages here can potentially overflow the [int] returned from [List.length]. *) @@ -132,4 +159,15 @@ module Make (PVM : Pvm.S) : S = struct let open Lwt_result_syntax in let*! predecessor_hash = Layer1.predecessor store head in transition_pvm node_ctxt store predecessor_hash hash + + (** [simulate store messages] interprets a list of messages and checks its + applicability. *) + let simulate node_ctxt store messages = + let open Lwt_result_syntax in + let* (Head {hash; level}) = Layer1.current_head store in + let*? level = Environment.wrap_tzresult @@ Raw_level.of_int32 level in + let* (state, ticks_diff) = evaluate node_ctxt store hash level messages in + let*! state_hash = PVM.state_hash state in + let*! status = PVM.get_status state in + Lwt.return_ok {state_hash; status; ticks_diff} end diff --git a/src/proto_alpha/bin_sc_rollup_node/layer1.ml b/src/proto_alpha/bin_sc_rollup_node/layer1.ml index bc9cf5195519..5fa777da3dcb 100644 --- a/src/proto_alpha/bin_sc_rollup_node/layer1.ml +++ b/src/proto_alpha/bin_sc_rollup_node/layer1.ml @@ -41,9 +41,24 @@ let synchronization_failure e = e ; Lwt_exit.exit_and_raise 1 -type error += Cannot_find_block of Block_hash.t +type error += Non_initialized_rollup_node | Cannot_find_block of Block_hash.t let () = + register_error_kind + `Temporary + ~id:"sc_rollup.node.non_initialized_node" + ~title:"The rollup node is not initialized" + ~description: + "Some component of the node tried to access the store while the node has \ + never been initialized." + ~pp:(fun ppf () -> + Format.fprintf + ppf + "Some component of the node tried to access the store while the node \ + has never been initialized.") + Data_encoding.unit + (function Non_initialized_rollup_node -> Some () | _ -> None) + (fun () -> Non_initialized_rollup_node) ; register_error_kind ~id:"sc_rollup.node.cannot_find_block" ~title:"Cannot find block from L1" @@ -375,14 +390,24 @@ let start configuration (node_ctxt : Node_context.t) store = let events = discard_pre_origination_blocks info event_stream in {node_ctxt; events; blocks_cache = State.Blocks_cache.create 32} +let current_head_opt store = State.last_seen_head store + +let current_head store = + let open Lwt_syntax in + let+ head = current_head_opt store in + Option.fold + head + ~some:ok + ~none:(Result_syntax.tzfail Non_initialized_rollup_node) + let current_head_hash store = let open Lwt_syntax in - let+ head = State.last_seen_head store in + let+ head = current_head_opt store in Option.map (fun (Head {hash; _}) -> hash) head let current_level store = let open Lwt_syntax in - let+ head = State.last_seen_head store in + let+ head = current_head_opt store in Option.map (fun (Head {level; _}) -> level) head let predecessor store (Head {hash; _}) = diff --git a/src/proto_alpha/bin_sc_rollup_node/layer1.mli b/src/proto_alpha/bin_sc_rollup_node/layer1.mli index e953c25e0a83..418fc1567ab2 100644 --- a/src/proto_alpha/bin_sc_rollup_node/layer1.mli +++ b/src/proto_alpha/bin_sc_rollup_node/layer1.mli @@ -33,6 +33,8 @@ reorganization occurred. *) +type error += Non_initialized_rollup_node + type head = Head of {hash : Block_hash.t; level : int32} val head_encoding : head Data_encoding.t @@ -63,6 +65,17 @@ val chain_event_head_hash : chain_event -> Block_hash.t declared in [configuration]. *) val start : Configuration.t -> Node_context.t -> Store.t -> t tzresult Lwt.t +(** [current_head_opt store] is the current hash and level of the head of the + Tezos chain as far as the smart-contract rollup node knows from the latest + synchronization. Returns [None] if no synchronization has ever been made. *) +val current_head_opt : Store.t -> head option Lwt.t + +(** [current_head store] is the current hash and level of the head of the Tezos + chain as far as the smart-contract rollup node knows from the latest + synchronization. Returns an error [Non_initialized_rollup_node] if no + synchronization has ever been made. *) +val current_head : Store.t -> head tzresult Lwt.t + (** [current_head_hash store] is the current hash of the head of the Tezos chain as far as the smart-contract rollup node knows from the latest synchronization. Returns [None] if no synchronization has -- GitLab From 499b8ff8e5b43cb08eb09745d5711290aecfd0ec Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Thu, 7 Apr 2022 16:42:08 +0200 Subject: [PATCH 17/22] SCORU/Node: add Messages with signature representation --- manifest/main.ml | 2 + src/proto_alpha/bin_sc_rollup_node/dune | 2 + src/proto_alpha/lib_sc_rollup/dune | 2 + .../lib_sc_rollup/sc_rollup_messages.ml | 94 +++++++++++++++++++ .../lib_sc_rollup/sc_rollup_messages.mli | 46 +++++++++ 5 files changed, 146 insertions(+) create mode 100644 src/proto_alpha/lib_sc_rollup/sc_rollup_messages.ml create mode 100644 src/proto_alpha/lib_sc_rollup/sc_rollup_messages.mli diff --git a/manifest/main.ml b/manifest/main.ml index d941b91a104e..871f2e67cbb4 100644 --- a/manifest/main.ml +++ b/manifest/main.ml @@ -3427,6 +3427,7 @@ end = struct ~deps: [ tezos_base |> open_ ~m:"TzPervasives"; + environment |> if_ N.(number >= 014) |> open_; main |> open_; plugin |> if_some |> open_; parameters |> if_some |> open_; @@ -3476,6 +3477,7 @@ end = struct tezos_context_encoding; tezos_context_helpers; main |> open_; + environment |> if_ N.(number >= 014) |> open_; plugin |> if_some |> open_; parameters |> if_some |> open_; tezos_rpc |> open_; diff --git a/src/proto_alpha/bin_sc_rollup_node/dune b/src/proto_alpha/bin_sc_rollup_node/dune index 501b78e992d8..88da530b71db 100644 --- a/src/proto_alpha/bin_sc_rollup_node/dune +++ b/src/proto_alpha/bin_sc_rollup_node/dune @@ -18,6 +18,7 @@ tezos-context.encoding tezos-context.helpers tezos-protocol-alpha + tezos-protocol-alpha.environment tezos-protocol-plugin-alpha tezos-protocol-alpha-parameters tezos-rpc @@ -42,6 +43,7 @@ -open Tezos_client_base_unix -open Tezos_client_alpha -open Tezos_protocol_alpha + -open Tezos_protocol_environment_alpha -open Tezos_protocol_plugin_alpha -open Tezos_protocol_alpha_parameters -open Tezos_rpc diff --git a/src/proto_alpha/lib_sc_rollup/dune b/src/proto_alpha/lib_sc_rollup/dune index a680588fc8d6..0da57d96fb86 100644 --- a/src/proto_alpha/lib_sc_rollup/dune +++ b/src/proto_alpha/lib_sc_rollup/dune @@ -7,6 +7,7 @@ (instrumentation (backend bisect_ppx)) (libraries tezos-base + tezos-protocol-alpha.environment tezos-protocol-alpha tezos-protocol-plugin-alpha tezos-protocol-alpha-parameters @@ -17,6 +18,7 @@ (flags (:standard -open Tezos_base.TzPervasives + -open Tezos_protocol_environment_alpha -open Tezos_protocol_alpha -open Tezos_protocol_plugin_alpha -open Tezos_protocol_alpha_parameters diff --git a/src/proto_alpha/lib_sc_rollup/sc_rollup_messages.ml b/src/proto_alpha/lib_sc_rollup/sc_rollup_messages.ml new file mode 100644 index 000000000000..3053823ec218 --- /dev/null +++ b/src/proto_alpha/lib_sc_rollup/sc_rollup_messages.ml @@ -0,0 +1,94 @@ +(*****************************************************************************) +(* *) +(* 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 signer = Environment.Bls_signature.pk + +type signature = Environment.Bls_signature.signature + +type message = {signer : signer; contents : string} + +type t = {messages : message list; aggregated_signatures : signature} + +module Hash = + Blake2B.Make + (Base58) + (struct + let name = "sc_rollup_l2_message_hash" + + let title = "An sc_rollup L2 message" + + (* FIXME: placeholder *) + let b58check_prefix = "\017\143\169\088" (* scL2(54) *) + + let size = None + end) + +let bls_pk_encoding = + Data_encoding.( + conv_with_guard + Environment.Bls_signature.pk_to_bytes + (fun x -> + Option.to_result + ~none:"not a valid bls public key" + (Environment.Bls_signature.pk_of_bytes_opt x)) + bytes) + +let message_encoding = + let open Data_encoding in + conv + (fun {signer; contents} -> (signer, contents)) + (fun (signer, contents) -> {signer; contents}) + (obj2 (req "signer" bls_pk_encoding) (req "contents" string)) + +let signature_encoding = + let open Data_encoding in + conv_with_guard + (fun signature -> Environment.Bls_signature.signature_to_bytes signature) + (fun bytes -> + match Environment.Bls_signature.signature_of_bytes_opt bytes with + | Some x -> Ok x + | None -> Error "Not a valid bls_signature") + (Fixed.bytes Environment.Bls_signature.signature_size_in_bytes) + +let encoding = + let open Data_encoding in + conv + (fun {messages; aggregated_signatures} -> (messages, aggregated_signatures)) + (fun (messages, aggregated_signatures) -> {messages; aggregated_signatures}) + (obj2 + (req "messages" (list message_encoding)) + (req "aggregated_signature" signature_encoding)) + +let hash batch = + Hash.hash_bytes [Data_encoding.Binary.to_bytes_exn encoding batch] + +type full = {hash : Hash.t; batch : t} + +let full_encoding = + let open Data_encoding in + conv + (fun {hash; batch} -> (hash, batch)) + (fun (hash, batch) -> {hash; batch}) + (obj2 (req "hash" Hash.encoding) (req "batch" encoding)) diff --git a/src/proto_alpha/lib_sc_rollup/sc_rollup_messages.mli b/src/proto_alpha/lib_sc_rollup/sc_rollup_messages.mli new file mode 100644 index 000000000000..bdd7f531ad92 --- /dev/null +++ b/src/proto_alpha/lib_sc_rollup/sc_rollup_messages.mli @@ -0,0 +1,46 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +(** An L2 public key *) +type signer = Environment.Bls_signature.pk + +type signature = Environment.Bls_signature.signature + +(** A message is identified by its signer and its contents. The signature itself + is contained by the batch. *) +type message = {signer : signer; contents : string} + +(** Batch containing messages and the aggregated signature for these messages *) +type t = {messages : message list; aggregated_signatures : signature} + +module Hash : S.HASH + +val encoding : t Data_encoding.t + +val hash : t -> Hash.t + +type full = {hash : Hash.t; batch : t} + +val full_encoding : full Data_encoding.t -- GitLab From 9b996dea98543c4914201f6b3bbac93ffda07c3c Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Thu, 14 Apr 2022 14:48:01 +0200 Subject: [PATCH 18/22] SCORU/Node: introduce batcher internal data structure --- src/proto_alpha/bin_sc_rollup_node/batcher.ml | 65 +++++++++++++++++++ .../bin_sc_rollup_node/batcher.mli | 50 ++++++++++++++ .../bin_sc_rollup_node/batcher_event.ml | 50 ++++++++++++++ .../bin_sc_rollup_node/batcher_event.mli | 42 ++++++++++++ .../bin_sc_rollup_node/components.ml | 3 + 5 files changed, 210 insertions(+) create mode 100644 src/proto_alpha/bin_sc_rollup_node/batcher.ml create mode 100644 src/proto_alpha/bin_sc_rollup_node/batcher.mli create mode 100644 src/proto_alpha/bin_sc_rollup_node/batcher_event.ml create mode 100644 src/proto_alpha/bin_sc_rollup_node/batcher_event.mli diff --git a/src/proto_alpha/bin_sc_rollup_node/batcher.ml b/src/proto_alpha/bin_sc_rollup_node/batcher.ml new file mode 100644 index 000000000000..12e68971ddec --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/batcher.ml @@ -0,0 +1,65 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +open Sc_rollup_messages + +type batch = Sc_rollup_messages.t + +module Queue = Hash_queue.Make (Hash) (Sc_rollup_messages) + +module type S = sig + module PVM : Pvm.S + + (** Interpreter used to simulate messages before registration of messages and + publication of the batch. *) + module Interpreter : Interpreter.S with module PVM = PVM + + (** Internal type of the batcher *) + type t + + (** [init ~capacity ~maximum_batch_size ()] creates a new batcher that can hold + a maximum of [capacity] messages and produces batches of + [maximum_batch_size] messages. *) + val init : ?capacity:int -> ?maximum_batch_size:int -> unit -> t Lwt.t +end + +module Make (PVM : Pvm.S) : S with module PVM = PVM = struct + module PVM = PVM + module Interpreter = Interpreter.Make (PVM) + + type t = {queue : Queue.t; maximum_batch_size : int} + + (* FIXME: to determine *) + let default_capacity = 100 + + (* FIXME: to determine *) + let default_batch_size = 10 + + let init ?(capacity = default_capacity) + ?(maximum_batch_size = default_batch_size) () = + let open Lwt_syntax in + let+ () = Batcher_event.starting () in + {queue = Queue.create capacity; maximum_batch_size} +end diff --git a/src/proto_alpha/bin_sc_rollup_node/batcher.mli b/src/proto_alpha/bin_sc_rollup_node/batcher.mli new file mode 100644 index 000000000000..429f2fd787d2 --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/batcher.mli @@ -0,0 +1,50 @@ +(*****************************************************************************) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +(** The rollup node can accumulate layer 2 messages, batch and send them to the + layer 1. + + The batcher queue is contained in memory. +*) + +type batch = Sc_rollup_messages.t + +module type S = sig + module PVM : Pvm.S + + (** Interpreter used to simulate messages before registration of messages and + publication of the batch. *) + module Interpreter : Interpreter.S with module PVM = PVM + + (** Internal type of the batcher *) + type t + + (** [init ~capacity ~maximum_batch_size ()] creates a new batcher that can hold + a maximum of [capacity] messages and produces batches of + [maximum_batch_size] messages. *) + val init : ?capacity:int -> ?maximum_batch_size:int -> unit -> t Lwt.t +end + +module Make (PVM : Pvm.S) : S with module PVM = PVM diff --git a/src/proto_alpha/bin_sc_rollup_node/batcher_event.ml b/src/proto_alpha/bin_sc_rollup_node/batcher_event.ml new file mode 100644 index 000000000000..9e2ab4a87816 --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/batcher_event.ml @@ -0,0 +1,50 @@ +(*****************************************************************************) +(* *) +(* 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 Simple = struct + include Internal_event.Simple + + let section = ["sc_rollup_node"; "batcher"] + + let starting = + declare_0 + ~section + ~name:"sc_rollup_node_batcher_starting" + ~msg:"Starting batcher of the smart contract rollup node" + ~level:Notice + () + + let stopping = + declare_0 + ~section + ~name:"sc_rollup_node_batcher_stopping" + ~msg:"Stopping batcher of the smart contract rollup node" + ~level:Notice + () +end + +let starting = Simple.(emit starting) + +let stopping = Simple.(emit stopping) diff --git a/src/proto_alpha/bin_sc_rollup_node/batcher_event.mli b/src/proto_alpha/bin_sc_rollup_node/batcher_event.mli new file mode 100644 index 000000000000..19488e5f86af --- /dev/null +++ b/src/proto_alpha/bin_sc_rollup_node/batcher_event.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. *) +(* *) +(*****************************************************************************) + +module Simple : sig + type 'a event := 'a Internal_event.Simple.t + + val section : string list + + (** Event for batcher initialization. *) + val starting : unit event + + (** Event for batcher interruption. *) + val stopping : unit event +end + +(** [starting ()] triggers a [Simple.starting] event. *) +val starting : unit -> unit Lwt.t + +(** [stopping ()] triggers a [Simple.stopping] event. *) +val stopping : unit -> unit Lwt.t diff --git a/src/proto_alpha/bin_sc_rollup_node/components.ml b/src/proto_alpha/bin_sc_rollup_node/components.ml index e6cb34157926..a43cc8bab7e2 100644 --- a/src/proto_alpha/bin_sc_rollup_node/components.ml +++ b/src/proto_alpha/bin_sc_rollup_node/components.ml @@ -30,6 +30,8 @@ module type S = sig module Commitment : Commitment.S with module PVM = PVM + module Batcher : Batcher.S with module PVM = PVM + module RPC_server : RPC_server.S with module PVM = PVM end @@ -37,6 +39,7 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct module PVM = PVM module Interpreter = Interpreter.Make (PVM) module Commitment = Commitment.Make (PVM) + module Batcher = Batcher.Make (PVM) module RPC_server = RPC_server.Make (PVM) end -- GitLab From 28d383b9eb779aed7d6012704753427f41940875 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Thu, 14 Apr 2022 14:52:18 +0200 Subject: [PATCH 19/22] SCORU/Node: register message in batcher queue --- .../bin_sc_rollup_node/RPC_server.ml | 41 ++++++++-- src/proto_alpha/bin_sc_rollup_node/batcher.ml | 75 +++++++++++++++++++ .../bin_sc_rollup_node/batcher.mli | 20 ++++- .../bin_sc_rollup_node/batcher_event.ml | 10 +++ .../bin_sc_rollup_node/batcher_event.mli | 7 ++ .../bin_sc_rollup_node/components.ml | 5 +- src/proto_alpha/bin_sc_rollup_node/daemon.ml | 3 +- .../lib_sc_rollup/sc_rollup_services.ml | 16 ++++ 8 files changed, 167 insertions(+), 10 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/RPC_server.ml b/src/proto_alpha/bin_sc_rollup_node/RPC_server.ml index 0e7bf48aa02c..47218ea7238a 100644 --- a/src/proto_alpha/bin_sc_rollup_node/RPC_server.ml +++ b/src/proto_alpha/bin_sc_rollup_node/RPC_server.ml @@ -119,21 +119,30 @@ end module type S = sig module PVM : Pvm.S + module Batcher : Batcher.S with module PVM = PVM + val shutdown : RPC_server.server -> unit Lwt.t val register : - Node_context.t -> PVM.context -> Configuration.t -> unit RPC_directory.t + Node_context.t -> + PVM.context -> + Configuration.t -> + Batcher.t -> + unit RPC_directory.t val start : Node_context.t -> PVM.context -> Configuration.t -> + Batcher.t -> RPC_server.server tzresult Lwt.t end -module Make (PVM : Pvm.S) : S with module PVM = PVM = struct +module Make (PVM : Pvm.S) (Batcher : Batcher.S with module PVM = PVM) : + S with module PVM = PVM and module Batcher = Batcher = struct include Common module PVM = PVM + module Batcher = Batcher let register_current_total_ticks store dir = RPC_directory.register0 @@ -191,7 +200,27 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct let*! status = PVM.get_status state in return (PVM.string_of_status status)) - let register node_ctxt store configuration = + let register_inject_messages node_ctxt store batcher dir = + RPC_directory.register + dir + (Sc_rollup_services.inject_message ()) + (fun () () message -> + let open Lwt_result_syntax in + let+ (hash, {state_hash; status; ticks_diff}) = + Batcher.register_message node_ctxt store batcher message + in + ( hash, + Protocol.Alpha_context.Sc_rollup.State_hash.to_b58check state_hash, + PVM.string_of_status status, + ticks_diff )) + + let register_batcher_messages batcher dir = + RPC_directory.register + dir + (Sc_rollup_services.batcher_messages ()) + (fun () () () -> Lwt.return_ok @@ Batcher.to_list batcher) + + let register node_ctxt store configuration batcher = RPC_directory.empty |> register_sc_rollup_address configuration |> register_current_tezos_head store @@ -203,7 +232,9 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct |> register_current_status store |> register_last_stored_commitment store |> register_last_published_commitment store + |> register_inject_messages node_ctxt store batcher + |> register_batcher_messages batcher - let start node_ctxt store configuration = - Common.start configuration (register node_ctxt store configuration) + let start node_ctxt store configuration batcher = + Common.start configuration (register node_ctxt store configuration batcher) end diff --git a/src/proto_alpha/bin_sc_rollup_node/batcher.ml b/src/proto_alpha/bin_sc_rollup_node/batcher.ml index 12e68971ddec..7fdde29318c5 100644 --- a/src/proto_alpha/bin_sc_rollup_node/batcher.ml +++ b/src/proto_alpha/bin_sc_rollup_node/batcher.ml @@ -29,6 +29,38 @@ type batch = Sc_rollup_messages.t module Queue = Hash_queue.Make (Hash) (Sc_rollup_messages) +type error += Full_queue | Signature_mismatch + +let () = + register_error_kind + `Temporary + ~id:"sc_rollup.node.full_queue" + ~title:"The node's message queue is full" + ~description: + "An message could not be registered since the queue of the node's \ + batcher is full." + ~pp:(fun ppf () -> + Format.fprintf + ppf + "An message could not be registered since the queue of the node's \ + batcher is full.") + Data_encoding.unit + (function Full_queue -> Some () | _ -> None) + (fun () -> Full_queue) ; + register_error_kind + `Temporary + ~id:"sc_rollup.node.signature_mismatch" + ~title:"A signature is invalid" + ~description: + "A signature does not correspond to its associated message and signer." + ~pp:(fun ppf () -> + Format.fprintf + ppf + "A signature does not correspond to its associated message and signer.") + Data_encoding.unit + (function Signature_mismatch -> Some () | _ -> None) + (fun () -> Signature_mismatch) + module type S = sig module PVM : Pvm.S @@ -43,6 +75,20 @@ module type S = sig a maximum of [capacity] messages and produces batches of [maximum_batch_size] messages. *) val init : ?capacity:int -> ?maximum_batch_size:int -> unit -> t Lwt.t + + (** [register_message node_ctxt store batcher message] registers a given + message into the batcher queue. Fails with [Full_queue] if the queue holds + as much message as its capacity. *) + val register_message : + Node_context.t -> + Store.t -> + t -> + batch -> + (Sc_rollup_messages.Hash.t * Interpreter.simulation_result) tzresult Lwt.t + + (** [to_list batcher] returns the list of messages held by the batcher from + oldest to newest. *) + val to_list : t -> Sc_rollup_messages.full list end module Make (PVM : Pvm.S) : S with module PVM = PVM = struct @@ -62,4 +108,33 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct let open Lwt_syntax in let+ () = Batcher_event.starting () in {queue = Queue.create capacity; maximum_batch_size} + + let check_message batch = + let messages = + List.map + (fun {signer; contents} -> (signer, Bytes.of_string contents)) + batch.messages + in + Environment.Bls_signature.aggregate_verify + messages + batch.aggregated_signatures + + let register_message node_ctxt store batcher batch = + let open Lwt_result_syntax in + if + Queue.length batcher.queue + >= Queue.capacity batcher.queue + List.length batch.messages + then tzfail Full_queue + else if not (check_message batch) then tzfail Signature_mismatch + else + let messages = List.map (fun {contents; _} -> contents) batch.messages in + let* evaluation_result = Interpreter.simulate node_ctxt store messages in + let hash = hash batch in + Queue.replace batcher.queue hash batch ; + let*! () = Batcher_event.register_messages ~hash in + return (hash, evaluation_result) + + let to_list batcher = + Queue.fold (fun hash batch acc -> {hash; batch} :: acc) batcher.queue [] + |> List.rev end diff --git a/src/proto_alpha/bin_sc_rollup_node/batcher.mli b/src/proto_alpha/bin_sc_rollup_node/batcher.mli index 429f2fd787d2..07f213798395 100644 --- a/src/proto_alpha/bin_sc_rollup_node/batcher.mli +++ b/src/proto_alpha/bin_sc_rollup_node/batcher.mli @@ -29,6 +29,8 @@ The batcher queue is contained in memory. *) +type error += Full_queue | Signature_mismatch + type batch = Sc_rollup_messages.t module type S = sig @@ -42,9 +44,23 @@ module type S = sig type t (** [init ~capacity ~maximum_batch_size ()] creates a new batcher that can hold - a maximum of [capacity] messages and produces batches of - [maximum_batch_size] messages. *) + a maximum of [capacity] messages and produces batches of + [maximum_batch_size] messages. *) val init : ?capacity:int -> ?maximum_batch_size:int -> unit -> t Lwt.t + + (** [register_message node_ctxt store batcher message] registers a given + message into the batcher queue. Fails with [Full_queue] if the queue holds + as much message as its capacity. *) + val register_message : + Node_context.t -> + Store.t -> + t -> + batch -> + (Sc_rollup_messages.Hash.t * Interpreter.simulation_result) tzresult Lwt.t + + (** [to_list batcher] returns the list of messages held by the batcher from + oldest to newest. *) + val to_list : t -> Sc_rollup_messages.full list end module Make (PVM : Pvm.S) : S with module PVM = PVM diff --git a/src/proto_alpha/bin_sc_rollup_node/batcher_event.ml b/src/proto_alpha/bin_sc_rollup_node/batcher_event.ml index 9e2ab4a87816..985297371d04 100644 --- a/src/proto_alpha/bin_sc_rollup_node/batcher_event.ml +++ b/src/proto_alpha/bin_sc_rollup_node/batcher_event.ml @@ -43,8 +43,18 @@ module Simple = struct ~msg:"Stopping batcher of the smart contract rollup node" ~level:Notice () + + let register_messages = + declare_1 + ~section + ~name:"sc_rollup_node_register_messages" + ~msg:"adding {msg_hash} to queue" + ~level:Notice + ("msg_hash", Sc_rollup_messages.Hash.encoding) end let starting = Simple.(emit starting) let stopping = Simple.(emit stopping) + +let register_messages ~hash = Simple.(emit register_messages hash) diff --git a/src/proto_alpha/bin_sc_rollup_node/batcher_event.mli b/src/proto_alpha/bin_sc_rollup_node/batcher_event.mli index 19488e5f86af..1b050be077bb 100644 --- a/src/proto_alpha/bin_sc_rollup_node/batcher_event.mli +++ b/src/proto_alpha/bin_sc_rollup_node/batcher_event.mli @@ -33,6 +33,10 @@ module Simple : sig (** Event for batcher interruption. *) val stopping : unit event + + (** Event triggered when the batcher receives a message, with the L2 operation + hash. *) + val register_messages : Sc_rollup_messages.Hash.t event end (** [starting ()] triggers a [Simple.starting] event. *) @@ -40,3 +44,6 @@ val starting : unit -> unit Lwt.t (** [stopping ()] triggers a [Simple.stopping] event. *) val stopping : unit -> unit Lwt.t + +(** [register_messages ~hash] triggers a [Simple.register_messages] event. *) +val register_messages : hash:Sc_rollup_messages.Hash.t -> unit Lwt.t diff --git a/src/proto_alpha/bin_sc_rollup_node/components.ml b/src/proto_alpha/bin_sc_rollup_node/components.ml index a43cc8bab7e2..86fa41923ce6 100644 --- a/src/proto_alpha/bin_sc_rollup_node/components.ml +++ b/src/proto_alpha/bin_sc_rollup_node/components.ml @@ -32,7 +32,8 @@ module type S = sig module Batcher : Batcher.S with module PVM = PVM - module RPC_server : RPC_server.S with module PVM = PVM + module RPC_server : + RPC_server.S with module PVM = PVM and module Batcher = Batcher end module Make (PVM : Pvm.S) : S with module PVM = PVM = struct @@ -40,7 +41,7 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct module Interpreter = Interpreter.Make (PVM) module Commitment = Commitment.Make (PVM) module Batcher = Batcher.Make (PVM) - module RPC_server = RPC_server.Make (PVM) + module RPC_server = RPC_server.Make (PVM) (Batcher) end let pvm_of_kind : Protocol.Alpha_context.Sc_rollup.Kind.t -> (module Pvm.S) = diff --git a/src/proto_alpha/bin_sc_rollup_node/daemon.ml b/src/proto_alpha/bin_sc_rollup_node/daemon.ml index 7b2ec24195c4..e1a0cca3e5c7 100644 --- a/src/proto_alpha/bin_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/bin_sc_rollup_node/daemon.ml @@ -195,8 +195,9 @@ module Make (PVM : Pvm.S) = struct let run node_ctxt configuration store = let open Lwt_result_syntax in let start () = + let*! batcher = Components.Batcher.init () in let* rpc_server = - Components.RPC_server.start node_ctxt store configuration + Components.RPC_server.start node_ctxt store configuration batcher in let* l1_ctxt = Layer1.start configuration node_ctxt store in let*! () = Inbox.start () in diff --git a/src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml b/src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml index 2ce16171a584..acfe4e4db47e 100644 --- a/src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml +++ b/src/proto_alpha/lib_sc_rollup/sc_rollup_services.ml @@ -109,3 +109,19 @@ let current_status () = ~query:RPC_query.empty ~output:Data_encoding.string RPC_path.(open_root / "status") + +let inject_message () = + RPC_service.post_service + ~description:"Inject an L2 message" + ~query:RPC_query.empty + ~input:Sc_rollup_messages.encoding + ~output: + Data_encoding.(tup4 Sc_rollup_messages.Hash.encoding string string z) + RPC_path.(open_root / "injection" / "messages") + +let batcher_messages () = + RPC_service.get_service + ~description:"Current messages waiting in the batcher" + ~query:RPC_query.empty + ~output:(Data_encoding.list Sc_rollup_messages.full_encoding) + RPC_path.(open_root / "batcher" / "messages") -- GitLab From 1bcadc3dfba5f0445accb1996b2d985af973aad7 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Thu, 14 Apr 2022 14:53:19 +0200 Subject: [PATCH 20/22] SCORU/Node: batch publication --- src/proto_alpha/bin_sc_rollup_node/batcher.ml | 117 +++++++++++++++++- .../bin_sc_rollup_node/batcher.mli | 18 ++- .../bin_sc_rollup_node/batcher_event.ml | 11 ++ .../bin_sc_rollup_node/batcher_event.mli | 8 ++ src/proto_alpha/bin_sc_rollup_node/daemon.ml | 26 ++-- 5 files changed, 167 insertions(+), 13 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/batcher.ml b/src/proto_alpha/bin_sc_rollup_node/batcher.ml index 7fdde29318c5..0925e4ebb959 100644 --- a/src/proto_alpha/bin_sc_rollup_node/batcher.ml +++ b/src/proto_alpha/bin_sc_rollup_node/batcher.ml @@ -23,6 +23,7 @@ (* *) (*****************************************************************************) +open Protocol.Alpha_context open Sc_rollup_messages type batch = Sc_rollup_messages.t @@ -64,8 +65,6 @@ let () = module type S = sig module PVM : Pvm.S - (** Interpreter used to simulate messages before registration of messages and - publication of the batch. *) module Interpreter : Interpreter.S with module PVM = PVM (** Internal type of the batcher *) @@ -89,6 +88,21 @@ module type S = sig (** [to_list batcher] returns the list of messages held by the batcher from oldest to newest. *) val to_list : t -> Sc_rollup_messages.full list + + (** [take_and_publish node_ctxt batcher] takes at most [maximum_batch_size] from + the batcher, build the corresponding batch and publish it to the L1 using + the operator signature. Does nothing if the queue is empty. *) + val take_and_publish : + Node_context.t -> + Store.t -> + t -> + (Sc_rollup_messages.Hash.t list * Interpreter.simulation_result) option + tzresult + Lwt.t + + (** [process_head node_ctxt batcher] publishes a new batch each time a + new head is pushed on L1. *) + val process_head : Node_context.t -> Store.t -> t -> unit tzresult Lwt.t end module Make (PVM : Pvm.S) : S with module PVM = PVM = struct @@ -100,7 +114,8 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct (* FIXME: to determine *) let default_capacity = 100 - (* FIXME: to determine *) + (* FIXME: to determine, a batch size should not probably in terms of messages + but effective size of the messages *) let default_batch_size = 10 let init ?(capacity = default_capacity) @@ -137,4 +152,100 @@ module Make (PVM : Pvm.S) : S with module PVM = PVM = struct let to_list batcher = Queue.fold (fun hash batch acc -> {hash; batch} :: acc) batcher.queue [] |> List.rev + + let aggregate aggregated signature = + match aggregated with + | Some aggregated -> + Environment.Bls_signature.aggregate_signature_opt + [aggregated; signature] + | None -> Some signature + + let build_batch t = + (* Allows exiting the fold early *) + let exception + Stop of (Sc_rollup_messages.message list * signature option * Hash.t list) + in + let (rev_messages, aggregated_signature, hashes) = + try + let (msgs, sgs, hs, _) = + Queue.fold + (fun hash + {messages; aggregated_signatures} + (rev_messages, prev_aggregation, hashes, max_remaining) -> + let messages_number = List.length messages in + if messages_number > max_remaining then + raise (Stop (rev_messages, prev_aggregation, hashes)) + else + (* Signatures are aggregated at this point, as such if one + signature makes the batch invalid, we can drop it. This works + thanks to the associativity of the signature aggregation. *) + let aggregation_result = + aggregate prev_aggregation aggregated_signatures + in + (* If the aggregation is invalid, we can continue and skip the + current batch in the queue. *) + let (messages, aggregation) = + Option.fold + ~some:(fun s -> + (List.rev_append messages rev_messages, Some s)) + ~none:(rev_messages, prev_aggregation) + aggregation_result + in + ( messages, + aggregation, + hash :: hashes, + (* The hash is always added to remove the message from + the queue whether it is valid or not *) + max_remaining - messages_number )) + t.queue + ([], None, [], t.maximum_batch_size) + in + (msgs, sgs, hs) + with Stop res -> res + in + ( Option.map + (fun aggregated_signatures -> + {messages = List.rev rev_messages; aggregated_signatures}) + aggregated_signature, + hashes ) + + let publish node_ctxt store messages = + let open Lwt_result_syntax in + let* evaluation_result = Interpreter.simulate node_ctxt store messages in + let operation = + Sc_rollup_add_messages {rollup = node_ctxt.rollup_address; messages} + in + let+ () = + Injector.add_pending_operation ~source:node_ctxt.operator operation + in + evaluation_result + + (* FIXME: to remove once the protocol accepts batches with signatures *) + let temporary_extract_messages batch = + List.map (fun {contents; _} -> contents) batch.messages + + let remove_sent_messages batcher hashes = + List.iter (Queue.remove batcher.queue) hashes + + let take_and_publish node_ctxt store batcher = + let open Lwt_result_syntax in + (* FIXME: should we send batched manager operations if there remains messages in the queue? *) + let (batch, hashes) = build_batch batcher in + match batch with + | None -> return_none + | Some batch -> + (* FIXME: The protocol does not expect signed messages yet *) + let* evaluation_result = + publish node_ctxt store (temporary_extract_messages batch) + in + let*! () = + Batcher_event.publish_sc_rollup_messages ~msgs:(List.length hashes) + in + return_some (hashes, evaluation_result) + + let process_head node_ctxt store batcher = + let open Lwt_result_syntax in + let* res = take_and_publish node_ctxt store batcher in + Option.iter (fun (hashes, _) -> remove_sent_messages batcher hashes) res ; + return_unit end diff --git a/src/proto_alpha/bin_sc_rollup_node/batcher.mli b/src/proto_alpha/bin_sc_rollup_node/batcher.mli index 07f213798395..0255cea2d0a9 100644 --- a/src/proto_alpha/bin_sc_rollup_node/batcher.mli +++ b/src/proto_alpha/bin_sc_rollup_node/batcher.mli @@ -36,8 +36,6 @@ type batch = Sc_rollup_messages.t module type S = sig module PVM : Pvm.S - (** Interpreter used to simulate messages before registration of messages and - publication of the batch. *) module Interpreter : Interpreter.S with module PVM = PVM (** Internal type of the batcher *) @@ -61,6 +59,22 @@ module type S = sig (** [to_list batcher] returns the list of messages held by the batcher from oldest to newest. *) val to_list : t -> Sc_rollup_messages.full list + + (** [take_and_publish node_ctxt store batcher] takes at most + [maximum_batch_size] from the batcher, build the corresponding batch and + publish it to the L1 using the operator signature. Does nothing if the + queue is empty. *) + val take_and_publish : + Node_context.t -> + Store.t -> + t -> + (Sc_rollup_messages.Hash.t list * Interpreter.simulation_result) option + tzresult + Lwt.t + + (** [process_head node_ctxt store batcher] publishes a new batch each + time a new head is pushed on L1. *) + val process_head : Node_context.t -> Store.t -> t -> unit tzresult Lwt.t end module Make (PVM : Pvm.S) : S with module PVM = PVM diff --git a/src/proto_alpha/bin_sc_rollup_node/batcher_event.ml b/src/proto_alpha/bin_sc_rollup_node/batcher_event.ml index 985297371d04..4efaf33cdc79 100644 --- a/src/proto_alpha/bin_sc_rollup_node/batcher_event.ml +++ b/src/proto_alpha/bin_sc_rollup_node/batcher_event.ml @@ -51,6 +51,14 @@ module Simple = struct ~msg:"adding {msg_hash} to queue" ~level:Notice ("msg_hash", Sc_rollup_messages.Hash.encoding) + + let publish_sc_rollup_messages = + declare_1 + ~section + ~name:"publish" + ~msg:"layer 1 operation with {msgs} messages added for injection" + ~level:Notice + ("msgs", Data_encoding.int31) end let starting = Simple.(emit starting) @@ -58,3 +66,6 @@ let starting = Simple.(emit starting) let stopping = Simple.(emit stopping) let register_messages ~hash = Simple.(emit register_messages hash) + +let publish_sc_rollup_messages ~msgs = + Simple.(emit publish_sc_rollup_messages msgs) diff --git a/src/proto_alpha/bin_sc_rollup_node/batcher_event.mli b/src/proto_alpha/bin_sc_rollup_node/batcher_event.mli index 1b050be077bb..f96ee5bea474 100644 --- a/src/proto_alpha/bin_sc_rollup_node/batcher_event.mli +++ b/src/proto_alpha/bin_sc_rollup_node/batcher_event.mli @@ -37,6 +37,10 @@ module Simple : sig (** Event triggered when the batcher receives a message, with the L2 operation hash. *) val register_messages : Sc_rollup_messages.Hash.t event + + (** Event triggered when the batcher sends a message to the L1 node, with the + operation hash. *) + val publish_sc_rollup_messages : int event end (** [starting ()] triggers a [Simple.starting] event. *) @@ -47,3 +51,7 @@ val stopping : unit -> unit Lwt.t (** [register_messages ~hash] triggers a [Simple.register_messages] event. *) val register_messages : hash:Sc_rollup_messages.Hash.t -> unit Lwt.t + +(** [publish_sc_rollup_messages ~hash] triggers a + [Simple.publish_sc_rollup_messages] event. *) +val publish_sc_rollup_messages : msgs:int -> unit Lwt.t diff --git a/src/proto_alpha/bin_sc_rollup_node/daemon.ml b/src/proto_alpha/bin_sc_rollup_node/daemon.ml index e1a0cca3e5c7..b08425c1c70f 100644 --- a/src/proto_alpha/bin_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/bin_sc_rollup_node/daemon.ml @@ -63,7 +63,7 @@ let categorise_heads (node_ctxt : Node_context.t) old_heads new_heads = module Make (PVM : Pvm.S) = struct module Components = Components.Make (PVM) - let process_head node_ctxt store head_state = + let process_head node_ctxt store batcher head_state = (* Because we keep track of finalized heads using transaction finality time, rather than block finality time, it is possible that heads with the same level are processed as finalized. Individual modules that process heads @@ -79,7 +79,8 @@ module Make (PVM : Pvm.S) = struct let* () = Inbox.process_head node_ctxt store head in (* Avoid storing and publishing commitments if the head is not final *) (* Avoid triggering the pvm execution if this has been done before for this head *) - Components.Interpreter.process_head node_ctxt store head + let* () = Components.Interpreter.process_head node_ctxt store head in + Components.Batcher.process_head node_ctxt store batcher in let* () = if finalized then Components.Commitment.process_head node_ctxt store head @@ -115,7 +116,8 @@ module Make (PVM : Pvm.S) = struct needs to be returned to be included as the rollup node started tracking a new branch. *) - let on_layer_1_chain_event l1_ctxt node_ctxt store chain_event old_heads = + let on_layer_1_chain_event l1_ctxt node_ctxt store batcher chain_event + old_heads = let open Lwt_result_syntax in let open Layer1 in let* () = notify_injector l1_ctxt store chain_event in @@ -128,7 +130,9 @@ module Make (PVM : Pvm.S) = struct old_heads (intermediate_heads @ [new_head]) in - let* () = List.iter_es (process_head node_ctxt store) head_states in + let* () = + List.iter_es (process_head node_ctxt store batcher) head_states + in (* Return new_head to be processed as finalized head if the next chain event is of type SameBranch. *) @@ -161,6 +165,7 @@ module Make (PVM : Pvm.S) = struct process_head node_ctxt store + batcher {head; finalized = true; seen_before = true}) final_heads in @@ -180,9 +185,9 @@ module Make (PVM : Pvm.S) = struct in go [] - let daemonize node_ctxt store (l1_ctxt : Layer1.t) = + let daemonize node_ctxt store (l1_ctxt : Layer1.t) batcher = Lwt.no_cancel @@ iter_stream l1_ctxt.events - @@ on_layer_1_chain_event l1_ctxt node_ctxt store + @@ on_layer_1_chain_event l1_ctxt node_ctxt store batcher let install_finalizer store rpc_server = let open Lwt_syntax in @@ -206,7 +211,12 @@ module Make (PVM : Pvm.S) = struct Injector.init node_ctxt.cctxt node_ctxt - ~signers:[(node_ctxt.operator, `Each_block, [Injector.Publish])] + ~signers: + [ + ( node_ctxt.operator, + `Each_block, + [Injector.Publish; Injector.Add_messages] ); + ] in let _ = install_finalizer store rpc_server in @@ -215,7 +225,7 @@ module Make (PVM : Pvm.S) = struct ~rpc_addr:configuration.rpc_addr ~rpc_port:configuration.rpc_port in - daemonize node_ctxt store l1_ctxt + daemonize node_ctxt store l1_ctxt batcher in start () end -- GitLab From 2d5de887365ef9838719a3b281f23e388b85189e Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Thu, 7 Apr 2022 17:32:28 +0200 Subject: [PATCH 21/22] SCORU/Client: message publication --- src/proto_alpha/bin_sc_rollup_client/RPC.ml | 3 + .../bin_sc_rollup_client/commands.ml | 56 +++++++++++++++++++ .../bin_sc_rollup_client/configuration.ml | 11 ++++ .../bin_sc_rollup_client/configuration.mli | 9 +++ 4 files changed, 79 insertions(+) diff --git a/src/proto_alpha/bin_sc_rollup_client/RPC.ml b/src/proto_alpha/bin_sc_rollup_client/RPC.ml index b61d72d2f07f..352deae23a18 100644 --- a/src/proto_alpha/bin_sc_rollup_client/RPC.ml +++ b/src/proto_alpha/bin_sc_rollup_client/RPC.ml @@ -29,3 +29,6 @@ let call (cctxt : #sc_client_context) = cctxt#call_service let get_sc_rollup_addresses_command cctxt = call cctxt (Sc_rollup_services.sc_rollup_address ()) () () () + +let publish_message cctxt (message : Sc_rollup_messages.t) = + call cctxt (Sc_rollup_services.inject_message ()) () () message diff --git a/src/proto_alpha/bin_sc_rollup_client/commands.ml b/src/proto_alpha/bin_sc_rollup_client/commands.ml index 4750ef242393..d0c60f8fd89d 100644 --- a/src/proto_alpha/bin_sc_rollup_client/commands.ml +++ b/src/proto_alpha/bin_sc_rollup_client/commands.ml @@ -26,6 +26,30 @@ open Clic open Protocol.Alpha_context +type tz4_account = + Aggregate_signature.public_key_hash + * Aggregate_signature.public_key + * Aggregate_signature.secret_key + +let sign_message (account : tz4_account) message = + let (_, Aggregate_signature.Bls12_381 pk, Aggregate_signature.Bls12_381 sk) = + account + in + (* FIXME: encoding - decoding to go from one type to another is not really + efficient. Any other solution? *) + let pk = + Data_encoding.Binary.to_bytes_exn Bls.Public_key.encoding pk + |> Bls12_381.Signature.MinPk.pk_of_bytes_exn + in + let sk = + Data_encoding.Binary.to_bytes_exn Bls.Secret_key.encoding sk + |> Bls12_381.Signature.sk_of_bytes_exn + in + let signature = + Bls12_381.Signature.MinPk.Aug.sign sk (Bytes.of_string message) + in + (Sc_rollup_messages.{signer = pk; contents = message}, signature) + let get_sc_rollup_addresses_command () = command ~desc: @@ -36,6 +60,37 @@ let get_sc_rollup_addresses_command () = RPC.get_sc_rollup_addresses_command cctxt >>=? fun addr -> cctxt#message "@[%a@]" Sc_rollup.Address.pp addr >>= fun () -> return_unit) +let publish_message () = + command + ~desc:"Publish a message to the rollup node" + no_options + (prefixes ["publish"; "message"] + @@ param ~name:"message" ~desc:"" (parameter (fun _ s -> return s)) + @@ stop) + (fun () message (cctxt : #Configuration.sc_client_context) -> + let open Lwt_result_syntax in + let (message, signature) = + sign_message Configuration.dummy_bls_account message + in + let message = + Sc_rollup_messages. + {messages = [message]; aggregated_signatures = signature} + in + let* (hash, state_hash, status, ticks_diff) = + RPC.publish_message cctxt message + in + let*! () = + cctxt#message + "@[Operation hash: %a\nState hash: %s\nStatus: %s\nTicks diff: %a@]" + Sc_rollup_messages.Hash.pp + hash + state_hash + status + Z.pp_print + ticks_diff + in + return_unit) + (** [display_answer cctxt answer] prints an RPC answer. *) let display_answer (cctxt : #Configuration.sc_client_context) : RPC_context.generic_call_result -> unit Lwt.t = function @@ -143,6 +198,7 @@ end let all () = [ get_sc_rollup_addresses_command (); + publish_message (); rpc_get_command; Keys.generate_keys (); Keys.list_keys (); diff --git a/src/proto_alpha/bin_sc_rollup_client/configuration.ml b/src/proto_alpha/bin_sc_rollup_client/configuration.ml index e5cd10551cee..73a9d64835ff 100644 --- a/src/proto_alpha/bin_sc_rollup_client/configuration.ml +++ b/src/proto_alpha/bin_sc_rollup_client/configuration.ml @@ -111,3 +111,14 @@ let make_unix_client_context {base_dir; endpoint} = {Tezos_rpc_http_client_unix.RPC_client_unix.default_config with endpoint} in new unix_sc_client_context ~base_dir ~rpc_config ~password_filename:None + +(* taken from `src/lib_crypto/test/key_encoding_vectors`, first seed from + bls12_381_key_encodings *) +let dummy_bls_account = + let seed = + Cstruct.( + to_bytes + (of_hex + "72fb8a8eec04f982f2da16b99b6a04fe267c9354c3423939b4b8bf956d6ebb90")) + in + Aggregate_signature.generate_key ~seed () diff --git a/src/proto_alpha/bin_sc_rollup_client/configuration.mli b/src/proto_alpha/bin_sc_rollup_client/configuration.mli index 9aebfc3ba4b7..f5fcfb65ecbd 100644 --- a/src/proto_alpha/bin_sc_rollup_client/configuration.mli +++ b/src/proto_alpha/bin_sc_rollup_client/configuration.mli @@ -62,3 +62,12 @@ class unix_sc_client_context : (** [make_unix_client_context config] generates a unix_sc_client_context from the client configuration. *) val make_unix_client_context : t -> unix_sc_client_context + +(** TODO: remove once https://gitlab.com/tezos/tezos/-/merge_requests/4741 has + been merged, only used for tests. The keys are extracted from + `src/lib_crypto/test/key_encoding_vectors`, first seed from + bls12_381_key_encodings *) +val dummy_bls_account : + Aggregate_signature.Public_key_hash.t + * Aggregate_signature.Public_key.t + * Aggregate_signature.Secret_key.t -- GitLab From aafc9506908469a11797cab8bc843f22276db4e9 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Fri, 8 Apr 2022 12:09:30 +0200 Subject: [PATCH 22/22] Tezt/Scoru: add tests for message publication --- .../sc_rollup_client_messages.out | 31 +++++ .../sc_rollup_node_publish_messages.out | 31 +++++ ...ode_publish_messages_above_batch_limit.out | 31 +++++ tezt/lib_tezos/sc_rollup_client.ml | 23 ++++ tezt/lib_tezos/sc_rollup_client.mli | 12 ++ tezt/tests/sc_rollup.ml | 106 +++++++++++++++++- 6 files changed, 233 insertions(+), 1 deletion(-) create mode 100644 tezt/_regressions/sc_rollup_client_messages.out create mode 100644 tezt/_regressions/sc_rollup_node_publish_messages.out create mode 100644 tezt/_regressions/sc_rollup_node_publish_messages_above_batch_limit.out diff --git a/tezt/_regressions/sc_rollup_client_messages.out b/tezt/_regressions/sc_rollup_client_messages.out new file mode 100644 index 000000000000..bdf6a0bfd45f --- /dev/null +++ b/tezt/_regressions/sc_rollup_client_messages.out @@ -0,0 +1,31 @@ +sc_rollup_client_messages.out + +./tezos-client --wait none originate sc rollup from '[PUBLIC_KEY_HASH]' of kind arith booting with --burn-cap 9999999 +Node is bootstrapped. +Estimated gas: 1600.648 units (will add 100 for safety) +Estimated storage: 6522 bytes added (will add 20 for safety) +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000402 + Expected counter: 1 + Gas limit: 1701 + Storage limit: 6542 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000402 + payload fees(the block proposer) ....... +ꜩ0.000402 + Originate smart contract rollup of kind arith with boot sector '' + This smart contract rollup origination was successfully applied + Consumed gas: 1600.648 + Storage size: 6522 bytes + Address: [SC_ROLLUP_HASH] + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ1.6305 + storage fees ........................... +ꜩ1.6305 + diff --git a/tezt/_regressions/sc_rollup_node_publish_messages.out b/tezt/_regressions/sc_rollup_node_publish_messages.out new file mode 100644 index 000000000000..32d36c59d60f --- /dev/null +++ b/tezt/_regressions/sc_rollup_node_publish_messages.out @@ -0,0 +1,31 @@ +sc_rollup_node_publish_messages.out + +./tezos-client --wait none originate sc rollup from '[PUBLIC_KEY_HASH]' of kind arith booting with --burn-cap 9999999 +Node is bootstrapped. +Estimated gas: 1600.648 units (will add 100 for safety) +Estimated storage: 6522 bytes added (will add 20 for safety) +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000402 + Expected counter: 1 + Gas limit: 1701 + Storage limit: 6542 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000402 + payload fees(the block proposer) ....... +ꜩ0.000402 + Originate smart contract rollup of kind arith with boot sector '' + This smart contract rollup origination was successfully applied + Consumed gas: 1600.648 + Storage size: 6522 bytes + Address: [SC_ROLLUP_HASH] + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ1.6305 + storage fees ........................... +ꜩ1.6305 + diff --git a/tezt/_regressions/sc_rollup_node_publish_messages_above_batch_limit.out b/tezt/_regressions/sc_rollup_node_publish_messages_above_batch_limit.out new file mode 100644 index 000000000000..254400ef7c2d --- /dev/null +++ b/tezt/_regressions/sc_rollup_node_publish_messages_above_batch_limit.out @@ -0,0 +1,31 @@ +sc_rollup_node_publish_messages_above_batch_limit.out + +./tezos-client --wait none originate sc rollup from '[PUBLIC_KEY_HASH]' of kind arith booting with --burn-cap 9999999 +Node is bootstrapped. +Estimated gas: 1600.648 units (will add 100 for safety) +Estimated storage: 6522 bytes added (will add 20 for safety) +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000402 + Expected counter: 1 + Gas limit: 1701 + Storage limit: 6542 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000402 + payload fees(the block proposer) ....... +ꜩ0.000402 + Originate smart contract rollup of kind arith with boot sector '' + This smart contract rollup origination was successfully applied + Consumed gas: 1600.648 + Storage size: 6522 bytes + Address: [SC_ROLLUP_HASH] + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ1.6305 + storage fees ........................... +ꜩ1.6305 + diff --git a/tezt/lib_tezos/sc_rollup_client.ml b/tezt/lib_tezos/sc_rollup_client.ml index 2dac9bf60929..9e3ece0919d1 100644 --- a/tezt/lib_tezos/sc_rollup_client.ml +++ b/tezt/lib_tezos/sc_rollup_client.ml @@ -200,3 +200,26 @@ let spawn_import_secret_key ?hooks ?(force = false) let import_secret_key ?hooks ?force key sc_client = spawn_import_secret_key ?hooks ?force key sc_client |> Process.check + +let publish_message ?hooks message sc_client = + let* out = + spawn_command ?hooks sc_client ["publish"; "message"; message] + |> Process.check_and_read_stdout + in + let op_hash = + out =~* rex "Operation hash: (\\w+)" |> mandatory "operation hash" + in + let state_hash = out =~* rex "State hash: (\\w+)" |> mandatory "state hash" in + let status = out =~* rex "Status: (\\w+)" |> mandatory "status" in + let ticks = out =~* rex "Ticks diff: ([0-9]+)" |> mandatory "ticks" in + return (op_hash, state_hash, status, ticks) + +let batcher_messages ?hooks sc_client = + let open Lwt.Syntax in + let+ res = rpc_get ?hooks sc_client ["batcher"; "messages"] in + res |> JSON.as_list + |> List.map (fun obj -> + match JSON.as_object obj with + | [("hash", hash); ("batch", batch)] -> + (JSON.as_string hash, JSON.encode batch) + | _ -> JSON.error obj "Illformed messages output") diff --git a/tezt/lib_tezos/sc_rollup_client.mli b/tezt/lib_tezos/sc_rollup_client.mli index 71a0a43cef40..307fb4961797 100644 --- a/tezt/lib_tezos/sc_rollup_client.mli +++ b/tezt/lib_tezos/sc_rollup_client.mli @@ -107,3 +107,15 @@ val import_secret_key : Account.aggregate_key -> t -> unit Lwt.t + +(** Run [sc_rollup_client publish message] and returns the corresponding L2 + hash, the resulting state hash, PVM status and tick number. *) +val publish_message : + ?hooks:Process.hooks -> + string -> + t -> + (string * string * string * string) Lwt.t + +(** [batcher_messages] gets the current messages waiting for publication in the + node. *) +val batcher_messages : ?hooks:Process.hooks -> t -> (string * string) list Lwt.t diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index 7b6098a271cf..b5d85b175074 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -1471,6 +1471,107 @@ let test_rollup_client_list_keys = pp maybe_keys) +let dummy_message n = if n <= 0 then "0" else Format.sprintf "%d +" n + +(* Messages are ordered from newer to older *) +let publish_dummy_messages sc_rollup_client n = + Lwt_list.fold_left_s + (fun messages n -> + let* hash = + Sc_rollup_client.publish_message (dummy_message n) sc_rollup_client + in + return (hash :: messages)) + [] + (range 0 (n - 1)) + +let test_rollup_client_messages = + let output_file _ = "sc_rollup_client_messages" in + let go sc_rollup_node = + let* () = Sc_rollup_node.run sc_rollup_node in + let sc_rollup_client = Sc_rollup_client.create sc_rollup_node in + let message = "this is a test message" in + let* (hash, _, _, _) = + Sc_rollup_client.publish_message message sc_rollup_client + in + let* messages = Sc_rollup_client.batcher_messages sc_rollup_client in + let (hashes, _) = List.split messages in + Check.([hash] = hashes) + Check.(list string) + ~error_msg:"Injected message is not in the batcher queue" ; + Lwt.return_unit + in + test + ~__FILE__ + ~output_file + ~tags:["run"; "client"; "batcher"] + "Register messages in the batcher" + (fun protocol -> + setup ~protocol @@ fun node client -> + with_fresh_rollup + (fun _sc_rollup_address sc_rollup_node _filename -> go sc_rollup_node) + node + client) + +let test_rollup_node_publish_messages = + let output_file _ = "sc_rollup_node_publish_messages" in + let go sc_rollup_node client = + let* () = Sc_rollup_node.run sc_rollup_node in + let sc_rollup_client = Sc_rollup_client.create sc_rollup_node in + let* _ = publish_dummy_messages sc_rollup_client 3 in + let* _ = Client.bake_for client in + let* messages = Sc_rollup_client.batcher_messages sc_rollup_client in + Check.([] = messages) + Check.(list (tuple2 string string)) + ~error_msg:"Batcher queue is not empty" ; + Lwt.return_unit + in + test + ~__FILE__ + ~output_file + ~tags:["run"; "client"; "batcher"] + "Publish messages with the batcher" + (fun protocol -> + setup ~protocol @@ fun node client -> + with_fresh_rollup + (fun _sc_rollup_address sc_rollup_node _filename -> + go sc_rollup_node client) + node + client) + +let test_rollup_node_publish_messages_above_batch_limit = + let output_file _ = "sc_rollup_node_publish_messages_above_batch_limit" in + let go sc_rollup_node client = + let* () = Sc_rollup_node.run sc_rollup_node in + let sc_rollup_client = Sc_rollup_client.create sc_rollup_node in + (* assumes a batch maximum size is 10 *) + let* published = publish_dummy_messages sc_rollup_client 11 in + let last_published_hash = + match published with (h, _, _, _) :: _ -> [h] | _ -> [] + in + let* _ = Client.bake_for client in + let* messages = Sc_rollup_client.batcher_messages sc_rollup_client in + let (remaining_hashes, _) = List.split messages in + + Check.(remaining_hashes = last_published_hash) + Check.(list string) + ~error_msg: + "Batcher queue does not contain the last message that didn't fit in \ + the batch" ; + Lwt.return_unit + in + test + ~__FILE__ + ~output_file + ~tags:["run"; "client"; "batcher"] + "Send more messages than the batch limit and publish" + (fun protocol -> + setup ~protocol @@ fun node client -> + with_fresh_rollup + (fun _sc_rollup_address sc_rollup_node _filename -> + go sc_rollup_node client) + node + client) + let register ~protocols = test_origination protocols ; test_rollup_node_configuration protocols ; @@ -1503,4 +1604,7 @@ let register ~protocols = test_rollup_node_uses_boot_sector protocols ; test_rollup_client_show_address protocols ; test_rollup_client_generate_keys protocols ; - test_rollup_client_list_keys protocols + test_rollup_client_list_keys protocols ; + test_rollup_client_messages protocols ; + test_rollup_node_publish_messages protocols ; + test_rollup_node_publish_messages_above_batch_limit protocols -- GitLab