• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

MinaProtocol / mina / 693

15 Oct 2025 02:40PM UTC coverage: 37.537% (+0.4%) from 37.108%
693

push

buildkite

web-flow
Merge pull request #17947 from MinaProtocol/dkijania/merge/compatible_to_develop_251015

merge compatible to develop 251015

2 of 53 new or added lines in 6 files covered. (3.77%)

43 existing lines in 10 files now uncovered.

27336 of 72824 relevant lines covered (37.54%)

36483.0 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

25.67
/src/lib/cli_lib/commands.ml
1
open Signature_lib
119✔
2

3
(* Alias ocamlp-streams before it's hidden by open
4
   Core_kernel *)
5
module Streams = Stream
6
open Core_kernel
7
open Async
8

9
let generate_keypair =
10
  Command.async ~summary:"Generate a new public, private keypair"
119✔
11
    (let%map_open.Command privkey_path = Flag.privkey_write_path in
12
     Exceptions.handle_nicely
×
13
     @@ fun () ->
14
     let env = Secrets.Keypair.env in
×
15
     if Option.is_some (Sys.getenv env) then
×
16
       eprintf "Using password from environment variable %s\n" env ;
×
17
     let kp = Keypair.create () in
×
18
     let%bind () = Secrets.Keypair.Terminal_stdin.write_exn kp ~privkey_path in
×
19
     printf "Keypair generated\nPublic key: %s\nRaw public key: %s\n"
×
20
       ( kp.public_key |> Public_key.compress
×
21
       |> Public_key.Compressed.to_base58_check )
×
22
       (Rosetta_coding.Coding.of_public_key kp.public_key) ;
×
23
     exit 0 )
×
24

25
let balance =
26
  Command.Arg_type.map Command.Param.string
119✔
27
    ~f:Currency.Balance.of_mina_string_exn
28

29
let generate_test_ledger =
30
  Command.async ~summary:"Generate a ledger for testing"
119✔
31
    (let open Command.Let_syntax in
32
    let%map_open n =
33
      Command.Param.flag "-n"
119✔
34
        ~doc:(Printf.sprintf "NN number of accounts to generate")
119✔
35
        (required int)
119✔
36
    and min_balance =
37
      flag "--min-balance" ~doc:"MINA Minimum balance of a key"
119✔
38
        (optional balance)
119✔
39
    and max_balance =
40
      flag "--max-balance" ~doc:"MINA Maximum balance of a key"
119✔
41
        (optional balance)
119✔
42
    in
43
    Exceptions.handle_nicely
2✔
44
    @@ fun () ->
45
    let min_balance = Option.value ~default:Currency.Balance.zero min_balance in
2✔
46
    let max_balance =
2✔
47
      Option.value ~default:(Currency.Balance.of_mina_int_exn 100) max_balance
2✔
48
    in
49
    let balance_seq =
2✔
50
      Quickcheck.random_sequence ~seed:`Nondeterministic
51
      @@ Currency.Balance.gen_incl min_balance max_balance
2✔
52
    in
53
    let ledger =
2✔
54
      Sequence.take balance_seq n
2✔
55
      |> Sequence.map ~f:(fun balance ->
2✔
56
             let kp = Keypair.create () in
40,000✔
57
             { Runtime_config.Json_layout.Accounts.Single.default with
40,000✔
58
               pk =
59
                 Public_key.compress kp.public_key
40,000✔
60
                 |> Public_key.Compressed.to_base58_check
40,000✔
61
             ; sk = Some (Private_key.to_base58_check kp.private_key)
40,000✔
62
             ; balance
63
             } )
64
      |> Sequence.to_list
65
    in
66
    Yojson.Safe.pretty_print Format.std_formatter
2✔
67
    @@ Runtime_config.Json_layout.Accounts.to_yojson ledger ;
2✔
68
    exit 0)
2✔
69

70
let validate_keypair =
71
  Command.async ~summary:"Validate a public, private keypair"
119✔
72
    (let open Command.Let_syntax in
73
    let open Core_kernel in
74
    let%map_open privkey_path = Flag.privkey_write_path
75
    and signature_kind = Flag.signature_kind in
UNCOV
76
    Exceptions.handle_nicely
×
77
    @@ fun () ->
78
    let read_pk () =
×
79
      let pubkey_path = privkey_path ^ ".pub" in
×
80
      try
81
        In_channel.with_file pubkey_path ~f:(fun in_channel ->
×
82
            match In_channel.input_line in_channel with
×
83
            | Some line -> (
×
84
                try Public_key.Compressed.of_base58_check_exn line
×
85
                with _exn ->
×
86
                  eprintf
87
                    "Could not create public key in file %s from text: %s\n"
88
                    pubkey_path line ;
89
                  exit 1 )
×
90
            | None ->
×
91
                eprintf "No public key found in file %s\n" pubkey_path ;
92
                exit 1 )
×
93
      with exn ->
×
94
        eprintf "Could not read public key file %s, error: %s\n" pubkey_path
95
          (Exn.to_string exn) ;
×
96
        exit 1
×
97
    in
98
    let compare_public_keys ~pk_from_disk ~pk_from_keypair =
99
      if Public_key.Compressed.equal pk_from_disk pk_from_keypair then
×
100
        printf "Public key on-disk is derivable from private key\n"
×
101
      else (
×
102
        eprintf
103
          "Public key read from disk %s different than public key %s derived \
104
           from private key\n"
105
          (Public_key.Compressed.to_base58_check pk_from_disk)
×
106
          (Public_key.Compressed.to_base58_check pk_from_keypair) ;
×
107
        exit 1 )
×
108
    in
109
    let validate_transaction keypair =
110
      let dummy_payload = Mina_base.Signed_command_payload.dummy in
×
111
      let signature =
112
        Mina_base.Signed_command.sign_payload ~signature_kind
113
          keypair.Keypair.private_key dummy_payload
114
      in
115
      let message = Mina_base.Signed_command.to_input_legacy dummy_payload in
×
116
      let verified =
×
117
        Schnorr.Legacy.verify ~signature_kind signature
118
          (Snark_params.Tick.Inner_curve.of_affine keypair.public_key)
×
119
          message
120
      in
121
      if verified then printf "Verified a transaction using specified keypair\n"
×
122
      else (
×
123
        eprintf "Failed to verify a transaction using the specific keypair\n" ;
124
        exit 1 )
×
125
    in
126
    let open Deferred.Let_syntax in
127
    let%bind () =
128
      let password =
129
        lazy (Secrets.Keypair.Terminal_stdin.prompt_password "Enter password: ")
×
130
      in
131
      match%map Secrets.Keypair.read ~privkey_path ~password with
132
      | Ok keypair ->
×
133
          let pk_from_disk = read_pk () in
134
          compare_public_keys ~pk_from_disk
×
135
            ~pk_from_keypair:(keypair.public_key |> Public_key.compress) ;
×
136
          validate_transaction keypair
137
      | Error err ->
×
138
          eprintf "Could not read the specified keypair: %s\n"
139
            (Secrets.Privkey_error.to_string err) ;
×
140
          exit 1
×
141
    in
142
    exit 0)
×
143

144
let validate_transaction =
145
  Command.async
119✔
146
    ~summary:
147
      "Validate the signature on one or more transactions, provided to stdin \
148
       in rosetta format"
149
    (let open Command.Let_syntax in
150
    let%map signature_kind = Flag.signature_kind in
151
    fun () ->
NEW
152
      let num_fails = ref 0 in
×
153
      (* TODO upgrade to yojson 2.0.0 when possible to use seq_from_channel
154
       * instead of the deprecated stream interface *)
155
      let jsons = Yojson.Safe.stream_from_channel In_channel.stdin in
NEW
156
      ( match
×
157
          Or_error.try_with (fun () ->
NEW
158
              Streams.iter
×
159
                (fun transaction_json ->
NEW
160
                  match
×
161
                    Rosetta_lib.Transaction.to_mina_signed transaction_json
162
                  with
NEW
163
                  | Ok cmd ->
×
164
                      if
165
                        Mina_base.Signed_command.check_signature ~signature_kind
166
                          cmd
NEW
167
                      then Format.eprintf "Transaction was valid@."
×
NEW
168
                      else (
×
169
                        incr num_fails ;
NEW
170
                        Format.eprintf "Transaction was invalid@." )
×
NEW
171
                  | Error err ->
×
172
                      incr num_fails ;
NEW
173
                      Format.eprintf
×
174
                        "@[<v>Failed to validate transaction:@,\
175
                         %s@,\
176
                         Failed with error:%s@]@."
NEW
177
                        (Yojson.Safe.pretty_to_string transaction_json)
×
NEW
178
                        (Yojson.Safe.pretty_to_string
×
NEW
179
                           (Error_json.error_to_yojson err) ) )
×
180
                jsons )
181
        with
NEW
182
      | Ok () ->
×
183
          ()
NEW
184
      | Error err ->
×
185
          Format.eprintf "@[<v>Error:@,%s@,@]@."
NEW
186
            (Yojson.Safe.pretty_to_string (Error_json.error_to_yojson err)) ;
×
NEW
187
          Format.printf "Invalid transaction.@." ;
×
NEW
188
          Core_kernel.exit 1 ) ;
×
NEW
189
      if !num_fails > 0 then (
×
190
        Format.printf "Some transactions failed to verify@." ;
NEW
191
        exit 1 )
×
192
      else
NEW
193
        let first = Streams.peek jsons in
×
NEW
194
        match first with
×
NEW
195
        | None ->
×
196
            Format.printf "Could not parse any transactions@." ;
NEW
197
            exit 1
×
NEW
198
        | _ ->
×
199
            Format.printf "All transactions were valid@." ;
NEW
200
            exit 0)
×
201

202
module Vrf = struct
203
  let generate_witness =
204
    Command.async
119✔
205
      ~summary:
206
        "Generate a vrf evaluation witness. This may be used to calculate \
207
         whether a given private key will win a given slot (by checking \
208
         threshold_met = true in the JSON output), or to generate a witness \
209
         that a 3rd account_update can use to verify a vrf evaluation."
210
      (let open Command.Let_syntax in
211
      let%map_open privkey_path = Flag.privkey_read_path
212
      and global_slot =
213
        flag "--global-slot" ~doc:"NUM Global slot to evaluate the VRF for"
119✔
214
          (required int)
119✔
215
      and epoch_seed =
216
        flag "--epoch-seed" ~doc:"SEED Epoch seed to evaluate the VRF with"
119✔
217
          (required string)
119✔
218
      and delegator_index =
219
        flag "--delegator-index"
119✔
220
          ~doc:"NUM The index of the delegating account in the epoch ledger"
221
          (required int)
119✔
222
      and generate_outputs =
223
        flag "--generate-outputs"
119✔
224
          ~doc:
225
            "true|false Whether to generate the vrf in addition to the witness \
226
             (default: false)"
227
          (optional_with_default false bool)
119✔
228
      and delegated_stake =
229
        flag "--delegated-stake"
119✔
230
          ~doc:
231
            "AMOUNT The balance of the delegating account in the epoch ledger"
232
          (optional int)
119✔
233
      and total_stake =
234
        flag "--total-stake"
119✔
235
          ~doc:"AMOUNT The total balance of all accounts in the epoch ledger"
236
          (optional int)
119✔
237
      in
238
      Exceptions.handle_nicely
×
239
      @@ fun () ->
240
      let env = Secrets.Keypair.env in
×
241
      let constraint_constants =
242
        Genesis_constants.Compiled.constraint_constants
243
      in
244
      if Option.is_some (Sys.getenv env) then
×
245
        eprintf "Using password from environment variable %s\n" env ;
×
246
      let open Deferred.Let_syntax in
×
247
      (* TODO-someday: constraint constants from config file. *)
248
      let%bind () =
249
        let password =
250
          lazy
251
            (Secrets.Keypair.Terminal_stdin.prompt_password "Enter password: ")
×
252
        in
253
        match%bind Secrets.Keypair.read ~privkey_path ~password with
254
        | Ok keypair ->
×
255
            let open Consensus_vrf.Layout in
256
            let evaluation =
257
              Evaluation.of_message_and_sk ~constraint_constants
258
                { global_slot =
259
                    Mina_numbers.Global_slot_since_hard_fork.of_int global_slot
×
260
                ; epoch_seed =
261
                    Mina_base.Epoch_seed.of_base58_check_exn epoch_seed
×
262
                ; delegator_index
263
                }
264
                keypair.private_key
265
            in
266
            let evaluation =
×
267
              match (delegated_stake, total_stake) with
268
              | Some delegated_stake, Some total_stake ->
×
269
                  { evaluation with
270
                    vrf_threshold =
271
                      Some
272
                        { delegated_stake =
273
                            Currency.Balance.of_nanomina_int_exn delegated_stake
×
274
                        ; total_stake =
275
                            Currency.Amount.of_nanomina_int_exn total_stake
×
276
                        }
277
                  }
278
              | _ ->
×
279
                  evaluation
280
            in
281
            let evaluation =
282
              if generate_outputs then
283
                Evaluation.compute_vrf ~constraint_constants evaluation
×
284
              else evaluation
×
285
            in
286
            Format.printf "%a@."
287
              (Yojson.Safe.pretty_print ?std:None)
288
              (Evaluation.to_yojson evaluation) ;
×
289
            Deferred.return ()
×
290
        | Error err ->
×
291
            eprintf "Could not read the specified keypair: %s\n"
292
              (Secrets.Privkey_error.to_string err) ;
×
293
            exit 1
×
294
      in
295
      exit 0)
×
296

297
  let batch_generate_witness =
298
    Command.async
119✔
299
      ~summary:
300
        "Generate a batch of vrf evaluation witnesses from {\"globalSlot\": _, \
301
         \"epochSeed\": _, \"delegatorIndex\": _} JSON message objects read on \
302
         stdin"
303
      (let open Command.Let_syntax in
304
      let%map_open privkey_path = Flag.privkey_read_path in
305
      Exceptions.handle_nicely
×
306
      @@ fun () ->
307
      let constraint_constants =
×
308
        Genesis_constants.Compiled.constraint_constants
309
      in
310
      let env = Secrets.Keypair.env in
311
      if Option.is_some (Sys.getenv env) then
×
312
        eprintf "Using password from environment variable %s\n" env ;
×
313
      let open Deferred.Let_syntax in
×
314
      (* TODO-someday: constraint constants from config file. *)
315
      let%bind () =
316
        let password =
317
          lazy
318
            (Secrets.Keypair.Terminal_stdin.prompt_password "Enter password: ")
×
319
        in
320
        match%bind Secrets.Keypair.read ~privkey_path ~password with
321
        | Ok keypair ->
×
322
            let lexbuf = Lexing.from_channel In_channel.stdin in
323
            let lexer = Yojson.init_lexer () in
×
324
            Deferred.repeat_until_finished () (fun () ->
×
325
                Deferred.Or_error.try_with ~here:[%here] (fun () ->
×
326
                    try
×
327
                      let message_json =
328
                        Yojson.Safe.from_lexbuf ~stream:true lexer lexbuf
329
                      in
330
                      let open Consensus_vrf.Layout in
×
331
                      let message =
332
                        Result.ok_or_failwith (Message.of_yojson message_json)
×
333
                      in
334
                      let evaluation =
×
335
                        Evaluation.of_message_and_sk ~constraint_constants
336
                          message keypair.private_key
337
                      in
338
                      Format.printf "%a@."
×
339
                        (Yojson.Safe.pretty_print ?std:None)
340
                        (Evaluation.to_yojson evaluation) ;
×
341
                      Deferred.return (`Repeat ())
×
342
                    with Yojson.End_of_input -> return (`Finished ()) )
×
343
                >>| function
344
                | Ok x ->
×
345
                    x
346
                | Error err ->
×
347
                    Format.eprintf "@[<v>Error:@,%s@,@]@."
348
                      (Yojson.Safe.pretty_to_string
×
349
                         (Error_json.error_to_yojson err) ) ;
×
350
                    `Repeat () )
×
351
        | Error err ->
×
352
            eprintf "Could not read the specified keypair: %s\n"
353
              (Secrets.Privkey_error.to_string err) ;
×
354
            exit 1
×
355
      in
356
      exit 0)
×
357

358
  let batch_check_witness =
359
    Command.async
119✔
360
      ~summary:
361
        "Check a batch of vrf evaluation witnesses read on stdin. Outputs the \
362
         verified vrf evaluations (or no vrf output if the witness is \
363
         invalid), and whether the vrf output satisfies the threshold values \
364
         if given. The threshold should be included in the JSON for each vrf \
365
         as the 'vrfThreshold' field, of format {delegatedStake: 1000, \
366
         totalStake: 1000000000}. The threshold is not checked against a \
367
         ledger; this should be done manually to confirm whether threshold_met \
368
         in the output corresponds to an actual won block."
369
      ( Command.Param.return @@ Exceptions.handle_nicely
119✔
370
      @@ fun () ->
371
      let open Deferred.Let_syntax in
×
372
      let constraint_constants =
373
        Genesis_constants.Compiled.constraint_constants
374
      in
375
      (* TODO-someday: constraint constants from config file. *)
376
      let lexbuf = Lexing.from_channel In_channel.stdin in
377
      let lexer = Yojson.init_lexer () in
×
378
      let%bind () =
379
        Deferred.repeat_until_finished () (fun () ->
×
380
            Deferred.Or_error.try_with ~here:[%here] (fun () ->
×
381
                try
×
382
                  let evaluation_json =
383
                    Yojson.Safe.from_lexbuf ~stream:true lexer lexbuf
384
                  in
385
                  let open Consensus_vrf.Layout in
×
386
                  let evaluation =
387
                    Result.ok_or_failwith (Evaluation.of_yojson evaluation_json)
×
388
                  in
389
                  let evaluation =
×
390
                    Evaluation.compute_vrf ~constraint_constants evaluation
391
                  in
392
                  Format.printf "%a@."
×
393
                    (Yojson.Safe.pretty_print ?std:None)
394
                    (Evaluation.to_yojson evaluation) ;
×
395
                  Deferred.return (`Repeat ())
×
396
                with Yojson.End_of_input -> return (`Finished ()) )
×
397
            >>| function
398
            | Ok x ->
×
399
                x
400
            | Error err ->
×
401
                Format.eprintf "@[<v>Error:@,%s@,@]@."
402
                  (Yojson.Safe.pretty_to_string
×
403
                     (Error_json.error_to_yojson err) ) ;
×
404
                `Repeat () )
×
405
      in
406
      exit 0 )
×
407

408
  let command_group =
409
    Command.group ~summary:"Commands for vrf evaluations"
119✔
410
      [ ("generate-witness", generate_witness)
411
      ; ("batch-generate-witness", batch_generate_witness)
412
      ; ("batch-check-witness", batch_check_witness)
413
      ]
414
end
238✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc