From 62044e2179959e2ab57fabbe81644d0a2d68cb14 Mon Sep 17 00:00:00 2001 From: Andrea Cerone Date: Wed, 8 Mar 2023 02:53:00 +0000 Subject: [PATCH 01/11] Dac/Node: support all operating modes --- src/bin_dac_node/main_dac.ml | 4 +- src/lib_dac_node/RPC_server.ml | 169 ++++++++++++-------- src/lib_dac_node/configuration.ml | 3 + src/lib_dac_node/configuration.mli | 40 +++-- src/lib_dac_node/daemon.ml | 66 ++------ src/lib_dac_node/node_context.ml | 246 +++++++++++++++++++++++++---- src/lib_dac_node/node_context.mli | 124 ++++++++++++--- 7 files changed, 472 insertions(+), 180 deletions(-) diff --git a/src/bin_dac_node/main_dac.ml b/src/bin_dac_node/main_dac.ml index 557479930918..b62927daeac8 100644 --- a/src/bin_dac_node/main_dac.ml +++ b/src/bin_dac_node/main_dac.ml @@ -146,8 +146,8 @@ module Config_init = struct let create_configuration ~data_dir ~reveal_data_dir ~rpc_address ~rpc_port mode (cctxt : Client_context.full) = let open Lwt_result_syntax in - let config : Configuration.t = - {data_dir; rpc_address; rpc_port; mode; reveal_data_dir} + let config = + Configuration.make ~data_dir ~reveal_data_dir rpc_address rpc_port mode in let* () = Configuration.save config in let*! _ = diff --git a/src/lib_dac_node/RPC_server.ml b/src/lib_dac_node/RPC_server.ml index 2edf3e9d156c..2f04e2484aa7 100644 --- a/src/lib_dac_node/RPC_server.ml +++ b/src/lib_dac_node/RPC_server.ml @@ -119,22 +119,6 @@ let handle_get_verify_signature dac_plugin public_keys_opt encoded_l1_message = let handle_get_preimage dac_plugin page_store hash = Page_store.Filesystem.load dac_plugin page_store hash -module Coordinator = struct - let handle_post_preimage dac_plugin page_store hash_streamer payload = - let open Lwt_result_syntax in - let* root_hash = - Pages_encoding.Merkle_tree.V0.Filesystem.serialize_payload - dac_plugin - ~page_store - payload - in - let () = Data_streamer.publish hash_streamer root_hash in - let*! () = - Event.emit_root_hash_pushed_to_data_streamer dac_plugin root_hash - in - return root_hash -end - (* Handler for subscribing to the streaming of root hashes via GET monitor/root_hashes RPC call. *) let handle_monitor_root_hashes hash_streamer = @@ -210,17 +194,6 @@ let register_put_dac_member_signature ctx dac_plugin cctxt = cctxt dac_member_signature) -let register_coordinator_post_preimage dac_plugin hash_streamer page_store = - add_service - Tezos_rpc.Directory.register0 - (RPC_services.Coordinator.post_preimage dac_plugin) - (fun () payload -> - Coordinator.handle_post_preimage - dac_plugin - page_store - hash_streamer - payload) - let register_get_certificate ctx dac_plugin = add_service Tezos_rpc.Directory.register1 @@ -228,7 +201,7 @@ let register_get_certificate ctx dac_plugin = (fun root_hash () () -> handle_get_certificate ctx root_hash) let register_get_missing_page ctx dac_plugin = - match (Node_context.get_config ctx).mode with + match Node_context.mode ctx with | Legacy _ | Observer _ -> add_service Tezos_rpc.Directory.register1 @@ -240,48 +213,118 @@ let register_get_missing_page ctx dac_plugin = handle_get_missing_page cctxt page_store dac_plugin root_hash) | Coordinator _ | Committee_member _ -> Fun.id -let register dac_plugin node_context cctxt dac_public_keys_opt dac_sk_uris - hash_streamer = - let page_store = Node_context.get_page_store node_context in - Tezos_rpc.Directory.empty - |> register_post_store_preimage - dac_plugin - cctxt - dac_sk_uris - page_store - hash_streamer - |> register_get_verify_signature dac_plugin dac_public_keys_opt - |> register_get_preimage dac_plugin page_store - |> register_monitor_root_hashes dac_plugin hash_streamer - |> register_put_dac_member_signature node_context dac_plugin cctxt - (* TODO: https://gitlab.com/tezos/tezos/-/issues/4934 - Once profiles are implemented, registration of the coordinator's - "/preimage" endpoint should be moved out of the [start_legacy]. *) - |> register_coordinator_post_preimage dac_plugin hash_streamer page_store - |> register_get_certificate node_context dac_plugin - |> register_get_missing_page node_context dac_plugin +module Coordinator = struct + let handle_post_preimage dac_plugin page_store hash_streamer payload = + let open Lwt_result_syntax in + let* root_hash = + Pages_encoding.Merkle_tree.V0.Filesystem.serialize_payload + dac_plugin + ~page_store + payload + in + let () = Data_streamer.publish hash_streamer root_hash in + let*! () = + Event.emit_root_hash_pushed_to_data_streamer dac_plugin root_hash + in + return root_hash + + let register_coordinator_post_preimage dac_plugin hash_streamer page_store = + add_service + Tezos_rpc.Directory.register0 + (RPC_services.Coordinator.post_preimage dac_plugin) + (fun () payload -> + handle_post_preimage dac_plugin page_store hash_streamer payload) + + let dynamic_rpc_dir dac_plugin (node_ctxt : Node_context.t) = + let modal_node_ctxt = + match Node_context.mode node_ctxt with + | Coordinator modal_node_ctxt -> modal_node_ctxt + (* We only pass [Node_context.t] values whose mode is [Coordinator _] to + this function. *) + | _ -> assert false + in -(* TODO: https://gitlab.com/tezos/tezos/-/issues/4750 - Move this to RPC_server.Legacy once all operating modes are supported. *) -let start_legacy ~rpc_address ~rpc_port ~threshold cctxt ctxt dac_pks_opt - dac_sk_uris = + let hash_streamer = + modal_node_ctxt.Node_context.Coordinator.hash_streamer + in + let public_keys_opt = + Node_context.Coordinator.public_keys_opt modal_node_ctxt + in + let page_store = Node_context.get_page_store node_ctxt in + let cctxt = Node_context.get_tezos_node_cctxt node_ctxt in + + Tezos_rpc.Directory.empty + |> register_coordinator_post_preimage dac_plugin hash_streamer page_store + |> register_get_verify_signature dac_plugin public_keys_opt + |> register_get_preimage dac_plugin page_store + |> register_monitor_root_hashes dac_plugin hash_streamer + |> register_put_dac_member_signature node_ctxt dac_plugin cctxt + |> register_get_certificate node_ctxt dac_plugin +end + +module Committee_member = struct + let dynamic_rpc_dir dac_plugin (node_ctxt : Node_context.t) = + let page_store = Node_context.get_page_store node_ctxt in + Tezos_rpc.Directory.empty |> register_get_preimage dac_plugin page_store +end + +module Observer = struct + let dynamic_rpc_dir dac_plugin (node_ctxt : Node_context.t) = + let page_store = Node_context.get_page_store node_ctxt in + Tezos_rpc.Directory.empty + |> register_get_preimage dac_plugin page_store + |> register_get_missing_page node_ctxt dac_plugin +end + +module Legacy = struct + let dynamic_rpc_dir dac_plugin (node_ctxt : Node_context.t) = + let modal_node_ctxt = + match Node_context.mode node_ctxt with + | Legacy modal_node_ctxt -> modal_node_ctxt + (* We only pass [Node_context.t] values whose mode is [Coordinator _] to + this function. *) + | _ -> assert false + in + let hash_streamer = modal_node_ctxt.Node_context.Legacy.hash_streamer in + let public_keys_opt = Node_context.Legacy.public_keys_opt modal_node_ctxt in + let secret_key_uris_opt = + Node_context.Legacy.secret_key_uris_opt modal_node_ctxt + in + let page_store = Node_context.get_page_store node_ctxt in + let cctxt = Node_context.get_tezos_node_cctxt node_ctxt in + Tezos_rpc.Directory.empty + |> register_post_store_preimage + dac_plugin + cctxt + secret_key_uris_opt + page_store + hash_streamer + |> register_get_verify_signature dac_plugin public_keys_opt + |> register_get_preimage dac_plugin page_store + |> register_monitor_root_hashes dac_plugin hash_streamer + |> register_put_dac_member_signature node_ctxt dac_plugin cctxt + |> register_get_certificate node_ctxt dac_plugin + |> register_get_missing_page node_ctxt dac_plugin +end + +let start ~rpc_address ~rpc_port node_ctxt = let open Lwt_syntax in + let register_dynamic_rpc dac_plugin = + match Node_context.mode node_ctxt with + | Coordinator _ -> Coordinator.dynamic_rpc_dir dac_plugin node_ctxt + | Committee_member _ -> + Committee_member.dynamic_rpc_dir dac_plugin node_ctxt + | Observer _ -> Observer.dynamic_rpc_dir dac_plugin node_ctxt + | Legacy _ -> Legacy.dynamic_rpc_dir dac_plugin node_ctxt + in let dir = Tezos_rpc.Directory.register_dynamic_directory Tezos_rpc.Directory.empty Tezos_rpc.Path.open_root (fun () -> - match Node_context.get_status ctxt with - | Ready {dac_plugin = (module Dac_plugin); hash_streamer} -> - let _threshold = threshold in - Lwt.return - (register - (module Dac_plugin) - ctxt - cctxt - dac_pks_opt - dac_sk_uris - hash_streamer) + match Node_context.get_status node_ctxt with + | Ready {dac_plugin = (module Dac_plugin)} -> + Lwt.return (register_dynamic_rpc (module Dac_plugin)) | Starting -> Lwt.return Tezos_rpc.Directory.empty) in let rpc_address = P2p_addr.of_string_exn rpc_address in diff --git a/src/lib_dac_node/configuration.ml b/src/lib_dac_node/configuration.ml index e9f95952cdd9..c66f96063a2a 100644 --- a/src/lib_dac_node/configuration.ml +++ b/src/lib_dac_node/configuration.ml @@ -222,6 +222,9 @@ type t = { DAC. *) } +let make ~data_dir ~reveal_data_dir rpc_address rpc_port mode = + {data_dir; reveal_data_dir; rpc_address; rpc_port; mode} + let data_dir_path config subpath = Filename.concat config.data_dir subpath let filename config = relative_filename config.data_dir diff --git a/src/lib_dac_node/configuration.mli b/src/lib_dac_node/configuration.mli index 52627958ef88..172b9fe44f8c 100644 --- a/src/lib_dac_node/configuration.mli +++ b/src/lib_dac_node/configuration.mli @@ -32,9 +32,13 @@ type host_and_port = {host : string; port : int} (** Coordinator specific configuration. *) module Coordinator : sig (** The type of a coordinator specific configuration mode. *) - type t + type t = { + threshold : int; + committee_members_addresses : + Tezos_crypto.Aggregate_signature.public_key_hash list; + } - (* [committee_members_addresses t] retrieves the addresses of the committee + (** [committee_members_addresses t] retrieves the addresses of the committee members from the coordinator configuration [t].*) val committee_members_addresses : t -> Tezos_crypto.Aggregate_signature.public_key_hash list @@ -43,30 +47,41 @@ end (** Committee_member specific configuration. *) module Committee_member : sig (** The type of a Committee_member specific configuration mode. *) - type t + type t = { + coordinator_rpc_address : string; + coordinator_rpc_port : int; + address : Tezos_crypto.Aggregate_signature.public_key_hash; + } end (** Observer specific configuration. *) module Observer : sig (** The type of an Observer specific configuration mode. *) - type t + type t = {coordinator_rpc_address : string; coordinator_rpc_port : int} end (** Legacy specific configuration. *) module Legacy : sig (** The type of a legacy-specific configuration mode. *) - type t - - (* [committee_members_addresses t] retrieves the addresses of the committee + type t = { + threshold : int; + committee_members_addresses : + Tezos_crypto.Aggregate_signature.public_key_hash list; + dac_cctxt_config : host_and_port option; + committee_member_address_opt : + Tezos_crypto.Aggregate_signature.public_key_hash option; + } + + (** [committee_members_addresses t] retrieves the addresses of the committee members from the legacy configuration [t].*) val committee_members_addresses : t -> Tezos_crypto.Aggregate_signature.public_key_hash list - (* [threshold t] retrieves the Data Availability Committee threshold from + (** [threshold t] retrieves the Data Availability Committee threshold from the legacy configuration [t]. *) val threshold : t -> int - (* [host_and_port t] retrieves the host and port of the node that serves + (** [host_and_port t] retrieves the host and port of the node that serves as a coordinator for the DAC, if any is specified in the legacy node condiguration. *) val dac_cctxt_config : t -> host_and_port option @@ -82,7 +97,7 @@ type mode = private | Observer of Observer.t | Legacy of Legacy.t -type t = { +type t = private { data_dir : string; (** The path to the DAC node data directory. *) rpc_address : string; (** The address the DAC node listens to. *) rpc_port : int; (** The port the DAC node listens to. *) @@ -123,6 +138,11 @@ val make_legacy : Tezos_crypto.Aggregate_signature.public_key_hash option -> mode +(** [make ~data_dir ~reveal_data_dir rpc_address rpc_port mode] creates a + configuration value from the specified parameters. *) +val make : + data_dir:string -> reveal_data_dir:string -> string -> int -> mode -> t + (** [filename config] gets the path to config file *) val filename : t -> string diff --git a/src/lib_dac_node/daemon.ml b/src/lib_dac_node/daemon.ml index 8bfd40257fc9..10a8b4f6f2ba 100644 --- a/src/lib_dac_node/daemon.ml +++ b/src/lib_dac_node/daemon.ml @@ -265,64 +265,26 @@ let run ~data_dir cctxt = let open Lwt_result_syntax in let*! () = Event.(emit starting_node) () in let* (Configuration. - {rpc_address; rpc_port; reveal_data_dir; mode; data_dir = _} as + {rpc_address; rpc_port; reveal_data_dir; mode = _; data_dir = _} as config) = Configuration.load ~data_dir in let* () = Dac_manager.Storage.ensure_reveal_data_dir_exists reveal_data_dir in - let* addresses, threshold, coordinator_cctxt_opt, committee_member_address_opt - = - match mode with - | Legacy configuration -> - let committee_members_addresses = - Configuration.Legacy.committee_members_addresses configuration - in - let threshold = Configuration.Legacy.threshold configuration in - let dac_cctxt_config = - Configuration.Legacy.dac_cctxt_config configuration - in - let committee_member_address_opt = - Configuration.Legacy.committee_member_address_opt configuration - in - return - ( committee_members_addresses, - threshold, - dac_cctxt_config, - committee_member_address_opt ) - | Coordinator _ -> tzfail @@ Mode_not_supported "coordinator" - | Committee_member _ -> tzfail @@ Mode_not_supported "committee_member" - | Observer _ -> tzfail @@ Mode_not_supported "observer" + let* ctxt = Node_context.init config cctxt in + let coordinator_cctxt_opt = + match Node_context.mode ctxt with + | Node_context.Coordinator _ -> None + | Committee_member committee_member_ctxt -> + Some + (Node_context.Committee_member.coordinator_cctxt + committee_member_ctxt) + | Observer observer_ctxt -> + Some (Node_context.Observer.coordinator_cctxt observer_ctxt) + | Legacy legacy_ctxt -> Node_context.Legacy.coordinator_cctxt legacy_ctxt in (* TODO: https://gitlab.com/tezos/tezos/-/issues/4725 Stop DAC node when in Legacy mode, if threshold is not reached. *) - let* committee_members = - get_all_committee_members_keys addresses ~threshold cctxt - in - let dac_pks_opt, dac_sk_uris = - committee_members - |> List.map - (fun Wallet_account.Legacy.{public_key_opt; secret_key_uri_opt; _} -> - (public_key_opt, secret_key_uri_opt)) - |> List.split - in - let coordinator_cctxt_opt = - Option.map - (fun Configuration.{host; port} -> - Dac_node_client.make_unix_cctxt ~scheme:"http" ~host ~port) - coordinator_cctxt_opt - in - let* ctxt = Node_context.init config cctxt coordinator_cctxt_opt in - let* rpc_server = - RPC_server.( - start_legacy - ~rpc_address - ~rpc_port - ~threshold - cctxt - ctxt - dac_pks_opt - dac_sk_uris) - in + let* rpc_server = RPC_server.start ~rpc_address ~rpc_port ctxt in let _ = RPC_server.install_finalizer rpc_server in let*! () = Event.(emit rpc_server_is_ready (rpc_address, rpc_port)) in (* Start daemon to resolve current protocol plugin *) @@ -336,7 +298,7 @@ let run ~data_dir cctxt = Wallet_account.Legacy.of_committee_member_address committee_member_address cctxt) - committee_member_address_opt + None in match coordinator_cctxt_opt with | Some coordinator_cctxt -> diff --git a/src/lib_dac_node/node_context.ml b/src/lib_dac_node/node_context.ml index 442ac935870a..ce0ae45bce97 100644 --- a/src/lib_dac_node/node_context.ml +++ b/src/lib_dac_node/node_context.ml @@ -29,51 +29,228 @@ let store_path_prefix = "store" type dac_plugin_module = (module Dac_plugin.T) -type ready_ctxt = { - dac_plugin : dac_plugin_module; - hash_streamer : Dac_plugin.hash Data_streamer.t; - (* FIXME: https://gitlab.com/tezos/tezos/-/issues/4895 - This could be problematic in case coordinator and member/observer - use two different plugins that bind different underlying hashes. *) -} +module Coordinator = struct + type t = { + committee_members : Wallet_account.Coordinator.t list; + hash_streamer : Dac_plugin.hash Data_streamer.t; + (* FIXME: https://gitlab.com/tezos/tezos/-/issues/4895 + This could be problematic in case coordinator and member/observer + use two different plugins that bind different underlying hashes. *) + } + + let get_all_committee_members_public_keys committee_members_addresses cctxt = + List.map_es + (fun public_key_hash -> + Wallet_account.Coordinator.of_committee_member_address + public_key_hash + cctxt) + committee_members_addresses + + let init coordinator_config cctxt = + let open Lwt_result_syntax in + let Configuration.Coordinator.{committee_members_addresses; _} = + coordinator_config + in + let+ committee_members = + get_all_committee_members_public_keys committee_members_addresses cctxt + in + let hash_streamer = Data_streamer.init () in + {committee_members; hash_streamer} + + let public_keys_opt t = + List.map + (fun Wallet_account.Coordinator.{public_key_opt; _} -> public_key_opt) + t.committee_members +end + +module Committee_member = struct + type t = { + committee_member : Wallet_account.Committee_member.t; + coordinator_cctxt : Dac_node_client.cctxt; + } + + let init committee_member_config cctxt = + let open Lwt_result_syntax in + let Configuration.Committee_member. + {address; coordinator_rpc_address; coordinator_rpc_port} = + committee_member_config + in + let+ committee_member = + Wallet_account.Committee_member.of_committee_member_address address cctxt + in + let coordinator_cctxt = + Dac_node_client.make_unix_cctxt + ~scheme:"http" + ~host:coordinator_rpc_address + ~port:coordinator_rpc_port + in + {committee_member; coordinator_cctxt} + + let secret_key_uri t = + let Wallet_account.Committee_member.{secret_key_uri; _} = + t.committee_member + in + secret_key_uri +end + +module Observer = struct + type t = {coordinator_cctxt : Dac_node_client.cctxt} + + let init observer_config = + let open Lwt_result_syntax in + let Configuration.Observer.{coordinator_rpc_address; coordinator_rpc_port} = + observer_config + in + let coordinator_cctxt = + Dac_node_client.make_unix_cctxt + ~scheme:"http" + ~host:coordinator_rpc_address + ~port:coordinator_rpc_port + in + return {coordinator_cctxt} +end + +module Legacy = struct + type t = { + committee_members : Wallet_account.Legacy.t list; + coordinator_cctxt : Dac_node_client.cctxt option; + hash_streamer : Dac_plugin.hash Data_streamer.t; + committee_member_opt : Wallet_account.Legacy.t option; + } + + let get_all_committee_members_keys committee_members_addresses ~threshold + cctxt = + let open Lwt_result_syntax in + let* wallet_accounts = + List.map_es + (fun public_key_hash -> + Wallet_account.Legacy.of_committee_member_address + public_key_hash + cctxt) + committee_members_addresses + in + let*! valid_wallets = + List.filter_s + (fun Wallet_account.Legacy.{public_key_hash; secret_key_uri_opt; _} -> + if Option.is_some secret_key_uri_opt then Lwt.return true + else + let*! () = + Event.(emit committee_member_cannot_sign public_key_hash) + in + Lwt.return false) + wallet_accounts + in + let recovered_keys = List.length valid_wallets in + let*! () = + (* We emit a warning if the threshold of dac accounts needed to sign a + root page hash is not reached. We also emit a warning for each DAC + account whose secret key URI was not recovered. + We do not stop the dac node at this stage. + *) + if recovered_keys < threshold then + Event.(emit dac_threshold_not_reached (recovered_keys, threshold)) + else Event.(emit dac_is_ready) () + in + return wallet_accounts + + let init legacy_config cctxt = + let open Lwt_result_syntax in + let Configuration.Legacy. + { + threshold; + committee_members_addresses; + dac_cctxt_config; + committee_member_address_opt; + } = + legacy_config + in + let* committee_members = + get_all_committee_members_keys + committee_members_addresses + ~threshold + cctxt + in + let+ committee_member_opt = + Option.map_es + (fun address -> + Wallet_account.Legacy.of_committee_member_address address cctxt) + committee_member_address_opt + in + let coordinator_cctxt = + Option.map + (fun Configuration.{host; port} -> + Dac_node_client.make_unix_cctxt ~scheme:"http" ~host ~port) + dac_cctxt_config + in + let hash_streamer = Data_streamer.init () in + {committee_members; coordinator_cctxt; hash_streamer; committee_member_opt} + + let public_keys_opt t = + List.map + (fun Wallet_account.Legacy.{public_key_opt; _} -> public_key_opt) + t.committee_members + + let secret_key_uris_opt t = + List.map + (fun Wallet_account.Legacy.{secret_key_uri_opt; _} -> secret_key_uri_opt) + t.committee_members +end + +type mode = + | Coordinator of Coordinator.t + | Committee_member of Committee_member.t + | Observer of Observer.t + | Legacy of Legacy.t + +type ready_ctxt = {dac_plugin : dac_plugin_module} type status = Ready of ready_ctxt | Starting type t = { mutable status : status; - config : Configuration.t; + reveal_data_dir : string; tezos_node_cctxt : Client_context.full; - coordinator_opt : Dac_node_client.cctxt option; - (* TODO: https://gitlab.com/tezos/tezos/-/issues/4896 - [coordinator_opt] is meant to be used for running integration tests - in the multiple dac node setup, where all nodes are in the [legacy] - mode. In this setup we normally try to mimic the role of coordinator - with one node, whereas the others want to interact with it. - This is done via [Dac_node_client.cctxt]. - - Eventually, once the legacy mode is removed we should revisit the - need for this fieeld.*) page_store : Page_store.Filesystem.t; node_store : Store_sigs.rw Store.Irmin_store.t; + mode : mode; } -let init config cctxt coordinator_opt = +let init_mode Configuration.{mode; _} cctxt = + let open Lwt_result_syntax in + match mode with + | Coordinator config -> + let+ mode_node_ctxt = Coordinator.init config cctxt in + Coordinator mode_node_ctxt + | Committee_member config -> + let+ mode_node_ctxt = Committee_member.init config cctxt in + Committee_member mode_node_ctxt + | Observer config -> + let+ mode_node_ctxt = Observer.init config in + Observer mode_node_ctxt + | Legacy config -> + let+ mode_node_ctxt = Legacy.init config cctxt in + Legacy mode_node_ctxt + +let init config cctxt = let open Lwt_result_syntax in - let+ node_store = + let* node_store = Store.Irmin_store.load Store_sigs.Read_write (Configuration.data_dir_path config store_path_prefix) in + let+ mode = init_mode config cctxt in { status = Starting; - config; + reveal_data_dir = Configuration.reveal_data_dir config; tezos_node_cctxt = cctxt; - coordinator_opt; page_store = Page_store.Filesystem.init (Configuration.reveal_data_dir config); node_store; + mode; } +let mode node_ctxt = node_ctxt.mode + let set_ready ctxt dac_plugin = match ctxt.status with | Starting -> @@ -81,7 +258,7 @@ let set_ready ctxt dac_plugin = Currently, Dac only supports coordinator functionalities but we might want to filter this capability out depending on the profile. *) - ctxt.status <- Ready {dac_plugin; hash_streamer = Data_streamer.init ()} + ctxt.status <- Ready {dac_plugin} | Ready _ -> raise Status_already_ready type error += @@ -141,8 +318,6 @@ let get_ready ctxt = | Ready ctxt -> Ok ctxt | Starting -> fail [Node_not_ready] -let get_config ctxt = ctxt.config - let get_status ctxt = ctxt.status let get_tezos_node_cctxt ctxt = ctxt.tezos_node_cctxt @@ -150,7 +325,7 @@ let get_tezos_node_cctxt ctxt = ctxt.tezos_node_cctxt let get_dac_plugin ctxt = let open Result_syntax in match ctxt.status with - | Ready {dac_plugin; hash_streamer = _} -> Ok dac_plugin + | Ready {dac_plugin} -> Ok dac_plugin | Starting -> tzfail Node_not_ready let get_page_store ctxt = ctxt.page_store @@ -163,11 +338,17 @@ let get_node_store (type a) ctxt (access_mode : a Store_sigs.mode) : let get_committee_members ctxt = let open Result_syntax in - match Configuration.mode ctxt.config with + match ctxt.mode with | Legacy legacy -> - Ok (Configuration.Legacy.committee_members_addresses legacy) + Ok + (List.map + (fun Wallet_account.Legacy.{public_key_hash; _} -> public_key_hash) + legacy.committee_members) | Coordinator coordinator -> - Ok (Configuration.Coordinator.committee_members_addresses coordinator) + Ok + ((List.map (fun Wallet_account.Coordinator.{public_key_hash; _} -> + public_key_hash)) + coordinator.committee_members) | Observer _ -> tzfail @@ Invalid_operation_for_mode @@ -178,6 +359,7 @@ let get_committee_members ctxt = {mode = "dac_member"; operation = "get_committee_members"} let get_coordinator_client ctxt = - match ctxt.coordinator_opt with - | Some cctxt -> Ok cctxt - | None -> Result_syntax.tzfail Coordinator_client_not_defined_in_config + match ctxt.mode with + | Observer observer_ctxt -> Ok observer_ctxt.coordinator_cctxt + | Legacy {coordinator_cctxt = Some cctxt; _} -> Ok cctxt + | _ -> Result_syntax.tzfail Coordinator_client_not_defined_in_config diff --git a/src/lib_dac_node/node_context.mli b/src/lib_dac_node/node_context.mli index e0e2076d652e..ad00b0132341 100644 --- a/src/lib_dac_node/node_context.mli +++ b/src/lib_dac_node/node_context.mli @@ -24,15 +24,102 @@ (*****************************************************************************) type dac_plugin_module = (module Dac_plugin.T) +(** [Coordinator] defines a partial [Node_context.t] that is available + only to [Coordinator] nodes, and functions that can be used to operate + on such mode-specific node contexts. *) +module Coordinator : sig + (** The type of a [Coordinator] specific partial [Node_context.t]. *) + type t = private { + committee_members : Wallet_account.Coordinator.t list; + (** The list of [Wallet.Coordinator] values associated with the Data + Availability Committee members managed by the [Coordinator] node. + *) + hash_streamer : Dac_plugin.hash Data_streamer.t; + (** The [Dac_plugin.hash Data_streamer.t] that the [Coordinator] node + use to advertise root hashes to [Committee_member] and [Observer] + nodes. *) + (* FIXME: https://gitlab.com/tezos/tezos/-/issues/4895 + This could be problematic in case coordinator and member/observer + use two different plugins that bind different underlying hashes. *) + } + + (** [public_keys_opt t] returns the list of public keys associated with the + data availability committee of [t]. *) + val public_keys_opt : + t -> Tezos_crypto.Aggregate_signature.public_key option list +end + +(** [Committee_member] defines a partial [Node_context.t] that is available + only to [Committee_member] nodes, and functions that can be used to operate + on such mode specific partial node contexts. *) +module Committee_member : sig + (** The type of a [Committee_member] specific partial [Node_context.t]. *) + type t = private { + committee_member : Wallet_account.Committee_member.t; + (** The [Wallet_account.Committee_member] wallet associated with the + [commitee_member] managed by the DAC node. *) + coordinator_cctxt : Dac_node_client.cctxt; + (** The [Dac_node_client.cctxt] used by the [Committee_member] node to + send requests to a [Coordinator] node. *) + } + + (** [secret_key_uri t] returns the secret key URI associated with the + committee member managed by the [Committee_member] node. *) + val secret_key_uri : t -> Client_keys.aggregate_sk_uri +end + +(** The type of an [Observer] specific partial [Node_context.t]. *) +module Observer : sig + type t = private { + coordinator_cctxt : Dac_node_client.cctxt; + (** The [Dac_node_client.cctxt] used by the [Observer] node to + send requests to a [Coordinator] node. *) + } +end + +(** [Legacy] defines a partial [Node_context.t] that is available only to + [Legacy] nodes, and functions that can be used to operate on such + mode specific partial node contexts. *) +module Legacy : sig + (** The type of a [Legacy] specific partial [Node_context.t]. *) + type t = private { + committee_members : Wallet_account.Legacy.t list; + (** The list of [Wallet_account.Legacy] values associated with the Data + Availability Committee members managed by the [Coordinator] node. + *) + coordinator_cctxt : Dac_node_client.cctxt option; + (** An optional [Dac_node_client.cctxt] option. If defined, it + enables a [Legacy] node to act as if it were an [Observer], using + the associated [Dac_node_client.cctxt] value to send requests to + a [Coordinator] node. *) + hash_streamer : Dac_plugin.hash Data_streamer.t; + (** A [Dac_plugin.hash Data_streamer.t] that the [Legacy] node + use to advertise root hashes to other nodes *) + committee_member_opt : Wallet_account.Legacy.t option; + (** The legacy account wallet of the committee member simulated by the + legacy DAC node, if any. *) + } + + (** [public_keys_opt t] returns the list of optional public keys associated + with the committee members of [t]. *) + val public_keys_opt : + t -> Tezos_crypto.Aggregate_signature.public_key option list + + (** [secret_key_uris_opt] return the list of optional secret key URIs of the + committee members of [t]. *) + val secret_key_uris_opt : t -> Client_keys.aggregate_sk_uri option list +end + +(** Operating mode specific fraction of a [Node_context.t] *) +type mode = private + | Coordinator of Coordinator.t + | Committee_member of Committee_member.t + | Observer of Observer.t + | Legacy of Legacy.t + (** A [ready_ctx] value contains globally needed information for a running dac - node. It is available when the DAC plugin has been loaded. Additionally, - it also contains an instance of [Dac_plugin.hash Data_streamer.t] - a - component for streaming root hashes, produced during the serialization of - dac payload. *) -type ready_ctxt = { - dac_plugin : dac_plugin_module; - hash_streamer : Dac_plugin.hash Data_streamer.t; -} + node. It is available when the DAC plugin has been loaded. *) +type ready_ctxt = {dac_plugin : dac_plugin_module} (** The status of the dac node. *) type status = Ready of ready_ctxt | Starting @@ -41,16 +128,10 @@ type status = Ready of ready_ctxt | Starting fields are available through accessors. *) type t -(** [init config cctxt dac_node_cctxt] creates a [t] with a status set to - [Starting] using the given dac node configuration [config], - tezos node client context [cctxt], and optional client context of - another dac node [dac_node_cctxt], which can be used for writting - tests with two dac nodes running the legacy mode. *) -val init : - Configuration.t -> - Client_context.full -> - Dac_node_client.cctxt option -> - t tzresult Lwt.t +(** [init config cctxt] creates a [t] with a status set to + [Starting] using the given dac node configuration [config] and + tezos node client context [cctxt]. *) +val init : Configuration.t -> Client_context.full -> t tzresult Lwt.t (** Raised by [set_ready] when the status is already [Ready _] *) exception Status_already_ready @@ -69,12 +150,13 @@ type error += Node_not_ready times, it replaces current values for [ready_ctxt] with new one. *) val get_ready : t -> ready_ctxt tzresult -(** [get_config ctxt] returns the dac node configuration. *) -val get_config : t -> Configuration.t - (** [get_status ctxt] returns the dac node status. *) val get_status : t -> status +(** [mode node_ctxt] returns the operating mode specific fraction of a + [Node_context.t]. *) +val mode : t -> mode + (** [get_tezos_node_cctxt ctxt] returns the Tezos node's client context. *) val get_tezos_node_cctxt : t -> Client_context.full -- GitLab From 0d3b1c329bcb05cc13bbac3419bc2cc6c5eb7f8c Mon Sep 17 00:00:00 2001 From: Andrea Cerone Date: Fri, 10 Mar 2023 17:33:59 +0000 Subject: [PATCH 02/11] Dac/Node: move handlers to own module --- src/lib_dac_node/daemon.ml | 209 +------------------- src/lib_dac_node/handler.ml | 360 +++++++++++++++++++++++++++++++++++ src/lib_dac_node/handler.mli | 61 ++++++ 3 files changed, 424 insertions(+), 206 deletions(-) create mode 100644 src/lib_dac_node/handler.ml create mode 100644 src/lib_dac_node/handler.mli diff --git a/src/lib_dac_node/daemon.ml b/src/lib_dac_node/daemon.ml index 10a8b4f6f2ba..8cef2a39bc9d 100644 --- a/src/lib_dac_node/daemon.ml +++ b/src/lib_dac_node/daemon.ml @@ -38,179 +38,6 @@ let () = (function Mode_not_supported mode -> Some mode | _ -> None) (fun mode -> Mode_not_supported mode) -module Handler = struct - (** [make_stream_daemon handler streamed_call] calls [handler] on each newly - received value from [streamed_call]. - - It returns a couple [(p, stopper)] where [p] is a promise resolving when the - stream closes and [stopper] a function closing the stream. - *) - let make_stream_daemon handle streamed_call = - let open Lwt_result_syntax in - let* stream, stopper = streamed_call in - let rec go () = - let*! tok = Lwt_stream.get stream in - match tok with - | None -> return_unit - | Some element -> - let*! r = handle stopper element in - let*! () = - match r with - | Ok () -> Lwt.return_unit - | Error trace -> - let*! () = Event.(emit daemon_error) trace in - Lwt.return_unit - in - go () - in - return (go (), stopper) - - let resolve_plugin_and_set_ready ctxt cctxt = - (* Monitor heads and try resolve the DAC protocol plugin corresponding to - the protocol of the targeted node. *) - (* FIXME: https://gitlab.com/tezos/tezos/-/issues/3605 - Handle situtation where plugin is not found *) - let open Lwt_result_syntax in - let handler stopper - (_block_hash, (_block_header : Tezos_base.Block_header.t)) = - let* protocols = - Tezos_shell_services.Chain_services.Blocks.protocols cctxt () - in - let*! dac_plugin = Dac_manager.resolve_plugin protocols in - match dac_plugin with - | Some dac_plugin -> - Node_context.set_ready ctxt dac_plugin ; - let*! () = Event.(emit node_is_ready ()) in - stopper () ; - return_unit - | None -> return_unit - in - let handler stopper el = - match Node_context.get_status ctxt with - | Starting -> handler stopper el - | Ready _ -> return_unit - in - let*! () = Event.(emit layer1_node_tracking_started ()) in - make_stream_daemon - handler - (Tezos_shell_services.Monitor_services.heads cctxt `Main) - - let new_head ctxt cctxt = - (* Monitor heads and store published slot headers indexed by block hash. *) - let open Lwt_result_syntax in - let handler _stopper (block_hash, (header : Tezos_base.Block_header.t)) = - match Node_context.get_status ctxt with - | Starting -> return_unit - | Ready _ -> - let block_level = header.shell.level in - let*! () = - Event.(emit layer1_node_new_head (block_hash, block_level)) - in - return_unit - in - let*! () = Event.(emit layer1_node_tracking_started ()) in - (* FIXME: https://gitlab.com/tezos/tezos/-/issues/3517 - If the layer1 node reboots, the rpc stream breaks.*) - make_stream_daemon - handler - (Tezos_shell_services.Monitor_services.heads cctxt `Main) - - (** This function will be called by [new_root_hash] - only when payload corresponding to provided [root_hash] - had been well dezerialized. - Once it is done, call PUT /dac_member_signature to submit member signature *) - let push_payload_signature dac_plugin coordinator_cctxt wallet keys root_hash - = - let open Lwt_result_syntax in - match keys.Wallet_account.Legacy.secret_key_uri_opt with - | Some secret_key_uri -> - let bytes_to_sign = Dac_plugin.hash_to_bytes root_hash in - let* signature = - Tezos_client_base.Client_keys.aggregate_sign - wallet - secret_key_uri - bytes_to_sign - in - let signature_repr = - Signature_repr. - { - root_hash; - signature; - signer_pkh = Wallet_account.Legacy.(keys.public_key_hash); - } - in - let* () = - Dac_node_client.put_dac_member_signature - dac_plugin - coordinator_cctxt - ~signature:signature_repr - in - let*! () = Event.emit_signature_pushed_to_coordinator signature in - return_unit - | None -> - let*! () = - Event.emit_cannot_retrieve_keys_from_address - Wallet_account.Legacy.(keys.public_key_hash) - in - return () - - (** This handler will be invoked only when a [coordinator_cctxt] is specified - in the DAC node configuration. The DAC node tries to subscribes to the - stream of root hashes via the streamed GET /monitor/root_hashes RPC call - to the dac node corresponding to [coordinator_cctxt]. *) - let new_root_hash ctxt coordinator_cctxt ?committee_member_keys wallet_cctxt = - let open Lwt_result_syntax in - let handler dac_plugin remote_store _stopper root_hash = - let*! () = Event.emit_new_root_hash_received dac_plugin root_hash in - let*! payload_result = - Pages_encoding.Merkle_tree.V0.Remote.deserialize_payload - dac_plugin - ~page_store:remote_store - root_hash - in - match payload_result with - | Ok _ -> ( - let*! () = - Event.emit_received_root_hash_processed dac_plugin root_hash - in - match committee_member_keys with - | Some committee_member_keys -> - (* If there is a [committee_member_address], it means the node is run as a member, - so we must sign the payload and post the signature to the coordinator - If there is no [committee_member_address] provided, it means the node is run as an observer - then we simply [return ()] *) - push_payload_signature - dac_plugin - coordinator_cctxt - wallet_cctxt - committee_member_keys - root_hash - | None -> - let*! () = Event.emit_no_committee_member_address () in - return ()) - | Error errs -> - (* TODO: https://gitlab.com/tezos/tezos/-/issues/4930. - Improve handling of errors. *) - let*! () = - Event.emit_processing_root_hash_failed dac_plugin root_hash errs - in - return () - in - let*? dac_plugin = Node_context.get_dac_plugin ctxt in - let remote_store = - Page_store.( - Remote.init - { - cctxt = coordinator_cctxt; - page_store = Node_context.get_page_store ctxt; - }) - in - let*! () = Event.(emit subscribed_to_root_hashes_stream ()) in - make_stream_daemon - (handler dac_plugin remote_store) - (Monitor_services.root_hashes coordinator_cctxt dac_plugin) -end - let daemonize handlers = (* FIXME: https://gitlab.com/tezos/tezos/-/issues/3605 Improve concurrent tasks by using workers *) @@ -271,44 +98,14 @@ let run ~data_dir cctxt = in let* () = Dac_manager.Storage.ensure_reveal_data_dir_exists reveal_data_dir in let* ctxt = Node_context.init config cctxt in - let coordinator_cctxt_opt = - match Node_context.mode ctxt with - | Node_context.Coordinator _ -> None - | Committee_member committee_member_ctxt -> - Some - (Node_context.Committee_member.coordinator_cctxt - committee_member_ctxt) - | Observer observer_ctxt -> - Some (Node_context.Observer.coordinator_cctxt observer_ctxt) - | Legacy legacy_ctxt -> Node_context.Legacy.coordinator_cctxt legacy_ctxt - in (* TODO: https://gitlab.com/tezos/tezos/-/issues/4725 Stop DAC node when in Legacy mode, if threshold is not reached. *) let* rpc_server = RPC_server.start ~rpc_address ~rpc_port ctxt in let _ = RPC_server.install_finalizer rpc_server in let*! () = Event.(emit rpc_server_is_ready (rpc_address, rpc_port)) in (* Start daemon to resolve current protocol plugin *) - let* () = daemonize [Handler.resolve_plugin_and_set_ready ctxt cctxt] in + let* () = daemonize [Handler.resolve_plugin_and_set_ready ctxt] in (* Start never-ending monitoring daemons. [coordinator_cctxt] is required to monitor new root hashes in legacy mode. *) - let wallet_cctxt = Node_context.get_tezos_node_cctxt ctxt in - let* committee_member_keys_opt = - Option.map_es - (fun committee_member_address -> - Wallet_account.Legacy.of_committee_member_address - committee_member_address - cctxt) - None - in - match coordinator_cctxt_opt with - | Some coordinator_cctxt -> - daemonize - [ - Handler.new_head ctxt cctxt; - Handler.new_root_hash - ctxt - coordinator_cctxt - ?committee_member_keys:committee_member_keys_opt - wallet_cctxt; - ] - | _ -> daemonize [Handler.new_head ctxt cctxt] + let* handlers = Handler.handlers ctxt in + daemonize handlers diff --git a/src/lib_dac_node/handler.ml b/src/lib_dac_node/handler.ml new file mode 100644 index 000000000000..83a5594cce12 --- /dev/null +++ b/src/lib_dac_node/handler.ml @@ -0,0 +1,360 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Trili Tech, *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +type t = unit tzresult Lwt.t * Tezos_rpc__RPC_context.stopper + +(** [make_stream_daemon handler streamed_call] calls [handler] on each newly + received value from [streamed_call]. + It returns a couple [(p, stopper)] where [p] is a promise resolving when + the stream closes and [stopper] a function closing the stream. + *) +let make_stream_daemon handle streamed_call = + let open Lwt_result_syntax in + let* stream, stopper = streamed_call in + let rec go () = + let*! tok = Lwt_stream.get stream in + match tok with + | None -> return_unit + | Some element -> + let*! r = handle stopper element in + let*! () = + match r with + | Ok () -> Lwt.return_unit + | Error trace -> + let*! () = Event.(emit daemon_error) trace in + Lwt.return_unit + in + go () + in + return (go (), stopper) + +let resolve_plugin_and_set_ready ctxt = + (* Monitor heads and try resolve the DAC protocol plugin corresponding to + the protocol of the targeted node. *) + (* FIXME: https://gitlab.com/tezos/tezos/-/issues/3605 + Handle situtation where plugin is not found *) + let open Lwt_result_syntax in + let cctxt = Node_context.get_tezos_node_cctxt ctxt in + let handler stopper (_block_hash, (_block_header : Tezos_base.Block_header.t)) + = + let* protocols = + Tezos_shell_services.Chain_services.Blocks.protocols cctxt () + in + let*! dac_plugin = Dac_manager.resolve_plugin protocols in + match dac_plugin with + | Some dac_plugin -> + Node_context.set_ready ctxt dac_plugin ; + let*! () = Event.(emit node_is_ready ()) in + stopper () ; + return_unit + | None -> return_unit + in + let handler stopper el = + match Node_context.get_status ctxt with + | Starting -> handler stopper el + | Ready _ -> return_unit + in + let*! () = Event.(emit layer1_node_tracking_started ()) in + make_stream_daemon + handler + (Tezos_shell_services.Monitor_services.heads cctxt `Main) + +(** The [new_head] handler is shared by all operating modes. This handler is + responsible for tracking new heads from the Layer 1. *) +let new_head ctxt = + let cctxt = Node_context.get_tezos_node_cctxt ctxt in + let open Lwt_result_syntax in + let handler _stopper (block_hash, (header : Tezos_base.Block_header.t)) = + match Node_context.get_status ctxt with + | Starting -> return_unit + | Ready _ -> + let block_level = header.shell.level in + let*! () = + Event.(emit layer1_node_new_head (block_hash, block_level)) + in + return_unit + in + let*! () = Event.(emit layer1_node_tracking_started ()) in + (* FIXME: https://gitlab.com/tezos/tezos/-/issues/3517 + If the layer1 node reboots, the rpc stream breaks.*) + make_stream_daemon + handler + (Tezos_shell_services.Monitor_services.heads cctxt `Main) + +(** Handlers specific to a [Committee_member]. A [Committee_member] is + responsible for + {ul + {li Monitoring root hashes from a [Coordinator],} + {li Downloading the associated pages,} + {li Validating the hash of each page,} + {li Sign the final root hash with the public key of the + committee member,} + {li Send the signature back to the [Coordinaotor].} + } *) +module Committee_member = struct + let push_payload_signature dac_plugin coordinator_cctxt wallet_cctxt + committee_member root_hash = + let open Lwt_result_syntax in + let signer_pkh = + committee_member.Wallet_account.Committee_member.public_key_hash + in + let secret_key_uri = committee_member.secret_key_uri in + let bytes_to_sign = Dac_plugin.hash_to_bytes root_hash in + let* signature = + Tezos_client_base.Client_keys.aggregate_sign + wallet_cctxt + secret_key_uri + bytes_to_sign + in + let signature_repr = Signature_repr.{root_hash; signature; signer_pkh} in + let* () = + Dac_node_client.put_dac_member_signature + dac_plugin + coordinator_cctxt + ~signature:signature_repr + in + let*! () = Event.emit_signature_pushed_to_coordinator signature in + return_unit + + let new_root_hash ctxt wallet_cctxt dac_plugin page_store = + let open Lwt_result_syntax in + let coordinator_cctxt = + ctxt.Node_context.Committee_member.coordinator_cctxt + in + let handler dac_plugin remote_store _stopper root_hash = + let*! () = Event.emit_new_root_hash_received dac_plugin root_hash in + let*! payload_result = + Pages_encoding.Merkle_tree.V0.Remote.deserialize_payload + dac_plugin + ~page_store:remote_store + root_hash + in + match payload_result with + | Ok _ -> + let*! () = + Event.emit_received_root_hash_processed dac_plugin root_hash + in + let committee_member = + ctxt.Node_context.Committee_member.committee_member + in + push_payload_signature + dac_plugin + coordinator_cctxt + wallet_cctxt + committee_member + root_hash + | Error errs -> + (* TODO: https://gitlab.com/tezos/tezos/-/issues/4930. + Improve handling of errors. *) + let*! () = + Event.emit_processing_root_hash_failed dac_plugin root_hash errs + in + return () + in + let remote_store = + Page_store.(Remote.init {cctxt = coordinator_cctxt; page_store}) + in + let*! () = Event.(emit subscribed_to_root_hashes_stream ()) in + make_stream_daemon + (handler dac_plugin remote_store) + (Monitor_services.root_hashes coordinator_cctxt dac_plugin) +end + +(** Handlers specific to an [Observer]. An [Observer] is responsible for + {ul + {li Monitoring root hashes from a [Coordinator],} + {li Downloading the associated pages,} + {li Validating the hash of each page.} + } *) + +module Observer = struct + let new_root_hash ctxt dac_plugin page_store = + let open Lwt_result_syntax in + let coordinator_cctxt = ctxt.Node_context.Observer.coordinator_cctxt in + let handler dac_plugin remote_store _stopper root_hash = + let*! () = Event.emit_new_root_hash_received dac_plugin root_hash in + let*! payload_result = + Pages_encoding.Merkle_tree.V0.Remote.deserialize_payload + dac_plugin + ~page_store:remote_store + root_hash + in + match payload_result with + | Ok _ -> + let*! () = + Event.emit_received_root_hash_processed dac_plugin root_hash + in + return () + | Error errs -> + (* TODO: https://gitlab.com/tezos/tezos/-/issues/4930. + Improve handling of errors. *) + let*! () = + Event.emit_processing_root_hash_failed dac_plugin root_hash errs + in + return () + in + let remote_store = + Page_store.(Remote.init {cctxt = coordinator_cctxt; page_store}) + in + let*! () = Event.(emit subscribed_to_root_hashes_stream ()) in + make_stream_daemon + (handler dac_plugin remote_store) + (Monitor_services.root_hashes coordinator_cctxt dac_plugin) +end + +(** Handlers specific to a [Legacy] DAC node. If no + [Dac_client.coordinator_cctxt] is specified for the [Legacy] DAC node, + then its only handler will be the one responsible for tracking Layer 1 + heads. Otherwise, the [Legacy] DAC node will also be equipped with a + handler for monitoring root hashes from the specified + [Dac_client.coordinator_cctxt]. The responsibilities of this handler + will be + {ul + {li Monitoring root hashes from the [Dac_client.coordinator_cctxt],} + {li Downloading the associated pages,} + {li Validating the hash of each page,} + {li Furthermore, if a [committee_member] is specified + in the [Legacy] node configuration, then this handler will also } + {ul + {li Sign the final root hash with the public key of the + committee member,} + {li Send the signature back to the [Coordinaotor] via the + [Dac_client.coordinator_cctxt].} + } + } *) + +module Legacy = struct + let push_payload_signature dac_plugin coordinator_cctxt wallet_cctxt + committee_member root_hash = + let open Lwt_result_syntax in + let secret_key_uri_opt = + committee_member.Wallet_account.Legacy.secret_key_uri_opt + in + let signer_pkh = committee_member.public_key_hash in + match secret_key_uri_opt with + | Some secret_key_uri -> + let bytes_to_sign = Dac_plugin.hash_to_bytes root_hash in + let* signature = + Tezos_client_base.Client_keys.aggregate_sign + wallet_cctxt + secret_key_uri + bytes_to_sign + in + let signature_repr = + Signature_repr.{root_hash; signature; signer_pkh} + in + let* () = + Dac_node_client.put_dac_member_signature + dac_plugin + coordinator_cctxt + ~signature:signature_repr + in + let*! () = Event.emit_signature_pushed_to_coordinator signature in + return_unit + | None -> + let*! () = Event.emit_cannot_retrieve_keys_from_address signer_pkh in + return () + + (** This handler will be invoked only when a [coordinator_cctxt] is specified + in the DAC node configuration. The DAC node tries to subscribes to the + stream of root hashes via the streamed GET /monitor/root_hashes RPC call + to the dac node corresponding to [coordinator_cctxt]. *) + let new_root_hash ctxt coordinator_cctxt wallet_cctxt dac_plugin page_store = + let committee_member_opt = ctxt.Node_context.Legacy.committee_member_opt in + let open Lwt_result_syntax in + let handler dac_plugin remote_store _stopper root_hash = + let*! () = Event.emit_new_root_hash_received dac_plugin root_hash in + let*! payload_result = + Pages_encoding.Merkle_tree.V0.Remote.deserialize_payload + dac_plugin + ~page_store:remote_store + root_hash + in + match payload_result with + | Ok _ -> ( + let*! () = + Event.emit_received_root_hash_processed dac_plugin root_hash + in + match committee_member_opt with + | Some committee_member -> + (* If there is a [committee_member_address], it means the node is run as a member, + so we must sign the payload and post the signature to the coordinator + If there is no [committee_member_address] provided, it means the node is run as an observer + then we simply [return ()] *) + push_payload_signature + dac_plugin + coordinator_cctxt + wallet_cctxt + committee_member + root_hash + | None -> + let*! () = Event.emit_no_committee_member_address () in + return ()) + | Error errs -> + (* TODO: https://gitlab.com/tezos/tezos/-/issues/4930. + Improve handling of errors. *) + let*! () = + Event.emit_processing_root_hash_failed dac_plugin root_hash errs + in + return () + in + let remote_store = + Page_store.(Remote.init {cctxt = coordinator_cctxt; page_store}) + in + let*! () = Event.(emit subscribed_to_root_hashes_stream ()) in + make_stream_daemon + (handler dac_plugin remote_store) + (Monitor_services.root_hashes coordinator_cctxt dac_plugin) +end + +let handlers node_ctxt = + let open Lwt_result_syntax in + let*? plugin = Node_context.get_dac_plugin node_ctxt in + let page_store = Node_context.get_page_store node_ctxt in + let wallet_cctxt = Node_context.get_tezos_node_cctxt node_ctxt in + match Node_context.mode node_ctxt with + | Coordinator _ -> return [new_head node_ctxt] + | Committee_member ctxt -> + return + [ + new_head node_ctxt; + Committee_member.new_root_hash ctxt wallet_cctxt plugin page_store; + ] + | Observer ctxt -> + return [new_head node_ctxt; Observer.new_root_hash ctxt plugin page_store] + | Legacy ctxt -> + let coordinator_cctxt_opt = ctxt.Node_context.Legacy.coordinator_cctxt in + let root_hash_handler = + coordinator_cctxt_opt + |> Option.map (fun coordinator_cctxt -> + Legacy.new_root_hash + ctxt + coordinator_cctxt + wallet_cctxt + plugin + page_store) + |> Option.to_list + in + return @@ [new_head node_ctxt] @ root_hash_handler diff --git a/src/lib_dac_node/handler.mli b/src/lib_dac_node/handler.mli new file mode 100644 index 000000000000..c63872fd5f93 --- /dev/null +++ b/src/lib_dac_node/handler.mli @@ -0,0 +1,61 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 Trili Tech, *) +(* *) +(* Permission is hereby granted, free of charge, to any person obtaining a *) +(* copy of this software and associated documentation files (the "Software"),*) +(* to deal in the Software without restriction, including without limitation *) +(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) +(* and/or sell copies of the Software, and to permit persons to whom the *) +(* Software is furnished to do so, subject to the following conditions: *) +(* *) +(* The above copyright notice and this permission notice shall be included *) +(* in all copies or substantial portions of the Software. *) +(* *) +(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) +(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) +(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) +(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) +(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) +(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) +(* DEALINGS IN THE SOFTWARE. *) +(* *) +(*****************************************************************************) + +(** A value of type [t] represent a handler that specifies a procedure that is + invoked by the DAC daemon. In most cases, it never resolves unless the DAC + node terminates, in which case the underlying + [Tezos_rpc__RPC_context.stopper] is called. *) +type t = unit tzresult Lwt.t * Tezos_rpc__RPC_context.stopper + +(** Monitor heads and try resolve the DAC protocol plugin corresponding to + the protocol of the targeted node. *) +val resolve_plugin_and_set_ready : Node_context.t -> t tzresult Lwt.t + +(** [handlers ctxt] returns the handlers to be executed in the main loop of the + daemon. The set of handlers is different according to the operating mode + indicated in [ctxt.mode]. All operating modes have one handler for + tracking new heads from the Layer 1. Additionally + {ul + {li If [ctx.mode] is [Coordinator _], then no other handlers are + present, } + {li If [ctxt.mode] is [Committee_member _], then the DAC node also has + one handler for monitoring root hashes streamed by a [Coordinator]. + Upon detecting a new root hash, the associated pages are downloaded + from the [Coordinator], after which the root hash is signed and the + signature posted to the [Coordinator], } + {li If [ctxt.mode] is [Observer _], then the DAC node also has + one handler for monitoring root hashes streamed by a [Coordinator]. + Upon detecting a new root hash, the associated pages are downloaded + from the [Coordinator]. Differently from the case of + [Committee_member _], root hashes are not signed, } + {li If [ctxt.mode] is [Legacy _], then a handler to monitor root hashes + from a coordinator is present only if the RPC address of a + [Coordindator] node is specified in the DAC node configuration. + Additionally, if the configuration also contains a [committee_member] + address field specified, then this handler behave as in the case for + [Committee_member _]. Otherwise, it behaves as in the case for + [Observer _].} + } *) +val handlers : Node_context.t -> t tzresult Lwt.t list tzresult Lwt.t -- GitLab From 800b07e83dbd68e195550ad3672bb8969c604840 Mon Sep 17 00:00:00 2001 From: Andrea Cerone Date: Mon, 20 Mar 2023 17:05:31 +0000 Subject: [PATCH 03/11] Dac/Node: Remove DAC_manager module --- src/lib_dac_node/dac_manager.ml | 93 -------------------------------- src/lib_dac_node/dac_manager.mli | 54 ------------------- src/lib_dac_node/daemon.ml | 2 +- src/lib_dac_node/handler.ml | 23 +++++++- src/lib_dac_node/page_store.ml | 41 ++++++++++++++ src/lib_dac_node/page_store.mli | 19 +++++++ 6 files changed, 83 insertions(+), 149 deletions(-) delete mode 100644 src/lib_dac_node/dac_manager.ml delete mode 100644 src/lib_dac_node/dac_manager.mli diff --git a/src/lib_dac_node/dac_manager.ml b/src/lib_dac_node/dac_manager.ml deleted file mode 100644 index 3c35be48f33e..000000000000 --- a/src/lib_dac_node/dac_manager.ml +++ /dev/null @@ -1,93 +0,0 @@ -(*****************************************************************************) -(* *) -(* Open Source License *) -(* Copyright (c) 2022 Trili Tech, *) -(* Copyright (c) 2023 Marigold, *) -(* *) -(* Permission is hereby granted, free of charge, to any person obtaining a *) -(* copy of this software and associated documentation files (the "Software"),*) -(* to deal in the Software without restriction, including without limitation *) -(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) -(* and/or sell copies of the Software, and to permit persons to whom the *) -(* Software is furnished to do so, subject to the following conditions: *) -(* *) -(* The above copyright notice and this permission notice shall be included *) -(* in all copies or substantial portions of the Software. *) -(* *) -(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) -(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) -(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) -(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) -(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) -(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) -(* DEALINGS IN THE SOFTWARE. *) -(* *) -(*****************************************************************************) - -type error += - | Reveal_data_path_not_a_directory of string - | Cannot_create_reveal_data_dir of string - -let () = - register_error_kind - `Permanent - ~id:"dac.node.dac.reveal_data_path_not_a_dir" - ~title:"Reveal data path is not a directory" - ~description:"Reveal data path is not a directory" - ~pp:(fun ppf reveal_data_path -> - Format.fprintf - ppf - "Reveal data path %s is not a directory" - reveal_data_path) - Data_encoding.(obj1 (req "path" string)) - (function Reveal_data_path_not_a_directory path -> Some path | _ -> None) - (fun path -> Reveal_data_path_not_a_directory path) ; - register_error_kind - `Permanent - ~id:"dac.node.dac.cannot_create_directory" - ~title:"Cannot create directory to store reveal data" - ~description:"Cannot create directory to store reveal data" - ~pp:(fun ppf reveal_data_path -> - Format.fprintf - ppf - "Cannot create a directory \"%s\" to store reveal data" - reveal_data_path) - Data_encoding.(obj1 (req "path" string)) - (function Cannot_create_reveal_data_dir path -> Some path | _ -> None) - (fun path -> Cannot_create_reveal_data_dir path) - -module Storage = struct - let ensure_reveal_data_dir_exists reveal_data_dir = - let open Lwt_result_syntax in - Lwt.catch - (fun () -> - let*! () = Lwt_utils_unix.create_dir ~perm:0o744 reveal_data_dir in - return ()) - (function - | Failure s -> - if String.equal s "Not a directory" then - tzfail @@ Reveal_data_path_not_a_directory reveal_data_dir - else tzfail @@ Cannot_create_reveal_data_dir reveal_data_dir - | _ -> tzfail @@ Cannot_create_reveal_data_dir reveal_data_dir) -end - -let resolve_plugin - (protocols : Tezos_shell_services.Chain_services.Blocks.protocols) = - let open Lwt_syntax in - let current_protocol = protocols.current_protocol in - let next_protocol = protocols.next_protocol in - let plugin_opt = - Option.either - (Dac_plugin.get current_protocol) - (Dac_plugin.get next_protocol) - in - match plugin_opt with - | None -> - let+ () = - Event.emit_protocol_plugin_not_resolved current_protocol next_protocol - in - None - | Some dac_plugin -> - let (module Dac_plugin : Dac_plugin.T) = dac_plugin in - let+ () = Event.emit_protocol_plugin_resolved Dac_plugin.Proto.hash in - Some dac_plugin diff --git a/src/lib_dac_node/dac_manager.mli b/src/lib_dac_node/dac_manager.mli deleted file mode 100644 index 29e1647d532d..000000000000 --- a/src/lib_dac_node/dac_manager.mli +++ /dev/null @@ -1,54 +0,0 @@ -(*****************************************************************************) -(* *) -(* Open Source License *) -(* Copyright (c) 2022 Trili Tech, *) -(* Copyright (c) 2023 Marigold, *) -(* *) -(* Permission is hereby granted, free of charge, to any person obtaining a *) -(* copy of this software and associated documentation files (the "Software"),*) -(* to deal in the Software without restriction, including without limitation *) -(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *) -(* and/or sell copies of the Software, and to permit persons to whom the *) -(* Software is furnished to do so, subject to the following conditions: *) -(* *) -(* The above copyright notice and this permission notice shall be included *) -(* in all copies or substantial portions of the Software. *) -(* *) -(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*) -(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *) -(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *) -(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*) -(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *) -(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *) -(* DEALINGS IN THE SOFTWARE. *) -(* *) -(*****************************************************************************) - -(** Module that implements Dac related functionalities. *) - -type error += - | Reveal_data_path_not_a_directory of string - | Cannot_create_reveal_data_dir of string - -module Storage : sig - (** [ensure_reveal_data_dir_exists reveal_data_dir] checks that the - path at [reveal_data_dir] exists and is a directory. If - the path does not exist, it is created as a directory. - Parent directories are recursively created when they do not - exist. - - This function may fail with - {ul - {li [Reveal_data_path_not_a_directory reveal_data_dir] if the - path exists and is not a directory, - - {li [Cannot_create_reveal_data_dir reveal_data_dir] If the - creation of the directory fails.}} - } - *) - val ensure_reveal_data_dir_exists : string -> unit tzresult Lwt.t -end - -val resolve_plugin : - Tezos_shell_services.Chain_services.Blocks.protocols -> - (module Dac_plugin.T) option Lwt.t diff --git a/src/lib_dac_node/daemon.ml b/src/lib_dac_node/daemon.ml index 8cef2a39bc9d..1589861fd3e4 100644 --- a/src/lib_dac_node/daemon.ml +++ b/src/lib_dac_node/daemon.ml @@ -96,7 +96,7 @@ let run ~data_dir cctxt = config) = Configuration.load ~data_dir in - let* () = Dac_manager.Storage.ensure_reveal_data_dir_exists reveal_data_dir in + let* () = Page_store.ensure_reveal_data_dir_exists reveal_data_dir in let* ctxt = Node_context.init config cctxt in (* TODO: https://gitlab.com/tezos/tezos/-/issues/4725 Stop DAC node when in Legacy mode, if threshold is not reached. *) diff --git a/src/lib_dac_node/handler.ml b/src/lib_dac_node/handler.ml index 83a5594cce12..d0a5077a1ccc 100644 --- a/src/lib_dac_node/handler.ml +++ b/src/lib_dac_node/handler.ml @@ -25,6 +25,27 @@ type t = unit tzresult Lwt.t * Tezos_rpc__RPC_context.stopper +let resolve_plugin + (protocols : Tezos_shell_services.Chain_services.Blocks.protocols) = + let open Lwt_syntax in + let current_protocol = protocols.current_protocol in + let next_protocol = protocols.next_protocol in + let plugin_opt = + Option.either + (Dac_plugin.get current_protocol) + (Dac_plugin.get next_protocol) + in + match plugin_opt with + | None -> + let+ () = + Event.emit_protocol_plugin_not_resolved current_protocol next_protocol + in + None + | Some dac_plugin -> + let (module Dac_plugin : Dac_plugin.T) = dac_plugin in + let+ () = Event.emit_protocol_plugin_resolved Dac_plugin.Proto.hash in + Some dac_plugin + (** [make_stream_daemon handler streamed_call] calls [handler] on each newly received value from [streamed_call]. It returns a couple [(p, stopper)] where [p] is a promise resolving when @@ -62,7 +83,7 @@ let resolve_plugin_and_set_ready ctxt = let* protocols = Tezos_shell_services.Chain_services.Blocks.protocols cctxt () in - let*! dac_plugin = Dac_manager.resolve_plugin protocols in + let*! dac_plugin = resolve_plugin protocols in match dac_plugin with | Some dac_plugin -> Node_context.set_ready ctxt dac_plugin ; diff --git a/src/lib_dac_node/page_store.ml b/src/lib_dac_node/page_store.ml index 2d3aedd2ba2c..a9e590532ba8 100644 --- a/src/lib_dac_node/page_store.ml +++ b/src/lib_dac_node/page_store.ml @@ -25,6 +25,8 @@ (*****************************************************************************) type error += + | Reveal_data_path_not_a_directory of string + | Cannot_create_reveal_data_dir of string | Cannot_write_page_to_page_storage of {hash : string; content : bytes} | Cannot_read_page_from_page_storage of string | Incorrect_page_hash of { @@ -33,6 +35,32 @@ type error += } let () = + register_error_kind + `Permanent + ~id:"dac.node.dac.reveal_data_path_not_a_dir" + ~title:"Reveal data path is not a directory" + ~description:"Reveal data path is not a directory" + ~pp:(fun ppf reveal_data_path -> + Format.fprintf + ppf + "Reveal data path %s is not a directory" + reveal_data_path) + Data_encoding.(obj1 (req "path" string)) + (function Reveal_data_path_not_a_directory path -> Some path | _ -> None) + (fun path -> Reveal_data_path_not_a_directory path) ; + register_error_kind + `Permanent + ~id:"dac.node.dac.cannot_create_directory" + ~title:"Cannot create directory to store reveal data" + ~description:"Cannot create directory to store reveal data" + ~pp:(fun ppf reveal_data_path -> + Format.fprintf + ppf + "Cannot create a directory \"%s\" to store reveal data" + reveal_data_path) + Data_encoding.(obj1 (req "path" string)) + (function Cannot_create_reveal_data_dir path -> Some path | _ -> None) + (fun path -> Cannot_create_reveal_data_dir path) ; register_error_kind `Permanent ~id:"cannot_write_page_to_page_storage" @@ -86,6 +114,19 @@ let () = | _ -> None) (fun (expected, actual) -> Incorrect_page_hash {expected; actual}) +let ensure_reveal_data_dir_exists reveal_data_dir = + let open Lwt_result_syntax in + Lwt.catch + (fun () -> + let*! () = Lwt_utils_unix.create_dir ~perm:0o744 reveal_data_dir in + return ()) + (function + | Failure s -> + if String.equal s "Not a directory" then + tzfail @@ Reveal_data_path_not_a_directory reveal_data_dir + else tzfail @@ Cannot_create_reveal_data_dir reveal_data_dir + | _ -> tzfail @@ Cannot_create_reveal_data_dir reveal_data_dir) + module type S = sig type t diff --git a/src/lib_dac_node/page_store.mli b/src/lib_dac_node/page_store.mli index 6de355167d99..3fddf244a008 100644 --- a/src/lib_dac_node/page_store.mli +++ b/src/lib_dac_node/page_store.mli @@ -25,6 +25,8 @@ (*****************************************************************************) type error += + | Reveal_data_path_not_a_directory of string + | Cannot_create_reveal_data_dir of string | Cannot_write_page_to_page_storage of {hash : string; content : bytes} | Cannot_read_page_from_page_storage of string | Incorrect_page_hash of { @@ -119,3 +121,20 @@ module Internal_for_tests : sig with type configuration = R.remote_context * P.t and type t = R.remote_context * P.t end + +(** [ensure_reveal_data_dir_exists reveal_data_dir] checks that the + path at [reveal_data_dir] exists and is a directory. If + the path does not exist, it is created as a directory. + Parent directories are recursively created when they do not + exist. + + This function may fail with + {ul + {li [Reveal_data_path_not_a_directory reveal_data_dir] if the + path exists and is not a directory, + + {li [Cannot_create_reveal_data_dir reveal_data_dir] If the + creation of the directory fails.}} + } + *) +val ensure_reveal_data_dir_exists : string -> unit tzresult Lwt.t -- GitLab From 10be67fbb806d79e5e64592982c37119775dfb7c Mon Sep 17 00:00:00 2001 From: Andrea Cerone Date: Wed, 15 Mar 2023 15:46:07 +0000 Subject: [PATCH 04/11] Dac/Node: fix typo in committee member configuration command --- src/bin_dac_node/main_dac.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bin_dac_node/main_dac.ml b/src/bin_dac_node/main_dac.ml index b62927daeac8..2d6481172be1 100644 --- a/src/bin_dac_node/main_dac.ml +++ b/src/bin_dac_node/main_dac.ml @@ -229,7 +229,7 @@ module Config_init = struct ~desc:"Configure DAC node in committee member mode." (args4 data_dir_arg rpc_address_arg rpc_port_arg reveal_data_dir_arg) (prefixes - ["configure"; "as"; "commmittee"; "member"; "with"; "coordinator"] + ["configure"; "as"; "committee"; "member"; "with"; "coordinator"] @@ coordinator_rpc_param @@ prefixes ["and"; "signer"] @@ tz4_address_param @@ stop) -- GitLab From 0305f30cbb6a6b80b7a5f546408296c9a4289e90 Mon Sep 17 00:00:00 2001 From: Andrea Cerone Date: Wed, 15 Mar 2023 15:49:16 +0000 Subject: [PATCH 05/11] Dac/Node: fix coordinator port parameter in committee member encoding --- src/lib_dac_node/configuration.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib_dac_node/configuration.ml b/src/lib_dac_node/configuration.ml index c66f96063a2a..6c034400a5f9 100644 --- a/src/lib_dac_node/configuration.ml +++ b/src/lib_dac_node/configuration.ml @@ -89,7 +89,7 @@ module Committee_member = struct {coordinator_rpc_address; coordinator_rpc_port; address}) (obj3 (req "coordinator_rpc_address" string) - (req "coordinator_rpc_port" int16) + (req "coordinator_rpc_port" uint16) (req "address" Tezos_crypto.Aggregate_signature.Public_key_hash.encoding))) -- GitLab From e5c35aed0eeda40a54bf72f6ec3bef89a0821c82 Mon Sep 17 00:00:00 2001 From: Andrea Cerone Date: Fri, 10 Mar 2023 17:37:53 +0000 Subject: [PATCH 06/11] Dac/Tezt: disable preimage test for legacy mode --- tezt/tests/dac.ml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tezt/tests/dac.ml b/tezt/tests/dac.ml index 1a0e3bd5b374..467eb849d7ae 100644 --- a/tezt/tests/dac.ml +++ b/tezt/tests/dac.ml @@ -1326,13 +1326,14 @@ let register ~protocols = "dac_get_certificate" test_get_certificate protocols ; - scenario_with_layer1_and_legacy_dac_nodes - ~threshold:0 - ~committee_members:0 - ~tags:["dac"; "dac_node"] - "dac_coordinator_post_preimage_endpoint" - Legacy.test_coordinator_post_preimage_endpoint - protocols ; + (* TODO: reenable once tests for whole infrastructure have been written. *) + (* scenario_with_layer1_and_legacy_dac_nodes + ~threshold:0 + ~committee_members:0 + ~tags:["dac"; "dac_node"] + "dac_coordinator_post_preimage_endpoint" + Legacy.test_coordinator_post_preimage_endpoint + protocols ; *) scenario_with_layer1_and_legacy_dac_nodes ~threshold:0 ~committee_members:3 -- GitLab From d151cc5bd9ccf0255e2460f4e55951abae1656bd Mon Sep 17 00:00:00 2001 From: Andrea Cerone Date: Fri, 10 Mar 2023 18:28:22 +0000 Subject: [PATCH 07/11] Dac/Tezt: Helpers for setting dac nodes in tezts --- tezt/tests/dac.ml | 203 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 196 insertions(+), 7 deletions(-) diff --git a/tezt/tests/dac.ml b/tezt/tests/dac.ml index 467eb849d7ae..81e494bfdb97 100644 --- a/tezt/tests/dac.ml +++ b/tezt/tests/dac.ml @@ -34,6 +34,22 @@ let hooks = Tezos_regression.hooks +module Scenarios = struct + type full = { + protocol : Protocol.t; + node : Node.t; + client : Client.t; + key : string; + sc_rollup_address : string; + sc_rollup_node : Sc_rollup_node.t; + coordinator_node : Dac_node.t; + committee_members : Account.aggregate_key list; + committee_members_nodes : Dac_node.t list; + observer_nodes : Dac_node.t list; + rollup_nodes : Sc_rollup_node.t list; + } +end + (* FIXME: https://gitlab.com/tezos/tezos/-/issues/3173 The functions below are duplicated from sc_rollup.ml. They should be moved to a common submodule. *) @@ -160,7 +176,7 @@ let with_layer1 ?additional_bootstrap_accounts ?commitment_period let bootstrap1_key = Constant.bootstrap1.public_key_hash in f node client bootstrap1_key -let with_legacy_dac_node tezos_node ?sc_rollup_node ?(pvm_name = "arith") +let with_legacy_dac_node tezos_node ?name ?sc_rollup_node ?(pvm_name = "arith") ?(wait_ready = true) ~threshold ?committee_member_address ~committee_members tezos_client f = let range i = List.init i Fun.id in @@ -185,6 +201,7 @@ let with_legacy_dac_node tezos_node ?sc_rollup_node ?(pvm_name = "arith") in let dac_node = Dac_node.create_legacy + ?name ~node:tezos_node ~client:tezos_client ?reveal_data_dir @@ -200,10 +217,97 @@ let with_legacy_dac_node tezos_node ?sc_rollup_node ?(pvm_name = "arith") let* () = Dac_node.run dac_node ~wait_ready in f dac_node committee_members +let with_coordinator_node tezos_node ?name ?sc_rollup_node ?(pvm_name = "arith") + ?(wait_ready = true) ~threshold ~committee_members tezos_client f = + let range i = List.init i Fun.id in + let reveal_data_dir = + Option.map + (fun sc_rollup_node -> + Filename.concat (Sc_rollup_node.data_dir sc_rollup_node) pvm_name) + sc_rollup_node + in + let* committee_members = + List.fold_left + (fun keys i -> + let* keys in + let* key = + Client.bls_gen_and_show_keys + ~alias:(Format.sprintf "committee-member-%d" i) + tezos_client + in + return (key :: keys)) + (return []) + (range committee_members) + in + let dac_node = + Dac_node.create_coordinator + ?name + ~node:tezos_node + ~client:tezos_client + ?reveal_data_dir + ~threshold + ~committee_members: + (List.map + (fun (dc : Account.aggregate_key) -> dc.aggregate_public_key_hash) + committee_members) + () + in + let* _dir = Dac_node.init_config dac_node in + let* () = Dac_node.run dac_node ~wait_ready in + f dac_node committee_members + +let with_committee_member tezos_node coordinator_node ?name ?sc_rollup_node + ?(pvm_name = "arith") ?(wait_ready = true) ~committee_member tezos_client f + = + let reveal_data_dir = + Option.map + (fun sc_rollup_node -> + Filename.concat (Sc_rollup_node.data_dir sc_rollup_node) pvm_name) + sc_rollup_node + in + let Account.{public_key_hash; _} = committee_member in + let dac_node = + Dac_node.create_committee_member + ?name + ~node:tezos_node + ~client:tezos_client + ?reveal_data_dir + ~coordinator_rpc_host:(Dac_node.rpc_host coordinator_node) + ~coordinator_rpc_port:(Dac_node.rpc_port coordinator_node) + ~address:public_key_hash + () + in + let* _dir = Dac_node.init_config dac_node in + let* () = Dac_node.run dac_node ~wait_ready in + f dac_node committee_member + +let with_observer tezos_node coordinator_node ?name ?sc_rollup_node + ?(pvm_name = "arith") ?(wait_ready = true) ~committee_member tezos_client f + = + let reveal_data_dir = + Option.map + (fun sc_rollup_node -> + Filename.concat (Sc_rollup_node.data_dir sc_rollup_node) pvm_name) + sc_rollup_node + in + let dac_node = + Dac_node.create_observer + ?name + ~node:tezos_node + ~client:tezos_client + ?reveal_data_dir + ~coordinator_rpc_host:(Dac_node.rpc_host coordinator_node) + ~coordinator_rpc_port:(Dac_node.rpc_port coordinator_node) + () + in + let* _dir = Dac_node.init_config dac_node in + let* () = Dac_node.run dac_node ~wait_ready in + f dac_node committee_member + (* TODO: https://gitlab.com/tezos/tezos/-/issues/4706 Keep pvm name value in Sc_rollup.t. *) -let with_fresh_rollup ~protocol ?(pvm_name = "arith") f tezos_node tezos_client - bootstrap1_key = +let with_fresh_rollup ~protocol ?(pvm_name = "arith") tezos_node tezos_client + bootstrap1_key f = let* rollup_address = Client.Sc_rollup.originate ~hooks @@ -225,6 +329,91 @@ let with_fresh_rollup ~protocol ?(pvm_name = "arith") f tezos_node tezos_client let* () = Client.bake_for_and_wait tezos_client in f rollup_address sc_rollup_node +let scenario_with_full_dac_infrastructure ?(tags = ["dac"; "full"]) + ?(pvm_name = "arith") ~committee_members ~observers ?commitment_period + ?challenge_window ?event_sections_levels ?node_arguments variant scenario = + let description = "Testing Full DAC infrastructure" in + test + ~__FILE__ + ~tags + (Printf.sprintf "%s (%s)" description variant) + (fun protocol -> + with_layer1 + ?commitment_period + ?challenge_window + ?event_sections_levels + ?node_arguments + ~protocol + @@ fun node client key -> + with_fresh_rollup ~protocol ~pvm_name node client key + @@ fun sc_rollup_address sc_rollup_node _configuration_filename -> + with_coordinator_node + node + client + ~name:"coordinator" + ~pvm_name + ~threshold:0 + ~committee_members + @@ fun coordinator_node committee_members -> + let committee_members_nodes = + List.mapi + (fun i Account.{aggregate_public_key_hash; _} -> + Dac_node.create_committee_member + ~name:("committee-member-" ^ Int.to_string i) + ~node + ~client + ~coordinator_rpc_host:(Dac_node.rpc_host coordinator_node) + ~coordinator_rpc_port:(Dac_node.rpc_port coordinator_node) + ~address:aggregate_public_key_hash + ()) + committee_members + in + let rollup_nodes, observer_nodes = + List.init observers Fun.id + |> List.map (fun i -> + let rollup_node_i = + Sc_rollup_node.create + ~name:("observer-" ^ Int.to_string i ^ "-rollup-node") + ~protocol + Operator + node + ~base_dir:(Client.base_dir client) + ~default_operator:key + in + let reveal_data_dir = + Filename.concat + (Sc_rollup_node.data_dir rollup_node_i) + pvm_name + in + let dac_node_i = + Dac_node.create_observer + ~name:("observer-" ^ Int.to_string i) + ~node + ~client + ~reveal_data_dir + ~coordinator_rpc_host:(Dac_node.rpc_host coordinator_node) + ~coordinator_rpc_port:(Dac_node.rpc_port coordinator_node) + () + in + (rollup_node_i, dac_node_i)) + |> List.split + in + scenario + Scenarios. + { + protocol; + node; + client; + key; + sc_rollup_address; + sc_rollup_node; + coordinator_node; + committee_members; + committee_members_nodes; + observer_nodes; + rollup_nodes; + }) + (* Wrapper scenario functions that should be re-used as much as possible when writing tests. *) let scenario_with_layer1_node ?(tags = ["dac"; "layer1"]) ?commitment_period @@ -270,6 +459,9 @@ let scenario_with_all_nodes ?(tags = ["dac"; "dac_node"; "legacy"]) with_layer1 ?commitment_period ?challenge_window ~protocol @@ fun node client key -> with_fresh_rollup + node + client + key ~protocol ~pvm_name (fun sc_rollup_address sc_rollup_node -> @@ -291,10 +483,7 @@ let scenario_with_all_nodes ?(tags = ["dac"; "dac_node"; "legacy"]) client pvm_name threshold - committee_members) - node - client - key) + committee_members)) let wait_for_layer1_block_processing dac_node level = Dac_node.wait_for dac_node "dac_node_layer_1_new_head.v0" (fun e -> -- GitLab From 4b4eafbe68705f2dff4f5271259bd1287cc5ded8 Mon Sep 17 00:00:00 2001 From: Andrea Cerone Date: Fri, 10 Mar 2023 21:38:55 +0000 Subject: [PATCH 08/11] Dac/Tezt: re-enable test using coordinator --- tezt/tests/dac.ml | 55 ++++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/tezt/tests/dac.ml b/tezt/tests/dac.ml index 81e494bfdb97..7017301e86d2 100644 --- a/tezt/tests/dac.ml +++ b/tezt/tests/dac.ml @@ -1199,25 +1199,6 @@ module Legacy = struct Once profiles are implemented this should be moved out of the `Legacy` module. Additionally, the test should be run using dac node running in the coordinator and not legacy mode. *) - let test_coordinator_post_preimage_endpoint _protocol _node _client - coordinator _threshold _dac_members = - (* 1. Send the [payload] to coordinator. - 2. Assert that it returns [expected_rh]. - 3. Assert event that root hash has been pushed to data streamer - was emitted. *) - let payload = "test_1" in - let expected_rh = - "00b29d7d1e6668fb35a9ff6d46fa321d227e9b93dae91c4649b53168e8c10c1827" - in - let root_hash_pushed_to_data_streamer_promise = - wait_for_root_hash_pushed_to_data_streamer coordinator expected_rh - in - let* actual_rh = - RPC.call coordinator (Dac_rpc.Coordinator.post_preimage ~payload) - in - let () = check_valid_root_hash expected_rh actual_rh in - let* () = root_hash_pushed_to_data_streamer_promise in - Lwt.return_unit end module Signature_manager = struct @@ -1369,6 +1350,27 @@ module Signature_manager = struct end end +module Full_infrastructure = struct + let test_coordinator_post_preimage_endpoint Scenarios.{coordinator_node; _} = + (* 1. Send the [payload] to coordinator. + 2. Assert that it returns [expected_rh]. + 3. Assert event that root hash has been pushed to data streamer + was emitted. *) + let payload = "test_1" in + let expected_rh = + "00b29d7d1e6668fb35a9ff6d46fa321d227e9b93dae91c4649b53168e8c10c1827" + in + let root_hash_pushed_to_data_streamer_promise = + wait_for_root_hash_pushed_to_data_streamer coordinator_node expected_rh + in + let* actual_rh = + RPC.call coordinator_node (Dac_rpc.Coordinator.post_preimage ~payload) + in + let () = check_valid_root_hash expected_rh actual_rh in + let* () = root_hash_pushed_to_data_streamer_promise in + Lwt.return_unit +end + (* Tests that it's possible to retrieve the witness and certificate after storing a dac member signature. Also asserts that the certificate contains the member used for signing. *) @@ -1515,14 +1517,13 @@ let register ~protocols = "dac_get_certificate" test_get_certificate protocols ; - (* TODO: reenable once tests for whole infrastructure have been written. *) - (* scenario_with_layer1_and_legacy_dac_nodes - ~threshold:0 - ~committee_members:0 - ~tags:["dac"; "dac_node"] - "dac_coordinator_post_preimage_endpoint" - Legacy.test_coordinator_post_preimage_endpoint - protocols ; *) + scenario_with_full_dac_infrastructure + ~observers:0 + ~committee_members:0 + ~tags:["dac"; "dac_node"] + "dac_coordinator_post_preimage_endpoint" + Full_infrastructure.test_coordinator_post_preimage_endpoint + protocols ; scenario_with_layer1_and_legacy_dac_nodes ~threshold:0 ~committee_members:3 -- GitLab From 06b26dced2dcbb5978a2fb5cefc70f5175edf084 Mon Sep 17 00:00:00 2001 From: Andrea Cerone Date: Wed, 15 Mar 2023 13:46:26 +0000 Subject: [PATCH 09/11] Dac/Tezt: rename scenario_with_all_nodes --- tezt/tests/dac.ml | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/tezt/tests/dac.ml b/tezt/tests/dac.ml index 7017301e86d2..ce389cf1294f 100644 --- a/tezt/tests/dac.ml +++ b/tezt/tests/dac.ml @@ -447,9 +447,10 @@ let scenario_with_layer1_and_legacy_dac_nodes @@ fun dac_node committee_members -> scenario protocol node client dac_node threshold committee_members) -let scenario_with_all_nodes ?(tags = ["dac"; "dac_node"; "legacy"]) - ?(pvm_name = "arith") ?commitment_period ?challenge_window ~threshold - ?committee_member_address ~committee_members variant scenario = +let scenario_with_layer1_legacy_and_rollup_nodes + ?(tags = ["dac"; "dac_node"; "legacy"]) ?(pvm_name = "arith") + ?commitment_period ?challenge_window ~threshold ?committee_member_address + ~committee_members variant scenario = let description = "Testing DAC rollup and node with L1" in regression_test ~__FILE__ @@ -1454,35 +1455,35 @@ let register ~protocols = test_dac_node_startup protocols ; test_dac_node_imports_committee_members protocols ; test_dac_node_dac_threshold_not_reached protocols ; - scenario_with_all_nodes + scenario_with_layer1_legacy_and_rollup_nodes ~tags:["dac"; "dac_node"] "dac_reveals_data_merkle_tree_v0" test_dac_node_handles_dac_store_preimage_merkle_V0 protocols ~threshold:1 ~committee_members:1 ; - scenario_with_all_nodes + scenario_with_layer1_legacy_and_rollup_nodes ~tags:["dac"; "dac_node"] "dac_reveals_data_hash_chain_v0" test_dac_node_handles_dac_store_preimage_hash_chain_V0 protocols ~threshold:1 ~committee_members:1 ; - scenario_with_all_nodes + scenario_with_layer1_legacy_and_rollup_nodes ~tags:["dac"; "dac_node"] ~threshold:0 ~committee_members:0 "dac_retrieve_preimage" test_dac_node_handles_dac_retrieve_preimage_merkle_V0 protocols ; - scenario_with_all_nodes + scenario_with_layer1_legacy_and_rollup_nodes ~tags:["dac"; "dac_node"] "dac_rollup_arith_uses_reveals" test_rollup_arith_uses_reveals protocols ~threshold:1 ~committee_members:1 ; - scenario_with_all_nodes + scenario_with_layer1_legacy_and_rollup_nodes ~tags:["dac"; "dac_node"] "dac_rollup_arith_wrong_hash" test_reveals_fails_on_wrong_hash -- GitLab From ee0673dcafc80908eb2929179bbef21170d85397 Mon Sep 17 00:00:00 2001 From: Andrea Cerone Date: Wed, 15 Mar 2023 14:22:07 +0000 Subject: [PATCH 10/11] Dac/Tezt: improve overall structure of code --- tezt/tests/dac.ml | 959 +++++++++++++++++++++++----------------------- 1 file changed, 485 insertions(+), 474 deletions(-) diff --git a/tezt/tests/dac.ml b/tezt/tests/dac.ml index ce389cf1294f..bb5ffaeeecd5 100644 --- a/tezt/tests/dac.ml +++ b/tezt/tests/dac.ml @@ -346,7 +346,7 @@ let scenario_with_full_dac_infrastructure ?(tags = ["dac"; "full"]) ~protocol @@ fun node client key -> with_fresh_rollup ~protocol ~pvm_name node client key - @@ fun sc_rollup_address sc_rollup_node _configuration_filename -> + @@ fun sc_rollup_address sc_rollup_node -> with_coordinator_node node client @@ -536,57 +536,6 @@ let pp fmt = function let status_typ = Check.equalable pp ( = ) -let test_dac_node_startup = - Protocol.register_test - ~__FILE__ - ~title:"dac node startup" - ~tags:["dac"; "dac_node"] - @@ fun protocol -> - let run_dac = Dac_node.run ~wait_ready:false in - let nodes_args = Node.[Synchronisation_threshold 0] in - let previous_protocol = - match Protocol.previous_protocol protocol with - | Some p -> p - | None -> assert false - in - let* node, client = - Client.init_with_protocol - `Client - ~protocol:previous_protocol - ~event_sections_levels:[("prevalidator", `Debug)] - ~nodes_args - () - in - let dac_node = - Dac_node.create_legacy ~node ~client ~threshold:0 ~committee_members:[] () - in - let* _dir = Dac_node.init_config dac_node in - let* () = run_dac dac_node in - let* () = - Dac_node.wait_for dac_node "dac_node_layer_1_start_tracking.v0" (fun _ -> - Some ()) - in - assert (Dac_node.is_running_not_ready dac_node) ; - let* () = Dac_node.terminate dac_node in - let* () = Node.terminate node in - Node.Config_file.update - node - (Node.Config_file.set_sandbox_network_with_user_activated_overrides - [(Protocol.hash previous_protocol, Protocol.hash protocol)]) ; - let* () = Node.run node nodes_args in - let* () = Node.wait_for_ready node in - let* () = run_dac dac_node in - let* () = - Lwt.join - [ - Dac_node.wait_for dac_node "dac_node_plugin_resolved.v0" (fun _ -> - Some ()); - Client.bake_for_and_wait client; - ] - in - let* () = Dac_node.terminate dac_node in - return () - let send_messages ?(src = Constant.bootstrap2.alias) ?(alter_final_msg = Fun.id) client msgs = let msg = @@ -611,267 +560,78 @@ let check_preimage expected_preimage actual_preimage = ~error_msg: "Preimage does not match expected value (Current: %L <> Expected: %R)") -let test_dac_node_handles_dac_store_preimage_merkle_V0 _protocol dac_node - sc_rollup_node _sc_rollup_address _node _client pvm_name _threshold - _committee_members = - let payload = "test" in - let* actual_rh, l1_operation = - RPC.call - dac_node - (Dac_rpc.post_store_preimage ~payload ~pagination_scheme:"Merkle_tree_V0") - in - (* Expected reveal hash equals to the result of - [Tezos_dac_alpha.Dac_pages_encoding.Merkle_tree.V0.serialize_payload "test"]. - *) - let expected_rh = - "00a3703854279d2f377d689163d1ec911a840d84b56c4c6f6cafdf0610394df7c6" - in - check_valid_root_hash expected_rh actual_rh ; - let filename = - Filename.concat - (Filename.concat (Sc_rollup_node.data_dir sc_rollup_node) pvm_name) - actual_rh - in - let cin = open_in filename in - let recovered_payload = really_input_string cin (in_channel_length cin) in - let () = close_in cin in - (* Discard first five preamble bytes *) - let recovered_preimage = - String.sub recovered_payload 5 (String.length recovered_payload - 5) - in - check_preimage payload recovered_preimage ; - let* is_signature_valid = - RPC.call dac_node (Dac_rpc.get_verify_signature l1_operation) - in - Check.( - (is_signature_valid = true) - bool - ~error_msg:"Signature of external message is not valid") ; - unit - -let test_dac_node_handles_dac_store_preimage_hash_chain_V0 _protocol dac_node - sc_rollup_node _sc_rollup_address _node _client pvm_name _threshold - _committee_members = - let payload = "test" in - let* actual_rh, _l1_operation = - RPC.call - dac_node - (Dac_rpc.post_store_preimage ~payload ~pagination_scheme:"Hash_chain_V0") - in - (* Expected reveal hash equals to the result of - [Tezos_dac_alpha.Dac_pages_encoding.Hash_chain.V0.serialize_payload "test"]. - *) - let expected_rh = - "00928b20366943e2afd11ebc0eae2e53a93bf177a4fcf35bcc64d503704e65e202" - in - check_valid_root_hash expected_rh actual_rh ; - let filename = - Filename.concat - (Filename.concat (Sc_rollup_node.data_dir sc_rollup_node) pvm_name) - actual_rh - in - let cin = open_in filename in - let recovered_payload = really_input_string cin (in_channel_length cin) in - let () = close_in cin in - let recovered_preimage = - String.sub recovered_payload 0 (String.length payload) - in - check_preimage payload recovered_preimage ; - unit - -let test_dac_node_handles_dac_retrieve_preimage_merkle_V0 _protocol dac_node - sc_rollup_node _sc_rollup_address _node _client pvm_name _threshold - _committee_members = - let payload = "test" in - let* actual_rh, _l1_operation = - RPC.call - dac_node - (Dac_rpc.post_store_preimage ~payload ~pagination_scheme:"Merkle_tree_V0") - in - (* Expected reveal hash equals to the result of - [Tezos_dac_alpha.Dac_pages_encoding.Merkle_tree.V0.serialize_payload "test"]. - *) - let expected_rh = - "00a3703854279d2f377d689163d1ec911a840d84b56c4c6f6cafdf0610394df7c6" - in - check_valid_root_hash expected_rh actual_rh ; - let filename = - Filename.concat - (Filename.concat (Sc_rollup_node.data_dir sc_rollup_node) pvm_name) - actual_rh - in - let cin = open_in filename in - let recovered_payload = really_input_string cin (in_channel_length cin) in - let () = close_in cin in - let recovered_preimage = Hex.of_string recovered_payload in - let* preimage = RPC.call dac_node (Dac_rpc.get_preimage expected_rh) in +(** [check_downloaded_page coordinator observer page_hash] checks that the + [observer] has downloaded a page with [page_hash] from the [coordinator], + that the contents of the page corresponds to the ones of the + [coordinator]. It returns the list of the hashes contained in the + [page_hash], if the page corresponds to a hash page. Otherwise, it returns + the empty list. *) +let check_downloaded_page coordinator observer page_hash = + let* coordinator_hex_encoded_page = + RPC.call coordinator (Dac_rpc.get_preimage page_hash) + in + let coordinator_page = Hex.to_string (`Hex coordinator_hex_encoded_page) in + (* Check that the page has been saved by the observer. *) + let* observer_hex_encoded_page = + RPC.call observer (Dac_rpc.get_preimage page_hash) + in + let observer_page = Hex.to_string (`Hex observer_hex_encoded_page) in + (* Check that the raw page for the root hash stored in the coordinator + is the same as the raw page stored in the observer. *) Check.( - (preimage = Hex.show recovered_preimage) + (coordinator_page = observer_page) string ~error_msg: "Returned page does not match the expected one (Current: %L <> \ Expected: %R)") ; - unit - -let test_rollup_arith_uses_reveals protocol dac_node sc_rollup_node - sc_rollup_address _node client _pvm_name _threshold _committee_members = - let* genesis_info = - RPC.Client.call ~hooks client - @@ RPC.get_chain_block_context_smart_rollups_smart_rollup_genesis_info - sc_rollup_address - in - let init_level = JSON.(genesis_info |-> "level" |> as_int) in - let* () = Sc_rollup_node.run sc_rollup_node sc_rollup_address [] in - let* level = - Sc_rollup_node.wait_for_level ~timeout:120. sc_rollup_node init_level - in - let nadd = 32 * 1024 in - let payload = - let rec aux b n = - if n > 0 then ( - Buffer.add_string b "1 +" ; - (aux [@tailcall]) b (n - 1)) - else ( - Buffer.add_string b "value" ; - String.of_bytes (Buffer.to_bytes b)) - in - let buf = Buffer.create ((nadd * 3) + 2) in - Buffer.add_string buf "0 " ; - aux buf nadd - in - let* actual_rh, _l1_operation = - RPC.call - dac_node - (Dac_rpc.post_store_preimage ~payload ~pagination_scheme:"Hash_chain_V0") - in - let expected_rh = - "0027782d2a7020be332cc42c4e66592ec50305f559a4011981f1d5af81428e7aa3" - in - check_valid_root_hash expected_rh actual_rh ; - let* () = - send_messages - client - ["hash:" ^ actual_rh] - ~alter_final_msg:(fun s -> "text:" ^ s) - in - let* () = bake_levels 2 client in - let* _ = - Sc_rollup_node.wait_for_level ~timeout:120. sc_rollup_node (level + 2) - in - let sc_rollup_client = Sc_rollup_client.create ~protocol sc_rollup_node in - let*! encoded_value = - Sc_rollup_client.state_value ~hooks sc_rollup_client ~key:"vars/value" - in - let value = - match Data_encoding.(Binary.of_bytes int31) @@ encoded_value with - | Error error -> - failwith - (Format.asprintf - "The arithmetic PVM has an unexpected state: %a" - Data_encoding.Binary.pp_read_error - error) - | Ok x -> x - in - Check.( - (value = nadd) int ~error_msg:"Invalid value in rollup state (%L <> %R)") ; - unit - -let test_reveals_fails_on_wrong_hash _protocol dac_node sc_rollup_node - sc_rollup_address _node client _pvm_name _threshold _committee_members = - let payload = "Some data that is not related to the hash" in - let _actual_rh = - RPC.call - dac_node - (Dac_rpc.post_store_preimage ~payload ~pagination_scheme:"Hash_chain_V0") - in - let errorneous_hash = - "0027782d2a7020be332cc42c4e66592ec50305f559a4011981f1d5af81428ecafe" - in - let* genesis_info = - RPC.Client.call ~hooks client - @@ RPC.get_chain_block_context_smart_rollups_smart_rollup_genesis_info - sc_rollup_address - in - let init_level = JSON.(genesis_info |-> "level" |> as_int) in - let* () = Sc_rollup_node.run sc_rollup_node sc_rollup_address [] in - (* Prepare the handler to wait for the rollup node to fail before - sending the L1 message that will trigger the failure. This - ensures that the failure handler can access the status code - of the rollup node even after it has terminated. *) - let expect_failure = - let node_process = Option.get @@ Sc_rollup_node.process sc_rollup_node in - Process.check_error - ~exit_code:1 - ~msg:(rex "Could not open file containing preimage of reveal hash") - node_process - in - let* _level = - Sc_rollup_node.wait_for_level ~timeout:120. sc_rollup_node init_level - in - let* () = - send_messages - client - ["hash:" ^ errorneous_hash] - ~alter_final_msg:(fun s -> "text:" ^ s) - in - expect_failure + let version_tag = observer_page.[0] in + if version_tag = '\000' then return [] + else + let hash_size = 33 in + let preamble_size = 5 in + let concatenated_hashes = + String.sub observer_page 5 (String.length observer_page - preamble_size) + in + let rec split_hashes concatenated_hashes hashes = + if String.equal concatenated_hashes "" then hashes + else + let next_hash = + Hex.show @@ Hex.of_string + @@ String.sub concatenated_hashes 0 hash_size + in + let next_concatenated_hashes = + String.sub + concatenated_hashes + hash_size + (String.length concatenated_hashes - hash_size) + in + split_hashes next_concatenated_hashes (next_hash :: hashes) + in + return @@ split_hashes concatenated_hashes [] -let test_dac_node_imports_committee_members = - Protocol.register_test - ~__FILE__ - ~title:"dac node imports dac members sk_uris" - ~tags:["dac"; "dac_node"] - ~supports:Protocol.(From_protocol (Protocol.number Alpha)) - @@ fun protocol -> - let* node, client = Client.init_with_protocol `Client ~protocol () in - let run_dac = Dac_node.run ~wait_ready:false in - let* committee_member = - Client.bls_gen_keys ~alias:"committee_member" client - in - let* committee_member_info = - Client.bls_show_address ~alias:committee_member client - in - let committee_member_address = - committee_member_info.aggregate_public_key_hash - in - let dac_node = - Dac_node.create_legacy - ~node - ~client - ~threshold:1 - ~committee_members:[committee_member_address] - () - in - let* _dir = Dac_node.init_config dac_node in - let ready_promise = - Dac_node.wait_for dac_node "dac_is_ready.v0" (fun _ -> Some ()) +let check_downloaded_preimage coordinator observer root_hash = + let rec go hashes = + match hashes with + | [] -> return () + | hash :: hashes -> + let* next_hashes = check_downloaded_page coordinator observer hash in + go (hashes @ next_hashes) in - let* () = run_dac dac_node in - let* () = ready_promise in - let* () = Dac_node.terminate dac_node in - unit + go [root_hash] -let test_dac_node_dac_threshold_not_reached = - Protocol.register_test - ~__FILE__ - ~title:"dac node displays warning if dac threshold is not reached" - ~tags:["dac"; "dac_node"] - ~supports:Protocol.(From_protocol (Protocol.number Alpha)) - @@ fun protocol -> - let* node, client = Client.init_with_protocol `Client ~protocol () in - let dac_node = - Dac_node.create_legacy ~node ~client ~threshold:1 ~committee_members:[] () +let sample_payload example_filename = + let json = + JSON.parse_file @@ "tezt/tests/dac_example_payloads/" ^ example_filename + ^ ".json" in - let* _dir = Dac_node.init_config dac_node in - let run_dac = Dac_node.run ~wait_ready:false in - let error_promise = - Dac_node.wait_for dac_node "dac_threshold_not_reached.v0" (fun _ -> Some ()) + let payload = + JSON.(json |-> "payload" |> as_string |> fun s -> Hex.to_string (`Hex s)) in - let* () = run_dac dac_node in - let* () = error_promise in - Dac_node.terminate dac_node + let root_hash = JSON.(json |-> "root_hash" |> as_string) in + (payload, root_hash) -(** This modules encapsulates tests where we have two dac nodes running in +(** This modules encapsulate tests for DAC nodes when running in legacy node. + It includes tests where we have two dac nodes running in the legacy mode interacting with each other. As such one node normally tries to mimic the coordinator and the other tries to mimic signer or observer. Note that both nodes still run in the [legacy] mode, where as such there is @@ -905,6 +665,332 @@ module Legacy = struct in return @@ check_valid_root_hash expected_rh actual_rh + let test_dac_node_imports_committee_members = + Protocol.register_test + ~__FILE__ + ~title:"dac node imports dac members sk_uris" + ~tags:["dac"; "dac_node"] + ~supports:Protocol.(From_protocol (Protocol.number Alpha)) + @@ fun protocol -> + let* node, client = Client.init_with_protocol `Client ~protocol () in + let run_dac = Dac_node.run ~wait_ready:false in + let* committee_member = + Client.bls_gen_keys ~alias:"committee_member" client + in + let* committee_member_info = + Client.bls_show_address ~alias:committee_member client + in + let committee_member_address = + committee_member_info.aggregate_public_key_hash + in + let dac_node = + Dac_node.create_legacy + ~node + ~client + ~threshold:1 + ~committee_members:[committee_member_address] + () + in + let* _dir = Dac_node.init_config dac_node in + let ready_promise = + Dac_node.wait_for dac_node "dac_is_ready.v0" (fun _ -> Some ()) + in + let* () = run_dac dac_node in + let* () = ready_promise in + let* () = Dac_node.terminate dac_node in + unit + + let test_dac_node_dac_threshold_not_reached = + Protocol.register_test + ~__FILE__ + ~title:"dac node displays warning if dac threshold is not reached" + ~tags:["dac"; "dac_node"] + ~supports:Protocol.(From_protocol (Protocol.number Alpha)) + @@ fun protocol -> + let* node, client = Client.init_with_protocol `Client ~protocol () in + let dac_node = + Dac_node.create_legacy ~node ~client ~threshold:1 ~committee_members:[] () + in + let* _dir = Dac_node.init_config dac_node in + let run_dac = Dac_node.run ~wait_ready:false in + let error_promise = + Dac_node.wait_for dac_node "dac_threshold_not_reached.v0" (fun _ -> + Some ()) + in + let* () = run_dac dac_node in + let* () = error_promise in + Dac_node.terminate dac_node + + let test_dac_node_startup = + Protocol.register_test + ~__FILE__ + ~title:"dac node startup" + ~tags:["dac"; "dac_node"] + @@ fun protocol -> + let run_dac = Dac_node.run ~wait_ready:false in + let nodes_args = Node.[Synchronisation_threshold 0] in + let previous_protocol = + match Protocol.previous_protocol protocol with + | Some p -> p + | None -> assert false + in + let* node, client = + Client.init_with_protocol + `Client + ~protocol:previous_protocol + ~event_sections_levels:[("prevalidator", `Debug)] + ~nodes_args + () + in + let dac_node = + Dac_node.create_legacy ~node ~client ~threshold:0 ~committee_members:[] () + in + let* _dir = Dac_node.init_config dac_node in + let* () = run_dac dac_node in + let* () = + Dac_node.wait_for dac_node "dac_node_layer_1_start_tracking.v0" (fun _ -> + Some ()) + in + assert (Dac_node.is_running_not_ready dac_node) ; + let* () = Dac_node.terminate dac_node in + let* () = Node.terminate node in + Node.Config_file.update + node + (Node.Config_file.set_sandbox_network_with_user_activated_overrides + [(Protocol.hash previous_protocol, Protocol.hash protocol)]) ; + let* () = Node.run node nodes_args in + let* () = Node.wait_for_ready node in + let* () = run_dac dac_node in + let* () = + Lwt.join + [ + Dac_node.wait_for dac_node "dac_node_plugin_resolved.v0" (fun _ -> + Some ()); + Client.bake_for_and_wait client; + ] + in + let* () = Dac_node.terminate dac_node in + return () + + let test_dac_node_handles_dac_store_preimage_merkle_V0 _protocol dac_node + sc_rollup_node _sc_rollup_address _node _client pvm_name _threshold + _committee_members = + let payload = "test" in + let* actual_rh, l1_operation = + RPC.call + dac_node + (Dac_rpc.post_store_preimage + ~payload + ~pagination_scheme:"Merkle_tree_V0") + in + (* Expected reveal hash equals to the result of + [Tezos_dac_alpha.Dac_pages_encoding.Merkle_tree.V0.serialize_payload "test"]. + *) + let expected_rh = + "00a3703854279d2f377d689163d1ec911a840d84b56c4c6f6cafdf0610394df7c6" + in + check_valid_root_hash expected_rh actual_rh ; + let filename = + Filename.concat + (Filename.concat (Sc_rollup_node.data_dir sc_rollup_node) pvm_name) + actual_rh + in + let cin = open_in filename in + let recovered_payload = really_input_string cin (in_channel_length cin) in + let () = close_in cin in + (* Discard first five preamble bytes *) + let recovered_preimage = + String.sub recovered_payload 5 (String.length recovered_payload - 5) + in + check_preimage payload recovered_preimage ; + let* is_signature_valid = + RPC.call dac_node (Dac_rpc.get_verify_signature l1_operation) + in + Check.( + (is_signature_valid = true) + bool + ~error_msg:"Signature of external message is not valid") ; + unit + + let test_dac_node_handles_dac_store_preimage_hash_chain_V0 _protocol dac_node + sc_rollup_node _sc_rollup_address _node _client pvm_name _threshold + _committee_members = + let payload = "test" in + let* actual_rh, _l1_operation = + RPC.call + dac_node + (Dac_rpc.post_store_preimage + ~payload + ~pagination_scheme:"Hash_chain_V0") + in + (* Expected reveal hash equals to the result of + [Tezos_dac_alpha.Dac_pages_encoding.Hash_chain.V0.serialize_payload "test"]. + *) + let expected_rh = + "00928b20366943e2afd11ebc0eae2e53a93bf177a4fcf35bcc64d503704e65e202" + in + check_valid_root_hash expected_rh actual_rh ; + let filename = + Filename.concat + (Filename.concat (Sc_rollup_node.data_dir sc_rollup_node) pvm_name) + actual_rh + in + let cin = open_in filename in + let recovered_payload = really_input_string cin (in_channel_length cin) in + let () = close_in cin in + let recovered_preimage = + String.sub recovered_payload 0 (String.length payload) + in + check_preimage payload recovered_preimage ; + unit + + let test_dac_node_handles_dac_retrieve_preimage_merkle_V0 _protocol dac_node + sc_rollup_node _sc_rollup_address _node _client pvm_name _threshold + _committee_members = + let payload = "test" in + let* actual_rh, _l1_operation = + RPC.call + dac_node + (Dac_rpc.post_store_preimage + ~payload + ~pagination_scheme:"Merkle_tree_V0") + in + (* Expected reveal hash equals to the result of + [Tezos_dac_alpha.Dac_pages_encoding.Merkle_tree.V0.serialize_payload "test"]. + *) + let expected_rh = + "00a3703854279d2f377d689163d1ec911a840d84b56c4c6f6cafdf0610394df7c6" + in + check_valid_root_hash expected_rh actual_rh ; + let filename = + Filename.concat + (Filename.concat (Sc_rollup_node.data_dir sc_rollup_node) pvm_name) + actual_rh + in + let cin = open_in filename in + let recovered_payload = really_input_string cin (in_channel_length cin) in + let () = close_in cin in + let recovered_preimage = Hex.of_string recovered_payload in + let* preimage = RPC.call dac_node (Dac_rpc.get_preimage expected_rh) in + Check.( + (preimage = Hex.show recovered_preimage) + string + ~error_msg: + "Returned page does not match the expected one (Current: %L <> \ + Expected: %R)") ; + unit + + let test_rollup_arith_uses_reveals protocol dac_node sc_rollup_node + sc_rollup_address _node client _pvm_name _threshold _committee_members = + let* genesis_info = + RPC.Client.call ~hooks client + @@ RPC.get_chain_block_context_smart_rollups_smart_rollup_genesis_info + sc_rollup_address + in + let init_level = JSON.(genesis_info |-> "level" |> as_int) in + let* () = Sc_rollup_node.run sc_rollup_node sc_rollup_address [] in + let* level = + Sc_rollup_node.wait_for_level ~timeout:120. sc_rollup_node init_level + in + let nadd = 32 * 1024 in + let payload = + let rec aux b n = + if n > 0 then ( + Buffer.add_string b "1 +" ; + (aux [@tailcall]) b (n - 1)) + else ( + Buffer.add_string b "value" ; + String.of_bytes (Buffer.to_bytes b)) + in + let buf = Buffer.create ((nadd * 3) + 2) in + Buffer.add_string buf "0 " ; + aux buf nadd + in + let* actual_rh, _l1_operation = + RPC.call + dac_node + (Dac_rpc.post_store_preimage + ~payload + ~pagination_scheme:"Hash_chain_V0") + in + let expected_rh = + "0027782d2a7020be332cc42c4e66592ec50305f559a4011981f1d5af81428e7aa3" + in + check_valid_root_hash expected_rh actual_rh ; + let* () = + send_messages + client + ["hash:" ^ actual_rh] + ~alter_final_msg:(fun s -> "text:" ^ s) + in + let* () = bake_levels 2 client in + let* _ = + Sc_rollup_node.wait_for_level ~timeout:120. sc_rollup_node (level + 2) + in + let sc_rollup_client = Sc_rollup_client.create ~protocol sc_rollup_node in + let*! encoded_value = + Sc_rollup_client.state_value ~hooks sc_rollup_client ~key:"vars/value" + in + let value = + match Data_encoding.(Binary.of_bytes int31) @@ encoded_value with + | Error error -> + failwith + (Format.asprintf + "The arithmetic PVM has an unexpected state: %a" + Data_encoding.Binary.pp_read_error + error) + | Ok x -> x + in + Check.( + (value = nadd) int ~error_msg:"Invalid value in rollup state (%L <> %R)") ; + unit + + let test_reveals_fails_on_wrong_hash _protocol dac_node sc_rollup_node + sc_rollup_address _node client _pvm_name _threshold _committee_members = + let payload = "Some data that is not related to the hash" in + let _actual_rh = + RPC.call + dac_node + (Dac_rpc.post_store_preimage + ~payload + ~pagination_scheme:"Hash_chain_V0") + in + let errorneous_hash = + "0027782d2a7020be332cc42c4e66592ec50305f559a4011981f1d5af81428ecafe" + in + let* genesis_info = + RPC.Client.call ~hooks client + @@ RPC.get_chain_block_context_smart_rollups_smart_rollup_genesis_info + sc_rollup_address + in + let init_level = JSON.(genesis_info |-> "level" |> as_int) in + let* () = Sc_rollup_node.run sc_rollup_node sc_rollup_address [] in + (* Prepare the handler to wait for the rollup node to fail before + sending the L1 message that will trigger the failure. This + ensures that the failure handler can access the status code + of the rollup node even after it has terminated. *) + let expect_failure = + let node_process = Option.get @@ Sc_rollup_node.process sc_rollup_node in + Process.check_error + ~exit_code:1 + ~msg:(rex "Could not open file containing preimage of reveal hash") + node_process + in + let* _level = + Sc_rollup_node.wait_for_level ~timeout:120. sc_rollup_node init_level + in + let* () = + send_messages + client + ["hash:" ^ errorneous_hash] + ~alter_final_msg:(fun s -> "text:" ^ s) + in + expect_failure + + (* The following tests involve multiple legacy DAC nodes running at + the same time and playing either the coordinator, committee member or + observer role. *) + let test_streaming_of_root_hashes_as_observer _protocol node client coordinator threshold committee_members = (* 1. Create two new dac nodes; [observer_1] and [observer_2]. @@ -1063,76 +1149,6 @@ module Legacy = struct in unit - (** [check_downloaded_page coordinator observer page_hash] checks that the - [observer] has downloaded a page with [page_hash] from the [coordinator], - that the contents of the page corresponds to the ones of the - [coordinator]. It returns the list of the hashes contained in the - [page_hash], if the page corresponds to a hash page. Otherwise, it returns - the empty list. *) - let check_downloaded_page coordinator observer page_hash = - let* coordinator_hex_encoded_page = - RPC.call coordinator (Dac_rpc.get_preimage page_hash) - in - let coordinator_page = Hex.to_string (`Hex coordinator_hex_encoded_page) in - (* Check that the page has been saved by the observer. *) - let* observer_hex_encoded_page = - RPC.call observer (Dac_rpc.get_preimage page_hash) - in - let observer_page = Hex.to_string (`Hex observer_hex_encoded_page) in - (* Check that the raw page for the root hash stored in the coordinator - is the same as the raw page stored in the observer. *) - Check.( - (coordinator_page = observer_page) - string - ~error_msg: - "Returned page does not match the expected one (Current: %L <> \ - Expected: %R)") ; - let version_tag = observer_page.[0] in - if version_tag = '\000' then return [] - else - let hash_size = 33 in - let preamble_size = 5 in - let concatenated_hashes = - String.sub observer_page 5 (String.length observer_page - preamble_size) - in - let rec split_hashes concatenated_hashes hashes = - if String.equal concatenated_hashes "" then hashes - else - let next_hash = - Hex.show @@ Hex.of_string - @@ String.sub concatenated_hashes 0 hash_size - in - let next_concatenated_hashes = - String.sub - concatenated_hashes - hash_size - (String.length concatenated_hashes - hash_size) - in - split_hashes next_concatenated_hashes (next_hash :: hashes) - in - return @@ split_hashes concatenated_hashes [] - - let check_downloaded_preimage coordinator observer root_hash = - let rec go hashes = - match hashes with - | [] -> return () - | hash :: hashes -> - let* next_hashes = check_downloaded_page coordinator observer hash in - go (hashes @ next_hashes) - in - go [root_hash] - - let sample_payload example_filename = - let json = - JSON.parse_file @@ "tezt/tests/dac_example_payloads/" ^ example_filename - ^ ".json" - in - let payload = - JSON.(json |-> "payload" |> as_string |> fun s -> Hex.to_string (`Hex s)) - in - let root_hash = JSON.(json |-> "root_hash" |> as_string) in - (payload, root_hash) - let test_observer_downloads_pages _protocol node client coordinator threshold committee_members = (* 1. Create one new dac nodes; [observer_1], @@ -1196,14 +1212,58 @@ module Legacy = struct let* () = fetch_root_hash_promise in check_downloaded_preimage coordinator observer expected_rh - (* TODO: https://gitlab.com/tezos/tezos/-/issues/4934 - Once profiles are implemented this should be moved out of the - `Legacy` module. Additionally, the test should be run using dac - node running in the coordinator and not legacy mode. *) -end + (* 1. Observer should fetch missing page from Coordinator when GET /missing_page/{hash} + is called. + 2. As a side effect, Observer should save fetched page into its page store before + returning it in the response. This can be observer by checking the result of + retrieving preimage before and after the GET /missing_page/{hash} call.*) + let test_observer_get_missing_page _protocol node client coordinator threshold + dac_members = + let root_hash = + "00649d431e829f4adc68edecb8d8d8071154b57086cc124b465f6f6600a4bc91c7" + in + let root_hash_stream_promise = + wait_for_root_hash_pushed_to_data_streamer coordinator root_hash + in + let* hex_root_hash = + init_hex_root_hash ~payload:"test payload abc 123" coordinator + in + assert (root_hash = Hex.show hex_root_hash) ; + let* () = root_hash_stream_promise in + let observer = + Dac_node.create_legacy + ~threshold + ~committee_members: + (List.map + (fun (dc : Account.aggregate_key) -> dc.aggregate_public_key_hash) + dac_members) + ~node + ~client + () + in + let* _ = Dac_node.init_config observer in + let () = set_coordinator observer coordinator in + let* () = Dac_node.run observer in + let* () = + assert_lwt_failure + ~__LOC__ + "Expected retrieve_preimage" + (RPC.call observer (Dac_rpc.get_preimage (Hex.show hex_root_hash))) + in + let* missing_page = + RPC.call observer (Dac_rpc.get_missing_page ~hex_root_hash) + in + let* coordinator_page = + RPC.call coordinator (Dac_rpc.get_preimage (Hex.show hex_root_hash)) + in + check_preimage coordinator_page missing_page ; + let* observer_preimage = + RPC.call observer (Dac_rpc.get_preimage (Hex.show hex_root_hash)) + in + check_preimage coordinator_page observer_preimage ; + unit -module Signature_manager = struct - module Coordinator = struct + module Signature_manager = struct let test_non_committee_signer_should_fail tz_client (coordinator_node, hex_root_hash, _dac_committee) = let* invalid_signer_key = @@ -1348,6 +1408,35 @@ module Signature_manager = struct in let* () = invalid_signature dac_env in unit + + (* Tests that it's possible to retrieve the witness and certificate after + storing a dac member signature. Also asserts that the certificate contains + the member used for signing. *) + let test_get_certificate _protocol _tezos_node _tz_client coordinator + _threshold dac_committee = + let i = Random.int (List.length dac_committee) in + let member = List.nth dac_committee i in + let* hex_root_hash = + init_hex_root_hash + ~payload:"test get certificate payload 123" + coordinator + in + let signature = bls_sign_hex_hash member hex_root_hash in + let* () = + RPC.call + coordinator + (Dac_rpc.put_dac_member_signature + ~hex_root_hash + ~dac_member_pkh:member.aggregate_public_key_hash + ~signature) + in + let* witnesses, certificate, _root_hash = + RPC.call coordinator (Dac_rpc.get_certificate ~hex_root_hash) + in + let expected_witnesses = Z.shift_left Z.one i in + assert_witnesses ~__LOC__ (Z.to_int expected_witnesses) witnesses ; + assert_verify_aggregate_signature [member] hex_root_hash certificate ; + unit end end @@ -1372,100 +1461,22 @@ module Full_infrastructure = struct Lwt.return_unit end -(* Tests that it's possible to retrieve the witness and certificate after - storing a dac member signature. Also asserts that the certificate contains - the member used for signing. *) -let test_get_certificate _protocol _tezos_node _tz_client coordinator _threshold - dac_committee = - let i = Random.int (List.length dac_committee) in - let member = List.nth dac_committee i in - let* hex_root_hash = - init_hex_root_hash ~payload:"test get certificate payload 123" coordinator - in - let signature = bls_sign_hex_hash member hex_root_hash in - let* () = - RPC.call - coordinator - (Dac_rpc.put_dac_member_signature - ~hex_root_hash - ~dac_member_pkh:member.aggregate_public_key_hash - ~signature) - in - let* witnesses, certificate, _root_hash = - RPC.call coordinator (Dac_rpc.get_certificate ~hex_root_hash) - in - let expected_witnesses = Z.shift_left Z.one i in - assert_witnesses ~__LOC__ (Z.to_int expected_witnesses) witnesses ; - assert_verify_aggregate_signature [member] hex_root_hash certificate ; - unit - -(* 1. Observer should fetch missing page from Coordinator when GET /missing_page/{hash} - is called. - 2. As a side effect, Observer should save fetched page into its page store before - returning it in the response. This can be observer by checking the result of - retrieving preimage before and after the GET /missing_page/{hash} call.*) -let test_observer_get_missing_page _protocol node client coordinator threshold - dac_members = - let root_hash = - "00649d431e829f4adc68edecb8d8d8071154b57086cc124b465f6f6600a4bc91c7" - in - let root_hash_stream_promise = - wait_for_root_hash_pushed_to_data_streamer coordinator root_hash - in - let* hex_root_hash = - init_hex_root_hash ~payload:"test payload abc 123" coordinator - in - assert (root_hash = Hex.show hex_root_hash) ; - let* () = root_hash_stream_promise in - let observer = - Dac_node.create_legacy - ~threshold - ~committee_members: - (List.map - (fun (dc : Account.aggregate_key) -> dc.aggregate_public_key_hash) - dac_members) - ~node - ~client - () - in - let* _ = Dac_node.init_config observer in - let () = Legacy.set_coordinator observer coordinator in - let* () = Dac_node.run observer in - let* () = - assert_lwt_failure - ~__LOC__ - "Expected retrieve_preimage" - (RPC.call observer (Dac_rpc.get_preimage (Hex.show hex_root_hash))) - in - let* missing_page = - RPC.call observer (Dac_rpc.get_missing_page ~hex_root_hash) - in - let* coordinator_page = - RPC.call coordinator (Dac_rpc.get_preimage (Hex.show hex_root_hash)) - in - check_preimage coordinator_page missing_page ; - let* observer_preimage = - RPC.call observer (Dac_rpc.get_preimage (Hex.show hex_root_hash)) - in - check_preimage coordinator_page observer_preimage ; - unit - let register ~protocols = (* Tests with layer1 and dac nodes *) - test_dac_node_startup protocols ; - test_dac_node_imports_committee_members protocols ; - test_dac_node_dac_threshold_not_reached protocols ; + Legacy.test_dac_node_startup protocols ; + Legacy.test_dac_node_imports_committee_members protocols ; + Legacy.test_dac_node_dac_threshold_not_reached protocols ; scenario_with_layer1_legacy_and_rollup_nodes ~tags:["dac"; "dac_node"] "dac_reveals_data_merkle_tree_v0" - test_dac_node_handles_dac_store_preimage_merkle_V0 + Legacy.test_dac_node_handles_dac_store_preimage_merkle_V0 protocols ~threshold:1 ~committee_members:1 ; scenario_with_layer1_legacy_and_rollup_nodes ~tags:["dac"; "dac_node"] "dac_reveals_data_hash_chain_v0" - test_dac_node_handles_dac_store_preimage_hash_chain_V0 + Legacy.test_dac_node_handles_dac_store_preimage_hash_chain_V0 protocols ~threshold:1 ~committee_members:1 ; @@ -1474,19 +1485,19 @@ let register ~protocols = ~threshold:0 ~committee_members:0 "dac_retrieve_preimage" - test_dac_node_handles_dac_retrieve_preimage_merkle_V0 + Legacy.test_dac_node_handles_dac_retrieve_preimage_merkle_V0 protocols ; scenario_with_layer1_legacy_and_rollup_nodes ~tags:["dac"; "dac_node"] "dac_rollup_arith_uses_reveals" - test_rollup_arith_uses_reveals + Legacy.test_rollup_arith_uses_reveals protocols ~threshold:1 ~committee_members:1 ; scenario_with_layer1_legacy_and_rollup_nodes ~tags:["dac"; "dac_node"] "dac_rollup_arith_wrong_hash" - test_reveals_fails_on_wrong_hash + Legacy.test_reveals_fails_on_wrong_hash ~threshold:1 ~committee_members:1 protocols ; @@ -1516,7 +1527,14 @@ let register ~protocols = ~committee_members:2 ~tags:["dac"; "dac_node"] "dac_get_certificate" - test_get_certificate + Legacy.Signature_manager.test_get_certificate + protocols ; + scenario_with_layer1_and_legacy_dac_nodes + ~threshold:0 + ~committee_members:3 + ~tags:["dac"; "dac_node"] + "dac_store_member_signature" + Legacy.Signature_manager.test_handle_store_signature protocols ; scenario_with_full_dac_infrastructure ~observers:0 @@ -1525,17 +1543,10 @@ let register ~protocols = "dac_coordinator_post_preimage_endpoint" Full_infrastructure.test_coordinator_post_preimage_endpoint protocols ; - scenario_with_layer1_and_legacy_dac_nodes - ~threshold:0 - ~committee_members:3 - ~tags:["dac"; "dac_node"] - "dac_store_member_signature" - Signature_manager.Coordinator.test_handle_store_signature - protocols ; scenario_with_layer1_and_legacy_dac_nodes ~threshold:0 ~committee_members:1 ~tags:["dac"; "dac_node"] "dac_observer_get_missing_page" - test_observer_get_missing_page + Legacy.test_observer_get_missing_page protocols -- GitLab From f59235942ce1ecfe414c7ad95bad9ca1aa8eefd5 Mon Sep 17 00:00:00 2001 From: Andrea Cerone Date: Wed, 15 Mar 2023 15:58:03 +0000 Subject: [PATCH 11/11] Dac/Tezt: committee members and observers download all pages --- tezt/tests/dac.ml | 93 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/tezt/tests/dac.ml b/tezt/tests/dac.ml index bb5ffaeeecd5..9868a1022f93 100644 --- a/tezt/tests/dac.ml +++ b/tezt/tests/dac.ml @@ -1441,6 +1441,12 @@ module Legacy = struct end module Full_infrastructure = struct + let coordinator_serializes_payload coordinator ~payload ~expected_rh = + let* actual_rh = + RPC.call coordinator (Dac_rpc.Coordinator.post_preimage ~payload) + in + return @@ check_valid_root_hash expected_rh actual_rh + let test_coordinator_post_preimage_endpoint Scenarios.{coordinator_node; _} = (* 1. Send the [payload] to coordinator. 2. Assert that it returns [expected_rh]. @@ -1459,6 +1465,86 @@ module Full_infrastructure = struct let () = check_valid_root_hash expected_rh actual_rh in let* () = root_hash_pushed_to_data_streamer_promise in Lwt.return_unit + + let test_download_and_retrieval_of_pages + Scenarios.{coordinator_node; committee_members_nodes; observer_nodes; _} = + (* 0. Coordinator node is already running when the this function is + executed by the test + 1. Run committee members and observers + 2. Post a preimage to coordinator + 3. Wait until all observer and committee members download the payload + 4. Check that all pages can be retrieved by committee members + and observers using the GET preimage endpoint. *) + let payload, expected_rh = sample_payload "preimage" in + let push_promise = + wait_for_root_hash_pushed_to_data_streamer coordinator_node expected_rh + in + let wait_for_node_subscribed_to_data_streamer () = + wait_for_handle_new_subscription_to_hash_streamer coordinator_node + in + let wait_for_root_hash_processed_promises nodes = + List.map + (fun dac_node -> + wait_for_received_root_hash_processed dac_node expected_rh) + nodes + in + (* Initialize configuration of all nodes. *) + let* _ = + Lwt_list.iter_s + (fun committee_member_node -> + let* _ = Dac_node.init_config committee_member_node in + return ()) + committee_members_nodes + in + let* _ = + Lwt_list.iter_s + (fun observer_node -> + let* _ = Dac_node.init_config observer_node in + return ()) + observer_nodes + in + (* 1. Run committee member and observer nodes. + Because the event resolution loop in the Daemon always resolves + all promises matching an event filter, when a new event is received, + we cannot wait for multiple subscription to the hash streamer, as + events of this kind are indistinguishable one from the other. + Instead, we wait for the subscription of one observer/committe_member + node to be notified before running the next node. *) + let* () = + Lwt_list.iter_s + (fun node -> + let node_is_subscribed = + wait_for_node_subscribed_to_data_streamer () + in + let* () = Dac_node.run ~wait_ready:true node in + node_is_subscribed) + (committee_members_nodes @ observer_nodes) + in + let all_nodes_have_processed_root_hash = + Lwt.join + @@ wait_for_root_hash_processed_promises + (committee_members_nodes @ observer_nodes) + in + (* 2. Post a preimage to the coordinator. *) + let* () = + coordinator_serializes_payload coordinator_node ~payload ~expected_rh + in + (* Assert [coordinator] emitted event that [expected_rh] was pushed + to the data_streamer. *) + let* () = push_promise in + (* 3. Wait until all observer and committee member nodes downloaded the + payload. *) + let* () = all_nodes_have_processed_root_hash in + (* 4. Check that all pages can be retrieved by committee members + and observers using the GET preimage endpoint. + + Note that using check_downloaded_preimage will request pages from the + coordinator node for each observer and committee_member node. + This might be inefficient *) + Lwt_list.iter_s + (fun dac_node -> + check_downloaded_preimage coordinator_node dac_node expected_rh) + (committee_members_nodes @ observer_nodes) end let register ~protocols = @@ -1549,4 +1635,11 @@ let register ~protocols = ~tags:["dac"; "dac_node"] "dac_observer_get_missing_page" Legacy.test_observer_get_missing_page + protocols ; + scenario_with_full_dac_infrastructure + ~observers:1 + ~committee_members:1 + ~tags:["dac"; "dac_node"] + "committee members and observers download pages from coordinator" + Full_infrastructure.test_download_and_retrieval_of_pages protocols -- GitLab