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

MinaProtocol / mina / 83

15 Apr 2025 11:52PM UTC coverage: 60.811% (-0.009%) from 60.82%
83

push

buildkite

web-flow
Merge pull request #16958 from MinaProtocol/georgeee/sexp_of-for-proof-cache-tag

Implement opaque sexp_of, to_yojson for proof_cache_tag

0 of 2 new or added lines in 1 file covered. (0.0%)

938 existing lines in 21 files now uncovered.

49992 of 82209 relevant lines covered (60.81%)

474659.34 hits per line

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

26.18
/src/app/cli/src/init/client.ml
1
open Core
6✔
2
open Async
3
open Signature_lib
4
open Mina_base
5
open Mina_transaction
6

7
module Client = Graphql_lib.Client.Make (struct
8
  let preprocess_variables_string = Fn.id
9

10
  let headers = String.Map.empty
11
end)
12

13
module Args = struct
14
  open Command.Param
15

16
  let zip2 = map2 ~f:(fun arg1 arg2 -> (arg1, arg2))
×
17

18
  let zip3 = map3 ~f:(fun arg1 arg2 arg3 -> (arg1, arg2, arg3))
×
19

20
  let zip4 arg1 arg2 arg3 arg4 =
21
    return (fun a b c d -> (a, b, c, d)) <*> arg1 <*> arg2 <*> arg3 <*> arg4
×
22

23
  let zip5 arg1 arg2 arg3 arg4 arg5 =
24
    return (fun a b c d e -> (a, b, c, d, e))
×
25
    <*> arg1 <*> arg2 <*> arg3 <*> arg4 <*> arg5
12✔
26

27
  let zip6 arg1 arg2 arg3 arg4 arg5 arg6 =
28
    return (fun a b c d e f -> (a, b, c, d, e, f))
×
29
    <*> arg1 <*> arg2 <*> arg3 <*> arg4 <*> arg5 <*> arg6
×
30

31
  let zip7 arg1 arg2 arg3 arg4 arg5 arg6 arg7 =
32
    return (fun a b c d e f g -> (a, b, c, d, e, f, g))
×
33
    <*> arg1 <*> arg2 <*> arg3 <*> arg4 <*> arg5 <*> arg6 <*> arg7
×
34
end
35

36
let or_error_str ~f_ok ~error = function
37
  | Ok x ->
×
38
      f_ok x
39
  | Error e ->
×
40
      sprintf "%s\n%s\n" error (Error.to_string_hum e)
×
41

42
let stop_daemon =
43
  let open Deferred.Let_syntax in
44
  let open Daemon_rpcs in
45
  let open Command.Param in
46
  Command.async ~summary:"Stop the daemon"
6✔
47
    (Cli_lib.Background_daemon.rpc_init (return ()) ~f:(fun port () ->
6✔
48
         let%map res = Daemon_rpcs.Client.dispatch Stop_daemon.rpc () port in
×
49
         printf "%s"
×
50
           (or_error_str res
×
51
              ~f_ok:(fun _ -> "Daemon stopping\n")
×
52
              ~error:"Daemon likely stopped" ) ) )
53

54
let get_balance_graphql =
55
  let open Command.Param in
56
  let pk_flag =
57
    flag "--public-key" ~aliases:[ "public-key" ]
58
      ~doc:"PUBLICKEY Public key for which you want to check the balance"
59
      (required Cli_lib.Arg_type.public_key_compressed)
6✔
60
  in
61
  let token_flag =
6✔
62
    flag "--token" ~aliases:[ "token" ]
63
      ~doc:"TOKEN_ID The token ID for the account"
64
      (optional_with_default Token_id.default Cli_lib.Arg_type.token_id)
6✔
65
  in
66
  Command.async ~summary:"Get balance associated with a public key"
6✔
67
    (Cli_lib.Background_daemon.graphql_init (Args.zip2 pk_flag token_flag)
6✔
68
       ~f:(fun graphql_endpoint (public_key, token) ->
69
         let%map response =
70
           Graphql_client.query_exn
×
71
             Graphql_queries.Get_tracked_account.(
72
               make @@ makeVariables ~public_key ~token ())
×
73
             graphql_endpoint
74
         in
75
         match response.account with
×
76
         | Some account ->
×
77
             if Token_id.(equal default) token then
×
78
               printf "Balance: %s mina\n"
×
79
                 (Currency.Balance.to_mina_string account.balance.total)
×
80
             else
81
               printf "Balance: %s tokens\n"
×
82
                 (Currency.Balance.to_mina_string account.balance.total)
×
83
         | None ->
×
84
             printf "There are no funds in this account\n" ) )
85

86
let get_tokens_graphql =
87
  let open Command.Param in
88
  let pk_flag =
89
    flag "--public-key" ~aliases:[ "public-key" ]
90
      ~doc:"PUBLICKEY Public key for which you want to find accounts"
91
      (required Cli_lib.Arg_type.public_key_compressed)
6✔
92
  in
93
  Command.async ~summary:"Get all token IDs that a public key has accounts for"
6✔
94
    (Cli_lib.Background_daemon.graphql_init pk_flag
6✔
95
       ~f:(fun graphql_endpoint public_key ->
96
         let%map response =
97
           Graphql_client.query_exn
×
98
             Graphql_queries.Get_all_accounts.(
99
               make @@ makeVariables ~public_key ())
×
100
             graphql_endpoint
101
         in
102
         printf "Accounts are held for token IDs:\n" ;
×
103
         Array.iter response.accounts ~f:(fun account ->
×
104
             printf "%s " (Token_id.to_string account.tokenId) ) ) )
×
105

106
let get_time_offset_graphql =
107
  Command.async
6✔
108
    ~summary:
109
      "Get the time offset in seconds used by the daemon to convert real time \
110
       into blockchain time"
111
    (Cli_lib.Background_daemon.graphql_init (Command.Param.return ())
6✔
112
       ~f:(fun graphql_endpoint () ->
113
         let%map response =
114
           Graphql_client.query_exn
×
115
             Graphql_queries.Time_offset.(make @@ makeVariables ())
×
116
             graphql_endpoint
117
         in
118
         let time_offset = response.timeOffset in
×
119
         printf
120
           "Current time offset:\n\
121
            %i\n\n\
122
            Start other daemons with this offset by setting the \
123
            MINA_TIME_OFFSET environment variable in the shell before \
124
            executing them:\n\
125
            export MINA_TIME_OFFSET=%i\n"
126
           time_offset time_offset ) )
127

128
let print_trust_statuses statuses json =
129
  if json then
×
130
    printf "%s\n"
×
131
      (Yojson.Safe.to_string
×
132
         (`List
133
           (List.map
×
134
              ~f:(fun (peer, status) ->
135
                `List
×
136
                  [ Network_peer.Peer.to_yojson peer
×
137
                  ; Trust_system.Peer_status.to_yojson status
×
138
                  ] )
139
              statuses ) ) )
140
  else
141
    let ban_status status =
×
142
      match status.Trust_system.Peer_status.banned with
×
143
      | Unbanned ->
×
144
          "Unbanned"
145
      | Banned_until tm ->
×
146
          sprintf "Banned_until %s" (Time.to_string_abs tm ~zone:Time.Zone.utc)
×
147
    in
148
    List.fold ~init:()
149
      ~f:(fun () (peer, status) ->
150
        printf "%s, %0.04f, %s\n"
×
151
          (Network_peer.Peer.to_multiaddr_string peer)
×
152
          status.trust (ban_status status) )
×
153
      statuses
154

155
let round_trust_score trust_status =
156
  let open Trust_system.Peer_status in
×
157
  let trust = Float.round_decimal trust_status.trust ~decimal_digits:4 in
158
  { trust_status with trust }
×
159

160
let get_trust_status =
161
  let open Command.Param in
162
  let open Deferred.Let_syntax in
163
  let address_flag =
164
    flag "--ip-address" ~aliases:[ "ip-address" ]
165
      ~doc:
166
        "IP An IPv4 or IPv6 address for which you want to query the trust \
167
         status"
168
      (required Cli_lib.Arg_type.ip_address)
6✔
169
  in
170
  let json_flag = Cli_lib.Flag.json in
6✔
171
  let flags = Args.zip2 address_flag json_flag in
172
  Command.async ~summary:"Get the trust status associated with an IP address"
6✔
173
    (Cli_lib.Background_daemon.rpc_init flags ~f:(fun port (ip_address, json) ->
6✔
174
         match%map
175
           Daemon_rpcs.Client.dispatch Daemon_rpcs.Get_trust_status.rpc
×
176
             ip_address port
177
         with
178
         | Ok statuses ->
×
179
             print_trust_statuses
180
               (List.map
×
181
                  ~f:(fun (peer, status) -> (peer, round_trust_score status))
×
182
                  statuses )
183
               json
184
         | Error e ->
×
185
             printf "Failed to get trust status %s\n" (Error.to_string_hum e) )
×
186
    )
187

188
let ip_trust_statuses_to_yojson ip_trust_statuses =
189
  let items =
×
190
    List.map ip_trust_statuses ~f:(fun (ip_addr, status) ->
191
        `Assoc
×
192
          [ ("ip", `String (Unix.Inet_addr.to_string ip_addr))
×
193
          ; ("status", Trust_system.Peer_status.to_yojson status)
×
194
          ] )
195
  in
196
  `List items
×
197

198
let get_trust_status_all =
199
  let open Command.Param in
200
  let open Deferred.Let_syntax in
201
  let nonzero_flag =
202
    flag "--nonzero-only" ~aliases:[ "nonzero-only" ] no_arg
203
      ~doc:"Only show trust statuses whose trust score is nonzero"
204
  in
205
  let json_flag = Cli_lib.Flag.json in
6✔
206
  let flags = Args.zip2 nonzero_flag json_flag in
207
  Command.async
6✔
208
    ~summary:"Get trust statuses for all peers known to the trust system"
209
    (Cli_lib.Background_daemon.rpc_init flags ~f:(fun port (nonzero, json) ->
6✔
210
         match%map
211
           Daemon_rpcs.Client.dispatch Daemon_rpcs.Get_trust_status_all.rpc ()
×
212
             port
213
         with
214
         | Ok ip_trust_statuses ->
×
215
             (* always round the trust scores for display *)
216
             let ip_rounded_trust_statuses =
217
               List.map ip_trust_statuses ~f:(fun (ip_addr, status) ->
218
                   (ip_addr, round_trust_score status) )
×
219
             in
220
             let filtered_ip_trust_statuses =
×
221
               if nonzero then
222
                 List.filter ip_rounded_trust_statuses
×
223
                   ~f:(fun (_ip_addr, status) ->
224
                     not Float.(equal status.trust zero) )
×
225
               else ip_rounded_trust_statuses
×
226
             in
227
             print_trust_statuses filtered_ip_trust_statuses json
228
         | Error e ->
×
229
             printf "Failed to get trust statuses %s\n" (Error.to_string_hum e) )
×
230
    )
231

232
let reset_trust_status =
233
  let open Command.Param in
234
  let open Deferred.Let_syntax in
235
  let address_flag =
236
    flag "--ip-address" ~aliases:[ "ip-address" ]
237
      ~doc:
238
        "IP An IPv4 or IPv6 address for which you want to reset the trust \
239
         status"
240
      (required Cli_lib.Arg_type.ip_address)
6✔
241
  in
242
  let json_flag = Cli_lib.Flag.json in
6✔
243
  let flags = Args.zip2 address_flag json_flag in
244
  Command.async ~summary:"Reset the trust status associated with an IP address"
6✔
245
    (Cli_lib.Background_daemon.rpc_init flags ~f:(fun port (ip_address, json) ->
6✔
246
         match%map
247
           Daemon_rpcs.Client.dispatch Daemon_rpcs.Reset_trust_status.rpc
×
248
             ip_address port
249
         with
250
         | Ok status ->
×
251
             print_trust_statuses status json
252
         | Error e ->
×
253
             printf "Failed to reset trust status %s\n" (Error.to_string_hum e) )
×
254
    )
255

256
let get_public_keys =
257
  let open Daemon_rpcs in
258
  let open Command.Param in
259
  let with_details_flag =
260
    flag "--with-details" ~aliases:[ "with-details" ] no_arg
261
      ~doc:"Show extra details (eg. balance, nonce) in addition to public keys"
262
  in
263
  let error_ctx = "Failed to get public-keys" in
6✔
264
  Command.async ~summary:"Get public keys"
6✔
265
    (Cli_lib.Background_daemon.rpc_init
6✔
266
       (Args.zip2 with_details_flag Cli_lib.Flag.json)
6✔
267
       ~f:(fun port (is_balance_included, json) ->
268
         if is_balance_included then
×
269
           Daemon_rpcs.Client.dispatch_pretty_message ~json
×
270
             ~join_error:Or_error.join ~error_ctx
271
             (module Cli_lib.Render.Public_key_with_details)
272
             Get_public_keys_with_details.rpc () port
273
         else
274
           Daemon_rpcs.Client.dispatch_pretty_message ~json
×
275
             ~join_error:Or_error.join ~error_ctx
276
             (module Cli_lib.Render.String_list_formatter)
277
             Get_public_keys.rpc () port ) )
278

279
let read_json filepath ~flag =
280
  let%map res =
281
    Deferred.Or_error.try_with ~here:[%here] (fun () ->
×
282
        let%map json_contents = Reader.file_contents filepath in
×
283
        Ok (Yojson.Safe.from_string json_contents) )
×
284
  in
285
  match res with
×
286
  | Ok c ->
×
287
      c
288
  | Error e ->
×
289
      Or_error.errorf "Could not read %s at %s\n%s" flag filepath
290
        (Error.to_string_hum e)
×
291

292
let verify_receipt =
293
  let open Deferred.Let_syntax in
294
  let open Daemon_rpcs in
295
  let open Command.Param in
296
  let open Cli_lib.Arg_type in
297
  let proof_path_flag =
298
    flag "--proof-path" ~aliases:[ "proof-path" ]
299
      ~doc:"PROOFFILE File to read json version of payment receipt"
300
      (required string)
6✔
301
  in
302
  let payment_path_flag =
6✔
303
    flag "--payment-path" ~aliases:[ "payment-path" ]
304
      ~doc:"PAYMENTPATH File to read json version of verifying payment"
305
      (required string)
6✔
306
  in
307
  let address_flag =
6✔
308
    flag "--address" ~aliases:[ "address" ]
309
      ~doc:"PUBLICKEY Public-key address of sender"
310
      (required public_key_compressed)
6✔
311
  in
312
  let token_flag =
6✔
313
    flag "--token" ~aliases:[ "token" ]
314
      ~doc:"TOKEN_ID The token ID for the account"
315
      (optional_with_default Token_id.default Cli_lib.Arg_type.token_id)
6✔
316
  in
317
  let legacy_json_flag =
6✔
318
    flag "--legacy" no_arg
319
      ~doc:"Use legacy json format (zkapp command with hashes)"
320
  in
321
  Command.async ~summary:"Verify a receipt of a sent payment"
6✔
322
    (Cli_lib.Background_daemon.rpc_init
6✔
323
       (Args.zip5 payment_path_flag proof_path_flag address_flag token_flag
6✔
324
          legacy_json_flag )
325
       ~f:(fun port (payment_path, proof_path, pk, token_id, use_legacy_json) ->
326
         let account_id = Account_id.create pk token_id in
×
327
         let dispatch_result =
×
328
           let open Deferred.Or_error.Let_syntax in
329
           let%bind payment_json =
330
             read_json payment_path ~flag:"payment-path"
×
331
           in
332
           let%bind proof_json = read_json proof_path ~flag:"proof-path" in
×
333
           let of_payment_json =
×
334
             if use_legacy_json then
335
               Fn.compose
×
336
                 (Result.map ~f:User_command.read_all_proofs_from_disk)
337
                 Mina_block.Legacy_format.User_command.of_yojson
338
             else User_command.Stable.Latest.of_yojson
×
339
           in
340
           let of_proof_json =
341
             let unwrap_proof =
342
               Tuple2.map_snd
343
                 ~f:(List.map ~f:User_command.read_all_proofs_from_disk)
344
             in
345
             if use_legacy_json then
346
               Fn.compose
×
347
                 (Result.map ~f:unwrap_proof)
348
                 [%of_yojson:
UNCOV
349
                   Receipt.Chain_hash.t
×
UNCOV
350
                   * Mina_block.Legacy_format.User_command.t list]
×
351
             else
352
               [%of_yojson:
UNCOV
353
                 Receipt.Chain_hash.t * User_command.Stable.Latest.t list]
×
354
           in
355
           let to_deferred_or_error result ~error =
UNCOV
356
             Result.map_error result ~f:(fun s ->
×
UNCOV
357
                 Error.of_string (sprintf "%s: %s" error s) )
×
358
             |> Deferred.return
359
           in
360
           let%bind payment =
361
             to_deferred_or_error
×
362
               ~error:
UNCOV
363
                 (sprintf "Payment file %s has invalid json format" payment_path)
×
364
               (of_payment_json payment_json)
×
365
           and proof =
366
             to_deferred_or_error
×
367
               ~error:
UNCOV
368
                 (sprintf "Proof file %s has invalid json format" proof_path)
×
369
               (of_proof_json proof_json)
×
370
           in
UNCOV
371
           Daemon_rpcs.Client.dispatch Verify_proof.rpc
×
372
             (account_id, payment, proof)
373
             port
374
         in
375
         match%map dispatch_result with
376
         | Ok (Ok ()) ->
×
377
             printf "Payment is valid on the existing blockchain!\n"
UNCOV
378
         | Error e | Ok (Error e) ->
×
UNCOV
379
             eprintf "Error verifying the receipt: %s\n" (Error.to_string_hum e) )
×
380
    )
381

382
let get_nonce :
383
       rpc:(Account_id.t, Account.Nonce.t option Or_error.t) Rpc.Rpc.t
384
    -> Account_id.t
385
    -> Host_and_port.t
386
    -> (Account.Nonce.t, string) Deferred.Result.t =
387
 fun ~rpc account_id port ->
388
  let open Deferred.Let_syntax in
×
389
  let%map res = Daemon_rpcs.Client.dispatch rpc account_id port in
×
UNCOV
390
  match Or_error.join res with
×
391
  | Ok (Some n) ->
×
392
      Ok n
393
  | Ok None ->
×
394
      Error "No account found at that public_key"
UNCOV
395
  | Error e ->
×
UNCOV
396
      Error (Error.to_string_hum e)
×
397

398
let get_nonce_cmd =
399
  let open Command.Param in
400
  (* Ignores deprecation of public_key type for backwards compatibility *)
401
  let[@warning "-3"] address_flag =
402
    flag "--address" ~aliases:[ "address" ]
403
      ~doc:"PUBLICKEY Public-key address you want the nonce for"
404
      (required Cli_lib.Arg_type.public_key_compressed)
6✔
405
  in
406
  let token_flag =
6✔
407
    flag "--token" ~aliases:[ "token" ]
408
      ~doc:"TOKEN_ID The token ID for the account"
409
      (optional_with_default Token_id.default Cli_lib.Arg_type.token_id)
6✔
410
  in
411
  let flags = Args.zip2 address_flag token_flag in
6✔
412
  Command.async ~summary:"Get the current nonce for an account"
6✔
413
    (Cli_lib.Background_daemon.rpc_init flags ~f:(fun port (pk, token_flag) ->
6✔
414
         let account_id = Account_id.create pk token_flag in
×
415
         match%bind
416
           get_nonce ~rpc:Daemon_rpcs.Get_nonce.rpc account_id port
×
417
         with
418
         | Error e ->
×
419
             eprintf "Failed to get nonce\n%s\n" e ;
420
             exit 2
×
421
         | Ok nonce ->
×
UNCOV
422
             printf "%s\n" (Account.Nonce.to_string nonce) ;
×
UNCOV
423
             exit 0 ) )
×
424

425
let status =
426
  let open Daemon_rpcs in
427
  let flag = Args.zip2 Cli_lib.Flag.json Cli_lib.Flag.performance in
428
  Command.async ~summary:"Get running daemon status"
6✔
429
    (Cli_lib.Background_daemon.rpc_init flag ~f:(fun port (json, performance) ->
6✔
UNCOV
430
         Daemon_rpcs.Client.dispatch_pretty_message ~json ~join_error:Fn.id
×
431
           ~error_ctx:"Failed to get status"
432
           (module Daemon_rpcs.Types.Status)
433
           Get_status.rpc
UNCOV
434
           (if performance then `Performance else `None)
×
435
           port ) )
436

437
let status_clear_hist =
438
  let open Daemon_rpcs in
439
  let flag = Args.zip2 Cli_lib.Flag.json Cli_lib.Flag.performance in
440
  Command.async ~summary:"Clear histograms reported in status"
6✔
441
    (Cli_lib.Background_daemon.rpc_init flag ~f:(fun port (json, performance) ->
6✔
UNCOV
442
         Daemon_rpcs.Client.dispatch_pretty_message ~json ~join_error:Fn.id
×
443
           ~error_ctx:"Failed to clear histograms reported in status"
444
           (module Daemon_rpcs.Types.Status)
445
           Clear_hist_status.rpc
UNCOV
446
           (if performance then `Performance else `None)
×
447
           port ) )
448

449
let get_nonce_exn ~rpc public_key port =
UNCOV
450
  match%bind get_nonce ~rpc public_key port with
×
451
  | Error e ->
×
452
      eprintf "Failed to get nonce\n%s\n" e ;
UNCOV
453
      exit 3
×
UNCOV
454
  | Ok nonce ->
×
455
      return nonce
456

UNCOV
457
let unwrap_user_command (`UserCommand x) = x
×
458

459
let batch_send_payments =
460
  let module Payment_info = struct
461
    type t =
×
462
      { receiver : string
×
463
      ; amount : Currency.Amount.t
×
UNCOV
464
      ; fee : Currency.Fee.t
×
UNCOV
465
      ; valid_until : Mina_numbers.Global_slot_since_genesis.t option
×
466
            [@sexp.option]
467
      }
468
    [@@deriving sexp]
469
  end in
470
  let payment_path_flag = Command.Param.(anon @@ ("payments-file" %: string)) in
6✔
471
  let get_infos payments_path =
472
    match%bind
473
      Reader.load_sexp payments_path [%of_sexp: Payment_info.t list]
×
474
    with
475
    | Ok x ->
×
476
        return x
477
    | Error _ ->
×
478
        let sample_info () : Payment_info.t =
UNCOV
479
          let keypair = Keypair.create () in
×
480
          { Payment_info.receiver =
×
481
              Public_key.(
482
                Compressed.to_base58_check (compress keypair.public_key))
×
483
          ; valid_until =
484
              Some (Mina_numbers.Global_slot_since_genesis.random ())
×
UNCOV
485
          ; amount = Currency.Amount.of_nanomina_int_exn (Random.int 100)
×
UNCOV
486
          ; fee = Currency.Fee.of_nanomina_int_exn (Random.int 100)
×
487
          }
488
        in
489
        eprintf "Could not read payments from %s.\n" payments_path ;
UNCOV
490
        eprintf
×
491
          "The file should be a sexp list of payments with optional expiry \
492
           slot number \"valid_until\". Here is an example file:\n\
493
           %s\n"
494
          (Sexp.to_string_hum
×
495
             ([%sexp_of: Payment_info.t list]
UNCOV
496
                (List.init 3 ~f:(fun _ -> sample_info ())) ) ) ;
×
UNCOV
497
        exit 5
×
498
  in
499
  let main port (privkey_path, payments_path) =
500
    let open Deferred.Let_syntax in
×
501
    let%bind keypair =
502
      Secrets.Keypair.Terminal_stdin.read_exn ~which:"Mina keypair" privkey_path
×
503
    and infos = get_infos payments_path in
×
504
    let ts : User_command_input.t list =
×
505
      List.map infos ~f:(fun { receiver; valid_until; amount; fee } ->
×
UNCOV
506
          let signer_pk = Public_key.compress keypair.public_key in
×
UNCOV
507
          let receiver_pk =
×
508
            Public_key.of_base58_check_decompress_exn receiver
509
          in
UNCOV
510
          User_command_input.create ~signer:signer_pk ~fee
×
511
            ~fee_payer_pk:signer_pk ~memo:Signed_command_memo.empty ~valid_until
512
            ~body:(Payment { receiver_pk; amount })
513
            ~sign_choice:(User_command_input.Sign_choice.Keypair keypair) () )
514
    in
515
    Daemon_rpcs.Client.dispatch_with_message Daemon_rpcs.Send_user_commands.rpc
516
      ts port
517
      ~success:(fun _ -> "Successfully enqueued payments in pool")
×
518
      ~error:(fun e ->
UNCOV
519
        sprintf "Failed to send payments %s" (Error.to_string_hum e) )
×
520
      ~join_error:Or_error.join
521
  in
522
  Command.async ~summary:"Send multiple payments from a file"
6✔
523
    (Cli_lib.Background_daemon.rpc_init
6✔
524
       (Args.zip2 Cli_lib.Flag.privkey_read_path payment_path_flag)
6✔
525
       ~f:main )
526

527
let transaction_id_to_string id =
UNCOV
528
  Yojson.Basic.to_string (Graphql_lib.Scalars.TransactionId.serialize id)
×
529

530
let send_payment_graphql =
531
  let open Command.Param in
532
  let open Cli_lib.Arg_type in
533
  let receiver_flag =
534
    flag "--receiver" ~aliases:[ "receiver" ]
535
      ~doc:"PUBLICKEY Public key to which you want to send money"
536
      (required public_key_compressed)
6✔
537
  in
538
  let amount_flag =
6✔
539
    flag "--amount" ~aliases:[ "amount" ]
540
      ~doc:"VALUE Payment amount you want to send" (required txn_amount)
6✔
541
  in
542
  let genesis_constants = Genesis_constants.Compiled.genesis_constants in
6✔
543
  let compile_config = Mina_compile_config.Compiled.t in
544
  let args =
545
    Args.zip3
546
      (Cli_lib.Flag.signed_command_common
547
         ~minimum_user_command_fee:genesis_constants.minimum_user_command_fee
548
         ~default_transaction_fee:compile_config.default_transaction_fee )
549
      receiver_flag amount_flag
550
  in
551
  Command.async ~summary:"Send payment to an address"
6✔
552
    (Cli_lib.Background_daemon.graphql_init args
6✔
553
       ~f:(fun
554
            graphql_endpoint
555
            ({ Cli_lib.Flag.sender; fee; nonce; memo }, receiver, amount)
556
          ->
557
         let%map response =
558
           let input =
559
             Mina_graphql.Types.Input.SendPaymentInput.make_input ~to_:receiver
560
               ~from:sender ~amount ~fee ?memo ?nonce ()
561
           in
UNCOV
562
           Graphql_client.query_exn
×
UNCOV
563
             Graphql_queries.Send_payment.(make @@ makeVariables ~input ())
×
564
             graphql_endpoint
565
         in
UNCOV
566
         printf "Dispatched payment with ID %s\n"
×
UNCOV
567
           (transaction_id_to_string response.sendPayment.payment.id) ) )
×
568

569
let delegate_stake_graphql =
570
  let open Command.Param in
571
  let open Cli_lib.Arg_type in
572
  let receiver_flag =
573
    flag "--receiver" ~aliases:[ "receiver" ]
574
      ~doc:"PUBLICKEY Public key to which you want to delegate your stake"
575
      (required public_key_compressed)
6✔
576
  in
577
  let genesis_constants = Genesis_constants.Compiled.genesis_constants in
6✔
578
  let compile_config = Mina_compile_config.Compiled.t in
579
  let args =
580
    Args.zip2
581
      (Cli_lib.Flag.signed_command_common
582
         ~minimum_user_command_fee:genesis_constants.minimum_user_command_fee
583
         ~default_transaction_fee:compile_config.default_transaction_fee )
584
      receiver_flag
585
  in
586
  Command.async ~summary:"Delegate your stake to another public key"
6✔
587
    (Cli_lib.Background_daemon.graphql_init args
6✔
588
       ~f:(fun
589
            graphql_endpoint
590
            ({ Cli_lib.Flag.sender; fee; nonce; memo }, receiver)
591
          ->
592
         let%map response =
593
           Graphql_client.query_exn
×
594
             Graphql_queries.Send_delegation.(
595
               make
×
UNCOV
596
               @@ makeVariables ~receiver ~sender
×
UNCOV
597
                    ~fee:(Currency.Fee.to_uint64 fee)
×
598
                    ?nonce ?memo ())
599
             graphql_endpoint
600
         in
UNCOV
601
         printf "Dispatched stake delegation with ID %s\n"
×
UNCOV
602
           (transaction_id_to_string response.sendDelegation.delegation.id) ) )
×
603

604
let cancel_transaction_graphql =
605
  let txn_id_flag =
606
    Command.Param.(
607
      flag "--id" ~aliases:[ "id" ] ~doc:"ID Transaction ID to be cancelled"
6✔
608
        (required Cli_lib.Arg_type.user_command))
6✔
609
  in
610
  Command.async
6✔
611
    ~summary:
612
      "Cancel a transaction -- this submits a replacement transaction with a \
613
       fee larger than the cancelled transaction."
614
    (Cli_lib.Background_daemon.graphql_init txn_id_flag
6✔
615
       ~f:(fun graphql_endpoint user_command ->
616
         let receiver_pk = Signed_command.receiver_pk user_command in
×
UNCOV
617
         let cancel_sender_pk = Signed_command.fee_payer_pk user_command in
×
618
         let open Deferred.Let_syntax in
×
619
         let cancel_fee =
UNCOV
620
           let fee = Currency.Fee.to_uint64 (Signed_command.fee user_command) in
×
UNCOV
621
           let replace_fee =
×
622
             Currency.Fee.to_uint64 Network_pool.Indexed_pool.replace_fee
623
           in
624
           let open Unsigned.UInt64.Infix in
×
625
           (* fee amount "inspired by" network_pool/indexed_pool.ml *)
UNCOV
626
           Currency.Fee.of_uint64 (fee + replace_fee)
×
627
         in
628
         printf "Fee to cancel transaction is %s coda.\n"
UNCOV
629
           (Currency.Fee.to_mina_string cancel_fee) ;
×
UNCOV
630
         let cancel_query =
×
631
           let input =
632
             Mina_graphql.Types.Input.SendPaymentInput.make_input
633
               ~to_:receiver_pk ~from:cancel_sender_pk
634
               ~amount:Currency.Amount.zero ~fee:cancel_fee
UNCOV
635
               ~nonce:(Signed_command.nonce user_command)
×
636
               ()
637
           in
UNCOV
638
           Graphql_queries.Send_payment.(make @@ makeVariables ~input ())
×
639
         in
640
         let%map cancel_response =
641
           Graphql_client.query_exn cancel_query graphql_endpoint
×
642
         in
UNCOV
643
         printf "🛑 Cancelled transaction! Cancel ID: %s\n"
×
UNCOV
644
           (transaction_id_to_string cancel_response.sendPayment.payment.id) )
×
645
    )
646

647
let send_rosetta_transactions_graphql =
648
  Command.async
6✔
649
    ~summary:
650
      "Dispatch one or more transactions, provided to stdin in rosetta format"
651
    (Cli_lib.Background_daemon.graphql_init (Command.Param.return ())
6✔
652
       ~f:(fun graphql_endpoint () ->
UNCOV
653
         let lexbuf = Lexing.from_channel In_channel.stdin in
×
654
         let lexer = Yojson.init_lexer () in
×
655
         match%bind
656
           Deferred.Or_error.try_with ~here:[%here] (fun () ->
×
UNCOV
657
               Deferred.repeat_until_finished () (fun () ->
×
UNCOV
658
                   try
×
659
                     let transaction_json =
660
                       Yojson.Basic.from_lexbuf ~stream:true lexer lexbuf
661
                     in
662
                     let%map response =
663
                       Graphql_client.query_exn
×
664
                         Graphql_queries.Send_rosetta_transaction.(
UNCOV
665
                           make
×
UNCOV
666
                           @@ makeVariables ~transaction:transaction_json ())
×
667
                         graphql_endpoint
668
                     in
UNCOV
669
                     printf "Dispatched command with TRANSACTION_ID %s\n"
×
670
                       (transaction_id_to_string
×
671
                          response.sendRosettaTransaction.userCommand.id ) ;
UNCOV
672
                     `Repeat ()
×
673
                   with Yojson.End_of_input -> return (`Finished ()) ) )
×
674
         with
675
         | Ok () ->
×
676
             Deferred.return ()
UNCOV
677
         | Error err ->
×
678
             Format.eprintf "@[<v>Error:@,%a@,@]@."
679
               (Yojson.Safe.pretty_print ?std:None)
UNCOV
680
               (Error_json.error_to_yojson err) ;
×
UNCOV
681
             Core_kernel.exit 1 ) )
×
682

683
module Export_logs = struct
UNCOV
684
  let pp_export_result tarfile = printf "Exported logs to %s\n%!" tarfile
×
685

686
  let tarfile_flag =
687
    let open Command.Param in
688
    flag "--tarfile" ~aliases:[ "tarfile" ]
6✔
689
      ~doc:"STRING Basename of the tar archive (default: date_time)"
690
      (optional string)
6✔
691

692
  let export_via_graphql =
693
    Command.async ~summary:"Export daemon logs to tar archive"
6✔
694
      (Cli_lib.Background_daemon.graphql_init tarfile_flag
6✔
695
         ~f:(fun graphql_endpoint basename ->
696
           let%map response =
UNCOV
697
             Graphql_client.query_exn
×
UNCOV
698
               Graphql_queries.Export_logs.(make @@ makeVariables ?basename ())
×
699
               graphql_endpoint
700
           in
UNCOV
701
           pp_export_result response.exportLogs.exportLogs.tarfile ) )
×
702

703
  let export_locally =
704
    let run ~tarfile ~conf_dir =
705
      let open Mina_lib in
×
706
      let conf_dir = Conf_dir.compute_conf_dir conf_dir in
707
      fun () ->
×
708
        match%map Conf_dir.export_logs_to_tar ?basename:tarfile ~conf_dir with
709
        | Ok result ->
×
710
            pp_export_result result
711
        | Error err ->
×
712
            failwithf "Error when exporting logs: %s"
UNCOV
713
              (Error_json.error_to_yojson err |> Yojson.Safe.to_string)
×
714
              ()
715
    in
716
    let open Command.Let_syntax in
717
    Command.async ~summary:"Export local logs (no daemon) to tar archive"
6✔
718
      (let%map tarfile = tarfile_flag and conf_dir = Cli_lib.Flag.conf_dir in
UNCOV
719
       run ~tarfile ~conf_dir )
×
720
end
721

722
let wrap_key =
723
  Command.async ~summary:"Wrap a private key into a private key file"
6✔
724
    (let open Command.Let_syntax in
725
    let%map_open privkey_path = Cli_lib.Flag.privkey_write_path in
726
    Cli_lib.Exceptions.handle_nicely
2✔
727
    @@ fun () ->
728
    let open Deferred.Let_syntax in
2✔
729
    let%bind privkey =
730
      Secrets.Password.hidden_line_or_env "Private key: " ~env:"CODA_PRIVKEY"
2✔
731
    in
732
    let pk = Private_key.of_base58_check_exn (Bytes.to_string privkey) in
2✔
733
    let kp = Keypair.of_private_key_exn pk in
2✔
734
    Secrets.Keypair.Terminal_stdin.write_exn kp ~privkey_path)
2✔
735

736
let dump_keypair =
737
  Command.async ~summary:"Print out a keypair from a private key file"
6✔
738
    (let open Command.Let_syntax in
739
    let%map_open privkey_path = Cli_lib.Flag.privkey_read_path in
740
    Cli_lib.Exceptions.handle_nicely
×
741
    @@ fun () ->
742
    let open Deferred.Let_syntax in
×
743
    let%map kp =
744
      Secrets.Keypair.Terminal_stdin.read_exn ~which:"Mina keypair" privkey_path
×
745
    in
746
    printf "Public key: %s\nPrivate key: %s\n"
×
747
      ( kp.public_key |> Public_key.compress
×
UNCOV
748
      |> Public_key.Compressed.to_base58_check )
×
UNCOV
749
      (kp.private_key |> Private_key.to_base58_check))
×
750

751
let handle_export_ledger_response ~json = function
752
  | Error e ->
×
753
      Daemon_rpcs.Client.print_rpc_error e ;
754
      exit 1
×
755
  | Ok (Error e) ->
×
756
      printf !"Ledger not found: %s\n" (Error.to_string_hum e) ;
×
757
      exit 1
×
UNCOV
758
  | Ok (Ok accounts) ->
×
759
      if json then (
×
760
        Format.fprintf Format.std_formatter "[\n  " ;
761
        let print_comma = ref false in
×
762
        List.iter accounts ~f:(fun a ->
763
            if !print_comma then Format.fprintf Format.std_formatter "\n, "
×
764
            else print_comma := true ;
×
UNCOV
765
            Genesis_ledger_helper.Accounts.Single.of_account a None
×
766
            |> Runtime_config.Accounts.Single.to_yojson
×
767
            |> Yojson.Safe.pretty_print Format.std_formatter ) ;
768
        Format.fprintf Format.std_formatter "\n]" ;
×
UNCOV
769
        printf "\n" )
×
UNCOV
770
      else printf !"%{sexp:Account.t list}\n" accounts ;
×
771
      return ()
772

773
let export_ledger =
774
  let state_hash_flag =
775
    Command.Param.(
776
      flag "--state-hash" ~aliases:[ "state-hash" ]
6✔
777
        ~doc:
778
          "STATE-HASH State hash, if printing a staged ledger (default: state \
779
           hash for the best tip)"
780
        (optional string))
6✔
781
  in
782
  let ledger_kind =
783
    let available_ledgers =
784
      [ "staged-ledger"
785
      ; "snarked-ledger"
786
      ; "staking-epoch-ledger"
787
      ; "next-epoch-ledger"
788
      ]
789
    in
790
    let t =
791
      Command.Param.Arg_type.of_alist_exn
792
        (List.map available_ledgers ~f:(fun s -> (s, s)))
6✔
793
    in
794
    let ledger_args = String.concat ~sep:"|" available_ledgers in
6✔
795
    Command.Param.(anon (ledger_args %: t))
6✔
796
  in
797
  let plaintext_flag = Cli_lib.Flag.plaintext in
798
  let flags = Args.zip3 state_hash_flag plaintext_flag ledger_kind in
799
  Command.async
6✔
800
    ~summary:
801
      "Print the specified ledger (default: staged ledger at the best tip). \
802
       Note: Exporting snarked ledger is an expensive operation and can take a \
803
       few seconds"
804
    (Cli_lib.Background_daemon.rpc_init flags
6✔
805
       ~f:(fun port (state_hash, plaintext, ledger_kind) ->
UNCOV
806
         let check_for_state_hash () =
×
UNCOV
807
           if Option.is_some state_hash then (
×
808
             Format.eprintf "A state hash should not be given for %s@."
809
               ledger_kind ;
UNCOV
810
             Core_kernel.exit 1 )
×
811
         in
812
         let response =
813
           match ledger_kind with
UNCOV
814
           | "staged-ledger" ->
×
815
               let state_hash =
816
                 Option.map ~f:State_hash.of_base58_check_exn state_hash
817
               in
818
               Daemon_rpcs.Client.dispatch Daemon_rpcs.Get_ledger.rpc state_hash
×
819
                 port
UNCOV
820
           | "snarked-ledger" ->
×
821
               let state_hash =
822
                 Option.map ~f:State_hash.of_base58_check_exn state_hash
823
               in
824
               printf
×
825
                 "Generating snarked ledger(this may take a few seconds)...\n" ;
826
               Daemon_rpcs.Client.dispatch Daemon_rpcs.Get_snarked_ledger.rpc
×
827
                 state_hash port
828
           | "staking-epoch-ledger" ->
×
829
               check_for_state_hash () ;
830
               Daemon_rpcs.Client.dispatch Daemon_rpcs.Get_staking_ledger.rpc
×
831
                 Daemon_rpcs.Get_staking_ledger.Current port
832
           | "next-epoch-ledger" ->
×
833
               check_for_state_hash () ;
834
               Daemon_rpcs.Client.dispatch Daemon_rpcs.Get_staking_ledger.rpc
×
835
                 Daemon_rpcs.Get_staking_ledger.Next port
836
           | _ ->
×
837
               (* unreachable *)
UNCOV
838
               failwithf "Unknown ledger kind: %s" ledger_kind ()
×
839
         in
840
         response >>= handle_export_ledger_response ~json:(not plaintext) ) )
841

842
let hash_ledger =
843
  let open Command.Let_syntax in
844
  Command.async
6✔
845
    ~summary:
846
      "Print the Merkle root of the ledger contained in the specified file"
847
    (let%map ledger_file =
848
       Command.Param.(
849
         flag "--ledger-file"
6✔
850
           ~doc:"LEDGER-FILE File containing an exported ledger"
851
           (required string))
6✔
852
     and plaintext = Cli_lib.Flag.plaintext in
853
     fun () ->
UNCOV
854
       let constraint_constants =
×
855
         Genesis_constants.Compiled.constraint_constants
856
       in
857
       let process_accounts accounts =
UNCOV
858
         let packed_ledger =
×
859
           Genesis_ledger_helper.Ledger.packed_genesis_ledger_of_accounts
860
             ~depth:constraint_constants.ledger_depth accounts
861
         in
862
         let ledger = Lazy.force @@ Genesis_ledger.Packed.t packed_ledger in
×
UNCOV
863
         Format.printf "%s@."
×
UNCOV
864
           (Mina_ledger.Ledger.merkle_root ledger |> Ledger_hash.to_base58_check)
×
865
       in
866
       Deferred.return
867
       @@
868
       if plaintext then
869
         In_channel.with_file ledger_file ~f:(fun in_channel ->
×
UNCOV
870
             let sexp = In_channel.input_all in_channel |> Sexp.of_string in
×
871
             let accounts =
×
872
               lazy
873
                 (List.map
×
874
                    ([%of_sexp: Account.t list] sexp)
UNCOV
875
                    ~f:(fun acct -> (None, acct)) )
×
876
             in
877
             process_accounts accounts )
878
       else
879
         let json = Yojson.Safe.from_file ledger_file in
×
UNCOV
880
         match Runtime_config.Accounts.of_yojson json with
×
881
         | Ok runtime_accounts ->
×
882
             let accounts =
883
               lazy (Genesis_ledger_helper.Accounts.to_full runtime_accounts)
×
884
             in
UNCOV
885
             process_accounts accounts
×
UNCOV
886
         | Error err ->
×
887
             Format.eprintf "Could not parse JSON in file %s: %s@" ledger_file
888
               err ;
UNCOV
889
             ignore (exit 1 : 'a Deferred.t) )
×
890

891
let currency_in_ledger =
892
  let open Command.Let_syntax in
893
  Command.async
6✔
894
    ~summary:
895
      "Print the total currency for each token present in the ledger contained \
896
       in the specified file"
897
    (let%map ledger_file =
898
       Command.Param.(
899
         flag "--ledger-file"
6✔
900
           ~doc:"LEDGER-FILE File containing an exported ledger"
901
           (required string))
6✔
902
     and plaintext = Cli_lib.Flag.plaintext in
903
     fun () ->
UNCOV
904
       let process_accounts accounts =
×
905
         (* track currency total for each token
906
            use uint64 to make arithmetic simple
907
         *)
UNCOV
908
         let currency_tbl : Unsigned.UInt64.t Token_id.Table.t =
×
UNCOV
909
           Token_id.Table.create ()
×
910
         in
911
         List.iter accounts ~f:(fun (acct : Account.t) ->
912
             let token_id = Account.token_id acct in
×
913
             let balance = acct.balance |> Currency.Balance.to_uint64 in
×
UNCOV
914
             match Token_id.Table.find currency_tbl token_id with
×
915
             | None ->
×
916
                 Token_id.Table.add_exn currency_tbl ~key:token_id ~data:balance
917
             | Some total ->
×
918
                 let new_total = Unsigned.UInt64.add total balance in
919
                 Token_id.Table.set currency_tbl ~key:token_id ~data:new_total ) ;
×
UNCOV
920
         let tokens =
×
UNCOV
921
           Token_id.Table.keys currency_tbl
×
922
           |> List.dedup_and_sort ~compare:Token_id.compare
923
         in
924
         List.iter tokens ~f:(fun token ->
×
925
             let total =
×
UNCOV
926
               Token_id.Table.find_exn currency_tbl token
×
927
               |> Currency.Balance.of_uint64 |> Currency.Balance.to_mina_string
×
928
             in
UNCOV
929
             if Token_id.equal token Token_id.default then
×
930
               Format.printf "MINA: %s@." total
×
931
             else
UNCOV
932
               Format.printf "TOKEN %s: %s@." (Token_id.to_string token) total )
×
933
       in
934
       Deferred.return
935
       @@
936
       if plaintext then
937
         In_channel.with_file ledger_file ~f:(fun in_channel ->
×
938
             let sexp = In_channel.input_all in_channel |> Sexp.of_string in
×
UNCOV
939
             let accounts = [%of_sexp: Account.t list] sexp in
×
940
             process_accounts accounts )
×
941
       else
942
         let json = Yojson.Safe.from_file ledger_file in
×
UNCOV
943
         match Runtime_config.Accounts.of_yojson json with
×
944
         | Ok runtime_accounts ->
×
945
             let accounts =
UNCOV
946
               Genesis_ledger_helper.Accounts.to_full runtime_accounts
×
947
               |> List.map ~f:(fun (_sk_opt, acct) -> acct)
×
948
             in
UNCOV
949
             process_accounts accounts
×
UNCOV
950
         | Error err ->
×
951
             Format.eprintf "Could not parse JSON in file %s: %s@" ledger_file
952
               err ;
UNCOV
953
             ignore (exit 1 : 'a Deferred.t) )
×
954

955
let constraint_system_digests =
956
  Command.async ~summary:"Print MD5 digest of each SNARK constraint"
6✔
957
    (Command.Param.return (fun () ->
6✔
UNCOV
958
         let constraint_constants =
×
959
           Genesis_constants.Compiled.constraint_constants
960
         in
961
         let proof_level = Genesis_constants.Compiled.proof_level in
962
         let all =
UNCOV
963
           Transaction_snark.constraint_system_digests ~constraint_constants ()
×
UNCOV
964
           @ Blockchain_snark.Blockchain_snark_state.constraint_system_digests
×
965
               ~proof_level ~constraint_constants ()
966
         in
967
         let all =
968
           List.sort ~compare:(fun (k1, _) (k2, _) -> String.compare k1 k2) all
×
969
         in
UNCOV
970
         List.iter all ~f:(fun (k, v) -> printf "%s\t%s\n" k (Md5.to_hex v)) ;
×
UNCOV
971
         Deferred.unit ) )
×
972

973
let snark_job_list =
974
  let open Deferred.Let_syntax in
975
  let open Command.Param in
976
  Command.async
6✔
977
    ~summary:
978
      "List of snark jobs in JSON format that are yet to be included in the \
979
       blocks"
980
    (Cli_lib.Background_daemon.rpc_init (return ()) ~f:(fun port () ->
6✔
981
         match%map
UNCOV
982
           Daemon_rpcs.Client.dispatch_join_errors
×
983
             Daemon_rpcs.Snark_job_list.rpc () port
984
         with
985
         | Ok str ->
×
986
             printf "%s" str
UNCOV
987
         | Error e ->
×
988
             Daemon_rpcs.Client.print_rpc_error e ) )
989

990
let snark_pool_list =
991
  let open Command.Param in
992
  Command.async ~summary:"List of snark works in the snark pool in JSON format"
6✔
993
    (Cli_lib.Background_daemon.graphql_init (return ())
6✔
994
       ~f:(fun graphql_endpoint () ->
995
         Deferred.map
×
UNCOV
996
           (Graphql_client.query_exn
×
UNCOV
997
              Graphql_queries.Snark_pool.(make @@ makeVariables ())
×
998
              graphql_endpoint )
999
           ~f:(fun response ->
1000
             let lst =
×
1001
               [%to_yojson: Cli_lib.Graphql_types.Completed_works.t]
×
UNCOV
1002
                 (Array.to_list
×
1003
                    (Array.map
×
1004
                       ~f:(fun w ->
UNCOV
1005
                         { Cli_lib.Graphql_types.Completed_works.Work.work_ids =
×
UNCOV
1006
                             Array.to_list w.work_ids
×
1007
                         ; fee = w.fee
1008
                         ; prover = w.prover
1009
                         } )
1010
                       response.snarkPool ) )
1011
             in
UNCOV
1012
             print_string (Yojson.Safe.to_string lst) ) ) )
×
1013

1014
let pooled_user_commands =
1015
  let public_key_flag =
1016
    Command.Param.(
1017
      anon @@ maybe @@ ("public-key" %: Cli_lib.Arg_type.public_key_compressed))
6✔
1018
  in
1019
  Command.async
6✔
1020
    ~summary:"Retrieve all the user commands that are pending inclusion"
1021
    (Cli_lib.Background_daemon.graphql_init public_key_flag
6✔
1022
       ~f:(fun graphql_endpoint public_key ->
1023
         let module Q = Graphql_queries.Pooled_user_commands in
×
1024
         let graphql = Q.(make @@ makeVariables ?public_key ()) in
×
1025
         let%map response = Graphql_client.query_exn graphql graphql_endpoint in
×
UNCOV
1026
         let json_response = Q.serialize response |> Q.toJson in
×
UNCOV
1027
         print_string (Yojson.Basic.to_string json_response) ) )
×
1028

1029
let pooled_zkapp_commands =
1030
  let public_key_flag =
1031
    Command.Param.(
1032
      anon @@ maybe @@ ("public-key" %: Cli_lib.Arg_type.public_key_compressed))
6✔
1033
  in
1034
  Command.async
6✔
1035
    ~summary:"Retrieve all the zkApp commands that are pending inclusion"
1036
    (Cli_lib.Background_daemon.graphql_init public_key_flag
6✔
1037
       ~f:(fun graphql_endpoint maybe_public_key ->
1038
         let public_key =
×
1039
           Yojson.Safe.to_basic
1040
           @@ [%to_yojson: Public_key.Compressed.t option] maybe_public_key
×
1041
         in
1042
         let graphql =
×
1043
           Graphql_queries.Pooled_zkapp_commands.(
UNCOV
1044
             make @@ makeVariables ~public_key ())
×
1045
         in
1046
         let%bind raw_response =
UNCOV
1047
           Graphql_client.query_json_exn graphql graphql_endpoint
×
1048
         in
1049
         let%map json_response =
1050
           try
1051
             let kvs = Yojson.Safe.Util.to_assoc raw_response in
UNCOV
1052
             List.hd_exn kvs |> snd |> return
×
1053
           with _ ->
×
1054
             eprintf "Failed to read result of pooled zkApp commands" ;
1055
             exit 1
×
1056
         in
UNCOV
1057
         print_string (Yojson.Safe.to_string json_response) ) )
×
1058

1059
let to_signed_fee_exn sign magnitude =
UNCOV
1060
  let sgn = match sign with `PLUS -> Sgn.Pos | `MINUS -> Neg in
×
1061
  Currency.Fee.Signed.create ~sgn ~magnitude
1062

1063
let pending_snark_work =
1064
  let open Command.Param in
1065
  Command.async
6✔
1066
    ~summary:
1067
      "List of snark works in JSON format that are not available in the pool \
1068
       yet"
1069
    (Cli_lib.Background_daemon.graphql_init (return ())
6✔
1070
       ~f:(fun graphql_endpoint () ->
1071
         Deferred.map
×
UNCOV
1072
           (Graphql_client.query_exn
×
UNCOV
1073
              Graphql_queries.Pending_snark_work.(make @@ makeVariables ())
×
1074
              graphql_endpoint )
1075
           ~f:(fun response ->
1076
             let lst =
×
UNCOV
1077
               [%to_yojson: Cli_lib.Graphql_types.Pending_snark_work.t]
×
1078
                 (Array.map
×
1079
                    ~f:(fun bundle ->
UNCOV
1080
                      Array.map bundle.workBundle ~f:(fun w ->
×
UNCOV
1081
                          let fee_excess_left = w.fee_excess.feeExcessLeft in
×
1082
                          { Cli_lib.Graphql_types.Pending_snark_work.Work
1083
                            .work_id = w.work_id
1084
                          ; fee_excess =
UNCOV
1085
                              Currency.Amount.Signed.of_fee
×
UNCOV
1086
                                (to_signed_fee_exn fee_excess_left.sign
×
1087
                                   fee_excess_left.feeMagnitude )
1088
                          ; supply_increase = w.supply_increase
1089
                          ; source_first_pass_ledger_hash =
1090
                              w.source_first_pass_ledger_hash
1091
                          ; target_first_pass_ledger_hash =
1092
                              w.target_first_pass_ledger_hash
1093
                          ; source_second_pass_ledger_hash =
1094
                              w.source_second_pass_ledger_hash
1095
                          ; target_second_pass_ledger_hash =
1096
                              w.target_second_pass_ledger_hash
1097
                          } ) )
1098
                    response.pendingSnarkWork )
1099
             in
UNCOV
1100
             print_string (Yojson.Safe.to_string lst) ) ) )
×
1101

1102
let start_tracing =
1103
  let open Deferred.Let_syntax in
1104
  let open Command.Param in
1105
  Command.async
6✔
1106
    ~summary:"Start async tracing to $config-directory/trace/$pid.trace"
1107
    (Cli_lib.Background_daemon.rpc_init (return ()) ~f:(fun port () ->
6✔
1108
         match%map
1109
           Daemon_rpcs.Client.dispatch Daemon_rpcs.Start_tracing.rpc () port
×
1110
         with
1111
         | Ok () ->
×
1112
             print_endline "Daemon started tracing!"
UNCOV
1113
         | Error e ->
×
1114
             Daemon_rpcs.Client.print_rpc_error e ) )
1115

1116
let stop_tracing =
1117
  let open Deferred.Let_syntax in
1118
  let open Command.Param in
1119
  Command.async ~summary:"Stop async tracing"
6✔
1120
    (Cli_lib.Background_daemon.rpc_init (return ()) ~f:(fun port () ->
6✔
1121
         match%map
1122
           Daemon_rpcs.Client.dispatch Daemon_rpcs.Stop_tracing.rpc () port
×
1123
         with
1124
         | Ok () ->
×
1125
             print_endline "Daemon stopped printing!"
UNCOV
1126
         | Error e ->
×
1127
             Daemon_rpcs.Client.print_rpc_error e ) )
1128

1129
let start_internal_tracing =
1130
  let open Deferred.Let_syntax in
1131
  let open Command.Param in
1132
  Command.async
6✔
1133
    ~summary:
1134
      "Start internal tracing to \
1135
       $config-directory/internal-tracing/internal-trace.jsonl"
1136
    (Cli_lib.Background_daemon.rpc_init (return ()) ~f:(fun port () ->
6✔
1137
         match%map
UNCOV
1138
           Daemon_rpcs.Client.dispatch Daemon_rpcs.Start_internal_tracing.rpc ()
×
1139
             port
1140
         with
1141
         | Ok () ->
×
1142
             print_endline "Daemon internal started tracing!"
UNCOV
1143
         | Error e ->
×
1144
             Daemon_rpcs.Client.print_rpc_error e ) )
1145

1146
let stop_internal_tracing =
1147
  let open Deferred.Let_syntax in
1148
  let open Command.Param in
1149
  Command.async ~summary:"Stop internal tracing"
6✔
1150
    (Cli_lib.Background_daemon.rpc_init (return ()) ~f:(fun port () ->
6✔
1151
         match%map
UNCOV
1152
           Daemon_rpcs.Client.dispatch Daemon_rpcs.Stop_internal_tracing.rpc ()
×
1153
             port
1154
         with
1155
         | Ok () ->
×
1156
             print_endline "Daemon internal tracing stopped!"
UNCOV
1157
         | Error e ->
×
1158
             Daemon_rpcs.Client.print_rpc_error e ) )
1159

1160
let set_coinbase_receiver_graphql =
1161
  let open Command.Param in
1162
  let open Cli_lib.Arg_type in
1163
  let pk_flag =
1164
    choose_one ~if_nothing_chosen:Raise
1165
      [ flag "--public-key" ~aliases:[ "public-key" ]
6✔
1166
          ~doc:"PUBLICKEY Public key of account to send coinbase rewards to"
1167
          (optional public_key_compressed)
6✔
1168
        |> map ~f:(Option.map ~f:Option.some)
6✔
1169
      ; flag "--block-producer" ~aliases:[ "block-producer" ]
6✔
1170
          ~doc:"Send coinbase rewards to the block producer's public key" no_arg
UNCOV
1171
        |> map ~f:(function true -> Some None | false -> None)
×
1172
      ]
1173
  in
1174
  Command.async ~summary:"Set the coinbase receiver"
6✔
1175
    (Cli_lib.Background_daemon.graphql_init pk_flag
6✔
1176
       ~f:(fun graphql_endpoint public_key ->
UNCOV
1177
         let print_pk_opt () = function
×
1178
           | None ->
×
1179
               "block producer"
UNCOV
1180
           | Some pk ->
×
UNCOV
1181
               "public key " ^ Public_key.Compressed.to_base58_check pk
×
1182
         in
1183
         let%map result =
1184
           Graphql_client.query_exn
×
1185
             Graphql_queries.Set_coinbase_receiver.(
UNCOV
1186
               make @@ makeVariables ?public_key ())
×
1187
             graphql_endpoint
1188
         in
UNCOV
1189
         printf
×
1190
           "Was sending coinbases to the %a\nNow sending coinbases to the %a\n"
1191
           print_pk_opt result.setCoinbaseReceiver.lastCoinbaseReceiver
1192
           print_pk_opt result.setCoinbaseReceiver.currentCoinbaseReceiver ) )
1193

1194
let set_snark_worker =
1195
  let open Command.Param in
1196
  let public_key_flag =
1197
    flag "--address" ~aliases:[ "address" ]
1198
      ~doc:
1199
        (sprintf
6✔
1200
           "PUBLICKEY Public-key address you wish to start snark-working on; \
1201
            null to stop doing any snark work. %s"
1202
           Cli_lib.Default.receiver_key_warning )
1203
      (optional Cli_lib.Arg_type.public_key_compressed)
6✔
1204
  in
1205
  Command.async
6✔
1206
    ~summary:"Set key you wish to snark work with or disable snark working"
1207
    (Cli_lib.Background_daemon.graphql_init public_key_flag
6✔
1208
       ~f:(fun graphql_endpoint optional_public_key ->
1209
         let graphql =
×
1210
           Graphql_queries.Set_snark_worker.(
1211
             make @@ makeVariables ?public_key:optional_public_key ())
×
1212
         in
1213
         Deferred.map (Graphql_client.query_exn graphql graphql_endpoint)
×
1214
           ~f:(fun response ->
1215
             ( match optional_public_key with
×
UNCOV
1216
             | Some public_key ->
×
1217
                 printf
×
1218
                   !"New snark worker public key : %s\n"
1219
                   (Public_key.Compressed.to_base58_check public_key)
×
UNCOV
1220
             | None ->
×
1221
                 printf "Will stop doing snark work\n" ) ;
×
1222
             printf "Previous snark worker public key : %s\n"
UNCOV
1223
               (Option.value_map response.setSnarkWorker.lastSnarkWorker
×
1224
                  ~default:"None" ~f:Public_key.Compressed.to_base58_check ) ) )
1225
    )
1226

1227
let set_snark_work_fee =
1228
  Command.async ~summary:"Set fee reward for doing transaction snark work"
6✔
1229
  @@ Cli_lib.Background_daemon.graphql_init
6✔
1230
       Command.Param.(anon @@ ("fee" %: Cli_lib.Arg_type.txn_fee))
6✔
1231
       ~f:(fun graphql_endpoint fee ->
1232
         let graphql =
×
1233
           Graphql_queries.Set_snark_work_fee.(
1234
             make @@ makeVariables ~fee:(Currency.Fee.to_uint64 fee) ())
×
1235
         in
1236
         Deferred.map (Graphql_client.query_exn graphql graphql_endpoint)
×
1237
           ~f:(fun response ->
1238
             printf
×
1239
               !"Updated snark work fee: %i\nOld snark work fee: %i\n"
UNCOV
1240
               (Currency.Fee.to_nanomina_int fee)
×
UNCOV
1241
               (Currency.Fee.to_nanomina_int response.setSnarkWorkFee.lastFee) )
×
1242
         )
1243

1244
let import_key =
1245
  Command.async
6✔
1246
    ~summary:
1247
      "Import a password protected private key to be tracked by the daemon.\n\
1248
       Set MINA_PRIVKEY_PASS environment variable to use non-interactively \
1249
       (key will be imported using the same password)."
1250
    (let%map_open.Command access_method =
1251
       choose_one
6✔
1252
         ~if_nothing_chosen:(Default_to `None)
1253
         [ Cli_lib.Flag.Uri.Client.rest_graphql_opt
1254
           |> map ~f:(Option.map ~f:(fun port -> `GraphQL port))
×
1255
         ; Cli_lib.Flag.conf_dir
UNCOV
1256
           |> map ~f:(Option.map ~f:(fun conf_dir -> `Conf_dir conf_dir))
×
1257
         ]
1258
     and privkey_path = Cli_lib.Flag.privkey_read_path in
1259
     fun () ->
UNCOV
1260
       let open Deferred.Let_syntax in
×
1261
       let initial_password = ref None in
1262
       let do_graphql graphql_endpoint =
1263
         let%bind password =
1264
           match Sys.getenv Secrets.Keypair.env with
1265
           | Some password ->
×
UNCOV
1266
               Deferred.return (Bytes.of_string password)
×
UNCOV
1267
           | None ->
×
1268
               let password =
1269
                 Secrets.Password.read_hidden_line ~error_help_message:""
1270
                   "Secret key password: "
1271
               in
UNCOV
1272
               initial_password := Some password ;
×
1273
               password
1274
         in
1275
         let graphql =
×
1276
           Graphql_queries.Import_account.(
1277
             make
×
UNCOV
1278
             @@ makeVariables ~path:privkey_path
×
1279
                  ~password:(Bytes.to_string password) ())
×
1280
         in
UNCOV
1281
         match%map Graphql_client.query graphql graphql_endpoint with
×
1282
         | Ok res ->
×
1283
             let res = res.importAccount in
1284
             if res.already_imported then Ok (`Already_imported res.public_key)
×
UNCOV
1285
             else Ok (`Imported res.public_key)
×
1286
         | Error (`Failed_request _ as err) ->
×
1287
             Error err
UNCOV
1288
         | Error (`Graphql_error _ as err) ->
×
1289
             Ok err
1290
       in
1291
       let do_local conf_dir =
UNCOV
1292
         let wallets_disk_location = conf_dir ^/ "wallets" in
×
1293
         let%bind ({ Keypair.public_key; _ } as keypair) =
1294
           let rec go () =
UNCOV
1295
             match !initial_password with
×
UNCOV
1296
             | None ->
×
1297
                 Secrets.Keypair.Terminal_stdin.read_exn ~which:"mina keypair"
1298
                   privkey_path
UNCOV
1299
             | Some password -> (
×
1300
                 (* We've already asked for the password once for a failed
1301
                    GraphQL query, try that one instead of asking again.
1302
                 *)
1303
                 match%bind
1304
                   Secrets.Keypair.read ~privkey_path
1305
                     ~password:(Lazy.return password)
×
1306
                 with
1307
                 | Ok res ->
×
1308
                     return res
1309
                 | Error `Incorrect_password_or_corrupted_privkey ->
×
1310
                     printf "Wrong password! Please try again\n" ;
1311
                     initial_password := None ;
×
1312
                     go ()
UNCOV
1313
                 | Error err ->
×
1314
                     Secrets.Privkey_error.raise ~which:"mina keypair" err )
1315
           in
1316
           go ()
×
1317
         in
1318
         let pk = Public_key.compress public_key in
×
1319
         let%bind wallets =
UNCOV
1320
           Secrets.Wallets.load ~logger:(Logger.create ())
×
1321
             ~disk_location:wallets_disk_location
1322
         in
1323
         (* Either we already are tracking it *)
UNCOV
1324
         match Secrets.Wallets.check_locked wallets ~needle:pk with
×
1325
         | Some _ ->
×
1326
             Deferred.return (`Already_imported pk)
UNCOV
1327
         | None ->
×
1328
             (* Or we import it *)
1329
             let%map pk =
1330
               Secrets.Wallets.import_keypair_terminal_stdin wallets keypair
×
1331
             in
UNCOV
1332
             `Imported pk
×
1333
       in
1334
       let print_result = function
UNCOV
1335
         | `Already_imported public_key ->
×
1336
             printf
1337
               !"Key already present, no need to import : %s\n"
UNCOV
1338
               (Public_key.Compressed.to_base58_check public_key)
×
UNCOV
1339
         | `Imported public_key ->
×
1340
             printf
1341
               !"\n😄 Imported account!\nPublic key: %s\n"
1342
               (Public_key.Compressed.to_base58_check public_key)
×
UNCOV
1343
         | `Graphql_error _ as e ->
×
UNCOV
1344
             don't_wait_for (Graphql_lib.Client.Connection_error.ok_exn e)
×
1345
       in
1346
       match access_method with
1347
       | `GraphQL graphql_endpoint -> (
×
UNCOV
1348
           match%map do_graphql graphql_endpoint with
×
1349
           | Ok res ->
×
1350
               print_result res
1351
           | Error err ->
×
1352
               don't_wait_for (Graphql_lib.Client.Connection_error.ok_exn err) )
×
1353
       | `Conf_dir conf_dir ->
×
1354
           let%map res = do_local conf_dir in
×
UNCOV
1355
           print_result res
×
UNCOV
1356
       | `None -> (
×
1357
           let default_graphql_endpoint =
1358
             Cli_lib.Flag.(Uri.Client.{ Types.name; value = default })
1359
           in
1360
           match%bind do_graphql default_graphql_endpoint with
×
1361
           | Ok res ->
×
UNCOV
1362
               Deferred.return (print_result res)
×
1363
           | Error _res ->
×
1364
               let conf_dir = Mina_lib.Conf_dir.compute_conf_dir None in
UNCOV
1365
               eprintf
×
1366
                 "%sWarning: Could not connect to a running daemon.\n\
1367
                  Importing to local directory %s%s\n"
1368
                 Bash_colors.orange conf_dir Bash_colors.none ;
UNCOV
1369
               let%map res = do_local conf_dir in
×
UNCOV
1370
               print_result res ) )
×
1371

1372
let export_key =
1373
  let privkey_path = Cli_lib.Flag.privkey_write_path in
1374
  let pk_flag =
1375
    let open Command.Param in
1376
    flag "--public-key" ~aliases:[ "public-key" ]
6✔
1377
      ~doc:"PUBLICKEY Public key of account to be exported"
1378
      (required Cli_lib.Arg_type.public_key_compressed)
6✔
1379
  in
1380
  let conf_dir = Cli_lib.Flag.conf_dir in
1381
  let flags = Args.zip3 privkey_path pk_flag conf_dir in
1382
  Command.async
6✔
1383
    ~summary:
1384
      "Export a tracked account so that it can be saved or transferred between \
1385
       machines.\n\
1386
      \ Set MINA_PRIVKEY_PASS environment variable to use non-interactively \
1387
       (key will be exported using the same password)."
1388
    (Cli_lib.Background_daemon.graphql_init flags
6✔
1389
       ~f:(fun _ (export_path, pk, conf_dir) ->
1390
         let open Deferred.Let_syntax in
×
UNCOV
1391
         let%bind home = Sys.home_directory () in
×
1392
         let conf_dir =
×
1393
           Option.value
UNCOV
1394
             ~default:(home ^/ Cli_lib.Default.conf_dir_name)
×
1395
             conf_dir
1396
         in
1397
         let wallets_disk_location = conf_dir ^/ "wallets" in
×
1398
         let%bind wallets =
UNCOV
1399
           Secrets.Wallets.load ~logger:(Logger.create ())
×
1400
             ~disk_location:wallets_disk_location
1401
         in
1402
         let password =
×
1403
           lazy
UNCOV
1404
             (Secrets.Password.hidden_line_or_env
×
1405
                "Password for exported account: " ~env:Secrets.Keypair.env )
1406
         in
1407
         let%bind account =
1408
           let open Deferred.Result.Let_syntax in
1409
           let%bind _ = Secrets.Wallets.unlock wallets ~needle:pk ~password in
×
UNCOV
1410
           Secrets.Wallets.find_identity wallets ~needle:pk
×
UNCOV
1411
           |> Result.of_option ~error:`Not_found
×
1412
           |> Deferred.return
1413
         in
1414
         let kp =
×
1415
           match account with
1416
           | Ok (`Keypair kp) ->
×
1417
               Ok kp
1418
           | Ok (`Hd_index i) ->
×
1419
               Error
UNCOV
1420
                 (sprintf
×
UNCOV
1421
                    !"account is an HD account (hardware wallet), the \
×
1422
                      associated index is %{Unsigned.UInt32}"
1423
                    i )
1424
           | Error `Bad_password ->
×
1425
               Error
UNCOV
1426
                 (sprintf
×
UNCOV
1427
                    !"wrong password provided for account \
×
1428
                      %{Public_key.Compressed.to_base58_check}"
1429
                    pk )
1430
           | Error (`Key_read_error e) ->
×
1431
               Error
UNCOV
1432
                 (sprintf
×
UNCOV
1433
                    !"Error reading the secret key file for account \
×
1434
                      %{Public_key.Compressed.to_base58_check}: %s"
1435
                    pk
UNCOV
1436
                    (Secrets.Privkey_error.to_string e) )
×
1437
           | Error `Not_found ->
×
1438
               Error
UNCOV
1439
                 (sprintf
×
UNCOV
1440
                    !"account not found corresponding to account \
×
1441
                      %{Public_key.Compressed.to_base58_check}"
1442
                    pk )
1443
         in
1444
         match kp with
1445
         | Ok kp ->
×
1446
             let%bind () =
UNCOV
1447
               Secrets.Keypair.Terminal_stdin.write_exn kp
×
1448
                 ~privkey_path:export_path
1449
             in
UNCOV
1450
             printf
×
1451
               !"😄 Account exported to %s: %s\n"
1452
               export_path
1453
               (Public_key.Compressed.to_base58_check pk) ;
×
UNCOV
1454
             Deferred.unit
×
1455
         | Error e ->
×
1456
             printf "❌ Export failed -- %s\n" e ;
UNCOV
1457
             Deferred.unit ) )
×
1458

1459
let list_accounts =
1460
  Command.async ~summary:"List all owned accounts"
6✔
1461
    (let%map_open.Command access_method =
1462
       choose_one
6✔
1463
         ~if_nothing_chosen:(Default_to `None)
1464
         [ Cli_lib.Flag.Uri.Client.rest_graphql_opt
1465
           |> map ~f:(Option.map ~f:(fun port -> `GraphQL port))
×
1466
         ; Cli_lib.Flag.conf_dir
UNCOV
1467
           |> map ~f:(Option.map ~f:(fun conf_dir -> `Conf_dir conf_dir))
×
1468
         ]
1469
     in
1470
     fun () ->
1471
       let do_graphql graphql_endpoint =
×
1472
         match%map
UNCOV
1473
           Graphql_client.query
×
UNCOV
1474
             Graphql_queries.Get_tracked_accounts.(make @@ makeVariables ())
×
1475
             graphql_endpoint
1476
         with
UNCOV
1477
         | Ok response -> (
×
1478
             match response.trackedAccounts with
1479
             | [||] ->
UNCOV
1480
                 printf
×
1481
                   "😢 You have no tracked accounts!\n\
1482
                    You can make a new one using `mina accounts create`\n" ;
UNCOV
1483
                 Ok ()
×
1484
             | accounts ->
×
1485
                 Array.iteri accounts ~f:(fun i w ->
UNCOV
1486
                     printf
×
1487
                       "Account .%d:\n\
1488
                       \  Public key: %s\n\
1489
                       \  Balance: %s\n\
1490
                       \  Locked: %b\n"
1491
                       (i + 1)
1492
                       (Public_key.Compressed.to_base58_check w.public_key)
×
1493
                       (Currency.Balance.to_mina_string w.balance.total)
×
1494
                       (Option.value ~default:true w.locked) ) ;
×
UNCOV
1495
                 Ok () )
×
1496
         | Error (`Failed_request _ as err) ->
×
1497
             Error err
1498
         | Error (`Graphql_error _ as err) ->
×
UNCOV
1499
             don't_wait_for (Graphql_lib.Client.Connection_error.ok_exn err) ;
×
UNCOV
1500
             Ok ()
×
1501
       in
1502
       let do_local conf_dir =
1503
         let wallets_disk_location = conf_dir ^/ "wallets" in
×
1504
         let%map wallets =
UNCOV
1505
           Secrets.Wallets.load ~logger:(Logger.create ())
×
1506
             ~disk_location:wallets_disk_location
1507
         in
UNCOV
1508
         match wallets |> Secrets.Wallets.pks with
×
UNCOV
1509
         | [] ->
×
1510
             printf
1511
               "😢 You have no tracked accounts!\n\
1512
                You can make a new one using `mina accounts create`\n"
1513
         | accounts ->
×
1514
             List.iteri accounts ~f:(fun i public_key ->
UNCOV
1515
                 printf "Account .%d:\n  Public key: %s\n" (i + 1)
×
UNCOV
1516
                   (Public_key.Compressed.to_base58_check public_key) )
×
1517
       in
1518
       match access_method with
1519
       | `GraphQL graphql_endpoint -> (
×
UNCOV
1520
           match%map do_graphql graphql_endpoint with
×
1521
           | Ok () ->
×
1522
               ()
1523
           | Error err ->
×
UNCOV
1524
               don't_wait_for (Graphql_lib.Client.Connection_error.ok_exn err) )
×
1525
       | `Conf_dir conf_dir ->
×
1526
           do_local conf_dir
UNCOV
1527
       | `None -> (
×
1528
           let default_graphql_endpoint =
1529
             Cli_lib.Flag.(Uri.Client.{ Types.name; value = default })
1530
           in
UNCOV
1531
           match%bind do_graphql default_graphql_endpoint with
×
1532
           | Ok () ->
×
1533
               Deferred.unit
1534
           | Error _res ->
×
1535
               let conf_dir = Mina_lib.Conf_dir.compute_conf_dir None in
UNCOV
1536
               eprintf
×
1537
                 "%sWarning: Could not connect to a running daemon.\n\
1538
                  Listing from local directory %s%s\n"
1539
                 Bash_colors.orange conf_dir Bash_colors.none ;
UNCOV
1540
               do_local conf_dir ) )
×
1541

1542
let create_account =
1543
  let open Command.Param in
1544
  Command.async ~summary:"Create new account"
6✔
1545
    (Cli_lib.Background_daemon.graphql_init (return ())
6✔
1546
       ~f:(fun graphql_endpoint () ->
1547
         let%bind password =
UNCOV
1548
           Secrets.Keypair.Terminal_stdin.prompt_password
×
1549
             "Password for new account: "
1550
         in
1551
         let%map response =
1552
           Graphql_client.query_exn
×
1553
             Graphql_queries.Create_account.(
UNCOV
1554
               make @@ makeVariables ~password:(Bytes.to_string password) ())
×
1555
             graphql_endpoint
1556
         in
UNCOV
1557
         let pk_string =
×
1558
           Public_key.Compressed.to_base58_check
1559
             response.createAccount.account.public_key
1560
         in
UNCOV
1561
         printf "\n😄 Added new account!\nPublic key: %s\n" pk_string ) )
×
1562

1563
let create_hd_account =
1564
  Command.async ~summary:Secrets.Hardware_wallets.create_hd_account_summary
6✔
1565
    (Cli_lib.Background_daemon.graphql_init Cli_lib.Flag.Signed_command.hd_index
6✔
1566
       ~f:(fun graphql_endpoint hd_index ->
1567
         let%map response =
1568
           Graphql_client.(
1569
             query_exn
×
1570
               Graphql_queries.Create_hd_account.(
UNCOV
1571
                 make @@ makeVariables ~hd_index ()))
×
1572
             graphql_endpoint
1573
         in
UNCOV
1574
         let pk_string =
×
1575
           Public_key.Compressed.to_base58_check
1576
             response.createHDAccount.account.public_key
1577
         in
UNCOV
1578
         printf "\n😄 created HD account with HD-index %s!\nPublic key: %s\n"
×
UNCOV
1579
           (Mina_numbers.Hd_index.to_string hd_index)
×
1580
           pk_string ) )
1581

1582
let unlock_account =
1583
  let open Command.Param in
1584
  let pk_flag =
1585
    flag "--public-key" ~aliases:[ "public-key" ]
1586
      ~doc:"PUBLICKEY Public key to be unlocked"
1587
      (required Cli_lib.Arg_type.public_key_compressed)
6✔
1588
  in
1589
  Command.async ~summary:"Unlock a tracked account"
6✔
1590
    (Cli_lib.Background_daemon.graphql_init pk_flag
6✔
1591
       ~f:(fun graphql_endpoint pk_str ->
1592
         let password =
×
1593
           Deferred.map ~f:Or_error.return
UNCOV
1594
             (Secrets.Password.hidden_line_or_env "Password to unlock account: "
×
1595
                ~env:Secrets.Keypair.env )
1596
         in
1597
         match%bind password with
1598
         | Ok password_bytes ->
×
1599
             let%map response =
1600
               Graphql_client.query_exn
×
1601
                 Graphql_queries.Unlock_account.(
1602
                   make
×
UNCOV
1603
                   @@ makeVariables ~public_key:pk_str
×
UNCOV
1604
                        ~password:(Bytes.to_string password_bytes)
×
1605
                        ())
1606
                 graphql_endpoint
1607
             in
UNCOV
1608
             let pk_string =
×
1609
               Public_key.Compressed.to_base58_check
1610
                 response.unlockAccount.account.public_key
1611
             in
UNCOV
1612
             printf "\n🔓 Unlocked account!\nPublic key: %s\n" pk_string
×
1613
         | Error e ->
×
1614
             Deferred.return
UNCOV
1615
               (printf "❌ Error unlocking account: %s\n" (Error.to_string_hum e)) )
×
1616
    )
1617

1618
let lock_account =
1619
  let open Command.Param in
1620
  let pk_flag =
1621
    flag "--public-key" ~aliases:[ "public-key" ]
1622
      ~doc:"PUBLICKEY Public key of account to be locked"
1623
      (required Cli_lib.Arg_type.public_key_compressed)
6✔
1624
  in
1625
  Command.async ~summary:"Lock a tracked account"
6✔
1626
    (Cli_lib.Background_daemon.graphql_init pk_flag
6✔
1627
       ~f:(fun graphql_endpoint pk ->
1628
         let%map response =
1629
           Graphql_client.query_exn
×
1630
             Graphql_queries.Lock_account.(
UNCOV
1631
               make @@ makeVariables ~public_key:pk ())
×
1632
             graphql_endpoint
1633
         in
UNCOV
1634
         let pk_string =
×
1635
           Public_key.Compressed.to_base58_check response.lockAccount.public_key
1636
         in
UNCOV
1637
         printf "🔒 Locked account!\nPublic key: %s\n" pk_string ) )
×
1638

1639
let generate_libp2p_keypair_do privkey_path =
1640
  Cli_lib.Exceptions.handle_nicely
×
1641
  @@ fun () ->
UNCOV
1642
  Deferred.ignore_m
×
1643
    (let open Deferred.Let_syntax in
1644
    (* FIXME: I'd like to accumulate messages into this logger and only dump them out in failure paths. *)
1645
    let logger = Logger.null () in
1646
    (* Using the helper only for keypair generation requires no state. *)
1647
    File_system.with_temp_dir "mina-generate-libp2p-keypair" ~f:(fun tmpd ->
×
1648
        match%bind
UNCOV
1649
          Mina_net2.create ~logger ~conf_dir:tmpd ~all_peers_seen_metric:false
×
UNCOV
1650
            ~pids:(Child_processes.Termination.create_pid_table ())
×
1651
            ~on_peer_connected:ignore ~on_peer_disconnected:ignore ()
1652
        with
1653
        | Ok net ->
×
UNCOV
1654
            let%bind me = Mina_net2.generate_random_keypair net in
×
1655
            let%bind () = Mina_net2.shutdown net in
×
1656
            let%map () =
1657
              Secrets.Libp2p_keypair.Terminal_stdin.write_exn ~privkey_path me
×
1658
            in
1659
            printf "libp2p keypair:\n%s\n" (Mina_net2.Keypair.to_string me)
×
1660
        | Error e ->
×
1661
            [%log fatal] "failed to generate libp2p keypair: $error"
×
UNCOV
1662
              ~metadata:[ ("error", Error_json.error_to_yojson e) ] ;
×
UNCOV
1663
            exit 20 ))
×
1664

1665
let generate_libp2p_keypair =
1666
  Command.async
6✔
1667
    ~summary:"Generate a new libp2p keypair and print out the peer ID"
1668
    (let open Command.Let_syntax in
1669
    let%map_open privkey_path = Cli_lib.Flag.privkey_write_path in
UNCOV
1670
    generate_libp2p_keypair_do privkey_path)
×
1671

1672
let dump_libp2p_keypair_do privkey_path =
1673
  Cli_lib.Exceptions.handle_nicely
×
1674
  @@ fun () ->
UNCOV
1675
  Deferred.ignore_m
×
1676
    (let open Deferred.Let_syntax in
1677
    let logger = Logger.null () in
1678
    (* Using the helper only for keypair generation requires no state. *)
1679
    File_system.with_temp_dir "mina-dump-libp2p-keypair" ~f:(fun tmpd ->
×
1680
        match%bind
UNCOV
1681
          Mina_net2.create ~logger ~conf_dir:tmpd ~all_peers_seen_metric:false
×
UNCOV
1682
            ~pids:(Child_processes.Termination.create_pid_table ())
×
1683
            ~on_peer_connected:ignore ~on_peer_disconnected:ignore ()
1684
        with
1685
        | Ok net ->
×
1686
            let%bind () = Mina_net2.shutdown net in
×
1687
            let%map me = Secrets.Libp2p_keypair.read_exn' privkey_path in
×
1688
            printf "libp2p keypair:\n%s\n" (Mina_net2.Keypair.to_string me)
×
1689
        | Error e ->
×
1690
            [%log fatal] "failed to dump libp2p keypair: $error"
×
UNCOV
1691
              ~metadata:[ ("error", Error_json.error_to_yojson e) ] ;
×
UNCOV
1692
            exit 20 ))
×
1693

1694
let dump_libp2p_keypair =
1695
  Command.async ~summary:"Print an existing libp2p keypair"
6✔
1696
    (let open Command.Let_syntax in
1697
    let%map_open privkey_path = Cli_lib.Flag.privkey_read_path in
UNCOV
1698
    dump_libp2p_keypair_do privkey_path)
×
1699

1700
let trustlist_ip_flag =
1701
  Command.Param.(
1702
    flag "--ip-address" ~aliases:[ "ip-address" ]
6✔
1703
      ~doc:"CIDR An IPv4 CIDR mask for the client trustlist (eg, 10.0.0.0/8)"
1704
      (required Cli_lib.Arg_type.cidr_mask))
6✔
1705

1706
let trustlist_add =
1707
  let open Deferred.Let_syntax in
1708
  let open Daemon_rpcs in
1709
  Command.async ~summary:"Add an IP to the trustlist"
6✔
1710
    (Cli_lib.Background_daemon.rpc_init trustlist_ip_flag
6✔
1711
       ~f:(fun port trustlist_ip ->
1712
         let trustlist_ip_string = Unix.Cidr.to_string trustlist_ip in
×
UNCOV
1713
         match%map Client.dispatch Add_trustlist.rpc trustlist_ip port with
×
1714
         | Ok (Ok ()) ->
×
1715
             printf "Added %s to client trustlist" trustlist_ip_string
1716
         | Ok (Error e) ->
×
1717
             eprintf "Error adding %s to client trustlist: %s"
UNCOV
1718
               trustlist_ip_string (Error.to_string_hum e)
×
1719
         | Error e ->
×
1720
             eprintf "Unknown error doing daemon RPC: %s"
UNCOV
1721
               (Error.to_string_hum e) ) )
×
1722

1723
let trustlist_remove =
1724
  let open Deferred.Let_syntax in
1725
  let open Daemon_rpcs in
1726
  Command.async ~summary:"Remove a CIDR mask from the trustlist"
6✔
1727
    (Cli_lib.Background_daemon.rpc_init trustlist_ip_flag
6✔
1728
       ~f:(fun port trustlist_ip ->
1729
         let trustlist_ip_string = Unix.Cidr.to_string trustlist_ip in
×
UNCOV
1730
         match%map Client.dispatch Remove_trustlist.rpc trustlist_ip port with
×
1731
         | Ok (Ok ()) ->
×
1732
             printf "Removed %s to client trustlist" trustlist_ip_string
1733
         | Ok (Error e) ->
×
1734
             eprintf "Error removing %s from client trustlist: %s"
UNCOV
1735
               trustlist_ip_string (Error.to_string_hum e)
×
1736
         | Error e ->
×
1737
             eprintf "Unknown error doing daemon RPC: %s"
UNCOV
1738
               (Error.to_string_hum e) ) )
×
1739

1740
let trustlist_list =
1741
  let open Deferred.Let_syntax in
1742
  let open Daemon_rpcs in
1743
  let open Command.Param in
1744
  Command.async ~summary:"List the CIDR masks in the trustlist"
6✔
1745
    (Cli_lib.Background_daemon.rpc_init (return ()) ~f:(fun port () ->
6✔
UNCOV
1746
         match%map Client.dispatch Get_trustlist.rpc () port with
×
UNCOV
1747
         | Ok ips ->
×
1748
             printf
1749
               "The following IPs are permitted to connect to the daemon \
1750
                control port:\n" ;
UNCOV
1751
             List.iter ips ~f:(fun ip -> printf "%s\n" (Unix.Cidr.to_string ip))
×
1752
         | Error e ->
×
1753
             eprintf "Unknown error doing daemon RPC: %s"
UNCOV
1754
               (Error.to_string_hum e) ) )
×
1755

1756
let get_peers_graphql =
1757
  Command.async ~summary:"List the peers currently connected to the daemon"
6✔
1758
    (Cli_lib.Background_daemon.graphql_init
6✔
1759
       Command.Param.(return ())
6✔
1760
       ~f:(fun graphql_endpoint () ->
1761
         let%map response =
UNCOV
1762
           Graphql_client.query_exn
×
UNCOV
1763
             Graphql_queries.Get_peers.(make @@ makeVariables ())
×
1764
             graphql_endpoint
1765
         in
1766
         Array.iter response.getPeers ~f:(fun peer ->
×
1767
             printf "%s\n"
×
UNCOV
1768
               (Network_peer.Peer.to_multiaddr_string
×
UNCOV
1769
                  { host = Unix.Inet_addr.of_string peer.host
×
1770
                  ; libp2p_port = peer.libp2pPort
1771
                  ; peer_id = peer.peerId
1772
                  } ) ) ) )
1773

1774
let add_peers_graphql =
1775
  let open Command in
1776
  let seed =
1777
    Param.(
1778
      flag "--seed" ~aliases:[ "-seed" ]
6✔
1779
        ~doc:
1780
          "true/false Whether to add these peers as 'seed' peers, which may \
1781
           perform peer exchange. Default: true"
1782
        (optional bool))
6✔
1783
  in
1784
  let peers =
1785
    Param.(anon Anons.(non_empty_sequence_as_list ("peer" %: string)))
6✔
1786
  in
1787
  Command.async
6✔
1788
    ~summary:
1789
      "Add peers to the daemon\n\n\
1790
       Addresses take the format /ip4/IPADDR/tcp/PORT/p2p/PEERID"
1791
    (Cli_lib.Background_daemon.graphql_init (Param.both peers seed)
6✔
1792
       ~f:(fun graphql_endpoint (input_peers, seed) ->
UNCOV
1793
         let open Deferred.Let_syntax in
×
1794
         let peers =
1795
           List.map input_peers ~f:(fun peer ->
UNCOV
1796
               match
×
UNCOV
1797
                 Mina_net2.Multiaddr.of_string peer
×
1798
                 |> Mina_net2.Multiaddr.to_peer
1799
               with
1800
               | Some peer ->
×
1801
                   peer
UNCOV
1802
               | None ->
×
1803
                   eprintf
1804
                     "Could not parse %s as a peer address. It should use the \
1805
                      format /ip4/IPADDR/tcp/PORT/p2p/PEERID"
1806
                     peer ;
1807
                   Core.exit 1 )
×
1808
         in
1809
         let seed = Option.value ~default:true seed in
×
1810
         let%map response =
UNCOV
1811
           Graphql_client.query_exn
×
UNCOV
1812
             Graphql_queries.Add_peers.(make @@ makeVariables ~peers ~seed ())
×
1813
             graphql_endpoint
1814
         in
1815
         printf "Requested to add peers:\n" ;
×
1816
         Array.iter response.addPeers ~f:(fun peer ->
×
1817
             printf "%s\n"
×
UNCOV
1818
               (Network_peer.Peer.to_multiaddr_string
×
UNCOV
1819
                  { host = Unix.Inet_addr.of_string peer.host
×
1820
                  ; libp2p_port = peer.libp2pPort
1821
                  ; peer_id = peer.peerId
1822
                  } ) ) ) )
1823

1824
let compile_time_constants =
1825
  let genesis_constants = Genesis_constants.Compiled.genesis_constants in
1826
  let constraint_constants = Genesis_constants.Compiled.constraint_constants in
1827
  let proof_level = Genesis_constants.Compiled.proof_level in
1828
  Command.async
6✔
1829
    ~summary:"Print a JSON map of the compile-time consensus parameters"
1830
    (Command.Param.return (fun () ->
6✔
1831
         let home = Core.Sys.home_directory () in
×
UNCOV
1832
         let conf_dir = home ^/ Cli_lib.Default.conf_dir_name in
×
1833
         let genesis_dir =
×
1834
           let home = Core.Sys.home_directory () in
UNCOV
1835
           home ^/ Cli_lib.Default.conf_dir_name
×
1836
         in
1837
         let config_file =
1838
           match Sys.getenv "MINA_CONFIG_FILE" with
1839
           | Some config_file ->
×
1840
               config_file
UNCOV
1841
           | None ->
×
UNCOV
1842
               conf_dir ^/ "daemon.json"
×
1843
         in
1844
         let open Async in
1845
         let%map ({ consensus_constants; _ } as precomputed_values), _ =
UNCOV
1846
           config_file |> Genesis_ledger_helper.load_config_json >>| Or_error.ok
×
UNCOV
1847
           >>| Option.value
×
1848
                 ~default:
1849
                   (`Assoc [ ("ledger", `Assoc [ ("accounts", `List []) ]) ])
1850
           >>| Runtime_config.of_yojson >>| Result.ok
×
1851
           >>| Option.value ~default:Runtime_config.default
×
UNCOV
1852
           >>= Genesis_ledger_helper.init_from_config_file ~genesis_constants
×
1853
                 ~constraint_constants ~logger:(Logger.null ()) ~proof_level
×
1854
                 ~cli_proof_level:None ~genesis_dir
1855
           >>| Or_error.ok_exn
×
1856
         in
UNCOV
1857
         let all_constants =
×
1858
           `Assoc
1859
             [ ( "genesis_state_timestamp"
1860
               , `String
1861
                   ( Block_time.to_time_exn
×
1862
                       consensus_constants.genesis_state_timestamp
1863
                   |> Core.Time.to_string_iso8601_basic ~zone:Core.Time.Zone.utc
×
1864
                   ) )
UNCOV
1865
             ; ("k", `Int (Unsigned.UInt32.to_int consensus_constants.k))
×
1866
             ; ( "coinbase"
1867
               , `String
UNCOV
1868
                   (Currency.Amount.to_mina_string
×
1869
                      precomputed_values.constraint_constants.coinbase_amount )
1870
               )
1871
             ; ( "block_window_duration_ms"
1872
               , `Int
1873
                   precomputed_values.constraint_constants
1874
                     .block_window_duration_ms )
UNCOV
1875
             ; ("delta", `Int (Unsigned.UInt32.to_int consensus_constants.delta))
×
1876
             ; ( "sub_windows_per_window"
1877
               , `Int
UNCOV
1878
                   (Unsigned.UInt32.to_int
×
1879
                      consensus_constants.sub_windows_per_window ) )
1880
             ; ( "slots_per_sub_window"
1881
               , `Int
UNCOV
1882
                   (Unsigned.UInt32.to_int
×
1883
                      consensus_constants.slots_per_sub_window ) )
1884
             ; ( "slots_per_window"
1885
               , `Int
UNCOV
1886
                   (Unsigned.UInt32.to_int consensus_constants.slots_per_window)
×
1887
               )
1888
             ; ( "slots_per_epoch"
1889
               , `Int
UNCOV
1890
                   (Unsigned.UInt32.to_int consensus_constants.slots_per_epoch)
×
1891
               )
1892
             ]
1893
         in
UNCOV
1894
         Core_kernel.printf "%s\n%!" (Yojson.Safe.to_string all_constants) ) )
×
1895

1896
let node_status =
1897
  let open Command.Param in
1898
  let open Deferred.Let_syntax in
1899
  let daemon_peers_flag =
1900
    flag "--daemon-peers" ~aliases:[ "daemon-peers" ] no_arg
1901
      ~doc:"Get node statuses for peers known to the daemon"
1902
  in
1903
  let peers_flag =
6✔
1904
    flag "--peers" ~aliases:[ "peers" ]
1905
      (optional (Arg_type.comma_separated string))
6✔
1906
      ~doc:"CSV-LIST Peer multiaddrs for obtaining node status"
1907
  in
1908
  let show_errors_flag =
6✔
1909
    flag "--show-errors" ~aliases:[ "show-errors" ] no_arg
1910
      ~doc:"Include error responses in output"
1911
  in
1912
  let flags = Args.zip3 daemon_peers_flag peers_flag show_errors_flag in
6✔
1913
  Command.async ~summary:"Get node statuses for a set of peers"
6✔
1914
    (Cli_lib.Background_daemon.rpc_init flags
6✔
1915
       ~f:(fun port (daemon_peers, peers, show_errors) ->
1916
         if
×
1917
           (Option.is_none peers && not daemon_peers)
×
UNCOV
1918
           || (Option.is_some peers && daemon_peers)
×
UNCOV
1919
         then (
×
1920
           eprintf
1921
             "Must provide exactly one of daemon-peers or peer-ids flags\n%!" ;
UNCOV
1922
           don't_wait_for (exit 33) ) ;
×
1923
         let peer_ids_opt =
×
1924
           Option.map peers ~f:(fun peers ->
UNCOV
1925
               List.map peers ~f:Mina_net2.Multiaddr.of_string )
×
1926
         in
1927
         match%map
UNCOV
1928
           Daemon_rpcs.Client.dispatch Daemon_rpcs.Get_node_status.rpc
×
1929
             peer_ids_opt port
1930
         with
1931
         | Ok all_status_data ->
×
1932
             let all_status_data =
1933
               if show_errors then all_status_data
×
1934
               else
UNCOV
1935
                 List.filter all_status_data ~f:(fun td ->
×
UNCOV
1936
                     match td with Ok _ -> true | Error _ -> false )
×
1937
             in
1938
             List.iter all_status_data ~f:(fun peer_status_data ->
1939
                 printf "%s\n%!"
×
UNCOV
1940
                   ( Yojson.Safe.to_string
×
1941
                   @@ Mina_networking.Node_status.response_to_yojson
×
1942
                        peer_status_data ) )
1943
         | Error err ->
×
1944
             printf "Failed to get node status: %s\n%!"
UNCOV
1945
               (Error.to_string_hum err) ) )
×
1946

1947
let object_lifetime_statistics =
1948
  let open Daemon_rpcs in
1949
  let open Command.Param in
1950
  Command.async ~summary:"Dump internal object lifetime statistics to JSON"
6✔
1951
    (Cli_lib.Background_daemon.rpc_init (return ()) ~f:(fun port () ->
6✔
1952
         match%map
1953
           Client.dispatch Get_object_lifetime_statistics.rpc () port
×
1954
         with
1955
         | Ok stats ->
×
1956
             print_endline stats
1957
         | Error err ->
×
1958
             printf "Failed to get object lifetime statistics: %s\n%!"
UNCOV
1959
               (Error.to_string_hum err) ) )
×
1960

1961
let archive_blocks =
1962
  let params =
1963
    let open Command.Let_syntax in
1964
    let%map_open files =
1965
      Command.Param.anon
6✔
1966
        Command.Anons.(sequence ("FILES" %: Command.Param.string))
6✔
1967
    and success_file =
1968
      Command.Param.flag "--successful-files" ~aliases:[ "successful-files" ]
6✔
1969
        ~doc:"PATH Appends the list of files that were processed successfully"
1970
        (Command.Flag.optional Command.Param.string)
6✔
1971
    and failure_file =
1972
      Command.Param.flag "--failed-files" ~aliases:[ "failed-files" ]
6✔
1973
        ~doc:"PATH Appends the list of files that failed to be processed"
1974
        (Command.Flag.optional Command.Param.string)
6✔
1975
    and log_successes =
1976
      Command.Param.flag "--log-successful" ~aliases:[ "log-successful" ]
6✔
1977
        ~doc:
1978
          "true/false Whether to log messages for files that were processed \
1979
           successfully"
1980
        (Command.Flag.optional_with_default true Command.Param.bool)
6✔
1981
    and archive_process_location = Cli_lib.Flag.Host_and_port.Daemon.archive
1982
    and precomputed_flag =
1983
      Command.Param.flag "--precomputed" ~aliases:[ "precomputed" ] no_arg
6✔
1984
        ~doc:"Blocks are in precomputed JSON format"
1985
    and extensional_flag =
1986
      Command.Param.flag "--extensional" ~aliases:[ "extensional" ] no_arg
6✔
1987
        ~doc:"Blocks are in extensional JSON format"
1988
    in
UNCOV
1989
    ( files
×
1990
    , success_file
1991
    , failure_file
1992
    , log_successes
1993
    , archive_process_location
1994
    , precomputed_flag
1995
    , extensional_flag )
1996
  in
1997
  Command.async
6✔
1998
    ~summary:
1999
      "Archive a block from a file.\n\n\
2000
       If an archive address is given, this process will communicate with the \
2001
       archive node directly; otherwise it will communicate through the daemon \
2002
       over the rest-server"
2003
    (Cli_lib.Background_daemon.graphql_init params
6✔
2004
       ~f:(fun
2005
            graphql_endpoint
2006
            ( files
2007
            , success_file
2008
            , failure_file
2009
            , log_successes
2010
            , archive_process_location
2011
            , precomputed_flag
2012
            , extensional_flag )
2013
          ->
UNCOV
2014
         if Bool.equal precomputed_flag extensional_flag then
×
2015
           failwith
×
2016
             "Must provide exactly one of -precomputed and -extensional flags" ;
2017
         let make_send_block ~graphql_make ~archive_dispatch block =
×
UNCOV
2018
           match archive_process_location with
×
UNCOV
2019
           | Some archive_process_location ->
×
2020
               (* Connect directly to the archive node. *)
2021
               archive_dispatch archive_process_location block
UNCOV
2022
           | None ->
×
2023
               (* Send the requests over GraphQL. *)
2024
               let%map.Deferred.Or_error _res =
2025
                 (* Don't catch this error: [query_exn] already handles
2026
                    printing etc.
2027
                 *)
2028
                 Graphql_client.query (graphql_make block) graphql_endpoint
×
UNCOV
2029
                 |> Deferred.Result.map_error ~f:(function
×
UNCOV
2030
                      | `Failed_request e ->
×
2031
                          Error.create "Unable to connect to Mina daemon" ()
2032
                            (fun () ->
UNCOV
2033
                              Sexp.List
×
2034
                                [ List
2035
                                    [ Atom "uri"
2036
                                    ; Atom
UNCOV
2037
                                        (Uri.to_string graphql_endpoint.value)
×
2038
                                    ]
2039
                                ; List
2040
                                    [ Atom "uri_flag"
2041
                                    ; Atom graphql_endpoint.name
2042
                                    ]
2043
                                ; List [ Atom "error_message"; Atom e ]
2044
                                ] )
UNCOV
2045
                      | `Graphql_error e ->
×
2046
                          Error.createf "GraphQL error: %s" e )
2047
               in
UNCOV
2048
               ()
×
2049
         in
2050
         let output_file_line path =
UNCOV
2051
           match path with
×
2052
           | Some path ->
×
2053
               let file = Out_channel.create ~append:true path in
2054
               fun line -> Out_channel.output_lines file [ line ]
×
UNCOV
2055
           | None ->
×
UNCOV
2056
               fun _line -> ()
×
2057
         in
2058
         let add_to_success_file = output_file_line success_file in
UNCOV
2059
         let add_to_failure_file = output_file_line failure_file in
×
UNCOV
2060
         let send_precomputed_block =
×
2061
           make_send_block
2062
             ~graphql_make:(fun block ->
UNCOV
2063
               Graphql_queries.Archive_precomputed_block.(
×
UNCOV
2064
                 make @@ makeVariables ~block ()) )
×
2065
             ~archive_dispatch:
2066
               Mina_lib.Archive_client.dispatch_precomputed_block
2067
         in
2068
         let send_extensional_block =
2069
           make_send_block
2070
             ~graphql_make:(fun block ->
UNCOV
2071
               Graphql_queries.Archive_extensional_block.(
×
UNCOV
2072
                 make @@ makeVariables ~block ()) )
×
2073
             ~archive_dispatch:
2074
               Mina_lib.Archive_client.dispatch_extensional_block
2075
         in
2076
         Deferred.List.iter files ~f:(fun path ->
2077
             match%map
2078
               let%bind.Deferred.Or_error block_json =
2079
                 Or_error.try_with (fun () ->
×
2080
                     In_channel.with_file path ~f:(fun in_channel ->
×
2081
                         Yojson.Safe.from_channel in_channel ) )
×
UNCOV
2082
                 |> Result.map_error ~f:(fun err ->
×
2083
                        Error.tag_arg err "Could not parse JSON from file" path
×
2084
                          String.sexp_of_t )
2085
                 |> Deferred.return
×
2086
               in
UNCOV
2087
               let open Deferred.Or_error.Let_syntax in
×
2088
               if precomputed_flag then
2089
                 let%bind precomputed_block =
2090
                   Mina_block.Precomputed.of_yojson block_json
×
UNCOV
2091
                   |> Result.map_error ~f:(fun err ->
×
UNCOV
2092
                          Error.tag_arg (Error.of_string err)
×
2093
                            "Could not parse JSON as a precomputed block from \
2094
                             file"
2095
                            path String.sexp_of_t )
2096
                   |> Deferred.return
×
2097
                 in
UNCOV
2098
                 send_precomputed_block precomputed_block
×
2099
               else if extensional_flag then
×
2100
                 let%bind extensional_block =
2101
                   Archive_lib.Extensional.Block.of_yojson block_json
×
UNCOV
2102
                   |> Result.map_error ~f:(fun err ->
×
UNCOV
2103
                          Error.tag_arg (Error.of_string err)
×
2104
                            "Could not parse JSON as an extensional block from \
2105
                             file"
2106
                            path String.sexp_of_t )
2107
                   |> Deferred.return
×
2108
                 in
UNCOV
2109
                 send_extensional_block extensional_block
×
2110
               else
2111
                 (* should be unreachable *)
UNCOV
2112
                 failwith
×
2113
                   "Expected exactly one of precomputed, extensional flags"
2114
             with
2115
             | Ok () ->
×
2116
                 if log_successes then
2117
                   Format.printf "Sent block to archive node from %s@." path ;
×
UNCOV
2118
                 add_to_success_file path
×
UNCOV
2119
             | Error err ->
×
2120
                 Format.eprintf
2121
                   "@[<v>Failed to send block to archive node from %s.@,\
2122
                    Error:@,\
2123
                    %s@]@."
UNCOV
2124
                   path (Error.to_string_hum err) ;
×
UNCOV
2125
                 add_to_failure_file path ) ) )
×
2126

2127
let receipt_chain_hash =
2128
  let open Command.Let_syntax in
2129
  Command.basic
6✔
2130
    ~summary:
2131
      "Compute the next receipt chain hash from the previous hash and \
2132
       transaction ID"
2133
    (let%map_open previous_hash =
2134
       flag "--previous-hash"
6✔
2135
         ~doc:"HASH Previous receipt chain hash, Base58Check-encoded"
2136
         (required string)
6✔
2137
     and transaction_id =
2138
       flag "--transaction-id"
6✔
2139
         ~doc:"TRANSACTION_ID Transaction ID, Base64-encoded" (required string)
6✔
2140
     and index =
2141
       flag "--index"
6✔
2142
         ~doc:
2143
           "NN For a zkApp, 0 for fee payer or 1-based index of account update"
2144
         (optional string)
6✔
2145
     in
2146
     fun () ->
UNCOV
2147
       let previous_hash =
×
2148
         Receipt.Chain_hash.of_base58_check_exn previous_hash
2149
       in
2150
       let hash =
×
2151
         match index with
2152
         | None ->
×
2153
             let signed_cmd =
2154
               Signed_command.of_base64 transaction_id |> Or_error.ok_exn
×
2155
             in
2156
             Receipt.Chain_hash.cons_signed_command_payload
×
2157
               (Signed_command_payload signed_cmd.payload) previous_hash
2158
         | Some n ->
×
2159
             let zkapp_cmd =
2160
               Zkapp_command.of_base64 transaction_id |> Or_error.ok_exn
×
2161
             in
UNCOV
2162
             let receipt_elt =
×
2163
               let _txn_commitment, full_txn_commitment =
2164
                 Zkapp_command.get_transaction_commitments
2165
                   (Zkapp_command.write_all_proofs_to_disk zkapp_cmd)
×
2166
               in
UNCOV
2167
               Receipt.Zkapp_command_elt.Zkapp_command_commitment
×
2168
                 full_txn_commitment
2169
             in
2170
             let account_update_index = Mina_numbers.Index.of_string n in
UNCOV
2171
             Receipt.Chain_hash.cons_zkapp_command_commitment
×
2172
               account_update_index receipt_elt previous_hash
2173
       in
UNCOV
2174
       printf "%s\n" (Receipt.Chain_hash.to_base58_check hash) )
×
2175

2176
let chain_id_inputs =
2177
  let open Deferred.Let_syntax in
2178
  Command.async ~summary:"Print the inputs that yield the current chain id"
6✔
2179
    (Cli_lib.Background_daemon.rpc_init (Command.Param.all_unit [])
6✔
2180
       ~f:(fun port () ->
2181
         let open Daemon_rpcs in
×
UNCOV
2182
         match%map Client.dispatch Chain_id_inputs.rpc () port with
×
UNCOV
2183
         | Ok
×
2184
             ( genesis_state_hash
2185
             , genesis_constants
2186
             , snark_keys
2187
             , protocol_transaction_version
2188
             , protocol_network_version ) ->
2189
             let open Format in
2190
             printf
2191
               "@[<v>Genesis state hash: %s@,\
2192
                @[<v 2>Genesis_constants:@,\
2193
                Protocol:          %a@,\
2194
                Txn pool max size: %d@,\
2195
                Num accounts:      %a@,\
2196
                @]@,\
2197
                @[<v 2>Snark keys:@,\
2198
                %a@]@,\
2199
                Protocol transaction version: %u@,\
2200
                Protocol network version: %u@]@."
2201
               (State_hash.to_base58_check genesis_state_hash)
×
2202
               Yojson.Safe.pp
2203
               (Genesis_constants.Protocol.to_yojson genesis_constants.protocol)
×
2204
               genesis_constants.txpool_max_size
UNCOV
2205
               (pp_print_option
×
UNCOV
2206
                  ~none:(fun ppf () -> pp_print_string ppf "None")
×
2207
                  pp_print_int )
2208
               genesis_constants.num_accounts
2209
               (pp_print_list ~pp_sep:pp_print_cut pp_print_string)
×
2210
               snark_keys protocol_transaction_version protocol_network_version
2211
         | Error err ->
×
2212
             Format.eprintf "Could not get chain id inputs: %s@."
UNCOV
2213
               (Error.to_string_hum err) ) )
×
2214

2215
let hash_transaction =
2216
  let open Command.Let_syntax in
2217
  Command.basic
6✔
2218
    ~summary:"Compute the hash of a transaction from its transaction ID"
2219
    (let%map_open transaction_id =
2220
       flag "--transaction-id" ~doc:"ID ID of the transaction to hash"
6✔
2221
         (required string)
6✔
2222
     in
2223
     fun () ->
2224
       match Transaction_hash.hash_of_transaction_id transaction_id with
×
2225
       | Ok hash ->
×
UNCOV
2226
           printf "%s\n" (Transaction_hash.to_base58_check hash)
×
2227
       | Error err ->
×
2228
           Format.eprintf "Could not hash transaction: %s@."
UNCOV
2229
             (Error.to_string_hum err) )
×
2230

2231
let humanize_graphql_error
2232
    ~(graphql_endpoint : Uri.t Cli_lib.Flag.Types.with_name) = function
2233
  | `Failed_request e ->
×
2234
      Error.create "Unable to connect to Mina daemon" () (fun () ->
UNCOV
2235
          Sexp.List
×
UNCOV
2236
            [ List [ Atom "uri"; Atom (Uri.to_string graphql_endpoint.value) ]
×
2237
            ; List [ Atom "uri_flag"; Atom graphql_endpoint.name ]
2238
            ; List [ Atom "error_message"; Atom e ]
2239
            ] )
UNCOV
2240
  | `Graphql_error e ->
×
2241
      Error.createf "GraphQL error: %s" e
2242

2243
let runtime_config =
2244
  Command.async
6✔
2245
    ~summary:"Compute the runtime configuration used by a running daemon"
2246
    (Cli_lib.Background_daemon.graphql_init (Command.Param.return ())
6✔
2247
       ~f:(fun graphql_endpoint () ->
2248
         match%bind
UNCOV
2249
           Graphql_client.query
×
UNCOV
2250
             Graphql_queries.Runtime_config.(make @@ makeVariables ())
×
2251
             graphql_endpoint
2252
         with
2253
         | Ok runtime_config ->
×
2254
             Format.printf "%s@."
2255
               (Yojson.Basic.pretty_to_string runtime_config.runtimeConfig) ;
×
UNCOV
2256
             return ()
×
UNCOV
2257
         | Error err ->
×
2258
             Format.eprintf
2259
               "@[<v>Failed to retrieve runtime configuration. Error:@,%s@]@."
2260
               (Error.to_string_hum
×
UNCOV
2261
                  (humanize_graphql_error ~graphql_endpoint err) ) ;
×
UNCOV
2262
             exit 1 ) )
×
2263

2264
let thread_graph =
2265
  Command.async
6✔
2266
    ~summary:
2267
      "Return a Graphviz Dot graph representation of the internal thread \
2268
       hierarchy"
2269
    (Cli_lib.Background_daemon.graphql_init (Command.Param.return ())
6✔
2270
       ~f:(fun graphql_endpoint () ->
2271
         match%bind
UNCOV
2272
           Graphql_client.query
×
UNCOV
2273
             Graphql_queries.Thread_graph.(make @@ makeVariables ())
×
2274
             graphql_endpoint
2275
         with
2276
         | Ok graph ->
×
2277
             print_endline graph.threadGraph ;
UNCOV
2278
             return ()
×
UNCOV
2279
         | Error e ->
×
2280
             Format.eprintf
2281
               "@[<v>Failed to retrieve runtime configuration. Error:@,%s@]@."
2282
               (Error.to_string_hum
×
UNCOV
2283
                  (humanize_graphql_error ~graphql_endpoint e) ) ;
×
UNCOV
2284
             exit 1 ) )
×
2285

2286
let signature_kind =
2287
  Command.basic
6✔
2288
    ~summary:"Print the signature kind that this binary is compiled with"
2289
    (let%map.Command () = Command.Param.return () in
6✔
2290
     fun () ->
2291
       let signature_kind_string =
×
2292
         match Mina_signature_kind.t with
2293
         | Mainnet ->
×
2294
             "mainnet"
2295
         | Testnet ->
×
2296
             "testnet"
UNCOV
2297
         | Other_network s ->
×
2298
             (* Prefix string to disambiguate *)
2299
             "other network: " ^ s
2300
       in
2301
       Core.print_endline signature_kind_string )
2302

2303
let test_genesis_creation =
2304
  Command.async ~summary:"Test genesis creation"
6✔
2305
    (let%map_open.Command () = Command.Param.return () in
6✔
UNCOV
2306
     Cli_lib.Exceptions.handle_nicely
×
2307
       Test_genesis_creation.time_genesis_creation )
2308

2309
let test_ledger_application =
2310
  Command.async ~summary:"Test ledger application"
6✔
2311
    (let%map_open.Command privkey_path = Cli_lib.Flag.privkey_read_path
2312
     and prev_block_path =
2313
       flag "--prev-block-path" ~doc:"FILE file with serialized block"
6✔
2314
         (optional string)
6✔
2315
     and ledger_path =
2316
       flag "--ledger-path" ~doc:"FILE directory with ledger DB"
6✔
2317
         (required string)
6✔
2318
     and num_txs =
2319
       flag "--num-txs"
6✔
2320
         ~doc:"NN Number of transactions to create after preparatory rounds"
2321
         (required int)
6✔
2322
     and num_txs_per_round =
2323
       flag "--num-txs-per-round"
6✔
2324
         ~doc:
2325
           "NN Number of transactions to create per preparatory round \
2326
            (default: 3)"
2327
         (optional int)
6✔
2328
     and rounds =
2329
       flag "--rounds" ~doc:"NN Number of preparatory rounds (default: 580)"
6✔
2330
         (optional int)
6✔
2331
     and first_partition_slots =
2332
       flag "--first-partition-slots"
6✔
2333
         ~doc:
2334
           "NN Number of slots in first partition of scan state (default: 128)"
2335
         (optional int)
6✔
2336
     and max_depth =
2337
       flag "--max-depth" ~doc:"NN Maximum depth of masks (default: 290)"
6✔
2338
         (optional int)
6✔
2339
     and no_new_stack =
2340
       flag "--old-stack" ~doc:"Use is_new_stack: false (scan state)" no_arg
6✔
2341
     and has_second_partition =
2342
       flag "--has-second-partition"
6✔
2343
         ~doc:"Assume there is a second partition (scan state)" no_arg
2344
     and tracing = flag "--tracing" ~doc:"Wrap test into tracing" no_arg
6✔
2345
     and no_masks = flag "--no-masks" ~doc:"Do not create masks" no_arg
6✔
2346
     and benchmark =
2347
       flag "--dump-benchmark" ~doc:"Dump json file with benchmark data"
6✔
2348
         (optional string)
6✔
2349
     in
2350
     Cli_lib.Exceptions.handle_nicely
2✔
2351
     @@ fun () ->
2352
     let first_partition_slots =
2✔
2353
       Option.value ~default:128 first_partition_slots
2354
     in
2355
     let num_txs_per_round = Option.value ~default:3 num_txs_per_round in
2✔
2356
     let rounds = Option.value ~default:580 rounds in
2✔
2357
     let max_depth = Option.value ~default:290 max_depth in
2✔
2358
     let constraint_constants =
2✔
2359
       Genesis_constants.Compiled.constraint_constants
2360
     in
2361
     let genesis_constants = Genesis_constants.Compiled.genesis_constants in
2362
     Test_ledger_application.test ~privkey_path ~ledger_path ?prev_block_path
2363
       ~first_partition_slots ~no_new_stack ~has_second_partition
2364
       ~num_txs_per_round ~rounds ~no_masks ~max_depth ~tracing num_txs
2365
       ~constraint_constants ~genesis_constants ~benchmark )
2366

2367
let itn_create_accounts =
2368
  let compile_config = Mina_compile_config.Compiled.t in
2369
  Command.async ~summary:"Fund new accounts for incentivized testnet"
6✔
2370
    (let open Command.Param in
2371
    let privkey_path = Cli_lib.Flag.privkey_read_path in
2372
    let key_prefix =
2373
      flag "--key-prefix" ~doc:"STRING prefix of keyfiles" (required string)
6✔
2374
    in
2375
    let num_accounts =
6✔
2376
      flag "--num-accounts" ~doc:"NN Number of new accounts" (required int)
6✔
2377
    in
2378
    let fee =
6✔
2379
      flag "--fee"
2380
        ~doc:
2381
          (sprintf "NN Fee in nanomina paid to create an account (minimum: %s)"
6✔
2382
             (Currency.Fee.to_string compile_config.minimum_user_command_fee) )
6✔
2383
        (required int)
6✔
2384
    in
2385
    let amount =
6✔
2386
      flag "--amount"
2387
        ~doc:"NN Amount in nanomina to be divided among new accounts"
2388
        (required int)
6✔
2389
    in
2390
    let args = Args.zip5 privkey_path key_prefix num_accounts fee amount in
6✔
2391
    let genesis_constants = Genesis_constants.Compiled.genesis_constants in
6✔
2392
    let constraint_constants =
2393
      Genesis_constants.Compiled.constraint_constants
2394
    in
2395
    Cli_lib.Background_daemon.rpc_init args
6✔
2396
      ~f:(Itn.create_accounts ~genesis_constants ~constraint_constants))
2397

2398
module Visualization = struct
2399
  let create_command (type rpc_response) ~name ~f
2400
      (rpc : (string, rpc_response) Rpc.Rpc.t) =
2401
    let open Deferred.Let_syntax in
12✔
2402
    Command.async
2403
      ~summary:(sprintf !"Produce a visualization of the %s" name)
12✔
2404
      (Cli_lib.Background_daemon.rpc_init
12✔
2405
         Command.Param.(anon @@ ("output-filepath" %: string))
12✔
2406
         ~f:(fun port filename ->
2407
           let%map message =
UNCOV
2408
             match%map Daemon_rpcs.Client.dispatch rpc filename port with
×
2409
             | Ok response ->
×
2410
                 f filename response
UNCOV
2411
             | Error e ->
×
2412
                 sprintf "Could not save file: %s\n" (Error.to_string_hum e)
×
2413
           in
UNCOV
2414
           print_string message ) )
×
2415

2416
  module Frontier = struct
2417
    let name = "transition-frontier"
2418

2419
    let command =
2420
      create_command ~name Daemon_rpcs.Visualization.Frontier.rpc
6✔
2421
        ~f:(fun filename -> function
2422
        | `Active () ->
×
2423
            Visualization_message.success name filename
UNCOV
2424
        | `Bootstrapping ->
×
2425
            Visualization_message.bootstrap name )
2426
  end
2427

2428
  module Registered_masks = struct
2429
    let name = "registered-masks"
2430

2431
    let command =
2432
      create_command ~name Daemon_rpcs.Visualization.Registered_masks.rpc
6✔
UNCOV
2433
        ~f:(fun filename () -> Visualization_message.success name filename)
×
2434
  end
2435

2436
  let command_group =
2437
    Command.group ~summary:"Visualize data structures special to Mina"
6✔
2438
      [ (Frontier.name, Frontier.command)
2439
      ; (Registered_masks.name, Registered_masks.command)
2440
      ]
2441
end
2442

2443
let accounts =
2444
  Command.group ~summary:"Client commands concerning account management"
6✔
2445
    ~preserve_subcommand_order:()
2446
    [ ("list", list_accounts)
2447
    ; ("create", create_account)
2448
    ; ("import", import_key)
2449
    ; ("export", export_key)
2450
    ; ("unlock", unlock_account)
2451
    ; ("lock", lock_account)
2452
    ]
2453

2454
let client =
2455
  Command.group ~summary:"Lightweight client commands"
6✔
2456
    ~preserve_subcommand_order:()
2457
    [ ("get-balance", get_balance_graphql)
2458
    ; ("get-tokens", get_tokens_graphql)
2459
    ; ("send-payment", send_payment_graphql)
2460
    ; ("delegate-stake", delegate_stake_graphql)
2461
    ; ("cancel-transaction", cancel_transaction_graphql)
2462
    ; ("set-snark-worker", set_snark_worker)
2463
    ; ("set-snark-work-fee", set_snark_work_fee)
2464
    ; ("export-logs", Export_logs.export_via_graphql)
2465
    ; ("export-local-logs", Export_logs.export_locally)
2466
    ; ("stop-daemon", stop_daemon)
2467
    ; ("status", status)
2468
    ]
2469

2470
let client_trustlist_group =
2471
  Command.group ~summary:"Client trustlist management"
6✔
2472
    ~preserve_subcommand_order:()
2473
    [ ("add", trustlist_add)
2474
    ; ("list", trustlist_list)
2475
    ; ("remove", trustlist_remove)
2476
    ]
2477

2478
let advanced ~itn_features =
2479
  let cmds0 =
6✔
2480
    [ ("get-nonce", get_nonce_cmd)
2481
    ; ("client-trustlist", client_trustlist_group)
2482
    ; ("get-trust-status", get_trust_status)
2483
    ; ("get-trust-status-all", get_trust_status_all)
2484
    ; ("get-public-keys", get_public_keys)
2485
    ; ("reset-trust-status", reset_trust_status)
2486
    ; ("batch-send-payments", batch_send_payments)
2487
    ; ("status-clear-hist", status_clear_hist)
2488
    ; ("wrap-key", wrap_key)
2489
    ; ("dump-keypair", dump_keypair)
2490
    ; ("constraint-system-digests", constraint_system_digests)
2491
    ; ("start-tracing", start_tracing)
2492
    ; ("stop-tracing", stop_tracing)
2493
    ; ("start-internal-tracing", start_internal_tracing)
2494
    ; ("stop-internal-tracing", stop_internal_tracing)
2495
    ; ("snark-job-list", snark_job_list)
2496
    ; ("pooled-user-commands", pooled_user_commands)
2497
    ; ("pooled-zkapp-commands", pooled_zkapp_commands)
2498
    ; ("snark-pool-list", snark_pool_list)
2499
    ; ("pending-snark-work", pending_snark_work)
2500
    ; ("compile-time-constants", compile_time_constants)
2501
    ; ("node-status", node_status)
2502
    ; ("visualization", Visualization.command_group)
2503
    ; ("verify-receipt", verify_receipt)
2504
    ; ("generate-keypair", Cli_lib.Commands.generate_keypair)
2505
    ; ("validate-keypair", Cli_lib.Commands.validate_keypair)
2506
    ; ("validate-transaction", Cli_lib.Commands.validate_transaction)
2507
    ; ("send-rosetta-transactions", send_rosetta_transactions_graphql)
2508
    ; ("time-offset", get_time_offset_graphql)
2509
    ; ("get-peers", get_peers_graphql)
2510
    ; ("add-peers", add_peers_graphql)
2511
    ; ("object-lifetime-statistics", object_lifetime_statistics)
2512
    ; ("archive-blocks", archive_blocks)
2513
    ; ("compute-receipt-chain-hash", receipt_chain_hash)
2514
    ; ("hash-transaction", hash_transaction)
2515
    ; ("set-coinbase-receiver", set_coinbase_receiver_graphql)
2516
    ; ("chain-id-inputs", chain_id_inputs)
2517
    ; ("runtime-config", runtime_config)
2518
    ; ("vrf", Cli_lib.Commands.Vrf.command_group)
2519
    ; ("thread-graph", thread_graph)
2520
    ; ("print-signature-kind", signature_kind)
2521
    ; ( "test"
2522
      , Command.group ~summary:"Testing-only commands"
6✔
2523
          [ ("create-genesis", test_genesis_creation) ] )
2524
    ]
2525
  in
2526
  let cmds =
UNCOV
2527
    if itn_features then ("itn-create-accounts", itn_create_accounts) :: cmds0
×
2528
    else cmds0
6✔
2529
  in
2530
  Command.group ~summary:"Advanced client commands" cmds
2531

2532
let ledger =
2533
  Command.group ~summary:"Ledger commands"
6✔
2534
    [ ("export", export_ledger)
2535
    ; ("hash", hash_ledger)
2536
    ; ("currency", currency_in_ledger)
2537
    ; ( "test"
2538
      , Command.group ~summary:"Testing-only commands"
6✔
2539
          [ ("apply", test_ledger_application)
2540
          ; ("generate-accounts", Cli_lib.Commands.generate_test_ledger)
2541
          ] )
2542
    ]
2543

2544
let libp2p =
2545
  Command.group ~summary:"Libp2p commands"
6✔
2546
    [ ("generate-keypair", generate_libp2p_keypair)
2547
    ; ("dump-keypair", dump_libp2p_keypair)
2548
    ]
6✔
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