From 6eb1bc80fe989a460ec7854adcb857112f5ea07d Mon Sep 17 00:00:00 2001 From: Ryan Tan Date: Mon, 3 Jul 2023 12:45:23 +0100 Subject: [PATCH 1/2] Dac: introduce parsed dac --- manifest/main.ml | 2 +- src/lib_dac/dac_clic_helpers.ml | 67 ++++++++++ src/lib_dac/dac_clic_helpers.mli | 40 ++++++ src/lib_dac/test/dune | 2 +- src/lib_dac/test/test_dac_clic_helpers.ml | 150 ++++++++++++++++++++++ 5 files changed, 259 insertions(+), 2 deletions(-) create mode 100644 src/lib_dac/dac_clic_helpers.ml create mode 100644 src/lib_dac/dac_clic_helpers.mli create mode 100644 src/lib_dac/test/test_dac_clic_helpers.ml diff --git a/manifest/main.ml b/manifest/main.ml index 911045aac376..8a3b2a7b6b80 100644 --- a/manifest/main.ml +++ b/manifest/main.ml @@ -4026,7 +4026,7 @@ let _octez_dac_node_lib_tests = let _octez_dac_lib_tests = tezt - ["test_certificate"; "test_dac_plugin"] + ["test_certificate"; "test_dac_plugin"; "test_dac_clic_helpers"] ~path:"src/lib_dac/test" ~opam:"tezos-dac-lib-test" ~synopsis:"Test for dac lib" diff --git a/src/lib_dac/dac_clic_helpers.ml b/src/lib_dac/dac_clic_helpers.ml new file mode 100644 index 000000000000..a412e4f97aa9 --- /dev/null +++ b/src/lib_dac/dac_clic_helpers.ml @@ -0,0 +1,67 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 TriliTech, *) +(* *) +(* 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 Parsed_rpc = struct + type t = {scheme : string; host : string; port : int} + + let default_https_port = 443 + + let default_http_port = 80 + + let http = "http" + + let https = "https" + + let is_http s = + [http; https] + |> List.map (fun scheme -> scheme ^ "://") + |> List.exists (fun prefix -> String.starts_with ~prefix s) + + let get_host uri = + match Uri.host uri with + | Some host -> Ok host + | None -> Error (`Parse_rpc_error "No host provided.") + + let of_string x = + let open Result_syntax in + if is_http x then + let parsed = Uri.of_string x in + let scheme = Stdlib.Option.get (Uri.scheme parsed) in + let+ host = get_host parsed in + let port = + Option.value_f + ~default:(fun () -> + if scheme = https then default_https_port else default_http_port) + (Uri.port parsed) + in + {scheme; host; port} + else + match Ipaddr.with_port_of_string ~default:default_http_port x with + | Ok (ipaddr, port) -> + let scheme = http in + let host = Ipaddr.to_string ipaddr in + Ok {scheme; host; port} + | Error (`Msg _) -> Error (`Parse_rpc_error "Not a valid rpc address.") +end diff --git a/src/lib_dac/dac_clic_helpers.mli b/src/lib_dac/dac_clic_helpers.mli new file mode 100644 index 000000000000..5c5e48b8d869 --- /dev/null +++ b/src/lib_dac/dac_clic_helpers.mli @@ -0,0 +1,40 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 TriliTech, *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +(* Represents a well formed RPC. *) +module Parsed_rpc : sig + type t = private {scheme : string; host : string; port : int} + + (** [of_string raw_rpc] tries to parse [raw_rpc] as a [Parsed_rpc.t]. + The default scheme for ip addresses is `http` which can be overidden + by prefixing the desired scheme. + Examples of supported address strings are: + * 127.0.0.1:10832 + * http://example.com + * http://example.com:10832 + * https://example.com + * https://example.come:10832 *) + val of_string : string -> (t, [> `Parse_rpc_error of string]) result +end diff --git a/src/lib_dac/test/dune b/src/lib_dac/test/dune index e1248db8c06b..2e0b3dc68114 100644 --- a/src/lib_dac/test/dune +++ b/src/lib_dac/test/dune @@ -26,7 +26,7 @@ -open Tezos_base_test_helpers -open Tezos_dac_lib -open Octez_alcotezt) - (modules test_certificate test_dac_plugin)) + (modules test_certificate test_dac_plugin test_dac_clic_helpers)) (executable (name main) diff --git a/src/lib_dac/test/test_dac_clic_helpers.ml b/src/lib_dac/test/test_dac_clic_helpers.ml new file mode 100644 index 000000000000..83bab6b97110 --- /dev/null +++ b/src/lib_dac/test/test_dac_clic_helpers.ml @@ -0,0 +1,150 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2023 TriliTech, *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +(** Testing + ------- + Component: Lib_dac Dac_clic_helpers + Invocation: dune exec src/lib_dac/test/main.exe -- --file test_dac_clic_helpers.ml + Subject: Tests for the Dac_clic_helpers. +*) + +open Dac_clic_helpers + +module Parsed_rpc_test = struct + type public_parsed_rpc = {scheme : string; host : string; port : int} + + let public_parsed_rpc Parsed_rpc.{scheme; host; port} = {scheme; host; port} + + let get_ok = Stdlib.Result.get_ok + + let public_ok_parsed_rpc rpc = + get_ok @@ Parsed_rpc.of_string rpc |> public_parsed_rpc + + let test_ipaddr_host_and_port () = + let rpc = "127.0.0.1:12345" in + Assert.equal + ~loc:__LOC__ + (public_ok_parsed_rpc rpc) + {scheme = "http"; host = "127.0.0.1"; port = 12345} + + let test_ipaddr_port_defaults_to_80 () = + let rpc = "127.0.0.1" in + Assert.equal + ~loc:__LOC__ + (public_ok_parsed_rpc rpc) + {scheme = "http"; host = "127.0.0.1"; port = 80} + + let test_invalid_ipaddr_fails () = + let rpc = Parsed_rpc.of_string "127.123:12345" in + Assert.equal + ~loc:__LOC__ + rpc + (Error (`Parse_rpc_error "Not a valid rpc address.")) + + let test_empty_addr_fails () = + let rpc = Parsed_rpc.of_string "" in + Assert.equal + ~loc:__LOC__ + rpc + (Error (`Parse_rpc_error "Not a valid rpc address.")) + + let test_endpoint_host_http_and_port () = + let rpc = "http://tezos.com:80" in + Assert.equal + ~loc:__LOC__ + (public_ok_parsed_rpc rpc) + {scheme = "http"; host = "tezos.com"; port = 80} + + let test_endpoint_host_https_and_port () = + let rpc = "https://tezos.com:10832" in + Assert.equal + ~loc:__LOC__ + (public_ok_parsed_rpc rpc) + {scheme = "https"; host = "tezos.com"; port = 10832} + + let test_endpoint_http_defaults_port_80 () = + let rpc = "http://tezos.com" in + Assert.equal + ~loc:__LOC__ + (public_ok_parsed_rpc rpc) + {scheme = "http"; host = "tezos.com"; port = 80} + + let test_endpoint_https_defaults_port_443 () = + let rpc = "https://tezos.com" in + Assert.equal + ~loc:__LOC__ + (public_ok_parsed_rpc rpc) + {scheme = "https"; host = "tezos.com"; port = 443} + + let test_ipaddr_with_http_treated_is_ok () = + let rpc = "http://127.0.0.1:10832" in + Assert.equal + ~loc:__LOC__ + (public_ok_parsed_rpc rpc) + {scheme = "http"; host = "127.0.0.1"; port = 10832} + + let tests = + [ + Alcotest.test_case + "Test `{ipaddr}:{port}` is valid" + `Quick + test_ipaddr_host_and_port; + Alcotest.test_case + "Test `{ipaddr}` defaults port to 80" + `Quick + test_ipaddr_port_defaults_to_80; + Alcotest.test_case + "Test invalid ipaddr fails." + `Quick + test_invalid_ipaddr_fails; + Alcotest.test_case "Test empty rpc fails" `Quick test_empty_addr_fails; + Alcotest.test_case + "Test `http://{endpoint}:{port}` is valid" + `Quick + test_endpoint_host_http_and_port; + Alcotest.test_case + "Test `https://{endpoint}:{port}` is valid" + `Quick + test_endpoint_host_https_and_port; + Alcotest.test_case + "Test `http://{endpoint}` defaults port to 80" + `Quick + test_endpoint_http_defaults_port_80; + Alcotest.test_case + "Test `https://{endpoint}` defaults port to 443" + `Quick + test_endpoint_https_defaults_port_443; + Alcotest.test_case + "Test `http://{ipaddr}:{port}` is valid" + `Quick + test_ipaddr_with_http_treated_is_ok; + ] +end + +let () = + Alcotest.run + ~__FILE__ + "lib_dac" + [("Dac_clic_helpers.Parsed_rpc", Parsed_rpc_test.tests)] -- GitLab From 32829399317e5f913580b482bce791aa97bcd649 Mon Sep 17 00:00:00 2001 From: Ryan Tan Date: Thu, 6 Jul 2023 12:35:31 +0100 Subject: [PATCH 2/2] Dac: refactor dac client cli coordinator rpc config --- src/bin_dac_client/main_dac_client.ml | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/bin_dac_client/main_dac_client.ml b/src/bin_dac_client/main_dac_client.ml index e873d38cdbe8..19f1185aac52 100644 --- a/src/bin_dac_client/main_dac_client.ml +++ b/src/bin_dac_client/main_dac_client.ml @@ -76,18 +76,15 @@ let wait_for_threshold_arg = positive_int_parameter let coordinator_rpc_parameter = - Tezos_clic.parameter (fun _cctxt h -> - match String.split ':' h with - | [host; port] -> - Lwt.catch - (fun () -> - Lwt_result.return - @@ Dac_node_client.make_unix_cctxt - ~scheme:"http" - ~host - ~port:(int_of_string port)) - (fun _ -> failwith "Address not in format :") - | _ -> failwith "Address not in format :") + let open Dac_clic_helpers in + Tezos_clic.parameter (fun _cctxt rpc -> + let open Lwt_result_syntax in + let parsed_rpc_result = Parsed_rpc.of_string rpc in + match parsed_rpc_result with + | Ok {scheme; host; port} -> + return (Dac_node_client.make_unix_cctxt ~scheme ~host ~port) + | Error (`Parse_rpc_error msg) -> + failwith "Parse Coordinator rpc address failed: %s" msg) let hex_parameter = Tezos_clic.parameter (fun _cctxt h -> @@ -99,7 +96,9 @@ let hex_parameter = let coordinator_rpc_param ?(name = "DAC coordinator rpc address parameter") ?(desc = "The address of the DAC coordinator") = let desc = - String.concat "\n" [desc; "An address of the form :"] + String.concat + "\n" + [desc; "A http/https url or an address in the form of :."] in Tezos_clic.param ~name ~desc coordinator_rpc_parameter -- GitLab