diff --git a/src/bin_dal_node/accuser.ml b/src/bin_dal_node/accuser.ml index bafad39baae449f43ad717efa30a4c2212b15886..e4021f2443a686bad23dcd73814d34a752f3056a 100644 --- a/src/bin_dal_node/accuser.ml +++ b/src/bin_dal_node/accuser.ml @@ -5,84 +5,122 @@ (* *) (*****************************************************************************) -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 +(* [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 -let inject_entrapment_evidences (type b) - (module Plugin : Dal_plugin.T with type block_info = b) node_ctxt rpc_ctxt - (block : b) = +(* [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 ~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 + let attestation_opt = + Signature.Public_key_hash.Map.find delegate attestation_map + in + match attestation_opt with + | 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 + | 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 + for each injectable trap that the delegate actually attested. + + 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 + 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 attestation_map = get_attestation_map (module Plugin) block in + let*! traps_to_inject = + filter_injectable_traps + ~attested_level + ~published_level + attestation_map + traps + in + List.iter_es + (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 - 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) + Plugin.inject_entrapment_evidence + rpc_ctxt + ~attested_level + attestation + ~slot_index + ~shard + ~proof:shard_proof + else return_unit) + traps_to_inject) diff --git a/src/bin_dal_node/accuser.mli b/src/bin_dal_node/accuser.mli index aecdce4f18172d18654359da427d0bd2fbbd32de..cbe01d7997af7eb1d3c717c4dcd47dde697b606a 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/event.ml b/src/bin_dal_node/event.ml index 3039734a87007816b3b81c47bb3021c5b6d3c183..3f25a693b768dc78f6da4825fee5a75409edae67 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) diff --git a/src/bin_dal_node/store.ml b/src/bin_dal_node/store.ml index 5a6dde31135d5b6808b752471da35e19063281a0..6e5715a2e28d1cbc9a5a3a8d93133c49d859e048 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 39299eca711d40a2dd1f2ed2b6c65eb90f3f24c3..d58c2c637c0312e298c482af95f800d9a6fc3890 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