diff --git a/src/lib_dal_node/daemon.ml b/src/lib_dal_node/daemon.ml index 1e714753c8bd00ea773c065d85bfc2eedf352968..1c5b4d2d85f31e1c8e3054cb76a6bf733780d015 100644 --- a/src/lib_dal_node/daemon.ml +++ b/src/lib_dal_node/daemon.ml @@ -298,20 +298,14 @@ let run ~data_dir ~configuration_override = let* p2p_config = Transport_layer_parameters.p2p_config config in let p2p_limits = Transport_layer_parameters.p2p_limits in (* Get the current L1 head and its DAL plugin and parameters. *) - let* header, (module Plugin : Dal_plugin.T) = - L1_helpers.wait_for_block_with_plugin cctxt - in + let* header, proto_plugins = L1_helpers.wait_for_block_with_plugin cctxt in let head_level = header.Block_header.shell.level in - let* proto_parameters = - Plugin.get_constants `Main (`Level head_level) cctxt - in - let proto_plugins = - Proto_plugins.singleton - ~first_level:head_level - ~proto_level:header.shell.proto_level - (module Plugin) - proto_parameters + let*? _plugin, proto_parameters = + Proto_plugins.get_plugin_and_parameters_for_level + proto_plugins + ~level:head_level in + (* Set proto number of slots hook. *) Value_size_hooks.set_number_of_slots proto_parameters.number_of_slots ; let* profile_ctxt = @@ -489,16 +483,6 @@ let run ~data_dir ~configuration_override = (* Wait for the L1 node to be bootstrapped. *) Node_context.(set_l1_crawler_status ctxt L1_bootstrapping) ; let* () = L1_helpers.wait_for_l1_bootstrapped cctxt in - let* proto_plugins = - Proto_plugins.get_proto_plugins - cctxt - profile_ctxt - ~last_processed_level - ~first_seen_level - ~head_level - proto_parameters - in - Node_context.set_proto_plugins ctxt proto_plugins ; let* () = match last_processed_level with | None -> (* there's nothing to clean up *) return_unit diff --git a/src/lib_dal_node/event.ml b/src/lib_dal_node/event.ml index d2cc0d4f2040aafa02eae6b841b29da5bbb75f7b..ba958e9d0ec8a8adfa8b04f66751cd01449dc273 100644 --- a/src/lib_dal_node/event.ml +++ b/src/lib_dal_node/event.ml @@ -235,14 +235,17 @@ open struct () let protocol_plugin_resolved = - declare_1 + declare_2 ~section ~prefix_name_with_section:true ~name:"plugin_resolved" - ~msg:"resolved plugin on protocol {proto_hash}" + ~msg: + "resolved plugin on protocol {proto_hash} that starts at level \ + {start_level}" ~level:Notice ~pp1:Protocol_hash.pp_short ("proto_hash", Protocol_hash.encoding) + ("start_level", Data_encoding.int32) let no_protocol_plugin = declare_1 @@ -1148,8 +1151,8 @@ let emit_layer1_node_final_block ~hash ~level ~round = let emit_layer1_node_tracking_started () = emit layer1_node_tracking_started () -let emit_protocol_plugin_resolved ~proto_hash = - emit protocol_plugin_resolved proto_hash +let emit_protocol_plugin_resolved ~proto_hash ~start_level = + emit protocol_plugin_resolved (proto_hash, start_level) let emit_no_protocol_plugin ~proto_hash = emit no_protocol_plugin proto_hash diff --git a/src/lib_dal_node/l1_helpers.ml b/src/lib_dal_node/l1_helpers.ml index 5a819946dd4273c325dac446452f09b3e2327d1f..d9db712e83b56dda5fc27f45331f7fa572debde1 100644 --- a/src/lib_dal_node/l1_helpers.ml +++ b/src/lib_dal_node/l1_helpers.ml @@ -110,18 +110,15 @@ let wait_for_block_with_plugin (cctxt : Rpc_context.t) = let*! head_opt = Lwt_stream.get stream in match head_opt with | None -> failwith "Lost the connection with the L1 node" - | Some (_hash, header) -> ( - let*! res = - Proto_plugins.resolve_plugin_for_level + | Some (_hash, header) -> + let* proto_plugins = + Proto_plugins.get_supported_proto_plugins cctxt - ~level:header.Block_header.shell.level + ~head_level:header.Block_header.shell.level in - match res with - | Error [Proto_plugins.No_plugin_for_proto _] -> wait_for_level () - | Error err -> - failwith "Unexpected error: %a" Error_monad.pp_print_trace err - | Ok (module Plugin : Dal_plugin.T) -> - let () = stop () in - return (header, (module Plugin : Dal_plugin.T))) + if Proto_plugins.has_plugins proto_plugins then + let () = stop () in + return (header, proto_plugins) + else wait_for_level () in wait_for_level () diff --git a/src/lib_dal_node/l1_helpers.mli b/src/lib_dal_node/l1_helpers.mli index 21d5c91fac46ebbaea1f1af09aadbb07209ef9fb..4906f24bdb141b3dadd0682d5f9f8d659ebe0257 100644 --- a/src/lib_dal_node/l1_helpers.mli +++ b/src/lib_dal_node/l1_helpers.mli @@ -41,4 +41,4 @@ val infer_dal_network_name : (** [wait_for_block_with_plugin cctxt] waits until a block is available with a known DAL plugin, and returns its header along with the plugin module. *) val wait_for_block_with_plugin : - Rpc_context.t -> (Block_header.t * (module Dal_plugin.T)) tzresult Lwt.t + Rpc_context.t -> (Block_header.t * Proto_plugins.t) tzresult Lwt.t diff --git a/src/lib_dal_node/proto_plugins.ml b/src/lib_dal_node/proto_plugins.ml index 6eb9007f1f9fa9417bc6d04ab69ed92fcf27117e..3b552899d94bdaefb9c9c195fb803cf5becdc836 100644 --- a/src/lib_dal_node/proto_plugins.ml +++ b/src/lib_dal_node/proto_plugins.ml @@ -55,7 +55,8 @@ let () = let last_failed_protocol = ref None -let resolve_plugin_by_hash proto_hash = +let resolve_plugin_by_hash ?(emit_failure_event = true) ~start_level proto_hash + = let open Lwt_result_syntax in let plugin_opt = Dal_plugin.get proto_hash in match plugin_opt with @@ -65,128 +66,24 @@ let resolve_plugin_by_hash proto_hash = | Some hash when Protocol_hash.equal hash proto_hash -> Lwt.return_unit | _ -> last_failed_protocol := Some proto_hash ; - Event.emit_no_protocol_plugin ~proto_hash + if emit_failure_event then Event.emit_no_protocol_plugin ~proto_hash + else Lwt.return_unit in tzfail (No_plugin_for_proto {proto_hash}) | Some plugin -> - let*! () = Event.emit_protocol_plugin_resolved ~proto_hash in + let*! () = Event.emit_protocol_plugin_resolved ~proto_hash ~start_level in return plugin -let resolve_plugin_for_level cctxt ~level = - let open Lwt_result_syntax in - let* protocols = - Chain_services.Blocks.protocols cctxt ~block:(`Level level) () - in - let proto_hash = protocols.next_protocol in - resolve_plugin_by_hash proto_hash - -let add_plugin_for_level cctxt plugins - (protocols : Chain_services.Blocks.protocols) ~level = - let open Lwt_result_syntax in - let* plugin = resolve_plugin_by_hash protocols.next_protocol in - let block = `Level level in - let* header = Shell_services.Blocks.Header.shell_header cctxt ~block () in - let proto_level = header.proto_level in - let (module Plugin) = plugin in - let+ proto_parameters = Plugin.get_constants `Main block cctxt in - Plugins.add plugins ~first_level:level ~proto_level plugin proto_parameters - -(* This function performs a (kind of) binary search to search for all values - that satisfy the given condition [cond] on values. There is bijection between - values and levels (which are here just positive int32 integers). The - functions [to_level] and [from_level] retrieve the associates levels/values - from given values/levels (respectively). The function [no_satisfying_value l1 - l2] returns true iff no value satisfying [cond] is present in the interval - [l1, l2] (both inclusive). The search is performed between (the levels - associated to) the values [first] and [last] (both inclusive). *) -let binary_search (cond : 'a -> bool) (no_satisfying_value : 'a -> 'a -> bool) - (to_level : 'a -> int32) (from_level : int32 -> 'a tzresult Lwt.t) - ~(first : 'a) ~(last : 'a) = - let open Lwt_result_syntax in - let rec search ~first ~last acc = - if no_satisfying_value first last then return acc - else - let first_level = to_level first in - let last_level = to_level last in - if first_level >= last_level then - (* search ended *) - if cond last then return (last :: acc) else return acc - else if Int32.succ first_level = last_level then - (* search ended as well *) - let acc = if cond last then last :: acc else acc in - return @@ if cond first then first :: acc else acc - else - let mid_level = - Int32.(add first_level (div (sub last_level first_level) 2l)) - in - let* mid = from_level mid_level in - let* acc = search ~first ~last:mid acc in - search ~first:mid ~last acc - in - search ~first ~last [] - -let migration protocols = - not - @@ Protocol_hash.equal - protocols.Chain_services.Blocks.current_protocol - protocols.Chain_services.Blocks.next_protocol - -type level_with_protos = { - level : int32; - protocols : Chain_services.Blocks.protocols; -} - -(* Return the smallest levels between [first_level] and [last_level] for which a - different plugin should be added. *) -let find_first_levels cctxt ~first_level ~last_level = - let open Lwt_result_syntax in - let to_level {level; _} = level in - let from_level level = - let* protocols = - Chain_services.Blocks.protocols cctxt ~block:(`Level level) () - in - return {level; protocols} - in - let cond {protocols; _} = migration protocols in - let no_satisfying_value first last = - let first_proto = first.protocols.Chain_services.Blocks.next_protocol in - let last_proto = last.protocols.Chain_services.Blocks.next_protocol in - Protocol_hash.equal first_proto last_proto - in - let* first = from_level first_level in - let* last = from_level last_level in - (* Performs a binary search between [first_working_level] and [last_level] - to search for migration levels. *) - let* migration_levels = - binary_search cond no_satisfying_value to_level from_level ~first ~last - in - let sorted_levels = - List.sort - (fun {level = level1; _} {level = level2; _} -> - Int32.compare level1 level2) - migration_levels - in - (* We need to add the plugin for [first_level] even if it's not a migration - level. *) - match sorted_levels with - | [] -> return [first] - | {level; _} :: _ when first.level <> level -> return (first :: sorted_levels) - | _ -> return sorted_levels - -let initial_plugins cctxt ~first_level ~last_level = - let open Lwt_result_syntax in - let* first_levels = find_first_levels cctxt ~first_level ~last_level in - List.fold_left_es - (fun plugins {level; protocols} -> - add_plugin_for_level cctxt plugins protocols ~level) - Plugins.empty - first_levels - let may_add cctxt plugins ~first_level ~proto_level = let open Lwt_result_syntax in let add first_level = - let* plugin = resolve_plugin_for_level cctxt ~level:first_level in - let (module Plugin) = plugin in + let* protocols = + Chain_services.Blocks.protocols cctxt ~block:(`Level first_level) () + in + let proto_hash = protocols.next_protocol in + let* ((module Plugin) as plugin) = + resolve_plugin_by_hash proto_hash ~start_level:first_level + in let+ proto_parameters = Plugin.get_constants `Main (`Level first_level) cctxt in @@ -230,39 +127,57 @@ let get_plugin_and_parameters_for_level plugins ~level = include Plugins -(* This function fetches the protocol plugins for levels in the past for which - the node may need a plugin, namely for adding skip list cells, or for - obtaining the protocol parameters. - - Concerning the skip list, getting the plugin is (almost) necessary as skip - list cells are stored in the storage for a certain period and - [store_skip_list_cells] needs the L1 context for levels in this period. (It - would actually not be necessary to go as far in the past, because the - protocol parameters and the relevant encodings do not change for now, so the - head plugin could be used). *) -let get_proto_plugins cctxt profile_ctxt ~last_processed_level ~first_seen_level - ~head_level proto_parameters = - let storage_period = - Profile_manager.get_storage_period - profile_ctxt - proto_parameters - ~head_level - ~first_seen_level - in - let first_level = - Int32.max - (match last_processed_level with None -> 1l | Some level -> level) - Int32.(sub head_level (of_int storage_period)) +(* [highest_level] is the highest known level of the protocol for which we + register the plugin. We use this level when getting the protocol parameters, + because the first level of the protocol (the activation level) might be too + old, in that the L1 node might not have the context for that protocol. *) +let add_plugin_for_proto cctxt plugins + Chain_services. + {protocol; proto_level; activation_block = _, activation_level} + highest_level = + let open Lwt_result_syntax in + let* plugin = + resolve_plugin_by_hash + ~emit_failure_event:false + protocol + ~start_level:activation_level in - let first_level = - if Profile_manager.supports_refutations profile_ctxt then - Int32.sub - first_level - (Int32.of_int (History_check.skip_list_offset proto_parameters)) - else - (* The DAL node may need the protocol parameters [attestation_lag] in the - past wrt to the head level. *) - Int32.sub first_level (Int32.of_int proto_parameters.attestation_lag) + let block = `Level highest_level in + let (module Plugin) = plugin in + let+ proto_parameters = Plugin.get_constants `Main block cctxt in + Plugins.add + plugins + ~first_level:activation_level + ~proto_level + plugin + proto_parameters + +let get_supported_proto_plugins cctxt ~head_level = + let open Lwt_result_syntax in + let* protocols = Chain_services.Protocols.list cctxt () in + (* [protocols] are ordered increasingly wrt their protocol level; we treat + them from the last one backwards, because we stop at the most recent one + which cannot be registered *) + let protocols = List.rev protocols in + let*! res = + List.fold_left_es + (fun (plugins, highest_level) protocol_info -> + let*! res = + add_plugin_for_proto cctxt plugins protocol_info highest_level + in + let _hash, level = protocol_info.activation_block in + let highest_level = Int32.pred level in + match res with + | Ok plugins -> return (plugins, highest_level) + | Error [No_plugin_for_proto {proto_hash}] + when Protocol_hash.equal proto_hash protocol_info.protocol -> + fail (`End_loop_ok plugins) + | Error err -> fail (`End_loop_nok err)) + (Plugins.empty, head_level) + protocols in - let first_level = Int32.(max 1l first_level) in - initial_plugins cctxt ~first_level ~last_level:head_level + match res with + | Ok (plugins, _) | Error (`End_loop_ok plugins) -> return plugins + | Error (`End_loop_nok err) -> fail err + +let has_plugins plugins = not (LevelMap.is_empty plugins) diff --git a/src/lib_dal_node/proto_plugins.mli b/src/lib_dal_node/proto_plugins.mli index 79e391f8fddcb78001d63a1540e104b18ecc7e77..6b86e8725b9e5ec73427d7ce3e0e6a039ccfd568 100644 --- a/src/lib_dal_node/proto_plugins.mli +++ b/src/lib_dal_node/proto_plugins.mli @@ -55,44 +55,15 @@ val get_plugin_and_parameters_for_level : val may_add : Rpc_context.t -> t -> first_level:int32 -> proto_level:int -> t tzresult Lwt.t -(** [initial_plugins rpc_ctxt ~first_level ~last_level] returns the plugins for - levels between [first_level] and [last_last]. Note that if migrations have - happened in this interval, there will be several plugins. - - It returns an error if the [Chain_services.Blocks.protocols] RPC fails, or - if some plugin is not registered, in which case it returns - [No_plugin_for_proto]. *) -val initial_plugins : - Rpc_context.t -> first_level:int32 -> last_level:int32 -> t tzresult Lwt.t - -(** [resolve_plugin_for_level rpc_ctxt ~level] returns the plugin for the given - [level]. - - It returns an error if the [Chain_services.Blocks.protocols] RPC fails, or - if the plugin is not registered, in which case it returns - [No_plugin_for_proto]. *) -val resolve_plugin_for_level : - Rpc_context.t -> level:int32 -> (module Dal_plugin.T) tzresult Lwt.t - -(** [get_proto_plugins cctxt profile_ctxt ~last_processed_level - ~first_seen_level ~head_level proto_parameters] returns the set of protocol - plugins required by the DAL node to operate correctly over its current - storage window. - - It computes the first level for which a plugin may be needed based on the - DAL node's storage period and whether refutation support is enabled - (which requires access to older levels for skip list storage or protocol - parameters). The returned plugin map covers the range from this computed - first level up to [head_level]. +(** [get_supported_proto_plugins cctxt ~head_level] fetches all the protocol plugins that + it can and for which it can also fetch the protocol parameters. This function is typically called once at startup after fetching the current [head_level] from L1. *) -val get_proto_plugins : - Rpc_context.t -> - Profile_manager.t -> - last_processed_level:int32 option -> - first_seen_level:int32 option -> - head_level:int32 -> - Types.proto_parameters -> - t tzresult Lwt.t +val get_supported_proto_plugins : + Rpc_context.t -> head_level:int32 -> t tzresult Lwt.t + +(** returns true if an only if the given proto_plugins structure has some + plugins registered in it. *) +val has_plugins : t -> bool diff --git a/tezt/tests/dal.ml b/tezt/tests/dal.ml index 5796b3aeccdfe2088302bf5a072de47cf888134e..1e512797bbecdcfd846b14b7c2e5044fffd2995e 100644 --- a/tezt/tests/dal.ml +++ b/tezt/tests/dal.ml @@ -4401,7 +4401,7 @@ let test_migration_plugin ~migrate_from ~migrate_to = let wait_for_plugin = Dal_node.wait_for dal_node "dal_plugin_resolved.v0" (fun json -> - let proto_hash = JSON.(json |> as_string) in + let proto_hash = JSON.(json |-> "proto_hash" |> as_string) in if String.equal proto_hash (Protocol.hash migrate_to) then Some () else None) in