From d183c7bc293bdedfbe2328e207ffffbbfc847380 Mon Sep 17 00:00:00 2001 From: phink Date: Fri, 17 Jan 2025 12:52:11 +0100 Subject: [PATCH 1/4] DAL/Node: inject entrapment evidence for each trap in the cache --- src/bin_dal_node/accuser.ml | 147 +++++++++++++++++------------------ src/bin_dal_node/accuser.mli | 13 ++-- src/bin_dal_node/store.ml | 33 ++++++-- src/bin_dal_node/store.mli | 24 ++++-- 4 files changed, 119 insertions(+), 98 deletions(-) diff --git a/src/bin_dal_node/accuser.ml b/src/bin_dal_node/accuser.ml index bafad39baae4..e0a6df61f7c8 100644 --- a/src/bin_dal_node/accuser.ml +++ b/src/bin_dal_node/accuser.ml @@ -5,84 +5,79 @@ (* *) (*****************************************************************************) -let get_monitored_slot_indices node_ctxt = - let profile = - Profile_manager.get_profiles (Node_context.get_profile_ctxt node_ctxt) - in - match profile with - | Bootstrap | Random_observer -> [] - | Operator operator_profile -> - Operator_profile.get_all_slot_indexes operator_profile +(* [inject_entrapment_evidences] processes and injects trap evidence + retrieving traps from a specific published level, filtering them to + identify injectable ones, and then injecting entrapment evidence + for each injectable trap that the delegate actually attested. -let inject_entrapment_evidences (type b) - (module Plugin : Dal_plugin.T with type block_info = b) node_ctxt rpc_ctxt - (block : b) = + Guarded by [proto_parameters.incentives_enable]. +*) +let inject_entrapment_evidences (type block_info) + (module Plugin : Dal_plugin.T with type block_info = block_info) node_ctxt + rpc_ctxt block = let open Lwt_result_syntax in let attested_level = (Plugin.block_shell_header block).level in let proto_parameters = Node_context.get_proto_parameters node_ctxt in when_ proto_parameters.incentives_enable (fun () -> - let monitored_slot_indices = get_monitored_slot_indices node_ctxt in - if List.is_empty monitored_slot_indices then return_unit - else - let published_level = - (* FIXME: https://gitlab.com/tezos/tezos/-/issues/4612 - Correctly compute [published_level] in case of protocol changes, in - particular a change of the value of [attestation_lag]. *) - Int32.(sub attested_level (of_int proto_parameters.attestation_lag)) - in - let store = Node_context.get_store node_ctxt in - let traps_store = Store.traps store in - let attestations = Plugin.get_attestation_operations block in - let attestation_map = - List.fold_left - (fun map (delegate_opt, operation, dal_attestation) -> - match delegate_opt with - | None -> map - | Some delegate -> - let new_map = - Signature.Public_key_hash.Map.add - delegate - (operation, dal_attestation) - map - in - new_map) - Signature.Public_key_hash.Map.empty - attestations - in - List.iter_es - (fun slot_index -> - let slot_id = - Types.Slot_id.{slot_index; slot_level = published_level} - in - let traps = Store.Traps.find traps_store ~slot_id in - List.iter_es - (fun (shard_index, (delegate, share, proof)) -> - let attestation_opt = - Signature.Public_key_hash.Map.find delegate attestation_map - in - match attestation_opt with - | None -> - (* It could happen if the delegate didn't attest at all. *) - return_unit - | Some (_, None) -> - (* No dal attestation found. *) - return_unit - | Some (attestation, Some dal_attestation) -> - if Plugin.is_attested dal_attestation slot_index then - let shard = Cryptobox.{index = shard_index; share} in - let*! () = - Event.( - emit - trap_injection - (delegate, published_level, slot_index, shard_index)) - in - Plugin.inject_entrapment_evidence - rpc_ctxt - ~attested_level - attestation - ~slot_index - ~shard - ~proof - else return_unit) - traps) - monitored_slot_indices) + let published_level = + (* FIXME: https://gitlab.com/tezos/tezos/-/issues/4612 + Correctly compute [published_level] in case of protocol changes, in + particular a change of the value of [attestation_lag]. *) + Int32.(sub attested_level (of_int proto_parameters.attestation_lag)) + in + let store = Node_context.get_store node_ctxt in + let traps_store = Store.traps store in + let traps = Store.Traps.find traps_store ~level:published_level in + match traps with + | [] -> return_unit + | traps -> + let attestations = Plugin.get_attestation_operations block in + let attestation_map = + List.fold_left + (fun map (delegate_opt, operation, dal_attestation) -> + match delegate_opt with + | None -> map + | Some delegate -> + let new_map = + Signature.Public_key_hash.Map.add + delegate + (operation, dal_attestation) + map + in + new_map) + Signature.Public_key_hash.Map.empty + attestations + in + List.iter_es + (fun trap -> + let Store.Traps.{delegate; slot_index; shard; shard_proof} = + trap + in + let Cryptobox.{index = shard_index; share = _} = shard in + let attestation_opt = + Signature.Public_key_hash.Map.find delegate attestation_map + in + match attestation_opt with + | None -> + (* It could happen if the delegate didn't attest at all. *) + return_unit + | Some (_, None) -> + (* No dal attestation found. *) + return_unit + | Some (attestation, Some dal_attestation) -> + if Plugin.is_attested dal_attestation slot_index then + let*! () = + Event.( + emit + trap_injection + (delegate, published_level, slot_index, shard_index)) + in + Plugin.inject_entrapment_evidence + rpc_ctxt + ~attested_level + attestation + ~slot_index + ~shard + ~proof:shard_proof + else return_unit) + traps) diff --git a/src/bin_dal_node/accuser.mli b/src/bin_dal_node/accuser.mli index aecdce4f1817..cbe01d7997af 100644 --- a/src/bin_dal_node/accuser.mli +++ b/src/bin_dal_node/accuser.mli @@ -6,14 +6,13 @@ (*****************************************************************************) (** [inject_entrapment_evidences plugin node_ctxt rpc_ctxt block] - processes entrapment evidence for slot indices monitored by the - DAL node. + processes and injects trap evidence retrieving traps from a + specific published level according to the [block]'s level, + filtering them to identify injectable ones, and then injecting + entrapment evidence for each injectable trap that the delegate + actually attested. - More notably, it: - - fetches attestation operations for the attested [block], - - processes potential entrapments for each monitored slot, - - injects entrapment evidence when found. -*) + Guarded by [proto_parameters.incentives_enable]. *) val inject_entrapment_evidences : (module Dal_plugin.T with type block_info = 'block) -> Node_context.t -> diff --git a/src/bin_dal_node/store.ml b/src/bin_dal_node/store.ml index 5a6dde31135d..6e5715a2e28d 100644 --- a/src/bin_dal_node/store.ml +++ b/src/bin_dal_node/store.ml @@ -347,6 +347,13 @@ module Traps = struct type t = payload Shard_index_map.t Slot_index_map.t Level_map.t + type v = { + delegate : Signature.Public_key_hash.t; + slot_index : Types.slot_index; + shard : Cryptobox.shard; + shard_proof : Cryptobox.shard_proof; + } + let create ~capacity = Level_map.create capacity let add_slot_index t ~slot_index ~shard_index ~delegate ~share ~shard_proof = @@ -379,14 +386,26 @@ module Traps = struct in Level_map.replace t slot_level new_slot_index_map - let find t ~slot_id = - let Types.Slot_id.{slot_level; slot_index} = slot_id in - match Level_map.find_opt t slot_level with + let find t ~level = + match Level_map.find_opt t level with | None -> [] - | Some m -> ( - match Slot_index_map.find_opt slot_index m with - | None -> [] - | Some m -> Shard_index_map.bindings m) + | Some m -> + Slot_index_map.fold + (fun slot_index m acc -> + let res = + List.map + (fun (shard_index, (delegate, share, shard_proof)) -> + { + delegate; + slot_index; + shard = Cryptobox.{index = shard_index; share}; + shard_proof; + }) + (Shard_index_map.bindings m) + in + res @ acc) + m + [] end module Statuses = struct diff --git a/src/bin_dal_node/store.mli b/src/bin_dal_node/store.mli index 39299eca711d..d58c2c637c03 100644 --- a/src/bin_dal_node/store.mli +++ b/src/bin_dal_node/store.mli @@ -130,6 +130,19 @@ module Traps : sig in memory. *) type t + (** The type containing all elements required to + construct a trap accusation. + - [delegate]: the baker who attested, + - [slot_index]: the index of the slot containing the trap, + - [shard]: the DAL shard containing the trap share, + - [shard_proof]: proof provided for the shard. *) + type v = { + delegate : Signature.Public_key_hash.t; + slot_index : Types.slot_index; + shard : Cryptobox.shard; + shard_proof : Cryptobox.shard_proof; + } + (** [add ~slot_id ~shard_index ~delegate ~share ~shard_proof] adds trap data to the cache. The cache maintains a maximum of [Constants.traps_cache_size] levels. Data is expected to be @@ -146,14 +159,9 @@ module Traps : sig shard_proof:Cryptobox.shard_proof -> unit - (** [find ~slot_id] retrieves the trap data associated with the - given [slot_id]. *) - val find : - t -> - slot_id:Types.Slot_id.t -> - (Types.shard_index - * (Signature.Public_key_hash.t * Cryptobox.share * Cryptobox.shard_proof)) - list + (** [find t ~level] retrieves all trap data associated with the given + level. Returns an empty list if no traps exist for that level. *) + val find : t -> level:Types.level -> v list end module Last_processed_level : Single_value_store.S with type value = int32 -- GitLab From 7cdc223bea4dafd9195e737ac490d520e4216245 Mon Sep 17 00:00:00 2001 From: phink Date: Fri, 17 Jan 2025 13:40:03 +0100 Subject: [PATCH 2/4] DAL/Node: extract the get_attestation_map function in the accuser --- src/bin_dal_node/accuser.ml | 39 +++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/src/bin_dal_node/accuser.ml b/src/bin_dal_node/accuser.ml index e0a6df61f7c8..b161706dd22f 100644 --- a/src/bin_dal_node/accuser.ml +++ b/src/bin_dal_node/accuser.ml @@ -5,6 +5,27 @@ (* *) (*****************************************************************************) +(* [get_attestation_map] retrieves DAL attestation operations from a + block and transforms them into a map where delegates are mapped to + their corresponding attestation operation and DAL attestation. *) +let get_attestation_map (type block_info attestation_operation dal_attestation) + (module Plugin : Dal_plugin.T + with type block_info = block_info + and type attestation_operation = attestation_operation + and type dal_attestation = dal_attestation) block = + let attestations = Plugin.get_attestation_operations block in + List.fold_left + (fun map (delegate_opt, operation, dal_attestation) -> + match delegate_opt with + | None -> map + | Some delegate -> + Signature.Public_key_hash.Map.add + delegate + (operation, dal_attestation) + map) + Signature.Public_key_hash.Map.empty + attestations + (* [inject_entrapment_evidences] processes and injects trap evidence retrieving traps from a specific published level, filtering them to identify injectable ones, and then injecting entrapment evidence @@ -31,23 +52,7 @@ let inject_entrapment_evidences (type block_info) match traps with | [] -> return_unit | traps -> - let attestations = Plugin.get_attestation_operations block in - let attestation_map = - List.fold_left - (fun map (delegate_opt, operation, dal_attestation) -> - match delegate_opt with - | None -> map - | Some delegate -> - let new_map = - Signature.Public_key_hash.Map.add - delegate - (operation, dal_attestation) - map - in - new_map) - Signature.Public_key_hash.Map.empty - attestations - in + let attestation_map = get_attestation_map (module Plugin) block in List.iter_es (fun trap -> let Store.Traps.{delegate; slot_index; shard; shard_proof} = -- GitLab From 868f688eb33050fe8c05eb26015412c3065f5444 Mon Sep 17 00:00:00 2001 From: phink Date: Fri, 17 Jan 2025 13:40:28 +0100 Subject: [PATCH 3/4] DAL/Node: extract the filter_injectable_traps function in the accuser --- src/bin_dal_node/accuser.ml | 85 +++++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 32 deletions(-) diff --git a/src/bin_dal_node/accuser.ml b/src/bin_dal_node/accuser.ml index b161706dd22f..bfa919ab7fae 100644 --- a/src/bin_dal_node/accuser.ml +++ b/src/bin_dal_node/accuser.ml @@ -26,6 +26,31 @@ let get_attestation_map (type block_info attestation_operation dal_attestation) Signature.Public_key_hash.Map.empty attestations +(* [filter_injectable_traps] filters a list of traps to identify which + ones are injectable by checking if each trap's delegate has both an + attestation and DAL attestation in [attestation_map]. *) +let filter_injectable_traps attestation_map traps = + List.filter_map_s + (fun trap -> + let Store.Traps.{delegate; slot_index; shard; shard_proof} = trap in + let attestation_opt = + Signature.Public_key_hash.Map.find delegate attestation_map + in + match attestation_opt with + | None -> Lwt.return_none + | Some (_attestation, None) -> + (* The delegate did not DAL attest at all. *) + Lwt.return_none + | Some (attestation, Some dal_attestation) -> + Lwt.return_some + ( delegate, + slot_index, + attestation, + dal_attestation, + shard, + shard_proof )) + traps + (* [inject_entrapment_evidences] processes and injects trap evidence retrieving traps from a specific published level, filtering them to identify injectable ones, and then injecting entrapment evidence @@ -53,36 +78,32 @@ let inject_entrapment_evidences (type block_info) | [] -> return_unit | traps -> let attestation_map = get_attestation_map (module Plugin) block in + let*! traps_to_inject = + filter_injectable_traps attestation_map traps + in List.iter_es - (fun trap -> - let Store.Traps.{delegate; slot_index; shard; shard_proof} = - trap - in - let Cryptobox.{index = shard_index; share = _} = shard in - let attestation_opt = - Signature.Public_key_hash.Map.find delegate attestation_map - in - match attestation_opt with - | None -> - (* It could happen if the delegate didn't attest at all. *) - return_unit - | Some (_, None) -> - (* No dal attestation found. *) - return_unit - | Some (attestation, Some dal_attestation) -> - if Plugin.is_attested dal_attestation slot_index then - let*! () = - Event.( - emit - trap_injection - (delegate, published_level, slot_index, shard_index)) - in - Plugin.inject_entrapment_evidence - rpc_ctxt - ~attested_level - attestation - ~slot_index - ~shard - ~proof:shard_proof - else return_unit) - traps) + (fun ( delegate, + slot_index, + attestation, + dal_attestation, + shard, + shard_proof ) -> + if Plugin.is_attested dal_attestation slot_index then + let*! () = + Event.( + emit + trap_injection + ( delegate, + published_level, + shard.Cryptobox.index, + slot_index )) + in + Plugin.inject_entrapment_evidence + rpc_ctxt + ~attested_level + attestation + ~slot_index + ~shard + ~proof:shard_proof + else return_unit) + traps_to_inject) -- GitLab From b4cd9c491321ac3a3e4ff8ae0c6bd537dc21356e Mon Sep 17 00:00:00 2001 From: phink Date: Mon, 20 Jan 2025 16:30:23 +0100 Subject: [PATCH 4/4] DAL/Node: warn when no attestation has been found for a delegate --- src/bin_dal_node/accuser.ml | 23 ++++++++++++++++++++--- src/bin_dal_node/event.ml | 16 ++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/bin_dal_node/accuser.ml b/src/bin_dal_node/accuser.ml index bfa919ab7fae..e4021f2443a6 100644 --- a/src/bin_dal_node/accuser.ml +++ b/src/bin_dal_node/accuser.ml @@ -29,7 +29,9 @@ let get_attestation_map (type block_info attestation_operation dal_attestation) (* [filter_injectable_traps] filters a list of traps to identify which ones are injectable by checking if each trap's delegate has both an attestation and DAL attestation in [attestation_map]. *) -let filter_injectable_traps attestation_map traps = +let filter_injectable_traps ~attested_level ~published_level attestation_map + traps = + let open Lwt_result_syntax in List.filter_map_s (fun trap -> let Store.Traps.{delegate; slot_index; shard; shard_proof} = trap in @@ -37,7 +39,18 @@ let filter_injectable_traps attestation_map traps = Signature.Public_key_hash.Map.find delegate attestation_map in match attestation_opt with - | None -> Lwt.return_none + | None -> + let*! () = + Event.( + emit + trap_delegate_attestation_not_found + ( delegate, + slot_index, + shard.Cryptobox.index, + published_level, + attested_level )) + in + Lwt.return_none | Some (_attestation, None) -> (* The delegate did not DAL attest at all. *) Lwt.return_none @@ -79,7 +92,11 @@ let inject_entrapment_evidences (type block_info) | traps -> let attestation_map = get_attestation_map (module Plugin) block in let*! traps_to_inject = - filter_injectable_traps attestation_map traps + filter_injectable_traps + ~attested_level + ~published_level + attestation_map + traps in List.iter_es (fun ( delegate, diff --git a/src/bin_dal_node/event.ml b/src/bin_dal_node/event.ml index 3039734a8700..3f25a693b768 100644 --- a/src/bin_dal_node/event.ml +++ b/src/bin_dal_node/event.ml @@ -784,3 +784,19 @@ let trap_registration_fail = ("delegate", Signature.Public_key_hash.encoding) ("slot_index", Data_encoding.int31) ("shard_index", Data_encoding.int31) + +let trap_delegate_attestation_not_found = + declare_5 + ~section + ~name:"trap_delegate_attestation_not_found" + ~msg: + "Unable to associate an attestation with delegate {delegate} for \ + attested level {attested_level}. Failed while injecting trap evidence \ + from published level {published_level} at slot index {slot_index} and \ + shard index {shard_index}" + ~level:Warning + ("delegate", Signature.Public_key_hash.encoding) + ("slot_index", Data_encoding.int31) + ("shard_index", Data_encoding.int31) + ("published_level", Data_encoding.int32) + ("attested_level", Data_encoding.int32) -- GitLab