diff --git a/src/bin_signer/handler.ml b/src/bin_signer/handler.ml index 307b39eec9e49b9de1e0ffa8649bee20a8e150ba..027f72a388a6ca20e15d3aa934534d6b09f38f5f 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 diff --git a/tezt/lib_tezos/signer.ml b/tezt/lib_tezos/signer.ml index a3af417eaed1f71dbd5608ea2efba37e748f7174..f1cb1e8f07d3c2d2a4e1dacd1cecdebc500dbed5 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 716ed41b22bd46dc086bef7affcc988e03430b6a..6a1f5094a9de328614cf9f20c5f6dd5f5b803fcf 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 diff --git a/tezt/tests/signer_test.ml b/tezt/tests/signer_test.ml index 37b2983a4e21ad7cf129c4e47f3e1a487b8a6b00..701696207ce015cfbe8d1ee583d660f6b652a0e0 100644 --- a/tezt/tests/signer_test.ml +++ b/tezt/tests/signer_test.ml @@ -272,10 +272,193 @@ 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_file = + base_dir // "preattestation_high_watermarks" + in + let preattestation_highwatermarks = + JSON.parse_file preattestation_highwatermarks_file + in + let attestation_highwatermarks_file = + base_dir // "attestation_high_watermarks" + in + let attestation_highwatermarks = + JSON.parse_file attestation_highwatermarks_file + 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] ; + + 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 = 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