Source file scenario_bake.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
open State_account
open State
open Scenario_dsl
open Log_helpers
open Scenario_base
(** Applies when baking the last block of a cycle *)
let apply_end_cycle current_cycle previous_block block state :
State.t tzresult Lwt.t =
let open Lwt_result_wrap_syntax in
Log.debug ~color:time_color "Ending cycle %a" Cycle.pp current_cycle ;
let* state =
Slashing_helpers.apply_all_slashes_at_cycle_end
current_cycle
previous_block
state
in
let*? state = State_ai_flags.Autostake.run_at_cycle_end block state in
let* state = update_map_es ~f:(compute_future_frozen_rights block) state in
let state, param_requests =
List.fold_left
(fun (state, remaining_requests) (name, params, wait) ->
if wait > 0 then (state, (name, params, wait - 1) :: remaining_requests)
else
let src = find_account name state in
let state =
update_account name {src with parameters = params} state
in
(state, remaining_requests))
(state, [])
state.param_requests
in
return {state with param_requests}
(** Applies when baking the first block of a cycle.
Technically nothing special happens, but we need to update the unslashable unstakes
since it's done lazily *)
let apply_new_cycle new_cycle state : State.t =
apply_unslashable_for_all new_cycle state
(** After baking and applying rewards in state *)
let check_all_balances block state : unit tzresult Lwt.t =
let open Lwt_result_syntax in
let State.{account_map; total_supply; _} = state in
let* actual_total_supply = Context.get_total_supply (B block) in
let*! r1 =
String.Map.fold_s
(fun name account acc ->
log_debug_balance name account_map ;
let* () = log_debug_rpc_balance name (Implicit account.pkh) block in
let*! r =
assert_balance_check ~loc:__LOC__ (B block) name account_map
in
Assert.join_errors r acc)
account_map
Result.return_unit
in
let*! r2 =
Assert.equal
~loc:__LOC__
Tez.equal
"Total supplies do not match"
Tez.pp
actual_total_supply
total_supply
in
Assert.join_errors r1 r2
let check_issuance_rpc block : unit tzresult Lwt.t =
let open Lwt_result_syntax in
let* rewards_per_block = Context.get_issuance_per_minute (B block) in
let* total_supply = Context.get_total_supply (B block) in
let* expected_issuance = Context.get_ai_expected_issuance (B block) in
let* () =
match expected_issuance with
| ei :: _ ->
Assert.equal_tez
~loc:__LOC__
rewards_per_block
ei.baking_reward_fixed_portion
| _ -> failwith "expected_issuance rpc: unexpected value"
in
let* yearly_rate = Context.get_ai_current_yearly_rate (B block) in
let* yearly_rate_exact = Context.get_ai_current_yearly_rate_exact (B block) in
let yr = float_of_string yearly_rate in
let yre = Q.to_float yearly_rate_exact in
let* () =
Assert.equal
~loc:__LOC__
(fun x y -> Float.(abs (x -. y) <= 0.001))
"Yearly rate (float)"
Format.pp_print_float
yr
yre
in
let issuance_from_rate =
Tez.(
mul_q total_supply Q.(div yearly_rate_exact ~$525_600_00)
|> of_q ~round:`Down)
in
let* () =
Assert.equal
~loc:__LOC__
Tez.equal
"Issuance"
Tez.pp
rewards_per_block
issuance_from_rate
in
return_unit
let attest_all_ =
let open Lwt_result_syntax in
fun (block, state) ->
let dlgs =
String.Map.bindings state.State.account_map
|> List.filter (fun (name, acc) ->
match acc.delegate with
| Some x -> String.equal x name
| None -> false)
|> List.map snd
in
let* ops =
List.map_es (fun dlg -> Op.attestation ~delegate:dlg.pkh block) dlgs
in
let state = State.add_pending_operations ops state in
return (block, state)
let attest_all = exec attest_all_
(** Bake a block, with the given baker and the given operations. *)
let bake ?baker : t -> t tzresult Lwt.t =
fun (block, state) ->
let open Lwt_result_wrap_syntax in
let previous_block = block in
let policy =
match baker with
| None -> state.baking_policy
| Some baker ->
let {pkh; _} =
try State.find_account baker state
with Not_found ->
Log.info
~color:warning_color
"Invalid baker: %s not found. Aborting"
baker ;
assert false
in
Some (Block.By_account pkh)
in
let* baker, _, _, _ = Block.get_next_baker ?policy block in
let baker_name, ({contract = baker_contract; _} as baker_acc) =
State.find_account_from_pkh baker state
in
let current_cycle = Block.current_cycle block in
let state =
State.update_map
~f:(fun acc_map ->
String.Map.add
baker_name
{baker_acc with last_active_cycle = current_cycle}
acc_map)
state
in
let* level = Plugin.RPC.current_level Block.rpc_ctxt block in
assert (Protocol.Alpha_context.Cycle.(level.cycle = Block.current_cycle block)) ;
Log.info
~color:time_color
"Baking level %d (cycle %ld) with %s"
(Int32.to_int (Int32.succ Block.(block.header.shell.level)))
(Protocol.Alpha_context.Cycle.to_int32 level.cycle)
baker_name ;
let adaptive_issuance_vote =
if state.force_ai_vote_yes then
Protocol.Alpha_context.Per_block_votes.Per_block_vote_on
else Per_block_vote_pass
in
let* () = check_issuance_rpc block in
let state, operations = State.pop_pending_operations state in
let* block, state =
let* block', _metadata =
Block.bake_with_metadata ?policy ~adaptive_issuance_vote ~operations block
in
if state.burn_rewards then
let* i =
Incremental.begin_construction ?policy ~adaptive_issuance_vote block
in
let* block_rewards = Context.get_issuance_per_minute (B block') in
let ctxt = Incremental.alpha_ctxt i in
let*@ context, _ =
Protocol.Alpha_context.Token.transfer
ctxt
(`Contract baker_contract)
`Burned
block_rewards
in
let i = Incremental.set_alpha_ctxt i context in
let* i = List.fold_left_es Incremental.add_operation i operations in
let* block = Incremental.finalize_block i in
let state = State.apply_burn block_rewards baker_name state in
return (block, state)
else return (block', state)
in
let* state =
State_ai_flags.AI_Activation.check_activation_cycle block state
in
let* state = State.apply_rewards ~baker:baker_name block state in
let new_current_cycle = Block.current_cycle block in
let* state =
if Protocol.Alpha_context.Cycle.(current_cycle = new_current_cycle) then
return state
else (
Log.info
~color:time_color
"Cycle %d"
(Protocol.Alpha_context.Cycle.to_int32 new_current_cycle |> Int32.to_int) ;
return @@ apply_new_cycle new_current_cycle state)
in
let* state =
if not (Block.last_block_of_cycle block) then return state
else apply_end_cycle current_cycle previous_block block state
in
let* () = check_all_balances block state in
let* block, state =
if state.force_attest_all then attest_all_ (block, state)
else return (block, state)
in
return (block, state)
(** Bake until a cycle is reached, using [bake] instead of [Block.bake] *)
let bake_until_next_cycle : t -> t tzresult Lwt.t =
fun (init_block, init_state) ->
let open Lwt_result_syntax in
let current_cycle = Block.current_cycle init_block in
let rec step (old_block, old_state) =
let step_cycle = Block.current_cycle old_block in
if Protocol.Alpha_context.Cycle.(step_cycle > current_cycle) then
return (old_block, old_state)
else
let* new_block, new_state = bake (old_block, old_state) in
step (new_block, new_state)
in
step (init_block, init_state)
(** Bake all the remaining blocks of the current cycle *)
let bake_until_dawn_of_next_cycle : t -> t tzresult Lwt.t =
fun (init_block, init_state) ->
let open Lwt_result_syntax in
let current_cycle = Block.current_cycle init_block in
let rec step (old_block, old_state) =
let* new_block, new_state = bake (old_block, old_state) in
let step_cycle = Block.current_cycle new_block in
if Protocol.Alpha_context.Cycle.(step_cycle > current_cycle) then
return (old_block, old_state)
else step (new_block, new_state)
in
step (init_block, init_state)
(** Bake a single block *)
let next_block =
exec (fun input ->
Log.info ~color:action_color "[Next block]" ;
bake input)
(** Bake a single block with a specific baker *)
let next_block_with_baker baker =
exec (fun input ->
Log.info ~color:action_color "[Next block (baker %s)]" baker ;
bake ~baker input)
(** Bake until the end of a cycle *)
let next_cycle_ input =
Log.info ~color:action_color "[Next cycle]" ;
bake_until_next_cycle input
(** Bake until the end of a cycle *)
let next_cycle = exec next_cycle_
(** Executes an operation: f should return a new state and a list of operations, which are then applied *)
let exec_op f =
let open Lwt_result_syntax in
Action
(fun ((block, _state) as input) ->
let* state, ops = f input in
let state = State.add_pending_operations ops state in
return (block, state))
--> next_block
(** Waiting functions *)
let wait_n_cycles n = loop n next_cycle
let wait_n_blocks n = loop n next_block
let wait_cycle_f_es (condition : t -> t -> bool tzresult Lwt.t) :
(t, t) scenarios =
let open Lwt_result_syntax in
exec (fun init_t ->
let rec bake_while t =
let* b = condition init_t t in
if b then return t
else
let* t = next_cycle_ t in
bake_while t
in
bake_while init_t)
(** Waits until [condition init_t current_t] is fulfilled.
It is checked on the first block of every cycle. If it returns false,
another cycle is baked, until it succeeds.
*)
let wait_cycle_f (condition : t -> t -> bool) : (t, t) scenarios =
let open Lwt_result_syntax in
let condition a b = return @@ condition a b in
wait_cycle_f_es condition
(** Wait until we are in a cycle satisfying the given condition.
Fails if AI_activation is requested and AI is not set to be activated in the future. *)
let wait_cycle_until condition =
let to_, done_ =
let rec get_names condition =
match condition with
| `AI_activation -> ("AI activation", "AI activated")
| `AI_activation_with_votes ->
("AI activation (with votes)", "AI activated")
| `delegate_parameters_activation ->
("delegate parameters activation", "delegate parameters activated")
| `right_before_delegate_parameters_activation ->
( "right before delegate parameters activation",
"delegate parameters will activate next cycle" )
| `And (cond1, cond2) ->
let to1, done1 = get_names cond1 in
let to2, done2 = get_names cond2 in
(to1 ^ " and " ^ to2, done1 ^ " and " ^ done2)
in
get_names condition
in
let condition (init_block, init_state) =
let rec stopper condition =
match condition with
| `AI_activation -> (
fun (block, _state) ->
match init_state.State.ai_activation_cycle with
| Some launch_cycle ->
let current_cycle = Block.current_cycle block in
Cycle.(current_cycle >= launch_cycle)
| _ ->
Log.error
"wait_cycle_until `AI_activation: launch cycle not found, \
aborting." ;
assert false)
| `AI_activation_with_votes ->
fun (block, state) ->
if State_ai_flags.AI_Activation.enabled init_state then
match state.State.ai_activation_cycle with
| Some launch_cycle ->
let current_cycle = Block.current_cycle block in
Cycle.(current_cycle >= launch_cycle)
| _ -> false
else (
Log.error
"wait_cycle_until `AI_activation_with_votes: AI cannot \
activate with the current protocol parameters, aborting." ;
assert false)
| `delegate_parameters_activation ->
fun (block, _state) ->
let init_cycle = Block.current_cycle init_block in
let cycles_to_wait =
init_state.constants.delegate_parameters_activation_delay + 1
in
Cycle.(Block.current_cycle block >= add init_cycle cycles_to_wait)
| `right_before_delegate_parameters_activation ->
fun (block, _state) ->
let init_cycle = Block.current_cycle init_block in
let cycles_to_wait =
init_state.constants.delegate_parameters_activation_delay
in
Cycle.(Block.current_cycle block >= add init_cycle cycles_to_wait)
| `And (cond1, cond2) ->
let stop1 = stopper cond1 in
let stop2 = stopper cond2 in
fun (block, state) ->
let b1 = stop1 (block, state) in
let b2 = stop2 (block, state) in
b1 && b2
in
stopper condition
in
log ~color:time_color "Fast forward to %s" to_
--> wait_cycle_f condition
--> log ~color:event_color "%s" done_
(** Wait until AI activates.
Fails if AI is not set to be activated in the future. *)
let wait_ai_activation =
wait_cycle_until `AI_activation
--> exec_unit (fun (block, state) ->
assert (State_ai_flags.AI.enabled block state) ;
Lwt_result_syntax.return_unit)
(** wait delegate_parameters_activation_delay cycles *)
let wait_delegate_parameters_activation =
wait_cycle_until `delegate_parameters_activation
let wait_n_cycles_f_es (n_cycles : t -> int tzresult Lwt.t) =
let open Lwt_result_syntax in
let condition ((init_block, _init_state) as t_init)
((current_block, _current_state) as _t_current) =
let* n = n_cycles t_init in
let init_cycle = Block.current_cycle init_block in
let current_cycle = Block.current_cycle current_block in
return Cycle.(current_cycle >= add init_cycle n)
in
wait_cycle_f_es condition
let wait_n_cycles_f (n_cycles : t -> int) =
let open Lwt_result_syntax in
let n_cycles n = return @@ n_cycles n in
wait_n_cycles_f_es n_cycles