diff --git a/CHANGES.rst b/CHANGES.rst index e463f2d04f16e8d0c439d4754c08f3456dd8b9cb..be16d0843593f7688c194b940f325dd7cef3f0b8 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -114,6 +114,11 @@ Baker - Remove ``preendorse for`` and ``endorse for`` deprecated commands from baker. (MR :gl:`!14096`) +- 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`) + Accuser ------- diff --git a/manifest/product_octez.ml b/manifest/product_octez.ml index e382288718a4188df9389098ebd1aaa463ebd087..af5c33d81e105512ccc642ead370ab0168979ec0 100644 --- a/manifest/product_octez.ml +++ b/manifest/product_octez.ml @@ -2191,10 +2191,10 @@ let _etherlink_print_version_exe = let _octez_version_tests = tezt - ["test_parser"] + ["test_parser"; "test_octez_node_version"] ~path:"src/lib_version/test" ~opam:"octez-libs" - ~deps:[octez_version |> open_; octez_version_parser] + ~deps:[octez_version |> open_; octez_version_parser; qcheck_tezt] let octez_p2p_services = octez_lib diff --git a/src/lib_version/octez_node_version.ml b/src/lib_version/octez_node_version.ml index 5f41a4b956bc7c08f8b1d4bdce3940c57a900304..7cd3027fdab7d6e545ec1334f0da910775ff2b57 100644 --- a/src/lib_version/octez_node_version.ml +++ b/src/lib_version/octez_node_version.ml @@ -41,6 +41,12 @@ let commit_info_encoding = (fun (commit_hash, commit_date) -> {commit_hash; commit_date}) (obj2 (req "commit_hash" string) (req "commit_date" string)) +let commit_info_pp f ({commit_hash; _} : commit_info) = + Format.fprintf f "%s" commit_hash + +let commit_info_pp_short f ({commit_hash; _} : commit_info) = + Format.fprintf f "%s" (String.sub commit_hash 0 8) + (* Locally defined encoding for Version.additional_info *) let additional_info_encoding = let open Data_encoding in @@ -109,3 +115,40 @@ let encoding = (req "version" version_encoding) (req "network_version" Network_version.encoding) (req "commit_info" (option commit_info_encoding))) + +let partially_compare (v1 : Version.t) (c1 : commit_info option) + (v2 : Version.t) (c2 : commit_info option) = + let is_dev v = + let open Tezos_version_parser in + match v.additional_info with + | Dev | Beta_dev _ | RC_dev _ -> true + | Beta _ | RC _ | Release -> false + in + let is_commit_equal = + match (c1, c2) with + | None, _ | _, None -> None + | Some x, Some y -> Some (x = y) + in + let version_comparison = + if v1 = v2 then Some 0 + else if v1.product <> v2.product || is_dev v1 || is_dev v2 then None + else + let major_comp = Int.compare v1.major v2.major in + if major_comp <> 0 then Some major_comp + else + let minor_comp = Int.compare v1.minor v2.minor in + if minor_comp <> 0 then Some minor_comp + else + match (v1.additional_info, v2.additional_info) with + | Beta n1, Beta n2 | RC n1, RC n2 -> Some (Int.compare n1 n2) + | Beta _, (RC _ | Release) | RC _, Release -> Some (-1) + | Release, (RC _ | Beta _) | RC _, Beta _ -> Some 1 + | _, _ -> None + in + match (is_commit_equal, version_comparison) with + | None, Some 0 -> + if is_dev v1 then (* dev versions need to have commit_info *) None + else Some 0 + | Some true, Some 0 -> Some 0 + | Some false, Some 0 -> None + | _, x -> x diff --git a/src/lib_version/octez_node_version.mli b/src/lib_version/octez_node_version.mli index 173e1a7c74556d970688df055ff63189093f93ba..2b3bca0af3ef9c18c8d4fa706daebdbddae20a31 100644 --- a/src/lib_version/octez_node_version.mli +++ b/src/lib_version/octez_node_version.mli @@ -44,4 +44,39 @@ val namespace : string val version_encoding : Version.t Data_encoding.t +val commit_info_encoding : commit_info Data_encoding.t + +val commit_info_pp : Format.formatter -> commit_info -> unit + +val commit_info_pp_short : Format.formatter -> commit_info -> unit + val encoding : t Data_encoding.t + +(** [partially_compare v1 c1 v2 c2] is similar to compare like function but + returns [None] when versions are not comparable. + If [v1], [c1] and [v2], [c2] are comparable and 1 is older than 2, [Some x] + with x<0 is returned. + + [v1], [c1] and [v2], [c2] are comparable if: + - they have the same product and + - they are [Beta], [RC] or [Release] or + - they are [Dev], [Beta_dev] or [RC_dev] and + - have commit info and + - version and commit info are equal. + 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. + + To determine which version is more recent or old: + - [major] has the priority on [minor] + - [minor] has priority on [additional_info] + - [Beta] is older than [RC] + - [RC] is older than [Release] +*) +val partially_compare : + Version.t -> + commit_info option -> + Version.t -> + commit_info option -> + int option diff --git a/src/lib_version/test/dune b/src/lib_version/test/dune index 966c9bf671a3dbfa7a2c9007fde313fc87c24fc7..e96dd6ea3758f6e657a956a2f83f629109063d18 100644 --- a/src/lib_version/test/dune +++ b/src/lib_version/test/dune @@ -7,14 +7,15 @@ (libraries tezt.core octez-libs.version - octez-libs.version.parser) + octez-libs.version.parser + octez-libs.qcheck-tezt) (library_flags (:standard -linkall)) (flags (:standard) -open Tezt_core -open Tezt_core.Base -open Tezos_version) - (modules test_parser)) + (modules test_parser test_octez_node_version)) (executable (name main) diff --git a/src/lib_version/test/test_octez_node_version.ml b/src/lib_version/test/test_octez_node_version.ml new file mode 100644 index 0000000000000000000000000000000000000000..1c2ab87a9ecec5624479d532658b0a0fed1891c2 --- /dev/null +++ b/src/lib_version/test/test_octez_node_version.ml @@ -0,0 +1,453 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* SPDX-FileCopyrightText: 2024 Nomadic Labs, *) +(* *) +(*****************************************************************************) + +(** Testing + _______ + Component: lib_version + Invocation: dune exec src/lib_version/test/main.exe \ + -- --file test_octez_node_version.ml + Subject: Test versions +*) +let all_products : Tezos_version_parser.product array = [|Octez; Etherlink|] + +(* Values of Beta x, Beta_dev x, RC x or RC_dev x in additional_info are + ignored. *) +let version_generator ?(product = all_products) ?major ?minor + ?(additional_info = + ([|Dev; Beta 0; Beta_dev 0; RC 0; RC_dev 0; Release|] + : Tezos_version_parser.additional_info array)) ?additional_info_value () + = + let product = QCheck2.Gen.oneofa product in + let major = Option.fold ~none:QCheck2.Gen.int ~some:QCheck2.Gen.pure major in + let minor = Option.fold ~none:QCheck2.Gen.int ~some:QCheck2.Gen.pure minor in + let additional_info_value = + Option.fold + ~none:QCheck2.Gen.int + ~some:QCheck2.Gen.pure + additional_info_value + in + let additional_info = + let open Tezos_version_parser in + QCheck2.Gen.map2 + (fun additional_info v -> + match additional_info with + | Beta _ -> Beta v + | Beta_dev _ -> Beta_dev v + | RC _ -> RC v + | RC_dev _ -> RC_dev v + | x -> x) + (QCheck2.Gen.oneofa additional_info) + additional_info_value + in + QCheck2.Gen.map + (fun (product, major, minor, additional_info) -> + Tezos_version_parser.{product; major; minor; additional_info}) + (QCheck2.Gen.tup4 product major minor additional_info) + +let commit_info_opt_generator ?(always_some = false) () = + let hash = QCheck2.Gen.small_string ?gen:None in + let date = QCheck2.Gen.small_string ?gen:None in + let commit_info = + QCheck2.Gen.map2 + (fun commit_hash commit_date : Octez_node_version.commit_info -> + {commit_hash; commit_date}) + hash + date + in + let commit_info_opt = + if always_some then QCheck2.Gen.map (fun c -> Some c) commit_info + else QCheck2.Gen.option commit_info + in + commit_info_opt + +let commit_info_opt_pp = + Format.pp_print_option + ~none:(fun ppf () -> Format.pp_print_string ppf "none") + Octez_node_version.commit_info_pp + +let print_opt ?v1 ?c1 ?v2 ?c2 () = + let pp_opt msg pp fmt = function + | None -> Format.fprintf fmt "@," + | Some v -> Format.fprintf fmt "%s: %a@," msg pp v + in + Format.asprintf + "%a%a%a%a" + (pp_opt "version1" Tezos_version_parser.pp) + v1 + (pp_opt "commit1" commit_info_opt_pp) + c1 + (pp_opt "version2" Tezos_version_parser.pp) + v2 + (pp_opt "commit2" commit_info_opt_pp) + c2 + +let print (v1, c1, v2, c2) = print_opt ~v1 ~c1 ~v2 ~c2 () + +(* Test reciprocity: ab uncomparable the ba uncomparable | a=b then b=a | if + a>b then b + match + ( Octez_node_version.partially_compare v1 c1 v2 c2, + Octez_node_version.partially_compare v2 c2 v1 c1 ) + with + | None, None | Some 0, Some 0 -> true + | Some x, Some y when x < 0 && y > 0 -> true + | Some x, Some y when x > 0 && y < 0 -> true + | _ -> QCheck2.Test.fail_reportf "this operator should have reciprocity" + +(* Test uncomparable cases *) +(* Test same version but different commit info *) +let () = + Qcheck_tezt.register ~__FILE__ ~tags:["version"] + @@ + let generator = + QCheck2.Gen.tup3 + (version_generator ()) + (commit_info_opt_generator ~always_some:true ()) + (commit_info_opt_generator ~always_some:true ()) + in + let print (v1, c1, c2) = print_opt ~v1 ~c1 ~c2 () in + QCheck2.Test.make + ~name:"partially_compare uncomparable different existing commits" + ~print + generator + @@ fun (v, c1, c2) -> + QCheck2.assume (c1 <> c2) ; + match Octez_node_version.partially_compare v c1 v c2 with + | None -> true + | _ -> QCheck2.Test.fail_reportf "These versions should be uncomparable" + +(* Test same version but different commit info or no commit info for dev *) +let () = + Qcheck_tezt.register ~__FILE__ ~tags:["version"] + @@ + let generator = + QCheck2.Gen.tup3 + (version_generator ~additional_info:[|Dev; Beta_dev 0; RC_dev 0|] ()) + (commit_info_opt_generator ()) + (commit_info_opt_generator ()) + in + let print (v1, c1, c2) = print_opt ~v1 ~c1 ~c2 () in + QCheck2.Test.make + ~name:"partially_compare uncomparable different commits" + ~print + generator + @@ fun (v, c1, c2) -> + QCheck2.assume (c1 <> c2) ; + match Octez_node_version.partially_compare v c1 v c2 with + | None -> true + | _ -> QCheck2.Test.fail_reportf "These versions should be uncomparable" + +(* Test different products *) +let () = + Qcheck_tezt.register ~__FILE__ ~tags:["version"] + @@ + let products = + QCheck2.Gen.map + (fun a -> ([|a.(0)|], Array.sub a 1 (Array.length a - 1))) + (QCheck2.Gen.shuffle_a all_products) + in + let generator = + QCheck2.Gen.bind products (fun (p1, p2) -> + QCheck2.Gen.tup3 + (version_generator ~product:p1 ()) + (commit_info_opt_generator ()) + (version_generator ~product:p2 ())) + in + let print (v1, c1, v2) = print_opt ~v1 ~c1 ~v2 () in + QCheck2.Test.make + ~name:"partially_compare uncomparable different products" + ~print + generator + @@ fun (v1, c, v2) -> + match Octez_node_version.partially_compare v1 c v2 c with + | None -> true + | _ -> QCheck2.Test.fail_reportf "These versions should be uncomparable" + +(* Test dev and different commit info *) +let () = + Qcheck_tezt.register ~__FILE__ ~tags:["version"] + @@ + let generator = + QCheck2.Gen.bind (QCheck2.Gen.oneofa all_products) (fun p -> + let product = [|p|] in + QCheck2.Gen.tup4 + (version_generator + ~product + ~additional_info:[|Dev; Beta_dev 0; RC_dev 0|] + ()) + (commit_info_opt_generator ()) + (version_generator ~product ()) + (commit_info_opt_generator ())) + in + QCheck2.Test.make + ~name:"partially_compare uncomparable additional_info" + ~print + generator + @@ fun (v1, c1, v2, c2) -> + QCheck2.assume (v1 <> v2) ; + QCheck2.assume (c1 <> c2) ; + match Octez_node_version.partially_compare v1 c1 v2 c2 with + | None -> true + | _ -> QCheck2.Test.fail_reportf "These versions should be uncomparable" + +(* Test comparable cases *) +(* Test equal cases *) +(* Test equality with commit info *) +let () = + Qcheck_tezt.register ~__FILE__ ~tags:["version"] + @@ + let generator = + QCheck2.Gen.tup2 + (version_generator ()) + (commit_info_opt_generator ~always_some:true ()) + in + let print (v1, c1) = print_opt ~v1 ~c1 () in + QCheck2.Test.make + ~name:"partially_compare comparable equality" + ~print + generator + @@ fun (v, c) -> + match Octez_node_version.partially_compare v c v c with + | Some 0 -> true + | _ -> QCheck2.Test.fail_reportf "These versions should be equal" + +(* Test equality not dev *) +let () = + Qcheck_tezt.register ~__FILE__ ~tags:["version"] + @@ + let generator = + QCheck2.Gen.tup2 + (version_generator ~additional_info:[|Beta 0; RC 0; Release|] ()) + (commit_info_opt_generator ()) + in + let print (v1, c1) = print_opt ~v1 ~c1 () in + QCheck2.Test.make + ~name:"partially_compare comparable equality no commit" + ~print + generator + @@ fun (v, c) -> + match Octez_node_version.partially_compare v None v c with + | Some 0 -> true + | _ -> QCheck2.Test.fail_reportf "These versions should be equal" + +(* Test not equal cases *) +(* Test different major *) +let () = + Qcheck_tezt.register ~__FILE__ ~tags:["version"] + @@ + let generator = + let product = QCheck2.Gen.oneofa all_products in + QCheck2.Gen.bind + (QCheck2.Gen.tup3 product QCheck2.Gen.int QCheck2.Gen.int) + (fun (p, x, y) -> + let product = [|p|] in + let major1, major2 = if x < y then (x, y) else (y, x) in + QCheck2.Gen.tup4 + (version_generator + ~product + ~major:major1 + ~additional_info:[|Beta 0; RC 0; Release|] + ()) + (commit_info_opt_generator ()) + (version_generator + ~product + ~major:major2 + ~additional_info:[|Beta 0; RC 0; Release|] + ()) + (commit_info_opt_generator ())) + in + QCheck2.Test.make ~name:"partially_compare different major" ~print generator + @@ fun (v1, c1, v2, c2) -> + QCheck2.assume (v1.major <> v2.major) ; + match Octez_node_version.partially_compare v1 c1 v2 c2 with + | Some x when x < 0 -> ( + match Octez_node_version.partially_compare v2 c2 v1 c1 with + | Some x when x > 0 -> true + | _ -> + QCheck2.Test.fail_reportf "version2 should be greater than version1") + | _ -> QCheck2.Test.fail_reportf "version1 should be less than version2" + +(* Test same major, different minor *) +let () = + Qcheck_tezt.register ~__FILE__ ~tags:["version"] + @@ + let generator = + let product = QCheck2.Gen.oneofa all_products in + QCheck2.Gen.bind + (QCheck2.Gen.tup4 product QCheck2.Gen.int QCheck2.Gen.int QCheck2.Gen.int) + (fun (p, major, x, y) -> + let product = [|p|] in + let minor1, minor2 = if x < y then (x, y) else (y, x) in + QCheck2.Gen.tup4 + (version_generator + ~product + ~major + ~minor:minor1 + ~additional_info:[|Beta 0; RC 0; Release|] + ()) + (commit_info_opt_generator ()) + (version_generator + ~product + ~major + ~minor:minor2 + ~additional_info:[|Beta 0; RC 0; Release|] + ()) + (commit_info_opt_generator ())) + in + QCheck2.Test.make ~name:"partially_compare different minor" ~print generator + @@ fun (v1, c1, v2, c2) -> + QCheck2.assume (v1.minor <> v2.minor) ; + match Octez_node_version.partially_compare v1 c1 v2 c2 with + | Some x when x < 0 -> ( + match Octez_node_version.partially_compare v2 c2 v1 c1 with + | Some x when x > 0 -> true + | _ -> + QCheck2.Test.fail_reportf "version2 should be greater than version1") + | _ -> QCheck2.Test.fail_reportf "version1 should be less than version2" + +(* Test same major, minor same additional_info but diff additional_info inner + value *) +let () = + Qcheck_tezt.register ~__FILE__ ~tags:["version"] + @@ + let generator = + let product = QCheck2.Gen.oneofa all_products in + let additional_info = + QCheck2.Gen.oneofa Tezos_version_parser.[|Beta 0; RC 0|] + in + QCheck2.Gen.bind + (QCheck2.Gen.tup6 + product + QCheck2.Gen.int + QCheck2.Gen.int + additional_info + QCheck2.Gen.int + QCheck2.Gen.int) + (fun (p, major, minor, ai, x, y) -> + let product = [|p|] in + let additional_info = [|ai|] in + let beta1, beta2 = if x < y then (x, y) else (y, x) in + QCheck2.Gen.tup4 + (version_generator + ~product + ~major + ~minor + ~additional_info + ~additional_info_value:beta1 + ()) + (commit_info_opt_generator ()) + (version_generator + ~product + ~major + ~minor + ~additional_info + ~additional_info_value:beta2 + ()) + (commit_info_opt_generator ())) + in + QCheck2.Test.make + ~name:"partially_compare different additional_info inner version" + ~print + generator + @@ fun (v1, c1, v2, c2) -> + QCheck2.assume (v1.minor <> v2.minor) ; + match Octez_node_version.partially_compare v1 c1 v2 c2 with + | Some x when x < 0 -> ( + match Octez_node_version.partially_compare v2 c2 v1 c1 with + | Some x when x > 0 -> true + | _ -> + QCheck2.Test.fail_reportf "version2 should be greater than version1") + | _ -> QCheck2.Test.fail_reportf "version1 should be less than version2" + +(* Test Beta < RC or Release *) +let () = + Qcheck_tezt.register ~__FILE__ ~tags:["version"] + @@ + let generator = + let product = QCheck2.Gen.oneofa all_products in + QCheck2.Gen.bind + (QCheck2.Gen.tup3 product QCheck2.Gen.int QCheck2.Gen.int) + (fun (p, major, minor) -> + let product = [|p|] in + QCheck2.Gen.tup4 + (version_generator + ~product + ~major + ~minor + ~additional_info:[|Beta 0|] + ()) + (commit_info_opt_generator ()) + (version_generator + ~product + ~major + ~minor + ~additional_info:[|RC 0; Release|] + ()) + (commit_info_opt_generator ())) + in + QCheck2.Test.make + ~name:"partially_compare beta < RC or Release" + ~print + generator + @@ fun (v1, c1, v2, c2) -> + QCheck2.assume (v1.minor <> v2.minor) ; + match Octez_node_version.partially_compare v1 c1 v2 c2 with + | Some x when x < 0 -> ( + match Octez_node_version.partially_compare v2 c2 v1 c1 with + | Some x when x > 0 -> true + | _ -> + QCheck2.Test.fail_reportf "version2 should be greater than version1") + | _ -> QCheck2.Test.fail_reportf "version1 should be less than version2" + +(* Test RC < Release *) +let () = + Qcheck_tezt.register ~__FILE__ ~tags:["version"] + @@ + let generator = + let product = QCheck2.Gen.oneofa all_products in + QCheck2.Gen.bind + (QCheck2.Gen.tup3 product QCheck2.Gen.int QCheck2.Gen.int) + (fun (p, major, minor) -> + let product = [|p|] in + QCheck2.Gen.tup4 + (version_generator + ~product + ~major + ~minor + ~additional_info:[|RC 0|] + ()) + (commit_info_opt_generator ()) + (version_generator + ~product + ~major + ~minor + ~additional_info:[|Release|] + ()) + (commit_info_opt_generator ())) + in + QCheck2.Test.make ~name:"partially_compare RC + QCheck2.assume (v1.minor <> v2.minor) ; + match Octez_node_version.partially_compare v1 c1 v2 c2 with + | Some x when x < 0 -> ( + match Octez_node_version.partially_compare v2 c2 v1 c1 with + | Some x when x > 0 -> true + | _ -> + QCheck2.Test.fail_reportf "version2 should be greater than version1") + | _ -> QCheck2.Test.fail_reportf "version1 should be less than version2" diff --git a/src/proto_alpha/lib_delegate/baking_commands.ml b/src/proto_alpha/lib_delegate/baking_commands.ml index 15178b08ab142aec06170507f7761ccca0c0ead9..d792ae97d2550d23fbadf27cf947e6e0195806ae 100644 --- a/src/proto_alpha/lib_delegate/baking_commands.ml +++ b/src/proto_alpha/lib_delegate/baking_commands.ml @@ -25,6 +25,7 @@ open Client_proto_args open Baking_errors +module Events = Baking_events.Commands let pidfile_arg = let open Lwt_result_syntax in @@ -45,6 +46,49 @@ let may_lock_pidfile pidfile_opt f = ~filename:pidfile f +let check_node_version cctxt check_node_version_bypass = + let open Lwt_result_syntax in + if check_node_version_bypass then + let*! () = Events.(emit node_version_check_bypass ()) in + return_unit + else + let baker_version = Tezos_version_value.Current_git_info.octez_version in + let (baker_commit_info + : Tezos_version.Octez_node_version.commit_info option) = + Some + { + commit_hash = Tezos_version_value.Current_git_info.commit_hash; + commit_date = Tezos_version_value.Current_git_info.committer_date; + } + in + let* node_version = Version_services.version cctxt in + let*! () = + Events.( + emit + node_version_check + ( node_version.version, + node_version.commit_info, + 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; + }) + let http_headers_env_variable = "TEZOS_CLIENT_REMOTE_OPERATIONS_POOL_HTTP_HEADERS" @@ -198,6 +242,14 @@ let state_recorder_switch_arg = consensus state in the filesystem, otherwise just in memory." ()) +let node_version_check_bypass_arg = + Tezos_clic.switch + ~long:"node-version-check-bypass" + ~doc: + "If node-version-check-bypass flag is set, the baker will not check its \ + compatibility with the version of the node to which it is connected." + () + let get_delegates (cctxt : Protocol_client_context.full) (pkhs : Signature.public_key_hash list) = let open Lwt_result_syntax in @@ -564,8 +616,9 @@ 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.args13 + Tezos_clic.args14 pidfile_arg + node_version_check_bypass_arg minimal_fees_arg minimal_nanotez_per_gas_unit_arg minimal_nanotez_per_byte_arg @@ -581,6 +634,7 @@ let baker_args = let run_baker ( pidfile, + check_node_version_bypass, minimal_fees, minimal_nanotez_per_gas_unit, minimal_nanotez_per_byte, @@ -595,6 +649,7 @@ 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*! 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 537462a771e310a14372d01e187fcb8d4f4f6cd8..54f2ddb40e199ae11a8b6b8a8506544659fd199b 100644 --- a/src/proto_alpha/lib_delegate/baking_errors.ml +++ b/src/proto_alpha/lib_delegate/baking_errors.ml @@ -25,6 +25,14 @@ type error += Node_connection_lost +type error += + | Node_version_incompatible of { + node_version : Tezos_version_parser.t; + node_commit_info : Tezos_version.Octez_node_version.commit_info option; + baker_version : Tezos_version_parser.t; + baker_commit_info : Tezos_version.Octez_node_version.commit_info option; + } + type error += Cannot_load_local_file of string type error += Broken_locked_values_invariant @@ -51,6 +59,52 @@ let () = Data_encoding.empty (function Node_connection_lost -> Some () | _ -> None) (fun () -> Node_connection_lost) ; + register_error_kind + `Temporary + ~id:"Baking_commands.node_version_incompatible" + ~title:"Node version is incompatible" + ~description:"The node version is incompatible with this baker" + ~pp:(fun fmt + ((node_version, node_commit_info, baker_version, baker_commit_info) : + Tezos_version_parser.t + * Tezos_version.Octez_node_version.commit_info option + * Tezos_version_parser.t + * Tezos_version.Octez_node_version.commit_info option) -> + Format.fprintf + 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 + (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 + 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) + Data_encoding.( + obj4 + (req "node_version" Tezos_version.Octez_node_version.version_encoding) + (opt + "node_commit_info" + Tezos_version.Octez_node_version.commit_info_encoding) + (req "baker_version" Tezos_version.Octez_node_version.version_encoding) + (opt + "baker_commit_info" + Tezos_version.Octez_node_version.commit_info_encoding)) + (function + | Node_version_incompatible + {node_version; node_commit_info; baker_version; baker_commit_info} -> + Some (node_version, node_commit_info, baker_version, baker_commit_info) + | _ -> None) + (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_scheduling.cannot_load_local_file" diff --git a/src/proto_alpha/lib_delegate/baking_events.ml b/src/proto_alpha/lib_delegate/baking_events.ml index 8d462de00fec766d17861f9778b721aa7a482846..9b629607e8b2215bc99be48cbc9ffe6c1322d01a 100644 --- a/src/proto_alpha/lib_delegate/baking_events.ml +++ b/src/proto_alpha/lib_delegate/baking_events.ml @@ -34,6 +34,45 @@ let pp_int64 fmt n = Format.fprintf fmt "%Ld" n let waiting_color = Internal_event.Magenta +module Commands = struct + include Internal_event.Simple + + let section = section @ ["commands"] + + let node_version_check_bypass = + declare_0 + ~section + ~name:"node_version_check_bypass" + ~level:Warning + ~msg:"Compatibility between node version and baker version by passed" + () + + let node_version_check = + declare_4 + ~section + ~name:"node_version_check" + ~level:Debug + ~msg: + "Checking compatibility between node version {node_version} \ + ({node_commit}) and baker version {baker_version} ({baker_commit})" + ~pp1:Tezos_version.Version.pp_simple + ("node_version", Tezos_version.Octez_node_version.version_encoding) + ~pp2: + (Format.pp_print_option + Tezos_version.Octez_node_version.commit_info_pp_short) + ( "node_commit", + Data_encoding.option + Tezos_version.Octez_node_version.commit_info_encoding ) + ~pp3:Tezos_version.Version.pp_simple + ("baker_version", Tezos_version.Octez_node_version.version_encoding) + ~pp4: + (Format.pp_print_option + Tezos_version.Octez_node_version.commit_info_pp_short) + ( "baker_commit", + Data_encoding.option + Tezos_version.Octez_node_version.commit_info_encoding ) +end + module State_transitions = struct include Internal_event.Simple diff --git a/src/proto_beta/lib_delegate/baking_commands.ml b/src/proto_beta/lib_delegate/baking_commands.ml index 15178b08ab142aec06170507f7761ccca0c0ead9..d792ae97d2550d23fbadf27cf947e6e0195806ae 100644 --- a/src/proto_beta/lib_delegate/baking_commands.ml +++ b/src/proto_beta/lib_delegate/baking_commands.ml @@ -25,6 +25,7 @@ open Client_proto_args open Baking_errors +module Events = Baking_events.Commands let pidfile_arg = let open Lwt_result_syntax in @@ -45,6 +46,49 @@ let may_lock_pidfile pidfile_opt f = ~filename:pidfile f +let check_node_version cctxt check_node_version_bypass = + let open Lwt_result_syntax in + if check_node_version_bypass then + let*! () = Events.(emit node_version_check_bypass ()) in + return_unit + else + let baker_version = Tezos_version_value.Current_git_info.octez_version in + let (baker_commit_info + : Tezos_version.Octez_node_version.commit_info option) = + Some + { + commit_hash = Tezos_version_value.Current_git_info.commit_hash; + commit_date = Tezos_version_value.Current_git_info.committer_date; + } + in + let* node_version = Version_services.version cctxt in + let*! () = + Events.( + emit + node_version_check + ( node_version.version, + node_version.commit_info, + 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; + }) + let http_headers_env_variable = "TEZOS_CLIENT_REMOTE_OPERATIONS_POOL_HTTP_HEADERS" @@ -198,6 +242,14 @@ let state_recorder_switch_arg = consensus state in the filesystem, otherwise just in memory." ()) +let node_version_check_bypass_arg = + Tezos_clic.switch + ~long:"node-version-check-bypass" + ~doc: + "If node-version-check-bypass flag is set, the baker will not check its \ + compatibility with the version of the node to which it is connected." + () + let get_delegates (cctxt : Protocol_client_context.full) (pkhs : Signature.public_key_hash list) = let open Lwt_result_syntax in @@ -564,8 +616,9 @@ 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.args13 + Tezos_clic.args14 pidfile_arg + node_version_check_bypass_arg minimal_fees_arg minimal_nanotez_per_gas_unit_arg minimal_nanotez_per_byte_arg @@ -581,6 +634,7 @@ let baker_args = let run_baker ( pidfile, + check_node_version_bypass, minimal_fees, minimal_nanotez_per_gas_unit, minimal_nanotez_per_byte, @@ -595,6 +649,7 @@ 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*! 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_beta/lib_delegate/baking_errors.ml b/src/proto_beta/lib_delegate/baking_errors.ml index 537462a771e310a14372d01e187fcb8d4f4f6cd8..54f2ddb40e199ae11a8b6b8a8506544659fd199b 100644 --- a/src/proto_beta/lib_delegate/baking_errors.ml +++ b/src/proto_beta/lib_delegate/baking_errors.ml @@ -25,6 +25,14 @@ type error += Node_connection_lost +type error += + | Node_version_incompatible of { + node_version : Tezos_version_parser.t; + node_commit_info : Tezos_version.Octez_node_version.commit_info option; + baker_version : Tezos_version_parser.t; + baker_commit_info : Tezos_version.Octez_node_version.commit_info option; + } + type error += Cannot_load_local_file of string type error += Broken_locked_values_invariant @@ -51,6 +59,52 @@ let () = Data_encoding.empty (function Node_connection_lost -> Some () | _ -> None) (fun () -> Node_connection_lost) ; + register_error_kind + `Temporary + ~id:"Baking_commands.node_version_incompatible" + ~title:"Node version is incompatible" + ~description:"The node version is incompatible with this baker" + ~pp:(fun fmt + ((node_version, node_commit_info, baker_version, baker_commit_info) : + Tezos_version_parser.t + * Tezos_version.Octez_node_version.commit_info option + * Tezos_version_parser.t + * Tezos_version.Octez_node_version.commit_info option) -> + Format.fprintf + 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 + (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 + 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) + Data_encoding.( + obj4 + (req "node_version" Tezos_version.Octez_node_version.version_encoding) + (opt + "node_commit_info" + Tezos_version.Octez_node_version.commit_info_encoding) + (req "baker_version" Tezos_version.Octez_node_version.version_encoding) + (opt + "baker_commit_info" + Tezos_version.Octez_node_version.commit_info_encoding)) + (function + | Node_version_incompatible + {node_version; node_commit_info; baker_version; baker_commit_info} -> + Some (node_version, node_commit_info, baker_version, baker_commit_info) + | _ -> None) + (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_scheduling.cannot_load_local_file" diff --git a/src/proto_beta/lib_delegate/baking_events.ml b/src/proto_beta/lib_delegate/baking_events.ml index a224fd1e6c8de480145d513fd5eeb827af5c7d2e..6a95d6ae574be9b32c901603474af7b68c7cc12c 100644 --- a/src/proto_beta/lib_delegate/baking_events.ml +++ b/src/proto_beta/lib_delegate/baking_events.ml @@ -34,6 +34,45 @@ let pp_int64 fmt n = Format.fprintf fmt "%Ld" n let waiting_color = Internal_event.Magenta +module Commands = struct + include Internal_event.Simple + + let section = section @ ["commands"] + + let node_version_check_bypass = + declare_0 + ~section + ~name:"node_version_check_bypass" + ~level:Warning + ~msg:"Compatibility between node version and baker version by passed" + () + + let node_version_check = + declare_4 + ~section + ~name:"node_version_check" + ~level:Debug + ~msg: + "Checking compatibility between node version {node_version} \ + ({node_commit}) and baker version {baker_version} ({baker_commit})" + ~pp1:Tezos_version.Version.pp_simple + ("node_version", Tezos_version.Octez_node_version.version_encoding) + ~pp2: + (Format.pp_print_option + Tezos_version.Octez_node_version.commit_info_pp_short) + ( "node_commit", + Data_encoding.option + Tezos_version.Octez_node_version.commit_info_encoding ) + ~pp3:Tezos_version.Version.pp_simple + ("baker_version", Tezos_version.Octez_node_version.version_encoding) + ~pp4: + (Format.pp_print_option + Tezos_version.Octez_node_version.commit_info_pp_short) + ( "baker_commit", + Data_encoding.option + Tezos_version.Octez_node_version.commit_info_encoding ) +end + module State_transitions = struct include Internal_event.Simple diff --git a/tezt/lib_tezos/baker.ml b/tezt/lib_tezos/baker.ml index 09f70af6f93663940ee766f242e9bf69fb0b800f..d0b702aaea4faa3b8e44cacfb560e749a29904de 100644 --- a/tezt/lib_tezos/baker.ml +++ b/tezt/lib_tezos/baker.ml @@ -53,6 +53,7 @@ module Parameters = struct operations_pool : string option; minimal_nanotez_per_gas_unit : int option; state_recorder : bool; + node_version_check_bypass : bool; } type session_state = {mutable ready : bool} @@ -99,7 +100,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) ~base_dir ~node_data_dir ~node_rpc_endpoint () = + ?(state_recorder = false) ?(node_version_check_bypass = false) ~base_dir + ~node_data_dir ~node_rpc_endpoint () = let baker = create ~path @@ -123,6 +125,7 @@ let create_from_uris ?runner ~protocol dal_node_rpc_endpoint; minimal_nanotez_per_gas_unit; state_recorder; + node_version_check_bypass; } in on_stdout baker (handle_raw_stdout baker) ; @@ -131,7 +134,8 @@ let create_from_uris ?runner ~protocol 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 client = + ?minimal_nanotez_per_gas_unit ?(state_recorder = false) + ?(node_version_check_bypass = false) node client = let dal_node_rpc_endpoint = Option.map Dal_node.as_rpc_endpoint dal_node in create_from_uris ?runner @@ -149,6 +153,7 @@ let create ?runner ~protocol ?path ?name ?color ?event_pipe ?(delegates = []) ?minimal_nanotez_per_gas_unit ?dal_node_rpc_endpoint ~state_recorder + ~node_version_check_bypass ~base_dir:(Client.base_dir client) ~node_data_dir:(Node.data_dir node) ~node_rpc_endpoint:(Node.as_rpc_endpoint node) @@ -196,6 +201,11 @@ let run ?event_level ?event_sections_levels (baker : t) = let state_recorder = Cli_arg.optional_switch "record-state" baker.persistent_state.state_recorder in + let node_version_check_bypass = + Cli_arg.optional_switch + "node-version-check-bypass" + baker.persistent_state.node_version_check_bypass + in let run_args = if baker.persistent_state.remote_mode then ["remotely"] else ["with"; "local"; "node"; node_data_dir] @@ -204,7 +214,7 @@ let run ?event_level ?event_sections_levels (baker : t) = ["--endpoint"; node_addr; "--base-dir"; base_dir; "run"] @ run_args @ liquidity_baking_toggle_vote @ votefile @ force_apply @ operations_pool @ dal_node_endpoint @ delegates - @ minimal_nanotez_per_gas_unit @ state_recorder + @ minimal_nanotez_per_gas_unit @ state_recorder @ node_version_check_bypass in let on_terminate _ = @@ -241,7 +251,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 client = + ?node_version_check_bypass node client = let* () = Node.wait_for_ready node in let baker = create @@ -259,6 +269,7 @@ let init ?runner ~protocol ?(path = Uses.path (Protocol.baker protocol)) ?name ?dal_node ?minimal_nanotez_per_gas_unit ?state_recorder + ?node_version_check_bypass ~delegates node client diff --git a/tezt/lib_tezos/baker.mli b/tezt/lib_tezos/baker.mli index 88c6e30267970051fa5bbd4a45794e968ed361ad..99b8b71165c8dbe51fc954ead5d1312267c142ae 100644 --- a/tezt/lib_tezos/baker.mli +++ b/tezt/lib_tezos/baker.mli @@ -126,8 +126,10 @@ 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] and [state_recorder] are passed to the baker - daemon through the flag [--operations-pool], [--force_apply] and [--record-state]. + [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]. If [remote_mode] is specified, the baker will run in RPC-only mode. @@ -154,6 +156,7 @@ val create : ?dal_node:Dal_node.t -> ?minimal_nanotez_per_gas_unit:int -> ?state_recorder:bool -> + ?node_version_check_bypass:bool -> Node.t -> Client.t -> t @@ -192,6 +195,7 @@ val create_from_uris : ?dal_node_rpc_endpoint:Endpoint.t -> ?minimal_nanotez_per_gas_unit:int -> ?state_recorder:bool -> + ?node_version_check_bypass:bool -> base_dir:string -> node_data_dir:string -> node_rpc_endpoint:Endpoint.t -> @@ -228,10 +232,12 @@ val create_from_uris : baker. This defaults to the empty list, which is a shortcut for "every known account". - [votefile], [liquidity_baking_toggle_vote], [force_apply], [state_recorder] - respectively [operations_pool] are passed to the baker daemon through the flags - [--votefile], [--liquidity-baking-toggle-vote], [--should-apply], [--record-state] - respectively [--operations-pool]. + [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]. If [remote_mode] is specified, the baker will run in RPC-only mode. @@ -256,6 +262,7 @@ val init : ?dal_node:Dal_node.t -> ?minimal_nanotez_per_gas_unit:int -> ?state_recorder:bool -> + ?node_version_check_bypass:bool -> Node.t -> Client.t -> t Lwt.t diff --git a/tezt/tests/baker_test.ml b/tezt/tests/baker_test.ml index 171676dd23881b799da3347a734cae26043aa287..a19e03d0d535968e0ee9f931ddcb0f2bf2964a66 100644 --- a/tezt/tests/baker_test.ml +++ b/tezt/tests/baker_test.ml @@ -34,6 +34,25 @@ let team = Tag.layer1 let hooks = Tezos_regression.hooks +let check_node_version_check_bypass_test = + Protocol.register_test + ~__FILE__ + ~title:"baker node version check bypass test" + ~tags:[team; "node"; "baker"] + ~supports:Protocol.(From_protocol 021) + ~uses:(fun protocol -> [Protocol.baker protocol]) + @@ fun protocol -> + let* node, client = Client.init_with_protocol `Client ~protocol () in + let baker = + Baker.create ~protocol ~node_version_check_bypass:true node client + in + let check_bypassed_event_promise = + Baker.wait_for baker "node_version_check_bypass.v0" (fun _ -> Some ()) + in + let* () = Baker.run baker in + let* () = check_bypassed_event_promise in + unit + let baker_reward_test = Protocol.register_regression_test ~__FILE__ @@ -241,6 +260,7 @@ let baker_check_consensus_branch = ops let register ~protocols = + check_node_version_check_bypass_test protocols ; baker_simple_test protocols ; baker_reward_test protocols ; baker_stresstest protocols ;