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

MinaProtocol / mina / 1661

18 Dec 2025 03:32PM UTC coverage: 61.328% (+27.9%) from 33.382%
1661

push

buildkite

web-flow
Merge pull request #18232 from MinaProtocol/amcie-merging-release-330-to-master

Merging 3.3.0 release branch to master

1229 of 2006 new or added lines in 108 files covered. (61.27%)

54 existing lines in 27 files now uncovered.

51257 of 83578 relevant lines covered (61.33%)

472886.36 hits per line

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

0.28
/src/app/cli/src/init/mina_run.ml
1
(*
2
  Mina_run provides the runtime layer for the Mina daemon, building on 
3
  Mina_lib’s core protocol logic. It manages GraphQL APIs, node status 
4
  reporting, crash/shutdown handling, and configuration setup, acting as the 
5
  control panel connecting Mina_lib to CLI tools and external services. *)
6

49✔
7
open Core
8
open Async
9
module Graphql_cohttp_async =
10
  Graphql_internal.Make (Graphql_async.Schema) (Cohttp_async.Io)
11
    (Cohttp_async.Body)
12

13
let snark_job_list_json t =
14
  let open Participating_state.Let_syntax in
×
15
  let%map sl = Mina_lib.best_staged_ledger t in
×
16
  Staged_ledger.Scan_state.snark_job_list_json (Staged_ledger.scan_state sl)
×
17

18
let snark_pool_list t =
19
  Mina_lib.snark_pool t |> Network_pool.Snark_pool.resource_pool
×
20
  |> Network_pool.Snark_pool.Resource_pool.snark_pool_json
×
21
  |> Yojson.Safe.to_string
22

23
(* create reader, writer for protocol versions, but really for any one-line item in conf_dir *)
24
let make_conf_dir_item_io ~conf_dir ~filename =
25
  let item_file = conf_dir ^/ filename in
×
26
  let read_item () =
×
27
    let open Stdlib in
×
28
    let inp = open_in item_file in
29
    let res = input_line inp in
×
30
    close_in inp ; res
×
31
  in
32
  let write_item item =
33
    let open Stdlib in
×
34
    let outp = open_out item_file in
35
    output_string outp (item ^ "\n") ;
×
36
    close_out outp
×
37
  in
38
  (read_item, write_item)
39

40
let get_proposed_protocol_version_opt ~conf_dir ~logger =
41
  let read_protocol_version, write_protocol_version =
×
42
    make_conf_dir_item_io ~conf_dir ~filename:"proposed_protocol_version"
43
  in
44
  function
45
  | None -> (
×
46
      try
47
        (* not provided on command line, try to read from config dir *)
48
        let protocol_version = read_protocol_version () in
49
        [%log info]
×
50
          "Setting proposed protocol version to $protocol_version from config"
51
          ~metadata:[ ("protocol_version", `String protocol_version) ] ;
52
        Some (Protocol_version.of_string_exn protocol_version)
×
53
      with Sys_error _ ->
×
54
        (* not on command-line, not in config dir, there's no proposed protocol version *)
55
        None )
56
  | Some protocol_version -> (
×
57
      let validate_cli_protocol_version protocol_version =
58
        if Option.is_none (Protocol_version.of_string_opt protocol_version) then (
×
59
          [%log fatal]
×
60
            "Proposed protocol version provided on command line is invalid"
61
            ~metadata:
62
              [ ("proposed_protocol_version", `String protocol_version) ] ;
63
          failwith "Proposed protocol version from command line is invalid" )
×
64
      in
65
      try
66
        (* overwrite if the command line value disagrees with the value in the config *)
67
        let config_protocol_version = read_protocol_version () in
68
        if String.equal config_protocol_version protocol_version then (
×
69
          [%log info]
×
70
            "Using proposed protocol version $protocol_version from command \
71
             line, which matches the one in the config"
72
            ~metadata:[ ("protocol_version", `String protocol_version) ] ;
73
          Some (Protocol_version.of_string_exn config_protocol_version) )
×
74
        else (
×
75
          validate_cli_protocol_version protocol_version ;
76
          write_protocol_version protocol_version ;
×
77
          [%log info]
×
78
            "Overwriting Mina config proposed protocol version \
79
             $config_proposed_protocol_version with proposed protocol version \
80
             $protocol_version from the command line"
81
            ~metadata:
82
              [ ( "config_proposed_protocol_version"
83
                , `String config_protocol_version )
84
              ; ("proposed_protocol_version", `String protocol_version)
85
              ] ;
86
          Some (Protocol_version.of_string_exn protocol_version) )
×
87
      with Sys_error _ ->
×
88
        (* use value provided on command line, write to config dir *)
89
        validate_cli_protocol_version protocol_version ;
90
        write_protocol_version protocol_version ;
×
91
        [%log info]
×
92
          "Using proposed protocol version from command line, writing to config"
93
          ~metadata:[ ("protocol_version", `String protocol_version) ] ;
94
        Some (Protocol_version.of_string_exn protocol_version) )
×
95

96
(*TODO check deferred now and copy theose files to the temp directory*)
97
let log_shutdown ~conf_dir ~top_logger coda_ref =
98
  let logger =
×
99
    Logger.extend top_logger
100
      [ ("coda_run", `String "Logging state before program ends") ]
101
  in
102
  let frontier_file = conf_dir ^/ "frontier.dot" in
×
103
  let mask_file = conf_dir ^/ "registered_masks.dot" in
×
104
  (* ledger visualization *)
105
  [%log debug] "%s" (Visualization_message.success "registered masks" mask_file) ;
×
106
  Mina_ledger.Ledger.Debug.visualize ~filename:mask_file ;
×
107
  match !coda_ref with
108
  | None ->
×
109
      [%log warn]
×
110
        "Shutdown before Mina instance was created, not saving a visualization"
111
  | Some t -> (
×
112
      (*Transition frontier visualization*)
113
      match Mina_lib.visualize_frontier ~filename:frontier_file t with
114
      | `Active () ->
×
115
          [%log debug] "%s"
×
116
            (Visualization_message.success "transition frontier" frontier_file)
×
117
      | `Bootstrapping ->
×
118
          [%log debug] "%s"
×
119
            (Visualization_message.bootstrap "transition frontier") )
×
120

121
let remove_prev_crash_reports ~conf_dir =
122
  Core.Sys.command (sprintf "rm -rf %s/coda_crash_report*" conf_dir)
×
123

124
let summary exn_json =
125
  let uname = Core.Unix.uname () in
×
126
  let daemon_command =
×
127
    sprintf !"Command: %{sexp: string array}" (Sys.get_argv ())
×
128
  in
129
  `Assoc
×
130
    [ ("OS_type", `String Sys.os_type)
131
    ; ("Release", `String (Core.Unix.Utsname.release uname))
×
132
    ; ("Machine", `String (Core.Unix.Utsname.machine uname))
×
133
    ; ("Sys_name", `String (Core.Unix.Utsname.sysname uname))
×
134
    ; ("Exception", exn_json)
135
    ; ("Command", `String daemon_command)
136
    ; ("Coda_commit", `String Mina_version.commit_id)
137
    ]
138

139
let coda_status coda_ref =
140
  Option.value_map coda_ref
×
141
    ~default:
142
      (Deferred.return (`String "Shutdown before Mina instance was created"))
×
143
    ~f:(fun t ->
144
      Mina_commands.get_status ~flag:`Performance t
×
145
      >>| Daemon_rpcs.Types.Status.to_yojson )
146

147
let make_report exn_json ~conf_dir ~top_logger coda_ref =
148
  (* TEMP MAKE REPORT TRACE *)
149
  [%log' trace top_logger] "make_report: enter" ;
×
150
  ignore (remove_prev_crash_reports ~conf_dir : int) ;
×
151
  let crash_time = Time.to_filename_string ~zone:Time.Zone.utc (Time.now ()) in
×
152
  let temp_config = conf_dir ^/ "coda_crash_report_" ^ crash_time in
×
153
  let () = Core.Unix.mkdir temp_config in
×
154
  (*Transition frontier and ledger visualization*)
155
  log_shutdown ~conf_dir:temp_config ~top_logger coda_ref ;
×
156
  let report_file = temp_config ^ ".tar.gz" in
×
157
  (*Coda status*)
158
  let status_file = temp_config ^/ "coda_status.json" in
159
  let%map status = coda_status !coda_ref in
×
160
  Yojson.Safe.to_file status_file status ;
×
161
  (* TEMP MAKE REPORT TRACE *)
162
  [%log' trace top_logger] "make_report: acquired and wrote status" ;
×
163
  (*coda logs*)
164
  let coda_log = conf_dir ^/ "mina.log" in
×
165
  let () =
×
166
    match Core.Sys.file_exists coda_log with
167
    | `Yes ->
×
168
        let coda_short_log = temp_config ^/ "coda_short.log" in
169
        (*get the last 4MB of the log*)
170
        let log_size = 4 * 1024 * 1024 |> Int64.of_int in
×
171
        let log =
×
172
          In_channel.with_file coda_log ~f:(fun in_chan ->
173
              let len = In_channel.length in_chan in
×
174
              In_channel.seek in_chan
×
175
                Int64.(max 0L (Int64.( + ) len (Int64.neg log_size))) ;
×
176
              In_channel.input_all in_chan )
×
177
        in
178
        Out_channel.write_all coda_short_log ~data:log
×
179
    | _ ->
×
180
        ()
181
  in
182
  (*System info/crash summary*)
183
  let summary = summary exn_json in
184
  Yojson.Safe.to_file (temp_config ^/ "crash_summary.json") summary ;
×
185
  (*copy daemon_json to the temp dir *)
186
  let daemon_config = conf_dir ^/ "daemon.json" in
×
187
  let eq = [%equal: [ `Yes | `Unknown | `No ]] in
×
188
  let () =
189
    if eq (Core.Sys.file_exists daemon_config) `Yes then
×
190
      ignore
×
191
        ( Core.Sys.command
×
192
            (sprintf "cp %s %s" daemon_config (temp_config ^/ "daemon.json"))
×
193
          : int )
194
  in
195
  (*Zip them all up*)
196
  let tmp_files =
197
    [ "coda_short.log"
198
    ; "registered_mask.dot"
199
    ; "frontier.dot"
200
    ; "coda_status.json"
201
    ; "crash_summary.json"
202
    ; "daemon.json"
203
    ]
204
    |> List.filter ~f:(fun f ->
205
           eq (Core.Sys.file_exists (temp_config ^/ f)) `Yes )
×
206
  in
207
  let files = tmp_files |> String.concat ~sep:" " in
×
208
  let tar_command =
×
209
    sprintf "tar  -C %s -czf %s %s" temp_config report_file files
210
  in
211
  let exit = Core.Sys.command tar_command in
×
212
  if exit = 2 then (
×
213
    [%log' fatal top_logger] "Error making the crash report. Exit code: %d" exit ;
×
214
    None )
×
215
  else Some (report_file, temp_config)
×
216

217
(* TODO: handle participation_status more appropriately than doing participate_exn *)
218
let setup_local_server ?(client_trustlist = []) ?rest_server_port
×
219
    ?limited_graphql_port ?itn_graphql_port ?auth_keys
220
    ?(open_limited_graphql_port = false) ?(insecure_rest_server = false) mina =
×
221
  let compile_config = (Mina_lib.config mina).compile_config in
×
222
  let itn_features = (Mina_lib.config mina).itn_features in
×
223
  let client_trustlist =
224
    ref
225
      (Unix.Cidr.Set.of_list
×
226
         ( Unix.Cidr.create ~base_address:Unix.Inet_addr.localhost ~bits:8
227
         :: client_trustlist ) )
228
  in
229
  (* Setup RPC server for client interactions *)
230
  let implement rpc f =
231
    Rpc.Rpc.implement rpc (fun () input ->
×
232
        O1trace.thread ("serve_" ^ Rpc.Rpc.name rpc) (fun () -> f () input) )
×
233
  in
234
  let implement_notrace = Rpc.Rpc.implement in
235
  let logger =
236
    Logger.extend
237
      (Mina_lib.top_level_logger mina)
×
238
      [ ("mina_run", `String "Setting up server logs") ]
239
  in
240
  let client_impls =
×
241
    [ implement Daemon_rpcs.Send_user_commands.rpc (fun () ts ->
×
242
          Deferred.map
×
243
            ( Mina_commands.setup_and_submit_user_commands mina ts
×
244
            |> Participating_state.to_deferred_or_error )
×
245
            ~f:Or_error.join )
246
    ; implement Daemon_rpcs.Send_zkapp_commands.rpc (fun () zkapps ->
×
247
          Deferred.map
×
248
            ( Mina_commands.setup_and_submit_zkapp_commands mina zkapps
×
249
            |> Participating_state.to_deferred_or_error )
×
250
            ~f:Or_error.join )
251
    ; implement Daemon_rpcs.Get_balance.rpc (fun () aid ->
×
252
          return
×
253
            ( Mina_commands.get_balance mina aid
×
254
            |> Participating_state.active_error ) )
×
255
    ; implement Daemon_rpcs.Get_trust_status.rpc (fun () ip_address ->
×
256
          return (Mina_commands.get_trust_status mina ip_address) )
×
257
    ; implement Daemon_rpcs.Get_trust_status_all.rpc (fun () () ->
×
258
          return (Mina_commands.get_trust_status_all mina) )
×
259
    ; implement Daemon_rpcs.Reset_trust_status.rpc (fun () ip_address ->
×
260
          return (Mina_commands.reset_trust_status mina ip_address) )
×
261
    ; implement Daemon_rpcs.Chain_id_inputs.rpc (fun () () ->
×
262
          return (Mina_commands.chain_id_inputs mina) )
×
263
    ; implement Daemon_rpcs.Verify_proof.rpc (fun () (aid, tx, proof) ->
×
264
          return
×
265
            ( Mina_commands.verify_payment mina aid tx proof
×
266
            |> Participating_state.active_error |> Or_error.join ) )
×
267
    ; implement Daemon_rpcs.Get_public_keys_with_details.rpc (fun () () ->
×
268
          let%map keys = Mina_commands.get_keys_with_details mina in
×
269
          Participating_state.active_error keys )
×
270
    ; implement Daemon_rpcs.Get_public_keys.rpc (fun () () ->
×
271
          let%map keys = Mina_commands.get_public_keys mina in
×
272
          Participating_state.active_error keys )
×
273
    ; implement Daemon_rpcs.Get_nonce.rpc (fun () aid ->
×
274
          return
×
275
            ( Mina_commands.get_nonce mina aid
×
276
            |> Participating_state.active_error ) )
×
277
    ; implement Daemon_rpcs.Get_inferred_nonce.rpc (fun () aid ->
×
278
          return
×
279
            ( Mina_lib.get_inferred_nonce_from_transaction_pool_and_ledger mina
×
280
                aid
281
            |> Participating_state.active_error ) )
×
282
    ; implement_notrace Daemon_rpcs.Get_status.rpc (fun () flag ->
×
283
          Mina_commands.get_status ~flag mina )
×
284
    ; implement Daemon_rpcs.Clear_hist_status.rpc (fun () flag ->
×
285
          Mina_commands.clear_hist_status ~flag mina )
×
286
    ; implement Daemon_rpcs.Get_ledger.rpc (fun () lh ->
×
287
          Mina_lib.get_ledger mina lh )
×
288
    ; implement Daemon_rpcs.Get_snarked_ledger.rpc (fun () lh ->
×
289
          Mina_lib.get_snarked_ledger mina lh )
×
290
    ; implement Daemon_rpcs.Get_staking_ledger.rpc (fun () which ->
×
291
          let ledger_or_error =
×
292
            match which with
293
            | Next ->
×
294
                Option.value_map (Mina_lib.next_epoch_ledger mina)
×
295
                  ~default:
296
                    (Or_error.error_string "next staking ledger not available")
×
297
                  ~f:(function
298
                  | `Finalized ledger ->
×
299
                      Ok ledger
300
                  | `Notfinalized ->
×
301
                      Or_error.error_string
302
                        "next staking ledger is not finalized yet" )
303
            | Current ->
×
304
                Option.value_map
×
305
                  (Mina_lib.staking_ledger mina)
×
306
                  ~default:
307
                    (Or_error.error_string
×
308
                       "current staking ledger not available" )
309
                  ~f:Or_error.return
310
          in
311
          match ledger_or_error with
312
          | Ok ledger -> (
×
313
              match ledger with
314
              | Genesis_epoch_ledger l ->
×
NEW
315
                  let l_inner = Lazy.force @@ Genesis_ledger.Packed.t l in
×
NEW
316
                  let%map accts = Mina_ledger.Ledger.to_list l_inner in
×
317
                  Ok accts
×
NEW
318
              | Ledger_root l ->
×
319
                  let casted = Mina_ledger.Ledger.Root.as_unmasked l in
320
                  let%map accts =
NEW
321
                    Mina_ledger.Ledger.Any_ledger.M.to_list casted
×
322
                  in
323
                  Ok accts )
×
324
          | Error err ->
×
325
              return (Error err) )
326
    ; implement Daemon_rpcs.Stop_daemon.rpc (fun () () ->
×
327
          Scheduler.yield () >>= (fun () -> exit 0) |> don't_wait_for ;
×
328
          Deferred.unit )
×
329
    ; implement Daemon_rpcs.Snark_job_list.rpc (fun () () ->
×
330
          return (snark_job_list_json mina |> Participating_state.active_error) )
×
331
    ; implement Daemon_rpcs.Snark_pool_list.rpc (fun () () ->
×
332
          return (snark_pool_list mina) )
×
333
    ; implement Daemon_rpcs.Start_tracing.rpc (fun () () ->
×
334
          let open Mina_lib.Config in
×
335
          Mina_tracing.start (Mina_lib.config mina).conf_dir )
×
336
    ; implement Daemon_rpcs.Stop_tracing.rpc (fun () () ->
×
337
          Mina_tracing.stop () ; Deferred.unit )
×
338
    ; implement Daemon_rpcs.Start_internal_tracing.rpc (fun () () ->
×
339
          Internal_tracing.toggle ~commit_id:Mina_version.commit_id ~logger
×
340
            `Enabled )
341
    ; implement Daemon_rpcs.Stop_internal_tracing.rpc (fun () () ->
×
342
          Internal_tracing.toggle ~commit_id:Mina_version.commit_id ~logger
×
343
            `Disabled )
344
    ; implement Daemon_rpcs.Visualization.Frontier.rpc (fun () filename ->
×
345
          return (Mina_lib.visualize_frontier ~filename mina) )
×
346
    ; implement Daemon_rpcs.Visualization.Registered_masks.rpc
×
347
        (fun () filename ->
348
          return (Mina_ledger.Ledger.Debug.visualize ~filename) )
×
349
    ; implement Daemon_rpcs.Add_trustlist.rpc (fun () cidr ->
×
350
          return
×
351
            (let cidr_str = Unix.Cidr.to_string cidr in
352
             if Unix.Cidr.Set.mem !client_trustlist cidr then
×
353
               Or_error.errorf "%s already present in trustlist" cidr_str
×
354
             else (
×
355
               client_trustlist := Unix.Cidr.Set.add !client_trustlist cidr ;
×
356
               Ok () ) ) )
357
    ; implement Daemon_rpcs.Remove_trustlist.rpc (fun () cidr ->
×
358
          return
×
359
            (let cidr_str = Unix.Cidr.to_string cidr in
360
             if not @@ Unix.Cidr.Set.mem !client_trustlist cidr then
×
361
               Or_error.errorf "%s not present in trustlist" cidr_str
×
362
             else (
×
363
               client_trustlist := Unix.Cidr.Set.remove !client_trustlist cidr ;
×
364
               Ok () ) ) )
365
    ; implement Daemon_rpcs.Get_trustlist.rpc (fun () () ->
×
366
          return (Set.to_list !client_trustlist) )
×
367
    ; implement Daemon_rpcs.Get_node_status.rpc (fun () peers ->
×
368
          Mina_networking.get_node_status_from_peers (Mina_lib.net mina) peers )
×
369
    ; implement Daemon_rpcs.Get_object_lifetime_statistics.rpc (fun () () ->
×
370
          return
×
371
            (Yojson.Safe.pretty_to_string @@ Allocation_functor.Table.dump ()) )
×
372
    ; implement Daemon_rpcs.Submit_internal_log.rpc
×
373
        (fun () { timestamp; message; metadata; process } ->
374
          let metadata =
×
375
            List.map metadata ~f:(fun (s, value) ->
376
                (s, Yojson.Safe.from_string value) )
×
377
          in
378
          return @@ Itn_logger.log ~process ~timestamp ~message ~metadata () )
×
379
    ]
380
  in
381
  let snark_worker_impls =
UNCOV
382
    [ implement Snark_worker.Rpcs_versioned.Get_work.Latest.rpc (fun () () ->
×
383
          Deferred.return
×
384
            (let open Option.Let_syntax in
385
            let%bind key =
386
              Option.merge
×
387
                (Mina_lib.snark_worker_key mina)
×
388
                (Mina_lib.snark_coordinator_key mina)
×
389
                ~f:Fn.const
390
            in
391
            let%map work = Mina_lib.request_work mina in
×
392
            let work =
×
393
              Snark_work_lib.Work.Spec.map work
394
                ~f:
395
                  (Snark_work_lib.Work.Single.Spec.map
396
                     ~f_proof:Ledger_proof.Cached.read_proof_from_disk
397
                     ~f_witness:Transaction_witness.read_all_proofs_from_disk )
398
            in
399
            [%log trace]
×
400
              ~metadata:
401
                [ ( "work_spec"
402
                  , Snark_work_lib.Selector.Spec.Stable.Latest.to_yojson work )
×
403
                ]
404
              "responding to a Get_work request with some new work" ;
405
            Mina_metrics.(Counter.inc_one Snark_work.snark_work_assigned_rpc) ;
×
406
            (work, key)) )
407
    ; implement Snark_worker.Rpcs_versioned.Submit_work.Latest.rpc
×
408
        (fun () (work : Snark_work_lib.Selector.Result.Stable.Latest.t) ->
409
          [%log trace] "received completed work from a snark worker"
×
410
            ~metadata:
411
              [ ( "work_spec"
412
                , Snark_work_lib.Selector.Spec.Stable.Latest.to_yojson work.spec
×
413
                )
414
              ] ;
415

NEW
416
          Mina_metrics.(
×
NEW
417
            Counter.inc_one Snark_work.completed_snark_work_received_rpc) ;
×
NEW
418
          One_or_two.zip_exn work.spec.instances work.metrics
×
419
          |> One_or_two.iter ~f:(fun (single_spec, (elapsed, _tag)) ->
NEW
420
                 Snark_work_lib.Metrics.(
×
421
                   emit_single_metrics ~logger ~single_spec
422
                     ~data:{ data = elapsed; proof = () }
423
                     ~on_zkapp_command:emit_zkapp_metrics_legacy ()) ) ;
424
          Deferred.return @@ Mina_lib.add_work mina work )
×
425
    ; implement Snark_worker.Rpcs_versioned.Failed_to_generate_snark.Latest.rpc
×
426
        (fun
427
          ()
428
          ((error, _work_spec, _prover_public_key) :
429
            Error.t
430
            * Snark_work_lib.Selector.Spec.Stable.Latest.t
431
            * Signature_lib.Public_key.Compressed.t )
432
        ->
433
          [%str_log error]
×
434
            (Snark_worker.Events.Generating_snark_work_failed
435
               { error = Error_json.error_to_yojson error } ) ;
×
436
          Mina_metrics.(Counter.inc_one Snark_work.snark_work_failed_rpc) ;
×
437
          Deferred.unit )
438
    ]
439
  in
440
  let create_graphql_server_with_auth ~mk_context ?auth_keys ~bind_to_address
441
      ~schema ~server_description ~require_auth port =
442
    if require_auth && Option.is_none auth_keys then
×
443
      failwith
×
444
        "Could not create GraphQL server, authentication is required, but no \
445
         authentication keys were provided" ;
446
    let auth_keys =
×
447
      Option.map auth_keys ~f:(fun s ->
448
          let pk_strs = String.split_on_chars ~on:[ ',' ] s in
×
449
          List.map pk_strs ~f:(fun pk_str ->
×
450
              match Itn_crypto.pubkey_of_base64 pk_str with
×
451
              | Ok pk ->
×
452
                  pk
453
              | Error _ ->
×
454
                  failwithf "Could not decode %s to an Ed25519 public key"
455
                    pk_str () ) )
456
    in
457
    let graphql_callback =
×
458
      Graphql_cohttp_async.make_callback ?auth_keys mk_context schema
459
    in
460
    Cohttp_async.(
×
461
      Server.create_expert
×
462
        ~on_handler_error:
463
          (`Call
464
            (fun _net exn ->
465
              [%log error]
×
466
                "Exception while handling REST server request: $error"
467
                ~metadata:
468
                  [ ("error", `String (Exn.to_string_mach exn))
×
469
                  ; ("context", `String "rest_server")
470
                  ] ) )
471
        (Tcp.Where_to_listen.bind_to bind_to_address (On_port port))
×
472
        (fun ~body _sock req ->
473
          let uri = Cohttp.Request.uri req in
×
474
          let status flag =
×
475
            let%bind status = Mina_commands.get_status ~flag mina in
×
476
            Server.respond_string
×
477
              ( status |> Daemon_rpcs.Types.Status.to_yojson
×
478
              |> Yojson.Safe.pretty_to_string )
×
479
          in
480
          let lift x = `Response x in
×
481
          match Uri.path uri with
482
          | "/" ->
×
483
              let body =
484
                "This page is intentionally left blank. The graphql endpoint \
485
                 can be found at `/graphql`."
486
              in
487
              Server.respond_string ~status:`OK body >>| lift
×
488
          | "/graphql" ->
×
489
              [%log debug] "Received graphql request. Uri: $uri"
×
490
                ~metadata:
491
                  [ ("uri", `String (Uri.to_string uri))
×
492
                  ; ("context", `String "rest_server")
493
                  ] ;
494
              graphql_callback () req body
×
495
          | "/status" ->
×
496
              status `None >>| lift
×
497
          | "/status/performance" ->
×
498
              status `Performance >>| lift
×
499
          | _ ->
×
500
              Server.respond_string ~status:`Not_found "Route not found"
×
501
              >>| lift ))
502
    |> Deferred.map ~f:(fun _ ->
503
           [%log info]
×
504
             !"Created %s at: http://localhost:%i/graphql"
505
             server_description port )
506
  in
507
  let create_graphql_server =
508
    create_graphql_server_with_auth
509
      ~mk_context:(fun ~with_seq_no:_ _req -> mina)
×
510
      ?auth_keys:None
511
  in
512
  Option.iter rest_server_port ~f:(fun rest_server_port ->
513
      O1trace.background_thread "serve_graphql" (fun () ->
×
514
          create_graphql_server
×
515
            ~bind_to_address:
516
              Tcp.Bind_to_address.(
517
                if insecure_rest_server then All_addresses else Localhost)
×
518
            ~schema:Mina_graphql.schema ~server_description:"GraphQL server"
519
            ~require_auth:false rest_server_port ) ) ;
520
  (* Second graphql server with limited queries exposed *)
521
  Option.iter limited_graphql_port ~f:(fun rest_server_port ->
×
522
      O1trace.background_thread "serve_limited_graphql" (fun () ->
×
523
          create_graphql_server
×
524
            ~bind_to_address:
525
              Tcp.Bind_to_address.(
526
                if open_limited_graphql_port then All_addresses else Localhost)
×
527
            ~schema:Mina_graphql.schema_limited
528
            ~server_description:"GraphQL server with limited queries"
529
            ~require_auth:false rest_server_port ) ) ;
530
  if itn_features then
×
531
    (* Third graphql server with ITN-particular queries exposed *)
532
    Option.iter itn_graphql_port ~f:(fun rest_server_port ->
×
533
        O1trace.background_thread "serve_itn_graphql" (fun () ->
×
534
            create_graphql_server_with_auth
×
535
              ~mk_context:(fun ~with_seq_no _req -> (with_seq_no, mina))
×
536
              ?auth_keys
537
              ~bind_to_address:
538
                Tcp.Bind_to_address.(
539
                  if insecure_rest_server then All_addresses else Localhost)
×
540
              ~schema:Mina_graphql.schema_itn
541
              ~server_description:"GraphQL server for ITN queries"
542
              ~require_auth:true rest_server_port ) ) ;
543
  let where_to_listen =
×
544
    Tcp.Where_to_listen.bind_to All_addresses
545
      (On_port (Mina_lib.client_port mina))
×
546
  in
547
  O1trace.background_thread "serve_client_rpcs" (fun () ->
×
548
      Deferred.ignore_m
×
549
        (Tcp.Server.create
×
550
           ~on_handler_error:
551
             (`Call
552
               (fun _net exn ->
553
                 [%log error]
×
554
                   "Exception while handling TCP server request: $error"
555
                   ~metadata:
556
                     [ ("error", `String (Exn.to_string_mach exn))
×
557
                     ; ("context", `String "rpc_tcp_server")
558
                     ] ) )
559
           where_to_listen
560
           (fun address reader writer ->
561
             let address = Socket.Address.Inet.addr address in
×
562
             if
×
563
               not
564
                 (Set.exists !client_trustlist ~f:(fun cidr ->
×
565
                      Unix.Cidr.does_match cidr address ) )
×
566
             then (
×
567
               [%log error]
×
568
                 !"Rejecting client connection from $address, it is not \
569
                   present in the trustlist."
570
                 ~metadata:
571
                   [ ("$address", `String (Unix.Inet_addr.to_string address)) ] ;
×
572
               Deferred.unit )
×
573
             else
574
               Rpc.Connection.server_with_close
×
575
                 ~handshake_timeout:compile_config.rpc_handshake_timeout
576
                 ~heartbeat_config:
577
                   (Rpc.Connection.Heartbeat_config.create
×
578
                      ~timeout:
579
                        (Time_ns.Span.of_sec
×
580
                           (Time.Span.to_sec
×
581
                              compile_config.rpc_heartbeat_timeout ) )
582
                      ~send_every:
583
                        (Time_ns.Span.of_sec
×
584
                           (Time.Span.to_sec
×
585
                              compile_config.rpc_heartbeat_send_every ) )
586
                      () )
587
                 reader writer
588
                 ~implementations:
589
                   (Rpc.Implementations.create_exn
590
                      ~implementations:(client_impls @ snark_worker_impls)
591
                      ~on_unknown_rpc:`Raise )
592
                 ~connection_state:(fun _ -> ())
×
593
                 ~on_handshake_error:
594
                   (`Call
595
                     (fun exn ->
596
                       [%log warn]
×
597
                         "Handshake error while handling RPC server request \
598
                          from $address"
599
                         ~metadata:
600
                           [ ("error", `String (Exn.to_string_mach exn))
×
601
                           ; ("context", `String "rpc_server")
602
                           ; ( "address"
603
                             , `String (Unix.Inet_addr.to_string address) )
×
604
                           ] ;
605
                       Deferred.unit ) ) ) ) )
×
606

607
let coda_crash_message ~log_issue ~action ~error =
608
  let followup =
×
609
    if log_issue then
610
      sprintf
×
611
        !{err| The Mina Protocol developers would like to know why!
612

613
    Please:
614
      Open an issue:
615
        <https://github.com/MinaProtocol/mina/issues/new>
616

617
      Briefly describe what you were doing and %s
618

619
    %!|err}
620
        action
621
    else action
×
622
  in
623
  sprintf !{err|
624

625
  ☠  Mina Daemon %s.
626
  %s
627
%!|err} error followup
628

629
let no_report exn_json status =
630
  sprintf
×
631
    "include the last 20 lines from .mina-config/mina.log and then paste the \
632
     following:\n\
633
     Summary:\n\
634
     %s\n\
635
     Status:\n\
636
     %s\n"
637
    (Yojson.Safe.to_string status)
×
638
    (Yojson.Safe.to_string (summary exn_json))
×
639

640
let handle_crash e ~time_controller ~conf_dir ~child_pids ~top_logger coda_ref =
641
  (* attempt to free up some memory before handling crash *)
642
  (* this circumvents using Child_processes.kill, and instead sends SIGKILL to all children *)
643
  Hashtbl.keys child_pids
×
644
  |> List.iter ~f:(fun pid ->
645
         ignore (Signal.send Signal.kill (`Pid pid) : [ `No_such_process | `Ok ]) ) ;
×
646
  let exn_json = Error_json.error_to_yojson (Error.of_exn ~backtrace:`Get e) in
×
647
  [%log' fatal top_logger]
×
648
    "Unhandled top-level exception: $exn\nGenerating crash report"
649
    ~metadata:[ ("exn", exn_json) ] ;
650
  let%bind status = coda_status !coda_ref in
×
651
  (* TEMP MAKE REPORT TRACE *)
652
  [%log' trace top_logger] "handle_crash: acquired coda status" ;
×
653
  let%map action_string =
654
    match%map
655
      Block_time.Timeout.await
×
656
        ~timeout_duration:(Block_time.Span.of_ms 30_000L)
×
657
        time_controller
658
        ( try
659
            make_report exn_json ~conf_dir coda_ref ~top_logger
×
660
            >>| fun k -> Ok k
×
661
          with exn -> return (Error (Error.of_exn exn)) )
×
662
    with
663
    | `Ok (Ok (Some (report_file, temp_config))) ->
×
664
        ( try ignore (Core.Sys.command (sprintf "rm -rf %s" temp_config) : int)
×
665
          with _ -> () ) ;
×
666
        sprintf "attach the crash report %s" report_file
667
    | `Ok (Ok None) ->
×
668
        (*TODO: tar failed, should we ask people to zip the temp directory themselves?*)
669
        no_report exn_json status
670
    | `Ok (Error e) ->
×
671
        [%log' fatal top_logger] "Exception when generating crash report: $exn"
×
672
          ~metadata:[ ("exn", Error_json.error_to_yojson e) ] ;
×
673
        no_report exn_json status
×
674
    | `Timeout ->
×
675
        [%log' fatal top_logger] "Timed out while generated crash report" ;
×
676
        no_report exn_json status
×
677
  in
678
  let message =
×
679
    coda_crash_message ~error:"crashed" ~action:action_string ~log_issue:true
680
  in
681
  Core.print_string message
682

683
let handle_shutdown ~monitor ~time_controller ~conf_dir ~child_pids ~top_logger
684
    coda_ref =
685
  Monitor.detach_and_iter_errors monitor ~f:(fun exn ->
×
686
      don't_wait_for
×
687
        (let%bind () =
688
           match Monitor.extract_exn exn with
689
           | Mina_networking.No_initial_peers ->
×
690
               let message =
691
                 coda_crash_message
692
                   ~error:"failed to connect to any initial peers"
693
                   ~action:
694
                     "You might be trying to connect to a different network \
695
                      version, or need to troubleshoot your configuration. See \
696
                      https://codaprotocol.com/docs/troubleshooting/ for \
697
                      details."
698
                   ~log_issue:false
699
               in
700
               Core.print_string message ; Deferred.unit
×
701
           | Genesis_ledger_helper.Genesis_state_initialization_error ->
×
702
               let message =
703
                 coda_crash_message
704
                   ~error:"failed to initialize the genesis state"
705
                   ~action:
706
                     "include the last 50 lines from .mina-config/mina.log"
707
                   ~log_issue:true
708
               in
709
               Core.print_string message ; Deferred.unit
×
710
           | Mina_stdlib.Mina_user_error.Mina_user_error { message; where } ->
×
711
               Core.print_string "\nFATAL ERROR" ;
712
               let error =
×
713
                 match where with
714
                 | None ->
×
715
                     "encountered a configuration error"
716
                 | Some where ->
×
717
                     sprintf "encountered a configuration error %s" where
×
718
               in
719
               let message =
720
                 coda_crash_message ~error ~action:("\n" ^ message)
721
                   ~log_issue:false
722
               in
723
               Core.print_string message ; Deferred.unit
×
724
           | Mina_lib.Offline_shutdown ->
×
725
               Core.print_string
726
                 "\n\
727
                  [FATAL] *** Mina daemon has been offline for too long ***\n\
728
                  *** Shutting down ***\n" ;
729
               handle_crash Mina_lib.Offline_shutdown ~time_controller ~conf_dir
×
730
                 ~child_pids ~top_logger coda_ref
731
           | Mina_lib.Bootstrap_stuck_shutdown ->
×
732
               Core.print_string
733
                 "\n\
734
                  [FATAL] *** Mina daemon has been stuck in bootstrap for too \
735
                  long ***\n\
736
                  *** Shutting down ***\n" ;
737
               handle_crash Mina_lib.Bootstrap_stuck_shutdown ~time_controller
×
738
                 ~conf_dir ~child_pids ~top_logger coda_ref
739
           | _exn ->
×
740
               let error = Error.of_exn ~backtrace:`Get exn in
741
               let%bind () =
742
                 Node_error_service.send_report
743
                   ~commit_id:Mina_version.commit_id ~logger:top_logger ~error
744
               in
745
               handle_crash exn ~time_controller ~conf_dir ~child_pids
×
746
                 ~top_logger coda_ref
747
         in
748
         Stdlib.exit 1 ) ) ;
×
749
  Async_unix.Signal.(
×
750
    handle terminating ~f:(fun signal ->
751
        log_shutdown ~conf_dir ~top_logger coda_ref ;
×
752
        let logger =
×
753
          Logger.extend top_logger
754
            [ ("coda_run", `String "Program was killed by signal") ]
755
        in
756
        [%log info]
×
757
          !"Mina process was interrupted by $signal"
758
          ~metadata:[ ("signal", `String (to_string signal)) ] ;
×
759
        (* causes async shutdown and at_exit handlers to run *)
760
        Async.shutdown 130 ))
×
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