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

MinaProtocol / mina / 693

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

push

buildkite

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

merge compatible to develop 251015

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

43 existing lines in 10 files now uncovered.

27336 of 72824 relevant lines covered (37.54%)

36483.0 hits per line

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

29.47
/src/app/cli/src/init/client.ml
1
open Core
110✔
2
open Async
3
open Signature_lib
4
open Mina_base
5
open Mina_transaction
6
module Client = Graphql_lib.Client
7

8
module Args = struct
9
  open Command.Param
10

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

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

15
  let zip4 arg1 arg2 arg3 arg4 =
16
    return (fun a b c d -> (a, b, c, d)) <*> arg1 <*> arg2 <*> arg3 <*> arg4
×
17

18
  let zip5 arg1 arg2 arg3 arg4 arg5 =
19
    return (fun a b c d e -> (a, b, c, d, e))
×
20
    <*> arg1 <*> arg2 <*> arg3 <*> arg4 <*> arg5
220✔
21

22
  let zip6 arg1 arg2 arg3 arg4 arg5 arg6 =
23
    return (fun a b c d e f -> (a, b, c, d, e, f))
×
24
    <*> arg1 <*> arg2 <*> arg3 <*> arg4 <*> arg5 <*> arg6
×
25

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

31
let or_error_str ~f_ok ~error = function
32
  | Ok x ->
×
33
      f_ok x
34
  | Error e ->
×
35
      sprintf "%s\n%s\n" error (Error.to_string_hum e)
×
36

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

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

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

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

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

150
let round_trust_score trust_status =
151
  let open Trust_system.Peer_status in
×
152
  let trust = Float.round_decimal trust_status.trust ~decimal_digits:4 in
153
  { trust_status with trust }
×
154

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

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

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

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

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

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

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

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

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

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

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

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

452
let unwrap_user_command (`UserCommand x) = x
×
453

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

522
let transaction_id_to_string id =
523
  Yojson.Basic.to_string (Graphql_lib.Scalars.TransactionId.serialize id)
×
524

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

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

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

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

678
module Export_logs = struct
679
  let pp_export_result tarfile = printf "Exported logs to %s\n%!" tarfile
×
680

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

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

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

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

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

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

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

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

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

951
let constraint_system_digests =
952
  Command.async ~summary:"Print MD5 digest of each SNARK constraint"
110✔
953
    (let open Command.Let_syntax in
954
    let%map signature_kind = Cli_lib.Flag.signature_kind in
955
    fun () ->
NEW
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 =
NEW
961
        Transaction_snark.constraint_system_digests ~signature_kind
×
962
          ~constraint_constants ()
NEW
963
        @ Blockchain_snark.Blockchain_snark_state.constraint_system_digests
×
964
            ~proof_level ~constraint_constants ()
965
      in
966
      let all =
NEW
967
        List.sort ~compare:(fun (k1, _) (k2, _) -> String.compare k1 k2) all
×
968
      in
NEW
969
      List.iter all ~f:(fun (k, v) -> printf "%s\t%s\n" k (Md5.to_hex v)) ;
×
NEW
970
      Deferred.unit)
×
971

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1697
let dump_libp2p_keypair =
1698
  Command.async ~summary:"Print an existing libp2p keypair"
110✔
1699
    (let open Command.Let_syntax in
1700
    let%map_open privkey_path = Cli_lib.Flag.privkey_read_path in
1701
    dump_libp2p_keypair_do privkey_path)
×
1702

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

2292
let signature_kind =
2293
  Command.basic
110✔
2294
    ~summary:"Print the signature kind that this binary is compiled with"
2295
    (let%map.Command () = Command.Param.return () in
110✔
2296
     fun () ->
2297
       let signature_kind_string =
×
2298
         match Mina_signature_kind.t_DEPRECATED with
2299
         | Mainnet ->
×
2300
             "mainnet"
2301
         | Testnet ->
×
2302
             "testnet"
2303
         | Other_network s ->
×
2304
             (* Prefix string to disambiguate *)
2305
             "other network: " ^ s
2306
       in
2307
       Core.print_endline signature_kind_string )
2308

2309
let test_genesis_creation =
2310
  Command.async ~summary:"Test genesis creation"
110✔
2311
    (let%map_open.Command () = Command.Param.return () in
110✔
2312
     Cli_lib.Exceptions.handle_nicely
×
2313
       Test_genesis_creation.time_genesis_creation )
2314

2315
let test_ledger_application =
2316
  Command.async ~summary:"Test ledger application"
110✔
2317
    (let%map_open.Command privkey_path = Cli_lib.Flag.privkey_read_path
2318
     and prev_block_path =
2319
       flag "--prev-block-path" ~doc:"FILE file with serialized block"
110✔
2320
         (optional string)
110✔
2321
     and ledger_path =
2322
       flag "--ledger-path" ~doc:"FILE directory with ledger DB"
110✔
2323
         (required string)
110✔
2324
     and num_txs =
2325
       flag "--num-txs"
110✔
2326
         ~doc:"NN Number of transactions to create after preparatory rounds"
2327
         (required int)
110✔
2328
     and num_txs_per_round =
2329
       flag "--num-txs-per-round"
110✔
2330
         ~doc:
2331
           "NN Number of transactions to create per preparatory round \
2332
            (default: 3)"
2333
         (optional int)
110✔
2334
     and rounds =
2335
       flag "--rounds" ~doc:"NN Number of preparatory rounds (default: 580)"
110✔
2336
         (optional int)
110✔
2337
     and first_partition_slots =
2338
       flag "--first-partition-slots"
110✔
2339
         ~doc:
2340
           "NN Number of slots in first partition of scan state (default: 128)"
2341
         (optional int)
110✔
2342
     and max_depth =
2343
       flag "--max-depth" ~doc:"NN Maximum depth of masks (default: 290)"
110✔
2344
         (optional int)
110✔
2345
     and no_new_stack =
2346
       flag "--old-stack" ~doc:"Use is_new_stack: false (scan state)" no_arg
110✔
2347
     and has_second_partition =
2348
       flag "--has-second-partition"
110✔
2349
         ~doc:"Assume there is a second partition (scan state)" no_arg
2350
     and tracing = flag "--tracing" ~doc:"Wrap test into tracing" no_arg
110✔
2351
     and no_masks = flag "--no-masks" ~doc:"Do not create masks" no_arg
110✔
2352
     and benchmark =
2353
       flag "--dump-benchmark" ~doc:"Dump json file with benchmark data"
110✔
2354
         (optional string)
110✔
2355
     and transfer_parties_get_actions_events =
2356
       flag "--transfer-parties-get-actions-events"
110✔
2357
         ~doc:
2358
           "If true, all updates in the ledger commands will have full actions \
2359
            and events. If false, they will have empty actions and events. \
2360
            Default: false."
2361
         no_arg
2362
     in
2363
     Cli_lib.Exceptions.handle_nicely
2✔
2364
     @@ fun () ->
2365
     let first_partition_slots =
2✔
2366
       Option.value ~default:128 first_partition_slots
2367
     in
2368
     let num_txs_per_round = Option.value ~default:3 num_txs_per_round in
2✔
2369
     let rounds = Option.value ~default:580 rounds in
2✔
2370
     let max_depth = Option.value ~default:290 max_depth in
2✔
2371
     let constraint_constants =
2✔
2372
       Genesis_constants.Compiled.constraint_constants
2373
     in
2374
     let genesis_constants = Genesis_constants.Compiled.genesis_constants in
2375
     Test_ledger_application.test ~privkey_path
2376
       ~ledger_path:(ledger_path, Stable_db) ?prev_block_path
2377
       ~first_partition_slots ~no_new_stack ~has_second_partition
2378
       ~num_txs_per_round ~rounds ~no_masks ~max_depth ~tracing
2379
       ~transfer_parties_get_actions_events num_txs ~constraint_constants
2380
       ~genesis_constants ~benchmark )
2381

2382
let itn_create_accounts =
2383
  let compile_config = Mina_compile_config.Compiled.t in
2384
  Command.async ~summary:"Fund new accounts for incentivized testnet"
110✔
2385
    (let open Command.Param in
2386
    let privkey_path = Cli_lib.Flag.privkey_read_path in
2387
    let key_prefix =
2388
      flag "--key-prefix" ~doc:"STRING prefix of keyfiles" (required string)
110✔
2389
    in
2390
    let num_accounts =
110✔
2391
      flag "--num-accounts" ~doc:"NN Number of new accounts" (required int)
110✔
2392
    in
2393
    let fee =
110✔
2394
      flag "--fee"
2395
        ~doc:
2396
          (sprintf "NN Fee in nanomina paid to create an account (minimum: %s)"
110✔
2397
             (Currency.Fee.to_string compile_config.minimum_user_command_fee) )
110✔
2398
        (required int)
110✔
2399
    in
2400
    let amount =
110✔
2401
      flag "--amount"
2402
        ~doc:"NN Amount in nanomina to be divided among new accounts"
2403
        (required int)
110✔
2404
    in
2405
    let args = Args.zip5 privkey_path key_prefix num_accounts fee amount in
110✔
2406
    let genesis_constants = Genesis_constants.Compiled.genesis_constants in
110✔
2407
    let constraint_constants =
2408
      Genesis_constants.Compiled.constraint_constants
2409
    in
2410
    Cli_lib.Background_daemon.rpc_init args
110✔
2411
      ~f:(Itn.create_accounts ~genesis_constants ~constraint_constants))
2412

2413
module Visualization = struct
2414
  let create_command (type rpc_response) ~name ~f
2415
      (rpc : (string, rpc_response) Rpc.Rpc.t) =
2416
    let open Deferred.Let_syntax in
220✔
2417
    Command.async
2418
      ~summary:(sprintf !"Produce a visualization of the %s" name)
220✔
2419
      (Cli_lib.Background_daemon.rpc_init
220✔
2420
         Command.Param.(anon @@ ("output-filepath" %: string))
220✔
2421
         ~f:(fun port filename ->
2422
           let%map message =
2423
             match%map Daemon_rpcs.Client.dispatch rpc filename port with
×
2424
             | Ok response ->
×
2425
                 f filename response
2426
             | Error e ->
×
2427
                 sprintf "Could not save file: %s\n" (Error.to_string_hum e)
×
2428
           in
2429
           print_string message ) )
×
2430

2431
  module Frontier = struct
2432
    let name = "transition-frontier"
2433

2434
    let command =
2435
      create_command ~name Daemon_rpcs.Visualization.Frontier.rpc
110✔
2436
        ~f:(fun filename -> function
2437
        | `Active () ->
×
2438
            Visualization_message.success name filename
2439
        | `Bootstrapping ->
×
2440
            Visualization_message.bootstrap name )
2441
  end
2442

2443
  module Registered_masks = struct
2444
    let name = "registered-masks"
2445

2446
    let command =
2447
      create_command ~name Daemon_rpcs.Visualization.Registered_masks.rpc
110✔
2448
        ~f:(fun filename () -> Visualization_message.success name filename)
×
2449
  end
2450

2451
  let command_group =
2452
    Command.group ~summary:"Visualize data structures special to Mina"
110✔
2453
      [ (Frontier.name, Frontier.command)
2454
      ; (Registered_masks.name, Registered_masks.command)
2455
      ]
2456
end
2457

2458
let accounts =
2459
  Command.group ~summary:"Client commands concerning account management"
110✔
2460
    ~preserve_subcommand_order:()
2461
    [ ("list", list_accounts)
2462
    ; ("create", create_account)
2463
    ; ("import", import_key)
2464
    ; ("export", export_key)
2465
    ; ("unlock", unlock_account)
2466
    ; ("lock", lock_account)
2467
    ]
2468

2469
let client =
2470
  Command.group ~summary:"Lightweight client commands"
110✔
2471
    ~preserve_subcommand_order:()
2472
    [ ("get-balance", get_balance_graphql)
2473
    ; ("get-tokens", get_tokens_graphql)
2474
    ; ("send-payment", send_payment_graphql)
2475
    ; ("delegate-stake", delegate_stake_graphql)
2476
    ; ("cancel-transaction", cancel_transaction_graphql)
2477
    ; ("set-snark-worker", set_snark_worker)
2478
    ; ("set-snark-work-fee", set_snark_work_fee)
2479
    ; ("export-logs", Export_logs.export_via_graphql)
2480
    ; ("export-local-logs", Export_logs.export_locally)
2481
    ; ("stop-daemon", stop_daemon)
2482
    ; ("status", status)
2483
    ]
2484

2485
let client_trustlist_group =
2486
  Command.group ~summary:"Client trustlist management"
110✔
2487
    ~preserve_subcommand_order:()
2488
    [ ("add", trustlist_add)
2489
    ; ("list", trustlist_list)
2490
    ; ("remove", trustlist_remove)
2491
    ]
2492

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

2547
let ledger =
2548
  Command.group ~summary:"Ledger commands"
110✔
2549
    [ ("export", export_ledger)
2550
    ; ("hash", hash_ledger)
2551
    ; ("currency", currency_in_ledger)
2552
    ; ( "test"
2553
      , Command.group ~summary:"Testing-only commands"
110✔
2554
          [ ("apply", test_ledger_application)
2555
          ; ("generate-accounts", Cli_lib.Commands.generate_test_ledger)
2556
          ] )
2557
    ]
2558

2559
let libp2p =
2560
  Command.group ~summary:"Libp2p commands"
110✔
2561
    [ ("generate-keypair", generate_libp2p_keypair)
2562
    ; ("dump-keypair", dump_libp2p_keypair)
2563
    ]
110✔
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