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

MinaProtocol / mina / 3207

23 Jan 2025 10:05PM UTC coverage: 60.545% (+27.8%) from 32.759%
3207

push

buildkite

web-flow
Merge pull request #16511 from MinaProtocol/dkijania/build_performance_tooling_in_ci_dev

[Dev] Run benchmarks in CI

49261 of 81362 relevant lines covered (60.55%)

473114.6 hits per line

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

25.53
/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
(* TODO consider a better way of setting a default transaction fee than
8
   a fixed compile-time value *)
9
let default_transaction_fee = Currency.Fee.of_nanomina_int_exn 250000000
3✔
10

11
module Client = Graphql_lib.Client.Make (struct
12
  let preprocess_variables_string = Fn.id
13

14
  let headers = String.Map.empty
15
end)
16

17
module Args = struct
18
  open Command.Param
19

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

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

24
  let zip4 arg1 arg2 arg3 arg4 =
25
    return (fun a b c d -> (a, b, c, d)) <*> arg1 <*> arg2 <*> arg3 <*> arg4
×
26

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

31
  let zip6 arg1 arg2 arg3 arg4 arg5 arg6 =
32
    return (fun a b c d e f -> (a, b, c, d, e, f))
×
33
    <*> arg1 <*> arg2 <*> arg3 <*> arg4 <*> arg5 <*> arg6
3✔
34

35
  let zip7 arg1 arg2 arg3 arg4 arg5 arg6 arg7 =
36
    return (fun a b c d e f g -> (a, b, c, d, e, f, g))
×
37
    <*> arg1 <*> arg2 <*> arg3 <*> arg4 <*> arg5 <*> arg6 <*> arg7
×
38
end
39

40
let or_error_str ~f_ok ~error = function
41
  | Ok x ->
×
42
      f_ok x
43
  | Error e ->
×
44
      sprintf "%s\n%s\n" error (Error.to_string_hum e)
×
45

46
let load_compile_config config_files =
47
  let%map conf = Runtime_config.Constants.load_constants config_files in
×
48
  Runtime_config.Constants.compile_config conf
×
49

50
let stop_daemon =
51
  let open Deferred.Let_syntax in
52
  let open Daemon_rpcs in
53
  Command.async ~summary:"Stop the daemon"
3✔
54
    (Cli_lib.Background_daemon.rpc_init Cli_lib.Flag.config_files
3✔
55
       ~f:(fun port config_files ->
56
         let%bind compile_config = load_compile_config config_files in
×
57
         let%map res =
58
           Daemon_rpcs.Client.dispatch ~compile_config Stop_daemon.rpc () port
×
59
         in
60
         printf "%s"
×
61
           (or_error_str res
×
62
              ~f_ok:(fun _ -> "Daemon stopping\n")
×
63
              ~error:"Daemon likely stopped" ) ) )
64

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

97
let get_tokens_graphql =
98
  let open Command.Param in
99
  let pk_flag =
100
    flag "--public-key" ~aliases:[ "public-key" ]
101
      ~doc:"PUBLICKEY Public key for which you want to find accounts"
102
      (required Cli_lib.Arg_type.public_key_compressed)
3✔
103
  in
104
  Command.async ~summary:"Get all token IDs that a public key has accounts for"
3✔
105
    (Cli_lib.Background_daemon.graphql_init pk_flag
3✔
106
       ~f:(fun graphql_endpoint public_key ->
107
         let%map response =
108
           Graphql_client.query_exn
×
109
             Graphql_queries.Get_all_accounts.(
110
               make @@ makeVariables ~public_key ())
×
111
             graphql_endpoint
112
         in
113
         printf "Accounts are held for token IDs:\n" ;
×
114
         Array.iter response.accounts ~f:(fun account ->
×
115
             printf "%s " (Token_id.to_string account.tokenId) ) ) )
×
116

117
let get_time_offset_graphql =
118
  Command.async
3✔
119
    ~summary:
120
      "Get the time offset in seconds used by the daemon to convert real time \
121
       into blockchain time"
122
    (Cli_lib.Background_daemon.graphql_init (Command.Param.return ())
3✔
123
       ~f:(fun graphql_endpoint () ->
124
         let%map response =
125
           Graphql_client.query_exn
×
126
             Graphql_queries.Time_offset.(make @@ makeVariables ())
×
127
             graphql_endpoint
128
         in
129
         let time_offset = response.timeOffset in
×
130
         printf
131
           "Current time offset:\n\
132
            %i\n\n\
133
            Start other daemons with this offset by setting the \
134
            MINA_TIME_OFFSET environment variable in the shell before \
135
            executing them:\n\
136
            export MINA_TIME_OFFSET=%i\n"
137
           time_offset time_offset ) )
138

139
let print_trust_statuses statuses json =
140
  if json then
×
141
    printf "%s\n"
×
142
      (Yojson.Safe.to_string
×
143
         (`List
144
           (List.map
×
145
              ~f:(fun (peer, status) ->
146
                `List
×
147
                  [ Network_peer.Peer.to_yojson peer
×
148
                  ; Trust_system.Peer_status.to_yojson status
×
149
                  ] )
150
              statuses ) ) )
151
  else
152
    let ban_status status =
×
153
      match status.Trust_system.Peer_status.banned with
×
154
      | Unbanned ->
×
155
          "Unbanned"
156
      | Banned_until tm ->
×
157
          sprintf "Banned_until %s" (Time.to_string_abs tm ~zone:Time.Zone.utc)
×
158
    in
159
    List.fold ~init:()
160
      ~f:(fun () (peer, status) ->
161
        printf "%s, %0.04f, %s\n"
×
162
          (Network_peer.Peer.to_multiaddr_string peer)
×
163
          status.trust (ban_status status) )
×
164
      statuses
165

166
let round_trust_score trust_status =
167
  let open Trust_system.Peer_status in
×
168
  let trust = Float.round_decimal trust_status.trust ~decimal_digits:4 in
169
  { trust_status with trust }
×
170

171
let get_trust_status =
172
  let open Command.Param in
173
  let open Deferred.Let_syntax in
174
  let address_flag =
175
    flag "--ip-address" ~aliases:[ "ip-address" ]
176
      ~doc:
177
        "IP An IPv4 or IPv6 address for which you want to query the trust \
178
         status"
179
      (required Cli_lib.Arg_type.ip_address)
3✔
180
  in
181
  let json_flag = Cli_lib.Flag.json in
3✔
182
  let config_files = Cli_lib.Flag.config_files in
183
  let flags = Args.zip3 config_files address_flag json_flag in
184
  Command.async ~summary:"Get the trust status associated with an IP address"
3✔
185
    (Cli_lib.Background_daemon.rpc_init flags
3✔
186
       ~f:(fun port (config_files, ip_address, json) ->
187
         let%bind compile_config = load_compile_config config_files in
×
188
         match%map
189
           Daemon_rpcs.Client.dispatch ~compile_config
×
190
             Daemon_rpcs.Get_trust_status.rpc ip_address port
191
         with
192
         | Ok statuses ->
×
193
             print_trust_statuses
194
               (List.map
×
195
                  ~f:(fun (peer, status) -> (peer, round_trust_score status))
×
196
                  statuses )
197
               json
198
         | Error e ->
×
199
             printf "Failed to get trust status %s\n" (Error.to_string_hum e) )
×
200
    )
201

202
let ip_trust_statuses_to_yojson ip_trust_statuses =
203
  let items =
×
204
    List.map ip_trust_statuses ~f:(fun (ip_addr, status) ->
205
        `Assoc
×
206
          [ ("ip", `String (Unix.Inet_addr.to_string ip_addr))
×
207
          ; ("status", Trust_system.Peer_status.to_yojson status)
×
208
          ] )
209
  in
210
  `List items
×
211

212
let get_trust_status_all =
213
  let open Command.Param in
214
  let open Deferred.Let_syntax in
215
  let nonzero_flag =
216
    flag "--nonzero-only" ~aliases:[ "nonzero-only" ] no_arg
217
      ~doc:"Only show trust statuses whose trust score is nonzero"
218
  in
219
  let json_flag = Cli_lib.Flag.json in
3✔
220
  let config_files = Cli_lib.Flag.config_files in
221
  let flags = Args.zip3 config_files nonzero_flag json_flag in
222
  Command.async
3✔
223
    ~summary:"Get trust statuses for all peers known to the trust system"
224
    (Cli_lib.Background_daemon.rpc_init flags
3✔
225
       ~f:(fun port (config_files, nonzero, json) ->
226
         let%bind compile_config = load_compile_config config_files in
×
227
         match%map
228
           Daemon_rpcs.Client.dispatch ~compile_config
×
229
             Daemon_rpcs.Get_trust_status_all.rpc () port
230
         with
231
         | Ok ip_trust_statuses ->
×
232
             (* always round the trust scores for display *)
233
             let ip_rounded_trust_statuses =
234
               List.map ip_trust_statuses ~f:(fun (ip_addr, status) ->
235
                   (ip_addr, round_trust_score status) )
×
236
             in
237
             let filtered_ip_trust_statuses =
×
238
               if nonzero then
239
                 List.filter ip_rounded_trust_statuses
×
240
                   ~f:(fun (_ip_addr, status) ->
241
                     not Float.(equal status.trust zero) )
×
242
               else ip_rounded_trust_statuses
×
243
             in
244
             print_trust_statuses filtered_ip_trust_statuses json
245
         | Error e ->
×
246
             printf "Failed to get trust statuses %s\n" (Error.to_string_hum e) )
×
247
    )
248

249
let reset_trust_status =
250
  let open Command.Param in
251
  let open Deferred.Let_syntax in
252
  let address_flag =
253
    flag "--ip-address" ~aliases:[ "ip-address" ]
254
      ~doc:
255
        "IP An IPv4 or IPv6 address for which you want to reset the trust \
256
         status"
257
      (required Cli_lib.Arg_type.ip_address)
3✔
258
  in
259
  let json_flag = Cli_lib.Flag.json in
3✔
260
  let config_files = Cli_lib.Flag.config_files in
261
  let flags = Args.zip3 config_files address_flag json_flag in
262
  Command.async ~summary:"Reset the trust status associated with an IP address"
3✔
263
    (Cli_lib.Background_daemon.rpc_init flags
3✔
264
       ~f:(fun port (config_files, ip_address, json) ->
265
         let%bind compile_config = load_compile_config config_files in
×
266
         match%map
267
           Daemon_rpcs.Client.dispatch ~compile_config
×
268
             Daemon_rpcs.Reset_trust_status.rpc ip_address port
269
         with
270
         | Ok status ->
×
271
             print_trust_statuses status json
272
         | Error e ->
×
273
             printf "Failed to reset trust status %s\n" (Error.to_string_hum e) )
×
274
    )
275

276
let get_public_keys =
277
  let open Daemon_rpcs in
278
  let open Command.Param in
279
  let with_details_flag =
280
    flag "--with-details" ~aliases:[ "with-details" ] no_arg
281
      ~doc:"Show extra details (eg. balance, nonce) in addition to public keys"
282
  in
283
  let error_ctx = "Failed to get public-keys" in
3✔
284
  let config_files = Cli_lib.Flag.config_files in
285
  Command.async ~summary:"Get public keys"
3✔
286
    (Cli_lib.Background_daemon.rpc_init
3✔
287
       (Args.zip3 config_files with_details_flag Cli_lib.Flag.json)
3✔
288
       ~f:(fun port (config_files, is_balance_included, json) ->
289
         let%bind compile_config = load_compile_config config_files in
×
290
         if is_balance_included then
×
291
           Daemon_rpcs.Client.dispatch_pretty_message ~compile_config ~json
×
292
             ~join_error:Or_error.join ~error_ctx
293
             (module Cli_lib.Render.Public_key_with_details)
294
             Get_public_keys_with_details.rpc () port
295
         else
296
           Daemon_rpcs.Client.dispatch_pretty_message ~compile_config ~json
×
297
             ~join_error:Or_error.join ~error_ctx
298
             (module Cli_lib.Render.String_list_formatter)
299
             Get_public_keys.rpc () port ) )
300

301
let read_json filepath ~flag =
302
  let%map res =
303
    Deferred.Or_error.try_with ~here:[%here] (fun () ->
×
304
        let%map json_contents = Reader.file_contents filepath in
×
305
        Ok (Yojson.Safe.from_string json_contents) )
×
306
  in
307
  match res with
×
308
  | Ok c ->
×
309
      c
310
  | Error e ->
×
311
      Or_error.errorf "Could not read %s at %s\n%s" flag filepath
312
        (Error.to_string_hum e)
×
313

314
let verify_receipt =
315
  let open Deferred.Let_syntax in
316
  let open Daemon_rpcs in
317
  let open Command.Param in
318
  let open Cli_lib.Arg_type in
319
  let proof_path_flag =
320
    flag "--proof-path" ~aliases:[ "proof-path" ]
321
      ~doc:"PROOFFILE File to read json version of payment receipt"
322
      (required string)
3✔
323
  in
324
  let payment_path_flag =
3✔
325
    flag "--payment-path" ~aliases:[ "payment-path" ]
326
      ~doc:"PAYMENTPATH File to read json version of verifying payment"
327
      (required string)
3✔
328
  in
329
  let address_flag =
3✔
330
    flag "--address" ~aliases:[ "address" ]
331
      ~doc:"PUBLICKEY Public-key address of sender"
332
      (required public_key_compressed)
3✔
333
  in
334
  let token_flag =
3✔
335
    flag "--token" ~aliases:[ "token" ]
336
      ~doc:"TOKEN_ID The token ID for the account"
337
      (optional_with_default Token_id.default Cli_lib.Arg_type.token_id)
3✔
338
  in
339
  let config_files = Cli_lib.Flag.config_files in
3✔
340
  Command.async ~summary:"Verify a receipt of a sent payment"
3✔
341
    (Cli_lib.Background_daemon.rpc_init
3✔
342
       (Args.zip5 config_files payment_path_flag proof_path_flag address_flag
3✔
343
          token_flag )
344
       ~f:(fun port (config_files, payment_path, proof_path, pk, token_id) ->
345
         let%bind compile_config = load_compile_config config_files in
×
346
         let account_id = Account_id.create pk token_id in
×
347
         let dispatch_result =
×
348
           let open Deferred.Or_error.Let_syntax in
349
           let%bind payment_json =
350
             read_json payment_path ~flag:"payment-path"
×
351
           in
352
           let%bind proof_json = read_json proof_path ~flag:"proof-path" in
×
353
           let to_deferred_or_error result ~error =
×
354
             Result.map_error result ~f:(fun s ->
×
355
                 Error.of_string (sprintf "%s: %s" error s) )
×
356
             |> Deferred.return
357
           in
358
           let%bind payment =
359
             User_command.of_yojson payment_json
×
360
             |> to_deferred_or_error
×
361
                  ~error:
362
                    (sprintf "Payment file %s has invalid json format"
×
363
                       payment_path )
364
           and proof =
365
             [%of_yojson: Receipt.Chain_hash.t * User_command.t list] proof_json
×
366
             |> to_deferred_or_error
×
367
                  ~error:
368
                    (sprintf "Proof file %s has invalid json format" proof_path)
×
369
           in
370
           Daemon_rpcs.Client.dispatch ~compile_config Verify_proof.rpc
×
371
             (account_id, payment, proof)
372
             port
373
         in
374
         match%map dispatch_result with
375
         | Ok (Ok ()) ->
×
376
             printf "Payment is valid on the existing blockchain!\n"
377
         | Error e | Ok (Error e) ->
×
378
             eprintf "Error verifying the receipt: %s\n" (Error.to_string_hum e) )
×
379
    )
380

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

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

431
let status =
432
  let open Daemon_rpcs in
433
  let flag =
434
    Args.zip3 Cli_lib.Flag.config_files Cli_lib.Flag.json
435
      Cli_lib.Flag.performance
436
  in
437
  Command.async ~summary:"Get running daemon status"
3✔
438
    (Cli_lib.Background_daemon.rpc_init flag
3✔
439
       ~f:(fun port (config_files, json, performance) ->
440
         let%bind compile_config = load_compile_config config_files in
×
441
         Daemon_rpcs.Client.dispatch_pretty_message ~compile_config ~json
×
442
           ~join_error:Fn.id ~error_ctx:"Failed to get status"
443
           (module Daemon_rpcs.Types.Status)
444
           Get_status.rpc
445
           (if performance then `Performance else `None)
×
446
           port ) )
447

448
let status_clear_hist =
449
  let open Daemon_rpcs in
450
  let flag =
451
    Args.zip3 Cli_lib.Flag.config_files Cli_lib.Flag.json
452
      Cli_lib.Flag.performance
453
  in
454
  Command.async ~summary:"Clear histograms reported in status"
3✔
455
    (Cli_lib.Background_daemon.rpc_init flag
3✔
456
       ~f:(fun port (config_files, json, performance) ->
457
         let%bind compile_config = load_compile_config config_files in
×
458
         Daemon_rpcs.Client.dispatch_pretty_message ~compile_config ~json
×
459
           ~join_error:Fn.id
460
           ~error_ctx:"Failed to clear histograms reported in status"
461
           (module Daemon_rpcs.Types.Status)
462
           Clear_hist_status.rpc
463
           (if performance then `Performance else `None)
×
464
           port ) )
465

466
let get_nonce_exn ~compile_config ~rpc public_key port =
467
  match%bind get_nonce ~compile_config ~rpc public_key port with
×
468
  | Error e ->
×
469
      eprintf "Failed to get nonce\n%s\n" e ;
470
      exit 3
×
471
  | Ok nonce ->
×
472
      return nonce
473

474
let unwrap_user_command (`UserCommand x) = x
×
475

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

546
let transaction_id_to_string id =
547
  Yojson.Basic.to_string (Graphql_lib.Scalars.TransactionId.serialize id)
×
548

549
let send_payment_graphql =
550
  let open Command.Param in
551
  let open Cli_lib.Arg_type in
552
  let receiver_flag =
553
    flag "--receiver" ~aliases:[ "receiver" ]
554
      ~doc:"PUBLICKEY Public key to which you want to send money"
555
      (required public_key_compressed)
3✔
556
  in
557
  let amount_flag =
3✔
558
    flag "--amount" ~aliases:[ "amount" ]
559
      ~doc:"VALUE Payment amount you want to send" (required txn_amount)
3✔
560
  in
561
  let args =
3✔
562
    Args.zip3 Cli_lib.Flag.signed_command_common receiver_flag amount_flag
563
  in
564
  Command.async ~summary:"Send payment to an address"
3✔
565
    (Cli_lib.Background_daemon.graphql_init args
3✔
566
       ~f:(fun
567
            graphql_endpoint
568
            ({ Cli_lib.Flag.sender; fee; nonce; memo }, receiver, amount)
569
          ->
570
         let open Deferred.Let_syntax in
×
571
         let fee = Option.value ~default:default_transaction_fee fee in
572
         let%map response =
573
           let input =
574
             Mina_graphql.Types.Input.SendPaymentInput.make_input ~to_:receiver
575
               ~from:sender ~amount ~fee ?memo ?nonce ()
576
           in
577
           Graphql_client.query_exn
×
578
             Graphql_queries.Send_payment.(make @@ makeVariables ~input ())
×
579
             graphql_endpoint
580
         in
581
         printf "Dispatched payment with ID %s\n"
×
582
           (transaction_id_to_string response.sendPayment.payment.id) ) )
×
583

584
let delegate_stake_graphql =
585
  let open Command.Param in
586
  let open Cli_lib.Arg_type in
587
  let receiver_flag =
588
    flag "--receiver" ~aliases:[ "receiver" ]
589
      ~doc:"PUBLICKEY Public key to which you want to delegate your stake"
590
      (required public_key_compressed)
3✔
591
  in
592
  let args = Args.zip2 Cli_lib.Flag.signed_command_common receiver_flag in
3✔
593
  Command.async ~summary:"Delegate your stake to another public key"
3✔
594
    (Cli_lib.Background_daemon.graphql_init args
3✔
595
       ~f:(fun
596
            graphql_endpoint
597
            ({ Cli_lib.Flag.sender; fee; nonce; memo }, receiver)
598
          ->
599
         let open Deferred.Let_syntax in
×
600
         let fee = Option.value ~default:default_transaction_fee fee in
601
         let%map response =
602
           Graphql_client.query_exn
×
603
             Graphql_queries.Send_delegation.(
604
               make
×
605
               @@ makeVariables ~receiver ~sender
×
606
                    ~fee:(Currency.Fee.to_uint64 fee)
×
607
                    ?nonce ?memo ())
608
             graphql_endpoint
609
         in
610
         printf "Dispatched stake delegation with ID %s\n"
×
611
           (transaction_id_to_string response.sendDelegation.delegation.id) ) )
×
612

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

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

692
module Export_logs = struct
693
  let pp_export_result tarfile = printf "Exported logs to %s\n%!" tarfile
×
694

695
  let tarfile_flag =
696
    let open Command.Param in
697
    flag "--tarfile" ~aliases:[ "tarfile" ]
3✔
698
      ~doc:"STRING Basename of the tar archive (default: date_time)"
699
      (optional string)
3✔
700

701
  let export_via_graphql =
702
    Command.async ~summary:"Export daemon logs to tar archive"
3✔
703
      (Cli_lib.Background_daemon.graphql_init tarfile_flag
3✔
704
         ~f:(fun graphql_endpoint basename ->
705
           let%map response =
706
             Graphql_client.query_exn
×
707
               Graphql_queries.Export_logs.(make @@ makeVariables ?basename ())
×
708
               graphql_endpoint
709
           in
710
           pp_export_result response.exportLogs.exportLogs.tarfile ) )
×
711

712
  let export_locally =
713
    let run ~tarfile ~conf_dir =
714
      let open Mina_lib in
×
715
      let conf_dir = Conf_dir.compute_conf_dir conf_dir in
716
      fun () ->
×
717
        match%map Conf_dir.export_logs_to_tar ?basename:tarfile ~conf_dir with
718
        | Ok result ->
×
719
            pp_export_result result
720
        | Error err ->
×
721
            failwithf "Error when exporting logs: %s"
722
              (Error_json.error_to_yojson err |> Yojson.Safe.to_string)
×
723
              ()
724
    in
725
    let open Command.Let_syntax in
726
    Command.async ~summary:"Export local logs (no daemon) to tar archive"
3✔
727
      (let%map tarfile = tarfile_flag and conf_dir = Cli_lib.Flag.conf_dir in
728
       run ~tarfile ~conf_dir )
×
729
end
730

731
let wrap_key =
732
  Command.async ~summary:"Wrap a private key into a private key file"
3✔
733
    (let open Command.Let_syntax in
734
    let%map_open privkey_path = Cli_lib.Flag.privkey_write_path in
735
    Cli_lib.Exceptions.handle_nicely
1✔
736
    @@ fun () ->
737
    let open Deferred.Let_syntax in
1✔
738
    let%bind privkey =
739
      Secrets.Password.hidden_line_or_env "Private key: " ~env:"CODA_PRIVKEY"
1✔
740
    in
741
    let pk = Private_key.of_base58_check_exn (Bytes.to_string privkey) in
1✔
742
    let kp = Keypair.of_private_key_exn pk in
1✔
743
    Secrets.Keypair.Terminal_stdin.write_exn kp ~privkey_path)
1✔
744

745
let dump_keypair =
746
  Command.async ~summary:"Print out a keypair from a private key file"
3✔
747
    (let open Command.Let_syntax in
748
    let%map_open privkey_path = Cli_lib.Flag.privkey_read_path in
749
    Cli_lib.Exceptions.handle_nicely
×
750
    @@ fun () ->
751
    let open Deferred.Let_syntax in
×
752
    let%map kp =
753
      Secrets.Keypair.Terminal_stdin.read_exn ~which:"Mina keypair" privkey_path
×
754
    in
755
    printf "Public key: %s\nPrivate key: %s\n"
×
756
      ( kp.public_key |> Public_key.compress
×
757
      |> Public_key.Compressed.to_base58_check )
×
758
      (kp.private_key |> Private_key.to_base58_check))
×
759

760
let handle_export_ledger_response ~json = function
761
  | Error e ->
×
762
      Daemon_rpcs.Client.print_rpc_error e ;
763
      exit 1
×
764
  | Ok (Error e) ->
×
765
      printf !"Ledger not found: %s\n" (Error.to_string_hum e) ;
×
766
      exit 1
×
767
  | Ok (Ok accounts) ->
×
768
      if json then (
×
769
        Yojson.Safe.pretty_print Format.std_formatter
770
          (Runtime_config.Accounts.to_yojson
×
771
             (List.map accounts ~f:(fun a ->
×
772
                  Genesis_ledger_helper.Accounts.Single.of_account a None ) ) ) ;
×
773
        printf "\n" )
×
774
      else printf !"%{sexp:Account.t list}\n" accounts ;
×
775
      return ()
776

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

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

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

969
let constraint_system_digests =
970
  let open Command.Let_syntax in
971
  Command.async ~summary:"Print MD5 digest of each SNARK constraint"
3✔
972
    (let%map_open config_files = Cli_lib.Flag.config_files in
973
     fun () ->
974
       let open Deferred.Let_syntax in
×
975
       let%bind constraint_constants, proof_level =
976
         let%map conf = Runtime_config.Constants.load_constants config_files in
×
977
         Runtime_config.Constants.(constraint_constants conf, proof_level conf)
×
978
       in
979
       let all =
×
980
         Transaction_snark.constraint_system_digests ~constraint_constants ()
×
981
         @ Blockchain_snark.Blockchain_snark_state.constraint_system_digests
×
982
             ~proof_level ~constraint_constants ()
983
       in
984
       let all =
985
         List.sort ~compare:(fun (k1, _) (k2, _) -> String.compare k1 k2) all
×
986
       in
987
       List.iter all ~f:(fun (k, v) -> printf "%s\t%s\n" k (Md5.to_hex v)) ;
×
988
       Deferred.unit )
×
989

990
let snark_job_list =
991
  Command.async
3✔
992
    ~summary:
993
      "List of snark jobs in JSON format that are yet to be included in the \
994
       blocks"
995
    (Cli_lib.Background_daemon.rpc_init Cli_lib.Flag.config_files
3✔
996
       ~f:(fun port config_files ->
997
         let%bind compile_config = load_compile_config config_files in
×
998
         match%map
999
           Daemon_rpcs.Client.dispatch_join_errors ~compile_config
×
1000
             Daemon_rpcs.Snark_job_list.rpc () port
1001
         with
1002
         | Ok str ->
×
1003
             printf "%s" str
1004
         | Error e ->
×
1005
             Daemon_rpcs.Client.print_rpc_error e ) )
1006

1007
let snark_pool_list =
1008
  let open Command.Param in
1009
  Command.async ~summary:"List of snark works in the snark pool in JSON format"
3✔
1010
    (Cli_lib.Background_daemon.graphql_init (return ())
3✔
1011
       ~f:(fun graphql_endpoint () ->
1012
         Deferred.map
×
1013
           (Graphql_client.query_exn
×
1014
              Graphql_queries.Snark_pool.(make @@ makeVariables ())
×
1015
              graphql_endpoint )
1016
           ~f:(fun response ->
1017
             let lst =
×
1018
               [%to_yojson: Cli_lib.Graphql_types.Completed_works.t]
×
1019
                 (Array.to_list
×
1020
                    (Array.map
×
1021
                       ~f:(fun w ->
1022
                         { Cli_lib.Graphql_types.Completed_works.Work.work_ids =
×
1023
                             Array.to_list w.work_ids
×
1024
                         ; fee = w.fee
1025
                         ; prover = w.prover
1026
                         } )
1027
                       response.snarkPool ) )
1028
             in
1029
             print_string (Yojson.Safe.to_string lst) ) ) )
×
1030

1031
let pooled_user_commands =
1032
  let public_key_flag =
1033
    Command.Param.(
1034
      anon @@ maybe @@ ("public-key" %: Cli_lib.Arg_type.public_key_compressed))
3✔
1035
  in
1036
  Command.async
3✔
1037
    ~summary:"Retrieve all the user commands that are pending inclusion"
1038
    (Cli_lib.Background_daemon.graphql_init public_key_flag
3✔
1039
       ~f:(fun graphql_endpoint public_key ->
1040
         let module Q = Graphql_queries.Pooled_user_commands in
×
1041
         let graphql = Q.(make @@ makeVariables ?public_key ()) in
×
1042
         let%map response = Graphql_client.query_exn graphql graphql_endpoint in
×
1043
         let json_response = Q.serialize response |> Q.toJson in
×
1044
         print_string (Yojson.Basic.to_string json_response) ) )
×
1045

1046
let pooled_zkapp_commands =
1047
  let public_key_flag =
1048
    Command.Param.(
1049
      anon @@ maybe @@ ("public-key" %: Cli_lib.Arg_type.public_key_compressed))
3✔
1050
  in
1051
  Command.async
3✔
1052
    ~summary:"Retrieve all the zkApp commands that are pending inclusion"
1053
    (Cli_lib.Background_daemon.graphql_init public_key_flag
3✔
1054
       ~f:(fun graphql_endpoint maybe_public_key ->
1055
         let public_key =
×
1056
           Yojson.Safe.to_basic
1057
           @@ [%to_yojson: Public_key.Compressed.t option] maybe_public_key
×
1058
         in
1059
         let graphql =
×
1060
           Graphql_queries.Pooled_zkapp_commands.(
1061
             make @@ makeVariables ~public_key ())
×
1062
         in
1063
         let%bind raw_response =
1064
           Graphql_client.query_json_exn graphql graphql_endpoint
×
1065
         in
1066
         let%map json_response =
1067
           try
1068
             let kvs = Yojson.Safe.Util.to_assoc raw_response in
1069
             List.hd_exn kvs |> snd |> return
×
1070
           with _ ->
×
1071
             eprintf "Failed to read result of pooled zkApp commands" ;
1072
             exit 1
×
1073
         in
1074
         print_string (Yojson.Safe.to_string json_response) ) )
×
1075

1076
let to_signed_fee_exn sign magnitude =
1077
  let sgn = match sign with `PLUS -> Sgn.Pos | `MINUS -> Neg in
×
1078
  Currency.Fee.Signed.create ~sgn ~magnitude
1079

1080
let pending_snark_work =
1081
  let open Command.Param in
1082
  Command.async
3✔
1083
    ~summary:
1084
      "List of snark works in JSON format that are not available in the pool \
1085
       yet"
1086
    (Cli_lib.Background_daemon.graphql_init (return ())
3✔
1087
       ~f:(fun graphql_endpoint () ->
1088
         Deferred.map
×
1089
           (Graphql_client.query_exn
×
1090
              Graphql_queries.Pending_snark_work.(make @@ makeVariables ())
×
1091
              graphql_endpoint )
1092
           ~f:(fun response ->
1093
             let lst =
×
1094
               [%to_yojson: Cli_lib.Graphql_types.Pending_snark_work.t]
×
1095
                 (Array.map
×
1096
                    ~f:(fun bundle ->
1097
                      Array.map bundle.workBundle ~f:(fun w ->
×
1098
                          let fee_excess_left = w.fee_excess.feeExcessLeft in
×
1099
                          { Cli_lib.Graphql_types.Pending_snark_work.Work
1100
                            .work_id = w.work_id
1101
                          ; fee_excess =
1102
                              Currency.Amount.Signed.of_fee
×
1103
                                (to_signed_fee_exn fee_excess_left.sign
×
1104
                                   fee_excess_left.feeMagnitude )
1105
                          ; supply_increase = w.supply_increase
1106
                          ; source_first_pass_ledger_hash =
1107
                              w.source_first_pass_ledger_hash
1108
                          ; target_first_pass_ledger_hash =
1109
                              w.target_first_pass_ledger_hash
1110
                          ; source_second_pass_ledger_hash =
1111
                              w.source_second_pass_ledger_hash
1112
                          ; target_second_pass_ledger_hash =
1113
                              w.target_second_pass_ledger_hash
1114
                          } ) )
1115
                    response.pendingSnarkWork )
1116
             in
1117
             print_string (Yojson.Safe.to_string lst) ) ) )
×
1118

1119
let start_tracing =
1120
  Command.async
3✔
1121
    ~summary:"Start async tracing to $config-directory/trace/$pid.trace"
1122
    (Cli_lib.Background_daemon.rpc_init Cli_lib.Flag.config_files
3✔
1123
       ~f:(fun port config_files ->
1124
         let%bind compile_config = load_compile_config config_files in
×
1125
         match%map
1126
           Daemon_rpcs.Client.dispatch ~compile_config
×
1127
             Daemon_rpcs.Start_tracing.rpc () port
1128
         with
1129
         | Ok () ->
×
1130
             print_endline "Daemon started tracing!"
1131
         | Error e ->
×
1132
             Daemon_rpcs.Client.print_rpc_error e ) )
1133

1134
let stop_tracing =
1135
  Command.async ~summary:"Stop async tracing"
3✔
1136
    (Cli_lib.Background_daemon.rpc_init Cli_lib.Flag.config_files
3✔
1137
       ~f:(fun port config_files ->
1138
         let%bind compile_config = load_compile_config config_files in
×
1139
         match%map
1140
           Daemon_rpcs.Client.dispatch ~compile_config
×
1141
             Daemon_rpcs.Stop_tracing.rpc () port
1142
         with
1143
         | Ok () ->
×
1144
             print_endline "Daemon stopped printing!"
1145
         | Error e ->
×
1146
             Daemon_rpcs.Client.print_rpc_error e ) )
1147

1148
let start_internal_tracing =
1149
  Command.async
3✔
1150
    ~summary:
1151
      "Start internal tracing to \
1152
       $config-directory/internal-tracing/internal-trace.jsonl"
1153
    (Cli_lib.Background_daemon.rpc_init Cli_lib.Flag.config_files
3✔
1154
       ~f:(fun port config_files ->
1155
         let%bind compile_config = load_compile_config config_files in
×
1156
         match%map
1157
           Daemon_rpcs.Client.dispatch ~compile_config
×
1158
             Daemon_rpcs.Start_internal_tracing.rpc () port
1159
         with
1160
         | Ok () ->
×
1161
             print_endline "Daemon internal started tracing!"
1162
         | Error e ->
×
1163
             Daemon_rpcs.Client.print_rpc_error e ) )
1164

1165
let stop_internal_tracing =
1166
  Command.async ~summary:"Stop internal tracing"
3✔
1167
    (Cli_lib.Background_daemon.rpc_init Cli_lib.Flag.config_files
3✔
1168
       ~f:(fun port config_files ->
1169
         let%bind compile_config = load_compile_config config_files in
×
1170
         match%map
1171
           Daemon_rpcs.Client.dispatch ~compile_config
×
1172
             Daemon_rpcs.Stop_internal_tracing.rpc () port
1173
         with
1174
         | Ok () ->
×
1175
             print_endline "Daemon internal tracing stopped!"
1176
         | Error e ->
×
1177
             Daemon_rpcs.Client.print_rpc_error e ) )
1178

1179
let set_coinbase_receiver_graphql =
1180
  let open Command.Param in
1181
  let open Cli_lib.Arg_type in
1182
  let pk_flag =
1183
    choose_one ~if_nothing_chosen:Raise
1184
      [ flag "--public-key" ~aliases:[ "public-key" ]
3✔
1185
          ~doc:"PUBLICKEY Public key of account to send coinbase rewards to"
1186
          (optional public_key_compressed)
3✔
1187
        |> map ~f:(Option.map ~f:Option.some)
3✔
1188
      ; flag "--block-producer" ~aliases:[ "block-producer" ]
3✔
1189
          ~doc:"Send coinbase rewards to the block producer's public key" no_arg
1190
        |> map ~f:(function true -> Some None | false -> None)
×
1191
      ]
1192
  in
1193
  Command.async ~summary:"Set the coinbase receiver"
3✔
1194
    (Cli_lib.Background_daemon.graphql_init pk_flag
3✔
1195
       ~f:(fun graphql_endpoint public_key ->
1196
         let print_pk_opt () = function
×
1197
           | None ->
×
1198
               "block producer"
1199
           | Some pk ->
×
1200
               "public key " ^ Public_key.Compressed.to_base58_check pk
×
1201
         in
1202
         let%map result =
1203
           Graphql_client.query_exn
×
1204
             Graphql_queries.Set_coinbase_receiver.(
1205
               make @@ makeVariables ?public_key ())
×
1206
             graphql_endpoint
1207
         in
1208
         printf
×
1209
           "Was sending coinbases to the %a\nNow sending coinbases to the %a\n"
1210
           print_pk_opt result.setCoinbaseReceiver.lastCoinbaseReceiver
1211
           print_pk_opt result.setCoinbaseReceiver.currentCoinbaseReceiver ) )
1212

1213
let set_snark_worker =
1214
  let open Command.Param in
1215
  let public_key_flag =
1216
    flag "--address" ~aliases:[ "address" ]
1217
      ~doc:
1218
        (sprintf
3✔
1219
           "PUBLICKEY Public-key address you wish to start snark-working on; \
1220
            null to stop doing any snark work. %s"
1221
           Cli_lib.Default.receiver_key_warning )
1222
      (optional Cli_lib.Arg_type.public_key_compressed)
3✔
1223
  in
1224
  Command.async
3✔
1225
    ~summary:"Set key you wish to snark work with or disable snark working"
1226
    (Cli_lib.Background_daemon.graphql_init public_key_flag
3✔
1227
       ~f:(fun graphql_endpoint optional_public_key ->
1228
         let graphql =
×
1229
           Graphql_queries.Set_snark_worker.(
1230
             make @@ makeVariables ?public_key:optional_public_key ())
×
1231
         in
1232
         Deferred.map (Graphql_client.query_exn graphql graphql_endpoint)
×
1233
           ~f:(fun response ->
1234
             ( match optional_public_key with
×
1235
             | Some public_key ->
×
1236
                 printf
×
1237
                   !"New snark worker public key : %s\n"
1238
                   (Public_key.Compressed.to_base58_check public_key)
×
1239
             | None ->
×
1240
                 printf "Will stop doing snark work\n" ) ;
×
1241
             printf "Previous snark worker public key : %s\n"
1242
               (Option.value_map response.setSnarkWorker.lastSnarkWorker
×
1243
                  ~default:"None" ~f:Public_key.Compressed.to_base58_check ) ) )
1244
    )
1245

1246
let set_snark_work_fee =
1247
  Command.async ~summary:"Set fee reward for doing transaction snark work"
3✔
1248
  @@ Cli_lib.Background_daemon.graphql_init
3✔
1249
       Command.Param.(anon @@ ("fee" %: Cli_lib.Arg_type.txn_fee))
3✔
1250
       ~f:(fun graphql_endpoint fee ->
1251
         let graphql =
×
1252
           Graphql_queries.Set_snark_work_fee.(
1253
             make @@ makeVariables ~fee:(Currency.Fee.to_uint64 fee) ())
×
1254
         in
1255
         Deferred.map (Graphql_client.query_exn graphql graphql_endpoint)
×
1256
           ~f:(fun response ->
1257
             printf
×
1258
               !"Updated snark work fee: %i\nOld snark work fee: %i\n"
1259
               (Currency.Fee.to_nanomina_int fee)
×
1260
               (Currency.Fee.to_nanomina_int response.setSnarkWorkFee.lastFee) )
×
1261
         )
1262

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

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

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

1558
let create_account =
1559
  let open Command.Param in
1560
  Command.async ~summary:"Create new account"
3✔
1561
    (Cli_lib.Background_daemon.graphql_init (return ())
3✔
1562
       ~f:(fun graphql_endpoint () ->
1563
         let%bind password =
1564
           Secrets.Keypair.Terminal_stdin.prompt_password
×
1565
             "Password for new account: "
1566
         in
1567
         let%map response =
1568
           Graphql_client.query_exn
×
1569
             Graphql_queries.Create_account.(
1570
               make @@ makeVariables ~password:(Bytes.to_string password) ())
×
1571
             graphql_endpoint
1572
         in
1573
         let pk_string =
×
1574
           Public_key.Compressed.to_base58_check
1575
             response.createAccount.account.public_key
1576
         in
1577
         printf "\n😄 Added new account!\nPublic key: %s\n" pk_string ) )
×
1578

1579
let create_hd_account =
1580
  Command.async ~summary:Secrets.Hardware_wallets.create_hd_account_summary
3✔
1581
    (Cli_lib.Background_daemon.graphql_init Cli_lib.Flag.Signed_command.hd_index
3✔
1582
       ~f:(fun graphql_endpoint hd_index ->
1583
         let%map response =
1584
           Graphql_client.(
1585
             query_exn
×
1586
               Graphql_queries.Create_hd_account.(
1587
                 make @@ makeVariables ~hd_index ()))
×
1588
             graphql_endpoint
1589
         in
1590
         let pk_string =
×
1591
           Public_key.Compressed.to_base58_check
1592
             response.createHDAccount.account.public_key
1593
         in
1594
         printf "\n😄 created HD account with HD-index %s!\nPublic key: %s\n"
×
1595
           (Mina_numbers.Hd_index.to_string hd_index)
×
1596
           pk_string ) )
1597

1598
let unlock_account =
1599
  let open Command.Param in
1600
  let pk_flag =
1601
    flag "--public-key" ~aliases:[ "public-key" ]
1602
      ~doc:"PUBLICKEY Public key to be unlocked"
1603
      (required Cli_lib.Arg_type.public_key_compressed)
3✔
1604
  in
1605
  Command.async ~summary:"Unlock a tracked account"
3✔
1606
    (Cli_lib.Background_daemon.graphql_init pk_flag
3✔
1607
       ~f:(fun graphql_endpoint pk_str ->
1608
         let password =
×
1609
           Deferred.map ~f:Or_error.return
1610
             (Secrets.Password.hidden_line_or_env "Password to unlock account: "
×
1611
                ~env:Secrets.Keypair.env )
1612
         in
1613
         match%bind password with
1614
         | Ok password_bytes ->
×
1615
             let%map response =
1616
               Graphql_client.query_exn
×
1617
                 Graphql_queries.Unlock_account.(
1618
                   make
×
1619
                   @@ makeVariables ~public_key:pk_str
×
1620
                        ~password:(Bytes.to_string password_bytes)
×
1621
                        ())
1622
                 graphql_endpoint
1623
             in
1624
             let pk_string =
×
1625
               Public_key.Compressed.to_base58_check
1626
                 response.unlockAccount.account.public_key
1627
             in
1628
             printf "\n🔓 Unlocked account!\nPublic key: %s\n" pk_string
×
1629
         | Error e ->
×
1630
             Deferred.return
1631
               (printf "❌ Error unlocking account: %s\n" (Error.to_string_hum e)) )
×
1632
    )
1633

1634
let lock_account =
1635
  let open Command.Param in
1636
  let pk_flag =
1637
    flag "--public-key" ~aliases:[ "public-key" ]
1638
      ~doc:"PUBLICKEY Public key of account to be locked"
1639
      (required Cli_lib.Arg_type.public_key_compressed)
3✔
1640
  in
1641
  Command.async ~summary:"Lock a tracked account"
3✔
1642
    (Cli_lib.Background_daemon.graphql_init pk_flag
3✔
1643
       ~f:(fun graphql_endpoint pk ->
1644
         let%map response =
1645
           Graphql_client.query_exn
×
1646
             Graphql_queries.Lock_account.(
1647
               make @@ makeVariables ~public_key:pk ())
×
1648
             graphql_endpoint
1649
         in
1650
         let pk_string =
×
1651
           Public_key.Compressed.to_base58_check response.lockAccount.public_key
1652
         in
1653
         printf "🔒 Locked account!\nPublic key: %s\n" pk_string ) )
×
1654

1655
let generate_libp2p_keypair_do privkey_path =
1656
  Cli_lib.Exceptions.handle_nicely
×
1657
  @@ fun () ->
1658
  Deferred.ignore_m
×
1659
    (let open Deferred.Let_syntax in
1660
    (* FIXME: I'd like to accumulate messages into this logger and only dump them out in failure paths. *)
1661
    let logger = Logger.null () in
1662
    (* Using the helper only for keypair generation requires no state. *)
1663
    File_system.with_temp_dir "mina-generate-libp2p-keypair" ~f:(fun tmpd ->
×
1664
        match%bind
1665
          Mina_net2.create ~logger ~conf_dir:tmpd ~all_peers_seen_metric:false
×
1666
            ~pids:(Child_processes.Termination.create_pid_table ())
×
1667
            ~on_peer_connected:ignore ~on_peer_disconnected:ignore ()
1668
        with
1669
        | Ok net ->
×
1670
            let%bind me = Mina_net2.generate_random_keypair net in
×
1671
            let%bind () = Mina_net2.shutdown net in
×
1672
            let%map () =
1673
              Secrets.Libp2p_keypair.Terminal_stdin.write_exn ~privkey_path me
×
1674
            in
1675
            printf "libp2p keypair:\n%s\n" (Mina_net2.Keypair.to_string me)
×
1676
        | Error e ->
×
1677
            [%log fatal] "failed to generate libp2p keypair: $error"
×
1678
              ~metadata:[ ("error", Error_json.error_to_yojson e) ] ;
×
1679
            exit 20 ))
×
1680

1681
let generate_libp2p_keypair =
1682
  Command.async
3✔
1683
    ~summary:"Generate a new libp2p keypair and print out the peer ID"
1684
    (let open Command.Let_syntax in
1685
    let%map_open privkey_path = Cli_lib.Flag.privkey_write_path in
1686
    generate_libp2p_keypair_do privkey_path)
×
1687

1688
let dump_libp2p_keypair_do privkey_path =
1689
  Cli_lib.Exceptions.handle_nicely
×
1690
  @@ fun () ->
1691
  Deferred.ignore_m
×
1692
    (let open Deferred.Let_syntax in
1693
    let logger = Logger.null () in
1694
    (* Using the helper only for keypair generation requires no state. *)
1695
    File_system.with_temp_dir "mina-dump-libp2p-keypair" ~f:(fun tmpd ->
×
1696
        match%bind
1697
          Mina_net2.create ~logger ~conf_dir:tmpd ~all_peers_seen_metric:false
×
1698
            ~pids:(Child_processes.Termination.create_pid_table ())
×
1699
            ~on_peer_connected:ignore ~on_peer_disconnected:ignore ()
1700
        with
1701
        | Ok net ->
×
1702
            let%bind () = Mina_net2.shutdown net in
×
1703
            let%map me = Secrets.Libp2p_keypair.read_exn' privkey_path in
×
1704
            printf "libp2p keypair:\n%s\n" (Mina_net2.Keypair.to_string me)
×
1705
        | Error e ->
×
1706
            [%log fatal] "failed to dump libp2p keypair: $error"
×
1707
              ~metadata:[ ("error", Error_json.error_to_yojson e) ] ;
×
1708
            exit 20 ))
×
1709

1710
let dump_libp2p_keypair =
1711
  Command.async ~summary:"Print an existing libp2p keypair"
3✔
1712
    (let open Command.Let_syntax in
1713
    let%map_open privkey_path = Cli_lib.Flag.privkey_read_path in
1714
    dump_libp2p_keypair_do privkey_path)
×
1715

1716
let trustlist_ip_flag =
1717
  Command.Param.(
1718
    flag "--ip-address" ~aliases:[ "ip-address" ]
3✔
1719
      ~doc:"CIDR An IPv4 CIDR mask for the client trustlist (eg, 10.0.0.0/8)"
1720
      (required Cli_lib.Arg_type.cidr_mask))
3✔
1721

1722
let trustlist_add =
1723
  let open Deferred.Let_syntax in
1724
  let open Daemon_rpcs in
1725
  Command.async ~summary:"Add an IP to the trustlist"
3✔
1726
    (Cli_lib.Background_daemon.rpc_init
3✔
1727
       (Args.zip2 Cli_lib.Flag.config_files trustlist_ip_flag)
3✔
1728
       ~f:(fun port (config_files, trustlist_ip) ->
1729
         let trustlist_ip_string = Unix.Cidr.to_string trustlist_ip in
×
1730
         let%bind compile_config = load_compile_config config_files in
×
1731
         match%map
1732
           Client.dispatch ~compile_config Add_trustlist.rpc trustlist_ip port
×
1733
         with
1734
         | Ok (Ok ()) ->
×
1735
             printf "Added %s to client trustlist" trustlist_ip_string
1736
         | Ok (Error e) ->
×
1737
             eprintf "Error adding %s to client trustlist: %s"
1738
               trustlist_ip_string (Error.to_string_hum e)
×
1739
         | Error e ->
×
1740
             eprintf "Unknown error doing daemon RPC: %s"
1741
               (Error.to_string_hum e) ) )
×
1742

1743
let trustlist_remove =
1744
  let open Deferred.Let_syntax in
1745
  let open Daemon_rpcs in
1746
  Command.async ~summary:"Remove a CIDR mask from the trustlist"
3✔
1747
    (Cli_lib.Background_daemon.rpc_init
3✔
1748
       (Args.zip2 Cli_lib.Flag.config_files trustlist_ip_flag)
3✔
1749
       ~f:(fun port (config_files, trustlist_ip) ->
1750
         let trustlist_ip_string = Unix.Cidr.to_string trustlist_ip in
×
1751
         let%bind compile_config = load_compile_config config_files in
×
1752
         match%map
1753
           Client.dispatch ~compile_config Remove_trustlist.rpc trustlist_ip
×
1754
             port
1755
         with
1756
         | Ok (Ok ()) ->
×
1757
             printf "Removed %s to client trustlist" trustlist_ip_string
1758
         | Ok (Error e) ->
×
1759
             eprintf "Error removing %s from client trustlist: %s"
1760
               trustlist_ip_string (Error.to_string_hum e)
×
1761
         | Error e ->
×
1762
             eprintf "Unknown error doing daemon RPC: %s"
1763
               (Error.to_string_hum e) ) )
×
1764

1765
let trustlist_list =
1766
  let open Daemon_rpcs in
1767
  Command.async ~summary:"List the CIDR masks in the trustlist"
3✔
1768
    (Cli_lib.Background_daemon.rpc_init Cli_lib.Flag.config_files
3✔
1769
       ~f:(fun port config_files ->
1770
         let%bind compile_config = load_compile_config config_files in
×
1771
         match%map
1772
           Client.dispatch ~compile_config Get_trustlist.rpc () port
×
1773
         with
1774
         | Ok ips ->
×
1775
             printf
1776
               "The following IPs are permitted to connect to the daemon \
1777
                control port:\n" ;
1778
             List.iter ips ~f:(fun ip -> printf "%s\n" (Unix.Cidr.to_string ip))
×
1779
         | Error e ->
×
1780
             eprintf "Unknown error doing daemon RPC: %s"
1781
               (Error.to_string_hum e) ) )
×
1782

1783
let get_peers_graphql =
1784
  Command.async ~summary:"List the peers currently connected to the daemon"
3✔
1785
    (Cli_lib.Background_daemon.graphql_init
3✔
1786
       Command.Param.(return ())
3✔
1787
       ~f:(fun graphql_endpoint () ->
1788
         let%map response =
1789
           Graphql_client.query_exn
×
1790
             Graphql_queries.Get_peers.(make @@ makeVariables ())
×
1791
             graphql_endpoint
1792
         in
1793
         Array.iter response.getPeers ~f:(fun peer ->
×
1794
             printf "%s\n"
×
1795
               (Network_peer.Peer.to_multiaddr_string
×
1796
                  { host = Unix.Inet_addr.of_string peer.host
×
1797
                  ; libp2p_port = peer.libp2pPort
1798
                  ; peer_id = peer.peerId
1799
                  } ) ) ) )
1800

1801
let add_peers_graphql =
1802
  let open Command in
1803
  let seed =
1804
    Param.(
1805
      flag "--seed" ~aliases:[ "-seed" ]
3✔
1806
        ~doc:
1807
          "true/false Whether to add these peers as 'seed' peers, which may \
1808
           perform peer exchange. Default: true"
1809
        (optional bool))
3✔
1810
  in
1811
  let peers =
1812
    Param.(anon Anons.(non_empty_sequence_as_list ("peer" %: string)))
3✔
1813
  in
1814
  Command.async
3✔
1815
    ~summary:
1816
      "Add peers to the daemon\n\n\
1817
       Addresses take the format /ip4/IPADDR/tcp/PORT/p2p/PEERID"
1818
    (Cli_lib.Background_daemon.graphql_init (Param.both peers seed)
3✔
1819
       ~f:(fun graphql_endpoint (input_peers, seed) ->
1820
         let open Deferred.Let_syntax in
×
1821
         let peers =
1822
           List.map input_peers ~f:(fun peer ->
1823
               match
×
1824
                 Mina_net2.Multiaddr.of_string peer
×
1825
                 |> Mina_net2.Multiaddr.to_peer
1826
               with
1827
               | Some peer ->
×
1828
                   peer
1829
               | None ->
×
1830
                   eprintf
1831
                     "Could not parse %s as a peer address. It should use the \
1832
                      format /ip4/IPADDR/tcp/PORT/p2p/PEERID"
1833
                     peer ;
1834
                   Core.exit 1 )
×
1835
         in
1836
         let seed = Option.value ~default:true seed in
×
1837
         let%map response =
1838
           Graphql_client.query_exn
×
1839
             Graphql_queries.Add_peers.(make @@ makeVariables ~peers ~seed ())
×
1840
             graphql_endpoint
1841
         in
1842
         printf "Requested to add peers:\n" ;
×
1843
         Array.iter response.addPeers ~f:(fun peer ->
×
1844
             printf "%s\n"
×
1845
               (Network_peer.Peer.to_multiaddr_string
×
1846
                  { host = Unix.Inet_addr.of_string peer.host
×
1847
                  ; libp2p_port = peer.libp2pPort
1848
                  ; peer_id = peer.peerId
1849
                  } ) ) ) )
1850

1851
let compile_time_constants =
1852
  let open Command.Let_syntax in
1853
  Command.async
3✔
1854
    ~summary:"Print a JSON map of the compile-time consensus parameters"
1855
    (let%map_open config_files = Cli_lib.Flag.config_files in
1856
     fun () ->
1857
       let home = Core.Sys.home_directory () in
×
1858
       let conf_dir = home ^/ Cli_lib.Default.conf_dir_name in
×
1859
       let genesis_dir =
×
1860
         let home = Core.Sys.home_directory () in
1861
         home ^/ Cli_lib.Default.conf_dir_name
×
1862
       in
1863
       let open Deferred.Let_syntax in
1864
       let%map ({ consensus_constants; _ } as precomputed_values), _ =
1865
         (* This is kind of ugly because we are allowing for supplying a runtime_config value directly, rather than force what is read from the environment *)
1866
         (* TODO: See if we can initialize consensus_constants without also initializing the ledger *)
1867
         let logger = Logger.null () in
1868
         let%bind m_conf =
1869
           Runtime_config.Json_loader.load_config_files ~conf_dir ~logger
×
1870
             config_files
1871
           >>| Or_error.ok
×
1872
         in
1873
         let default =
×
1874
           Runtime_config.of_json_layout
×
1875
             { Runtime_config.Json_layout.default with
1876
               ledger =
1877
                 Some
1878
                   { Runtime_config.Json_layout.Ledger.default with
1879
                     accounts = Some []
1880
                   }
1881
             }
1882
           |> Result.ok_or_failwith
1883
         in
1884
         let runtime_config = Option.value ~default m_conf in
×
1885
         let constants =
×
1886
           Runtime_config.Constants.load_constants' runtime_config
1887
         in
1888
         Genesis_ledger_helper.Config_loader.init_from_config_file ~genesis_dir
×
1889
           ~logger ~constants runtime_config
1890
         |> Deferred.Or_error.ok_exn
1891
       in
1892
       let all_constants =
×
1893
         `Assoc
1894
           [ ( "genesis_state_timestamp"
1895
             , `String
1896
                 ( Block_time.to_time_exn
×
1897
                     consensus_constants.genesis_state_timestamp
1898
                 |> Core.Time.to_string_iso8601_basic ~zone:Core.Time.Zone.utc
×
1899
                 ) )
1900
           ; ("k", `Int (Unsigned.UInt32.to_int consensus_constants.k))
×
1901
           ; ( "coinbase"
1902
             , `String
1903
                 (Currency.Amount.to_mina_string
×
1904
                    precomputed_values.constraint_constants.coinbase_amount ) )
1905
           ; ( "block_window_duration_ms"
1906
             , `Int
1907
                 precomputed_values.constraint_constants
1908
                   .block_window_duration_ms )
1909
           ; ("delta", `Int (Unsigned.UInt32.to_int consensus_constants.delta))
×
1910
           ; ( "sub_windows_per_window"
1911
             , `Int
1912
                 (Unsigned.UInt32.to_int
×
1913
                    consensus_constants.sub_windows_per_window ) )
1914
           ; ( "slots_per_sub_window"
1915
             , `Int
1916
                 (Unsigned.UInt32.to_int
×
1917
                    consensus_constants.slots_per_sub_window ) )
1918
           ; ( "slots_per_window"
1919
             , `Int
1920
                 (Unsigned.UInt32.to_int consensus_constants.slots_per_window)
×
1921
             )
1922
           ; ( "slots_per_epoch"
1923
             , `Int (Unsigned.UInt32.to_int consensus_constants.slots_per_epoch)
×
1924
             )
1925
           ]
1926
       in
1927
       Core_kernel.printf "%s\n%!" (Yojson.Safe.to_string all_constants) )
×
1928

1929
let node_status =
1930
  let open Command.Param in
1931
  let open Deferred.Let_syntax in
1932
  let daemon_peers_flag =
1933
    flag "--daemon-peers" ~aliases:[ "daemon-peers" ] no_arg
1934
      ~doc:"Get node statuses for peers known to the daemon"
1935
  in
1936
  let peers_flag =
3✔
1937
    flag "--peers" ~aliases:[ "peers" ]
1938
      (optional (Arg_type.comma_separated string))
3✔
1939
      ~doc:"CSV-LIST Peer multiaddrs for obtaining node status"
1940
  in
1941
  let show_errors_flag =
3✔
1942
    flag "--show-errors" ~aliases:[ "show-errors" ] no_arg
1943
      ~doc:"Include error responses in output"
1944
  in
1945
  let flags =
3✔
1946
    Args.zip4 Cli_lib.Flag.config_files daemon_peers_flag peers_flag
1947
      show_errors_flag
1948
  in
1949
  Command.async ~summary:"Get node statuses for a set of peers"
3✔
1950
    (Cli_lib.Background_daemon.rpc_init flags
3✔
1951
       ~f:(fun port (config_files, daemon_peers, peers, show_errors) ->
1952
         if
×
1953
           (Option.is_none peers && not daemon_peers)
×
1954
           || (Option.is_some peers && daemon_peers)
×
1955
         then (
×
1956
           eprintf
1957
             "Must provide exactly one of daemon-peers or peer-ids flags\n%!" ;
1958
           don't_wait_for (exit 33) ) ;
×
1959
         let peer_ids_opt =
×
1960
           Option.map peers ~f:(fun peers ->
1961
               List.map peers ~f:Mina_net2.Multiaddr.of_string )
×
1962
         in
1963
         let%bind compile_config = load_compile_config config_files in
×
1964
         match%map
1965
           Daemon_rpcs.Client.dispatch ~compile_config
×
1966
             Daemon_rpcs.Get_node_status.rpc peer_ids_opt port
1967
         with
1968
         | Ok all_status_data ->
×
1969
             let all_status_data =
1970
               if show_errors then all_status_data
×
1971
               else
1972
                 List.filter all_status_data ~f:(fun td ->
×
1973
                     match td with Ok _ -> true | Error _ -> false )
×
1974
             in
1975
             List.iter all_status_data ~f:(fun peer_status_data ->
1976
                 printf "%s\n%!"
×
1977
                   ( Yojson.Safe.to_string
×
1978
                   @@ Mina_networking.Node_status.response_to_yojson
×
1979
                        peer_status_data ) )
1980
         | Error err ->
×
1981
             printf "Failed to get node status: %s\n%!"
1982
               (Error.to_string_hum err) ) )
×
1983

1984
let object_lifetime_statistics =
1985
  let open Daemon_rpcs in
1986
  Command.async ~summary:"Dump internal object lifetime statistics to JSON"
3✔
1987
    (Cli_lib.Background_daemon.rpc_init Cli_lib.Flag.config_files
3✔
1988
       ~f:(fun port config_files ->
1989
         let%bind compile_config = load_compile_config config_files in
×
1990
         match%map
1991
           Client.dispatch ~compile_config Get_object_lifetime_statistics.rpc ()
×
1992
             port
1993
         with
1994
         | Ok stats ->
×
1995
             print_endline stats
1996
         | Error err ->
×
1997
             printf "Failed to get object lifetime statistics: %s\n%!"
1998
               (Error.to_string_hum err) ) )
×
1999

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

2171
let receipt_chain_hash =
2172
  let open Command.Let_syntax in
2173
  Command.basic
3✔
2174
    ~summary:
2175
      "Compute the next receipt chain hash from the previous hash and \
2176
       transaction ID"
2177
    (let%map_open previous_hash =
2178
       flag "--previous-hash"
3✔
2179
         ~doc:"HASH Previous receipt chain hash, Base58Check-encoded"
2180
         (required string)
3✔
2181
     and transaction_id =
2182
       flag "--transaction-id"
3✔
2183
         ~doc:"TRANSACTION_ID Transaction ID, Base64-encoded" (required string)
3✔
2184
     and index =
2185
       flag "--index"
3✔
2186
         ~doc:
2187
           "NN For a zkApp, 0 for fee payer or 1-based index of account update"
2188
         (optional string)
3✔
2189
     in
2190
     fun () ->
2191
       let previous_hash =
×
2192
         Receipt.Chain_hash.of_base58_check_exn previous_hash
2193
       in
2194
       let hash =
×
2195
         match index with
2196
         | None ->
×
2197
             let signed_cmd =
2198
               Signed_command.of_base64 transaction_id |> Or_error.ok_exn
×
2199
             in
2200
             Receipt.Chain_hash.cons_signed_command_payload
×
2201
               (Signed_command_payload signed_cmd.payload) previous_hash
2202
         | Some n ->
×
2203
             let zkapp_cmd =
2204
               Zkapp_command.of_base64 transaction_id |> Or_error.ok_exn
×
2205
             in
2206
             let receipt_elt =
×
2207
               let _txn_commitment, full_txn_commitment =
2208
                 Zkapp_command.get_transaction_commitments zkapp_cmd
2209
               in
2210
               Receipt.Zkapp_command_elt.Zkapp_command_commitment
×
2211
                 full_txn_commitment
2212
             in
2213
             let account_update_index = Mina_numbers.Index.of_string n in
2214
             Receipt.Chain_hash.cons_zkapp_command_commitment
×
2215
               account_update_index receipt_elt previous_hash
2216
       in
2217
       printf "%s\n" (Receipt.Chain_hash.to_base58_check hash) )
×
2218

2219
let chain_id_inputs =
2220
  let open Deferred.Let_syntax in
2221
  Command.async ~summary:"Print the inputs that yield the current chain id"
3✔
2222
    (Cli_lib.Background_daemon.rpc_init Cli_lib.Flag.config_files
3✔
2223
       ~f:(fun port config_files ->
2224
         let open Daemon_rpcs in
×
2225
         let%bind compile_config = load_compile_config config_files in
×
2226
         match%map
2227
           Client.dispatch ~compile_config Chain_id_inputs.rpc () port
×
2228
         with
2229
         | Ok
×
2230
             ( genesis_state_hash
2231
             , genesis_constants
2232
             , snark_keys
2233
             , protocol_transaction_version
2234
             , protocol_network_version ) ->
2235
             let open Format in
2236
             printf
2237
               "@[<v>Genesis state hash: %s@,\
2238
                @[<v 2>Genesis_constants:@,\
2239
                Protocol:          %a@,\
2240
                Txn pool max size: %d@,\
2241
                Num accounts:      %a@,\
2242
                @]@,\
2243
                @[<v 2>Snark keys:@,\
2244
                %a@]@,\
2245
                Protocol transaction version: %u@,\
2246
                Protocol network version: %u@]@."
2247
               (State_hash.to_base58_check genesis_state_hash)
×
2248
               Yojson.Safe.pp
2249
               (Genesis_constants.Protocol.to_yojson genesis_constants.protocol)
×
2250
               genesis_constants.txpool_max_size
2251
               (pp_print_option
×
2252
                  ~none:(fun ppf () -> pp_print_string ppf "None")
×
2253
                  pp_print_int )
2254
               genesis_constants.num_accounts
2255
               (pp_print_list ~pp_sep:pp_print_cut pp_print_string)
×
2256
               snark_keys protocol_transaction_version protocol_network_version
2257
         | Error err ->
×
2258
             Format.eprintf "Could not get chain id inputs: %s@."
2259
               (Error.to_string_hum err) ) )
×
2260

2261
let hash_transaction =
2262
  let open Command.Let_syntax in
2263
  Command.basic
3✔
2264
    ~summary:"Compute the hash of a transaction from its transaction ID"
2265
    (let%map_open transaction_id =
2266
       flag "--transaction-id" ~doc:"ID ID of the transaction to hash"
3✔
2267
         (required string)
3✔
2268
     in
2269
     fun () ->
2270
       match Transaction_hash.hash_of_transaction_id transaction_id with
×
2271
       | Ok hash ->
×
2272
           printf "%s\n" (Transaction_hash.to_base58_check hash)
×
2273
       | Error err ->
×
2274
           Format.eprintf "Could not hash transaction: %s@."
2275
             (Error.to_string_hum err) )
×
2276

2277
let humanize_graphql_error
2278
    ~(graphql_endpoint : Uri.t Cli_lib.Flag.Types.with_name) = function
2279
  | `Failed_request e ->
×
2280
      Error.create "Unable to connect to Mina daemon" () (fun () ->
2281
          Sexp.List
×
2282
            [ List [ Atom "uri"; Atom (Uri.to_string graphql_endpoint.value) ]
×
2283
            ; List [ Atom "uri_flag"; Atom graphql_endpoint.name ]
2284
            ; List [ Atom "error_message"; Atom e ]
2285
            ] )
2286
  | `Graphql_error e ->
×
2287
      Error.createf "GraphQL error: %s" e
2288

2289
let runtime_config =
2290
  Command.async
3✔
2291
    ~summary:"Compute the runtime configuration used by a running daemon"
2292
    (Cli_lib.Background_daemon.graphql_init (Command.Param.return ())
3✔
2293
       ~f:(fun graphql_endpoint () ->
2294
         match%bind
2295
           Graphql_client.query
×
2296
             Graphql_queries.Runtime_config.(make @@ makeVariables ())
×
2297
             graphql_endpoint
2298
         with
2299
         | Ok runtime_config ->
×
2300
             Format.printf "%s@."
2301
               (Yojson.Basic.pretty_to_string runtime_config.runtimeConfig) ;
×
2302
             return ()
×
2303
         | Error err ->
×
2304
             Format.eprintf
2305
               "@[<v>Failed to retrieve runtime configuration. Error:@,%s@]@."
2306
               (Error.to_string_hum
×
2307
                  (humanize_graphql_error ~graphql_endpoint err) ) ;
×
2308
             exit 1 ) )
×
2309

2310
let thread_graph =
2311
  Command.async
3✔
2312
    ~summary:
2313
      "Return a Graphviz Dot graph representation of the internal thread \
2314
       hierarchy"
2315
    (Cli_lib.Background_daemon.graphql_init (Command.Param.return ())
3✔
2316
       ~f:(fun graphql_endpoint () ->
2317
         match%bind
2318
           Graphql_client.query
×
2319
             Graphql_queries.Thread_graph.(make @@ makeVariables ())
×
2320
             graphql_endpoint
2321
         with
2322
         | Ok graph ->
×
2323
             print_endline graph.threadGraph ;
2324
             return ()
×
2325
         | Error e ->
×
2326
             Format.eprintf
2327
               "@[<v>Failed to retrieve runtime configuration. Error:@,%s@]@."
2328
               (Error.to_string_hum
×
2329
                  (humanize_graphql_error ~graphql_endpoint e) ) ;
×
2330
             exit 1 ) )
×
2331

2332
let signature_kind =
2333
  Command.basic
3✔
2334
    ~summary:"Print the signature kind that this binary is compiled with"
2335
    (let%map.Command () = Command.Param.return () in
3✔
2336
     fun () ->
2337
       let signature_kind_string =
×
2338
         match Mina_signature_kind.t with
2339
         | Mainnet ->
×
2340
             "mainnet"
2341
         | Testnet ->
×
2342
             "testnet"
2343
         | Other_network s ->
×
2344
             (* Prefix string to disambiguate *)
2345
             "other network: " ^ s
2346
       in
2347
       Core.print_endline signature_kind_string )
2348

2349
let test_ledger_application =
2350
  Command.async ~summary:"Test ledger application"
3✔
2351
    (let%map_open.Command privkey_path = Cli_lib.Flag.privkey_read_path
2352
     and prev_block_path =
2353
       flag "--prev-block-path" ~doc:"FILE file with serialized block"
3✔
2354
         (optional string)
3✔
2355
     and ledger_path =
2356
       flag "--ledger-path" ~doc:"FILE directory with ledger DB"
3✔
2357
         (required string)
3✔
2358
     and num_txs =
2359
       flag "--num-txs"
3✔
2360
         ~doc:"NN Number of transactions to create after preparatory rounds"
2361
         (required int)
3✔
2362
     and num_txs_per_round =
2363
       flag "--num-txs-per-round"
3✔
2364
         ~doc:
2365
           "NN Number of transactions to create per preparatory round \
2366
            (default: 3)"
2367
         (optional int)
3✔
2368
     and rounds =
2369
       flag "--rounds" ~doc:"NN Number of preparatory rounds (default: 580)"
3✔
2370
         (optional int)
3✔
2371
     and first_partition_slots =
2372
       flag "--first-partition-slots"
3✔
2373
         ~doc:
2374
           "NN Number of slots in first partition of scan state (default: 128)"
2375
         (optional int)
3✔
2376
     and max_depth =
2377
       flag "--max-depth" ~doc:"NN Maximum depth of masks (default: 290)"
3✔
2378
         (optional int)
3✔
2379
     and no_new_stack =
2380
       flag "--old-stack" ~doc:"Use is_new_stack: false (scan state)" no_arg
3✔
2381
     and has_second_partition =
2382
       flag "--has-second-partition"
3✔
2383
         ~doc:"Assume there is a second partition (scan state)" no_arg
2384
     and tracing = flag "--tracing" ~doc:"Wrap test into tracing" no_arg
3✔
2385
     and config_files = Cli_lib.Flag.config_files
2386
     and no_masks = flag "--no-masks" ~doc:"Do not create masks" no_arg in
3✔
2387
     Cli_lib.Exceptions.handle_nicely
1✔
2388
     @@ fun () ->
2389
     let open Deferred.Let_syntax in
1✔
2390
     let%bind genesis_constants, constraint_constants =
2391
       let%map conf =
2392
         Runtime_config.Constants.load_constants_with_logging
1✔
2393
           ~logger:(Logger.create ()) config_files
1✔
2394
       in
2395
       Runtime_config.Constants.
1✔
2396
         (genesis_constants conf, constraint_constants conf)
1✔
2397
     in
2398
     let first_partition_slots =
1✔
2399
       Option.value ~default:128 first_partition_slots
2400
     in
2401
     let num_txs_per_round = Option.value ~default:3 num_txs_per_round in
1✔
2402
     let rounds = Option.value ~default:580 rounds in
1✔
2403
     let max_depth = Option.value ~default:290 max_depth in
1✔
2404
     Test_ledger_application.test ~privkey_path ~ledger_path ?prev_block_path
1✔
2405
       ~first_partition_slots ~no_new_stack ~has_second_partition
2406
       ~num_txs_per_round ~rounds ~no_masks ~max_depth ~tracing num_txs
2407
       ~constraint_constants ~genesis_constants )
2408

2409
let itn_create_accounts =
2410
  Command.async ~summary:"Fund new accounts for incentivized testnet"
3✔
2411
    (let open Command.Param in
2412
    let privkey_path = Cli_lib.Flag.privkey_read_path in
2413
    let key_prefix =
2414
      flag "--key-prefix" ~doc:"STRING prefix of keyfiles" (required string)
3✔
2415
    in
2416
    let num_accounts =
3✔
2417
      flag "--num-accounts" ~doc:"NN Number of new accounts" (required int)
3✔
2418
    in
2419
    let fee =
3✔
2420
      flag "--fee" ~doc:"NN Fee in nanomina paid to create an account"
2421
        (required int)
3✔
2422
    in
2423
    let amount =
3✔
2424
      flag "--amount"
2425
        ~doc:"NN Amount in nanomina to be divided among new accounts"
2426
        (required int)
3✔
2427
    in
2428
    let config_files = Cli_lib.Flag.config_files in
3✔
2429
    let args =
2430
      Args.zip6 privkey_path key_prefix num_accounts fee amount config_files
2431
    in
2432
    Cli_lib.Background_daemon.rpc_init args
3✔
2433
      ~f:(fun
2434
           port
2435
           (privkey_path, key_prefix, num_accounts, fee, amount, config_files)
2436
         ->
2437
        let open Deferred.Let_syntax in
×
2438
        let%bind genesis_constants, constraint_constants, compile_config =
2439
          let%map conf = Runtime_config.Constants.load_constants config_files in
×
2440
          Runtime_config.Constants.
×
2441
            ( genesis_constants conf
×
2442
            , constraint_constants conf
×
2443
            , compile_config conf )
×
2444
        in
2445
        let args' = (privkey_path, key_prefix, num_accounts, fee, amount) in
×
2446
        let genesis_constants = genesis_constants in
2447
        let constraint_constants = constraint_constants in
2448
        Itn.create_accounts ~genesis_constants ~constraint_constants
2449
          ~compile_config port args' ))
2450

2451
module Visualization = struct
2452
  let create_command (type rpc_response) ~name ~f
2453
      (rpc : (string, rpc_response) Rpc.Rpc.t) =
2454
    let open Deferred.Let_syntax in
6✔
2455
    Command.async
2456
      ~summary:(sprintf !"Produce a visualization of the %s" name)
6✔
2457
      (Cli_lib.Background_daemon.rpc_init
6✔
2458
         (Args.zip2 Cli_lib.Flag.config_files
6✔
2459
            Command.Param.(anon @@ ("output-filepath" %: string)) )
6✔
2460
         ~f:(fun port (config_files, filename) ->
2461
           let%bind compile_config = load_compile_config config_files in
×
2462
           let%map message =
2463
             match%map
2464
               Daemon_rpcs.Client.dispatch ~compile_config rpc filename port
×
2465
             with
2466
             | Ok response ->
×
2467
                 f filename response
2468
             | Error e ->
×
2469
                 sprintf "Could not save file: %s\n" (Error.to_string_hum e)
×
2470
           in
2471
           print_string message ) )
×
2472

2473
  module Frontier = struct
2474
    let name = "transition-frontier"
2475

2476
    let command =
2477
      create_command ~name Daemon_rpcs.Visualization.Frontier.rpc
3✔
2478
        ~f:(fun filename -> function
2479
        | `Active () ->
×
2480
            Visualization_message.success name filename
2481
        | `Bootstrapping ->
×
2482
            Visualization_message.bootstrap name )
2483
  end
2484

2485
  module Registered_masks = struct
2486
    let name = "registered-masks"
2487

2488
    let command =
2489
      create_command ~name Daemon_rpcs.Visualization.Registered_masks.rpc
3✔
2490
        ~f:(fun filename () -> Visualization_message.success name filename)
×
2491
  end
2492

2493
  let command_group =
2494
    Command.group ~summary:"Visualize data structures special to Mina"
3✔
2495
      [ (Frontier.name, Frontier.command)
2496
      ; (Registered_masks.name, Registered_masks.command)
2497
      ]
2498
end
2499

2500
let accounts =
2501
  Command.group ~summary:"Client commands concerning account management"
3✔
2502
    ~preserve_subcommand_order:()
2503
    [ ("list", list_accounts)
2504
    ; ("create", create_account)
2505
    ; ("import", import_key)
2506
    ; ("export", export_key)
2507
    ; ("unlock", unlock_account)
2508
    ; ("lock", lock_account)
2509
    ]
2510

2511
let client =
2512
  Command.group ~summary:"Lightweight client commands"
3✔
2513
    ~preserve_subcommand_order:()
2514
    [ ("get-balance", get_balance_graphql)
2515
    ; ("get-tokens", get_tokens_graphql)
2516
    ; ("send-payment", send_payment_graphql)
2517
    ; ("delegate-stake", delegate_stake_graphql)
2518
    ; ("cancel-transaction", cancel_transaction_graphql)
2519
    ; ("set-snark-worker", set_snark_worker)
2520
    ; ("set-snark-work-fee", set_snark_work_fee)
2521
    ; ("export-logs", Export_logs.export_via_graphql)
2522
    ; ("export-local-logs", Export_logs.export_locally)
2523
    ; ("stop-daemon", stop_daemon)
2524
    ; ("status", status)
2525
    ]
2526

2527
let client_trustlist_group =
2528
  Command.group ~summary:"Client trustlist management"
3✔
2529
    ~preserve_subcommand_order:()
2530
    [ ("add", trustlist_add)
2531
    ; ("list", trustlist_list)
2532
    ; ("remove", trustlist_remove)
2533
    ]
2534

2535
let advanced ~itn_features =
2536
  let cmds0 =
3✔
2537
    [ ("get-nonce", get_nonce_cmd)
2538
    ; ("client-trustlist", client_trustlist_group)
2539
    ; ("get-trust-status", get_trust_status)
2540
    ; ("get-trust-status-all", get_trust_status_all)
2541
    ; ("get-public-keys", get_public_keys)
2542
    ; ("reset-trust-status", reset_trust_status)
2543
    ; ("batch-send-payments", batch_send_payments)
2544
    ; ("status-clear-hist", status_clear_hist)
2545
    ; ("wrap-key", wrap_key)
2546
    ; ("dump-keypair", dump_keypair)
2547
    ; ("constraint-system-digests", constraint_system_digests)
2548
    ; ("start-tracing", start_tracing)
2549
    ; ("stop-tracing", stop_tracing)
2550
    ; ("start-internal-tracing", start_internal_tracing)
2551
    ; ("stop-internal-tracing", stop_internal_tracing)
2552
    ; ("snark-job-list", snark_job_list)
2553
    ; ("pooled-user-commands", pooled_user_commands)
2554
    ; ("pooled-zkapp-commands", pooled_zkapp_commands)
2555
    ; ("snark-pool-list", snark_pool_list)
2556
    ; ("pending-snark-work", pending_snark_work)
2557
    ; ("compile-time-constants", compile_time_constants)
2558
    ; ("node-status", node_status)
2559
    ; ("visualization", Visualization.command_group)
2560
    ; ("verify-receipt", verify_receipt)
2561
    ; ("generate-keypair", Cli_lib.Commands.generate_keypair)
2562
    ; ("validate-keypair", Cli_lib.Commands.validate_keypair)
2563
    ; ("validate-transaction", Cli_lib.Commands.validate_transaction)
2564
    ; ("send-rosetta-transactions", send_rosetta_transactions_graphql)
2565
    ; ("time-offset", get_time_offset_graphql)
2566
    ; ("get-peers", get_peers_graphql)
2567
    ; ("add-peers", add_peers_graphql)
2568
    ; ("object-lifetime-statistics", object_lifetime_statistics)
2569
    ; ("archive-blocks", archive_blocks)
2570
    ; ("compute-receipt-chain-hash", receipt_chain_hash)
2571
    ; ("hash-transaction", hash_transaction)
2572
    ; ("set-coinbase-receiver", set_coinbase_receiver_graphql)
2573
    ; ("chain-id-inputs", chain_id_inputs)
2574
    ; ("runtime-config", runtime_config)
2575
    ; ("vrf", Cli_lib.Commands.Vrf.command_group)
2576
    ; ("thread-graph", thread_graph)
2577
    ; ("print-signature-kind", signature_kind)
2578
    ]
2579
  in
2580
  let cmds =
2581
    if itn_features then ("itn-create-accounts", itn_create_accounts) :: cmds0
×
2582
    else cmds0
3✔
2583
  in
2584
  Command.group ~summary:"Advanced client commands" cmds
2585

2586
let ledger =
2587
  Command.group ~summary:"Ledger commands"
3✔
2588
    [ ("export", export_ledger)
2589
    ; ("hash", hash_ledger)
2590
    ; ("currency", currency_in_ledger)
2591
    ; ( "test"
2592
      , Command.group ~summary:"Testing-only commands"
3✔
2593
          [ ("apply", test_ledger_application)
2594
          ; ("generate-accounts", Cli_lib.Commands.generate_test_ledger)
2595
          ] )
2596
    ]
2597

2598
let libp2p =
2599
  Command.group ~summary:"Libp2p commands"
3✔
2600
    [ ("generate-keypair", generate_libp2p_keypair)
2601
    ; ("dump-keypair", dump_libp2p_keypair)
2602
    ]
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