From 73f230f7c5627d3d35e6f9eca6d93dd7b9f7c67c Mon Sep 17 00:00:00 2001 From: "iguerNL@Functori" Date: Mon, 20 Oct 2025 11:47:25 +0200 Subject: [PATCH 1/5] DAL/Skip-list: extend find_by_slot_id_opt to also return the attestation lag --- src/lib_dal_node/dal_store_sqlite3.ml | 18 +++++++++--------- src/lib_dal_node/dal_store_sqlite3.mli | 9 ++++++--- src/lib_dal_node/slot_manager.ml | 2 +- src/lib_dal_node/store.mli | 7 ++++--- 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/src/lib_dal_node/dal_store_sqlite3.ml b/src/lib_dal_node/dal_store_sqlite3.ml index 747791abb16f..b15ff5f84708 100644 --- a/src/lib_dal_node/dal_store_sqlite3.ml +++ b/src/lib_dal_node/dal_store_sqlite3.ml @@ -175,18 +175,18 @@ module Skip_list_cells = struct WHERE hash = $1 |sql} + (* Returns the skip list cell together with its attestation_lag *) let find_by_slot_id_opt : - (level * slot_index, Skip_list_cell.t, [`One | `Zero]) t = + (level * slot_index, Skip_list_cell.t * int, [`One | `Zero]) t = let open Caqti_type.Std in - (t2 published_level dal_slot_index ->? skip_list_cell) + (t2 published_level dal_slot_index ->? t2 skip_list_cell attestation_lag) @@ {sql| - SELECT cell - FROM skip_list_cells - WHERE hash = ( - SELECT skip_list_cell_hash - FROM skip_list_slots - WHERE published_level = $1 AND slot_index = $2 - )|sql} + SELECT c.cell, s.attestation_lag + FROM skip_list_cells AS c + JOIN skip_list_slots AS s + ON s.skip_list_cell_hash = c.hash + WHERE s.published_level = $1 AND s.slot_index = $2 + |sql} let find_by_level : ( level, diff --git a/src/lib_dal_node/dal_store_sqlite3.mli b/src/lib_dal_node/dal_store_sqlite3.mli index 2a6d9693fa5b..c910e19156af 100644 --- a/src/lib_dal_node/dal_store_sqlite3.mli +++ b/src/lib_dal_node/dal_store_sqlite3.mli @@ -49,10 +49,13 @@ module Skip_list_cells : sig Skip_list_cell.t option tzresult Lwt.t (** [find_by_slot_id_opt ?conn store ~slot_id] returns the cell associated to - ([slot_id]) in the [store], if any. Uses the [conn] if provided (defaults - to [None]). *) + ([slot_id]) in the [store] alongside the attestation lag, if any. Uses the + [conn] if provided (defaults to [None]). *) val find_by_slot_id_opt : - ?conn:conn -> t -> Types.slot_id -> Skip_list_cell.t option tzresult Lwt.t + ?conn:conn -> + t -> + Types.slot_id -> + (Skip_list_cell.t * int) option tzresult Lwt.t (** [find_by_level ?conn store ~published_level] retrieves the tuples (cell * hash * slot_index) for the given published level, if any. The results are diff --git a/src/lib_dal_node/slot_manager.ml b/src/lib_dal_node/slot_manager.ml index 7e024be922dc..39cdf8b5f592 100644 --- a/src/lib_dal_node/slot_manager.ml +++ b/src/lib_dal_node/slot_manager.ml @@ -371,7 +371,7 @@ let try_get_slot_header_from_indexed_skip_list (module Plugin : Dal_plugin.T) in match cell_bytes_opt with | None -> return_none - | Some cell_bytes -> + | Some (cell_bytes, __attestation_lag) -> Dal_proto_types.Skip_list_cell.to_proto Plugin.Skip_list.cell_encoding cell_bytes diff --git a/src/lib_dal_node/store.mli b/src/lib_dal_node/store.mli index 69c9c4c81a1d..3ac6b9c9119a 100644 --- a/src/lib_dal_node/store.mli +++ b/src/lib_dal_node/store.mli @@ -247,13 +247,14 @@ module Skip_list_cells : sig Skip_list_hash.t -> Skip_list_cell.t option tzresult Lwt.t - (** [find_by_slot_id_opt ?conn store slot_id] returns the cell associated to - ([slot_id.slot_level], [slot_id.slot_index]) in the [store], if any. *) + (** [find_by_slot_id_opt ?conn store slot_id] returns the cell and the + attestation lag associated to ([slot_id.slot_level], [slot_id.slot_index]) + in the [store], if any. *) val find_by_slot_id_opt : ?conn:Sqlite.conn -> t -> Types.slot_id -> - Dal_proto_types.Skip_list_cell.t option tzresult Lwt.t + (Dal_proto_types.Skip_list_cell.t * int) option tzresult Lwt.t (** See {!Dal_store_sqlite3.Skip_list_cells.find_by_level}. *) val find_by_level : -- GitLab From 181eb0f3fb9beb384e85b5437245399baea74bbc Mon Sep 17 00:00:00 2001 From: "iguerNL@Functori" Date: Mon, 20 Oct 2025 11:54:42 +0200 Subject: [PATCH 2/5] DAL/Node: use the right attestation_lag + published level to fetch the plugin --- src/lib_dal_node/RPC_server.ml | 19 ++-------------- src/lib_dal_node/slot_manager.ml | 36 ++++++++----------------------- src/lib_dal_node/slot_manager.mli | 1 - 3 files changed, 11 insertions(+), 45 deletions(-) diff --git a/src/lib_dal_node/RPC_server.ml b/src/lib_dal_node/RPC_server.ml index a864d5349dfc..e4e9e0f8300e 100644 --- a/src/lib_dal_node/RPC_server.ml +++ b/src/lib_dal_node/RPC_server.ml @@ -236,25 +236,10 @@ module Slots_handlers = struct let get_slot_commitment ctxt slot_level slot_index () () = call_handler1 (fun () -> - let slot_id : Types.slot_id = {slot_level; slot_index} in let open Lwt_result_syntax in - let published_level = slot_id.Types.Slot_id.slot_level in - let*? proto_parameters = - Node_context.get_proto_parameters ctxt ~level:(`Level published_level) - |> Errors.other_result - in - let attested_level = - Int32.(add published_level (of_int proto_parameters.attestation_lag)) - in - let*? plugin = - Node_context.get_plugin_for_level ctxt ~level:attested_level - |> Errors.other_result - in + let slot_id : Types.slot_id = {slot_level; slot_index} in let* slot_header_opt = - Slot_manager.try_get_slot_header_from_indexed_skip_list - plugin - ctxt - slot_id + Slot_manager.try_get_slot_header_from_indexed_skip_list ctxt slot_id |> Errors.other_lwt_result in match slot_header_opt with diff --git a/src/lib_dal_node/slot_manager.ml b/src/lib_dal_node/slot_manager.ml index 39cdf8b5f592..aeb19feb1493 100644 --- a/src/lib_dal_node/slot_manager.ml +++ b/src/lib_dal_node/slot_manager.ml @@ -361,8 +361,7 @@ let try_get_commitment_of_slot_id_from_memory ctxt slot_id = - Return the extracted slot header, if available. Returns [None] if the cell is not found in the store. *) -let try_get_slot_header_from_indexed_skip_list (module Plugin : Dal_plugin.T) - ctxt slot_id = +let try_get_slot_header_from_indexed_skip_list ctxt slot_id = let open Lwt_result_syntax in let* cell_bytes_opt = Store.Skip_list_cells.find_by_slot_id_opt @@ -371,7 +370,11 @@ let try_get_slot_header_from_indexed_skip_list (module Plugin : Dal_plugin.T) in match cell_bytes_opt with | None -> return_none - | Some (cell_bytes, __attestation_lag) -> + | Some (cell_bytes, attestation_lag) -> + let*? (module Plugin : Dal_plugin.T) = + let level = Int32.(add slot_id.slot_level (of_int attestation_lag)) in + Node_context.get_plugin_for_level ctxt ~level + in Dal_proto_types.Skip_list_cell.to_proto Plugin.Skip_list.cell_encoding cell_bytes @@ -429,11 +432,11 @@ let _try_get_slot_header_from_L1_skip_list (module Plugin : Dal_plugin.T) ctxt Returns [Some commitment] if found and matches the [slot_id], [None] otherwise. Performs assertions to ensure the returned slot header matches the requested [slot_id]. *) -let try_get_commitment_of_slot_id_from_skip_list dal_plugin ctxt slot_id = +let try_get_commitment_of_slot_id_from_skip_list ctxt slot_id = let open Lwt_result_syntax in let*! published_slot_header_opt = let*! from_sqlite = - try_get_slot_header_from_indexed_skip_list dal_plugin ctxt slot_id + try_get_slot_header_from_indexed_skip_list ctxt slot_id in match from_sqlite with | Ok (Some _header as res) -> return res @@ -471,28 +474,7 @@ let get_commitment_from_slot_id ctxt slot_id = match try_get_commitment_of_slot_id_from_memory ctxt slot_id with | Some res -> return res | None -> ( - let published_level = slot_id.Types.Slot_id.slot_level in - let*? parameters = - Node_context.get_proto_parameters ctxt ~level:(`Level published_level) - in - let attested_level = - Int32.add - published_level - (Int32.of_int parameters.Types.attestation_lag) - in - (* Note: the precise [attested_level] is not important; however, it is - important that we use the right plugin to decode cells in - [try_get_slot_header_from_indexed_skip_list]. Even if the - [attestation_lag] value decreases in protocol P2 wrt P1, we are sure to - get the right plugin, because [attested_level] is computed with the - (bigger) value from P1, so we are sure [attested_level] is in P2 when - needed. *) - let*? dal_plugin = - Node_context.get_plugin_for_level ctxt ~level:attested_level - in - let*! res = - try_get_commitment_of_slot_id_from_skip_list dal_plugin ctxt slot_id - in + let*! res = try_get_commitment_of_slot_id_from_skip_list ctxt slot_id in match res with | Ok (Some res) -> return res | Ok None -> diff --git a/src/lib_dal_node/slot_manager.mli b/src/lib_dal_node/slot_manager.mli index 64a0b032b68d..a6f91df6f41e 100644 --- a/src/lib_dal_node/slot_manager.mli +++ b/src/lib_dal_node/slot_manager.mli @@ -115,7 +115,6 @@ val get_slot_content : Returns [None] if the cell is not found in the store. *) val try_get_slot_header_from_indexed_skip_list : - (module Dal_plugin.T) -> Node_context.t -> Types.slot_id -> Dal_plugin.slot_header option tzresult Lwt.t -- GitLab From 6182d6262ff880c6dd3b4a648c8dc2e522e373cd Mon Sep 17 00:00:00 2001 From: Julien Sagot Date: Tue, 14 Oct 2025 16:02:20 +0200 Subject: [PATCH 3/5] DAL/Node: add Unpublished case to header_status type and store it --- src/lib_dal_node/store.ml | 14 +++++++++++--- src/lib_dal_node_services/types.ml | 10 +++++++++- src/lib_dal_node_services/types.mli | 2 +- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/lib_dal_node/store.ml b/src/lib_dal_node/store.ml index 453d8dd19a33..9f291013fa61 100644 --- a/src/lib_dal_node/store.ml +++ b/src/lib_dal_node/store.ml @@ -991,9 +991,11 @@ let add_slot_headers ~number_of_slots ~block_level slot_headers t = in (* This invariant should hold. *) assert (Int32.equal published_level block_level) ; - let index = Types.Slot_id.{slot_level = published_level; slot_index} in + let slot_id = + Types.Slot_id.{slot_level = published_level; slot_index} + in let () = - Statuses_cache.add_status statuses_cache `Waiting_attestation index + Statuses_cache.add_status statuses_cache `Waiting_attestation slot_id in Slot_id_cache.add ~number_of_slots t.finalized_commitments slot_header ; return (SI.add slot_index waiting)) @@ -1002,6 +1004,12 @@ let add_slot_headers ~number_of_slots ~block_level slot_headers t = in List.iter (fun i -> - Dal_metrics.slot_waiting_for_attestation ~set:(SI.mem i waiting) i) + let i_is_waiting_for_attestation = SI.mem i waiting in + Dal_metrics.slot_waiting_for_attestation + ~set:i_is_waiting_for_attestation + i ; + let slot_id = Types.Slot_id.{slot_level = block_level; slot_index = i} in + if not i_is_waiting_for_attestation then + Statuses_cache.add_status statuses_cache `Unpublished slot_id) (0 -- (number_of_slots - 1)) ; return_unit diff --git a/src/lib_dal_node_services/types.ml b/src/lib_dal_node_services/types.ml index 6941c2ae75bc..7a7b00e5e41d 100644 --- a/src/lib_dal_node_services/types.ml +++ b/src/lib_dal_node_services/types.ml @@ -347,7 +347,8 @@ type slot_set = {slots : bool list; published_level : int32} type attestable_slots = Attestable_slots of slot_set | Not_in_committee -type header_status = [`Waiting_attestation | `Attested | `Unattested] +type header_status = + [`Waiting_attestation | `Attested | `Unattested | `Unpublished] type shard_index = int @@ -448,12 +449,19 @@ let header_status_encoding : header_status Data_encoding.t = (constant "unattested") (function `Unattested -> Some () | _ -> None) (function () -> `Unattested); + case + ~title:"unpublished" + (Tag 3) + (constant "unpublished") + (function `Unpublished -> Some () | _ -> None) + (function () -> `Unpublished); ] let pp_header_status fmt = function | `Waiting_attestation -> Format.fprintf fmt "waiting_attestation" | `Attested -> Format.fprintf fmt "attested" | `Unattested -> Format.fprintf fmt "unattested" + | `Unpublished -> Format.fprintf fmt "unpublished" let slot_header_encoding = let open Data_encoding in diff --git a/src/lib_dal_node_services/types.mli b/src/lib_dal_node_services/types.mli index 2518b735c819..cf89aec1294f 100644 --- a/src/lib_dal_node_services/types.mli +++ b/src/lib_dal_node_services/types.mli @@ -242,7 +242,7 @@ type header_status = | `Attested (** The slot header was included in an L1 block and attested. *) | `Unattested (** The slot header was included in an L1 block but not timely attested. *) - ] + | `Unpublished (** The slot header was not included in any L1 block. *) ] (** A DAL node can be in one of two profiles (aka modes): bootstrap or controller. A controller node can have one or more (sub)profiles that -- GitLab From 4a5291c261b269c73d16c8239452777f122e0994 Mon Sep 17 00:00:00 2001 From: "iguerNL@Functori" Date: Mon, 20 Oct 2025 17:29:39 +0200 Subject: [PATCH 4/5] DAL: get_slot_status uses the right attestation_lag to decode the cells --- src/lib_dal_node/slot_manager.ml | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/src/lib_dal_node/slot_manager.ml b/src/lib_dal_node/slot_manager.ml index aeb19feb1493..2295d133c650 100644 --- a/src/lib_dal_node/slot_manager.ml +++ b/src/lib_dal_node/slot_manager.ml @@ -765,31 +765,17 @@ module Statuses = struct in match slot_cell with | None -> return_none - | Some cell -> ( - let*? proto_parameters = - Node_context.get_proto_parameters - ctxt - ~level:(`Level slot_id.slot_level) - in - let attested_level = - Int32.( - add slot_id.slot_level (of_int proto_parameters.attestation_lag)) - in + | Some (cell, attestation_lag) -> let*? (module Plugin : Dal_plugin.T) = - Node_context.get_plugin_for_level ctxt ~level:attested_level - in - let cell = - Dal_proto_types.Skip_list_cell.to_proto - Plugin.Skip_list.cell_encoding - cell + let level = Int32.(add slot_id.slot_level (of_int attestation_lag)) in + Node_context.get_plugin_for_level ctxt ~level in - match Plugin.Skip_list.proto_attestation_status cell with - | None -> - (* Old protocols that do not expose the information *) - return_none - | Some `Unpublished -> return_none - | Some `Attested -> return_some `Attested - | Some `Unattested -> return_some `Unattested) + Dal_proto_types.Skip_list_cell.to_proto + Plugin.Skip_list.cell_encoding + cell + |> Plugin.Skip_list.proto_attestation_status + |> Option.map (fun s -> (s :> Types.header_status)) + |> return let find_status ctxt (slot_id : Types.slot_id) = let open Lwt_result_syntax in -- GitLab From ff8023841ad5892c1d143119ce50b9f69edfbdc4 Mon Sep 17 00:00:00 2001 From: Julien Sagot Date: Wed, 15 Oct 2025 09:29:37 +0200 Subject: [PATCH 5/5] DAL: document RPC behavior change --- CHANGES.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 98d15c9acb2d..c15afce04b6e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -69,6 +69,10 @@ Data Availability Layer (DAL) DAL node ~~~~~~~~ +- **Breaking change** The ``/levels//slots//status`` + RPC now answers with ``unpublished`` status for unpublished slots instead + of a 404 empty response. (MR :gl:`!19613`) + - Added RPC ``GET /profiles/{pkh}/monitor/attestable_slots`` to open a monitoring stream that emits a JSON ``slot_id`` each time a slot becomes attestable for the given public key hash (``pkh``). A slot id is emitted when all shards assigned to -- GitLab