Source file mempool.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
open Protocol
open Alpha_context
type nanotez = Q.t
let nanotez_enc : nanotez Data_encoding.t =
let open Data_encoding in
def
"nanotez"
~title:"A thousandth of a mutez"
~description:"One thousand nanotez make a mutez (1 tez = 1e9 nanotez)"
(conv
(fun q -> (q.Q.num, q.Q.den))
(fun (num, den) -> {Q.num; den})
(tup2 z z))
let manager_op_replacement_factor_enc : Q.t Data_encoding.t =
let open Data_encoding in
def
"manager operation replacement factor"
~title:"A manager operation's replacement factor"
~description:"The fee and fee/gas ratio of an operation to replace another"
(conv
(fun q -> (q.Q.num, q.Q.den))
(fun (num, den) -> {Q.num; den})
(tup2 z z))
type config = {
minimal_fees : Tez.t;
minimal_nanotez_per_gas_unit : nanotez;
minimal_nanotez_per_byte : nanotez;
clock_drift : Period.t option;
replace_by_fee_factor : Q.t;
(** Factor by which the fee and fee/gas ratio of an old operation in
the mempool are both multiplied to determine the values that a new
operation must exceed in order to replace the old operation. See
the [better_fees_and_ratio] function further below. *)
}
let default_minimal_fees =
match Tez.of_mutez 100L with None -> assert false | Some t -> t
let default_minimal_nanotez_per_gas_unit = Q.of_int 100
let default_minimal_nanotez_per_byte = Q.of_int 1000
let managers_quota =
Stdlib.List.nth Main.validation_passes Operation_repr.manager_pass
let default_config =
{
minimal_fees = default_minimal_fees;
minimal_nanotez_per_gas_unit = default_minimal_nanotez_per_gas_unit;
minimal_nanotez_per_byte = default_minimal_nanotez_per_byte;
clock_drift = None;
replace_by_fee_factor =
Q.make (Z.of_int 105) (Z.of_int 100)
;
}
let config_encoding : config Data_encoding.t =
let open Data_encoding in
conv
(fun {
minimal_fees;
minimal_nanotez_per_gas_unit;
minimal_nanotez_per_byte;
clock_drift;
replace_by_fee_factor;
} ->
( minimal_fees,
minimal_nanotez_per_gas_unit,
minimal_nanotez_per_byte,
clock_drift,
replace_by_fee_factor ))
(fun ( minimal_fees,
minimal_nanotez_per_gas_unit,
minimal_nanotez_per_byte,
clock_drift,
replace_by_fee_factor ) ->
{
minimal_fees;
minimal_nanotez_per_gas_unit;
minimal_nanotez_per_byte;
clock_drift;
replace_by_fee_factor;
})
(obj5
(dft "minimal_fees" Tez.encoding default_config.minimal_fees)
(dft
"minimal_nanotez_per_gas_unit"
nanotez_enc
default_config.minimal_nanotez_per_gas_unit)
(dft
"minimal_nanotez_per_byte"
nanotez_enc
default_config.minimal_nanotez_per_byte)
(opt "clock_drift" Period.encoding)
(dft
"replace_by_fee_factor"
manager_op_replacement_factor_enc
default_config.replace_by_fee_factor))
(** Static information to store in the filter state. *)
type info = {
head : Block_header.shell_header;
round_durations : Round.round_durations;
hard_gas_limit_per_block : Gas.Arith.integral;
head_round : Round.t;
round_zero_duration : Period.t;
grandparent_level_start : Timestamp.t;
}
let init_state_prototzresult ~head round_durations hard_gas_limit_per_block =
let open Lwt_result_syntax in
let*? head_round =
Alpha_context.Fitness.round_from_raw head.Tezos_base.Block_header.fitness
in
let round_zero_duration = Round.round_duration round_durations Round.zero in
let*? grandparent_round =
Alpha_context.Fitness.predecessor_round_from_raw head.fitness
in
let*? proposal_level_offset =
Round.level_offset_of_round
round_durations
~round:Round.(succ grandparent_round)
in
let*? proposal_round_offset =
Round.level_offset_of_round round_durations ~round:head_round
in
let*? proposal_offset =
Period.(add proposal_level_offset proposal_round_offset)
in
let grandparent_level_start = Timestamp.(head.timestamp - proposal_offset) in
return
{
head;
round_durations;
hard_gas_limit_per_block;
head_round;
round_zero_duration;
grandparent_level_start;
}
let init_state ~head round_durations hard_gas_limit_per_block =
Lwt.map
Environment.wrap_tzresult
(init_state_prototzresult ~head round_durations hard_gas_limit_per_block)
let init context ~(head : Tezos_base.Block_header.shell_header) =
let open Lwt_result_syntax in
let* ( ctxt,
(_ : Receipt.balance_updates),
(_ : Migration.origination_result list) ) =
prepare
context
~level:(Int32.succ head.level)
~predecessor_timestamp:head.timestamp
~timestamp:head.timestamp
|> Lwt.map Environment.wrap_tzresult
in
let round_durations = Constants.round_durations ctxt in
let hard_gas_limit_per_block = Constants.hard_gas_limit_per_block ctxt in
init_state ~head round_durations hard_gas_limit_per_block
let flush old_info ~head =
init_state ~head old_info.round_durations old_info.hard_gas_limit_per_block
let manager_prio p = `Low p
let consensus_prio = `High
let other_prio = `Medium
let compute_manager_contents_fee_and_gas_limit contents =
let open Operation in
let l = to_list (Contents_list contents) in
List.fold_left
(fun acc -> function
| Contents (Manager_operation {fee; gas_limit; _}) -> (
match acc with
| Error _ as e -> e
| Ok (total_fee, total_gas) -> (
match Tez.(total_fee +? fee) with
| Ok total_fee -> Ok (total_fee, Gas.Arith.add total_gas gas_limit)
| Error _ as e -> e))
| _ -> acc)
(Ok (Tez.zero, Gas.Arith.zero))
l
type Environment.Error_monad.error += Fees_too_low
let () =
Environment.Error_monad.register_error_kind
`Permanent
~id:"prefilter.fees_too_low"
~title:"Operation fees are too low"
~description:"Operation fees are too low"
~pp:(fun ppf () -> Format.fprintf ppf "Operation fees are too low")
Data_encoding.unit
(function Fees_too_low -> Some () | _ -> None)
(fun () -> Fees_too_low)
let size_of_operation op =
(WithExceptions.Option.get ~loc:__LOC__
@@ Data_encoding.Binary.fixed_length
Tezos_base.Operation.shell_header_encoding)
+ Data_encoding.Binary.length Operation.protocol_data_encoding op
(** Returns the weight and resources consumption of an operation. The weight
corresponds to the one implemented by the baker, to decide which operations
to put in a block first (the code is largely duplicated).
See {!Tezos_baking_alpha.Operation_selection.weight_manager} *)
let weight_and_resources_manager_operation ~hard_gas_limit_per_block ?size ~fee
~gas op =
let max_size = managers_quota.max_size in
let size = match size with None -> size_of_operation op | Some s -> s in
let size_f = Q.of_int size in
let gas_f = Q.of_bigint (Gas.Arith.integral_to_z gas) in
let fee_f = Q.of_int64 (Tez.to_mutez fee) in
let size_ratio = Q.(size_f / Q.of_int max_size) in
let gas_ratio =
Q.(gas_f / Q.of_bigint (Gas.Arith.integral_to_z hard_gas_limit_per_block))
in
let resources = Q.max size_ratio gas_ratio in
(Q.(fee_f / resources), resources)
let pre_filter_manager :
type t.
info ->
config ->
Operation.packed_protocol_data ->
t Kind.manager contents_list ->
[ `Passed_prefilter of Q.t list
| `Branch_refused of tztrace
| `Branch_delayed of tztrace
| `Refused of tztrace
| `Outdated of tztrace ] =
fun info config packed_op op ->
let size = size_of_operation packed_op in
let check_gas_and_fee fee gas_limit =
let fees_in_nanotez =
Q.mul (Q.of_int64 (Tez.to_mutez fee)) (Q.of_int 1000)
in
let minimal_fees_in_nanotez =
Q.mul (Q.of_int64 (Tez.to_mutez config.minimal_fees)) (Q.of_int 1000)
in
let minimal_fees_for_gas_in_nanotez =
Q.mul
config.minimal_nanotez_per_gas_unit
(Q.of_bigint @@ Gas.Arith.integral_to_z gas_limit)
in
let minimal_fees_for_size_in_nanotez =
Q.mul config.minimal_nanotez_per_byte (Q.of_int size)
in
if
Q.compare
fees_in_nanotez
(Q.add
minimal_fees_in_nanotez
(Q.add
minimal_fees_for_gas_in_nanotez
minimal_fees_for_size_in_nanotez))
>= 0
then `Fees_ok
else `Refused [Environment.wrap_tzerror Fees_too_low]
in
match compute_manager_contents_fee_and_gas_limit op with
| Error err -> `Refused (Environment.wrap_tztrace err)
| Ok (fee, gas_limit) -> (
match check_gas_and_fee fee gas_limit with
| `Refused _ as err -> err
| `Fees_ok ->
let weight, _op_resources =
weight_and_resources_manager_operation
~hard_gas_limit_per_block:info.hard_gas_limit_per_block
~fee
~gas:gas_limit
packed_op
in
`Passed_prefilter [weight])
type Environment.Error_monad.error += Wrong_operation
let () =
Environment.Error_monad.register_error_kind
`Temporary
~id:"prefilter.wrong_operation"
~title:"Wrong operation"
~description:"Failing_noop operations are not accepted in the mempool."
~pp:(fun ppf () ->
Format.fprintf
ppf
"Failing_noop operations are not accepted in the mempool")
Data_encoding.unit
(function Wrong_operation -> Some () | _ -> None)
(fun () -> Wrong_operation)
type Environment.Error_monad.error += Consensus_operation_in_far_future
let () =
Environment.Error_monad.register_error_kind
`Branch
~id:"prefilter.Consensus_operation_in_far_future"
~title:"Consensus operation in far future"
~description:"Consensus operation too far in the future are not accepted."
~pp:(fun ppf () ->
Format.fprintf
ppf
"Consensus operation too far in the future are not accepted.")
Data_encoding.unit
(function Consensus_operation_in_far_future -> Some () | _ -> None)
(fun () -> Consensus_operation_in_far_future)
(** {2 consensus operation filtering}
In Tenderbake, we increased a lot the number of consensus
operations, therefore it seems necessary to be able to filter consensus
operations that could be produced by a Byzantine baker mis-using
its right to produce operations in future rounds or levels.
We consider the situation where the head is at level [h_l],
round [h_r], and with timestamp [h_ts], with the predecessor of the head
being at round [hp_r].
We receive at a time [now] a consensus operation for level [op_l] and
round [op_r].
A consensus operation is considered too far in the future, and therefore filtered,
if the earliest possible starting time of its round is greater than the
current time plus a safety margin of [config.clock_drift].
To consider potential level 2 reorgs, we first compute the expected
timestamp of round zero at previous level [hp0_ts],
All ops at level p_l and round r' such that time(r') is greater than (now + drift) are
deemed too far in the future:
h_r op_ts now+drift (h_l,r')
hp0_ts h_0 h_l | | |
+----+-----+---------+-------------------+--+-----+--------------+-----------
| | | | | | |
| h_ts h_r end time | now | earliest expected
| | | | time of round r'
|<----op_r rounds duration -------->| |
|
|<--------------- operations kept ---->|<-rejected----------...
|
|<-----------operations considered by the filter -----------...
For an operation on a proposal at the next level, we consider the minimum
starting time of the operation's round, obtained by assuming that the proposal
at the next level was built on top of a proposal at round 0 for the current
level, itself based on a proposal at round 0 of previous level.
Operations on proposal with higher levels are treated similarly.
All ops at the next level and round r' such that timestamp(r') > now+drift
are deemed too far in the future.
r=0 r=1 h_r now now+drift (h_l+1,r')
hp0_ts h_0 h_l h_l | | |
+----+---- |-------+----+---------+----------+----------+----------
| | | | |
| t0 | h_ts earliest expected
| | | | time of round r'
|<--- | earliest| |
| next level| |
| |<---------------------------------->|
round_offset(r')
*)
(** At a given level a consensus operation is acceptable if its earliest
expected timestamp, [op_earliest_ts] is below the current clock with an
accepted drift for the clock given by a configuration. *)
let acceptable ~drift ~op_earliest_ts ~now_timestamp =
let open Result_syntax in
Timestamp.(
let+ now_drifted = now_timestamp +? drift in
op_earliest_ts <= now_drifted)
(** Check that an operation with the given [op_round], at level [op_level]
is likely to be correct, meaning it could have been produced before
now (+ the safety margin from configuration).
Given an operation at level greater or equal than/to the current level, we
compute the expected timestamp of the operation's round. If the operation
is at a greater level, we assume that it is based on the proposal at round
zero of the current level.
All operations whose (level, round) is lower than or equal to the current
head are deemed valid.
Note that in case where their is a high drift in the computer clock, they
might not have been considered valid by comparing their expected timestamp
to the clock.
This is a stricter than necessary filter as it will reject operations that
could be valid in the current timeframe if the proposal they attest is
built over a predecessor of the current proposal that would be of lower
round than the current one.
What can we do that would be smarter: get current head's predecessor round
and timestamp to compute the timestamp t0 of a predecessor that would have
been proposed at round 0.
Timestamp of round at current level for an alternative head that would be
based on such proposal would be computed based on t0.
For level higher than current head, compute the round's earliest timestamp
if all proposal passed at round 0 starting from t0.
*)
let acceptable_op ~config ~round_durations ~round_zero_duration ~proposal_level
~proposal_round ~proposal_timestamp
~(proposal_predecessor_level_start : Timestamp.t) ~op_level ~op_round
~now_timestamp =
let open Result_syntax in
if
Raw_level.(succ op_level < proposal_level)
|| (op_level = proposal_level && op_round <= proposal_round)
then
Ok true
else
let now_timestamp = Timestamp.(max now_timestamp proposal_timestamp) in
let drift = Option.value ~default:round_zero_duration config.clock_drift in
let level_offset = Raw_level.(diff (succ op_level) proposal_level) in
let* time_shift = Period.mult level_offset round_zero_duration in
let* earliest_op_level_start =
Timestamp.(proposal_predecessor_level_start +? time_shift)
in
let* op_earliest_ts =
Round.timestamp_of_another_round_same_level
round_durations
~current_round:Round.zero
~current_timestamp:earliest_op_level_start
~considered_round:op_round
in
acceptable ~drift ~op_earliest_ts ~now_timestamp
type level_and_round = {level : Raw_level.t; round : Round.t}
let pre_filter_far_future_consensus_ops info config
({level = op_level; round = op_round} : level_and_round) : bool Lwt.t =
let open Result_syntax in
let res =
let now_timestamp = Time.System.now () |> Time.System.to_protocol in
let* proposal_level = Raw_level.of_int32 info.head.level in
acceptable_op
~config
~round_durations:info.round_durations
~round_zero_duration:info.round_zero_duration
~proposal_level
~proposal_round:info.head_round
~proposal_timestamp:info.head.timestamp
~proposal_predecessor_level_start:info.grandparent_level_start
~op_level
~op_round
~now_timestamp
in
match res with Ok b -> Lwt.return b | Error _ -> Lwt.return_false
let prefilter_consensus_operation info config level_and_round =
let open Lwt_syntax in
let* keep = pre_filter_far_future_consensus_ops info config level_and_round in
if keep then return (`Passed_prefilter consensus_prio)
else
return
(`Branch_refused
[Environment.wrap_tzerror Consensus_operation_in_far_future])
(** A quasi infinite amount of "valid" (pre)attestations could be
sent by a committee member, one for each possible round number.
This filter rejects (pre)attestations that refer to a round
that could not have been reached within the time span between
the last head's timestamp and the current local clock.
We add [config.clock_drift] time as a safety margin.
*)
let pre_filter info config
({shell = _; protocol_data = Operation_data {contents; _} as op} :
Main.operation) =
let open Lwt_syntax in
let prefilter_manager_op manager_op =
return
@@
match pre_filter_manager info config op manager_op with
| `Passed_prefilter prio -> `Passed_prefilter (manager_prio prio)
| (`Branch_refused _ | `Branch_delayed _ | `Refused _ | `Outdated _) as err
->
err
in
match contents with
| Single (Failing_noop _) ->
return (`Refused [Environment.wrap_tzerror Wrong_operation])
| Single (Preattestation consensus_content)
| Single (Attestation {consensus_content; dal_content = _}) ->
let level_and_round : level_and_round =
{level = consensus_content.level; round = consensus_content.round}
in
prefilter_consensus_operation info config level_and_round
| Single (Seed_nonce_revelation _)
| Single (Double_preattestation_evidence _)
| Single (Double_attestation_evidence _)
| Single (Double_baking_evidence _)
| Single (Activate_account _)
| Single (Proposals _)
| Single (Vdf_revelation _)
| Single (Drain_delegate _)
| Single (Ballot _) ->
return (`Passed_prefilter other_prio)
| Single (Manager_operation _) as op -> prefilter_manager_op op
| Cons (Manager_operation _, _) as op -> prefilter_manager_op op
let syntactic_check _ = Lwt.return `Well_formed
let is_manager_operation op =
match Operation.acceptable_pass op with
| Some pass -> Compare.Int.equal pass Operation_repr.manager_pass
| None -> false
let compute_fee_and_gas_limit {protocol_data = Operation_data data; _} =
compute_manager_contents_fee_and_gas_limit data.contents
let gas_as_q gas = Gas.Arith.integral_to_z gas |> Q.of_bigint
let fee_and_ratio_as_q fee gas =
let fee = Tez.to_mutez fee |> Z.of_int64 |> Q.of_bigint in
let gas = gas_as_q gas in
let ratio = Q.div fee gas in
(fee, ratio)
let bumped_fee_and_ratio_as_q config fee gas =
let bump = Q.mul config.replace_by_fee_factor in
let fee, ratio = fee_and_ratio_as_q fee gas in
(bump fee, bump ratio)
(** Determine whether the new manager operation is sufficiently better
than the old manager operation to replace it. Sufficiently better
means that the new operation's fee and fee/gas ratio are both
greater than or equal to the old operation's same metrics bumped by
the factor [config.replace_by_fee_factor]. *)
let better_fees_and_ratio config old_gas old_fee new_gas new_fee =
let bumped_old_fee, bumped_old_ratio =
bumped_fee_and_ratio_as_q config old_fee old_gas
in
let new_fee, new_ratio = fee_and_ratio_as_q new_fee new_gas in
Q.compare new_fee bumped_old_fee >= 0
&& Q.compare new_ratio bumped_old_ratio >= 0
(** [conflict_handler config] returns a conflict handler for
{!Mempool.add_operation} (see {!Mempool.conflict_handler}).
- For non-manager operations, we select the greater operation
according to {!Operation.compare}.
- A manager operation is replaced only when the new operation's
fee and fee/gas ratio both exceed the old operation's by at least a
factor of [config.replace_by_fee_factor] (see {!better_fees_and_ratio}).
Precondition: both operations must be individually valid (because
of the call to {!Operation.compare}). *)
let conflict_handler config : Mempool.conflict_handler =
let open Result_syntax in
fun ~existing_operation ~new_operation ->
let (_ : Operation_hash.t), old_op = existing_operation in
let (_ : Operation_hash.t), new_op = new_operation in
if is_manager_operation old_op && is_manager_operation new_op then
let new_op_is_better =
let* old_fee, old_gas_limit = compute_fee_and_gas_limit old_op in
let* new_fee, new_gas_limit = compute_fee_and_gas_limit new_op in
return
(better_fees_and_ratio
config
old_gas_limit
old_fee
new_gas_limit
new_fee)
in
match new_op_is_better with
| Ok b when b -> `Replace
| Ok _ | Error _ -> `Keep
else if Operation.compare existing_operation new_operation < 0 then `Replace
else `Keep
let int64_ceil_of_q q =
let n = Q.to_int64 q in
if Q.(equal q (of_int64 n)) then n else Int64.succ n
let fee_needed_to_replace_by_fee config ~op_to_replace ~candidate_op =
let open Result_syntax in
if is_manager_operation candidate_op && is_manager_operation op_to_replace
then
(let* _fee, candidate_gas = compute_fee_and_gas_limit candidate_op in
let* old_fee, old_gas = compute_fee_and_gas_limit op_to_replace in
if Gas.Arith.(old_gas = zero || candidate_gas = zero) then
return_none
else
let candidate_gas = gas_as_q candidate_gas in
let bumped_old_fee, bumped_old_ratio =
bumped_fee_and_ratio_as_q config old_fee old_gas
in
let fee_needed_for_fee = int64_ceil_of_q bumped_old_fee in
let fee_needed_for_ratio =
int64_ceil_of_q Q.(bumped_old_ratio * candidate_gas)
in
return_some (max fee_needed_for_fee fee_needed_for_ratio))
|> Option.of_result |> Option.join
else None
let find_manager {shell = _; protocol_data = Operation_data {contents; _}} =
match contents with
| Single (Manager_operation {source; _}) -> Some source
| Cons (Manager_operation {source; _}, _) -> Some source
| Single
( Preattestation _ | Attestation _ | Proposals _ | Ballot _
| Seed_nonce_revelation _ | Vdf_revelation _ | Double_baking_evidence _
| Double_preattestation_evidence _ | Double_attestation_evidence _
| Activate_account _ | Drain_delegate _ | Failing_noop _ ) ->
None
module Conflict_map = struct
type t = packed_operation Signature.Public_key_hash.Map.t
let empty = Signature.Public_key_hash.Map.empty
let update conflict_map ~new_operation ~replacements =
let conflict_map =
List.fold_left
(fun conflict_map op ->
match find_manager op with
| Some manager ->
Signature.Public_key_hash.Map.remove manager conflict_map
| None -> conflict_map)
conflict_map
replacements
in
match find_manager new_operation with
| Some manager ->
Signature.Public_key_hash.Map.add manager new_operation conflict_map
| None -> conflict_map
let fee_needed_to_replace_by_fee config ~candidate_op ~conflict_map =
match find_manager candidate_op with
| None -> None
| Some manager -> (
match Signature.Public_key_hash.Map.find manager conflict_map with
| None ->
None
| Some op_to_replace ->
fee_needed_to_replace_by_fee config ~candidate_op ~op_to_replace)
end
let fee_needed_to_overtake ~op_to_overtake ~candidate_op =
let open Result_syntax in
if is_manager_operation candidate_op && is_manager_operation op_to_overtake
then
(let* _fee, candidate_gas = compute_fee_and_gas_limit candidate_op in
let* target_fee, target_gas = compute_fee_and_gas_limit op_to_overtake in
if Gas.Arith.(target_gas = zero || candidate_gas = zero) then
return_none
else
let target_fee = Q.of_int64 (Tez.to_mutez target_fee) in
let target_gas = Q.of_bigint (Gas.Arith.integral_to_z target_gas) in
let target_ratio = Q.(target_fee / target_gas) in
let candidate_gas =
Q.of_bigint (Gas.Arith.integral_to_z candidate_gas)
in
return_some (Int64.succ Q.(to_int64 (target_ratio * candidate_gas))))
|> Option.of_result |> Option.join
else None
module Internal_for_tests = struct
let default_config_with_clock_drift clock_drift =
{default_config with clock_drift}
let default_config_with_replace_factor replace_by_fee_factor =
{default_config with replace_by_fee_factor}
let get_clock_drift {clock_drift; _} = clock_drift
let acceptable_op = acceptable_op
let fee_needed_to_replace_by_fee = fee_needed_to_replace_by_fee
end