diff --git a/CHANGES.rst b/CHANGES.rst index 407085cfa1cdc069be297e2d086fd536d7306544..8d338769e4616cd8a90cf80fb9ac1c4264cadbdd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -124,8 +124,8 @@ Baker - By default, the Baker only accepts to communicate with nodes of the same or more recent version. To allow the Baker to communicate with nodes of older - version or dev version, use the --node-version-check-bypass option. (MR - :gl:`!14044`) + version or dev version, use the --node-version-check-bypass or + --node-version-allowed option. (MRs :gl:`!14044`, :gl:`!14189`) Accuser ------- diff --git a/src/lib_version/octez_node_version.ml b/src/lib_version/octez_node_version.ml index 7cd3027fdab7d6e545ec1334f0da910775ff2b57..ff411acec5ffe0b4510c67ffde7800e858b4f072 100644 --- a/src/lib_version/octez_node_version.ml +++ b/src/lib_version/octez_node_version.ml @@ -116,9 +116,14 @@ let encoding = (req "network_version" Network_version.encoding) (req "commit_info" (option commit_info_encoding))) +let commit_info_equivalent c1 c2 = + let c1 = String.lowercase_ascii c1.commit_hash in + let c2 = String.lowercase_ascii c2.commit_hash in + String.starts_with ~prefix:c1 c2 || String.starts_with ~prefix:c2 c1 + let partially_compare (v1 : Version.t) (c1 : commit_info option) (v2 : Version.t) (c2 : commit_info option) = - let is_dev v = + let is_dev (v : Tezos_version_parser.t) = let open Tezos_version_parser in match v.additional_info with | Dev | Beta_dev _ | RC_dev _ -> true @@ -127,7 +132,7 @@ let partially_compare (v1 : Version.t) (c1 : commit_info option) let is_commit_equal = match (c1, c2) with | None, _ | _, None -> None - | Some x, Some y -> Some (x = y) + | Some x, Some y -> Some (commit_info_equivalent x y) in let version_comparison = if v1 = v2 then Some 0 diff --git a/src/lib_version/octez_node_version.mli b/src/lib_version/octez_node_version.mli index 2b3bca0af3ef9c18c8d4fa706daebdbddae20a31..08156b69546c688704dea758e4efcc185a631b2f 100644 --- a/src/lib_version/octez_node_version.mli +++ b/src/lib_version/octez_node_version.mli @@ -50,6 +50,8 @@ val commit_info_pp : Format.formatter -> commit_info -> unit val commit_info_pp_short : Format.formatter -> commit_info -> unit +val commit_info_equivalent : commit_info -> commit_info -> bool + val encoding : t Data_encoding.t (** [partially_compare v1 c1 v2 c2] is similar to compare like function but @@ -66,7 +68,8 @@ val encoding : t Data_encoding.t If [v1] and [v2] are equal, but [c1] and [c2] are not then they are not comparable. - [c1] and [c2] are used only for equality. + [c1] and [c2] are used only for equality. If the hash of [c1] or [c2] is a + prefix or equal to the other one, they are considered as equal. To determine which version is more recent or old: - [major] has the priority on [minor] diff --git a/src/lib_version/parser/tezos_version_parser.mll b/src/lib_version/parser/tezos_version_parser.mll index 6863ad0977ef979d69edcf3d786313d3d9b6e9ac..f4704829525bb9a212a0738536948022b44dc79e 100644 --- a/src/lib_version/parser/tezos_version_parser.mll +++ b/src/lib_version/parser/tezos_version_parser.mll @@ -42,9 +42,11 @@ let int s = int_of_string_opt s |> Option.value ~default: 0 let default = { product = Octez; major = 0 ; minor = 0 ; additional_info = Dev } + } let num = ['0'-'9']+ +let hexa = ['0'-'9' 'A'-'F' 'a'-'f']+ rule version_tag = parse | ("octez" | "etherlink" as product) "-" 'v'? (num as major) '.' (num as minor) ".0"? @@ -77,3 +79,61 @@ and extra = parse { Release } | _ { Dev } + +and version_commit = parse + | ("octez" | "etherlink" as product) "-" 'v'? (num as major) '.' (num as minor) ".0"? + { + let product = match product with + | "etherlink" -> Etherlink + | "octez" -> Octez + | _ -> (* this case cannot happen, see pattern above *) + assert false + in + let extra = extra_noeof lexbuf in + match extra with + | None -> None + | Some additional_info -> + (let commit = commit lexbuf in + match commit with + | Some commit -> + Some ( + { + product; + major = int major; + minor = int minor; + additional_info; + }, + commit) + | _ -> None) + } + | _ | eof + { None } + +(* This rule is similar to rule extra, but can be followed by a commit hash *) +and extra_noeof = parse + | "-rc" (num as rc) (eof | ':') + { Some (RC (int rc)) } + | "-rc" (num as rc) "+dev" (eof | ':') + { Some (RC_dev (int rc)) } + | "-beta" (num as beta) (eof | ':') + { Some (Beta (int beta)) } + | "-beta" (num as beta) "+dev" (eof | ':') + { Some (Beta_dev (int beta)) } + | "+dev" (eof | ':') + { Some Dev } + | (eof | ':') + { Some Release } + | _ + { None } + +and commit = parse + | (hexa as hash) eof + { let l = String.length hash in + if l >= 8 && l <= 40 + then Some (Some (String.lowercase_ascii hash)) + else None + } + | eof + { Some None } + | _ + { None } diff --git a/src/lib_version/test/test_octez_node_version.ml b/src/lib_version/test/test_octez_node_version.ml index 1c2ab87a9ecec5624479d532658b0a0fed1891c2..8bf18b71366f5cf03a6e91331041c3c66331eaf4 100644 --- a/src/lib_version/test/test_octez_node_version.ml +++ b/src/lib_version/test/test_octez_node_version.ml @@ -69,6 +69,12 @@ let commit_info_opt_pp = ~none:(fun ppf () -> Format.pp_print_string ppf "none") Octez_node_version.commit_info_pp +let commit_info_opt_different c1 c2 = + match (c1, c2) with + | Some c1, Some c2 -> not (Octez_node_version.commit_info_equivalent c1 c2) + | None, None -> true + | _ -> false + let print_opt ?v1 ?c1 ?v2 ?c2 () = let pp_opt msg pp fmt = function | None -> Format.fprintf fmt "@," @@ -127,7 +133,7 @@ let () = ~print generator @@ fun (v, c1, c2) -> - QCheck2.assume (c1 <> c2) ; + QCheck2.assume (commit_info_opt_different c1 c2) ; match Octez_node_version.partially_compare v c1 v c2 with | None -> true | _ -> QCheck2.Test.fail_reportf "These versions should be uncomparable" @@ -148,7 +154,7 @@ let () = ~print generator @@ fun (v, c1, c2) -> - QCheck2.assume (c1 <> c2) ; + QCheck2.assume (commit_info_opt_different c1 c2) ; match Octez_node_version.partially_compare v c1 v c2 with | None -> true | _ -> QCheck2.Test.fail_reportf "These versions should be uncomparable" @@ -201,7 +207,7 @@ let () = generator @@ fun (v1, c1, v2, c2) -> QCheck2.assume (v1 <> v2) ; - QCheck2.assume (c1 <> c2) ; + QCheck2.assume (commit_info_opt_different c1 c2) ; match Octez_node_version.partially_compare v1 c1 v2 c2 with | None -> true | _ -> QCheck2.Test.fail_reportf "These versions should be uncomparable" diff --git a/src/lib_version/version.ml b/src/lib_version/version.ml index 420c8a4be962f36a0b138ef1f1913f9e78d896ad..607c6e7fc8728f4e7ec20227b8697016af31ae28 100644 --- a/src/lib_version/version.ml +++ b/src/lib_version/version.ml @@ -69,6 +69,15 @@ let pp_simple f {product = _; major; minor; additional_info} = minor (string_of_additional_info additional_info) +let pp_arg f {product; major; minor; additional_info} = + Format.fprintf + f + "%s-%i.%i%s" + (String.lowercase_ascii (string_of_product product)) + major + minor + (string_of_additional_info additional_info) + let to_string x = Format.asprintf "%a" pp x let to_json {product; major; minor; additional_info} commit_hash = diff --git a/src/lib_version/version.mli b/src/lib_version/version.mli index fadfe89690d0976bebbff9e360fbbd255280c6fd..23912a92476814878f4b5234a5090714bcbca0dc 100644 --- a/src/lib_version/version.mli +++ b/src/lib_version/version.mli @@ -111,6 +111,12 @@ val pp : Format.formatter -> t -> unit Same as [pp] but does not print the product name. *) val pp_simple : Format.formatter -> t -> unit +(** A Version printer compatible with command line argments. + + Same as [pp] but print the product name in lowercase and followed by a dash + instead of a space. *) +val pp_arg : Format.formatter -> t -> unit + (** Parse an Octez version. Returns None if the version cannot be parsed. *) diff --git a/src/proto_020_PsParisC/lib_delegate/baking_commands.ml b/src/proto_020_PsParisC/lib_delegate/baking_commands.ml index 89dc129c8e83be3f444e39d03facb3584b85ead6..d0ec936ce26715043039d45c1337c87c98a49739 100644 --- a/src/proto_020_PsParisC/lib_delegate/baking_commands.ml +++ b/src/proto_020_PsParisC/lib_delegate/baking_commands.ml @@ -46,9 +46,42 @@ let may_lock_pidfile pidfile_opt f = ~filename:pidfile f -let check_node_version cctxt check_node_version_bypass = +let check_node_version cctxt bypass allowed = let open Lwt_result_syntax in - if check_node_version_bypass then + (* Parse and check allowed versions *) + let*? allowed = + let open Result_syntax in + Option.map_e + (fun allowed -> + match + Tezos_version_parser.version_commit (Lexing.from_string allowed) + with + | None -> tzfail (Node_version_malformatted allowed) + | Some x -> return x) + allowed + in + let is_allowed node_version + (node_commit_info : Tezos_version.Octez_node_version.commit_info option) = + match allowed with + | None -> false + | Some (v, c) -> ( + let c = + Option.map + (fun commit_hash -> + Tezos_version.Octez_node_version.{commit_hash; commit_date = ""}) + c + in + match + Tezos_version.Octez_node_version.partially_compare + v + c + node_version + node_commit_info + with + | None -> false + | Some x -> x = 0) + in + if bypass then let*! () = Events.(emit node_version_check_bypass ()) in return_unit else @@ -71,23 +104,25 @@ let check_node_version cctxt check_node_version_bypass = baker_version, baker_commit_info )) in - match - Tezos_version.Octez_node_version.partially_compare - baker_version - baker_commit_info - node_version.version - node_version.commit_info - with - | Some r when r <= 0 -> return_unit - | _ -> - tzfail - (Node_version_incompatible - { - node_version = node_version.version; - node_commit_info = node_version.commit_info; - baker_version; - baker_commit_info; - }) + if is_allowed node_version.version node_version.commit_info then return_unit + else + match + Tezos_version.Octez_node_version.partially_compare + baker_version + baker_commit_info + node_version.version + node_version.commit_info + with + | Some r when r <= 0 -> return_unit + | _ -> + tzfail + (Node_version_incompatible + { + node_version = node_version.version; + node_commit_info = node_version.commit_info; + baker_version; + baker_commit_info; + }) let http_headers_env_variable = "TEZOS_CLIENT_REMOTE_OPERATIONS_POOL_HTTP_HEADERS" @@ -250,6 +285,19 @@ let node_version_check_bypass_arg = compatibility with the version of the node to which it is connected." () +let node_version_allowed_arg = + Tezos_clic.arg + ~long:"node-version-allowed" + ~placeholder:"-[v].[.0][:]" + ~doc: + "When specified the baker will accept to run with a node of this \ + version. The specified version is composed of the product, for example \ + 'octez'; the major and the minor versions that are positive integers; \ + the info, for example '-rc', '-beta1+dev' or realese if none is \ + provided; optionally the commit that is the hash of the last git commit \ + or a prefix of at least 8 characters long." + string_parameter + let get_delegates (cctxt : Protocol_client_context.full) (pkhs : Signature.public_key_hash list) = let open Lwt_result_syntax in @@ -635,9 +683,10 @@ let lookup_default_vote_file_path (cctxt : Protocol_client_context.full) = type baking_mode = Local of {local_data_dir_path : string} | Remote let baker_args = - Tezos_clic.args14 + Tezos_clic.args15 pidfile_arg node_version_check_bypass_arg + node_version_allowed_arg minimal_fees_arg minimal_nanotez_per_gas_unit_arg minimal_nanotez_per_byte_arg @@ -653,7 +702,8 @@ let baker_args = let run_baker ( pidfile, - check_node_version_bypass, + node_version_check_bypass, + node_version_allowed, minimal_fees, minimal_nanotez_per_gas_unit, minimal_nanotez_per_byte, @@ -668,7 +718,9 @@ let run_baker pre_emptive_forge_time ) baking_mode sources cctxt = let open Lwt_result_syntax in may_lock_pidfile pidfile @@ fun () -> - let* () = check_node_version cctxt check_node_version_bypass in + let* () = + check_node_version cctxt node_version_check_bypass node_version_allowed + in let*! per_block_vote_file = if per_block_vote_file = None then (* If the votes file was not explicitly given, we diff --git a/src/proto_020_PsParisC/lib_delegate/baking_errors.ml b/src/proto_020_PsParisC/lib_delegate/baking_errors.ml index 54f2ddb40e199ae11a8b6b8a8506544659fd199b..d374b4bf1b26b3e21376405712cdd80ef06e444a 100644 --- a/src/proto_020_PsParisC/lib_delegate/baking_errors.ml +++ b/src/proto_020_PsParisC/lib_delegate/baking_errors.ml @@ -33,6 +33,8 @@ type error += baker_commit_info : Tezos_version.Octez_node_version.commit_info option; } +type error += Node_version_malformatted of string + type error += Cannot_load_local_file of string type error += Broken_locked_values_invariant @@ -74,19 +76,30 @@ let () = fmt "@[Node version is %a (%a) but it is expected to be more recent than \ %a (%a).@ This check can be bypassed at your own risk with the flag \ - --node-version-check-bypass.@]" - Tezos_version.Version.pp_simple + --node-version-check-bypass or the argument --node-version-allowed \ + %a%a .@]" + Tezos_version.Version.pp node_version (Format.pp_print_option ~none:(fun ppf () -> Format.pp_print_string ppf "none") Tezos_version.Octez_node_version.commit_info_pp_short) node_commit_info - Tezos_version.Version.pp_simple + Tezos_version.Version.pp baker_version (Format.pp_print_option ~none:(fun ppf () -> Format.pp_print_string ppf "none") Tezos_version.Octez_node_version.commit_info_pp_short) - baker_commit_info) + baker_commit_info + Tezos_version.Version.pp_arg + node_version + (Format.pp_print_option + ~none:(fun _ppf () -> ()) + (fun ppf -> + Format.fprintf + ppf + ":%a" + Tezos_version.Octez_node_version.commit_info_pp_short)) + node_commit_info) Data_encoding.( obj4 (req "node_version" Tezos_version.Octez_node_version.version_encoding) @@ -105,6 +118,20 @@ let () = (fun (node_version, node_commit_info, baker_version, baker_commit_info) -> Node_version_incompatible {node_version; node_commit_info; baker_version; baker_commit_info}) ; + register_error_kind + `Temporary + ~id:"Baking_commands.node_version_malformatted" + ~title:"Node version in command argument is malformatted" + ~description:"The node version provided in command argument is malformatted" + ~pp:(fun fmt version -> + Format.fprintf + fmt + "The node version provided in command argument: '%s' is a malformatted \ + version" + version) + Data_encoding.(obj1 (req "provided_version" string)) + (function Node_version_malformatted v -> Some v | _ -> None) + (fun v -> Node_version_malformatted v) ; register_error_kind `Temporary ~id:"Baking_scheduling.cannot_load_local_file" diff --git a/src/proto_021_PtQuebec/lib_delegate/baking_commands.ml b/src/proto_021_PtQuebec/lib_delegate/baking_commands.ml index c8e0011903a43b72541525d1a31bffb8dc31b8e2..c4ad01d017d5fd216b35f3d6b00f532c2ebd5850 100644 --- a/src/proto_021_PtQuebec/lib_delegate/baking_commands.ml +++ b/src/proto_021_PtQuebec/lib_delegate/baking_commands.ml @@ -46,9 +46,42 @@ let may_lock_pidfile pidfile_opt f = ~filename:pidfile f -let check_node_version cctxt check_node_version_bypass = +let check_node_version cctxt bypass allowed = let open Lwt_result_syntax in - if check_node_version_bypass then + (* Parse and check allowed versions *) + let*? allowed = + let open Result_syntax in + Option.map_e + (fun allowed -> + match + Tezos_version_parser.version_commit (Lexing.from_string allowed) + with + | None -> tzfail (Node_version_malformatted allowed) + | Some x -> return x) + allowed + in + let is_allowed node_version + (node_commit_info : Tezos_version.Octez_node_version.commit_info option) = + match allowed with + | None -> false + | Some (v, c) -> ( + let c = + Option.map + (fun commit_hash -> + Tezos_version.Octez_node_version.{commit_hash; commit_date = ""}) + c + in + match + Tezos_version.Octez_node_version.partially_compare + v + c + node_version + node_commit_info + with + | None -> false + | Some x -> x = 0) + in + if bypass then let*! () = Events.(emit node_version_check_bypass ()) in return_unit else @@ -71,23 +104,25 @@ let check_node_version cctxt check_node_version_bypass = baker_version, baker_commit_info )) in - match - Tezos_version.Octez_node_version.partially_compare - baker_version - baker_commit_info - node_version.version - node_version.commit_info - with - | Some r when r <= 0 -> return_unit - | _ -> - tzfail - (Node_version_incompatible - { - node_version = node_version.version; - node_commit_info = node_version.commit_info; - baker_version; - baker_commit_info; - }) + if is_allowed node_version.version node_version.commit_info then return_unit + else + match + Tezos_version.Octez_node_version.partially_compare + baker_version + baker_commit_info + node_version.version + node_version.commit_info + with + | Some r when r <= 0 -> return_unit + | _ -> + tzfail + (Node_version_incompatible + { + node_version = node_version.version; + node_commit_info = node_version.commit_info; + baker_version; + baker_commit_info; + }) let http_headers_env_variable = "TEZOS_CLIENT_REMOTE_OPERATIONS_POOL_HTTP_HEADERS" @@ -250,6 +285,19 @@ let node_version_check_bypass_arg = compatibility with the version of the node to which it is connected." () +let node_version_allowed_arg = + Tezos_clic.arg + ~long:"node-version-allowed" + ~placeholder:"-[v].[.0][:]" + ~doc: + "When specified the baker will accept to run with a node of this \ + version. The specified version is composed of the product, for example \ + 'octez'; the major and the minor versions that are positive integers; \ + the info, for example '-rc', '-beta1+dev' or realese if none is \ + provided; optionally the commit that is the hash of the last git commit \ + or a prefix of at least 8 characters long." + string_parameter + let get_delegates (cctxt : Protocol_client_context.full) (pkhs : Signature.public_key_hash list) = let open Lwt_result_syntax in @@ -616,9 +664,10 @@ let lookup_default_vote_file_path (cctxt : Protocol_client_context.full) = type baking_mode = Local of {local_data_dir_path : string} | Remote let baker_args = - Tezos_clic.args14 + Tezos_clic.args15 pidfile_arg node_version_check_bypass_arg + node_version_allowed_arg minimal_fees_arg minimal_nanotez_per_gas_unit_arg minimal_nanotez_per_byte_arg @@ -634,7 +683,8 @@ let baker_args = let run_baker ( pidfile, - check_node_version_bypass, + node_version_check_bypass, + node_version_allowed, minimal_fees, minimal_nanotez_per_gas_unit, minimal_nanotez_per_byte, @@ -649,7 +699,9 @@ let run_baker pre_emptive_forge_time ) baking_mode sources cctxt = let open Lwt_result_syntax in may_lock_pidfile pidfile @@ fun () -> - let* () = check_node_version cctxt check_node_version_bypass in + let* () = + check_node_version cctxt node_version_check_bypass node_version_allowed + in let*! per_block_vote_file = if per_block_vote_file = None then (* If the votes file was not explicitly given, we diff --git a/src/proto_021_PtQuebec/lib_delegate/baking_errors.ml b/src/proto_021_PtQuebec/lib_delegate/baking_errors.ml index 54f2ddb40e199ae11a8b6b8a8506544659fd199b..d374b4bf1b26b3e21376405712cdd80ef06e444a 100644 --- a/src/proto_021_PtQuebec/lib_delegate/baking_errors.ml +++ b/src/proto_021_PtQuebec/lib_delegate/baking_errors.ml @@ -33,6 +33,8 @@ type error += baker_commit_info : Tezos_version.Octez_node_version.commit_info option; } +type error += Node_version_malformatted of string + type error += Cannot_load_local_file of string type error += Broken_locked_values_invariant @@ -74,19 +76,30 @@ let () = fmt "@[Node version is %a (%a) but it is expected to be more recent than \ %a (%a).@ This check can be bypassed at your own risk with the flag \ - --node-version-check-bypass.@]" - Tezos_version.Version.pp_simple + --node-version-check-bypass or the argument --node-version-allowed \ + %a%a .@]" + Tezos_version.Version.pp node_version (Format.pp_print_option ~none:(fun ppf () -> Format.pp_print_string ppf "none") Tezos_version.Octez_node_version.commit_info_pp_short) node_commit_info - Tezos_version.Version.pp_simple + Tezos_version.Version.pp baker_version (Format.pp_print_option ~none:(fun ppf () -> Format.pp_print_string ppf "none") Tezos_version.Octez_node_version.commit_info_pp_short) - baker_commit_info) + baker_commit_info + Tezos_version.Version.pp_arg + node_version + (Format.pp_print_option + ~none:(fun _ppf () -> ()) + (fun ppf -> + Format.fprintf + ppf + ":%a" + Tezos_version.Octez_node_version.commit_info_pp_short)) + node_commit_info) Data_encoding.( obj4 (req "node_version" Tezos_version.Octez_node_version.version_encoding) @@ -105,6 +118,20 @@ let () = (fun (node_version, node_commit_info, baker_version, baker_commit_info) -> Node_version_incompatible {node_version; node_commit_info; baker_version; baker_commit_info}) ; + register_error_kind + `Temporary + ~id:"Baking_commands.node_version_malformatted" + ~title:"Node version in command argument is malformatted" + ~description:"The node version provided in command argument is malformatted" + ~pp:(fun fmt version -> + Format.fprintf + fmt + "The node version provided in command argument: '%s' is a malformatted \ + version" + version) + Data_encoding.(obj1 (req "provided_version" string)) + (function Node_version_malformatted v -> Some v | _ -> None) + (fun v -> Node_version_malformatted v) ; register_error_kind `Temporary ~id:"Baking_scheduling.cannot_load_local_file" diff --git a/src/proto_alpha/lib_delegate/baking_commands.ml b/src/proto_alpha/lib_delegate/baking_commands.ml index c8e0011903a43b72541525d1a31bffb8dc31b8e2..c4ad01d017d5fd216b35f3d6b00f532c2ebd5850 100644 --- a/src/proto_alpha/lib_delegate/baking_commands.ml +++ b/src/proto_alpha/lib_delegate/baking_commands.ml @@ -46,9 +46,42 @@ let may_lock_pidfile pidfile_opt f = ~filename:pidfile f -let check_node_version cctxt check_node_version_bypass = +let check_node_version cctxt bypass allowed = let open Lwt_result_syntax in - if check_node_version_bypass then + (* Parse and check allowed versions *) + let*? allowed = + let open Result_syntax in + Option.map_e + (fun allowed -> + match + Tezos_version_parser.version_commit (Lexing.from_string allowed) + with + | None -> tzfail (Node_version_malformatted allowed) + | Some x -> return x) + allowed + in + let is_allowed node_version + (node_commit_info : Tezos_version.Octez_node_version.commit_info option) = + match allowed with + | None -> false + | Some (v, c) -> ( + let c = + Option.map + (fun commit_hash -> + Tezos_version.Octez_node_version.{commit_hash; commit_date = ""}) + c + in + match + Tezos_version.Octez_node_version.partially_compare + v + c + node_version + node_commit_info + with + | None -> false + | Some x -> x = 0) + in + if bypass then let*! () = Events.(emit node_version_check_bypass ()) in return_unit else @@ -71,23 +104,25 @@ let check_node_version cctxt check_node_version_bypass = baker_version, baker_commit_info )) in - match - Tezos_version.Octez_node_version.partially_compare - baker_version - baker_commit_info - node_version.version - node_version.commit_info - with - | Some r when r <= 0 -> return_unit - | _ -> - tzfail - (Node_version_incompatible - { - node_version = node_version.version; - node_commit_info = node_version.commit_info; - baker_version; - baker_commit_info; - }) + if is_allowed node_version.version node_version.commit_info then return_unit + else + match + Tezos_version.Octez_node_version.partially_compare + baker_version + baker_commit_info + node_version.version + node_version.commit_info + with + | Some r when r <= 0 -> return_unit + | _ -> + tzfail + (Node_version_incompatible + { + node_version = node_version.version; + node_commit_info = node_version.commit_info; + baker_version; + baker_commit_info; + }) let http_headers_env_variable = "TEZOS_CLIENT_REMOTE_OPERATIONS_POOL_HTTP_HEADERS" @@ -250,6 +285,19 @@ let node_version_check_bypass_arg = compatibility with the version of the node to which it is connected." () +let node_version_allowed_arg = + Tezos_clic.arg + ~long:"node-version-allowed" + ~placeholder:"-[v].[.0][:]" + ~doc: + "When specified the baker will accept to run with a node of this \ + version. The specified version is composed of the product, for example \ + 'octez'; the major and the minor versions that are positive integers; \ + the info, for example '-rc', '-beta1+dev' or realese if none is \ + provided; optionally the commit that is the hash of the last git commit \ + or a prefix of at least 8 characters long." + string_parameter + let get_delegates (cctxt : Protocol_client_context.full) (pkhs : Signature.public_key_hash list) = let open Lwt_result_syntax in @@ -616,9 +664,10 @@ let lookup_default_vote_file_path (cctxt : Protocol_client_context.full) = type baking_mode = Local of {local_data_dir_path : string} | Remote let baker_args = - Tezos_clic.args14 + Tezos_clic.args15 pidfile_arg node_version_check_bypass_arg + node_version_allowed_arg minimal_fees_arg minimal_nanotez_per_gas_unit_arg minimal_nanotez_per_byte_arg @@ -634,7 +683,8 @@ let baker_args = let run_baker ( pidfile, - check_node_version_bypass, + node_version_check_bypass, + node_version_allowed, minimal_fees, minimal_nanotez_per_gas_unit, minimal_nanotez_per_byte, @@ -649,7 +699,9 @@ let run_baker pre_emptive_forge_time ) baking_mode sources cctxt = let open Lwt_result_syntax in may_lock_pidfile pidfile @@ fun () -> - let* () = check_node_version cctxt check_node_version_bypass in + let* () = + check_node_version cctxt node_version_check_bypass node_version_allowed + in let*! per_block_vote_file = if per_block_vote_file = None then (* If the votes file was not explicitly given, we diff --git a/src/proto_alpha/lib_delegate/baking_errors.ml b/src/proto_alpha/lib_delegate/baking_errors.ml index 54f2ddb40e199ae11a8b6b8a8506544659fd199b..d374b4bf1b26b3e21376405712cdd80ef06e444a 100644 --- a/src/proto_alpha/lib_delegate/baking_errors.ml +++ b/src/proto_alpha/lib_delegate/baking_errors.ml @@ -33,6 +33,8 @@ type error += baker_commit_info : Tezos_version.Octez_node_version.commit_info option; } +type error += Node_version_malformatted of string + type error += Cannot_load_local_file of string type error += Broken_locked_values_invariant @@ -74,19 +76,30 @@ let () = fmt "@[Node version is %a (%a) but it is expected to be more recent than \ %a (%a).@ This check can be bypassed at your own risk with the flag \ - --node-version-check-bypass.@]" - Tezos_version.Version.pp_simple + --node-version-check-bypass or the argument --node-version-allowed \ + %a%a .@]" + Tezos_version.Version.pp node_version (Format.pp_print_option ~none:(fun ppf () -> Format.pp_print_string ppf "none") Tezos_version.Octez_node_version.commit_info_pp_short) node_commit_info - Tezos_version.Version.pp_simple + Tezos_version.Version.pp baker_version (Format.pp_print_option ~none:(fun ppf () -> Format.pp_print_string ppf "none") Tezos_version.Octez_node_version.commit_info_pp_short) - baker_commit_info) + baker_commit_info + Tezos_version.Version.pp_arg + node_version + (Format.pp_print_option + ~none:(fun _ppf () -> ()) + (fun ppf -> + Format.fprintf + ppf + ":%a" + Tezos_version.Octez_node_version.commit_info_pp_short)) + node_commit_info) Data_encoding.( obj4 (req "node_version" Tezos_version.Octez_node_version.version_encoding) @@ -105,6 +118,20 @@ let () = (fun (node_version, node_commit_info, baker_version, baker_commit_info) -> Node_version_incompatible {node_version; node_commit_info; baker_version; baker_commit_info}) ; + register_error_kind + `Temporary + ~id:"Baking_commands.node_version_malformatted" + ~title:"Node version in command argument is malformatted" + ~description:"The node version provided in command argument is malformatted" + ~pp:(fun fmt version -> + Format.fprintf + fmt + "The node version provided in command argument: '%s' is a malformatted \ + version" + version) + Data_encoding.(obj1 (req "provided_version" string)) + (function Node_version_malformatted v -> Some v | _ -> None) + (fun v -> Node_version_malformatted v) ; register_error_kind `Temporary ~id:"Baking_scheduling.cannot_load_local_file" diff --git a/tezt/lib_tezos/baker.ml b/tezt/lib_tezos/baker.ml index d0b702aaea4faa3b8e44cacfb560e749a29904de..11574a6d4530e9c6b20e0a97522f34a86f8722b0 100644 --- a/tezt/lib_tezos/baker.ml +++ b/tezt/lib_tezos/baker.ml @@ -54,6 +54,7 @@ module Parameters = struct minimal_nanotez_per_gas_unit : int option; state_recorder : bool; node_version_check_bypass : bool; + node_version_allowed : string option; } type session_state = {mutable ready : bool} @@ -100,8 +101,8 @@ let create_from_uris ?runner ~protocol ?(delegates = []) ?votefile ?(liquidity_baking_toggle_vote = Some Pass) ?(force_apply = false) ?(remote_mode = false) ?operations_pool ?dal_node_rpc_endpoint ?minimal_nanotez_per_gas_unit - ?(state_recorder = false) ?(node_version_check_bypass = false) ~base_dir - ~node_data_dir ~node_rpc_endpoint () = + ?(state_recorder = false) ?(node_version_check_bypass = false) + ?node_version_allowed ~base_dir ~node_data_dir ~node_rpc_endpoint () = let baker = create ~path @@ -126,6 +127,7 @@ let create_from_uris ?runner ~protocol minimal_nanotez_per_gas_unit; state_recorder; node_version_check_bypass; + node_version_allowed; } in on_stdout baker (handle_raw_stdout baker) ; @@ -135,7 +137,7 @@ let create ?runner ~protocol ?path ?name ?color ?event_pipe ?(delegates = []) ?votefile ?(liquidity_baking_toggle_vote = Some Pass) ?(force_apply = false) ?(remote_mode = false) ?operations_pool ?dal_node ?minimal_nanotez_per_gas_unit ?(state_recorder = false) - ?(node_version_check_bypass = false) node client = + ?(node_version_check_bypass = false) ?node_version_allowed node client = let dal_node_rpc_endpoint = Option.map Dal_node.as_rpc_endpoint dal_node in create_from_uris ?runner @@ -154,6 +156,7 @@ let create ?runner ~protocol ?path ?name ?color ?event_pipe ?(delegates = []) ?dal_node_rpc_endpoint ~state_recorder ~node_version_check_bypass + ?node_version_allowed ~base_dir:(Client.base_dir client) ~node_data_dir:(Node.data_dir node) ~node_rpc_endpoint:(Node.as_rpc_endpoint node) @@ -206,6 +209,12 @@ let run ?event_level ?event_sections_levels (baker : t) = "node-version-check-bypass" baker.persistent_state.node_version_check_bypass in + let node_version_allowed = + Cli_arg.optional_arg + "node-version-allowed" + Fun.id + baker.persistent_state.node_version_allowed + in let run_args = if baker.persistent_state.remote_mode then ["remotely"] else ["with"; "local"; "node"; node_data_dir] @@ -215,6 +224,7 @@ let run ?event_level ?event_sections_levels (baker : t) = @ run_args @ liquidity_baking_toggle_vote @ votefile @ force_apply @ operations_pool @ dal_node_endpoint @ delegates @ minimal_nanotez_per_gas_unit @ state_recorder @ node_version_check_bypass + @ node_version_allowed in let on_terminate _ = @@ -251,7 +261,7 @@ let init ?runner ~protocol ?(path = Uses.path (Protocol.baker protocol)) ?name ?color ?event_level ?event_pipe ?event_sections_levels ?(delegates = []) ?votefile ?liquidity_baking_toggle_vote ?force_apply ?remote_mode ?operations_pool ?dal_node ?minimal_nanotez_per_gas_unit ?state_recorder - ?node_version_check_bypass node client = + ?node_version_check_bypass ?node_version_allowed node client = let* () = Node.wait_for_ready node in let baker = create @@ -270,6 +280,7 @@ let init ?runner ~protocol ?(path = Uses.path (Protocol.baker protocol)) ?name ?minimal_nanotez_per_gas_unit ?state_recorder ?node_version_check_bypass + ?node_version_allowed ~delegates node client diff --git a/tezt/lib_tezos/baker.mli b/tezt/lib_tezos/baker.mli index 99b8b71165c8dbe51fc954ead5d1312267c142ae..80e3dc1ce01e49dce1edcc8e5fda2373d7ffb4ad 100644 --- a/tezt/lib_tezos/baker.mli +++ b/tezt/lib_tezos/baker.mli @@ -126,10 +126,11 @@ val liquidity_baking_votefile : ?path:string -> liquidity_baking_vote -> string is not passed. If it is [Some x] then [--liquidity-baking-toggle-vote x] is passed. The default value is [Some Pass]. - [operations_pool], [force_apply], [state_recorder] and - [node_version_check_bypass] are passed to the baker daemon through the flag - [--operations-pool], [--force_apply], [--record-state] and - [--node-version-check-bypass]. + [operations_pool], [force_apply], [state_recorder], + [node_version_check_bypass] and [node_version_allowed] are passed to the + baker daemon through the flag [--operations-pool], [--force_apply], + [--record-state], [--node-version-check-bypass] and + [--node-version-allowed]. If [remote_mode] is specified, the baker will run in RPC-only mode. @@ -157,6 +158,7 @@ val create : ?minimal_nanotez_per_gas_unit:int -> ?state_recorder:bool -> ?node_version_check_bypass:bool -> + ?node_version_allowed:string -> Node.t -> Client.t -> t @@ -196,6 +198,7 @@ val create_from_uris : ?minimal_nanotez_per_gas_unit:int -> ?state_recorder:bool -> ?node_version_check_bypass:bool -> + ?node_version_allowed:string -> base_dir:string -> node_data_dir:string -> node_rpc_endpoint:Endpoint.t -> @@ -233,11 +236,11 @@ val create_from_uris : account". [votefile], [liquidity_baking_toggle_vote], [force_apply], - [state_recorder], [node_version_check_bypass] respectively - [operations_pool] are passed to the baker daemon through the flags - [--votefile], [--liquidity-baking-toggle-vote], [--should-apply], - [--record-state], [--node-version-check-bypass] respectively - [--operations-pool]. + [state_recorder], [node_version_check_bypass], [node_version_allowed] + respectively [operations_pool] are passed to the baker daemon through the + flags [--votefile], [--liquidity-baking-toggle-vote], [--should-apply], + [--record-state], [--node-version-check-bypass], [--node-version-allowed] + respectively [--operations-pool]. If [remote_mode] is specified, the baker will run in RPC-only mode. @@ -263,6 +266,7 @@ val init : ?minimal_nanotez_per_gas_unit:int -> ?state_recorder:bool -> ?node_version_check_bypass:bool -> + ?node_version_allowed:string -> Node.t -> Client.t -> t Lwt.t diff --git a/tezt/tests/baker_test.ml b/tezt/tests/baker_test.ml index a19e03d0d535968e0ee9f931ddcb0f2bf2964a66..c3f2ee040dd0af706148e928745f9a8ab3fbdb0a 100644 --- a/tezt/tests/baker_test.ml +++ b/tezt/tests/baker_test.ml @@ -53,6 +53,38 @@ let check_node_version_check_bypass_test = let* () = check_bypassed_event_promise in unit +let check_node_version_allowed_test = + Protocol.register_test + ~__FILE__ + ~title:"baker node version allowed test" + ~tags:[team; "node"; "baker"] + ~supports:Protocol.(From_protocol 022) + ~uses:(fun protocol -> [Protocol.baker protocol]) + @@ fun protocol -> + let* node, client = Client.init_with_protocol `Client ~protocol () in + let* _baker = + Baker.init + ~protocol + ~node_version_allowed:"octez-v7894.789:1a991a03" + node + client + in + unit + +let check_node_version_no_commit_allowed_test = + Protocol.register_test + ~__FILE__ + ~title:"baker node version no commit allowed test" + ~tags:[team; "node"; "baker"] + ~supports:Protocol.(From_protocol 022) + ~uses:(fun protocol -> [Protocol.baker protocol]) + @@ fun protocol -> + let* node, client = Client.init_with_protocol `Client ~protocol () in + let* _baker = + Baker.init ~protocol ~node_version_allowed:"octez-v7894.789" node client + in + unit + let baker_reward_test = Protocol.register_regression_test ~__FILE__ @@ -261,6 +293,8 @@ let baker_check_consensus_branch = let register ~protocols = check_node_version_check_bypass_test protocols ; + check_node_version_allowed_test protocols ; + check_node_version_no_commit_allowed_test protocols ; baker_simple_test protocols ; baker_reward_test protocols ; baker_stresstest protocols ;