diff --git a/.gitlab/ci/templates.yml b/.gitlab/ci/templates.yml index 6d6428f28f3fbdaa3e4f86c44c7aa673efe00fdc..7604caff34493105df8752c75bcf4eb9e96afc6d 100644 --- a/.gitlab/ci/templates.yml +++ b/.gitlab/ci/templates.yml @@ -2,7 +2,7 @@ variables: # /!\ CI_REGISTRY is overriden to use a private Docker registry mirror in AWS ECR # in GitLab namespaces `nomadic-labs` and `tezos` ## This value MUST be the same as `opam_repository_tag` in `scripts/version.sh` - build_deps_image_version: fba11a81e22ba2949ac9d7f5608966fdc5aa6712 + build_deps_image_version: e32bc2375404721076cc9d2e7b3fa0fb8702628b build_deps_image_name: "${CI_REGISTRY}/tezos/opam-repository" GIT_STRATEGY: fetch GIT_DEPTH: "1" diff --git a/docs/alpha/proof_of_stake.rst b/docs/alpha/proof_of_stake.rst index 5475a41f4c8fde7e61768501626af5589a7b1057..3ccddf8a820c58165f2213e1e610b425c3b3e63e 100644 --- a/docs/alpha/proof_of_stake.rst +++ b/docs/alpha/proof_of_stake.rst @@ -80,51 +80,7 @@ To each cycle is associated a random number called the seed. This seed is used within its cycle to generate pseudo-random values in the protocol, in particular for selecting delegates to participate in consensus. -The seed is the output of a simple multi-party randomness protocol -(similar in spirit with the `RANDAO protocol `_): -at each cycle end, a new seed is computed as the hash of the previous -seed and of *nonces* (for "number used only once") provided by -delegates during that cycle and stored on the chain. -Nonces are supposed to be random. -To ensure that participants do not adaptively chose their -nonces and therefore that the seed is not easily biased, -participants in the protocol use a ''commit and reveal'' scheme: in the -previous cycle, they first commit to nonces and they only reveal their -committed nonces later, in the current cycle. - -We make the assumption that at least one participant is honest, that -is, it has indeed chosen a random value and this values was revealed. This is a necessary -condition for the seed to be random. The randomness could however -be biased as this protocol suffers from the following low-impact weakness: -if a malicious participant can make sure she is the last revealer, -then she can choose to reveal or not its committed value and can -thus choose between two different predetermined seeds. - -Concretely, the random seed for cycle -``n`` is a 256-bit long number computed at the very end of cycle ``n-1-PRESERVED_CYCLES`` from -nonces to which delegates commit during cycle ``n-2-PRESERVED_CYCLES``. Every -``BLOCKS_PER_COMMITMENT`` levels, the corresponding block contains a nonce commitment. -More precisely, a block contains a commitment if and only if its cycle -position modulo ``BLOCKS_PER_COMMITMENT`` is ``BLOCKS_PER_COMMITMENT - 1``. -The nonce is a 256-bit number generated by the block proposer and its commitment is included in -the block header. The commitment is simply the hash of the nonce. - -The committed nonce must be revealed by the original block proposer -during cycle ``n-1-PRESERVED_CYCLES`` under penalty of forfeiting all of its expected -endorsing rewards for that cycle. The associated security -deposit is not affected. - -A *nonce revelation* is an operation and multiple nonce revelations can thus be -included in a block. A reward ``SEED_NONCE_REVELATION_TIP`` is given for -including a revelation. Revelations are free operations which do not compete -with transactions for block space. Up to ``MAX_ANON_OPS_PER_BLOCK`` revelations, -wallet activations and denunciations can be contained in any given block. - -The seed for cycle ``n`` is the bitstring obtained by iterating -through the nonces revealed in cycle ``n-1`` as follows: initially it -is the seed of cycle ``n-1``; at each iteration, the new bitstring is -the hash of the concatenation of the previous bitstring with the iterated -revealed nonce. +For more information on randomness generation, see :doc:`randomness-generation`. .. _snapshots_alpha: @@ -208,12 +164,6 @@ Proof-of-stake parameters - 8192 blocks * - ``PRESERVED_CYCLES`` - 5 cycles - * - ``BLOCKS_PER_COMMITMENT`` - - 64 blocks - * - ``MAX_ANON_OPS_PER_BLOCK`` - - 132 revelations - * - ``SEED_NONCE_REVELATION_TIP`` - - 1/8 ꜩ * - ``TOKENS_PER_ROLL`` - 6,000 ꜩ * - ``BLOCKS_PER_STAKE_SNAPSHOT`` diff --git a/docs/alpha/protocol.rst b/docs/alpha/protocol.rst index 05ac61a2d07057b623a6972bd108917176e7654b..1e263d2cb0458e77bdef2bfd3bc597c72d81f7a5 100644 --- a/docs/alpha/protocol.rst +++ b/docs/alpha/protocol.rst @@ -21,6 +21,11 @@ Sapling, etc), and some details about its implementation. proof_of_stake +.. toctree:: + :maxdepth: 2 + + randomness_generation + .. toctree:: :maxdepth: 2 diff --git a/docs/alpha/randomness_generation.rst b/docs/alpha/randomness_generation.rst new file mode 100644 index 0000000000000000000000000000000000000000..190a284258fc6a3561947d392cb13a31eb5b76d4 --- /dev/null +++ b/docs/alpha/randomness_generation.rst @@ -0,0 +1,150 @@ +Randomness generation +===================== + +Overview +-------- +This document presents the randomness generation protocol, used to select the +delegates to participate in the consensus, as well as the cryptographic +primitives used. + +Cryptographic primitives +------------------------ +We introduce RANDAO and Verifiable Delay Functions, two cryptographic +protocols for generating random values. + +RANDAO +^^^^^^ + +RANDAO is a simple multi-party protocol used to generate a random seed based +on a ''commit and reveal'' scheme (similar in spirit with the +`RANDAO protocol `_). + +During the setup of the protocol, the cryptographic primitives and security +parameters are chosen. The members participating in the protocol can also be +chosen at this stage. + +During the first phase (the "commitment" phase) each party commits to a +random value, called the nonce, and publishes the commitment. Typically, a hash +function is used for committing. + +During the second phase, (the "nonce revelation" phase) each party +reveals their nonce. The revealed nonces are then verified with respect to the +commitments. Typically, this means checking that the hash of each revealed nonce +is equal to its corresponding commitment. If the verification fails, the nonce +is discarded, otherwise it is accepted. + +Once the revelation phase is finished, nonces are combined to generate the +seed. More precisely, the nonces are hashed together in the same order as the +commitment publication. In the case of a rolling RANDAO, the previous seed may +be used to initilialise the hash. + +We make the assumption that at least one participant is honest, that is, it +has indeed chosen a random value and this values was revealed. This is a +necessary condition for the seed to be random. The randomness could however be +biased as this protocol suffers from the following low-impact weakness: +if a malicious participant can make sure she is the last revealer, then she +can choose whether to reveal its committed value, effectively choosing between +two different predetermined seeds. + +Verifiable Delay Function +^^^^^^^^^^^^^^^^^^^^^^^^^ + +Verifiable Delay Functions, also called VDF, are a recent cryptographic +primitive formalised in 2018. They can be seen as a trapdoor-less timelock: +the goal of VDF is making sure a party cannot compute a value before a +specific time. + +This new cryptographic building block is based on modular squaring in a group +of unknown order (e.g. class groups or MPC-generated RSA groups) that is +believed to be expensive and hard to parallelize. + +More precisely, the goal of a VDF is for a user to compute a certain value +h = g^2^T mod N ∈ G and a proof of correctness π_h by recursive modular +squarings of h. The variables g, h and T are respectively called the challenge, +solution, and difficulfy parameter. The main difference between VDF and +timelocks is that the latter offers a backdoor to efficiently generate the +challenge from the solution. + +To this day, two main schemes exist for generating the VDF proofs: +`Wesolowski `_ and +`Pietrzak `_. +The former presents shorter proofs and is based on a stronger security +assumption (adaptive root assumption) while the latter is computationally +cheaper and based on a weaker security assumption (low order assumption). + +Protocol +-------- + +Randomness generation overview +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The randomness generation can be summed up as follows. We first use RANDAO to +produce biasable entropy which is used as a VDF challenge to generate an +unbiasable seed (given the adversary cannot compute the VDF before the reveal +time ends). To ensure liveness, we fallback to RANDAO entropy if no VDF output +was published and verified on-chain. + +Concretely, the random seed for cycle ``n`` is a 256-bit long number computed +at the end of cycle ``n-1-PRESERVED_CYCLES``. It is the VDF output (or, in its +absence, the RANDAO output) computed from nonces to which delegates commit +during cycle ``n-2-PRESERVED_CYCLES``. + +Every ``BLOCKS_PER_COMMITMENT`` levels, the corresponding block contains a +nonce commitment. More precisely, a block contains a commitment if and only if +its cycle position modulo ``BLOCKS_PER_COMMITMENT`` is +``BLOCKS_PER_COMMITMENT - 1``. The nonce is a 256-bit number generated by the +block proposer and its commitment is included in the block header. The +commitment is simply the hash of the nonce. + +The committed nonce must be revealed by the original block proposer during the +nonce revelation phase, that is during the first ``NONCE_REVELATION_THRESHOLD`` +blocks, of cycle ``n-1-PRESERVED_CYCLES`` under penalty of forfeiting all of +its expected endorsing rewards for that cycle. The associated security deposit +and baking rewards are not affected. The RANDAO output is then computed and +stored on-chain as the temporary seed for cycle ``n``. The RANDAO output is the +bitstring obtained by iterating through the nonces revealed in cycle ``n-1`` as +follows: initially it is the seed of cycle ``n-1``; at each iteration, the new +bitstring is the hash of the concatenation of the previous bitstring with the +iterated revealed nonce. + +A *nonce revelation* is an operation and multiple nonce revelations can thus be +included in a block. A reward ``SEED_NONCE_REVELATION_TIP`` is given for +including a revelation. Revelations are free operations which do not compete +with transactions for block space. Up to ``MAX_ANON_OPS_PER_BLOCK`` revelations, +wallet activations and denunciations can be contained in any given block. + +During the rest of the cycle, informally called the VDF revelation period, any +party can query the protocol for the *seed computation status* to compute the +VDF solution and publish it on-chain together with a proof of randomness. +If the verification of the solution and proof succeeds, the seed for cycle +``n`` is then updated with the solution: its value is set to be the hash of +the RANDAO output and the solution. + +A *VDF revelation* is an operation. A reward ``SEED_NONCE_REVELATION_TIP`` is +given for the first correct VDF revelation, subsequent VDF revelation +operations being discarded. + +.. _rg_constants_alpha: + +Randomness generation parameters +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. list-table:: + :widths: 55 25 + :header-rows: 1 + + * - Parameter name + - Parameter value + * - ``BLOCKS_PER_COMMITMENT`` + - 64 blocks + * - ``NONCE_REVELATION_THRESHOLD`` + - 64 blocks + * - ``MAX_ANON_OPS_PER_BLOCK`` + - 132 revelations + * - ``SEED_NONCE_REVELATION_TIP`` + - 1/8 ꜩ + * - ``VDF_DIFFICULTY`` + - 1,000,000,000 + +The variables ``BLOCKS_PER_CYCLE`` and ``PRESERVED_CYCLES`` are already defined +in the :doc:`proof of stake ` page. diff --git a/manifest/main.ml b/manifest/main.ml index f7ff3362ff0be40d12ad95984051ae737cd7abbb..56a50ea78b487639fbe2937caeb5f54e397ae1f3 100644 --- a/manifest/main.ml +++ b/manifest/main.ml @@ -330,6 +330,9 @@ let zarith_stubs_js = external_lib ~js_compatible:true "zarith_stubs_js" V.True let ledgerwallet_tezos = vendored_lib "ledgerwallet-tezos" +let vdf = + external_lib ~js_compatible:true "class_group_vdf" V.(at_least "0.0.2") + (* INTERNAL LIBS *) let tezos_test_helpers = @@ -1811,6 +1814,7 @@ protocols.|} zarith_stubs_js; bls12_381; plonk; + vdf; ringo; ringo_lwt; tezos_base |> open_ ~m:"TzPervasives"; diff --git a/opam/tezos-protocol-environment.opam b/opam/tezos-protocol-environment.opam index e444f8b44cd19fd1e61ae50e6281964759771a0b..e2d5e44a88ff2f7703e6720dd75fd88b3abfecdb 100644 --- a/opam/tezos-protocol-environment.opam +++ b/opam/tezos-protocol-environment.opam @@ -19,6 +19,7 @@ depends: [ "tezos-plonk" { >= "0.1.0" } "zarith" { >= "1.12" & < "1.13" } "zarith_stubs_js" + "class_group_vdf" { >= "0.0.2" } "ringo" { >= "0.9" } "ringo-lwt" { >= "0.9" } "tezos-base" diff --git a/scripts/install_build_deps.raw.sh b/scripts/install_build_deps.raw.sh index d47cddb3bd01bbaf91d23654f9adf45dec3ca8c4..cb1085ecaee9a890ecacc9b8db6ca7ff5792614f 100755 --- a/scripts/install_build_deps.raw.sh +++ b/scripts/install_build_deps.raw.sh @@ -25,7 +25,7 @@ export OPAMYES="${OPAMYES:=true}" # Note that install_build_deps.sh calls install_build_deps.rust.sh # which checks whether Rust is installed with the right version and explains how # to install it if needed, so using opam depext is redundant anyway. -conf_packages="conf-gmp conf-libev conf-pkg-config conf-hidapi conf-autoconf conf-zlib" #conf-rust +conf_packages="conf-gmp conf-libev conf-pkg-config conf-hidapi conf-autoconf conf-zlib conf-g++" #conf-rust # Opam < 2.1 uses opam-depext as a plugin, later versions provide the option # `--depext-only`: diff --git a/scripts/version.sh b/scripts/version.sh index 41a5d2e7b2a6e96d54590e4e20df0c1e6e7478e3..3ba01323dce6f735d054fddcbf21674a6b08839a 100755 --- a/scripts/version.sh +++ b/scripts/version.sh @@ -25,7 +25,7 @@ export full_opam_repository_tag=bf63beadbea0c69b499037cd707fd607ed844f0d ## opam_repository is an additional, tezos-specific opam repository. ## This value MUST be the same as `build_deps_image_version` in `.gitlab/ci/templates.yml export opam_repository_url=https://gitlab.com/tezos/opam-repository -export opam_repository_tag=fba11a81e22ba2949ac9d7f5608966fdc5aa6712 +export opam_repository_tag=e32bc2375404721076cc9d2e7b3fa0fb8702628b export opam_repository_git=$opam_repository_url.git export opam_repository=$opam_repository_git\#$opam_repository_tag diff --git a/src/lib_protocol_environment/dune b/src/lib_protocol_environment/dune index 7f0f6b50e9ad96b375dc723b4ed4110c13fdfebd..188734a9fc291daaa155e21b6912e31ccd73f4d2 100644 --- a/src/lib_protocol_environment/dune +++ b/src/lib_protocol_environment/dune @@ -10,6 +10,7 @@ zarith_stubs_js bls12-381 tezos-plonk + class_group_vdf ringo ringo-lwt tezos-base diff --git a/src/lib_protocol_environment/environment_V6.ml b/src/lib_protocol_environment/environment_V6.ml index 9239434a28d41b3aa4baf297f8a22889ceb06c0c..12e3fc0f6492d726901bf07b001fd121093bc06b 100644 --- a/src/lib_protocol_environment/environment_V6.ml +++ b/src/lib_protocol_environment/environment_V6.ml @@ -285,6 +285,7 @@ struct module Signature = Signature module Pvss_secp256k1 = Pvss_secp256k1 module Timelock = Timelock + module Vdf = Class_group_vdf.Vdf_self_contained module S = struct module type T = Tezos_base.S.T diff --git a/src/lib_protocol_environment/sigs/v6.in.ml b/src/lib_protocol_environment/sigs/v6.in.ml index 8bca0a4fdd42da7c12159b19ac5f2f4510279f8d..8e6cd775e6cefe69ac37be6a7b36021a438f02f1 100644 --- a/src/lib_protocol_environment/sigs/v6.in.ml +++ b/src/lib_protocol_environment/sigs/v6.in.ml @@ -112,6 +112,8 @@ module type T = sig module Timelock : [%sig "v6/timelock.mli"] [@@coq_plain_module] + module Vdf : [%sig "v6/vdf.mli"] [@@coq_plain_module] + module Micheline : [%sig "v6/micheline.mli"] [@@coq_plain_module] module Block_header : [%sig "v6/block_header.mli"] [@@coq_plain_module] diff --git a/src/lib_protocol_environment/sigs/v6.ml b/src/lib_protocol_environment/sigs/v6.ml index a95dacf098c6e2dcae15c6be2cb875eb86ee3f48..ad963f449467df70adb2f620cdb6aa87ca5150d0 100644 --- a/src/lib_protocol_environment/sigs/v6.ml +++ b/src/lib_protocol_environment/sigs/v6.ml @@ -9677,6 +9677,94 @@ val open_chest : chest -> chest_key -> time:int -> opening_result val get_plaintext_size : chest -> int end # 113 "v6.in.ml" + [@@coq_plain_module] + + module Vdf : sig +# 1 "v6/vdf.mli" +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs, *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +(** Size of a group element, also called form, in bytes *) +val form_size_bytes : int + +(** Size of the class group discriminant in bytes *) +val discriminant_size_bytes : int + +(** Class group discriminant, prime number uniquely defining a class group *) +type discriminant + +(** VDF challenge *) +type challenge + +(** VDF result *) +type result + +(** VDF proof *) +type proof + +(** VDF difficulty, that is log of the number of group element compositions + done in the prover *) +type difficulty = Int64.t + +val discriminant_to_bytes : discriminant -> bytes + +val discriminant_of_bytes_opt : bytes -> discriminant option + +val challenge_to_bytes : challenge -> bytes + +val challenge_of_bytes_opt : bytes -> challenge option + +val result_to_bytes : result -> bytes + +val result_of_bytes_opt : bytes -> result option + +val proof_to_bytes : proof -> bytes + +val proof_of_bytes_opt : bytes -> proof option + +(** [generate_discriminant ?seed size], function generating a + discriminant/group *) +val generate_discriminant : ?seed:Bytes.t -> int -> discriminant + +(** [generate_challenge discriminant seed], function generating a class group + element used as a VDF challenge *) +val generate_challenge : discriminant -> Bytes.t -> challenge + +(** [prove_vdf discriminant challenge difficulty], function taking a class + group/discriminant, a vdf challenge and a difficulty and returning a vdf + result and proof *) +val prove : discriminant -> challenge -> difficulty -> result * proof + +(** [verify_vdf discriminant challenge difficulty result proof] function taking + a class group/discriminant, a vdf challenge, difficulty, result and proof and + returning true if the proof verifies else false + + @raise Invalid_argument when inputs are invalid *) +val verify : discriminant -> challenge -> difficulty -> result -> proof -> bool +end +# 115 "v6.in.ml" [@@coq_plain_module] module Micheline : sig @@ -9736,7 +9824,7 @@ val annotations : ('l, 'p) node -> string list val strip_locations : (_, 'p) node -> 'p canonical end -# 115 "v6.in.ml" +# 117 "v6.in.ml" [@@coq_plain_module] module Block_header : sig @@ -9793,7 +9881,7 @@ type t = {shell : shell_header; protocol_data : bytes} include S.HASHABLE with type t := t and type hash := Block_hash.t end -# 117 "v6.in.ml" +# 119 "v6.in.ml" [@@coq_plain_module] module Bounded : sig @@ -9871,7 +9959,7 @@ module Int32 : sig module NonNegative : S end end -# 119 "v6.in.ml" +# 121 "v6.in.ml" [@@coq_plain_module] module Fitness : sig @@ -9905,7 +9993,7 @@ end compared in a lexicographical order (longer list are greater). *) include S.T with type t = bytes list end -# 121 "v6.in.ml" +# 123 "v6.in.ml" [@@coq_plain_module] module Operation : sig @@ -9949,7 +10037,7 @@ type t = {shell : shell_header; proto : bytes} include S.HASHABLE with type t := t and type hash := Operation_hash.t end -# 123 "v6.in.ml" +# 125 "v6.in.ml" [@@coq_plain_module] module Context : sig @@ -10586,7 +10674,7 @@ module Cache : and type key = cache_key and type value = cache_value end -# 125 "v6.in.ml" +# 127 "v6.in.ml" [@@coq_plain_module] module Updater : sig @@ -10891,7 +10979,7 @@ end not complete until [init] in invoked. *) val activate : Context.t -> Protocol_hash.t -> Context.t Lwt.t end -# 127 "v6.in.ml" +# 129 "v6.in.ml" [@@coq_plain_module] module RPC_context : sig @@ -11046,7 +11134,7 @@ val make_opt_call3 : 'i -> 'o option shell_tzresult Lwt.t end -# 129 "v6.in.ml" +# 131 "v6.in.ml" [@@coq_plain_module] module Wasm_2_0_0 : sig @@ -11099,7 +11187,7 @@ module Make val get_info : Tree.tree -> info Lwt.t end end -# 131 "v6.in.ml" +# 133 "v6.in.ml" [@@coq_plain_module] module Plonk : sig @@ -11184,6 +11272,6 @@ val verify_multi_circuits : proof -> bool end -# 133 "v6.in.ml" +# 135 "v6.in.ml" [@@coq_plain_module] end diff --git a/src/lib_protocol_environment/sigs/v6/vdf.mli b/src/lib_protocol_environment/sigs/v6/vdf.mli new file mode 100644 index 0000000000000000000000000000000000000000..a0fa235b8a80e090f0d8dab3e5386bd5903492e8 --- /dev/null +++ b/src/lib_protocol_environment/sigs/v6/vdf.mli @@ -0,0 +1,82 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs, *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +(** Size of a group element, also called form, in bytes *) +val form_size_bytes : int + +(** Size of the class group discriminant in bytes *) +val discriminant_size_bytes : int + +(** Class group discriminant, prime number uniquely defining a class group *) +type discriminant + +(** VDF challenge *) +type challenge + +(** VDF result *) +type result + +(** VDF proof *) +type proof + +(** VDF difficulty, that is log of the number of group element compositions + done in the prover *) +type difficulty = Int64.t + +val discriminant_to_bytes : discriminant -> bytes + +val discriminant_of_bytes_opt : bytes -> discriminant option + +val challenge_to_bytes : challenge -> bytes + +val challenge_of_bytes_opt : bytes -> challenge option + +val result_to_bytes : result -> bytes + +val result_of_bytes_opt : bytes -> result option + +val proof_to_bytes : proof -> bytes + +val proof_of_bytes_opt : bytes -> proof option + +(** [generate_discriminant ?seed size], function generating a + discriminant/group *) +val generate_discriminant : ?seed:Bytes.t -> int -> discriminant + +(** [generate_challenge discriminant seed], function generating a class group + element used as a VDF challenge *) +val generate_challenge : discriminant -> Bytes.t -> challenge + +(** [prove_vdf discriminant challenge difficulty], function taking a class + group/discriminant, a vdf challenge and a difficulty and returning a vdf + result and proof *) +val prove : discriminant -> challenge -> difficulty -> result * proof + +(** [verify_vdf discriminant challenge difficulty result proof] function taking + a class group/discriminant, a vdf challenge, difficulty, result and proof and + returning true if the proof verifies else false + + @raise Invalid_argument when inputs are invalid *) +val verify : discriminant -> challenge -> difficulty -> result -> proof -> bool diff --git a/src/proto_alpha/lib_client/mockup.ml b/src/proto_alpha/lib_client/mockup.ml index 61f6f776485495a0b1880390339038fe6dd172d5..9ea1b7b7ea82372c5b61c15d7f44f176fce72330 100644 --- a/src/proto_alpha/lib_client/mockup.ml +++ b/src/proto_alpha/lib_client/mockup.ml @@ -69,12 +69,14 @@ module Protocol_constants_overrides = struct preserved_cycles : int option; blocks_per_cycle : int32 option; blocks_per_commitment : int32 option; + nonce_revelation_threshold : int32 option; blocks_per_stake_snapshot : int32 option; cycles_per_voting_period : int32 option; hard_gas_limit_per_operation : Gas.Arith.integral option; hard_gas_limit_per_block : Gas.Arith.integral option; proof_of_work_threshold : int64 option; tokens_per_roll : Tez.t option; + vdf_difficulty : int64 option; seed_nonce_revelation_tip : Tez.t option; origination_size : int option; baking_reward_fixed_portion : Tez.t option; @@ -233,13 +235,15 @@ module Protocol_constants_overrides = struct ( ( c.preserved_cycles, c.blocks_per_cycle, c.blocks_per_commitment, + c.nonce_revelation_threshold, c.blocks_per_stake_snapshot, c.cycles_per_voting_period, c.hard_gas_limit_per_operation, c.hard_gas_limit_per_block, c.proof_of_work_threshold, c.tokens_per_roll ), - ( ( c.seed_nonce_revelation_tip, + ( ( c.vdf_difficulty, + c.seed_nonce_revelation_tip, c.origination_size, c.baking_reward_fixed_portion, c.baking_reward_bonus_per_slot, @@ -272,13 +276,15 @@ module Protocol_constants_overrides = struct (fun ( ( preserved_cycles, blocks_per_cycle, blocks_per_commitment, + nonce_revelation_threshold, blocks_per_stake_snapshot, cycles_per_voting_period, hard_gas_limit_per_operation, hard_gas_limit_per_block, proof_of_work_threshold, tokens_per_roll ), - ( ( seed_nonce_revelation_tip, + ( ( vdf_difficulty, + seed_nonce_revelation_tip, origination_size, baking_reward_fixed_portion, baking_reward_bonus_per_slot, @@ -312,12 +318,14 @@ module Protocol_constants_overrides = struct preserved_cycles; blocks_per_cycle; blocks_per_commitment; + nonce_revelation_threshold; blocks_per_stake_snapshot; cycles_per_voting_period; hard_gas_limit_per_operation; hard_gas_limit_per_block; proof_of_work_threshold; tokens_per_roll; + vdf_difficulty; seed_nonce_revelation_tip; origination_size; baking_reward_fixed_portion; @@ -352,10 +360,11 @@ module Protocol_constants_overrides = struct initial_seed; }) (merge_objs - (obj9 + (obj10 (opt "preserved_cycles" uint8) (opt "blocks_per_cycle" int32) (opt "blocks_per_commitment" int32) + (opt "nonce_revelation_threshold" int32) (opt "blocks_per_stake_snapshot" int32) (opt "cycles_per_voting_period" int32) (opt "hard_gas_limit_per_operation" Gas.Arith.z_integral_encoding) @@ -363,7 +372,8 @@ module Protocol_constants_overrides = struct (opt "proof_of_work_threshold" int64) (opt "tokens_per_roll" Tez.encoding)) (merge_objs - (obj8 + (obj9 + (opt "vdf_difficulty" int64) (opt "seed_nonce_revelation_tip" Tez.encoding) (opt "origination_size" int31) (opt "baking_reward_fixed_portion" Tez.encoding) @@ -427,6 +437,7 @@ module Protocol_constants_overrides = struct preserved_cycles = Some parametric.preserved_cycles; blocks_per_cycle = Some parametric.blocks_per_cycle; blocks_per_commitment = Some parametric.blocks_per_commitment; + nonce_revelation_threshold = Some parametric.nonce_revelation_threshold; blocks_per_stake_snapshot = Some parametric.blocks_per_stake_snapshot; cycles_per_voting_period = Some parametric.cycles_per_voting_period; hard_gas_limit_per_operation = @@ -434,6 +445,7 @@ module Protocol_constants_overrides = struct hard_gas_limit_per_block = Some parametric.hard_gas_limit_per_block; proof_of_work_threshold = Some parametric.proof_of_work_threshold; tokens_per_roll = Some parametric.tokens_per_roll; + vdf_difficulty = Some parametric.vdf_difficulty; seed_nonce_revelation_tip = Some parametric.seed_nonce_revelation_tip; origination_size = Some parametric.origination_size; baking_reward_fixed_portion = @@ -527,12 +539,14 @@ module Protocol_constants_overrides = struct preserved_cycles = None; blocks_per_cycle = None; blocks_per_commitment = None; + nonce_revelation_threshold = None; blocks_per_stake_snapshot = None; cycles_per_voting_period = None; hard_gas_limit_per_operation = None; hard_gas_limit_per_block = None; proof_of_work_threshold = None; tokens_per_roll = None; + vdf_difficulty = None; seed_nonce_revelation_tip = None; origination_size = None; baking_reward_fixed_portion = None; @@ -636,6 +650,12 @@ module Protocol_constants_overrides = struct override_value = o.blocks_per_commitment; pp = pp_print_int32; }; + O + { + name = "nonce_revelation_threshold"; + override_value = o.nonce_revelation_threshold; + pp = pp_print_int32; + }; O { name = "blocks_per_stake_snapshot"; @@ -672,6 +692,12 @@ module Protocol_constants_overrides = struct override_value = o.tokens_per_roll; pp = Tez.pp; }; + O + { + name = "vdf_difficulty"; + override_value = o.vdf_difficulty; + pp = pp_print_int64; + }; O { name = "seed_nonce_revelation_tip"; @@ -913,6 +939,10 @@ module Protocol_constants_overrides = struct Option.value ~default:c.blocks_per_cycle o.blocks_per_cycle; blocks_per_commitment = Option.value ~default:c.blocks_per_commitment o.blocks_per_commitment; + nonce_revelation_threshold = + Option.value + ~default:c.nonce_revelation_threshold + o.nonce_revelation_threshold; blocks_per_stake_snapshot = Option.value ~default:c.blocks_per_stake_snapshot @@ -935,6 +965,8 @@ module Protocol_constants_overrides = struct o.proof_of_work_threshold; tokens_per_roll = Option.value ~default:c.tokens_per_roll o.tokens_per_roll; + vdf_difficulty = + Option.value ~default:c.vdf_difficulty o.vdf_difficulty; seed_nonce_revelation_tip = Option.value ~default:c.seed_nonce_revelation_tip diff --git a/src/proto_alpha/lib_client/operation_result.ml b/src/proto_alpha/lib_client/operation_result.ml index d156bd67bc800eb5563654104a4e2d1885d07ace..a4e211c7da0753af2b1f750b7761937117777d06 100644 --- a/src/proto_alpha/lib_client/operation_result.ml +++ b/src/proto_alpha/lib_client/operation_result.ml @@ -849,6 +849,14 @@ let pp_contents_and_result : (Nonce.hash nonce) pp_balance_updates bus + | Vdf_revelation {solution}, Vdf_revelation_result bus -> + Format.fprintf + ppf + "@[Vdf revelation:@,Solution: %a@,Balance updates:@,%a@]" + Seed.pp_solution + solution + pp_balance_updates + bus | Double_baking_evidence {bh1; bh2}, Double_baking_evidence_result bus -> Format.fprintf ppf diff --git a/src/proto_alpha/lib_delegate/baking_commands.ml b/src/proto_alpha/lib_delegate/baking_commands.ml index 78d6ec9e61a1bfcea816b01223c67e5e329f29b6..2ba6b4dbce6b509a1ec984e261118779d379c8ee 100644 --- a/src/proto_alpha/lib_delegate/baking_commands.ml +++ b/src/proto_alpha/lib_delegate/baking_commands.ml @@ -369,6 +369,14 @@ let baker_commands () : Protocol_client_context.full Clic.command list = ~context_path ~keep_alive delegates); + command + ~group + ~desc:"Launch the VDF daemon" + (* no_options *) + (args1 keep_alive_arg) + (prefixes ["run"; "vdf"] @@ stop) + (fun keep_alive cctxt -> + Client_daemon.VDF.run cctxt ~chain:cctxt#chain ~keep_alive); ] let accuser_commands () = diff --git a/src/proto_alpha/lib_delegate/baking_events.ml b/src/proto_alpha/lib_delegate/baking_events.ml index d0e2f9a35f2eb7e10fe323969f710a69fea90a74..426e4dcfa798695e0551a60af735922913592cfe 100644 --- a/src/proto_alpha/lib_delegate/baking_events.ml +++ b/src/proto_alpha/lib_delegate/baking_events.ml @@ -667,6 +667,61 @@ module Actions = struct .liquidity_baking_toggle_vote_encoding ) end +module VDF = struct + include Internal_event.Simple + + let section = section @ ["vdf"] + + let vdf_revelation_injected = + declare_3 + ~section + ~name:"vdf_revelation_injected" + ~level:Notice + ~msg: + "injected VDF revelation for cycle {cycle} (chain {chain} with \ + operation {ophash})" + ~pp1:pp_int32 + ("cycle", Data_encoding.int32) + ~pp2:Format.pp_print_string + ("chain", Data_encoding.string) + ~pp3:Operation_hash.pp + ("ophash", Operation_hash.encoding) + + let vdf_daemon_start = + declare_1 + ~section + ~level:Info + ~name:"vdf_daemon_start" + ~msg:"starting {worker} VDF daemon" + ("worker", Data_encoding.string) + + let vdf_daemon_error = + declare_2 + ~section + ~level:Error + ~name:"vdf_daemon_error" + ~msg:"{worker}: error while running VDF daemon: {errors}" + ~pp2:pp_print_top_error_of_trace + ("worker", Data_encoding.string) + ("errors", Error_monad.(TzTrace.encoding error_encoding)) + + let vdf_daemon_connection_lost = + declare_1 + ~section + ~level:Error + ~name:"vdf_daemon_connection_lost" + ~msg:"connection to node lost, VDF daemon {worker} exiting" + ("worker", Data_encoding.string) + + let vdf_info = + declare_1 + ~section + ~name:"vdf_internal" + ~level:Debug + ~msg:"{msg}" + ("msg", Data_encoding.string) +end + module Nonces = struct include Internal_event.Simple diff --git a/src/proto_alpha/lib_delegate/baking_vdf.ml b/src/proto_alpha/lib_delegate/baking_vdf.ml new file mode 100644 index 0000000000000000000000000000000000000000..287c9597404f8a312778c8af1e75bac2c7f8d627 --- /dev/null +++ b/src/proto_alpha/lib_delegate/baking_vdf.ml @@ -0,0 +1,179 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +open Protocol +open Alpha_context +module Events = Baking_events.VDF +module D_Events = Delegate_events.Denunciator +open Client_baking_blocks + +type status = Not_started | Started | Finished + +type 'a state = { + cctxt : Protocol_client_context.full; + constants : Constants.t; + block_stream : (block_info, 'a) result Lwt_stream.t; + mutable cycle : Cycle_repr.t option; + mutable computation_status : status; +} + +let rec wait_for_first_block ~name stream = + Lwt_stream.get stream >>= function + | None | Some (Error _) -> + Delegate_events.Baking_scheduling.(emit cannot_fetch_event) name + >>= fun () -> + (* NOTE: this is not a tight loop because of Lwt_stream.get *) + wait_for_first_block ~name stream + | Some (Ok bi) -> Lwt.return bi + +let log_errors_and_continue ~name p = + p >>= function + | Ok () -> Lwt.return_unit + | Error errs -> Events.(emit vdf_daemon_error) (name, errs) + +let cycle_of_level state level = + let {Constants.parametric = {blocks_per_cycle; _}; _} = state.constants in + let level = Raw_level.to_int32 level in + Int32.(div (pred level) blocks_per_cycle) + +let is_in_nonce_revelation_period state level = + let { + Constants.parametric = {blocks_per_cycle; nonce_revelation_threshold; _}; + _; + } = + state.constants + in + let current_cycle = cycle_of_level state level in + let level = Raw_level.to_int32 level in + let position_in_cycle = + Int32.(sub level (mul current_cycle blocks_per_cycle)) + in + Int32.compare position_in_cycle nonce_revelation_threshold < 0 + +let check_new_cycle state level = + let current_cycle = Cycle_repr.of_int32_exn (cycle_of_level state level) in + match state.cycle with + | None -> state.cycle <- Some current_cycle + | Some cycle -> + if Cycle_repr.(succ cycle = current_cycle) then ( + state.cycle <- Some current_cycle ; + state.computation_status <- Not_started) + +let process_new_block (cctxt : #Protocol_client_context.full) state + {hash; chain_id; protocol; next_protocol; level; _} = + let open Lwt_result_syntax in + let level_str = Int32.to_string (Raw_level.to_int32 level) in + check_new_cycle state level ; + if Protocol_hash.(protocol <> next_protocol) then + D_Events.(emit protocol_change_detected) () >>= fun () -> return_unit + else if is_in_nonce_revelation_period state level then + Events.(emit vdf_info) + ("Skipping, still in nonce revelation period (level " ^ level_str ^ ")") + >>= fun _ -> return_unit + else if state.computation_status = Finished then + Events.(emit vdf_info) + ("Skipping, computation finished (level " ^ level_str ^ ")") + >>= fun _ -> return_unit + else + let chain = `Hash chain_id in + let block = `Hash (hash, 0) in + Alpha_services.Seed_computation.get cctxt (chain, block) >>=? fun x -> + (match x with + | Vdf_revelation_stage {seed_discriminant; seed_challenge} -> + if state.computation_status = Started then + Events.(emit vdf_info) + ("Skipping, computation already started (level " ^ level_str ^ ")") + >>= fun () -> return_unit + else + Events.(emit vdf_info) + ("Started to compute VDF (level " ^ level_str ^ ")") + >>= fun () -> + state.computation_status <- Started ; + let discriminant, challenge = + Seed.generate_vdf_setup ~seed_discriminant ~seed_challenge + in + let solution = + Environment.Vdf.prove + discriminant + challenge + state.constants.parametric.vdf_difficulty + in + let* bytes = + Plugin.RPC.Forge.vdf_revelation + cctxt + (chain, block) + ~branch:hash + ~solution + () + in + let bytes = Signature.concat bytes Signature.zero in + let* op_hash = + Shell_services.Injection.operation cctxt ~chain bytes + in + Events.(emit vdf_revelation_injected) + (cycle_of_level state level, Chain_services.to_string chain, op_hash) + >>= fun () -> return_unit + | Nonce_revelation_stage -> + (* this should never actually happen *) + Events.(emit vdf_info) + ("Nonce revelation stage (level " ^ level_str ^ ")") + >>= fun () -> return_unit + | Computation_finished -> + (* this should happen at most once per cycle *) + state.computation_status <- Finished ; + Events.(emit vdf_info) ("Computation finished (level " ^ level_str ^ ")") + >>= fun () -> return_unit) + >>= fun _ -> return_unit + +let start_vdf_worker (cctxt : Protocol_client_context.full) ~canceler constants + (block_stream : Client_baking_blocks.block_info tzresult Lwt_stream.t) = + let state = + { + cctxt; + constants; + block_stream; + cycle = None; + computation_status = Not_started; + } + in + Lwt_canceler.on_cancel canceler (fun () -> Lwt.return_unit) ; + wait_for_first_block ~name state.block_stream >>= fun _first_event -> + let rec worker_loop () = + Lwt.choose + [ + (Lwt_exit.clean_up_starts >|= fun _ -> `Termination); + (Lwt_stream.get state.block_stream >|= fun e -> `Block e); + ] + >>= function + | `Termination -> return_unit + | `Block (None | Some (Error _)) -> + (* exit when the node is unavailable *) + Events.(emit vdf_daemon_connection_lost) name >>= fun () -> + fail Baking_errors.Node_connection_lost + | `Block (Some (Ok bi)) -> + log_errors_and_continue ~name @@ process_new_block cctxt state bi + >>= fun () -> worker_loop () + in + Events.(emit vdf_daemon_start) name >>= fun () -> worker_loop () diff --git a/src/proto_alpha/lib_delegate/baking_vdf.mli b/src/proto_alpha/lib_delegate/baking_vdf.mli new file mode 100644 index 0000000000000000000000000000000000000000..460a4b13b890b0d1ef96ed5b613c4442ef19e909 --- /dev/null +++ b/src/proto_alpha/lib_delegate/baking_vdf.mli @@ -0,0 +1,33 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +open Protocol.Alpha_context + +val start_vdf_worker : + Protocol_client_context.full -> + canceler:Lwt_canceler.t -> + Constants.t -> + Client_baking_blocks.block_info tzresult Lwt_stream.t -> + unit tzresult Lwt.t diff --git a/src/proto_alpha/lib_delegate/client_daemon.ml b/src/proto_alpha/lib_delegate/client_daemon.ml index cb5d92e4b6bc8f7b9922039a3fdcd4e5884320e1..88cf65ec608dca0bca8eca1c8bd6330ae3f52a5d 100644 --- a/src/proto_alpha/lib_delegate/client_daemon.ml +++ b/src/proto_alpha/lib_delegate/client_daemon.ml @@ -148,3 +148,41 @@ module Accuser = struct >>=? fun () -> if keep_alive then retry_on_disconnection cctxt process else process () end + +module VDF = struct + let run (cctxt : Protocol_client_context.full) ~chain ~keep_alive = + let open Lwt_result_syntax in + let process () = + cctxt#message + "VDF daemon v%a (%s) for %a started." + Tezos_version.Version.pp + Tezos_version.Current_git_info.version + Tezos_version.Current_git_info.abbreviated_commit_hash + Protocol_hash.pp_short + Protocol.hash + >>= fun () -> + let* chain_id = Shell_services.Chain.chain_id cctxt ~chain () in + let* constants = + Protocol.Alpha_services.Constants.all cctxt (`Hash chain_id, `Head 0) + in + let* block_stream = + Client_baking_blocks.monitor_valid_blocks + ~next_protocols:(Some [Protocol.hash]) + cctxt + ~chains:[chain] + () + in + let canceler = Lwt_canceler.create () in + let _ = + Lwt_exit.register_clean_up_callback ~loc:__LOC__ (fun _ -> + cctxt#message "Shutting down the VDF daemon..." >>= fun () -> + Lwt_canceler.cancel canceler >>= fun _ -> Lwt.return_unit) + in + Baking_vdf.start_vdf_worker cctxt ~canceler constants block_stream + in + Client_confirmations.wait_for_bootstrapped + ~retry:(retry cctxt ~delay:1. ~factor:1.5 ~tries:5) + cctxt + >>=? fun () -> + if keep_alive then retry_on_disconnection cctxt process else process () +end diff --git a/src/proto_alpha/lib_delegate/client_daemon.mli b/src/proto_alpha/lib_delegate/client_daemon.mli index 0e6f799861058fffabcd8e5f5cdca36a0ad0e9e9..4587a92a7959c23ebe1c2f431ce400ea1361ec14 100644 --- a/src/proto_alpha/lib_delegate/client_daemon.mli +++ b/src/proto_alpha/lib_delegate/client_daemon.mli @@ -53,3 +53,13 @@ module Accuser : sig keep_alive:bool -> unit tzresult Lwt.t end + +(** {1 VDF computation daemon} *) + +module VDF : sig + val run : + Protocol_client_context.full -> + chain:Chain_services.chain -> + keep_alive:bool -> + unit tzresult Lwt.t +end diff --git a/src/proto_alpha/lib_parameters/default_parameters.ml b/src/proto_alpha/lib_parameters/default_parameters.ml index 2ed4df78331e81bffe0c188edc27f6a007dc6d73..2133abfee93981b3301c977527c2b01f92b692d3 100644 --- a/src/proto_alpha/lib_parameters/default_parameters.ml +++ b/src/proto_alpha/lib_parameters/default_parameters.ml @@ -79,12 +79,21 @@ let constants_mainnet = Constants.Parametric.preserved_cycles = 5; blocks_per_cycle = 8192l; blocks_per_commitment = 64l; + nonce_revelation_threshold = 32l; blocks_per_stake_snapshot = 512l; cycles_per_voting_period = 5l; hard_gas_limit_per_operation = Gas.Arith.(integral_of_int_exn 1_040_000); hard_gas_limit_per_block = Gas.Arith.(integral_of_int_exn 5_200_000); proof_of_work_threshold = Int64.(sub (shift_left 1L 46) 1L); tokens_per_roll = Tez.(mul_exn one 6_000); + (* VDF's difficulty must be a multiple of `nonce_revelation_threshold` times + the block time. At the moment it is equal to 1B = 1000 * 5 * .2M with + - 1000 ~= 32 * 30 that is nonce_revelation_threshold * block time + - .2M ~= number of modular squaring per second on benchmark machine + with 2.8GHz CPU + - 5: security factor (strictly higher than the ratio between highest CPU + clock rate and benchmark machine that is 8.43/2.8 ~= 3 *) + vdf_difficulty = 1_000_000_000L; seed_nonce_revelation_tip = (match Tez.(one /? 8L) with Ok c -> c | Error _ -> assert false); origination_size = 257; @@ -233,9 +242,11 @@ let constants_sandbox = Constants.Parametric.preserved_cycles = 2; blocks_per_cycle = 8l; blocks_per_commitment = 4l; + nonce_revelation_threshold = 4l; blocks_per_stake_snapshot = 4l; cycles_per_voting_period = 8l; proof_of_work_threshold = Int64.of_int (-1); + vdf_difficulty = 50_000L; liquidity_baking_sunset_level = 128l; minimal_block_delay = Period.of_seconds_exn (Int64.of_int block_time); delay_increment_per_round = Period.one_second; @@ -266,9 +277,11 @@ let constants_test = Constants.Parametric.preserved_cycles = 3; blocks_per_cycle = 12l; blocks_per_commitment = 4l; + nonce_revelation_threshold = 4l; blocks_per_stake_snapshot = 4l; cycles_per_voting_period = 2l; proof_of_work_threshold = Int64.of_int (-1); + vdf_difficulty = 50_000L; liquidity_baking_sunset_level = 4096l; consensus_committee_size; consensus_threshold (* 17 slots *); diff --git a/src/proto_alpha/lib_plugin/RPC.ml b/src/proto_alpha/lib_plugin/RPC.ml index f45fb752462d11914f03109bbb94eaf5b26aba52..f04e17e07fe2305b406e9d593fea4b507ac29689 100644 --- a/src/proto_alpha/lib_plugin/RPC.ml +++ b/src/proto_alpha/lib_plugin/RPC.ml @@ -2315,6 +2315,9 @@ module Forge = struct let seed_nonce_revelation ctxt block ~branch ~level ~nonce () = operation ctxt block ~branch (Seed_nonce_revelation {level; nonce}) + let vdf_revelation ctxt block ~branch ~solution () = + operation ctxt block ~branch (Vdf_revelation {solution}) + let double_baking_evidence ctxt block ~branch ~bh1 ~bh2 () = operation ctxt block ~branch (Double_baking_evidence {bh1; bh2}) diff --git a/src/proto_alpha/lib_plugin/mempool.ml b/src/proto_alpha/lib_plugin/mempool.ml index 4a3163b6061d5f903ecdbf7b3aca2b73864ba520..500144cf377c2eab8307194d317a6a933c5f67a1 100644 --- a/src/proto_alpha/lib_plugin/mempool.ml +++ b/src/proto_alpha/lib_plugin/mempool.ml @@ -946,6 +946,7 @@ let pre_filter config ~(filter_state : state) ?validation_state_before | Single (Double_baking_evidence _) | Single (Activate_account _) | Single (Proposals _) + | Single (Vdf_revelation _) | Single (Ballot _) -> Lwt.return @@ `Passed_prefilter other_prio | Single (Manager_operation {source; _}) as op -> @@ -1222,6 +1223,7 @@ let post_filter config ~(filter_state : state) ~validation_state_before:_ | Single_result (Double_baking_evidence_result _) | Single_result (Activate_account_result _) | Single_result Proposals_result + | Single_result (Vdf_revelation_result _) | Single_result Ballot_result -> Lwt.return (`Passed_postfilter filter_state) | Single_result (Manager_operation_result _) as result -> diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 2047d1f6da02e5b63d14a27e06d2da358f389e49..2ba259aafd6dc70d751c02c640ce45bd700fab01 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -812,12 +812,14 @@ module Constants : sig preserved_cycles : int; blocks_per_cycle : int32; blocks_per_commitment : int32; + nonce_revelation_threshold : int32; blocks_per_stake_snapshot : int32; cycles_per_voting_period : int32; hard_gas_limit_per_operation : Gas.Arith.integral; hard_gas_limit_per_block : Gas.Arith.integral; proof_of_work_threshold : int64; tokens_per_roll : Tez.t; + vdf_difficulty : int64; seed_nonce_revelation_tip : Tez.t; origination_size : int; baking_reward_fixed_portion : Tez.t; @@ -877,6 +879,8 @@ module Constants : sig val blocks_per_commitment : context -> int32 + val nonce_revelation_threshold : context -> int32 + val blocks_per_stake_snapshot : context -> int32 val cycles_per_voting_period : context -> int32 @@ -893,6 +897,8 @@ module Constants : sig val tokens_per_roll : context -> Tez.t + val vdf_difficulty : context -> int64 + val seed_nonce_revelation_tip : context -> Tez.t val origination_size : context -> int @@ -1207,6 +1213,8 @@ module Level : sig val dawn_of_a_new_cycle : context -> Cycle.t option val may_snapshot_rolls : context -> bool + + val may_compute_randao : context -> bool end (** This module re-exports definitions from {!Fitness_repr}. *) @@ -1277,14 +1285,43 @@ end module Seed : sig type seed - type error += Unknown of {oldest : Cycle.t; cycle : Cycle.t; latest : Cycle.t} + val seed_encoding : seed Data_encoding.t - val for_cycle : context -> Cycle.t -> seed tzresult Lwt.t + type vdf_solution = Vdf.result * Vdf.proof + + val vdf_solution_encoding : vdf_solution Data_encoding.t + + val pp_solution : Format.formatter -> vdf_solution -> unit + + type vdf_setup = Vdf.discriminant * Vdf.challenge + + type error += + | Unknown of {oldest : Cycle.t; cycle : Cycle.t; latest : Cycle.t} + | Already_accepted + | Unverified_vdf + | Too_early_revelation + + val generate_vdf_setup : + seed_discriminant:seed -> seed_challenge:seed -> vdf_setup + + val check_vdf_and_update_seed : + context -> vdf_solution -> context tzresult Lwt.t + + val compute_randao : context -> context tzresult Lwt.t val cycle_end : context -> Cycle.t -> (context * Nonce.unrevealed list) tzresult Lwt.t - val seed_encoding : seed Data_encoding.t + (* RPC *) + type seed_computation_status = + | Nonce_revelation_stage + | Vdf_revelation_stage of {seed_discriminant : seed; seed_challenge : seed} + | Computation_finished + + val for_cycle : context -> Cycle.t -> seed tzresult Lwt.t + + val get_seed_computation_status : + context -> seed_computation_status tzresult Lwt.t end module Big_map : sig @@ -3383,6 +3420,8 @@ module Kind : sig type seed_nonce_revelation = Seed_nonce_revelation_kind + type vdf_revelation = Vdf_revelation_kind + type 'a double_consensus_operation_evidence = | Double_consensus_operation_evidence @@ -3532,6 +3571,10 @@ and _ contents = nonce : Nonce.t; } -> Kind.seed_nonce_revelation contents + | Vdf_revelation : { + solution : Seed.vdf_solution; + } + -> Kind.vdf_revelation contents | Double_preendorsement_evidence : { op1 : Kind.preendorsement operation; op2 : Kind.preendorsement operation; @@ -3810,6 +3853,8 @@ module Operation : sig val seed_nonce_revelation_case : Kind.seed_nonce_revelation case + val vdf_revelation_case : Kind.vdf_revelation case + val double_preendorsement_evidence_case : Kind.double_preendorsement_evidence case diff --git a/src/proto_alpha/lib_protocol/alpha_services.ml b/src/proto_alpha/lib_protocol/alpha_services.ml index ad6eccf851a5488604fdb8cdb2abf85975acc173..1dfb872f9ccab3e5c7301e775fe12edae85a0200 100644 --- a/src/proto_alpha/lib_protocol/alpha_services.ml +++ b/src/proto_alpha/lib_protocol/alpha_services.ml @@ -28,6 +28,56 @@ open Alpha_context let custom_root = RPC_path.open_root +module Seed_computation = struct + module S = struct + let seed_computation_status_encoding = + let open Seed in + Data_encoding.( + union + [ + case + (Tag 0) + ~title:"Nonce revelation stage" + (obj1 (req "nonce_revelation_stage" unit)) + (function Nonce_revelation_stage -> Some () | _ -> None) + (fun () -> Nonce_revelation_stage); + case + (Tag 1) + ~title:"VDF revelation stage" + (obj2 + (req "seed_discriminant" Seed.seed_encoding) + (req "seed_challenge" Seed.seed_encoding)) + (function + | Vdf_revelation_stage {seed_discriminant; seed_challenge} -> + Some (seed_discriminant, seed_challenge) + | _ -> None) + (fun (seed_discriminant, seed_challenge) -> + Vdf_revelation_stage {seed_discriminant; seed_challenge}); + case + (Tag 2) + ~title:"Computation finished" + (obj1 (req "computation_finished" unit)) + (function Computation_finished -> Some () | _ -> None) + (fun () -> Computation_finished); + ]) + + let seed_computation = + RPC_service.get_service + ~description:"Seed computation status" + ~query:RPC_query.empty + ~output:seed_computation_status_encoding + RPC_path.(custom_root / "context" / "seed_computation") + end + + let () = + let open Services_registration in + register0 ~chunked:false S.seed_computation (fun ctxt () () -> + Seed.get_seed_computation_status ctxt) + + let get ctxt block = + RPC_context.make_call0 S.seed_computation ctxt block () () +end + module Seed = struct module S = struct open Data_encoding diff --git a/src/proto_alpha/lib_protocol/alpha_services.mli b/src/proto_alpha/lib_protocol/alpha_services.mli index 5bfc4054c68bb5259cbbae65200a5d0537f44e6c..14235f19a699785940202b9a499741ff6932bf8d 100644 --- a/src/proto_alpha/lib_protocol/alpha_services.mli +++ b/src/proto_alpha/lib_protocol/alpha_services.mli @@ -35,6 +35,13 @@ open Alpha_context +module Seed_computation : sig + val get : + 'a #RPC_context.simple -> + 'a -> + Seed.seed_computation_status shell_tzresult Lwt.t +end + module Seed : sig val get : 'a #RPC_context.simple -> 'a -> Seed.seed shell_tzresult Lwt.t end diff --git a/src/proto_alpha/lib_protocol/apply.ml b/src/proto_alpha/lib_protocol/apply.ml index 5478fc5d53b6d5f8dbe3eb4a451213984cbb6f2e..f7e2e3b932b9ca375418f67a862fa3f7400e9238 100644 --- a/src/proto_alpha/lib_protocol/apply.ml +++ b/src/proto_alpha/lib_protocol/apply.ml @@ -2429,8 +2429,9 @@ let record_operation (type kind) ctxt hash (operation : kind operation) : | Single (Dal_slot_availability _) -> ctxt | Single ( Failing_noop _ | Proposals _ | Ballot _ | Seed_nonce_revelation _ - | Double_endorsement_evidence _ | Double_preendorsement_evidence _ - | Double_baking_evidence _ | Activate_account _ | Manager_operation _ ) + | Vdf_revelation _ | Double_endorsement_evidence _ + | Double_preendorsement_evidence _ | Double_baking_evidence _ + | Activate_account _ | Manager_operation _ ) | Cons (Manager_operation _, _) -> record_non_consensus_operation_hash ctxt hash @@ -2941,6 +2942,13 @@ let apply_contents_list (type kind) ctxt chain_id (apply_mode : apply_mode) mode Token.transfer ctxt `Revelation_rewards (`Contract contract) tip >|=? fun (ctxt, balance_updates) -> (ctxt, Single_result (Seed_nonce_revelation_result balance_updates)) + | Single (Vdf_revelation {solution}) -> + Seed.check_vdf_and_update_seed ctxt solution >>=? fun ctxt -> + let tip = Constants.seed_nonce_revelation_tip ctxt in + let contract = Contract.Implicit payload_producer in + Token.transfer ctxt `Revelation_rewards (`Contract contract) tip + >|=? fun (ctxt, balance_updates) -> + (ctxt, Single_result (Vdf_revelation_result balance_updates)) | Single (Double_preendorsement_evidence {op1; op2}) -> punish_double_endorsement_or_preendorsement ctxt @@ -3483,6 +3491,10 @@ let finalize_application ctxt (mode : finalize_application_mode) protocol_data ~baking_reward ~reward_bonus >>=? fun (ctxt, baking_receipts) -> + (* if end of nonce revelation period, compute seed *) + (if Level.may_compute_randao ctxt then Seed.compute_randao ctxt + else return ctxt) + >>=? fun ctxt -> (* end of cycle *) (if Level.may_snapshot_rolls ctxt then Stake_distribution.snapshot ctxt else return ctxt) diff --git a/src/proto_alpha/lib_protocol/apply_results.ml b/src/proto_alpha/lib_protocol/apply_results.ml index 42e4072b1e068e50859c3fb1b74f6b4d0ea33670..421078a585894f33cb70f5017e4ffa0c21fa3d4e 100644 --- a/src/proto_alpha/lib_protocol/apply_results.ml +++ b/src/proto_alpha/lib_protocol/apply_results.ml @@ -925,6 +925,9 @@ type 'kind contents_result = | Seed_nonce_revelation_result : Receipt.balance_updates -> Kind.seed_nonce_revelation contents_result + | Vdf_revelation_result : + Receipt.balance_updates + -> Kind.vdf_revelation contents_result | Double_endorsement_evidence_result : Receipt.balance_updates -> Kind.double_endorsement_evidence contents_result @@ -1156,6 +1159,24 @@ module Encoding = struct inj = (fun bus -> Seed_nonce_revelation_result bus); } + let[@coq_axiom_with_reason "gadt"] vdf_revelation_case = + Case + { + op_case = Operation.Encoding.vdf_revelation_case; + encoding = + obj1 (dft "balance_updates" Receipt.balance_updates_encoding []); + select = + (function + | Contents_result (Vdf_revelation_result _ as op) -> Some op + | _ -> None); + mselect = + (function + | Contents_and_result ((Vdf_revelation _ as op), res) -> Some (op, res) + | _ -> None); + proj = (fun (Vdf_revelation_result bus) -> bus); + inj = (fun bus -> Vdf_revelation_result bus); + } + let[@coq_axiom_with_reason "gadt"] double_endorsement_evidence_case = Case { @@ -1325,6 +1346,7 @@ module Encoding = struct | Contents_result (Dal_slot_availability_result _) -> None | Contents_result Ballot_result -> None | Contents_result (Seed_nonce_revelation_result _) -> None + | Contents_result (Vdf_revelation_result _) -> None | Contents_result (Double_endorsement_evidence_result _) -> None | Contents_result (Double_preendorsement_evidence_result _) -> None | Contents_result (Double_baking_evidence_result _) -> None @@ -1636,6 +1658,7 @@ let contents_result_encoding = @@ union [ make seed_nonce_revelation_case; + make vdf_revelation_case; make endorsement_case; make preendorsement_case; make dal_slot_availability_case; @@ -1696,6 +1719,7 @@ let contents_and_result_encoding = @@ union [ make seed_nonce_revelation_case; + make vdf_revelation_case; make endorsement_case; make preendorsement_case; make dal_slot_availability_case; @@ -1843,6 +1867,8 @@ let kind_equal : | Dal_slot_availability _, _ -> None | Seed_nonce_revelation _, Seed_nonce_revelation_result _ -> Some Eq | Seed_nonce_revelation _, _ -> None + | Vdf_revelation _, Vdf_revelation_result _ -> Some Eq + | Vdf_revelation _, _ -> None | Double_preendorsement_evidence _, Double_preendorsement_evidence_result _ -> Some Eq | Double_preendorsement_evidence _, _ -> None diff --git a/src/proto_alpha/lib_protocol/apply_results.mli b/src/proto_alpha/lib_protocol/apply_results.mli index c3d168803da107c4471a0674ed8424c44fc4d84f..c26723abb33c6332778d1e58310a29beab90190b 100644 --- a/src/proto_alpha/lib_protocol/apply_results.mli +++ b/src/proto_alpha/lib_protocol/apply_results.mli @@ -77,6 +77,9 @@ and 'kind contents_result = | Seed_nonce_revelation_result : Receipt.balance_updates -> Kind.seed_nonce_revelation contents_result + | Vdf_revelation_result : + Receipt.balance_updates + -> Kind.vdf_revelation contents_result | Double_endorsement_evidence_result : Receipt.balance_updates -> Kind.double_endorsement_evidence contents_result diff --git a/src/proto_alpha/lib_protocol/constants_parametric_repr.ml b/src/proto_alpha/lib_protocol/constants_parametric_repr.ml index 2ca203edaa06f4c55869de711b564e34049b60b8..51e79d326438f9c3ae4d4411346f6bbf4a4d1789 100644 --- a/src/proto_alpha/lib_protocol/constants_parametric_repr.ml +++ b/src/proto_alpha/lib_protocol/constants_parametric_repr.ml @@ -110,12 +110,14 @@ type t = { preserved_cycles : int; blocks_per_cycle : int32; blocks_per_commitment : int32; + nonce_revelation_threshold : int32; blocks_per_stake_snapshot : int32; cycles_per_voting_period : int32; hard_gas_limit_per_operation : Gas_limit_repr.Arith.integral; hard_gas_limit_per_block : Gas_limit_repr.Arith.integral; proof_of_work_threshold : int64; tokens_per_roll : Tez_repr.t; + vdf_difficulty : int64; seed_nonce_revelation_tip : Tez_repr.t; origination_size : int; baking_reward_fixed_portion : Tez_repr.t; @@ -271,13 +273,15 @@ let encoding = ( ( c.preserved_cycles, c.blocks_per_cycle, c.blocks_per_commitment, + c.nonce_revelation_threshold, c.blocks_per_stake_snapshot, c.cycles_per_voting_period, c.hard_gas_limit_per_operation, c.hard_gas_limit_per_block, c.proof_of_work_threshold, c.tokens_per_roll ), - ( ( c.seed_nonce_revelation_tip, + ( ( c.vdf_difficulty, + c.seed_nonce_revelation_tip, c.origination_size, c.baking_reward_fixed_portion, c.baking_reward_bonus_per_slot, @@ -308,13 +312,15 @@ let encoding = (fun ( ( preserved_cycles, blocks_per_cycle, blocks_per_commitment, + nonce_revelation_threshold, blocks_per_stake_snapshot, cycles_per_voting_period, hard_gas_limit_per_operation, hard_gas_limit_per_block, proof_of_work_threshold, tokens_per_roll ), - ( ( seed_nonce_revelation_tip, + ( ( vdf_difficulty, + seed_nonce_revelation_tip, origination_size, baking_reward_fixed_portion, baking_reward_bonus_per_slot, @@ -346,12 +352,14 @@ let encoding = preserved_cycles; blocks_per_cycle; blocks_per_commitment; + nonce_revelation_threshold; blocks_per_stake_snapshot; cycles_per_voting_period; hard_gas_limit_per_operation; hard_gas_limit_per_block; proof_of_work_threshold; tokens_per_roll; + vdf_difficulty; seed_nonce_revelation_tip; origination_size; baking_reward_fixed_portion; @@ -384,10 +392,11 @@ let encoding = sc_rollup; }) (merge_objs - (obj9 + (obj10 (req "preserved_cycles" uint8) (req "blocks_per_cycle" int32) (req "blocks_per_commitment" int32) + (req "nonce_revelation_threshold" int32) (req "blocks_per_stake_snapshot" int32) (req "cycles_per_voting_period" int32) (req @@ -399,7 +408,8 @@ let encoding = (req "proof_of_work_threshold" int64) (req "tokens_per_roll" Tez_repr.encoding)) (merge_objs - (obj8 + (obj9 + (req "vdf_difficulty" int64) (req "seed_nonce_revelation_tip" Tez_repr.encoding) (req "origination_size" int31) (req "baking_reward_fixed_portion" Tez_repr.encoding) diff --git a/src/proto_alpha/lib_protocol/constants_parametric_repr.mli b/src/proto_alpha/lib_protocol/constants_parametric_repr.mli index d5d38f6985068df3d9e9c85d3473a37a90259ba4..e0cafb5f9ae62ffbdd9f15987e61168d743264f9 100644 --- a/src/proto_alpha/lib_protocol/constants_parametric_repr.mli +++ b/src/proto_alpha/lib_protocol/constants_parametric_repr.mli @@ -106,12 +106,14 @@ type t = { preserved_cycles : int; blocks_per_cycle : int32; blocks_per_commitment : int32; + nonce_revelation_threshold : int32; blocks_per_stake_snapshot : int32; cycles_per_voting_period : int32; hard_gas_limit_per_operation : Gas_limit_repr.Arith.integral; hard_gas_limit_per_block : Gas_limit_repr.Arith.integral; proof_of_work_threshold : int64; tokens_per_roll : Tez_repr.t; + vdf_difficulty : int64; seed_nonce_revelation_tip : Tez_repr.t; origination_size : int; baking_reward_fixed_portion : Tez_repr.t; diff --git a/src/proto_alpha/lib_protocol/constants_repr.ml b/src/proto_alpha/lib_protocol/constants_repr.ml index 8c72bf528424be6d098ea1fbef1d1bba7c31cdad..100d7dfce194d01a03b086a36363216bb401ae52 100644 --- a/src/proto_alpha/lib_protocol/constants_repr.ml +++ b/src/proto_alpha/lib_protocol/constants_repr.ml @@ -229,6 +229,33 @@ let check_constants constants = "The ratio blocks_per_cycle per blocks_per_stake_snapshot should be \ between 1 and 65535") >>? fun () -> + error_unless + Compare.Int32.( + constants.nonce_revelation_threshold > Int32.zero + && constants.nonce_revelation_threshold < constants.blocks_per_cycle) + (Invalid_protocol_constants + "The nonce revelation threshold must be strictly smaller than \ + blocks_per_cycle and strictly positive.") + >>? fun () -> + error_unless + Compare.Int64.( + let threshold = Int64.of_int32 constants.nonce_revelation_threshold in + let block = Period_repr.to_seconds constants.minimal_block_delay in + let ips = + (* We reduce the ips for short blocks_per_commitment so that we have + low difficulty during tests *) + if Compare.Int32.(constants.blocks_per_commitment > 32l) then + Int64.of_int 200_000 + else Int64.one + in + let factor = Int64.of_int 5 in + let difficulty = Int64.(mul (mul ips factor) (mul threshold block)) in + constants.vdf_difficulty > difficulty) + (Invalid_protocol_constants + "The VDF difficulty must be strictly greater than the product of the \ + nonce_revelation_threshold, the minimial_block_delay, a benchmark of \ + modulo squaring in class groups and a security threshold.") + >>? fun () -> error_unless Compare.Int.(constants.sc_rollup.origination_size >= 0) (Invalid_protocol_constants diff --git a/src/proto_alpha/lib_protocol/constants_storage.ml b/src/proto_alpha/lib_protocol/constants_storage.ml index 85d75f3fc71b2f01254b3f13540082a78ed4e1db..572dd125b69f881647a7c765922f02e254958d56 100644 --- a/src/proto_alpha/lib_protocol/constants_storage.ml +++ b/src/proto_alpha/lib_protocol/constants_storage.ml @@ -36,6 +36,10 @@ let blocks_per_commitment c = let constants = Raw_context.constants c in constants.blocks_per_commitment +let nonce_revelation_threshold c = + let constants = Raw_context.constants c in + constants.nonce_revelation_threshold + let blocks_per_stake_snapshot c = let constants = Raw_context.constants c in constants.blocks_per_stake_snapshot @@ -68,6 +72,10 @@ let tokens_per_roll c = let constants = Raw_context.constants c in constants.tokens_per_roll +let vdf_difficulty c = + let constants = Raw_context.constants c in + constants.vdf_difficulty + let seed_nonce_revelation_tip c = let constants = Raw_context.constants c in constants.seed_nonce_revelation_tip diff --git a/src/proto_alpha/lib_protocol/constants_storage.mli b/src/proto_alpha/lib_protocol/constants_storage.mli index a278ae0d67ea196a693a2e3ca0da04f30b045bb3..05c9bc1abff9a78740d6173168b427803878ba6a 100644 --- a/src/proto_alpha/lib_protocol/constants_storage.mli +++ b/src/proto_alpha/lib_protocol/constants_storage.mli @@ -35,6 +35,8 @@ val blocks_per_cycle : Raw_context.t -> int32 val blocks_per_commitment : Raw_context.t -> int32 +val nonce_revelation_threshold : Raw_context.t -> int32 + val blocks_per_stake_snapshot : Raw_context.t -> int32 val cycles_per_voting_period : Raw_context.t -> int32 @@ -52,6 +54,8 @@ val proof_of_work_threshold : Raw_context.t -> int64 val tokens_per_roll : Raw_context.t -> Tez_repr.t +val vdf_difficulty : Raw_context.t -> int64 + val seed_nonce_revelation_tip : Raw_context.t -> Tez_repr.t val origination_size : Raw_context.t -> int diff --git a/src/proto_alpha/lib_protocol/level_storage.ml b/src/proto_alpha/lib_protocol/level_storage.ml index 526e64e9638870941134b059a08fc0f847f9f058..852e8a84899b925b0d360231ccdd43d11da958fe 100644 --- a/src/proto_alpha/lib_protocol/level_storage.ml +++ b/src/proto_alpha/lib_protocol/level_storage.ml @@ -120,3 +120,8 @@ let may_snapshot_rolls ctxt = Compare.Int32.equal (Int32.rem level.cycle_position blocks_per_stake_snapshot) (Int32.pred blocks_per_stake_snapshot) + +let may_compute_randao ctxt = + let level = current ctxt in + let nonce_reveal_cutoff = Constants_storage.nonce_revelation_threshold ctxt in + Compare.Int32.equal level.cycle_position nonce_reveal_cutoff diff --git a/src/proto_alpha/lib_protocol/level_storage.mli b/src/proto_alpha/lib_protocol/level_storage.mli index aeebe659b64ffaba710f569e85427bc2453b4b44..4329f93f7c43b2054f587cd35fb96c2d92e8cb7f 100644 --- a/src/proto_alpha/lib_protocol/level_storage.mli +++ b/src/proto_alpha/lib_protocol/level_storage.mli @@ -69,3 +69,8 @@ val dawn_of_a_new_cycle : Raw_context.t -> Cycle_repr.t option (** Returns [true] if the rolls should be snapshot at the current level. *) val may_snapshot_rolls : Raw_context.t -> bool + +(** Returns [true] if RANDAO should be computed at the current level, that is + if the current level, relative to the cycle's start, equals the nonce + revelation period cut-off. *) +val may_compute_randao : Raw_context.t -> bool diff --git a/src/proto_alpha/lib_protocol/main.ml b/src/proto_alpha/lib_protocol/main.ml index 5652db49d3d0e67776ed196abcaf179d645f857f..12ee15cff4cf3f5675ae1029ba6f84c924dda62a 100644 --- a/src/proto_alpha/lib_protocol/main.ml +++ b/src/proto_alpha/lib_protocol/main.ml @@ -733,6 +733,9 @@ let relative_position_within_block op1 op2 = | Single (Seed_nonce_revelation _), Single (Seed_nonce_revelation _) -> 0 | _, Single (Seed_nonce_revelation _) -> 1 | Single (Seed_nonce_revelation _), _ -> -1 + | Single (Vdf_revelation _), Single (Vdf_revelation _) -> 0 + | _, Single (Vdf_revelation _) -> 1 + | Single (Vdf_revelation _), _ -> -1 | ( Single (Double_preendorsement_evidence _), Single (Double_preendorsement_evidence _) ) -> 0 diff --git a/src/proto_alpha/lib_protocol/nonce_storage.ml b/src/proto_alpha/lib_protocol/nonce_storage.ml index 60e8d8f0dff655027c7d8a9b659fdf16e35ebf1c..9ffc286eaa5f352246420940032b5fd678e12065 100644 --- a/src/proto_alpha/lib_protocol/nonce_storage.ml +++ b/src/proto_alpha/lib_protocol/nonce_storage.ml @@ -80,17 +80,22 @@ let () = (function Inconsistent_nonce -> Some () | _ -> None) (fun () -> Inconsistent_nonce) -(* checks that the level of a revelation is not too early or too late wrt to the - current context and that a nonce has not been already revealed for that level *) +(* Checks that the level of a revelation is not too early or too late wrt to the + current context and that a nonce has not been already revealed for that level. + Also checks that we are not past the nonce revelation period. *) let get_unrevealed ctxt (level : Level_repr.t) = - let cur_level = Level_storage.current ctxt in - match Cycle_repr.pred cur_level.cycle with + let current_level = Level_storage.current ctxt in + match Cycle_repr.pred current_level.cycle with | None -> fail Too_early_revelation (* no revelations during cycle 0 *) | Some revealed_cycle -> ( if Cycle_repr.(revealed_cycle < level.Level_repr.cycle) then fail Too_early_revelation - else if Cycle_repr.(level.Level_repr.cycle < revealed_cycle) then - fail Too_late_revelation + else if + Cycle_repr.(level.Level_repr.cycle < revealed_cycle) + || Compare.Int32.( + current_level.cycle_position + >= Constants_storage.nonce_revelation_threshold ctxt) + then fail Too_late_revelation else Storage.Seed.Nonce.get ctxt level >>=? function | Revealed _ -> fail Previously_revealed_nonce diff --git a/src/proto_alpha/lib_protocol/operation_repr.ml b/src/proto_alpha/lib_protocol/operation_repr.ml index 82c822477d132d94a45af4fafaa05c232bb73cde..e48c71e606163deebb6555906195df7f723ffd91 100644 --- a/src/proto_alpha/lib_protocol/operation_repr.ml +++ b/src/proto_alpha/lib_protocol/operation_repr.ml @@ -43,6 +43,8 @@ module Kind = struct type seed_nonce_revelation = Seed_nonce_revelation_kind + type vdf_revelation = Vdf_revelation_kind + type 'a double_consensus_operation_evidence = | Double_consensus_operation_evidence @@ -260,6 +262,10 @@ and _ contents = nonce : Seed_repr.nonce; } -> Kind.seed_nonce_revelation contents + | Vdf_revelation : { + solution : Seed_repr.vdf_solution; + } + -> Kind.vdf_revelation contents | Double_preendorsement_evidence : { op1 : Kind.preendorsement operation; op2 : Kind.preendorsement operation; @@ -1297,6 +1303,18 @@ module Encoding = struct inj = (fun (level, nonce) -> Seed_nonce_revelation {level; nonce}); } + let[@coq_axiom_with_reason "gadt"] vdf_revelation_case = + Case + { + tag = 8; + name = "vdf_revelation"; + encoding = obj1 (req "solution" Seed_repr.vdf_solution_encoding); + select = + (function Contents (Vdf_revelation _ as op) -> Some op | _ -> None); + proj = (function Vdf_revelation {solution} -> solution); + inj = (fun solution -> Vdf_revelation {solution}); + } + let[@coq_axiom_with_reason "gadt"] double_preendorsement_evidence_case : Kind.double_preendorsement_evidence case = Case @@ -1583,6 +1601,7 @@ module Encoding = struct make preendorsement_case; make dal_slot_availability_case; make seed_nonce_revelation_case; + make vdf_revelation_case; make double_endorsement_evidence_case; make double_preendorsement_evidence_case; make double_baking_evidence_case; @@ -1677,6 +1696,7 @@ let acceptable_passes (op : packed_operation) = | Single (Proposals _) -> [1] | Single (Ballot _) -> [1] | Single (Seed_nonce_revelation _) -> [2] + | Single (Vdf_revelation _) -> [2] | Single (Double_endorsement_evidence _) -> [2] | Single (Double_preendorsement_evidence _) -> [2] | Single (Double_baking_evidence _) -> [2] @@ -1759,9 +1779,9 @@ let check_signature (type kind) key chain_id signature | Single ( Failing_noop _ | Proposals _ | Ballot _ | Seed_nonce_revelation _ - | Double_endorsement_evidence _ | Double_preendorsement_evidence _ - | Double_baking_evidence _ | Activate_account _ | Manager_operation _ - ) -> + | Vdf_revelation _ | Double_endorsement_evidence _ + | Double_preendorsement_evidence _ | Double_baking_evidence _ + | Activate_account _ | Manager_operation _ ) -> check ~watermark:Generic_operation (Contents_list protocol_data.contents) @@ -1856,6 +1876,8 @@ let equal_contents_kind : type a b. a contents -> b contents -> (a, b) eq option | Dal_slot_availability _, _ -> None | Seed_nonce_revelation _, Seed_nonce_revelation _ -> Some Eq | Seed_nonce_revelation _, _ -> None + | Vdf_revelation _, Vdf_revelation _ -> Some Eq + | Vdf_revelation _, _ -> None | Double_endorsement_evidence _, Double_endorsement_evidence _ -> Some Eq | Double_endorsement_evidence _, _ -> None | Double_preendorsement_evidence _, Double_preendorsement_evidence _ -> diff --git a/src/proto_alpha/lib_protocol/operation_repr.mli b/src/proto_alpha/lib_protocol/operation_repr.mli index 90c0919415b42e56fd4869b6ca83e3a9bae80c30..5818f07e4eec77600baaae513b1bb60436e16891 100644 --- a/src/proto_alpha/lib_protocol/operation_repr.mli +++ b/src/proto_alpha/lib_protocol/operation_repr.mli @@ -72,6 +72,8 @@ module Kind : sig type seed_nonce_revelation = Seed_nonce_revelation_kind + type vdf_revelation = Vdf_revelation_kind + type 'a double_consensus_operation_evidence = | Double_consensus_operation_evidence @@ -255,6 +257,12 @@ and _ contents = nonce : Seed_repr.nonce; } -> Kind.seed_nonce_revelation contents + (* Vdf_revelation: VDF are computed from the seed generated by the revealed + nonces. *) + | Vdf_revelation : { + solution : Seed_repr.vdf_solution; + } + -> Kind.vdf_revelation contents (* Double_preendorsement_evidence: Double-preendorsement is a kind of malicious attack where a byzantine attempts to fork the chain by preendorsing blocks with different @@ -589,6 +597,8 @@ module Encoding : sig val seed_nonce_revelation_case : Kind.seed_nonce_revelation case + val vdf_revelation_case : Kind.vdf_revelation case + val double_preendorsement_evidence_case : Kind.double_preendorsement_evidence case diff --git a/src/proto_alpha/lib_protocol/raw_context.ml b/src/proto_alpha/lib_protocol/raw_context.ml index fac00958b95c54ad3b73c7ae0c4c6c8522ae71fd..4665d5bb114791fbb80048cf011efa15459048c3 100644 --- a/src/proto_alpha/lib_protocol/raw_context.ml +++ b/src/proto_alpha/lib_protocol/raw_context.ml @@ -908,12 +908,19 @@ let prepare_first_block ~level ~timestamp ctxt = preserved_cycles = c.preserved_cycles; blocks_per_cycle = c.blocks_per_cycle; blocks_per_commitment = c.blocks_per_commitment; + nonce_revelation_threshold = + (if Compare.Int32.(32l < c.blocks_per_commitment) then 32l + else c.blocks_per_commitment); blocks_per_stake_snapshot = c.blocks_per_stake_snapshot; cycles_per_voting_period = c.cycles_per_voting_period; hard_gas_limit_per_operation = c.hard_gas_limit_per_operation; hard_gas_limit_per_block = c.hard_gas_limit_per_block; proof_of_work_threshold = c.proof_of_work_threshold; tokens_per_roll = c.tokens_per_roll; + vdf_difficulty = + (if Compare.Int32.(32l < c.blocks_per_commitment) then + 1_000_000_000L + else 50_000L); seed_nonce_revelation_tip = c.seed_nonce_revelation_tip; origination_size = c.origination_size; max_operations_time_to_live = c.max_operations_time_to_live; diff --git a/src/proto_alpha/lib_protocol/seed_repr.ml b/src/proto_alpha/lib_protocol/seed_repr.ml index f3873d795502c2362431ce610b01000beb309cd1..23dc8fe39f521bea333b0de621164d736030bed1 100644 --- a/src/proto_alpha/lib_protocol/seed_repr.ml +++ b/src/proto_alpha/lib_protocol/seed_repr.ml @@ -33,9 +33,73 @@ type sequence = S of State_hash.t type nonce = bytes -let nonce_encoding = Data_encoding.Fixed.bytes Constants_repr.nonce_length +type vdf_setup = Vdf.discriminant * Vdf.challenge + +type vdf_solution = Vdf.result * Vdf.proof + +let seed_to_bytes x = + let seed_to_state_hash (B b) = b in + State_hash.to_bytes (seed_to_state_hash x) + +let vdf_setup_encoding = + let open Data_encoding in + let vdf_discriminant_encoding = + conv_with_guard + Vdf.discriminant_to_bytes + (fun b -> + Option.to_result + ~none:"VDF discriminant could not be deserialised" + (Vdf.discriminant_of_bytes_opt b)) + (Fixed.bytes Vdf.discriminant_size_bytes) + in + let vdf_challenge_encoding = + conv_with_guard + Vdf.challenge_to_bytes + (fun b -> + Option.to_result + ~none:"VDF challenge could not be deserialised" + (Vdf.challenge_of_bytes_opt b)) + (Fixed.bytes Vdf.form_size_bytes) + in + tup2 vdf_discriminant_encoding vdf_challenge_encoding + +let vdf_solution_encoding = + let open Data_encoding in + let vdf_result_encoding = + conv_with_guard + Vdf.result_to_bytes + (fun b -> + Option.to_result + ~none:"VDF result could not be deserialised" + (Vdf.result_of_bytes_opt b)) + (Fixed.bytes Vdf.form_size_bytes) + in + let vdf_proof_encoding = + conv_with_guard + Vdf.proof_to_bytes + (fun b -> + Option.to_result + ~none:"VDF proof could not be deserialised" + (Vdf.proof_of_bytes_opt b)) + (Fixed.bytes Vdf.form_size_bytes) + in + tup2 vdf_result_encoding vdf_proof_encoding -let initial_seed = "Laissez-faire les proprietaires." +let pp_solution ppf solution = + let result, proof = solution in + Format.fprintf + ppf + "@[VDF result: %a" + Hex.pp + (Hex.of_bytes (Vdf.result_to_bytes result)) ; + Format.fprintf + ppf + "@,VDF proof: %a" + Hex.pp + (Hex.of_bytes (Vdf.proof_to_bytes proof)) ; + Format.fprintf ppf "@]" + +let nonce_encoding = Data_encoding.Fixed.bytes Constants_repr.nonce_length let zero_bytes = Bytes.make Nonce_hash.size '\000' @@ -47,9 +111,7 @@ let seed_encoding = let open Data_encoding in conv (fun (B b) -> b) (fun b -> B b) state_hash_encoding -let empty = B (State_hash.hash_string [initial_seed]) - -let nonce (B state) nonce = +let update_seed (B state) nonce = B (State_hash.hash_bytes [State_hash.to_bytes state; nonce]) let initialize_new (B state) append = @@ -150,7 +212,7 @@ let initial_nonce_0 = zero_bytes let initial_nonce_hash_0 = hash initial_nonce_0 -let deterministic_seed seed = nonce seed zero_bytes +let deterministic_seed seed = update_seed seed zero_bytes let initial_seeds ?initial_seed n = let[@coq_struct "i"] rec loop acc elt i = @@ -159,7 +221,39 @@ let initial_seeds ?initial_seed n = in let first_seed = match initial_seed with - | Some initial_seed -> nonce (B initial_seed) initial_nonce_0 + | Some initial_seed -> update_seed (B initial_seed) initial_nonce_0 | None -> B (State_hash.hash_bytes []) in loop [] first_seed n + +let nonce_discriminant = Bytes.of_string "Tezos_generating_vdf_discriminant" + +let nonce_challenge = Bytes.of_string "Tezos_generating_vdf_challenge" + +let generate_vdf_setup ~seed_discriminant ~seed_challenge = + let size = Vdf.discriminant_size_bytes in + let seed = + update_seed seed_discriminant nonce_discriminant |> seed_to_bytes + in + let discriminant = Vdf.generate_discriminant ~seed size in + let input = update_seed seed_challenge nonce_challenge |> seed_to_bytes in + let challenge = Vdf.generate_challenge discriminant input in + (discriminant, challenge) + +let verify (discriminant, challenge) vdf_difficulty solution = + (* We return false when getting non group elements as input *) + let result, proof = solution in + Option.catch + ~catch_only:(function Invalid_argument _ -> true | _ -> false) + (fun () -> Vdf.verify discriminant challenge vdf_difficulty result proof) + +let vdf_to_seed seed_challenge solution = + let result, _ = solution in + update_seed seed_challenge (Vdf.result_to_bytes result) + +type seed_status = RANDAO_seed | VDF_seed + +let seed_status_encoding = + let to_bool = function RANDAO_seed -> false | VDF_seed -> true in + let of_bool t = if t then VDF_seed else RANDAO_seed in + Data_encoding.conv to_bool of_bool Data_encoding.bool diff --git a/src/proto_alpha/lib_protocol/seed_repr.mli b/src/proto_alpha/lib_protocol/seed_repr.mli index f823eea0311628f5e2c0ad5f7161b9d26158f10e..9816c1e98c298c16e77c0270b942c80b7ad36306 100644 --- a/src/proto_alpha/lib_protocol/seed_repr.mli +++ b/src/proto_alpha/lib_protocol/seed_repr.mli @@ -43,6 +43,21 @@ type seed (** A random sequence, to derive random values from *) type sequence +(** A VDF discriminant and challenge *) +type vdf_setup = Vdf.discriminant * Vdf.challenge + +(** A VDF result, to derive a seed from *) +type vdf_solution = Vdf.result * Vdf.proof + +val pp_solution : Format.formatter -> vdf_solution -> unit + +val generate_vdf_setup : + seed_discriminant:seed -> seed_challenge:seed -> vdf_setup + +val verify : vdf_setup -> Int64.t -> vdf_solution -> bool option + +val vdf_to_seed : seed -> vdf_solution -> seed + (** [initialize_new state ident] returns a new generator *) val initialize_new : seed -> bytes list -> t @@ -72,7 +87,7 @@ val take_int64 : sequence -> int64 -> int64 * sequence type nonce (** Add entropy to the seed generator *) -val nonce : seed -> nonce -> seed +val update_seed : seed -> nonce -> seed (** Use a byte sequence as a nonce *) val make_nonce : bytes -> nonce tzresult @@ -86,10 +101,6 @@ val check_hash : nonce -> Nonce_hash.t -> bool (** For using nonce hashes as keys in the hierarchical database *) val nonce_hash_key_part : Nonce_hash.t -> string list -> string list -(** {2 Predefined seeds} *) - -val empty : seed - (** Returns a new seed by hashing the one passed with a constant. *) val deterministic_seed : seed -> seed @@ -110,3 +121,11 @@ val initial_nonce_hash_0 : Nonce_hash.t val nonce_encoding : nonce Data_encoding.t val seed_encoding : seed Data_encoding.t + +val vdf_setup_encoding : vdf_setup Data_encoding.t + +val vdf_solution_encoding : vdf_solution Data_encoding.t + +type seed_status = RANDAO_seed | VDF_seed + +val seed_status_encoding : seed_status Data_encoding.t diff --git a/src/proto_alpha/lib_protocol/seed_storage.ml b/src/proto_alpha/lib_protocol/seed_storage.ml index 0985e678da7ac11771bdb25227ff2ec85d340310..99d6396575a7075b4822d22dadaa6b5ebb2fbe88 100644 --- a/src/proto_alpha/lib_protocol/seed_storage.ml +++ b/src/proto_alpha/lib_protocol/seed_storage.ml @@ -23,12 +23,25 @@ (* *) (*****************************************************************************) +open Lwt_result_syntax + +type seed_computation_status = + | Nonce_revelation_stage + | Vdf_revelation_stage of { + seed_discriminant : Seed_repr.seed; + seed_challenge : Seed_repr.seed; + } + | Computation_finished + type error += | Unknown of { oldest : Cycle_repr.t; cycle : Cycle_repr.t; latest : Cycle_repr.t; } + | Already_accepted + | Unverified_vdf + | Too_early_revelation (* `Permanent *) @@ -65,32 +78,139 @@ let () = (function | Unknown {oldest; cycle; latest} -> Some (oldest, cycle, latest) | _ -> None) - (fun (oldest, cycle, latest) -> Unknown {oldest; cycle; latest}) + (fun (oldest, cycle, latest) -> Unknown {oldest; cycle; latest}) ; + register_error_kind + `Temporary + ~id:"vdf.too_early_revelation" + ~title:"Too early VDF revelation" + ~description:"VDF revelation before the end of the nonce revelation period" + Data_encoding.unit + (function Too_early_revelation -> Some () | _ -> None) + (fun () -> Too_early_revelation) ; + register_error_kind + `Branch + ~id:"vdf.unverified_result" + ~title:"Unverified VDF" + ~description:"VDF verification failed" + ~pp:(fun ppf () -> + Format.fprintf + ppf + "A correct VDF result and Wesolowski's proof are expected") + Data_encoding.unit + (function Unverified_vdf -> Some () | _ -> None) + (fun () -> Unverified_vdf) ; + register_error_kind + `Branch + ~id:"vdf.previously_revealed" + ~title:"Previously revealed VDF" + ~description:"Duplicate VDF revelation in cycle" + Data_encoding.unit + (function Already_accepted -> Some () | _ -> None) + (fun () -> Already_accepted) -let compute_for_cycle c ~revealed cycle = - match Cycle_repr.pred cycle with - | None -> assert false (* should not happen *) - | Some previous_cycle -> - let levels = Level_storage.levels_with_commitments_in_cycle c revealed in - let combine (c, random_seed, unrevealed) level = +let purge_nonces_and_get_unrevealed ctxt ~cycle = + let levels = Level_storage.levels_with_commitments_in_cycle ctxt cycle in + let combine (c, unrevealed) level = + Storage.Seed.Nonce.get c level >>=? function + | Revealed _ -> + let+ c = Storage.Seed.Nonce.remove_existing c level in + (c, unrevealed) + | Unrevealed u -> + let+ c = Storage.Seed.Nonce.remove_existing c level in + (c, u :: unrevealed) + in + List.fold_left_es combine (ctxt, []) levels + +let compute_randao ctxt = + let current_cycle = (Level_storage.current ctxt).cycle in + let preserved = Constants_storage.preserved_cycles ctxt in + let cycle_computed = Cycle_repr.add current_cycle (preserved + 1) in + let*! seed_computed = Storage.Seed.For_cycle.mem ctxt cycle_computed in + (* Check if seed has already been computed, and not in cycle 0. *) + match Cycle_repr.(pred current_cycle, pred cycle_computed) with + | Some prev_cycle, Some prev_cycle_computed when not seed_computed -> + (* Retrieve the levels with nonce commitments in the previous cycle. *) + let levels = + Level_storage.levels_with_commitments_in_cycle ctxt prev_cycle + in + (* Retrieve previous preserved seed. *) + let* prev_seed = Storage.Seed.For_cycle.get ctxt prev_cycle_computed in + (* Generate preserved seed by updating previous preserved seed with current revealed nonces. *) + let combine (c, random_seed) level = Storage.Seed.Nonce.get c level >>=? function - | Revealed nonce -> - Storage.Seed.Nonce.remove_existing c level >|=? fun c -> - (c, Seed_repr.nonce random_seed nonce, unrevealed) - | Unrevealed u -> - Storage.Seed.Nonce.remove_existing c level >|=? fun c -> - (c, random_seed, u :: unrevealed) + | Revealed nonce -> return (c, Seed_repr.update_seed random_seed nonce) + | Unrevealed _ -> return (c, random_seed) in - Storage.Seed.For_cycle.get c previous_cycle >>=? fun prev_seed -> let seed = Seed_repr.deterministic_seed prev_seed in - List.fold_left_es combine (c, seed, []) levels - >>=? fun (c, seed, unrevealed) -> - Storage.Seed.For_cycle.init c cycle seed >|=? fun c -> (c, unrevealed) + let* c, seed = List.fold_left_es combine (ctxt, seed) levels in + Storage.Seed.For_cycle.init c cycle_computed seed + | _, _ -> return ctxt -let for_cycle ctxt cycle = - let preserved = Constants_storage.preserved_cycles ctxt in +let get_seed_computation_status ctxt = let current_level = Level_storage.current ctxt in let current_cycle = current_level.cycle in + let nonce_revelation_threshold = + Constants_storage.nonce_revelation_threshold ctxt + in + if Compare.Int32.(current_level.cycle_position < nonce_revelation_threshold) + then return Nonce_revelation_stage + else + let* status = Storage.Seed.get_status ctxt in + match status with + | RANDAO_seed -> + let preserved = Constants_storage.preserved_cycles ctxt in + let cycle_computed = Cycle_repr.add current_cycle (preserved + 1) in + let previous_cycle = Cycle_repr.add current_cycle preserved in + let* seed_discriminant = + Storage.Seed.For_cycle.get ctxt previous_cycle + in + let* seed_challenge = Storage.Seed.For_cycle.get ctxt cycle_computed in + return (Vdf_revelation_stage {seed_discriminant; seed_challenge}) + | VDF_seed -> return Computation_finished + +let check_vdf_and_update_seed ctxt vdf_solution = + let* r = get_seed_computation_status ctxt in + let*? seed_discriminant, seed_challenge = + match r with + | Computation_finished -> error Already_accepted + | Nonce_revelation_stage -> error Too_early_revelation + | Vdf_revelation_stage {seed_discriminant; seed_challenge} -> + ok (seed_discriminant, seed_challenge) + in + (* To avoid recomputing the discriminant and challenge for every (potentially + * invalid) submission in a cycle, we compute them once and store them *) + let* stored = Storage.Seed.VDF_setup.find ctxt in + let* ctxt, setup = + match stored with + | None -> + let setup = + Seed_repr.generate_vdf_setup ~seed_discriminant ~seed_challenge + in + let*! ctxt = Storage.Seed.VDF_setup.add ctxt setup in + return (ctxt, setup) + | Some setup -> return (ctxt, setup) + in + (* If VDF is valid, compute and update seed and change seed status from + * RANDAO to VDF *) + let*? () = + error_unless + (Option.value + ~default:false + (Seed_repr.verify + setup + (Constants_storage.vdf_difficulty ctxt) + vdf_solution)) + Unverified_vdf + in + let new_seed = Seed_repr.vdf_to_seed seed_challenge vdf_solution in + let current_cycle = (Level_storage.current ctxt).cycle in + let preserved = Constants_storage.preserved_cycles ctxt in + let cycle_computed = Cycle_repr.add current_cycle (preserved + 1) in + Storage.Seed.For_cycle.update ctxt cycle_computed new_seed Seed_repr.VDF_seed + +let for_cycle ctxt cycle = + let preserved = Constants_storage.preserved_cycles ctxt in + let current_cycle = (Level_storage.current ctxt).cycle in let latest = if Cycle_repr.(current_cycle = root) then Cycle_repr.add current_cycle (preserved + 1) @@ -101,27 +221,30 @@ let for_cycle ctxt cycle = | None -> Cycle_repr.root | Some oldest -> oldest in - error_unless - Cycle_repr.(oldest <= cycle && cycle <= latest) - (Unknown {oldest; cycle; latest}) - >>?= fun () -> Storage.Seed.For_cycle.get ctxt cycle + let*? () = + error_unless + Cycle_repr.(oldest <= cycle && cycle <= latest) + (Unknown {oldest; cycle; latest}) + in + Storage.Seed.For_cycle.get ctxt cycle let init ?initial_seed ctxt = let preserved = Constants_storage.preserved_cycles ctxt in + let* ctxt = Storage.Seed_status.init ctxt Seed_repr.RANDAO_seed in List.fold_left_es (fun (c, ctxt) seed -> let cycle = Cycle_repr.of_int32_exn (Int32.of_int c) in - Storage.Seed.For_cycle.init ctxt cycle seed >|=? fun ctxt -> (c + 1, ctxt)) + let+ ctxt = Storage.Seed.For_cycle.init ctxt cycle seed in + (c + 1, ctxt)) (0, ctxt) (Seed_repr.initial_seeds ?initial_seed (preserved + 2)) >|=? snd let cycle_end ctxt last_cycle = + let*! ctxt = Storage.Seed.VDF_setup.remove ctxt in (* NB: the clearing of past seeds is done elsewhere by the caller *) - let preserved = Constants_storage.preserved_cycles ctxt in match Cycle_repr.pred last_cycle with | None -> return (ctxt, []) - | Some revealed -> + | Some previous_cycle -> (* cycle with revelations *) - let inited_seed_cycle = Cycle_repr.add last_cycle (preserved + 1) in - compute_for_cycle ctxt ~revealed inited_seed_cycle + purge_nonces_and_get_unrevealed ctxt ~cycle:previous_cycle diff --git a/src/proto_alpha/lib_protocol/seed_storage.mli b/src/proto_alpha/lib_protocol/seed_storage.mli index de05bd8a4bb1605f3dda517d21f56467774308f5..8942a010c9fc3db7e60a34276fbd23c2ec355c36 100644 --- a/src/proto_alpha/lib_protocol/seed_storage.mli +++ b/src/proto_alpha/lib_protocol/seed_storage.mli @@ -23,12 +23,23 @@ (* *) (*****************************************************************************) +type seed_computation_status = + | Nonce_revelation_stage + | Vdf_revelation_stage of { + seed_discriminant : Seed_repr.seed; + seed_challenge : Seed_repr.seed; + } + | Computation_finished + type error += | Unknown of { oldest : Cycle_repr.t; cycle : Cycle_repr.t; latest : Cycle_repr.t; } + | Already_accepted + | Unverified_vdf + | Too_early_revelation (* `Permanent *) @@ -37,12 +48,26 @@ type error += val init : ?initial_seed:State_hash.t -> Raw_context.t -> Raw_context.t tzresult Lwt.t +(** Verifies if a VDF (result, proof) is valid, if so updates the seed with a + function of the VDF result. *) +val check_vdf_and_update_seed : + Raw_context.t -> Seed_repr.vdf_solution -> Raw_context.t tzresult Lwt.t + val for_cycle : Raw_context.t -> Cycle_repr.t -> Seed_repr.seed tzresult Lwt.t -(** If it is the end of the cycle, computes and stores the seed of cycle at - distance [preserved_cycle+2] in the future using the seed of the previous - cycle and the revelations of the current one. *) +(** Computes RANDAO output for cycle #(current_cycle + preserved + 1) *) +val compute_randao : Raw_context.t -> Raw_context.t tzresult Lwt.t + +(** Must be run at the end of the cycle, resets the VDF state and returns + unrevealed nonces to know which party has to forfeit its endorsing + rewards for that cycle. *) val cycle_end : Raw_context.t -> Cycle_repr.t -> (Raw_context.t * Nonce_storage.unrevealed list) tzresult Lwt.t + +(** Return the random seed computation status, that is whether the VDF + computation period has started, and if so the information needed, or if it has + finished for the current cycle. *) +val get_seed_computation_status : + Raw_context.t -> seed_computation_status tzresult Lwt.t diff --git a/src/proto_alpha/lib_protocol/storage.ml b/src/proto_alpha/lib_protocol/storage.ml index 8e6ec9675905ecdefe43feb21d23496c736eefd7..6fa5bebb3a82a536ba41dcfa47a6ba6795432174 100644 --- a/src/proto_alpha/lib_protocol/storage.ml +++ b/src/proto_alpha/lib_protocol/storage.ml @@ -1197,12 +1197,30 @@ module type FOR_CYCLE = sig val get : Raw_context.t -> Cycle_repr.t -> Seed_repr.seed tzresult Lwt.t + val update : + Raw_context.t -> + Cycle_repr.t -> + Seed_repr.seed -> + Seed_repr.seed_status -> + Raw_context.t tzresult Lwt.t + val remove_existing : Raw_context.t -> Cycle_repr.t -> Raw_context.t tzresult Lwt.t end (** Seed *) +module Seed_status = + Make_single_data_storage (Registered) (Raw_context) + (struct + let name = ["seed_status"] + end) + (struct + type t = Seed_repr.seed_status + + let encoding = Seed_repr.seed_status_encoding + end) + module Seed = struct type unrevealed_nonce = Cycle.unrevealed_nonce = { nonce_hash : Nonce_hash.t; @@ -1247,7 +1265,36 @@ module Seed = struct Cycle.Nonce.remove (ctxt, l.cycle) l.level end - module For_cycle : FOR_CYCLE = Cycle.Seed + module VDF_setup = + Make_single_data_storage (Registered) (Raw_context) + (struct + let name = ["vdf_challenge"] + end) + (struct + type t = Seed_repr.vdf_setup + + let encoding = Seed_repr.vdf_setup_encoding + end) + + module For_cycle : FOR_CYCLE = struct + let init ctxt cycle seed = + let open Lwt_result_syntax in + let* ctxt = Cycle.Seed.init ctxt cycle seed in + let*! ctxt = Seed_status.add ctxt Seed_repr.RANDAO_seed in + return ctxt + + let mem = Cycle.Seed.mem + + let get = Cycle.Seed.get + + let update ctxt cycle seed status = + Cycle.Seed.update ctxt cycle seed >>=? fun ctxt -> + Seed_status.update ctxt status + + let remove_existing = Cycle.Seed.remove_existing + end + + let get_status = Seed_status.get end (** Commitments *) diff --git a/src/proto_alpha/lib_protocol/storage.mli b/src/proto_alpha/lib_protocol/storage.mli index e6aa79faa30a4e6cd6930a5147047c545cf5c9f9..d182c09add358ae890d9f2fb41335d29d03eb94c 100644 --- a/src/proto_alpha/lib_protocol/storage.mli +++ b/src/proto_alpha/lib_protocol/storage.mli @@ -466,12 +466,22 @@ module type FOR_CYCLE = sig val get : Raw_context.t -> Cycle_repr.t -> Seed_repr.seed tzresult Lwt.t + val update : + Raw_context.t -> + Cycle_repr.t -> + Seed_repr.seed -> + Seed_repr.seed_status -> + Raw_context.t tzresult Lwt.t + val remove_existing : Raw_context.t -> Cycle_repr.t -> Raw_context.t tzresult Lwt.t end (** Seed *) +module Seed_status : + Simple_single_data_storage with type value = Seed_repr.seed_status + module Seed : sig (** Storage from this submodule must only be accessed through the module `Seed`. *) @@ -491,7 +501,14 @@ module Seed : sig and type value := nonce_status and type t := Raw_context.t + module VDF_setup : + Single_data_storage + with type value = Seed_repr.vdf_setup + and type t := Raw_context.t + module For_cycle : FOR_CYCLE + + val get_status : Raw_context.t -> Seed_repr.seed_status tzresult Lwt.t end (** Commitments *) diff --git a/src/proto_alpha/lib_protocol/test/helpers/block.ml b/src/proto_alpha/lib_protocol/test/helpers/block.ml index 953b74c3071ddb642f50c6f56cc7f75e817afd64..41f5b880a6e239515318a9f10a3612b1dd0b9d8d 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/block.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/block.ml @@ -257,7 +257,13 @@ let protocol_param_key = ["protocol_parameters"] let check_constants_consistency constants = let open Constants.Parametric in - let {blocks_per_cycle; blocks_per_commitment; blocks_per_stake_snapshot; _} = + let { + blocks_per_cycle; + blocks_per_commitment; + nonce_revelation_threshold; + blocks_per_stake_snapshot; + _; + } = constants in Error_monad.unless (blocks_per_commitment <= blocks_per_cycle) (fun () -> @@ -265,6 +271,11 @@ let check_constants_consistency constants = "Inconsistent constants : blocks per commitment must be less than \ blocks per cycle") >>=? fun () -> + Error_monad.unless (nonce_revelation_threshold <= blocks_per_cycle) (fun () -> + failwith + "Inconsistent constants : blocks per reveal period must be less than \ + blocks per cycle") + >>=? fun () -> Error_monad.unless (blocks_per_cycle >= blocks_per_stake_snapshot) (fun () -> failwith "Inconsistent constants : blocks per cycle must be superior than \ @@ -423,7 +434,8 @@ let prepare_initial_context_params ?consensus_threshold ?min_proposal_quorum ?baking_reward_bonus_per_slot ?baking_reward_fixed_portion ?origination_size ?blocks_per_cycle ?cycles_per_voting_period ?tx_rollup_enable ?tx_rollup_sunset_level ?tx_rollup_origination_size ?sc_rollup_enable - ?dal_enable ?hard_gas_limit_per_block initial_accounts = + ?dal_enable ?hard_gas_limit_per_block ?nonce_revelation_threshold + initial_accounts = let open Tezos_protocol_alpha_parameters in let constants = Default_parameters.constants_test in let min_proposal_quorum = @@ -490,6 +502,11 @@ let prepare_initial_context_params ?consensus_threshold ?min_proposal_quorum ~default:constants.hard_gas_limit_per_block hard_gas_limit_per_block in + let nonce_revelation_threshold = + Option.value + ~default:constants.nonce_revelation_threshold + nonce_revelation_threshold + in let constants = { constants with @@ -513,6 +530,7 @@ let prepare_initial_context_params ?consensus_threshold ?min_proposal_quorum sc_rollup = {constants.sc_rollup with enable = sc_rollup_enable}; dal = {constants.dal with feature_enable = dal_enable}; hard_gas_limit_per_block; + nonce_revelation_threshold; } in (* Check there is at least one roll *) @@ -561,7 +579,7 @@ let genesis ?commitments ?consensus_threshold ?min_proposal_quorum ?baking_reward_fixed_portion ?origination_size ?blocks_per_cycle ?cycles_per_voting_period ?tx_rollup_enable ?tx_rollup_sunset_level ?tx_rollup_origination_size ?sc_rollup_enable ?dal_enable - ?hard_gas_limit_per_block + ?hard_gas_limit_per_block ?nonce_revelation_threshold (initial_accounts : (Account.t * Tez.t * Signature.Public_key_hash.t option) list) = prepare_initial_context_params @@ -582,6 +600,7 @@ let genesis ?commitments ?consensus_threshold ?min_proposal_quorum ?sc_rollup_enable ?dal_enable ?hard_gas_limit_per_block + ?nonce_revelation_threshold initial_accounts >>=? fun (constants, shell, hash) -> initial_context diff --git a/src/proto_alpha/lib_protocol/test/helpers/block.mli b/src/proto_alpha/lib_protocol/test/helpers/block.mli index 74ea23ec0fa25eaec5160c686c557445eafa8a2b..79683b6841eadce7ec01bcab041ecab0d16e4755 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/block.mli +++ b/src/proto_alpha/lib_protocol/test/helpers/block.mli @@ -127,6 +127,7 @@ val genesis : ?sc_rollup_enable:bool -> ?dal_enable:bool -> ?hard_gas_limit_per_block:Gas.Arith.integral -> + ?nonce_revelation_threshold:int32 -> (Account.t * Tez.tez * Signature.Public_key_hash.t option) list -> block tzresult Lwt.t @@ -270,6 +271,7 @@ val prepare_initial_context_params : ?sc_rollup_enable:bool -> ?dal_enable:bool -> ?hard_gas_limit_per_block:Gas.Arith.integral -> + ?nonce_revelation_threshold:int32 -> (Account.t * Tez.t * Signature.Public_key_hash.t option) list -> ( Constants.Parametric.t * Block_header.shell_header * Block_hash.t, tztrace ) diff --git a/src/proto_alpha/lib_protocol/test/helpers/context.ml b/src/proto_alpha/lib_protocol/test/helpers/context.ml index 245918e388dea68e7957cfe687e1617f022f16ed..92f4c0723f2287dd4ad21a8a4b40108b31371189 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/context.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/context.ml @@ -181,6 +181,9 @@ let get_seed_nonce_hash ctxt = let get_seed ctxt = Alpha_services.Seed.get rpc_ctxt ctxt +let get_seed_computation ctxt = + Alpha_services.Seed_computation.get rpc_ctxt ctxt + let get_constants ctxt = Alpha_services.Constants.all rpc_ctxt ctxt let default_test_constants = @@ -399,7 +402,7 @@ let init_gen tup ?rng_state ?commitments ?(initial_balances = []) ?baking_reward_fixed_portion ?origination_size ?blocks_per_cycle ?cycles_per_voting_period ?tx_rollup_enable ?tx_rollup_sunset_level ?tx_rollup_origination_size ?sc_rollup_enable ?dal_enable - ?hard_gas_limit_per_block () = + ?hard_gas_limit_per_block ?nonce_revelation_threshold () = let n = tup_n tup in let accounts = Account.generate_accounts @@ -433,6 +436,7 @@ let init_gen tup ?rng_state ?commitments ?(initial_balances = []) ?sc_rollup_enable ?dal_enable ?hard_gas_limit_per_block + ?nonce_revelation_threshold accounts >|=? fun blk -> (blk, tup_get tup contracts) diff --git a/src/proto_alpha/lib_protocol/test/helpers/context.mli b/src/proto_alpha/lib_protocol/test/helpers/context.mli index 344e0ba7fbff0665135138f8b6ff860c64ec64fc..c11e6c1020213f4857eede9db5d9e3aedbce3adc 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/context.mli +++ b/src/proto_alpha/lib_protocol/test/helpers/context.mli @@ -70,6 +70,8 @@ val get_seed_nonce_hash : t -> Nonce_hash.t tzresult Lwt.t (** Returns the seed of the cycle to which the block belongs to. *) val get_seed : t -> Seed.seed tzresult Lwt.t +val get_seed_computation : t -> Seed.seed_computation_status tzresult Lwt.t + (** Returns all the constants of the protocol *) val get_constants : t -> Constants.t tzresult Lwt.t @@ -243,6 +245,7 @@ type 'accounts init := ?sc_rollup_enable:bool -> ?dal_enable:bool -> ?hard_gas_limit_per_block:Gas.Arith.integral -> + ?nonce_revelation_threshold:int32 -> unit -> (Block.t * 'accounts) tzresult Lwt.t diff --git a/src/proto_alpha/lib_protocol/test/helpers/op.ml b/src/proto_alpha/lib_protocol/test/helpers/op.ml index 54ad5fe783c7b6594426ccd10c08e1c1730d077b..b0a4f357151bc07827c9344d64c54c1ef4251d1d 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/op.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/op.ml @@ -527,6 +527,14 @@ let seed_nonce_revelation ctxt level nonce = }; } +let vdf_revelation ctxt solution = + { + shell = {branch = Context.branch ctxt}; + protocol_data = + Operation_data + {contents = Single (Vdf_revelation {solution}); signature = None}; + } + let proposals ctxt (pkh : Contract.t) proposals = let source = Context.Contract.pkh pkh in Context.Vote.get_current_period ctxt diff --git a/src/proto_alpha/lib_protocol/test/helpers/op.mli b/src/proto_alpha/lib_protocol/test/helpers/op.mli index 4432852798cd10e8dbaf01be9702d68dcaf366d5..d7e61c96028ca3f021a626143c28d25100ab1853 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/op.mli +++ b/src/proto_alpha/lib_protocol/test/helpers/op.mli @@ -266,6 +266,9 @@ val batch_operations : val seed_nonce_revelation : Context.t -> Raw_level.t -> Nonce.t -> Operation.packed +(** Reveals a VDF with a proof of correctness *) +val vdf_revelation : Context.t -> Seed.vdf_solution -> Operation.packed + (** Propose a list of protocol hashes during the approval voting *) val proposals : Context.t -> diff --git a/src/proto_alpha/lib_protocol/test/integration/consensus/test_seed.ml b/src/proto_alpha/lib_protocol/test/integration/consensus/test_seed.ml index afb73d4646eaa534995c33099c880f82360a7760..58ee25898137a4a40fd18f9dcd235913b1bb6d23 100644 --- a/src/proto_alpha/lib_protocol/test/integration/consensus/test_seed.ml +++ b/src/proto_alpha/lib_protocol/test/integration/consensus/test_seed.ml @@ -35,21 +35,82 @@ *) open Protocol +open Lwt_result_syntax + +(** Checking that, in the absence of nonce revelations and VDF computation, + the seed of each cycle is correctly computed based on the seed of + the previous cycle. *) +let test_seed_no_commitment () = + let n_cycles = 15 in + let (Hash initial_seed) = + let empty_bytes = Bytes.(copy empty) in + Tezos_crypto.Hacl.Blake2b.direct empty_bytes Nonce_hash.size + in + let seeds = + (* compute the first `n_cycles` expected seeds *) + let zero_bytes = Bytes.make Nonce_hash.size '\000' in + let rec make_seeds s = function + | 0 -> [] + | n -> + let (Hash h) = + Tezos_crypto.Hacl.Blake2b.direct + (Bytes.cat s zero_bytes) + Nonce_hash.size + in + h :: make_seeds h (n - 1) + in + make_seeds initial_seed n_cycles + in + let check_seed b expected_seed = + let open Alpha_context in + let* s = Context.get_seed (B b) in + let seed_bytes = Data_encoding.Binary.to_bytes_exn Seed.seed_encoding s in + (if expected_seed <> seed_bytes then + let seed_pp = + Hex.show + (Hex.of_string + (Data_encoding.Binary.to_string_exn Seed.seed_encoding s)) + in + let expected_seed_pp = Hex.show (Hex.of_bytes expected_seed) in + Stdlib.failwith + (Format.sprintf "Seed: %s\nExpected: %s\n\n" seed_pp expected_seed_pp)) ; + return b + in + let rec bake_and_check_seed b = function + | [] -> return b + | s :: seeds -> + let* b = Block.bake_until_cycle_end b in + let* b = check_seed b s in + let* b = Block.bake_n 2 b in + bake_and_check_seed b seeds + in + let* b, _delegates = + Context.init3 + ~blocks_per_cycle:8l + ~consensus_threshold:0 + ~nonce_revelation_threshold:2l + () + in + let* b = check_seed b initial_seed in + let* _ = bake_and_check_seed b seeds in + return_unit (** Baking [blocks_per_commitment] blocks without a [seed_nonce_hash] commitment fails with an "Invalid commitment in block header" error. *) let test_no_commitment () = - Context.init_n ~consensus_threshold:0 5 () >>=? fun (b, _contracts) -> - Context.get_constants (B b) - >>=? fun {parametric = {blocks_per_commitment; _}; _} -> + let* b, _contracts = Context.init_n ~consensus_threshold:0 5 () in + let* {parametric = {blocks_per_commitment; _}; _} = + Context.get_constants (B b) + in let blocks_per_commitment = Int32.to_int blocks_per_commitment in (* Bake normally until before the commitment *) - Block.bake_n (blocks_per_commitment - 2) b >>=? fun b -> + let* b = Block.bake_n (blocks_per_commitment - 2) b in (* Forge a block with empty commitment and apply it *) - Block.Forge.forge_header b >>=? fun header -> - Block.Forge.set_seed_nonce_hash None header |> Block.Forge.sign_header - >>=? fun header -> - Block.apply header b >>= fun e -> + let* header = Block.Forge.forge_header b in + let* header = + Block.Forge.set_seed_nonce_hash None header |> Block.Forge.sign_header + in + let*! e = Block.apply header b in Assert.proto_error_with_info ~loc:__LOC__ e @@ -63,8 +124,8 @@ let test_no_commitment () = - revealing twice produces an error *) let test_revelation_early_wrong_right_twice () = let open Assert in - Context.init_n ~consensus_threshold:0 5 () >>=? fun (b, _contracts) -> - Context.get_constants (B b) >>=? fun csts -> + let* b, _contracts = Context.init_n ~consensus_threshold:0 5 () in + let* csts = Context.get_constants (B b) in let tip = csts.parametric.seed_nonce_revelation_tip in let blocks_per_commitment = Int32.to_int csts.parametric.blocks_per_commitment @@ -73,71 +134,77 @@ let test_revelation_early_wrong_right_twice () = csts.parametric.baking_reward_fixed_portion in (* get the pkh of a baker *) - Block.get_next_baker b >>=? fun (pkh, _, _) -> + let* pkh, _, _ = Block.get_next_baker b in let id = Alpha_context.Contract.Implicit pkh in let policy = Block.Excluding [pkh] in (* bake until commitment - 2, excluding id *) - Block.bake_n ~policy (blocks_per_commitment - 2) b >>=? fun b -> - Context.Contract.balance (B b) id >>=? fun bal_main -> + let* b = Block.bake_n ~policy (blocks_per_commitment - 2) b in + let* bal_main = Context.Contract.balance (B b) id in (* the baker [id] will include a seed_nonce commitment *) - Block.bake ~policy:(Block.By_account pkh) b >>=? fun b -> - Context.get_level (B b) >>?= fun level_commitment -> - Context.get_seed_nonce_hash (B b) >>=? fun committed_hash -> + let* b = Block.bake ~policy:(Block.By_account pkh) b in + let*? level_commitment = Context.get_level (B b) in + let* committed_hash = Context.get_seed_nonce_hash (B b) in (* test that the baking reward is received *) - balance_was_credited - ~loc:__LOC__ - (B b) - id - bal_main - baking_reward_fixed_portion - >>=? fun () -> + let* () = + balance_was_credited + ~loc:__LOC__ + (B b) + id + bal_main + baking_reward_fixed_portion + in (* test that revealing too early produces an error *) - Op.seed_nonce_revelation - (B b) - level_commitment - (WithExceptions.Option.to_exn ~none:Not_found @@ Nonce.get committed_hash) - |> fun operation -> - Block.bake ~policy ~operation b >>= fun e -> - Assert.proto_error_with_info ~loc:__LOC__ e "Too early nonce revelation" - >>=? fun () -> + let operation = + Op.seed_nonce_revelation + (B b) + level_commitment + (WithExceptions.Option.to_exn ~none:Not_found @@ Nonce.get committed_hash) + in + let*! e = Block.bake ~policy ~operation b in + let* () = + Assert.proto_error_with_info ~loc:__LOC__ e "Too early nonce revelation" + in (* finish the cycle excluding the committing baker, id *) - Block.bake_until_cycle_end ~policy b >>=? fun b -> + let* b = Block.bake_until_cycle_end ~policy b in (* test that revealing at the right time but the wrong value produces an error *) let wrong_hash, _ = Nonce.generate () in - Op.seed_nonce_revelation - (B b) - level_commitment - (WithExceptions.Option.to_exn ~none:Not_found @@ Nonce.get wrong_hash) - |> fun operation -> - Block.bake ~operation b >>= fun e -> - Assert.proto_error_with_info ~loc:__LOC__ e "Inconsistent nonce" - >>=? fun () -> + let operation = + Op.seed_nonce_revelation + (B b) + level_commitment + (WithExceptions.Option.to_exn ~none:Not_found @@ Nonce.get wrong_hash) + in + let*! e = Block.bake ~operation b in + let* () = Assert.proto_error_with_info ~loc:__LOC__ e "Inconsistent nonce" in (* reveals correctly *) - Op.seed_nonce_revelation - (B b) - level_commitment - (WithExceptions.Option.to_exn ~none:Not_found @@ Nonce.get committed_hash) - |> fun operation -> - Block.get_next_baker ~policy b >>=? fun (baker_pkh, _, _) -> + let operation = + Op.seed_nonce_revelation + (B b) + level_commitment + (WithExceptions.Option.to_exn ~none:Not_found @@ Nonce.get committed_hash) + in + let* baker_pkh, _, _ = Block.get_next_baker ~policy b in let baker = Alpha_context.Contract.Implicit baker_pkh in - Context.Contract.balance (B b) baker >>=? fun baker_bal -> - Block.bake ~policy:(Block.By_account baker_pkh) ~operation b >>=? fun b -> + let* baker_bal = Context.Contract.balance (B b) baker in + let* b = Block.bake ~policy:(Block.By_account baker_pkh) ~operation b in (* test that the baker gets the tip reward plus the baking reward*) - balance_was_credited - ~loc:__LOC__ - (B b) - baker - baker_bal - Test_tez.(tip +! baking_reward_fixed_portion) - >>=? fun () -> + let* () = + balance_was_credited + ~loc:__LOC__ + (B b) + baker + baker_bal + Test_tez.(tip +! baking_reward_fixed_portion) + in (* test that revealing twice produces an error *) - Op.seed_nonce_revelation - (B b) - level_commitment - (WithExceptions.Option.to_exn ~none:Not_found @@ Nonce.get wrong_hash) - |> fun operation -> - Block.bake ~operation ~policy b >>= fun e -> + let operation = + Op.seed_nonce_revelation + (B b) + level_commitment + (WithExceptions.Option.to_exn ~none:Not_found @@ Nonce.get wrong_hash) + in + let*! e = Block.bake ~operation ~policy b in Assert.proto_error_with_info ~loc:__LOC__ e "Previously revealed nonce" (** Test that revealing too late produces an error. Note that a @@ -145,30 +212,46 @@ let test_revelation_early_wrong_right_twice () = let test_revelation_missing_and_late () = let open Context in let open Assert in - Context.init_n ~consensus_threshold:0 5 () >>=? fun (b, _contracts) -> - get_constants (B b) >>=? fun csts -> + let* b, _contracts = Context.init_n ~consensus_threshold:0 5 () in + let* csts = get_constants (B b) in let blocks_per_commitment = Int32.to_int csts.parametric.blocks_per_commitment in + let nonce_revelation_threshold = + Int32.to_int csts.parametric.nonce_revelation_threshold + in (* bake until commitment *) - Block.bake_n (blocks_per_commitment - 2) b >>=? fun b -> + let* b = Block.bake_n (blocks_per_commitment - 2) b in (* the next baker [id] will include a seed_nonce commitment *) - Block.get_next_baker b >>=? fun (pkh, _, _) -> - Block.bake b >>=? fun b -> - Context.get_level (B b) >>?= fun level_commitment -> - Context.get_seed_nonce_hash (B b) >>=? fun committed_hash -> + let* pkh, _, _ = Block.get_next_baker b in + let* b = Block.bake b in + let*? level_commitment = Context.get_level (B b) in + let* committed_hash = Context.get_seed_nonce_hash (B b) in (* finish cycle 0 excluding the committing baker [id] *) let policy = Block.Excluding [pkh] in - Block.bake_until_cycle_end ~policy b >>=? fun b -> + let* b = Block.bake_until_cycle_end ~policy b in + (* test that revealing after revelation period produces an error *) + let* b = Block.bake_n (nonce_revelation_threshold - 1) b in + let operation = + Op.seed_nonce_revelation + (B b) + level_commitment + (WithExceptions.Option.to_exn ~none:Not_found @@ Nonce.get committed_hash) + in + let*! e = Block.bake ~operation ~policy b in + let* () = + Assert.proto_error_with_info ~loc:__LOC__ e "Too late nonce revelation" + in (* finish cycle 1 excluding the committing baker [id] *) - Block.bake_until_cycle_end ~policy b >>=? fun b -> - (* test that revealing too late (after cycle 1) produces an error *) - Op.seed_nonce_revelation - (B b) - level_commitment - (WithExceptions.Option.to_exn ~none:Not_found @@ Nonce.get committed_hash) - |> fun operation -> - Block.bake ~operation b >>= fun e -> + let* b = Block.bake_until_cycle_end ~policy b in + (* test that revealing too late after cycle 1 produces an error *) + let operation = + Op.seed_nonce_revelation + (B b) + level_commitment + (WithExceptions.Option.to_exn ~none:Not_found @@ Nonce.get committed_hash) + in + let*! e = Block.bake ~operation b in Assert.proto_error_with_info ~loc:__LOC__ e "Too late nonce revelation" let wrap e = e >|= Environment.wrap_tzresult @@ -188,44 +271,240 @@ let test_unrevealed () = minimal_participation_ratio = Ratio.{numerator = 0; denominator = 1}; } in - Context.init_with_constants2 constants >>=? fun (b, (_account1, account2)) -> + let* b, (_account1, account2) = Context.init_with_constants2 constants in let delegate2 = Context.Contract.pkh account2 in (* Delegate 2 will add a nonce but never reveals it *) - Context.get_constants (B b) >>=? fun csts -> + let* csts = Context.get_constants (B b) in let blocks_per_commitment = Int32.to_int csts.parametric.blocks_per_commitment in let bake_and_endorse_block ?policy (pred_b, b) = - Context.get_endorsers (B b) >>=? fun slots -> - List.map_es - (fun {Plugin.RPC.Validators.delegate; slots; _} -> - Op.endorsement - ~delegate:(delegate, slots) - ~endorsed_block:b - (B pred_b) - () - >>=? fun op -> Operation.pack op |> return) - slots - >>=? fun endorsements -> Block.bake ?policy ~operations:endorsements b + let* slots = Context.get_endorsers (B b) in + let* endorsements = + List.map_es + (fun {Plugin.RPC.Validators.delegate; slots; _} -> + Op.endorsement + ~delegate:(delegate, slots) + ~endorsed_block:b + (B pred_b) + () + >>=? fun op -> Operation.pack op |> return) + slots + in + Block.bake ?policy ~operations:endorsements b in (* Bake until commitment *) - Block.bake_n (blocks_per_commitment - 2) b >>=? fun b -> + let* b = Block.bake_n (blocks_per_commitment - 2) b in (* Baker delegate 2 will include a seed_nonce commitment *) let policy = Block.By_account delegate2 in - Block.bake_until_cycle_end ~policy b >>=? fun b -> - Context.Delegate.info (B b) delegate2 >>=? fun info_before -> - Block.bake ~policy b >>=? fun b' -> - bake_and_endorse_block ~policy (b, b') >>=? fun b -> + let* b = Block.bake_until_cycle_end ~policy b in + let* info_before = Context.Delegate.info (B b) delegate2 in + let* b' = Block.bake ~policy b in + let* b = bake_and_endorse_block ~policy (b, b') in (* Finish cycle 1 excluding the first baker *) - Block.bake_until_cycle_end ~policy b >>=? fun b -> - Context.Delegate.info (B b) delegate2 >>=? fun info_after -> + let* b = Block.bake_until_cycle_end ~policy b in + let* info_after = Context.Delegate.info (B b) delegate2 in (* Assert that we did not received a reward because we didn't reveal the nonce. *) - Assert.equal_tez ~loc:__LOC__ info_before.full_balance info_after.full_balance - >>=? fun () -> return_unit + let* () = + Assert.equal_tez + ~loc:__LOC__ + info_before.full_balance + info_after.full_balance + in + return_unit + +let test_vdf_status () = + let* b, _ = Context.init3 ~consensus_threshold:0 () in + let* b = Block.bake b in + let* status = Context.get_seed_computation (B b) in + assert (status = Alpha_context.Seed.Nonce_revelation_stage) ; + let* constants = Context.get_constants (B b) in + let* b = + Block.bake_n + (Int32.to_int constants.parametric.nonce_revelation_threshold) + b + in + let* status = Context.get_seed_computation (B b) in + assert ( + match status with + | Alpha_context.Seed.Vdf_revelation_stage _ -> true + | _ -> false) ; + return_unit + +(** Choose a baker, denote it by id. In the first cycle, make id bake only once. + Check that: + - when the vdf is revealed too early, there's an error + - when the vdf is revealed at the right time but the wrong value, there's an error + - when the vdf is revealed at the right time and the correct value, + - the baker receives a reward + - the VDF status is updated to "Computation_finished" + - the seed is updated with the vdf solution + - another vdf revelation produces an error *) +let test_early_incorrect_unverified_correct_already_vdf () = + let open Assert in + let* b, _ = Context.init3 ~consensus_threshold:0 () in + let* csts = Context.get_constants (B b) in + let blocks_per_commitment = + Int32.to_int csts.parametric.blocks_per_commitment + in + let nonce_revelation_threshold = + Int32.to_int csts.parametric.nonce_revelation_threshold + in + let baking_reward_fixed_portion = + csts.parametric.baking_reward_fixed_portion + in + let seed_nonce_revelation_tip = csts.parametric.seed_nonce_revelation_tip in + let vdf_nonce_revelation_tip = csts.parametric.seed_nonce_revelation_tip in + (* get the pkh of a baker *) + let* pkh, _, _ = Block.get_next_baker b in + let id = Alpha_context.Contract.Implicit pkh in + let policy = Block.Excluding [pkh] in + (* bake until commitment - 2, excluding id *) + let* b = Block.bake_n ~policy (blocks_per_commitment - 2) b in + let* bal_main = Context.Contract.balance (B b) id in + (* the baker [id] will include a seed_nonce commitment *) + let* b = Block.bake ~policy:(Block.By_account pkh) b in + let*? level_commitment = Context.get_level (B b) in + let* committed_hash = Context.get_seed_nonce_hash (B b) in + (* test that the baking reward is received *) + let* () = + balance_was_credited + ~loc:__LOC__ + (B b) + id + bal_main + baking_reward_fixed_portion + in + (* finish the cycle excluding the committing baker, id *) + let* b = Block.bake_until_cycle_end ~policy b in + (* reveals correctly *) + let operation = + Op.seed_nonce_revelation + (B b) + level_commitment + (WithExceptions.Option.to_exn ~none:Not_found @@ Nonce.get committed_hash) + in + let* baker_pkh, _, _ = Block.get_next_baker ~policy b in + let baker = Alpha_context.Contract.Implicit baker_pkh in + let* baker_bal = Context.Contract.balance (B b) baker in + let* b = Block.bake ~policy:(Block.By_account baker_pkh) ~operation b in + (* test that the baker gets the tip reward plus the baking reward*) + let* () = + balance_was_credited + ~loc:__LOC__ + (B b) + baker + baker_bal + Test_tez.(seed_nonce_revelation_tip +! baking_reward_fixed_portion) + in + (* test that revealing the VDF early produces an error *) + let dummy_solution = + let open Environment.Vdf in + let dummy = Bytes.create Environment.Vdf.form_size_bytes in + let result = Stdlib.Option.get @@ result_of_bytes_opt dummy in + let proof = Stdlib.Option.get @@ proof_of_bytes_opt dummy in + (result, proof) + in + let operation = Op.vdf_revelation (B b) dummy_solution in + let*! e = Block.bake ~operation b in + let* () = + Assert.proto_error_with_info ~loc:__LOC__ e "Too early VDF revelation" + in + (* bake until nonce reveal period finishes *) + let* b = Block.bake_n ~policy nonce_revelation_threshold b in + (* test that revealing non group elements produces an error *) + let operation = Op.vdf_revelation (B b) dummy_solution in + let*! e = Block.bake ~operation b in + let* () = Assert.proto_error_with_info ~loc:__LOC__ e "Unverified VDF" in + let* seed_status = Context.get_seed_computation (B b) in + match seed_status with + | Nonce_revelation_stage -> assert false + | Computation_finished -> assert false + | Vdf_revelation_stage info -> ( + (* generate the VDF discriminant and challenge *) + let discriminant, challenge = + Alpha_context.Seed.generate_vdf_setup + ~seed_discriminant:info.seed_discriminant + ~seed_challenge:info.seed_challenge + in + (* test that revealing wrong VDF produces an error *) + let wrong_solution = + let open Environment.Vdf in + let f = challenge_to_bytes challenge in + let result = Stdlib.Option.get @@ result_of_bytes_opt f in + let proof = Stdlib.Option.get @@ proof_of_bytes_opt f in + (result, proof) + in + let operation = Op.vdf_revelation (B b) wrong_solution in + let*! e = Block.bake ~operation b in + let* () = Assert.proto_error_with_info ~loc:__LOC__ e "Unverified VDF" in + (* test with correct input *) + (* compute the VDF solution (the result and the proof ) *) + let solution = + (* generate the result and proof *) + Environment.Vdf.prove + discriminant + challenge + csts.parametric.vdf_difficulty + in + let* baker_bal = Context.Contract.balance (B b) baker in + let operation = Op.vdf_revelation (B b) solution in + (* verify the balance was credited following operation inclusion *) + let* b = Block.bake ~policy:(Block.By_account baker_pkh) ~operation b in + let* () = + balance_was_credited + ~loc:__LOC__ + (B b) + baker + baker_bal + Test_tez.(vdf_nonce_revelation_tip +! baking_reward_fixed_portion) + in + (* verify the seed status has changed *) + let* seed_status = Context.get_seed_computation (B b) in + match seed_status with + | Nonce_revelation_stage -> assert false + | Vdf_revelation_stage _ -> assert false + | Computation_finished -> + (* test than sending another VDF reveal produces an error *) + let operation = Op.vdf_revelation (B b) solution in + let*! e = Block.bake ~operation b in + let* () = + Assert.proto_error_with_info + ~loc:__LOC__ + e + "Previously revealed VDF" + in + (* verify the stored seed has the expected value *) + let open Data_encoding.Binary in + let open Alpha_context in + (* retrieving & converting seed stored in cycle n + preserved_cycle + 1 *) + let* b = + Block.bake_until_n_cycle_end + ~policy + (csts.parametric.preserved_cycles + 1) + b + in + let* stored_seed = Context.get_seed (B b) in + let vdf_stored_seed = to_bytes_exn Seed.seed_encoding stored_seed in + (* recomputing seed with randao output and vdf solution *) + let vdf_expected_seed = + let randao_seed = + to_bytes_exn Seed.seed_encoding info.seed_challenge + |> of_bytes_exn Seed_repr.seed_encoding + in + Seed_repr.vdf_to_seed randao_seed solution + |> to_bytes_exn Seed_repr.seed_encoding + in + assert (Bytes.(equal vdf_expected_seed vdf_stored_seed)) ; + return_unit) let tests = [ + Tztest.tztest + "seed computation (no commitment)" + `Quick + test_seed_no_commitment; Tztest.tztest "no commitment" `Quick test_no_commitment; Tztest.tztest "revelation_early_wrong_right_twice" @@ -236,4 +515,9 @@ let tests = `Quick test_revelation_missing_and_late; Tztest.tztest "test unrevealed" `Quick test_unrevealed; + Tztest.tztest + "test_early_incorrect_unverified_correct_already_vdf" + `Quick + test_early_incorrect_unverified_correct_already_vdf; + Tztest.tztest "test VDF status" `Quick test_vdf_status; ] diff --git a/src/proto_alpha/lib_protocol/test/integration/operations/test_voting.ml b/src/proto_alpha/lib_protocol/test/integration/operations/test_voting.ml index 4ea7ebaf3b1bade635361e033c5ac416ac3baa68..c02237ef02ead409e9662bdb208ac11611494fa5 100644 --- a/src/proto_alpha/lib_protocol/test/integration/operations/test_voting.ml +++ b/src/proto_alpha/lib_protocol/test/integration/operations/test_voting.ml @@ -260,6 +260,7 @@ let context_init = ~endorsing_reward_per_slot:Tez.zero ~baking_reward_bonus_per_slot:Tez.zero ~baking_reward_fixed_portion:Tez.zero + ~nonce_revelation_threshold:2l (** A normal and successful vote sequence. *) let test_successful_vote num_delegates () = diff --git a/src/proto_alpha/lib_protocol/validate_operation.ml b/src/proto_alpha/lib_protocol/validate_operation.ml index 42c5e4fd911742fa34f3c781ee42e4d0e1ee5ee2..db328e1359f6f940cfd2e5044f1d8064a17d101f 100644 --- a/src/proto_alpha/lib_protocol/validate_operation.ml +++ b/src/proto_alpha/lib_protocol/validate_operation.ml @@ -666,6 +666,7 @@ let validate_operation (vi : validate_operation_info) | Single (Endorsement _) | Single (Dal_slot_availability _) | Single (Seed_nonce_revelation _) + | Single (Vdf_revelation _) | Single (Proposals _) | Single (Ballot _) | Single (Activate_account _) @@ -720,6 +721,7 @@ module TMP_for_plugin = struct | Single (Endorsement _) | Single (Dal_slot_availability _) | Single (Seed_nonce_revelation _) + | Single (Vdf_revelation _) | Single (Proposals _) | Single (Ballot _) | Single (Activate_account _) diff --git a/tests_python/tests_alpha/test_mockup.py b/tests_python/tests_alpha/test_mockup.py index 1a39706f7306b7223f50a94588d1a021941e10b7..bd77360793051b5b64585d3d5b92ee5a4977b802 100644 --- a/tests_python/tests_alpha/test_mockup.py +++ b/tests_python/tests_alpha/test_mockup.py @@ -620,6 +620,7 @@ def _test_create_mockup_init_show_roundtrip( "baking_reward_bonus_per_slot": "2500", "endorsing_reward_per_slot": "2857", "origination_size": 258, + "vdf_difficulty": "1000000000", "seed_nonce_revelation_tip": "125001", "tokens_per_roll": "8000000001", "proof_of_work_threshold": "-2", @@ -640,6 +641,7 @@ def _test_create_mockup_init_show_roundtrip( "cycles_per_voting_period": 7, "blocks_per_stake_snapshot": 5, "blocks_per_commitment": 5, + "nonce_revelation_threshold": 5, "blocks_per_cycle": 9, "preserved_cycles": 3, "liquidity_baking_toggle_ema_threshold": 1000000000, diff --git a/tests_python/tests_alpha/test_voting.py b/tests_python/tests_alpha/test_voting.py index e0f268928b7ebf5747802a42dc318a4a8a3dea7a..8e8f229d35ae65238ef75866ed41632d64ba0769 100644 --- a/tests_python/tests_alpha/test_voting.py +++ b/tests_python/tests_alpha/test_voting.py @@ -17,6 +17,7 @@ def client(sandbox): proto_params = dict(protocol.PARAMETERS) parameters = copy.deepcopy(proto_params) parameters["blocks_per_cycle"] = 4 + parameters["nonce_revelation_threshold"] = 2 parameters["cycles_per_voting_period"] = 1 parameters['consensus_threshold'] = 0 sandbox.add_node(0, params=constants.NODE_PARAMS) diff --git a/tezt/_regressions/sc_rollup_commitment_of_rollup_node_first_published_at_level_global.out b/tezt/_regressions/sc_rollup_commitment_of_rollup_node_first_published_at_level_global.out new file mode 100644 index 0000000000000000000000000000000000000000..9a0b5ee0cd10230788919a8ae95d2bbb1a553f21 --- /dev/null +++ b/tezt/_regressions/sc_rollup_commitment_of_rollup_node_first_published_at_level_global.out @@ -0,0 +1,105 @@ +sc_rollup_commitment_of_rollup_node_first_published_at_level_global.out + +./tezos-client --wait none originate sc rollup from '[PUBLIC_KEY_HASH]' of kind arith booting with --burn-cap 9999999 +Node is bootstrapped. +Estimated gas: 1600.696 units (will add 100 for safety) +Estimated storage: 6534 bytes added (will add 20 for safety) +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000402 + Expected counter: 1 + Gas limit: 1701 + Storage limit: 6554 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000402 + payload fees(the block proposer) ....... +ꜩ0.000402 + Originate smart contract rollup of kind arith with boot sector '' + This smart contract rollup origination was successfully applied + Consumed gas: 1600.696 + Storage size: 6534 bytes + Address: [SC_ROLLUP_HASH] + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ1.6335 + storage fees ........................... +ꜩ1.6335 + + +./tezos-client rpc get '/chains/main/blocks/head/context/sc_rollup/[SC_ROLLUP_HASH]/initial_level' +2 + +./tezos-client rpc get /chains/main/blocks/head/context/constants +{ "proof_of_work_nonce_size": 8, "nonce_length": 32, + "max_anon_ops_per_block": 132, "max_operation_data_length": 32768, + "max_proposals_per_delegate": 20, "max_micheline_node_count": 50000, + "max_micheline_bytes_limit": 50000, + "max_allowed_global_constants_depth": 10000, "cache_layout_size": 3, + "michelson_maximum_type_size": 2001, "preserved_cycles": 2, + "blocks_per_cycle": 8, "blocks_per_commitment": 4, + "nonce_revelation_threshold": 4, "blocks_per_stake_snapshot": 4, + "cycles_per_voting_period": 8, "hard_gas_limit_per_operation": "1040000", + "hard_gas_limit_per_block": "5200000", "proof_of_work_threshold": "-1", + "tokens_per_roll": "6000000000", "vdf_difficulty": "50000", + "seed_nonce_revelation_tip": "125000", "origination_size": 257, + "baking_reward_fixed_portion": "333333", + "baking_reward_bonus_per_slot": "3921", + "endorsing_reward_per_slot": "2604", "cost_per_byte": "250", + "hard_storage_limit_per_operation": "60000", "quorum_min": 2000, + "quorum_max": 7000, "min_proposal_quorum": 500, + "liquidity_baking_subsidy": "2500000", + "liquidity_baking_sunset_level": 128, + "liquidity_baking_toggle_ema_threshold": 1000000000, + "max_operations_time_to_live": 120, "minimal_block_delay": "1", + "delay_increment_per_round": "1", "consensus_committee_size": 256, + "consensus_threshold": 0, + "minimal_participation_ratio": { "numerator": 2, "denominator": 3 }, + "max_slashing_period": 2, "frozen_deposits_percentage": 5, + "double_baking_punishment": "640000000", + "ratio_of_frozen_deposits_slashed_per_double_endorsement": + { "numerator": 1, "denominator": 2 }, "cache_script_size": 100000000, + "cache_stake_distribution_cycles": 8, "cache_sampler_state_cycles": 8, + "tx_rollup_enable": true, "tx_rollup_origination_size": 4000, + "tx_rollup_hard_size_limit_per_inbox": 500000, + "tx_rollup_hard_size_limit_per_message": 5000, + "tx_rollup_max_withdrawals_per_batch": 15, + "tx_rollup_commitment_bond": "10000000000", + "tx_rollup_finality_period": 40000, "tx_rollup_withdraw_period": 40000, + "tx_rollup_max_inboxes_count": 40100, + "tx_rollup_max_messages_per_inbox": 1010, + "tx_rollup_max_commitments_count": 80100, + "tx_rollup_cost_per_byte_ema_factor": 120, + "tx_rollup_max_ticket_payload_size": 2048, + "tx_rollup_rejection_max_proof_size": 30000, + "tx_rollup_sunset_level": 3473409, "sc_rollup_enable": true, + "sc_rollup_origination_size": 6314, + "sc_rollup_challenge_window_in_blocks": 20160, + "sc_rollup_max_available_messages": 1000000, + "sc_rollup_stake_amount_in_mutez": 32000000, + "sc_rollup_commitment_period_in_blocks": 30, + "sc_rollup_commitment_storage_size_in_bytes": 84, + "sc_rollup_max_lookahead_in_blocks": 30000 } + +./tezos-sc-rollup-client-alpha rpc get /last_published_commitment +{ "commitment": + { "compressed_state": + "[SC_ROLLUP_STATE_HASH]", + "inbox_level": 32, + "predecessor": "[SC_ROLLUP_COMMITMENT_HASH]", + "number_of_messages": 0, "number_of_ticks": 0 }, + "hash": "[SC_ROLLUP_COMMITMENT_HASH]", + "published_at_level": 35 } + +./tezos-sc-rollup-client-alpha rpc get /last_published_commitment +{ "commitment": + { "compressed_state": + "[SC_ROLLUP_STATE_HASH]", + "inbox_level": 32, + "predecessor": "[SC_ROLLUP_COMMITMENT_HASH]", + "number_of_messages": 0, "number_of_ticks": 0 }, + "hash": "[SC_ROLLUP_COMMITMENT_HASH]", + "published_at_level": 35 } diff --git a/tezt/_regressions/sc_rollup_commitment_of_rollup_node_handles_chain_reorgs.out b/tezt/_regressions/sc_rollup_commitment_of_rollup_node_handles_chain_reorgs.out new file mode 100644 index 0000000000000000000000000000000000000000..34e62a2196d33a0aa9c82550bad0f9b0f14264ee --- /dev/null +++ b/tezt/_regressions/sc_rollup_commitment_of_rollup_node_handles_chain_reorgs.out @@ -0,0 +1,147 @@ +sc_rollup_commitment_of_rollup_node_handles_chain_reorgs.out + +./tezos-client --wait none originate sc rollup from '[PUBLIC_KEY_HASH]' of kind arith booting with --burn-cap 9999999 +Node is bootstrapped. +Estimated gas: 1600.696 units (will add 100 for safety) +Estimated storage: 6534 bytes added (will add 20 for safety) +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000402 + Expected counter: 1 + Gas limit: 1701 + Storage limit: 6554 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000402 + payload fees(the block proposer) ....... +ꜩ0.000402 + Originate smart contract rollup of kind arith with boot sector '' + This smart contract rollup origination was successfully applied + Consumed gas: 1600.696 + Storage size: 6534 bytes + Address: [SC_ROLLUP_HASH] + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ1.6335 + storage fees ........................... +ꜩ1.6335 + + +./tezos-client rpc get '/chains/main/blocks/head/context/sc_rollup/[SC_ROLLUP_HASH]/initial_level' +2 + +./tezos-client rpc get /chains/main/blocks/head/context/constants +{ "proof_of_work_nonce_size": 8, "nonce_length": 32, + "max_anon_ops_per_block": 132, "max_operation_data_length": 32768, + "max_proposals_per_delegate": 20, "max_micheline_node_count": 50000, + "max_micheline_bytes_limit": 50000, + "max_allowed_global_constants_depth": 10000, "cache_layout_size": 3, + "michelson_maximum_type_size": 2001, "preserved_cycles": 2, + "blocks_per_cycle": 8, "blocks_per_commitment": 4, + "nonce_revelation_threshold": 4, "blocks_per_stake_snapshot": 4, + "cycles_per_voting_period": 8, "hard_gas_limit_per_operation": "1040000", + "hard_gas_limit_per_block": "5200000", "proof_of_work_threshold": "-1", + "tokens_per_roll": "6000000000", "vdf_difficulty": "50000", + "seed_nonce_revelation_tip": "125000", "origination_size": 257, + "baking_reward_fixed_portion": "333333", + "baking_reward_bonus_per_slot": "3921", + "endorsing_reward_per_slot": "2604", "cost_per_byte": "250", + "hard_storage_limit_per_operation": "60000", "quorum_min": 2000, + "quorum_max": 7000, "min_proposal_quorum": 500, + "liquidity_baking_subsidy": "2500000", + "liquidity_baking_sunset_level": 128, + "liquidity_baking_toggle_ema_threshold": 1000000000, + "max_operations_time_to_live": 120, "minimal_block_delay": "1", + "delay_increment_per_round": "1", "consensus_committee_size": 256, + "consensus_threshold": 0, + "minimal_participation_ratio": { "numerator": 2, "denominator": 3 }, + "max_slashing_period": 2, "frozen_deposits_percentage": 5, + "double_baking_punishment": "640000000", + "ratio_of_frozen_deposits_slashed_per_double_endorsement": + { "numerator": 1, "denominator": 2 }, "cache_script_size": 100000000, + "cache_stake_distribution_cycles": 8, "cache_sampler_state_cycles": 8, + "tx_rollup_enable": true, "tx_rollup_origination_size": 4000, + "tx_rollup_hard_size_limit_per_inbox": 500000, + "tx_rollup_hard_size_limit_per_message": 5000, + "tx_rollup_max_withdrawals_per_batch": 15, + "tx_rollup_commitment_bond": "10000000000", + "tx_rollup_finality_period": 40000, "tx_rollup_withdraw_period": 40000, + "tx_rollup_max_inboxes_count": 40100, + "tx_rollup_max_messages_per_inbox": 1010, + "tx_rollup_max_commitments_count": 80100, + "tx_rollup_cost_per_byte_ema_factor": 120, + "tx_rollup_max_ticket_payload_size": 2048, + "tx_rollup_rejection_max_proof_size": 30000, + "tx_rollup_sunset_level": 3473409, "sc_rollup_enable": true, + "sc_rollup_origination_size": 6314, + "sc_rollup_challenge_window_in_blocks": 20160, + "sc_rollup_max_available_messages": 1000000, + "sc_rollup_stake_amount_in_mutez": 32000000, + "sc_rollup_commitment_period_in_blocks": 30, + "sc_rollup_commitment_storage_size_in_bytes": 84, + "sc_rollup_max_lookahead_in_blocks": 30000 } + +./tezos-client --wait none send sc rollup message 'text:["CAFEBABE"]' from bootstrap1 to '[SC_ROLLUP_HASH]' +Node is bootstrapped. +Estimated gas: 1652.301 units (will add 100 for safety) +Estimated storage: no bytes added +Operation successfully injected in the node. +Operation hash is '[OPERATION_HASH]' +NOT waiting for the operation to be included. +Use command + tezos-client wait for [OPERATION_HASH] to be included --confirmations 1 --branch [BLOCK_HASH] +and/or an external block explorer to make sure that it has been included. +This sequence of operations was run: + Manager signed operations: + From: [PUBLIC_KEY_HASH] + Fee to the baker: ꜩ0.000457 + Expected counter: 2 + Gas limit: 1753 + Storage limit: 0 bytes + Balance updates: + [PUBLIC_KEY_HASH] ... -ꜩ0.000457 + payload fees(the block proposer) ....... +ꜩ0.000457 + Add a message to the inbox of the smart contract rollup at address [SC_ROLLUP_HASH] + This operation sending a message to a smart contract rollup was successfully applied + Consumed gas: 1652.301 + Resulting inbox state: + rollup = [SC_ROLLUP_HASH] + level = 32 + current messages hash = CoWCiKBE7uyrqCwVM1ZRQEXatHvkqQU9GPf94ktpjasEgkRgKst3 + nb_available_messages = 1 + nb_messages_in_commitment_period = 1 + starting_level_of_current_commitment_period = 32 + message_counter = 1 + old_levels_messages = + content = CoUkdBQ53N7FWav8LuTvrcp3jyoxnpqk3xnEo3gSCgNwia4fq44j + index = 30 + back_pointers = CoVombgbRqaZQavbAhd8Fe9uPd2VkwetxbaeuN3ky612Y1BW3NvM + CoVombgbRqaZQavbAhd8Fe9uPd2VkwetxbaeuN3ky612Y1BW3NvM + CoW1mKR4SV9TNevf2emDd2y5vBSJoHpPZAaXcK4rg3qYRqQpqc7G + CoUoi6qvq4GfPg1uwb68W7M74XnGgLYcsZWw6tM21DhnF66Th1jz + CoVs7qYHAiJBuPs36A7NMDo47y1nhL6mbdb2gEtMQi1zrBbRQzaX + + + + +./tezos-sc-rollup-client-alpha rpc get /last_stored_commitment +{ "commitment": + { "compressed_state": + "[SC_ROLLUP_STATE_HASH]", + "inbox_level": 32, + "predecessor": "[SC_ROLLUP_COMMITMENT_HASH]", + "number_of_messages": 0, "number_of_ticks": 0 }, + "hash": "[SC_ROLLUP_COMMITMENT_HASH]" } + +./tezos-sc-rollup-client-alpha rpc get /last_published_commitment +{ "commitment": + { "compressed_state": + "[SC_ROLLUP_STATE_HASH]", + "inbox_level": 32, + "predecessor": "[SC_ROLLUP_COMMITMENT_HASH]", + "number_of_messages": 0, "number_of_ticks": 0 }, + "hash": "[SC_ROLLUP_COMMITMENT_HASH]", + "published_at_level": 35 } diff --git a/tezt/lib_tezos/RPC_legacy.ml b/tezt/lib_tezos/RPC_legacy.ml index 6a3671465c3039ab9c41a73475161a410100271e..0431488f3abf5cf40c7410420f575031d2845cae 100644 --- a/tezt/lib_tezos/RPC_legacy.ml +++ b/tezt/lib_tezos/RPC_legacy.ml @@ -462,6 +462,20 @@ module Delegates = struct get_sub ?endpoint ?hooks ~chain ~block ~pkh "voting_power" client end +module Seed = struct + let get_seed ?endpoint ?hooks ?(chain = "main") ?(block = "head") client = + let path = ["chains"; chain; "blocks"; block; "context"; "seed"] in + let* json = Client.rpc ?endpoint ?hooks POST path client in + return (JSON.as_string json) + + let get_seed_status ?endpoint ?hooks ?(chain = "main") ?(block = "head") + client = + let path = + ["chains"; chain; "blocks"; block; "context"; "seed_computation"] + in + Client.rpc ?endpoint ?hooks GET path client +end + module Votes = struct let sub_path ~chain ~block sub = ["chains"; chain; "blocks"; block; "votes"; sub] diff --git a/tezt/lib_tezos/RPC_legacy.mli b/tezt/lib_tezos/RPC_legacy.mli index be1c439cb359f44715942dbb9cd21b9fb98e77ae..e5eb15e6e3a564c0f2cea8164b6107ee0ac4635b 100644 --- a/tezt/lib_tezos/RPC_legacy.mli +++ b/tezt/lib_tezos/RPC_legacy.mli @@ -689,6 +689,24 @@ module Delegates : sig Process.t end +module Seed : sig + val get_seed : + ?endpoint:Client.endpoint -> + ?hooks:Process.hooks -> + ?chain:string -> + ?block:string -> + Client.t -> + string Lwt.t + + val get_seed_status : + ?endpoint:Client.endpoint -> + ?hooks:Process.hooks -> + ?chain:string -> + ?block:string -> + Client.t -> + JSON.t Lwt.t +end + module Votes : sig (** Common protocol RPSs for votes (i.e. under [/votes]). *) diff --git a/tezt/lib_tezos/vdf.ml b/tezt/lib_tezos/vdf.ml new file mode 100644 index 0000000000000000000000000000000000000000..525aed31326d33a6abd6c9aafbcb426553041c07 --- /dev/null +++ b/tezt/lib_tezos/vdf.ml @@ -0,0 +1,123 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +module Parameters = struct + type persistent_state = { + protocol : Protocol.t; + runner : Runner.t option; + node : Node.t; + mutable pending_ready : unit option Lwt.u list; + } + + type session_state = {mutable ready : bool} + + let base_default_name = "vdf" + + let default_colors = + Log.Color. + [| + BG.magenta ++ FG.black; + BG.magenta ++ FG.gray; + BG.magenta ++ FG.bright_white; + |] +end + +open Parameters +include Daemon.Make (Parameters) + +let node_rpc_port accuser = Node.rpc_port accuser.persistent_state.node + +let trigger_ready vdf_baker value = + let pending = vdf_baker.persistent_state.pending_ready in + vdf_baker.persistent_state.pending_ready <- [] ; + List.iter (fun pending -> Lwt.wakeup_later pending value) pending + +let set_ready vdf_baker = + (match vdf_baker.status with + | Not_running -> () + | Running status -> status.session_state.ready <- true) ; + trigger_ready vdf_baker (Some ()) + +let handle_raw_stdout vdf_baker line = + if line =~ rex "^VDF daemon v.+ for .+ started.$" then set_ready vdf_baker + +let create ~protocol ?name ?color ?event_pipe ?runner node = + let name = match name with None -> fresh_name () | Some name -> name in + let vdf_baker = + create + ~path:(Protocol.baker protocol) + ?name:(Some name) + ?color + ?event_pipe + ?runner + {protocol; runner; node; pending_ready = []} + in + on_stdout vdf_baker (handle_raw_stdout vdf_baker) ; + vdf_baker + +let run (vdf_baker : t) = + (match vdf_baker.status with + | Not_running -> () + | Running _ -> Test.fail "VDF daemon %s is already running" vdf_baker.name) ; + let runner = vdf_baker.persistent_state.runner in + let node_runner = Node.runner vdf_baker.persistent_state.node in + let node_rpc_port = node_rpc_port vdf_baker in + let address = "http://" ^ Runner.address ?from:runner node_runner ^ ":" in + let arguments = ["-E"; address ^ string_of_int node_rpc_port; "run"; "vdf"] in + let on_terminate _ = + (* Cancel all [Ready] event listeners. *) + trigger_ready vdf_baker None ; + unit + in + run vdf_baker {ready = false} arguments ~on_terminate ?runner + +let check_event ?where vdf_baker name promise = + let* result = promise in + match result with + | None -> + raise + (Terminated_before_event {daemon = vdf_baker.name; event = name; where}) + | Some x -> return x + +let wait_for_ready vdf_baker = + match vdf_baker.status with + | Running {session_state = {ready = true; _}; _} -> unit + | Not_running | Running {session_state = {ready = false; _}; _} -> + let promise, resolver = Lwt.task () in + vdf_baker.persistent_state.pending_ready <- + resolver :: vdf_baker.persistent_state.pending_ready ; + check_event vdf_baker "VDF daemon started." promise + +let init ~protocol ?name ?color ?event_pipe ?runner node = + let* () = Node.wait_for_ready node in + let vdf_baker = create ~protocol ?name ?color ?event_pipe ?runner node in + let* () = run vdf_baker in + let* () = wait_for_ready vdf_baker in + return vdf_baker + +let restart vdf_baker = + let* () = terminate vdf_baker in + let* () = run vdf_baker in + wait_for_ready vdf_baker diff --git a/tezt/lib_tezos/vdf.mli b/tezt/lib_tezos/vdf.mli new file mode 100644 index 0000000000000000000000000000000000000000..77efd8f9091cd1cdc7b850c6895d8e5e4cee4e27 --- /dev/null +++ b/tezt/lib_tezos/vdf.mli @@ -0,0 +1,54 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs *) +(* *) +(* 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. *) +(* *) +(*****************************************************************************) + +type t + +type event = {name : string; value : JSON.t} + +val create : + protocol:Protocol.t -> + ?name:string -> + ?color:Log.Color.t -> + ?event_pipe:string -> + ?runner:Runner.t -> + Node.t -> + t + +val terminate : ?kill:bool -> t -> unit Lwt.t + +val run : t -> unit Lwt.t + +val init : + protocol:Protocol.t -> + ?name:string -> + ?color:Log.Color.t -> + ?event_pipe:string -> + ?runner:Runner.t -> + Node.t -> + t Lwt.t + +val restart : t -> unit Lwt.t + +val on_event : t -> (event -> unit) -> unit diff --git a/tezt/tests/RPC_test.ml b/tezt/tests/RPC_test.ml index 70b32c31bdae1c15dda807314e2e71c50b13709d..d8812d44cc61d49089e42f5ac90289509dc7214a 100644 --- a/tezt/tests/RPC_test.ml +++ b/tezt/tests/RPC_test.ml @@ -1236,13 +1236,25 @@ let register protocols = ~test_function:test_votes ~parameter_overrides:(fun protocol -> (* reduced periods duration to get to testing vote period faster *) - let cycles_per_voting_period = - if Protocol.number protocol >= 013 then - (["cycles_per_voting_period"], Some "1") - else (["blocks_per_voting_period"], Some "4") - in - [(["blocks_per_cycle"], Some "4"); cycles_per_voting_period] - @ consensus_threshold protocol) ; + if Protocol.number protocol >= 14 then + (* We need nonce_revelation_threshold < blocks_per_cycle for sanity + checks *) + [ + (["blocks_per_cycle"], Some "4"); + (["cycles_per_voting_period"], Some "1"); + (["nonce_revelation_threshold"], Some "3"); + ] + else if Protocol.number protocol >= 13 then + [ + (["blocks_per_cycle"], Some "4"); + (["cycles_per_voting_period"], Some "1"); + ] + else + [ + (["blocks_per_cycle"], Some "4"); + (["blocks_per_voting_period"], Some "4"); + ] + @ consensus_threshold protocol) ; check_rpc_regression "misc_protocol" ~test_function:test_misc_protocol diff --git a/tezt/tests/expected/RPC_test.ml/Alpha- (mode client) RPC regression tests- misc_protocol.out b/tezt/tests/expected/RPC_test.ml/Alpha- (mode client) RPC regression tests- misc_protocol.out index 390aabb512a204ade53dba64bdb5f386147af3a2..8bdcef4091ad44d339b137d01bffba3dda989095 100644 --- a/tezt/tests/expected/RPC_test.ml/Alpha- (mode client) RPC regression tests- misc_protocol.out +++ b/tezt/tests/expected/RPC_test.ml/Alpha- (mode client) RPC regression tests- misc_protocol.out @@ -8,11 +8,12 @@ "michelson_maximum_type_size": 2001, "max_wrapped_proof_binary_size": 30000, "preserved_cycles": 2, "blocks_per_cycle": 8, "blocks_per_commitment": 4, - "blocks_per_stake_snapshot": 4, "cycles_per_voting_period": 8, - "hard_gas_limit_per_operation": "1040000", + "nonce_revelation_threshold": 4, "blocks_per_stake_snapshot": 4, + "cycles_per_voting_period": 8, "hard_gas_limit_per_operation": "1040000", "hard_gas_limit_per_block": "5200000", "proof_of_work_threshold": "-1", - "tokens_per_roll": "6000000000", "seed_nonce_revelation_tip": "125000", - "origination_size": 257, "baking_reward_fixed_portion": "333333", + "tokens_per_roll": "6000000000", "vdf_difficulty": "50000", + "seed_nonce_revelation_tip": "125000", "origination_size": 257, + "baking_reward_fixed_portion": "333333", "baking_reward_bonus_per_slot": "3921", "endorsing_reward_per_slot": "2604", "cost_per_byte": "250", "hard_storage_limit_per_operation": "60000", "quorum_min": 2000, diff --git a/tezt/tests/expected/RPC_test.ml/Alpha- (mode light) RPC regression tests- misc_protocol.out b/tezt/tests/expected/RPC_test.ml/Alpha- (mode light) RPC regression tests- misc_protocol.out index 91d7cfb45c1da64b59857ef82d83ae3abf9be18b..ed793c174314a1a253dbffd1f1447f07423a5f2e 100644 --- a/tezt/tests/expected/RPC_test.ml/Alpha- (mode light) RPC regression tests- misc_protocol.out +++ b/tezt/tests/expected/RPC_test.ml/Alpha- (mode light) RPC regression tests- misc_protocol.out @@ -8,11 +8,12 @@ "michelson_maximum_type_size": 2001, "max_wrapped_proof_binary_size": 30000, "preserved_cycles": 2, "blocks_per_cycle": 8, "blocks_per_commitment": 4, - "blocks_per_stake_snapshot": 4, "cycles_per_voting_period": 8, - "hard_gas_limit_per_operation": "1040000", + "nonce_revelation_threshold": 4, "blocks_per_stake_snapshot": 4, + "cycles_per_voting_period": 8, "hard_gas_limit_per_operation": "1040000", "hard_gas_limit_per_block": "5200000", "proof_of_work_threshold": "-1", - "tokens_per_roll": "6000000000", "seed_nonce_revelation_tip": "125000", - "origination_size": 257, "baking_reward_fixed_portion": "333333", + "tokens_per_roll": "6000000000", "vdf_difficulty": "50000", + "seed_nonce_revelation_tip": "125000", "origination_size": 257, + "baking_reward_fixed_portion": "333333", "baking_reward_bonus_per_slot": "3921", "endorsing_reward_per_slot": "2604", "cost_per_byte": "250", "hard_storage_limit_per_operation": "60000", "quorum_min": 2000, diff --git a/tezt/tests/expected/RPC_test.ml/Alpha- (mode proxy) RPC regression tests- misc_protocol.out b/tezt/tests/expected/RPC_test.ml/Alpha- (mode proxy) RPC regression tests- misc_protocol.out index 642d8368c71ec8ffa55ac667baf8c51efddd2b9d..f6802eaef9b9537964abc79eef00090e49bf567b 100644 --- a/tezt/tests/expected/RPC_test.ml/Alpha- (mode proxy) RPC regression tests- misc_protocol.out +++ b/tezt/tests/expected/RPC_test.ml/Alpha- (mode proxy) RPC regression tests- misc_protocol.out @@ -8,11 +8,12 @@ "michelson_maximum_type_size": 2001, "max_wrapped_proof_binary_size": 30000, "preserved_cycles": 2, "blocks_per_cycle": 8, "blocks_per_commitment": 4, - "blocks_per_stake_snapshot": 4, "cycles_per_voting_period": 8, - "hard_gas_limit_per_operation": "1040000", + "nonce_revelation_threshold": 4, "blocks_per_stake_snapshot": 4, + "cycles_per_voting_period": 8, "hard_gas_limit_per_operation": "1040000", "hard_gas_limit_per_block": "5200000", "proof_of_work_threshold": "-1", - "tokens_per_roll": "6000000000", "seed_nonce_revelation_tip": "125000", - "origination_size": 257, "baking_reward_fixed_portion": "333333", + "tokens_per_roll": "6000000000", "vdf_difficulty": "50000", + "seed_nonce_revelation_tip": "125000", "origination_size": 257, + "baking_reward_fixed_portion": "333333", "baking_reward_bonus_per_slot": "3921", "endorsing_reward_per_slot": "2604", "cost_per_byte": "250", "hard_storage_limit_per_operation": "60000", "quorum_min": 2000, diff --git a/tezt/tests/expected/RPC_test.ml/Alpha- (mode proxy_server_data_dir) RPC regression tests- misc_protocol.out b/tezt/tests/expected/RPC_test.ml/Alpha- (mode proxy_server_data_dir) RPC regression tests- misc_protocol.out index 2a549b3dbd768f71b68ad5a59ef5beb9c7b6ff6f..0a29a978e6aa8c80d58ea7ca4d88a64979898651 100644 --- a/tezt/tests/expected/RPC_test.ml/Alpha- (mode proxy_server_data_dir) RPC regression tests- misc_protocol.out +++ b/tezt/tests/expected/RPC_test.ml/Alpha- (mode proxy_server_data_dir) RPC regression tests- misc_protocol.out @@ -8,11 +8,12 @@ "michelson_maximum_type_size": 2001, "max_wrapped_proof_binary_size": 30000, "preserved_cycles": 2, "blocks_per_cycle": 8, "blocks_per_commitment": 4, - "blocks_per_stake_snapshot": 4, "cycles_per_voting_period": 8, - "hard_gas_limit_per_operation": "1040000", + "nonce_revelation_threshold": 4, "blocks_per_stake_snapshot": 4, + "cycles_per_voting_period": 8, "hard_gas_limit_per_operation": "1040000", "hard_gas_limit_per_block": "5200000", "proof_of_work_threshold": "-1", - "tokens_per_roll": "6000000000", "seed_nonce_revelation_tip": "125000", - "origination_size": 257, "baking_reward_fixed_portion": "333333", + "tokens_per_roll": "6000000000", "vdf_difficulty": "50000", + "seed_nonce_revelation_tip": "125000", "origination_size": 257, + "baking_reward_fixed_portion": "333333", "baking_reward_bonus_per_slot": "3921", "endorsing_reward_per_slot": "2604", "cost_per_byte": "250", "hard_storage_limit_per_operation": "60000", "quorum_min": 2000, diff --git a/tezt/tests/expected/RPC_test.ml/Alpha- (mode proxy_server_rpc) RPC regression tests- misc_protocol.out b/tezt/tests/expected/RPC_test.ml/Alpha- (mode proxy_server_rpc) RPC regression tests- misc_protocol.out index 2a549b3dbd768f71b68ad5a59ef5beb9c7b6ff6f..0a29a978e6aa8c80d58ea7ca4d88a64979898651 100644 --- a/tezt/tests/expected/RPC_test.ml/Alpha- (mode proxy_server_rpc) RPC regression tests- misc_protocol.out +++ b/tezt/tests/expected/RPC_test.ml/Alpha- (mode proxy_server_rpc) RPC regression tests- misc_protocol.out @@ -8,11 +8,12 @@ "michelson_maximum_type_size": 2001, "max_wrapped_proof_binary_size": 30000, "preserved_cycles": 2, "blocks_per_cycle": 8, "blocks_per_commitment": 4, - "blocks_per_stake_snapshot": 4, "cycles_per_voting_period": 8, - "hard_gas_limit_per_operation": "1040000", + "nonce_revelation_threshold": 4, "blocks_per_stake_snapshot": 4, + "cycles_per_voting_period": 8, "hard_gas_limit_per_operation": "1040000", "hard_gas_limit_per_block": "5200000", "proof_of_work_threshold": "-1", - "tokens_per_roll": "6000000000", "seed_nonce_revelation_tip": "125000", - "origination_size": 257, "baking_reward_fixed_portion": "333333", + "tokens_per_roll": "6000000000", "vdf_difficulty": "50000", + "seed_nonce_revelation_tip": "125000", "origination_size": 257, + "baking_reward_fixed_portion": "333333", "baking_reward_bonus_per_slot": "3921", "endorsing_reward_per_slot": "2604", "cost_per_byte": "250", "hard_storage_limit_per_operation": "60000", "quorum_min": 2000, diff --git a/tezt/tests/expected/sc_rollup.ml/Alpha- observing the correct handling of commitments in the rollup node (no_comm.out b/tezt/tests/expected/sc_rollup.ml/Alpha- observing the correct handling of commitments in the rollup node (no_comm.out index 6cec34aebe1a0df4235b89dfebc4878eccf3f3e6..88dcc7344b13f86f93bdededb186046bbc85dbc7 100644 --- a/tezt/tests/expected/sc_rollup.ml/Alpha- observing the correct handling of commitments in the rollup node (no_comm.out +++ b/tezt/tests/expected/sc_rollup.ml/Alpha- observing the correct handling of commitments in the rollup node (no_comm.out @@ -60,11 +60,12 @@ This sequence of operations was run: "michelson_maximum_type_size": 2001, "max_wrapped_proof_binary_size": 30000, "preserved_cycles": 2, "blocks_per_cycle": 8, "blocks_per_commitment": 4, - "blocks_per_stake_snapshot": 4, "cycles_per_voting_period": 8, - "hard_gas_limit_per_operation": "1040000", + "nonce_revelation_threshold": 4, "blocks_per_stake_snapshot": 4, + "cycles_per_voting_period": 8, "hard_gas_limit_per_operation": "1040000", "hard_gas_limit_per_block": "5200000", "proof_of_work_threshold": "-1", - "tokens_per_roll": "6000000000", "seed_nonce_revelation_tip": "125000", - "origination_size": 257, "baking_reward_fixed_portion": "333333", + "tokens_per_roll": "6000000000", "vdf_difficulty": "50000", + "seed_nonce_revelation_tip": "125000", "origination_size": 257, + "baking_reward_fixed_portion": "333333", "baking_reward_bonus_per_slot": "3921", "endorsing_reward_per_slot": "2604", "cost_per_byte": "250", "hard_storage_limit_per_operation": "60000", "quorum_min": 2000, @@ -160,11 +161,12 @@ This sequence of operations was run: "michelson_maximum_type_size": 2001, "max_wrapped_proof_binary_size": 30000, "preserved_cycles": 2, "blocks_per_cycle": 8, "blocks_per_commitment": 4, - "blocks_per_stake_snapshot": 4, "cycles_per_voting_period": 8, - "hard_gas_limit_per_operation": "1040000", + "nonce_revelation_threshold": 4, "blocks_per_stake_snapshot": 4, + "cycles_per_voting_period": 8, "hard_gas_limit_per_operation": "1040000", "hard_gas_limit_per_block": "5200000", "proof_of_work_threshold": "-1", - "tokens_per_roll": "6000000000", "seed_nonce_revelation_tip": "125000", - "origination_size": 257, "baking_reward_fixed_portion": "333333", + "tokens_per_roll": "6000000000", "vdf_difficulty": "50000", + "seed_nonce_revelation_tip": "125000", "origination_size": 257, + "baking_reward_fixed_portion": "333333", "baking_reward_bonus_per_slot": "3921", "endorsing_reward_per_slot": "2604", "cost_per_byte": "250", "hard_storage_limit_per_operation": "60000", "quorum_min": 2000, diff --git a/tezt/tests/main.ml b/tezt/tests/main.ml index 6cfcbcdeb07ad78f820fb8285a46f40a26490c19..45819f68879a6f0afe06dfe3efaff27d795af310 100644 --- a/tezt/tests/main.ml +++ b/tezt/tests/main.ml @@ -47,6 +47,7 @@ let () = Baker_test.register ~protocols:[Alpha] ; Signer_test.register ~protocols:[Alpha] ; Basic.register ~protocols:[Alpha] ; + Vdf_test.register ~protocols:[Alpha] ; Client_config.register ~protocols:[Alpha] ; Client_commands.register ~protocols ; Global_constants.register ~protocols:[Alpha] ; diff --git a/tezt/tests/vdf_test.ml b/tezt/tests/vdf_test.ml new file mode 100644 index 0000000000000000000000000000000000000000..a1be5895df9df13d6662ac591b6b601626d35dc5 --- /dev/null +++ b/tezt/tests/vdf_test.ml @@ -0,0 +1,230 @@ +(*****************************************************************************) +(* *) +(* Open Source License *) +(* Copyright (c) 2022 Nomadic Labs *) +(* *) +(* 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 client level = + let* seed_status = + RPC.Seed.get_seed_status ~block:(string_of_int level) client + in + return + (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) + +let assert_computation_status client level status = + let* current_status = get_seed_computation_status 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 () + +(* 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` *) +let bake_until min_starting_level max_level client node = + 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 + loop level + else return level + in + let* level = loop min_starting_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 vdf_baker = + let injected = ref false in + + let* () = assert_computation_status client level Nonce_revelation_stage in + Vdf.on_event vdf_baker (fun Vdf.{name; _} -> + if name = "vdf_revelation_injected.v0" then injected := true) ; + 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 in + return @@ assert !injected + +let check_cycle (blocks_per_cycle, nonce_revelation_threshold) starting_level + client node vdf_baker = + (* 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 + 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 + vdf_baker + 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 vdf_baker = + let rec loop n level = + if n > 0 then + let* level = check_cycle constants level client node vdf_baker 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 in + let* vdf_baker = Vdf.init ~protocol node in + + let* constants = RPC.get_constants client 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 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 vdf_baker + 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 + vdf_baker + 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 + let* () = + repeat blocks_per_cycle (fun () -> + let* () = Client.bake_for client in + let* level = Node.wait_for_level node 1 in + let* current_status = get_seed_computation_status client level in + return @@ assert (current_status <> Computation_finished)) + in + let* level = Node.wait_for_level node (level + blocks_per_cycle) in + + (* Restart a VDF daemon and check correct behaviour after a RANDAO cycle *) + let* vdf_baker = Vdf.init ~protocol node in + assert_level level (((n_cycles + 2) * blocks_per_cycle) + 1) ; + let* _level = + check_n_cycles + n_cycles + (blocks_per_cycle, nonce_revelation_threshold) + level + client + node + vdf_baker + in + + Vdf.terminate vdf_baker + +let register ~protocols = test_vdf protocols diff --git a/tezt/tests/voting.ml b/tezt/tests/voting.ml index aa37bbe286b50f8b8cc75274866b5a24ebdab097..73cb887604e3585d1ed268b9486fb8355ea5e823 100644 --- a/tezt/tests/voting.ml +++ b/tezt/tests/voting.ml @@ -285,6 +285,7 @@ let test_voting ~from_protocol ~(to_protocol : target_protocol) ~loser_protocols if Protocol.number from_protocol >= 013 then [ (["blocks_per_cycle"], Some "4"); + (["nonce_revelation_threshold"], Some "2"); (["cycles_per_voting_period"], Some "1"); ] else