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

MinaProtocol / mina / 767

04 Nov 2025 01:59PM UTC coverage: 32.374% (-4.5%) from 36.902%
767

push

buildkite

web-flow
Merge pull request #18063 from MinaProtocol/lyh/compat-into-dev-nov4-2025

Merge compatible into develop Nov. 4th 2025

87 of 228 new or added lines in 10 files covered. (38.16%)

3416 existing lines in 136 files now uncovered.

23591 of 72871 relevant lines covered (32.37%)

26590.67 hits per line

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

0.77
/src/lib/transition_handler/validator.ml
1
open Async_kernel
112✔
2
open Core_kernel
3
open Pipe_lib.Strict_pipe
4
open Mina_base
5
open Mina_state
6
open Mina_block
7
open Network_peer
8

9
module type CONTEXT = sig
10
  val logger : Logger.t
11

12
  val precomputed_values : Precomputed_values.t
13

14
  val constraint_constants : Genesis_constants.Constraint_constants.t
15

16
  val consensus_constants : Consensus.Constants.t
17
end
18

19
let validate_header_is_relevant ~context:(module Context : CONTEXT) ~frontier
20
    ~slot_chain_end header_with_hash =
21
  let open Result.Let_syntax in
×
22
  let module Context = struct
23
    include Context
24

25
    let logger =
26
      Logger.extend logger
×
27
        [ ("selection_context", `String "Transition_handler.Validator") ]
28
  end in
29
  let transition_hash =
30
    State_hash.With_state_hashes.state_hash header_with_hash
31
  in
32
  [%log' internal Context.logger] "Validate_transition" ;
×
33
  let get_consensus_constants h =
×
34
    Mina_block.Header.protocol_state h |> Protocol_state.consensus_state
×
35
  in
36
  let header = With_hash.data header_with_hash in
37
  let block_slot =
×
38
    Consensus.Data.Consensus_state.curr_global_slot
39
    @@ Protocol_state.consensus_state
×
40
    @@ Header.protocol_state header
×
41
  in
42
  let%bind () =
43
    match slot_chain_end with
44
    | Some slot_chain_end
×
45
      when Mina_numbers.Global_slot_since_hard_fork.(
46
             block_slot >= slot_chain_end) ->
47
        [%log' internal Context.logger] "Block after slot_chain_end, rejecting" ;
×
48
        Result.fail `Block_after_after_stop_slot
×
49
    | None | Some _ ->
×
50
        Result.return ()
×
51
  in
52
  let blockchain_length = Mina_block.Header.blockchain_length header in
×
53
  let root_breadcrumb = Transition_frontier.root frontier in
×
54
  [%log' internal Context.logger] "@block_metadata"
×
55
    ~metadata:
56
      [ ("blockchain_length", Mina_numbers.Length.to_yojson blockchain_length) ] ;
×
57
  [%log' internal Context.logger] "Check_transition_not_in_frontier" ;
×
58
  let open Result.Let_syntax in
×
59
  let%bind () =
60
    Option.fold
×
61
      (Transition_frontier.find frontier transition_hash)
×
62
      ~init:Result.(Ok ())
63
      ~f:(fun _ _ -> Result.Error (`In_frontier transition_hash))
×
64
  in
65
  [%log' internal Context.logger] "Check_transition_can_be_connected" ;
×
66
  Result.ok_if_true
×
67
    (Consensus.Hooks.equal_select_status `Take
×
68
       (Consensus.Hooks.select
69
          ~context:(module Context)
70
          ~existing:
71
            (Transition_frontier.Breadcrumb.consensus_state_with_hashes
×
72
               root_breadcrumb )
73
          ~candidate:(With_hash.map ~f:get_consensus_constants header_with_hash) ) )
×
74
    ~error:`Disconnected
75

76
let validate_transition_is_relevant ~context:(module Context : CONTEXT)
77
    ~frontier ~unprocessed_transition_cache ~slot_tx_end ~slot_chain_end
78
    enveloped_transition =
79
  let open Result.Let_syntax in
×
80
  [%log' internal Context.logger] "Validate_transition" ;
×
81
  let transition =
×
82
    Envelope.Incoming.data enveloped_transition
×
83
    |> Mina_block.Validation.block_with_hash
84
  in
85
  let transition_data = With_hash.data transition in
×
86
  let block_slot =
×
87
    Consensus.Data.Consensus_state.curr_global_slot
88
    @@ Protocol_state.consensus_state @@ Header.protocol_state
×
89
    @@ Mina_block.header transition_data
×
90
  in
91
  let%bind () =
92
    match slot_tx_end with
93
    | Some slot_tx_end
×
94
      when Mina_numbers.Global_slot_since_hard_fork.(block_slot >= slot_tx_end)
95
      ->
96
        [%log' internal Context.logger]
×
97
          "Block after slot_tx_end, validating it is empty" ;
98
        let staged_ledger_diff =
×
99
          Body.staged_ledger_diff @@ body transition_data
×
100
        in
101
        Result.ok_if_true
×
102
          (Staged_ledger_diff.is_empty staged_ledger_diff)
×
103
          ~error:`Non_empty_staged_ledger_diff_after_stop_slot
104
    | None | Some _ ->
×
105
        Result.(Ok ())
106
  in
107
  [%log' internal Context.logger] "Check_transition_not_in_process" ;
×
108
  let%bind () =
109
    Option.fold
×
110
      (Unprocessed_transition_cache.final_state unprocessed_transition_cache
×
111
         enveloped_transition )
112
      ~init:Result.(Ok ())
113
      ~f:(fun _ final_state -> Result.Error (`In_process final_state))
×
114
  in
115
  let header_with_hash = With_hash.map ~f:Mina_block.header transition in
×
116
  let%map () =
117
    validate_header_is_relevant
×
118
      ~context:(module Context)
119
      ~frontier ~slot_chain_end header_with_hash
120
  in
121
  [%log' internal Context.logger] "Register_transition_for_processing" ;
×
122
  (* we expect this to be Ok since we just checked the cache *)
123
  Unprocessed_transition_cache.register_exn unprocessed_transition_cache
×
124
    enveloped_transition
125

126
let validate_transition_or_header_is_relevant
127
    ~context:(module Context : CONTEXT) ~slot_tx_end ~slot_chain_end ~frontier
128
    ~unprocessed_transition_cache b_or_h =
129
  match b_or_h with
×
130
  | `Block b ->
×
131
      Result.map ~f:(fun cached_block -> `Block cached_block)
×
132
      @@ validate_transition_is_relevant
×
133
           ~context:(module Context)
134
           ~slot_tx_end ~slot_chain_end ~frontier ~unprocessed_transition_cache
135
           b
136
  | `Header h ->
×
137
      let header_with_hash, _ = Envelope.Incoming.data h in
138
      Result.map ~f:(fun () -> `Header h)
×
139
      @@ validate_header_is_relevant
×
140
           ~context:(module Context)
141
           ~slot_chain_end ~frontier header_with_hash
142

143
let run ~context:(module Context : CONTEXT) ~trust_system ~time_controller
144
    ~frontier ~transition_reader ~valid_transition_writer
145
    ~unprocessed_transition_cache =
UNCOV
146
  let open Context in
×
147
  let module Lru = Core_extended_cache.Lru in
148
  let outdated_root_cache = Lru.create ~destruct:None 1000 in
UNCOV
149
  O1trace.background_thread "validate_blocks_against_frontier" (fun () ->
×
UNCOV
150
      Pipe_lib.Choosable_synchronous_pipe.iter transition_reader
×
151
        ~f:(fun (b_or_h, `Valid_cb vc) ->
152
          let header_with_hash, sender =
×
153
            match b_or_h with
154
            | `Block b ->
×
155
                let block_with_hash, _ = Envelope.Incoming.data b in
156
                ( With_hash.map ~f:Mina_block.header block_with_hash
×
157
                , Envelope.Incoming.sender b )
×
158
            | `Header h ->
×
159
                let header_with_hash, _ = Envelope.Incoming.data h in
160
                (header_with_hash, Envelope.Incoming.sender h)
×
161
          in
162
          let header = With_hash.data header_with_hash in
163
          let transition_hash =
×
164
            State_hash.With_state_hashes.state_hash header_with_hash
165
          in
166
          Internal_tracing.Context_call.with_call_id
×
167
            ~tag:"transition_handler_validator"
168
          @@ fun () ->
169
          Internal_tracing.with_state_hash transition_hash
×
170
          @@ fun () ->
171
          let slot_tx_end =
×
172
            Runtime_config.slot_tx_end
173
              precomputed_values.Precomputed_values.runtime_config
174
          in
175
          let slot_chain_end =
×
176
            Runtime_config.slot_chain_end precomputed_values.runtime_config
177
          in
178
          match
×
179
            validate_transition_or_header_is_relevant
180
              ~context:(module Context)
181
              ~frontier ~unprocessed_transition_cache ~slot_tx_end
182
              ~slot_chain_end b_or_h
183
          with
184
          | Ok b_or_h' ->
×
185
              let%map () =
186
                Trust_system.record_envelope_sender trust_system logger sender
×
187
                  ( Trust_system.Actions.Sent_useful_gossip
188
                  , Some
189
                      ( "external transition $state_hash"
190
                      , [ ("state_hash", State_hash.to_yojson transition_hash)
×
191
                        ; ("header", Mina_block.Header.to_yojson header)
×
192
                        ] ) )
193
              in
194
              let transition_time =
×
195
                Mina_block.Header.protocol_state header
×
196
                |> Protocol_state.blockchain_state |> Blockchain_state.timestamp
×
197
                |> Block_time.to_time_exn
198
              in
199
              Perf_histograms.add_span
×
200
                ~name:"accepted_transition_remote_latency"
201
                (Core_kernel.Time.diff
×
202
                   Block_time.(now time_controller |> to_time_exn)
×
203
                   transition_time ) ;
204
              [%log internal] "Validate_transition_done" ;
×
205
              Writer.write valid_transition_writer (b_or_h', `Valid_cb vc)
×
206
          | Error (`In_frontier _) | Error (`In_process _) ->
×
207
              [%log internal] "Failure"
×
208
                ~metadata:[ ("reason", `String "In_frontier or In_process") ] ;
209
              Trust_system.record_envelope_sender trust_system logger sender
×
210
                ( Trust_system.Actions.Sent_old_gossip
211
                , Some
212
                    ( "external transition with state hash $state_hash"
213
                    , [ ("state_hash", State_hash.to_yojson transition_hash)
×
214
                      ; ("header", Mina_block.Header.to_yojson header)
×
215
                      ] ) )
216
          | Error `Disconnected ->
×
217
              [%log internal] "Failure"
×
218
                ~metadata:[ ("reason", `String "Disconnected") ] ;
219
              Mina_metrics.(Counter.inc_one Rejected_blocks.worse_than_root) ;
×
220
              let protocol_state = Mina_block.Header.protocol_state header in
221
              [%log error]
×
222
                ~metadata:
223
                  [ ("state_hash", State_hash.to_yojson transition_hash)
×
224
                  ; ("reason", `String "not selected over current root")
225
                  ; ( "protocol_state"
226
                    , Mina_block.Header.protocol_state header
×
227
                      |> Protocol_state.value_to_yojson )
×
228
                  ]
229
                "Validation error: external transition with state hash \
230
                 $state_hash was rejected for reason $reason" ;
231
              let is_in_root_history =
×
232
                let open Transition_frontier.Extensions in
233
                get_extension
×
234
                  (Transition_frontier.extensions frontier)
×
235
                  Root_history
236
                |> Root_history.mem
×
237
              in
238
              let parent_hash =
239
                Protocol_state.previous_state_hash protocol_state
240
              in
241
              let action =
×
242
                if
243
                  is_in_root_history transition_hash
×
244
                  || Option.is_some
×
245
                       (Lru.find outdated_root_cache transition_hash)
×
246
                then Trust_system.Actions.Sent_old_gossip
×
247
                else if
×
248
                  is_in_root_history parent_hash
×
249
                  || Option.is_some (Lru.find outdated_root_cache parent_hash)
×
250
                then (
×
251
                  Lru.add outdated_root_cache ~key:transition_hash ~data:() ;
252
                  Sent_useless_gossip )
×
253
                else Disconnected_chain
×
254
              in
255
              Trust_system.record_envelope_sender trust_system logger sender
256
                ( action
257
                , Some
258
                    ( "received transition that was not connected to our chain \
259
                       from $sender"
260
                    , [ ("sender", Envelope.Sender.to_yojson sender)
×
261
                      ; ("header", Mina_block.Header.to_yojson header)
×
262
                      ] ) )
263
          | Error `Non_empty_staged_ledger_diff_after_stop_slot ->
×
264
              [%log error]
×
265
                ~metadata:
266
                  [ ("state_hash", State_hash.to_yojson transition_hash)
×
267
                  ; ( "reason"
268
                    , `String "not empty staged ledger diff after slot_tx_end"
269
                    )
270
                  ; ( "block_slot"
271
                    , Mina_numbers.Global_slot_since_hard_fork.to_yojson
×
272
                      @@ Consensus.Data.Consensus_state.curr_global_slot
×
273
                      @@ Protocol_state.consensus_state
×
274
                      @@ Header.protocol_state header )
×
275
                  ]
276
                "Validation error: external transition with state hash \
277
                 $state_hash was rejected for reason $reason" ;
278
              Deferred.unit
×
279
          | Error `Block_after_after_stop_slot ->
×
280
              [%log error]
×
281
                ~metadata:
282
                  [ ("state_hash", State_hash.to_yojson transition_hash)
×
283
                  ; ("reason", `String "block after slot_chain_end")
284
                  ; ( "block_slot"
285
                    , Mina_numbers.Global_slot_since_hard_fork.to_yojson
×
286
                      @@ Consensus.Data.Consensus_state.curr_global_slot
×
287
                      @@ Protocol_state.consensus_state
×
288
                      @@ Header.protocol_state header )
×
289
                  ]
290
                "Validation error: external transition with state hash \
291
                 $state_hash was rejected for reason $reason" ;
292
              Deferred.unit ) )
×
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