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
module Wasm = Tezos_webassembly_interpreter
open Wasm_pvm_state.Internal_state
let version_for_protocol : Pvm_input_kind.protocol -> Wasm_pvm_state.version =
function
| Nairobi -> V1
| Oxford -> V2
| Proto_alpha -> V2
let link_finished (ast : Wasm.Ast.module_) offset =
offset >= Wasm.Ast.Vector.num_elements ast.it.imports
let eval_has_finished = function
| Eval {config = {step_kont = Wasm.Eval.(SK_Result _); _}; _} -> true
| Padding -> true
| Snapshot | Decode _ | Link _ | Init _ | Eval _ | Collect | Stuck _ -> false
let ticks_to_snapshot {current_tick; last_top_level_call; max_nb_ticks; _} =
let open Z in
max_nb_ticks - one - (current_tick - last_top_level_call)
let is_time_for_snapshot pvm_state =
Z.Compare.(ticks_to_snapshot pvm_state <= Z.zero)
let has_reboot_flag durable =
let open Lwt_syntax in
let+ allows_reboot =
Lwt.catch
(fun () -> Durable.(find_value durable Constants.reboot_flag_key))
(function exn -> raise exn)
in
allows_reboot <> None
let has_stuck_flag durable =
let open Lwt_syntax in
let+ stuck = Durable.(find_value durable Constants.stuck_flag_key) in
Option.is_some stuck
let has_upgrade_error_flag durable =
let open Lwt_syntax in
let+ error = Durable.(find_value durable Constants.upgrade_error_flag_key) in
Option.is_some error
let get_wasm_version {durable; _} =
let open Lwt_syntax in
let* cbv = Durable.find_value_exn durable Constants.version_key in
let+ bytes = Tezos_lazy_containers.Chunked_byte_vector.to_bytes cbv in
Data_encoding.Binary.of_bytes_exn Wasm_pvm_state.version_encoding bytes
let stack_size_limit = function
| Wasm_pvm_state.V0 -> 300
| V1 | V2 | V3 -> 60_000
let patch_flags_on_eval_successful durable =
let open Lwt_syntax in
let* has_stuck_flag = has_stuck_flag durable in
let* durable =
if has_stuck_flag then
Durable.(
delete
~edit_readonly:true
~kind:Directory
durable
Constants.stuck_flag_key)
else Lwt.return durable
in
let* has_upgrade_error_flag = has_upgrade_error_flag durable in
let+ durable =
if has_upgrade_error_flag then
Durable.(
delete
~edit_readonly:true
~kind:Directory
durable
Constants.upgrade_error_flag_key)
else Lwt.return durable
in
durable
let mark_for_reboot {reboot_counter; durable; _} =
let open Lwt_syntax in
let+ has_reboot_flag = has_reboot_flag durable in
if has_reboot_flag then
if Z.Compare.(reboot_counter <= Z.zero) then `Forcing_yield else `Reboot
else `Yielding
let has_fallback_kernel durable =
let open Lwt_syntax in
let* kernel_hash = Durable.hash ~kind:Value durable Constants.kernel_key in
let+ fallback_hash =
Durable.hash ~kind:Durable.Value durable Constants.kernel_fallback_key
in
Option.is_some fallback_hash && kernel_hash <> fallback_hash
let initial_boot_state () =
Decode
(Tezos_webassembly_interpreter.Decode.initial_decode_kont
~name:Constants.wasm_main_module_name)
let save_fallback_kernel durable =
let open Lwt.Syntax in
let* kernel_hash = Durable.hash ~kind:Value durable Constants.kernel_key in
let* kernel_fallback_hash =
Durable.hash ~kind:Value durable Constants.kernel_fallback_key
in
if kernel_hash <> kernel_fallback_hash then
Durable.copy_tree_exn
durable
~edit_readonly:true
Constants.kernel_key
Constants.kernel_fallback_key
else Lwt.return durable
let unsafe_next_tick_state ~version ~stack_size_limit host_funcs
({buffers; durable; tick_state; _} as pvm_state) =
let open Lwt_syntax in
let return ?(status = Running) ?(durable = durable) state =
Lwt.return (durable, state, status)
in
match tick_state with
| Stuck ((Decode_error _ | Init_error _ | Link_error _) as e) ->
let cause =
match e with
| Decode_error e -> Wasm_pvm_errors.Decode_cause e
| Link_error e -> Wasm_pvm_errors.Link_cause e
| Init_error e -> Wasm_pvm_errors.Init_cause e
| _ -> assert false
in
let* has_fallback = has_fallback_kernel durable in
if has_fallback then
let* durable =
Durable.copy_tree_exn
durable
Constants.kernel_fallback_key
Constants.kernel_key
in
let* durable =
Durable.write_value_exn
~edit_readonly:true
durable
Constants.upgrade_error_flag_key
0L
""
in
return ~durable Padding
else return ~status:Failing (Stuck (No_fallback_kernel cause))
| Stuck e -> return ~status:Failing (Stuck e)
| Snapshot -> return (initial_boot_state ())
| Collect ->
return
~status:Failing
(Stuck (Wasm_pvm_errors.invalid_state "Collect is a input tick"))
| Padding when is_time_for_snapshot pvm_state -> (
let* reboot_status = mark_for_reboot pvm_state in
match reboot_status with
| `Reboot -> return ~status:Reboot Snapshot
| `Forcing_yield -> return ~status:Forcing_yield Collect
| `Yielding -> return ~status:Yielding Collect)
| _ when is_time_for_snapshot pvm_state ->
return ~status:Failing (Stuck Too_many_ticks)
| Decode {module_kont = MKStop ast_module; _} ->
return
(Link
{
ast_module;
externs = Wasm.Instance.Vector.empty ();
imports_offset = 0l;
})
| Decode m ->
let* kernel = Durable.find_value_exn durable Constants.kernel_key in
let* m =
Tezos_webassembly_interpreter.Decode.module_step
~allow_floats:false
kernel
m
in
return (Decode m)
| Link {ast_module; externs; imports_offset}
when link_finished ast_module imports_offset ->
let self = Wasm.Instance.Module_key Constants.wasm_main_module_name in
let module_reg = Wasm.Instance.ModuleMap.create () in
return (Init {self; ast_module; init_kont = IK_Start externs; module_reg})
| Link {ast_module; externs; imports_offset} ->
let* {it = {module_name; item_name; _}; _} =
Wasm.Ast.Vector.get imports_offset ast_module.it.imports
in
if module_name = Constants.wasm_host_funcs_virual_module then
match Host_funcs.lookup_opt ~version item_name with
| Some extern ->
let externs, _ = Wasm.Ast.Vector.append extern externs in
return
(Link
{
ast_module;
externs;
imports_offset = Int32.succ imports_offset;
})
| None ->
return
~status:Failing
(Stuck (Wasm_pvm_errors.link_error `Item ~module_name ~item_name))
else
return
~status:Failing
(Stuck (Wasm_pvm_errors.link_error `Module ~module_name ~item_name))
| Init {self; ast_module = _; init_kont = IK_Stop; module_reg} -> (
let* module_inst =
Wasm.Instance.ModuleMap.get Constants.wasm_main_module_name module_reg
in
let* extern =
Lwt.catch
(fun () ->
let+ extern =
Wasm.Instance.NameMap.get
Constants.wasm_entrypoint
module_inst.Wasm.Instance.exports
in
Some extern)
(function
| Tezos_lazy_containers.Lazy_map.UnexpectedAccess -> return_none
| exn -> raise exn)
in
match extern with
| Some (Wasm.Instance.ExternFunc main_func) ->
let admin_instr' = Wasm.Eval.Invoke main_func in
let admin_instr = Wasm.Source.{it = admin_instr'; at = no_region} in
let config =
Wasm.Eval.config
~stack_size_limit
self
(Tezos_lazy_containers.Lazy_vector.Int32Vector.empty ())
(Tezos_lazy_containers.Lazy_vector.Int32Vector.singleton
admin_instr)
in
let* durable = save_fallback_kernel durable in
return ~durable (Eval {config; module_reg})
| _ ->
return
~status:Failing
(Stuck
(Wasm_pvm_errors.invalid_state
(Format.sprintf
"Invalid_module: no `%s` function exported"
Constants.wasm_entrypoint))))
| Init {self; ast_module; init_kont; module_reg} ->
let* init_kont =
Wasm.Eval.init_step
~stack_size_limit
~filter_exports:true
~check_module_exports:Exports_memory_0
~module_reg
~self
buffers
host_funcs
ast_module
init_kont
in
return (Init {self; ast_module; init_kont; module_reg})
| Padding -> return Padding
| Eval {config = {step_kont = Wasm.Eval.(SK_Trapped _msg); _}; _} ->
let* durable =
Durable.write_value_exn
~edit_readonly:true
durable
Constants.stuck_flag_key
0L
""
in
return ~durable Padding
| _ when eval_has_finished tick_state ->
let* durable = patch_flags_on_eval_successful durable in
return ~durable Padding
| Eval {config; module_reg} ->
let store = Durable.to_storage durable in
let* store', config =
Wasm.Eval.step ~host_funcs ~durable:store module_reg config buffers
in
let durable' = Durable.of_storage ~default:durable store' in
return ~durable:durable' (Eval {config; module_reg})
let exn_to_stuck pvm_state exn =
let error = Wasm_pvm_errors.extract_interpreter_error exn in
let wasm_error =
match error with
| `Interpreter error -> (
match pvm_state.tick_state with
| Decode _ -> Wasm_pvm_errors.Decode_error error
| Link _ -> Link_error error.Wasm_pvm_errors.raw_exception
| Init _ -> Init_error error
| Eval _ -> Eval_error error
| Snapshot | Stuck _ | Collect | Padding ->
Unknown_error error.raw_exception)
| `Unknown raw_exception -> Unknown_error raw_exception
in
Lwt.return (Stuck wasm_error)
let next_tick_state ~version ~stack_size_limit host_function_registry pvm_state
=
let open Lwt_syntax in
Lwt.catch
(fun () ->
unsafe_next_tick_state
~version
~stack_size_limit
host_function_registry
pvm_state)
(fun exn ->
let+ tick_state = exn_to_stuck pvm_state exn in
(pvm_state.durable, tick_state, Failing))
let next_last_top_level_call {current_tick; last_top_level_call; _} = function
| Forcing_yield | Yielding | Reboot -> Z.succ current_tick
| Failing | Running -> last_top_level_call
let next_reboot_counter {reboot_counter; maximum_reboots_per_input; _} status =
match status with
| Reboot -> Z.pred reboot_counter
| Forcing_yield | Yielding | Failing ->
Z.succ maximum_reboots_per_input
| Running -> reboot_counter
(** When successfully restarting the VM, we can remove the
[Too_many_reboot] flag if it exists. On the contrary, we create it
in case we are failing. *)
let patch_too_many_reboot_flag durable =
let open Lwt_syntax in
function
| Yielding ->
Durable.delete
~edit_readonly:true
~kind:Directory
durable
Constants.too_many_reboot_flag_key
| Forcing_yield ->
Durable.(
write_value_exn
~edit_readonly:true
durable
Constants.too_many_reboot_flag_key
0L
"")
| _ -> return durable
(** When rebooting, we can remove the [Reboot] flag (because it has
achieved its purpose). On the contrary, when [Yielding] or
[Forcing_yield], we set the flag, because we will want to reboot
once the inbox is loaded. *)
let patch_reboot_flag durable =
let open Lwt_syntax in
function
| Reboot ->
Durable.delete
~edit_readonly:true
~kind:Directory
durable
Constants.reboot_flag_key
| Forcing_yield | Yielding ->
Durable.(write_value_exn durable Constants.reboot_flag_key 0L "")
| _ -> return durable
(** When rebooting, we update the new [reboot_counter] exposed to the
kernel. *)
let patch_reboot_counter durable reboot_counter =
let open Lwt_syntax in
function
| Running | Failing -> return durable
| Reboot | Forcing_yield | Yielding ->
let* durable =
Durable.delete
~edit_readonly:true
~kind:Directory
durable
Constants.reboot_counter_key
in
Durable.write_value_exn
~edit_readonly:true
durable
Constants.reboot_counter_key
0L
Data_encoding.(
Binary.to_string_exn
Data_encoding_utils.Little_endian.int32
(Z.to_int32 reboot_counter))
(** Every time the kernel yields, we reset the input buffer. *)
let clean_up_input_buffer buffers =
let open Tezos_webassembly_interpreter in
function
| Forcing_yield | Yielding -> Input_buffer.reset buffers.Eval.input | _ -> ()
(** [compute_step pvm_state] does one computation step on [pvm_state].
Returns the new state.
*)
let compute_step_with_host_functions ~version ~stack_size_limit registry
pvm_state =
let open Lwt_syntax in
let* durable, tick_state, status =
next_tick_state ~version ~stack_size_limit registry pvm_state
in
let current_tick = Z.succ pvm_state.current_tick in
let last_top_level_call = next_last_top_level_call pvm_state status in
let reboot_counter = next_reboot_counter pvm_state status in
let* durable = patch_too_many_reboot_flag durable status in
let* durable = patch_reboot_flag durable status in
let* durable = patch_reboot_counter durable reboot_counter status in
let () = clean_up_input_buffer pvm_state.buffers status in
let pvm_state =
{
pvm_state with
tick_state;
durable;
current_tick;
last_top_level_call;
reboot_counter;
}
in
return pvm_state
let compute_step pvm_state =
let open Lwt_syntax in
let* version = get_wasm_version pvm_state in
let stack_size_limit = stack_size_limit version in
compute_step_with_host_functions
~version
~stack_size_limit
(Host_funcs.registry ~version ~write_debug:Noop)
pvm_state
let compute_step_with_debug ~write_debug pvm_state =
let open Lwt_syntax in
let* version = get_wasm_version pvm_state in
compute_step_with_host_functions
~version
~stack_size_limit:(stack_size_limit version)
(Host_funcs.registry ~version ~write_debug)
pvm_state
let input_request pvm_state =
match pvm_state.tick_state with
| Stuck (Decode_error _ | Init_error _ | Link_error _) ->
Wasm_pvm_state.No_input_required
| Stuck _ -> Wasm_pvm_state.Input_required
| Snapshot -> Wasm_pvm_state.No_input_required
| Collect -> Wasm_pvm_state.Input_required
| Eval {config; _} -> (
match Tezos_webassembly_interpreter.Eval.is_reveal_tick config with
| Some reveal -> Wasm_pvm_state.Reveal_required reveal
| None -> Wasm_pvm_state.No_input_required)
| _ -> Wasm_pvm_state.No_input_required
let is_top_level_padding pvm_state =
match pvm_state.tick_state with
| Padding -> not @@ is_time_for_snapshot pvm_state
| _ -> false
let measure_executed_ticks (transition : pvm_state -> pvm_state Lwt.t)
(initial_state : pvm_state) : (pvm_state * int64) Lwt.t =
let open Lwt.Syntax in
let open Z in
let+ final_state = transition initial_state in
let ticks_executed = final_state.current_tick - initial_state.current_tick in
(final_state, to_int64 ticks_executed)
let reveal_step payload pvm_state =
let open Lwt_syntax in
let return tick_state =
Lwt.return
{pvm_state with current_tick = Z.succ pvm_state.current_tick; tick_state}
in
match pvm_state.tick_state with
| Eval {config; module_reg} ->
let* config =
Tezos_webassembly_interpreter.Eval.reveal_step
Host_funcs.Aux.reveal
module_reg
payload
config
in
return (Eval {config; module_reg})
| Snapshot ->
return
(Stuck (Wasm_pvm_errors.invalid_state "No reveal expected during start"))
| Decode _ ->
return
(Stuck
(Wasm_pvm_errors.invalid_state "No reveal expected during decoding"))
| Link _ ->
return
(Stuck (Wasm_pvm_errors.invalid_state "No reveal expected during link"))
| Init _ ->
return
(Stuck
(Wasm_pvm_errors.invalid_state
"No reveal expected during initialization"))
| Collect ->
return
(Stuck
(Wasm_pvm_errors.invalid_state
"No reveal expected during collecting"))
| Stuck _ | Padding -> return pvm_state.tick_state
let compute_step_many_until ?(max_steps = 1L) ?reveal_builtins
?(write_debug = Builtins.Noop) should_continue pvm_state =
let open Lwt.Syntax in
assert (max_steps > 0L) ;
let* version = get_wasm_version pvm_state in
let stack_size_limit = stack_size_limit version in
let host_function_registry = Host_funcs.registry ~version ~write_debug in
let compute_step_with_reveal =
match reveal_builtins with
| Some reveal_builtins -> (
fun pvm_state ->
let info = input_request pvm_state in
match info with
| Reveal_required req ->
let* res = reveal_builtins req in
reveal_step (Bytes.of_string res) pvm_state
| _ ->
compute_step_with_host_functions
~version
~stack_size_limit
host_function_registry
pvm_state)
| None ->
compute_step_with_host_functions
~version
~stack_size_limit
host_function_registry
in
let rec go steps_left pvm_state =
let* continue = should_continue pvm_state in
if steps_left > 0L && continue then
if is_top_level_padding pvm_state then
let bulk_ticks =
Z.(min (ticks_to_snapshot pvm_state) (of_int64 steps_left))
in
let pvm_state =
{
pvm_state with
tick_state = Padding;
current_tick = Z.add pvm_state.current_tick bulk_ticks;
}
in
go (Int64.sub steps_left (Z.to_int64 bulk_ticks)) pvm_state
else
let* pvm_state = compute_step_with_reveal pvm_state in
go (Int64.pred steps_left) pvm_state
else Lwt.return pvm_state
in
let one_or_more_steps pvm_state =
let* pvm_state =
compute_step_with_host_functions
~version
~stack_size_limit
host_function_registry
pvm_state
in
go (Int64.pred max_steps) pvm_state
in
measure_executed_ticks one_or_more_steps pvm_state
let should_compute ?reveal_builtins pvm_state =
let input_request_val = input_request pvm_state in
match input_request_val with
| Input_required -> false
| No_input_required -> true
| Reveal_required _ -> Option.is_some reveal_builtins
let compute_step_many ?reveal_builtins ?write_debug ?(stop_at_snapshot = false)
~max_steps pvm_state =
compute_step_many_until
~max_steps
?reveal_builtins
?write_debug
(fun pvm_state ->
Lwt.return
(should_compute ?reveal_builtins pvm_state
&& ((not stop_at_snapshot) || pvm_state.tick_state <> Snapshot)))
pvm_state
let update_output_buffer pvm_state level =
let output_buffer = pvm_state.buffers.Wasm.Eval.output in
if Wasm.Output_buffer.is_initialized output_buffer then
Wasm.Output_buffer.move_outbox_forward output_buffer
else Wasm.Output_buffer.initialize_outbox output_buffer level
let set_input_step input_info message pvm_state =
let open Lwt_syntax in
let open Wasm_pvm_state in
let {inbox_level; message_counter} = input_info in
let raw_level = Bounded.Non_negative_int32.to_value inbox_level in
let return ?(durable = pvm_state.durable) x = Lwt.return (durable, x) in
let return_stuck state_name =
return
(Stuck
(Wasm_pvm_errors.invalid_state
@@ Format.sprintf "No input required during %s" state_name))
in
let next_tick_state () =
match pvm_state.tick_state with
| Collect -> (
let* () =
Wasm.Input_buffer.(
enqueue
pvm_state.buffers.input
{raw_level; message_counter; payload = String.to_bytes message})
in
match Pvm_input_kind.from_raw_input message with
| Internal End_of_level -> return Padding
| Internal (Protocol_migration proto) ->
let* durable =
Durable.set_value_exn
~edit_readonly:true
pvm_state.durable
Constants.version_key
(Data_encoding.Binary.to_string_exn
Wasm_pvm_state.version_encoding
(version_for_protocol proto))
in
return ~durable Collect
| Internal Start_of_level ->
update_output_buffer pvm_state raw_level ;
return Collect
| _ -> return Collect)
| Stuck _ -> return pvm_state.tick_state
| Snapshot -> return_stuck "start"
| Decode _ -> return_stuck "decoding"
| Link _ -> return_stuck "link"
| Init _ -> return_stuck "initialization"
| Eval _ -> return_stuck "evaluation"
| Padding -> return_stuck "padding"
in
let+ durable, tick_state =
Lwt.catch next_tick_state (fun exn ->
let+ tick_state = exn_to_stuck pvm_state exn in
(pvm_state.durable, tick_state))
in
{
pvm_state with
durable;
tick_state;
current_tick = Z.succ pvm_state.current_tick;
last_input_info = Some input_info;
}
let get_output output_info output =
let open Lwt_syntax in
let open Wasm_pvm_state in
let {outbox_level; message_index} = output_info in
let outbox_level = Bounded.Non_negative_int32.to_value outbox_level in
let+ payload =
Wasm.Output_buffer.get_message
output
Wasm.Output_buffer.{outbox_level; message_index}
in
Bytes.to_string payload
let get_info ({current_tick; last_input_info; _} as pvm_state) =
let open Lwt_syntax in
let input_request = input_request pvm_state in
return
@@ Wasm_pvm_state.
{current_tick; last_input_read = last_input_info; input_request}
module Internal_for_tests = struct
let compute_step_many_with_hooks ?reveal_builtins ?write_debug
?after_fast_exec:_ =
compute_step_many ?reveal_builtins ?write_debug
end