Revision cbda8dd964df0dfd3415209db7ff9d1d9e91c1cf authored by Alain Mebsout on 08 July 2022, 08:12:22 UTC, committed by Alain Mebsout on 06 September 2022, 14:48:23 UTC
1 parent dcc833b
vdf_test.ml
(*****************************************************************************)
(* *)
(* Open Source License *)
(* Copyright (c) 2022 Nomadic Labs <contact@nomadic-labs.com> *)
(* *)
(* Permission is hereby granted, free of charge, to any person obtaining a *)
(* copy of this software and associated documentation files (the "Software"),*)
(* to deal in the Software without restriction, including without limitation *)
(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)
(* and/or sell copies of the Software, and to permit persons to whom the *)
(* Software is furnished to do so, subject to the following conditions: *)
(* *)
(* The above copyright notice and this permission notice shall be included *)
(* in all copies or substantial portions of the Software. *)
(* *)
(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)
(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)
(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)
(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)
(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)
(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)
(* DEALINGS IN THE SOFTWARE. *)
(* *)
(*****************************************************************************)
(* Testing
-------
Component: Basic
Invocation: dune exec tezt/tests/main.exe -- --file vdf_test.ml
Subject: Test that an instance of the VDF computation daemon can
compute and inject the VDF at the right time and that the
[get_seed_status] RPC reports the seed computation
status as expected throughout the cycle.
*)
(* Stripped down version of [Seed_storage.seed_computation_status] *)
type seed_computation_status =
| Nonce_revelation_stage
| Vdf_revelation_stage
| Computation_finished
let get_seed_computation_status ?(info = false) client level =
let* seed_status =
RPC.Seed.get_seed_status ~block:(string_of_int level) client
in
let status =
match List.map fst (JSON.as_object seed_status) with
| ["nonce_revelation_stage"] -> Nonce_revelation_stage
| ["seed_discriminant"; "seed_challenge"] -> Vdf_revelation_stage
| ["computation_finished"] -> Computation_finished
| _ -> assert false
in
let pp_status = function
| Nonce_revelation_stage -> "nonce revelation stage"
| Vdf_revelation_stage -> "vdf revelation stage"
| Computation_finished -> "computation finished"
in
if info then Log.info "At level %d we are in %s" level (pp_status status) ;
return status
let assert_computation_status ?(info = false) client level status =
let* current_status = get_seed_computation_status ~info client level in
return @@ assert (current_status = status)
let assert_level actual expected =
if actual <> expected then (
Log.info "Expected to be at level %d, actually at level %d" expected actual ;
assert false)
else ()
let init_vdf_event_listener vdf_baker injected =
Vdf.on_event vdf_baker (fun Vdf.{name; _} ->
if name = "vdf_revelation_injected.v0" then injected := true)
(* Bakes at most `max_level - min_starting_level + 1` blocks, starting from
* a level not lower than `min_starting_level` and finishing exactly
* at `max_level`.
* Optionally checks that the computation status is never `Computation_finished`
* (used when checking a cycle with no VDF daemon running) *)
let bake_until min_starting_level max_level client node status_check =
let check_status level =
if status_check then
let* current_status = get_seed_computation_status client level in
return @@ assert (current_status <> Computation_finished)
else Lwt.return_unit
in
let rec loop level =
if level < max_level then
let* () = Client.bake_for client in
let* level = Node.wait_for_level node (level + 1) in
let* () = check_status level in
loop level
else return level
in
let* level = Node.wait_for_level node min_starting_level in
let* level = loop level in
let* level = Node.wait_for_level node level in
assert_level level max_level ;
return level
(* Checks that we are in the last block of the nonce revelation period,
* sets an event handler for the VDF revelation operation, then bakes
* the whole of the VDF revelation period. Checks that (at least one)
* VDF revelation operation has been injected *)
let bake_vdf_revelation_period level max_level client node injected =
injected := false ;
let* () = assert_computation_status client level Nonce_revelation_stage in
let* () = Client.bake_for client in
let* level = Node.wait_for_level node (level + 1) in
let* () = assert_computation_status client level Vdf_revelation_stage in
let* _level = bake_until level max_level client node false in
return @@ assert !injected
let check_cycle (blocks_per_cycle, nonce_revelation_threshold) starting_level
client node injected =
(* Check that at the beginning of the cycle we are in the nonce
revelation period *)
let* level = Node.wait_for_level node starting_level in
assert_level level starting_level ;
let* () = assert_computation_status client level Nonce_revelation_stage in
(* Bake until the end of the nonce revelation period *)
let* level =
bake_until
level
(starting_level + nonce_revelation_threshold - 1)
client
node
false
in
(* Bake throughout the VDF revelation period, checking that a VDF revelation
* operation is injected at some point. Check that the seed computation
* status is set to [Computation_finished] at the end of the period. *)
let* () =
bake_vdf_revelation_period
level
(starting_level + blocks_per_cycle - 1)
client
node
injected
in
let* level =
Node.wait_for_level node (starting_level + blocks_per_cycle - 1)
in
assert_level level (starting_level + blocks_per_cycle - 1) ;
let* () = assert_computation_status client level Computation_finished in
(* Bake one more block, and check that it is the first block of
* the following cycle *)
let* () = Client.bake_for client in
let* level = Node.wait_for_level node (starting_level + blocks_per_cycle) in
assert_level level (starting_level + blocks_per_cycle) ;
return level
let check_n_cycles n constants starting_level client node injected =
let rec loop n level =
if n > 0 then
let* level = check_cycle constants level client node injected in
loop (n - 1) level
else return level
in
loop n starting_level
(* In total, [test_vdf] bakes `2 * (n_cycles + 1)` cycles *)
let n_cycles = 5
let test_vdf : Protocol.t list -> unit =
(* [check_n_cycles] requires that the starting_level is the beginning of
* a new cycle. Since that's not the case when the VDF daemon is launched,
* the first cycle is checked here directly and the following cycles are
* checked using [check_n_cycles] *)
Protocol.register_test ~__FILE__ ~title:"VDF daemon" ~tags:["vdf"]
@@ fun protocol ->
let* node = Node.init [Synchronisation_threshold 0; Private_mode] in
let* client = Client.init ~endpoint:(Node node) () in
let* () = Client.activate_protocol ~protocol client
and* vdf_baker = Vdf.init ~protocol node in
(* Track whether a VDF revelation has been injected during the correct period.
* It is set to `false` at the beginning of [bake_vdf_revelation_period] and
* to `true` by a listener for `vdf_revelation_injected` events. *)
let injected = ref false in
init_vdf_event_listener vdf_baker injected ;
let* constants =
RPC.Client.call client @@ RPC.get_chain_block_context_constants ()
in
let* blocks_per_cycle =
return JSON.(constants |-> "blocks_per_cycle" |> as_int)
in
let* nonce_revelation_threshold =
return JSON.(constants |-> "nonce_revelation_threshold" |> as_int)
in
(* Bake and check that we are in the nonce revelation period *)
let* () = Client.bake_for client in
let* level = Node.wait_for_level node 1 in
let* () = assert_computation_status client level Nonce_revelation_stage in
(* Bake until the end of the nonce revelation period *)
let* level = bake_until level nonce_revelation_threshold client node false in
(* Bake until the end of the cycle, checking that a VDF revelation
operation was injected during the VDF revelation period and that
the computation status is set to finished at the end of the cycle *)
let* () =
bake_vdf_revelation_period level blocks_per_cycle client node injected
in
let* level = Node.wait_for_level node blocks_per_cycle in
let* () = assert_computation_status client level Computation_finished in
(* Bake, check that we are in the first block of the following cycle *)
let* () = Client.bake_for client in
let* level = Node.wait_for_level node (blocks_per_cycle + 1) in
assert_level level (blocks_per_cycle + 1) ;
(* Check correct behaviour for the following cycles *)
let* level =
check_n_cycles
n_cycles
(blocks_per_cycle, nonce_revelation_threshold)
level
client
node
injected
in
(* Kill the VDF daemon and bake one cycle with no VDF submission.
Check that since no VDF daemon is running the computation status is
never "finished" in this cycle. *)
let* () = Vdf.terminate vdf_baker in
assert_level level ((blocks_per_cycle * (n_cycles + 1)) + 1) ;
let* level =
bake_until level ((blocks_per_cycle * (n_cycles + 2)) + 1) client node true
in
(* Restart a VDF daemon and check correct behaviour after a RANDAO cycle *)
let* vdf_baker = Vdf.init ~protocol node in
init_vdf_event_listener vdf_baker injected ;
let* _level =
check_n_cycles
n_cycles
(blocks_per_cycle, nonce_revelation_threshold)
level
client
node
injected
in
Vdf.terminate vdf_baker
let register ~protocols = test_vdf protocols
![swh spinner](/static/img/swh-spinner.gif)
Computing file changes ...