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

MinaProtocol / mina / 2903

15 Nov 2024 01:59PM UTC coverage: 36.723% (-25.0%) from 61.682%
2903

Pull #16342

buildkite

dkijania
Merge branch 'dkijania/remove_publish_job_from_pr_comp' into dkijania/remove_publish_job_from_pr_dev
Pull Request #16342: [DEV] Publish debians only on nightly and stable

15 of 40 new or added lines in 14 files covered. (37.5%)

15175 existing lines in 340 files now uncovered.

24554 of 66863 relevant lines covered (36.72%)

20704.91 hits per line

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

7.24
/src/lib/network_pool/transaction_pool.ml
1
(** A pool of transactions that can be included in future blocks. Combined with
2
    the Network_pool module, this handles storing and gossiping the correct
3
    transactions (user commands) and providing them to the block producer code.
4
*)
5

4✔
6
open Core
7
open Async
8
open Mina_base
9
open Mina_transaction
10
open Pipe_lib
11
open Network_peer
12

13
let max_per_15_seconds = 10
14

15
(* TEMP HACK UNTIL DEFUNCTORING: transition frontier interface is simplified *)
16
module type Transition_frontier_intf = sig
17
  type t
18

19
  type staged_ledger
20

21
  module Breadcrumb : sig
22
    type t
23

24
    val staged_ledger : t -> staged_ledger
25
  end
26

27
  type best_tip_diff =
28
    { new_commands : User_command.Valid.t With_status.t list
29
    ; removed_commands : User_command.Valid.t With_status.t list
30
    ; reorg_best_tip : bool
31
    }
32

33
  val best_tip : t -> Breadcrumb.t
34

35
  val best_tip_diff_pipe : t -> best_tip_diff Broadcast_pipe.Reader.t
36
end
37

38
(* versioned type, outside of functors *)
39
module Diff_versioned = struct
40
  [%%versioned
41
  module Stable = struct
42
    [@@@no_toplevel_latest_type]
43

44
    module V2 = struct
45
      type t = User_command.Stable.V2.t list [@@deriving sexp, yojson, hash]
×
46

47
      let to_latest = Fn.id
48
    end
49
  end]
50

51
  (* We defer do any checking on signed-commands until the call to
52
     [add_from_gossip_gossip_exn].
53

54
     The real solution would be to have more explicit queueing to make sure things don't happen out of order, factor
55
     [add_from_gossip_gossip_exn] into [check_from_gossip_exn] (which just does
56
     the checks) and [set_from_gossip_exn] (which just does the mutating the pool),
57
     and do the same for snapp commands as well.
58
  *)
59
  type t = User_command.t list [@@deriving sexp, yojson]
×
60

61
  module Diff_error = struct
62
    [%%versioned
63
    module Stable = struct
64
      [@@@no_toplevel_latest_type]
65

66
      module V3 = struct
67
        type t =
8✔
68
          | Insufficient_replace_fee
×
69
          | Duplicate
×
70
          | Invalid_nonce
×
71
          | Insufficient_funds
×
72
          | Overflow
×
73
          | Bad_token
×
74
          | Unwanted_fee_token
×
75
          | Expired
×
76
          | Overloaded
×
77
          | Fee_payer_account_not_found
×
78
          | Fee_payer_not_permitted_to_send
×
79
          | After_slot_tx_end
×
80
        [@@deriving sexp, yojson, compare]
20✔
81

82
        let to_latest = Fn.id
83
      end
84
    end]
85

86
    (* IMPORTANT! Do not change the names of these errors as to adjust the
87
     * to_yojson output without updating Rosetta's construction API to handle
88
     * the changes *)
UNCOV
89
    type t = Stable.Latest.t =
×
90
      | Insufficient_replace_fee
×
91
      | Duplicate
×
92
      | Invalid_nonce
×
93
      | Insufficient_funds
×
94
      | Overflow
×
95
      | Bad_token
×
96
      | Unwanted_fee_token
×
97
      | Expired
×
98
      | Overloaded
×
99
      | Fee_payer_account_not_found
×
100
      | Fee_payer_not_permitted_to_send
×
101
      | After_slot_tx_end
×
102
    [@@deriving sexp, yojson]
103

104
    let to_string_name = function
105
      | Insufficient_replace_fee ->
×
106
          "insufficient_replace_fee"
107
      | Duplicate ->
×
108
          "duplicate"
109
      | Invalid_nonce ->
×
110
          "invalid_nonce"
111
      | Insufficient_funds ->
×
112
          "insufficient_funds"
113
      | Overflow ->
×
114
          "overflow"
115
      | Bad_token ->
×
116
          "bad_token"
117
      | Unwanted_fee_token ->
×
118
          "unwanted_fee_token"
UNCOV
119
      | Expired ->
×
120
          "expired"
121
      | Overloaded ->
×
122
          "overloaded"
123
      | Fee_payer_account_not_found ->
×
124
          "fee_payer_account_not_found"
125
      | Fee_payer_not_permitted_to_send ->
×
126
          "fee_payer_not_permitted_to_send"
127
      | After_slot_tx_end ->
×
128
          "after_slot_tx_end"
129

130
    let to_string_hum = function
131
      | Insufficient_replace_fee ->
×
132
          "This transaction would have replaced an existing transaction in the \
133
           pool, but the fee was too low"
134
      | Duplicate ->
×
135
          "This transaction is a duplicate of one already in the pool"
136
      | Invalid_nonce ->
×
137
          "This transaction had an invalid nonce"
138
      | Insufficient_funds ->
×
139
          "There are not enough funds in the fee-payer's account to execute \
140
           this transaction"
141
      | Overflow ->
×
142
          "Executing this transaction would result in an integer overflow"
143
      | Bad_token ->
×
144
          "This transaction uses non-default tokens where they are not \
145
           permitted"
146
      | Unwanted_fee_token ->
×
147
          "This transaction pays fees in a non-default token that this pool \
148
           does not accept"
149
      | Expired ->
×
150
          "This transaction has expired"
151
      | Overloaded ->
×
152
          "The diff containing this transaction was too large"
153
      | Fee_payer_account_not_found ->
×
154
          "Fee payer account was not found in the best tip ledger"
155
      | Fee_payer_not_permitted_to_send ->
×
156
          "Fee payer account permissions don't allow sending funds"
157
      | After_slot_tx_end ->
×
158
          "This transaction was submitted after the slot defined to stop \
159
           accepting transactions"
160
  end
161

162
  module Rejected = struct
163
    [%%versioned
164
    module Stable = struct
165
      [@@@no_toplevel_latest_type]
166

167
      module V3 = struct
168
        type t = (User_command.Stable.V2.t * Diff_error.Stable.V3.t) list
×
169
        [@@deriving sexp, yojson, compare]
20✔
170

171
        let to_latest = Fn.id
172
      end
173
    end]
174

175
    type t = Stable.Latest.t [@@deriving sexp, yojson, compare]
×
176
  end
177

178
  type rejected = Rejected.t [@@deriving sexp, yojson, compare]
×
179

180
  type verified = Transaction_hash.User_command_with_valid_signature.t list
×
181
  [@@deriving sexp, to_yojson]
182

183
  let summary t =
184
    Printf.sprintf
×
185
      !"Transaction_pool_diff of length %d with fee payer summary %s"
186
      (List.length t)
×
187
      ( String.concat ~sep:","
×
188
      @@ List.map ~f:User_command.fee_payer_summary_string t )
×
189

190
  let is_empty t = List.is_empty t
×
191
end
192

193
type Structured_log_events.t +=
194
  | Rejecting_command_for_reason of
UNCOV
195
      { command : User_command.t
×
UNCOV
196
      ; reason : Diff_versioned.Diff_error.t
×
UNCOV
197
      ; error_extra : (string * Yojson.Safe.t) list
×
198
      }
199
  [@@deriving register_event { msg = "Rejecting command because: $reason" }]
3✔
200

201
module type S = sig
202
  open Intf
203

204
  type transition_frontier
205

206
  module Resource_pool : sig
207
    include
208
      Transaction_resource_pool_intf
209
        with type transition_frontier := transition_frontier
210

211
    module Diff :
212
      Transaction_pool_diff_intf
213
        with type resource_pool := t
214
         and type Diff_error.t = Diff_versioned.Diff_error.t
215
         and type Rejected.t = Diff_versioned.Rejected.t
216
  end
217

218
  include
219
    Network_pool_base_intf
220
      with type resource_pool := Resource_pool.t
221
       and type transition_frontier := transition_frontier
222
       and type resource_pool_diff := Diff_versioned.t
223
       and type resource_pool_diff_verified := Diff_versioned.verified
224
       and type config := Resource_pool.Config.t
225
       and type transition_frontier_diff :=
226
        Resource_pool.transition_frontier_diff
227
       and type rejected_diff := Diff_versioned.rejected
228
end
229

230
(* Functor over user command, base ledger and transaction validator for
231
   mocking. *)
232
module Make0
233
    (Base_ledger : Intf.Base_ledger_intf) (Staged_ledger : sig
234
      type t
235

236
      val ledger : t -> Base_ledger.t
237
    end)
238
    (Transition_frontier : Transition_frontier_intf
239
                             with type staged_ledger := Staged_ledger.t) =
240
struct
241
  type verification_failure =
×
242
    | Command_failure of Diff_versioned.Diff_error.t
×
243
    | Invalid_failure of Verifier.invalid
×
244
  [@@deriving to_yojson]
245

246
  module Breadcrumb = Transition_frontier.Breadcrumb
247

248
  module Resource_pool = struct
249
    type transition_frontier_diff =
250
      Transition_frontier.best_tip_diff * Base_ledger.t
251

252
    let label = "transaction_pool"
253

254
    let preload_accounts ledger account_ids =
255
      let existing_account_ids, existing_account_locs =
12✔
256
        Set.to_list account_ids
12✔
257
        |> Base_ledger.location_of_account_batch ledger
12✔
258
        |> List.filter_map ~f:(function
12✔
UNCOV
259
             | id, Some loc ->
×
260
                 Some (id, loc)
UNCOV
261
             | _, None ->
×
262
                 None )
263
        |> List.unzip
264
      in
265
      Base_ledger.get_batch ledger existing_account_locs
12✔
266
      |> List.map ~f:snd
12✔
267
      |> List.zip_exn existing_account_ids
12✔
268
      |> List.fold ~init:Account_id.Map.empty ~f:(fun map (id, maybe_account) ->
UNCOV
269
             let account =
×
270
               Option.value_exn maybe_account
271
                 ~message:"Somehow a public key has a location but no account"
272
             in
UNCOV
273
             Map.add_exn map ~key:id ~data:account )
×
274

275
    module Config = struct
276
      type t =
×
277
        { trust_system : (Trust_system.t[@sexp.opaque])
×
278
        ; pool_max_size : int
×
279
              (* note this value needs to be mostly the same across gossipping nodes, so
280
                 nodes with larger pools don't send nodes with smaller pools lots of
281
                 low fee transactions the smaller-pooled nodes consider useless and get
282
                 themselves banned.
283

284
                 we offer this value separately from the one in genesis_constants, because
285
                 we may wish a different value for testing
286
              *)
287
        ; verifier : (Verifier.t[@sexp.opaque])
×
288
        ; genesis_constants : Genesis_constants.t
×
289
        ; slot_tx_end : Mina_numbers.Global_slot_since_hard_fork.t option
×
290
        ; compile_config : Mina_compile_config.t
×
291
        }
292
      [@@deriving sexp_of]
293

294
      (* remove next line if there's a way to force [@@deriving make] write a
295
         named parameter instead of an optional parameter *)
296
      let make ~trust_system ~pool_max_size ~verifier ~genesis_constants
297
          ~slot_tx_end ~compile_config =
298
        { trust_system
6✔
299
        ; pool_max_size
300
        ; verifier
301
        ; genesis_constants
302
        ; slot_tx_end
303
        ; compile_config
304
        }
305
    end
306

307
    let make_config = Config.make
308

309
    module Batcher = Batcher.Transaction_pool
310

311
    module Mutex = struct
312
      open Async
313

314
      type t = unit Mvar.Read_write.t
315

316
      let acquire (t : t) = Mvar.take t
×
317

318
      let release (t : t) =
319
        assert (Mvar.is_empty t) ;
×
320
        don't_wait_for (Mvar.put t ())
×
321

322
      let with_ t ~f =
323
        let%bind () = acquire t in
×
324
        let%map x = f () in
×
325
        release t ; x
×
326

327
      let create () =
328
        let t = Mvar.create () in
×
329
        don't_wait_for (Mvar.put t ()) ;
×
330
        t
×
331
    end
332

333
    module Vk_refcount_table = struct
334
      type t =
335
        { verification_keys :
336
            (int * Verification_key_wire.t) Zkapp_basic.F_map.Table.t
337
        ; account_id_to_vks : int Zkapp_basic.F_map.Map.t Account_id.Table.t
338
        ; vk_to_account_ids : int Account_id.Map.t Zkapp_basic.F_map.Table.t
339
        }
340

341
      let create () =
342
        { verification_keys = Zkapp_basic.F_map.Table.create ()
6✔
343
        ; account_id_to_vks = Account_id.Table.create ()
6✔
344
        ; vk_to_account_ids = Zkapp_basic.F_map.Table.create ()
6✔
345
        }
346

UNCOV
347
      let find_vk (t : t) = Hashtbl.find t.verification_keys
×
348

349
      let find_vks_by_account_id (t : t) account_id =
UNCOV
350
        match Hashtbl.find t.account_id_to_vks account_id with
×
UNCOV
351
        | None ->
×
352
            []
UNCOV
353
        | Some vks ->
×
UNCOV
354
            Map.keys vks
×
UNCOV
355
            |> List.map ~f:(find_vk t)
×
UNCOV
356
            |> Option.all
×
UNCOV
357
            |> Option.value_exn ~message:"malformed Vk_refcount_table.t"
×
358
            |> List.map ~f:snd
359

360
      let inc (t : t) ~account_id ~(vk : Verification_key_wire.t) =
UNCOV
361
        let inc_map ~default_map key map =
×
UNCOV
362
          Map.update (Option.value map ~default:default_map) key ~f:(function
×
UNCOV
363
            | None ->
×
364
                1
UNCOV
365
            | Some count ->
×
366
                count + 1 )
367
        in
368
        Hashtbl.update t.verification_keys vk.hash ~f:(function
UNCOV
369
          | None ->
×
370
              (1, vk)
UNCOV
371
          | Some (count, vk) ->
×
372
              (count + 1, vk) ) ;
UNCOV
373
        Hashtbl.update t.account_id_to_vks account_id
×
UNCOV
374
          ~f:(inc_map ~default_map:Zkapp_basic.F_map.Map.empty vk.hash) ;
×
UNCOV
375
        Hashtbl.update t.vk_to_account_ids vk.hash
×
UNCOV
376
          ~f:(inc_map ~default_map:Account_id.Map.empty account_id) ;
×
UNCOV
377
        Mina_metrics.(
×
378
          Gauge.set Transaction_pool.vk_refcount_table_size
UNCOV
379
            (Float.of_int (Zkapp_basic.F_map.Table.length t.verification_keys)))
×
380

381
      let dec (t : t) ~account_id ~vk_hash =
UNCOV
382
        let open Option.Let_syntax in
×
UNCOV
383
        let dec count = if count = 1 then None else Some (count - 1) in
×
384
        let dec_map key map =
UNCOV
385
          let map' = Map.change map key ~f:(Option.bind ~f:dec) in
×
UNCOV
386
          if Map.is_empty map' then None else Some map'
×
387
        in
388
        Hashtbl.change t.verification_keys vk_hash
389
          ~f:
390
            (Option.bind ~f:(fun (count, value) ->
UNCOV
391
                 let%map count' = dec count in
×
UNCOV
392
                 (count', value) ) ) ;
×
UNCOV
393
        Hashtbl.change t.account_id_to_vks account_id
×
UNCOV
394
          ~f:(Option.bind ~f:(dec_map vk_hash)) ;
×
UNCOV
395
        Hashtbl.change t.vk_to_account_ids vk_hash
×
UNCOV
396
          ~f:(Option.bind ~f:(dec_map account_id)) ;
×
UNCOV
397
        Mina_metrics.(
×
398
          Gauge.set Transaction_pool.vk_refcount_table_size
UNCOV
399
            (Float.of_int (Zkapp_basic.F_map.Table.length t.verification_keys)))
×
400

401
      let lift_common (t : t) table_modify cmd =
UNCOV
402
        User_command.extract_vks cmd
×
UNCOV
403
        |> List.iter ~f:(fun (account_id, vk) -> table_modify t ~account_id ~vk)
×
404

405
      let lift (t : t) table_modify (cmd : User_command.Valid.t With_status.t) =
UNCOV
406
        With_status.data cmd |> User_command.forget_check
×
407
        |> lift_common t table_modify
408

409
      let lift_hashed (t : t) table_modify cmd =
UNCOV
410
        Transaction_hash.User_command_with_valid_signature.forget_check cmd
×
UNCOV
411
        |> With_hash.data |> lift_common t table_modify
×
412
    end
413

414
    type t =
×
415
      { mutable pool : Indexed_pool.t
×
416
      ; locally_generated_uncommitted :
×
417
          ( Transaction_hash.User_command_with_valid_signature.t
418
          , Time.t * [ `Batch of int ] )
419
          Hashtbl.t
420
            (** Commands generated on this machine, that are not included in the
421
          current best tip, along with the time they were added. *)
422
      ; locally_generated_committed :
×
423
          ( Transaction_hash.User_command_with_valid_signature.t
424
          , Time.t * [ `Batch of int ] )
425
          Hashtbl.t
426
            (** Ones that are included in the current best tip. *)
427
      ; mutable current_batch : int
×
428
      ; mutable remaining_in_batch : int
×
429
      ; config : Config.t
×
430
      ; logger : (Logger.t[@sexp.opaque])
×
431
      ; batcher : Batcher.t
×
432
      ; mutable best_tip_diff_relay : (unit Deferred.t[@sexp.opaque]) Option.t
×
433
      ; mutable best_tip_ledger : (Base_ledger.t[@sexp.opaque]) Option.t
×
434
      ; verification_key_table : (Vk_refcount_table.t[@sexp.opaque])
×
435
      }
436
    [@@deriving sexp_of]
437

438
    let member t x =
UNCOV
439
      Indexed_pool.member t.pool (Transaction_hash.User_command.of_checked x)
×
440

UNCOV
441
    let transactions t = Indexed_pool.transactions ~logger:t.logger t.pool
×
442

443
    let all_from_account { pool; _ } = Indexed_pool.all_from_account pool
×
444

445
    let get_all { pool; _ } = Indexed_pool.get_all pool
×
446

447
    let find_by_hash x hash = Indexed_pool.find_by_hash x.pool hash
×
448

449
    (** Get the best tip ledger*)
450
    let get_best_tip_ledger frontier =
451
      Transition_frontier.best_tip frontier
24✔
452
      |> Breadcrumb.staged_ledger |> Staged_ledger.ledger
24✔
453

454
    let drop_until_below_max_size :
455
           pool_max_size:int
456
        -> Indexed_pool.t
457
        -> Indexed_pool.t
458
           * Transaction_hash.User_command_with_valid_signature.t Sequence.t =
459
     fun ~pool_max_size pool ->
UNCOV
460
      let rec go pool' dropped =
×
UNCOV
461
        if Indexed_pool.size pool' > pool_max_size then (
×
462
          let dropped', pool'' = Indexed_pool.remove_lowest_fee pool' in
UNCOV
463
          assert (not (Sequence.is_empty dropped')) ;
×
UNCOV
464
          go pool'' @@ Sequence.append dropped dropped' )
×
UNCOV
465
        else (pool', dropped)
×
466
      in
467
      go pool @@ Sequence.empty
468

469
    let has_sufficient_fee ~pool_max_size pool cmd : bool =
470
      match Indexed_pool.min_fee pool with
×
471
      | None ->
×
472
          true
473
      | Some min_fee ->
×
474
          if Indexed_pool.size pool >= pool_max_size then
×
475
            Currency.Fee_rate.(User_command.fee_per_wu cmd > min_fee)
×
476
          else true
×
477

478
    let diff_error_of_indexed_pool_error :
479
        Command_error.t -> Diff_versioned.Diff_error.t = function
UNCOV
480
      | Invalid_nonce _ ->
×
481
          Invalid_nonce
UNCOV
482
      | Insufficient_funds _ ->
×
483
          Insufficient_funds
UNCOV
484
      | Insufficient_replace_fee _ ->
×
485
          Insufficient_replace_fee
486
      | Overflow ->
×
487
          Overflow
488
      | Bad_token ->
×
489
          Bad_token
490
      | Unwanted_fee_token _ ->
×
491
          Unwanted_fee_token
UNCOV
492
      | Expired _ ->
×
493
          Expired
UNCOV
494
      | After_slot_tx_end ->
×
495
          After_slot_tx_end
496

497
    let indexed_pool_error_metadata = function
UNCOV
498
      | Command_error.Invalid_nonce (`Between (low, hi), nonce) ->
×
499
          let nonce_json = Account.Nonce.to_yojson in
500
          [ ( "between"
UNCOV
501
            , `Assoc [ ("low", nonce_json low); ("hi", nonce_json hi) ] )
×
UNCOV
502
          ; ("nonce", nonce_json nonce)
×
503
          ]
UNCOV
504
      | Invalid_nonce (`Expected enonce, nonce) ->
×
505
          let nonce_json = Account.Nonce.to_yojson in
UNCOV
506
          [ ("expected_nonce", nonce_json enonce); ("nonce", nonce_json nonce) ]
×
UNCOV
507
      | Insufficient_funds (`Balance bal, amt) ->
×
508
          let amt_json = Currency.Amount.to_yojson in
509

UNCOV
510
          [ ("balance", amt_json bal); ("amount", amt_json amt) ]
×
UNCOV
511
      | Insufficient_replace_fee (`Replace_fee rfee, fee) ->
×
512
          let fee_json = Currency.Fee.to_yojson in
UNCOV
513
          [ ("replace_fee", fee_json rfee); ("fee", fee_json fee) ]
×
514
      | Overflow ->
×
515
          []
516
      | Bad_token ->
×
517
          []
518
      | Unwanted_fee_token fee_token ->
×
519
          [ ("fee_token", Token_id.to_yojson fee_token) ]
×
UNCOV
520
      | Expired
×
521
          ( `Valid_until valid_until
522
          , `Global_slot_since_genesis global_slot_since_genesis ) ->
523
          [ ( "valid_until"
UNCOV
524
            , Mina_numbers.Global_slot_since_genesis.to_yojson valid_until )
×
525
          ; ( "current_global_slot_since_genesis"
UNCOV
526
            , Mina_numbers.Global_slot_since_genesis.to_yojson
×
527
                global_slot_since_genesis )
528
          ]
UNCOV
529
      | After_slot_tx_end ->
×
530
          []
531

532
    let indexed_pool_error_log_info e =
UNCOV
533
      ( Diff_versioned.Diff_error.to_string_name
×
UNCOV
534
          (diff_error_of_indexed_pool_error e)
×
UNCOV
535
      , indexed_pool_error_metadata e )
×
536

537
    let handle_transition_frontier_diff
538
        ( ({ new_commands; removed_commands; reorg_best_tip = _ } :
539
            Transition_frontier.best_tip_diff )
540
        , best_tip_ledger ) t =
541
      (* This runs whenever the best tip changes. The simple case is when the
542
         new best tip is an extension of the old one. There, we just remove any
543
         user commands that were included in it from the transaction pool.
544
         Dealing with a fork is more intricate. In general we want to remove any
545
         commands from the pool that are included in the new best tip; and add
546
         any commands to the pool that were included in the old one but not the
547
         new one, provided they are still valid against the ledger of the best
548
         tip. The goal is that transactions are carried from losing forks to
549
         winning ones as much as possible.
550

551
         The locally generated commands need to move from
552
         locally_generated_uncommitted to locally_generated_committed and vice
553
         versa so those hashtables remain in sync with reality.
554

555
         Don't forget to modify the refcount table as well as remove from the
556
         index pool.
557
      *)
558
      let vk_table_inc = Vk_refcount_table.inc in
12✔
559
      let vk_table_dec t ~account_id ~(vk : Verification_key_wire.t) =
UNCOV
560
        Vk_refcount_table.dec t ~account_id ~vk_hash:vk.hash
×
561
      in
562
      let vk_table_lift = Vk_refcount_table.lift t.verification_key_table in
563
      let vk_table_lift_hashed =
12✔
564
        Vk_refcount_table.lift_hashed t.verification_key_table
565
      in
566
      let global_slot = Indexed_pool.global_slot_since_genesis t.pool in
12✔
567
      t.best_tip_ledger <- Some best_tip_ledger ;
12✔
568
      let pool_max_size = t.config.pool_max_size in
569
      let log_indexed_pool_error error_str ~metadata cmd =
UNCOV
570
        [%log' debug t.logger]
×
571
          "Couldn't re-add locally generated command $cmd, not valid against \
572
           new ledger. Error: $error"
573
          ~metadata:
574
            ( [ ( "cmd"
UNCOV
575
                , Transaction_hash.User_command_with_valid_signature.to_yojson
×
576
                    cmd )
577
              ; ("error", `String error_str)
578
              ]
579
            @ metadata )
580
      in
581
      List.iter new_commands ~f:(vk_table_lift vk_table_inc) ;
12✔
582
      List.iter removed_commands ~f:(vk_table_lift vk_table_dec) ;
12✔
583
      let compact_json =
12✔
584
        Fn.compose User_command.fee_payer_summary_json User_command.forget_check
585
      in
586
      [%log' trace t.logger]
12✔
587
        ~metadata:
588
          [ ( "removed"
589
            , `List
590
                (List.map removed_commands
12✔
591
                   ~f:(With_status.to_yojson compact_json) ) )
12✔
592
          ; ( "added"
593
            , `List
594
                (List.map new_commands ~f:(With_status.to_yojson compact_json))
12✔
595
            )
596
          ]
597
        "Diff: removed: $removed added: $added from best tip" ;
598
      let pool', dropped_backtrack =
12✔
599
        List.fold (List.rev removed_commands) ~init:(t.pool, Sequence.empty)
12✔
600
          ~f:(fun (pool, dropped_so_far) unhashed_cmd ->
UNCOV
601
            let cmd =
×
602
              Transaction_hash.User_command_with_valid_signature.create
603
                unhashed_cmd.data
604
            in
UNCOV
605
            ( match
×
606
                Hashtbl.find_and_remove t.locally_generated_committed cmd
607
              with
UNCOV
608
            | None ->
×
609
                ()
UNCOV
610
            | Some time_added ->
×
UNCOV
611
                [%log' info t.logger]
×
612
                  "Locally generated command $cmd committed in a block!"
613
                  ~metadata:
614
                    [ ( "cmd"
UNCOV
615
                      , With_status.to_yojson User_command.Valid.to_yojson
×
616
                          unhashed_cmd )
617
                    ] ;
UNCOV
618
                Hashtbl.add_exn t.locally_generated_uncommitted ~key:cmd
×
619
                  ~data:time_added ) ;
620
            let pool', dropped_seq =
621
              match cmd |> Indexed_pool.add_from_backtrack pool with
UNCOV
622
              | Error e ->
×
623
                  let error_str, metadata = indexed_pool_error_log_info e in
UNCOV
624
                  log_indexed_pool_error error_str ~metadata cmd ;
×
UNCOV
625
                  (pool, Sequence.empty)
×
UNCOV
626
              | Ok indexed_pool ->
×
UNCOV
627
                  drop_until_below_max_size ~pool_max_size indexed_pool
×
628
            in
UNCOV
629
            (pool', Sequence.append dropped_so_far dropped_seq) )
×
630
      in
631
      Sequence.iter dropped_backtrack ~f:(vk_table_lift_hashed vk_table_dec) ;
12✔
632
      (* Track what locally generated commands were removed from the pool
633
         during backtracking due to the max size constraint. *)
634
      let locally_generated_dropped =
12✔
635
        Sequence.filter dropped_backtrack
12✔
636
          ~f:(Hashtbl.mem t.locally_generated_uncommitted)
12✔
637
        |> Sequence.to_list_rev
638
      in
639
      if not (List.is_empty locally_generated_dropped) then
12✔
640
        [%log' debug t.logger]
×
641
          "Dropped locally generated commands $cmds during backtracking to \
642
           maintain max size. Will attempt to re-add after forwardtracking."
643
          ~metadata:
644
            [ ( "cmds"
645
              , `List
646
                  (List.map
×
647
                     ~f:
648
                       Transaction_hash.User_command_with_valid_signature
649
                       .to_yojson locally_generated_dropped ) )
650
            ] ;
651
      let pool'', dropped_commands =
12✔
652
        let accounts_to_check =
653
          List.fold (new_commands @ removed_commands) ~init:Account_id.Set.empty
654
            ~f:(fun set cmd ->
UNCOV
655
              let set' =
×
UNCOV
656
                With_status.data cmd |> User_command.forget_check
×
UNCOV
657
                |> User_command.accounts_referenced |> Account_id.Set.of_list
×
658
              in
UNCOV
659
              Set.union set set' )
×
660
        in
661
        let get_account =
12✔
662
          let existing_account_states_by_id =
663
            preload_accounts best_tip_ledger accounts_to_check
664
          in
665
          fun id ->
12✔
UNCOV
666
            match Map.find existing_account_states_by_id id with
×
UNCOV
667
            | Some account ->
×
668
                account
669
            | None ->
×
670
                if Set.mem accounts_to_check id then Account.empty
×
671
                else
672
                  failwith
×
673
                    "did not expect Indexed_pool.revalidate to call \
674
                     get_account on account not in accounts_to_check"
675
        in
676
        Indexed_pool.revalidate pool' ~logger:t.logger
12✔
677
          (`Subset accounts_to_check) get_account
678
      in
679
      let committed_commands, dropped_commit_conflicts =
680
        let command_hashes =
681
          List.fold_left new_commands ~init:Transaction_hash.Set.empty
682
            ~f:(fun set cmd ->
UNCOV
683
              let cmd_hash =
×
UNCOV
684
                With_status.data cmd
×
UNCOV
685
                |> Transaction_hash.User_command_with_valid_signature.create
×
686
                |> Transaction_hash.User_command_with_valid_signature.hash
687
              in
UNCOV
688
              Set.add set cmd_hash )
×
689
        in
690
        Sequence.to_list dropped_commands
12✔
691
        |> List.partition_tf ~f:(fun cmd ->
12✔
UNCOV
692
               Set.mem command_hashes
×
UNCOV
693
                 (Transaction_hash.User_command_with_valid_signature.hash cmd) )
×
694
      in
695
      List.iter committed_commands ~f:(fun cmd ->
UNCOV
696
          vk_table_lift_hashed vk_table_dec cmd ;
×
UNCOV
697
          Hashtbl.find_and_remove t.locally_generated_uncommitted cmd
×
698
          |> Option.iter ~f:(fun data ->
UNCOV
699
                 Hashtbl.add_exn t.locally_generated_committed ~key:cmd ~data ) ) ;
×
700
      let commit_conflicts_locally_generated =
12✔
701
        List.filter dropped_commit_conflicts ~f:(fun cmd ->
UNCOV
702
            Hashtbl.find_and_remove t.locally_generated_uncommitted cmd
×
703
            |> Option.is_some )
704
      in
705
      if not (List.is_empty commit_conflicts_locally_generated) then
12✔
UNCOV
706
        [%log' info t.logger]
×
707
          "Locally generated commands $cmds dropped because they conflicted \
708
           with a committed command."
709
          ~metadata:
710
            [ ( "cmds"
711
              , `List
UNCOV
712
                  (List.map commit_conflicts_locally_generated
×
713
                     ~f:
714
                       Transaction_hash.User_command_with_valid_signature
715
                       .to_yojson ) )
716
            ] ;
717
      [%log' debug t.logger]
12✔
718
        !"Finished handling diff. Old pool size %i, new pool size %i. Dropped \
719
          %i commands during backtracking to maintain max size."
720
        (Indexed_pool.size t.pool) (Indexed_pool.size pool'')
12✔
721
        (Sequence.length dropped_backtrack) ;
12✔
722
      Mina_metrics.(
12✔
723
        Gauge.set Transaction_pool.pool_size
12✔
724
          (Float.of_int (Indexed_pool.size pool''))) ;
12✔
725
      t.pool <- pool'' ;
726
      List.iter locally_generated_dropped ~f:(fun cmd ->
727
          (* If the dropped transaction was included in the winning chain, it'll
728
             be in locally_generated_committed. If it wasn't, try re-adding to
729
             the pool. *)
730
          let remove_cmd () =
×
731
            vk_table_lift_hashed vk_table_dec cmd ;
×
732
            assert (
×
733
              Option.is_some
×
734
              @@ Hashtbl.find_and_remove t.locally_generated_uncommitted cmd )
×
735
          in
736
          let log_and_remove ?(metadata = []) error_str =
×
737
            log_indexed_pool_error error_str ~metadata cmd ;
×
738
            remove_cmd ()
×
739
          in
740
          if not (Hashtbl.mem t.locally_generated_committed cmd) then
×
741
            if
×
742
              not
743
                (has_sufficient_fee t.pool
×
744
                   (Transaction_hash.User_command_with_valid_signature.command
×
745
                      cmd )
746
                   ~pool_max_size )
747
            then (
×
748
              [%log' info t.logger]
×
749
                "Not re-adding locally generated command $cmd to pool, \
750
                 insufficient fee"
751
                ~metadata:
752
                  [ ( "cmd"
753
                    , Transaction_hash.User_command_with_valid_signature
754
                      .to_yojson cmd )
×
755
                  ] ;
756
              remove_cmd () )
×
757
            else
758
              let unchecked =
×
759
                Transaction_hash.User_command_with_valid_signature.command cmd
760
              in
761
              match
×
762
                Option.bind
763
                  (Base_ledger.location_of_account best_tip_ledger
×
764
                     (User_command.fee_payer unchecked) )
×
765
                  ~f:(Base_ledger.get best_tip_ledger)
×
766
              with
767
              | Some acct -> (
×
768
                  match
769
                    Indexed_pool.add_from_gossip_exn t.pool cmd acct.nonce
770
                      ( Account.liquid_balance_at_slot ~global_slot acct
×
771
                      |> Currency.Balance.to_amount )
×
772
                  with
773
                  | Error e ->
×
774
                      let error_str, metadata = indexed_pool_error_log_info e in
775
                      log_and_remove error_str
×
776
                        ~metadata:
777
                          ( ("user_command", User_command.to_yojson unchecked)
×
778
                          :: metadata )
779
                  | Ok (_, pool''', _) ->
×
780
                      [%log' debug t.logger]
×
781
                        "re-added locally generated command $cmd to \
782
                         transaction pool after reorg"
783
                        ~metadata:
784
                          [ ( "cmd"
785
                            , Transaction_hash.User_command_with_valid_signature
786
                              .to_yojson cmd )
×
787
                          ] ;
788
                      vk_table_lift_hashed Vk_refcount_table.inc cmd ;
×
789
                      Mina_metrics.(
×
790
                        Gauge.set Transaction_pool.pool_size
×
791
                          (Float.of_int (Indexed_pool.size pool'''))) ;
×
792
                      t.pool <- pool''' )
793
              | None ->
×
794
                  log_and_remove "Fee_payer_account not found"
795
                    ~metadata:
796
                      [ ("user_command", User_command.to_yojson unchecked) ] ) ;
×
797
      (*Remove any expired user commands*)
798
      let expired_commands, pool = Indexed_pool.remove_expired t.pool in
12✔
799
      Sequence.iter expired_commands ~f:(fun cmd ->
12✔
UNCOV
800
          [%log' debug t.logger]
×
801
            "Dropping expired user command from the pool $cmd"
802
            ~metadata:
803
              [ ( "cmd"
UNCOV
804
                , Transaction_hash.User_command_with_valid_signature.to_yojson
×
805
                    cmd )
806
              ] ;
UNCOV
807
          vk_table_lift_hashed vk_table_dec cmd ;
×
UNCOV
808
          ignore
×
UNCOV
809
            ( Hashtbl.find_and_remove t.locally_generated_uncommitted cmd
×
810
              : (Time.t * [ `Batch of int ]) option ) ) ;
811
      Mina_metrics.(
12✔
812
        Gauge.set Transaction_pool.pool_size
12✔
813
          (Float.of_int (Indexed_pool.size pool))) ;
12✔
814
      t.pool <- pool
815

816
    let create ~constraint_constants ~consensus_constants ~time_controller
817
        ~frontier_broadcast_pipe ~config ~logger ~tf_diff_writer =
818
      let t =
6✔
819
        { pool =
820
            Indexed_pool.empty ~constraint_constants ~consensus_constants
821
              ~time_controller ~slot_tx_end:config.Config.slot_tx_end
822
        ; locally_generated_uncommitted =
823
            Hashtbl.create
6✔
824
              ( module Transaction_hash.User_command_with_valid_signature.Stable
825
                       .Latest )
826
        ; locally_generated_committed =
827
            Hashtbl.create
6✔
828
              ( module Transaction_hash.User_command_with_valid_signature.Stable
829
                       .Latest )
830
        ; current_batch = 0
831
        ; remaining_in_batch = max_per_15_seconds
832
        ; config
833
        ; logger
834
        ; batcher = Batcher.create ~logger config.verifier
6✔
835
        ; best_tip_diff_relay = None
836
        ; best_tip_ledger = None
837
        ; verification_key_table = Vk_refcount_table.create ()
6✔
838
        }
839
      in
840
      don't_wait_for
841
        (Broadcast_pipe.Reader.iter frontier_broadcast_pipe
6✔
842
           ~f:(fun frontier_opt ->
843
             match frontier_opt with
18✔
844
             | None -> (
6✔
845
                 [%log debug] "no frontier" ;
6✔
846
                 t.best_tip_ledger <- None ;
6✔
847
                 (* Sanity check: the view pipe should have been closed before
848
                    the frontier was destroyed. *)
849
                 match t.best_tip_diff_relay with
850
                 | None ->
6✔
851
                     Deferred.unit
UNCOV
852
                 | Some hdl ->
×
853
                     let is_finished = ref false in
854
                     Deferred.any_unit
855
                       [ (let%map () = hdl in
UNCOV
856
                          t.best_tip_diff_relay <- None ;
×
857
                          is_finished := true )
UNCOV
858
                       ; (let%map () = Async.after (Time.Span.of_sec 5.) in
×
859
                          if not !is_finished then (
×
860
                            [%log fatal]
×
861
                              "Transition frontier closed without first \
862
                               closing best tip view pipe" ;
863
                            assert false )
×
UNCOV
864
                          else () )
×
865
                       ] )
866
             | Some frontier ->
12✔
867
                 [%log debug] "Got frontier!" ;
12✔
868
                 let validation_ledger = get_best_tip_ledger frontier in
12✔
869
                 (* update our cache *)
870
                 t.best_tip_ledger <- Some validation_ledger ;
12✔
871
                 (* The frontier has changed, so transactions in the pool may
872
                    not be valid against the current best tip. *)
873
                 let new_pool, dropped =
874
                   Indexed_pool.revalidate t.pool ~logger:t.logger `Entire_pool
875
                     (fun sender ->
UNCOV
876
                       match
×
877
                         Base_ledger.location_of_account validation_ledger
878
                           sender
879
                       with
880
                       | None ->
×
881
                           Account.empty
UNCOV
882
                       | Some loc ->
×
883
                           Option.value_exn
884
                             ~message:
885
                               "Somehow a public key has a location but no \
886
                                account"
UNCOV
887
                             (Base_ledger.get validation_ledger loc) )
×
888
                 in
889
                 let dropped_locally_generated =
12✔
890
                   Sequence.filter dropped ~f:(fun cmd ->
UNCOV
891
                       let find_remove_bool tbl =
×
UNCOV
892
                         Hashtbl.find_and_remove tbl cmd |> Option.is_some
×
893
                       in
894
                       let dropped_committed =
895
                         find_remove_bool t.locally_generated_committed
896
                       in
UNCOV
897
                       let dropped_uncommitted =
×
898
                         find_remove_bool t.locally_generated_uncommitted
899
                       in
900
                       (* Nothing should be in both tables. *)
901
                       assert (not (dropped_committed && dropped_uncommitted)) ;
×
902
                       dropped_committed || dropped_uncommitted )
×
903
                 in
904
                 (* In this situation we don't know whether the commands aren't
905
                    valid against the new ledger because they were already
906
                    committed or because they conflict with others,
907
                    unfortunately. *)
908
                 if not (Sequence.is_empty dropped_locally_generated) then
12✔
UNCOV
909
                   [%log info]
×
910
                     "Dropped locally generated commands $cmds from pool when \
911
                      transition frontier was recreated."
912
                     ~metadata:
913
                       [ ( "cmds"
914
                         , `List
UNCOV
915
                             (List.map
×
UNCOV
916
                                (Sequence.to_list dropped_locally_generated)
×
917
                                ~f:
918
                                  Transaction_hash
919
                                  .User_command_with_valid_signature
920
                                  .to_yojson ) )
921
                       ] ;
922
                 [%log debug]
12✔
923
                   !"Re-validated transaction pool after restart: dropped %i \
924
                     of %i previously in pool"
925
                   (Sequence.length dropped) (Indexed_pool.size t.pool) ;
12✔
926
                 Mina_metrics.(
12✔
927
                   Gauge.set Transaction_pool.pool_size
12✔
928
                     (Float.of_int (Indexed_pool.size new_pool))) ;
12✔
929
                 t.pool <- new_pool ;
930
                 t.best_tip_diff_relay <-
931
                   Some
932
                     (Broadcast_pipe.Reader.iter
12✔
933
                        (Transition_frontier.best_tip_diff_pipe frontier)
12✔
934
                        ~f:(fun diff ->
935
                          Strict_pipe.Writer.write tf_diff_writer
12✔
936
                            (diff, get_best_tip_ledger frontier)
12✔
937
                          |> Deferred.don't_wait_for ;
938
                          Deferred.unit ) ) ;
12✔
939
                 Deferred.unit ) ) ;
940
      t
6✔
941

942
    type pool = t
943

944
    module Diff = struct
945
      type t = User_command.t list [@@deriving sexp, yojson]
×
946

947
      let (_ : (t, Diff_versioned.t) Type_equal.t) = Type_equal.T
948

949
      let label = label
950

951
      module Diff_error = struct
952
        type t = Diff_versioned.Diff_error.t =
×
953
          (*Indexed_pool*)
954
          | Insufficient_replace_fee
×
955
          (*apply*)
956
          | Duplicate
×
957
          (*Indexed_pool*)
958
          | Invalid_nonce
×
959
          (*Indexed_pool*)
960
          | Insufficient_funds
×
961
          (*Indexed_pool*)
962
          | Overflow
×
963
          (*Indexed_pool*)
964
          | Bad_token
×
965
          (*Indexed_pool*)
966
          | Unwanted_fee_token
×
967
          (*Indexed_pool*)
968
          | Expired
×
969
          (*Sink*)
970
          | Overloaded
×
971
          (*apply*)
972
          | Fee_payer_account_not_found
×
973
          | Fee_payer_not_permitted_to_send
×
974
          (*Indexed_pool*)
975
          | After_slot_tx_end
×
976
        [@@deriving sexp, yojson, compare]
977

978
        let to_string_hum = Diff_versioned.Diff_error.to_string_hum
979

980
        let grounds_for_diff_rejection = function
UNCOV
981
          | Expired
×
UNCOV
982
          | Invalid_nonce
×
UNCOV
983
          | Insufficient_funds
×
UNCOV
984
          | Insufficient_replace_fee
×
985
          | Duplicate
×
986
          | Overloaded
×
987
          | Fee_payer_account_not_found
×
UNCOV
988
          | Fee_payer_not_permitted_to_send
×
UNCOV
989
          | After_slot_tx_end ->
×
990
              false
991
          | Overflow | Bad_token | Unwanted_fee_token ->
×
992
              true
993
      end
994

995
      module Rejected = struct
996
        type t = (User_command.t * Diff_error.t) list
×
997
        [@@deriving sexp, yojson, compare]
998

999
        let (_ : (t, Diff_versioned.Rejected.t) Type_equal.t) = Type_equal.T
1000
      end
1001

1002
      type rejected = Rejected.t [@@deriving sexp, yojson, compare]
×
1003

1004
      type verified = Diff_versioned.verified [@@deriving sexp, to_yojson]
×
1005

1006
      let reject_overloaded_diff (diff : verified) : rejected =
1007
        List.map diff ~f:(fun cmd ->
×
1008
            ( Transaction_hash.User_command_with_valid_signature.command cmd
×
1009
            , Diff_error.Overloaded ) )
1010

1011
      let empty = []
1012

1013
      let size = List.length
1014

UNCOV
1015
      let score x = Int.max 1 (List.length x)
×
1016

1017
      let max_per_15_seconds = max_per_15_seconds
1018

1019
      let summary t =
UNCOV
1020
        Printf.sprintf
×
1021
          !"Transaction_pool_diff of length %d with fee payer summary %s"
UNCOV
1022
          (List.length t)
×
UNCOV
1023
          ( String.concat ~sep:","
×
UNCOV
1024
          @@ List.map ~f:User_command.fee_payer_summary_string t )
×
1025

UNCOV
1026
      let is_empty t = List.is_empty t
×
1027

1028
      let log_and_punish ?(punish = true) t d e =
×
1029
        let sender = Envelope.Incoming.sender d in
×
1030
        let trust_record =
×
1031
          Trust_system.record_envelope_sender t.config.trust_system t.logger
1032
            sender
1033
        in
1034
        let is_local = Envelope.Sender.(equal Local sender) in
×
1035
        let metadata =
1036
          [ ("error", Error_json.error_to_yojson e)
×
1037
          ; ("sender", Envelope.Sender.to_yojson sender)
×
1038
          ]
1039
        in
1040
        [%log' error t.logger] ~metadata
×
1041
          "Error verifying transaction pool diff from $sender: $error" ;
1042
        if punish && not is_local then
×
1043
          (* TODO: Make this error more specific (could also be a bad signature). *)
1044
          trust_record
×
1045
            ( Trust_system.Actions.Sent_invalid_proof
1046
            , Some ("Error verifying transaction pool diff: $error", metadata)
1047
            )
1048
        else Deferred.return ()
×
1049

1050
      let of_indexed_pool_error e =
UNCOV
1051
        (diff_error_of_indexed_pool_error e, indexed_pool_error_metadata e)
×
1052

1053
      let report_command_error ~logger ~is_sender_local tx (e : Command_error.t)
1054
          =
UNCOV
1055
        let diff_err, error_extra = of_indexed_pool_error e in
×
UNCOV
1056
        if is_sender_local then
×
UNCOV
1057
          [%str_log error]
×
1058
            (Rejecting_command_for_reason
1059
               { command = tx; reason = diff_err; error_extra } ) ;
1060
        let log = if is_sender_local then [%log error] else [%log debug] in
×
1061
        match e with
UNCOV
1062
        | Insufficient_replace_fee (`Replace_fee rfee, fee) ->
×
1063
            log
1064
              "rejecting $cmd because of insufficient replace fee ($rfee > \
1065
               $fee)"
1066
              ~metadata:
UNCOV
1067
                [ ("cmd", User_command.to_yojson tx)
×
UNCOV
1068
                ; ("rfee", Currency.Fee.to_yojson rfee)
×
UNCOV
1069
                ; ("fee", Currency.Fee.to_yojson fee)
×
1070
                ]
1071
        | Unwanted_fee_token fee_token ->
×
1072
            log "rejecting $cmd because we don't accept fees in $token"
1073
              ~metadata:
1074
                [ ("cmd", User_command.to_yojson tx)
×
1075
                ; ("token", Token_id.to_yojson fee_token)
×
1076
                ]
UNCOV
1077
        | _ ->
×
1078
            ()
1079

1080
      (** DO NOT mutate any transaction pool state in this function, you may only mutate in the synchronous `apply` function. *)
1081
      let verify (t : pool) (diff : t Envelope.Incoming.t) :
1082
          ( verified Envelope.Incoming.t
1083
          , Intf.Verification_error.t )
1084
          Deferred.Result.t =
UNCOV
1085
        let open Deferred.Result.Let_syntax in
×
1086
        let open Intf.Verification_error in
1087
        let%bind () =
1088
          let well_formedness_errors =
UNCOV
1089
            List.fold (Envelope.Incoming.data diff) ~init:[]
×
1090
              ~f:(fun acc user_cmd ->
UNCOV
1091
                match
×
1092
                  User_command.check_well_formedness
1093
                    ~genesis_constants:t.config.genesis_constants
1094
                    ~compile_config:t.config.compile_config user_cmd
1095
                with
UNCOV
1096
                | Ok () ->
×
1097
                    acc
1098
                | Error errs ->
×
1099
                    [%log' debug t.logger]
×
1100
                      "User command $cmd from $sender has one or more \
1101
                       well-formedness errors."
1102
                      ~metadata:
1103
                        [ ("cmd", User_command.to_yojson user_cmd)
×
1104
                        ; ( "sender"
1105
                          , Envelope.(Sender.to_yojson (Incoming.sender diff))
×
1106
                          )
1107
                        ; ( "errors"
1108
                          , `List
1109
                              (List.map errs
×
1110
                                 ~f:User_command.Well_formedness_error.to_yojson )
1111
                          )
1112
                        ] ;
1113
                    errs @ acc )
×
1114
          in
UNCOV
1115
          match
×
1116
            List.dedup_and_sort well_formedness_errors
1117
              ~compare:User_command.Well_formedness_error.compare
1118
          with
UNCOV
1119
          | [] ->
×
UNCOV
1120
              return ()
×
1121
          | errs ->
×
1122
              let err_str =
1123
                List.map errs ~f:User_command.Well_formedness_error.to_string
×
1124
                |> String.concat ~sep:","
1125
              in
1126
              Deferred.Result.fail
×
1127
              @@ Invalid
1128
                   (Error.createf
×
1129
                      "Some commands have one or more well-formedness errors: \
1130
                       %s "
1131
                      err_str )
1132
        in
1133
        let%bind ledger =
1134
          match t.best_tip_ledger with
UNCOV
1135
          | Some ledger ->
×
UNCOV
1136
              return ledger
×
UNCOV
1137
          | None ->
×
UNCOV
1138
              Deferred.Result.fail
×
1139
              @@ Failure
UNCOV
1140
                   (Error.of_string
×
1141
                      "We don't have a transition frontier at the moment, so \
1142
                       we're unable to verify any transactions." )
1143
        in
1144

1145
        let%bind diff' =
UNCOV
1146
          O1trace.sync_thread "convert_transactions_to_verifiable" (fun () ->
×
UNCOV
1147
              Envelope.Incoming.map diff ~f:(fun diff ->
×
UNCOV
1148
                  User_command.Unapplied_sequence.to_all_verifiable diff
×
1149
                    ~load_vk_cache:(fun account_ids ->
UNCOV
1150
                      let account_ids = Set.to_list account_ids in
×
UNCOV
1151
                      let ledger_vks =
×
1152
                        Zkapp_command.Verifiable.load_vks_from_ledger
1153
                          ~location_of_account_batch:
UNCOV
1154
                            (Base_ledger.location_of_account_batch ledger)
×
UNCOV
1155
                          ~get_batch:(Base_ledger.get_batch ledger)
×
1156
                          account_ids
1157
                      in
UNCOV
1158
                      let ledger_vks =
×
1159
                        Map.map ledger_vks ~f:(fun vk ->
UNCOV
1160
                            Zkapp_basic.F_map.Map.singleton vk.hash vk )
×
1161
                      in
UNCOV
1162
                      let mempool_vks =
×
UNCOV
1163
                        List.map account_ids ~f:(fun account_id ->
×
UNCOV
1164
                            let vks =
×
1165
                              Vk_refcount_table.find_vks_by_account_id
1166
                                t.verification_key_table account_id
1167
                            in
UNCOV
1168
                            let vks =
×
1169
                              vks
UNCOV
1170
                              |> List.map ~f:(fun vk -> (vk.hash, vk))
×
1171
                              |> Zkapp_basic.F_map.Map.of_alist_exn
1172
                            in
UNCOV
1173
                            (account_id, vks) )
×
1174
                        |> Account_id.Map.of_alist_exn
1175
                      in
UNCOV
1176
                      Map.merge_skewed ledger_vks mempool_vks
×
1177
                        ~combine:(fun ~key:_ ->
UNCOV
1178
                          Map.merge_skewed ~combine:(fun ~key:_ _ x -> x) ) ) ) )
×
UNCOV
1179
          |> Envelope.Incoming.lift_error
×
1180
          |> Result.map_error ~f:(fun e -> Invalid e)
×
UNCOV
1181
          |> Deferred.return
×
1182
        in
1183
        match%bind.Deferred
UNCOV
1184
          O1trace.thread "batching_transaction_verification" (fun () ->
×
UNCOV
1185
              Batcher.verify t.batcher diff' )
×
1186
        with
1187
        | Error e ->
×
1188
            [%log' error t.logger] "Transaction verification error: $error"
×
1189
              ~metadata:[ ("error", `String (Error.to_string_hum e)) ] ;
×
1190
            [%log' debug t.logger]
×
1191
              "Failed to batch verify $transaction_pool_diff"
1192
              ~metadata:
1193
                [ ( "transaction_pool_diff"
1194
                  , Diff_versioned.to_yojson (Envelope.Incoming.data diff) )
×
1195
                ] ;
1196
            Deferred.Result.fail (Failure e)
×
UNCOV
1197
        | Ok (Error invalid) ->
×
1198
            let err = Verifier.invalid_to_error invalid in
UNCOV
1199
            [%log' error t.logger]
×
1200
              "Batch verification failed when adding from gossip"
UNCOV
1201
              ~metadata:[ ("error", Error_json.error_to_yojson err) ] ;
×
1202
            let%map.Deferred () =
UNCOV
1203
              Trust_system.record_envelope_sender t.config.trust_system t.logger
×
UNCOV
1204
                (Envelope.Incoming.sender diff)
×
1205
                ( Trust_system.Actions.Sent_useless_gossip
1206
                , Some
1207
                    ( "rejecting command because had invalid signature or proof"
1208
                    , [] ) )
1209
            in
UNCOV
1210
            Error (Invalid err)
×
UNCOV
1211
        | Ok (Ok commands) ->
×
1212
            (* TODO: avoid duplicate hashing (#11706) *)
1213
            O1trace.sync_thread "hashing_transactions_after_verification"
1214
              (fun () ->
UNCOV
1215
                return
×
1216
                  { diff with
1217
                    data =
UNCOV
1218
                      List.map commands
×
1219
                        ~f:
1220
                          Transaction_hash.User_command_with_valid_signature
1221
                          .create
1222
                  } )
1223

1224
      let register_locally_generated t txn =
UNCOV
1225
        Hashtbl.update t.locally_generated_uncommitted txn ~f:(function
×
1226
          | Some (_, `Batch batch_num) ->
×
1227
              (* Use the existing [batch_num] on a re-issue, to avoid splitting
1228
                 existing batches.
1229
              *)
1230
              (Time.now (), `Batch batch_num)
×
UNCOV
1231
          | None ->
×
1232
              let batch_num =
UNCOV
1233
                if t.remaining_in_batch > 0 then (
×
1234
                  t.remaining_in_batch <- t.remaining_in_batch - 1 ;
1235
                  t.current_batch )
UNCOV
1236
                else (
×
1237
                  t.remaining_in_batch <- max_per_15_seconds - 1 ;
1238
                  t.current_batch <- t.current_batch + 1 ;
1239
                  t.current_batch )
1240
              in
UNCOV
1241
              (Time.now (), `Batch batch_num) )
×
1242

1243
      (* This must be synchronous, but you MAY modify state here (do not modify pool state in `verify` *)
1244
      let apply t (diff : verified Envelope.Incoming.t) =
UNCOV
1245
        let open Or_error.Let_syntax in
×
1246
        let is_sender_local =
UNCOV
1247
          Envelope.Sender.(equal Local) (Envelope.Incoming.sender diff)
×
1248
        in
UNCOV
1249
        let pool_size_before = Indexed_pool.size t.pool in
×
1250
        (* preload fee payer accounts from the best tip ledger *)
1251
        let%map ledger =
1252
          match t.best_tip_ledger with
1253
          | None ->
×
1254
              Or_error.error_string
×
1255
                "Got transaction pool diff when transitin frontier is \
1256
                 unavailable, ignoring."
UNCOV
1257
          | Some ledger ->
×
UNCOV
1258
              return ledger
×
1259
        in
UNCOV
1260
        let fee_payer_account_ids =
×
UNCOV
1261
          List.map (Envelope.Incoming.data diff) ~f:(fun cmd ->
×
UNCOV
1262
              Transaction_hash.User_command_with_valid_signature.command cmd
×
1263
              |> User_command.fee_payer )
1264
          |> Account_id.Set.of_list
1265
        in
UNCOV
1266
        let fee_payer_accounts =
×
1267
          preload_accounts ledger fee_payer_account_ids
1268
        in
1269
        (* add new commands to the pool *)
UNCOV
1270
        let fee_payer =
×
1271
          Fn.compose User_command.fee_payer
1272
            Transaction_hash.User_command_with_valid_signature.command
1273
        in
UNCOV
1274
        let check_command pool cmd =
×
UNCOV
1275
          let already_in_pool =
×
1276
            Indexed_pool.member pool
UNCOV
1277
              (Transaction_hash.User_command.of_checked cmd)
×
1278
          in
1279
          let%map.Result () =
1280
            if already_in_pool then
1281
              if is_sender_local then Ok () else Error Diff_error.Duplicate
×
1282
            else
UNCOV
1283
              match Map.find fee_payer_accounts (fee_payer cmd) with
×
1284
              | None ->
×
1285
                  Error Diff_error.Fee_payer_account_not_found
UNCOV
1286
              | Some account ->
×
UNCOV
1287
                  Result.ok_if_true
×
UNCOV
1288
                    ( Account.has_permission_to_send account
×
UNCOV
1289
                    && Account.has_permission_to_increment_nonce account )
×
1290
                    ~error:Diff_error.Fee_payer_not_permitted_to_send
1291
          in
UNCOV
1292
          already_in_pool
×
1293
        in
1294
        (* Dedicated variant to track whether the transaction was already in
1295
           the pool. We use this to signal that the user wants to re-broadcast
1296
           a txn that already exists in their local pool.
1297
        *)
1298
        let module Command_state = struct
1299
          type t = New_command | Rebroadcast
1300
        end in
1301
        let pool, add_results =
UNCOV
1302
          List.fold_map (Envelope.Incoming.data diff) ~init:t.pool
×
1303
            ~f:(fun pool cmd ->
UNCOV
1304
              let result =
×
UNCOV
1305
                let%bind.Result already_in_pool = check_command pool cmd in
×
UNCOV
1306
                let global_slot =
×
1307
                  Indexed_pool.global_slot_since_genesis t.pool
1308
                in
UNCOV
1309
                let account = Map.find_exn fee_payer_accounts (fee_payer cmd) in
×
UNCOV
1310
                if already_in_pool then
×
1311
                  Ok ((cmd, pool, Sequence.empty), Command_state.Rebroadcast)
×
1312
                else
UNCOV
1313
                  match
×
1314
                    Indexed_pool.add_from_gossip_exn pool cmd account.nonce
UNCOV
1315
                      ( Account.liquid_balance_at_slot ~global_slot account
×
UNCOV
1316
                      |> Currency.Balance.to_amount )
×
1317
                  with
UNCOV
1318
                  | Ok x ->
×
1319
                      Ok (x, Command_state.New_command)
UNCOV
1320
                  | Error err ->
×
1321
                      report_command_error ~logger:t.logger ~is_sender_local
1322
                        (Transaction_hash.User_command_with_valid_signature
UNCOV
1323
                         .command cmd )
×
1324
                        err ;
UNCOV
1325
                      Error (diff_error_of_indexed_pool_error err)
×
1326
              in
UNCOV
1327
              match result with
×
UNCOV
1328
              | Ok ((cmd', pool', dropped), cmd_state) ->
×
1329
                  (pool', Ok (cmd', dropped, cmd_state))
UNCOV
1330
              | Error err ->
×
1331
                  (pool, Error (cmd, err)) )
1332
        in
UNCOV
1333
        let added_cmds =
×
1334
          List.filter_map add_results ~f:(function
UNCOV
1335
            | Ok (cmd, _, Command_state.New_command) ->
×
1336
                Some cmd
1337
            | Ok (_, _, Command_state.Rebroadcast) | Error _ ->
×
1338
                None )
1339
        in
UNCOV
1340
        let dropped_for_add =
×
UNCOV
1341
          List.filter_map add_results ~f:(function
×
UNCOV
1342
            | Ok (_, dropped, Command_state.New_command) ->
×
UNCOV
1343
                Some (Sequence.to_list dropped)
×
1344
            | Ok (_, _, Command_state.Rebroadcast) | Error _ ->
×
1345
                None )
1346
          |> List.concat
1347
        in
1348
        (* drop commands from the pool to retain max size *)
UNCOV
1349
        let pool, dropped_for_size =
×
1350
          let pool, dropped =
1351
            drop_until_below_max_size pool ~pool_max_size:t.config.pool_max_size
1352
          in
UNCOV
1353
          (pool, Sequence.to_list dropped)
×
1354
        in
1355
        (* handle drops of locally generated commands *)
1356
        let all_dropped_cmds = dropped_for_add @ dropped_for_size in
1357

1358
        (* apply changes to the vk-refcount-table here *)
1359
        let () =
1360
          let lift = Vk_refcount_table.lift_hashed t.verification_key_table in
UNCOV
1361
          List.iter added_cmds ~f:(lift Vk_refcount_table.inc) ;
×
UNCOV
1362
          List.iter all_dropped_cmds
×
1363
            ~f:
UNCOV
1364
              (lift (fun t ~account_id ~vk ->
×
1365
                   Vk_refcount_table.dec t ~account_id ~vk_hash:vk.hash ) )
×
1366
        in
1367
        let dropped_for_add_hashes =
UNCOV
1368
          List.map dropped_for_add
×
1369
            ~f:Transaction_hash.User_command_with_valid_signature.hash
1370
          |> Transaction_hash.Set.of_list
1371
        in
UNCOV
1372
        let dropped_for_size_hashes =
×
UNCOV
1373
          List.map dropped_for_size
×
1374
            ~f:Transaction_hash.User_command_with_valid_signature.hash
1375
          |> Transaction_hash.Set.of_list
1376
        in
UNCOV
1377
        let all_dropped_cmd_hashes =
×
1378
          Transaction_hash.Set.union dropped_for_add_hashes
1379
            dropped_for_size_hashes
1380
        in
UNCOV
1381
        [%log' debug t.logger]
×
1382
          "Dropping $num_for_add commands from pool while adding new commands, \
1383
           and $num_for_size commands due to pool size"
1384
          ~metadata:
UNCOV
1385
            [ ("num_for_add", `Int (List.length dropped_for_add))
×
UNCOV
1386
            ; ("num_for_size", `Int (List.length dropped_for_size))
×
1387
            ] ;
UNCOV
1388
        let locally_generated_dropped =
×
1389
          List.filter all_dropped_cmds ~f:(fun cmd ->
UNCOV
1390
              Hashtbl.find_and_remove t.locally_generated_uncommitted cmd
×
1391
              |> Option.is_some )
1392
        in
UNCOV
1393
        if not (List.is_empty locally_generated_dropped) then
×
UNCOV
1394
          [%log' info t.logger]
×
1395
            "Dropped locally generated commands $cmds from transaction pool \
1396
             due to replacement or max size"
1397
            ~metadata:
1398
              [ ( "cmds"
1399
                , `List
UNCOV
1400
                    (List.map
×
1401
                       ~f:
1402
                         Transaction_hash.User_command_with_valid_signature
1403
                         .to_yojson locally_generated_dropped ) )
1404
              ] ;
1405
        (* register locally generated commands *)
UNCOV
1406
        if is_sender_local then
×
UNCOV
1407
          List.iter add_results ~f:(function
×
UNCOV
1408
            | Ok (cmd, _dropped, _command_type) ->
×
1409
                if
1410
                  not
UNCOV
1411
                    (Set.mem all_dropped_cmd_hashes
×
UNCOV
1412
                       (Transaction_hash.User_command_with_valid_signature.hash
×
1413
                          cmd ) )
UNCOV
1414
                then register_locally_generated t cmd
×
UNCOV
1415
            | Error _ ->
×
1416
                () ) ;
1417
        (* finalize the update to the pool *)
UNCOV
1418
        t.pool <- pool ;
×
1419
        let pool_size_after = Indexed_pool.size pool in
UNCOV
1420
        Mina_metrics.(
×
UNCOV
1421
          Gauge.set Transaction_pool.pool_size (Float.of_int pool_size_after) ;
×
UNCOV
1422
          List.iter
×
UNCOV
1423
            (List.init (max 0 (pool_size_after - pool_size_before)) ~f:Fn.id)
×
1424
            ~f:(fun _ ->
UNCOV
1425
              Counter.inc_one Transaction_pool.transactions_added_to_pool )) ;
×
1426
        (* partition the results *)
1427
        let accepted, rejected, _dropped =
1428
          List.partition3_map add_results ~f:(function
UNCOV
1429
            | Ok (cmd, _dropped, _cmd_state) ->
×
1430
                (* NB: We ignore the command state here, so that commands only
1431
                   for rebroadcast are still included in the bundle that we
1432
                   rebroadcast.
1433
                *)
1434
                if
1435
                  Set.mem all_dropped_cmd_hashes
UNCOV
1436
                    (Transaction_hash.User_command_with_valid_signature.hash cmd)
×
UNCOV
1437
                then `Trd cmd
×
UNCOV
1438
                else `Fst cmd
×
UNCOV
1439
            | Error (cmd, error) ->
×
1440
                `Snd (cmd, error) )
1441
        in
1442
        (* determine if we should re-broadcast this diff *)
UNCOV
1443
        let decision =
×
1444
          if
1445
            List.exists rejected ~f:(fun (_, error) ->
UNCOV
1446
                Diff_error.grounds_for_diff_rejection error )
×
1447
          then `Reject
×
UNCOV
1448
          else `Accept
×
1449
        in
1450
        (decision, accepted, rejected)
1451

1452
      let unsafe_apply (t : pool) (diff : verified Envelope.Incoming.t) :
1453
          ([ `Accept | `Reject ] * t * rejected, _) Result.t =
UNCOV
1454
        match apply t diff with
×
UNCOV
1455
        | Ok (decision, accepted, rejected) ->
×
UNCOV
1456
            ( if not (List.is_empty accepted) then
×
UNCOV
1457
              Mina_metrics.(
×
UNCOV
1458
                Gauge.set Transaction_pool.useful_transactions_received_time_sec
×
1459
                  (let x =
UNCOV
1460
                     Time.(now () |> to_span_since_epoch |> Span.to_sec)
×
1461
                   in
1462
                   x -. Mina_metrics.time_offset_sec )) ) ;
UNCOV
1463
            let forget_cmd =
×
1464
              Transaction_hash.User_command_with_valid_signature.command
1465
            in
1466
            Ok
1467
              ( decision
UNCOV
1468
              , List.map ~f:forget_cmd accepted
×
UNCOV
1469
              , List.map ~f:(Tuple2.map_fst ~f:forget_cmd) rejected )
×
1470
        | Error e ->
×
1471
            Error (`Other e)
1472

1473
      type Structured_log_events.t +=
1474
        | Transactions_received of
1475
            { fee_payer_summaries : User_command.fee_payer_summary_t list
×
1476
            ; sender : Envelope.Sender.t
×
1477
            }
1478
        [@@deriving
1479
          register_event
3✔
1480
            { msg =
1481
                "Received transaction-pool $fee_payer_summaries from $sender"
1482
            }]
1483

1484
      let update_metrics ~logger ~log_gossip_heard envelope valid_cb =
1485
        Mina_metrics.(Counter.inc_one Network.gossip_messages_received) ;
×
1486
        Mina_metrics.(Gauge.inc_one Network.transaction_pool_diff_received) ;
×
1487
        let diff = Envelope.Incoming.data envelope in
1488
        if log_gossip_heard then (
×
1489
          let fee_payer_summaries =
1490
            List.map ~f:User_command.fee_payer_summary diff
1491
          in
1492
          [%str_log debug]
×
1493
            (Transactions_received
1494
               { fee_payer_summaries
1495
               ; sender = Envelope.Incoming.sender envelope
×
1496
               } ) ;
1497
          Mina_net2.Validation_callback.set_message_type valid_cb `Transaction ;
×
1498
          Mina_metrics.(Counter.inc_one Network.Transaction.received) )
×
1499

1500
      let log_internal ?reason ~logger msg
1501
          { Envelope.Incoming.data = diff; sender; _ } =
UNCOV
1502
        let metadata =
×
1503
          [ ( "diff"
1504
            , `List
UNCOV
1505
                (List.map diff
×
1506
                   ~f:Mina_transaction.Transaction.yojson_summary_of_command )
1507
            )
1508
          ]
1509
        in
1510
        let metadata =
1511
          match sender with
1512
          | Remote addr ->
×
1513
              ("sender", `String (Core.Unix.Inet_addr.to_string @@ Peer.ip addr))
×
1514
              :: metadata
UNCOV
1515
          | Local ->
×
1516
              metadata
1517
        in
1518
        let metadata =
1519
          Option.value_map reason
UNCOV
1520
            ~f:(fun r -> List.cons ("reason", `String r))
×
1521
            ~default:ident metadata
1522
        in
UNCOV
1523
        if not (is_empty diff) then
×
UNCOV
1524
          [%log internal] "%s" ("Transaction_diff_" ^ msg) ~metadata
×
1525

1526
      let t_of_verified =
1527
        List.map ~f:Transaction_hash.User_command_with_valid_signature.command
1528
    end
1529

1530
    let get_rebroadcastable (t : t) ~has_timed_out =
1531
      let metadata ~key ~time =
6✔
UNCOV
1532
        [ ( "cmd"
×
UNCOV
1533
          , Transaction_hash.User_command_with_valid_signature.to_yojson key )
×
UNCOV
1534
        ; ("time", `String (Time.to_string_abs ~zone:Time.Zone.utc time))
×
1535
        ]
1536
      in
1537
      let added_str =
1538
        "it was added at $time and its rebroadcast period is now expired."
1539
      in
1540
      let logger = t.logger in
1541
      Hashtbl.filteri_inplace t.locally_generated_uncommitted
1542
        ~f:(fun ~key ~data:(time, `Batch _) ->
UNCOV
1543
          match has_timed_out time with
×
UNCOV
1544
          | `Timed_out ->
×
UNCOV
1545
              [%log info]
×
1546
                "No longer rebroadcasting uncommitted command $cmd, %s"
1547
                added_str ~metadata:(metadata ~key ~time) ;
UNCOV
1548
              false
×
UNCOV
1549
          | `Ok ->
×
1550
              true ) ;
1551
      Hashtbl.filteri_inplace t.locally_generated_committed
6✔
1552
        ~f:(fun ~key ~data:(time, `Batch _) ->
UNCOV
1553
          match has_timed_out time with
×
1554
          | `Timed_out ->
×
1555
              [%log debug]
×
1556
                "Removing committed locally generated command $cmd from \
1557
                 possible rebroadcast pool, %s"
1558
                added_str ~metadata:(metadata ~key ~time) ;
1559
              false
×
UNCOV
1560
          | `Ok ->
×
1561
              true ) ;
1562
      (* Important to maintain ordering here *)
1563
      let rebroadcastable_txs =
6✔
1564
        Hashtbl.to_alist t.locally_generated_uncommitted
6✔
1565
        |> List.sort
6✔
1566
             ~compare:(fun (txn1, (_, `Batch batch1)) (txn2, (_, `Batch batch2))
1567
                      ->
UNCOV
1568
               let cmp = compare batch1 batch2 in
×
UNCOV
1569
               let get_hash =
×
1570
                 Transaction_hash.User_command_with_valid_signature.hash
1571
               in
1572
               let get_nonce txn =
UNCOV
1573
                 Transaction_hash.User_command_with_valid_signature.command txn
×
1574
                 |> User_command.applicable_at_nonce
1575
               in
1576
               if cmp <> 0 then cmp
×
1577
               else
UNCOV
1578
                 let cmp =
×
UNCOV
1579
                   Mina_numbers.Account_nonce.compare (get_nonce txn1)
×
UNCOV
1580
                     (get_nonce txn2)
×
1581
                 in
1582
                 if cmp <> 0 then cmp
×
UNCOV
1583
                 else Transaction_hash.compare (get_hash txn1) (get_hash txn2) )
×
1584
        |> List.group
6✔
1585
             ~break:(fun (_, (_, `Batch batch1)) (_, (_, `Batch batch2)) ->
UNCOV
1586
               batch1 <> batch2 )
×
1587
        |> List.map
1588
             ~f:
1589
               (List.map ~f:(fun (txn, _) ->
UNCOV
1590
                    Transaction_hash.User_command_with_valid_signature.command
×
1591
                      txn ) )
1592
      in
1593
      rebroadcastable_txs
6✔
1594
  end
1595

1596
  include Network_pool_base.Make (Transition_frontier) (Resource_pool)
1597
end
1598

1599
(* Use this one in downstream consumers *)
1600
module Make (Staged_ledger : sig
1601
  type t
1602

1603
  val ledger : t -> Mina_ledger.Ledger.t
1604
end)
1605
(Transition_frontier : Transition_frontier_intf
1606
                         with type staged_ledger := Staged_ledger.t) :
1607
  S with type transition_frontier := Transition_frontier.t =
1608
  Make0 (Mina_ledger.Ledger) (Staged_ledger) (Transition_frontier)
1609

1610
(* TODO: defunctor or remove monkey patching (#3731) *)
1611
include
1612
  Make
1613
    (Staged_ledger)
1614
    (struct
1615
      include Transition_frontier
1616

1617
      type best_tip_diff = Extensions.Best_tip_diff.view =
1618
        { new_commands : User_command.Valid.t With_status.t list
1619
        ; removed_commands : User_command.Valid.t With_status.t list
1620
        ; reorg_best_tip : bool
1621
        }
1622

1623
      let best_tip_diff_pipe t =
1624
        Extensions.(get_view_pipe (extensions t) Best_tip_diff)
12✔
1625
    end)
1626

1627
let%test_module _ =
1628
  ( module struct
1629
    open Signature_lib
1630
    module Mock_base_ledger = Mocks.Base_ledger
1631
    module Mock_staged_ledger = Mocks.Staged_ledger
1632

1633
    let () =
1634
      Core.Backtrace.elide := false ;
UNCOV
1635
      Async.Scheduler.set_record_backtraces true
×
1636

1637
    let num_test_keys = 10
1638

1639
    (* keys for accounts in the ledger *)
1640
    let test_keys =
UNCOV
1641
      Array.init num_test_keys ~f:(fun _ -> Signature_lib.Keypair.create ())
×
1642

1643
    let num_extra_keys = 30
1644

1645
    let block_window_duration =
NEW
1646
      Float.of_int
×
1647
        Genesis_constants.For_unit_tests.Constraint_constants.t
1648
          .block_window_duration_ms
NEW
1649
      |> Time.Span.of_ms
×
1650

1651
    (* keys that can be used when generating new accounts *)
1652
    let extra_keys =
UNCOV
1653
      Array.init num_extra_keys ~f:(fun _ -> Signature_lib.Keypair.create ())
×
1654

UNCOV
1655
    let precomputed_values = Lazy.force Precomputed_values.for_unit_tests
×
1656

1657
    let constraint_constants = precomputed_values.constraint_constants
1658

1659
    let consensus_constants = precomputed_values.consensus_constants
1660

1661
    let proof_level = precomputed_values.proof_level
1662

1663
    let genesis_constants = precomputed_values.genesis_constants
1664

1665
    let compile_config = precomputed_values.compile_config
1666

1667
    let minimum_fee =
UNCOV
1668
      Currency.Fee.to_nanomina_int genesis_constants.minimum_user_command_fee
×
1669

UNCOV
1670
    let logger = Logger.null ()
×
1671

1672
    let time_controller = Block_time.Controller.basic ~logger
1673

1674
    let verifier =
UNCOV
1675
      Async.Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
1676
          Verifier.For_tests.default ~constraint_constants ~logger ~proof_level
×
1677
            () )
1678

1679
    let `VK vk, `Prover prover =
UNCOV
1680
      Transaction_snark.For_tests.create_trivial_snapp ~constraint_constants ()
×
1681

UNCOV
1682
    let vk = Async.Thread_safe.block_on_async_exn (fun () -> vk)
×
1683

1684
    let dummy_state_view =
1685
      let state_body =
1686
        let consensus_constants =
1687
          Consensus.Constants.create ~constraint_constants
1688
            ~protocol_constants:genesis_constants.protocol
1689
        in
1690
        let compile_time_genesis =
1691
          (*not using Precomputed_values.for_unit_test because of dependency cycle*)
1692
          Mina_state.Genesis_protocol_state.t
UNCOV
1693
            ~genesis_ledger:Genesis_ledger.(Packed.t for_unit_tests)
×
1694
            ~genesis_epoch_data:Consensus.Genesis_epoch_data.for_unit_tests
1695
            ~constraint_constants ~consensus_constants
1696
            ~genesis_body_reference:Staged_ledger_diff.genesis_body_reference
1697
        in
UNCOV
1698
        compile_time_genesis.data |> Mina_state.Protocol_state.body
×
1699
      in
UNCOV
1700
      { (Mina_state.Protocol_state.Body.view state_body) with
×
1701
        global_slot_since_genesis = Mina_numbers.Global_slot_since_genesis.zero
1702
      }
1703

1704
    module Mock_transition_frontier = struct
1705
      module Breadcrumb = struct
1706
        type t = Mock_staged_ledger.t
1707

1708
        let staged_ledger = Fn.id
1709
      end
1710

1711
      type best_tip_diff =
1712
        { new_commands : User_command.Valid.t With_status.t list
1713
        ; removed_commands : User_command.Valid.t With_status.t list
1714
        ; reorg_best_tip : bool
1715
        }
1716

1717
      type t = best_tip_diff Broadcast_pipe.Reader.t * Breadcrumb.t ref
1718

1719
      let create ?permissions :
1720
          unit -> t * best_tip_diff Broadcast_pipe.Writer.t =
UNCOV
1721
       fun () ->
×
UNCOV
1722
        let zkappify_account (account : Account.t) : Account.t =
×
UNCOV
1723
          let zkapp =
×
1724
            Some { Zkapp_account.default with verification_key = Some vk }
1725
          in
1726
          { account with
1727
            zkapp
1728
          ; permissions =
1729
              ( match permissions with
UNCOV
1730
              | Some p ->
×
1731
                  p
UNCOV
1732
              | None ->
×
1733
                  Permissions.user_default )
1734
          }
1735
        in
1736
        let pipe_r, pipe_w =
1737
          Broadcast_pipe.create
1738
            { new_commands = []; removed_commands = []; reorg_best_tip = false }
1739
        in
UNCOV
1740
        let initial_balance =
×
1741
          Currency.Balance.of_mina_string_exn "900000000.0"
1742
        in
UNCOV
1743
        let ledger = Mina_ledger.Ledger.create_ephemeral ~depth:10 () in
×
UNCOV
1744
        Array.iteri test_keys ~f:(fun i kp ->
×
UNCOV
1745
            let account_id =
×
1746
              Account_id.create
UNCOV
1747
                (Public_key.compress kp.public_key)
×
1748
                Token_id.default
1749
            in
UNCOV
1750
            let _tag, account, loc =
×
1751
              Or_error.ok_exn
UNCOV
1752
              @@ Mina_ledger.Ledger.Ledger_inner.get_or_create ledger account_id
×
1753
            in
1754
            (* set the account balance *)
UNCOV
1755
            let account = { account with balance = initial_balance } in
×
1756
            (* zkappify every other account *)
1757
            let account =
UNCOV
1758
              if i mod 2 = 0 then account else zkappify_account account
×
1759
            in
1760
            Mina_ledger.Ledger.Ledger_inner.set ledger loc account ) ;
UNCOV
1761
        ((pipe_r, ref ledger), pipe_w)
×
1762

UNCOV
1763
      let best_tip (_, best_tip) = !best_tip
×
1764

UNCOV
1765
      let best_tip_diff_pipe (pipe, _) = pipe
×
1766
    end
1767

1768
    module Test =
1769
      Make0 (Mock_base_ledger) (Mock_staged_ledger) (Mock_transition_frontier)
1770

1771
    type test =
1772
      { txn_pool : Test.Resource_pool.t
1773
      ; best_tip_diff_w :
1774
          Mock_transition_frontier.best_tip_diff Broadcast_pipe.Writer.t
1775
      ; best_tip_ref : Mina_ledger.Ledger.t ref
1776
      ; frontier_pipe_w :
1777
          Mock_transition_frontier.t option Broadcast_pipe.Writer.t
1778
      }
1779

1780
    let pool_max_size = 25
1781

1782
    let assert_user_command_sets_equal cs1 cs2 =
UNCOV
1783
      let index cs =
×
UNCOV
1784
        let decompose c =
×
UNCOV
1785
          ( Transaction_hash.User_command.hash c
×
UNCOV
1786
          , Transaction_hash.User_command.command c )
×
1787
        in
UNCOV
1788
        List.map cs ~f:decompose |> Transaction_hash.Map.of_alist_exn
×
1789
      in
1790
      let index1 = index cs1 in
UNCOV
1791
      let index2 = index cs2 in
×
UNCOV
1792
      let set1 = Transaction_hash.Set.of_list @@ Map.keys index1 in
×
UNCOV
1793
      let set2 = Transaction_hash.Set.of_list @@ Map.keys index2 in
×
1794
      if not (Set.equal set1 set2) then (
×
1795
        let additional1, additional2 =
1796
          Set.symmetric_diff set1 set2
×
1797
          |> Sequence.map
×
1798
               ~f:
1799
                 (Either.map ~first:(Map.find_exn index1)
×
1800
                    ~second:(Map.find_exn index2) )
×
1801
          |> Sequence.to_list
×
1802
          |> List.partition_map ~f:Fn.id
1803
        in
1804
        assert (List.length additional1 + List.length additional2 > 0) ;
×
1805
        let report_additional commands a b =
1806
          Core.Printf.printf "%s user commands not in %s:\n" a b ;
×
1807
          List.iter commands ~f:(fun c ->
×
1808
              Core.Printf.printf !"  %{Sexp}\n" (User_command.sexp_of_t c) )
×
1809
        in
1810
        if List.length additional1 > 0 then
×
1811
          report_additional additional1 "actual" "expected" ;
×
1812
        if List.length additional2 > 0 then
×
1813
          report_additional additional2 "expected" "actual" ) ;
×
UNCOV
1814
      [%test_eq: Transaction_hash.Set.t] set1 set2
×
1815

1816
    let replace_valid_zkapp_command_authorizations ~keymap ~ledger valid_cmds :
1817
        User_command.Valid.t list Deferred.t =
UNCOV
1818
      let open Deferred.Let_syntax in
×
1819
      let%map zkapp_commands_fixed =
UNCOV
1820
        Deferred.List.map
×
1821
          (valid_cmds : User_command.Valid.t list)
1822
          ~f:(function
UNCOV
1823
            | Zkapp_command zkapp_command_dummy_auths ->
×
1824
                let%map cmd =
UNCOV
1825
                  Zkapp_command_builder.replace_authorizations ~keymap ~prover
×
UNCOV
1826
                    (Zkapp_command.Valid.forget zkapp_command_dummy_auths)
×
1827
                in
UNCOV
1828
                User_command.Zkapp_command cmd
×
1829
            | Signed_command _ ->
×
1830
                failwith "Expected Zkapp_command valid user command" )
1831
      in
UNCOV
1832
      match
×
UNCOV
1833
        User_command.Unapplied_sequence.to_all_verifiable zkapp_commands_fixed
×
1834
          ~load_vk_cache:(fun account_ids ->
UNCOV
1835
            Set.to_list account_ids
×
UNCOV
1836
            |> Zkapp_command.Verifiable.load_vks_from_ledger
×
UNCOV
1837
                 ~get_batch:(Mina_ledger.Ledger.get_batch ledger)
×
1838
                 ~location_of_account_batch:
UNCOV
1839
                   (Mina_ledger.Ledger.location_of_account_batch ledger)
×
1840
            |> Map.map ~f:(fun vk ->
UNCOV
1841
                   Zkapp_basic.F_map.Map.singleton vk.hash vk ) )
×
1842
        |> Or_error.bind ~f:(fun xs ->
UNCOV
1843
               List.map xs ~f:User_command.check_verifiable
×
1844
               |> Or_error.combine_errors )
1845
      with
UNCOV
1846
      | Ok cmds ->
×
1847
          cmds
1848
      | Error err ->
×
1849
          Error.raise
1850
          @@ Error.tag ~tag:"Could not create Zkapp_command.Valid.t" err
×
1851

1852
    (** Assert the invariants of the locally generated command tracking system. *)
1853
    let assert_locally_generated (pool : Test.Resource_pool.t) =
UNCOV
1854
      ignore
×
UNCOV
1855
        ( Hashtbl.merge pool.locally_generated_committed
×
1856
            pool.locally_generated_uncommitted ~f:(fun ~key -> function
1857
            | `Both ((committed, _), (uncommitted, _)) ->
×
1858
                failwithf
1859
                  !"Command \
×
1860
                    %{sexp:Transaction_hash.User_command_with_valid_signature.t} \
1861
                    in both locally generated committed and uncommitted with \
1862
                    times %s and %s"
1863
                  key (Time.to_string committed)
×
1864
                  (Time.to_string uncommitted)
×
1865
                  ()
UNCOV
1866
            | `Left cmd ->
×
1867
                Some cmd
UNCOV
1868
            | `Right cmd ->
×
1869
                (* Locally generated uncommitted transactions should be in the
1870
                   pool, so long as we're not in the middle of updating it. *)
UNCOV
1871
                assert (
×
UNCOV
1872
                  Indexed_pool.member pool.pool
×
UNCOV
1873
                    (Transaction_hash.User_command.of_checked key) ) ;
×
1874
                Some cmd )
1875
          : ( Transaction_hash.User_command_with_valid_signature.t
1876
            , Time.t * [ `Batch of int ] )
1877
            Hashtbl.t )
1878

1879
    let assert_fee_wu_ordering (pool : Test.Resource_pool.t) =
UNCOV
1880
      let txns = Test.Resource_pool.transactions pool |> Sequence.to_list in
×
UNCOV
1881
      let compare txn1 txn2 =
×
UNCOV
1882
        let open Transaction_hash.User_command_with_valid_signature in
×
1883
        let cmd1 = command txn1 in
UNCOV
1884
        let cmd2 = command txn2 in
×
1885
        (* ascending order of nonces, if same fee payer *)
UNCOV
1886
        if
×
1887
          Account_id.equal
UNCOV
1888
            (User_command.fee_payer cmd1)
×
UNCOV
1889
            (User_command.fee_payer cmd2)
×
1890
        then
UNCOV
1891
          Account.Nonce.compare
×
UNCOV
1892
            (User_command.applicable_at_nonce cmd1)
×
UNCOV
1893
            (User_command.applicable_at_nonce cmd2)
×
1894
        else
UNCOV
1895
          let get_fee_wu cmd = User_command.fee_per_wu cmd in
×
1896
          (* descending order of fee/weight *)
UNCOV
1897
          Currency.Fee_rate.compare (get_fee_wu cmd2) (get_fee_wu cmd1)
×
1898
      in
UNCOV
1899
      assert (List.is_sorted txns ~compare)
×
1900

1901
    let assert_pool_txs test txs =
UNCOV
1902
      Indexed_pool.For_tests.assert_pool_consistency test.txn_pool.pool ;
×
UNCOV
1903
      assert_locally_generated test.txn_pool ;
×
UNCOV
1904
      assert_fee_wu_ordering test.txn_pool ;
×
UNCOV
1905
      assert_user_command_sets_equal
×
UNCOV
1906
        ( Sequence.to_list
×
UNCOV
1907
        @@ Sequence.map ~f:Transaction_hash.User_command.of_checked
×
UNCOV
1908
        @@ Test.Resource_pool.transactions test.txn_pool )
×
UNCOV
1909
        (List.map
×
1910
           ~f:
UNCOV
1911
             (Fn.compose Transaction_hash.User_command.create
×
1912
                User_command.forget_check )
1913
           txs )
1914

UNCOV
1915
    let setup_test ?(verifier = verifier) ?permissions ?slot_tx_end () =
×
UNCOV
1916
      let frontier, best_tip_diff_w =
×
1917
        Mock_transition_frontier.create ?permissions ()
1918
      in
UNCOV
1919
      let _, best_tip_ref = frontier in
×
1920
      let frontier_pipe_r, frontier_pipe_w =
1921
        Broadcast_pipe.create @@ Some frontier
1922
      in
UNCOV
1923
      let trust_system = Trust_system.null () in
×
UNCOV
1924
      let config =
×
1925
        Test.Resource_pool.make_config ~trust_system ~pool_max_size ~verifier
1926
          ~genesis_constants ~slot_tx_end ~compile_config
1927
      in
1928
      let pool_, _, _ =
1929
        Test.create ~config ~logger ~constraint_constants ~consensus_constants
1930
          ~time_controller ~frontier_broadcast_pipe:frontier_pipe_r
UNCOV
1931
          ~log_gossip_heard:false ~on_remote_push:(Fn.const Deferred.unit)
×
1932
          ~block_window_duration
1933
      in
1934
      let txn_pool = Test.resource_pool pool_ in
UNCOV
1935
      let%map () = Async.Scheduler.yield_until_no_jobs_remain () in
×
UNCOV
1936
      { txn_pool; best_tip_diff_w; best_tip_ref; frontier_pipe_w }
×
1937

1938
    let independent_cmds : User_command.Valid.t list =
1939
      let rec go n cmds =
UNCOV
1940
        let open Quickcheck.Generator.Let_syntax in
×
UNCOV
1941
        if n < Array.length test_keys then
×
1942
          let%bind cmd =
1943
            let sender = test_keys.(n) in
UNCOV
1944
            User_command.Valid.Gen.payment ~sign_type:`Real
×
1945
              ~key_gen:
UNCOV
1946
                (Quickcheck.Generator.tuple2 (return sender)
×
UNCOV
1947
                   (Quickcheck_lib.of_array test_keys) )
×
1948
              ~max_amount:1_000_000_000 ~fee_range:1_000_000_000 ()
1949
          in
UNCOV
1950
          go (n + 1) (cmd :: cmds)
×
UNCOV
1951
        else Quickcheck.Generator.return @@ List.rev cmds
×
1952
      in
UNCOV
1953
      Quickcheck.random_value ~seed:(`Deterministic "constant") (go 0 [])
×
1954

1955
    let mk_payment' ?valid_until ~sender_idx ~receiver_idx ~fee ~nonce ~amount
1956
        () =
UNCOV
1957
      let get_pk idx = Public_key.compress test_keys.(idx).public_key in
×
1958
      Signed_command.sign test_keys.(sender_idx)
1959
        (Signed_command_payload.create
UNCOV
1960
           ~fee:(Currency.Fee.of_nanomina_int_exn fee)
×
UNCOV
1961
           ~fee_payer_pk:(get_pk sender_idx) ~valid_until
×
UNCOV
1962
           ~nonce:(Account.Nonce.of_int nonce)
×
UNCOV
1963
           ~memo:(Signed_command_memo.create_by_digesting_string_exn "foo")
×
1964
           ~body:
1965
             (Signed_command_payload.Body.Payment
UNCOV
1966
                { receiver_pk = get_pk receiver_idx
×
UNCOV
1967
                ; amount = Currency.Amount.of_nanomina_int_exn amount
×
1968
                } ) )
1969

1970
    let mk_single_account_update ~chain ~fee_payer_idx ~zkapp_account_idx ~fee
1971
        ~nonce ~ledger =
UNCOV
1972
      let fee = Currency.Fee.of_nanomina_int_exn fee in
×
UNCOV
1973
      let fee_payer_kp = test_keys.(fee_payer_idx) in
×
UNCOV
1974
      let nonce = Account.Nonce.of_int nonce in
×
UNCOV
1975
      let spec : Transaction_snark.For_tests.Single_account_update_spec.t =
×
1976
        Transaction_snark.For_tests.Single_account_update_spec.
1977
          { fee_payer = (fee_payer_kp, nonce)
1978
          ; fee
UNCOV
1979
          ; memo = Signed_command_memo.create_from_string_exn "invalid proof"
×
1980
          ; zkapp_account_keypair = test_keys.(zkapp_account_idx)
1981
          ; update = { Account_update.Update.noop with zkapp_uri = Set "abcd" }
1982
          ; call_data = Snark_params.Tick.Field.zero
1983
          ; events = []
1984
          ; actions = []
1985
          }
1986
      in
1987
      let%map zkapp_command =
UNCOV
1988
        Transaction_snark.For_tests.single_account_update ~chain
×
1989
          ~constraint_constants spec
1990
      in
UNCOV
1991
      Or_error.ok_exn
×
UNCOV
1992
        (Zkapp_command.Verifiable.create ~failed:false
×
1993
           ~find_vk:
1994
             (Zkapp_command.Verifiable.load_vk_from_ledger
UNCOV
1995
                ~get:(Mina_ledger.Ledger.get ledger)
×
1996
                ~location_of_account:
UNCOV
1997
                  (Mina_ledger.Ledger.location_of_account ledger) )
×
1998
           zkapp_command )
1999

2000
    let mk_transfer_zkapp_command ?valid_period ?fee_payer_idx ~sender_idx
2001
        ~receiver_idx ~fee ~nonce ~amount () =
UNCOV
2002
      let sender_kp = test_keys.(sender_idx) in
×
UNCOV
2003
      let sender_nonce = Account.Nonce.of_int nonce in
×
UNCOV
2004
      let sender = (sender_kp, sender_nonce) in
×
2005
      let amount = Currency.Amount.of_nanomina_int_exn amount in
UNCOV
2006
      let receiver_kp = test_keys.(receiver_idx) in
×
UNCOV
2007
      let receiver =
×
2008
        receiver_kp.public_key |> Signature_lib.Public_key.compress
2009
      in
UNCOV
2010
      let fee_payer =
×
2011
        match fee_payer_idx with
UNCOV
2012
        | None ->
×
2013
            None
UNCOV
2014
        | Some (idx, nonce) ->
×
2015
            let fee_payer_kp = test_keys.(idx) in
UNCOV
2016
            let fee_payer_nonce = Account.Nonce.of_int nonce in
×
UNCOV
2017
            Some (fee_payer_kp, fee_payer_nonce)
×
2018
      in
2019
      let fee = Currency.Fee.of_nanomina_int_exn fee in
UNCOV
2020
      let protocol_state_precondition =
×
2021
        match valid_period with
UNCOV
2022
        | None ->
×
2023
            Zkapp_precondition.Protocol_state.accept
UNCOV
2024
        | Some time ->
×
UNCOV
2025
            Zkapp_precondition.Protocol_state.valid_until time
×
2026
      in
2027
      let test_spec : Transaction_snark.For_tests.Multiple_transfers_spec.t =
2028
        { sender
2029
        ; fee_payer
2030
        ; fee
2031
        ; receivers = [ (receiver, amount) ]
2032
        ; amount
2033
        ; zkapp_account_keypairs = []
UNCOV
2034
        ; memo = Signed_command_memo.create_from_string_exn "expiry tests"
×
2035
        ; new_zkapp_account = false
2036
        ; snapp_update = Account_update.Update.dummy
2037
        ; call_data = Snark_params.Tick.Field.zero
2038
        ; events = []
2039
        ; actions = []
2040
        ; preconditions =
2041
            Some
2042
              { Account_update.Preconditions.network =
2043
                  protocol_state_precondition
2044
              ; account =
2045
                  (let nonce =
2046
                     if Option.is_none fee_payer then
UNCOV
2047
                       Account.Nonce.succ sender_nonce
×
UNCOV
2048
                     else sender_nonce
×
2049
                   in
UNCOV
2050
                   Zkapp_precondition.Account.nonce nonce )
×
2051
              ; valid_while = Ignore
2052
              }
2053
        }
2054
      in
2055
      let zkapp_command =
2056
        Transaction_snark.For_tests.multiple_transfers ~constraint_constants
2057
          test_spec
2058
      in
UNCOV
2059
      let zkapp_command =
×
2060
        Or_error.ok_exn
UNCOV
2061
          (Zkapp_command.Valid.to_valid ~failed:false
×
2062
             ~find_vk:
2063
               (Zkapp_command.Verifiable.load_vk_from_ledger
2064
                  ~get:(fun _ -> failwith "Not expecting proof zkapp_command")
×
2065
                  ~location_of_account:(fun _ ->
2066
                    failwith "Not expecting proof zkapp_command" ) )
×
2067
             zkapp_command )
2068
      in
UNCOV
2069
      User_command.Zkapp_command zkapp_command
×
2070

2071
    let mk_payment ?valid_until ~sender_idx ~receiver_idx ~fee ~nonce ~amount ()
2072
        =
UNCOV
2073
      User_command.Signed_command
×
UNCOV
2074
        (mk_payment' ?valid_until ~sender_idx ~fee ~nonce ~receiver_idx ~amount
×
2075
           () )
2076

2077
    let mk_zkapp_commands_single_block num_cmds (pool : Test.Resource_pool.t) :
2078
        User_command.Valid.t list Deferred.t =
UNCOV
2079
      assert (num_cmds < Array.length test_keys - 1) ;
×
2080
      let best_tip_ledger = Option.value_exn pool.best_tip_ledger in
UNCOV
2081
      let keymap =
×
UNCOV
2082
        Array.fold (Array.append test_keys extra_keys)
×
2083
          ~init:Public_key.Compressed.Map.empty
2084
          ~f:(fun map { public_key; private_key } ->
UNCOV
2085
            let key = Public_key.compress public_key in
×
UNCOV
2086
            Public_key.Compressed.Map.add_exn map ~key ~data:private_key )
×
2087
      in
UNCOV
2088
      let account_state_tbl =
×
UNCOV
2089
        List.take (Array.to_list test_keys) num_cmds
×
UNCOV
2090
        |> List.map ~f:(fun kp ->
×
UNCOV
2091
               let id =
×
2092
                 Account_id.create
UNCOV
2093
                   (Public_key.compress kp.public_key)
×
2094
                   Token_id.default
2095
               in
UNCOV
2096
               let state =
×
2097
                 Option.value_exn
2098
                   (let%bind.Option loc =
UNCOV
2099
                      Mina_ledger.Ledger.location_of_account best_tip_ledger id
×
2100
                    in
UNCOV
2101
                    Mina_ledger.Ledger.get best_tip_ledger loc )
×
2102
               in
UNCOV
2103
               (id, (state, `Fee_payer)) )
×
2104
        |> Account_id.Table.of_alist_exn
2105
      in
UNCOV
2106
      let rec go n cmds =
×
UNCOV
2107
        let open Quickcheck.Generator.Let_syntax in
×
UNCOV
2108
        if n >= num_cmds then Quickcheck.Generator.return @@ List.rev cmds
×
2109
        else
2110
          let%bind cmd =
2111
            let fee_payer_keypair = test_keys.(n) in
2112
            let%map (zkapp_command : Zkapp_command.t) =
UNCOV
2113
              Mina_generators.Zkapp_command_generators.gen_zkapp_command_from
×
2114
                ~max_token_updates:1 ~keymap ~account_state_tbl
2115
                ~fee_payer_keypair ~ledger:best_tip_ledger ~constraint_constants
2116
                ~genesis_constants
2117
                ~map_account_update:(fun (p : Account_update.t) ->
NEW
2118
                  Zkapp_command.For_tests.replace_vk vk
×
2119
                    { p with
2120
                      body =
2121
                        { p.body with
2122
                          preconditions =
2123
                            { p.body.preconditions with
2124
                              account =
2125
                                ( match p.body.preconditions.account.nonce with
NEW
2126
                                | Zkapp_basic.Or_ignore.Check n as c
×
2127
                                  when Zkapp_precondition.Numeric.(
NEW
2128
                                         is_constant Tc.nonce c) ->
×
NEW
2129
                                    Zkapp_precondition.Account.nonce n.lower
×
NEW
2130
                                | _ ->
×
2131
                                    Zkapp_precondition.Account.accept )
2132
                            }
2133
                        }
2134
                    } )
2135
                ()
2136
            in
UNCOV
2137
            let valid_zkapp_command =
×
2138
              Or_error.ok_exn
UNCOV
2139
                (Zkapp_command.Valid.to_valid ~failed:false
×
2140
                   ~find_vk:
2141
                     (Zkapp_command.Verifiable.load_vk_from_ledger
UNCOV
2142
                        ~get:(Mina_ledger.Ledger.get best_tip_ledger)
×
2143
                        ~location_of_account:
UNCOV
2144
                          (Mina_ledger.Ledger.location_of_account
×
2145
                             best_tip_ledger ) )
2146
                   zkapp_command )
2147
            in
UNCOV
2148
            User_command.Zkapp_command valid_zkapp_command
×
2149
          in
UNCOV
2150
          go (n + 1) (cmd :: cmds)
×
2151
      in
2152
      let valid_zkapp_commands =
UNCOV
2153
        Quickcheck.random_value ~seed:(`Deterministic "zkapp_command") (go 0 [])
×
2154
      in
UNCOV
2155
      replace_valid_zkapp_command_authorizations ~keymap ~ledger:best_tip_ledger
×
2156
        valid_zkapp_commands
2157

2158
    type pool_apply = (User_command.t list, [ `Other of Error.t ]) Result.t
×
2159
    [@@deriving sexp, compare]
2160

2161
    let canonicalize t =
UNCOV
2162
      Result.map t ~f:(List.sort ~compare:User_command.compare)
×
2163

2164
    let compare_pool_apply (t1 : pool_apply) (t2 : pool_apply) =
UNCOV
2165
      compare_pool_apply (canonicalize t1) (canonicalize t2)
×
2166

2167
    let assert_pool_apply expected_commands result =
UNCOV
2168
      let accepted_commands =
×
UNCOV
2169
        Result.map result ~f:(fun (_, accepted, _) -> accepted)
×
2170
      in
UNCOV
2171
      [%test_eq: pool_apply] accepted_commands
×
UNCOV
2172
        (Ok (List.map ~f:User_command.forget_check expected_commands))
×
2173

2174
    let mk_with_status (cmd : User_command.Valid.t) =
UNCOV
2175
      { With_status.data = cmd; status = Applied }
×
2176

UNCOV
2177
    let add_commands ?(local = true) test cs =
×
UNCOV
2178
      let sender =
×
UNCOV
2179
        if local then Envelope.Sender.Local
×
2180
        else
UNCOV
2181
          Envelope.Sender.Remote
×
UNCOV
2182
            (Peer.create
×
UNCOV
2183
               (Unix.Inet_addr.of_string "1.2.3.4")
×
2184
               ~peer_id:
UNCOV
2185
                 (Peer.Id.unsafe_of_string "contents should be irrelevant")
×
2186
               ~libp2p_port:8302 )
2187
      in
2188
      let tm0 = Time.now () in
2189
      let%map verified =
UNCOV
2190
        Test.Resource_pool.Diff.verify test.txn_pool
×
2191
          (Envelope.Incoming.wrap
UNCOV
2192
             ~data:(List.map ~f:User_command.forget_check cs)
×
2193
             ~sender )
UNCOV
2194
        >>| Fn.compose Or_error.ok_exn
×
2195
              (Result.map_error ~f:Intf.Verification_error.to_error)
2196
      in
UNCOV
2197
      let result =
×
2198
        Test.Resource_pool.Diff.unsafe_apply test.txn_pool verified
2199
      in
UNCOV
2200
      let tm1 = Time.now () in
×
UNCOV
2201
      [%log' info test.txn_pool.logger] "Time for add_commands: %0.04f sec"
×
UNCOV
2202
        (Time.diff tm1 tm0 |> Time.Span.to_sec) ;
×
UNCOV
2203
      let debug = false in
×
2204
      ( match result with
UNCOV
2205
      | Ok (`Accept, _, rejects) ->
×
2206
          if debug then
2207
            List.iter rejects ~f:(fun (cmd, err) ->
×
2208
                Core.Printf.printf
×
2209
                  !"command was rejected because %s: %{Yojson.Safe}\n%!"
×
2210
                  (Diff_versioned.Diff_error.to_string_name err)
×
2211
                  (User_command.to_yojson cmd) )
×
2212
      | Ok (`Reject, _, _) ->
×
2213
          failwith "diff was rejected during application"
2214
      | Error (`Other err) ->
×
2215
          if debug then
2216
            Core.Printf.printf
×
2217
              !"failed to apply diff to pool: %s\n%!"
2218
              (Error.to_string_hum err) ) ;
×
2219
      result
2220

2221
    let add_commands' ?local test cs =
UNCOV
2222
      add_commands ?local test cs >>| assert_pool_apply cs
×
2223

UNCOV
2224
    let reorg ?(reorg_best_tip = false) test new_commands removed_commands =
×
2225
      let%bind () =
UNCOV
2226
        Broadcast_pipe.Writer.write test.best_tip_diff_w
×
2227
          { Mock_transition_frontier.new_commands =
UNCOV
2228
              List.map ~f:mk_with_status new_commands
×
UNCOV
2229
          ; removed_commands = List.map ~f:mk_with_status removed_commands
×
2230
          ; reorg_best_tip
2231
          }
2232
      in
UNCOV
2233
      Async.Scheduler.yield_until_no_jobs_remain ()
×
2234

2235
    let commit_commands test cs =
UNCOV
2236
      let ledger = Option.value_exn test.txn_pool.best_tip_ledger in
×
UNCOV
2237
      List.iter cs ~f:(fun c ->
×
UNCOV
2238
          match User_command.forget_check c with
×
UNCOV
2239
          | User_command.Signed_command c -> (
×
2240
              let (`If_this_is_used_it_should_have_a_comment_justifying_it valid)
2241
                  =
2242
                Signed_command.to_valid_unsafe c
2243
              in
UNCOV
2244
              let applied =
×
2245
                Or_error.ok_exn
UNCOV
2246
                @@ Mina_ledger.Ledger.apply_user_command ~constraint_constants
×
2247
                     ~txn_global_slot:
2248
                       Mina_numbers.Global_slot_since_genesis.zero ledger valid
2249
              in
UNCOV
2250
              match applied.body with
×
2251
              | Failed ->
×
2252
                  failwith "failed to apply user command to ledger"
UNCOV
2253
              | _ ->
×
2254
                  () )
UNCOV
2255
          | User_command.Zkapp_command p -> (
×
2256
              let applied, _ =
2257
                Or_error.ok_exn
UNCOV
2258
                @@ Mina_ledger.Ledger.apply_zkapp_command_unchecked
×
2259
                     ~constraint_constants
2260
                     ~global_slot:dummy_state_view.global_slot_since_genesis
2261
                     ~state_view:dummy_state_view ledger p
2262
              in
UNCOV
2263
              match With_status.status applied.command with
×
2264
              | Failed failures ->
×
2265
                  failwithf
2266
                    "failed to apply zkapp_command transaction to ledger: [%s]"
2267
                    ( String.concat ~sep:", "
×
2268
                    @@ List.bind
×
2269
                         ~f:(List.map ~f:Transaction_status.Failure.to_string)
2270
                         failures )
2271
                    ()
UNCOV
2272
              | Applied ->
×
2273
                  () ) )
2274

2275
    let commit_commands' test cs =
UNCOV
2276
      let open Mina_ledger in
×
2277
      let ledger = Option.value_exn test.txn_pool.best_tip_ledger in
UNCOV
2278
      test.best_tip_ref :=
×
UNCOV
2279
        Ledger.Maskable.register_mask
×
UNCOV
2280
          (Ledger.Any_ledger.cast (module Mina_ledger.Ledger) ledger)
×
UNCOV
2281
          (Ledger.Mask.create ~depth:(Ledger.depth ledger) ()) ;
×
UNCOV
2282
      let%map () = reorg test [] [] in
×
UNCOV
2283
      assert (
×
UNCOV
2284
        not (phys_equal (Option.value_exn test.txn_pool.best_tip_ledger) ledger) ) ;
×
UNCOV
2285
      assert (
×
UNCOV
2286
        phys_equal
×
UNCOV
2287
          (Option.value_exn test.txn_pool.best_tip_ledger)
×
2288
          !(test.best_tip_ref) ) ;
2289
      commit_commands test cs ;
UNCOV
2290
      assert (
×
UNCOV
2291
        not (phys_equal (Option.value_exn test.txn_pool.best_tip_ledger) ledger) ) ;
×
UNCOV
2292
      assert (
×
UNCOV
2293
        phys_equal
×
UNCOV
2294
          (Option.value_exn test.txn_pool.best_tip_ledger)
×
2295
          !(test.best_tip_ref) ) ;
2296
      ledger
2297

UNCOV
2298
    let advance_chain test cs = commit_commands test cs ; reorg test cs []
×
2299

2300
    (* TODO: remove this (all of these test should be expressed by committing txns to the ledger, not mutating accounts *)
2301
    let modify_ledger ledger ~idx ~balance ~nonce =
UNCOV
2302
      let id =
×
2303
        Account_id.create
UNCOV
2304
          (Signature_lib.Public_key.compress test_keys.(idx).public_key)
×
2305
          Token_id.default
2306
      in
UNCOV
2307
      let loc =
×
UNCOV
2308
        Option.value_exn @@ Mina_ledger.Ledger.location_of_account ledger id
×
2309
      in
UNCOV
2310
      let account = Option.value_exn @@ Mina_ledger.Ledger.get ledger loc in
×
UNCOV
2311
      Mina_ledger.Ledger.set ledger loc
×
2312
        { account with
UNCOV
2313
          balance = Currency.Balance.of_nanomina_int_exn balance
×
UNCOV
2314
        ; nonce = Account.Nonce.of_int nonce
×
2315
        }
2316

2317
    let mk_linear_case_test t cmds =
UNCOV
2318
      assert_pool_txs t [] ;
×
UNCOV
2319
      let%bind () = add_commands' t cmds in
×
UNCOV
2320
      let%bind () = advance_chain t (List.take independent_cmds 1) in
×
UNCOV
2321
      assert_pool_txs t (List.drop cmds 1) ;
×
2322
      let%bind () =
UNCOV
2323
        advance_chain t (List.take (List.drop independent_cmds 1) 2)
×
2324
      in
UNCOV
2325
      assert_pool_txs t (List.drop cmds 3) ;
×
UNCOV
2326
      Deferred.unit
×
2327

2328
    let%test_unit "transactions are removed in linear case (user cmds)" =
UNCOV
2329
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
2330
          let%bind test = setup_test () in
×
UNCOV
2331
          mk_linear_case_test test independent_cmds )
×
2332

2333
    let%test_unit "transactions are removed in linear case (zkapps)" =
UNCOV
2334
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
2335
          let%bind test = setup_test () in
×
UNCOV
2336
          mk_zkapp_commands_single_block 7 test.txn_pool
×
UNCOV
2337
          >>= mk_linear_case_test test )
×
2338

2339
    let mk_remove_and_add_test t cmds =
UNCOV
2340
      assert_pool_txs t [] ;
×
2341
      (* omit the 1st (0-based) command *)
UNCOV
2342
      let%bind () = add_commands' t (List.hd_exn cmds :: List.drop cmds 2) in
×
UNCOV
2343
      commit_commands t (List.take cmds 1) ;
×
UNCOV
2344
      let%bind () = reorg t (List.take cmds 1) (List.slice cmds 1 2) in
×
UNCOV
2345
      assert_pool_txs t (List.tl_exn cmds) ;
×
UNCOV
2346
      Deferred.unit
×
2347

2348
    let%test_unit "Transactions are removed and added back in fork changes \
2349
                   (user cmds)" =
UNCOV
2350
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
2351
          let%bind test = setup_test () in
×
UNCOV
2352
          mk_remove_and_add_test test independent_cmds )
×
2353

2354
    let%test_unit "Transactions are removed and added back in fork changes \
2355
                   (zkapps)" =
UNCOV
2356
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
2357
          let%bind test = setup_test () in
×
UNCOV
2358
          mk_zkapp_commands_single_block 7 test.txn_pool
×
UNCOV
2359
          >>= mk_remove_and_add_test test )
×
2360

2361
    let mk_invalid_test t cmds =
UNCOV
2362
      assert_pool_txs t [] ;
×
UNCOV
2363
      let%bind () = advance_chain t (List.take cmds 2) in
×
2364
      let%bind () =
UNCOV
2365
        add_commands t cmds >>| assert_pool_apply (List.drop cmds 2)
×
2366
      in
UNCOV
2367
      assert_pool_txs t (List.drop cmds 2) ;
×
UNCOV
2368
      Deferred.unit
×
2369

2370
    let%test_unit "invalid transactions are not accepted (user cmds)" =
UNCOV
2371
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
2372
          let%bind test = setup_test () in
×
UNCOV
2373
          mk_invalid_test test independent_cmds )
×
2374

2375
    let%test_unit "invalid transactions are not accepted (zkapps)" =
UNCOV
2376
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
2377
          let%bind test = setup_test () in
×
UNCOV
2378
          mk_zkapp_commands_single_block 7 test.txn_pool
×
UNCOV
2379
          >>= mk_invalid_test test )
×
2380

2381
    let current_global_slot () =
UNCOV
2382
      let current_time = Block_time.now time_controller in
×
2383
      (* for testing, consider this slot to be a since-genesis slot *)
UNCOV
2384
      Consensus.Data.Consensus_time.(
×
UNCOV
2385
        of_time_exn ~constants:consensus_constants current_time
×
UNCOV
2386
        |> to_global_slot)
×
UNCOV
2387
      |> Mina_numbers.Global_slot_since_hard_fork.to_uint32
×
2388
      |> Mina_numbers.Global_slot_since_genesis.of_uint32
2389

2390
    let mk_now_invalid_test t _cmds ~mk_command =
UNCOV
2391
      let cmd1 =
×
2392
        mk_command ~sender_idx:0 ~receiver_idx:5 ~fee:minimum_fee ~nonce:0
2393
          ~amount:99_999_999_999 ()
2394
      in
UNCOV
2395
      let cmd2 =
×
2396
        mk_command ~sender_idx:0 ~receiver_idx:5 ~fee:minimum_fee ~nonce:0
2397
          ~amount:999_000_000_000 ()
2398
      in
UNCOV
2399
      assert_pool_txs t [] ;
×
UNCOV
2400
      let%bind () = add_commands' t [ cmd1 ] in
×
UNCOV
2401
      assert_pool_txs t [ cmd1 ] ;
×
UNCOV
2402
      let%bind () = advance_chain t [ cmd2 ] in
×
UNCOV
2403
      assert_pool_txs t [] ; Deferred.unit
×
2404

2405
    let%test_unit "Now-invalid transactions are removed from the pool on fork \
2406
                   changes (user cmds)" =
UNCOV
2407
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
2408
          let%bind test = setup_test () in
×
UNCOV
2409
          mk_now_invalid_test test independent_cmds
×
2410
            ~mk_command:(mk_payment ?valid_until:None) )
2411

2412
    let%test_unit "Now-invalid transactions are removed from the pool on fork \
2413
                   changes (zkapps)" =
UNCOV
2414
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
2415
          let%bind test = setup_test () in
×
UNCOV
2416
          mk_zkapp_commands_single_block 7 test.txn_pool
×
UNCOV
2417
          >>= mk_now_invalid_test test
×
2418
                ~mk_command:
2419
                  (mk_transfer_zkapp_command ?valid_period:None
2420
                     ?fee_payer_idx:None ) )
2421

2422
    let mk_expired_not_accepted_test t ~padding cmds =
UNCOV
2423
      assert_pool_txs t [] ;
×
2424
      let%bind () =
2425
        let current_time = Block_time.now time_controller in
UNCOV
2426
        let slot_end =
×
2427
          Consensus.Data.Consensus_time.(
UNCOV
2428
            of_time_exn ~constants:consensus_constants current_time
×
UNCOV
2429
            |> end_time ~constants:consensus_constants)
×
2430
        in
UNCOV
2431
        at (Block_time.to_time_exn slot_end)
×
2432
      in
UNCOV
2433
      let curr_slot = current_global_slot () in
×
UNCOV
2434
      let slot_padding = Mina_numbers.Global_slot_span.of_int padding in
×
UNCOV
2435
      let curr_slot_plus_padding =
×
2436
        Mina_numbers.Global_slot_since_genesis.add curr_slot slot_padding
2437
      in
UNCOV
2438
      let valid_command =
×
2439
        mk_payment ~valid_until:curr_slot_plus_padding ~sender_idx:1
2440
          ~fee:minimum_fee ~nonce:1 ~receiver_idx:7 ~amount:1_000_000_000 ()
2441
      in
UNCOV
2442
      let expired_commands =
×
UNCOV
2443
        [ mk_payment ~valid_until:curr_slot ~sender_idx:0 ~fee:minimum_fee
×
2444
            ~nonce:1 ~receiver_idx:9 ~amount:1_000_000_000 ()
UNCOV
2445
        ; mk_payment ~sender_idx:0 ~fee:minimum_fee ~nonce:2 ~receiver_idx:9
×
2446
            ~amount:1_000_000_000 ()
2447
        ]
2448
      in
2449
      (* Wait till global slot increases by 1 which invalidates
2450
         the commands with valid_until = curr_slot
2451
      *)
2452
      let%bind () =
UNCOV
2453
        after
×
UNCOV
2454
          (Block_time.Span.to_time_span
×
2455
             consensus_constants.block_window_duration_ms )
2456
      in
UNCOV
2457
      let all_valid_commands = cmds @ [ valid_command ] in
×
2458
      let%bind () =
UNCOV
2459
        add_commands t (all_valid_commands @ expired_commands)
×
UNCOV
2460
        >>| assert_pool_apply all_valid_commands
×
2461
      in
UNCOV
2462
      assert_pool_txs t all_valid_commands ;
×
UNCOV
2463
      Deferred.unit
×
2464

2465
    let%test_unit "expired transactions are not accepted (user cmds)" =
UNCOV
2466
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
2467
          let%bind test = setup_test () in
×
UNCOV
2468
          mk_expired_not_accepted_test test ~padding:10 independent_cmds )
×
2469

2470
    let%test_unit "expired transactions are not accepted (zkapps)" =
UNCOV
2471
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
2472
          let%bind test = setup_test () in
×
UNCOV
2473
          mk_zkapp_commands_single_block 7 test.txn_pool
×
UNCOV
2474
          >>= mk_expired_not_accepted_test test ~padding:55 )
×
2475

2476
    let%test_unit "Expired transactions that are already in the pool are \
2477
                   removed from the pool when best tip changes (user commands)"
2478
        =
UNCOV
2479
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
2480
          let%bind t = setup_test () in
×
UNCOV
2481
          assert_pool_txs t [] ;
×
UNCOV
2482
          let curr_slot = current_global_slot () in
×
UNCOV
2483
          let curr_slot_plus_three =
×
2484
            Mina_numbers.Global_slot_since_genesis.add curr_slot
UNCOV
2485
              (Mina_numbers.Global_slot_span.of_int 3)
×
2486
          in
UNCOV
2487
          let curr_slot_plus_seven =
×
2488
            Mina_numbers.Global_slot_since_genesis.add curr_slot
UNCOV
2489
              (Mina_numbers.Global_slot_span.of_int 7)
×
2490
          in
UNCOV
2491
          let few_now =
×
UNCOV
2492
            List.take independent_cmds (List.length independent_cmds / 2)
×
2493
          in
UNCOV
2494
          let expires_later1 =
×
2495
            mk_payment ~valid_until:curr_slot_plus_three ~sender_idx:0
2496
              ~fee:minimum_fee ~nonce:1 ~receiver_idx:9 ~amount:10_000_000_000
2497
              ()
2498
          in
UNCOV
2499
          let expires_later2 =
×
2500
            mk_payment ~valid_until:curr_slot_plus_seven ~sender_idx:0
2501
              ~fee:minimum_fee ~nonce:2 ~receiver_idx:9 ~amount:10_000_000_000
2502
              ()
2503
          in
UNCOV
2504
          let valid_commands = few_now @ [ expires_later1; expires_later2 ] in
×
UNCOV
2505
          let%bind () = add_commands' t valid_commands in
×
UNCOV
2506
          assert_pool_txs t valid_commands ;
×
2507
          (* new commands from best tip diff should be removed from the pool *)
2508
          (* update the nonce to be consistent with the commands in the block *)
UNCOV
2509
          modify_ledger !(t.best_tip_ref) ~idx:0 ~balance:1_000_000_000_000_000
×
2510
            ~nonce:2 ;
UNCOV
2511
          let%bind () = reorg t [ List.nth_exn few_now 0; expires_later1 ] [] in
×
UNCOV
2512
          let%bind () = Async.Scheduler.yield_until_no_jobs_remain () in
×
UNCOV
2513
          assert_pool_txs t (expires_later2 :: List.drop few_now 1) ;
×
2514
          (* Add new commands, remove old commands some of which are now expired *)
UNCOV
2515
          let expired_command =
×
2516
            mk_payment ~valid_until:curr_slot ~sender_idx:9 ~fee:minimum_fee
2517
              ~nonce:0 ~receiver_idx:5 ~amount:1_000_000_000 ()
2518
          in
UNCOV
2519
          let unexpired_command =
×
2520
            mk_payment ~valid_until:curr_slot_plus_seven ~sender_idx:8
2521
              ~fee:minimum_fee ~nonce:0 ~receiver_idx:9 ~amount:1_000_000_000 ()
2522
          in
UNCOV
2523
          let valid_forever = List.nth_exn few_now 0 in
×
UNCOV
2524
          let removed_commands =
×
2525
            [ valid_forever
2526
            ; expires_later1
2527
            ; expired_command
2528
            ; unexpired_command
2529
            ]
2530
          in
2531
          let n_block_times n =
UNCOV
2532
            Int64.(
×
UNCOV
2533
              Block_time.Span.to_ms consensus_constants.block_window_duration_ms
×
2534
              * n)
2535
            |> Block_time.Span.of_ms
2536
          in
2537
          let%bind () =
UNCOV
2538
            after (Block_time.Span.to_time_span (n_block_times 3L))
×
2539
          in
UNCOV
2540
          modify_ledger !(t.best_tip_ref) ~idx:0 ~balance:1_000_000_000_000_000
×
2541
            ~nonce:1 ;
UNCOV
2542
          let%bind _ = reorg t [ valid_forever ] removed_commands in
×
2543
          (* expired_command should not be in the pool because they are expired
2544
             and (List.nth few_now 0) because it was committed in a block
2545
          *)
UNCOV
2546
          assert_pool_txs t
×
2547
            ( expires_later1 :: expires_later2 :: unexpired_command
UNCOV
2548
            :: List.drop few_now 1 ) ;
×
2549
          (* after 5 block times there should be no expired transactions *)
2550
          let%bind () =
UNCOV
2551
            after (Block_time.Span.to_time_span (n_block_times 5L))
×
2552
          in
UNCOV
2553
          let%bind _ = reorg t [] [] in
×
UNCOV
2554
          assert_pool_txs t (List.drop few_now 1) ;
×
UNCOV
2555
          Deferred.unit )
×
2556

2557
    let%test_unit "Expired transactions that are already in the pool are \
2558
                   removed from the pool when best tip changes (zkapps)" =
UNCOV
2559
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
2560
          let%bind t = setup_test () in
×
UNCOV
2561
          assert_pool_txs t [] ;
×
UNCOV
2562
          let curr_slot = current_global_slot () in
×
UNCOV
2563
          let curr_slot_plus_three =
×
2564
            Mina_numbers.Global_slot_since_genesis.add curr_slot
UNCOV
2565
              (Mina_numbers.Global_slot_span.of_int 3)
×
2566
          in
UNCOV
2567
          let curr_slot_plus_seven =
×
2568
            Mina_numbers.Global_slot_since_genesis.add curr_slot
UNCOV
2569
              (Mina_numbers.Global_slot_span.of_int 7)
×
2570
          in
UNCOV
2571
          let few_now =
×
UNCOV
2572
            List.take independent_cmds (List.length independent_cmds / 2)
×
2573
          in
UNCOV
2574
          let expires_later1 =
×
2575
            mk_transfer_zkapp_command
2576
              ~valid_period:{ lower = curr_slot; upper = curr_slot_plus_three }
2577
              ~fee_payer_idx:(0, 1) ~sender_idx:1 ~receiver_idx:9
2578
              ~fee:minimum_fee ~amount:10_000_000_000 ~nonce:1 ()
2579
          in
UNCOV
2580
          let expires_later2 =
×
2581
            mk_transfer_zkapp_command
2582
              ~valid_period:{ lower = curr_slot; upper = curr_slot_plus_seven }
2583
              ~fee_payer_idx:(2, 1) ~sender_idx:3 ~receiver_idx:9
2584
              ~fee:minimum_fee ~amount:10_000_000_000 ~nonce:1 ()
2585
          in
UNCOV
2586
          let valid_commands = few_now @ [ expires_later1; expires_later2 ] in
×
UNCOV
2587
          let%bind () = add_commands' t valid_commands in
×
UNCOV
2588
          assert_pool_txs t valid_commands ;
×
UNCOV
2589
          let n_block_times n =
×
UNCOV
2590
            Int64.(
×
UNCOV
2591
              Block_time.Span.to_ms consensus_constants.block_window_duration_ms
×
2592
              * n)
2593
            |> Block_time.Span.of_ms
2594
          in
2595
          let%bind () =
UNCOV
2596
            after (Block_time.Span.to_time_span (n_block_times 4L))
×
2597
          in
UNCOV
2598
          let%bind () = reorg t [] [] in
×
UNCOV
2599
          assert_pool_txs t (expires_later2 :: few_now) ;
×
2600
          (* after 5 block times there should be no expired transactions *)
2601
          let%bind () =
UNCOV
2602
            after (Block_time.Span.to_time_span (n_block_times 5L))
×
2603
          in
UNCOV
2604
          let%bind () = reorg t [] [] in
×
UNCOV
2605
          assert_pool_txs t few_now ; Deferred.unit )
×
2606

2607
    let%test_unit "Now-invalid transactions are removed from the pool when the \
2608
                   transition frontier is recreated (user cmds)" =
UNCOV
2609
      Thread_safe.block_on_async_exn (fun () ->
×
2610
          (* Set up initial frontier *)
UNCOV
2611
          let%bind t = setup_test () in
×
UNCOV
2612
          assert_pool_txs t [] ;
×
UNCOV
2613
          let%bind _ = add_commands t independent_cmds in
×
UNCOV
2614
          assert_pool_txs t independent_cmds ;
×
2615
          (* Destroy initial frontier *)
UNCOV
2616
          Broadcast_pipe.Writer.close t.best_tip_diff_w ;
×
UNCOV
2617
          let%bind _ = Broadcast_pipe.Writer.write t.frontier_pipe_w None in
×
2618
          (* Set up second frontier *)
UNCOV
2619
          let ((_, ledger_ref2) as frontier2), _best_tip_diff_w2 =
×
2620
            Mock_transition_frontier.create ()
2621
          in
UNCOV
2622
          modify_ledger !ledger_ref2 ~idx:0 ~balance:20_000_000_000_000 ~nonce:5 ;
×
UNCOV
2623
          modify_ledger !ledger_ref2 ~idx:1 ~balance:0 ~nonce:0 ;
×
UNCOV
2624
          modify_ledger !ledger_ref2 ~idx:2 ~balance:0 ~nonce:1 ;
×
2625
          let%bind _ =
UNCOV
2626
            Broadcast_pipe.Writer.write t.frontier_pipe_w (Some frontier2)
×
2627
          in
UNCOV
2628
          assert_pool_txs t (List.drop independent_cmds 3) ;
×
UNCOV
2629
          Deferred.unit )
×
2630

2631
    let%test_unit "transaction replacement works" =
UNCOV
2632
      Thread_safe.block_on_async_exn
×
2633
      @@ fun () ->
UNCOV
2634
      let%bind t = setup_test () in
×
UNCOV
2635
      let set_sender idx (tx : Signed_command.t) =
×
UNCOV
2636
        let sender_kp = test_keys.(idx) in
×
UNCOV
2637
        let sender_pk = Public_key.compress sender_kp.public_key in
×
UNCOV
2638
        let payload : Signed_command.Payload.t =
×
2639
          match tx.payload with
UNCOV
2640
          | { common; body = Payment payload } ->
×
2641
              { common = { common with fee_payer_pk = sender_pk }
2642
              ; body = Payment payload
2643
              }
2644
          | { common; body = Stake_delegation (Set_delegate payload) } ->
×
2645
              { common = { common with fee_payer_pk = sender_pk }
2646
              ; body = Stake_delegation (Set_delegate payload)
2647
              }
2648
        in
UNCOV
2649
        User_command.Signed_command (Signed_command.sign sender_kp payload)
×
2650
      in
2651
      let txs0 =
UNCOV
2652
        [ mk_payment' ~sender_idx:0 ~fee:minimum_fee ~nonce:0 ~receiver_idx:9
×
2653
            ~amount:20_000_000_000 ()
UNCOV
2654
        ; mk_payment' ~sender_idx:0 ~fee:minimum_fee ~nonce:1 ~receiver_idx:9
×
2655
            ~amount:12_000_000_000 ()
UNCOV
2656
        ; mk_payment' ~sender_idx:0 ~fee:minimum_fee ~nonce:2 ~receiver_idx:9
×
2657
            ~amount:500_000_000_000 ()
2658
        ]
2659
      in
2660
      let txs0' = List.map txs0 ~f:Signed_command.forget_check in
UNCOV
2661
      let txs1 = List.map ~f:(set_sender 1) txs0' in
×
UNCOV
2662
      let txs2 = List.map ~f:(set_sender 2) txs0' in
×
UNCOV
2663
      let txs3 = List.map ~f:(set_sender 3) txs0' in
×
UNCOV
2664
      let txs_all =
×
UNCOV
2665
        List.map ~f:(fun x -> User_command.Signed_command x) txs0
×
2666
        @ txs1 @ txs2 @ txs3
2667
      in
UNCOV
2668
      let%bind () = add_commands' t txs_all in
×
UNCOV
2669
      assert_pool_txs t txs_all ;
×
UNCOV
2670
      let replace_txs =
×
2671
        [ (* sufficient fee *)
UNCOV
2672
          mk_payment ~sender_idx:0
×
2673
            ~fee:
2674
              ( minimum_fee
UNCOV
2675
              + Currency.Fee.to_nanomina_int Indexed_pool.replace_fee )
×
2676
            ~nonce:0 ~receiver_idx:1 ~amount:440_000_000_000 ()
2677
        ; (* insufficient fee *)
UNCOV
2678
          mk_payment ~sender_idx:1 ~fee:minimum_fee ~nonce:0 ~receiver_idx:1
×
2679
            ~amount:788_000_000_000 ()
2680
        ; (* sufficient *)
UNCOV
2681
          mk_payment ~sender_idx:2
×
2682
            ~fee:
2683
              ( minimum_fee
UNCOV
2684
              + Currency.Fee.to_nanomina_int Indexed_pool.replace_fee )
×
2685
            ~nonce:1 ~receiver_idx:4 ~amount:721_000_000_000 ()
2686
        ; (* insufficient *)
2687
          (let amount = 927_000_000_000 in
2688
           let fee =
2689
             let ledger = !(t.best_tip_ref) in
2690
             let sender_kp = test_keys.(3) in
UNCOV
2691
             let sender_pk = Public_key.compress sender_kp.public_key in
×
UNCOV
2692
             let sender_aid = Account_id.create sender_pk Token_id.default in
×
UNCOV
2693
             let location =
×
UNCOV
2694
               Mock_base_ledger.location_of_account ledger sender_aid
×
2695
               |> Option.value_exn
2696
             in
2697
             (* Spend all of the tokens in the account. Should fail because the
2698
                command with nonce=0 will already have spent some.
2699
             *)
UNCOV
2700
             let account =
×
UNCOV
2701
               Mock_base_ledger.get ledger location |> Option.value_exn
×
2702
             in
UNCOV
2703
             Currency.Balance.to_nanomina_int account.balance - amount
×
2704
           in
UNCOV
2705
           mk_payment ~sender_idx:3 ~fee ~nonce:1 ~receiver_idx:4 ~amount () )
×
2706
        ]
2707
      in
UNCOV
2708
      add_commands t replace_txs
×
UNCOV
2709
      >>| assert_pool_apply
×
UNCOV
2710
            [ List.nth_exn replace_txs 0; List.nth_exn replace_txs 2 ]
×
2711

2712
    let%test_unit "it drops queued transactions if a committed one makes there \
2713
                   be insufficient funds" =
UNCOV
2714
      Thread_safe.block_on_async_exn
×
2715
      @@ fun () ->
UNCOV
2716
      let%bind t = setup_test () in
×
UNCOV
2717
      let txs =
×
UNCOV
2718
        [ mk_payment ~sender_idx:0 ~fee:minimum_fee ~nonce:0 ~receiver_idx:9
×
2719
            ~amount:20_000_000_000 ()
UNCOV
2720
        ; mk_payment ~sender_idx:0 ~fee:minimum_fee ~nonce:1 ~receiver_idx:5
×
2721
            ~amount:77_000_000_000 ()
UNCOV
2722
        ; mk_payment ~sender_idx:0 ~fee:minimum_fee ~nonce:2 ~receiver_idx:3
×
2723
            ~amount:891_000_000_000 ()
2724
        ]
2725
      in
2726
      let committed_tx =
2727
        mk_payment ~sender_idx:0 ~fee:minimum_fee ~nonce:0 ~receiver_idx:2
2728
          ~amount:25_000_000_000 ()
2729
      in
UNCOV
2730
      let%bind () = add_commands' t txs in
×
UNCOV
2731
      assert_pool_txs t txs ;
×
UNCOV
2732
      modify_ledger !(t.best_tip_ref) ~idx:0 ~balance:970_000_000_000 ~nonce:1 ;
×
UNCOV
2733
      let%bind () = reorg t [ committed_tx ] [] in
×
UNCOV
2734
      assert_pool_txs t [ List.nth_exn txs 1 ] ;
×
UNCOV
2735
      Deferred.unit
×
2736

2737
    let%test_unit "max size is maintained" =
UNCOV
2738
      Quickcheck.test ~trials:500
×
2739
        (let open Quickcheck.Generator.Let_syntax in
2740
        let%bind init_ledger_state =
2741
          Mina_ledger.Ledger.gen_initial_ledger_state
2742
        in
UNCOV
2743
        let%bind cmds_count = Int.gen_incl pool_max_size (pool_max_size * 2) in
×
2744
        let%bind cmds =
UNCOV
2745
          User_command.Valid.Gen.sequence ~sign_type:`Real ~length:cmds_count
×
2746
            init_ledger_state
2747
        in
UNCOV
2748
        return (init_ledger_state, cmds))
×
2749
        ~f:(fun (init_ledger_state, cmds) ->
UNCOV
2750
          Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
2751
              let%bind t = setup_test () in
×
UNCOV
2752
              let new_ledger =
×
2753
                Mina_ledger.Ledger.create_ephemeral
UNCOV
2754
                  ~depth:(Mina_ledger.Ledger.depth !(t.best_tip_ref))
×
2755
                  ()
2756
              in
UNCOV
2757
              Mina_ledger.Ledger.apply_initial_ledger_state new_ledger
×
2758
                init_ledger_state ;
UNCOV
2759
              t.best_tip_ref := new_ledger ;
×
UNCOV
2760
              let%bind () = reorg ~reorg_best_tip:true t [] [] in
×
UNCOV
2761
              let cmds1, cmds2 = List.split_n cmds pool_max_size in
×
UNCOV
2762
              let%bind apply_res1 = add_commands t cmds1 in
×
UNCOV
2763
              assert (Result.is_ok apply_res1) ;
×
UNCOV
2764
              [%test_eq: int] pool_max_size (Indexed_pool.size t.txn_pool.pool) ;
×
UNCOV
2765
              let%map _apply_res2 = add_commands t cmds2 in
×
2766
              (* N.B. Adding a transaction when the pool is full may drop > 1
2767
                 command, so the size now is not necessarily the maximum.
2768
                 Applying the diff may also return an error if none of the new
2769
                 commands have higher fee than the lowest one already in the
2770
                 pool.
2771
              *)
UNCOV
2772
              assert (Indexed_pool.size t.txn_pool.pool <= pool_max_size) ) )
×
2773

2774
    let assert_rebroadcastable test cmds =
UNCOV
2775
      let expected =
×
UNCOV
2776
        if List.is_empty cmds then []
×
2777
        else
UNCOV
2778
          [ List.map cmds
×
2779
              ~f:
UNCOV
2780
                (Fn.compose Transaction_hash.User_command.create
×
2781
                   User_command.forget_check )
2782
          ]
2783
      in
2784
      let actual =
UNCOV
2785
        Test.Resource_pool.get_rebroadcastable test.txn_pool
×
UNCOV
2786
          ~has_timed_out:(Fn.const `Ok)
×
2787
        |> List.map ~f:(List.map ~f:Transaction_hash.User_command.create)
2788
      in
UNCOV
2789
      if List.length actual > 1 then
×
2790
        failwith "unexpected number of rebroadcastable diffs" ;
×
2791

UNCOV
2792
      List.iter (List.zip_exn actual expected) ~f:(fun (a, b) ->
×
UNCOV
2793
          assert_user_command_sets_equal a b )
×
2794

2795
    let mk_rebroadcastable_test t cmds =
UNCOV
2796
      assert_pool_txs t [] ;
×
UNCOV
2797
      assert_rebroadcastable t [] ;
×
2798
      (* Locally generated transactions are rebroadcastable *)
UNCOV
2799
      let%bind () = add_commands' ~local:true t (List.take cmds 2) in
×
UNCOV
2800
      assert_pool_txs t (List.take cmds 2) ;
×
UNCOV
2801
      assert_rebroadcastable t (List.take cmds 2) ;
×
2802
      (* Adding non-locally-generated transactions doesn't affect
2803
         rebroadcastable pool *)
UNCOV
2804
      let%bind () = add_commands' ~local:false t (List.slice cmds 2 5) in
×
UNCOV
2805
      assert_pool_txs t (List.take cmds 5) ;
×
UNCOV
2806
      assert_rebroadcastable t (List.take cmds 2) ;
×
2807
      (* When locally generated transactions are committed they are no
2808
         longer rebroadcastable *)
UNCOV
2809
      let%bind () = add_commands' ~local:true t (List.slice cmds 5 7) in
×
UNCOV
2810
      let%bind checkpoint_1 = commit_commands' t (List.take cmds 1) in
×
UNCOV
2811
      let%bind checkpoint_2 = commit_commands' t (List.slice cmds 1 5) in
×
UNCOV
2812
      let%bind () = reorg t (List.take cmds 5) [] in
×
UNCOV
2813
      assert_pool_txs t (List.slice cmds 5 7) ;
×
UNCOV
2814
      assert_rebroadcastable t (List.slice cmds 5 7) ;
×
2815
      (* Reorgs put locally generated transactions back into the
2816
         rebroadcastable pool, if they were removed and not re-added *)
2817
      (* restore up to after the application of the first command *)
UNCOV
2818
      t.best_tip_ref := checkpoint_2 ;
×
2819
      (* reorg both removes and re-adds the first command (which is local) *)
UNCOV
2820
      let%bind () = reorg t (List.take cmds 1) (List.take cmds 5) in
×
UNCOV
2821
      assert_pool_txs t (List.slice cmds 1 7) ;
×
UNCOV
2822
      assert_rebroadcastable t (List.nth_exn cmds 1 :: List.slice cmds 5 7) ;
×
2823
      (* Committing them again removes them from the pool again. *)
UNCOV
2824
      commit_commands t (List.slice cmds 1 5) ;
×
UNCOV
2825
      let%bind () = reorg t (List.slice cmds 1 5) [] in
×
UNCOV
2826
      assert_pool_txs t (List.slice cmds 5 7) ;
×
UNCOV
2827
      assert_rebroadcastable t (List.slice cmds 5 7) ;
×
2828
      (* When transactions expire from rebroadcast pool they are gone. This
2829
         doesn't affect the main pool.
2830
      *)
UNCOV
2831
      t.best_tip_ref := checkpoint_1 ;
×
UNCOV
2832
      let%bind () = reorg t [] (List.take cmds 5) in
×
UNCOV
2833
      assert_pool_txs t (List.take cmds 7) ;
×
UNCOV
2834
      assert_rebroadcastable t (List.take cmds 2 @ List.slice cmds 5 7) ;
×
UNCOV
2835
      ignore
×
UNCOV
2836
        ( Test.Resource_pool.get_rebroadcastable t.txn_pool
×
UNCOV
2837
            ~has_timed_out:(Fn.const `Timed_out)
×
2838
          : User_command.t list list ) ;
2839
      assert_rebroadcastable t [] ;
UNCOV
2840
      Deferred.unit
×
2841

2842
    let%test_unit "rebroadcastable transaction behavior (user cmds)" =
UNCOV
2843
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
2844
          let%bind test = setup_test () in
×
UNCOV
2845
          mk_rebroadcastable_test test independent_cmds )
×
2846

2847
    let%test_unit "rebroadcastable transaction behavior (zkapps)" =
UNCOV
2848
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
2849
          let%bind test = setup_test () in
×
UNCOV
2850
          mk_zkapp_commands_single_block 7 test.txn_pool
×
UNCOV
2851
          >>= mk_rebroadcastable_test test )
×
2852

2853
    let%test_unit "apply user cmds and zkapps" =
UNCOV
2854
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
2855
          let%bind t = setup_test () in
×
UNCOV
2856
          let num_cmds = Array.length test_keys in
×
2857
          (* the user cmds and snapp cmds are taken from the same list of keys,
2858
             so splitting by the order from that list makes sure that they
2859
             don't share fee payer keys
2860
             therefore, the original nonces in the accounts are valid
2861
          *)
UNCOV
2862
          let take_len = num_cmds / 2 in
×
2863
          let%bind snapp_cmds =
UNCOV
2864
            let%map cmds = mk_zkapp_commands_single_block 7 t.txn_pool in
×
UNCOV
2865
            List.take cmds take_len
×
2866
          in
UNCOV
2867
          let user_cmds = List.drop independent_cmds take_len in
×
UNCOV
2868
          let all_cmds = snapp_cmds @ user_cmds in
×
2869
          assert_pool_txs t [] ;
UNCOV
2870
          let%bind () = add_commands' t all_cmds in
×
UNCOV
2871
          assert_pool_txs t all_cmds ; Deferred.unit )
×
2872

2873
    let mk_zkapp_user_cmd (pool : Test.Resource_pool.t) zkapp_command =
UNCOV
2874
      let best_tip_ledger = Option.value_exn pool.best_tip_ledger in
×
UNCOV
2875
      let keymap =
×
UNCOV
2876
        Array.fold (Array.append test_keys extra_keys)
×
2877
          ~init:Public_key.Compressed.Map.empty
2878
          ~f:(fun map { public_key; private_key } ->
UNCOV
2879
            let key = Public_key.compress public_key in
×
UNCOV
2880
            Public_key.Compressed.Map.add_exn map ~key ~data:private_key )
×
2881
      in
UNCOV
2882
      let zkapp_command =
×
2883
        Or_error.ok_exn
UNCOV
2884
          (Zkapp_command.Valid.to_valid ~failed:false
×
2885
             ~find_vk:
2886
               (Zkapp_command.Verifiable.load_vk_from_ledger
UNCOV
2887
                  ~get:(Mina_ledger.Ledger.get best_tip_ledger)
×
2888
                  ~location_of_account:
UNCOV
2889
                    (Mina_ledger.Ledger.location_of_account best_tip_ledger) )
×
2890
             zkapp_command )
2891
      in
UNCOV
2892
      let zkapp_command = User_command.Zkapp_command zkapp_command in
×
2893
      let%bind zkapp_command =
UNCOV
2894
        replace_valid_zkapp_command_authorizations ~keymap
×
2895
          ~ledger:best_tip_ledger [ zkapp_command ]
2896
      in
UNCOV
2897
      let zkapp_command = List.hd_exn zkapp_command in
×
UNCOV
2898
      Deferred.return zkapp_command
×
2899

UNCOV
2900
    let mk_basic_zkapp ?(fee = 10_000_000_000) ?(empty_update = false)
×
2901
        ?preconditions ?permissions nonce fee_payer_kp =
UNCOV
2902
      let open Zkapp_command_builder in
×
2903
      let preconditions =
2904
        Option.value preconditions
2905
          ~default:
2906
            Account_update.Preconditions.
2907
              { network = Zkapp_precondition.Protocol_state.accept
2908
              ; account = Zkapp_precondition.Account.accept
2909
              ; valid_while = Ignore
2910
              }
2911
      in
UNCOV
2912
      let update : Account_update.Update.t =
×
2913
        let permissions =
2914
          match permissions with
UNCOV
2915
          | None ->
×
2916
              Zkapp_basic.Set_or_keep.Keep
UNCOV
2917
          | Some perms ->
×
2918
              Zkapp_basic.Set_or_keep.Set perms
2919
        in
2920
        { Account_update.Update.noop with permissions }
2921
      in
2922
      let account_updates =
UNCOV
2923
        if empty_update then []
×
2924
        else
UNCOV
2925
          mk_forest
×
UNCOV
2926
            [ mk_node
×
UNCOV
2927
                (mk_account_update_body Signature No fee_payer_kp
×
2928
                   Token_id.default 0 ~preconditions ~update )
2929
                []
2930
            ]
2931
      in
2932
      account_updates
2933
      |> mk_zkapp_command ~memo:"" ~fee
UNCOV
2934
           ~fee_payer_pk:(Public_key.compress fee_payer_kp.public_key)
×
UNCOV
2935
           ~fee_payer_nonce:(Account.Nonce.of_int nonce)
×
2936

2937
    let%test_unit "zkapp cmd with same nonce should replace previous submitted \
2938
                   zkapp with same nonce" =
UNCOV
2939
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
2940
          let%bind () = after (Time.Span.of_sec 2.) in
×
UNCOV
2941
          let%bind t = setup_test () in
×
UNCOV
2942
          assert_pool_txs t [] ;
×
UNCOV
2943
          let fee_payer_kp = test_keys.(0) in
×
2944
          let%bind valid_command1 =
UNCOV
2945
            mk_basic_zkapp ~fee:10_000_000_000 0 fee_payer_kp
×
UNCOV
2946
            |> mk_zkapp_user_cmd t.txn_pool
×
2947
          in
2948
          let%bind valid_command2 =
UNCOV
2949
            mk_basic_zkapp ~fee:20_000_000_000 ~empty_update:true 0 fee_payer_kp
×
UNCOV
2950
            |> mk_zkapp_user_cmd t.txn_pool
×
2951
          in
2952
          let%bind () =
UNCOV
2953
            add_commands t ([ valid_command1 ] @ [ valid_command2 ])
×
UNCOV
2954
            >>| assert_pool_apply [ valid_command2 ]
×
2955
          in
UNCOV
2956
          Deferred.unit )
×
2957

2958
    let%test_unit "commands are rejected if fee payer permissions are not \
2959
                   handled" =
UNCOV
2960
      let test_permissions ~is_able_to_send send_command permissions =
×
UNCOV
2961
        let%bind t = setup_test () in
×
UNCOV
2962
        assert_pool_txs t [] ;
×
2963
        let%bind set_permissions_command =
UNCOV
2964
          mk_basic_zkapp 0 test_keys.(0) ~permissions
×
UNCOV
2965
          |> mk_zkapp_user_cmd t.txn_pool
×
2966
        in
UNCOV
2967
        let%bind () = add_commands' t [ set_permissions_command ] in
×
UNCOV
2968
        let%bind () = advance_chain t [ set_permissions_command ] in
×
UNCOV
2969
        assert_pool_txs t [] ;
×
UNCOV
2970
        let%map result = add_commands t [ send_command ] in
×
UNCOV
2971
        let expectation = if is_able_to_send then [ send_command ] else [] in
×
2972
        assert_pool_apply expectation result
2973
      in
2974
      let run_test_cases send_cmd =
2975
        let%bind () =
UNCOV
2976
          test_permissions ~is_able_to_send:true send_cmd
×
2977
            { Permissions.user_default with
2978
              send = Permissions.Auth_required.Signature
2979
            }
2980
        in
2981
        let%bind () =
UNCOV
2982
          test_permissions ~is_able_to_send:true send_cmd
×
2983
            { Permissions.user_default with
2984
              send = Permissions.Auth_required.Either
2985
            }
2986
        in
2987
        let%bind () =
UNCOV
2988
          test_permissions ~is_able_to_send:true send_cmd
×
2989
            { Permissions.user_default with
2990
              send = Permissions.Auth_required.None
2991
            }
2992
        in
2993
        let%bind () =
UNCOV
2994
          test_permissions ~is_able_to_send:false send_cmd
×
2995
            { Permissions.user_default with
2996
              send = Permissions.Auth_required.Impossible
2997
            }
2998
        in
2999
        let%bind () =
UNCOV
3000
          test_permissions ~is_able_to_send:false send_cmd
×
3001
            { Permissions.user_default with
3002
              send = Permissions.Auth_required.Proof
3003
            }
3004
        in
3005
        let%bind () =
UNCOV
3006
          test_permissions ~is_able_to_send:true send_cmd
×
3007
            { Permissions.user_default with
3008
              increment_nonce = Permissions.Auth_required.Signature
3009
            }
3010
        in
3011
        let%bind () =
UNCOV
3012
          test_permissions ~is_able_to_send:true send_cmd
×
3013
            { Permissions.user_default with
3014
              increment_nonce = Permissions.Auth_required.Either
3015
            }
3016
        in
3017
        let%bind () =
UNCOV
3018
          test_permissions ~is_able_to_send:true send_cmd
×
3019
            { Permissions.user_default with
3020
              increment_nonce = Permissions.Auth_required.None
3021
            }
3022
        in
3023
        let%bind () =
UNCOV
3024
          test_permissions ~is_able_to_send:false send_cmd
×
3025
            { Permissions.user_default with
3026
              increment_nonce = Permissions.Auth_required.Impossible
3027
            }
3028
        in
3029
        let%bind () =
UNCOV
3030
          test_permissions ~is_able_to_send:false send_cmd
×
3031
            { Permissions.user_default with
3032
              increment_nonce = Permissions.Auth_required.Proof
3033
            }
3034
        in
3035
        let%bind () =
UNCOV
3036
          test_permissions ~is_able_to_send:true send_cmd
×
3037
            { Permissions.user_default with
3038
              access = Permissions.Auth_required.Signature
3039
            }
3040
        in
3041
        let%bind () =
UNCOV
3042
          test_permissions ~is_able_to_send:true send_cmd
×
3043
            { Permissions.user_default with
3044
              access = Permissions.Auth_required.Either
3045
            }
3046
        in
3047
        let%bind () =
UNCOV
3048
          test_permissions ~is_able_to_send:true send_cmd
×
3049
            { Permissions.user_default with
3050
              access = Permissions.Auth_required.None
3051
            }
3052
        in
3053
        let%bind () =
UNCOV
3054
          test_permissions ~is_able_to_send:false send_cmd
×
3055
            { Permissions.user_default with
3056
              access = Permissions.Auth_required.Impossible
3057
            }
3058
        in
3059
        let%bind () =
UNCOV
3060
          test_permissions ~is_able_to_send:false send_cmd
×
3061
            { Permissions.user_default with
3062
              access = Permissions.Auth_required.Proof
3063
            }
3064
        in
UNCOV
3065
        return ()
×
3066
      in
3067
      Thread_safe.block_on_async_exn (fun () ->
3068
          let%bind () =
3069
            let send_command =
3070
              mk_payment ~sender_idx:0 ~fee:minimum_fee ~nonce:1 ~receiver_idx:1
3071
                ~amount:1_000_000 ()
3072
            in
UNCOV
3073
            run_test_cases send_command
×
3074
          in
3075
          let%bind () =
3076
            let send_command =
3077
              mk_transfer_zkapp_command ~fee_payer_idx:(0, 1) ~sender_idx:0
3078
                ~fee:minimum_fee ~nonce:2 ~receiver_idx:1 ~amount:1_000_000 ()
3079
            in
UNCOV
3080
            run_test_cases send_command
×
3081
          in
UNCOV
3082
          return () )
×
3083

3084
    let%test "account update with a different network id that uses proof \
3085
              authorization would be rejected" =
UNCOV
3086
      Thread_safe.block_on_async_exn (fun () ->
×
3087
          let%bind verifier_full =
UNCOV
3088
            Verifier.For_tests.default ~constraint_constants ~logger
×
3089
              ~proof_level:Full ()
3090
          in
3091
          let%bind test =
UNCOV
3092
            setup_test ~verifier:verifier_full
×
3093
              ~permissions:
3094
                { Permissions.user_default with set_zkapp_uri = Proof }
3095
              ()
3096
          in
3097
          let%bind zkapp_command =
3098
            mk_single_account_update
3099
              ~chain:Mina_signature_kind.(Other_network "invalid")
3100
              ~fee_payer_idx:0 ~fee:minimum_fee ~nonce:0 ~zkapp_account_idx:1
UNCOV
3101
              ~ledger:(Option.value_exn test.txn_pool.best_tip_ledger)
×
3102
          in
3103
          match%map
UNCOV
3104
            Test.Resource_pool.Diff.verify test.txn_pool
×
3105
              (Envelope.Incoming.wrap
3106
                 ~data:
UNCOV
3107
                   [ User_command.forget_check
×
3108
                     @@ Zkapp_command
UNCOV
3109
                          (Zkapp_command.Valid.of_verifiable zkapp_command)
×
3110
                   ]
3111
                 ~sender:Envelope.Sender.Local )
3112
          with
UNCOV
3113
          | Error (Intf.Verification_error.Invalid e) ->
×
UNCOV
3114
              String.is_substring (Error.to_string_hum e) ~substring:"proof"
×
3115
          | _ ->
×
3116
              false )
3117

3118
    let%test_unit "transactions added before slot_tx_end are accepted" =
UNCOV
3119
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
3120
          let curr_slot =
×
3121
            Mina_numbers.(
UNCOV
3122
              Global_slot_since_hard_fork.of_uint32
×
UNCOV
3123
              @@ Global_slot_since_genesis.to_uint32 @@ current_global_slot ())
×
3124
          in
3125
          let slot_tx_end =
UNCOV
3126
            Mina_numbers.Global_slot_since_hard_fork.(succ @@ succ curr_slot)
×
3127
          in
UNCOV
3128
          let%bind t = setup_test ~slot_tx_end () in
×
UNCOV
3129
          assert_pool_txs t [] ;
×
UNCOV
3130
          add_commands t independent_cmds >>| assert_pool_apply independent_cmds )
×
3131

3132
    let%test_unit "transactions added at slot_tx_end are rejected" =
UNCOV
3133
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
3134
          let curr_slot =
×
3135
            Mina_numbers.(
UNCOV
3136
              Global_slot_since_hard_fork.of_uint32
×
UNCOV
3137
              @@ Global_slot_since_genesis.to_uint32 @@ current_global_slot ())
×
3138
          in
UNCOV
3139
          let%bind t = setup_test ~slot_tx_end:curr_slot () in
×
UNCOV
3140
          assert_pool_txs t [] ;
×
UNCOV
3141
          add_commands t independent_cmds >>| assert_pool_apply [] )
×
3142

3143
    let%test_unit "transactions added after slot_tx_end are rejected" =
UNCOV
3144
      Thread_safe.block_on_async_exn (fun () ->
×
UNCOV
3145
          let curr_slot =
×
3146
            Mina_numbers.(
UNCOV
3147
              Global_slot_since_hard_fork.of_uint32
×
UNCOV
3148
              @@ Global_slot_since_genesis.to_uint32 @@ current_global_slot ())
×
3149
          in
3150
          let slot_tx_end =
3151
            Option.value_exn
3152
            @@ Mina_numbers.(
3153
                 Global_slot_since_hard_fork.(
UNCOV
3154
                   sub curr_slot @@ Global_slot_span.of_int 1))
×
3155
          in
UNCOV
3156
          let%bind t = setup_test ~slot_tx_end () in
×
UNCOV
3157
          assert_pool_txs t [] ;
×
UNCOV
3158
          add_commands t independent_cmds >>| assert_pool_apply [] )
×
3159
  end )
4✔
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