diff --git a/CHANGES.rst b/CHANGES.rst index 83f06a6a21bd9de0b7ef61e8707a6accd200ba76..aad6e5cbfb189a0a648c6a0b0e40e9a4a39edbc8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -246,6 +246,10 @@ DAL node - **Breaking change** The baker daemon ``--dal-node-timeout-percentage`` argument has been removed. (MR :gl:`!15554`) +- Added a new RPC ``attested_levels//slots//traps`` + which for a given attested level and slot index returns the shards that are + traps, together with their proofs. (MR :gl:`!16188`) + Protocol ~~~~~~~~ diff --git a/src/bin_dal_node/RPC_server.ml b/src/bin_dal_node/RPC_server.ml index 75bd288a52bfd3d9c20795332ebd499686d87575..825fe20a50beb609c67ad10f14e3aade23a96ebc 100644 --- a/src/bin_dal_node/RPC_server.ml +++ b/src/bin_dal_node/RPC_server.ml @@ -259,6 +259,12 @@ module Slots_handlers = struct let slot_id : Types.slot_id = {slot_level; slot_index} in Slot_manager.get_slot_shard store slot_id shard_index) + let get_slot_shard_with_proof ctxt slot_level slot_index shard_index () () = + call_handler1 (fun () -> + let store = Node_context.get_store ctxt in + let slot_id : Types.slot_id = {slot_level; slot_index} in + Slot_manager.get_slot_shard_with_proof store slot_id shard_index) + let get_slot_pages ctxt slot_level slot_index () () = call_handler1 (fun () -> let slot_id : Types.slot_id = {slot_level; slot_index} in @@ -478,6 +484,90 @@ module Profile_handlers = struct store proto_parameters ~attested_level) + + (* TODO https://gitlab.com/tezos/tezos/-/issues/7683 + Possibly replace this by a `monitor_traps` RPC. To monitor traps we could + use the stream of finalized heads given to us by the crawler (but note that + the crawler is not yet in the DAL node's context... so something needs to + be done about it). *) + let get_traps ctxt attested_level slot_index () () = + let open Lwt_result_syntax in + let proto_parameters = Node_context.get_proto_parameters ctxt in + let number_of_shards = + proto_parameters.Dal_plugin.cryptobox_parameters.number_of_shards + in + let traps_fraction = proto_parameters.traps_fraction in + let attestation_lag = Int32.of_int proto_parameters.attestation_lag in + let attestation_level = Int32.pred attested_level in + call_handler1 (fun () -> + let store = Node_context.get_store ctxt in + 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 attestation_lag + in + let slot_id = + Types.Slot_id.{slot_level = published_level; slot_index} + in + let shards = + Store.Shards.read_all (Store.shards store) slot_id ~number_of_shards + |> Seq_s.filter_map (function + | _, index, Ok share -> Some Cryptobox.{index; share} + | _ -> None) + in + let*! () = + let*! num_stored_shards = Seq_s.length shards in + if num_stored_shards <> number_of_shards then + Event.( + emit + get_traps_missing_shards + (slot_index, num_stored_shards, number_of_shards)) + else Lwt.return_unit + in + let* committee = + Node_context.fetch_committee ctxt ~level:attestation_level + |> Errors.other_lwt_result + in + let module Index_map = Map.Make (Int) in + let shard_indexes_map = + Signature.Public_key_hash.Map.fold + (fun pkh shard_indexes acc -> + List.fold_right + (fun index acc' -> Index_map.add index pkh acc') + shard_indexes + acc) + committee + Index_map.empty + in + let* traps = + Seq_es.ES.fold_left + (fun acc Cryptobox.{index; share} -> + match Index_map.find_opt index shard_indexes_map with + | None -> (* unreachable *) return acc + | Some delegate -> ( + let trap_res = + Trap.share_is_trap delegate share ~traps_fraction + in + match trap_res with + | Ok trap -> + if trap then + let* shard_with_proof = + Slot_manager.get_slot_shard_with_proof + store + slot_id + index + in + return @@ (shard_with_proof :: acc) + else return acc + | Error _ -> + (* TODO: emit warning *) + (* do not accuse if we're not sure *) + return acc)) + [] + (Seq_es.of_seqs shards) + in + return traps) end let version ctxt () () = @@ -627,6 +717,10 @@ let register : Tezos_rpc.Directory.opt_register2 Services.get_attestable_slots (Profile_handlers.get_attestable_slots ctxt) + |> add_service + Tezos_rpc.Directory.opt_register2 + Services.get_traps + (Profile_handlers.get_traps ctxt) |> add_service Tezos_rpc.Directory.opt_register2 Services.get_slot_pages diff --git a/src/bin_dal_node/event.ml b/src/bin_dal_node/event.ml index 348abdfde2527749c1d5addbbb1ecf4bd6697ed3..2c942264b99f74c508943333c3930d67f4ac563b 100644 --- a/src/bin_dal_node/event.ml +++ b/src/bin_dal_node/event.ml @@ -711,6 +711,18 @@ let get_attestable_slots_not_ok_warning = stored_shards expected_shards)) +let get_traps_missing_shards = + declare_3 + ~section + ~name:"get_traps__missing_shards_warning" + ~msg: + "For slot index {slots_index}, only {num_stored_shards} are stored, \ + expected {num_expected}." + ~level:Warning + ("slots_index", Data_encoding.(int31)) + ("num_stored_shards", Data_encoding.(int31)) + ("num_expected", Data_encoding.(int31)) + let get_attestable_slots_future_level_warning = declare_2 ~section diff --git a/src/bin_dal_node/slot_manager.ml b/src/bin_dal_node/slot_manager.ml index 375804771bc710345bdf0164c79bb2fd3f109215..a61267ce53de44dede29b502cbd9ec30df61ee7c 100644 --- a/src/bin_dal_node/slot_manager.ml +++ b/src/bin_dal_node/slot_manager.ml @@ -351,6 +351,25 @@ let get_slot_status ~slot_id node_store = let get_slot_shard (store : Store.t) (slot_id : Types.slot_id) shard_index = Store.Shards.read (Store.shards store) slot_id shard_index +let get_slot_shard_with_proof (store : Store.t) (slot_id : Types.slot_id) + shard_index = + let open Lwt_result_syntax in + let* shard = Store.Shards.read (Store.shards store) slot_id shard_index in + let commitment_opt = + Store.Slot_id_cache.find_opt (Store.finalized_commitments store) slot_id + in + match commitment_opt with + | None -> Lwt.return (Error `Not_found) + | Some commitment -> ( + let res = + Store.Commitment_indexed_cache.find_opt (Store.cache store) commitment + in + match res with + | None -> Lwt.return (Error `Not_found) + | Some (_slot, _shards, shard_proofs) -> + let proof = Array.get shard_proofs shard_index in + return (shard, proof)) + let get_slot_pages ~reconstruct_if_missing node_context slot_id = let open Lwt_result_syntax in let proto_parameters = Node_context.get_proto_parameters node_context in diff --git a/src/bin_dal_node/slot_manager.mli b/src/bin_dal_node/slot_manager.mli index bf6dae168fc440139fadd0572d7f4bac7227feb3..ec3b75fcefdab2557aa489bc5eec6079f63eea2d 100644 --- a/src/bin_dal_node/slot_manager.mli +++ b/src/bin_dal_node/slot_manager.mli @@ -197,3 +197,14 @@ val get_slot_shard : Types.slot_id -> Types.shard_index -> (Cryptobox.shard, [Errors.other | Errors.not_found]) result Lwt.t + +(** [get_slot_shard_with_proof store slot_id shard_index] returns the shard at + index [shard_index] of the slot given by [slot_id], and its proof. *) +val get_slot_shard_with_proof : + Store.t -> + Types.slot_id -> + Types.shard_index -> + ( Cryptobox.shard * Cryptobox.shard_proof, + [Errors.other | Errors.not_found] ) + result + Lwt.t diff --git a/src/lib_dal_node_services/services.ml b/src/lib_dal_node_services/services.ml index 5fe039ec51e1a31b6b3ba423ca2ba5624d0b97f8..04753be15ce2fafc20e52fe41e734b57381e078c 100644 --- a/src/lib_dal_node_services/services.ml +++ b/src/lib_dal_node_services/services.ml @@ -250,6 +250,32 @@ let get_attestable_slots : open_root / "profiles" /: Signature.Public_key_hash.rpc_arg / "attested_levels" /: Tezos_rpc.Arg.int32 / "attestable_slots") +let get_traps : + < meth : [`GET] + ; input : unit + ; output : + (Tezos_crypto_dal.Cryptobox.shard + * Tezos_crypto_dal.Cryptobox.shard_proof) + list + ; prefix : unit + ; params : (unit * level) * slot_index + ; query : unit > + service = + Tezos_rpc.Service.get_service + ~description: + "Get all the shards that are traps for a given slot index at the given \ + attested level." + ~query:Tezos_rpc.Query.empty + ~output: + Data_encoding.( + list + (obj2 + (req "shard" Cryptobox.shard_encoding) + (req "proof" Cryptobox.shard_proof_encoding))) + Tezos_rpc.Path.( + open_root / "attested_levels" /: Tezos_rpc.Arg.int32 / "slots" + /: Tezos_rpc.Arg.int / "traps") + let get_slot_shard : < meth : [`GET] ; input : unit diff --git a/src/lib_dal_node_services/services.mli b/src/lib_dal_node_services/services.mli index 3341780347abf641078a2afb8ef18fbb57e56d67..663940918e2edcdb5c2f27dded20e35c8fd4be4d 100644 --- a/src/lib_dal_node_services/services.mli +++ b/src/lib_dal_node_services/services.mli @@ -171,6 +171,21 @@ val get_attestable_slots : ; query : unit > service +(** For a given delegate (identified by its public key hash), attested level, + and slot index, return all the shards that are traps, together with the + associated shard proof. *) +val get_traps : + < meth : [`GET] + ; input : unit + ; output : + (Tezos_crypto_dal.Cryptobox.shard + * Tezos_crypto_dal.Cryptobox.shard_proof) + list + ; prefix : unit + ; params : (unit * Types.level) * Types.slot_index + ; query : unit > + service + (** Return the shard associated to the given index. *) val get_slot_shard : < meth : [`GET] diff --git a/tezt/tests/expected/dal.ml/Alpha- Testing DAL node (dal node list RPCs).out b/tezt/tests/expected/dal.ml/Alpha- Testing DAL node (dal node list RPCs).out index 7dfc832c9ba56f127c76e5dac1216210aa3680cb..ca59c64f73d229edb5293f9f2696e51d00f61402 100644 --- a/tezt/tests/expected/dal.ml/Alpha- Testing DAL node (dal node list RPCs).out +++ b/tezt/tests/expected/dal.ml/Alpha- Testing DAL node (dal node list RPCs).out @@ -3,6 +3,9 @@ Available services: + - GET /attested_levels//slots//traps + Get all the shards that are traps for a given slot index at the given + attested level. - GET /health Performs health checks on the DAL node, evaluating key components of the DAL node. Returns a health status indicating whether the DAL node @@ -118,6 +121,8 @@ Dynamic parameter description: int int int32 + int + int32 Warning: Failed to acquire the protocol version from the node diff --git a/tezt/tests/expected/dal.ml/Parisc- Testing DAL node (dal node list RPCs).out b/tezt/tests/expected/dal.ml/Parisc- Testing DAL node (dal node list RPCs).out index 7dfc832c9ba56f127c76e5dac1216210aa3680cb..ca59c64f73d229edb5293f9f2696e51d00f61402 100644 --- a/tezt/tests/expected/dal.ml/Parisc- Testing DAL node (dal node list RPCs).out +++ b/tezt/tests/expected/dal.ml/Parisc- Testing DAL node (dal node list RPCs).out @@ -3,6 +3,9 @@ Available services: + - GET /attested_levels//slots//traps + Get all the shards that are traps for a given slot index at the given + attested level. - GET /health Performs health checks on the DAL node, evaluating key components of the DAL node. Returns a health status indicating whether the DAL node @@ -118,6 +121,8 @@ Dynamic parameter description: int int int32 + int + int32 Warning: Failed to acquire the protocol version from the node diff --git a/tezt/tests/expected/dal.ml/Quebec- Testing DAL node (dal node list RPCs).out b/tezt/tests/expected/dal.ml/Quebec- Testing DAL node (dal node list RPCs).out index 7dfc832c9ba56f127c76e5dac1216210aa3680cb..ca59c64f73d229edb5293f9f2696e51d00f61402 100644 --- a/tezt/tests/expected/dal.ml/Quebec- Testing DAL node (dal node list RPCs).out +++ b/tezt/tests/expected/dal.ml/Quebec- Testing DAL node (dal node list RPCs).out @@ -3,6 +3,9 @@ Available services: + - GET /attested_levels//slots//traps + Get all the shards that are traps for a given slot index at the given + attested level. - GET /health Performs health checks on the DAL node, evaluating key components of the DAL node. Returns a health status indicating whether the DAL node @@ -118,6 +121,8 @@ Dynamic parameter description: int int int32 + int + int32 Warning: Failed to acquire the protocol version from the node