diff --git a/CHANGES.rst b/CHANGES.rst index 757e3f1615f1b9095f01f139b8c2b35d5abf0395..8d202a0ad8bd9498cf0d9bfa0007b8b9d49ec7ef 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -236,6 +236,9 @@ DAL node - **Breaking change** The baker daemon ``--dal-node-timeout-percentage`` argument has been removed. (MR :gl:`!15554`) +- A warning is emitted when registering a public key hash (as an attester + profile) that does not correspond to that of a delegate. (MR :gl:`!16336`) + Protocol ~~~~~~~~ diff --git a/src/bin_dal_node/RPC_server.ml b/src/bin_dal_node/RPC_server.ml index 523334ddd018e5745649fd89031af88e09382918..ed5ba21bce827f3613c386b97e12676b0d6e4346 100644 --- a/src/bin_dal_node/RPC_server.ml +++ b/src/bin_dal_node/RPC_server.ml @@ -270,6 +270,10 @@ module Profile_handlers = struct let open Lwt_result_syntax in let gs_worker = Node_context.get_gs_worker ctxt in call_handler1 (fun () -> + let* () = + Node_context.warn_if_attesters_not_delegates ctxt operator_profiles + |> lwt_map_error (fun e -> `Other e) + in let proto_parameters = Node_context.get_proto_parameters ctxt in match Profile_manager.add_and_register_operator_profile diff --git a/src/bin_dal_node/daemon.ml b/src/bin_dal_node/daemon.ml index 9d21b270c8de9f619d7ac43de87873f816fd75be..9d79b1274169f2240cb9446c525e871796e7ae5f 100644 --- a/src/bin_dal_node/daemon.ml +++ b/src/bin_dal_node/daemon.ml @@ -1374,6 +1374,15 @@ let run ~data_dir ~configuration_override = transport_layer cctxt in + let* () = + match Profile_manager.get_profiles profile_ctxt with + | Operator profile -> + Node_context.warn_if_attesters_not_delegates + ctxt + ~level:head_level + profile + | _ -> return_unit + in Gossipsub.Worker.Validate_message_hook.set (Handler.gossipsub_app_messages_validation ctxt diff --git a/src/bin_dal_node/event.ml b/src/bin_dal_node/event.ml index 3f25a693b768dc78f6da4825fee5a75409edae67..5b427ba74991c4660418800aab4e109f33d18d1b 100644 --- a/src/bin_dal_node/event.ml +++ b/src/bin_dal_node/event.ml @@ -800,3 +800,13 @@ let trap_delegate_attestation_not_found = ("shard_index", Data_encoding.int31) ("published_level", Data_encoding.int32) ("attested_level", Data_encoding.int32) + +let registered_pkh_not_a_delegate = + declare_1 + ~section + ~name:"register_pkh_not_a_delegate" + ~msg: + "The public key hash {pkh} registered by PATCH /profiles is not a \ + delegate." + ~level:Warning + ("pkh", Signature.Public_key_hash.encoding) diff --git a/src/bin_dal_node/node_context.ml b/src/bin_dal_node/node_context.ml index 037b6dfa776fb022f939625851bcda699f7b36c7..3447e0f97d9d487ba2d8084dcd76f33834b9a4d2 100644 --- a/src/bin_dal_node/node_context.ml +++ b/src/bin_dal_node/node_context.ml @@ -202,6 +202,35 @@ let version {config; _} = let network_name = config.Configuration_file.network_name in Types.Version.make ~network_version:(Gossipsub.version ~network_name) +(* TODO: https://gitlab.com/tezos/tezos/-/issues/7706 + This level argument would not be needed if we had the head level in the context. *) +let warn_if_attesters_not_delegates ctxt ?level operator_profiles = + let open Lwt_result_syntax in + let pkh_set = Operator_profile.attesters operator_profiles in + if Signature.Public_key_hash.Set.is_empty pkh_set then return_unit + else + let* level_opt = + match level with + | Some _ -> return level + | None -> + let store = get_store ctxt in + let lpl_store = Store.last_processed_level store in + Store.Last_processed_level.load lpl_store + in + Option.iter_es + (fun level -> + let cctxt = get_tezos_node_cctxt ctxt in + let*? (module Plugin) = get_plugin_for_level ctxt ~level in + Signature.Public_key_hash.Set.iter_es + (fun pkh -> + let* is_delegate = Plugin.is_delegate cctxt ~pkh in + if not is_delegate then + let*! () = Event.(emit registered_pkh_not_a_delegate pkh) in + return_unit + else return_unit) + pkh_set) + level_opt + module P2P = struct let connect {transport_layer; _} ?timeout point = Gossipsub.Transport_layer.connect transport_layer ?timeout point diff --git a/src/bin_dal_node/node_context.mli b/src/bin_dal_node/node_context.mli index 628bb67694ce005127bfb93409dd5ba5acd3400c..34b83a9955c4a7e1aea0e98fadc77e991c5f1db8 100644 --- a/src/bin_dal_node/node_context.mli +++ b/src/bin_dal_node/node_context.mli @@ -168,6 +168,13 @@ val fetch_committee : (** [version ctxt] returns the current version of the node *) val version : t -> Types.Version.t +(** Emit a warning for each public key hash in the given operator profile (if + any) that is not that of a L1-registered delegate. The optional [level] + argument is used to specify for which level to obtain the plugin; if not + given the last process level is used (if found in the store). *) +val warn_if_attesters_not_delegates : + t -> ?level:int32 -> Operator_profile.t -> unit tzresult Lwt.t + (** Module for P2P-related accessors. *) module P2P : sig (** [connect t ?timeout point] initiates a connection to the point diff --git a/src/lib_dal_node/dal_plugin.ml b/src/lib_dal_node/dal_plugin.ml index 50abdc0a4e252189d9fe6bd7550ed1b33ce03df4..0a1ed7a0bf254657fe6adeb4f4700cc51764cf38 100644 --- a/src/lib_dal_node/dal_plugin.ml +++ b/src/lib_dal_node/dal_plugin.ml @@ -176,6 +176,11 @@ module type T = sig proof:Cryptobox.shard_proof -> unit tzresult Lwt.t + val is_delegate : + Tezos_rpc.Context.generic -> + pkh:Signature.Public_key_hash.t -> + bool tzresult Lwt.t + (* Section of helpers for Skip lists *) module Skip_list : sig diff --git a/src/lib_dal_node/dal_plugin.mli b/src/lib_dal_node/dal_plugin.mli index ab66a17f5e991a8efe7a11967566b0b7224d68ce..1e5fcb255e8cd81fcf5acdd221bb30a5e7b1e621 100644 --- a/src/lib_dal_node/dal_plugin.mli +++ b/src/lib_dal_node/dal_plugin.mli @@ -139,6 +139,11 @@ module type T = sig proof:Cryptobox.shard_proof -> unit tzresult Lwt.t + val is_delegate : + Tezos_rpc.Context.generic -> + pkh:Signature.Public_key_hash.t -> + bool tzresult Lwt.t + (* Section of helpers for Skip lists *) module Skip_list : sig diff --git a/src/proto_020_PsParisC/lib_dal/dal_plugin_registration.ml b/src/proto_020_PsParisC/lib_dal/dal_plugin_registration.ml index bb9902374da088cb5bf9c1244f1393f56de1d657..03fbc81402eede4e48ae05dad9cd859817417891 100644 --- a/src/proto_020_PsParisC/lib_dal/dal_plugin_registration.ml +++ b/src/proto_020_PsParisC/lib_dal/dal_plugin_registration.ml @@ -204,6 +204,9 @@ module Plugin = struct let is_attested attestation slot_index = match Bitset.mem attestation slot_index with Ok b -> b | Error _ -> false + let is_delegate _ctxt ~pkh:_ = + failwith "Plugin.ParisC.is_delegate is not available" + (* Section of helpers for Skip lists *) module Skip_list = struct diff --git a/src/proto_021_PsQuebec/lib_dal/dal_plugin_registration.ml b/src/proto_021_PsQuebec/lib_dal/dal_plugin_registration.ml index bb6943c11d37c9ee8355ded55e6394748c47d9e2..00fe7bcfed3425e21c0ca8dc66d4313e26ec50ae 100644 --- a/src/proto_021_PsQuebec/lib_dal/dal_plugin_registration.ml +++ b/src/proto_021_PsQuebec/lib_dal/dal_plugin_registration.ml @@ -204,6 +204,17 @@ module Plugin = struct let is_attested attestation slot_index = match Bitset.mem attestation slot_index with Ok b -> b | Error _ -> false + let is_delegate ctxt ~pkh = + let open Lwt_result_syntax in + let cpctxt = new Protocol_client_context.wrap_rpc_context ctxt in + (* We just want to know whether is a delegate. We call + 'context/delegates//deactivated' just because it should be cheaper + than calling 'context/delegates//' (called [Delegate.info]). *) + let*! res = + Plugin.Alpha_services.Delegate.deactivated cpctxt (`Main, `Head 0) pkh + in + return @@ match res with Ok _deactivated -> true | Error _ -> false + (* Section of helpers for Skip lists *) module Skip_list = struct diff --git a/src/proto_alpha/lib_dal/dal_plugin_registration.ml b/src/proto_alpha/lib_dal/dal_plugin_registration.ml index 52c418c72fcc553d252f18ea14400ee3529b9938..f132674c363ae1c9cec727774f026505ed2e62a2 100644 --- a/src/proto_alpha/lib_dal/dal_plugin_registration.ml +++ b/src/proto_alpha/lib_dal/dal_plugin_registration.ml @@ -245,6 +245,17 @@ module Plugin = struct | Ok b -> b | Error _ -> false + let is_delegate ctxt ~pkh = + let open Lwt_result_syntax in + let cpctxt = new Protocol_client_context.wrap_rpc_context ctxt in + (* We just want to know whether is a delegate. We call + 'context/delegates//deactivated' just because it should be cheaper + than calling 'context/delegates//' (called [Delegate.info]). *) + let*! res = + Plugin.Alpha_services.Delegate.deactivated cpctxt (`Main, `Head 0) pkh + in + return @@ match res with Ok _deactivated -> true | Error _ -> false + (* Section of helpers for Skip lists *) module Skip_list = struct