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

MinaProtocol / mina / 745

29 Oct 2025 09:20AM UTC coverage: 37.022%. First build
745

push

buildkite

web-flow
Merge pull request #18015 from MinaProtocol/lyh/compat-into-dev-oct28

Merge compatible into develop Oct. 28th

68 of 134 new or added lines in 15 files covered. (50.75%)

26927 of 72733 relevant lines covered (37.02%)

36514.45 hits per line

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

0.27
/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

108✔
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
module Root_ledger = Mina_ledger.Root
13

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

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

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

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

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

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

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

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

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

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

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

596
let coda_crash_message ~log_issue ~action ~error =
597
  let followup =
×
598
    if log_issue then
599
      sprintf
×
600
        !{err| The Mina Protocol developers would like to know why!
601

602
    Please:
603
      Open an issue:
604
        <https://github.com/MinaProtocol/mina/issues/new>
605

606
      Briefly describe what you were doing and %s
607

608
    %!|err}
609
        action
610
    else action
×
611
  in
612
  sprintf !{err|
613

614
  ☠  Mina Daemon %s.
615
  %s
616
%!|err} error followup
617

618
let no_report exn_json status =
619
  sprintf
×
620
    "include the last 20 lines from .mina-config/mina.log and then paste the \
621
     following:\n\
622
     Summary:\n\
623
     %s\n\
624
     Status:\n\
625
     %s\n"
626
    (Yojson.Safe.to_string status)
×
627
    (Yojson.Safe.to_string (summary exn_json))
×
628

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

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