From 66d1beb66957e8c7aee373df67c2bb7dd841dbdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Thir=C3=A9?= Date: Tue, 14 Jan 2025 17:13:23 +0100 Subject: [PATCH 1/8] DAL/Node: use attestation_operation to refine the operation data --- src/lib_dal_node/dal_plugin.ml | 4 +- src/lib_dal_node/dal_plugin.mli | 4 +- .../lib_dal/dal_plugin_registration.ml | 2 + .../lib_dal/dal_plugin_registration.ml | 2 + .../lib_dal/dal_plugin_registration.ml | 66 +++++++------------ 5 files changed, 32 insertions(+), 46 deletions(-) diff --git a/src/lib_dal_node/dal_plugin.ml b/src/lib_dal_node/dal_plugin.ml index d3aff5755d91..0ef620011d41 100644 --- a/src/lib_dal_node/dal_plugin.ml +++ b/src/lib_dal_node/dal_plugin.ml @@ -127,6 +127,8 @@ module type T = sig type dal_attestation + type attestation_operation + val block_info : ?chain:Tezos_shell_services.Block_services.chain -> ?block:Tezos_shell_services.Block_services.block -> @@ -164,7 +166,7 @@ module type T = sig val inject_entrapment_evidence : Tezos_rpc.Context.generic -> attested_level:Int32.t -> - Proto.operation -> + attestation_operation -> slot_index:slot_index -> shard:Cryptobox.shard -> proof:Cryptobox.shard_proof -> diff --git a/src/lib_dal_node/dal_plugin.mli b/src/lib_dal_node/dal_plugin.mli index 0fb0a8420ca1..bd78e9d91929 100644 --- a/src/lib_dal_node/dal_plugin.mli +++ b/src/lib_dal_node/dal_plugin.mli @@ -65,6 +65,8 @@ module type T = sig type dal_attestation + type attestation_operation + (** [block_info ?chain ?block ~metadata ctxt] returns the information of the [block] in [ctxt] for the given [chain]. Block's metadata are included or skipped depending on the value of [metadata]. This is a wrapper on top of @@ -125,7 +127,7 @@ module type T = sig val inject_entrapment_evidence : Tezos_rpc.Context.generic -> attested_level:Int32.t -> - Proto.operation -> + attestation_operation -> slot_index:slot_index -> shard:Cryptobox.shard -> proof:Cryptobox.shard_proof -> 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 b6d44f6c9fd0..47c792a70c79 100644 --- a/src/proto_020_PsParisC/lib_dal/dal_plugin_registration.ml +++ b/src/proto_020_PsParisC/lib_dal/dal_plugin_registration.ml @@ -35,6 +35,8 @@ module Plugin = struct type dal_attestation = Bitset.t + type attestation_operation = Kind.attestation Alpha_context.operation + let parametric_constants chain block ctxt = let cpctxt = new Protocol_client_context.wrap_rpc_context ctxt in Protocol.Constants_services.parametric cpctxt (chain, block) 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 79b5fec08a73..50a41ce30e52 100644 --- a/src/proto_021_PsQuebec/lib_dal/dal_plugin_registration.ml +++ b/src/proto_021_PsQuebec/lib_dal/dal_plugin_registration.ml @@ -35,6 +35,8 @@ module Plugin = struct type dal_attestation = Bitset.t + type attestation_operation = Kind.attestation Alpha_context.operation + let parametric_constants chain block ctxt = let cpctxt = new Protocol_client_context.wrap_rpc_context ctxt in Protocol.Constants_services.parametric cpctxt (chain, block) diff --git a/src/proto_alpha/lib_dal/dal_plugin_registration.ml b/src/proto_alpha/lib_dal/dal_plugin_registration.ml index 4bd4a866b113..733dddbd848e 100644 --- a/src/proto_alpha/lib_dal/dal_plugin_registration.ml +++ b/src/proto_alpha/lib_dal/dal_plugin_registration.ml @@ -35,6 +35,8 @@ module Plugin = struct type dal_attestation = Environment.Bitset.t + type attestation_operation = Kind.attestation Alpha_context.operation + let parametric_constants chain block ctxt = let cpctxt = new Protocol_client_context.wrap_rpc_context ctxt in Protocol.Constants_services.parametric cpctxt (chain, block) @@ -86,28 +88,8 @@ module Plugin = struct if Compare.Int32.(level_with_offset >= 0l) then return (`Head offset) else return (`Head 0) - (* We can provide more context in the future. *) - type error += Not_an_attestation - - let () = - register_error_kind - `Permanent - ~id:"not_an_attestation" - ~title:"Not an attestation" - ~description: - "This error is raised if the DAL node tries to accuse with an \ - operation which is not an attestation" - ~pp:(fun fmt () -> - Format.fprintf - fmt - "This error is raised if the DAL node tries to accuse with an \ - operation which is not an attestation") - Data_encoding.unit - (function Not_an_attestation -> Some () | _ -> None) - (fun () -> Not_an_attestation) - - let inject_entrapment_evidence cctxt ~attested_level operation ~slot_index - ~shard ~proof = + let inject_entrapment_evidence cctxt ~attested_level + (operation : attestation_operation) ~slot_index ~shard ~proof = let open Lwt_result_syntax in let cpctxt = new Protocol_client_context.wrap_rpc_context cctxt in let chain = `Main in @@ -132,28 +114,24 @@ module Plugin = struct Protocol_client_context.Alpha_block_services.hash cctxt ~chain ~block () in let shard_with_proof = Dal.Shard_with_proof.{shard; proof} in - match operation.protocol_data with - | Operation_data protocol_data -> ( - match protocol_data.contents with - | Single (Attestation _) -> - let attestation : Kind.attestation Alpha_context.operation = - {shell = operation.shell; protocol_data} - in - let* bytes = - Plugin.RPC.Forge.dal_entrapment_evidence - cpctxt - (chain, block) - ~branch:block_hash - ~attestation - ~slot_index - ~shard_with_proof - in - let bytes = Signature.concat bytes Signature.zero in - let* _op_hash = - Shell_services.Injection.operation cctxt ~chain bytes - in - return_unit - | _ -> fail [Not_an_attestation]) + let protocol_data = operation.protocol_data in + match operation.protocol_data.contents with + | Single (Attestation _) -> + let attestation : Kind.attestation Alpha_context.operation = + {shell = operation.shell; protocol_data} + in + let* bytes = + Plugin.RPC.Forge.dal_entrapment_evidence + cpctxt + (chain, block) + ~branch:block_hash + ~attestation + ~slot_index + ~shard_with_proof + in + let bytes = Signature.concat bytes Signature.zero in + let* _op_hash = Shell_services.Injection.operation cctxt ~chain bytes in + return_unit let block_info ?chain ?block ~metadata ctxt = let cpctxt = new Protocol_client_context.wrap_rpc_context ctxt in -- GitLab From 46307db73088d1ef849ec118bf37e2c66df6af5d Mon Sep 17 00:00:00 2001 From: phink Date: Wed, 15 Jan 2025 13:01:16 +0100 Subject: [PATCH 2/8] DAL/Node: extend the protocol plugin with get_dal_attestation_operations --- src/lib_dal_node/dal_plugin.ml | 7 ++++ src/lib_dal_node/dal_plugin.mli | 11 ++++++ .../lib_dal/dal_plugin_registration.ml | 39 +++++++++++++++++++ .../lib_dal/dal_plugin_registration.ml | 39 +++++++++++++++++++ .../lib_dal/dal_plugin_registration.ml | 39 +++++++++++++++++++ 5 files changed, 135 insertions(+) diff --git a/src/lib_dal_node/dal_plugin.ml b/src/lib_dal_node/dal_plugin.ml index 0ef620011d41..a4442e48bc79 100644 --- a/src/lib_dal_node/dal_plugin.ml +++ b/src/lib_dal_node/dal_plugin.ml @@ -146,6 +146,13 @@ module type T = sig block_info -> (slot_header * operation_application_result) list tzresult Lwt.t + val get_attestation_operations : + block_info -> + (Signature.public_key_hash option + * attestation_operation + * dal_attestation option) + list + val get_dal_content_of_attestations : block_info -> (int * Signature.Public_key_hash.t option * dal_attestation option) list diff --git a/src/lib_dal_node/dal_plugin.mli b/src/lib_dal_node/dal_plugin.mli index bd78e9d91929..07ca19ada91d 100644 --- a/src/lib_dal_node/dal_plugin.mli +++ b/src/lib_dal_node/dal_plugin.mli @@ -88,6 +88,17 @@ module type T = sig block_info -> (slot_header * operation_application_result) list tzresult Lwt.t + (** For a given block, returns for each included attestation, as a + list, its attester if available, its [attestation] operation + and, if it exists, its [dal_attestation] to be passed to the + [is_attested] function. *) + val get_attestation_operations : + block_info -> + (Signature.public_key_hash option + * attestation_operation + * dal_attestation option) + list + (** For a given block, returns for each included attestation, as a list, its Tenderbake slot, its attester (if available in the operation receipt), and its DAL attestation. *) 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 47c792a70c79..dadbaff371a0 100644 --- a/src/proto_020_PsParisC/lib_dal/dal_plugin_registration.ml +++ b/src/proto_020_PsParisC/lib_dal/dal_plugin_registration.ml @@ -169,6 +169,45 @@ module Plugin = struct | _ -> None) consensus_ops + let get_attestation_operations block_info = + let open Protocol.Alpha_context in + let open Protocol_client_context.Alpha_block_services in + match block_info.operations with + | [consensus_ops; _anonymous; _votes; _managers] -> + List.filter_map + (fun operation -> + let (Operation_data operation_data) = operation.protocol_data in + match operation_data.contents with + | Single (Attestation attestation) -> ( + let packed_operation : Kind.attestation Alpha_context.operation + = + { + Alpha_context.shell = operation.shell; + protocol_data = operation_data; + } + in + let dal_attestation : dal_attestation option = + Option.map + (fun x -> (x.attestation :> dal_attestation)) + attestation.dal_content + in + match operation.receipt with + | Receipt (Operation_metadata operation_metadata) -> ( + match operation_metadata.contents with + | Single_result (Attestation_result result) -> + Some + ( Some result.delegate, + packed_operation, + dal_attestation ) + | _ -> Some (None, packed_operation, dal_attestation)) + | Empty | Too_large | Receipt No_operation_metadata -> + Some (None, packed_operation, dal_attestation)) + | _ -> None) + consensus_ops + | _ -> + (* that should be unreachable, as there are 4 operation passes *) + [] + let get_committee ctxt ~level = let open Lwt_result_syntax in let cpctxt = new Protocol_client_context.wrap_rpc_context ctxt in 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 50a41ce30e52..04990e5fe9d5 100644 --- a/src/proto_021_PsQuebec/lib_dal/dal_plugin_registration.ml +++ b/src/proto_021_PsQuebec/lib_dal/dal_plugin_registration.ml @@ -169,6 +169,45 @@ module Plugin = struct | _ -> None) consensus_ops + let get_attestation_operations block_info = + let open Protocol.Alpha_context in + let open Protocol_client_context.Alpha_block_services in + match block_info.operations with + | [consensus_ops; _anonymous; _votes; _managers] -> + List.filter_map + (fun operation -> + let (Operation_data operation_data) = operation.protocol_data in + match operation_data.contents with + | Single (Attestation attestation) -> ( + let packed_operation : Kind.attestation Alpha_context.operation + = + { + Alpha_context.shell = operation.shell; + protocol_data = operation_data; + } + in + let dal_attestation : dal_attestation option = + Option.map + (fun x -> (x.attestation :> dal_attestation)) + attestation.dal_content + in + match operation.receipt with + | Receipt (Operation_metadata operation_metadata) -> ( + match operation_metadata.contents with + | Single_result (Attestation_result result) -> + Some + ( Some result.delegate, + packed_operation, + dal_attestation ) + | _ -> Some (None, packed_operation, dal_attestation)) + | Empty | Too_large | Receipt No_operation_metadata -> + Some (None, packed_operation, dal_attestation)) + | _ -> None) + consensus_ops + | _ -> + (* that should be unreachable, as there are 4 operation passes *) + [] + let get_committee ctxt ~level = let open Lwt_result_syntax in let cpctxt = new Protocol_client_context.wrap_rpc_context ctxt in diff --git a/src/proto_alpha/lib_dal/dal_plugin_registration.ml b/src/proto_alpha/lib_dal/dal_plugin_registration.ml index 733dddbd848e..615fb8c8e7d4 100644 --- a/src/proto_alpha/lib_dal/dal_plugin_registration.ml +++ b/src/proto_alpha/lib_dal/dal_plugin_registration.ml @@ -208,6 +208,45 @@ module Plugin = struct | _ -> None) consensus_ops + let get_attestation_operations block_info = + let open Protocol.Alpha_context in + let open Protocol_client_context.Alpha_block_services in + match block_info.operations with + | [consensus_ops; _anonymous; _votes; _managers] -> + List.filter_map + (fun operation -> + let (Operation_data operation_data) = operation.protocol_data in + match operation_data.contents with + | Single (Attestation attestation) -> ( + let packed_operation : Kind.attestation Alpha_context.operation + = + { + Alpha_context.shell = operation.shell; + protocol_data = operation_data; + } + in + let dal_attestation : dal_attestation option = + Option.map + (fun x -> (x.attestation :> dal_attestation)) + attestation.dal_content + in + match operation.receipt with + | Receipt (Operation_metadata operation_metadata) -> ( + match operation_metadata.contents with + | Single_result (Attestation_result result) -> + Some + ( Some result.delegate, + packed_operation, + dal_attestation ) + | _ -> Some (None, packed_operation, dal_attestation)) + | Empty | Too_large | Receipt No_operation_metadata -> + Some (None, packed_operation, dal_attestation)) + | _ -> None) + consensus_ops + | _ -> + (* that should be unreachable, as there are 4 operation passes *) + [] + let get_committee ctxt ~level = let open Lwt_result_syntax in let cpctxt = new Protocol_client_context.wrap_rpc_context ctxt in -- GitLab From c6c03969fd7abd75eaa268572465256589acc253 Mon Sep 17 00:00:00 2001 From: phink Date: Thu, 16 Jan 2025 17:55:28 +0100 Subject: [PATCH 3/8] DAL/Node: add a cache for traps in the node store --- src/bin_dal_node/constants.ml | 8 +++++ src/bin_dal_node/constants.mli | 8 +++++ src/bin_dal_node/store.ml | 66 ++++++++++++++++++++++++++++++++++ src/bin_dal_node/store.mli | 35 ++++++++++++++++++ 4 files changed, 117 insertions(+) diff --git a/src/bin_dal_node/constants.ml b/src/bin_dal_node/constants.ml index f64a9f254951..ee09d41e018c 100644 --- a/src/bin_dal_node/constants.ml +++ b/src/bin_dal_node/constants.ml @@ -92,3 +92,11 @@ let crawler_re_processing_delay = 5. (* Sleep delay between refreshing the ips associated to bootstrap dns names *) let bootstrap_dns_refresh_delay = 300. + +(* This size is being used for the node store's traps cache. While + [proto_parameters.Dal_plugin.attestation_lag] defines the minimum + number of levels for which traps must be retained, we maintain a + larger cache capacity of 50 levels. This extended size is + acceptable since the cache is sparsely populated due to + [proto_parameters.traps_fraction]. *) +let traps_cache_size = 50 diff --git a/src/bin_dal_node/constants.mli b/src/bin_dal_node/constants.mli index 78e12e6de696..961b57277f81 100644 --- a/src/bin_dal_node/constants.mli +++ b/src/bin_dal_node/constants.mli @@ -81,3 +81,11 @@ val crawler_re_processing_delay : float (* Sleep delay between refreshing the ips associated to bootstrap dns names *) val bootstrap_dns_refresh_delay : float + +(** This size is being used for the store's traps cache. While + [proto_parameters.Dal_plugin.attestation_lag] should define the + minimum number of levels for which traps must be retained, we + maintain a larger cache capacity of 50 levels. This extended size + is acceptable since the cache is sparsely populated due to + [proto_parameters.traps_fraction]. *) +val traps_cache_size : int diff --git a/src/bin_dal_node/store.ml b/src/bin_dal_node/store.ml index 14fa1049bf18..5a6dde31135d 100644 --- a/src/bin_dal_node/store.ml +++ b/src/bin_dal_node/store.ml @@ -328,6 +328,67 @@ module Slot_id_cache = struct |> Option.filter_map (Fun.flip get_opt slot_index) end +module Traps = struct + module Level_map = + Aches.Vache.Map (Aches.Vache.FIFO_Precise) (Aches.Vache.Strong) + (struct + type t = Types.level + + let equal = Int32.equal + + let hash = Hashtbl.hash + end) + + module Slot_index_map = Map.Make (Int) + module Shard_index_map = Map.Make (Int) + + type payload = + Signature.Public_key_hash.t * Cryptobox.share * Cryptobox.shard_proof + + type t = payload Shard_index_map.t Slot_index_map.t Level_map.t + + let create ~capacity = Level_map.create capacity + + let add_slot_index t ~slot_index ~shard_index ~delegate ~share ~shard_proof = + let shard_index_map_opt = Slot_index_map.find_opt shard_index t in + let shard_index_map = + Option.value ~default:Shard_index_map.empty shard_index_map_opt + in + let new_shard_index_map = + Shard_index_map.add + shard_index + (delegate, share, shard_proof) + shard_index_map + in + Slot_index_map.add slot_index new_shard_index_map t + + let add t ~slot_id ~shard_index ~delegate ~share ~shard_proof = + let Types.Slot_id.{slot_level; slot_index} = slot_id in + let slot_index_map_opt = Level_map.find_opt t slot_level in + let slot_index_map = + Option.value ~default:Slot_index_map.empty slot_index_map_opt + in + let new_slot_index_map = + add_slot_index + slot_index_map + ~slot_index + ~shard_index + ~delegate + ~share + ~shard_proof + 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 + | None -> [] + | Some m -> ( + match Slot_index_map.find_opt slot_index m with + | None -> [] + | Some m -> Shard_index_map.bindings m) +end + module Statuses = struct type t = (int32, int, Types.header_status) KVS.t @@ -542,6 +603,7 @@ type t = { slot_header_statuses : Statuses.t; shards : Shards.t; slots : Slots.t; + traps : Traps.t; cache : (Cryptobox.slot * Cryptobox.share array * Cryptobox.shard_proof array) Commitment_indexed_cache.t; @@ -568,6 +630,8 @@ let slot_header_statuses {slot_header_statuses; _} = slot_header_statuses let slots {slots; _} = slots +let traps {traps; _} = traps + let init_sqlite_skip_list_cells_store ?(perm = `Read_write) data_dir = let open Lwt_result_syntax in let open Filename.Infix in @@ -850,6 +914,7 @@ let init config = let* slot_header_statuses = Statuses.init base_dir Stores_dirs.status in let* shards = Shards.init base_dir Stores_dirs.shard in let* slots = Slots.init base_dir Stores_dirs.slot in + let traps = Traps.create ~capacity:Constants.traps_cache_size in let* last_processed_level = Last_processed_level.init ~root_dir:base_dir in let* first_seen_level = First_seen_level.init ~root_dir:base_dir in let* skip_list_cells_store = init_sqlite_skip_list_cells_store base_dir in @@ -858,6 +923,7 @@ let init config = { shards; slots; + traps; slot_header_statuses; cache = Commitment_indexed_cache.create Constants.cache_size; finalized_commitments = diff --git a/src/bin_dal_node/store.mli b/src/bin_dal_node/store.mli index dfe38550da5f..39299eca711d 100644 --- a/src/bin_dal_node/store.mli +++ b/src/bin_dal_node/store.mli @@ -125,6 +125,37 @@ module Commitment_indexed_cache : sig val find_opt : 'a t -> commitment -> 'a option end +module Traps : sig + (** A cache for trap data that stores up to [Constants.traps_cache_size] levels + in memory. *) + type t + + (** [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 + added in ascending order within a window of + [proto_parameters.attestation_lag]. When the cache reaches its + capacity, the oldest trap data (relative to the highest stored + level) is removed when adding new entries. *) + val add : + t -> + slot_id:Types.Slot_id.t -> + shard_index:Types.shard_index -> + delegate:Signature.public_key_hash -> + share:Cryptobox.share -> + 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 +end + module Last_processed_level : Single_value_store.S with type value = int32 module First_seen_level : Single_value_store.S with type value = int32 @@ -171,6 +202,10 @@ val slot_header_statuses : t -> Statuses.t [t]. *) val slots : t -> Slots.t +(** [traps t] returns the traps store associated with the store + [t]. *) +val traps : t -> Traps.t + (** [cache_entry store commitment entry] adds or replace an entry to the cache with key [commitment]. *) val cache_entry : -- GitLab From 3225da3a740717e92739ce6cd3a59b979a800721 Mon Sep 17 00:00:00 2001 From: phink Date: Thu, 16 Jan 2025 17:53:27 +0100 Subject: [PATCH 4/8] DAL/Node: add maybe_register_trap in Slot_manager --- src/bin_dal_node/event.ml | 12 ++++++++++++ src/bin_dal_node/slot_manager.ml | 30 ++++++++++++++++++++++++++++++ src/bin_dal_node/slot_manager.mli | 8 ++++++++ 3 files changed, 50 insertions(+) diff --git a/src/bin_dal_node/event.ml b/src/bin_dal_node/event.ml index a1cc5ff87181..d2e9dc39c28f 100644 --- a/src/bin_dal_node/event.ml +++ b/src/bin_dal_node/event.ml @@ -759,3 +759,15 @@ let trap_check_failure = ("published_level", Data_encoding.int32) ("slot_index", Data_encoding.int31) ("shard_index", Data_encoding.int31) + +let trap_registration_fail = + declare_3 + ~section + ~name:"trap_registration_fail" + ~msg: + "An error occurred when checking if the shard for delegate {delegate}, \ + slot index {slot_index} and shard index {shard_index} is a trap" + ~level:Warning + ("delegate", Signature.Public_key_hash.encoding) + ("slot_index", Data_encoding.int31) + ("shard_index", Data_encoding.int31) diff --git a/src/bin_dal_node/slot_manager.ml b/src/bin_dal_node/slot_manager.ml index 375804771bc7..6a7ac9307d80 100644 --- a/src/bin_dal_node/slot_manager.ml +++ b/src/bin_dal_node/slot_manager.ml @@ -195,6 +195,36 @@ let commit cryptobox polynomial = (* Main functions *) +let maybe_register_trap ctxt message_id message = + let proto_parameters = Node_context.get_proto_parameters ctxt in + let delegate = message_id.Types.Message_id.pkh in + let Types.Message.{share; shard_proof} = message in + let Types.Message_id.{slot_index; level; shard_index; _} = message_id in + let trap_res = + Trap.share_is_trap + ~traps_fraction:proto_parameters.traps_fraction + delegate + share + in + match trap_res with + | Ok true -> + let store = Node_context.get_store ctxt in + let traps_store = Store.traps store in + let slot_id = Types.Slot_id.{slot_index; slot_level = level} in + Store.Traps.add + traps_store + ~slot_id + ~shard_index + ~delegate + ~share + ~shard_proof + | Ok false -> () + | Error _ -> + Event.( + emit__dont_wait__use_with_care + trap_registration_fail + (delegate, slot_index, shard_index)) + let add_commitment_shards ~shards_proofs_precomputation node_store cryptobox commitment slot polynomial = let open Lwt_result_syntax in diff --git a/src/bin_dal_node/slot_manager.mli b/src/bin_dal_node/slot_manager.mli index bf6dae168fc4..15095c646a00 100644 --- a/src/bin_dal_node/slot_manager.mli +++ b/src/bin_dal_node/slot_manager.mli @@ -197,3 +197,11 @@ val get_slot_shard : Types.slot_id -> Types.shard_index -> (Cryptobox.shard, [Errors.other | Errors.not_found]) result Lwt.t + +(** [maybe_register_trap ctxt message_id message] checks if the given + message is a trap according to [Trap.share_is_trap]. If the share + is identified as a trap, it is stored in the traps cache of the + DAL node store. Otherwise does nothing. +*) +val maybe_register_trap : + Node_context.t -> Types.Message_id.t -> Types.Message.t -> unit -- GitLab From 16912aa59830960e078ee7e8eea04941c1e75a36 Mon Sep 17 00:00:00 2001 From: phink Date: Thu, 16 Jan 2025 19:06:59 +0100 Subject: [PATCH 5/8] DAL/Node: maybe register a trap when receiving a gossipsub message --- src/bin_dal_node/daemon.ml | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/bin_dal_node/daemon.ml b/src/bin_dal_node/daemon.ml index 2a34027b4232..f44ee04d9eee 100644 --- a/src/bin_dal_node/daemon.ml +++ b/src/bin_dal_node/daemon.ml @@ -240,11 +240,20 @@ module Handler = struct with | `Valid -> (* 3. Only check for message validity if the message_id is valid. *) - Option.fold - message - ~none:`Valid - ~some: - (gossipsub_app_message_payload_validation cryptobox message_id) + let res = + Option.fold + message + ~none:`Valid + ~some: + (gossipsub_app_message_payload_validation + cryptobox + message_id) + in + if res = `Valid then + Option.iter + (Slot_manager.maybe_register_trap ctxt message_id) + message ; + res | other -> (* 4. In the case the message id is not Valid. *) other -- GitLab From 8f0d699cc7336859fdbb09aaa42ed3e75cd5f08d Mon Sep 17 00:00:00 2001 From: phink Date: Thu, 16 Jan 2025 19:04:39 +0100 Subject: [PATCH 6/8] DAL/Node: maybe register a trap when publishing a gossipsub message --- src/bin_dal_node/amplificator.ml | 1 + src/bin_dal_node/daemon.ml | 2 +- src/bin_dal_node/slot_manager.ml | 9 ++++++--- src/bin_dal_node/slot_manager.mli | 3 ++- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/bin_dal_node/amplificator.ml b/src/bin_dal_node/amplificator.ml index 8acb345b0eba..dcfd1a0401ce 100644 --- a/src/bin_dal_node/amplificator.ml +++ b/src/bin_dal_node/amplificator.ml @@ -304,6 +304,7 @@ let reply_receiver_job {process; query_store; _} node_context = in let* () = Slot_manager.publish_proved_shards + node_context slot_id ~level_committee:(Node_context.fetch_committee node_context) proto_parameters diff --git a/src/bin_dal_node/daemon.ml b/src/bin_dal_node/daemon.ml index f44ee04d9eee..cea937dfb429 100644 --- a/src/bin_dal_node/daemon.ml +++ b/src/bin_dal_node/daemon.ml @@ -582,9 +582,9 @@ module Handler = struct {slot_level = published_level; slot_index} in Slot_manager.publish_slot_data + ctxt ~level_committee:(Node_context.fetch_committee ctxt) ~slot_size:proto_parameters.cryptobox_parameters.slot_size - store (Node_context.get_gs_worker ctxt) proto_parameters commitment diff --git a/src/bin_dal_node/slot_manager.ml b/src/bin_dal_node/slot_manager.ml index 6a7ac9307d80..8c22250d0f47 100644 --- a/src/bin_dal_node/slot_manager.ml +++ b/src/bin_dal_node/slot_manager.ml @@ -273,7 +273,7 @@ let shards_to_attesters committee = (** This function publishes the shards of a commitment that is waiting for attestion on L1 if this node has those shards and their proofs in memory. *) -let publish_proved_shards (slot_id : Types.slot_id) ~level_committee +let publish_proved_shards ctxt (slot_id : Types.slot_id) ~level_committee proto_parameters commitment shards shard_proofs gs_worker = let open Lwt_result_syntax in let attestation_level = @@ -313,6 +313,7 @@ let publish_proved_shards (slot_id : Types.slot_id) ~level_committee pkh; } in + maybe_register_trap ctxt message_id message ; Gossipsub.Worker.( Publish_message {message; topic; message_id} |> app_input gs_worker) ; @@ -321,9 +322,10 @@ let publish_proved_shards (slot_id : Types.slot_id) ~level_committee (** This function publishes the shards of a commitment that is waiting for attestion on L1 if this node has those shards and their proofs in memory. *) -let publish_slot_data ~level_committee (node_store : Store.t) ~slot_size - gs_worker proto_parameters commitment slot_id = +let publish_slot_data ctxt ~level_committee ~slot_size gs_worker + proto_parameters commitment slot_id = let open Lwt_result_syntax in + let node_store = Node_context.get_store ctxt in let cache = Store.cache node_store in match Store.Commitment_indexed_cache.find_opt cache commitment with | None -> @@ -353,6 +355,7 @@ let publish_slot_data ~level_committee (node_store : Store.t) ~slot_size |> Errors.to_tzresult in publish_proved_shards + ctxt slot_id ~level_committee proto_parameters diff --git a/src/bin_dal_node/slot_manager.mli b/src/bin_dal_node/slot_manager.mli index 15095c646a00..683d56e42b40 100644 --- a/src/bin_dal_node/slot_manager.mli +++ b/src/bin_dal_node/slot_manager.mli @@ -126,6 +126,7 @@ val add_commitment_shards : (** This function publishes the given shards and their proofs. *) val publish_proved_shards : + Node_context.t -> Types.slot_id -> level_committee: (level:int32 -> @@ -141,10 +142,10 @@ val publish_proved_shards : attestion on L1 if this node has those shards on disk and their proofs in memory. *) val publish_slot_data : + Node_context.t -> level_committee: (level:int32 -> Committee_cache.shard_indexes Signature.Public_key_hash.Map.t tzresult Lwt.t) -> - Store.t -> slot_size:int -> Gossipsub.Worker.t -> Dal_plugin.proto_parameters -> -- GitLab From 6eac5d3b1d83c56ee4c638b6eab9ce17e38ba5b5 Mon Sep 17 00:00:00 2001 From: phink Date: Wed, 15 Jan 2025 00:48:43 +0100 Subject: [PATCH 7/8] DAL/Node: implement the DAL accuser daemon handler --- src/bin_dal_node/accuser.ml | 82 ++++++++++++++++++++++++++++++++++++ src/bin_dal_node/accuser.mli | 22 ++++++++++ src/bin_dal_node/daemon.ml | 6 ++- 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 src/bin_dal_node/accuser.ml create mode 100644 src/bin_dal_node/accuser.mli diff --git a/src/bin_dal_node/accuser.ml b/src/bin_dal_node/accuser.ml new file mode 100644 index 000000000000..733b30923072 --- /dev/null +++ b/src/bin_dal_node/accuser.ml @@ -0,0 +1,82 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* SPDX-FileCopyrightText: 2025 Nomadic Labs, *) +(* *) +(*****************************************************************************) + +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 + +let inject_entrapment_evidences (type b) + (module Plugin : Dal_plugin.T with type block_info = b) node_ctxt rpc_ctxt + (block : b) = + 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 + Plugin.inject_entrapment_evidence + rpc_ctxt + ~attested_level + attestation + ~slot_index + ~shard + ~proof + else return_unit) + traps) + monitored_slot_indices) diff --git a/src/bin_dal_node/accuser.mli b/src/bin_dal_node/accuser.mli new file mode 100644 index 000000000000..aecdce4f1817 --- /dev/null +++ b/src/bin_dal_node/accuser.mli @@ -0,0 +1,22 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* SPDX-FileCopyrightText: 2025 Nomadic Labs, *) +(* *) +(*****************************************************************************) + +(** [inject_entrapment_evidences plugin node_ctxt rpc_ctxt block] + processes entrapment evidence for slot indices monitored by the + DAL node. + + More notably, it: + - fetches attestation operations for the attested [block], + - processes potential entrapments for each monitored slot, + - injects entrapment evidence when found. +*) +val inject_entrapment_evidences : + (module Dal_plugin.T with type block_info = 'block) -> + Node_context.t -> + Rpc_context.t -> + 'block -> + unit tzresult Lwt.t diff --git a/src/bin_dal_node/daemon.ml b/src/bin_dal_node/daemon.ml index cea937dfb429..9ce9a1dd2159 100644 --- a/src/bin_dal_node/daemon.ml +++ b/src/bin_dal_node/daemon.ml @@ -621,7 +621,11 @@ module Handler = struct get_attestations Plugin.is_attested in - return_unit + Accuser.inject_entrapment_evidences + (module Plugin) + ctxt + cctxt + block_info else return_unit in let*? block_round = Plugin.get_round finalized_shell_header.fitness in -- GitLab From a4bd59e37c0059b74cb5b4289d354a129ee40cd1 Mon Sep 17 00:00:00 2001 From: phink Date: Fri, 17 Jan 2025 12:11:21 +0100 Subject: [PATCH 8/8] DAL/Node: emit events when accusing --- src/bin_dal_node/accuser.ml | 6 ++++++ src/bin_dal_node/event.ml | 13 +++++++++++++ 2 files changed, 19 insertions(+) diff --git a/src/bin_dal_node/accuser.ml b/src/bin_dal_node/accuser.ml index 733b30923072..bafad39baae4 100644 --- a/src/bin_dal_node/accuser.ml +++ b/src/bin_dal_node/accuser.ml @@ -70,6 +70,12 @@ let inject_entrapment_evidences (type b) | 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 diff --git a/src/bin_dal_node/event.ml b/src/bin_dal_node/event.ml index d2e9dc39c28f..3039734a8700 100644 --- a/src/bin_dal_node/event.ml +++ b/src/bin_dal_node/event.ml @@ -748,6 +748,19 @@ let warn_attester_did_not_attest_slot = ("attested_level", Data_encoding.int32) ~pp1:Signature.Public_key_hash.pp_short +let trap_injection = + declare_4 + ~section + ~name:"trap_injection" + ~msg: + "Injecting entrapment evidence for delegate {delegate}, published level \ + {published_level}, slot index {slot_index}, shard index {shard_index}" + ~level:Notice + ("delegate", Signature.Public_key_hash.encoding) + ("published_level", Data_encoding.int32) + ("slot_index", Data_encoding.int31) + ("shard_index", Data_encoding.int31) + let trap_check_failure = declare_3 ~section -- GitLab