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

MinaProtocol / mina / 663

06 Oct 2025 01:05PM UTC coverage: 32.117% (-5.4%) from 37.53%
663

push

buildkite

web-flow
Merge pull request #17902 from MinaProtocol/dw/zkapps_example_revamp_3

zkapps_examples/empty_update: move to Alcotest

23239 of 72358 relevant lines covered (32.12%)

23741.77 hits per line

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

0.56
/src/lib/bootstrap_controller/bootstrap_controller.ml
1
(* Only show stdout for failed inline tests. *)
139✔
2
open Core
3
open Async
4
open Mina_base
5
open Mina_state
6
open Pipe_lib.Strict_pipe
7
open Network_peer
8
open Mina_stdlib
9
module Ledger = Mina_ledger.Ledger
10
module Sync_ledger = Mina_ledger.Sync_ledger
11
module Transition_cache = Transition_cache
12

13
module type CONTEXT = sig
14
  val logger : Logger.t
15

16
  val precomputed_values : Precomputed_values.t
17

18
  val constraint_constants : Genesis_constants.Constraint_constants.t
19

20
  val consensus_constants : Consensus.Constants.t
21

22
  val ledger_sync_config : Syncable_ledger.daemon_config
23

24
  val proof_cache_db : Proof_cache_tag.cache_db
25
end
26

27
type Structured_log_events.t += Bootstrap_complete
28
  [@@deriving register_event { msg = "Bootstrap state: complete." }]
×
29

30
type t =
31
  { context : (module CONTEXT)
32
  ; trust_system : Trust_system.t
33
  ; verifier : Verifier.t
34
  ; mutable best_seen_transition : Mina_block.initial_valid_block
35
  ; mutable current_root : Mina_block.initial_valid_block
36
  ; network : Mina_networking.t
37
  ; mutable num_of_root_snarked_ledger_retargeted : int
38
  }
39

40
(** An auxiliary data structure for collecting various metrics for bootstrap controller. *)
41
type bootstrap_cycle_stats =
×
42
  { cycle_result : string
×
43
  ; sync_ledger_time : Time.Span.t
×
44
  ; staged_ledger_data_download_time : Time.Span.t
×
45
  ; staged_ledger_construction_time : Time.Span.t option
×
46
  ; local_state_sync_required : bool
×
47
  ; local_state_sync_time : Time.Span.t option
×
48
  }
49
[@@deriving to_yojson]
50

51
let time_deferred deferred =
52
  let start_time = Time.now () in
×
53
  let%map result = deferred in
54
  let end_time = Time.now () in
×
55
  (Time.diff end_time start_time, result)
×
56

57
let worth_getting_root ({ context = (module Context); _ } as t) candidate =
58
  let module Consensus_context = struct
×
59
    include Context
60

61
    let logger =
62
      Logger.extend logger
×
63
        [ ( "selection_context"
64
          , `String "Bootstrap_controller.worth_getting_root" )
65
        ]
66
  end in
67
  Consensus.Hooks.equal_select_status `Take
68
  @@ Consensus.Hooks.select
69
       ~context:(module Consensus_context)
70
       ~existing:
71
         ( t.best_seen_transition |> Mina_block.Validation.block_with_hash
×
72
         |> With_hash.map ~f:Mina_block.consensus_state )
×
73
       ~candidate
74

75
let received_bad_proof ({ context = (module Context); _ } as t) host e =
76
  let open Context in
×
77
  Trust_system.(
78
    record t.trust_system logger host
79
      Actions.
80
        ( Violated_protocol
81
        , Some
82
            ( "Bad ancestor proof: $error"
83
            , [ ("error", Error_json.error_to_yojson e) ] ) ))
×
84

85
let done_syncing_root root_sync_ledger =
86
  Option.is_some (Sync_ledger.Root.peek_valid_tree root_sync_ledger)
×
87

88
let should_sync ~root_sync_ledger t candidate_state =
89
  (not @@ done_syncing_root root_sync_ledger)
×
90
  && worth_getting_root t candidate_state
×
91

92
(** Update [Synced_ledger]'s target and [best_seen_transition] and [current_root] accordingly. *)
93
let start_sync_job_with_peer ~sender ~root_sync_ledger
94
    ({ context = (module Context); _ } as t) peer_best_tip peer_root =
95
  let open Context in
×
96
  let%bind () =
97
    Trust_system.(
98
      record t.trust_system logger sender
×
99
        Actions.
100
          ( Fulfilled_request
101
          , Some ("Received verified peer root and best tip", []) ))
102
  in
103
  t.best_seen_transition <- peer_best_tip ;
×
104
  t.current_root <- peer_root ;
105
  let blockchain_state =
106
    t.current_root |> Mina_block.Validation.block |> Mina_block.header
×
107
    |> Mina_block.Header.protocol_state |> Protocol_state.blockchain_state
×
108
  in
109
  let expected_staged_ledger_hash =
×
110
    blockchain_state |> Blockchain_state.staged_ledger_hash
111
  in
112
  let snarked_ledger_hash =
×
113
    blockchain_state |> Blockchain_state.snarked_ledger_hash
114
  in
115
  return
×
116
  @@
117
  match
118
    Sync_ledger.Root.new_goal root_sync_ledger
119
      (Frozen_ledger_hash.to_ledger_hash snarked_ledger_hash)
×
120
      ~data:
121
        ( State_hash.With_state_hashes.state_hash
×
122
          @@ Mina_block.Validation.block_with_hash t.current_root
×
123
        , sender
124
        , expected_staged_ledger_hash )
125
      ~equal:(fun (hash1, _, _) (hash2, _, _) -> State_hash.equal hash1 hash2)
×
126
  with
127
  | `New ->
×
128
      t.num_of_root_snarked_ledger_retargeted <-
129
        t.num_of_root_snarked_ledger_retargeted + 1 ;
130
      `Syncing_new_snarked_ledger
131
  | `Update_data ->
×
132
      `Updating_root_transition
133
  | `Repeat ->
×
134
      `Ignored
135

136
let to_consensus_state h =
137
  Mina_block.Header.protocol_state h |> Protocol_state.consensus_state
×
138

139
(** For each transition, this function would compare it with the existing one.
140
    If the incoming transition is better, then download the merkle list from
141
    that transition to its root and verify it. If we get a better root than
142
    the existing one, then reset the Sync_ledger's target by calling
143
    [start_sync_job_with_peer] function. *)
144
let on_transition ({ context = (module Context); _ } as t) ~sender
145
    ~root_sync_ledger candidate_header =
146
  let open Context in
×
147
  let candidate_consensus_state =
148
    With_hash.map ~f:to_consensus_state candidate_header
149
  in
150
  if not @@ should_sync ~root_sync_ledger t candidate_consensus_state then
×
151
    Deferred.return `Ignored
×
152
  else
153
    match%bind
154
      Mina_networking.get_ancestry t.network sender.Peer.peer_id
×
155
        (With_hash.map_hash candidate_consensus_state
×
156
           ~f:State_hash.State_hashes.state_hash )
157
    with
158
    | Error e ->
×
159
        [%log error]
×
160
          ~metadata:[ ("error", Error_json.error_to_yojson e) ]
×
161
          !"Could not get the proof of the root transition from the network: \
162
            $error" ;
163
        Deferred.return `Ignored
×
164
    | Ok peer_root_with_proof -> (
×
165
        let pcd =
166
          peer_root_with_proof.data
167
          |> Proof_carrying_data.map
×
168
               ~f:(Mina_block.write_all_proofs_to_disk ~proof_cache_db)
169
          |> Proof_carrying_data.map_proof
170
               ~f:
171
                 (Tuple2.map_snd
172
                    ~f:(Mina_block.write_all_proofs_to_disk ~proof_cache_db) )
173
        in
174
        match%bind
175
          Mina_block.verify_on_header
×
176
            ~verify:
177
              (Sync_handler.Root.verify
×
178
                 ~context:(module Context)
179
                 ~verifier:t.verifier candidate_consensus_state )
180
            pcd
181
        with
182
        | Ok (`Root root, `Best_tip best_tip) ->
×
183
            if done_syncing_root root_sync_ledger then return `Ignored
×
184
            else
185
              start_sync_job_with_peer ~sender ~root_sync_ledger t best_tip root
×
186
        | Error e ->
×
187
            return (received_bad_proof t sender e |> Fn.const `Ignored) )
×
188

189
(** A helper function that wraps the calls to Sync_ledger and iterate through
190
    incoming transitions, add those to the transition_cache and calls
191
    [on_transition] function. *)
192
let sync_ledger ({ context = (module Context); _ } as t) ~preferred
193
    ~root_sync_ledger ~transition_graph ~sync_ledger_reader =
194
  let open Context in
×
195
  let query_reader = Sync_ledger.Root.query_reader root_sync_ledger in
196
  let response_writer = Sync_ledger.Root.answer_writer root_sync_ledger in
×
197
  Mina_networking.glue_sync_ledger ~preferred t.network query_reader
×
198
    response_writer ;
199
  Pipe_lib.Choosable_synchronous_pipe.iter sync_ledger_reader
×
200
    ~f:(fun (b_or_h, `Valid_cb vc) ->
201
      let header_with_hash, sender, transition_cache_element =
×
202
        match b_or_h with
203
        | `Block b_env ->
×
204
            ( Envelope.Incoming.data b_env
×
205
              |> Mina_block.Validation.block_with_hash
×
206
              |> With_hash.map ~f:Mina_block.header
×
207
            , Envelope.Incoming.remote_sender_exn b_env
×
208
            , Envelope.Incoming.map ~f:(fun x -> `Block x) b_env )
×
209
        | `Header h_env ->
×
210
            ( Envelope.Incoming.data h_env
×
211
              |> Mina_block.Validation.header_with_hash
×
212
            , Envelope.Incoming.remote_sender_exn h_env
×
213
            , Envelope.Incoming.map ~f:(fun x -> `Header x) h_env )
×
214
      in
215
      let previous_state_hash =
216
        With_hash.data header_with_hash
×
217
        |> Mina_block.Header.protocol_state
×
218
        |> Protocol_state.previous_state_hash
219
      in
220
      Transition_cache.add transition_graph ~parent:previous_state_hash
×
221
        (transition_cache_element, vc) ;
222
      (* TODO: Efficiently limiting the number of green threads in #1337 *)
223
      if
×
224
        worth_getting_root t
225
          (With_hash.map ~f:to_consensus_state header_with_hash)
×
226
      then (
×
227
        [%log trace] "Added the transition from sync_ledger_reader into cache"
×
228
          ~metadata:
229
            [ ( "state_hash"
230
              , State_hash.to_yojson
×
231
                  (State_hash.With_state_hashes.state_hash header_with_hash) )
×
232
            ; ( "header"
233
              , Mina_block.Header.to_yojson (With_hash.data header_with_hash) )
×
234
            ] ;
235

236
        Deferred.ignore_m
×
237
        @@ on_transition t ~sender ~root_sync_ledger header_with_hash )
×
238
      else Deferred.unit )
×
239

240
let external_transition_compare ~context:(module Context : CONTEXT) =
241
  let get_consensus_state =
×
242
    Fn.compose Protocol_state.consensus_state Mina_block.Header.protocol_state
243
  in
244
  Comparable.lift
×
245
    (fun existing candidate ->
246
      (* To prevent the logger to spam a lot of messages, the logger input is set to null *)
247
      if
×
248
        State_hash.equal
249
          (State_hash.With_state_hashes.state_hash existing)
×
250
          (State_hash.With_state_hashes.state_hash candidate)
×
251
      then 0
×
252
      else if
×
253
        Consensus.Hooks.equal_select_status `Keep
254
        @@ Consensus.Hooks.select ~context:(module Context) ~existing ~candidate
255
      then -1
×
256
      else 1 )
×
257
    ~f:(With_hash.map ~f:get_consensus_state)
258

259
let download_snarked_ledger ~trust_system ~preferred_peers ~transition_graph
260
    ~sync_ledger_reader ~context t temp_snarked_ledger =
261
  time_deferred
×
262
    (let root_sync_ledger =
263
       Sync_ledger.Root.create temp_snarked_ledger ~context ~trust_system
264
     in
265
     don't_wait_for
×
266
       (sync_ledger t ~preferred:preferred_peers ~root_sync_ledger
×
267
          ~transition_graph ~sync_ledger_reader ) ;
268
     (* We ignore the resulting ledger returned here since it will always
269
        * be the same as the ledger we started with because we are syncing
270
        * a db ledger. *)
271
     let%map _, data = Sync_ledger.Root.valid_tree root_sync_ledger in
×
272
     Sync_ledger.Root.destroy root_sync_ledger ;
×
273
     data )
×
274

275
(** Run one bootstrap cycle *)
276
let run_cycle ~context:(module Context : CONTEXT) ~trust_system ~verifier
277
    ~network ~consensus_local_state ~network_transition_pipe ~preferred_peers
278
    ~persistent_root ~persistent_frontier ~initial_root_transition ~catchup_mode
279
    ~signature_kind previous_cycles =
280
  let open Context in
×
281
  (* The short-lived pipe allocated here will be closed
282
     when a follow-up pipe is allocated: in the next cycle of bootstrap
283
     or when controll is passed to the transition frontier controller.staged_ledger_construction_time
284
     Because [Choosable_synchronous_pipe.t] is used, no data will be lost: any
285
     successful read is followed by mom-blocking handling, and if the read/write
286
     pair is not consumed, it will continue in a follow-up pipe allocated in the
287
     next call to [Swappable.swap_reader].
288
  *)
289
  let%bind sync_ledger_reader = Swappable.swap_reader network_transition_pipe in
×
290
  let initial_root_transition =
×
291
    initial_root_transition |> Mina_block.Validated.remember
×
292
    |> Mina_block.Validation.reset_frontier_dependencies_validation
×
293
    |> Mina_block.Validation.reset_staged_ledger_diff_validation
294
  in
295
  let t =
×
296
    { network
297
    ; context = (module Context)
298
    ; trust_system
299
    ; verifier
300
    ; best_seen_transition = initial_root_transition
301
    ; current_root = initial_root_transition
302
    ; num_of_root_snarked_ledger_retargeted = 0
303
    }
304
  in
305
  let transition_graph = Transition_cache.create () in
306
  let temp_persistent_root_instance =
×
307
    Transition_frontier.Persistent_root.create_instance_exn persistent_root
308
  in
309
  let temp_snarked_ledger =
×
310
    Transition_frontier.Persistent_root.Instance.snarked_ledger
311
      temp_persistent_root_instance
312
  in
313
  (* step 1. download snarked_ledger *)
314
  let%bind sync_ledger_time, (hash, sender, expected_staged_ledger_hash) =
315
    download_snarked_ledger
×
316
      ~context:(module Context)
317
      ~trust_system ~preferred_peers ~transition_graph ~sync_ledger_reader t
318
      temp_snarked_ledger
319
  in
320
  Mina_metrics.(
×
321
    Counter.inc Bootstrap.root_snarked_ledger_sync_ms
×
322
      Time.Span.(to_ms sync_ledger_time)) ;
×
323
  Mina_metrics.(
324
    Gauge.set Bootstrap.num_of_root_snarked_ledger_retargeted
×
325
      (Float.of_int t.num_of_root_snarked_ledger_retargeted)) ;
×
326
  (* step 2. Download scan state and pending coinbases. *)
327
  let%bind ( staged_ledger_data_download_time
328
           , staged_ledger_construction_time
329
           , staged_ledger_aux_result ) =
330
    let%bind ( staged_ledger_data_download_time
331
             , staged_ledger_data_download_result ) =
332
      time_deferred
×
333
        (Mina_networking.get_staged_ledger_aux_and_pending_coinbases_at_hash
×
334
           t.network sender.peer_id hash )
335
    in
336
    match staged_ledger_data_download_result with
×
337
    | Error err ->
×
338
        Deferred.return (staged_ledger_data_download_time, None, Error err)
339
    | Ok
×
340
        ( scan_state_uncached
341
        , expected_merkle_root
342
        , pending_coinbases
343
        , protocol_states ) -> (
344
        let%map staged_ledger_construction_result =
345
          O1trace.thread "construct_root_staged_ledger" (fun () ->
×
346
              let open Deferred.Or_error.Let_syntax in
×
347
              let received_staged_ledger_hash =
348
                Staged_ledger_hash.of_aux_ledger_and_coinbase_hash
349
                  (Staged_ledger.Scan_state.Stable.Latest.hash
×
350
                     scan_state_uncached )
351
                  expected_merkle_root pending_coinbases
352
              in
353
              [%log debug]
×
354
                ~metadata:
355
                  [ ( "expected_staged_ledger_hash"
356
                    , Staged_ledger_hash.to_yojson expected_staged_ledger_hash
×
357
                    )
358
                  ; ( "received_staged_ledger_hash"
359
                    , Staged_ledger_hash.to_yojson received_staged_ledger_hash
×
360
                    )
361
                  ]
362
                "Comparing $expected_staged_ledger_hash to \
363
                 $received_staged_ledger_hash" ;
364
              let%bind new_root =
365
                t.current_root
366
                |> Mina_block.Validation.skip_frontier_dependencies_validation
×
367
                     `This_block_belongs_to_a_detached_subtree
368
                |> Mina_block.Validation.validate_staged_ledger_hash
×
369
                     (`Staged_ledger_already_materialized
370
                       received_staged_ledger_hash )
371
                |> Result.map_error ~f:(fun _ ->
×
372
                       Error.of_string "received faulty scan state from peer" )
×
373
                |> Deferred.return
×
374
              in
375
              let protocol_states =
×
376
                List.map protocol_states
377
                  ~f:(With_hash.of_data ~hash_data:Protocol_state.hashes)
378
              in
379
              let scan_state =
×
380
                Staged_ledger.Scan_state.write_all_proofs_to_disk
381
                  ~proof_cache_db scan_state_uncached
382
              in
383
              let%bind protocol_states =
384
                Staged_ledger.Scan_state.check_required_protocol_states
×
385
                  scan_state ~protocol_states
386
                |> Deferred.return
×
387
              in
388
              let protocol_states_map =
×
389
                protocol_states
390
                |> List.map ~f:(fun ps ->
×
391
                       (State_hash.With_state_hashes.state_hash ps, ps) )
×
392
                |> State_hash.Map.of_alist_exn
393
              in
394
              let get_state hash =
×
395
                match Map.find protocol_states_map hash with
×
396
                | None ->
×
397
                    let new_state_hash =
398
                      State_hash.With_state_hashes.state_hash (fst new_root)
×
399
                    in
400
                    [%log error]
×
401
                      ~metadata:
402
                        [ ("new_root", State_hash.to_yojson new_state_hash)
×
403
                        ; ("state_hash", State_hash.to_yojson hash)
×
404
                        ]
405
                      "Protocol state (for scan state transactions) for \
406
                       $state_hash not found when bootstrapping to the new \
407
                       root $new_root" ;
408
                    Or_error.errorf
×
409
                      !"Protocol state (for scan state transactions) for \
×
410
                        %{sexp:State_hash.t} not found when bootstrapping to \
411
                        the new root %{sexp:State_hash.t}"
412
                      hash new_state_hash
413
                | Some protocol_state ->
×
414
                    Ok (With_hash.data protocol_state)
×
415
              in
416
              (* step 3. Construct staged ledger from snarked ledger, scan state
417
                 and pending coinbases. *)
418
              (* Construct the staged ledger before constructing the transition
419
               * frontier in order to verify the scan state we received.
420
               * TODO: reorganize the code to avoid doing this twice (#3480) *)
421
              let open Deferred.Let_syntax in
422
              let%map staged_ledger_construction_time, construction_result =
423
                time_deferred
×
424
                  (let open Deferred.Let_syntax in
425
                  let temp_mask = Ledger.Root.as_masked temp_snarked_ledger in
426
                  let%map result =
427
                    Staged_ledger
428
                    .of_scan_state_pending_coinbases_and_snarked_ledger ~logger
429
                      ~snarked_local_state:
430
                        Mina_block.(
431
                          t.current_root |> Validation.block |> header
×
432
                          |> Header.protocol_state
×
433
                          |> Protocol_state.blockchain_state
×
434
                          |> Blockchain_state.snarked_local_state)
×
435
                      ~verifier ~constraint_constants ~scan_state
436
                      ~snarked_ledger:temp_mask ~expected_merkle_root
437
                      ~pending_coinbases ~get_state ~signature_kind
438
                  in
439
                  ignore
×
440
                    ( Ledger.Maskable.unregister_mask_exn ~loc:__LOC__ temp_mask
×
441
                      : Ledger.unattached_mask ) ;
442
                  Result.map result
443
                    ~f:
444
                      (const
×
445
                         ( scan_state
446
                         , pending_coinbases
447
                         , new_root
448
                         , protocol_states ) ))
449
              in
450
              Ok (staged_ledger_construction_time, construction_result) )
×
451
        in
452
        match staged_ledger_construction_result with
×
453
        | Error err ->
×
454
            (staged_ledger_data_download_time, None, Error err)
455
        | Ok (staged_ledger_construction_time, result) ->
×
456
            ( staged_ledger_data_download_time
457
            , Some staged_ledger_construction_time
458
            , result ) )
459
  in
460
  Transition_frontier.Persistent_root.Instance.close
×
461
    temp_persistent_root_instance ;
462
  match staged_ledger_aux_result with
×
463
  | Error e ->
×
464
      let%map () =
465
        Trust_system.(
466
          record t.trust_system logger sender
×
467
            Actions.
468
              ( Outgoing_connection_error
469
              , Some
470
                  ( "Can't find scan state from the peer or received faulty \
471
                     scan state from the peer."
472
                  , [] ) ))
473
      in
474
      [%log error]
×
475
        ~metadata:
476
          [ ("error", Error_json.error_to_yojson e)
×
477
          ; ("state_hash", State_hash.to_yojson hash)
×
478
          ; ( "expected_staged_ledger_hash"
479
            , Staged_ledger_hash.to_yojson expected_staged_ledger_hash )
×
480
          ]
481
        "Failed to find scan state for the transition with hash $state_hash \
482
         from the peer or received faulty scan state: $error. Retry bootstrap" ;
483
      let this_cycle =
×
484
        { cycle_result = "failed to download and construct scan state"
485
        ; sync_ledger_time
486
        ; staged_ledger_data_download_time
487
        ; staged_ledger_construction_time
488
        ; local_state_sync_required = false
489
        ; local_state_sync_time = None
490
        }
491
      in
492
      `Repeat (this_cycle :: previous_cycles)
493
  | Ok (scan_state, pending_coinbase, new_root, protocol_states) -> (
×
494
      let%bind () =
495
        Trust_system.(
496
          record t.trust_system logger sender
×
497
            Actions.
498
              ( Fulfilled_request
499
              , Some ("Received valid scan state from peer", []) ))
500
      in
501
      let best_seen_block_with_hash, _ = t.best_seen_transition in
×
502
      let consensus_state =
503
        With_hash.data best_seen_block_with_hash
×
504
        |> Mina_block.header |> Mina_block.Header.protocol_state
×
505
        |> Protocol_state.consensus_state
506
      in
507
      (* step 4. Synchronize consensus local state if necessary *)
508
      let%bind ( local_state_sync_time
509
               , (local_state_sync_required, local_state_sync_result) ) =
510
        time_deferred
×
511
          ( match
512
              Consensus.Hooks.required_local_state_sync
513
                ~constants:precomputed_values.consensus_constants
514
                ~consensus_state ~local_state:consensus_local_state
515
            with
516
          | None ->
×
517
              [%log debug]
×
518
                ~metadata:
519
                  [ ( "local_state"
520
                    , Consensus.Data.Local_state.to_yojson consensus_local_state
×
521
                    )
522
                  ; ( "consensus_state"
523
                    , Consensus.Data.Consensus_state.Value.to_yojson
×
524
                        consensus_state )
525
                  ]
526
                "Not synchronizing consensus local state" ;
527
              Deferred.return (false, Or_error.return ())
×
528
          | Some sync_jobs ->
×
529
              [%log info] "Synchronizing consensus local state" ;
×
530
              let%map result =
531
                Consensus.Hooks.sync_local_state
×
532
                  ~context:(module Context)
533
                  ~local_state:consensus_local_state ~trust_system
534
                  ~glue_sync_ledger:(Mina_networking.glue_sync_ledger t.network)
×
535
                  sync_jobs
536
              in
537
              (true, result) )
×
538
      in
539
      match local_state_sync_result with
×
540
      | Error e ->
×
541
          [%log error]
×
542
            ~metadata:[ ("error", Error_json.error_to_yojson e) ]
×
543
            "Local state sync failed: $error. Retry bootstrap" ;
544
          let this_cycle =
×
545
            { cycle_result = "failed to synchronize local state"
546
            ; sync_ledger_time
547
            ; staged_ledger_data_download_time
548
            ; staged_ledger_construction_time
549
            ; local_state_sync_required
550
            ; local_state_sync_time = Some local_state_sync_time
551
            }
552
          in
553
          Deferred.return (`Repeat (this_cycle :: previous_cycles))
554
      | Ok () ->
×
555
          (* step 5. Close the old frontier and reload a new one from disk. *)
556
          let new_root_data : Transition_frontier.Root_data.Limited.t =
557
            Transition_frontier.Root_data.Limited.create
558
              ~transition:(Mina_block.Validated.lift new_root)
×
559
              ~scan_state ~pending_coinbase ~protocol_states
560
          in
561
          let%bind () =
562
            Transition_frontier.Persistent_frontier.reset_database_exn
×
563
              persistent_frontier ~root_data:new_root_data
564
              ~genesis_state_hash:
565
                (State_hash.With_state_hashes.state_hash
×
566
                   precomputed_values.protocol_state_with_hashes )
567
          in
568
          (* TODO: lazy load db in persistent root to avoid unnecessary opens like this *)
569
          Transition_frontier.Persistent_root.(
×
570
            with_instance_exn persistent_root ~f:(fun instance ->
×
571
                Instance.set_root_state_hash instance
×
572
                @@ Mina_block.Validated.state_hash
×
573
                @@ Mina_block.Validated.lift new_root )) ;
×
574
          let%map new_frontier =
575
            let fail msg =
576
              failwith
×
577
                ( "failed to initialize transition frontier after \
578
                   bootstrapping: " ^ msg )
579
            in
580
            Transition_frontier.load
×
581
              ~context:(module Context)
582
              ~retry_with_fresh_db:false ~verifier ~consensus_local_state
583
              ~persistent_root ~persistent_frontier ~catchup_mode ()
584
            >>| function
×
585
            | Ok frontier ->
×
586
                frontier
587
            | Error (`Failure msg) ->
×
588
                fail msg
589
            | Error `Bootstrap_required ->
×
590
                fail
591
                  "bootstrap still required (indicates logical error in code)"
592
            | Error `Persistent_frontier_malformed ->
×
593
                fail "persistent frontier was malformed"
594
            | Error `Snarked_ledger_mismatch ->
×
595
                fail
596
                  "this should not happen, because we just reset the \
597
                   snarked_ledger"
598
          in
599
          [%str_log info] Bootstrap_complete ;
×
600
          let collected_transitions = Transition_cache.data transition_graph in
×
601
          let logger =
×
602
            Logger.extend logger
603
              [ ("context", `String "Filter collected transitions in bootstrap")
604
              ]
605
          in
606
          let root_consensus_state =
×
607
            Transition_frontier.(
608
              Breadcrumb.consensus_state_with_hashes (root new_frontier))
×
609
          in
610
          let filtered_collected_transitions =
611
            List.filter collected_transitions
612
              ~f:(fun (incoming_transition, _) ->
613
                let transition =
×
614
                  Envelope.Incoming.data incoming_transition
×
615
                  |> Transition_cache.header_with_hash
616
                in
617
                Consensus.Hooks.equal_select_status `Take
×
618
                @@ Consensus.Hooks.select
619
                     ~context:(module Context)
620
                     ~existing:root_consensus_state
621
                     ~candidate:
622
                       (With_hash.map
×
623
                          ~f:
624
                            (Fn.compose Protocol_state.consensus_state
×
625
                               Mina_block.Header.protocol_state )
626
                          transition ) )
627
          in
628
          [%log debug] "Sorting filtered transitions by consensus state"
×
629
            ~metadata:[] ;
630
          let sorted_filtered_collected_transitions =
×
631
            O1trace.sync_thread "sorting_collected_transitions" (fun () ->
632
                List.sort filtered_collected_transitions
×
633
                  ~compare:
634
                    (Comparable.lift
×
635
                       ~f:(fun (x, _) ->
636
                         Transition_cache.header_with_hash
×
637
                         @@ Envelope.Incoming.data x )
×
638
                       (external_transition_compare ~context:(module Context)) ) )
639
          in
640
          let this_cycle =
×
641
            { cycle_result = "success"
642
            ; sync_ledger_time
643
            ; staged_ledger_data_download_time
644
            ; staged_ledger_construction_time
645
            ; local_state_sync_required
646
            ; local_state_sync_time = Some local_state_sync_time
647
            }
648
          in
649
          `Finished
650
            ( this_cycle :: previous_cycles
651
            , (new_frontier, sorted_filtered_collected_transitions) ) )
652

653
(** The entry point function for bootstrap controller. When bootstrap finished
654
    it would return a transition frontier with the root breadcrumb and a list
655
    of transitions collected during bootstrap.
656

657
    Bootstrap controller would do the following steps to contrust the
658
    transition frontier:
659
    1. Download the root snarked_ledger.
660
    2. Download the scan state and pending coinbases.
661
    3. Construct the staged ledger from the snarked ledger, scan state and
662
       pending coinbases.
663
    4. Synchronize the consensus local state if necessary.
664
    5. Close the old frontier and reload a new one from disk.
665
 *)
666
let run ~context:(module Context : CONTEXT) ~trust_system ~verifier ~network
667
    ~consensus_local_state ~network_transition_pipe ~preferred_peers
668
    ~persistent_root ~persistent_frontier ~initial_root_transition ~catchup_mode
669
    ~signature_kind =
670
  let open Context in
×
671
  let run_cycle =
672
    run_cycle
673
      ~context:(module Context : CONTEXT)
674
      ~trust_system ~verifier ~network ~consensus_local_state
675
      ~network_transition_pipe ~preferred_peers ~persistent_root
676
      ~persistent_frontier ~initial_root_transition ~catchup_mode
677
      ~signature_kind
678
  in
679
  O1trace.thread "bootstrap"
680
  @@ fun () ->
681
  let%map time_elapsed, (cycles, result) =
682
    time_deferred @@ Deferred.repeat_until_finished [] run_cycle
×
683
  in
684
  [%log info] "Bootstrap completed in $time_elapsed: $bootstrap_stats"
×
685
    ~metadata:
686
      [ ("time_elapsed", Time.Span.to_yojson_hum time_elapsed)
×
687
      ; ( "bootstrap_stats"
688
        , `List (List.map ~f:bootstrap_cycle_stats_to_yojson cycles) )
×
689
      ] ;
690
  Mina_metrics.(
×
691
    Gauge.set Bootstrap.bootstrap_time_ms Core.Time.(Span.to_ms @@ time_elapsed)) ;
×
692
  result
693

694
let%test_module "Bootstrap_controller tests" =
695
  ( module struct
696
    let max_frontier_length =
697
      Transition_frontier.global_max_length Genesis_constants.For_unit_tests.t
×
698

699
    let logger = Logger.null ()
×
700

701
    let () =
702
      (* Disable log messages from best_tip_diff logger. *)
703
      Logger.Consumer_registry.register ~commit_id:""
×
704
        ~id:Logger.Logger_id.best_tip_diff ~processor:(Logger.Processor.raw ())
×
705
        ~transport:
706
          (Logger.Transport.create
×
707
             ( module struct
708
               type t = unit
709

710
               let transport () _ = ()
×
711
             end )
712
             () )
713
        ()
714

715
    let trust_system =
716
      let s = Trust_system.null () in
717
      don't_wait_for
×
718
        (Pipe_lib.Strict_pipe.Reader.iter
×
719
           (Trust_system.upcall_pipe s)
×
720
           ~f:(const Deferred.unit) ) ;
×
721
      s
×
722

723
    let precomputed_values = Lazy.force Precomputed_values.for_unit_tests
×
724

725
    let proof_level = precomputed_values.proof_level
726

727
    let constraint_constants = precomputed_values.constraint_constants
728

729
    let ledger_sync_config =
730
      Syncable_ledger.create_config
×
731
        ~compile_config:Mina_compile_config.For_unit_tests.t
732
        ~max_subtree_depth:None ~default_subtree_depth:None ()
733

734
    module Context = struct
735
      let logger = logger
736

737
      let precomputed_values = precomputed_values
738

739
      let constraint_constants =
740
        Genesis_constants.For_unit_tests.Constraint_constants.t
741

742
      let consensus_constants = precomputed_values.consensus_constants
743

744
      let ledger_sync_config =
745
        Syncable_ledger.create_config
×
746
          ~compile_config:Mina_compile_config.For_unit_tests.t
747
          ~max_subtree_depth:None ~default_subtree_depth:None ()
748

749
      let proof_cache_db = Proof_cache_tag.For_tests.create_db ()
×
750
    end
751

752
    let verifier =
753
      Async.Thread_safe.block_on_async_exn (fun () ->
×
754
          Verifier.For_tests.default ~constraint_constants ~logger ~proof_level
×
755
            () )
756

757
    module Genesis_ledger = (val precomputed_values.genesis_ledger)
758

759
    let downcast_transition ~sender transition =
760
      let transition =
×
761
        transition |> Mina_block.Validated.remember
×
762
        |> Mina_block.Validation.reset_frontier_dependencies_validation
×
763
        |> Mina_block.Validation.reset_staged_ledger_diff_validation
764
      in
765
      Envelope.Incoming.wrap ~data:transition
×
766
        ~sender:(Envelope.Sender.Remote sender)
767

768
    let downcast_breadcrumb ~sender breadcrumb =
769
      downcast_transition ~sender
×
770
        (Transition_frontier.Breadcrumb.validated_transition breadcrumb)
×
771

772
    let make_non_running_bootstrap ~genesis_root ~network =
773
      let transition =
×
774
        genesis_root
775
        |> Mina_block.Validation.reset_frontier_dependencies_validation
×
776
        |> Mina_block.Validation.reset_staged_ledger_diff_validation
777
      in
778
      { context = (module Context)
×
779
      ; trust_system
780
      ; verifier
781
      ; best_seen_transition = transition
782
      ; current_root = transition
783
      ; network
784
      ; num_of_root_snarked_ledger_retargeted = 0
785
      }
786

787
    let%test_unit "Bootstrap controller caches all transitions it is passed \
788
                   through the transition_reader" =
789
      let branch_size = (max_frontier_length * 2) + 2 in
×
790
      Quickcheck.test ~trials:1
791
        (let open Quickcheck.Generator.Let_syntax in
792
        (* we only need one node for this test, but we need more than one peer so that mina_networking does not throw an error *)
793
        let%bind fake_network =
794
          Fake_network.Generator.(
795
            gen ~precomputed_values ~verifier ~max_frontier_length
×
796
              ~ledger_sync_config [ fresh_peer; fresh_peer ])
797
        in
798
        let%map make_branch =
799
          Transition_frontier.Breadcrumb.For_tests.gen_seq ~precomputed_values
×
800
            ~verifier
801
            ~accounts_with_secret_keys:(Lazy.force Genesis_ledger.accounts)
×
802
            branch_size
803
        in
804
        let [ me; _ ] = fake_network.peer_networks in
×
805
        let branch =
806
          Async.Thread_safe.block_on_async_exn (fun () ->
807
              make_branch (Transition_frontier.root me.state.frontier) )
×
808
        in
809
        (fake_network, branch))
×
810
        ~f:(fun (fake_network, branch) ->
811
          let [ me; other ] = fake_network.peer_networks in
×
812
          let genesis_root =
813
            Transition_frontier.(
814
              Breadcrumb.validated_transition @@ root me.state.frontier)
×
815
            |> Mina_block.Validated.remember
816
          in
817
          let transition_graph = Transition_cache.create () in
×
818
          let sync_ledger_reader, sync_ledger_writer =
×
819
            Pipe_lib.Choosable_synchronous_pipe.create ()
820
          in
821
          let bootstrap =
×
822
            make_non_running_bootstrap ~genesis_root ~network:me.network
823
          in
824
          let root_sync_ledger =
825
            Sync_ledger.Root.create
826
              (Transition_frontier.root_snarked_ledger me.state.frontier)
×
827
              ~context:(module Context)
828
              ~trust_system
829
          in
830
          Async.Thread_safe.block_on_async_exn (fun () ->
×
831
              let sync_deferred =
×
832
                sync_ledger bootstrap ~root_sync_ledger ~transition_graph
833
                  ~preferred:[] ~sync_ledger_reader
834
              in
835
              let%bind sync_ledger_writer' =
836
                Deferred.List.fold ~init:sync_ledger_writer branch
×
837
                  ~f:(fun sync_ledger_writer breadcrumb ->
838
                    Pipe_lib.Choosable_synchronous_pipe.write sync_ledger_writer
×
839
                      ( `Block
840
                          (downcast_breadcrumb ~sender:other.peer breadcrumb)
×
841
                      , `Valid_cb None ) )
842
              in
843
              Pipe_lib.Choosable_synchronous_pipe.close sync_ledger_writer' ;
×
844
              sync_deferred ) ;
×
845
          let expected_transitions =
×
846
            List.map branch
847
              ~f:
848
                (Fn.compose
×
849
                   (With_hash.map ~f:Mina_block.header)
850
                   (Fn.compose Mina_block.Validation.block_with_hash
×
851
                      (Fn.compose Mina_block.Validated.remember
×
852
                         Transition_frontier.Breadcrumb.validated_transition ) ) )
853
          in
854
          let saved_transitions =
×
855
            Transition_cache.data transition_graph
×
856
            |> List.map ~f:(fun (x, _) ->
857
                   Transition_cache.header_with_hash @@ Envelope.Incoming.data x )
×
858
          in
859
          let module E = struct
×
860
            module T = struct
861
              type t = Mina_block.Header.t State_hash.With_state_hashes.t
×
862
              [@@deriving sexp]
863

864
              let compare = external_transition_compare ~context:(module Context)
865
            end
866

867
            include Comparable.Make (T)
868
          end in
869
          [%test_result: E.Set.t]
×
870
            (E.Set.of_list saved_transitions)
×
871
            ~expect:(E.Set.of_list expected_transitions) )
×
872

873
    let run_bootstrap ~timeout_duration ~my_net ~network_transition_pipe =
874
      let open Fake_network in
×
875
      let time_controller = Block_time.Controller.basic ~logger in
876
      let persistent_root =
877
        Transition_frontier.persistent_root my_net.state.frontier
878
      in
879
      let persistent_frontier =
×
880
        Transition_frontier.persistent_frontier my_net.state.frontier
881
      in
882
      let initial_root_transition =
×
883
        Transition_frontier.(
884
          Breadcrumb.validated_transition (root my_net.state.frontier))
×
885
      in
886
      let%bind () =
887
        Transition_frontier.close ~loc:__LOC__ my_net.state.frontier
×
888
      in
889
      [%log info] "bootstrap begin" ;
×
890
      Block_time.Timeout.await_exn time_controller ~timeout_duration
×
891
        (run
892
           ~context:(module Context)
893
           ~trust_system ~verifier ~network:my_net.network ~preferred_peers:[]
894
           ~consensus_local_state:my_net.state.consensus_local_state
895
           ~network_transition_pipe ~persistent_root ~persistent_frontier
896
           ~catchup_mode:`Super ~initial_root_transition ~signature_kind:Testnet )
897

898
    let assert_transitions_increasingly_sorted ~root
899
        (incoming_transitions : Transition_cache.element list) =
900
      let root =
×
901
        Transition_frontier.Breadcrumb.block root |> Mina_block.header
×
902
      in
903
      ignore
×
904
        ( List.fold_result ~init:root incoming_transitions
×
905
            ~f:(fun max_acc incoming_transition ->
906
              let With_hash.{ data = header; _ } =
×
907
                Transition_cache.header_with_hash
908
                  (Envelope.Incoming.data @@ fst incoming_transition)
×
909
              in
910
              let header_len h =
×
911
                Mina_block.Header.protocol_state h
×
912
                |> Protocol_state.consensus_state
×
913
                |> Consensus.Data.Consensus_state.blockchain_length
914
              in
915
              let open Result.Let_syntax in
916
              let%map () =
917
                Result.ok_if_true
×
918
                  Mina_numbers.Length.(header_len max_acc <= header_len header)
×
919
                  ~error:
920
                    (Error.of_string
×
921
                       "The blocks are not sorted in increasing order" )
922
              in
923
              header )
×
924
          |> Or_error.ok_exn
×
925
          : Mina_block.Header.t )
926

927
    let%test_unit "sync with one node after receiving a transition" =
928
      Quickcheck.test ~trials:1
×
929
        Fake_network.Generator.(
930
          gen ~precomputed_values ~verifier ~max_frontier_length
×
931
            ~ledger_sync_config
932
            [ fresh_peer
933
            ; peer_with_branch
934
                ~frontier_branch_size:((max_frontier_length * 2) + 2)
935
            ])
936
        ~f:(fun fake_network ->
937
          let [ my_net; peer_net ] = fake_network.peer_networks in
×
938
          let block =
939
            Envelope.Incoming.wrap
940
              ~data:
941
                ( Transition_frontier.best_tip peer_net.state.frontier
×
942
                |> Transition_frontier.Breadcrumb.validated_transition
×
943
                |> Mina_block.Validated.remember
×
944
                |> Mina_block.Validation.reset_frontier_dependencies_validation
×
945
                |> Mina_block.Validation.reset_staged_ledger_diff_validation )
×
946
              ~sender:(Envelope.Sender.Remote peer_net.peer)
947
          in
948
          let network_transition_pipe =
949
            Swappable.create ~name:(__MODULE__ ^ __LOC__)
950
              (Buffered (`Capacity 10, `Overflow (Drop_head ignore)))
951
          in
952
          Swappable.write network_transition_pipe (`Block block, `Valid_cb None) ;
×
953
          let new_frontier, sorted_external_transitions =
×
954
            Async.Thread_safe.block_on_async_exn (fun () ->
955
                run_bootstrap
×
956
                  ~timeout_duration:(Block_time.Span.of_ms 30_000L)
×
957
                  ~my_net ~network_transition_pipe )
958
          in
959
          assert_transitions_increasingly_sorted
×
960
            ~root:(Transition_frontier.root new_frontier)
×
961
            sorted_external_transitions ;
962
          [%test_result: Ledger_hash.t]
×
963
            ( Ledger.Root.merkle_root
×
964
            @@ Transition_frontier.root_snarked_ledger new_frontier )
×
965
            ~expect:
966
              ( Ledger.Root.merkle_root
×
967
              @@ Transition_frontier.root_snarked_ledger peer_net.state.frontier
×
968
              ) )
969

970
    let%test_unit "reconstruct staged_ledgers using \
971
                   of_scan_state_and_snarked_ledger" =
972
      Quickcheck.test ~trials:1
×
973
        (Transition_frontier.For_tests.gen ~precomputed_values ~verifier
×
974
           ~max_length:max_frontier_length ~size:max_frontier_length () )
975
        ~f:(fun frontier ->
976
          Thread_safe.block_on_async_exn
×
977
          @@ fun () ->
978
          Deferred.List.iter (Transition_frontier.all_breadcrumbs frontier)
×
979
            ~f:(fun breadcrumb ->
980
              let staged_ledger =
×
981
                Transition_frontier.Breadcrumb.staged_ledger breadcrumb
982
              in
983
              let expected_merkle_root =
×
984
                Staged_ledger.ledger staged_ledger |> Ledger.merkle_root
×
985
              in
986
              let snarked_ledger =
×
987
                Transition_frontier.root_snarked_ledger frontier
×
988
                |> Ledger.Root.as_masked
989
              in
990
              let snarked_local_state =
×
991
                Transition_frontier.root frontier
×
992
                |> Transition_frontier.Breadcrumb.protocol_state
×
993
                |> Protocol_state.blockchain_state
×
994
                |> Blockchain_state.snarked_local_state
995
              in
996
              let scan_state = Staged_ledger.scan_state staged_ledger in
×
997
              let get_state hash =
×
998
                match Transition_frontier.find_protocol_state frontier hash with
×
999
                | Some protocol_state ->
×
1000
                    Ok protocol_state
1001
                | None ->
×
1002
                    Or_error.errorf
1003
                      !"Protocol state (for scan state transactions) for \
×
1004
                        %{sexp:State_hash.t} not found"
1005
                      hash
1006
              in
1007
              let pending_coinbases =
1008
                Staged_ledger.pending_coinbase_collection staged_ledger
1009
              in
1010
              let%map actual_staged_ledger =
1011
                Staged_ledger.of_scan_state_pending_coinbases_and_snarked_ledger
1012
                  ~scan_state ~logger ~verifier ~constraint_constants
1013
                  ~snarked_ledger ~snarked_local_state ~expected_merkle_root
1014
                  ~pending_coinbases ~get_state ~signature_kind:Testnet
1015
                |> Deferred.Or_error.ok_exn
×
1016
              in
1017
              let height =
×
1018
                Transition_frontier.Breadcrumb.consensus_state breadcrumb
×
1019
                |> Consensus.Data.Consensus_state.blockchain_length
×
1020
                |> Mina_numbers.Length.to_int
1021
              in
1022
              [%test_eq: Staged_ledger_hash.t]
×
1023
                ~message:
1024
                  (sprintf "mismatch of staged ledger hash height %d" height)
×
1025
                (Transition_frontier.Breadcrumb.staged_ledger_hash breadcrumb)
×
1026
                (Staged_ledger.hash actual_staged_ledger) ) )
×
1027

1028
    (*
1029
    let%test_unit "if we see a new transition that is better than the \
1030
                   transition that we are syncing from, than we should \
1031
                   retarget our root" =
1032
      Quickcheck.test ~trials:1
1033
        Fake_network.Generator.(
1034
          gen ~max_frontier_length
1035
            [ fresh_peer
1036
            ; peer_with_branch ~frontier_branch_size:max_frontier_length
1037
            ; peer_with_branch
1038
                ~frontier_branch_size:((max_frontier_length * 2) + 2) ])
1039
        ~f:(fun fake_network ->
1040
          let [me; weaker_chain; stronger_chain] =
1041
            fake_network.peer_networks
1042
          in
1043
          let transition_reader, transition_writer =
1044
            Pipe_lib.Strict_pipe.create ~name:(__MODULE__ ^ __LOC__)
1045
              (Buffered (`Capacity 10, `Overflow Drop_head))
1046
          in
1047
          Envelope.Incoming.wrap
1048
            ~data:
1049
              ( Transition_frontier.best_tip weaker_chain.state.frontier
1050
              |> Transition_frontier.Breadcrumb.validated_transition
1051
              |> Mina_block.Validated.to_initial_validated )
1052
            ~sender:
1053
              (Envelope.Sender.Remote
1054
                 (weaker_chain.peer.host, weaker_chain.peer.peer_id))
1055
          |> Pipe_lib.Strict_pipe.Writer.write transition_writer ;
1056
          Envelope.Incoming.wrap
1057
            ~data:
1058
              ( Transition_frontier.best_tip stronger_chain.state.frontier
1059
              |> Transition_frontier.Breadcrumb.validated_transition
1060
              |> Mina_block.Validated.to_initial_validated )
1061
            ~sender:
1062
              (Envelope.Sender.Remote
1063
                 (stronger_chain.peer.host, stronger_chain.peer.peer_id))
1064
          |> Pipe_lib.Strict_pipe.Writer.write transition_writer ;
1065
          let new_frontier, sorted_external_transitions =
1066
            Async.Thread_safe.block_on_async_exn (fun () ->
1067
                run_bootstrap
1068
                  ~timeout_duration:(Block_time.Span.of_ms 60_000L)
1069
                  ~my_net:me ~transition_reader )
1070
          in
1071
          assert_transitions_increasingly_sorted
1072
            ~root:(Transition_frontier.root new_frontier)
1073
            sorted_external_transitions ;
1074
          [%test_result: Ledger_hash.t]
1075
            ( Ledger.Db.merkle_root
1076
            @@ Transition_frontier.root_snarked_ledger new_frontier )
1077
            ~expect:
1078
              ( Ledger.Db.merkle_root
1079
              @@ Transition_frontier.root_snarked_ledger
1080
                   stronger_chain.state.frontier ) )
1081
*)
1082
  end )
278✔
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