From 8ab8305f10dee4e11551f3708b23a2d3a797d081 Mon Sep 17 00:00:00 2001 From: Albin Coquereau Date: Fri, 18 Jul 2025 12:22:00 +0200 Subject: [PATCH 1/4] Tezt/lib_tezos: add signer check_highwatermark option and base_dir function --- tezt/lib_tezos/signer.ml | 20 ++++++++++++++++---- tezt/lib_tezos/signer.mli | 7 +++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/tezt/lib_tezos/signer.ml b/tezt/lib_tezos/signer.ml index a3af417eaed1..f1cb1e8f07d3 100644 --- a/tezt/lib_tezos/signer.ml +++ b/tezt/lib_tezos/signer.ml @@ -33,6 +33,7 @@ module Parameters = struct launch_mode : launch_mode option; keys : Account.key list; magic_byte : string option; + check_highwatermark : bool; allow_list_known_keys : bool; allow_to_prove_possession : bool; mutable pending_ready : unit option Lwt.u list; @@ -65,6 +66,8 @@ include Daemon.Make (Parameters) let uri signer = signer.persistent_state.uri +let base_dir signer = signer.persistent_state.base_dir + let trigger_ready signer value = let pending = signer.persistent_state.pending_ready in signer.persistent_state.pending_ready <- [] ; @@ -112,7 +115,7 @@ let import_secret_key signer (key : Account.key) = spawn_import_secret_key signer key |> Process.check let create ?name ?color ?event_pipe ?base_dir ?launch_mode ?uri ?runner - ?magic_byte ?(allow_list_known_keys = false) + ?(check_highwatermark = true) ?magic_byte ?(allow_list_known_keys = false) ?(allow_to_prove_possession = false) ?(keys = [Constant.bootstrap1]) () = let name = match name with None -> fresh_name () | Some name -> name in let base_dir = @@ -141,6 +144,7 @@ let create ?name ?color ?event_pipe ?base_dir ?launch_mode ?uri ?runner uri; keys; pending_ready = []; + check_highwatermark; magic_byte; allow_list_known_keys; allow_to_prove_possession; @@ -178,6 +182,11 @@ let run signer = in ["launch"; "local"; "signer"] @ socket_args in + let check_highwatermark_args = + if signer.persistent_state.check_highwatermark then + ["--check-high-watermark"] + else [] + in let magic_bytes_args = match signer.persistent_state.magic_byte with | None -> [] @@ -194,8 +203,9 @@ let run signer = else [] in let arguments = - base_dir_arg @ launch_mode_args @ magic_bytes_args - @ allow_list_known_keys_args @ allow_to_prove_possession_args + base_dir_arg @ launch_mode_args @ check_highwatermark_args + @ magic_bytes_args @ allow_list_known_keys_args + @ allow_to_prove_possession_args in let arguments = if !passfile = "" then arguments @@ -226,7 +236,8 @@ let wait_for_ready signer = check_event signer "Signer started." promise let init ?name ?color ?event_pipe ?base_dir ?launch_mode ?uri ?runner ?keys - ?magic_byte ?allow_list_known_keys ?allow_to_prove_possession () = + ?check_highwatermark ?magic_byte ?allow_list_known_keys + ?allow_to_prove_possession () = let* signer = create ?name @@ -237,6 +248,7 @@ let init ?name ?color ?event_pipe ?base_dir ?launch_mode ?uri ?runner ?keys ?uri ?runner ?keys + ?check_highwatermark ?magic_byte ?allow_list_known_keys ?allow_to_prove_possession diff --git a/tezt/lib_tezos/signer.mli b/tezt/lib_tezos/signer.mli index 716ed41b22bd..6a1f5094a9de 100644 --- a/tezt/lib_tezos/signer.mli +++ b/tezt/lib_tezos/signer.mli @@ -61,6 +61,7 @@ val init : ?uri:Uri.t -> ?runner:Runner.t -> ?keys:Account.key list -> + ?check_highwatermark:bool -> ?magic_byte:string -> ?allow_list_known_keys:bool -> ?allow_to_prove_possession:bool -> @@ -109,3 +110,9 @@ val restart : t -> unit Lwt.t (* Get the [uri] that was passed to [init]. *) val uri : t -> Uri.t + +(** Get the base directory of a signer. + + The base directory is the location where signers store their keys, logs and + highwatermarks. It corresponds to the [--base-dir] option. *) +val base_dir : t -> string -- GitLab From ed8a87ede64abb043d34c08856fc6a940c26cc6b Mon Sep 17 00:00:00 2001 From: Albin Coquereau Date: Fri, 18 Jul 2025 12:22:35 +0200 Subject: [PATCH 2/4] Tezt/tests: add a test that check highwatermark value for the signer --- tezt/tests/signer_test.ml | 121 +++++++++++++++++++++++++++++++++++++- 1 file changed, 120 insertions(+), 1 deletion(-) diff --git a/tezt/tests/signer_test.ml b/tezt/tests/signer_test.ml index 37b2983a4e21..5dbe8f5f6062 100644 --- a/tezt/tests/signer_test.ml +++ b/tezt/tests/signer_test.ml @@ -272,10 +272,129 @@ let signer_prove_possession_test = in Process.check process +let signer_highwatermark_test = + register_signer_test + ~__FILE__ + ~title:"Check highwatermark consistency" + ~tags:[team; "signer"; "highwatermark"] + ~uses:(fun _ -> [Constant.octez_signer]) + ~supports:Protocol.(From_protocol (number S023)) + @@ fun launch_mode protocol -> + let consensus_rights_delay = 1 in + let consensus_committee_size = 256 in + let* parameter_file = + Protocol.write_parameter_file + ~base:(Right (protocol, None)) + [ + (["consensus_committee_size"], `Int consensus_committee_size); + (["consensus_threshold_size"], `Int 200); + (["minimal_block_delay"], `String "2"); + (["delay_increment_per_round"], `String "0"); + (["blocks_per_cycle"], `Int 2); + (["nonce_revelation_threshold"], `Int 1); + (["consensus_rights_delay"], `Int consensus_rights_delay); + (["cache_sampler_state_cycles"], `Int (consensus_rights_delay + 3)); + (["cache_stake_distribution_cycles"], `Int (consensus_rights_delay + 3)); + ] + in + let* node, client = + Client.init_with_protocol + `Client + ~protocol + ~parameter_file + ~timestamp:Now + () + in + let* consensus_key1 = Client.gen_and_show_keys ~sig_alg:"p256" client in + let keys = [Constant.tz4_account; consensus_key1] in + let* signer = + Signer.init + ~launch_mode + ~keys + ~check_highwatermark:true + ~allow_to_prove_possession:true + () + in + let* () = + Client.import_signer_key + ~force:true + ~alias:Constant.tz4_account.alias + client + ~signer:(Signer.uri signer) + ~public_key_hash:Constant.tz4_account.public_key_hash + in + let* () = + Client.update_consensus_key + ~src:Constant.bootstrap1.alias + ~pk:Constant.tz4_account.alias + client + in + let* () = + Client.import_signer_key + ~force:true + ~alias:consensus_key1.alias + client + ~signer:(Signer.uri signer) + ~public_key_hash:consensus_key1.public_key_hash + in + let* () = + Client.update_consensus_key + ~src:Constant.bootstrap2.alias + ~pk:consensus_key1.alias + client + in + let keys = + List.map + (fun (account : Account.key) -> account.public_key_hash) + [ + consensus_key1; + Constant.tz4_account; + Constant.bootstrap1; + Constant.bootstrap2; + Constant.bootstrap3; + Constant.bootstrap4; + Constant.bootstrap5; + ] + in + Log.info "Bake until BLS consensus keys are activated" ; + let* _ = Client.bake_for_and_wait ~keys ~count:8 client in + + Log.info "Preattest with all the keys to update the highwatermarks" ; + let* _ = Client.preattest_for ~key:keys client in + + let* current_lvl = Node.get_level node in + let base_dir = Signer.base_dir signer in + let preattestation_highwatermarks = + JSON.parse_file (base_dir // "preattestation_high_watermarks") + in + let attestation_highwatermarks = + JSON.parse_file (base_dir // "attestation_high_watermarks") + in + let check_highwatermark pkh lvl json = + let u = JSON.unannotate json in + match u with + | `O [(_, x)] -> + let x = JSON.annotate ~origin:"" x in + let level = JSON.(x |-> pkh |-> "level" |> as_int) in + Check.( + (lvl = level) + int + ~error_msg:"Highwatermark Level expected was %L, got %R") + | _ -> assert false + in + Log.info "Check that highwatermark level are correct for the signer keys" ; + List.iter + (fun pkh -> + check_highwatermark pkh current_lvl preattestation_highwatermarks ; + check_highwatermark pkh (current_lvl - 1) attestation_highwatermarks) + [consensus_key1.public_key_hash; Constant.tz4_account.public_key_hash] ; + unit + let register ~protocols = signer_simple_test protocols ; signer_magic_bytes_test protocols ; signer_bls_test protocols ; signer_known_remote_keys_test protocols ; signer_prove_possession_test - (List.filter (fun p -> Protocol.number p > 022) protocols) + (List.filter (fun p -> Protocol.number p > 022) protocols) ; + signer_highwatermark_test protocols -- GitLab From b081650104a73e615f0e5cf869fbf8a2e8f11658 Mon Sep 17 00:00:00 2001 From: Albin Coquereau Date: Fri, 18 Jul 2025 10:01:31 +0200 Subject: [PATCH 3/4] Signer: fix tz4 highwatermark --- src/bin_signer/handler.ml | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/bin_signer/handler.ml b/src/bin_signer/handler.ml index 307b39eec9e4..027f72a388a6 100644 --- a/src/bin_signer/handler.ml +++ b/src/bin_signer/handler.ml @@ -67,11 +67,19 @@ module High_watermark = struct "Failed to retrieve level and round of a Tenderbake block: %s" (Printexc.to_string exn) - let get_level_and_round_for_tenderbake_attestation bytes = + let get_level_and_round_for_tenderbake_attestation + (pkh : Signature.public_key_hash) bytes = (* ... *) let open Lwt_result_syntax in try - let level_offset = 1 + 4 + 32 + 1 + 2 in + let level_offset = + match pkh with + | Bls _ -> + (* Slot is not part of the signed payload when + signing with a tz4 address *) + 1 + 4 + 32 + 1 + | Ed25519 _ | Secp256k1 _ | P256 _ -> 1 + 4 + 32 + 1 + 2 + in let level = Bytes.get_int32_be bytes level_offset in let round = Bytes.get_int32_be bytes (level_offset + 4) in return (level, Some round) @@ -215,11 +223,11 @@ module High_watermark = struct | 0x12 -> (* tenderbake preattestation *) mark "a" "preattestation" (fun () -> - get_level_and_round_for_tenderbake_attestation bytes) + get_level_and_round_for_tenderbake_attestation pkh bytes) | 0x13 -> (* tenderbake attestation *) mark "an" "attestation" (fun () -> - get_level_and_round_for_tenderbake_attestation bytes) + get_level_and_round_for_tenderbake_attestation pkh bytes) | _ -> sign bytes end -- GitLab From fdc1ece7019a9ece12430265d163a64a2bd7539e Mon Sep 17 00:00:00 2001 From: Albin Coquereau Date: Fri, 18 Jul 2025 14:54:30 +0200 Subject: [PATCH 4/4] Tezt/tests: add checks for signer highwatermark values --- tezt/tests/signer_test.ml | 68 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 66 insertions(+), 2 deletions(-) diff --git a/tezt/tests/signer_test.ml b/tezt/tests/signer_test.ml index 5dbe8f5f6062..701696207ce0 100644 --- a/tezt/tests/signer_test.ml +++ b/tezt/tests/signer_test.ml @@ -364,11 +364,17 @@ let signer_highwatermark_test = let* current_lvl = Node.get_level node in let base_dir = Signer.base_dir signer in + let preattestation_highwatermarks_file = + base_dir // "preattestation_high_watermarks" + in let preattestation_highwatermarks = - JSON.parse_file (base_dir // "preattestation_high_watermarks") + JSON.parse_file preattestation_highwatermarks_file + in + let attestation_highwatermarks_file = + base_dir // "attestation_high_watermarks" in let attestation_highwatermarks = - JSON.parse_file (base_dir // "attestation_high_watermarks") + JSON.parse_file attestation_highwatermarks_file in let check_highwatermark pkh lvl json = let u = JSON.unannotate json in @@ -388,6 +394,64 @@ let signer_highwatermark_test = check_highwatermark pkh current_lvl preattestation_highwatermarks ; check_highwatermark pkh (current_lvl - 1) attestation_highwatermarks) [consensus_key1.public_key_hash; Constant.tz4_account.public_key_hash] ; + + Log.info "Rewrite highwatermarks level to a higher value" ; + let* _ = Client.bake_for_and_wait ~keys ~count:2 client in + let reset_highwatermark_level file highwatermarks level = + let highwatermarks_s = JSON.encode highwatermarks in + let contents = + Re.replace_string + (Re.compile (Re.Perl.re (sf {|"level": %d|} level))) + ~by:{|"level": 100|} + highwatermarks_s + in + write_file file ~contents + in + let () = + reset_highwatermark_level + preattestation_highwatermarks_file + preattestation_highwatermarks + current_lvl ; + reset_highwatermark_level + attestation_highwatermarks_file + attestation_highwatermarks + (current_lvl - 1) + in + + Log.info + "Check that signing a preattestation with a lower level than the \ + highwatermark fails" ; + let preattest = + Client.spawn_preattest_for ~key:[consensus_key1.public_key_hash] client + in + let* stdout = Process.check_and_read_stdout preattest in + let* () = + if + stdout + =~! rex {|preattestation level ([\d]+) below high watermark ([\d]+)|} + then unit + else + Test.fail + "The preattest call should have returned a level below high watermark \ + error" + in + + Log.info + "Check that signing an attestation with a lower level than the \ + highwatermark fails" ; + let attest = + Client.spawn_attest_for ~key:[Constant.tz4_account.public_key_hash] client + in + let* stdout = Process.check_and_read_stdout attest in + let* () = + if stdout =~! rex {|attestation level ([\d]+) below high watermark ([\d]+)|} + then unit + else + Test.fail + "The attest call should have returned a level below high watermark \ + error" + in + unit let register ~protocols = -- GitLab