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

MinaProtocol / mina / 538

25 Aug 2025 05:35PM UTC coverage: 61.202% (+0.4%) from 60.772%
538

push

buildkite

web-flow
Merge pull request #17673 from MinaProtocol/amcie-merge-release320-to-master

amcie-merge-release320-to-master

3142 of 4828 new or added lines in 308 files covered. (65.08%)

205 existing lines in 68 files now uncovered.

50733 of 82894 relevant lines covered (61.2%)

470098.9 hits per line

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

1.6
/src/lib/testing/integration_test_lib/wait_condition.ml
1
open Core_kernel
2✔
2
open Mina_base
3

4
let all_equal ~equal ~compare ls =
5
  Option.value_map (List.hd ls) ~default:true ~f:(fun h ->
×
6
      List.equal equal [ h ] (List.find_all_dups ~compare ls) )
×
7

8
module Make
9
    (Engine : Intf.Engine.S)
10
    (Event_router : Intf.Dsl.Event_router_intf with module Engine := Engine)
11
    (Network_state : Intf.Dsl.Network_state_intf
12
                       with module Engine := Engine
13
                        and module Event_router := Event_router) =
14
struct
15
  open Network_state
16
  module Node = Engine.Network.Node
17

18
  type 'a predicate_result =
19
    | Predicate_passed
20
    | Predicate_continuation of 'a
21
    | Predicate_failure of Error.t
22

23
  (* NEED TO LIFT THIS UP OR FUNCTOR IT *)
24
  type predicate =
25
    | Network_state_predicate :
26
        (Network_state.t -> 'a predicate_result)
27
        * ('a -> Network_state.t -> 'a predicate_result)
28
        -> predicate
29
    | Event_predicate :
30
        'b Event_type.t * 'a * ('a -> Node.t -> 'b -> 'a predicate_result)
31
        -> predicate
32

33
  type wait_condition_id =
34
    | Nodes_to_initialize
35
    | Blocks_to_be_produced
36
    | Nodes_to_synchronize
37
    | Signed_command_to_be_included_in_frontier
38
    | Ledger_proofs_emitted_since_genesis
39
    | Block_height_growth
40
    | Zkapp_to_be_included_in_frontier
41
    | Persisted_frontier_loaded
42
    | Transition_frontier_loaded_from_persistence
43

44
  type t =
45
    { id : wait_condition_id
46
    ; description : string
47
    ; predicate : predicate
48
    ; soft_timeout : Network_time_span.t
49
    ; hard_timeout : Network_time_span.t
50
    }
51

52
  let with_timeouts ?soft_timeout ?hard_timeout t =
53
    { t with
×
54
      soft_timeout = Option.value soft_timeout ~default:t.soft_timeout
×
55
    ; hard_timeout = Option.value hard_timeout ~default:t.hard_timeout
×
56
    }
57

58
  let network_state ~id ~description ~(f : Network_state.t -> bool) : t =
59
    let check () (state : Network_state.t) =
×
60
      if f state then Predicate_passed else Predicate_continuation ()
×
61
    in
62
    { id
63
    ; description
64
    ; predicate = Network_state_predicate (check (), check)
×
65
    ; soft_timeout = Literal (Time.Span.of_hr 1.0)
×
66
    ; hard_timeout = Literal (Time.Span.of_hr 2.0)
×
67
    }
68

69
  let wait_condition_id t = t.id
×
70

71
  let nodes_to_initialize nodes =
72
    let open Network_state in
×
73
    network_state ~id:Nodes_to_initialize
74
      ~description:
75
        ( nodes |> List.map ~f:Node.id |> String.concat ~sep:", "
×
76
        |> sprintf "[%s] to initialize" )
×
77
      ~f:(fun (state : Network_state.t) ->
78
        List.for_all nodes ~f:(fun node ->
×
79
            String.Map.find state.node_initialization (Node.id node)
×
80
            |> Option.value ~default:false ) )
81
    |> with_timeouts
82
         ~soft_timeout:(Literal (Time.Span.of_min 10.0))
×
83
         ~hard_timeout:(Literal (Time.Span.of_min 15.0))
×
84

85
  let node_to_initialize node = nodes_to_initialize [ node ]
×
86

87
  (* let blocks_produced ?(active_stake_percentage = 1.0) n = *)
88
  let blocks_to_be_produced n =
89
    let init state = Predicate_continuation state.blocks_generated in
×
90
    let check init_blocks_generated state =
91
      if state.blocks_generated - init_blocks_generated >= n then
×
92
        Predicate_passed
×
93
      else Predicate_continuation init_blocks_generated
×
94
    in
95
    let soft_timeout_in_slots =
96
      (* We add 1 here to make sure that we see the entirety of at least 2*n
97
         full slots, since slot time may be misaligned with wait times after
98
         non-block-related waits.
99
         This ensures that low numbers of blocks (e.g. 1 or 2) have a
100
         reasonable probability of success, reducing flakiness of the tests.
101
      *)
102
      (2 * n) + 1
103
    in
104
    { id = Blocks_to_be_produced
105
    ; description = sprintf "%d blocks to be produced" n
×
106
    ; predicate = Network_state_predicate (init, check)
107
    ; soft_timeout = Slots soft_timeout_in_slots
108
    ; hard_timeout = Slots (soft_timeout_in_slots * 2)
109
    }
110

111
  let transition_frontier_loaded_from_persistence ~fresh_data ~sync_needed =
112
    let init state =
×
113
      Predicate_continuation
×
114
        ( state.num_persisted_frontier_loaded
115
        , state.num_persisted_frontier_fresh_boot
116
        , state.num_bootstrap_required
117
        , state.num_persisted_frontier_dropped
118
        , state.num_transition_frontier_loaded_from_persistence )
119
    in
120

121
    let check init state =
122
      let ( num_init_persisted_frontier_loaded
×
123
          , num_init_persisted_frontier_fresh_boot
124
          , num_init_bootstrap_required
125
          , num_init_persisted_frontier_dropped
126
          , num_init_transition_frontier_loaded_from_persistence ) =
127
        init
128
      in
129

130
      let fresh_data_condition =
131
        if fresh_data then
132
          state.num_persisted_frontier_fresh_boot
×
133
          > num_init_persisted_frontier_fresh_boot
134
          && state.num_persisted_frontier_dropped
×
135
             > num_init_persisted_frontier_dropped
136
        else
137
          state.num_persisted_frontier_fresh_boot
×
138
          = num_init_persisted_frontier_fresh_boot
139
          && state.num_persisted_frontier_dropped
×
140
             = num_init_persisted_frontier_dropped
141
      in
142
      let sync_needed_condition =
143
        if sync_needed then
144
          state.num_bootstrap_required >= num_init_bootstrap_required
×
145
        else state.num_bootstrap_required = num_init_bootstrap_required
×
146
      in
147
      if
148
        fresh_data_condition && sync_needed_condition
×
149
        && state.num_persisted_frontier_loaded
×
150
           > num_init_persisted_frontier_loaded
151
        && state.num_transition_frontier_loaded_from_persistence
×
152
           > num_init_transition_frontier_loaded_from_persistence
153
      then Predicate_passed
×
154
      else
155
        Predicate_continuation
×
156
          ( num_init_persisted_frontier_loaded
157
          , num_init_persisted_frontier_fresh_boot
158
          , num_init_bootstrap_required
159
          , num_init_persisted_frontier_dropped
160
          , num_init_transition_frontier_loaded_from_persistence )
161
    in
162
    { id = Transition_frontier_loaded_from_persistence
163
    ; description =
164
        sprintf
×
165
          "Transition frontier loaded with 'fresh_data' set to %b and \
166
           'sync_needed' set to %b"
167
          fresh_data sync_needed
168
    ; predicate = Network_state_predicate (init, check)
169
    ; soft_timeout = Literal (Time.Span.of_min 10.0)
×
170
    ; hard_timeout = Literal (Time.Span.of_min 15.0)
×
171
    }
172

173
  let block_height_growth ~height_growth =
174
    (* block height is an objective measurement for the whole chain.  block height growth checks that the block height increased by the desired_height since the wait condition was called *)
175
    let init state = Predicate_continuation state.block_height in
×
176
    let check initial_height (state : Network_state.t) =
177
      if state.block_height - initial_height >= height_growth then
×
178
        Predicate_passed
×
179
      else Predicate_continuation initial_height
×
180
    in
181
    let description =
182
      sprintf "chain block height greater than equal to [%d] " height_growth
183
    in
184
    let soft_timeout_in_slots = (2 * height_growth) + 1 in
×
185
    { id = Block_height_growth
186
    ; description
187
    ; predicate = Network_state_predicate (init, check)
188
    ; soft_timeout = Slots soft_timeout_in_slots
189
    ; hard_timeout = Slots (soft_timeout_in_slots * 2)
190
    }
191

192
  let nodes_to_synchronize (nodes : Node.t list) =
193
    let check () state =
×
194
      let all_best_tips_equal =
×
195
        all_equal ~equal:[%equal: State_hash.t option]
×
196
          ~compare:[%compare: State_hash.t option]
×
197
      in
198
      let best_tips =
199
        List.map nodes ~f:(fun node ->
200
            String.Map.find state.best_tips_by_node (Node.id node) )
×
201
      in
202
      if
×
203
        List.for_all best_tips ~f:Option.is_some
×
204
        && all_best_tips_equal best_tips
×
205
      then Predicate_passed
×
206
      else Predicate_continuation ()
×
207
    in
208
    let soft_timeout_in_slots = 4 in
209
    let hard_timeout_in_slots = 6 in
210
    let formatted_nodes =
211
      nodes
212
      |> List.map ~f:(fun node -> "\"" ^ Node.id node ^ "\"")
×
213
      |> String.concat ~sep:", "
214
    in
215
    { id = Nodes_to_synchronize
×
216
    ; description = sprintf "%s to synchronize" formatted_nodes
×
217
    ; predicate = Network_state_predicate (check (), check)
×
218
    ; soft_timeout = Slots soft_timeout_in_slots
219
    ; hard_timeout = Slots hard_timeout_in_slots
220
    }
221

222
  let signed_command_to_be_included_in_frontier ~txn_hash
223
      ~(node_included_in : [ `Any_node | `Node of Node.t ]) =
224
    let check () state =
×
225
      let blocks_with_txn_set_opt =
×
226
        Map.find state.blocks_including_txn txn_hash
227
      in
228
      match blocks_with_txn_set_opt with
×
229
      | None ->
×
230
          Predicate_continuation ()
231
      | Some blocks_with_txn_set -> (
×
232
          match node_included_in with
233
          | `Any_node ->
×
234
              Predicate_passed
235
          | `Node n ->
×
236
              let blocks_seen_by_n =
237
                Map.find state.blocks_seen_by_node (Node.id n)
×
238
                |> Option.value ~default:State_hash.Set.empty
239
              in
240
              let intersection =
×
241
                State_hash.Set.inter blocks_with_txn_set blocks_seen_by_n
242
              in
243
              if State_hash.Set.is_empty intersection then
×
244
                Predicate_continuation ()
×
245
              else Predicate_passed )
×
246
    in
247

248
    let soft_timeout_in_slots = 8 in
249
    { id = Signed_command_to_be_included_in_frontier
250
    ; description =
251
        sprintf "signed command with hash %s"
×
252
          (Mina_transaction.Transaction_hash.to_base58_check txn_hash)
×
253
    ; predicate = Network_state_predicate (check (), check)
×
254
    ; soft_timeout = Slots soft_timeout_in_slots
255
    ; hard_timeout = Slots (soft_timeout_in_slots * 2)
256
    }
257

258
  let ledger_proofs_emitted_since_genesis ~test_config ~num_proofs =
259
    let open Network_state in
×
260
    let check () (state : Network_state.t) =
261
      if state.snarked_ledgers_generated >= num_proofs then Predicate_passed
×
262
      else Predicate_continuation ()
×
263
    in
264
    let description =
265
      sprintf "[%d] snarked_ledgers to be generated since genesis" num_proofs
266
    in
267
    let slots_for_first_proof =
×
268
      Test_config.(
269
        slots_for_blocks @@ blocks_for_first_ledger_proof test_config)
×
270
    in
271
    let slots_for_additional_proofs =
272
      Test_config.slots_for_blocks (num_proofs - 1)
273
    in
274
    let total_slots = slots_for_first_proof + slots_for_additional_proofs in
×
275
    { id = Ledger_proofs_emitted_since_genesis
276
    ; description
277
    ; predicate = Network_state_predicate (check (), check)
×
278
    ; soft_timeout = Network_time_span.Slots (total_slots + 6)
279
    ; hard_timeout = Network_time_span.Slots (total_slots + 12)
280
    }
281

282
  let zkapp_to_be_included_in_frontier ~has_failures ~zkapp_command =
283
    (* TODO: don't use read_all_proofs_from_disk, instead avoid computing hashes on the level above *)
NEW
284
    let zkapp_command = Zkapp_command.read_all_proofs_from_disk zkapp_command in
×
285
    let txn_hash =
×
286
      Mina_transaction.Transaction_hash.hash_zkapp_command zkapp_command
287
    in
288
    let check () _node (breadcrumb_added : Event_type.Breadcrumb_added.t) =
×
289
      let zkapp_opt =
×
290
        List.find breadcrumb_added.transaction_hashes
291
          ~f:
292
            (Fn.compose
×
293
               (Mina_transaction.Transaction_hash.equal txn_hash)
×
294
               With_status.data )
295
      in
296
      [%log' info (Logger.create ())]
×
297
        "Looking for a zkApp transaction match for txn $txn with hash \
298
         $txn_hash in block with state_hash $state_hash"
299
        ~metadata:
300
          [ ("state_hash", State_hash.to_yojson breadcrumb_added.state_hash)
×
301
          ; ("txn_hash", Mina_transaction.Transaction_hash.to_yojson txn_hash)
×
NEW
302
          ; ("txn", Zkapp_command.Stable.Latest.to_yojson zkapp_command)
×
303
          ] ;
304
      [%log' debug (Logger.create ())]
×
305
        "wait_condition check, zkapp_to_be_included_in_frontier, \
306
         zkapp_command: $zkapp_command "
307
        ~metadata:
308
          [ ( "zkapp_command"
NEW
309
            , Zkapp_command.Stable.Latest.to_yojson zkapp_command )
×
310
          ] ;
311
      [%log' debug (Logger.create ())]
×
312
        "wait_condition check, zkapp_to_be_included_in_frontier, user_commands \
313
         from breadcrumb: $tx_hashes state_hash: $state_hash"
314
        ~metadata:
315
          [ ( "tx_hashes"
316
            , `List
317
                (List.map breadcrumb_added.transaction_hashes
×
318
                   ~f:
319
                     (With_status.to_yojson
×
320
                        Mina_transaction.Transaction_hash.to_yojson ) ) )
321
          ; ("state_hash", State_hash.to_yojson breadcrumb_added.state_hash)
×
322
          ] ;
323
      match zkapp_opt with
×
324
      | Some hash_with_status ->
×
325
          [%log' debug (Logger.create ())]
×
326
            "wait_condition check, zkapp_to_be_included_in_frontier, \
327
             cmd_with_status: $cmd_with_status "
328
            ~metadata:
329
              [ ( "cmd_with_status"
330
                , (With_status.to_yojson
×
331
                     Mina_transaction.Transaction_hash.to_yojson )
332
                    hash_with_status )
333
              ] ;
334
          let actual_status = hash_with_status.With_status.status in
×
335
          let successful =
336
            match actual_status with
337
            | Transaction_status.Applied ->
×
338
                not has_failures
339
            | Failed _ ->
×
340
                has_failures
341
          in
342
          if successful then Predicate_passed
×
343
          else
344
            Predicate_failure
×
345
              (Error.createf "Unexpected status in matching payment: %s"
×
346
                 ( Transaction_status.to_yojson actual_status
×
347
                 |> Yojson.Safe.to_string ) )
×
348
      | None ->
×
349
          Predicate_continuation ()
350
    in
351
    let soft_timeout_in_slots = 8 in
352
    let is_first = ref true in
353
    { id = Zkapp_to_be_included_in_frontier
354
    ; description =
355
        sprintf "zkApp with fee payer %s and other zkapp_command (%s), memo: %s"
×
356
          (Signature_lib.Public_key.Compressed.to_base58_check
×
357
             zkapp_command.fee_payer.body.public_key )
358
          (Zkapp_command.Call_forest.Tree.fold_forest ~init:""
×
359
             zkapp_command.account_updates ~f:(fun acc account_update ->
360
               let str =
×
361
                 Signature_lib.Public_key.Compressed.to_base58_check
362
                   account_update.body.public_key
363
               in
364
               if !is_first then (
×
365
                 is_first := false ;
366
                 str )
367
               else acc ^ ", " ^ str ) )
×
368
          (Signed_command_memo.to_string_hum zkapp_command.memo)
×
369
    ; predicate = Event_predicate (Event_type.Breadcrumb_added, (), check)
370
    ; soft_timeout = Slots soft_timeout_in_slots
371
    ; hard_timeout = Slots (soft_timeout_in_slots * 2)
372
    }
373

374
  let persisted_frontier_loaded node =
375
    let check () event_node
×
376
        (_frontier_loaded : Event_type.Persisted_frontier_loaded.t) =
377
      let event_node_id = Node.id event_node in
×
378
      let node_id = Node.id node in
×
379
      if String.equal event_node_id node_id then Predicate_passed
×
380
      else Predicate_continuation ()
×
381
    in
382
    let soft_timeout_in_slots = 4 in
383
    { id = Persisted_frontier_loaded
384
    ; description = "persisted transition frontier to load"
385
    ; predicate =
386
        Event_predicate (Event_type.Persisted_frontier_loaded, (), check)
387
    ; soft_timeout = Slots soft_timeout_in_slots
388
    ; hard_timeout = Slots (soft_timeout_in_slots * 2)
389
    }
390
end
2✔
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