From 51dfb9cffa1f5a4024f01e1c6259864257d8b02f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Thir=C3=A9?= Date: Mon, 7 Feb 2022 17:02:24 +0100 Subject: [PATCH] Protocol/Tx_rollup: Refactorisation rollup storage --- src/proto_alpha/lib_protocol/alpha_context.ml | 13 -- .../lib_protocol/alpha_context.mli | 97 +++++++------ src/proto_alpha/lib_protocol/apply.ml | 22 ++- src/proto_alpha/lib_protocol/storage.mli | 2 +- .../integration/operations/test_tx_rollup.ml | 39 +++-- .../lib_protocol/tx_rollup_inbox_repr.ml | 40 +++--- .../lib_protocol/tx_rollup_inbox_repr.mli | 28 ++-- .../lib_protocol/tx_rollup_inbox_storage.ml | 115 +++++---------- .../lib_protocol/tx_rollup_inbox_storage.mli | 23 +-- .../lib_protocol/tx_rollup_message_repr.ml | 38 ++--- .../lib_protocol/tx_rollup_message_repr.mli | 19 +-- .../lib_protocol/tx_rollup_state_storage.ml | 135 ++++++++++++++++-- .../lib_protocol/tx_rollup_state_storage.mli | 33 +++-- .../lib_protocol/tx_rollup_storage.ml | 2 +- .../tx_rollup_simple_use_case.out | 4 +- tezt/tests/tx_rollup.ml | 4 +- 16 files changed, 351 insertions(+), 263 deletions(-) diff --git a/src/proto_alpha/lib_protocol/alpha_context.ml b/src/proto_alpha/lib_protocol/alpha_context.ml index 4321bb3f17a0..7a3a74015f41 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.ml +++ b/src/proto_alpha/lib_protocol/alpha_context.ml @@ -265,19 +265,6 @@ end module Tx_rollup_message = struct include Tx_rollup_message_repr - - type size = int - - let make_batch ctxt string = - let message = Batch string in - let message_limit = - Constants_storage.tx_rollup_hard_size_limit_per_message ctxt - in - let message_size = size message in - error_unless - Compare.Int.(message_size < message_limit) - Tx_rollup_inbox_storage.Tx_rollup_message_size_exceeds_limit - >>? fun () -> Ok (message, message_size) end module Tx_rollup_inbox = struct diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index aa3a2fbd5782..8b3ff3845209 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -2001,38 +2001,6 @@ module Tx_rollup : sig end end -(** This module re-exports definitions from {!Tx_rollup_state_repr} - and {!Tx_rollup_state_storage}. *) -module Tx_rollup_state : sig - type t - - val initial_state : t - - val encoding : t Data_encoding.t - - val pp : Format.formatter -> t -> unit - - val find : context -> Tx_rollup.t -> (context * t option) tzresult Lwt.t - - val get : context -> Tx_rollup.t -> (context * t) tzresult Lwt.t - - val update : context -> Tx_rollup.t -> t -> context tzresult Lwt.t - - val fees : t -> int -> Tez.t tzresult - - val last_inbox_level : t -> Raw_level.t option - - type error += - | Tx_rollup_already_exists of Tx_rollup.t - | Tx_rollup_does_not_exist of Tx_rollup.t - - module Internal_for_tests : sig - val initial_state_with_fees_per_byte : Tez.t -> t - - val update_fees_per_byte : t -> final_size:int -> hard_limit:int -> t - end -end - (** This module re-exports definitions from {!Tx_rollup_message_repr}. *) module Tx_rollup_message : sig type deposit = { @@ -2041,17 +2009,15 @@ module Tx_rollup_message : sig amount : int64; } - type t = private Batch of string | Deposit of deposit + type message = Batch of string | Deposit of deposit - type size = int + type t = private {message : message; size : int} - val make_batch : context -> string -> (t * size) tzresult + val make : message -> t - val size : t -> int + val encoding : message Data_encoding.t - val encoding : t Data_encoding.t - - val pp : Format.formatter -> t -> unit + val pp : Format.formatter -> message -> unit type hash @@ -2059,30 +2025,34 @@ module Tx_rollup_message : sig val pp_hash : Format.formatter -> hash -> unit - val hash : t -> hash + val hash : message -> hash end (** This module re-exports definitions from {!Tx_rollup_inbox_repr} and {!Tx_rollup_inbox_storage}. *) module Tx_rollup_inbox : sig - type t = {contents : Tx_rollup_message.hash list; cumulated_size : int} + type content = private Tx_rollup_message.hash list + + type metadata = private { + cumulated_size : int; + predecessor : Raw_level_repr.t option; + successor : Raw_level_repr.t option; + } + + type t = {content : content; metadata : metadata} val pp : Format.formatter -> t -> unit val encoding : t Data_encoding.t val append_message : - context -> - Tx_rollup.t -> - Tx_rollup_state.t -> - Tx_rollup_message.t -> - (context * Tx_rollup_state.t) tzresult Lwt.t + context -> Tx_rollup.t -> t -> Tx_rollup_message.t -> context tzresult Lwt.t val messages : context -> level:[`Current | `Level of Raw_level.t] -> Tx_rollup.t -> - (context * Tx_rollup_message.hash list) tzresult Lwt.t + (context * content) tzresult Lwt.t val size : context -> @@ -2114,6 +2084,39 @@ module Tx_rollup_inbox : sig | Tx_rollup_message_size_exceeds_limit end +(** This module re-exports definitions from {!Tx_rollup_state_repr} + and {!Tx_rollup_state_storage}. *) +module Tx_rollup_state : sig + type t + + val initial_state : t + + val encoding : t Data_encoding.t + + val pp : Format.formatter -> t -> unit + + val find : context -> Tx_rollup.t -> (context * t option) tzresult Lwt.t + + val get : context -> Tx_rollup.t -> (context * t) tzresult Lwt.t + + val make_inbox : + context -> Tx_rollup.t -> t -> (context * Tx_rollup_inbox.t) tzresult Lwt.t + + val fees : t -> int -> Tez.t tzresult + + val last_inbox_level : t -> Raw_level.t option + + type error += + | Tx_rollup_already_exists of Tx_rollup.t + | Tx_rollup_does_not_exist of Tx_rollup.t + + module Internal_for_tests : sig + val initial_state_with_fees_per_byte : Tez.t -> t + + val update_fees_per_byte : t -> final_size:int -> hard_limit:int -> t + end +end + module Kind : sig type preendorsement_consensus_kind = Preendorsement_consensus_kind diff --git a/src/proto_alpha/lib_protocol/apply.ml b/src/proto_alpha/lib_protocol/apply.ml index 19effe035bbb..26584669be6d 100644 --- a/src/proto_alpha/lib_protocol/apply.ml +++ b/src/proto_alpha/lib_protocol/apply.ml @@ -1169,15 +1169,22 @@ let apply_manager_operation_content : return (ctxt, result, []) | Tx_rollup_submit_batch {tx_rollup; content} -> assert_tx_rollup_feature_enabled ctxt >>=? fun () -> - Tx_rollup_message.make_batch ctxt content - >>?= fun (message, message_size) -> + let (Tx_rollup_message.{size = message_size; message = _} as full_message) + = + Tx_rollup_message.make (Batch content) + in Tx_rollup_state.get ctxt tx_rollup >>=? fun (ctxt, state) -> + (* We first preleve the fees to fail early if the source does + not have enough fees. *) Tx_rollup_state.fees state message_size >>?= fun cost -> Token.transfer ctxt (`Contract source) `Burned cost >>=? fun (ctxt, balance_updates) -> - Tx_rollup_inbox.append_message ctxt tx_rollup state message - >>=? fun (ctxt, state) -> - Tx_rollup_state.update ctxt tx_rollup state >>=? fun ctxt -> + (Tx_rollup_inbox.find ctxt ~level:`Current tx_rollup >>=? function + | (ctxt, None) -> Tx_rollup_state.make_inbox ctxt tx_rollup state + | (ctxt, Some inbox) -> return (ctxt, inbox)) + >>=? fun (ctxt, inbox) -> + Tx_rollup_inbox.append_message ctxt tx_rollup inbox full_message + >>=? fun ctxt -> let result = Tx_rollup_submit_batch_result { @@ -1320,11 +1327,12 @@ let precheck_manager_contents (type kind) ctxt (op : kind Kind.manager contents) Do we need to take into account the carbonation of hasing [content] here? *) assert_tx_rollup_feature_enabled ctxt >>=? fun () -> - let size_limit = + let message = Tx_rollup_message.make (Batch content) in + let message_limit = Alpha_context.Constants.tx_rollup_hard_size_limit_per_message ctxt in fail_unless - Compare.Int.(String.length content < size_limit) + Compare.Int.(message.Tx_rollup_message.size < message_limit) Tx_rollup_inbox.Tx_rollup_message_size_exceeds_limit >|=? fun () -> ctxt | Sc_rollup_originate _ | Sc_rollup_add_messages _ -> diff --git a/src/proto_alpha/lib_protocol/storage.mli b/src/proto_alpha/lib_protocol/storage.mli index 9e27b8bbe615..43a1725ecf45 100644 --- a/src/proto_alpha/lib_protocol/storage.mli +++ b/src/proto_alpha/lib_protocol/storage.mli @@ -719,7 +719,7 @@ module Tx_rollup : sig Non_iterable_indexed_carbonated_data_storage with type t := Raw_context.t * Raw_level_repr.t and type key = Tx_rollup_repr.t - and type value = Tx_rollup_message_repr.hash list + and type value = Tx_rollup_inbox_repr.content (** [fold (ctxt, level) ~order ~init ~f] traverses all rollups with a nonempty inbox at [level]. diff --git a/src/proto_alpha/lib_protocol/test/integration/operations/test_tx_rollup.ml b/src/proto_alpha/lib_protocol/test/integration/operations/test_tx_rollup.ml index ce5e9d823ed7..457f733174f3 100644 --- a/src/proto_alpha/lib_protocol/test/integration/operations/test_tx_rollup.ml +++ b/src/proto_alpha/lib_protocol/test/integration/operations/test_tx_rollup.ml @@ -86,13 +86,10 @@ let fees_per_byte state = inbox_fees state 1 (** [check_batch_in_inbox inbox n expected] checks that the [n]th element of [inbox] is a batch equal to [expected]. *) let check_batch_in_inbox : - context -> Tx_rollup_inbox.t -> int -> string -> unit tzresult Lwt.t = - fun ctxt inbox n expected -> - Lwt.return - @@ Environment.wrap_tzresult (Tx_rollup_message.make_batch ctxt expected) - >>=? fun (expected_batch, _) -> - let expected_hash = Tx_rollup_message.hash expected_batch in - match List.nth inbox.contents n with + Tx_rollup_inbox.t -> int -> string -> unit tzresult Lwt.t = + fun inbox n expected -> + let expected_hash = Tx_rollup_message.hash (Batch expected) in + match List.nth (inbox.content :> Tx_rollup_message.hash list) n with | Some content -> Alcotest.( check @@ -251,10 +248,11 @@ let test_add_batch () = let contents = String.make contents_size 'c' in init_originate_and_submit ~batch:contents () >>=? fun ((contract, balance), state, tx_rollup, b) -> - Context.Tx_rollup.inbox (B b) tx_rollup >>=? fun {contents; cumulated_size} -> - let length = List.length contents in + Context.Tx_rollup.inbox (B b) tx_rollup >>=? fun {content; metadata} -> + let length = List.length (content :> Tx_rollup_message.hash list) in Alcotest.(check int "Expect an inbox with a single item" 1 length) ; - Alcotest.(check int "Expect cumulated size" contents_size cumulated_size) ; + Alcotest.( + check int "Expect cumulated size" contents_size metadata.cumulated_size) ; inbox_fees state contents_size >>?= fun cost -> Assert.balance_was_debited ~loc:__LOC__ (B b) contract balance cost @@ -283,23 +281,24 @@ let test_add_two_batches () = >>=? fun op2 -> Block.bake ~operations:[op1; op2] b >>=? fun b -> Context.Tx_rollup.inbox (B b) tx_rollup >>=? fun inbox -> - let length = List.length inbox.contents in + let length = List.length (inbox.content :> Tx_rollup_message.hash list) in let expected_cumulated_size = contents_size1 + contents_size2 in - Alcotest.(check int "Expect an inbox with two items" 2 length) ; Alcotest.( check int "Expect cumulated size" expected_cumulated_size - inbox.cumulated_size) ; - - Context.Tx_rollup.inbox (B b) tx_rollup >>=? fun {contents; _} -> - Alcotest.(check int "Expect an inbox with two items" 2 (List.length contents)) ; - Incremental.begin_construction b >>=? fun i -> - let ctxt = Incremental.alpha_ctxt i in - check_batch_in_inbox ctxt inbox 0 contents1 >>=? fun () -> - check_batch_in_inbox ctxt inbox 1 contents2 >>=? fun () -> + inbox.metadata.cumulated_size) ; + Context.Tx_rollup.inbox (B b) tx_rollup >>=? fun {content; _} -> + Alcotest.( + check + int + "Expect an inbox with two items" + 2 + (List.length (content :> Tx_rollup_message.hash list))) ; + check_batch_in_inbox inbox 0 contents1 >>=? fun () -> + check_batch_in_inbox inbox 1 contents2 >>=? fun () -> inbox_fees state expected_cumulated_size >>?= fun cost -> Assert.balance_was_debited ~loc:__LOC__ (B b) contract balance cost diff --git a/src/proto_alpha/lib_protocol/tx_rollup_inbox_repr.ml b/src/proto_alpha/lib_protocol/tx_rollup_inbox_repr.ml index 7468b98526f9..252383667d7c 100644 --- a/src/proto_alpha/lib_protocol/tx_rollup_inbox_repr.ml +++ b/src/proto_alpha/lib_protocol/tx_rollup_inbox_repr.ml @@ -25,24 +25,6 @@ (* *) (*****************************************************************************) -type t = {contents : Tx_rollup_message_repr.hash list; cumulated_size : int} - -let pp fmt {contents; cumulated_size} = - Format.fprintf - fmt - "tx rollup inbox: %d messages using %d bytes" - (List.length contents) - cumulated_size - -let encoding = - let open Data_encoding in - conv - (fun {contents; cumulated_size} -> (contents, cumulated_size)) - (fun (contents, cumulated_size) -> {contents; cumulated_size}) - (obj2 - (req "contents" @@ list Tx_rollup_message_repr.hash_encoding) - (req "cumulated_size" int31)) - type metadata = { cumulated_size : int; predecessor : Raw_level_repr.t option; @@ -60,3 +42,25 @@ let metadata_encoding = (req "cumulated_size" int31) (req "predecessor" (option Raw_level_repr.encoding)) (req "successor" (option Raw_level_repr.encoding))) + +type content = Tx_rollup_message_repr.hash list + +let content_encoding = Data_encoding.list Tx_rollup_message_repr.hash_encoding + +type t = {content : content; metadata : metadata} + +let pp fmt {content; metadata = {cumulated_size; _}} = + Format.fprintf + fmt + "tx rollup inbox: %d messages using %d bytes" + (List.length content) + cumulated_size + +let encoding = + let open Data_encoding in + conv + (fun {content; metadata} -> (content, metadata)) + (fun (content, metadata) -> {content; metadata}) + (obj2 + (req "content" @@ content_encoding) + (req "metadata" metadata_encoding)) diff --git a/src/proto_alpha/lib_protocol/tx_rollup_inbox_repr.mli b/src/proto_alpha/lib_protocol/tx_rollup_inbox_repr.mli index b104ae91d217..35ed8afb56fd 100644 --- a/src/proto_alpha/lib_protocol/tx_rollup_inbox_repr.mli +++ b/src/proto_alpha/lib_protocol/tx_rollup_inbox_repr.mli @@ -25,6 +25,18 @@ (* *) (*****************************************************************************) +(** The metadata for an inbox stores the [cumulated_size] in + bytes for the inbox, so that we do not need to retrieve the entries + for the inbox just to get the size. It also stores the + [predecessor] and [successor] levels. For the first inbox of a + rollup, the [predecessor] will be None. For all inboxes, the + [successor] will be None until a subsequent inbox is created. *) +type metadata = { + cumulated_size : int; + predecessor : Raw_level_repr.t option; + successor : Raw_level_repr.t option; +} + (** An inbox gathers, for a given Tezos level, messages crafted by the layer-1 for the layer-2 to interpret. @@ -35,22 +47,12 @@ We recall that a transaction rollup can have up to one inbox per Tezos level, starting from its origination. See {!Storage.Tx_rollup} for more information. *) -type t = {contents : Tx_rollup_message_repr.hash list; cumulated_size : int} +type content = Tx_rollup_message_repr.hash list + +type t = {content : content; metadata : metadata} val pp : Format.formatter -> t -> unit val encoding : t Data_encoding.t -(* The metadata for an inbox stores the [cumulated_size] in - bytes for the inbox, so that we do not need to retrieve the entries - for the inbox just to get the size. It also stores the - [predecessor] and [successor] levels. For the first inbox of a - rollup, the [predecessor] will be None. For all inboxes, the - [successor] will be None until a subsequent inbox is created. *) -type metadata = { - cumulated_size : int; - predecessor : Raw_level_repr.t option; - successor : Raw_level_repr.t option; -} - val metadata_encoding : metadata Data_encoding.t diff --git a/src/proto_alpha/lib_protocol/tx_rollup_inbox_storage.ml b/src/proto_alpha/lib_protocol/tx_rollup_inbox_storage.ml index f7d287876ad1..e7af0d3c503b 100644 --- a/src/proto_alpha/lib_protocol/tx_rollup_inbox_storage.ml +++ b/src/proto_alpha/lib_protocol/tx_rollup_inbox_storage.ml @@ -30,89 +30,34 @@ type error += | Tx_rollup_inbox_size_would_exceed_limit of Tx_rollup_repr.t | Tx_rollup_message_size_exceeds_limit -(** [prepare_metadata ctxt rollup state level] prepares the metadata for - an inbox at [level]. This may involve updating the predecessor's - successor pointer. It also returns a new state which point - the tail of the linked list of inboxes to this inbox. *) -let prepare_metadata : - Raw_context.t -> - Tx_rollup_repr.t -> - Tx_rollup_state_repr.t -> - Raw_level_repr.t -> - (Raw_context.t * Tx_rollup_state_repr.t * Tx_rollup_inbox_repr.metadata) - tzresult - Lwt.t = - fun ctxt rollup state level -> - Storage.Tx_rollup.Inbox_metadata.find (ctxt, level) rollup - >>=? fun (ctxt, metadata) -> - match metadata with - | Some metadata -> return (ctxt, state, metadata) - | None -> - (* First message in inbox: need to update linked list and pending - inbox count *) - let predecessor = Tx_rollup_state_repr.last_inbox_level state in - let new_state = Tx_rollup_state_repr.append_inbox state level in - (match predecessor with - | None -> return ctxt - | Some predecessor_level -> - Storage.Tx_rollup.Inbox_metadata.get (ctxt, predecessor_level) rollup - >>=? fun (ctxt, predecessor_metadata) -> - (* Here, we update the predecessor inbox's successor to point - to this inbox. *) - Storage.Tx_rollup.Inbox_metadata.add - (ctxt, predecessor_level) - rollup - {predecessor_metadata with successor = Some level} - >|=? fun (ctxt, _, _) -> ctxt) - >>=? fun ctxt -> - let new_metadata : Tx_rollup_inbox_repr.metadata = - {cumulated_size = 0; predecessor; successor = None} - in - return (ctxt, new_state, new_metadata) - -let append_message : - Raw_context.t -> - Tx_rollup_repr.t -> - Tx_rollup_state_repr.t -> - Tx_rollup_message_repr.t -> - (Raw_context.t * Tx_rollup_state_repr.t) tzresult Lwt.t = - fun ctxt rollup state message -> - let level = (Raw_context.current_level ctxt).level in - let message_size = Tx_rollup_message_repr.size message in - let message_limit = - Constants_storage.tx_rollup_hard_size_limit_per_message ctxt +let append_message ctxt rollup inbox message = + let cumulated_size = + inbox.Tx_rollup_inbox_repr.metadata.cumulated_size + + message.Tx_rollup_message_repr.size + in + let cumulated_size_limit = + Constants_storage.tx_rollup_hard_size_limit_per_inbox ctxt in - fail_unless - Compare.Int.(message_size < message_limit) - Tx_rollup_message_size_exceeds_limit - >>=? fun () -> - prepare_metadata ctxt rollup state level - >>=? fun (ctxt, new_state, new_metadata) -> + error_unless + Compare.Int.(cumulated_size < cumulated_size_limit) + (Tx_rollup_inbox_size_would_exceed_limit rollup) + >>?= fun () -> let new_metadata = - { - new_metadata with - cumulated_size = message_size + new_metadata.cumulated_size; - } + Tx_rollup_inbox_repr. + { + cumulated_size; + predecessor = inbox.metadata.predecessor; + successor = inbox.metadata.successor; + } + in + let new_content = + Tx_rollup_message_repr.hash message.message :: inbox.content in + let level = (Raw_context.current_level ctxt).level in Storage.Tx_rollup.Inbox_metadata.add (ctxt, level) rollup new_metadata >>=? fun (ctxt, _, _) -> - let new_size = new_metadata.cumulated_size in - let inbox_limit = - Constants_storage.tx_rollup_hard_size_limit_per_inbox ctxt - in - fail_unless - Compare.Int.(new_size < inbox_limit) - (Tx_rollup_inbox_size_would_exceed_limit rollup) - >>=? fun () -> - Storage.Tx_rollup.Inbox_rev_contents.find (ctxt, level) rollup - >>=? fun (ctxt, mcontents) -> - (* FIXME: https://gitlab.com/tezos/tezos/-/issues/2408 - Carbonate hashing the message. *) - Storage.Tx_rollup.Inbox_rev_contents.add - (ctxt, level) - rollup - (Tx_rollup_message_repr.hash message :: Option.value ~default:[] mcontents) - >>=? fun (ctxt, _, _) -> return (ctxt, new_state) + Storage.Tx_rollup.Inbox_rev_contents.add (ctxt, level) rollup new_content + >>=? fun (ctxt, _, _) -> return ctxt let get_level : Raw_context.t -> [`Current | `Level of Raw_level_repr.t] -> Raw_level_repr.t @@ -151,6 +96,10 @@ let messages : | (_, None) -> fail (Tx_rollup_inbox_does_not_exist (tx_rollup, get_level ctxt level)) +let metadata ctxt ~level tx_rollup = + let level = get_level ctxt level in + Storage.Tx_rollup.Inbox_metadata.find (ctxt, level) tx_rollup + let size : Raw_context.t -> level:[`Current | `Level of Raw_level_repr.t] -> @@ -181,9 +130,11 @@ let find : we do not have to do it here. *) messages_opt ctxt ~level tx_rollup >>=? function - | (ctxt, Some contents) -> - size ctxt ~level tx_rollup >>=? fun (ctxt, cumulated_size) -> - return (ctxt, Some {cumulated_size; contents}) + | (ctxt, Some content) -> ( + metadata ctxt ~level tx_rollup >>=? fun (ctxt, metadata) -> + match metadata with + | None -> (* not supposed to happen *) return (ctxt, None) + | Some metadata -> return (ctxt, Some {content; metadata})) | (ctxt, None) -> return (ctxt, None) let get : @@ -239,7 +190,7 @@ let () = | _ -> None) (fun (rollup, level) -> Tx_rollup_inbox_does_not_exist (rollup, level)) ; register_error_kind - `Permanent + `Temporary ~id:"tx_rollup_inbox_size_would_exceed_limit" ~title:"Transaction rollup inbox’s size would exceed the limit" ~description:"Transaction rollup inbox’s size would exceed the limit" diff --git a/src/proto_alpha/lib_protocol/tx_rollup_inbox_storage.mli b/src/proto_alpha/lib_protocol/tx_rollup_inbox_storage.mli index 3e0d3ac3e028..77e90f005dee 100644 --- a/src/proto_alpha/lib_protocol/tx_rollup_inbox_storage.mli +++ b/src/proto_alpha/lib_protocol/tx_rollup_inbox_storage.mli @@ -35,32 +35,23 @@ type error += | Tx_rollup_inbox_size_would_exceed_limit of Tx_rollup_repr.t | Tx_rollup_message_size_exceeds_limit -(** [append_message ctxt tx_rollup state message] tries to append - [message] to the inbox of [tx_rollup] at the current level, creating - it in theprocess if need be. This function returns the size of the - appended message (in bytes), in order for the appropriate fees to be - taken from the message author, as well as the new state. It - is the caller's responsibility to store the returned state. +(** [append_message ctxt tx_rollup inbox message] tries to append + [message] to the [inbox] of [tx_rollup] at the current level. {b Note:} [tx_rollup] needs to be a valid transaction address. It - is the responsibility of the caller to assert it. + is the responsibility of the caller to assert it. Returns the error {ul {li [Tx_rollup_hard_size_limit_reached] if appending [message] - to the inbox would make it exceed the maximum size - specified by the [tx_rollup_hard_size_limit_per_inbox] - protocol parameter.} - {li [Tx_rollup_message_size_exceeds_limit] if the size of - [message] is greater than the - [tx_rollup_hard_size_limit_per_message] protocol - parameter.}} *) + to the inbox would make it exceed the maximum size specified by the + [tx_rollup_hard_size_limit_per_inbox] protocol parameter.}} *) val append_message : Raw_context.t -> Tx_rollup_repr.t -> - Tx_rollup_state_repr.t -> + Tx_rollup_inbox_repr.t -> Tx_rollup_message_repr.t -> - (Raw_context.t * Tx_rollup_state_repr.t) tzresult Lwt.t + Raw_context.t tzresult Lwt.t (** [messages ctxt ~level tx_rollup] returns the list of messages hashes stored in the inbox of [tx_rollup] at level [level]. diff --git a/src/proto_alpha/lib_protocol/tx_rollup_message_repr.ml b/src/proto_alpha/lib_protocol/tx_rollup_message_repr.ml index a0a0fbe7d426..8935e43e59cf 100644 --- a/src/proto_alpha/lib_protocol/tx_rollup_message_repr.ml +++ b/src/proto_alpha/lib_protocol/tx_rollup_message_repr.ml @@ -43,7 +43,26 @@ let deposit_encoding = (req "ticket_hash" Ticket_hash_repr.encoding) (req "amount" int64) -type t = Batch of string | Deposit of deposit +type message = Batch of string | Deposit of deposit + +type t = {message : message; size : int} + +let size = function + | Batch batch -> String.length batch + | Deposit {destination = d; ticket_hash = _; amount = _} -> + (* Size of a BLS public key, that is the underlying type of a + l2 address. See [Tx_rollup_l2_address] *) + let destination_size = Tx_rollup_l2_address.Indexable.size d in + (* Size of a [Script_expr_hash.t], that is the underlying type + of [Ticket_hash_repr.t]. *) + let key_hash_size = 32 in + (* [int64] *) + let amount_size = 8 in + destination_size + key_hash_size + amount_size + +let make message = + let size = size message in + {message; size} let encoding = let open Data_encoding in @@ -90,19 +109,6 @@ let pp fmt = ticket_hash amount -let size = function - | Batch batch -> String.length batch - | Deposit {destination = d; ticket_hash = _; amount = _} -> - (* Size of a BLS public key, that is the underlying type of a - l2 address. See [Tx_rollup_l2_address] *) - let destination_size = Tx_rollup_l2_address.Indexable.size d in - (* Size of a [Script_expr_hash.t], that is the underlying type - of [Ticket_hash_repr.t]. *) - let key_hash_size = 32 in - (* [int64] *) - let amount_size = 8 in - destination_size + key_hash_size + amount_size - let hash_size = 32 module Message_hash = @@ -126,5 +132,5 @@ let pp_hash = Message_hash.pp let hash_encoding = Message_hash.encoding -let hash msg = - Message_hash.hash_bytes [Data_encoding.Binary.to_bytes_exn encoding msg] +let hash message = + Message_hash.hash_bytes [Data_encoding.Binary.to_bytes_exn encoding message] diff --git a/src/proto_alpha/lib_protocol/tx_rollup_message_repr.mli b/src/proto_alpha/lib_protocol/tx_rollup_message_repr.mli index 270e5984966a..f509b2f176d6 100644 --- a/src/proto_alpha/lib_protocol/tx_rollup_message_repr.mli +++ b/src/proto_alpha/lib_protocol/tx_rollup_message_repr.mli @@ -40,6 +40,8 @@ type deposit = { amount : int64; } +type message = Batch of string | Deposit of deposit + (** A [message] is a piece of data originated from the layer-1 to be interpreted by the layer-2. @@ -48,16 +50,17 @@ type deposit = { {ul {li An array of bytes that supposedly contains a valid sequence of layer-2 operations; their interpretation and validation is deferred to the layer-2..} - {li A deposit order for a L1 ticket.}} *) -type t = Batch of string | Deposit of deposit + {li A deposit order for a L1 ticket.}} + + We pack the size with the message to ensure that the size is computed + by this module. *) +type t = private {message : message; size : int} -(** [size msg] returns the number of bytes that are allocated in an - inbox by [msg]. *) -val size : t -> int +val make : message -> t -val encoding : t Data_encoding.t +val encoding : message Data_encoding.t -val pp : Format.formatter -> t -> unit +val pp : Format.formatter -> message -> unit (** The Blake2B hash of a message. @@ -73,4 +76,4 @@ val hash_encoding : hash Data_encoding.t val pp_hash : Format.formatter -> hash -> unit (** [hash msg] computes the hash of [msg] to be stored in the inbox. *) -val hash : t -> hash +val hash : message -> hash diff --git a/src/proto_alpha/lib_protocol/tx_rollup_state_storage.ml b/src/proto_alpha/lib_protocol/tx_rollup_state_storage.ml index ff9921d52efd..5bed7307fcce 100644 --- a/src/proto_alpha/lib_protocol/tx_rollup_state_storage.ml +++ b/src/proto_alpha/lib_protocol/tx_rollup_state_storage.ml @@ -26,6 +26,11 @@ (*****************************************************************************) type error += + | (* FIXME: register these two one. *) + Tx_rollup_inbox_does_not_exist of + Tx_rollup_repr.t * Raw_level_repr.t + | Tx_rollup_inbox_already_exist of Tx_rollup_repr.t * Raw_level_repr.t + | Tx_rollup_inbox_inconsistency of Tx_rollup_repr.t * Raw_level_repr.t | Tx_rollup_already_exists of Tx_rollup_repr.t | Tx_rollup_does_not_exist of Tx_rollup_repr.t @@ -60,14 +65,51 @@ let assert_exist : fail_unless tx_rollup_exists (Tx_rollup_does_not_exist tx_rollup) >>=? fun () -> return ctxt -let update : - Raw_context.t -> - Tx_rollup_repr.t -> - Tx_rollup_state_repr.t -> - Raw_context.t tzresult Lwt.t = - fun ctxt tx_rollup t -> - Storage.Tx_rollup.State.update ctxt tx_rollup t >>=? fun (ctxt, _) -> - return ctxt +module Comparable_raw_level_repr_opt = Compare.Option (Raw_level_repr) + +let make_inbox ctxt rollup state = + let last_inbox_level = Tx_rollup_state_repr.last_inbox_level state in + let current_level = (Raw_context.current_level ctxt).level in + let metadata = + Tx_rollup_inbox_repr. + {cumulated_size = 0; predecessor = last_inbox_level; successor = None} + in + let inbox = Tx_rollup_inbox_repr.{content = []; metadata} in + let state = Tx_rollup_state_repr.append_inbox state current_level in + (* We update the storage accordingly. *) + match last_inbox_level with + | None -> + Storage.Tx_rollup.State.update ctxt rollup state >>=? fun (ctxt, _) -> + return (ctxt, inbox) + | Some last_inbox_level -> ( + (* This error should never occur, and is here as a safety net to + avoid erasing an inbox. *) + error_unless + Raw_level_repr.(last_inbox_level <> current_level) + (Tx_rollup_inbox_already_exist (rollup, current_level)) + >>?= fun () -> + Storage.Tx_rollup.Inbox_metadata.find (ctxt, last_inbox_level) rollup + >>=? fun (ctxt, metadata) -> + match metadata with + | None -> + (* This error should never occur, and is caught here as a + safety net. *) + fail (Tx_rollup_inbox_does_not_exist (rollup, last_inbox_level)) + | Some metadata -> + (* This error should never occur, and is caught here as a + safety net. *) + error_unless + Comparable_raw_level_repr_opt.(metadata.successor = None) + (Tx_rollup_inbox_inconsistency (rollup, current_level)) + >>?= fun () -> + let metadata = {metadata with successor = Some current_level} in + Storage.Tx_rollup.Inbox_metadata.add + (ctxt, last_inbox_level) + rollup + metadata + >>=? fun (ctxt, _, _) -> + Storage.Tx_rollup.State.update ctxt rollup state >>=? fun (ctxt, _) -> + return (ctxt, inbox)) (* ------ Error registration ------------------------------------------------ *) @@ -95,7 +137,7 @@ let () = (* Tx_rollup_does_not_exist *) register_error_kind `Temporary - ~id:"tx_rollup_does_not_exist" + ~id:"tx_rollup_does_not_exist_" ~title:"Transaction rollup does not exist" ~description:"An invalid transaction rollup address was submitted" ~pp:(fun ppf addr -> @@ -106,4 +148,77 @@ let () = addr) (obj1 (req "rollup_address" Tx_rollup_repr.encoding)) (function Tx_rollup_does_not_exist rollup -> Some rollup | _ -> None) - (fun rollup -> Tx_rollup_does_not_exist rollup) + (fun rollup -> Tx_rollup_does_not_exist rollup) ; + (* Tx_rollup_inbox_does_not_exist *) + register_error_kind + `Permanent + ~id:"tx_rollup_inbox_does_not_exist_" + ~title:"The last inbox level recorded is not stored" + ~description: + "The state of the rollup references an inbox which is not stored in the \ + context" + ~pp:(fun ppf (addr, level) -> + Format.fprintf + ppf + "The rollup state for %a recorded that the last inbox level is %a, but \ + the inbox associated to was not stored." + Tx_rollup_repr.pp + addr + Raw_level_repr.pp + level) + (obj2 + (req "rollup_address" Tx_rollup_repr.encoding) + (req "level" Raw_level_repr.encoding)) + (function + | Tx_rollup_inbox_does_not_exist (rollup, level) -> Some (rollup, level) + | _ -> None) + (fun (rollup, level) -> Tx_rollup_inbox_does_not_exist (rollup, level)) ; + (* Tx_rollup_inbox_already_exist *) + register_error_kind + `Permanent + ~id:"tx_rollup_inbox_already_exist" + ~title:"An inbox for the current level already exist" + ~description: + "We try to create a new inbox for the given level while an inbox already \ + existed for this level" + ~pp:(fun ppf (addr, level) -> + Format.fprintf + ppf + "The creation of a new inbox failed. The rollup state for %a already \ + has an inbox for the level %a." + Tx_rollup_repr.pp + addr + Raw_level_repr.pp + level) + (obj2 + (req "rollup_address" Tx_rollup_repr.encoding) + (req "level" Raw_level_repr.encoding)) + (function + | Tx_rollup_inbox_already_exist (rollup, level) -> Some (rollup, level) + | _ -> None) + (fun (rollup, level) -> Tx_rollup_inbox_already_exist (rollup, level)) ; + (* Tx_rollup_inbox_inconsistency *) + register_error_kind + `Permanent + ~id:"tx_rollup_inbox_inconsistency" + ~title:"The internal storage for the inbox is consistent." + ~description: + "While creating an inbox, we have detected that the state is in an \ + inconsistent state." + ~pp:(fun ppf (addr, level) -> + Format.fprintf + ppf + "The creation of a new inbox failed. The rollup state for %a \ + references an inbox at level %a, but the internal storage contains no \ + metadata about this inbox." + Tx_rollup_repr.pp + addr + Raw_level_repr.pp + level) + (obj2 + (req "rollup_address" Tx_rollup_repr.encoding) + (req "level" Raw_level_repr.encoding)) + (function + | Tx_rollup_inbox_inconsistency (rollup, level) -> Some (rollup, level) + | _ -> None) + (fun (rollup, level) -> Tx_rollup_inbox_inconsistency (rollup, level)) diff --git a/src/proto_alpha/lib_protocol/tx_rollup_state_storage.mli b/src/proto_alpha/lib_protocol/tx_rollup_state_storage.mli index aa313fcad784..ce102808dad7 100644 --- a/src/proto_alpha/lib_protocol/tx_rollup_state_storage.mli +++ b/src/proto_alpha/lib_protocol/tx_rollup_state_storage.mli @@ -32,6 +32,9 @@ module are carbonated. *) type error += + | Tx_rollup_inbox_does_not_exist of Tx_rollup_repr.t * Raw_level_repr.t + | Tx_rollup_inbox_already_exist of Tx_rollup_repr.t * Raw_level_repr.t + | Tx_rollup_inbox_inconsistency of Tx_rollup_repr.t * Raw_level_repr.t | Tx_rollup_already_exists of Tx_rollup_repr.t | Tx_rollup_does_not_exist of Tx_rollup_repr.t @@ -67,16 +70,30 @@ val get : Tx_rollup_repr.t -> (Raw_context.t * Tx_rollup_state_repr.t) tzresult Lwt.t -(** [update ctxt tx_rollup new_state] replaces the stored state of - [tx_rollup] with [new_state]. *) -val update : - Raw_context.t -> - Tx_rollup_repr.t -> - Tx_rollup_state_repr.t -> - Raw_context.t tzresult Lwt.t - (** [assert_exist ctxt tx_rollup] fails with [Tx_rollup_does_not_exist] when [tx_rollup] is not a valid transaction rollup address. *) val assert_exist : Raw_context.t -> Tx_rollup_repr.t -> Raw_context.t tzresult Lwt.t + +(** [make_inbox ctxt tx_rollup state] creates a new inbox for the + current level. An error is returned in the following cases: + + {ul {li [Tx_rollup_does_not_exist]} + + {li [Tx_rollup_inbox_already_exist]} + + {li [Tx_rollup_inbox_inconsistency]} + + {li [Tx_rollup_inbox_does_not_exist]}} + + {b Note:} It is the responsability of the caller to ensure that the + [tx_rollup] exists and at the current level, there is no + [inbox]. The errors [Tx_rollup_inbox_inconsistency] and + [Tx_rollup_inbox_does_not_exist] can be raised only if there is an + inconsistency in the internal rollup storage. *) +val make_inbox : + Raw_context.t -> + Tx_rollup_repr.t -> + Tx_rollup_state_repr.t -> + (Raw_context.t * Tx_rollup_inbox_repr.t) tzresult Lwt.t diff --git a/src/proto_alpha/lib_protocol/tx_rollup_storage.ml b/src/proto_alpha/lib_protocol/tx_rollup_storage.ml index 2e9666382f3f..20a91c2e212d 100644 --- a/src/proto_alpha/lib_protocol/tx_rollup_storage.ml +++ b/src/proto_alpha/lib_protocol/tx_rollup_storage.ml @@ -50,7 +50,7 @@ let update_tx_rollups_at_block_finalization : let state = Tx_rollup_state_repr.update_fees_per_byte state - ~final_size:inbox.cumulated_size + ~final_size:inbox.metadata.cumulated_size ~hard_limit in Storage.Tx_rollup.State.add ctxt tx_rollup state >|=? fun (ctxt, _, _) -> diff --git a/tezt/_regressions/tx_rollup_simple_use_case.out b/tezt/_regressions/tx_rollup_simple_use_case.out index cb96534eb5b9..a709f88561f8 100644 --- a/tezt/_regressions/tx_rollup_simple_use_case.out +++ b/tezt/_regressions/tx_rollup_simple_use_case.out @@ -32,5 +32,5 @@ This sequence of operations was run: ./tezos-client rpc get /chains/main/blocks/head/context/tx_rollup/tru1EL3YqhLS3kwni3ikbqMrui61fA5k7StHz/inbox -{ "contents": [ "M21tdhc2Wn76n164oJvyKW4JVZsDSDeuDsbLgp61XZWtrXjL5WA" ], - "cumulated_size": 5 } +{ "content": [ "M21tdhc2Wn76n164oJvyKW4JVZsDSDeuDsbLgp61XZWtrXjL5WA" ], + "metadata": { "cumulated_size": 5, "predecessor": null, "successor": null } } diff --git a/tezt/tests/tx_rollup.ml b/tezt/tests/tx_rollup.ml index a464504a42ff..a2d2f9c9377f 100644 --- a/tezt/tests/tx_rollup.ml +++ b/tezt/tests/tx_rollup.ml @@ -49,7 +49,9 @@ let get_state ?hooks tx_rollup client = let get_inbox ?hooks tx_rollup client = let* json = RPC.Tx_rollup.get_inbox ?hooks ~tx_rollup client in - let cumulated_size = JSON.(json |-> "cumulated_size" |> as_int) in + let cumulated_size = + JSON.(json |-> "metadata" |-> "cumulated_size" |> as_int) + in let contents = JSON.(json |-> "contents" |> as_list |> List.map as_string) in return {cumulated_size; contents} -- GitLab