Source file operation_selection.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
open Protocol
open Alpha_context
open Operation_pool
module Events = Baking_events.Selection
let quota = Main.validation_passes
let consensus_quota = Stdlib.List.nth quota Operation_repr.consensus_pass
let votes_quota = Stdlib.List.nth quota Operation_repr.voting_pass
let anonymous_quota = Stdlib.List.nth quota Operation_repr.anonymous_pass
let managers_quota = Stdlib.List.nth quota Operation_repr.manager_pass
type prioritized_manager = {
op : Prioritized_operation.t;
size : int;
fee : Tez.t;
gas : Fixed_point_repr.integral_tag Gas.Arith.t;
weight : Q.t;
source : public_key_hash;
counter : Manager_counter.t;
}
module PrioritizedManagerSet = Set.Make (struct
type t = prioritized_manager
let compare {source; counter; weight; op; _}
{source = source'; counter = counter'; weight = weight'; op = op'; _} =
let cmp_src = Signature.Public_key_hash.compare source source' in
if cmp_src = 0 then
let c = Manager_counter.compare counter counter' in
if c <> 0 then c
else
let c = Prioritized_operation.compare_priority op' op in
if c <> 0 then c else Q.compare weight' weight
else
let c = Prioritized_operation.compare_priority op' op in
if c <> 0 then c
else
let c = Q.compare weight' weight in
if c <> 0 then c else cmp_src
end)
let prioritize_manager ~max_size ~hard_gas_limit_per_block ~minimal_fees
~minimal_nanotez_per_gas_unit ~minimal_nanotez_per_byte operation =
let open Result_syntax in
let op = Operation_pool.Prioritized_operation.packed operation in
let {protocol_data = Operation_data {contents; _}; _} = op in
let open Operation in
let l = to_list (Contents_list contents) in
List.fold_left_e
(fun ((first_source, first_counter, total_fee, total_gas) as acc) ->
function
| Contents (Manager_operation {source; counter; fee; gas_limit; _}) ->
let* total_fee =
Environment.wrap_tzresult @@ Tez.(total_fee +? fee)
in
let first_source = Option.value ~default:source first_source in
let first_counter = Option.value ~default:counter first_counter in
return
( Some first_source,
Some first_counter,
total_fee,
Gas.Arith.add total_gas gas_limit )
| _ -> return acc)
(None, None, Tez.zero, Gas.Arith.zero)
l
|> function
| Ok (Some source, Some counter, fee, gas) ->
if Tez.(fee < minimal_fees) then None
else
let size = Data_encoding.Binary.length Operation.encoding op 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 weight = Q.(fee_f / max size_ratio gas_ratio) in
let fees_in_nanotez =
Q.mul (Q.of_int64 (Tez.to_mutez fee)) (Q.of_int 1000)
in
let enough_fees_for_gas =
let minimal_fees_in_nanotez =
Q.mul
minimal_nanotez_per_gas_unit
(Q.of_bigint @@ Gas.Arith.integral_to_z gas)
in
Q.compare minimal_fees_in_nanotez fees_in_nanotez <= 0
in
let enough_fees_for_size =
let minimal_fees_in_nanotez =
Q.mul minimal_nanotez_per_byte (Q.of_int size)
in
Q.compare minimal_fees_in_nanotez fees_in_nanotez <= 0
in
if enough_fees_for_size && enough_fees_for_gas then
Some {op = operation; size; weight; fee; gas; source; counter}
else None
| _ -> None
let prioritize_managers ~hard_gas_limit_per_block ~minimal_fees
~minimal_nanotez_per_gas_unit ~minimal_nanotez_per_byte managers =
Prioritized_operation_set.fold
(fun op acc ->
match
prioritize_manager
~max_size:managers_quota.max_size
~hard_gas_limit_per_block
~minimal_fees
~minimal_nanotez_per_gas_unit
~minimal_nanotez_per_byte
op
with
| None -> acc
| Some w_op -> PrioritizedManagerSet.add w_op acc)
managers
PrioritizedManagerSet.empty
(** Simulation *)
type simulation_result = {
validation_result : Tezos_protocol_environment.validation_result option;
block_header_metadata : block_header_metadata option;
operations : packed_operation list list;
operations_hash : Operation_list_list_hash.t;
}
let validate_operation inc op =
let open Lwt_syntax in
let* result = Baking_simulator.add_operation inc op in
match result with
| Error errs ->
let* () =
Events.(emit invalid_operation_filtered) (Operation.hash_packed op, errs)
in
return_none
| Ok (resulting_state, None) ->
return_some resulting_state
| Ok (resulting_state, Some receipt) -> (
let encoding_result =
let enc = Protocol.operation_receipt_encoding in
Option.bind
(Data_encoding.Binary.to_bytes_opt enc receipt)
(Data_encoding.Binary.of_bytes_opt enc)
in
match encoding_result with
| None ->
let* () =
Events.(emit cannot_serialize_operation_metadata)
(Operation.hash_packed op)
in
return_none
| Some _b -> return_some resulting_state)
let filter_valid_operations_up_to_quota inc (ops, quota) =
let open Lwt_syntax in
let {Tezos_protocol_environment.max_size; max_op} = quota in
let exception Full of (Baking_simulator.incremental * packed_operation list)
in
try
let* inc, _, _, l =
List.fold_left_s
(fun (inc, curr_size, nb_ops, acc) op ->
let op_size =
Data_encoding.Binary.length Alpha_context.Operation.encoding op
in
let new_size = curr_size + op_size in
if new_size > max_size then return (inc, curr_size, nb_ops, acc)
else (
Option.iter
(fun max_op ->
if max_op = nb_ops + 1 then raise (Full (inc, acc)))
max_op ;
let* inc'_opt = validate_operation inc op in
match inc'_opt with
| None -> return (inc, curr_size, nb_ops, acc)
| Some inc' -> return (inc', new_size, nb_ops + 1, op :: acc)))
(inc, 0, 0, [])
ops
in
return (inc, List.rev l)
with Full (inc, l) -> return (inc, List.rev l)
let filter_valid_managers_up_to_quota inc ~hard_gas_limit_per_block (ops, quota)
=
let open Lwt_syntax in
let {Tezos_protocol_environment.max_size; max_op} = quota in
let rec loop (inc, curr_size, nb_ops, remaining_gas, acc) = function
| [] -> return (inc, List.rev acc)
| {op; size = op_size; gas = op_gas; _} :: l -> (
match max_op with
| Some max_op when max_op = nb_ops + 1 -> return (inc, List.rev acc)
| None | Some _ -> (
if Gas.Arith.(remaining_gas < op_gas) then
loop (inc, curr_size, nb_ops, remaining_gas, acc) l
else
let new_size = curr_size + op_size in
if new_size > max_size then
loop (inc, curr_size, nb_ops, remaining_gas, acc) l
else
let packed_op = Prioritized_operation.packed op in
let* inc'_opt = validate_operation inc packed_op in
match inc'_opt with
| None -> loop (inc, curr_size, nb_ops, remaining_gas, acc) l
| Some inc' ->
let new_remaining_gas =
Gas.Arith.sub remaining_gas op_gas
in
loop
( inc',
new_size,
succ nb_ops,
new_remaining_gas,
packed_op :: acc )
l))
in
loop (inc, 0, 0, hard_gas_limit_per_block, []) ops
let filter_operations_with_simulation initial_inc fees_config
~hard_gas_limit_per_block {consensus; votes; anonymous; managers} =
let open Lwt_result_syntax in
let {
Baking_configuration.minimal_fees;
minimal_nanotez_per_gas_unit;
minimal_nanotez_per_byte;
} =
fees_config
in
let*! inc, consensus =
filter_valid_operations_up_to_quota
initial_inc
(Prioritized_operation_set.operations consensus, consensus_quota)
in
let*! inc, votes =
filter_valid_operations_up_to_quota
inc
(Prioritized_operation_set.operations votes, votes_quota)
in
let*! inc, anonymous =
filter_valid_operations_up_to_quota
inc
(Prioritized_operation_set.operations anonymous, anonymous_quota)
in
let prioritized_managers =
prioritize_managers
~hard_gas_limit_per_block
~minimal_fees
~minimal_nanotez_per_gas_unit
~minimal_nanotez_per_byte
managers
in
let*! inc, managers =
filter_valid_managers_up_to_quota
inc
~hard_gas_limit_per_block
(PrioritizedManagerSet.elements prioritized_managers, managers_quota)
in
let operations = [consensus; votes; anonymous; managers] in
let operations_hash =
Operation_list_list_hash.compute
(List.map
(fun sl ->
Operation_list_hash.compute (List.map Operation.hash_packed sl))
operations)
in
let inc = {inc with header = {inc.header with operations_hash}} in
let* result = Baking_simulator.finalize_construction inc in
match result with
| Some (validation_result, ) ->
return
{
validation_result = Some validation_result;
block_header_metadata = Some block_header_metadata;
operations;
operations_hash;
}
| None ->
return
{
validation_result = None;
block_header_metadata = None;
operations;
operations_hash;
}
let filter_valid_operations_up_to_quota_without_simulation (ops, quota) =
let {Tezos_protocol_environment.max_size; max_op} = quota in
let exception Full of packed_operation list in
try
List.fold_left
(fun (curr_size, nb_ops, acc) op ->
let op_size =
Data_encoding.Binary.length Alpha_context.Operation.encoding op
in
let new_size = curr_size + op_size in
if new_size > max_size then (curr_size, nb_ops, acc)
else (
Option.iter
(fun max_op -> if max_op = nb_ops + 1 then raise (Full acc))
max_op ;
(new_size, nb_ops + 1, op :: acc)))
(0, 0, [])
ops
|> fun (_, _, l) -> List.rev l
with Full l -> List.rev l
let filter_operations_without_simulation fees_config ~hard_gas_limit_per_block
{consensus; votes; anonymous; managers} =
let consensus =
filter_valid_operations_up_to_quota_without_simulation
(Prioritized_operation_set.operations consensus, consensus_quota)
in
let votes =
filter_valid_operations_up_to_quota_without_simulation
(Prioritized_operation_set.operations votes, votes_quota)
in
let anonymous =
filter_valid_operations_up_to_quota_without_simulation
(Prioritized_operation_set.operations anonymous, anonymous_quota)
in
let {
Baking_configuration.minimal_fees;
minimal_nanotez_per_gas_unit;
minimal_nanotez_per_byte;
} =
fees_config
in
let prioritized_managers =
prioritize_managers
~hard_gas_limit_per_block
~minimal_fees
~minimal_nanotez_per_gas_unit
~minimal_nanotez_per_byte
managers
in
let managers =
filter_valid_operations_up_to_quota_without_simulation
( PrioritizedManagerSet.elements prioritized_managers
|> List.map (fun {op; _} -> Prioritized_operation.packed op),
managers_quota )
in
let operations = [consensus; votes; anonymous; managers] in
operations
let filter_consensus_operations_only inc
({consensus; votes; anonymous; managers} as ordered_pool) =
let open Lwt_result_syntax in
let*! incremental, filtered_consensus =
filter_valid_operations_up_to_quota inc (consensus, consensus_quota)
in
let payload = Operation_pool.payload_of_ordered_pool ordered_pool in
let* incremental =
List.fold_left_es
(fun inc op ->
let* inc, _ = Baking_simulator.add_operation inc op in
return inc)
incremental
(List.flatten [votes; anonymous; managers])
in
let filtered_pool =
Operation_pool.ordered_pool_of_payload
~consensus_operations:filtered_consensus
payload
in
return (incremental, filtered_pool)