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

MinaProtocol / mina / 300

19 May 2025 10:09PM UTC coverage: 60.768% (+24.9%) from 35.868%
300

push

buildkite

web-flow
Merge pull request #17209 from MinaProtocol/dkijania/once_again_fix_rosetta

[Rosetta fix] Install git & revert rosetta -> mesh change

49852 of 82036 relevant lines covered (60.77%)

472500.54 hits per line

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

66.06
/src/app/replayer/replayer.ml
1
(* replayer.ml -- replay transactions from archive node database *)
2

3
open Core
4
open Async
5
open Mina_base
6
module Ledger = Mina_ledger.Ledger
7
module Processor = Archive_lib.Processor
8
module Load_data = Archive_lib.Load_data
9
module Account_comparables = Comparable.Make_binable (Account.Stable.Latest)
10
module Account_set = Account_comparables.Set
11

12
(* identify a target block B containing staking and next epoch ledgers
13
   to be used in a hard fork, by giving its state hash
14

15
   from B, we choose a predecessor block B_fork, which is the block to
16
   fork from
17

18
   we replay all commands, one by one, from the genesis block through
19
   B_fork
20

21
   when the Merkle root of the replay ledger matches one of the
22
   epoch ledger hashes, we make a copy of the replay ledger to
23
   become that target epoch ledger
24

25
   when all commands from a block have been replayed, we verify
26
   that the Merkle root of the replay ledger matches the stored
27
   ledger hash in the archive database
28
*)
29

30
type input =
28✔
31
  { target_epoch_ledgers_state_hash : State_hash.t option [@default None]
×
32
  ; start_slot_since_genesis : int64 [@default 0L]
×
33
  ; genesis_ledger : Runtime_config.Ledger.t
2✔
34
  ; first_pass_ledger_hashes : Ledger_hash.t list [@default []]
5✔
35
  ; last_snarked_ledger_hash : Ledger_hash.t option [@default None]
×
36
  }
37
[@@deriving yojson]
×
38

39
type output =
×
40
  { target_epoch_ledgers_state_hash : State_hash.t
×
41
  ; target_fork_state_hash : State_hash.t
×
42
  ; target_genesis_ledger : Runtime_config.Ledger.t
×
43
  ; target_epoch_data : Runtime_config.Epoch_data.t
×
44
  }
45
[@@deriving yojson]
46

47
module type Get_command_ids = sig
48
  val run :
49
       Caqti_async.connection
50
    -> state_hash:string
51
    -> start_slot:int64
52
    -> (int list, [> Caqti_error.call_or_retrieve ]) Deferred.Result.t
53
end
54

55
let error_count = ref 0
56

57
let json_ledger_hash_of_ledger ledger =
58
  Ledger_hash.to_yojson @@ Ledger.merkle_root ledger
48✔
59

60
let create_ledger_as_list ledger =
61
  let%map accounts = Ledger.to_list ledger in
5✔
62
  List.map accounts ~f:(fun acc ->
5✔
63
      Genesis_ledger_helper.Accounts.Single.of_account acc None )
1,210✔
64

65
module First_pass_ledger_hashes = struct
66
  (* ledger hashes after 1st pass, indexed by order of occurrence *)
67

68
  module T = struct
69
    type t = Ledger_hash.Stable.Latest.t * int
2✔
70
    [@@deriving bin_io_unversioned, compare, sexp, hash]
8✔
71
  end
72

73
  include T
74
  include Hashable.Make_binable (T)
75

76
  let hash_set = Hash_set.create ()
2✔
77

78
  let add =
79
    let count = ref 0 in
80
    fun ledger_hash ->
81
      Base.Hash_set.add hash_set (ledger_hash, !count) ;
1,210✔
82
      incr count
1,210✔
83

84
  let find ledger_hash =
85
    Base.Hash_set.find hash_set ~f:(fun (hash, _n) ->
×
86
        Ledger_hash.equal hash ledger_hash )
×
87

88
  (* once we find a snarked ledger hash corresponding to a ledger hash, don't need to store earlier ones *)
89
  let flush_older_than ndx =
90
    let elts = Base.Hash_set.to_list hash_set in
×
91
    List.iter elts ~f:(fun ((_hash, n) as elt) ->
×
92
        if n < ndx then Base.Hash_set.remove hash_set elt )
×
93

94
  let get_last_snarked_hash, set_last_snarked_hash =
95
    let last_snarked_hash = ref Ledger_hash.empty_hash in
96
    let getter () = !last_snarked_hash in
53✔
97
    let setter hash = last_snarked_hash := hash in
×
98
    (getter, setter)
99
end
100

101
let create_output ~target_epoch_ledgers_state_hash ~target_fork_state_hash
102
    ~ledger ~staking_epoch_ledger ~staking_seed ~next_epoch_ledger ~next_seed
103
    (input_genesis_ledger : Runtime_config.Ledger.t) =
104
  let%bind genesis_ledger_as_list = create_ledger_as_list ledger in
×
105
  let target_genesis_ledger =
×
106
    { input_genesis_ledger with base = Accounts genesis_ledger_as_list }
107
  in
108
  let%bind staking_epoch_ledger_as_list =
109
    create_ledger_as_list staking_epoch_ledger
×
110
  in
111
  let%map next_epoch_ledger_as_list = create_ledger_as_list next_epoch_ledger in
×
112
  let target_staking_epoch_data : Runtime_config.Epoch_data.Data.t =
×
113
    let ledger =
114
      { input_genesis_ledger with base = Accounts staking_epoch_ledger_as_list }
115
    in
116
    { ledger; seed = staking_seed }
117
  in
118
  let target_next_epoch_data : Runtime_config.Epoch_data.Data.t =
119
    let ledger =
120
      { input_genesis_ledger with base = Accounts next_epoch_ledger_as_list }
121
    in
122
    { ledger; seed = next_seed }
123
  in
124
  let target_epoch_data : Runtime_config.Epoch_data.t =
125
    { staking = target_staking_epoch_data; next = Some target_next_epoch_data }
126
  in
127
  { target_fork_state_hash
128
  ; target_epoch_ledgers_state_hash
129
  ; target_genesis_ledger
130
  ; target_epoch_data
131
  }
132

133
let create_replayer_checkpoint ~ledger ~start_slot_since_genesis :
134
    input Deferred.t =
135
  let%map accounts = create_ledger_as_list ledger in
5✔
136
  let genesis_ledger : Runtime_config.Ledger.t =
5✔
137
    { base = Accounts accounts
138
    ; num_accounts = None
139
    ; balances = []
140
    ; hash = Some (Ledger.merkle_root ledger |> Ledger_hash.to_base58_check)
5✔
141
    ; s3_data_hash = None
142
    ; name = None
143
    ; add_genesis_winner = Some true
144
    }
145
  in
146
  let first_pass_ledger_hashes =
147
    let elts = Base.Hash_set.to_list First_pass_ledger_hashes.hash_set in
148
    List.sort elts ~compare:(fun (_h1, n1) (_h2, n2) -> Int.compare n1 n2)
5✔
149
    |> List.map ~f:(fun (h, _n) -> h)
5✔
150
  in
151
  let last_snarked_ledger_hash =
152
    Some (First_pass_ledger_hashes.get_last_snarked_hash ())
5✔
153
  in
154
  { target_epoch_ledgers_state_hash = None
155
  ; start_slot_since_genesis
156
  ; genesis_ledger
157
  ; first_pass_ledger_hashes
158
  ; last_snarked_ledger_hash
159
  }
160

161
(* map from global slots (since genesis) to state hash, ledger hash, snarked ledger hash triples *)
162
let global_slot_hashes_tbl :
163
    (Int64.t, State_hash.t * Ledger_hash.t * Frozen_ledger_hash.t) Hashtbl.t =
164
  Int64.Table.create ()
2✔
165

166
let get_slot_hashes slot = Hashtbl.find global_slot_hashes_tbl slot
94✔
167

168
let process_block_infos_of_state_hash ~logger pool ~state_hash ~start_slot ~f =
169
  match%bind
170
    Caqti_async.Pool.use
2✔
171
      (fun db -> Sql.Block_info.run db ~state_hash ~start_slot)
2✔
172
      pool
173
  with
174
  | Ok block_infos ->
2✔
175
      f block_infos
176
  | Error msg ->
×
177
      [%log error] "Error getting block information for state hash"
×
178
        ~metadata:
179
          [ ("error", `String (Caqti_error.show msg))
×
180
          ; ("state_hash", `String state_hash)
181
          ] ;
182
      exit 1
×
183

184
let update_epoch_ledger ~logger ~name ~ledger ~epoch_ledger epoch_ledger_hash =
185
  let epoch_ledger_hash = Ledger_hash.of_base58_check_exn epoch_ledger_hash in
4,840✔
186
  let curr_ledger_hash = Ledger.merkle_root ledger in
4,840✔
187
  if Frozen_ledger_hash.equal epoch_ledger_hash curr_ledger_hash then (
×
188
    [%log info]
×
189
      "Creating %s epoch ledger from ledger with Merkle root matching epoch \
190
       ledger hash %s"
191
      name
192
      (Ledger_hash.to_base58_check epoch_ledger_hash) ;
×
193
    (* Ledger.copy doesn't actually copy, roll our own here *)
194
    let%map accounts = Ledger.to_list ledger in
×
195
    let epoch_ledger = Ledger.create ~depth:(Ledger.depth ledger) () in
×
196
    List.iter accounts ~f:(fun account ->
×
197
        let pk = Account.public_key account in
×
198
        let token = Account.token_id account in
×
199
        let account_id = Account_id.create pk token in
×
200
        match Ledger.get_or_create_account epoch_ledger account_id account with
×
201
        | Ok (`Added, _loc) ->
×
202
            ()
203
        | Ok (`Existed, _loc) ->
×
204
            failwithf
205
              "When creating epoch ledger, account with public key %s and \
206
               token %s already existed"
207
              (Signature_lib.Public_key.Compressed.to_string pk)
×
208
              (Token_id.to_string token) ()
×
209
        | Error err ->
×
210
            Error.tag_arg err
211
              "When creating epoch ledger, error when adding account"
212
              (("public_key", pk), ("token", token))
213
              [%sexp_of:
214
                (string * Signature_lib.Public_key.Compressed.t)
215
                * (string * Token_id.t)]
216
            |> Error.raise ) ;
×
217
    epoch_ledger )
×
218
  else return epoch_ledger
4,840✔
219

220
let update_staking_epoch_data ~logger pool ~ledger ~last_block_id
221
    ~staking_epoch_ledger ~staking_seed =
222
  let query_db = Mina_caqti.query pool in
2,420✔
223
  let%bind state_hash =
224
    query_db ~f:(fun db -> Sql.Block.get_state_hash db last_block_id)
2,420✔
225
  in
226
  let%bind staking_epoch_id =
227
    query_db ~f:(fun db ->
228
        Sql.Epoch_data.get_staking_epoch_data_id db state_hash )
2,420✔
229
  in
230
  let%bind { epoch_ledger_hash; epoch_data_seed } =
231
    query_db ~f:(fun db -> Sql.Epoch_data.get_epoch_data db staking_epoch_id)
2,420✔
232
  in
233
  let%map ledger =
234
    update_epoch_ledger ~logger ~name:"staking" ~ledger
2,420✔
235
      ~epoch_ledger:!staking_epoch_ledger epoch_ledger_hash
236
  in
237
  staking_epoch_ledger := ledger ;
2,420✔
238
  staking_seed := epoch_data_seed
239

240
let update_next_epoch_data ~logger pool ~ledger ~last_block_id
241
    ~next_epoch_ledger ~next_seed =
242
  let query_db = Mina_caqti.query pool in
2,420✔
243
  let%bind state_hash =
244
    query_db ~f:(fun db -> Sql.Block.get_state_hash db last_block_id)
2,420✔
245
  in
246
  let%bind next_epoch_id =
247
    query_db ~f:(fun db -> Sql.Epoch_data.get_next_epoch_data_id db state_hash)
2,420✔
248
  in
249
  let%bind { epoch_ledger_hash; epoch_data_seed } =
250
    query_db ~f:(fun db -> Sql.Epoch_data.get_epoch_data db next_epoch_id)
2,420✔
251
  in
252
  let%map ledger =
253
    update_epoch_ledger ~logger ~name:"next" ~ledger
2,420✔
254
      ~epoch_ledger:!next_epoch_ledger epoch_ledger_hash
255
  in
256
  next_epoch_ledger := ledger ;
2,420✔
257
  next_seed := epoch_data_seed
258

259
(* cache of fee transfers for coinbases *)
260
module Fee_transfer_key = struct
261
  module T = struct
262
    type t = int64 * int * int [@@deriving hash, sexp, compare]
24✔
263
  end
264

265
  type t = T.t
266

267
  include Hashable.Make (T)
268
end
269

270
let fee_transfer_tbl : (Fee_transfer_key.t, Coinbase_fee_transfer.t) Hashtbl.t =
271
  Fee_transfer_key.Table.create ()
2✔
272

273
let cache_fee_transfer_via_coinbase pool (internal_cmd : Sql.Internal_command.t)
274
    =
275
  match internal_cmd.typ with
148✔
276
  | "fee_transfer_via_coinbase" ->
24✔
277
      let%map receiver_pk = Load_data.pk_of_id pool internal_cmd.receiver_id in
24✔
278
      let fee =
24✔
279
        Currency.Fee.of_uint64 (Unsigned.UInt64.of_int64 internal_cmd.fee)
24✔
280
      in
281
      let fee_transfer = Coinbase_fee_transfer.create ~receiver_pk ~fee in
24✔
282
      Hashtbl.add_exn fee_transfer_tbl
283
        ~key:
284
          ( internal_cmd.global_slot_since_genesis
285
          , internal_cmd.sequence_no
286
          , internal_cmd.secondary_sequence_no )
287
        ~data:fee_transfer
288
  | _ ->
124✔
289
      Deferred.unit
290

291
module User_command_helpers = struct
292
  let body_of_sql_user_cmd pool
293
      ({ typ; source_id = _; receiver_id; amount; global_slot_since_genesis; _ } :
294
        Sql.User_command.t ) : Signed_command_payload.Body.t Deferred.t =
295
    let open Signed_command_payload.Body in
360✔
296
    let open Deferred.Let_syntax in
297
    let%map receiver_pk = Load_data.pk_of_id pool receiver_id in
360✔
298
    let amount =
360✔
299
      Option.map amount
300
        ~f:(Fn.compose Currency.Amount.of_uint64 Unsigned.UInt64.of_int64)
360✔
301
    in
302
    (* possibilities from user_command_type enum in SQL schema *)
303
    match typ with
360✔
304
    | "payment" ->
360✔
305
        if Option.is_none amount then
306
          failwithf "Payment at global slot since genesis %Ld has NULL amount"
×
307
            global_slot_since_genesis () ;
308
        let amount = Option.value_exn amount in
360✔
309
        Payment Payment_payload.Poly.{ receiver_pk; amount }
360✔
310
    | "delegation" ->
×
311
        Stake_delegation
312
          (Stake_delegation.Set_delegate { new_delegate = receiver_pk })
313
    | _ ->
×
314
        failwithf "Invalid user command type: %s" typ ()
315
end
316

317
(* internal commands in result are remaining internal commands to process
318

319
   in most cases, those are the input list `ics`
320
   when we combine fee transfers, it's the tail of `ics`
321
*)
322
let internal_cmds_to_transaction ~logger ~pool (ic : Sql.Internal_command.t)
323
    (ics : Sql.Internal_command.t list) :
324
    (Mina_transaction.Transaction.t option * Sql.Internal_command.t list)
325
    Deferred.t =
326
  [%log spam]
124✔
327
    "Converting internal command (%s) with global slot since genesis %Ld, \
328
     sequence number %d, and secondary sequence number %d to transaction"
329
    ic.typ ic.global_slot_since_genesis ic.sequence_no ic.secondary_sequence_no ;
330
  let fee_transfer_of_cmd (cmd : Sql.Internal_command.t) =
124✔
331
    if not (String.equal cmd.typ "fee_transfer") then
78✔
332
      failwithf "Expected fee transfer, got: %s" cmd.typ () ;
×
333
    let fee = Currency.Fee.of_uint64 (Unsigned.UInt64.of_int64 cmd.fee) in
78✔
334
    let%map receiver_pk = Load_data.pk_of_id pool cmd.receiver_id in
78✔
335
    let fee_token = Token_id.default in
78✔
336
    Fee_transfer.Single.create ~receiver_pk ~fee ~fee_token
337
  in
338
  match ics with
339
  | ic2 :: ics2
124✔
340
    when Int64.equal ic.global_slot_since_genesis ic2.global_slot_since_genesis
124✔
341
         && Int.equal ic.sequence_no ic2.sequence_no
102✔
342
         && String.equal ic.typ "fee_transfer"
48✔
343
         && String.equal ic.typ ic2.typ -> (
24✔
344
      (* combining situation 2
345
         two fee transfer commands with same global slot since genesis, sequence number
346
      *)
347
      [%log spam]
24✔
348
        "Combining two fee transfers at global slot since genesis %Ld with \
349
         sequence number %d"
350
        ic.global_slot_since_genesis ic.sequence_no ;
351
      let%bind fee_transfer1 = fee_transfer_of_cmd ic in
24✔
352
      let%map fee_transfer2 = fee_transfer_of_cmd ic2 in
24✔
353
      match Fee_transfer.create fee_transfer1 (Some fee_transfer2) with
24✔
354
      | Ok ft ->
24✔
355
          (Some (Mina_transaction.Transaction.Fee_transfer ft), ics2)
356
      | Error err ->
×
357
          Error.tag err ~tag:"Could not create combined fee transfer"
358
          |> Error.raise )
×
359
  | _ -> (
100✔
360
      match ic.typ with
361
      | "fee_transfer" -> (
30✔
362
          let%map fee_transfer = fee_transfer_of_cmd ic in
30✔
363
          match Fee_transfer.create fee_transfer None with
30✔
364
          | Ok ft ->
30✔
365
              (Some (Mina_transaction.Transaction.Fee_transfer ft), ics)
366
          | Error err ->
×
367
              Error.tag err ~tag:"Could not create fee transfer" |> Error.raise
×
368
          )
369
      | "coinbase" -> (
46✔
370
          let fee = Currency.Fee.of_uint64 (Unsigned.UInt64.of_int64 ic.fee) in
46✔
371
          let amount =
46✔
372
            Currency.Fee.to_uint64 fee |> Currency.Amount.of_uint64
46✔
373
          in
374
          (* combining situation 1: add cached coinbase fee transfer, if it exists *)
375
          let fee_transfer =
46✔
376
            Hashtbl.find fee_transfer_tbl
377
              ( ic.global_slot_since_genesis
378
              , ic.sequence_no
379
              , ic.secondary_sequence_no )
380
          in
381
          if Option.is_some fee_transfer then
46✔
382
            [%log spam]
24✔
383
              "Coinbase transaction at global slot since genesis %Ld, sequence \
384
               number %d, and secondary sequence number %d contains a fee \
385
               transfer"
386
              ic.global_slot_since_genesis ic.sequence_no
387
              ic.secondary_sequence_no ;
388
          let%map receiver = Load_data.pk_of_id pool ic.receiver_id in
46✔
389
          match Coinbase.create ~amount ~receiver ~fee_transfer with
46✔
390
          | Ok cb ->
46✔
391
              (Some (Mina_transaction.Transaction.Coinbase cb), ics)
392
          | Error err ->
×
393
              Error.tag err ~tag:"Could not create coinbase" |> Error.raise )
×
394
      | "fee_transfer_via_coinbase" ->
24✔
395
          (* handled in the coinbase case *)
396
          return (None, ics)
397
      | ty ->
×
398
          failwithf "Unknown internal command type: %s" ty () )
399

400
let user_command_to_transaction ~logger ~pool (cmd : Sql.User_command.t) :
401
    Mina_transaction.Transaction.t Deferred.t =
402
  [%log spam]
360✔
403
    "Converting user command (%s) with nonce %Ld, global slot since genesis \
404
     %Ld, and sequence number %d to transaction"
405
    cmd.typ cmd.nonce cmd.global_slot_since_genesis cmd.sequence_no ;
406
  let%bind body = User_command_helpers.body_of_sql_user_cmd pool cmd in
360✔
407
  let%bind fee_payer_pk = Load_data.pk_of_id pool cmd.fee_payer_id in
360✔
408
  let memo = Signed_command_memo.of_base58_check_exn cmd.memo in
360✔
409
  let valid_until =
360✔
410
    Option.map cmd.valid_until ~f:(fun slot ->
411
        Mina_numbers.Global_slot_since_genesis.of_uint32
×
412
        @@ Unsigned.UInt32.of_int64 slot )
×
413
  in
414
  let payload =
360✔
415
    Signed_command_payload.create
416
      ~fee:(Currency.Fee.of_uint64 @@ Unsigned.UInt64.of_int64 cmd.fee)
360✔
417
      ~fee_payer_pk
418
      ~nonce:(Unsigned.UInt32.of_int64 cmd.nonce)
360✔
419
      ~valid_until ~memo ~body
420
  in
421
  (* when applying the transaction, there's a check that the fee payer and
422
     signer keys are the same; since this transaction was accepted, we know
423
     those keys are the same
424
  *)
425
  let signer = Signature_lib.Public_key.decompress_exn fee_payer_pk in
426
  let signed_cmd =
360✔
427
    Signed_command.Poly.{ payload; signer; signature = Signature.dummy }
428
  in
429
  let user_cmd = User_command.Signed_command signed_cmd in
430
  return @@ Mina_transaction.Transaction.Command user_cmd
431

432
let get_parent_state_view ~pool block_id =
433
  let%bind state_view =
434
    let query_db = Mina_caqti.query pool in
435
    let%bind parent_id =
436
      query_db ~f:(fun db -> Sql.Block.get_parent_id db block_id)
46✔
437
    in
438
    let%bind parent_block =
439
      query_db ~f:(fun db -> Processor.Block.load db ~id:parent_id)
46✔
440
    in
441
    let%bind snarked_ledger_hash_str =
442
      query_db ~f:(fun db ->
443
          Sql.Snarked_ledger_hashes.run db parent_block.snarked_ledger_hash_id )
46✔
444
    in
445
    let snarked_ledger_hash =
46✔
446
      Frozen_ledger_hash.of_base58_check_exn snarked_ledger_hash_str
447
    in
448
    let blockchain_length =
46✔
449
      parent_block.height |> Unsigned.UInt32.of_int64
450
      |> Mina_numbers.Length.of_uint32
46✔
451
    in
452
    let min_window_density =
46✔
453
      parent_block.min_window_density |> Unsigned.UInt32.of_int64
454
      |> Mina_numbers.Length.of_uint32
46✔
455
    in
456
    let total_currency =
46✔
457
      Currency.Amount.of_string parent_block.total_currency
458
    in
459
    let global_slot_since_genesis =
46✔
460
      parent_block.global_slot_since_genesis |> Unsigned.UInt32.of_int64
461
      |> Mina_numbers.Global_slot_since_genesis.of_uint32
46✔
462
    in
463
    let epoch_data_of_raw_epoch_data (raw_epoch_data : Processor.Epoch_data.t) :
46✔
464
        Mina_base.Epoch_data.Value.t Deferred.t =
465
      let%bind hash_str =
466
        query_db ~f:(fun db ->
467
            Sql.Snarked_ledger_hashes.run db raw_epoch_data.ledger_hash_id )
92✔
468
      in
469
      let hash = Frozen_ledger_hash.of_base58_check_exn hash_str in
92✔
470
      let total_currency =
92✔
471
        Currency.Amount.of_string raw_epoch_data.total_currency
472
      in
473
      let ledger = { Mina_base.Epoch_ledger.Poly.hash; total_currency } in
92✔
474
      let seed = raw_epoch_data.seed |> Epoch_seed.of_base58_check_exn in
475
      let start_checkpoint =
92✔
476
        raw_epoch_data.start_checkpoint |> State_hash.of_base58_check_exn
477
      in
478
      let lock_checkpoint =
92✔
479
        raw_epoch_data.lock_checkpoint |> State_hash.of_base58_check_exn
480
      in
481
      let epoch_length =
92✔
482
        raw_epoch_data.epoch_length |> Unsigned.UInt32.of_int64
483
        |> Mina_numbers.Length.of_uint32
92✔
484
      in
485
      return
92✔
486
        { Mina_base.Epoch_data.Poly.ledger
487
        ; seed
488
        ; start_checkpoint
489
        ; lock_checkpoint
490
        ; epoch_length
491
        }
492
    in
493
    let%bind staking_epoch_raw =
494
      query_db ~f:(fun db ->
495
          Processor.Epoch_data.load db parent_block.staking_epoch_data_id )
46✔
496
    in
497
    let%bind (staking_epoch_data : Mina_base.Epoch_data.Value.t) =
498
      epoch_data_of_raw_epoch_data staking_epoch_raw
46✔
499
    in
500
    let%bind next_epoch_raw =
501
      query_db ~f:(fun db ->
502
          Processor.Epoch_data.load db parent_block.staking_epoch_data_id )
46✔
503
    in
504
    let%bind next_epoch_data = epoch_data_of_raw_epoch_data next_epoch_raw in
46✔
505
    return
46✔
506
      { Zkapp_precondition.Protocol_state.Poly.snarked_ledger_hash
507
      ; blockchain_length
508
      ; min_window_density
509
      ; total_currency
510
      ; global_slot_since_genesis
511
      ; staking_epoch_data
512
      ; next_epoch_data
513
      }
514
  in
515
  return state_view
46✔
516

517
let zkapp_command_to_transaction ~logger ~pool (cmd : Sql.Zkapp_command.t) :
518
    Mina_transaction.Transaction.t Deferred.t =
519
  let query_db = Mina_caqti.query pool in
750✔
520
  (* use dummy authorizations *)
521
  let%bind (fee_payer : Account_update.Fee_payer.t) =
522
    let%map (body : Account_update.Body.Fee_payer.t) =
523
      Load_data.get_fee_payer_body ~pool cmd.zkapp_fee_payer_body_id
750✔
524
    in
525
    ({ body; authorization = Signature.dummy } : Account_update.Fee_payer.t)
750✔
526
  in
527
  let nonce_str = Mina_numbers.Account_nonce.to_string fee_payer.body.nonce in
750✔
528
  [%log spam]
750✔
529
    "Converting zkApp command with fee payer nonce %s, global slot since \
530
     genesis %Ld, and sequence number %d to transaction"
531
    nonce_str cmd.global_slot_since_genesis cmd.sequence_no ;
532
  let%bind (account_updates : Account_update.Simple.t list) =
533
    Deferred.List.map (Array.to_list cmd.zkapp_account_updates_ids)
750✔
534
      ~f:(fun id ->
535
        let%bind { body_id } =
536
          query_db ~f:(fun db -> Processor.Zkapp_account_update.load db id)
1,126✔
537
        in
538
        let%map body =
539
          Archive_lib.Load_data.get_account_update_body ~pool body_id
1,126✔
540
        in
541
        let (authorization : Control.t) =
1,126✔
542
          match body.authorization_kind with
543
          | Proof _ ->
×
544
              Proof (Lazy.force Proof.transaction_dummy)
×
545
          | Signature ->
752✔
546
              Signature Signature.dummy
547
          | None_given ->
374✔
548
              None_given
549
        in
550
        ({ body; authorization } : Account_update.Simple.t) )
551
  in
552
  let memo = Signed_command_memo.of_base58_check_exn cmd.memo in
750✔
553
  let zkapp_command =
750✔
554
    Zkapp_command.of_simple { fee_payer; account_updates; memo }
555
  in
556
  return
750✔
557
  @@ Mina_transaction.Transaction.Command
558
       (User_command.Zkapp_command zkapp_command)
559

560
let find_canonical_chain ~logger pool slot =
561
  (* find longest canonical chain
562
     a slot may represent several blocks, only one of which can be on canonical chain
563
     starting with max slot, look for chain, decrementing slot until chain found
564
  *)
565
  let query_db = Mina_caqti.query pool in
2✔
566
  let find_state_hash_chain state_hash =
2✔
567
    match%map query_db ~f:(fun db -> Sql.Block.get_chain db state_hash) with
2✔
568
    | [] ->
×
569
        [%log spam] "Block with state hash %s is not along canonical chain"
×
570
          state_hash ;
571
        None
×
572
    | _ ->
2✔
573
        Some state_hash
574
  in
575
  let%bind state_hashes =
576
    query_db ~f:(fun db -> Sql.Block.get_state_hashes_by_slot db slot)
2✔
577
  in
578
  Deferred.List.find_map state_hashes ~f:find_state_hash_chain
2✔
579

580
let try_slot ~logger pool slot =
581
  let num_tries = 5 in
2✔
582
  let rec go ~slot ~tries_left =
583
    if tries_left <= 0 then (
×
584
      [%log fatal] "Could not find canonical chain after trying %d slots"
×
585
        num_tries ;
586
      Core_kernel.exit 1 ) ;
×
587
    match%bind find_canonical_chain ~logger pool slot with
2✔
588
    | None ->
×
589
        go ~slot:(Int64.pred slot) ~tries_left:(tries_left - 1)
×
590
    | Some state_hash ->
2✔
591
        [%log spam]
2✔
592
          "Found possible canonical chain to target state hash %s at slot %Ld"
593
          state_hash slot ;
594
        return state_hash
2✔
595
  in
596
  go ~slot ~tries_left:num_tries
597

598
let write_replayer_checkpoint ~logger ~ledger ~last_global_slot_since_genesis
599
    ~max_canonical_slot ~checkpoint_output_folder_opt ~checkpoint_file_prefix =
600
  if Int64.( <= ) last_global_slot_since_genesis max_canonical_slot then (
5✔
601
    (* start replaying at the slot after the one we've just finished with *)
602
    let start_slot_since_genesis = Int64.succ last_global_slot_since_genesis in
603
    let%map replayer_checkpoint =
604
      let%map input =
605
        create_replayer_checkpoint ~ledger ~start_slot_since_genesis
606
      in
607
      input_to_yojson input |> Yojson.Safe.pretty_to_string
5✔
608
    in
609
    let checkpoint_file =
5✔
610
      let checkpoint_filename =
611
        sprintf "%s-checkpoint-%Ld.json" checkpoint_file_prefix
612
          start_slot_since_genesis
613
      in
614
      match checkpoint_output_folder_opt with
5✔
615
      | Some parent ->
×
616
          Filename.concat parent checkpoint_filename
×
617
      | None ->
5✔
618
          checkpoint_filename
619
    in
620
    [%log info] "Writing checkpoint file"
5✔
621
      ~metadata:[ ("checkpoint_file", `String checkpoint_file) ] ;
622
    Out_channel.with_file checkpoint_file ~f:(fun oc ->
5✔
623
        Out_channel.output_string oc replayer_checkpoint ) )
5✔
624
  else (
×
625
    [%log info] "Not writing checkpoint file at slot %Ld, because not canonical"
×
626
      last_global_slot_since_genesis
627
      ~metadata:
628
        [ ("max_canonical_slot", `String (Int64.to_string max_canonical_slot)) ] ;
×
629
    Deferred.unit )
×
630

631
let main ~input_file ~output_file_opt ~archive_uri ~continue_on_error
632
    ~checkpoint_interval ~checkpoint_output_folder_opt ~checkpoint_file_prefix
633
    ~genesis_dir_opt ~log_json ~log_level ~log_filename ~file_log_level
634
    ~constraint_constants ~proof_level () =
635
  Cli_lib.Stdout_log.setup log_json log_level ;
2✔
636
  Option.iter log_filename ~f:(fun log_filename ->
2✔
637
      Logger.Consumer_registry.register ~id:"default"
×
638
        ~processor:(Logger.Processor.raw ~log_level:file_log_level ())
×
639
        ~transport:(Logger_file_system.evergrowing ~log_filename)
640
        () ) ;
641
  let logger = Logger.create () in
2✔
642
  let json = Yojson.Safe.from_file input_file in
2✔
643
  let input =
2✔
644
    match input_of_yojson json with
645
    | Ok inp ->
2✔
646
        inp
647
    | Error msg ->
×
648
        failwith
649
          (sprintf "Could not parse JSON in input file \"%s\": %s" input_file
×
650
             msg )
651
  in
652
  let archive_uri = Uri.of_string archive_uri in
653
  match Caqti_async.connect_pool ~max_size:128 archive_uri with
2✔
654
  | Error e ->
×
655
      [%log fatal]
×
656
        ~metadata:[ ("error", `String (Caqti_error.show e)) ]
×
657
        "Failed to create a Caqti pool for Postgresql" ;
658
      exit 1
×
659
  | Ok pool -> (
2✔
660
      [%log info] "Successfully created Caqti pool for Postgresql" ;
2✔
661
      (* load from runtime config in same way as daemon
662
         except that we don't consider loading from a tar file
663
      *)
664
      let query_db = Mina_caqti.query pool in
2✔
665
      let%bind packed_ledger =
666
        match%bind
667
          Genesis_ledger_helper.Ledger.load ~proof_level
2✔
668
            ~genesis_dir:
669
              (Option.value ~default:Cache_dir.autogen_path genesis_dir_opt)
2✔
670
            ~logger ~constraint_constants input.genesis_ledger
671
        with
672
        | Error e ->
×
673
            [%log fatal]
×
674
              "Could not load accounts from input runtime genesis ledger %s"
675
              (Error.to_string_hum e) ;
×
676
            exit 1
×
677
        | Ok (packed_ledger, _, _) ->
2✔
678
            return packed_ledger
679
      in
680
      let ledger = Lazy.force @@ Genesis_ledger.Packed.t packed_ledger in
2✔
681
      let epoch_ledgers_state_hash_opt =
2✔
682
        Option.map input.target_epoch_ledgers_state_hash
683
          ~f:State_hash.to_base58_check
684
      in
685
      let%bind target_state_hash =
686
        match epoch_ledgers_state_hash_opt with
687
        | Some hash ->
×
688
            return hash
×
689
        | None ->
2✔
690
            [%log info]
2✔
691
              "Searching for block with greatest height on canonical chain" ;
692
            let%bind max_slot =
693
              query_db ~f:(fun db -> Sql.Block.get_max_slot db ())
2✔
694
            in
695
            [%log info] "Maximum global slot since genesis in blocks is %Ld"
2✔
696
              max_slot ;
697
            try_slot ~logger pool max_slot
2✔
698
      in
699
      if not @@ List.is_empty input.first_pass_ledger_hashes then (
×
700
        [%log info] "Populating set of first-pass ledger hashes" ;
×
701
        List.iter input.first_pass_ledger_hashes ~f:First_pass_ledger_hashes.add
×
702
        ) ;
703
      Option.iter input.last_snarked_ledger_hash ~f:(fun h ->
2✔
704
          [%log info] "Setting last snarked ledger hash" ;
×
705
          First_pass_ledger_hashes.set_last_snarked_hash h ) ;
×
706
      [%log info]
2✔
707
        "Loading block information using target state hash and start slot" ;
708
      let%bind block_ids, oldest_block_id =
709
        process_block_infos_of_state_hash ~logger pool
2✔
710
          ~state_hash:target_state_hash
711
          ~start_slot:input.start_slot_since_genesis ~f:(fun block_infos ->
712
            let ({ id = oldest_block_id; _ } : Sql.Block_info.t) =
2✔
713
              Option.value_exn
714
                (List.min_elt block_infos ~compare:(fun bi1 bi2 ->
2✔
715
                     Int64.compare bi1.global_slot_since_genesis
46✔
716
                       bi2.global_slot_since_genesis ) )
717
            in
718
            let ids = List.map block_infos ~f:(fun { id; _ } -> id) in
2✔
719
            (* build mapping from global slots to state and ledger hashes *)
720
            let%bind () =
721
              Deferred.List.iter block_infos
2✔
722
                ~f:(fun
723
                     { global_slot_since_genesis
724
                     ; state_hash
725
                     ; ledger_hash
726
                     ; snarked_ledger_hash_id
727
                     ; _
728
                     }
729
                   ->
730
                  let%map snarked_hash =
731
                    query_db ~f:(fun db ->
732
                        Sql.Snarked_ledger_hashes.run db snarked_ledger_hash_id )
48✔
733
                  in
734

735
                  Hashtbl.add_exn global_slot_hashes_tbl
48✔
736
                    ~key:global_slot_since_genesis
737
                    ~data:
738
                      ( State_hash.of_base58_check_exn state_hash
48✔
739
                      , Ledger_hash.of_base58_check_exn ledger_hash
48✔
740
                      , Frozen_ledger_hash.of_base58_check_exn snarked_hash ) )
48✔
741
            in
742
            return (Int.Set.of_list ids, oldest_block_id) )
2✔
743
      in
744
      if Int64.equal input.start_slot_since_genesis 0L then
2✔
745
        (* check that genesis block is in chain to target hash                                                                                                                                                                                          assumption: genesis block occupies global slot 0
746

747
           if nonzero start slot, can't assume there's a block at that slot *)
748
        if Int64.Table.mem global_slot_hashes_tbl Int64.zero then
2✔
749
          [%log info]
2✔
750
            "Block chain leading to target state hash includes genesis block, \
751
             length = %d"
752
            (Int.Set.length block_ids)
2✔
753
        else (
×
754
          [%log fatal]
×
755
            "Block chain leading to target state hash does not include genesis \
756
             block" ;
757
          Core_kernel.exit 1 ) ;
×
758
      (* some mutable state, less painful than passing epoch ledgers throughout *)
759
      let staking_epoch_ledger = ref ledger in
2✔
760
      let next_epoch_ledger = ref ledger in
761
      let%bind staking_seed, next_seed =
762
        let slots = Int64.Table.keys global_slot_hashes_tbl in
763
        let least_slot =
2✔
764
          Option.value_exn @@ List.min_elt slots ~compare:Int64.compare
2✔
765
        in
766
        let state_hash, _ledger_hash, _snarked_hash =
2✔
767
          Int64.Table.find_exn global_slot_hashes_tbl least_slot
768
        in
769
        let%bind { staking_epoch_data_id; next_epoch_data_id; _ } =
770
          let%bind block_id =
771
            query_db ~f:(fun db -> Processor.Block.find db ~state_hash)
2✔
772
          in
773
          query_db ~f:(fun db -> Processor.Block.load db ~id:block_id)
2✔
774
        in
775
        let%bind { epoch_data_seed = staking_seed; _ } =
776
          query_db ~f:(fun db ->
777
              Sql.Epoch_data.get_epoch_data db staking_epoch_data_id )
2✔
778
        in
779
        let%map { epoch_data_seed = next_seed; _ } =
780
          query_db ~f:(fun db ->
781
              Sql.Epoch_data.get_epoch_data db next_epoch_data_id )
2✔
782
        in
783
        (ref staking_seed, ref next_seed)
2✔
784
      in
785
      (* end mutable state *)
786
      let get_command_ids (module Command_ids : Get_command_ids) name =
2✔
787
        match%bind
788
          Caqti_async.Pool.use
6✔
789
            (fun db ->
790
              Command_ids.run db ~state_hash:target_state_hash
6✔
791
                ~start_slot:input.start_slot_since_genesis )
792
            pool
793
        with
794
        | Ok ids ->
6✔
795
            return ids
796
        | Error msg ->
×
797
            [%log error] "Error getting %s command ids" name
×
798
              ~metadata:[ ("error", `String (Caqti_error.show msg)) ] ;
×
799
            exit 1
×
800
      in
801
      [%log info] "Loading internal command ids" ;
2✔
802
      let%bind internal_cmd_ids =
803
        get_command_ids (module Sql.Internal_command_ids) "internal"
2✔
804
      in
805
      [%log info] "Loading user command ids" ;
2✔
806
      let%bind user_cmd_ids =
807
        get_command_ids (module Sql.User_command_ids) "user"
2✔
808
      in
809
      [%log info] "Loading zkApp command ids" ;
2✔
810
      let%bind zkapp_cmd_ids =
811
        get_command_ids (module Sql.Zkapp_command_ids) "zkApp"
2✔
812
      in
813
      [%log info]
2✔
814
        "Obtained %d user command ids, %d internal command ids, and %d zkApp \
815
         command ids"
816
        (List.length user_cmd_ids)
2✔
817
        (List.length internal_cmd_ids)
2✔
818
        (List.length zkapp_cmd_ids) ;
2✔
819
      [%log info] "Loading internal commands" ;
2✔
820
      let%bind unsorted_internal_cmds_list =
821
        Deferred.List.map internal_cmd_ids ~f:(fun id ->
2✔
822
            let open Deferred.Let_syntax in
67✔
823
            match%map
824
              Caqti_async.Pool.use
67✔
825
                (fun db ->
826
                  Sql.Internal_command.run db
67✔
827
                    ~start_slot:input.start_slot_since_genesis
828
                    ~internal_cmd_id:id )
829
                pool
830
            with
831
            | Ok [] ->
×
832
                failwithf "Could not find any internal commands with id: %d" id
833
                  ()
834
            | Ok internal_cmds ->
67✔
835
                internal_cmds
836
            | Error msg ->
×
837
                failwithf
838
                  "Error querying for internal commands with id %d, error %s" id
839
                  (Caqti_error.show msg) () )
×
840
      in
841
      let unsorted_internal_cmds = List.concat unsorted_internal_cmds_list in
2✔
842
      (* filter out internal commands in blocks not along chain from target state hash *)
843
      let filtered_internal_cmds =
2✔
844
        List.filter unsorted_internal_cmds ~f:(fun cmd ->
845
            Int.Set.mem block_ids cmd.block_id )
188✔
846
      in
847
      [%log info] "Will replay %d internal commands"
2✔
848
        (List.length filtered_internal_cmds) ;
2✔
849
      let sorted_internal_cmds =
2✔
850
        List.sort filtered_internal_cmds ~compare:(fun ic1 ic2 ->
851
            let tuple (ic : Sql.Internal_command.t) =
716✔
852
              ( ic.global_slot_since_genesis
1,432✔
853
              , ic.sequence_no
854
              , ic.secondary_sequence_no )
855
            in
856
            let cmp = [%compare: int64 * int * int] (tuple ic1) (tuple ic2) in
161✔
857
            if cmp = 0 then
716✔
858
              match (ic1.typ, ic2.typ) with
24✔
859
              | "coinbase", "fee_transfer_via_coinbase" ->
22✔
860
                  -1
861
              | "fee_transfer_via_coinbase", "coinbase" ->
2✔
862
                  1
863
              | _ ->
×
864
                  failwith
865
                    "Two internal commands have the same global slot since \
866
                     genesis %Ld, sequence no %d, and secondary sequence no \
867
                     %d, but are not a coinbase and fee transfer via coinbase"
868
            else cmp )
692✔
869
      in
870
      (* populate cache of fee transfer via coinbase items *)
871
      [%log info] "Populating fee transfer via coinbase cache" ;
2✔
872
      let%bind () =
873
        Deferred.List.iter sorted_internal_cmds
2✔
874
          ~f:(cache_fee_transfer_via_coinbase pool)
2✔
875
      in
876
      [%log info] "Loading user commands" ;
2✔
877
      let%bind (unsorted_user_cmds_list : Sql.User_command.t list list) =
878
        Deferred.List.map user_cmd_ids ~f:(fun id ->
2✔
879
            let open Deferred.Let_syntax in
360✔
880
            match%map
881
              Caqti_async.Pool.use (fun db -> Sql.User_command.run db id) pool
360✔
882
            with
883
            | Ok [] ->
×
884
                failwithf "Expected at least one user command with id %d" id ()
885
            | Ok user_cmds ->
360✔
886
                user_cmds
887
            | Error msg ->
×
888
                failwithf
889
                  "Error querying for user commands with id %d, error %s" id
890
                  (Caqti_error.show msg) () )
×
891
      in
892
      let unsorted_user_cmds = List.concat unsorted_user_cmds_list in
2✔
893
      (* filter out user commands in blocks not along chain from target state hash *)
894
      let filtered_user_cmds =
2✔
895
        List.filter unsorted_user_cmds ~f:(fun cmd ->
896
            Int.Set.mem block_ids cmd.block_id )
470✔
897
      in
898
      [%log info] "Will replay %d user commands"
2✔
899
        (List.length filtered_user_cmds) ;
2✔
900
      let sorted_user_cmds =
2✔
901
        List.sort filtered_user_cmds ~compare:(fun uc1 uc2 ->
902
            let tuple (uc : Sql.User_command.t) =
2,270✔
903
              (uc.global_slot_since_genesis, uc.sequence_no)
4,540✔
904
            in
905
            [%compare: int64 * int] (tuple uc1) (tuple uc2) )
2,270✔
906
      in
907
      [%log info] "Loading zkApp commands" ;
2✔
908
      let%bind unsorted_zkapp_cmds_list =
909
        Deferred.List.map zkapp_cmd_ids ~f:(fun id ->
2✔
910
            let open Deferred.Let_syntax in
750✔
911
            match%map
912
              Caqti_async.Pool.use (fun db -> Sql.Zkapp_command.run db id) pool
750✔
913
            with
914
            | Ok [] ->
×
915
                failwithf "Expected at least one zkApp command with id %d" id ()
916
            | Ok zkapp_cmds ->
750✔
917
                zkapp_cmds
918
            | Error msg ->
×
919
                failwithf
920
                  "Error querying for zkApp commands with id %d, error %s" id
921
                  (Caqti_error.show msg) () )
×
922
      in
923
      let unsorted_zkapp_cmds = List.concat unsorted_zkapp_cmds_list in
2✔
924
      let filtered_zkapp_cmds =
2✔
925
        List.filter unsorted_zkapp_cmds ~f:(fun (cmd : Sql.Zkapp_command.t) ->
926
            Int64.( >= ) cmd.global_slot_since_genesis
1,011✔
927
              input.start_slot_since_genesis
928
            && Int.Set.mem block_ids cmd.block_id )
1,011✔
929
      in
930
      [%log info] "Will replay %d zkApp commands"
2✔
931
        (List.length filtered_zkapp_cmds) ;
2✔
932
      let sorted_zkapp_cmds =
2✔
933
        List.sort filtered_zkapp_cmds ~compare:(fun sc1 sc2 ->
934
            let tuple (sc : Sql.Zkapp_command.t) =
5,515✔
935
              (sc.global_slot_since_genesis, sc.sequence_no)
11,030✔
936
            in
937
            [%compare: int64 * int] (tuple sc1) (tuple sc2) )
5,515✔
938
      in
939
      let checkpoint_interval_i64 =
2✔
940
        Option.map checkpoint_interval ~f:Int64.of_int
941
      in
942
      let checkpoint_target =
2✔
943
        ref
944
          (Option.map checkpoint_interval_i64 ~f:(fun interval ->
2✔
945
               Int64.(input.start_slot_since_genesis + interval) ) )
1✔
946
      in
947
      let%bind max_canonical_slot =
948
        query_db ~f:(fun db -> Sql.Block.get_max_canonical_slot db ())
2✔
949
      in
950
      let%bind genesis_snarked_ledger_hash =
951
        let%map hash_str =
952
          query_db ~f:(fun db ->
953
              Sql.Block.genesis_snarked_ledger db input.start_slot_since_genesis )
2✔
954
        in
955
        Frozen_ledger_hash.of_base58_check_exn hash_str
2✔
956
      in
957
      let incr_checkpoint_target () =
2✔
958
        Option.iter !checkpoint_target ~f:(fun target ->
3✔
959
            match checkpoint_interval_i64 with
3✔
960
            | Some interval ->
3✔
961
                let new_target = Int64.(target + interval) in
962
                if Int64.( <= ) new_target max_canonical_slot then (
2✔
963
                  [%log info] "Checkpoint target was %Ld, setting to %Ld" target
2✔
964
                    new_target ;
965
                  checkpoint_target := Some new_target )
2✔
966
                else (
1✔
967
                  (* set target so it can't be reached *)
968
                  [%log info]
1✔
969
                    "Checkpoint target was %Ld, new target would be at \
970
                     noncanonical slot, set target to unreachable value"
971
                    target ;
972
                  checkpoint_target := Some Int64.max_value )
1✔
973
            | None ->
×
974
                failwith "Expected a checkpoint interval" )
975
      in
976
      let found_snarked_ledger_hash = ref false in
977
      (* See PR #9782. *)
978
      let state_hashes_to_avoid =
979
        (* devnet *)
980
        [ "3NKNU4WceYUjnQbxaUAmcHQzhGhC8ZxkYKqDKojKMpVjoj9WQZM6"
981
        ; "3NKvaxewhJ9e1GWvFFT83p4MA2MPFChFoQTmdJ2zeBNX9rLorGFH"
982
        ; "3NKNDWt8f1vVeFdBN8FCyHnAwnc5oXR18UvqriW2tQtHABTgX2tu"
983
        ; "3NLR1VaGKs36byogm4atXtiNVre3TtWrrg1Btt3HxGZ2mEBEN5hg"
984
        ; "3NL3fHu5bAqBNMmQ1Jh3HPZq7xX67WKFyAEUs2FFHJeiYxbEiq69"
985
        ; "3NLuEU1bJ462uzkJpQXDCo2DcpkkJLbK9kXgwpCYHDuofXo7Smrr"
986
        ; "3NK9ZYikzJDzXK7CPjyr7jo1S4ZGfKUKa18uTZd96X7YVcQihW1W"
987
        ; "3NK6PXGHsrRo8iYzWx43rtnyN2ynmZ7enWU7CMWH6oTUi5CRck7c"
988
        ; "3NKNrQG3DvyxDAuhudq7UhYRQ4GL7suKHcfQ3q7nUDXS1NjHyuZE"
989
        ; "3NKcbRVyPHeBoWiK8AHXVVYxswv5c4kpmECRS3LTbAVCHzrxbzuH"
990
        ; "3NKoxfcbnSkFSkFaMpmpgfVFqQmrwR6xkfpCHePLPh5uYKQLdpJt"
991
        ; "3NKVfBD1kXDJ8ZM3xhR5TFZXBRAs5UanSewxQRpXFmoxGxDddkP5"
992
        ; "3NKuotwHEdKRBK1yuDNkVUXqCnz5dPpS1xL4UJWghrAUB3t9pRQg"
993
        ; "3NKou5o9gJpcKBp8LnkUQgFBBQFZB6z7GD88pWDdwgVkfV8HmQTW"
994
        ; "3NKaqDsxAAFMvvxw22PLDkFaQyeHqioxhvb6BcpzbB3i8uaQdta7"
995
        ; "3NKCDffYzX5VMH3eH3G8CU6Ba6FndrevGBaJ9NCU7U6nv3iv2bpN"
996
        ; "3NL9sVkyZLLHFEvGPzr4ihYPzzZ3W3GixxjofeZib8qbe8hE4jUg"
997
        ; "3NLWggBpXBZxaV2R1DaJrL51uS6NshyDejbPn1gpWhp4E9t1cvPU"
998
        ; "3NL2cRDQkXEwnv33jTY5UKxLpyUpxKhcnwq7hQcfdDwc9QqXg5K9"
999
        ; "3NKvh8kXNtaJAhuC6Eqa8K4r2whzLuN5G7F8M14GTB1tcFKERp3z"
1000
        ; "3NKVo2E1TGfb2pQ3jv84m7tVid4d6AHnKbDMZLWcrPxWwQJTYiQg"
1001
        ; "3NL8d9RgUbDXjy92eH6ZVFc1ozn3darQx8u9EViMiCqrv5Ywe42x"
1002
        ; "3NLGBF3yXbnQQxophx4YZfdXGcTt11KjC7jv8qaf3b8B8tZdrCBh"
1003
        ; "3NKAbgwCDsdW6m18FSj8mBE2SpdMd7gopc5NMqGHxkqqLykMZrCR"
1004
        ; "3NKFtTCSqVqKdbm6unoCiQWnKnozwYsiey5aPegV5xMR2MNR9kjZ"
1005
        ; "3NKEyaQdmFtYWx2swLjkcpBeQGsfo8riNH6Kbdr9myWUQrZkbqjV"
1006
        ; "3NK7jasQXuACjzJ5mBPNr13Y6Yt8vD6piLc1waFBU4Jte8WzEZ4h"
1007
        ; "3NLPbRNQi6JFbh7DW9ibPgRhUnExwGaMpiuGJdEQ6Na47kWcP3PW"
1008
        ; "3NK4EUku6d9fmU3P1t7NnoxsN4sg699JSskJZ4nJs1mWSHs1CL4v"
1009
        ; "3NLDE8xKUvKMiSpLSd9uvnQaRiXYW6MWw7UXpFD2wbq4WeKRAnKM"
1010
        ; "3NLZYyohgxGFE3hw6o6tfvXnZLoG6gyzr67WNGPFfpugHtoXaFGq" (*mainnet*)
1011
        ; "3NKCedb2xxrgiaBFKVpxAJ9Kp7Tu1JG4qCUppJmt3c1RULQYQtrZ"
1012
        ; "3NKjb8G3Sc4Z5hLckDy2Wg8soZmdghenyAYDuHVF5uNV36Td9DZt"
1013
        ; "3NKKrhT3QX5tUS18kyGkYnjfNCCppgjy9zcZrmtMeNynEce47zpj"
1014
        ; "3NKzdd7UjWLygUvgKfJwd3MVj3PD1GnXDHHGoTLyXinVvSoFRyX5"
1015
        ; "3NKpPvE3NfGnbqRU4U6fDCMdUi9c54kphuDY4jniJuHZ62MyPWmr"
1016
        ; "3NLP9qXSUAo9b8XjLZ8YpvdvEJ1g4TUdeznBH5kDAFv6kYYCGY51"
1017
        ; "3NLLEfqqPdWHDtM7nw4S27uejcLZQ5D7N3BzmCNR5acYSaGuJ4pH"
1018
        ; "3NKJoVQRihbwMUTDJKv4patDDMF8xGrvAaxQ9d2QJovJFegSuEQV"
1019
        ; "3NKQ8W6L77xjPbz9sPNp357gfJ6a5LMD8K8kwGnW5jyULcCbd5Su"
1020
        ; "3NLYL2dmLyGd889rwF5EmdjWE2BBGCZcHAwJA8MotiPVDiXLvktw"
1021
        ; "3NLhmVdQvxpQLNvyehAXZKDsVrkgjb812VfpbgKqBRWSLa9c1kAA"
1022
        ; "3NKX71ifBPJCZFDLdeLmhY97Zc8Y3xt2JZTFLgqtTnv53JvRcbzx"
1023
        ; "3NLUhVLiWav4kBszLKZ6oDNC2BkbeX2cftsUGb6egeixjvqu3qqt"
1024
        ; "3NKtmu2j6UJ9oggDJbtY6UDzZrMgDF8CH5A5xs3GyQUqMq4nS1ZN"
1025
        ; "3NKJbfqrRzsjDC3FkSv5tsKQ7yp1xxYsXcs6arsoj18MKhcMVNeA"
1026
        ; "3NKy6JUgC8r6ZGCKcQxPmKu9pN8xv5mGqVEiC1z1dLuVYhc2KEvV"
1027
        ; "3NLi5AWq9YCT2XrB5vxkC5Psxrjv3JjHmK5DX87duztbCcL2oJkD"
1028
        ; "3NKm5VGDQXtekWf3erWfjcHciGzjAWB4u7WNE7gAHGYjvkFbF5xj"
1029
        ; "3NLnTnSMFayzpAahTgyL9mY1og2HDJjcg9kpKsQT5iQcPJ7FtJtJ"
1030
        ; "3NKGm1K6T9pV7ygFqtScugMQB7EhDozsiz8Fe9LAYG2yx8yYie6p"
1031
        ; "3NKj4YqZq1RCMDK3YM1gRtFZ2fspR1sNbRjQw4GA6Ut2Ar16jEYF"
1032
        ; "3NLC7zugGno2Emt3w2DRJx6LdRzmoiW4F9PuAYurLQFmWFqEer1V"
1033
        ; "3NKAKrhJrvFJtgMvr72ZXTaWPFisgDaND3AxZoPR6gAff5qDuKcq"
1034
        ; "3NKDaz4DVLp4bQ5FJiXjEQPWmwgzvXAfCLY3w8d5NjzJZNVXnF6Q"
1035
        ; "3NLKV4BUZwzpYvqcByRhJoKyME6Q5KEcrHPVY21XPfXsbPp486eu"
1036
        ; "3NLbwy3BrF7gxEPF9WPoJPeED9NUC2RXdDnoJf6GvRwzksJThnM9"
1037
        ; "3NKfHZ5DJkL3jjzstrfTCjHczMrPzvBsgPQ66trR9yqkE9dHJ7AD"
1038
        ; "3NKUaab3R1mcG9caqyUUntpjvYD7VYUJ7rWjTCbgxzob3miG3WHK"
1039
        ; "3NL2aQWNi6JadvfmwkyJVCtd9MjVwDCbvY1bfHbwCm1nJrDwSV2A"
1040
        ; "3NK7ANsW4LQ62Hk8DJNEF36yyccoECJKmdeAyaf96WmksR2TyM3C"
1041
        ; "3NKa82y8gNUe8ePYjq7jEh38vyWVLEteHynK686D8qaebeHpmfsS"
1042
        ; "3NLcmRzkBdmFKqhpKEbw33A7GAd1EG7dg6CVwUhC4RKDTBZGnYDQ"
1043
        ; "3NLHTdvTPXxUn8YFy4z59NxDcX9DYhthFtv8aPNMmpm2pYuA6Tf6"
1044
        ]
1045
      in
1046
      (* apply commands in global slot, sequence order *)
1047
      let rec apply_commands ~last_global_slot_since_genesis ~last_block_id
1048
          ~(block_txns : Mina_transaction.Transaction.t list)
1049
          (internal_cmds : Sql.Internal_command.t list)
1050
          (user_cmds : Sql.User_command.t list)
1051
          (zkapp_cmds : Sql.Zkapp_command.t list) =
1052
        let check_ledger_hash_at_slot state_hash ledger_hash =
1,236✔
1053
          if Ledger_hash.equal (Ledger.merkle_root ledger) ledger_hash then
46✔
1054
            [%log info]
46✔
1055
              "Applied all commands at global slot since genesis %Ld, got \
1056
               expected ledger hash"
1057
              ~metadata:
1058
                [ ("ledger_hash", json_ledger_hash_of_ledger ledger)
46✔
1059
                ; ("state_hash", State_hash.to_yojson state_hash)
46✔
1060
                ; ( "global_slot_since_genesis"
1061
                  , `String (Int64.to_string last_global_slot_since_genesis) )
46✔
1062
                ; ("block_id", `Int last_block_id)
1063
                ]
1064
              last_global_slot_since_genesis
1065
          else if
×
1066
            List.mem state_hashes_to_avoid
1067
              (State_hash.to_base58_check state_hash)
×
1068
              ~equal:String.equal
1069
          then
1070
            [%log info]
×
1071
              ~metadata:
1072
                [ ("state_hash", `String (State_hash.to_base58_check state_hash))
×
1073
                ]
1074
              "This block has an inconsistent ledger hash due to a known \
1075
               historical issue."
1076
          else (
×
1077
            [%log error]
×
1078
              "Applied all commands at global slot since genesis %Ld, ledger \
1079
               hash differs from expected ledger hash"
1080
              ~metadata:
1081
                [ ("ledger_hash", json_ledger_hash_of_ledger ledger)
×
1082
                ; ("expected_ledger_hash", Ledger_hash.to_yojson ledger_hash)
×
1083
                ; ("state_hash", State_hash.to_yojson state_hash)
×
1084
                ; ( "global_slot_since_genesis"
1085
                  , `String (Int64.to_string last_global_slot_since_genesis) )
×
1086
                ]
1087
              last_global_slot_since_genesis ;
1088
            if continue_on_error then incr error_count else Core_kernel.exit 1 )
×
1089
        in
1090
        let check_account_accessed state_hash =
1091
          [%log spam] "Checking accounts accessed in block just processed"
46✔
1092
            ~metadata:
1093
              [ ("state_hash", State_hash.to_yojson state_hash)
46✔
1094
              ; ( "global_slot_since_genesis"
1095
                , `String (Int64.to_string last_global_slot_since_genesis) )
46✔
1096
              ; ("block_id", `Int last_block_id)
1097
              ] ;
1098
          let%bind accounts_accessed_db =
1099
            query_db ~f:(fun db ->
1100
                Processor.Accounts_accessed.all_from_block db last_block_id )
46✔
1101
          in
1102
          let%bind accounts_created_db =
1103
            query_db ~f:(fun db ->
1104
                Processor.Accounts_created.all_from_block db last_block_id )
46✔
1105
          in
1106
          [%log spam]
46✔
1107
            "Verifying that accounts created are also deemed accessed in block \
1108
             with global slot since genesis %Ld"
1109
            last_global_slot_since_genesis ;
1110
          (* every account created in preceding block is an accessed account in preceding block *)
1111
          List.iter accounts_created_db
46✔
1112
            ~f:(fun { account_identifier_id = acct_id_created; _ } ->
1113
              if
×
1114
                Option.is_none
1115
                  (List.find accounts_accessed_db
×
1116
                     ~f:(fun { account_identifier_id = acct_id_accessed; _ } ->
1117
                       acct_id_accessed = acct_id_created ) )
×
1118
              then (
×
1119
                [%log error] "Created account not present in accessed accounts"
×
1120
                  ~metadata:
1121
                    [ ("created_account_identifier_id", `Int acct_id_created)
1122
                    ; ("state_hash", State_hash.to_yojson state_hash)
×
1123
                    ; ( "global_slot_since_genesis"
1124
                      , `String (Int64.to_string last_global_slot_since_genesis)
×
1125
                      )
1126
                    ; ("block_id", `Int last_block_id)
1127
                    ] ;
1128
                if continue_on_error then incr error_count
×
1129
                else Core_kernel.exit 1 ) ) ;
×
1130
          [%log spam]
46✔
1131
            "Verifying accounts accessed in block with global slot since \
1132
             genesis %Ld"
1133
            last_global_slot_since_genesis ;
1134
          let%map accounts_accessed =
1135
            Deferred.List.map accounts_accessed_db
46✔
1136
              ~f:(Archive_lib.Load_data.get_account_accessed ~pool)
1137
          in
1138
          List.iter accounts_accessed ~f:(fun (index, account) ->
46✔
1139
              let account_id =
150✔
1140
                Account_id.create account.public_key account.token_id
1141
              in
1142
              let index_in_ledger =
150✔
1143
                Ledger.index_of_account_exn ledger account_id
1144
              in
1145
              if index <> index_in_ledger then (
×
1146
                [%log error]
×
1147
                  "Account index in ledger does not match index in database"
1148
                  ~metadata:
1149
                    [ ("index_in_ledger", `Int index_in_ledger)
1150
                    ; ("index_in_account_accessed", `Int index)
1151
                    ] ;
1152
                if continue_on_error then incr error_count
×
1153
                else Core_kernel.exit 1 ) ;
×
1154
              match Ledger.location_of_account ledger account_id with
150✔
1155
              | None ->
×
1156
                  [%log error] "Accessed account not in ledger"
×
1157
                    ~metadata:
1158
                      [ ("account_id", Account_id.to_yojson account_id) ] ;
×
1159
                  if continue_on_error then incr error_count
×
1160
                  else Core_kernel.exit 1
×
1161
              | Some loc ->
150✔
1162
                  let account_in_ledger =
1163
                    match Ledger.get ledger loc with
1164
                    | Some acct ->
150✔
1165
                        acct
1166
                    | None ->
×
1167
                        (* should be unreachable *)
1168
                        failwith
1169
                          "Account not in ledger, even though there's a \
1170
                           location for it"
1171
                  in
1172
                  if not @@ Account.equal account account_in_ledger then (
×
1173
                    [%log error]
×
1174
                      "Account in ledger does not match account in database"
1175
                      ~metadata:
1176
                        [ ("account_id", Account_id.to_yojson account_id)
×
1177
                        ; ( "account_in_ledger"
1178
                          , Account.to_yojson account_in_ledger )
×
1179
                        ; ("account_in_database", Account.to_yojson account)
×
1180
                        ] ;
1181
                    if continue_on_error then incr error_count
×
1182
                    else Core_kernel.exit 1 ) )
×
1183
        in
1184
        let log_state_hash_on_next_slot curr_global_slot_since_genesis =
1185
          match get_slot_hashes curr_global_slot_since_genesis with
46✔
1186
          | None ->
×
1187
              [%log fatal]
×
1188
                "Missing state hash information for current global slot" ;
1189
              Core.exit 1
×
1190
          | Some (state_hash, _ledger_hash, _snarked_hash) ->
46✔
1191
              [%log spam]
46✔
1192
                ~metadata:
1193
                  [ ( "state_hash"
1194
                    , `String (State_hash.to_base58_check state_hash) )
46✔
1195
                  ]
1196
                "Starting processing of commands in block with state_hash \
1197
                 $state_hash at global slot since genesis %Ld"
1198
                curr_global_slot_since_genesis
1199
        in
1200
        let run_transactions_on_slot_change ?(last_block = false) block_txns ()
46✔
1201
            =
1202
          match get_slot_hashes last_global_slot_since_genesis with
48✔
1203
          | None ->
×
1204
              if
1205
                Int64.equal last_global_slot_since_genesis
1206
                  input.start_slot_since_genesis
1207
              then (
×
1208
                [%log info]
×
1209
                  "No ledger hash information at start slot, not checking \
1210
                   against ledger, not running transactions" ;
1211
                Deferred.unit )
×
1212
              else (
×
1213
                [%log fatal]
×
1214
                  "Missing ledger hash information for last global slot, which \
1215
                   is not the start slot" ;
1216
                Core.exit 1 )
×
1217
          | Some (state_hash, ledger_hash, snarked_hash) ->
48✔
1218
              let write_checkpoint_file ~checkpoint_output_folder_opt
1219
                  ~checkpoint_file_prefix () =
1220
                let write_checkpoint () =
46✔
1221
                  write_replayer_checkpoint ~logger ~ledger
5✔
1222
                    ~last_global_slot_since_genesis ~max_canonical_slot
1223
                    ~checkpoint_output_folder_opt ~checkpoint_file_prefix
1224
                in
1225
                if last_block then write_checkpoint ()
2✔
1226
                else
1227
                  match !checkpoint_target with
44✔
1228
                  | None ->
22✔
1229
                      Deferred.unit
1230
                  | Some target ->
22✔
1231
                      if Int64.(last_global_slot_since_genesis >= target) then (
3✔
1232
                        incr_checkpoint_target () ; write_checkpoint () )
3✔
1233
                      else Deferred.unit
19✔
1234
              in
1235
              let rec count_txns ~signed_count ~zkapp_count ~fee_transfer_count
1236
                  ~coinbase_count = function
1237
                | [] ->
46✔
1238
                    [%log spam]
46✔
1239
                      "Replaying transactions in block with state hash \
1240
                       $state_hash"
1241
                      ~metadata:
1242
                        [ ("state_hash", State_hash.to_yojson state_hash)
46✔
1243
                        ; ( "global_slot_since_genesis"
1244
                          , `String
1245
                              (Int64.to_string last_global_slot_since_genesis)
46✔
1246
                          )
1247
                        ; ("block_id", `Int last_block_id)
1248
                        ; ("no_signed_commands", `Int signed_count)
1249
                        ; ("no_zkapp_commands", `Int zkapp_count)
1250
                        ; ("no_fee_transfers", `Int fee_transfer_count)
1251
                        ; ("no_coinbases", `Int coinbase_count)
1252
                        ]
1253
                | txn :: txns -> (
1,210✔
1254
                    match txn with
1255
                    | Mina_transaction.Transaction.Command cmd -> (
1,110✔
1256
                        match cmd with
1257
                        | User_command.Signed_command _ ->
360✔
1258
                            count_txns ~signed_count:(signed_count + 1)
1259
                              ~zkapp_count ~fee_transfer_count ~coinbase_count
1260
                              txns
1261
                        | Zkapp_command _ ->
750✔
1262
                            count_txns ~signed_count
1263
                              ~zkapp_count:(zkapp_count + 1) ~fee_transfer_count
1264
                              ~coinbase_count txns )
1265
                    | Fee_transfer _ ->
54✔
1266
                        count_txns ~signed_count ~zkapp_count
1267
                          ~fee_transfer_count:(fee_transfer_count + 1)
1268
                          ~coinbase_count txns
1269
                    | Coinbase _ ->
46✔
1270
                        count_txns ~signed_count ~zkapp_count
1271
                          ~fee_transfer_count
1272
                          ~coinbase_count:(coinbase_count + 1) txns )
1273
              in
1274
              let run_transactions () =
1275
                count_txns ~signed_count:0 ~zkapp_count:0 ~fee_transfer_count:0
46✔
1276
                  ~coinbase_count:0 block_txns ;
1277
                let%bind txn_state_view =
1278
                  get_parent_state_view ~pool last_block_id
46✔
1279
                in
1280
                let apply_transaction_phases txns =
46✔
1281
                  let%bind phase_1s =
1282
                    Deferred.List.mapi txns ~f:(fun n txn ->
46✔
1283
                        match
1,210✔
1284
                          Ledger.apply_transaction_first_pass
1285
                            ~constraint_constants
1286
                            ~global_slot:
1287
                              (Mina_numbers.Global_slot_since_genesis.of_uint32
1,210✔
1288
                                 (Unsigned.UInt32.of_int64
1,210✔
1289
                                    last_global_slot_since_genesis ) )
1290
                            ~txn_state_view ledger txn
1291
                        with
1292
                        | Ok partially_applied ->
1,210✔
1293
                            (* the current ledger may become a snarked ledger *)
1294
                            First_pass_ledger_hashes.add
1295
                              (Ledger.merkle_root ledger) ;
1,210✔
1296
                            let%bind () =
1297
                              update_staking_epoch_data ~logger pool
1,210✔
1298
                                ~last_block_id ~ledger ~staking_epoch_ledger
1299
                                ~staking_seed
1300
                            in
1301
                            let%map () =
1302
                              update_next_epoch_data ~logger pool ~last_block_id
1,210✔
1303
                                ~ledger ~next_epoch_ledger ~next_seed
1304
                            in
1305
                            partially_applied
1,210✔
1306
                        | Error err ->
×
1307
                            [%log error]
×
1308
                              "Error during Phase 1 application of transaction \
1309
                               %d (0-based) in block with state hash \
1310
                               $state_hash"
1311
                              n
1312
                              ~metadata:
1313
                                [ ("state_hash", State_hash.to_yojson state_hash)
×
1314
                                ; ( "transaction"
1315
                                  , Mina_transaction.Transaction.to_yojson txn
×
1316
                                  )
1317
                                ; ("error", `String (Error.to_string_hum err))
×
1318
                                ] ;
1319
                            Error.raise err )
×
1320
                  in
1321
                  Deferred.List.iter phase_1s ~f:(fun partial ->
46✔
1322
                      match
1,210✔
1323
                        Ledger.apply_transaction_second_pass ledger partial
1324
                      with
1325
                      | Ok _applied ->
1,210✔
1326
                          let%bind () =
1327
                            update_staking_epoch_data ~logger pool
1,210✔
1328
                              ~last_block_id ~ledger ~staking_epoch_ledger
1329
                              ~staking_seed
1330
                          in
1331
                          update_next_epoch_data ~logger pool ~last_block_id
1,210✔
1332
                            ~ledger ~next_epoch_ledger ~next_seed
1333
                      | Error err ->
×
1334
                          (* must be a zkApp *)
1335
                          ( match partial with
1336
                          | Ledger.Transaction_partially_applied.Zkapp_command
×
1337
                              zk_partial ->
1338
                              let cmd = zk_partial.command in
1339
                              [%log error]
×
1340
                                "Error during Phase 2 application of \
1341
                                 partially-applied zkApp in block with state \
1342
                                 hash $state_hash"
1343
                                ~metadata:
1344
                                  [ ( "state_hash"
1345
                                    , State_hash.to_yojson state_hash )
×
1346
                                  ; ( "zkapp_command"
1347
                                    , Zkapp_command.to_yojson cmd )
×
1348
                                  ; ("error", `String (Error.to_string_hum err))
×
1349
                                  ]
1350
                          | _ ->
×
1351
                              failwith
1352
                                "Unexpected phase 2 failure of non-zkApp \
1353
                                 command" ) ;
1354
                          Error.raise err )
1355
                in
1356
                apply_transaction_phases (List.rev block_txns)
46✔
1357
              in
1358
              ( if
1359
                Frozen_ledger_hash.equal snarked_hash
1360
                  (First_pass_ledger_hashes.get_last_snarked_hash ())
48✔
1361
              then
1362
                [%log spam]
×
1363
                  "Snarked ledger hash same as in the preceding block, not \
1364
                   checking it again"
1365
              else if
48✔
1366
              Frozen_ledger_hash.equal snarked_hash genesis_snarked_ledger_hash
1367
            then
1368
                [%log spam] "Snarked ledger hash is genesis snarked ledger hash"
48✔
1369
              else
1370
                match First_pass_ledger_hashes.find snarked_hash with
×
1371
                | None ->
×
1372
                    if not !found_snarked_ledger_hash then (
×
1373
                      [%log info]
×
1374
                        "Current snarked ledger hash not among first-pass \
1375
                         ledger hashes, but we haven't yet found one. The \
1376
                         transaction that created this ledger hash might have \
1377
                         been in an older replayer run that created a \
1378
                         checkpoint file without saved first-pass ledger \
1379
                         hashes" ;
1380
                      First_pass_ledger_hashes.set_last_snarked_hash
×
1381
                        snarked_hash )
1382
                    else
1383
                      [%log error]
×
1384
                        "Current snarked ledger hash does not appear among \
1385
                         first-pass ledger hashes" ;
1386
                    if continue_on_error then incr error_count
×
1387
                    else Core_kernel.exit 1
×
1388
                | Some (_hash, n) ->
×
1389
                    [%log spam]
×
1390
                      "Found snarked ledger hash among first-pass ledger hashes" ;
1391
                    found_snarked_ledger_hash := true ;
×
1392
                    First_pass_ledger_hashes.set_last_snarked_hash snarked_hash ;
1393
                    First_pass_ledger_hashes.flush_older_than n ) ;
×
1394
              if List.is_empty block_txns then (
2✔
1395
                [%log spam]
2✔
1396
                  "No transactions to run for block with state hash $state_hash"
1397
                  ~metadata:
1398
                    [ ("state_hash", State_hash.to_yojson state_hash)
2✔
1399
                    ; ( "global_slot_since_genesis"
1400
                      , `String (Int64.to_string last_global_slot_since_genesis)
2✔
1401
                      )
1402
                    ; ("block_id", `Int last_block_id)
1403
                    ] ;
1404
                Deferred.unit )
2✔
1405
              else
1406
                let%bind () = run_transactions () in
46✔
1407
                let () = check_ledger_hash_at_slot state_hash ledger_hash in
46✔
1408
                (* don't check ledger hash, because depth changed from mainnet *)
1409
                let%bind () = check_account_accessed state_hash in
46✔
1410
                log_state_hash_on_next_slot last_global_slot_since_genesis ;
46✔
1411
                write_checkpoint_file ~checkpoint_output_folder_opt
46✔
1412
                  ~checkpoint_file_prefix ()
1413
        in
1414
        (* a sequence is a command type, slot, sequence number triple *)
1415
        let get_internal_cmd_sequence (ic : Sql.Internal_command.t) =
1416
          (`Internal_command, ic.global_slot_since_genesis, ic.sequence_no)
1,228✔
1417
        in
1418
        let get_user_cmd_sequence (uc : Sql.User_command.t) =
1419
          (`User_command, uc.global_slot_since_genesis, uc.sequence_no)
1,178✔
1420
        in
1421
        let get_zkapp_cmd_sequence (sc : Sql.Zkapp_command.t) =
1422
          (`Zkapp_command, sc.global_slot_since_genesis, sc.sequence_no)
1,228✔
1423
        in
1424
        let command_type_of_sequences seqs =
1425
          let compare (_cmd_ty1, slot1, seq_no1) (_cmd_ty2, slot2, seq_no2) =
1,228✔
1426
            [%compare: int64 * int] (slot1, seq_no1) (slot2, seq_no2)
3,222✔
1427
          in
1428
          let sorted_seqs = List.sort seqs ~compare in
1429
          let cmd_ty, _slot, _seq_no = List.hd_exn sorted_seqs in
1,228✔
1430
          cmd_ty
1,228✔
1431
        in
1432
        let check_for_complete_block ~cmd_global_slot_since_genesis =
1433
          if
1,234✔
1434
            Int64.( > ) cmd_global_slot_since_genesis
1435
              last_global_slot_since_genesis
1436
          then
1437
            let%map () = run_transactions_on_slot_change block_txns () in
46✔
1438
            []
46✔
1439
          else return block_txns
1,188✔
1440
        in
1441
        match (internal_cmds, user_cmds, zkapp_cmds) with
1442
        | [], [], [] ->
2✔
1443
            (* all done *)
1444
            let%bind _ =
1445
              run_transactions_on_slot_change ~last_block:true block_txns ()
2✔
1446
            in
1447
            Deferred.return
2✔
1448
              (staking_epoch_ledger, staking_seed, next_epoch_ledger, next_seed)
1449
        | ic :: ics, [], [] ->
6✔
1450
            (* only internal commands *)
1451
            let%bind block_txns0 =
1452
              check_for_complete_block
1453
                ~cmd_global_slot_since_genesis:ic.global_slot_since_genesis
1454
            in
1455
            let%bind block_txns, ics' =
1456
              let%map txn, ics' =
1457
                internal_cmds_to_transaction ~logger ~pool ic ics
6✔
1458
              in
1459
              ( Option.value_map txn ~default:block_txns0 ~f:(fun txn ->
6✔
1460
                    txn :: block_txns0 )
4✔
1461
              , ics' )
1462
            in
1463
            apply_commands ~block_txns
6✔
1464
              ~last_global_slot_since_genesis:ic.global_slot_since_genesis
1465
              ~last_block_id:ic.block_id ics' user_cmds zkapp_cmds
1466
        | [], uc :: ucs, [] ->
×
1467
            (* only user commands *)
1468
            let%bind block_txns =
1469
              check_for_complete_block
1470
                ~cmd_global_slot_since_genesis:uc.global_slot_since_genesis
1471
            in
1472
            let%bind txn = user_command_to_transaction ~logger ~pool uc in
×
1473
            apply_commands ~block_txns:(txn :: block_txns)
×
1474
              ~last_global_slot_since_genesis:uc.global_slot_since_genesis
1475
              ~last_block_id:uc.block_id internal_cmds ucs zkapp_cmds
1476
        | [], [], zkc :: zkcs ->
×
1477
            (* only zkApp commands *)
1478
            let%bind block_txns =
1479
              check_for_complete_block
1480
                ~cmd_global_slot_since_genesis:zkc.global_slot_since_genesis
1481
            in
1482
            let%bind txn = zkapp_command_to_transaction ~logger ~pool zkc in
×
1483
            apply_commands ~block_txns:(txn :: block_txns)
×
1484
              ~last_global_slot_since_genesis:zkc.global_slot_since_genesis
1485
              ~last_block_id:zkc.block_id internal_cmds user_cmds zkcs
1486
        | [], uc :: ucs, zkc :: zkcs -> (
×
1487
            (* no internal commands *)
1488
            let seqs =
1489
              [ get_user_cmd_sequence uc; get_zkapp_cmd_sequence zkc ]
×
1490
            in
1491
            match command_type_of_sequences seqs with
1492
            | `User_command ->
×
1493
                let%bind block_txns =
1494
                  check_for_complete_block
1495
                    ~cmd_global_slot_since_genesis:uc.global_slot_since_genesis
1496
                in
1497
                let%bind txn = user_command_to_transaction ~logger ~pool uc in
×
1498
                apply_commands ~block_txns:(txn :: block_txns)
×
1499
                  ~last_global_slot_since_genesis:uc.global_slot_since_genesis
1500
                  ~last_block_id:uc.block_id internal_cmds ucs zkapp_cmds
1501
            | `Zkapp_command ->
×
1502
                let%bind block_txns =
1503
                  check_for_complete_block
1504
                    ~cmd_global_slot_since_genesis:zkc.global_slot_since_genesis
1505
                in
1506
                let%bind txn = zkapp_command_to_transaction ~logger ~pool zkc in
×
1507
                apply_commands ~block_txns:(txn :: block_txns)
×
1508
                  ~last_global_slot_since_genesis:zkc.global_slot_since_genesis
1509
                  ~last_block_id:zkc.block_id internal_cmds user_cmds zkcs )
1510
        | ic :: ics, [], zkc :: zkcs -> (
50✔
1511
            (* no user commands *)
1512
            let seqs =
1513
              [ get_internal_cmd_sequence ic; get_zkapp_cmd_sequence zkc ]
50✔
1514
            in
1515
            match command_type_of_sequences seqs with
1516
            | `Internal_command ->
30✔
1517
                let%bind block_txns0 =
1518
                  check_for_complete_block
1519
                    ~cmd_global_slot_since_genesis:ic.global_slot_since_genesis
1520
                in
1521
                let%bind block_txns, ics' =
1522
                  let%map txn, ics' =
1523
                    internal_cmds_to_transaction ~logger ~pool ic ics
30✔
1524
                  in
1525
                  ( Option.value_map txn ~default:block_txns0 ~f:(fun txn ->
30✔
1526
                        txn :: block_txns0 )
20✔
1527
                  , ics' )
1528
                in
1529
                apply_commands ~block_txns
30✔
1530
                  ~last_global_slot_since_genesis:ic.global_slot_since_genesis
1531
                  ~last_block_id:ic.block_id ics' user_cmds zkapp_cmds
1532
            | `Zkapp_command ->
20✔
1533
                let%bind block_txns =
1534
                  check_for_complete_block
1535
                    ~cmd_global_slot_since_genesis:zkc.global_slot_since_genesis
1536
                in
1537
                let%bind txn = zkapp_command_to_transaction ~logger ~pool zkc in
20✔
1538
                apply_commands ~block_txns:(txn :: block_txns)
20✔
1539
                  ~last_global_slot_since_genesis:zkc.global_slot_since_genesis
1540
                  ~last_block_id:zkc.block_id internal_cmds user_cmds zkcs )
1541
        | ic :: ics, uc :: ucs, [] -> (
×
1542
            (* no zkApp commands *)
1543
            let seqs =
1544
              [ get_internal_cmd_sequence ic; get_user_cmd_sequence uc ]
×
1545
            in
1546
            match command_type_of_sequences seqs with
1547
            | `Internal_command ->
×
1548
                let%bind block_txns0 =
1549
                  check_for_complete_block
1550
                    ~cmd_global_slot_since_genesis:ic.global_slot_since_genesis
1551
                in
1552
                let%bind block_txns, ics' =
1553
                  let%map txn, ics' =
1554
                    internal_cmds_to_transaction ~logger ~pool ic ics
×
1555
                  in
1556
                  ( Option.value_map txn ~default:block_txns0 ~f:(fun txn ->
×
1557
                        txn :: block_txns0 )
×
1558
                  , ics' )
1559
                in
1560
                apply_commands ~block_txns
×
1561
                  ~last_global_slot_since_genesis:ic.global_slot_since_genesis
1562
                  ~last_block_id:ic.block_id ics' user_cmds zkapp_cmds
1563
            | `User_command ->
×
1564
                let%bind block_txns =
1565
                  check_for_complete_block
1566
                    ~cmd_global_slot_since_genesis:uc.global_slot_since_genesis
1567
                in
1568
                let%bind txn = user_command_to_transaction ~logger ~pool uc in
×
1569
                apply_commands ~block_txns:(txn :: block_txns)
×
1570
                  ~last_global_slot_since_genesis:uc.global_slot_since_genesis
1571
                  ~last_block_id:uc.block_id internal_cmds ucs zkapp_cmds )
1572
        | ic :: ics, uc :: ucs, zkc :: zkcs -> (
1,178✔
1573
            (* internal, user, and zkApp commands *)
1574
            let seqs =
1575
              [ get_internal_cmd_sequence ic
1,178✔
1576
              ; get_user_cmd_sequence uc
1,178✔
1577
              ; get_zkapp_cmd_sequence zkc
1,178✔
1578
              ]
1579
            in
1580
            match command_type_of_sequences seqs with
1581
            | `Internal_command ->
88✔
1582
                let%bind block_txns0 =
1583
                  check_for_complete_block
1584
                    ~cmd_global_slot_since_genesis:ic.global_slot_since_genesis
1585
                in
1586
                let%bind block_txns, ics' =
1587
                  let%map txn, ics' =
1588
                    internal_cmds_to_transaction ~logger ~pool ic ics
88✔
1589
                  in
1590
                  ( Option.value_map txn ~default:block_txns0 ~f:(fun txn ->
88✔
1591
                        txn :: block_txns0 )
76✔
1592
                  , ics' )
1593
                in
1594
                apply_commands ~block_txns
88✔
1595
                  ~last_global_slot_since_genesis:ic.global_slot_since_genesis
1596
                  ~last_block_id:ic.block_id ics' user_cmds zkapp_cmds
1597
            | `User_command ->
360✔
1598
                let%bind block_txns =
1599
                  check_for_complete_block
1600
                    ~cmd_global_slot_since_genesis:uc.global_slot_since_genesis
1601
                in
1602
                let%bind txn = user_command_to_transaction ~logger ~pool uc in
360✔
1603
                apply_commands ~block_txns:(txn :: block_txns)
360✔
1604
                  ~last_global_slot_since_genesis:uc.global_slot_since_genesis
1605
                  ~last_block_id:uc.block_id internal_cmds ucs zkapp_cmds
1606
            | `Zkapp_command ->
730✔
1607
                let%bind block_txns =
1608
                  check_for_complete_block
1609
                    ~cmd_global_slot_since_genesis:zkc.global_slot_since_genesis
1610
                in
1611
                let%bind txn = zkapp_command_to_transaction ~logger ~pool zkc in
730✔
1612
                apply_commands ~block_txns:(txn :: block_txns)
730✔
1613
                  ~last_global_slot_since_genesis:zkc.global_slot_since_genesis
1614
                  ~last_block_id:zkc.block_id internal_cmds user_cmds zkcs )
1615
      in
1616
      let%bind start_slot_since_genesis =
1617
        let%map slot_opt =
1618
          query_db ~f:(fun db ->
1619
              Sql.Block.get_next_slot db input.start_slot_since_genesis )
2✔
1620
        in
1621
        match slot_opt with
2✔
1622
        | Some slot ->
2✔
1623
            slot
1624
        | None ->
×
1625
            failwithf
1626
              "There is no slot in the database greater than equal to the \
1627
               start slot %Ld given in the input file"
1628
              input.start_slot_since_genesis ()
1629
      in
1630
      if
2✔
1631
        not
1632
          (Int64.equal start_slot_since_genesis input.start_slot_since_genesis)
2✔
1633
      then
1634
        [%log info]
×
1635
          "Starting with next available global slot in the archive database"
1636
          ~metadata:
1637
            [ ( "input_start_slot"
1638
              , `String (Int64.to_string input.start_slot_since_genesis) )
×
1639
            ; ( "available_start_slot"
1640
              , `String (Int64.to_string start_slot_since_genesis) )
×
1641
            ] ;
1642
      [%log info] "At start global slot %Ld, ledger hash"
2✔
1643
        start_slot_since_genesis
1644
        ~metadata:[ ("ledger_hash", json_ledger_hash_of_ledger ledger) ] ;
2✔
1645
      let%bind staking_epoch_ledger, staking_seed, next_epoch_ledger, next_seed
1646
          =
1647
        apply_commands ~block_txns:[]
2✔
1648
          ~last_global_slot_since_genesis:start_slot_since_genesis
1649
          ~last_block_id:oldest_block_id sorted_internal_cmds sorted_user_cmds
1650
          sorted_zkapp_cmds
1651
      in
1652
      match input.target_epoch_ledgers_state_hash with
2✔
1653
      | None ->
2✔
1654
          [%log info] "No target epoch ledger hash supplied, not writing output" ;
2✔
1655
          Deferred.unit
2✔
1656
      | Some target_epoch_ledgers_state_hash -> (
×
1657
          match output_file_opt with
1658
          | None ->
×
1659
              [%log info] "Output file not supplied, not writing output" ;
×
1660
              return ()
×
1661
          | Some output_file ->
×
1662
              if Int.equal !error_count 0 then (
×
1663
                [%log info] "Writing output to $output_file"
×
1664
                  ~metadata:[ ("output_file", `String output_file) ] ;
1665
                let%bind output =
1666
                  let%map output =
1667
                    create_output ~target_epoch_ledgers_state_hash
×
1668
                      ~target_fork_state_hash:
1669
                        (State_hash.of_base58_check_exn target_state_hash)
×
1670
                      ~ledger ~staking_epoch_ledger:!staking_epoch_ledger
1671
                      ~staking_seed:!staking_seed
1672
                      ~next_epoch_ledger:!next_epoch_ledger
1673
                      ~next_seed:!next_seed input.genesis_ledger
1674
                  in
1675
                  output_to_yojson output |> Yojson.Safe.pretty_to_string
×
1676
                in
1677
                return
×
1678
                @@ Out_channel.with_file output_file ~f:(fun oc ->
×
1679
                       Out_channel.output_string oc output ) )
×
1680
              else (
×
1681
                [%log error] "There were %d errors, not writing output"
×
1682
                  !error_count ;
1683
                exit 1 ) ) )
×
1684

1685
let () =
1686
  let constraint_constants = Genesis_constants.Compiled.constraint_constants in
1687
  let proof_level = Genesis_constants.Proof_level.Full in
1688
  Command.(
1689
    run
×
1690
      (let open Let_syntax in
1691
      Command.async ~summary:"Replay transactions from Mina archive database"
2✔
1692
        (let%map input_file =
1693
           Param.flag "--input-file"
2✔
1694
             ~doc:"file File containing the starting ledger"
1695
             Param.(required string)
2✔
1696
         and output_file_opt =
1697
           Param.flag "--output-file"
2✔
1698
             ~doc:"file File containing the resulting ledger"
1699
             Param.(optional string)
2✔
1700
         and archive_uri =
1701
           Param.flag "--archive-uri"
2✔
1702
             ~doc:
1703
               "URI URI for connecting to the archive database (e.g., \
1704
                postgres://$USER@localhost:5432/archiver)"
1705
             Param.(required string)
2✔
1706
         and continue_on_error =
1707
           Param.flag "--continue-on-error"
2✔
1708
             ~doc:"Continue processing after errors" Param.no_arg
1709
         and checkpoint_interval =
1710
           Param.flag "--checkpoint-interval"
2✔
1711
             ~doc:"NN Write checkpoint file every NN slots"
1712
             Param.(optional int)
2✔
1713
         and checkpoint_output_folder_opt =
1714
           Param.flag "--checkpoint-output-folder"
2✔
1715
             ~doc:"file Folder containing the resulting checkpoints"
1716
             Param.(optional string)
2✔
1717
         and genesis_dir_opt =
1718
           Param.flag "--genesis-ledger-dir"
2✔
1719
             ~doc:"DIR Directory that contains the genesis ledger"
1720
             Param.(optional string)
2✔
1721
         and checkpoint_file_prefix =
1722
           Param.flag "--checkpoint-file-prefix"
2✔
1723
             ~doc:"string Checkpoint file prefix (default: 'replayer')"
1724
             Param.(optional_with_default "replayer" string)
2✔
1725
         and log_json = Cli_lib.Flag.Log.json
1726
         and log_level = Cli_lib.Flag.Log.level
1727
         and file_log_level = Cli_lib.Flag.Log.file_log_level
1728
         and log_filename = Cli_lib.Flag.Log.file in
1729
         main ~input_file ~output_file_opt ~archive_uri ~checkpoint_interval
2✔
1730
           ~continue_on_error ~checkpoint_output_folder_opt
1731
           ~checkpoint_file_prefix ~genesis_dir_opt ~log_json ~log_level
1732
           ~file_log_level ~log_filename ~constraint_constants ~proof_level )))
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