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

MinaProtocol / mina / 3533

26 Mar 2025 10:35PM UTC coverage: 35.851% (-24.9%) from 60.775%
3533

push

buildkite

web-flow
Merge pull request #16784 from MinaProtocol/georgeee/polymorphize-funs-in-zkapp_command

Polymorphize some funs in Zkapp_command

2 of 7 new or added lines in 1 file covered. (28.57%)

16247 existing lines in 346 files now uncovered.

25590 of 71378 relevant lines covered (35.85%)

26657.87 hits per line

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

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

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

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

13
module Args = struct
14
  open Command.Param
15

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

292
let verify_receipt =
293
  let open Deferred.Let_syntax in
294
  let open Daemon_rpcs in
295
  let open Command.Param in
296
  let open Cli_lib.Arg_type in
297
  let proof_path_flag =
298
    flag "--proof-path" ~aliases:[ "proof-path" ]
299
      ~doc:"PROOFFILE File to read json version of payment receipt"
300
      (required string)
3✔
301
  in
302
  let payment_path_flag =
3✔
303
    flag "--payment-path" ~aliases:[ "payment-path" ]
304
      ~doc:"PAYMENTPATH File to read json version of verifying payment"
305
      (required string)
3✔
306
  in
307
  let address_flag =
3✔
308
    flag "--address" ~aliases:[ "address" ]
309
      ~doc:"PUBLICKEY Public-key address of sender"
310
      (required public_key_compressed)
3✔
311
  in
312
  let token_flag =
3✔
313
    flag "--token" ~aliases:[ "token" ]
314
      ~doc:"TOKEN_ID The token ID for the account"
315
      (optional_with_default Token_id.default Cli_lib.Arg_type.token_id)
3✔
316
  in
317
  Command.async ~summary:"Verify a receipt of a sent payment"
3✔
318
    (Cli_lib.Background_daemon.rpc_init
3✔
319
       (Args.zip4 payment_path_flag proof_path_flag address_flag token_flag)
3✔
320
       ~f:(fun port (payment_path, proof_path, pk, token_id) ->
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 to_deferred_or_error result ~error =
×
329
             Result.map_error result ~f:(fun s ->
×
330
                 Error.of_string (sprintf "%s: %s" error s) )
×
331
             |> Deferred.return
332
           in
333
           let%bind payment =
334
             User_command.of_yojson payment_json
×
335
             |> to_deferred_or_error
×
336
                  ~error:
337
                    (sprintf "Payment file %s has invalid json format"
×
338
                       payment_path )
339
           and proof =
340
             [%of_yojson: Receipt.Chain_hash.t * User_command.t list] proof_json
×
341
             |> to_deferred_or_error
×
342
                  ~error:
343
                    (sprintf "Proof file %s has invalid json format" proof_path)
×
344
           in
345
           Daemon_rpcs.Client.dispatch Verify_proof.rpc
×
346
             (account_id, payment, proof)
347
             port
348
         in
349
         match%map dispatch_result with
350
         | Ok (Ok ()) ->
×
351
             printf "Payment is valid on the existing blockchain!\n"
352
         | Error e | Ok (Error e) ->
×
353
             eprintf "Error verifying the receipt: %s\n" (Error.to_string_hum e) )
×
354
    )
355

356
let get_nonce :
357
       rpc:(Account_id.t, Account.Nonce.t option Or_error.t) Rpc.Rpc.t
358
    -> Account_id.t
359
    -> Host_and_port.t
360
    -> (Account.Nonce.t, string) Deferred.Result.t =
361
 fun ~rpc account_id port ->
362
  let open Deferred.Let_syntax in
×
363
  let%map res = Daemon_rpcs.Client.dispatch rpc account_id port in
×
364
  match Or_error.join res with
×
365
  | Ok (Some n) ->
×
366
      Ok n
367
  | Ok None ->
×
368
      Error "No account found at that public_key"
369
  | Error e ->
×
370
      Error (Error.to_string_hum e)
×
371

372
let get_nonce_cmd =
373
  let open Command.Param in
374
  (* Ignores deprecation of public_key type for backwards compatibility *)
375
  let[@warning "-3"] address_flag =
376
    flag "--address" ~aliases:[ "address" ]
377
      ~doc:"PUBLICKEY Public-key address you want the nonce for"
378
      (required Cli_lib.Arg_type.public_key_compressed)
3✔
379
  in
380
  let token_flag =
3✔
381
    flag "--token" ~aliases:[ "token" ]
382
      ~doc:"TOKEN_ID The token ID for the account"
383
      (optional_with_default Token_id.default Cli_lib.Arg_type.token_id)
3✔
384
  in
385
  let flags = Args.zip2 address_flag token_flag in
3✔
386
  Command.async ~summary:"Get the current nonce for an account"
3✔
387
    (Cli_lib.Background_daemon.rpc_init flags ~f:(fun port (pk, token_flag) ->
3✔
388
         let account_id = Account_id.create pk token_flag in
×
389
         match%bind
390
           get_nonce ~rpc:Daemon_rpcs.Get_nonce.rpc account_id port
×
391
         with
392
         | Error e ->
×
393
             eprintf "Failed to get nonce\n%s\n" e ;
394
             exit 2
×
395
         | Ok nonce ->
×
396
             printf "%s\n" (Account.Nonce.to_string nonce) ;
×
397
             exit 0 ) )
×
398

399
let status =
400
  let open Daemon_rpcs in
401
  let flag = Args.zip2 Cli_lib.Flag.json Cli_lib.Flag.performance in
402
  Command.async ~summary:"Get running daemon status"
3✔
403
    (Cli_lib.Background_daemon.rpc_init flag ~f:(fun port (json, performance) ->
3✔
404
         Daemon_rpcs.Client.dispatch_pretty_message ~json ~join_error:Fn.id
×
405
           ~error_ctx:"Failed to get status"
406
           (module Daemon_rpcs.Types.Status)
407
           Get_status.rpc
408
           (if performance then `Performance else `None)
×
409
           port ) )
410

411
let status_clear_hist =
412
  let open Daemon_rpcs in
413
  let flag = Args.zip2 Cli_lib.Flag.json Cli_lib.Flag.performance in
414
  Command.async ~summary:"Clear histograms reported in status"
3✔
415
    (Cli_lib.Background_daemon.rpc_init flag ~f:(fun port (json, performance) ->
3✔
416
         Daemon_rpcs.Client.dispatch_pretty_message ~json ~join_error:Fn.id
×
417
           ~error_ctx:"Failed to clear histograms reported in status"
418
           (module Daemon_rpcs.Types.Status)
419
           Clear_hist_status.rpc
420
           (if performance then `Performance else `None)
×
421
           port ) )
422

423
let get_nonce_exn ~rpc public_key port =
424
  match%bind get_nonce ~rpc public_key port with
×
425
  | Error e ->
×
426
      eprintf "Failed to get nonce\n%s\n" e ;
427
      exit 3
×
428
  | Ok nonce ->
×
429
      return nonce
430

431
let unwrap_user_command (`UserCommand x) = x
×
432

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

501
let transaction_id_to_string id =
502
  Yojson.Basic.to_string (Graphql_lib.Scalars.TransactionId.serialize id)
×
503

504
let send_payment_graphql =
505
  let open Command.Param in
506
  let open Cli_lib.Arg_type in
507
  let receiver_flag =
508
    flag "--receiver" ~aliases:[ "receiver" ]
509
      ~doc:"PUBLICKEY Public key to which you want to send money"
510
      (required public_key_compressed)
3✔
511
  in
512
  let amount_flag =
3✔
513
    flag "--amount" ~aliases:[ "amount" ]
514
      ~doc:"VALUE Payment amount you want to send" (required txn_amount)
3✔
515
  in
516
  let genesis_constants = Genesis_constants.Compiled.genesis_constants in
3✔
517
  let compile_config = Mina_compile_config.Compiled.t in
518
  let args =
519
    Args.zip3
520
      (Cli_lib.Flag.signed_command_common
521
         ~minimum_user_command_fee:genesis_constants.minimum_user_command_fee
522
         ~default_transaction_fee:compile_config.default_transaction_fee )
523
      receiver_flag amount_flag
524
  in
525
  Command.async ~summary:"Send payment to an address"
3✔
526
    (Cli_lib.Background_daemon.graphql_init args
3✔
527
       ~f:(fun
528
            graphql_endpoint
529
            ({ Cli_lib.Flag.sender; fee; nonce; memo }, receiver, amount)
530
          ->
531
         let%map response =
532
           let input =
533
             Mina_graphql.Types.Input.SendPaymentInput.make_input ~to_:receiver
534
               ~from:sender ~amount ~fee ?memo ?nonce ()
535
           in
536
           Graphql_client.query_exn
×
537
             Graphql_queries.Send_payment.(make @@ makeVariables ~input ())
×
538
             graphql_endpoint
539
         in
540
         printf "Dispatched payment with ID %s\n"
×
541
           (transaction_id_to_string response.sendPayment.payment.id) ) )
×
542

543
let delegate_stake_graphql =
544
  let open Command.Param in
545
  let open Cli_lib.Arg_type in
546
  let receiver_flag =
547
    flag "--receiver" ~aliases:[ "receiver" ]
548
      ~doc:"PUBLICKEY Public key to which you want to delegate your stake"
549
      (required public_key_compressed)
3✔
550
  in
551
  let genesis_constants = Genesis_constants.Compiled.genesis_constants in
3✔
552
  let compile_config = Mina_compile_config.Compiled.t in
553
  let args =
554
    Args.zip2
555
      (Cli_lib.Flag.signed_command_common
556
         ~minimum_user_command_fee:genesis_constants.minimum_user_command_fee
557
         ~default_transaction_fee:compile_config.default_transaction_fee )
558
      receiver_flag
559
  in
560
  Command.async ~summary:"Delegate your stake to another public key"
3✔
561
    (Cli_lib.Background_daemon.graphql_init args
3✔
562
       ~f:(fun
563
            graphql_endpoint
564
            ({ Cli_lib.Flag.sender; fee; nonce; memo }, receiver)
565
          ->
566
         let%map response =
567
           Graphql_client.query_exn
×
568
             Graphql_queries.Send_delegation.(
569
               make
×
570
               @@ makeVariables ~receiver ~sender
×
571
                    ~fee:(Currency.Fee.to_uint64 fee)
×
572
                    ?nonce ?memo ())
573
             graphql_endpoint
574
         in
575
         printf "Dispatched stake delegation with ID %s\n"
×
576
           (transaction_id_to_string response.sendDelegation.delegation.id) ) )
×
577

578
let cancel_transaction_graphql =
579
  let txn_id_flag =
580
    Command.Param.(
581
      flag "--id" ~aliases:[ "id" ] ~doc:"ID Transaction ID to be cancelled"
3✔
582
        (required Cli_lib.Arg_type.user_command))
3✔
583
  in
584
  Command.async
3✔
585
    ~summary:
586
      "Cancel a transaction -- this submits a replacement transaction with a \
587
       fee larger than the cancelled transaction."
588
    (Cli_lib.Background_daemon.graphql_init txn_id_flag
3✔
589
       ~f:(fun graphql_endpoint user_command ->
590
         let receiver_pk = Signed_command.receiver_pk user_command in
×
591
         let cancel_sender_pk = Signed_command.fee_payer_pk user_command in
×
592
         let open Deferred.Let_syntax in
×
593
         let cancel_fee =
594
           let fee = Currency.Fee.to_uint64 (Signed_command.fee user_command) in
×
595
           let replace_fee =
×
596
             Currency.Fee.to_uint64 Network_pool.Indexed_pool.replace_fee
597
           in
598
           let open Unsigned.UInt64.Infix in
×
599
           (* fee amount "inspired by" network_pool/indexed_pool.ml *)
600
           Currency.Fee.of_uint64 (fee + replace_fee)
×
601
         in
602
         printf "Fee to cancel transaction is %s coda.\n"
603
           (Currency.Fee.to_mina_string cancel_fee) ;
×
604
         let cancel_query =
×
605
           let input =
606
             Mina_graphql.Types.Input.SendPaymentInput.make_input
607
               ~to_:receiver_pk ~from:cancel_sender_pk
608
               ~amount:Currency.Amount.zero ~fee:cancel_fee
609
               ~nonce:(Signed_command.nonce user_command)
×
610
               ()
611
           in
612
           Graphql_queries.Send_payment.(make @@ makeVariables ~input ())
×
613
         in
614
         let%map cancel_response =
615
           Graphql_client.query_exn cancel_query graphql_endpoint
×
616
         in
617
         printf "🛑 Cancelled transaction! Cancel ID: %s\n"
×
618
           (transaction_id_to_string cancel_response.sendPayment.payment.id) )
×
619
    )
620

621
let send_rosetta_transactions_graphql =
622
  Command.async
3✔
623
    ~summary:
624
      "Dispatch one or more transactions, provided to stdin in rosetta format"
625
    (Cli_lib.Background_daemon.graphql_init (Command.Param.return ())
3✔
626
       ~f:(fun graphql_endpoint () ->
627
         let lexbuf = Lexing.from_channel In_channel.stdin in
×
628
         let lexer = Yojson.init_lexer () in
×
629
         match%bind
630
           Deferred.Or_error.try_with ~here:[%here] (fun () ->
×
631
               Deferred.repeat_until_finished () (fun () ->
×
632
                   try
×
633
                     let transaction_json =
634
                       Yojson.Basic.from_lexbuf ~stream:true lexer lexbuf
635
                     in
636
                     let%map response =
637
                       Graphql_client.query_exn
×
638
                         Graphql_queries.Send_rosetta_transaction.(
639
                           make
×
640
                           @@ makeVariables ~transaction:transaction_json ())
×
641
                         graphql_endpoint
642
                     in
643
                     printf "Dispatched command with TRANSACTION_ID %s\n"
×
644
                       (transaction_id_to_string
×
645
                          response.sendRosettaTransaction.userCommand.id ) ;
646
                     `Repeat ()
×
647
                   with Yojson.End_of_input -> return (`Finished ()) ) )
×
648
         with
649
         | Ok () ->
×
650
             Deferred.return ()
651
         | Error err ->
×
652
             Format.eprintf "@[<v>Error:@,%a@,@]@."
653
               (Yojson.Safe.pretty_print ?std:None)
654
               (Error_json.error_to_yojson err) ;
×
655
             Core_kernel.exit 1 ) )
×
656

657
module Export_logs = struct
658
  let pp_export_result tarfile = printf "Exported logs to %s\n%!" tarfile
×
659

660
  let tarfile_flag =
661
    let open Command.Param in
662
    flag "--tarfile" ~aliases:[ "tarfile" ]
3✔
663
      ~doc:"STRING Basename of the tar archive (default: date_time)"
664
      (optional string)
3✔
665

666
  let export_via_graphql =
667
    Command.async ~summary:"Export daemon logs to tar archive"
3✔
668
      (Cli_lib.Background_daemon.graphql_init tarfile_flag
3✔
669
         ~f:(fun graphql_endpoint basename ->
670
           let%map response =
671
             Graphql_client.query_exn
×
672
               Graphql_queries.Export_logs.(make @@ makeVariables ?basename ())
×
673
               graphql_endpoint
674
           in
675
           pp_export_result response.exportLogs.exportLogs.tarfile ) )
×
676

677
  let export_locally =
678
    let run ~tarfile ~conf_dir =
679
      let open Mina_lib in
×
680
      let conf_dir = Conf_dir.compute_conf_dir conf_dir in
681
      fun () ->
×
682
        match%map Conf_dir.export_logs_to_tar ?basename:tarfile ~conf_dir with
683
        | Ok result ->
×
684
            pp_export_result result
685
        | Error err ->
×
686
            failwithf "Error when exporting logs: %s"
687
              (Error_json.error_to_yojson err |> Yojson.Safe.to_string)
×
688
              ()
689
    in
690
    let open Command.Let_syntax in
691
    Command.async ~summary:"Export local logs (no daemon) to tar archive"
3✔
692
      (let%map tarfile = tarfile_flag and conf_dir = Cli_lib.Flag.conf_dir in
693
       run ~tarfile ~conf_dir )
×
694
end
695

696
let wrap_key =
697
  Command.async ~summary:"Wrap a private key into a private key file"
3✔
698
    (let open Command.Let_syntax in
699
    let%map_open privkey_path = Cli_lib.Flag.privkey_write_path in
700
    Cli_lib.Exceptions.handle_nicely
1✔
701
    @@ fun () ->
702
    let open Deferred.Let_syntax in
1✔
703
    let%bind privkey =
704
      Secrets.Password.hidden_line_or_env "Private key: " ~env:"CODA_PRIVKEY"
1✔
705
    in
706
    let pk = Private_key.of_base58_check_exn (Bytes.to_string privkey) in
1✔
707
    let kp = Keypair.of_private_key_exn pk in
1✔
708
    Secrets.Keypair.Terminal_stdin.write_exn kp ~privkey_path)
1✔
709

710
let dump_keypair =
711
  Command.async ~summary:"Print out a keypair from a private key file"
3✔
712
    (let open Command.Let_syntax in
713
    let%map_open privkey_path = Cli_lib.Flag.privkey_read_path in
714
    Cli_lib.Exceptions.handle_nicely
×
715
    @@ fun () ->
716
    let open Deferred.Let_syntax in
×
717
    let%map kp =
718
      Secrets.Keypair.Terminal_stdin.read_exn ~which:"Mina keypair" privkey_path
×
719
    in
720
    printf "Public key: %s\nPrivate key: %s\n"
×
721
      ( kp.public_key |> Public_key.compress
×
722
      |> Public_key.Compressed.to_base58_check )
×
723
      (kp.private_key |> Private_key.to_base58_check))
×
724

725
let handle_export_ledger_response ~json = function
726
  | Error e ->
×
727
      Daemon_rpcs.Client.print_rpc_error e ;
728
      exit 1
×
729
  | Ok (Error e) ->
×
730
      printf !"Ledger not found: %s\n" (Error.to_string_hum e) ;
×
731
      exit 1
×
732
  | Ok (Ok accounts) ->
×
733
      if json then (
×
734
        Format.fprintf Format.std_formatter "[\n  " ;
735
        let print_comma = ref false in
×
736
        List.iter accounts ~f:(fun a ->
737
            if !print_comma then Format.fprintf Format.std_formatter "\n, "
×
738
            else print_comma := true ;
×
739
            Genesis_ledger_helper.Accounts.Single.of_account a None
×
740
            |> Runtime_config.Accounts.Single.to_yojson
×
741
            |> Yojson.Safe.pretty_print Format.std_formatter ) ;
742
        Format.fprintf Format.std_formatter "\n]" ;
×
743
        printf "\n" )
×
744
      else printf !"%{sexp:Account.t list}\n" accounts ;
×
745
      return ()
746

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

816
let hash_ledger =
817
  let open Command.Let_syntax in
818
  Command.async
3✔
819
    ~summary:
820
      "Print the Merkle root of the ledger contained in the specified file"
821
    (let%map ledger_file =
822
       Command.Param.(
823
         flag "--ledger-file"
3✔
824
           ~doc:"LEDGER-FILE File containing an exported ledger"
825
           (required string))
3✔
826
     and plaintext = Cli_lib.Flag.plaintext in
827
     fun () ->
828
       let constraint_constants =
×
829
         Genesis_constants.Compiled.constraint_constants
830
       in
831
       let process_accounts accounts =
832
         let packed_ledger =
×
833
           Genesis_ledger_helper.Ledger.packed_genesis_ledger_of_accounts
834
             ~depth:constraint_constants.ledger_depth accounts
835
         in
836
         let ledger = Lazy.force @@ Genesis_ledger.Packed.t packed_ledger in
×
837
         Format.printf "%s@."
×
838
           (Mina_ledger.Ledger.merkle_root ledger |> Ledger_hash.to_base58_check)
×
839
       in
840
       Deferred.return
841
       @@
842
       if plaintext then
843
         In_channel.with_file ledger_file ~f:(fun in_channel ->
×
844
             let sexp = In_channel.input_all in_channel |> Sexp.of_string in
×
845
             let accounts =
×
846
               lazy
847
                 (List.map
×
848
                    ([%of_sexp: Account.t list] sexp)
849
                    ~f:(fun acct -> (None, acct)) )
×
850
             in
851
             process_accounts accounts )
852
       else
853
         let json = Yojson.Safe.from_file ledger_file in
×
854
         match Runtime_config.Accounts.of_yojson json with
×
855
         | Ok runtime_accounts ->
×
856
             let accounts =
857
               lazy (Genesis_ledger_helper.Accounts.to_full runtime_accounts)
×
858
             in
859
             process_accounts accounts
×
860
         | Error err ->
×
861
             Format.eprintf "Could not parse JSON in file %s: %s@" ledger_file
862
               err ;
863
             ignore (exit 1 : 'a Deferred.t) )
×
864

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

929
let constraint_system_digests =
930
  Command.async ~summary:"Print MD5 digest of each SNARK constraint"
3✔
931
    (Command.Param.return (fun () ->
3✔
932
         let constraint_constants =
×
933
           Genesis_constants.Compiled.constraint_constants
934
         in
935
         let proof_level = Genesis_constants.Compiled.proof_level in
936
         let all =
937
           Transaction_snark.constraint_system_digests ~constraint_constants ()
×
938
           @ Blockchain_snark.Blockchain_snark_state.constraint_system_digests
×
939
               ~proof_level ~constraint_constants ()
940
         in
941
         let all =
942
           List.sort ~compare:(fun (k1, _) (k2, _) -> String.compare k1 k2) all
×
943
         in
944
         List.iter all ~f:(fun (k, v) -> printf "%s\t%s\n" k (Md5.to_hex v)) ;
×
945
         Deferred.unit ) )
×
946

947
let snark_job_list =
948
  let open Deferred.Let_syntax in
949
  let open Command.Param in
950
  Command.async
3✔
951
    ~summary:
952
      "List of snark jobs in JSON format that are yet to be included in the \
953
       blocks"
954
    (Cli_lib.Background_daemon.rpc_init (return ()) ~f:(fun port () ->
3✔
955
         match%map
956
           Daemon_rpcs.Client.dispatch_join_errors
×
957
             Daemon_rpcs.Snark_job_list.rpc () port
958
         with
959
         | Ok str ->
×
960
             printf "%s" str
961
         | Error e ->
×
962
             Daemon_rpcs.Client.print_rpc_error e ) )
963

964
let snark_pool_list =
965
  let open Command.Param in
966
  Command.async ~summary:"List of snark works in the snark pool in JSON format"
3✔
967
    (Cli_lib.Background_daemon.graphql_init (return ())
3✔
968
       ~f:(fun graphql_endpoint () ->
969
         Deferred.map
×
970
           (Graphql_client.query_exn
×
971
              Graphql_queries.Snark_pool.(make @@ makeVariables ())
×
972
              graphql_endpoint )
973
           ~f:(fun response ->
974
             let lst =
×
975
               [%to_yojson: Cli_lib.Graphql_types.Completed_works.t]
×
976
                 (Array.to_list
×
977
                    (Array.map
×
978
                       ~f:(fun w ->
979
                         { Cli_lib.Graphql_types.Completed_works.Work.work_ids =
×
980
                             Array.to_list w.work_ids
×
981
                         ; fee = w.fee
982
                         ; prover = w.prover
983
                         } )
984
                       response.snarkPool ) )
985
             in
986
             print_string (Yojson.Safe.to_string lst) ) ) )
×
987

988
let pooled_user_commands =
989
  let public_key_flag =
990
    Command.Param.(
991
      anon @@ maybe @@ ("public-key" %: Cli_lib.Arg_type.public_key_compressed))
3✔
992
  in
993
  Command.async
3✔
994
    ~summary:"Retrieve all the user commands that are pending inclusion"
995
    (Cli_lib.Background_daemon.graphql_init public_key_flag
3✔
996
       ~f:(fun graphql_endpoint public_key ->
997
         let module Q = Graphql_queries.Pooled_user_commands in
×
998
         let graphql = Q.(make @@ makeVariables ?public_key ()) in
×
999
         let%map response = Graphql_client.query_exn graphql graphql_endpoint in
×
1000
         let json_response = Q.serialize response |> Q.toJson in
×
1001
         print_string (Yojson.Basic.to_string json_response) ) )
×
1002

1003
let pooled_zkapp_commands =
1004
  let public_key_flag =
1005
    Command.Param.(
1006
      anon @@ maybe @@ ("public-key" %: Cli_lib.Arg_type.public_key_compressed))
3✔
1007
  in
1008
  Command.async
3✔
1009
    ~summary:"Retrieve all the zkApp commands that are pending inclusion"
1010
    (Cli_lib.Background_daemon.graphql_init public_key_flag
3✔
1011
       ~f:(fun graphql_endpoint maybe_public_key ->
1012
         let public_key =
×
1013
           Yojson.Safe.to_basic
1014
           @@ [%to_yojson: Public_key.Compressed.t option] maybe_public_key
×
1015
         in
1016
         let graphql =
×
1017
           Graphql_queries.Pooled_zkapp_commands.(
1018
             make @@ makeVariables ~public_key ())
×
1019
         in
1020
         let%bind raw_response =
1021
           Graphql_client.query_json_exn graphql graphql_endpoint
×
1022
         in
1023
         let%map json_response =
1024
           try
1025
             let kvs = Yojson.Safe.Util.to_assoc raw_response in
1026
             List.hd_exn kvs |> snd |> return
×
1027
           with _ ->
×
1028
             eprintf "Failed to read result of pooled zkApp commands" ;
1029
             exit 1
×
1030
         in
1031
         print_string (Yojson.Safe.to_string json_response) ) )
×
1032

1033
let to_signed_fee_exn sign magnitude =
1034
  let sgn = match sign with `PLUS -> Sgn.Pos | `MINUS -> Neg in
×
1035
  Currency.Fee.Signed.create ~sgn ~magnitude
1036

1037
let pending_snark_work =
1038
  let open Command.Param in
1039
  Command.async
3✔
1040
    ~summary:
1041
      "List of snark works in JSON format that are not available in the pool \
1042
       yet"
1043
    (Cli_lib.Background_daemon.graphql_init (return ())
3✔
1044
       ~f:(fun graphql_endpoint () ->
1045
         Deferred.map
×
1046
           (Graphql_client.query_exn
×
1047
              Graphql_queries.Pending_snark_work.(make @@ makeVariables ())
×
1048
              graphql_endpoint )
1049
           ~f:(fun response ->
1050
             let lst =
×
1051
               [%to_yojson: Cli_lib.Graphql_types.Pending_snark_work.t]
×
1052
                 (Array.map
×
1053
                    ~f:(fun bundle ->
1054
                      Array.map bundle.workBundle ~f:(fun w ->
×
1055
                          let fee_excess_left = w.fee_excess.feeExcessLeft in
×
1056
                          { Cli_lib.Graphql_types.Pending_snark_work.Work
1057
                            .work_id = w.work_id
1058
                          ; fee_excess =
1059
                              Currency.Amount.Signed.of_fee
×
1060
                                (to_signed_fee_exn fee_excess_left.sign
×
1061
                                   fee_excess_left.feeMagnitude )
1062
                          ; supply_increase = w.supply_increase
1063
                          ; source_first_pass_ledger_hash =
1064
                              w.source_first_pass_ledger_hash
1065
                          ; target_first_pass_ledger_hash =
1066
                              w.target_first_pass_ledger_hash
1067
                          ; source_second_pass_ledger_hash =
1068
                              w.source_second_pass_ledger_hash
1069
                          ; target_second_pass_ledger_hash =
1070
                              w.target_second_pass_ledger_hash
1071
                          } ) )
1072
                    response.pendingSnarkWork )
1073
             in
1074
             print_string (Yojson.Safe.to_string lst) ) ) )
×
1075

1076
let start_tracing =
1077
  let open Deferred.Let_syntax in
1078
  let open Command.Param in
1079
  Command.async
3✔
1080
    ~summary:"Start async tracing to $config-directory/trace/$pid.trace"
1081
    (Cli_lib.Background_daemon.rpc_init (return ()) ~f:(fun port () ->
3✔
1082
         match%map
1083
           Daemon_rpcs.Client.dispatch Daemon_rpcs.Start_tracing.rpc () port
×
1084
         with
1085
         | Ok () ->
×
1086
             print_endline "Daemon started tracing!"
1087
         | Error e ->
×
1088
             Daemon_rpcs.Client.print_rpc_error e ) )
1089

1090
let stop_tracing =
1091
  let open Deferred.Let_syntax in
1092
  let open Command.Param in
1093
  Command.async ~summary:"Stop async tracing"
3✔
1094
    (Cli_lib.Background_daemon.rpc_init (return ()) ~f:(fun port () ->
3✔
1095
         match%map
1096
           Daemon_rpcs.Client.dispatch Daemon_rpcs.Stop_tracing.rpc () port
×
1097
         with
1098
         | Ok () ->
×
1099
             print_endline "Daemon stopped printing!"
1100
         | Error e ->
×
1101
             Daemon_rpcs.Client.print_rpc_error e ) )
1102

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

1120
let stop_internal_tracing =
1121
  let open Deferred.Let_syntax in
1122
  let open Command.Param in
1123
  Command.async ~summary:"Stop internal tracing"
3✔
1124
    (Cli_lib.Background_daemon.rpc_init (return ()) ~f:(fun port () ->
3✔
1125
         match%map
1126
           Daemon_rpcs.Client.dispatch Daemon_rpcs.Stop_internal_tracing.rpc ()
×
1127
             port
1128
         with
1129
         | Ok () ->
×
1130
             print_endline "Daemon internal tracing stopped!"
1131
         | Error e ->
×
1132
             Daemon_rpcs.Client.print_rpc_error e ) )
1133

1134
let set_coinbase_receiver_graphql =
1135
  let open Command.Param in
1136
  let open Cli_lib.Arg_type in
1137
  let pk_flag =
1138
    choose_one ~if_nothing_chosen:Raise
1139
      [ flag "--public-key" ~aliases:[ "public-key" ]
3✔
1140
          ~doc:"PUBLICKEY Public key of account to send coinbase rewards to"
1141
          (optional public_key_compressed)
3✔
1142
        |> map ~f:(Option.map ~f:Option.some)
3✔
1143
      ; flag "--block-producer" ~aliases:[ "block-producer" ]
3✔
1144
          ~doc:"Send coinbase rewards to the block producer's public key" no_arg
1145
        |> map ~f:(function true -> Some None | false -> None)
×
1146
      ]
1147
  in
1148
  Command.async ~summary:"Set the coinbase receiver"
3✔
1149
    (Cli_lib.Background_daemon.graphql_init pk_flag
3✔
1150
       ~f:(fun graphql_endpoint public_key ->
1151
         let print_pk_opt () = function
×
1152
           | None ->
×
1153
               "block producer"
1154
           | Some pk ->
×
1155
               "public key " ^ Public_key.Compressed.to_base58_check pk
×
1156
         in
1157
         let%map result =
1158
           Graphql_client.query_exn
×
1159
             Graphql_queries.Set_coinbase_receiver.(
1160
               make @@ makeVariables ?public_key ())
×
1161
             graphql_endpoint
1162
         in
1163
         printf
×
1164
           "Was sending coinbases to the %a\nNow sending coinbases to the %a\n"
1165
           print_pk_opt result.setCoinbaseReceiver.lastCoinbaseReceiver
1166
           print_pk_opt result.setCoinbaseReceiver.currentCoinbaseReceiver ) )
1167

1168
let set_snark_worker =
1169
  let open Command.Param in
1170
  let public_key_flag =
1171
    flag "--address" ~aliases:[ "address" ]
1172
      ~doc:
1173
        (sprintf
3✔
1174
           "PUBLICKEY Public-key address you wish to start snark-working on; \
1175
            null to stop doing any snark work. %s"
1176
           Cli_lib.Default.receiver_key_warning )
1177
      (optional Cli_lib.Arg_type.public_key_compressed)
3✔
1178
  in
1179
  Command.async
3✔
1180
    ~summary:"Set key you wish to snark work with or disable snark working"
1181
    (Cli_lib.Background_daemon.graphql_init public_key_flag
3✔
1182
       ~f:(fun graphql_endpoint optional_public_key ->
1183
         let graphql =
×
1184
           Graphql_queries.Set_snark_worker.(
1185
             make @@ makeVariables ?public_key:optional_public_key ())
×
1186
         in
1187
         Deferred.map (Graphql_client.query_exn graphql graphql_endpoint)
×
1188
           ~f:(fun response ->
1189
             ( match optional_public_key with
×
1190
             | Some public_key ->
×
1191
                 printf
×
1192
                   !"New snark worker public key : %s\n"
1193
                   (Public_key.Compressed.to_base58_check public_key)
×
1194
             | None ->
×
1195
                 printf "Will stop doing snark work\n" ) ;
×
1196
             printf "Previous snark worker public key : %s\n"
1197
               (Option.value_map response.setSnarkWorker.lastSnarkWorker
×
1198
                  ~default:"None" ~f:Public_key.Compressed.to_base58_check ) ) )
1199
    )
1200

1201
let set_snark_work_fee =
1202
  Command.async ~summary:"Set fee reward for doing transaction snark work"
3✔
1203
  @@ Cli_lib.Background_daemon.graphql_init
3✔
1204
       Command.Param.(anon @@ ("fee" %: Cli_lib.Arg_type.txn_fee))
3✔
1205
       ~f:(fun graphql_endpoint fee ->
1206
         let graphql =
×
1207
           Graphql_queries.Set_snark_work_fee.(
1208
             make @@ makeVariables ~fee:(Currency.Fee.to_uint64 fee) ())
×
1209
         in
1210
         Deferred.map (Graphql_client.query_exn graphql graphql_endpoint)
×
1211
           ~f:(fun response ->
1212
             printf
×
1213
               !"Updated snark work fee: %i\nOld snark work fee: %i\n"
1214
               (Currency.Fee.to_nanomina_int fee)
×
1215
               (Currency.Fee.to_nanomina_int response.setSnarkWorkFee.lastFee) )
×
1216
         )
1217

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

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

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

1516
let create_account =
1517
  let open Command.Param in
1518
  Command.async ~summary:"Create new account"
3✔
1519
    (Cli_lib.Background_daemon.graphql_init (return ())
3✔
1520
       ~f:(fun graphql_endpoint () ->
1521
         let%bind password =
1522
           Secrets.Keypair.Terminal_stdin.prompt_password
×
1523
             "Password for new account: "
1524
         in
1525
         let%map response =
1526
           Graphql_client.query_exn
×
1527
             Graphql_queries.Create_account.(
1528
               make @@ makeVariables ~password:(Bytes.to_string password) ())
×
1529
             graphql_endpoint
1530
         in
1531
         let pk_string =
×
1532
           Public_key.Compressed.to_base58_check
1533
             response.createAccount.account.public_key
1534
         in
1535
         printf "\n😄 Added new account!\nPublic key: %s\n" pk_string ) )
×
1536

1537
let create_hd_account =
1538
  Command.async ~summary:Secrets.Hardware_wallets.create_hd_account_summary
3✔
1539
    (Cli_lib.Background_daemon.graphql_init Cli_lib.Flag.Signed_command.hd_index
3✔
1540
       ~f:(fun graphql_endpoint hd_index ->
1541
         let%map response =
1542
           Graphql_client.(
1543
             query_exn
×
1544
               Graphql_queries.Create_hd_account.(
1545
                 make @@ makeVariables ~hd_index ()))
×
1546
             graphql_endpoint
1547
         in
1548
         let pk_string =
×
1549
           Public_key.Compressed.to_base58_check
1550
             response.createHDAccount.account.public_key
1551
         in
1552
         printf "\n😄 created HD account with HD-index %s!\nPublic key: %s\n"
×
1553
           (Mina_numbers.Hd_index.to_string hd_index)
×
1554
           pk_string ) )
1555

1556
let unlock_account =
1557
  let open Command.Param in
1558
  let pk_flag =
1559
    flag "--public-key" ~aliases:[ "public-key" ]
1560
      ~doc:"PUBLICKEY Public key to be unlocked"
1561
      (required Cli_lib.Arg_type.public_key_compressed)
3✔
1562
  in
1563
  Command.async ~summary:"Unlock a tracked account"
3✔
1564
    (Cli_lib.Background_daemon.graphql_init pk_flag
3✔
1565
       ~f:(fun graphql_endpoint pk_str ->
1566
         let password =
×
1567
           Deferred.map ~f:Or_error.return
1568
             (Secrets.Password.hidden_line_or_env "Password to unlock account: "
×
1569
                ~env:Secrets.Keypair.env )
1570
         in
1571
         match%bind password with
1572
         | Ok password_bytes ->
×
1573
             let%map response =
1574
               Graphql_client.query_exn
×
1575
                 Graphql_queries.Unlock_account.(
1576
                   make
×
1577
                   @@ makeVariables ~public_key:pk_str
×
1578
                        ~password:(Bytes.to_string password_bytes)
×
1579
                        ())
1580
                 graphql_endpoint
1581
             in
1582
             let pk_string =
×
1583
               Public_key.Compressed.to_base58_check
1584
                 response.unlockAccount.account.public_key
1585
             in
1586
             printf "\n🔓 Unlocked account!\nPublic key: %s\n" pk_string
×
1587
         | Error e ->
×
1588
             Deferred.return
1589
               (printf "❌ Error unlocking account: %s\n" (Error.to_string_hum e)) )
×
1590
    )
1591

1592
let lock_account =
1593
  let open Command.Param in
1594
  let pk_flag =
1595
    flag "--public-key" ~aliases:[ "public-key" ]
1596
      ~doc:"PUBLICKEY Public key of account to be locked"
1597
      (required Cli_lib.Arg_type.public_key_compressed)
3✔
1598
  in
1599
  Command.async ~summary:"Lock a tracked account"
3✔
1600
    (Cli_lib.Background_daemon.graphql_init pk_flag
3✔
1601
       ~f:(fun graphql_endpoint pk ->
1602
         let%map response =
1603
           Graphql_client.query_exn
×
1604
             Graphql_queries.Lock_account.(
1605
               make @@ makeVariables ~public_key:pk ())
×
1606
             graphql_endpoint
1607
         in
1608
         let pk_string =
×
1609
           Public_key.Compressed.to_base58_check response.lockAccount.public_key
1610
         in
1611
         printf "🔒 Locked account!\nPublic key: %s\n" pk_string ) )
×
1612

1613
let generate_libp2p_keypair_do privkey_path =
1614
  Cli_lib.Exceptions.handle_nicely
×
1615
  @@ fun () ->
1616
  Deferred.ignore_m
×
1617
    (let open Deferred.Let_syntax in
1618
    (* FIXME: I'd like to accumulate messages into this logger and only dump them out in failure paths. *)
1619
    let logger = Logger.null () in
1620
    (* Using the helper only for keypair generation requires no state. *)
1621
    File_system.with_temp_dir "mina-generate-libp2p-keypair" ~f:(fun tmpd ->
×
1622
        match%bind
1623
          Mina_net2.create ~logger ~conf_dir:tmpd ~all_peers_seen_metric:false
×
1624
            ~pids:(Child_processes.Termination.create_pid_table ())
×
1625
            ~on_peer_connected:ignore ~on_peer_disconnected:ignore ()
1626
        with
1627
        | Ok net ->
×
1628
            let%bind me = Mina_net2.generate_random_keypair net in
×
1629
            let%bind () = Mina_net2.shutdown net in
×
1630
            let%map () =
1631
              Secrets.Libp2p_keypair.Terminal_stdin.write_exn ~privkey_path me
×
1632
            in
1633
            printf "libp2p keypair:\n%s\n" (Mina_net2.Keypair.to_string me)
×
1634
        | Error e ->
×
1635
            [%log fatal] "failed to generate libp2p keypair: $error"
×
1636
              ~metadata:[ ("error", Error_json.error_to_yojson e) ] ;
×
1637
            exit 20 ))
×
1638

1639
let generate_libp2p_keypair =
1640
  Command.async
3✔
1641
    ~summary:"Generate a new libp2p keypair and print out the peer ID"
1642
    (let open Command.Let_syntax in
1643
    let%map_open privkey_path = Cli_lib.Flag.privkey_write_path in
1644
    generate_libp2p_keypair_do privkey_path)
×
1645

1646
let dump_libp2p_keypair_do privkey_path =
1647
  Cli_lib.Exceptions.handle_nicely
×
1648
  @@ fun () ->
1649
  Deferred.ignore_m
×
1650
    (let open Deferred.Let_syntax in
1651
    let logger = Logger.null () in
1652
    (* Using the helper only for keypair generation requires no state. *)
1653
    File_system.with_temp_dir "mina-dump-libp2p-keypair" ~f:(fun tmpd ->
×
1654
        match%bind
1655
          Mina_net2.create ~logger ~conf_dir:tmpd ~all_peers_seen_metric:false
×
1656
            ~pids:(Child_processes.Termination.create_pid_table ())
×
1657
            ~on_peer_connected:ignore ~on_peer_disconnected:ignore ()
1658
        with
1659
        | Ok net ->
×
1660
            let%bind () = Mina_net2.shutdown net in
×
1661
            let%map me = Secrets.Libp2p_keypair.read_exn' privkey_path in
×
1662
            printf "libp2p keypair:\n%s\n" (Mina_net2.Keypair.to_string me)
×
1663
        | Error e ->
×
1664
            [%log fatal] "failed to dump libp2p keypair: $error"
×
1665
              ~metadata:[ ("error", Error_json.error_to_yojson e) ] ;
×
1666
            exit 20 ))
×
1667

1668
let dump_libp2p_keypair =
1669
  Command.async ~summary:"Print an existing libp2p keypair"
3✔
1670
    (let open Command.Let_syntax in
1671
    let%map_open privkey_path = Cli_lib.Flag.privkey_read_path in
1672
    dump_libp2p_keypair_do privkey_path)
×
1673

1674
let trustlist_ip_flag =
1675
  Command.Param.(
1676
    flag "--ip-address" ~aliases:[ "ip-address" ]
3✔
1677
      ~doc:"CIDR An IPv4 CIDR mask for the client trustlist (eg, 10.0.0.0/8)"
1678
      (required Cli_lib.Arg_type.cidr_mask))
3✔
1679

1680
let trustlist_add =
1681
  let open Deferred.Let_syntax in
1682
  let open Daemon_rpcs in
1683
  Command.async ~summary:"Add an IP to the trustlist"
3✔
1684
    (Cli_lib.Background_daemon.rpc_init trustlist_ip_flag
3✔
1685
       ~f:(fun port trustlist_ip ->
1686
         let trustlist_ip_string = Unix.Cidr.to_string trustlist_ip in
×
1687
         match%map Client.dispatch Add_trustlist.rpc trustlist_ip port with
×
1688
         | Ok (Ok ()) ->
×
1689
             printf "Added %s to client trustlist" trustlist_ip_string
1690
         | Ok (Error e) ->
×
1691
             eprintf "Error adding %s to client trustlist: %s"
1692
               trustlist_ip_string (Error.to_string_hum e)
×
1693
         | Error e ->
×
1694
             eprintf "Unknown error doing daemon RPC: %s"
1695
               (Error.to_string_hum e) ) )
×
1696

1697
let trustlist_remove =
1698
  let open Deferred.Let_syntax in
1699
  let open Daemon_rpcs in
1700
  Command.async ~summary:"Remove a CIDR mask from the trustlist"
3✔
1701
    (Cli_lib.Background_daemon.rpc_init trustlist_ip_flag
3✔
1702
       ~f:(fun port trustlist_ip ->
1703
         let trustlist_ip_string = Unix.Cidr.to_string trustlist_ip in
×
1704
         match%map Client.dispatch Remove_trustlist.rpc trustlist_ip port with
×
1705
         | Ok (Ok ()) ->
×
1706
             printf "Removed %s to client trustlist" trustlist_ip_string
1707
         | Ok (Error e) ->
×
1708
             eprintf "Error removing %s from client trustlist: %s"
1709
               trustlist_ip_string (Error.to_string_hum e)
×
1710
         | Error e ->
×
1711
             eprintf "Unknown error doing daemon RPC: %s"
1712
               (Error.to_string_hum e) ) )
×
1713

1714
let trustlist_list =
1715
  let open Deferred.Let_syntax in
1716
  let open Daemon_rpcs in
1717
  let open Command.Param in
1718
  Command.async ~summary:"List the CIDR masks in the trustlist"
3✔
1719
    (Cli_lib.Background_daemon.rpc_init (return ()) ~f:(fun port () ->
3✔
1720
         match%map Client.dispatch Get_trustlist.rpc () port with
×
1721
         | Ok ips ->
×
1722
             printf
1723
               "The following IPs are permitted to connect to the daemon \
1724
                control port:\n" ;
1725
             List.iter ips ~f:(fun ip -> printf "%s\n" (Unix.Cidr.to_string ip))
×
1726
         | Error e ->
×
1727
             eprintf "Unknown error doing daemon RPC: %s"
1728
               (Error.to_string_hum e) ) )
×
1729

1730
let get_peers_graphql =
1731
  Command.async ~summary:"List the peers currently connected to the daemon"
3✔
1732
    (Cli_lib.Background_daemon.graphql_init
3✔
1733
       Command.Param.(return ())
3✔
1734
       ~f:(fun graphql_endpoint () ->
1735
         let%map response =
1736
           Graphql_client.query_exn
×
1737
             Graphql_queries.Get_peers.(make @@ makeVariables ())
×
1738
             graphql_endpoint
1739
         in
1740
         Array.iter response.getPeers ~f:(fun peer ->
×
1741
             printf "%s\n"
×
1742
               (Network_peer.Peer.to_multiaddr_string
×
1743
                  { host = Unix.Inet_addr.of_string peer.host
×
1744
                  ; libp2p_port = peer.libp2pPort
1745
                  ; peer_id = peer.peerId
1746
                  } ) ) ) )
1747

1748
let add_peers_graphql =
1749
  let open Command in
1750
  let seed =
1751
    Param.(
1752
      flag "--seed" ~aliases:[ "-seed" ]
3✔
1753
        ~doc:
1754
          "true/false Whether to add these peers as 'seed' peers, which may \
1755
           perform peer exchange. Default: true"
1756
        (optional bool))
3✔
1757
  in
1758
  let peers =
1759
    Param.(anon Anons.(non_empty_sequence_as_list ("peer" %: string)))
3✔
1760
  in
1761
  Command.async
3✔
1762
    ~summary:
1763
      "Add peers to the daemon\n\n\
1764
       Addresses take the format /ip4/IPADDR/tcp/PORT/p2p/PEERID"
1765
    (Cli_lib.Background_daemon.graphql_init (Param.both peers seed)
3✔
1766
       ~f:(fun graphql_endpoint (input_peers, seed) ->
1767
         let open Deferred.Let_syntax in
×
1768
         let peers =
1769
           List.map input_peers ~f:(fun peer ->
1770
               match
×
1771
                 Mina_net2.Multiaddr.of_string peer
×
1772
                 |> Mina_net2.Multiaddr.to_peer
1773
               with
1774
               | Some peer ->
×
1775
                   peer
1776
               | None ->
×
1777
                   eprintf
1778
                     "Could not parse %s as a peer address. It should use the \
1779
                      format /ip4/IPADDR/tcp/PORT/p2p/PEERID"
1780
                     peer ;
1781
                   Core.exit 1 )
×
1782
         in
1783
         let seed = Option.value ~default:true seed in
×
1784
         let%map response =
1785
           Graphql_client.query_exn
×
1786
             Graphql_queries.Add_peers.(make @@ makeVariables ~peers ~seed ())
×
1787
             graphql_endpoint
1788
         in
1789
         printf "Requested to add peers:\n" ;
×
1790
         Array.iter response.addPeers ~f:(fun peer ->
×
1791
             printf "%s\n"
×
1792
               (Network_peer.Peer.to_multiaddr_string
×
1793
                  { host = Unix.Inet_addr.of_string peer.host
×
1794
                  ; libp2p_port = peer.libp2pPort
1795
                  ; peer_id = peer.peerId
1796
                  } ) ) ) )
1797

1798
let compile_time_constants =
1799
  let genesis_constants = Genesis_constants.Compiled.genesis_constants in
1800
  let constraint_constants = Genesis_constants.Compiled.constraint_constants in
1801
  let proof_level = Genesis_constants.Compiled.proof_level in
1802
  Command.async
3✔
1803
    ~summary:"Print a JSON map of the compile-time consensus parameters"
1804
    (Command.Param.return (fun () ->
3✔
1805
         let home = Core.Sys.home_directory () in
×
1806
         let conf_dir = home ^/ Cli_lib.Default.conf_dir_name in
×
1807
         let genesis_dir =
×
1808
           let home = Core.Sys.home_directory () in
1809
           home ^/ Cli_lib.Default.conf_dir_name
×
1810
         in
1811
         let config_file =
1812
           match Sys.getenv "MINA_CONFIG_FILE" with
1813
           | Some config_file ->
×
1814
               config_file
1815
           | None ->
×
1816
               conf_dir ^/ "daemon.json"
×
1817
         in
1818
         let open Async in
1819
         let%map ({ consensus_constants; _ } as precomputed_values), _ =
1820
           config_file |> Genesis_ledger_helper.load_config_json >>| Or_error.ok
×
1821
           >>| Option.value
×
1822
                 ~default:
1823
                   (`Assoc [ ("ledger", `Assoc [ ("accounts", `List []) ]) ])
1824
           >>| Runtime_config.of_yojson >>| Result.ok
×
1825
           >>| Option.value ~default:Runtime_config.default
×
1826
           >>= Genesis_ledger_helper.init_from_config_file ~genesis_constants
×
1827
                 ~constraint_constants ~logger:(Logger.null ()) ~proof_level
×
1828
                 ~cli_proof_level:None ~genesis_dir
1829
           >>| Or_error.ok_exn
×
1830
         in
1831
         let all_constants =
×
1832
           `Assoc
1833
             [ ( "genesis_state_timestamp"
1834
               , `String
1835
                   ( Block_time.to_time_exn
×
1836
                       consensus_constants.genesis_state_timestamp
1837
                   |> Core.Time.to_string_iso8601_basic ~zone:Core.Time.Zone.utc
×
1838
                   ) )
1839
             ; ("k", `Int (Unsigned.UInt32.to_int consensus_constants.k))
×
1840
             ; ( "coinbase"
1841
               , `String
1842
                   (Currency.Amount.to_mina_string
×
1843
                      precomputed_values.constraint_constants.coinbase_amount )
1844
               )
1845
             ; ( "block_window_duration_ms"
1846
               , `Int
1847
                   precomputed_values.constraint_constants
1848
                     .block_window_duration_ms )
1849
             ; ("delta", `Int (Unsigned.UInt32.to_int consensus_constants.delta))
×
1850
             ; ( "sub_windows_per_window"
1851
               , `Int
1852
                   (Unsigned.UInt32.to_int
×
1853
                      consensus_constants.sub_windows_per_window ) )
1854
             ; ( "slots_per_sub_window"
1855
               , `Int
1856
                   (Unsigned.UInt32.to_int
×
1857
                      consensus_constants.slots_per_sub_window ) )
1858
             ; ( "slots_per_window"
1859
               , `Int
1860
                   (Unsigned.UInt32.to_int consensus_constants.slots_per_window)
×
1861
               )
1862
             ; ( "slots_per_epoch"
1863
               , `Int
1864
                   (Unsigned.UInt32.to_int consensus_constants.slots_per_epoch)
×
1865
               )
1866
             ]
1867
         in
1868
         Core_kernel.printf "%s\n%!" (Yojson.Safe.to_string all_constants) ) )
×
1869

1870
let node_status =
1871
  let open Command.Param in
1872
  let open Deferred.Let_syntax in
1873
  let daemon_peers_flag =
1874
    flag "--daemon-peers" ~aliases:[ "daemon-peers" ] no_arg
1875
      ~doc:"Get node statuses for peers known to the daemon"
1876
  in
1877
  let peers_flag =
3✔
1878
    flag "--peers" ~aliases:[ "peers" ]
1879
      (optional (Arg_type.comma_separated string))
3✔
1880
      ~doc:"CSV-LIST Peer multiaddrs for obtaining node status"
1881
  in
1882
  let show_errors_flag =
3✔
1883
    flag "--show-errors" ~aliases:[ "show-errors" ] no_arg
1884
      ~doc:"Include error responses in output"
1885
  in
1886
  let flags = Args.zip3 daemon_peers_flag peers_flag show_errors_flag in
3✔
1887
  Command.async ~summary:"Get node statuses for a set of peers"
3✔
1888
    (Cli_lib.Background_daemon.rpc_init flags
3✔
1889
       ~f:(fun port (daemon_peers, peers, show_errors) ->
1890
         if
×
1891
           (Option.is_none peers && not daemon_peers)
×
1892
           || (Option.is_some peers && daemon_peers)
×
1893
         then (
×
1894
           eprintf
1895
             "Must provide exactly one of daemon-peers or peer-ids flags\n%!" ;
1896
           don't_wait_for (exit 33) ) ;
×
1897
         let peer_ids_opt =
×
1898
           Option.map peers ~f:(fun peers ->
1899
               List.map peers ~f:Mina_net2.Multiaddr.of_string )
×
1900
         in
1901
         match%map
1902
           Daemon_rpcs.Client.dispatch Daemon_rpcs.Get_node_status.rpc
×
1903
             peer_ids_opt port
1904
         with
1905
         | Ok all_status_data ->
×
1906
             let all_status_data =
1907
               if show_errors then all_status_data
×
1908
               else
1909
                 List.filter all_status_data ~f:(fun td ->
×
1910
                     match td with Ok _ -> true | Error _ -> false )
×
1911
             in
1912
             List.iter all_status_data ~f:(fun peer_status_data ->
1913
                 printf "%s\n%!"
×
1914
                   ( Yojson.Safe.to_string
×
1915
                   @@ Mina_networking.Node_status.response_to_yojson
×
1916
                        peer_status_data ) )
1917
         | Error err ->
×
1918
             printf "Failed to get node status: %s\n%!"
1919
               (Error.to_string_hum err) ) )
×
1920

1921
let object_lifetime_statistics =
1922
  let open Daemon_rpcs in
1923
  let open Command.Param in
1924
  Command.async ~summary:"Dump internal object lifetime statistics to JSON"
3✔
1925
    (Cli_lib.Background_daemon.rpc_init (return ()) ~f:(fun port () ->
3✔
1926
         match%map
1927
           Client.dispatch Get_object_lifetime_statistics.rpc () port
×
1928
         with
1929
         | Ok stats ->
×
1930
             print_endline stats
1931
         | Error err ->
×
1932
             printf "Failed to get object lifetime statistics: %s\n%!"
1933
               (Error.to_string_hum err) ) )
×
1934

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

2101
let receipt_chain_hash =
2102
  let open Command.Let_syntax in
2103
  Command.basic
3✔
2104
    ~summary:
2105
      "Compute the next receipt chain hash from the previous hash and \
2106
       transaction ID"
2107
    (let%map_open previous_hash =
2108
       flag "--previous-hash"
3✔
2109
         ~doc:"HASH Previous receipt chain hash, Base58Check-encoded"
2110
         (required string)
3✔
2111
     and transaction_id =
2112
       flag "--transaction-id"
3✔
2113
         ~doc:"TRANSACTION_ID Transaction ID, Base64-encoded" (required string)
3✔
2114
     and index =
2115
       flag "--index"
3✔
2116
         ~doc:
2117
           "NN For a zkApp, 0 for fee payer or 1-based index of account update"
2118
         (optional string)
3✔
2119
     in
2120
     fun () ->
2121
       let previous_hash =
×
2122
         Receipt.Chain_hash.of_base58_check_exn previous_hash
2123
       in
2124
       let hash =
×
2125
         match index with
2126
         | None ->
×
2127
             let signed_cmd =
2128
               Signed_command.of_base64 transaction_id |> Or_error.ok_exn
×
2129
             in
2130
             Receipt.Chain_hash.cons_signed_command_payload
×
2131
               (Signed_command_payload signed_cmd.payload) previous_hash
2132
         | Some n ->
×
2133
             let zkapp_cmd =
2134
               Zkapp_command.of_base64 transaction_id |> Or_error.ok_exn
×
2135
             in
2136
             let receipt_elt =
×
2137
               let _txn_commitment, full_txn_commitment =
2138
                 Zkapp_command.get_transaction_commitments zkapp_cmd
2139
               in
2140
               Receipt.Zkapp_command_elt.Zkapp_command_commitment
×
2141
                 full_txn_commitment
2142
             in
2143
             let account_update_index = Mina_numbers.Index.of_string n in
2144
             Receipt.Chain_hash.cons_zkapp_command_commitment
×
2145
               account_update_index receipt_elt previous_hash
2146
       in
2147
       printf "%s\n" (Receipt.Chain_hash.to_base58_check hash) )
×
2148

2149
let chain_id_inputs =
2150
  let open Deferred.Let_syntax in
2151
  Command.async ~summary:"Print the inputs that yield the current chain id"
3✔
2152
    (Cli_lib.Background_daemon.rpc_init (Command.Param.all_unit [])
3✔
2153
       ~f:(fun port () ->
2154
         let open Daemon_rpcs in
×
2155
         match%map Client.dispatch Chain_id_inputs.rpc () port with
×
2156
         | Ok
×
2157
             ( genesis_state_hash
2158
             , genesis_constants
2159
             , snark_keys
2160
             , protocol_transaction_version
2161
             , protocol_network_version ) ->
2162
             let open Format in
2163
             printf
2164
               "@[<v>Genesis state hash: %s@,\
2165
                @[<v 2>Genesis_constants:@,\
2166
                Protocol:          %a@,\
2167
                Txn pool max size: %d@,\
2168
                Num accounts:      %a@,\
2169
                @]@,\
2170
                @[<v 2>Snark keys:@,\
2171
                %a@]@,\
2172
                Protocol transaction version: %u@,\
2173
                Protocol network version: %u@]@."
2174
               (State_hash.to_base58_check genesis_state_hash)
×
2175
               Yojson.Safe.pp
2176
               (Genesis_constants.Protocol.to_yojson genesis_constants.protocol)
×
2177
               genesis_constants.txpool_max_size
2178
               (pp_print_option
×
2179
                  ~none:(fun ppf () -> pp_print_string ppf "None")
×
2180
                  pp_print_int )
2181
               genesis_constants.num_accounts
2182
               (pp_print_list ~pp_sep:pp_print_cut pp_print_string)
×
2183
               snark_keys protocol_transaction_version protocol_network_version
2184
         | Error err ->
×
2185
             Format.eprintf "Could not get chain id inputs: %s@."
2186
               (Error.to_string_hum err) ) )
×
2187

2188
let hash_transaction =
2189
  let open Command.Let_syntax in
2190
  Command.basic
3✔
2191
    ~summary:"Compute the hash of a transaction from its transaction ID"
2192
    (let%map_open transaction_id =
2193
       flag "--transaction-id" ~doc:"ID ID of the transaction to hash"
3✔
2194
         (required string)
3✔
2195
     in
2196
     fun () ->
2197
       match Transaction_hash.hash_of_transaction_id transaction_id with
×
2198
       | Ok hash ->
×
2199
           printf "%s\n" (Transaction_hash.to_base58_check hash)
×
2200
       | Error err ->
×
2201
           Format.eprintf "Could not hash transaction: %s@."
2202
             (Error.to_string_hum err) )
×
2203

2204
let humanize_graphql_error
2205
    ~(graphql_endpoint : Uri.t Cli_lib.Flag.Types.with_name) = function
2206
  | `Failed_request e ->
×
2207
      Error.create "Unable to connect to Mina daemon" () (fun () ->
2208
          Sexp.List
×
2209
            [ List [ Atom "uri"; Atom (Uri.to_string graphql_endpoint.value) ]
×
2210
            ; List [ Atom "uri_flag"; Atom graphql_endpoint.name ]
2211
            ; List [ Atom "error_message"; Atom e ]
2212
            ] )
2213
  | `Graphql_error e ->
×
2214
      Error.createf "GraphQL error: %s" e
2215

2216
let runtime_config =
2217
  Command.async
3✔
2218
    ~summary:"Compute the runtime configuration used by a running daemon"
2219
    (Cli_lib.Background_daemon.graphql_init (Command.Param.return ())
3✔
2220
       ~f:(fun graphql_endpoint () ->
2221
         match%bind
2222
           Graphql_client.query
×
2223
             Graphql_queries.Runtime_config.(make @@ makeVariables ())
×
2224
             graphql_endpoint
2225
         with
2226
         | Ok runtime_config ->
×
2227
             Format.printf "%s@."
2228
               (Yojson.Basic.pretty_to_string runtime_config.runtimeConfig) ;
×
2229
             return ()
×
2230
         | Error err ->
×
2231
             Format.eprintf
2232
               "@[<v>Failed to retrieve runtime configuration. Error:@,%s@]@."
2233
               (Error.to_string_hum
×
2234
                  (humanize_graphql_error ~graphql_endpoint err) ) ;
×
2235
             exit 1 ) )
×
2236

2237
let thread_graph =
2238
  Command.async
3✔
2239
    ~summary:
2240
      "Return a Graphviz Dot graph representation of the internal thread \
2241
       hierarchy"
2242
    (Cli_lib.Background_daemon.graphql_init (Command.Param.return ())
3✔
2243
       ~f:(fun graphql_endpoint () ->
2244
         match%bind
2245
           Graphql_client.query
×
2246
             Graphql_queries.Thread_graph.(make @@ makeVariables ())
×
2247
             graphql_endpoint
2248
         with
2249
         | Ok graph ->
×
2250
             print_endline graph.threadGraph ;
2251
             return ()
×
2252
         | Error e ->
×
2253
             Format.eprintf
2254
               "@[<v>Failed to retrieve runtime configuration. Error:@,%s@]@."
2255
               (Error.to_string_hum
×
2256
                  (humanize_graphql_error ~graphql_endpoint e) ) ;
×
2257
             exit 1 ) )
×
2258

2259
let signature_kind =
2260
  Command.basic
3✔
2261
    ~summary:"Print the signature kind that this binary is compiled with"
2262
    (let%map.Command () = Command.Param.return () in
3✔
2263
     fun () ->
2264
       let signature_kind_string =
×
2265
         match Mina_signature_kind.t with
2266
         | Mainnet ->
×
2267
             "mainnet"
2268
         | Testnet ->
×
2269
             "testnet"
2270
         | Other_network s ->
×
2271
             (* Prefix string to disambiguate *)
2272
             "other network: " ^ s
2273
       in
2274
       Core.print_endline signature_kind_string )
2275

2276
let test_genesis_creation =
2277
  Command.async ~summary:"Test genesis creation"
3✔
2278
    (let%map_open.Command () = Command.Param.return () in
3✔
UNCOV
2279
     Cli_lib.Exceptions.handle_nicely
×
2280
       Test_genesis_creation.time_genesis_creation )
2281

2282
let test_ledger_application =
2283
  Command.async ~summary:"Test ledger application"
3✔
2284
    (let%map_open.Command privkey_path = Cli_lib.Flag.privkey_read_path
2285
     and prev_block_path =
2286
       flag "--prev-block-path" ~doc:"FILE file with serialized block"
3✔
2287
         (optional string)
3✔
2288
     and ledger_path =
2289
       flag "--ledger-path" ~doc:"FILE directory with ledger DB"
3✔
2290
         (required string)
3✔
2291
     and num_txs =
2292
       flag "--num-txs"
3✔
2293
         ~doc:"NN Number of transactions to create after preparatory rounds"
2294
         (required int)
3✔
2295
     and num_txs_per_round =
2296
       flag "--num-txs-per-round"
3✔
2297
         ~doc:
2298
           "NN Number of transactions to create per preparatory round \
2299
            (default: 3)"
2300
         (optional int)
3✔
2301
     and rounds =
2302
       flag "--rounds" ~doc:"NN Number of preparatory rounds (default: 580)"
3✔
2303
         (optional int)
3✔
2304
     and first_partition_slots =
2305
       flag "--first-partition-slots"
3✔
2306
         ~doc:
2307
           "NN Number of slots in first partition of scan state (default: 128)"
2308
         (optional int)
3✔
2309
     and max_depth =
2310
       flag "--max-depth" ~doc:"NN Maximum depth of masks (default: 290)"
3✔
2311
         (optional int)
3✔
2312
     and no_new_stack =
2313
       flag "--old-stack" ~doc:"Use is_new_stack: false (scan state)" no_arg
3✔
2314
     and has_second_partition =
2315
       flag "--has-second-partition"
3✔
2316
         ~doc:"Assume there is a second partition (scan state)" no_arg
2317
     and tracing = flag "--tracing" ~doc:"Wrap test into tracing" no_arg
3✔
2318
     and no_masks = flag "--no-masks" ~doc:"Do not create masks" no_arg in
3✔
2319
     Cli_lib.Exceptions.handle_nicely
1✔
2320
     @@ fun () ->
2321
     let first_partition_slots =
1✔
2322
       Option.value ~default:128 first_partition_slots
2323
     in
2324
     let num_txs_per_round = Option.value ~default:3 num_txs_per_round in
1✔
2325
     let rounds = Option.value ~default:580 rounds in
1✔
2326
     let max_depth = Option.value ~default:290 max_depth in
1✔
2327
     let constraint_constants =
1✔
2328
       Genesis_constants.Compiled.constraint_constants
2329
     in
2330
     let genesis_constants = Genesis_constants.Compiled.genesis_constants in
2331
     Test_ledger_application.test ~privkey_path ~ledger_path ?prev_block_path
2332
       ~first_partition_slots ~no_new_stack ~has_second_partition
2333
       ~num_txs_per_round ~rounds ~no_masks ~max_depth ~tracing num_txs
2334
       ~constraint_constants ~genesis_constants )
2335

2336
let itn_create_accounts =
2337
  let compile_config = Mina_compile_config.Compiled.t in
2338
  Command.async ~summary:"Fund new accounts for incentivized testnet"
3✔
2339
    (let open Command.Param in
2340
    let privkey_path = Cli_lib.Flag.privkey_read_path in
2341
    let key_prefix =
2342
      flag "--key-prefix" ~doc:"STRING prefix of keyfiles" (required string)
3✔
2343
    in
2344
    let num_accounts =
3✔
2345
      flag "--num-accounts" ~doc:"NN Number of new accounts" (required int)
3✔
2346
    in
2347
    let fee =
3✔
2348
      flag "--fee"
2349
        ~doc:
2350
          (sprintf "NN Fee in nanomina paid to create an account (minimum: %s)"
3✔
2351
             (Currency.Fee.to_string compile_config.minimum_user_command_fee) )
3✔
2352
        (required int)
3✔
2353
    in
2354
    let amount =
3✔
2355
      flag "--amount"
2356
        ~doc:"NN Amount in nanomina to be divided among new accounts"
2357
        (required int)
3✔
2358
    in
2359
    let args = Args.zip5 privkey_path key_prefix num_accounts fee amount in
3✔
2360
    let genesis_constants = Genesis_constants.Compiled.genesis_constants in
3✔
2361
    let constraint_constants =
2362
      Genesis_constants.Compiled.constraint_constants
2363
    in
2364
    Cli_lib.Background_daemon.rpc_init args
3✔
2365
      ~f:(Itn.create_accounts ~genesis_constants ~constraint_constants))
2366

2367
module Visualization = struct
2368
  let create_command (type rpc_response) ~name ~f
2369
      (rpc : (string, rpc_response) Rpc.Rpc.t) =
2370
    let open Deferred.Let_syntax in
6✔
2371
    Command.async
2372
      ~summary:(sprintf !"Produce a visualization of the %s" name)
6✔
2373
      (Cli_lib.Background_daemon.rpc_init
6✔
2374
         Command.Param.(anon @@ ("output-filepath" %: string))
6✔
2375
         ~f:(fun port filename ->
2376
           let%map message =
2377
             match%map Daemon_rpcs.Client.dispatch rpc filename port with
×
UNCOV
2378
             | Ok response ->
×
2379
                 f filename response
UNCOV
2380
             | Error e ->
×
UNCOV
2381
                 sprintf "Could not save file: %s\n" (Error.to_string_hum e)
×
2382
           in
UNCOV
2383
           print_string message ) )
×
2384

2385
  module Frontier = struct
2386
    let name = "transition-frontier"
2387

2388
    let command =
2389
      create_command ~name Daemon_rpcs.Visualization.Frontier.rpc
3✔
2390
        ~f:(fun filename -> function
UNCOV
2391
        | `Active () ->
×
2392
            Visualization_message.success name filename
UNCOV
2393
        | `Bootstrapping ->
×
2394
            Visualization_message.bootstrap name )
2395
  end
2396

2397
  module Registered_masks = struct
2398
    let name = "registered-masks"
2399

2400
    let command =
2401
      create_command ~name Daemon_rpcs.Visualization.Registered_masks.rpc
3✔
UNCOV
2402
        ~f:(fun filename () -> Visualization_message.success name filename)
×
2403
  end
2404

2405
  let command_group =
2406
    Command.group ~summary:"Visualize data structures special to Mina"
3✔
2407
      [ (Frontier.name, Frontier.command)
2408
      ; (Registered_masks.name, Registered_masks.command)
2409
      ]
2410
end
2411

2412
let accounts =
2413
  Command.group ~summary:"Client commands concerning account management"
3✔
2414
    ~preserve_subcommand_order:()
2415
    [ ("list", list_accounts)
2416
    ; ("create", create_account)
2417
    ; ("import", import_key)
2418
    ; ("export", export_key)
2419
    ; ("unlock", unlock_account)
2420
    ; ("lock", lock_account)
2421
    ]
2422

2423
let client =
2424
  Command.group ~summary:"Lightweight client commands"
3✔
2425
    ~preserve_subcommand_order:()
2426
    [ ("get-balance", get_balance_graphql)
2427
    ; ("get-tokens", get_tokens_graphql)
2428
    ; ("send-payment", send_payment_graphql)
2429
    ; ("delegate-stake", delegate_stake_graphql)
2430
    ; ("cancel-transaction", cancel_transaction_graphql)
2431
    ; ("set-snark-worker", set_snark_worker)
2432
    ; ("set-snark-work-fee", set_snark_work_fee)
2433
    ; ("export-logs", Export_logs.export_via_graphql)
2434
    ; ("export-local-logs", Export_logs.export_locally)
2435
    ; ("stop-daemon", stop_daemon)
2436
    ; ("status", status)
2437
    ]
2438

2439
let client_trustlist_group =
2440
  Command.group ~summary:"Client trustlist management"
3✔
2441
    ~preserve_subcommand_order:()
2442
    [ ("add", trustlist_add)
2443
    ; ("list", trustlist_list)
2444
    ; ("remove", trustlist_remove)
2445
    ]
2446

2447
let advanced ~itn_features =
2448
  let cmds0 =
3✔
2449
    [ ("get-nonce", get_nonce_cmd)
2450
    ; ("client-trustlist", client_trustlist_group)
2451
    ; ("get-trust-status", get_trust_status)
2452
    ; ("get-trust-status-all", get_trust_status_all)
2453
    ; ("get-public-keys", get_public_keys)
2454
    ; ("reset-trust-status", reset_trust_status)
2455
    ; ("batch-send-payments", batch_send_payments)
2456
    ; ("status-clear-hist", status_clear_hist)
2457
    ; ("wrap-key", wrap_key)
2458
    ; ("dump-keypair", dump_keypair)
2459
    ; ("constraint-system-digests", constraint_system_digests)
2460
    ; ("start-tracing", start_tracing)
2461
    ; ("stop-tracing", stop_tracing)
2462
    ; ("start-internal-tracing", start_internal_tracing)
2463
    ; ("stop-internal-tracing", stop_internal_tracing)
2464
    ; ("snark-job-list", snark_job_list)
2465
    ; ("pooled-user-commands", pooled_user_commands)
2466
    ; ("pooled-zkapp-commands", pooled_zkapp_commands)
2467
    ; ("snark-pool-list", snark_pool_list)
2468
    ; ("pending-snark-work", pending_snark_work)
2469
    ; ("compile-time-constants", compile_time_constants)
2470
    ; ("node-status", node_status)
2471
    ; ("visualization", Visualization.command_group)
2472
    ; ("verify-receipt", verify_receipt)
2473
    ; ("generate-keypair", Cli_lib.Commands.generate_keypair)
2474
    ; ("validate-keypair", Cli_lib.Commands.validate_keypair)
2475
    ; ("validate-transaction", Cli_lib.Commands.validate_transaction)
2476
    ; ("send-rosetta-transactions", send_rosetta_transactions_graphql)
2477
    ; ("time-offset", get_time_offset_graphql)
2478
    ; ("get-peers", get_peers_graphql)
2479
    ; ("add-peers", add_peers_graphql)
2480
    ; ("object-lifetime-statistics", object_lifetime_statistics)
2481
    ; ("archive-blocks", archive_blocks)
2482
    ; ("compute-receipt-chain-hash", receipt_chain_hash)
2483
    ; ("hash-transaction", hash_transaction)
2484
    ; ("set-coinbase-receiver", set_coinbase_receiver_graphql)
2485
    ; ("chain-id-inputs", chain_id_inputs)
2486
    ; ("runtime-config", runtime_config)
2487
    ; ("vrf", Cli_lib.Commands.Vrf.command_group)
2488
    ; ("thread-graph", thread_graph)
2489
    ; ("print-signature-kind", signature_kind)
2490
    ; ( "test"
2491
      , Command.group ~summary:"Testing-only commands"
3✔
2492
          [ ("create-genesis", test_genesis_creation) ] )
2493
    ]
2494
  in
2495
  let cmds =
UNCOV
2496
    if itn_features then ("itn-create-accounts", itn_create_accounts) :: cmds0
×
2497
    else cmds0
3✔
2498
  in
2499
  Command.group ~summary:"Advanced client commands" cmds
2500

2501
let ledger =
2502
  Command.group ~summary:"Ledger commands"
3✔
2503
    [ ("export", export_ledger)
2504
    ; ("hash", hash_ledger)
2505
    ; ("currency", currency_in_ledger)
2506
    ; ( "test"
2507
      , Command.group ~summary:"Testing-only commands"
3✔
2508
          [ ("apply", test_ledger_application)
2509
          ; ("generate-accounts", Cli_lib.Commands.generate_test_ledger)
2510
          ] )
2511
    ]
2512

2513
let libp2p =
2514
  Command.group ~summary:"Libp2p commands"
3✔
2515
    [ ("generate-keypair", generate_libp2p_keypair)
2516
    ; ("dump-keypair", dump_libp2p_keypair)
2517
    ]
3✔
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