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

MinaProtocol / mina / 80

14 Apr 2025 10:52PM UTC coverage: 60.82% (+24.9%) from 35.871%
80

push

buildkite

web-flow
Merge pull request #16871 from MinaProtocol/georgeee/last-before-zkapp-stable-hash-removal

Some changes required for hash removal from Zkapp_command.Stable

2 of 3 new or added lines in 2 files covered. (66.67%)

1322 existing lines in 28 files now uncovered.

49978 of 82173 relevant lines covered (60.82%)

475582.51 hits per line

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

26.21
/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) ->
UNCOV
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
UNCOV
332
           let%bind proof_json = read_json proof_path ~flag:"proof-path" in
×
UNCOV
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
                 User_command.of_yojson
UNCOV
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
UNCOV
346
               Fn.compose
×
347
                 (Result.map ~f:unwrap_proof)
UNCOV
348
                 [%of_yojson: Receipt.Chain_hash.t * User_command.t list]
×
349
             else
350
               [%of_yojson:
UNCOV
351
                 Receipt.Chain_hash.t * User_command.Stable.Latest.t list]
×
352
           in
353
           let to_deferred_or_error result ~error =
UNCOV
354
             Result.map_error result ~f:(fun s ->
×
UNCOV
355
                 Error.of_string (sprintf "%s: %s" error s) )
×
356
             |> Deferred.return
357
           in
358
           let%bind payment =
UNCOV
359
             to_deferred_or_error
×
360
               ~error:
UNCOV
361
                 (sprintf "Payment file %s has invalid json format" payment_path)
×
362
               (of_payment_json payment_json)
×
363
           and proof =
364
             to_deferred_or_error
×
365
               ~error:
UNCOV
366
                 (sprintf "Proof file %s has invalid json format" proof_path)
×
367
               (of_proof_json proof_json)
×
368
           in
369
           Daemon_rpcs.Client.dispatch Verify_proof.rpc
×
370
             (account_id, payment, proof)
371
             port
372
         in
373
         match%map dispatch_result with
UNCOV
374
         | Ok (Ok ()) ->
×
375
             printf "Payment is valid on the existing blockchain!\n"
UNCOV
376
         | Error e | Ok (Error e) ->
×
UNCOV
377
             eprintf "Error verifying the receipt: %s\n" (Error.to_string_hum e) )
×
378
    )
379

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

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

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

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

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

UNCOV
455
let unwrap_user_command (`UserCommand x) = x
×
456

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2414
  module Frontier = struct
2415
    let name = "transition-frontier"
2416

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

2426
  module Registered_masks = struct
2427
    let name = "registered-masks"
2428

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

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

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

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

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

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

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

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