From 7417392fdefa48f771fb9408df658ab59c77a608 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Thu, 4 May 2023 10:51:14 +0200 Subject: [PATCH 1/5] SCORU/Node/Store: singleton for storing protocol updates --- .../lib_sc_rollup_node/store_v2.ml | 63 ++++++++++++++++++- .../lib_sc_rollup_node/store_v2.mli | 22 +++++++ 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/lib_sc_rollup_node/store_v2.ml b/src/proto_alpha/lib_sc_rollup_node/store_v2.ml index c8363e787eff..18b77336aa55 100644 --- a/src/proto_alpha/lib_sc_rollup_node/store_v2.ml +++ b/src/proto_alpha/lib_sc_rollup_node/store_v2.ml @@ -145,7 +145,62 @@ module Commitments = include Add_empty_header end) -type nonrec 'a store = { +module Protocols = struct + type level = First_known of int32 | Activation_level of int32 + + type proto_info = { + level : level; + proto_level : int; + protocol : Protocol_hash.t; + } + + type value = proto_info list + + let level_encoding = + let open Data_encoding in + conv + (function First_known l -> (l, false) | Activation_level l -> (l, true)) + (function l, false -> First_known l | l, true -> Activation_level l) + @@ obj2 (req "level" int32) (req "activates" bool) + + let proto_info_encoding = + let open Data_encoding in + conv + (fun {level; proto_level; protocol} -> (level, proto_level, protocol)) + (fun (level, proto_level, protocol) -> {level; proto_level; protocol}) + @@ obj3 + (req "level" level_encoding) + (req "proto_level" int31) + (req "protocol" Protocol_hash.encoding) + + include Indexed_store.Make_singleton (struct + type t = value + + let name = "protocols" + + let level_encoding = + let open Data_encoding in + conv + (function + | First_known l -> (l, false) | Activation_level l -> (l, true)) + (function l, false -> First_known l | l, true -> Activation_level l) + @@ obj2 (req "level" int32) (req "activates" bool) + + let proto_info_encoding = + let open Data_encoding in + conv + (fun {level; proto_level; protocol} -> (level, proto_level, protocol)) + (fun (level, proto_level, protocol) -> {level; proto_level; protocol}) + @@ obj3 + (req "level" level_encoding) + (req "proto_level" int31) + (req "protocol" Protocol_hash.encoding) + + let encoding = Data_encoding.list proto_info_encoding + end) +end + +type 'a store = { l2_blocks : 'a L2_blocks.t; messages : 'a Messages.t; inboxes : 'a Inboxes.t; @@ -154,6 +209,7 @@ type nonrec 'a store = { l2_head : 'a L2_head.t; last_finalized_level : 'a Last_finalized_level.t; levels_to_hashes : 'a Levels_to_hashes.t; + protocols : 'a Protocols.t; irmin_store : 'a Irmin_store.t; } @@ -173,6 +229,7 @@ let readonly l2_head; last_finalized_level; levels_to_hashes; + protocols; irmin_store; } : _ t) : ro = @@ -186,6 +243,7 @@ let readonly l2_head = L2_head.readonly l2_head; last_finalized_level = Last_finalized_level.readonly last_finalized_level; levels_to_hashes = Levels_to_hashes.readonly levels_to_hashes; + protocols = Protocols.readonly protocols; irmin_store = Irmin_store.readonly irmin_store; } @@ -199,6 +257,7 @@ let close l2_head = _; last_finalized_level = _; levels_to_hashes; + protocols = _; irmin_store; } : _ t) = @@ -235,6 +294,7 @@ let load (type a) (mode : a mode) ~l2_blocks_cache_size data_dir : let* levels_to_hashes = Levels_to_hashes.load mode ~path:(path "levels_to_hashes") in + let* protocols = Protocols.load mode ~path:(path "protocols") in let+ irmin_store = Irmin_store.load mode (path "irmin_store") in { l2_blocks; @@ -245,6 +305,7 @@ let load (type a) (mode : a mode) ~l2_blocks_cache_size data_dir : l2_head; last_finalized_level; levels_to_hashes; + protocols; irmin_store; } diff --git a/src/proto_alpha/lib_sc_rollup_node/store_v2.mli b/src/proto_alpha/lib_sc_rollup_node/store_v2.mli index dcb851d224f3..fb8e1bc4313f 100644 --- a/src/proto_alpha/lib_sc_rollup_node/store_v2.mli +++ b/src/proto_alpha/lib_sc_rollup_node/store_v2.mli @@ -57,6 +57,27 @@ module Commitments : and type value := Sc_rollup.Commitment.t and type header := unit +module Protocols : sig + type level = First_known of int32 | Activation_level of int32 + + (** Each element of this type represents information we have about a Tezos + protocol regarding its activation. *) + type proto_info = { + level : level; + (** The level at which we have seen the protocol for the first time, + either because we saw its activation or because the first block we + saw (at the origination of the rollup) was from this protocol. *) + proto_level : int; + (** The protocol level, i.e. its number in the sequence of protocol + activations on the chain. *) + protocol : Protocol_hash.t; (** The protocol this information concerns. *) + } + + val proto_info_encoding : proto_info Data_encoding.t + + include SINGLETON_STORE with type value = proto_info list +end + type +'a store = { l2_blocks : 'a L2_blocks.t; messages : 'a Messages.t; @@ -66,6 +87,7 @@ type +'a store = { l2_head : 'a L2_head.t; last_finalized_level : 'a Last_finalized_level.t; levels_to_hashes : 'a Levels_to_hashes.t; + protocols : 'a Protocols.t; irmin_store : 'a Irmin_store.t; } -- GitLab From 6d0e2f7431c17a6fcf37fbaddfad5117623ae572 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Thu, 4 May 2023 12:16:24 +0200 Subject: [PATCH 2/5] SCORU/Node: protocol information for level/block --- src/proto_alpha/lib_sc_rollup_node/daemon.ml | 2 +- .../lib_sc_rollup_node/node_context.ml | 148 ++++++++++++++++++ .../lib_sc_rollup_node/node_context.mli | 20 +++ 3 files changed, 169 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/lib_sc_rollup_node/daemon.ml b/src/proto_alpha/lib_sc_rollup_node/daemon.ml index 689d8e124ff6..b36b97802391 100644 --- a/src/proto_alpha/lib_sc_rollup_node/daemon.ml +++ b/src/proto_alpha/lib_sc_rollup_node/daemon.ml @@ -349,7 +349,7 @@ let rec process_head (node_ctxt : _ Node_context.t) ~catching_up Node_context.save_level node_ctxt {Layer1.hash = head.hash; level = head.level} - in + and* () = Node_context.save_protocol_info node_ctxt head ~predecessor in let* inbox_hash, inbox, inbox_witness, messages = Inbox.process_head node_ctxt ~predecessor head in diff --git a/src/proto_alpha/lib_sc_rollup_node/node_context.ml b/src/proto_alpha/lib_sc_rollup_node/node_context.ml index 0d70afb90b81..2f5d3b6b1410 100644 --- a/src/proto_alpha/lib_sc_rollup_node/node_context.ml +++ b/src/proto_alpha/lib_sc_rollup_node/node_context.ml @@ -877,6 +877,154 @@ let get_full_l2_block node_ctxt block_hash = in return {block with content = {Sc_rollup_block.inbox; messages; commitment}} +type proto_info = { + proto_level : int; + first_level_of_protocol : bool; + protocol : Protocol_hash.t; +} + +let protocol_of_level node_ctxt level = + let open Lwt_result_syntax in + assert (level >= Raw_level.to_int32 node_ctxt.genesis_info.level) ; + let* protocols = Store.Protocols.read node_ctxt.store.protocols in + let*? protocols = + match protocols with + | None | Some [] -> + error_with "Cannot infer protocol for level %ld: no protocol info" level + | Some protos -> Ok protos + in + let rec find = function + | [] -> + error_with "Cannot infer protocol for level %ld: no information" level + | {Store.Protocols.level = p_level; proto_level; protocol} :: protos -> ( + (* Latest protocols appear first in the list *) + match p_level with + | First_known l when level >= l -> + Ok {protocol; proto_level; first_level_of_protocol = false} + | Activation_level l when level > l -> + (* The block at the activation level is of the previous protocol, so + we are in the protocol that was activated at [l] only when the + level we query is after [l]. *) + Ok + { + protocol; + proto_level; + first_level_of_protocol = level = Int32.succ l; + } + | _ -> (find [@tailcall]) protos) + in + Lwt.return (find protocols) + +let save_protocol_info node_ctxt (block : Layer1.header) + ~(predecessor : Layer1.header) = + let open Lwt_result_syntax in + let* protocols = Store.Protocols.read node_ctxt.store.protocols in + match protocols with + | Some ({proto_level; _} :: _) + when proto_level = block.header.proto_level + && block.header.proto_level = predecessor.header.proto_level -> + (* Nominal case, no protocol change. Nothing to do. *) + return_unit + | None | Some [] -> + (* No protocols information saved in the rollup node yet, initialize with + information by looking at the current head and its predecessor. + We need to figure out if a protocol upgrade happened in one of these two blocks. + *) + let* {current_protocol; next_protocol} = + Tezos_shell_services.Shell_services.Blocks.protocols + node_ctxt.cctxt + ~block:(`Hash (block.hash, 0)) + () + and* { + current_protocol = pred_current_protocol; + next_protocol = pred_next_protocol; + } = + Tezos_shell_services.Shell_services.Blocks.protocols + node_ctxt.cctxt + ~block:(`Hash (predecessor.hash, 0)) + () + in + (* The first point in the protocol list is the one regarding + [predecessor]. If it is a migration block we register the activation + level, otherwise we don't go back any further and consider it as the + first known block of the protocol. *) + let pred_proto_info = + Store.Protocols. + { + level = + (if Protocol_hash.(pred_current_protocol = pred_next_protocol) + then First_known predecessor.level + else Activation_level predecessor.level); + proto_level = predecessor.header.proto_level; + protocol = pred_next_protocol; + } + in + let protocols = + if Protocol_hash.(current_protocol = next_protocol) then + (* There is no protocol upgrade in [head], so no new point to add the protocol list. *) + [pred_proto_info] + else + (* [head] is a migration block, add the new protocol with its activation in the list. *) + let proto_info = + Store.Protocols. + { + level = Activation_level block.level; + proto_level = block.header.proto_level; + protocol = next_protocol; + } + in + [proto_info; pred_proto_info] + in + Store.Protocols.write node_ctxt.store.protocols protocols + | Some + ({proto_level = last_proto_level; _} :: previous_protocols as protocols) + -> + (* block.header.proto_level <> last_proto_level or head is a migration + block, i.e. there is a protocol change w.r.t. last registered one. *) + let is_head_migration_block = + block.header.proto_level <> predecessor.header.proto_level + in + let* proto_info = + let+ {next_protocol = protocol; _} = + Tezos_shell_services.Shell_services.Blocks.protocols + node_ctxt.cctxt + ~block:(`Hash (block.hash, 0)) + () + in + let level = + if is_head_migration_block then + Store.Protocols.Activation_level block.level + else First_known block.level + in + Store.Protocols. + {level; proto_level = block.header.proto_level; protocol} + in + let protocols = + if block.header.proto_level > last_proto_level then + (* Protocol upgrade, add new item to protocol list *) + proto_info :: protocols + else if block.header.proto_level < last_proto_level then ( + (* Reorganization in which a protocol migration block was + backtracked. *) + match previous_protocols with + | [] -> + (* No info further back, store what we know. *) + [proto_info] + | previous_proto :: _ -> + (* make sure that we are in the case where we backtracked the + migration block. *) + assert ( + Protocol_hash.(proto_info.protocol = previous_proto.protocol)) ; + (* Remove last stored protocol *) + previous_protocols) + else + (* block.header.proto_level = last_proto_level && is_migration_block *) + (* Reorganization where we are doing a different protocol + upgrade. Replace last stored protocol. *) + proto_info :: previous_protocols + in + Store.Protocols.write node_ctxt.store.protocols protocols + let get_slot_header {store; _} ~published_in_block_hash slot_index = Error.trace_lwt_result_with "Could not retrieve slot header for slot index %a published in block %a" diff --git a/src/proto_alpha/lib_sc_rollup_node/node_context.mli b/src/proto_alpha/lib_sc_rollup_node/node_context.mli index 8d61848a6492..ae996303fb00 100644 --- a/src/proto_alpha/lib_sc_rollup_node/node_context.mli +++ b/src/proto_alpha/lib_sc_rollup_node/node_context.mli @@ -379,6 +379,26 @@ val save_messages : Sc_rollup.Inbox_message.t list -> unit tzresult Lwt.t +(** Return values for {!protocol_of_level}. *) +type proto_info = { + proto_level : int; + (** Protocol level for operations of block (can be different from L1 + header value in the case of a migration block). *) + first_level_of_protocol : bool; + (** [true] if the level is the first of the protocol. *) + protocol : Protocol_hash.t; + (** Hash of the {e current} protocol for this level. *) +} + +(** [protocol_of_level t level] returns the protocol of block level [level]. *) +val protocol_of_level : _ t -> int32 -> proto_info tzresult Lwt.t + +(** [save_protocol_info t block ~predecessor] saves to disk the protocol + information associated to the [block], if there is a protocol change + between [block] and [predecessor]. *) +val save_protocol_info : + rw -> Layer1.header -> predecessor:Layer1.header -> unit tzresult Lwt.t + (** {3 DAL} *) (** [get_slot_header t ~published_in_block_hash slot_index] returns the slot -- GitLab From a3fb86a4cfe8d9efa8852e1ae208438fbc5d9a06 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Fri, 12 May 2023 15:29:13 +0200 Subject: [PATCH 3/5] SCORU/Node: use protocol information to decide if block is first --- src/proto_alpha/lib_sc_rollup_node/inbox.ml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/proto_alpha/lib_sc_rollup_node/inbox.ml b/src/proto_alpha/lib_sc_rollup_node/inbox.ml index 8f725c9142b5..ff490d6cb7d9 100644 --- a/src/proto_alpha/lib_sc_rollup_node/inbox.ml +++ b/src/proto_alpha/lib_sc_rollup_node/inbox.ml @@ -172,14 +172,8 @@ let process_head (node_ctxt : _ Node_context.t) ~(predecessor : Layer1.header) head.level (List.length collected_messages) in - let* grandparent = - Node_context.get_predecessor_header node_ctxt predecessor - in - let is_first_block = - (* head is the first block of the protocol if its predecessor is a - migration block. *) - grandparent.header.proto_level <> predecessor.header.proto_level - in + let* head_proto = Node_context.protocol_of_level node_ctxt head.level in + let is_first_block = head_proto.first_level_of_protocol in process_messages node_ctxt ~is_first_block -- GitLab From 91e0c557e6318dc77f49f37fdfcf5acc7609d5f2 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Thu, 4 May 2023 10:51:14 +0200 Subject: [PATCH 4/5] SCORU/Node/Nairobi: backport !8648 - SCORU/Node/Store: singleton for storing protocol updates - SCORU/Node: protocol information for level/block - SCORU/Node: use protocol information to decide if block is first --- .../lib_sc_rollup_node/daemon.ml | 2 +- .../lib_sc_rollup_node/inbox.ml | 10 +- .../lib_sc_rollup_node/node_context.ml | 148 ++++++++++++++++++ .../lib_sc_rollup_node/node_context.mli | 20 +++ .../lib_sc_rollup_node/store_v2.ml | 63 +++++++- .../lib_sc_rollup_node/store_v2.mli | 22 +++ 6 files changed, 255 insertions(+), 10 deletions(-) diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/daemon.ml b/src/proto_017_PtNairob/lib_sc_rollup_node/daemon.ml index af7b5afbcfc1..0887106d956b 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/daemon.ml +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/daemon.ml @@ -349,7 +349,7 @@ let rec process_head (node_ctxt : _ Node_context.t) ~catching_up Node_context.save_level node_ctxt {Layer1.hash = head.hash; level = head.level} - in + and* () = Node_context.save_protocol_info node_ctxt head ~predecessor in let* inbox_hash, inbox, inbox_witness, messages = Inbox.process_head node_ctxt ~predecessor head in diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/inbox.ml b/src/proto_017_PtNairob/lib_sc_rollup_node/inbox.ml index 9d54bcb1af3f..40c50eda8acc 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/inbox.ml +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/inbox.ml @@ -172,14 +172,8 @@ let process_head (node_ctxt : _ Node_context.t) ~(predecessor : Layer1.header) head.level (List.length collected_messages) in - let* grandparent = - Node_context.get_predecessor_header node_ctxt predecessor - in - let is_first_block = - (* head is the first block of the protocol if its predecessor is a - migration block. *) - grandparent.header.proto_level <> predecessor.header.proto_level - in + let* head_proto = Node_context.protocol_of_level node_ctxt head.level in + let is_first_block = head_proto.first_level_of_protocol in process_messages node_ctxt ~is_first_block diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/node_context.ml b/src/proto_017_PtNairob/lib_sc_rollup_node/node_context.ml index e8efb161e2c9..fa06ce3e7861 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/node_context.ml +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/node_context.ml @@ -874,6 +874,154 @@ let get_full_l2_block node_ctxt block_hash = in return {block with content = {Sc_rollup_block.inbox; messages; commitment}} +type proto_info = { + proto_level : int; + first_level_of_protocol : bool; + protocol : Protocol_hash.t; +} + +let protocol_of_level node_ctxt level = + let open Lwt_result_syntax in + assert (level >= Raw_level.to_int32 node_ctxt.genesis_info.level) ; + let* protocols = Store.Protocols.read node_ctxt.store.protocols in + let*? protocols = + match protocols with + | None | Some [] -> + error_with "Cannot infer protocol for level %ld: no protocol info" level + | Some protos -> Ok protos + in + let rec find = function + | [] -> + error_with "Cannot infer protocol for level %ld: no information" level + | {Store.Protocols.level = p_level; proto_level; protocol} :: protos -> ( + (* Latest protocols appear first in the list *) + match p_level with + | First_known l when level >= l -> + Ok {protocol; proto_level; first_level_of_protocol = false} + | Activation_level l when level > l -> + (* The block at the activation level is of the previous protocol, so + we are in the protocol that was activated at [l] only when the + level we query is after [l]. *) + Ok + { + protocol; + proto_level; + first_level_of_protocol = level = Int32.succ l; + } + | _ -> (find [@tailcall]) protos) + in + Lwt.return (find protocols) + +let save_protocol_info node_ctxt (block : Layer1.header) + ~(predecessor : Layer1.header) = + let open Lwt_result_syntax in + let* protocols = Store.Protocols.read node_ctxt.store.protocols in + match protocols with + | Some ({proto_level; _} :: _) + when proto_level = block.header.proto_level + && block.header.proto_level = predecessor.header.proto_level -> + (* Nominal case, no protocol change. Nothing to do. *) + return_unit + | None | Some [] -> + (* No protocols information saved in the rollup node yet, initialize with + information by looking at the current head and its predecessor. + We need to figure out if a protocol upgrade happened in one of these two blocks. + *) + let* {current_protocol; next_protocol} = + Tezos_shell_services.Shell_services.Blocks.protocols + node_ctxt.cctxt + ~block:(`Hash (block.hash, 0)) + () + and* { + current_protocol = pred_current_protocol; + next_protocol = pred_next_protocol; + } = + Tezos_shell_services.Shell_services.Blocks.protocols + node_ctxt.cctxt + ~block:(`Hash (predecessor.hash, 0)) + () + in + (* The first point in the protocol list is the one regarding + [predecessor]. If it is a migration block we register the activation + level, otherwise we don't go back any further and consider it as the + first known block of the protocol. *) + let pred_proto_info = + Store.Protocols. + { + level = + (if Protocol_hash.(pred_current_protocol = pred_next_protocol) + then First_known predecessor.level + else Activation_level predecessor.level); + proto_level = predecessor.header.proto_level; + protocol = pred_next_protocol; + } + in + let protocols = + if Protocol_hash.(current_protocol = next_protocol) then + (* There is no protocol upgrade in [head], so no new point to add the protocol list. *) + [pred_proto_info] + else + (* [head] is a migration block, add the new protocol with its activation in the list. *) + let proto_info = + Store.Protocols. + { + level = Activation_level block.level; + proto_level = block.header.proto_level; + protocol = next_protocol; + } + in + [proto_info; pred_proto_info] + in + Store.Protocols.write node_ctxt.store.protocols protocols + | Some + ({proto_level = last_proto_level; _} :: previous_protocols as protocols) + -> + (* block.header.proto_level <> last_proto_level or head is a migration + block, i.e. there is a protocol change w.r.t. last registered one. *) + let is_head_migration_block = + block.header.proto_level <> predecessor.header.proto_level + in + let* proto_info = + let+ {next_protocol = protocol; _} = + Tezos_shell_services.Shell_services.Blocks.protocols + node_ctxt.cctxt + ~block:(`Hash (block.hash, 0)) + () + in + let level = + if is_head_migration_block then + Store.Protocols.Activation_level block.level + else First_known block.level + in + Store.Protocols. + {level; proto_level = block.header.proto_level; protocol} + in + let protocols = + if block.header.proto_level > last_proto_level then + (* Protocol upgrade, add new item to protocol list *) + proto_info :: protocols + else if block.header.proto_level < last_proto_level then ( + (* Reorganization in which a protocol migration block was + backtracked. *) + match previous_protocols with + | [] -> + (* No info further back, store what we know. *) + [proto_info] + | previous_proto :: _ -> + (* make sure that we are in the case where we backtracked the + migration block. *) + assert ( + Protocol_hash.(proto_info.protocol = previous_proto.protocol)) ; + (* Remove last stored protocol *) + previous_protocols) + else + (* block.header.proto_level = last_proto_level && is_migration_block *) + (* Reorganization where we are doing a different protocol + upgrade. Replace last stored protocol. *) + proto_info :: previous_protocols + in + Store.Protocols.write node_ctxt.store.protocols protocols + let get_slot_header {store; _} ~published_in_block_hash slot_index = Error.trace_lwt_result_with "Could not retrieve slot header for slot index %a published in block %a" diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/node_context.mli b/src/proto_017_PtNairob/lib_sc_rollup_node/node_context.mli index 292952b71810..21b99c5872a4 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/node_context.mli +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/node_context.mli @@ -374,6 +374,26 @@ val save_messages : Sc_rollup.Inbox_message.t list -> unit tzresult Lwt.t +(** Return values for {!protocol_of_level}. *) +type proto_info = { + proto_level : int; + (** Protocol level for operations of block (can be different from L1 + header value in the case of a migration block). *) + first_level_of_protocol : bool; + (** [true] if the level is the first of the protocol. *) + protocol : Protocol_hash.t; + (** Hash of the {e current} protocol for this level. *) +} + +(** [protocol_of_level t level] returns the protocol of block level [level]. *) +val protocol_of_level : _ t -> int32 -> proto_info tzresult Lwt.t + +(** [save_protocol_info t block ~predecessor] saves to disk the protocol + information associated to the [block], if there is a protocol change + between [block] and [predecessor]. *) +val save_protocol_info : + rw -> Layer1.header -> predecessor:Layer1.header -> unit tzresult Lwt.t + (** {3 DAL} *) (** [get_slot_header t ~published_in_block_hash slot_index] returns the slot diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/store_v2.ml b/src/proto_017_PtNairob/lib_sc_rollup_node/store_v2.ml index c8363e787eff..18b77336aa55 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/store_v2.ml +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/store_v2.ml @@ -145,7 +145,62 @@ module Commitments = include Add_empty_header end) -type nonrec 'a store = { +module Protocols = struct + type level = First_known of int32 | Activation_level of int32 + + type proto_info = { + level : level; + proto_level : int; + protocol : Protocol_hash.t; + } + + type value = proto_info list + + let level_encoding = + let open Data_encoding in + conv + (function First_known l -> (l, false) | Activation_level l -> (l, true)) + (function l, false -> First_known l | l, true -> Activation_level l) + @@ obj2 (req "level" int32) (req "activates" bool) + + let proto_info_encoding = + let open Data_encoding in + conv + (fun {level; proto_level; protocol} -> (level, proto_level, protocol)) + (fun (level, proto_level, protocol) -> {level; proto_level; protocol}) + @@ obj3 + (req "level" level_encoding) + (req "proto_level" int31) + (req "protocol" Protocol_hash.encoding) + + include Indexed_store.Make_singleton (struct + type t = value + + let name = "protocols" + + let level_encoding = + let open Data_encoding in + conv + (function + | First_known l -> (l, false) | Activation_level l -> (l, true)) + (function l, false -> First_known l | l, true -> Activation_level l) + @@ obj2 (req "level" int32) (req "activates" bool) + + let proto_info_encoding = + let open Data_encoding in + conv + (fun {level; proto_level; protocol} -> (level, proto_level, protocol)) + (fun (level, proto_level, protocol) -> {level; proto_level; protocol}) + @@ obj3 + (req "level" level_encoding) + (req "proto_level" int31) + (req "protocol" Protocol_hash.encoding) + + let encoding = Data_encoding.list proto_info_encoding + end) +end + +type 'a store = { l2_blocks : 'a L2_blocks.t; messages : 'a Messages.t; inboxes : 'a Inboxes.t; @@ -154,6 +209,7 @@ type nonrec 'a store = { l2_head : 'a L2_head.t; last_finalized_level : 'a Last_finalized_level.t; levels_to_hashes : 'a Levels_to_hashes.t; + protocols : 'a Protocols.t; irmin_store : 'a Irmin_store.t; } @@ -173,6 +229,7 @@ let readonly l2_head; last_finalized_level; levels_to_hashes; + protocols; irmin_store; } : _ t) : ro = @@ -186,6 +243,7 @@ let readonly l2_head = L2_head.readonly l2_head; last_finalized_level = Last_finalized_level.readonly last_finalized_level; levels_to_hashes = Levels_to_hashes.readonly levels_to_hashes; + protocols = Protocols.readonly protocols; irmin_store = Irmin_store.readonly irmin_store; } @@ -199,6 +257,7 @@ let close l2_head = _; last_finalized_level = _; levels_to_hashes; + protocols = _; irmin_store; } : _ t) = @@ -235,6 +294,7 @@ let load (type a) (mode : a mode) ~l2_blocks_cache_size data_dir : let* levels_to_hashes = Levels_to_hashes.load mode ~path:(path "levels_to_hashes") in + let* protocols = Protocols.load mode ~path:(path "protocols") in let+ irmin_store = Irmin_store.load mode (path "irmin_store") in { l2_blocks; @@ -245,6 +305,7 @@ let load (type a) (mode : a mode) ~l2_blocks_cache_size data_dir : l2_head; last_finalized_level; levels_to_hashes; + protocols; irmin_store; } diff --git a/src/proto_017_PtNairob/lib_sc_rollup_node/store_v2.mli b/src/proto_017_PtNairob/lib_sc_rollup_node/store_v2.mli index dcb851d224f3..fb8e1bc4313f 100644 --- a/src/proto_017_PtNairob/lib_sc_rollup_node/store_v2.mli +++ b/src/proto_017_PtNairob/lib_sc_rollup_node/store_v2.mli @@ -57,6 +57,27 @@ module Commitments : and type value := Sc_rollup.Commitment.t and type header := unit +module Protocols : sig + type level = First_known of int32 | Activation_level of int32 + + (** Each element of this type represents information we have about a Tezos + protocol regarding its activation. *) + type proto_info = { + level : level; + (** The level at which we have seen the protocol for the first time, + either because we saw its activation or because the first block we + saw (at the origination of the rollup) was from this protocol. *) + proto_level : int; + (** The protocol level, i.e. its number in the sequence of protocol + activations on the chain. *) + protocol : Protocol_hash.t; (** The protocol this information concerns. *) + } + + val proto_info_encoding : proto_info Data_encoding.t + + include SINGLETON_STORE with type value = proto_info list +end + type +'a store = { l2_blocks : 'a L2_blocks.t; messages : 'a Messages.t; @@ -66,6 +87,7 @@ type +'a store = { l2_head : 'a L2_head.t; last_finalized_level : 'a Last_finalized_level.t; levels_to_hashes : 'a Levels_to_hashes.t; + protocols : 'a Protocols.t; irmin_store : 'a Irmin_store.t; } -- GitLab From 43edb1a497e48461be57a1a61e949dd2ce44fd48 Mon Sep 17 00:00:00 2001 From: Alain Mebsout Date: Thu, 4 May 2023 10:51:14 +0200 Subject: [PATCH 5/5] SCORU/Node/Mumbai: backport !8648 - SCORU/Node/Store: singleton for storing protocol updates - SCORU/Node: protocol information for level/block --- .../lib_sc_rollup_node/daemon.ml | 2 +- .../lib_sc_rollup_node/node_context.ml | 148 ++++++++++++++++++ .../lib_sc_rollup_node/node_context.mli | 20 +++ .../lib_sc_rollup_node/store_v2.ml | 63 +++++++- .../lib_sc_rollup_node/store_v2.mli | 22 +++ 5 files changed, 253 insertions(+), 2 deletions(-) diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/daemon.ml b/src/proto_016_PtMumbai/lib_sc_rollup_node/daemon.ml index 5d4b2e2b5dd7..d6ea37809f74 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/daemon.ml +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/daemon.ml @@ -351,7 +351,7 @@ let rec process_head (node_ctxt : _ Node_context.t) ~catching_up Node_context.save_level node_ctxt {Layer1.hash = head.hash; level = head.level} - in + and* () = Node_context.save_protocol_info node_ctxt head ~predecessor in let* inbox_hash, inbox, inbox_witness, messages = Inbox.process_head node_ctxt ~predecessor head in diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/node_context.ml b/src/proto_016_PtMumbai/lib_sc_rollup_node/node_context.ml index 361ab3f8e859..69a87e1b2d59 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/node_context.ml +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/node_context.ml @@ -868,6 +868,154 @@ let get_full_l2_block node_ctxt block_hash = in return {block with content = {Sc_rollup_block.inbox; messages; commitment}} +type proto_info = { + proto_level : int; + first_level_of_protocol : bool; + protocol : Protocol_hash.t; +} + +let protocol_of_level node_ctxt level = + let open Lwt_result_syntax in + assert (level >= Raw_level.to_int32 node_ctxt.genesis_info.level) ; + let* protocols = Store.Protocols.read node_ctxt.store.protocols in + let*? protocols = + match protocols with + | None | Some [] -> + error_with "Cannot infer protocol for level %ld: no protocol info" level + | Some protos -> Ok protos + in + let rec find = function + | [] -> + error_with "Cannot infer protocol for level %ld: no information" level + | {Store.Protocols.level = p_level; proto_level; protocol} :: protos -> ( + (* Latest protocols appear first in the list *) + match p_level with + | First_known l when level >= l -> + Ok {protocol; proto_level; first_level_of_protocol = false} + | Activation_level l when level > l -> + (* The block at the activation level is of the previous protocol, so + we are in the protocol that was activated at [l] only when the + level we query is after [l]. *) + Ok + { + protocol; + proto_level; + first_level_of_protocol = level = Int32.succ l; + } + | _ -> (find [@tailcall]) protos) + in + Lwt.return (find protocols) + +let save_protocol_info node_ctxt (block : Layer1.header) + ~(predecessor : Layer1.header) = + let open Lwt_result_syntax in + let* protocols = Store.Protocols.read node_ctxt.store.protocols in + match protocols with + | Some ({proto_level; _} :: _) + when proto_level = block.header.proto_level + && block.header.proto_level = predecessor.header.proto_level -> + (* Nominal case, no protocol change. Nothing to do. *) + return_unit + | None | Some [] -> + (* No protocols information saved in the rollup node yet, initialize with + information by looking at the current head and its predecessor. + We need to figure out if a protocol upgrade happened in one of these two blocks. + *) + let* {current_protocol; next_protocol} = + Tezos_shell_services.Shell_services.Blocks.protocols + node_ctxt.cctxt + ~block:(`Hash (block.hash, 0)) + () + and* { + current_protocol = pred_current_protocol; + next_protocol = pred_next_protocol; + } = + Tezos_shell_services.Shell_services.Blocks.protocols + node_ctxt.cctxt + ~block:(`Hash (predecessor.hash, 0)) + () + in + (* The first point in the protocol list is the one regarding + [predecessor]. If it is a migration block we register the activation + level, otherwise we don't go back any further and consider it as the + first known block of the protocol. *) + let pred_proto_info = + Store.Protocols. + { + level = + (if Protocol_hash.(pred_current_protocol = pred_next_protocol) + then First_known predecessor.level + else Activation_level predecessor.level); + proto_level = predecessor.header.proto_level; + protocol = pred_next_protocol; + } + in + let protocols = + if Protocol_hash.(current_protocol = next_protocol) then + (* There is no protocol upgrade in [head], so no new point to add the protocol list. *) + [pred_proto_info] + else + (* [head] is a migration block, add the new protocol with its activation in the list. *) + let proto_info = + Store.Protocols. + { + level = Activation_level block.level; + proto_level = block.header.proto_level; + protocol = next_protocol; + } + in + [proto_info; pred_proto_info] + in + Store.Protocols.write node_ctxt.store.protocols protocols + | Some + ({proto_level = last_proto_level; _} :: previous_protocols as protocols) + -> + (* block.header.proto_level <> last_proto_level or head is a migration + block, i.e. there is a protocol change w.r.t. last registered one. *) + let is_head_migration_block = + block.header.proto_level <> predecessor.header.proto_level + in + let* proto_info = + let+ {next_protocol = protocol; _} = + Tezos_shell_services.Shell_services.Blocks.protocols + node_ctxt.cctxt + ~block:(`Hash (block.hash, 0)) + () + in + let level = + if is_head_migration_block then + Store.Protocols.Activation_level block.level + else First_known block.level + in + Store.Protocols. + {level; proto_level = block.header.proto_level; protocol} + in + let protocols = + if block.header.proto_level > last_proto_level then + (* Protocol upgrade, add new item to protocol list *) + proto_info :: protocols + else if block.header.proto_level < last_proto_level then ( + (* Reorganization in which a protocol migration block was + backtracked. *) + match previous_protocols with + | [] -> + (* No info further back, store what we know. *) + [proto_info] + | previous_proto :: _ -> + (* make sure that we are in the case where we backtracked the + migration block. *) + assert ( + Protocol_hash.(proto_info.protocol = previous_proto.protocol)) ; + (* Remove last stored protocol *) + previous_protocols) + else + (* block.header.proto_level = last_proto_level && is_migration_block *) + (* Reorganization where we are doing a different protocol + upgrade. Replace last stored protocol. *) + proto_info :: previous_protocols + in + Store.Protocols.write node_ctxt.store.protocols protocols + let get_slot_header {store; _} ~published_in_block_hash slot_index = Error.trace_lwt_result_with "Could not retrieve slot header for slot index %a published in block %a" diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/node_context.mli b/src/proto_016_PtMumbai/lib_sc_rollup_node/node_context.mli index f2fdb44a470f..4eace29c4523 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/node_context.mli +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/node_context.mli @@ -375,6 +375,26 @@ val save_messages : Sc_rollup.Inbox_message.t list -> unit tzresult Lwt.t +(** Return values for {!protocol_of_level}. *) +type proto_info = { + proto_level : int; + (** Protocol level for operations of block (can be different from L1 + header value in the case of a migration block). *) + first_level_of_protocol : bool; + (** [true] if the level is the first of the protocol. *) + protocol : Protocol_hash.t; + (** Hash of the {e current} protocol for this level. *) +} + +(** [protocol_of_level t level] returns the protocol of block level [level]. *) +val protocol_of_level : _ t -> int32 -> proto_info tzresult Lwt.t + +(** [save_protocol_info t block ~predecessor] saves to disk the protocol + information associated to the [block], if there is a protocol change + between [block] and [predecessor]. *) +val save_protocol_info : + rw -> Layer1.header -> predecessor:Layer1.header -> unit tzresult Lwt.t + (** {3 DAL} *) (** [get_slot_header t ~published_in_block_hash slot_index] returns the slot diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/store_v2.ml b/src/proto_016_PtMumbai/lib_sc_rollup_node/store_v2.ml index c8363e787eff..18b77336aa55 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/store_v2.ml +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/store_v2.ml @@ -145,7 +145,62 @@ module Commitments = include Add_empty_header end) -type nonrec 'a store = { +module Protocols = struct + type level = First_known of int32 | Activation_level of int32 + + type proto_info = { + level : level; + proto_level : int; + protocol : Protocol_hash.t; + } + + type value = proto_info list + + let level_encoding = + let open Data_encoding in + conv + (function First_known l -> (l, false) | Activation_level l -> (l, true)) + (function l, false -> First_known l | l, true -> Activation_level l) + @@ obj2 (req "level" int32) (req "activates" bool) + + let proto_info_encoding = + let open Data_encoding in + conv + (fun {level; proto_level; protocol} -> (level, proto_level, protocol)) + (fun (level, proto_level, protocol) -> {level; proto_level; protocol}) + @@ obj3 + (req "level" level_encoding) + (req "proto_level" int31) + (req "protocol" Protocol_hash.encoding) + + include Indexed_store.Make_singleton (struct + type t = value + + let name = "protocols" + + let level_encoding = + let open Data_encoding in + conv + (function + | First_known l -> (l, false) | Activation_level l -> (l, true)) + (function l, false -> First_known l | l, true -> Activation_level l) + @@ obj2 (req "level" int32) (req "activates" bool) + + let proto_info_encoding = + let open Data_encoding in + conv + (fun {level; proto_level; protocol} -> (level, proto_level, protocol)) + (fun (level, proto_level, protocol) -> {level; proto_level; protocol}) + @@ obj3 + (req "level" level_encoding) + (req "proto_level" int31) + (req "protocol" Protocol_hash.encoding) + + let encoding = Data_encoding.list proto_info_encoding + end) +end + +type 'a store = { l2_blocks : 'a L2_blocks.t; messages : 'a Messages.t; inboxes : 'a Inboxes.t; @@ -154,6 +209,7 @@ type nonrec 'a store = { l2_head : 'a L2_head.t; last_finalized_level : 'a Last_finalized_level.t; levels_to_hashes : 'a Levels_to_hashes.t; + protocols : 'a Protocols.t; irmin_store : 'a Irmin_store.t; } @@ -173,6 +229,7 @@ let readonly l2_head; last_finalized_level; levels_to_hashes; + protocols; irmin_store; } : _ t) : ro = @@ -186,6 +243,7 @@ let readonly l2_head = L2_head.readonly l2_head; last_finalized_level = Last_finalized_level.readonly last_finalized_level; levels_to_hashes = Levels_to_hashes.readonly levels_to_hashes; + protocols = Protocols.readonly protocols; irmin_store = Irmin_store.readonly irmin_store; } @@ -199,6 +257,7 @@ let close l2_head = _; last_finalized_level = _; levels_to_hashes; + protocols = _; irmin_store; } : _ t) = @@ -235,6 +294,7 @@ let load (type a) (mode : a mode) ~l2_blocks_cache_size data_dir : let* levels_to_hashes = Levels_to_hashes.load mode ~path:(path "levels_to_hashes") in + let* protocols = Protocols.load mode ~path:(path "protocols") in let+ irmin_store = Irmin_store.load mode (path "irmin_store") in { l2_blocks; @@ -245,6 +305,7 @@ let load (type a) (mode : a mode) ~l2_blocks_cache_size data_dir : l2_head; last_finalized_level; levels_to_hashes; + protocols; irmin_store; } diff --git a/src/proto_016_PtMumbai/lib_sc_rollup_node/store_v2.mli b/src/proto_016_PtMumbai/lib_sc_rollup_node/store_v2.mli index dcb851d224f3..fb8e1bc4313f 100644 --- a/src/proto_016_PtMumbai/lib_sc_rollup_node/store_v2.mli +++ b/src/proto_016_PtMumbai/lib_sc_rollup_node/store_v2.mli @@ -57,6 +57,27 @@ module Commitments : and type value := Sc_rollup.Commitment.t and type header := unit +module Protocols : sig + type level = First_known of int32 | Activation_level of int32 + + (** Each element of this type represents information we have about a Tezos + protocol regarding its activation. *) + type proto_info = { + level : level; + (** The level at which we have seen the protocol for the first time, + either because we saw its activation or because the first block we + saw (at the origination of the rollup) was from this protocol. *) + proto_level : int; + (** The protocol level, i.e. its number in the sequence of protocol + activations on the chain. *) + protocol : Protocol_hash.t; (** The protocol this information concerns. *) + } + + val proto_info_encoding : proto_info Data_encoding.t + + include SINGLETON_STORE with type value = proto_info list +end + type +'a store = { l2_blocks : 'a L2_blocks.t; messages : 'a Messages.t; @@ -66,6 +87,7 @@ type +'a store = { l2_head : 'a L2_head.t; last_finalized_level : 'a Last_finalized_level.t; levels_to_hashes : 'a Levels_to_hashes.t; + protocols : 'a Protocols.t; irmin_store : 'a Irmin_store.t; } -- GitLab