From 3cfa14e6306da9a2bbda37a48b814a0cf4507535 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Mon, 28 Oct 2024 10:04:53 +0100 Subject: [PATCH 1/3] Proto/Compiler: prepare alternative map of protocol hashes --- manifest/product_octez.ml | 10 +++++++++ src/lib_protocol_compiler/compiler.ml | 5 ++++- src/lib_protocol_compiler/dune | 4 +++- src/lib_protocol_compiler/hashes/dune | 12 +++++++++++ .../hashes/protocol_hash_representative.ml | 21 +++++++++++++++++++ src/lib_protocol_updater/dune | 1 + src/lib_shell/dune | 1 + src/lib_shell/node.ml | 9 +++++++- 8 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 src/lib_protocol_compiler/hashes/dune create mode 100644 src/lib_protocol_compiler/hashes/protocol_hash_representative.ml diff --git a/manifest/product_octez.ml b/manifest/product_octez.ml index 2a0735b604d5..007ab3034095 100644 --- a/manifest/product_octez.ml +++ b/manifest/product_octez.ml @@ -3370,6 +3370,13 @@ let octez_protocol_compiler_registerer = [octez_base |> open_ ~m:"TzPervasives"; tezos_protocol_environment_sigs] ~flags:(Flags.standard ~opaque:true ()) +let octez_protocol_compiler_alternative_hashes = + public_lib + "octez-protocol-compiler.alternative-hashes" + ~path:"src/lib_protocol_compiler/hashes" + ~internal_name:"octez_protocol_alternative_hashes" + ~deps:[octez_base |> open_ ~m:"TzPervasives"] + let octez_protocol_compiler_compat = public_lib "octez-protocol-compiler-compat" @@ -3434,6 +3441,7 @@ let octez_protocol_compiler_lib = tezos_protocol_environment_sigs; octez_stdlib_unix |> open_; octez_protocol_compiler_compat |> open_; + octez_protocol_compiler_alternative_hashes |> open_; lwt_unix; ocplib_ocamlres; unix; @@ -3547,6 +3555,7 @@ let octez_protocol_updater = octez_micheline |> open_; octez_shell_services |> open_; octez_protocol_environment; + octez_protocol_compiler_alternative_hashes; octez_protocol_compiler_registerer; octez_protocol_compiler_native; octez_context_ops |> open_; @@ -3795,6 +3804,7 @@ let octez_shell = octez_stdlib_unix |> open_; octez_shell_services |> open_; octez_p2p_services |> open_; + octez_protocol_compiler_alternative_hashes; octez_protocol_updater |> open_; octez_requester |> open_; octez_workers |> open_; diff --git a/src/lib_protocol_compiler/compiler.ml b/src/lib_protocol_compiler/compiler.ml index d1443433c91a..c0e1422664a0 100644 --- a/src/lib_protocol_compiler/compiler.ml +++ b/src/lib_protocol_compiler/compiler.ml @@ -217,7 +217,10 @@ let main {compile_ml; pack_objects; link_shared} version = | None -> computed_hash | Some stored_hash when !check_protocol_hash - && not (Protocol_hash.equal computed_hash stored_hash) -> + && not + (Protocol_hash_representative.equivalent + computed_hash + stored_hash) -> Format.eprintf "Inconsistent hash for protocol in TEZOS_PROTOCOL.@\n\ Computed hash: %a@\n\ diff --git a/src/lib_protocol_compiler/dune b/src/lib_protocol_compiler/dune index 7b2a58224a31..13189adf3235 100644 --- a/src/lib_protocol_compiler/dune +++ b/src/lib_protocol_compiler/dune @@ -11,6 +11,7 @@ octez-proto-libs.protocol-environment.sigs octez-libs.stdlib-unix octez-protocol-compiler-compat + octez-protocol-compiler.alternative-hashes lwt.unix ocplib-ocamlres unix) @@ -19,7 +20,8 @@ -open Tezos_base.TzPervasives -open Tezos_base_unix -open Tezos_stdlib_unix - -open Octez_protocol_compiler_compat) + -open Octez_protocol_compiler_compat + -open Octez_protocol_alternative_hashes) (modules Embedded_cmis_env Embedded_cmis_register Packer Compiler Defaults)) (rule diff --git a/src/lib_protocol_compiler/hashes/dune b/src/lib_protocol_compiler/hashes/dune new file mode 100644 index 000000000000..29da01b71745 --- /dev/null +++ b/src/lib_protocol_compiler/hashes/dune @@ -0,0 +1,12 @@ +; This file was automatically generated, do not edit. +; Edit file manifest/main.ml instead. + +(library + (name octez_protocol_alternative_hashes) + (public_name octez-protocol-compiler.alternative-hashes) + (instrumentation (backend bisect_ppx)) + (libraries + octez-libs.base) + (flags + (:standard) + -open Tezos_base.TzPervasives)) diff --git a/src/lib_protocol_compiler/hashes/protocol_hash_representative.ml b/src/lib_protocol_compiler/hashes/protocol_hash_representative.ml new file mode 100644 index 000000000000..1fc662c307e5 --- /dev/null +++ b/src/lib_protocol_compiler/hashes/protocol_hash_representative.ml @@ -0,0 +1,21 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2024 Nomadic Labs *) +(* *) +(*****************************************************************************) + +(** Maps protocol hashes to their representative. *) +module Representatives = Map.Make (Protocol_hash) + +let representatives = Representatives.empty + +(* Resolve is not transitive on purpose: there is no reason a protocol hash is + represented by another protocol hash that has itself a representative. *) +let resolve_repr hash = + Representatives.find_opt hash representatives |> Option.value ~default:hash + +let equivalent protocol_hash protocol_hash' = + let protocol_hash = resolve_repr protocol_hash in + let protocol_hash' = resolve_repr protocol_hash' in + Protocol_hash.equal protocol_hash protocol_hash' diff --git a/src/lib_protocol_updater/dune b/src/lib_protocol_updater/dune index ed4be641b5f5..1d331d59571d 100644 --- a/src/lib_protocol_updater/dune +++ b/src/lib_protocol_updater/dune @@ -11,6 +11,7 @@ octez-libs.micheline octez-shell-libs.shell-services octez-proto-libs.protocol-environment + octez-protocol-compiler.alternative-hashes octez-protocol-compiler.registerer octez-protocol-compiler.native octez-shell-libs.context-ops diff --git a/src/lib_shell/dune b/src/lib_shell/dune index 116e52376211..649976b8743a 100644 --- a/src/lib_shell/dune +++ b/src/lib_shell/dune @@ -20,6 +20,7 @@ octez-libs.stdlib-unix octez-shell-libs.shell-services octez-libs.tezos-p2p-services + octez-protocol-compiler.alternative-hashes octez-shell-libs.protocol-updater octez-shell-libs.requester octez-libs.tezos-workers diff --git a/src/lib_shell/node.ml b/src/lib_shell/node.ml index dd68e35915eb..bf9cfe333207 100644 --- a/src/lib_shell/node.ml +++ b/src/lib_shell/node.ml @@ -165,7 +165,14 @@ let store_known_protocols store = | None -> Node_event.(emit store_protocol_missing_files) protocol_hash | Some protocol -> ( let hash = Protocol.hash protocol in - if not (Protocol_hash.equal hash protocol_hash) then + if + not + (Octez_protocol_alternative_hashes + .Protocol_hash_representative + .equivalent + hash + protocol_hash) + then if List.mem ~equal:Protocol_hash.equal -- GitLab From 4b2ed42c972183f6652098f7c7f137a51f384c61 Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Mon, 28 Oct 2024 11:33:24 +0100 Subject: [PATCH 2/3] Proto/Compiler: slightly modifiy compiler output Since protocols can have a different hash, simply add it in the output if it is the case. --- src/lib_protocol_compiler/compiler.ml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/lib_protocol_compiler/compiler.ml b/src/lib_protocol_compiler/compiler.ml index c0e1422664a0..10f5dbb4c095 100644 --- a/src/lib_protocol_compiler/compiler.ml +++ b/src/lib_protocol_compiler/compiler.ml @@ -137,6 +137,18 @@ type driver = { let parse_options errflag s = Option.iter Location.(prerr_alert none) (Warnings.parse_options errflag s) +let pp_protocol_hash_result ppf (computed_hash, stored_hash_opt) = + match stored_hash_opt with + | Some stored_hash when not (Protocol_hash.equal stored_hash computed_hash) -> + Format.fprintf + ppf + "%a as %a" + Protocol_hash.pp + stored_hash + Protocol_hash.pp + computed_hash + | None | Some _ -> Protocol_hash.pp ppf computed_hash + let main {compile_ml; pack_objects; link_shared} version = Random.self_init () ; parse_options false default_warnings ; @@ -310,4 +322,7 @@ let main {compile_ml; pack_objects; link_shared} version = Format.printf "let src_digest = %S ;;\n" (Digest.to_hex dsrc) ; Format.printf "let impl_digest = %S ;;\n" (Digest.to_hex dimpl) ; Format.printf "let intf_digest = %S ;;\n" (Digest.to_hex dintf)) ; - Format.printf "Success: %a.@." Protocol_hash.pp hash + Format.printf + "Success: %a.@." + pp_protocol_hash_result + (computed_hash, stored_hash_opt) -- GitLab From 6198e3b09ceeb2b6a5b5c9c5d8403d03b4181d9c Mon Sep 17 00:00:00 2001 From: Pierrick Couderc Date: Mon, 4 Nov 2024 13:06:18 +0100 Subject: [PATCH 3/3] Tezt: test protocol_compiler alternative hashes --- .../hashes/protocol_hash_representative.ml | 13 +++- tezt/lib_tezos/protocol_compiler.ml | 9 ++- tezt/tests/injection.ml | 72 ++++++++++++++++++- 3 files changed, 89 insertions(+), 5 deletions(-) diff --git a/src/lib_protocol_compiler/hashes/protocol_hash_representative.ml b/src/lib_protocol_compiler/hashes/protocol_hash_representative.ml index 1fc662c307e5..e09093fd1260 100644 --- a/src/lib_protocol_compiler/hashes/protocol_hash_representative.ml +++ b/src/lib_protocol_compiler/hashes/protocol_hash_representative.ml @@ -8,7 +8,18 @@ (** Maps protocol hashes to their representative. *) module Representatives = Map.Make (Protocol_hash) -let representatives = Representatives.empty +let register_protocol_hash computed_hash declared_hash map = + Representatives.add + (Protocol_hash.of_b58check_exn computed_hash) + (Protocol_hash.of_b58check_exn declared_hash) + map + +let representatives = + Representatives.empty + (* Protocol used in tests only, see `tezt/test/injection.ml`. *) + |> register_protocol_hash + "Pry4stD6qN1ZagUX6YCHvMxA1xvSjARkdt8bhs86j74JGLoLDKN" + "Ps8MVx2JuQaFrXpbuwSeBvXmi1xraGmXuJZHULEbPBY7mzFchxz" (* Resolve is not transitive on purpose: there is no reason a protocol hash is represented by another protocol hash that has itself a representative. *) diff --git a/tezt/lib_tezos/protocol_compiler.ml b/tezt/lib_tezos/protocol_compiler.ml index f0901efd4f59..0fee105bc50f 100644 --- a/tezt/lib_tezos/protocol_compiler.ml +++ b/tezt/lib_tezos/protocol_compiler.ml @@ -40,7 +40,10 @@ let compile ?path ?hooks ?(hash_only = false) proto_dir = in if hash_only then return (String.trim output) else - match output =~* rex "Success: ([a-zA-Z0-9]+)" with - | Some hash -> return hash - | None -> + match + ( output =~* rex "Success: ([a-zA-Z0-9]+)", + output =~** rex "Success: ([a-zA-Z0-9]+) as ([a-zA-Z0-9]+)" ) + with + | Some hash, _ | _, Some (hash, _) -> return hash + | None, None -> Test.fail "Couldn't parse output from compiling %s: %s" proto_dir output diff --git a/tezt/tests/injection.ml b/tezt/tests/injection.ml index afc35a9dd317..adbe7344d63f 100644 --- a/tezt/tests/injection.ml +++ b/tezt/tests/injection.ml @@ -207,6 +207,76 @@ let test_activation () : unit = unit +let test_alternative_protocol_hash = + Test.register + ~__FILE__ + ~title:"Protocol is compiled with another expected hash" + ~tags:[team; "protocol"; "compilation"; "hash"] + ~uses:[Constant.octez_protocol_compiler] + @@ fun () -> + (* First, prepare a new protocol. *) + let protocol_desc_file = "TEZOS_PROTOCOL" in + let protocol_desc = + `O [("modules", `A [`String "Main"]); ("expected_env_version", `Float 13.)] + |> JSON.annotate ~origin:"" + in + let protocol_main_file = "main.ml" in + let protocol_main = "(* This is a dummy protocol *)" in + + (* And write it in a temporary folder. *) + let protocol = Tezt.Temp.dir "protocol" in + JSON.encode_to_file + (Filename.concat protocol protocol_desc_file) + protocol_desc ; + Base.write_file + (Filename.concat protocol protocol_main_file) + ~contents:protocol_main ; + + (* Let's hash the protocol to fill the `TEZOS_PROTOCOL` file. *) + let* protocol_hash = Protocol_compiler.compile ~hash_only:true protocol in + let protocol_desc_patched = + JSON.( + put ("hash", annotate ~origin:"" (`String protocol_hash)) protocol_desc) + in + JSON.encode_to_file + (Filename.concat protocol protocol_desc_file) + protocol_desc_patched ; + + (* Now we can start patching the files so that we produce different hashes. *) + (* This one should be rejected by the protocol compiler, as the hash it will + produce is not registered. *) + let patched_protocol_main = + protocol_main ^ "(* This hash will be rejected. *)" + in + Base.write_file + (Filename.concat protocol protocol_main_file) + ~contents:patched_protocol_main ; + + (* Then compile it and see it fail as the hash is inconsistent. *) + let failing_compilation_process = Protocol_compiler.spawn_compile protocol in + let* () = Process.check ~expect_failure:true failing_compilation_process in + + (* This time, let's patch it so that it produces a hash we are aware of: + `Pry4stD6qN1ZagUX6YCHvMxA1xvSjARkdt8bhs86j74JGLoLDKN`. See + `src/lib_protocol_compiler/hashes/protocol_hash_representatives.ml`. *) + let patched_protocol_main = + protocol_main + ^ "(* This is a harmless comment that will change the protocol hash. *)" + in + Base.write_file + (Filename.concat protocol protocol_main_file) + ~contents:patched_protocol_main ; + + (* This time, the compilation will succeed and the resulting hash will be the + one declared in `TEZOS_PROTOCOL`. *) + let* hash = Protocol_compiler.compile protocol in + Check.( + (hash = protocol_hash) + string + ~error_msg:"Protocol hash expected was %R, but got %L") ; + unit + let register_protocol_independent () = test_injection_and_activation () ; - test_activation () + test_activation () ; + test_alternative_protocol_hash -- GitLab