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
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 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; _}) ->
(Environment.wrap_tzresult @@ Tez.(total_fee +? fee))
>>? fun total_fee ->
let first_source = Option.value ~default:source first_source in
let first_counter = Option.value ~default:counter first_counter in
ok
( Some first_source,
Some first_counter,
total_fee,
Gas.Arith.add total_gas gas_limit )
| _ -> ok 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 =
Baking_simulator.add_operation inc op >>= function
| Error errs ->
Events.(emit invalid_operation_filtered) (Operation.hash_packed op, errs)
>>= fun () -> Lwt.return_none
| Ok (resulting_state, None) ->
Lwt.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 ->
Events.(emit cannot_serialize_operation_metadata)
(Operation.hash_packed op)
>>= fun () -> Lwt.return_none
| Some _b -> Lwt.return_some resulting_state)
let filter_valid_operations_up_to_quota inc (ops, quota) =
let {Tezos_protocol_environment.max_size; max_op} = quota in
let exception Full of (Baking_simulator.incremental * packed_operation list)
in
try
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 Lwt.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 ;
validate_operation inc op >>= function
| None -> Lwt.return (inc, curr_size, nb_ops, acc)
| Some inc' -> Lwt.return (inc', new_size, nb_ops + 1, op :: acc)))
(inc, 0, 0, [])
ops
>>= fun (inc, _, _, l) -> Lwt.return (inc, List.rev l)
with Full (inc, l) -> Lwt.return (inc, List.rev l)
let filter_operations_with_simulation initial_inc fees_config
~hard_gas_limit_per_block {consensus; votes; anonymous; managers} =
let {
Baking_configuration.minimal_fees;
minimal_nanotez_per_gas_unit;
minimal_nanotez_per_byte;
} =
fees_config
in
filter_valid_operations_up_to_quota
initial_inc
(Prioritized_operation_set.operations consensus, consensus_quota)
>>= fun (inc, consensus) ->
filter_valid_operations_up_to_quota
inc
(Prioritized_operation_set.operations votes, votes_quota)
>>= fun (inc, votes) ->
filter_valid_operations_up_to_quota
inc
(Prioritized_operation_set.operations anonymous, anonymous_quota)
>>= fun (inc, anonymous) ->
let prioritized_managers =
prioritize_managers
~hard_gas_limit_per_block
~minimal_fees
~minimal_nanotez_per_gas_unit
~minimal_nanotez_per_byte
managers
in
filter_valid_operations_up_to_quota
inc
( PrioritizedManagerSet.elements prioritized_managers
|> List.map (fun {op; _} -> Prioritized_operation.packed op),
managers_quota )
>>= fun (inc, managers) ->
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
Baking_simulator.finalize_construction inc >>=? function
| 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) =
filter_valid_operations_up_to_quota inc (consensus, consensus_quota)
>>= fun (incremental, filtered_consensus) ->
let payload = Operation_pool.payload_of_ordered_pool ordered_pool in
List.fold_left_es
(fun inc op ->
Baking_simulator.add_operation inc op >>=? fun (inc, _) -> return inc)
incremental
(List.flatten [votes; anonymous; managers])
>>=? fun incremental ->
let filtered_pool =
Operation_pool.ordered_pool_of_payload
~consensus_operations:filtered_consensus
payload
in
return (incremental, filtered_pool)