From 268eb880fc813794c4b9f994faa6e4151463d7d4 Mon Sep 17 00:00:00 2001 From: Sylvain Ribstein Date: Thu, 15 Dec 2022 15:28:08 +0100 Subject: [PATCH 1/3] proto/scoru: add index storage for stakers --- src/proto_alpha/lib_protocol/TEZOS_PROTOCOL | 2 + src/proto_alpha/lib_protocol/dune | 8 ++ .../lib_protocol/sc_rollup_stake_storage.ml | 6 ++ .../sc_rollup_staker_index_repr.ml | 54 +++++++++++++ .../sc_rollup_staker_index_repr.mli | 38 ++++++++++ .../sc_rollup_staker_index_storage.ml | 76 +++++++++++++++++++ .../sc_rollup_staker_index_storage.mli | 64 ++++++++++++++++ .../lib_protocol/sc_rollup_storage.ml | 1 + src/proto_alpha/lib_protocol/storage.ml | 24 ++++++ src/proto_alpha/lib_protocol/storage.mli | 19 +++++ .../test/unit/test_sc_rollup_storage.ml | 33 +++++++- 11 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 src/proto_alpha/lib_protocol/sc_rollup_staker_index_repr.ml create mode 100644 src/proto_alpha/lib_protocol/sc_rollup_staker_index_repr.mli create mode 100644 src/proto_alpha/lib_protocol/sc_rollup_staker_index_storage.ml create mode 100644 src/proto_alpha/lib_protocol/sc_rollup_staker_index_storage.mli diff --git a/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL b/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL index a69c8b9f69af..204804015767 100644 --- a/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL +++ b/src/proto_alpha/lib_protocol/TEZOS_PROTOCOL @@ -67,6 +67,7 @@ "Sc_rollups", "Sc_rollup_data_version_sig", "Sc_rollup_inbox_repr", + "Sc_rollup_staker_index_repr", "Sc_rollup_commitment_repr", "Sc_rollup_proof_repr", "Skip_list_costs", @@ -179,6 +180,7 @@ "Tx_rollup_inbox_storage", "Tx_rollup_commitment_storage", "Tx_rollup_storage", + "Sc_rollup_staker_index_storage", "Sc_rollup_commitment_storage", "Sc_rollup_outbox_storage", "Sc_rollup_stake_storage", diff --git a/src/proto_alpha/lib_protocol/dune b/src/proto_alpha/lib_protocol/dune index ed8e43d778e5..0d950c5f51a9 100644 --- a/src/proto_alpha/lib_protocol/dune +++ b/src/proto_alpha/lib_protocol/dune @@ -93,6 +93,7 @@ Sc_rollups Sc_rollup_data_version_sig Sc_rollup_inbox_repr + Sc_rollup_staker_index_repr Sc_rollup_commitment_repr Sc_rollup_proof_repr Skip_list_costs @@ -193,6 +194,7 @@ Tx_rollup_inbox_storage Tx_rollup_commitment_storage Tx_rollup_storage + Sc_rollup_staker_index_storage Sc_rollup_commitment_storage Sc_rollup_outbox_storage Sc_rollup_stake_storage @@ -372,6 +374,7 @@ sc_rollups.ml sc_rollups.mli sc_rollup_data_version_sig.ml sc_rollup_inbox_repr.ml sc_rollup_inbox_repr.mli + sc_rollup_staker_index_repr.ml sc_rollup_staker_index_repr.mli sc_rollup_commitment_repr.ml sc_rollup_commitment_repr.mli sc_rollup_proof_repr.ml sc_rollup_proof_repr.mli skip_list_costs.ml skip_list_costs.mli @@ -475,6 +478,7 @@ tx_rollup_inbox_storage.ml tx_rollup_inbox_storage.mli tx_rollup_commitment_storage.ml tx_rollup_commitment_storage.mli tx_rollup_storage.ml tx_rollup_storage.mli + sc_rollup_staker_index_storage.ml sc_rollup_staker_index_storage.mli sc_rollup_commitment_storage.ml sc_rollup_commitment_storage.mli sc_rollup_outbox_storage.ml sc_rollup_outbox_storage.mli sc_rollup_stake_storage.ml sc_rollup_stake_storage.mli @@ -634,6 +638,7 @@ sc_rollups.ml sc_rollups.mli sc_rollup_data_version_sig.ml sc_rollup_inbox_repr.ml sc_rollup_inbox_repr.mli + sc_rollup_staker_index_repr.ml sc_rollup_staker_index_repr.mli sc_rollup_commitment_repr.ml sc_rollup_commitment_repr.mli sc_rollup_proof_repr.ml sc_rollup_proof_repr.mli skip_list_costs.ml skip_list_costs.mli @@ -737,6 +742,7 @@ tx_rollup_inbox_storage.ml tx_rollup_inbox_storage.mli tx_rollup_commitment_storage.ml tx_rollup_commitment_storage.mli tx_rollup_storage.ml tx_rollup_storage.mli + sc_rollup_staker_index_storage.ml sc_rollup_staker_index_storage.mli sc_rollup_commitment_storage.ml sc_rollup_commitment_storage.mli sc_rollup_outbox_storage.ml sc_rollup_outbox_storage.mli sc_rollup_stake_storage.ml sc_rollup_stake_storage.mli @@ -901,6 +907,7 @@ sc_rollups.ml sc_rollups.mli sc_rollup_data_version_sig.ml sc_rollup_inbox_repr.ml sc_rollup_inbox_repr.mli + sc_rollup_staker_index_repr.ml sc_rollup_staker_index_repr.mli sc_rollup_commitment_repr.ml sc_rollup_commitment_repr.mli sc_rollup_proof_repr.ml sc_rollup_proof_repr.mli skip_list_costs.ml skip_list_costs.mli @@ -1004,6 +1011,7 @@ tx_rollup_inbox_storage.ml tx_rollup_inbox_storage.mli tx_rollup_commitment_storage.ml tx_rollup_commitment_storage.mli tx_rollup_storage.ml tx_rollup_storage.mli + sc_rollup_staker_index_storage.ml sc_rollup_staker_index_storage.mli sc_rollup_commitment_storage.ml sc_rollup_commitment_storage.mli sc_rollup_outbox_storage.ml sc_rollup_outbox_storage.mli sc_rollup_stake_storage.ml sc_rollup_stake_storage.mli diff --git a/src/proto_alpha/lib_protocol/sc_rollup_stake_storage.ml b/src/proto_alpha/lib_protocol/sc_rollup_stake_storage.ml index 7d9799bbad14..e720416ce7f1 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_stake_storage.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_stake_storage.ml @@ -110,6 +110,9 @@ let deposit_stake ctxt rollup staker = (`Frozen_bonds (staker_contract, bond_id)) stake in + let* ctxt, _staker_index = + Sc_rollup_staker_index_storage.fresh_staker_index ctxt rollup staker + in let* ctxt, _size = Store.Stakers.init (ctxt, rollup) staker lcc in let* ctxt = modify_staker_count ctxt rollup Int32.succ in return (ctxt, balance_updates, lcc) @@ -138,6 +141,9 @@ let withdraw_stake ctxt rollup staker = let* ctxt, _size_freed = Store.Stakers.remove_existing (ctxt, rollup) staker in + let* ctxt, _size_freed = + Sc_rollup_staker_index_storage.remove_staker ctxt rollup staker + in let+ ctxt = modify_staker_count ctxt rollup Int32.pred in (ctxt, balance_updates) diff --git a/src/proto_alpha/lib_protocol/sc_rollup_staker_index_repr.ml b/src/proto_alpha/lib_protocol/sc_rollup_staker_index_repr.ml new file mode 100644 index 000000000000..818c5cb2098d --- /dev/null +++ b/src/proto_alpha/lib_protocol/sc_rollup_staker_index_repr.ml @@ -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. *) +(* *) +(*****************************************************************************) + +include Z + +let encoding = Data_encoding.n + +module Index : Storage_description.INDEX with type t = t = struct + type nonrec t = t + + let encoding = encoding + + let compare = compare + + let path_length = 1 + + let to_path c l = Z.to_string c :: l + + let of_path = function + | [] | _ :: _ :: _ -> None + | [c] -> Some (Z.of_string c) + + let rpc_arg = + let z_of_string s = + try Ok (Z.of_string s) with Failure _ -> Error "Cannot parse z value" + in + RPC_arg.make ~name:"z" ~destruct:z_of_string ~construct:Z.to_string () +end + +module Internal_for_tests = struct + let of_z z = z +end diff --git a/src/proto_alpha/lib_protocol/sc_rollup_staker_index_repr.mli b/src/proto_alpha/lib_protocol/sc_rollup_staker_index_repr.mli new file mode 100644 index 000000000000..75d2dec50fb9 --- /dev/null +++ b/src/proto_alpha/lib_protocol/sc_rollup_staker_index_repr.mli @@ -0,0 +1,38 @@ +(*****************************************************************************) +(* *) +(* 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 = private Z.t + +val zero : t + +val succ : t -> t + +val encoding : t Data_encoding.t + +module Index : Storage_description.INDEX with type t = t + +module Internal_for_tests : sig + val of_z : Z.t -> t +end diff --git a/src/proto_alpha/lib_protocol/sc_rollup_staker_index_storage.ml b/src/proto_alpha/lib_protocol/sc_rollup_staker_index_storage.ml new file mode 100644 index 000000000000..fae8f177d816 --- /dev/null +++ b/src/proto_alpha/lib_protocol/sc_rollup_staker_index_storage.ml @@ -0,0 +1,76 @@ +(*****************************************************************************) +(* *) +(* 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 Sc_rollup_errors + +let init ctxt rollup = + Storage.Sc_rollup.Staker_index_counter.init + (ctxt, rollup) + Sc_rollup_staker_index_repr.zero + +let fresh_staker_index ctxt rollup staker = + let open Lwt_result_syntax in + (* This is safe because this storage is initialized at the rollup creation + wit {!init_storage} .*) + let* staker_index = + Storage.Sc_rollup.Staker_index_counter.get (ctxt, rollup) + in + let* ctxt = + Storage.Sc_rollup.Staker_index_counter.update + (ctxt, rollup) + (Sc_rollup_staker_index_repr.succ staker_index) + in + let* ctxt, _size, _existed = + Storage.Sc_rollup.Staker_index.add (ctxt, rollup) staker staker_index + in + let* ctxt, _size, _existed = + Storage.Sc_rollup.Stakers_by_rollup.add (ctxt, rollup) staker_index + in + return (ctxt, staker_index) + +let find_staker_index_unsafe ctxt rollup staker = + let open Lwt_result_syntax in + let* ctxt, res = Storage.Sc_rollup.Staker_index.find (ctxt, rollup) staker in + match res with + | None -> tzfail Sc_rollup_not_staked + | Some staker_index -> return (ctxt, staker_index) + +let find_staker_index ctxt rollup staker = + let open Lwt_result_syntax in + let* ctxt, res = Storage.Sc_rollup.Last_cemented_commitment.mem ctxt rollup in + if not res then tzfail (Sc_rollup_does_not_exist rollup) + else find_staker_index_unsafe ctxt rollup staker + +let remove_staker ctxt rollup staker = + let open Lwt_result_syntax in + let* ctxt, staker_index = find_staker_index ctxt rollup staker in + + let* ctxt, staker_index_size_diff = + Storage.Sc_rollup.Staker_index.remove_existing (ctxt, rollup) staker + in + let* ctxt, stakers_by_rollup_size_diff, _existed = + Storage.Sc_rollup.Stakers_by_rollup.remove (ctxt, rollup) staker_index + in + return (ctxt, staker_index_size_diff + stakers_by_rollup_size_diff) diff --git a/src/proto_alpha/lib_protocol/sc_rollup_staker_index_storage.mli b/src/proto_alpha/lib_protocol/sc_rollup_staker_index_storage.mli new file mode 100644 index 000000000000..22b03ea3ebfc --- /dev/null +++ b/src/proto_alpha/lib_protocol/sc_rollup_staker_index_storage.mli @@ -0,0 +1,64 @@ +(*****************************************************************************) +(* *) +(* 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 Sc_rollup_staker_index_repr + +(** [init ctxt rollup] initialize a staker index counter for [rollup]. *) +val init : Raw_context.t -> Sc_rollup_repr.t -> Raw_context.t tzresult Lwt.t + +(** [fresh_staker_index ctxt rollup staker] creates a new index for [staker] and + store it in {!Storage.Sc_rollup.Staker_index}. *) +val fresh_staker_index : + Raw_context.t -> + Sc_rollup_repr.t -> + Signature.public_key_hash -> + (Raw_context.t * t) tzresult Lwt.t + +(** [find_staker_index_unsafe ctxt rollup staker] returns the index for the + [rollup]'s [staker]. This function *must* be called only after they have + checked for the existence of the rollup, and therefore it is not necessary + for it to check for the existence of the rollup again. Otherwise, use the + safe function {!find_staker_index}. + + May fail with [Sc_rollup_not_staked] if [staker] is not staked. *) +val find_staker_index_unsafe : + Raw_context.t -> + Sc_rollup_repr.t -> + Signature.public_key_hash -> + (Raw_context.t * t) tzresult Lwt.t + +(** Same as {!find_staker_index_unsafe} but checks for the existence of the +[rollup] before. *) +val find_staker_index : + Raw_context.t -> + Sc_rollup_repr.t -> + Signature.public_key_hash -> + (Raw_context.t * t) tzresult Lwt.t + +val remove_staker : + Raw_context.t -> + Sc_rollup_repr.t -> + Signature.public_key_hash -> + (Raw_context.t * int) tzresult Lwt.t diff --git a/src/proto_alpha/lib_protocol/sc_rollup_storage.ml b/src/proto_alpha/lib_protocol/sc_rollup_storage.ml index aec89a96c18f..a66b35b0d36f 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_storage.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_storage.ml @@ -67,6 +67,7 @@ let originate ctxt ~kind ~parameters_ty ~genesis_commitment = let* ctxt, param_ty_size_diff, _added = Store.Parameters_type.add ctxt address parameters_ty in + let* ctxt = Sc_rollup_staker_index_storage.init ctxt address in let* ctxt, lcc_size_diff = Store.Last_cemented_commitment.init ctxt address genesis_commitment_hash in diff --git a/src/proto_alpha/lib_protocol/storage.ml b/src/proto_alpha/lib_protocol/storage.ml index db2b480fce27..9e7598a80167 100644 --- a/src/proto_alpha/lib_protocol/storage.ml +++ b/src/proto_alpha/lib_protocol/storage.ml @@ -1743,6 +1743,30 @@ module Sc_rollup = struct let encoding = Sc_rollup_commitment_repr.Hash.encoding end) + module Staker_index_counter = + Make_single_data_storage (Registered) (Indexed_context.Raw_context) + (struct + let name = ["staker_index_counter"] + end) + (Sc_rollup_staker_index_repr) + + module Staker_index = + Make_indexed_carbonated_data_storage + (Make_subcontext (Registered) (Indexed_context.Raw_context) + (struct + let name = ["staker_index"] + end)) + (Public_key_hash_index) + (Sc_rollup_staker_index_repr) + + module Stakers_by_rollup = + Make_carbonated_data_set_storage + (Make_subcontext (Registered) (Indexed_context.Raw_context) + (struct + let name = ["stakers_by_rollup"] + end)) + (Make_index (Sc_rollup_staker_index_repr.Index)) + module Stakers = Make_indexed_carbonated_data_storage (Make_subcontext (Registered) (Indexed_context.Raw_context) diff --git a/src/proto_alpha/lib_protocol/storage.mli b/src/proto_alpha/lib_protocol/storage.mli index 675829618d2f..19c562e3fea5 100644 --- a/src/proto_alpha/lib_protocol/storage.mli +++ b/src/proto_alpha/lib_protocol/storage.mli @@ -764,6 +764,25 @@ module Sc_rollup : sig and type value = Sc_rollup_commitment_repr.Hash.t and type t := Raw_context.t + (** Contains the current latest attributed index for stakers. *) + module Staker_index_counter : + Single_data_storage + with type value = Sc_rollup_staker_index_repr.t + and type t = Raw_context.t * Sc_rollup_repr.t + + (** Contains the index of any staker that currently have stake. *) + module Staker_index : + Non_iterable_indexed_carbonated_data_storage + with type key = Signature.Public_key_hash.t + and type value = Sc_rollup_staker_index_repr.t + and type t = Raw_context.t * Sc_rollup_repr.t + + (** Contains the stakers' index that currently have stake indexed by rollup. *) + module Stakers_by_rollup : + Carbonated_data_set_storage + with type t = Raw_context.t * Sc_rollup_repr.t + and type elt = Sc_rollup_staker_index_repr.t + module Stakers : Non_iterable_indexed_carbonated_data_storage with type key = Signature.Public_key_hash.t diff --git a/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_storage.ml b/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_storage.ml index efc39da717e4..2d126eef213e 100644 --- a/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_storage.ml +++ b/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_storage.ml @@ -436,7 +436,15 @@ let withdraw_stake_and_check_balances ctxt rollup staker = in let bond_id = Bond_id_repr.Sc_rollup_bond_id rollup in let bonds_account = `Frozen_bonds (staker_contract, bond_id) in - let+ () = assert_balance_decreased ctxt ctxt' bonds_account stake in + let* () = assert_balance_decreased ctxt ctxt' bonds_account stake in + let+ () = + assert_not_exist + ~loc:__LOC__ + ~pp:(fun fmt (z : Sc_rollup_staker_index_repr.t) -> + Z.pp_print fmt (z :> Z.t)) + @@ lift + @@ Storage.Sc_rollup.Staker_index.find (ctxt', rollup) staker + in ctxt') let test_deposit_then_withdraw () = @@ -2695,6 +2703,25 @@ let test_unrelated_commitments () = in Assert.equal_bool ~loc:__LOC__ are_commitments_related false +let test_fresh_index_correctly_increment () = + let* ctxt, rollup, _genesis_hash, staker1, staker2 = + originate_rollup_and_deposit_with_two_stakers () + in + let assert_staker_index ~__LOC__ ctxt staker expected_index = + let* _, (found_index : Sc_rollup_staker_index_repr.t) = + lift @@ Storage.Sc_rollup.Staker_index.get (ctxt, rollup) staker + in + Assert.equal_z ~loc:__LOC__ (found_index :> Z.t) expected_index + in + let* () = assert_staker_index ~__LOC__ ctxt staker1 Z.zero in + let* () = assert_staker_index ~__LOC__ ctxt staker2 Z.one in + let Account.{pkh; _} = Account.new_account () in + let* ctxt, fresh_staker3_index = + lift @@ Sc_rollup_staker_index_storage.fresh_staker_index ctxt rollup pkh + in + let* () = assert_staker_index ~__LOC__ ctxt pkh Z.(succ one) in + Assert.equal_z ~loc:__LOC__ (fresh_staker3_index :> Z.t) Z.(succ one) + let tests = [ Tztest.tztest @@ -2940,6 +2967,10 @@ let tests = "Unrelated commitments are classified as such" `Quick test_unrelated_commitments; + Tztest.tztest + "test fresh index is correcly incremented" + `Quick + test_fresh_index_correctly_increment; ] (* FIXME: https://gitlab.com/tezos/tezos/-/issues/2460 -- GitLab From c73ddb55a885292fb6afd382e3b77120831a57fd Mon Sep 17 00:00:00 2001 From: Sylvain Ribstein Date: Thu, 15 Dec 2022 13:37:29 +0100 Subject: [PATCH 2/3] proto/scoru: store stakers of commits --- .../lib_protocol/sc_rollup_stake_storage.ml | 71 ++++++++++++++----- src/proto_alpha/lib_protocol/storage.ml | 22 ++++++ src/proto_alpha/lib_protocol/storage.mli | 19 +++++ .../test/unit/test_sc_rollup_storage.ml | 15 ++++ 4 files changed, 108 insertions(+), 19 deletions(-) diff --git a/src/proto_alpha/lib_protocol/sc_rollup_stake_storage.ml b/src/proto_alpha/lib_protocol/sc_rollup_stake_storage.ml index e720416ce7f1..20c4742dbaf0 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_stake_storage.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_stake_storage.ml @@ -110,12 +110,12 @@ let deposit_stake ctxt rollup staker = (`Frozen_bonds (staker_contract, bond_id)) stake in - let* ctxt, _staker_index = + let* ctxt, staker_index = Sc_rollup_staker_index_storage.fresh_staker_index ctxt rollup staker in let* ctxt, _size = Store.Stakers.init (ctxt, rollup) staker lcc in let* ctxt = modify_staker_count ctxt rollup Int32.succ in - return (ctxt, balance_updates, lcc) + return (ctxt, balance_updates, lcc, staker_index) let withdraw_stake ctxt rollup staker = let open Lwt_result_syntax in @@ -300,6 +300,7 @@ let deallocate_commitment_metadata ctxt rollup node = (ctxt, rollup) commitment.inbox_level in + let*! ctxt, _sc_rollup = Store.clean_commitment_stakers ctxt rollup node in if Compare.Int32.(count = 1l) then let* ctxt, _size_freed = Store.Commitment_count_per_inbox_level.remove_existing @@ -344,20 +345,29 @@ let find_commitment_to_deallocate ctxt rollup commitment_hash in aux ctxt commitment_hash num_commitments_to_keep -let decrease_commitment_stake_count ctxt rollup node = +let decrease_commitment_stake_count ctxt rollup node ~staker_index = let open Lwt_result_syntax in - let* new_count, _size_diff, ctxt = + let* ctxt, _stakers_size_diff, _existed = + Storage.Sc_rollup.Commitment_stakers.remove + ((ctxt, rollup), node) + staker_index + in + let* new_count, _commitment_stake_count_size_diff, ctxt = modify_commitment_stake_count ctxt rollup node Int32.pred in if Compare.Int32.(new_count <= 0l) then deallocate ctxt rollup node else return ctxt -let increase_commitment_stake_count ctxt rollup node = +let increase_commitment_stake_count ctxt rollup node ~staker_index = let open Lwt_result_syntax in - let* _new_count, size_diff, ctxt = + let* ctxt, stakers_size_diff, _existed = + Storage.Sc_rollup.Commitment_stakers.add ((ctxt, rollup), node) staker_index + in + + let* _new_count, commmitment_count_diff, ctxt = modify_commitment_stake_count ctxt rollup node Int32.succ in - return (size_diff, ctxt) + return (stakers_size_diff + commmitment_count_diff, ctxt) (* 77 for Commitments entry + 4 for Commitment_stake_count entry @@ -366,7 +376,7 @@ let increase_commitment_stake_count ctxt rollup node = + 0 for Staker_count_update entry *) let commitment_storage_size_in_bytes = 89 -let refine_stake ctxt rollup staker staked_on commitment = +let refine_stake ctxt rollup staker staked_on commitment ~staker_index = let open Lwt_result_syntax in let* lcc, lcc_inbox_level, ctxt = Commitment_storage.last_cemented_commitment_hash_with_level ctxt rollup @@ -439,7 +449,7 @@ let refine_stake ctxt rollup staker staked_on commitment = Store.Stakers.update (ctxt, rollup) staker new_hash in let* stake_count_size_diff, ctxt = - increase_commitment_stake_count ctxt rollup new_hash + increase_commitment_stake_count ctxt rollup new_hash ~staker_index in (* WARNING: [commitment_storage_size] is a defined constant, and used to set a bound on the relationship between [max_lookahead], @@ -470,7 +480,9 @@ let refine_stake ctxt rollup staker staked_on commitment = let* pred, ctxt = Commitment_storage.get_predecessor_unsafe ctxt rollup node in - let* _size, ctxt = increase_commitment_stake_count ctxt rollup node in + let* _size, ctxt = + increase_commitment_stake_count ctxt rollup node ~staker_index + in (go [@ocaml.tailcall]) pred ctxt in go Commitment.(commitment.predecessor) ctxt @@ -484,13 +496,20 @@ let publish_commitment ctxt rollup staker commitment = Sc_rollup_zero_tick_commitment in let* ctxt, staked_on_opt = Store.Stakers.find (ctxt, rollup) staker in - let* ctxt, balance_updates, staked_on = + let* ctxt, balance_updates, staked_on, staker_index = match staked_on_opt with - | Some staked_on -> return (ctxt, [], staked_on) + | Some staked_on -> + let* ctxt, staker_index = + Sc_rollup_staker_index_storage.find_staker_index_unsafe + ctxt + rollup + staker + in + return (ctxt, [], staked_on, staker_index) | None -> deposit_stake ctxt rollup staker in let+ commitment_hash, ctxt, level = - refine_stake ctxt rollup staker staked_on commitment + refine_stake ctxt rollup staker staked_on commitment ~staker_index in (commitment_hash, ctxt, level, balance_updates) @@ -581,6 +600,9 @@ let remove_staker ctxt rollup staker = is_staked_on_lcc_or_ancestor ctxt rollup ~staked_on ~lcc_inbox_level in let* () = fail_when is_staked_on_cemented Sc_rollup_remove_lcc_or_ancestor in + let* ctxt, staker_index = + Sc_rollup_staker_index_storage.find_staker_index_unsafe ctxt rollup staker + in let staker_contract, stake = get_contract_and_stake ctxt staker in let bond_id = Bond_id_repr.Sc_rollup_bond_id rollup in let* ctxt, balance_updates = @@ -610,23 +632,34 @@ let remove_staker ctxt rollup staker = let* pred, ctxt = Commitment_storage.get_predecessor_unsafe ctxt rollup node in - let* ctxt = decrease_commitment_stake_count ctxt rollup node in + let* ctxt = + decrease_commitment_stake_count ctxt rollup node ~staker_index + in (go [@ocaml.tailcall]) pred ctxt in let+ ctxt = go staked_on ctxt in (ctxt, balance_updates) module Internal_for_tests = struct - let deposit_stake = deposit_stake + let deposit_stake ctxt rollup staker = + let open Lwt_result_syntax in + let* ctxt, balance_updates, staked_on, _staker_index = + deposit_stake ctxt rollup staker + in + return (ctxt, balance_updates, staked_on) let refine_stake ctxt rollup staker ?staked_on commitment = let open Lwt_result_syntax in + let* ctxt, staker_index = + Sc_rollup_staker_index_storage.find_staker_index ctxt rollup staker + in match staked_on with - | Some staked_on -> refine_stake ctxt rollup staker staked_on commitment + | Some staked_on -> + refine_stake ctxt rollup staker staked_on commitment ~staker_index | None -> (* This allows to call {!refine_stake} without explicitely passing the - staked_on parameter, it's more convenient for tests. However, - it still enforce that {!deposit_stake} was called before. *) + staked_on parameter, it's more convenient for tests. However, + it still enforce that {!deposit_stake} was called before. *) let* _ctxt, staked_on = Store.Stakers.get (ctxt, rollup) staker in - refine_stake ctxt rollup staker staked_on commitment + refine_stake ctxt rollup staker staked_on commitment ~staker_index end diff --git a/src/proto_alpha/lib_protocol/storage.ml b/src/proto_alpha/lib_protocol/storage.ml index 9e7598a80167..a55bfd260814 100644 --- a/src/proto_alpha/lib_protocol/storage.ml +++ b/src/proto_alpha/lib_protocol/storage.ml @@ -1839,6 +1839,28 @@ module Sc_rollup = struct let encoding = Data_encoding.int32 end) + module Commitment_indexed_context = + Make_indexed_subcontext + (Make_subcontext (Registered) (Indexed_context.Raw_context) + (struct + let name = ["commitment_index"] + end)) + (Make_index (Sc_rollup_commitment_repr.Hash)) + + module Commitment_subcontext = + Make_subcontext (Registered) (Commitment_indexed_context.Raw_context) + (struct + let name = ["commitment_stakers"] + end) + + let clean_commitment_stakers ctxt rollup commitment_hash = + Commitment_indexed_context.remove (ctxt, rollup) commitment_hash + + module Commitment_stakers = + Make_carbonated_data_set_storage + (Commitment_subcontext) + (Make_index (Sc_rollup_staker_index_repr.Index)) + module Commitment_first_publication_level = Make_indexed_carbonated_data_storage (Make_subcontext (Registered) (Indexed_context.Raw_context) diff --git a/src/proto_alpha/lib_protocol/storage.mli b/src/proto_alpha/lib_protocol/storage.mli index 19c562e3fea5..1b6bdad82a28 100644 --- a/src/proto_alpha/lib_protocol/storage.mli +++ b/src/proto_alpha/lib_protocol/storage.mli @@ -843,6 +843,25 @@ module Sc_rollup : sig and type value = int32 and type t = Raw_context.t * Sc_rollup_repr.t + (** Contains for all commitment not yet cemented the list of stakers that have + staked on it. This storage must contains only commitments that are not yet + cemented and so only stakers that currently have stake. The number of + element for any commitment must be equal to the value stored in + {!Commitment_stake_count} *) + module Commitment_stakers : + Carbonated_data_set_storage + with type t = + (Raw_context.t * Sc_rollup_repr.t) * Sc_rollup_commitment_repr.Hash.t + and type elt = Sc_rollup_staker_index_repr.t + + (** Delete the staker list storage. This function must be called only on + a cemented commitment. *) + val clean_commitment_stakers : + Raw_context.t -> + Sc_rollup_repr.t -> + Sc_rollup_commitment_repr.Hash.t -> + (Raw_context.t * Sc_rollup_repr.t) Lwt.t + (** This storage contains for each rollup and inbox level not yet cemented the level of publication of the first commitment. This is used to compute the curfew for a given rollup and inbox level. diff --git a/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_storage.ml b/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_storage.ml index 2d126eef213e..96bfe2369dd0 100644 --- a/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_storage.ml +++ b/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_storage.ml @@ -943,7 +943,12 @@ let test_cement () = let staker = Sc_rollup_repr.Staker.of_b58check_exn "tz1SdKt9kjPp1HRQFkBmXtBhgMfvdgFhSjmG" in + let staker2 = + Sc_rollup_repr.Staker.of_b58check_exn "tz1RikjCkrEde1QQmuesp796jCxeiyE6t3Vo" + in + let* ctxt = deposit_stake_and_check_balances ctxt rollup staker in + let* ctxt = deposit_stake_and_check_balances ctxt rollup staker2 in let commitment = Commitment_repr. { @@ -996,6 +1001,16 @@ let test_cement () = (ctxt, rollup) old_lcc.inbox_level in + + let* () = + let* _ctxt, staker_exists = + lift + @@ Storage.Sc_rollup.Commitment_stakers.mem + ((ctxt, rollup), old_lcc_hash) + (Sc_rollup_staker_index_repr.Internal_for_tests.of_z Z.one) + in + Assert.equal_bool ~loc:__LOC__ false staker_exists + in assert_true ctxt (* Create and cement three commitments: -- GitLab From 87a2f15fe832b991ef416993fbf8b21157dca581 Mon Sep 17 00:00:00 2001 From: Yann Regis-Gianas Date: Fri, 2 Dec 2022 09:12:49 +0100 Subject: [PATCH 3/3] Proto,SCORU: Require conflicting commitments hashes to start game Since get_conflict_point is not gas friendly, we stop using it in the protocol when we start a game. This computation must be done offchain. This commit is a bit large but it simply amounts to push the commitment hashes provided by the initial refutation. Signed-off-by: Yann Regis-Gianas --- .../bin_sc_rollup_node/refutation_game.ml | 29 +- .../lib_client/client_proto_context.mli | 2 +- .../lib_client/operation_result.ml | 6 +- .../lib_protocol/alpha_context.mli | 23 +- src/proto_alpha/lib_protocol/apply.ml | 13 +- .../lib_protocol/operation_repr.ml | 4 +- .../lib_protocol/operation_repr.mli | 2 +- .../lib_protocol/sc_rollup_errors.ml | 65 ++++- .../lib_protocol/sc_rollup_game_repr.ml | 100 ++++--- .../lib_protocol/sc_rollup_game_repr.mli | 55 ++-- .../sc_rollup_refutation_storage.ml | 136 +++++++-- .../sc_rollup_refutation_storage.mli | 15 +- .../lib_protocol/test/helpers/op.mli | 2 +- .../test/helpers/operation_generator.ml | 4 +- .../integration/operations/test_sc_rollup.ml | 173 +++++++----- .../validate/manager_operation_helpers.ml | 4 +- .../test/pbt/test_refutation_game.ml | 58 ++-- .../test/unit/test_sc_rollup_game.ml | 258 ++++++++++++++++-- tezt/lib_tezos/operation_core.ml | 46 +++- tezt/lib_tezos/operation_core.mli | 15 +- ...a refutation game are slashed-rewarded.out | 36 +-- ...Alpha- arith - recover bond of stakers.out | 36 +-- tezt/tests/sc_rollup.ml | 44 ++- 23 files changed, 836 insertions(+), 290 deletions(-) diff --git a/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml b/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml index e62800382478..b7cf8582f12c 100644 --- a/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml +++ b/src/proto_alpha/bin_sc_rollup_node/refutation_game.ml @@ -73,14 +73,14 @@ module Make (Interpreter : Interpreter.S) : | Alice, Bob -> Their_turn | Bob, Alice -> Their_turn - (** [inject_next_move node_ctxt source ~refutation ~opponent] submits an L1 - operation (signed by [source]) to issue the next move in the refutation - game. *) - let inject_next_move (node_ctxt : _ Node_context.t) source ~refutation - ~opponent = + (** [inject_next_move node_ctxt source ~refutation ~opponent ~commitment + ~opponent_commitment] submits an L1 operation (signed by [source]) to + issue the next move in the refutation game. *) + let inject_next_move node_ctxt source ~refutation ~opponent = let open Lwt_result_syntax in let refute_operation = - Sc_rollup_refute {rollup = node_ctxt.rollup_address; refutation; opponent} + Sc_rollup_refute + {rollup = node_ctxt.Node_context.rollup_address; refutation; opponent} in let* _hash = Injector.add_pending_operation ~source refute_operation in return_unit @@ -368,9 +368,9 @@ module Make (Interpreter : Interpreter.S) : Sc_rollup.Proof.serialize_pvm_step ~pvm:(module PVM) proof.pvm_step |> Environment.wrap_tzresult in - let proof = {proof with pvm_step} in + let step = Proof {proof with pvm_step} in let choice = start_tick in - return {choice; step = Proof proof} + return (Move {choice; step}) in match game.game_state with @@ -383,7 +383,7 @@ module Make (Interpreter : Interpreter.S) : dissection in if Z.(equal chosen_section_len one) then final_move choice - else return {choice; step = Dissection dissection} + else return (Move {choice; step = Dissection dissection}) | Final_move {agreed_start_chunk; refuted_stop_chunk = _} -> let choice = agreed_start_chunk.tick in final_move choice @@ -391,7 +391,7 @@ module Make (Interpreter : Interpreter.S) : let play_next_move node_ctxt game self opponent = let open Lwt_result_syntax in let* refutation = next_move node_ctxt game in - inject_next_move node_ctxt self ~refutation:(Some refutation) ~opponent + inject_next_move node_ctxt self ~refutation ~opponent let play_timeout (node_ctxt : _ Node_context.t) self stakers = let open Lwt_result_syntax in @@ -448,7 +448,14 @@ module Make (Interpreter : Interpreter.S) : let open Lwt_syntax in let open Sc_rollup.Refutation_storage in let* () = Refutation_game_event.conflict_detected conflict in - inject_next_move node_ctxt self ~refutation:None ~opponent:conflict.other + let player_commitment_hash = + Sc_rollup.Commitment.hash_uncarbonated conflict.our_commitment + in + let opponent_commitment_hash = + Sc_rollup.Commitment.hash_uncarbonated conflict.their_commitment + in + let refutation = Start {player_commitment_hash; opponent_commitment_hash} in + inject_next_move node_ctxt self ~refutation ~opponent:conflict.other let start_game_if_conflict head_block node_ctxt self = let open Lwt_result_syntax in diff --git a/src/proto_alpha/lib_client/client_proto_context.mli b/src/proto_alpha/lib_client/client_proto_context.mli index 22e36f72a569..b078fc504922 100644 --- a/src/proto_alpha/lib_client/client_proto_context.mli +++ b/src/proto_alpha/lib_client/client_proto_context.mli @@ -990,7 +990,7 @@ val sc_rollup_refute : ?counter:Manager_counter.t -> source:public_key_hash -> rollup:Alpha_context.Sc_rollup.t -> - refutation:Alpha_context.Sc_rollup.Game.refutation option -> + refutation:Alpha_context.Sc_rollup.Game.refutation -> opponent:Alpha_context.Sc_rollup.Staker.t -> src_pk:public_key -> src_sk:Client_keys.sk_uri -> diff --git a/src/proto_alpha/lib_client/operation_result.ml b/src/proto_alpha/lib_client/operation_result.ml index c46e1d9d0c91..699f8c001a6e 100644 --- a/src/proto_alpha/lib_client/operation_result.ml +++ b/src/proto_alpha/lib_client/operation_result.ml @@ -357,14 +357,12 @@ let pp_manager_operation_content (type kind) source ppf "Smart contract rollup refutation move:@,\ Address: %a@,\ Staker: %a@,\ - Move: %a" + Refutation: %a" Sc_rollup.Address.pp rollup Sc_rollup.Staker.pp opponent - (fun fmt -> function - | None -> Format.pp_print_string fmt "opening of the game" - | Some refutation -> Sc_rollup.Game.pp_refutation fmt refutation) + Sc_rollup.Game.pp_refutation refutation | Sc_rollup_timeout {rollup; stakers = {alice; bob}} -> Format.fprintf diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 4dd1745dfffb..1e2f0818727c 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -3967,7 +3967,12 @@ module Sc_rollup : sig | Dissection of dissection_chunk list | Proof of Proof.serialized Proof.t - type refutation = {choice : Tick.t; step : step} + type refutation = + | Start of { + player_commitment_hash : Commitment.Hash.t; + opponent_commitment_hash : Commitment.Hash.t; + } + | Move of {choice : Tick.t; step : step} val refutation_encoding : refutation Data_encoding.t @@ -3997,8 +4002,8 @@ module Sc_rollup : sig Inbox.history_proof -> Dal.Slots_history.t -> start_level:Raw_level.t -> - parent:Commitment.t -> - child:Commitment.t -> + parent_commitment:Commitment.t -> + defender_commitment:Commitment.t -> refuter:Staker.t -> defender:Staker.t -> default_number_of_sections:int -> @@ -4011,7 +4016,8 @@ module Sc_rollup : sig stakers:Index.t -> Metadata.t -> t -> - refutation -> + step:step -> + choice:Tick.t -> (game_result, t) Either.t tzresult Lwt.t type timeout = {alice : int; bob : int; last_turn_level : Raw_level.t} @@ -4097,8 +4103,8 @@ module Sc_rollup : sig val start_game : context -> t -> - player:public_key_hash -> - opponent:public_key_hash -> + player:public_key_hash * Commitment.Hash.t -> + opponent:public_key_hash * Commitment.Hash.t -> context tzresult Lwt.t val game_move : @@ -4106,7 +4112,8 @@ module Sc_rollup : sig t -> player:Staker.t -> opponent:Staker.t -> - Game.refutation -> + step:Game.step -> + choice:Tick.t -> (Game.game_result option * context) tzresult Lwt.t val get_timeout : @@ -4706,7 +4713,7 @@ and _ manager_operation = | Sc_rollup_refute : { rollup : Sc_rollup.t; opponent : Sc_rollup.Staker.t; - refutation : Sc_rollup.Game.refutation option; + refutation : Sc_rollup.Game.refutation; } -> Kind.sc_rollup_refute manager_operation | Sc_rollup_timeout : { diff --git a/src/proto_alpha/lib_protocol/apply.ml b/src/proto_alpha/lib_protocol/apply.ml index b8691d066330..47573ce62492 100644 --- a/src/proto_alpha/lib_protocol/apply.ml +++ b/src/proto_alpha/lib_protocol/apply.ml @@ -1525,10 +1525,15 @@ let apply_manager_operation : let open Sc_rollup.Refutation_storage in let player = source in (match refutation with - | None -> - start_game ctxt rollup ~player ~opponent >>=? fun ctxt -> - return (None, ctxt) - | Some refutation -> game_move ctxt rollup ~player ~opponent refutation) + | Start {player_commitment_hash; opponent_commitment_hash} -> + start_game + ctxt + rollup + ~player:(player, player_commitment_hash) + ~opponent:(opponent, opponent_commitment_hash) + >>=? fun ctxt -> return (None, ctxt) + | Move {step; choice} -> + game_move ctxt rollup ~player ~opponent ~step ~choice) >>=? fun (game_result, ctxt) -> (match game_result with | None -> return (Sc_rollup.Game.Ongoing, ctxt, []) diff --git a/src/proto_alpha/lib_protocol/operation_repr.ml b/src/proto_alpha/lib_protocol/operation_repr.ml index 3c3070bb3fcf..819cc348323b 100644 --- a/src/proto_alpha/lib_protocol/operation_repr.ml +++ b/src/proto_alpha/lib_protocol/operation_repr.ml @@ -451,7 +451,7 @@ and _ manager_operation = | Sc_rollup_refute : { rollup : Sc_rollup_repr.t; opponent : Sc_rollup_repr.Staker.t; - refutation : Sc_rollup_game_repr.refutation option; + refutation : Sc_rollup_game_repr.refutation; } -> Kind.sc_rollup_refute manager_operation | Sc_rollup_timeout : { @@ -1231,7 +1231,7 @@ module Encoding = struct obj3 (req "rollup" Sc_rollup_repr.encoding) (req "opponent" Sc_rollup_repr.Staker.encoding) - (opt "refutation" Sc_rollup_game_repr.refutation_encoding); + (req "refutation" Sc_rollup_game_repr.refutation_encoding); select = (function | Manager (Sc_rollup_refute _ as op) -> Some op | _ -> None); diff --git a/src/proto_alpha/lib_protocol/operation_repr.mli b/src/proto_alpha/lib_protocol/operation_repr.mli index bfd56723fb7b..68ffecfdf2d4 100644 --- a/src/proto_alpha/lib_protocol/operation_repr.mli +++ b/src/proto_alpha/lib_protocol/operation_repr.mli @@ -528,7 +528,7 @@ and _ manager_operation = | Sc_rollup_refute : { rollup : Sc_rollup_repr.t; opponent : Sc_rollup_repr.Staker.t; - refutation : Sc_rollup_game_repr.refutation option; + refutation : Sc_rollup_game_repr.refutation; } -> Kind.sc_rollup_refute manager_operation (** [Sc_rollup_refute { rollup; opponent; refutation }] makes a move diff --git a/src/proto_alpha/lib_protocol/sc_rollup_errors.ml b/src/proto_alpha/lib_protocol/sc_rollup_errors.ml index fadb9f81a8fe..4b9484b9f1b8 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_errors.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_errors.ml @@ -80,6 +80,15 @@ type error += | (* `Permanent *) Sc_rollup_address_generation | (* `Permanent *) Sc_rollup_zero_tick_commitment | (* `Permanent *) Sc_rollup_commitment_past_curfew + | (* `Permanent *) + Sc_rollup_not_valid_commitments_conflict of + Sc_rollup_commitment_repr.Hash.t + * Signature.public_key_hash + * Sc_rollup_commitment_repr.Hash.t + * Signature.public_key_hash + | (* `Permanent *) + Sc_rollup_wrong_staker_for_conflict_commitment of + Signature.public_key_hash * Sc_rollup_commitment_repr.Hash.t let () = register_error_kind @@ -533,4 +542,58 @@ let () = (function | Sc_rollup_max_number_of_parallel_games_reached staker -> Some staker | _ -> None) - (fun staker -> Sc_rollup_max_number_of_parallel_games_reached staker) + (fun staker -> Sc_rollup_max_number_of_parallel_games_reached staker) ; + register_error_kind + `Permanent + ~id:"Sc_rollup_not_valid_commitments_conflict" + ~title:"Conflicting commitments does not have a common ancestor" + ~pp:(fun ppf (c1, s1, c2, s2) -> + Format.fprintf + ppf + "The two commitments %a, staked by %a, and %a, staked by %a, does not \ + have a common predecessor. Two commitments are in conflict when there \ + direct predecessor is the same." + Sc_rollup_commitment_repr.Hash.pp + c1 + Signature.Public_key_hash.pp_short + s1 + Sc_rollup_commitment_repr.Hash.pp + c2 + Signature.Public_key_hash.pp_short + s2) + ~description + Data_encoding.( + obj4 + (req "commitment" Sc_rollup_commitment_repr.Hash.encoding) + (req "player" Signature.Public_key_hash.encoding) + (req "opponent_commitment" Sc_rollup_commitment_repr.Hash.encoding) + (req "opponent" Signature.Public_key_hash.encoding)) + (function + | Sc_rollup_not_valid_commitments_conflict (c1, s1, c2, s2) -> + Some (c1, s1, c2, s2) + | _ -> None) + (fun (c1, s1, c2, s2) -> + Sc_rollup_not_valid_commitments_conflict (c1, s1, c2, s2)) ; + register_error_kind + `Permanent + ~id:"Sc_rollup_wrong_staker_for_conflict_commitment" + ~title:"Given commitment is not staked by given staker" + ~pp:(fun ppf (staker, commitment) -> + Format.fprintf + ppf + "The staker %a has not staked commitment %a" + Signature.Public_key_hash.pp + staker + Sc_rollup_commitment_repr.Hash.pp + commitment) + ~description + Data_encoding.( + obj2 + (req "player" Signature.Public_key_hash.encoding) + (req "commitment" Sc_rollup_commitment_repr.Hash.encoding)) + (function + | Sc_rollup_wrong_staker_for_conflict_commitment (staker, commitment) -> + Some (staker, commitment) + | _ -> None) + (fun (staker, commitment) -> + Sc_rollup_wrong_staker_for_conflict_commitment (staker, commitment)) diff --git a/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml b/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml index b3c049969710..450fa19acf3a 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml @@ -471,20 +471,20 @@ end let make_chunk state_hash tick = {state_hash; tick} let initial inbox dal_snapshot ~start_level - ~(parent : Sc_rollup_commitment_repr.t) - ~(child : Sc_rollup_commitment_repr.t) ~refuter ~defender + ~(parent_commitment : Sc_rollup_commitment_repr.t) + ~(defender_commitment : Sc_rollup_commitment_repr.t) ~refuter ~defender ~default_number_of_sections = let ({alice; _} : Index.t) = Index.make refuter defender in let alice_to_play = Staker.equal alice refuter in let open Sc_rollup_tick_repr in - let tick = of_number_of_ticks child.number_of_ticks in + let tick = of_number_of_ticks defender_commitment.number_of_ticks in let game_state = Dissecting { dissection = [ - make_chunk (Some parent.compressed_state) initial; - make_chunk (Some child.compressed_state) tick; + make_chunk (Some parent_commitment.compressed_state) initial; + make_chunk (Some defender_commitment.compressed_state) tick; make_chunk None (next tick); ]; default_number_of_sections; @@ -496,7 +496,7 @@ let initial inbox dal_snapshot ~start_level inbox_snapshot = inbox; dal_snapshot; start_level; - inbox_level = child.inbox_level; + inbox_level = defender_commitment.inbox_level; game_state; } @@ -541,25 +541,63 @@ let pp_step ppf step = states | Proof proof -> Format.fprintf ppf "proof: %a" Sc_rollup_proof_repr.pp proof -type refutation = {choice : Sc_rollup_tick_repr.t; step : step} +type refutation = + | Start of { + player_commitment_hash : Sc_rollup_commitment_repr.Hash.t; + opponent_commitment_hash : Sc_rollup_commitment_repr.Hash.t; + } + | Move of {choice : Sc_rollup_tick_repr.t; step : step} -let pp_refutation ppf {choice; step} = - Format.fprintf - ppf - "Tick: %a@ Step: %a" - Sc_rollup_tick_repr.pp - choice - pp_step - step +let pp_refutation ppf = function + | Start {player_commitment_hash; opponent_commitment_hash} -> + Format.fprintf + ppf + "Start game between commitment hashes %a and %a" + Sc_rollup_commitment_repr.Hash.pp + player_commitment_hash + Sc_rollup_commitment_repr.Hash.pp + opponent_commitment_hash + | Move {choice; step} -> + Format.fprintf + ppf + "Tick: %a@ Step: %a" + Sc_rollup_tick_repr.pp + choice + pp_step + step let refutation_encoding = let open Data_encoding in - conv - (fun {choice; step} -> (choice, step)) - (fun (choice, step) -> {choice; step}) - (obj2 - (req "choice" Sc_rollup_tick_repr.encoding) - (req "step" step_encoding)) + union + ~tag_size:`Uint8 + [ + case + ~title:"Start" + (Tag 0) + (obj3 + (req "refutation_kind" (constant "start")) + (req + "player_commitment_hash" + Sc_rollup_commitment_repr.Hash.encoding) + (req + "opponent_commitment_hash" + Sc_rollup_commitment_repr.Hash.encoding)) + (function + | Start {player_commitment_hash; opponent_commitment_hash} -> + Some ((), player_commitment_hash, opponent_commitment_hash) + | _ -> None) + (fun ((), player_commitment_hash, opponent_commitment_hash) -> + Start {player_commitment_hash; opponent_commitment_hash}); + case + ~title:"Move" + (Tag 1) + (obj3 + (req "refutation_kind" (constant "move")) + (req "choice" Sc_rollup_tick_repr.encoding) + (req "step" step_encoding)) + (function Move {choice; step} -> Some ((), choice, step) | _ -> None) + (fun ((), choice, step) -> Move {choice; step}); + ] type reason = Conflict_resolved | Timeout @@ -808,12 +846,12 @@ let loser_of_results ~alice_result ~bob_result = | false, true -> Some Alice | true, false -> Some Bob -let cost_play _game refutation = - match refutation.step with +let cost_play ~step ~choice = + match step with | Dissection states -> let number_of_states = List.length states in let hash_size = State_hash.size in - let tick_size = Sc_rollup_tick_repr.size_in_bytes refutation.choice in + let tick_size = Sc_rollup_tick_repr.size_in_bytes choice in Sc_rollup_costs.cost_check_dissection ~number_of_states ~tick_size @@ -865,19 +903,17 @@ let cost_play _game refutation = scale10 @@ Gas_limit_repr.atomic_step_cost @@ cost_N_IBlake2b overapproximated_hashing_size -let play kind dal_parameters ~dal_attestation_lag ~stakers metadata game - refutation = +let play kind dal_parameters ~dal_attestation_lag ~stakers metadata game ~step + ~choice = let open Lwt_result_syntax in let (Packed ((module PVM) as pvm)) = Sc_rollups.Kind.pvm_of kind in let mk_loser loser = let loser = Index.staker stakers loser in Either.Left (Loser {loser; reason = Conflict_resolved}) in - match (refutation.step, game.game_state) with + match (step, game.game_state) with | Dissection states, Dissecting {dissection; default_number_of_sections} -> - let*? start_chunk, stop_chunk = - find_choice dissection refutation.choice - in + let*? start_chunk, stop_chunk = find_choice dissection choice in let*? () = PVM.check_dissection ~default_number_of_sections @@ -900,9 +936,7 @@ let play kind dal_parameters ~dal_attestation_lag ~stakers metadata game }) | Dissection _, Final_move _ -> tzfail Dissecting_during_final_move | Proof proof, Dissecting {dissection; default_number_of_sections = _} -> - let*? start_chunk, stop_chunk = - find_choice dissection refutation.choice - in + let*? start_chunk, stop_chunk = find_choice dissection choice in let*? pvm_step = Sc_rollup_proof_repr.unserialize_pvm_step ~pvm proof.pvm_step in diff --git a/src/proto_alpha/lib_protocol/sc_rollup_game_repr.mli b/src/proto_alpha/lib_protocol/sc_rollup_game_repr.mli index ac183ecd7f58..5145218b4537 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_game_repr.mli +++ b/src/proto_alpha/lib_protocol/sc_rollup_game_repr.mli @@ -191,6 +191,12 @@ module V1 : sig (** A game is characterized by: + - [refuter_commitment_hash], the hash of the commitment of the player that + has initiated the game. + + - [defender_commitment_hash], the hash of the commitment of the player that + is tentatively refuted. + - [turn], the player that must provide the next move. - [inbox_snapshot], a snapshot of the inbox state at the moment the @@ -284,20 +290,20 @@ end (** To begin a game, first the conflict point in the commit tree is found, and then this function is applied. - [initial inbox dal_slots_history ~start_level ~parent ~child - ~refuter ~defender ~default_number_of_sections] will construct an - initial game where [refuter] is next to play. The game has + [initial inbox dal_slots_history ~start_level ~parent_commitment + ~defender_commitment ~refuter ~defender ~default_number_of_sections] will + construct an initial game where [refuter] is next to play. The game has [dissection] with three states: - - firstly, the state (with tick zero) of [parent], the commitment - that both stakers agree on. + - firstly, the state (with tick zero) of [parent_commitment], the + commitment that both stakers agree on. - - secondly, the state and tick count of [child], the commitment - that [defender] has staked on. + - secondly, the state and tick count of [defender_commitment], the + commitment that [defender] has staked on. - - thirdly, a [None] state which is a single tick after the [child] - commitment. This represents the claim, implicit in the commitment, - that the state given is blocked. + - thirdly, a [None] state which is a single tick after the + [defender_commitment] commitment. This represents the claim, implicit in + the commitment, that the state given is blocked. This gives [refuter] a binary choice: she can refute the commit itself by providing a new dissection between the two committed @@ -308,10 +314,10 @@ val initial : Sc_rollup_inbox_repr.history_proof -> Dal_slot_repr.History.t -> start_level:Raw_level_repr.t -> - parent:Sc_rollup_commitment_repr.t -> - child:Sc_rollup_commitment_repr.t -> - refuter:Staker.t -> - defender:Staker.t -> + parent_commitment:Sc_rollup_commitment_repr.t -> + defender_commitment:Sc_rollup_commitment_repr.t -> + refuter:Signature.public_key_hash -> + defender:Signature.public_key_hash -> default_number_of_sections:int -> t @@ -321,9 +327,15 @@ type step = | Dissection of dissection_chunk list | Proof of Sc_rollup_proof_repr.serialized Sc_rollup_proof_repr.t -(** A [refutation] is a move in the game. [choice] is the final tick - in the current dissection at which the two players agree. *) -type refutation = {choice : Sc_rollup_tick_repr.t; step : step} +(** A [refutation] is a move in the game. *) +type refutation = + | Start of { + player_commitment_hash : Sc_rollup_commitment_repr.Hash.t; + opponent_commitment_hash : Sc_rollup_commitment_repr.Hash.t; + } + | Move of {choice : Sc_rollup_tick_repr.t; step : step} + (** [choice] is the final tick in the current dissection at which + the two players agree. *) val pp_refutation : Format.formatter -> refutation -> unit @@ -385,12 +397,13 @@ val play : stakers:Index.t -> Sc_rollup_metadata_repr.t -> t -> - refutation -> + step:step -> + choice:Sc_rollup_tick_repr.t -> (game_result, t) Either.t tzresult Lwt.t -(** [cost_play game refutation] returns the gas cost of [play] applied with [game] - and [refutation]. *) -val cost_play : t -> refutation -> Gas_limit_repr.cost +(** [cost_play ~step ~choice] returns the gas cost of [play] applied with[step], + and [choice]. *) +val cost_play : step:step -> choice:Sc_rollup_tick_repr.t -> Gas_limit_repr.cost (** A type that represents the number of blocks left for players to play. Each player has her timeout value. `timeout` is expressed in the number of diff --git a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml index cc88007c67a0..ed18d2cd2656 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml @@ -243,12 +243,72 @@ let remove_game ctxt rollup stakers = in return ctxt -(** [start_game ctxt rollup refuter defender] initialises the game or - if it already exists fails with `Sc_rollup_game_already_started`. +(** [check_conflict_point ctxt rollup ~refuter ~refuter_commitment_hash + ~defender ~defender_commitment_hash] checks that the refuter is staked on + [commitment] with hash [refuter_commitment_hash], res. for [defender] and + [defender_commitment] with hash [defender_commitment_hash]. Fails with + {!Sc_rollup_errors.Sc_rollup_wrong_staker_for_conflict_commitment}. - The game is created with `refuter` as the first player to move. The - initial state of the game will be obtained from the commitment pair - belonging to [defender] at the conflict point. See + It also verifies that both are pointing to the same predecessor and thus are + in conflict, fails with + {!Sc_rollup_errors.Sc_rollup_not_first_conflict_between_stakers} otherwise. +*) +let check_conflict_point ctxt rollup ~refuter ~refuter_commitment_hash ~defender + ~defender_commitment_hash = + let open Lwt_result_syntax in + let fail_unless_staker_is_staked_on_commitment ctxt staker commitment_hash = + let* ctxt, staker_index = + Storage.Sc_rollup.Staker_index.get (ctxt, rollup) staker + in + let* ctxt, valid_staker_index = + Storage.Sc_rollup.Commitment_stakers.mem + ((ctxt, rollup), commitment_hash) + staker_index + in + let* () = + fail_unless + valid_staker_index + (Sc_rollup_wrong_staker_for_conflict_commitment (staker, commitment_hash)) + in + return ctxt + in + let* ctxt = + fail_unless_staker_is_staked_on_commitment + ctxt + refuter + refuter_commitment_hash + in + let* ctxt = + fail_unless_staker_is_staked_on_commitment + ctxt + defender + defender_commitment_hash + in + let* refuter_commitment, ctxt = + Commitment_storage.get_commitment_unsafe ctxt rollup refuter_commitment_hash + in + let* defender_commitment, ctxt = + Commitment_storage.get_commitment_unsafe + ctxt + rollup + defender_commitment_hash + in + let* () = + fail_unless + Commitment_hash.( + refuter_commitment.predecessor = defender_commitment.predecessor) + (Sc_rollup_errors.Sc_rollup_not_valid_commitments_conflict + (refuter_commitment_hash, refuter, defender_commitment_hash, defender)) + in + return (defender_commitment, ctxt) + +(** [start_game ctxt rollup ~player:(player, player_commitment_hash) + ~opponent:(opponent, opponent_commitment_hash)] initialises the game or if + it already exists fails with [Sc_rollup_game_already_started]. + + The game is created with [player] as the first player to + move. The initial state of the game will be obtained from the + commitment pair belonging to [opponent] at the conflict point. See [Sc_rollup_game_repr.initial] for documentation on how a pair of commitments is turned into an initial game state. @@ -258,22 +318,32 @@ let remove_game ctxt rollup stakers = since a staker can publish at most one commitment per inbox level. It also initialises the timeout level to the current level plus - [timeout_period_in_blocks] (which will become a protocol constant - soon) to mark the block level at which it becomes possible for - anyone to end the game by timeout. + [timeout_period_in_blocks] to mark the block level at which it becomes + possible for anyone to end the game by timeout. May fail with: - {ul - {li [Sc_rollup_does_not_exist] if [rollup] does not exist} - {li [Sc_rollup_no_conflict] if [refuter] is staked on an ancestor of - the commitment staked on by [defender], or vice versa} - {li [Sc_rollup_not_staked] if one of the [refuter] or [defender] is - not actually staked} - {li [Sc_rollup_staker_in_game] if one of the [refuter] or [defender] - is already playing a game} - } *) -let start_game ctxt rollup ~player:refuter ~opponent:defender = + + {ul + {li [Sc_rollup_does_not_exist] if [rollup] does not exist} + {li [Sc_rollup_no_conflict] if [player] is staked on an + ancestor of the commitment staked on by [opponent], or vice versa} + {li [Sc_rollup_not_staked] if one of the [player] or [opponent] is + not actually staked} + {li [Sc_rollup_staker_in_game] if one of the [player] or [opponent] + is already playing a game} + {li [Sc_rollup_not_first_conflict_between_stakers] if the provided + commitments are not the first commitments in conflict between + [player] and [opponent].} +*) +let start_game ctxt rollup ~player:(player, player_commitment_hash) + ~opponent:(opponent, opponent_commitment_hash) = let open Lwt_result_syntax in + (* When the game is started by a given [player], this player is + called the [refuter] and its opponent is the [defender]. *) + let refuter = player + and refuter_commitment_hash = player_commitment_hash + and defender = opponent + and defender_commitment_hash = opponent_commitment_hash in let stakers = Sc_rollup_game_repr.Index.make refuter defender in let* ctxt, game_exists = Store.Game_info.mem (ctxt, rollup) stakers in let* () = fail_when game_exists Sc_rollup_game_already_started in @@ -290,13 +360,20 @@ let start_game ctxt rollup ~player:refuter ~opponent:defender = in let* ctxt = check_staker_availability ctxt stakers.alice in let* ctxt = check_staker_availability ctxt stakers.bob in - let* ( ( {hash = _refuter_commit; commitment = _info}, - {hash = _defender_commit; commitment = child_info} ), - ctxt ) = - get_conflict_point ctxt rollup refuter defender + let* defender_commitment, ctxt = + check_conflict_point + ctxt + rollup + ~refuter + ~defender + ~refuter_commitment_hash + ~defender_commitment_hash in - let* parent_info, ctxt = - Commitment_storage.get_commitment_unsafe ctxt rollup child_info.predecessor + let* parent_commitment, ctxt = + Commitment_storage.get_commitment_unsafe + ctxt + rollup + defender_commitment.predecessor in let* inbox, ctxt = Sc_rollup_inbox_storage.get_inbox ctxt in let default_number_of_sections = @@ -311,11 +388,11 @@ let start_game ctxt rollup ~player:refuter ~opponent:defender = ~start_level:current_level (Sc_rollup_inbox_repr.take_snapshot inbox) slots_history_snapshot - ~parent:parent_info - ~child:child_info ~refuter ~defender ~default_number_of_sections + ~parent_commitment + ~defender_commitment in let* ctxt = create_game ctxt rollup stakers game in let* ctxt, _ = @@ -335,7 +412,7 @@ let check_stakes ctxt rollup (stakers : Sc_rollup_game_repr.Index.t) = | true, false -> return (Some (game_over stakers.bob), ctxt) | false, false -> return (Some Draw, ctxt) -let game_move ctxt rollup ~player ~opponent refutation = +let game_move ctxt rollup ~player ~opponent ~step ~choice = let open Lwt_result_syntax in let stakers = Sc_rollup_game_repr.Index.make player opponent in let* game, ctxt = get_game ctxt rollup stakers in @@ -353,7 +430,7 @@ let game_move ctxt rollup ~player ~opponent refutation = match check_result with | Some game_result -> return (Some game_result, ctxt) | None -> ( - let play_cost = Sc_rollup_game_repr.cost_play game refutation in + let play_cost = Sc_rollup_game_repr.cost_play ~step ~choice in let*? ctxt = Raw_context.consume_gas ctxt play_cost in let* move_result = Sc_rollup_game_repr.play @@ -363,7 +440,8 @@ let game_move ctxt rollup ~player ~opponent refutation = ~stakers metadata game - refutation + ~step + ~choice in match move_result with | Either.Left game_result -> return (Some game_result, ctxt) diff --git a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli index c928e9f068b0..63c7e952bead 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli +++ b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli @@ -70,13 +70,17 @@ val conflicting_stakers_uncarbonated : Sc_rollup_repr.Staker.t -> conflict list tzresult Lwt.t -(** [start_game ctxt rollup ~player ~opponent] initiates a refutation - game between [player] and [opponent] in the given [rollup]. *) +(** [start_game ctxt rollup ~player:(player, player_commitment_hash) + ~opponent:(opponent, opponent_commitment_hash)] initiates a refutation game + between [player] and [opponent] in the given [rollup] as they are in + conflict with [commitment] and [opponent_commitment]. Where [commitment] is + the commitment in the storage with hash [player_commitment_hash] + (resp. [opponent_commitment] with [opponent_commitment_hash]). *) val start_game : Raw_context.t -> Sc_rollup_repr.t -> - player:Signature.public_key_hash -> - opponent:Signature.public_key_hash -> + player:Signature.public_key_hash * Sc_rollup_commitment_repr.Hash.t -> + opponent:Signature.public_key_hash * Sc_rollup_commitment_repr.Hash.t -> Raw_context.t tzresult Lwt.t (** [game_move ctxt rollup player opponent refutation] @@ -117,7 +121,8 @@ val game_move : Sc_rollup_repr.t -> player:Sc_rollup_repr.Staker.t -> opponent:Sc_rollup_repr.Staker.t -> - Sc_rollup_game_repr.refutation -> + step:Sc_rollup_game_repr.step -> + choice:Sc_rollup_tick_repr.t -> (Sc_rollup_game_repr.game_result option * Raw_context.t) tzresult Lwt.t (** [timeout ctxt rollup stakers] checks that the timeout has diff --git a/src/proto_alpha/lib_protocol/test/helpers/op.mli b/src/proto_alpha/lib_protocol/test/helpers/op.mli index 7bcecfbdbb31..c24b7551d5d5 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/op.mli +++ b/src/proto_alpha/lib_protocol/test/helpers/op.mli @@ -733,7 +733,7 @@ val sc_rollup_refute : Contract.t -> Sc_rollup.t -> public_key_hash -> - Sc_rollup.Game.refutation option -> + Sc_rollup.Game.refutation -> Operation.packed tzresult Lwt.t val sc_rollup_timeout : diff --git a/src/proto_alpha/lib_protocol/test/helpers/operation_generator.ml b/src/proto_alpha/lib_protocol/test/helpers/operation_generator.ml index 6503b38e7574..553c3dfaa52e 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/operation_generator.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/operation_generator.ml @@ -706,8 +706,8 @@ let generate_sc_rollup_refute = let open QCheck2.Gen in let* opponent = random_pkh in let+ rollup = random_sc_rollup in - let refutation : Sc_rollup.Game.refutation option = - Some {choice = Sc_rollup.Tick.initial; step = Dissection []} + let refutation : Sc_rollup.Game.refutation = + Sc_rollup.Game.Move {choice = Sc_rollup.Tick.initial; step = Dissection []} in Sc_rollup_refute {rollup; opponent; refutation} diff --git a/src/proto_alpha/lib_protocol/test/integration/operations/test_sc_rollup.ml b/src/proto_alpha/lib_protocol/test/integration/operations/test_sc_rollup.ml index d9637860deb8..4dc11a982a7f 100644 --- a/src/proto_alpha/lib_protocol/test/integration/operations/test_sc_rollup.ml +++ b/src/proto_alpha/lib_protocol/test/integration/operations/test_sc_rollup.ml @@ -1800,46 +1800,69 @@ let test_number_of_parallel_games_bounded () = accounts commitments in - match accounts with - | staker :: opponents -> - let expect_apply_failure = function - | Environment.Ecoproto_error - (Sc_rollup_errors.Sc_rollup_max_number_of_parallel_games_reached - xstaker) - :: _ -> - assert ( - Tezos_crypto.Signature.Public_key_hash.( - xstaker = Account.pkh_of_contract_exn staker)) ; - return_unit - | _ -> - failwith - "It should have failed with \ - [Sc_rollup_max_number_of_parallel_games_reached]" - in - let* block = Incremental.begin_construction block in - let* _block, _counter = - List.fold_left_es - (fun (block, counter) opponent -> - let addr = Account.pkh_of_contract_exn staker in - let* op = Op.sc_rollup_refute (I block) opponent rollup addr None in - let* block = - if counter = max_number_of_parallel_games then - Incremental.add_operation ~expect_apply_failure block op - else Incremental.add_operation block op - in - return (block, counter + 1)) - (block, 0) - opponents - in - return () - | [] -> - (* Because [max_number_of_parallel_games] is strictly positive. *) - assert false + let staker, opponents = + match accounts with + | staker :: opponents -> (staker, opponents) + | [] -> + (* Because [max_number_of_parallel_games] is strictly positive. *) + assert false + in + let staker_commitment, opponents_commitments = + match commitments with + | staker_commitment :: opponents_commitments -> + (staker_commitment, opponents_commitments) + | [] -> + (* Because [max_number_of_parallel_games] is strictly positive. *) + assert false + in + let expect_apply_failure = function + | Environment.Ecoproto_error + (Sc_rollup_errors.Sc_rollup_max_number_of_parallel_games_reached + xstaker) + :: _ -> + assert ( + Tezos_crypto.Signature.Public_key_hash.( + xstaker = Account.pkh_of_contract_exn staker)) ; + return_unit + | _ -> + failwith + "It should have failed with \ + [Sc_rollup_max_number_of_parallel_games_reached]" + in + let* block = Incremental.begin_construction block in + let* _block, _counter = + List.fold_left2_es + ~when_different_lengths:[] + (fun (block, counter) opponent opponent_commitment -> + let addr = Account.pkh_of_contract_exn staker in + let refutation = + Sc_rollup.Game.Start + { + player_commitment_hash = + Sc_rollup.Commitment.hash_uncarbonated opponent_commitment; + opponent_commitment_hash = + Sc_rollup.Commitment.hash_uncarbonated staker_commitment; + } + in + let* op = + Op.sc_rollup_refute (I block) opponent rollup addr refutation + in + let* block = + if counter = max_number_of_parallel_games then + Incremental.add_operation ~expect_apply_failure block op + else Incremental.add_operation block op + in + return (block, counter + 1)) + (block, 0) + opponents + opponents_commitments + in + return () (** [test_timeout] test multiple cases of the timeout logic. - - Test to timeout a player before it's allowed and fails. - - Test that the timeout left by player decreases as expected. - - Test another account can timeout a late player. +- Test to timeout a player before it's allowed and fails. +- Test that the timeout left by player decreases as expected. +- Test another account can timeout a late player. *) let test_timeout () = let* block, (account1, account2, account3) = context_init Context.T3 in @@ -1873,8 +1896,17 @@ let test_timeout () = let* block = add_publish ~rollup block account1 commitment1 in let* block = add_publish ~rollup block account2 commitment2 in + let refutation = + Sc_rollup.Game.Start + { + player_commitment_hash = + Sc_rollup.Commitment.hash_uncarbonated commitment1; + opponent_commitment_hash = + Sc_rollup.Commitment.hash_uncarbonated commitment2; + } + in let* start_game_op = - Op.sc_rollup_refute (B block) account1 rollup pkh2 None + Op.sc_rollup_refute (B block) account1 rollup pkh2 refutation in let* block = add_op block start_game_op in let* block = Block.bake_n (timeout_period_in_blocks - 1) block in @@ -1934,8 +1966,8 @@ let test_timeout () = return Sc_rollup.Dissection_chunk.{state_hash; tick}) in let step = Sc_rollup.Game.Dissection (first_chunk :: rest) in - let move = Sc_rollup.Game.{choice = tick; step} in - Op.sc_rollup_refute (B block) account1 rollup pkh2 (Some move) + let refutation = Sc_rollup.Game.(Move {choice = tick; step}) in + Op.sc_rollup_refute (B block) account1 rollup pkh2 refutation in let* block = add_op block refute in let* pkh1_timeout, pkh2_timeout = @@ -1979,8 +2011,17 @@ let init_with_conflict () = in let* block = add_publish ~rollup block account1 commitment1 in let* block = add_publish ~rollup block account2 commitment2 in + let refutation = + Sc_rollup.Game.Start + { + player_commitment_hash = + Sc_rollup.Commitment.hash_uncarbonated commitment1; + opponent_commitment_hash = + Sc_rollup.Commitment.hash_uncarbonated commitment2; + } + in let* start_game_op = - Op.sc_rollup_refute (B block) account1 rollup pkh2 None + Op.sc_rollup_refute (B block) account1 rollup pkh2 refutation in let* block = add_op block start_game_op in return (block, (account1, pkh1), (account2, pkh2), rollup) @@ -2015,7 +2056,7 @@ let dumb_proof ~choice = } in let proof = Sc_rollup.Proof.{pvm_step; input_proof = Some inbox_proof} in - return Sc_rollup.Game.{choice; step = Proof proof} + return Sc_rollup.Game.(Move {choice; step = Proof proof}) (** Test that two invalid proofs from the two players lead to a draw in the refutation game. *) @@ -2029,7 +2070,7 @@ let test_draw_with_two_invalid_moves () = dumb_proof ~choice in let* p1_final_move_op = - Op.sc_rollup_refute (B block) p1 rollup p2_pkh (Some p1_refutation) + Op.sc_rollup_refute (B block) p1 rollup p2_pkh p1_refutation in add_op block p1_final_move_op in @@ -2045,7 +2086,7 @@ let test_draw_with_two_invalid_moves () = dumb_proof ~choice in let* p2_final_move_op = - Op.sc_rollup_refute (B block) p2 rollup p1_pkh (Some p2_refutation) + Op.sc_rollup_refute (B block) p2 rollup p1_pkh p2_refutation in let* incr = Incremental.begin_construction block in Incremental.add_operation incr p2_final_move_op @@ -2089,7 +2130,7 @@ let test_timeout_during_final_move () = in let* p1_final_move_op = - Op.sc_rollup_refute (B block) p1 rollup p2_pkh (Some p1_refutation) + Op.sc_rollup_refute (B block) p1 rollup p2_pkh p1_refutation in add_op block p1_final_move_op in @@ -2124,7 +2165,7 @@ let test_dissection_during_final_move () = in let* p1_final_move_op = - Op.sc_rollup_refute (B block) p1 rollup p2_pkh (Some p1_refutation) + Op.sc_rollup_refute (B block) p1 rollup p2_pkh p1_refutation in add_op block p1_final_move_op in @@ -2132,11 +2173,9 @@ let test_dissection_during_final_move () = (* Player2 will play a dissection. *) let dumb_dissection = let choice = Sc_rollup.Tick.initial in - Sc_rollup.Game.{choice; step = Dissection []} - in - let* p2_op = - Op.sc_rollup_refute (B block) p2 rollup p1_pkh (Some dumb_dissection) + Sc_rollup.Game.(Move {choice; step = Dissection []}) in + let* p2_op = Op.sc_rollup_refute (B block) p2 rollup p1_pkh dumb_dissection in (* Dissecting is no longer accepted. *) let* incr = Incremental.begin_construction block in let expect_apply_failure = function @@ -2199,7 +2238,7 @@ let make_refutation_metadata ?(boot_sector = "") metadata = let input_proof = Some Sc_rollup.Proof.(Reveal_proof Metadata_proof) in Proof {pvm_step; input_proof} in - return Sc_rollup.Game.{choice; step} + return Sc_rollup.Game.(Move {choice; step}) (** Test that during a refutation game when one malicious player lied on the metadata, he can not win the game. *) @@ -2223,7 +2262,7 @@ let test_refute_invalid_metadata () = } in let* block = add_publish ~rollup block account commitment in - return (block, state1, state2, state3) + return (commitment, block, state1, state2, state3) in (* [account1] will play a valid commitment with the correct evaluation of @@ -2232,7 +2271,7 @@ let test_refute_invalid_metadata () = Sc_rollup.Metadata. {address = rollup; origination_level = Raw_level.(succ root)} in - let* block, state1, state2, state3 = + let* commitment1, block, state1, state2, state3 = post_commitment_from_metadata block account1 valid_metadata in @@ -2240,7 +2279,7 @@ let test_refute_invalid_metadata () = let invalid_metadata = {valid_metadata with origination_level = Raw_level.of_int32_exn 42l} in - let* block, _, _, _ = + let* commitment2, block, _, _, _ = post_commitment_from_metadata block account2 invalid_metadata in @@ -2249,9 +2288,19 @@ let test_refute_invalid_metadata () = know which tick we want to refute. *) let* start_game_op = - Op.sc_rollup_refute (B block) account1 rollup pkh2 None + let refutation = + Sc_rollup.Game.Start + { + player_commitment_hash = + Sc_rollup.Commitment.hash_uncarbonated commitment1; + opponent_commitment_hash = + Sc_rollup.Commitment.hash_uncarbonated commitment2; + } + in + Op.sc_rollup_refute (B block) account1 rollup pkh2 refutation in let* block = add_op block start_game_op in + Format.eprintf "Foo\n%!" ; let dissection : Sc_rollup.Game.refutation = let choice = Sc_rollup.Tick.initial in let step : Sc_rollup.Game.step = @@ -2265,26 +2314,24 @@ let test_refute_invalid_metadata () = {tick = two; state_hash = Some state3}; ] in - {choice; step} + Move {choice; step} in let* dissection_op = - Op.sc_rollup_refute (B block) account1 rollup pkh2 (Some dissection) + Op.sc_rollup_refute (B block) account1 rollup pkh2 dissection in let* block = add_op block dissection_op in (* [account2] will play an invalid proof about the invalid metadata. *) let*! proof = make_refutation_metadata invalid_metadata in - let* proof1_op = - Op.sc_rollup_refute (B block) account2 rollup pkh1 (Some proof) - in + let* proof1_op = Op.sc_rollup_refute (B block) account2 rollup pkh1 proof in let* block = add_op block proof1_op in (* We can implicitely check that [proof1_op] was invalid if [account1] wins the game. *) - let*! proof = make_refutation_metadata valid_metadata in + let*! refutation = make_refutation_metadata valid_metadata in let* incr = Incremental.begin_construction block in let* proof2_op = - Op.sc_rollup_refute (I incr) account1 rollup pkh2 (Some proof) + Op.sc_rollup_refute (I incr) account1 rollup pkh2 refutation in let* incr = Incremental.add_operation incr proof2_op in let expected_game_status : Sc_rollup.Game.status = diff --git a/src/proto_alpha/lib_protocol/test/integration/validate/manager_operation_helpers.ml b/src/proto_alpha/lib_protocol/test/integration/validate/manager_operation_helpers.ml index 2972422ef03a..483561d1eacb 100644 --- a/src/proto_alpha/lib_protocol/test/integration/validate/manager_operation_helpers.ml +++ b/src/proto_alpha/lib_protocol/test/integration/validate/manager_operation_helpers.ml @@ -1052,7 +1052,7 @@ let mk_sc_rollup_refute (oinfos : operation_req) (infos : infos) = let open Lwt_result_syntax in let* sc_rollup = sc_rollup_of infos.ctxt.sc_rollup in let refutation : Sc_rollup.Game.refutation = - {choice = Sc_rollup.Tick.initial; step = Dissection []} + Move {choice = Sc_rollup.Tick.initial; step = Dissection []} in Op.sc_rollup_refute ?fee:oinfos.fee @@ -1066,7 +1066,7 @@ let mk_sc_rollup_refute (oinfos : operation_req) (infos : infos) = (match infos.accounts.dest with | None -> (get_source infos).pkh | Some dest -> dest.pkh) - (Some refutation) + refutation let mk_sc_rollup_add_messages (oinfos : operation_req) (infos : infos) = Op.sc_rollup_add_messages diff --git a/src/proto_alpha/lib_protocol/test/pbt/test_refutation_game.ml b/src/proto_alpha/lib_protocol/test/pbt/test_refutation_game.ml index 45fa391f8b08..27d7dc2aa28c 100644 --- a/src/proto_alpha/lib_protocol/test/pbt/test_refutation_game.ml +++ b/src/proto_alpha/lib_protocol/test/pbt/test_refutation_game.ml @@ -1283,7 +1283,10 @@ let operation_publish_commitment ctxt rollup predecessor inbox_level let*! commitment = create_commitment ~predecessor ~inbox_level ~our_states:player_client.states in - Op.sc_rollup_publish ctxt player_client.player.contract rollup commitment + let* op = + Op.sc_rollup_publish ctxt player_client.player.contract rollup commitment + in + return (op, commitment) (** [build_proof ~player_client start_tick game] builds a valid proof regarding the vision [player_client] has. The proof refutes the @@ -1368,7 +1371,7 @@ let next_move ~player_client (game : Game.t) = | (start_chunk, _stop_chunk) :: _ -> let tick = start_chunk.tick in let+ proof = build_proof ~player_client tick game in - Game.{choice = tick; step = Proof proof} + Game.(Move {choice = tick; step = Proof proof}) | [] -> (* If we reach this case, there is necessarily a disputed section. *) let start_chunk, stop_chunk = Stdlib.List.hd disputed_sections in @@ -1379,11 +1382,13 @@ let next_move ~player_client (game : Game.t) = ~stop_chunk ~our_states:player_client.states in - return Game.{choice = start_chunk.tick; step = Dissection dissection}) + return + Game.( + Move {choice = start_chunk.tick; step = Dissection dissection})) | Final_move {agreed_start_chunk; refuted_stop_chunk = _} -> let tick = agreed_start_chunk.tick in let+ proof = build_proof ~player_client tick game in - Game.{choice = tick; step = Proof proof} + Game.(Move {choice = tick; step = Proof proof}) type game_result_for_tests = Defender_wins | Refuter_wins @@ -1414,7 +1419,7 @@ let play_until_game_result ~refuter_client ~defender_client ~rollup block = player_turn.player.contract rollup opponent.player.pkh - (Some refutation) + refutation in let* incr = Incremental.add_operation incr operation_refutation in match game_status_of_refute_op_result (Incremental.rev_tickets incr) with @@ -1538,44 +1543,57 @@ let gen_game ~p1_strategy ~p2_strategy = let prepare_game ~p1_start block rollup lcc commitment_level p1_client p2_client = let open Lwt_result_syntax in - let* p1_commitment = + let* p1_op, p1_commitment = operation_publish_commitment (B block) rollup lcc commitment_level p1_client in - let* p2_commitment = + let* p2_op, p2_commitment = operation_publish_commitment (B block) rollup lcc commitment_level p2_client in - let commit_then_commit_and_refute ~defender_commitment ~refuter_commitment - (refuter, defender) = + let commit_then_commit_and_refute ~defender_op ~refuter_op refuter + refuter_commitment defender defender_commitment = + let refutation = + Sc_rollup.Game.Start + { + player_commitment_hash = + Sc_rollup.Commitment.hash_uncarbonated refuter_commitment; + opponent_commitment_hash = + Sc_rollup.Commitment.hash_uncarbonated defender_commitment; + } + in let* start_game = Op.sc_rollup_refute (B block) refuter.player.contract rollup defender.player.pkh - None + refutation in let* refuter_batch = Op.batch_operations ~recompute_counters:true ~source:refuter.player.contract (B block) - [refuter_commitment; start_game] - in - let* block = - Block.bake ~operations:[defender_commitment; refuter_batch] block + [refuter_op; start_game] in + let* block = Block.bake ~operations:[defender_op; refuter_batch] block in return (block, refuter, defender) in if p1_start then commit_then_commit_and_refute - ~defender_commitment:p2_commitment - ~refuter_commitment:p1_commitment - (p1_client, p2_client) + ~defender_op:p2_op + ~refuter_op:p1_op + p1_client + p1_commitment + p2_client + p2_commitment else commit_then_commit_and_refute - ~defender_commitment:p1_commitment - ~refuter_commitment:p2_commitment - (p2_client, p1_client) + ~defender_op:p1_op + ~refuter_op:p2_op + p2_client + p2_commitment + p1_client + p1_commitment let check_distribution = function | fst :: snd :: rst -> diff --git a/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_game.ml b/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_game.ml index 8d8bc6ac1daa..7559f0fb68a3 100644 --- a/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_game.ml +++ b/src/proto_alpha/lib_protocol/test/unit/test_sc_rollup_game.ml @@ -80,15 +80,13 @@ let init_dissection ~size ?init_tick start_hash = Stdlib.List.init (size + 1) init_tick let init_refutation ~size ?init_tick start_hash = - G. - { - choice = Sc_rollup_tick_repr.initial; - step = Dissection (init_dissection ~size ?init_tick start_hash); - } + let choice = Sc_rollup_tick_repr.initial in + let step = G.Dissection (init_dissection ~size ?init_tick start_hash) in + (choice, step) let two_stakers_in_conflict () = - let* ctxt, rollup, genesis_hash, refuter, defender = - T.originate_rollup_and_deposit_with_two_stakers () + let* ctxt, rollup, genesis_hash, refuter, defender, staker3 = + T.originate_rollup_and_deposit_with_three_stakers () in let hash1 = hash_string "foo" in let hash2 = hash_string "bar" in @@ -133,14 +131,35 @@ let two_stakers_in_conflict () = T.lift @@ Sc_rollup_stake_storage.publish_commitment ctxt rollup refuter child2 in - return (ctxt, rollup, refuter, defender) + let defender_commitment_hash = + Sc_rollup_commitment_repr.hash_uncarbonated child1 + in + let refuter_commitment_hash = + Sc_rollup_commitment_repr.hash_uncarbonated child2 + in + return + ( ctxt, + rollup, + refuter, + defender, + staker3, + refuter_commitment_hash, + defender_commitment_hash ) (** A dissection is 'poorly distributed' if its tick counts are not very evenly spread through the total tick-duration. Formally, the maximum tick-distance between two consecutive states in a dissection may not be more than half of the total tick-duration. *) let test_poorly_distributed_dissection () = - let* ctxt, rollup, refuter, defender = two_stakers_in_conflict () in + let* ( ctxt, + rollup, + refuter, + defender, + _staker3, + refuter_commitment_hash, + defender_commitment_hash ) = + two_stakers_in_conflict () + in let start_hash = hash_string "foo" in let init_tick size i = mk_dissection_chunk @@ -148,20 +167,38 @@ let test_poorly_distributed_dissection () = if i = size then (None, tick_of_int_exn 10000) else (Some (if i = 0 then start_hash else hash_int i), tick_of_int_exn i) in + let player = refuter and opponent = defender in + let player_commitment_hash = refuter_commitment_hash + and opponent_commitment_hash = defender_commitment_hash in let* ctxt = - T.lift @@ R.start_game ctxt rollup ~player:refuter ~opponent:defender + T.lift + @@ R.start_game + ctxt + rollup + ~player:(player, player_commitment_hash) + ~opponent:(opponent, opponent_commitment_hash) in let size = Constants_storage.sc_rollup_number_of_sections_in_dissection ctxt in - let move = init_refutation ~size ~init_tick start_hash in + let choice, step = init_refutation ~size ~init_tick start_hash in assert_fails_with_f ~__LOC__ - (T.lift @@ R.game_move ctxt rollup ~player:refuter ~opponent:defender move) + (T.lift + @@ R.game_move ctxt rollup ~player:refuter ~opponent:defender ~step ~choice + ) (function D.Dissection_invalid_distribution _ -> true | _ -> false) let test_single_valid_game_move () = - let* ctxt, rollup, refuter, defender = two_stakers_in_conflict () in + let* ( ctxt, + rollup, + refuter, + defender, + _staker3, + refuter_commitment_hash, + defender_commitment_hash ) = + two_stakers_in_conflict () + in let start_hash = hash_string "foo" in let size = Constants_storage.sc_rollup_number_of_sections_in_dissection ctxt @@ -175,15 +212,22 @@ let test_single_valid_game_move () = else if i = size then (None, tick_of_int_exn 10000) else (Some (hash_int i), tick_of_int_exn (i * tick_per_state))) in + let player = refuter and opponent = defender in + let player_commitment_hash = refuter_commitment_hash + and opponent_commitment_hash = defender_commitment_hash in + let* ctxt = - T.lift @@ R.start_game ctxt rollup ~player:refuter ~opponent:defender - in - let move = - Sc_rollup_game_repr. - {choice = Sc_rollup_tick_repr.initial; step = Dissection dissection} + T.lift + @@ R.start_game + ctxt + rollup + ~player:(player, player_commitment_hash) + ~opponent:(opponent, opponent_commitment_hash) in + let choice, step = (Sc_rollup_tick_repr.initial, G.Dissection dissection) in let* game_result, _ctxt = - T.lift @@ R.game_move ctxt rollup ~player:refuter ~opponent:defender move + T.lift + @@ R.game_move ctxt rollup ~player:refuter ~opponent:defender ~choice ~step in Assert.is_none ~loc:__LOC__ ~pp:Sc_rollup_game_repr.pp_game_result game_result @@ -240,6 +284,164 @@ let test_invalid_serialized_inbox_proof () = res (( = ) Sc_rollup_proof_repr.Sc_rollup_invalid_serialized_inbox_proof) +let test_first_move_with_swapped_commitment () = + let* ( ctxt, + rollup, + refuter, + defender, + _staker3, + refuter_commitment_hash, + defender_commitment_hash ) = + two_stakers_in_conflict () + in + let player = refuter + and opponent = defender + and player_commitment_hash = refuter_commitment_hash + and opponent_commitment_hash = defender_commitment_hash in + let*! res = + T.lift + @@ R.start_game + ctxt + rollup + ~player:(player, opponent_commitment_hash) + ~opponent:(opponent, player_commitment_hash) + in + Assert.proto_error + ~loc:__LOC__ + res + (( = ) + (Sc_rollup_errors.Sc_rollup_wrong_staker_for_conflict_commitment + (player, opponent_commitment_hash))) + +let test_first_move_from_invalid_player () = + let* ( ctxt, + rollup, + _refuter, + defender, + staker3, + refuter_commitment_hash, + defender_commitment_hash ) = + two_stakers_in_conflict () + in + let opponent = defender + and player_commitment_hash = refuter_commitment_hash + and opponent_commitment_hash = defender_commitment_hash in + let*! res = + T.lift + @@ R.start_game + ctxt + rollup + ~player:(staker3, player_commitment_hash) + ~opponent:(opponent, opponent_commitment_hash) + in + Assert.proto_error + ~loc:__LOC__ + res + (( = ) + (Sc_rollup_errors.Sc_rollup_wrong_staker_for_conflict_commitment + (staker3, player_commitment_hash))) + +let test_first_move_with_invalid_opponent () = + let* ( ctxt, + rollup, + refuter, + _defender, + staker3, + refuter_commitment_hash, + defender_commitment_hash ) = + two_stakers_in_conflict () + in + let player = refuter + and player_commitment_hash = refuter_commitment_hash + and opponent_commitment_hash = defender_commitment_hash in + let*! res = + T.lift + @@ R.start_game + ctxt + rollup + ~player:(player, player_commitment_hash) + ~opponent:(staker3, opponent_commitment_hash) + in + Assert.proto_error + ~loc:__LOC__ + res + (( = ) + (Sc_rollup_errors.Sc_rollup_wrong_staker_for_conflict_commitment + (staker3, opponent_commitment_hash))) + +let test_first_move_with_invalid_ancestor () = + let* ( ctxt, + rollup, + refuter, + defender, + _staker3, + refuter_commitment_hash, + defender_commitment_hash ) = + two_stakers_in_conflict () + in + let* inbox_level = T.lift @@ T.proper_valid_inbox_level (ctxt, rollup) 3 in + let refuter_commitment = + let context_hash11 = hash_string "child11" in + Commitment_repr. + { + predecessor = refuter_commitment_hash; + inbox_level; + number_of_ticks = T.number_of_ticks_exn 10000L; + compressed_state = context_hash11; + } + in + let defender_commitment = + let context_hash21 = hash_string "child21" in + Commitment_repr. + { + predecessor = defender_commitment_hash; + inbox_level; + number_of_ticks = T.number_of_ticks_exn 10000L; + compressed_state = context_hash21; + } + in + let ctxt = T.advance_level_for_commitment ctxt refuter_commitment in + let* _, _, ctxt, _ = + T.lift + @@ Sc_rollup_stake_storage.publish_commitment + ctxt + rollup + refuter + refuter_commitment + in + let* _, _, ctxt, _ = + T.lift + @@ Sc_rollup_stake_storage.publish_commitment + ctxt + rollup + defender + defender_commitment + in + let refuter_commitment_hash = + Sc_rollup_commitment_repr.hash_uncarbonated refuter_commitment + in + let defender_commitment_hash = + Sc_rollup_commitment_repr.hash_uncarbonated defender_commitment + in + let player = refuter + and opponent = defender + and player_commitment_hash = refuter_commitment_hash + and opponent_commitment_hash = defender_commitment_hash in + let*! res = + T.lift + @@ R.start_game + ctxt + rollup + ~player:(player, player_commitment_hash) + ~opponent:(opponent, opponent_commitment_hash) + in + Assert.proto_error + ~loc:__LOC__ + res + (( = ) + (Sc_rollup_errors.Sc_rollup_not_valid_commitments_conflict + (player_commitment_hash, player, opponent_commitment_hash, opponent))) + let tests = [ Tztest.tztest @@ -254,4 +456,22 @@ let tests = "Invalid serialized inbox proof is rejected." `Quick test_invalid_serialized_inbox_proof; + Tztest.tztest + "Test to start a game with invalid commitment hash (swap commitment)." + `Quick + test_first_move_with_swapped_commitment; + Tztest.tztest + "Test to start a game with invalid commitment hash (op from outsider)." + `Quick + test_first_move_from_invalid_player; + Tztest.tztest + "Test to start a game with invalid commitment hash (opponent is not in \ + game)." + `Quick + test_first_move_with_invalid_opponent; + Tztest.tztest + "Test to start a game with commitment hash that are not the first \ + conflict." + `Quick + test_first_move_with_invalid_ancestor; ] diff --git a/tezt/lib_tezos/operation_core.ml b/tezt/lib_tezos/operation_core.ml index 49d417bf5e35..88e8ee844f86 100644 --- a/tezt/lib_tezos/operation_core.ml +++ b/tezt/lib_tezos/operation_core.ml @@ -302,10 +302,15 @@ module Manager = struct | Proof of sc_rollup_proof | Dissection of sc_rollup_dissection_chunk list - type sc_rollup_refutation = { - choice_tick : int; - refutation_step : sc_rollup_game_refutation_step; - } + type sc_rollup_refutation = + | Start of { + player_commitment_hash : string; + opponent_commitment_hash : string; + } + | Move of { + choice_tick : int; + refutation_step : sc_rollup_game_refutation_step; + } let json_of_sc_rollup_dissection ~dissection = Ezjsonm.list @@ -318,15 +323,28 @@ module Manager = struct let json_payload_binding_of_sc_rollup_refutation sc_rollup opponent refutation = - let json_of_refutation_step {choice_tick; refutation_step} = - let step = - match refutation_step with - | Proof proof -> proof - | Dissection dissection -> json_of_sc_rollup_dissection ~dissection - in - `O [("choice", json_of_int_as_string choice_tick); ("step", step)] + let json_of_refutation_step = function + | Start {player_commitment_hash; opponent_commitment_hash} -> + `O + [ + ("refutation_kind", `String "start"); + ("player_commitment_hash", `String player_commitment_hash); + ("opponent_commitment_hash", `String opponent_commitment_hash); + ] + | Move {choice_tick; refutation_step} -> + let step = + match refutation_step with + | Proof proof -> proof + | Dissection dissection -> json_of_sc_rollup_dissection ~dissection + in + `O + [ + ("refutation_kind", `String "move"); + ("choice", json_of_int_as_string choice_tick); + ("step", step); + ] in - let refutation = json_of_option json_of_refutation_step refutation in + let refutation = json_of_refutation_step refutation in strip_null_fields [ ("kind", `String "sc_rollup_refute"); @@ -355,7 +373,7 @@ module Manager = struct (* See details in {!Operation_repr} module. *) sc_rollup : string; opponent : string; - refutation : sc_rollup_refutation option; + refutation : sc_rollup_refutation; } let reveal account = Reveal account @@ -372,7 +390,7 @@ module Manager = struct let delegation ?(delegate = Constant.bootstrap2) () = Delegation {delegate} - let sc_rollup_refute ?refutation ~sc_rollup ~opponent () = + let sc_rollup_refute ~refutation ~sc_rollup ~opponent () = Sc_rollup_refute {sc_rollup; opponent; refutation} type t = { diff --git a/tezt/lib_tezos/operation_core.mli b/tezt/lib_tezos/operation_core.mli index 26f3b29cc427..65029fb516d3 100644 --- a/tezt/lib_tezos/operation_core.mli +++ b/tezt/lib_tezos/operation_core.mli @@ -304,10 +304,15 @@ module Manager : sig (** An sc_rollup_refutation is the information submitted by players during a (refutation) game. *) - type sc_rollup_refutation = { - choice_tick : int; - refutation_step : sc_rollup_game_refutation_step; - } + type sc_rollup_refutation = + | Start of { + player_commitment_hash : string; + opponent_commitment_hash : string; + } + | Move of { + choice_tick : int; + refutation_step : sc_rollup_game_refutation_step; + } (** [sc_rollup_refute ?refutation ~rollup ~oppenent] builds an Sc rollup refutation manager operation payload. The refutation is [None] in case @@ -316,7 +321,7 @@ module Manager : sig public key hash of the staker who published the commitment we are about to refute. *) val sc_rollup_refute : - ?refutation:sc_rollup_refutation -> + refutation:sc_rollup_refutation -> sc_rollup:string -> opponent:string -> unit -> diff --git a/tezt/tests/expected/sc_rollup.ml/Alpha- arith - participant of a refutation game are slashed-rewarded.out b/tezt/tests/expected/sc_rollup.ml/Alpha- arith - participant of a refutation game are slashed-rewarded.out index 53166c849b23..2529d57643df 100644 --- a/tezt/tests/expected/sc_rollup.ml/Alpha- arith - participant of a refutation game are slashed-rewarded.out +++ b/tezt/tests/expected/sc_rollup.ml/Alpha- arith - participant of a refutation game are slashed-rewarded.out @@ -35,7 +35,7 @@ This sequence of operations was run: ./octez-client --wait none publish commitment from '[PUBLIC_KEY_HASH]' for sc rollup '[SMART_ROLLUP_HASH]' with compressed state '[SC_ROLLUP_PVM_STATE_HASH]' at inbox level 4 and predecessor '[SC_ROLLUP_COMMITMENT_HASH]' and number of ticks 1 Node is bootstrapped. -Estimated gas: 7842.075 units (will add 100 for safety) +Estimated gas: 8528.079 units (will add 100 for safety) Estimated storage: no bytes added Operation successfully injected in the node. Operation hash is '[OPERATION_HASH]' @@ -46,13 +46,13 @@ 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.001115 + Fee to the baker: ꜩ0.001183 Expected counter: 1 - Gas limit: 7943 + Gas limit: 8629 Storage limit: 0 bytes Balance updates: - [PUBLIC_KEY_HASH] ... -ꜩ0.001115 - payload fees(the block proposer) ....... +ꜩ0.001115 + [PUBLIC_KEY_HASH] ... -ꜩ0.001183 + payload fees(the block proposer) ....... +ꜩ0.001183 Smart contract rollup commitment publishing: Address: [SMART_ROLLUP_HASH] Commitment: @@ -61,7 +61,7 @@ This sequence of operations was run: predecessor: [SC_ROLLUP_COMMITMENT_HASH] number_of_ticks: 1 This smart contract rollup commitment publishing was successfully applied - Consumed gas: 7842.075 + Consumed gas: 8528.079 Hash of commit: [SC_ROLLUP_COMMITMENT_HASH] Commitment published at level: 6 Balance updates: @@ -71,7 +71,7 @@ This sequence of operations was run: ./octez-client --wait none publish commitment from '[PUBLIC_KEY_HASH]' for sc rollup '[SMART_ROLLUP_HASH]' with compressed state '[SC_ROLLUP_PVM_STATE_HASH]' at inbox level 4 and predecessor '[SC_ROLLUP_COMMITMENT_HASH]' and number of ticks 2 Node is bootstrapped. -Estimated gas: 8072.075 units (will add 100 for safety) +Estimated gas: 8758.079 units (will add 100 for safety) Estimated storage: no bytes added Operation successfully injected in the node. Operation hash is '[OPERATION_HASH]' @@ -82,13 +82,13 @@ 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.001138 + Fee to the baker: ꜩ0.001206 Expected counter: 1 - Gas limit: 8173 + Gas limit: 8859 Storage limit: 0 bytes Balance updates: - [PUBLIC_KEY_HASH] ... -ꜩ0.001138 - payload fees(the block proposer) ....... +ꜩ0.001138 + [PUBLIC_KEY_HASH] ... -ꜩ0.001206 + payload fees(the block proposer) ....... +ꜩ0.001206 Smart contract rollup commitment publishing: Address: [SMART_ROLLUP_HASH] Commitment: @@ -97,7 +97,7 @@ This sequence of operations was run: predecessor: [SC_ROLLUP_COMMITMENT_HASH] number_of_ticks: 2 This smart contract rollup commitment publishing was successfully applied - Consumed gas: 8072.075 + Consumed gas: 8758.079 Hash of commit: [SC_ROLLUP_COMMITMENT_HASH] Commitment published at level: 7 Balance updates: @@ -107,7 +107,7 @@ This sequence of operations was run: ./octez-client --wait none timeout dispute on sc rollup '[SMART_ROLLUP_HASH]' with '[PUBLIC_KEY_HASH]' against '[PUBLIC_KEY_HASH]' from bootstrap1 Node is bootstrapped. -Estimated gas: 8796.496 units (will add 100 for safety) +Estimated gas: 9466.498 units (will add 100 for safety) Estimated storage: no bytes added Operation successfully injected in the node. Operation hash is '[OPERATION_HASH]' @@ -118,19 +118,19 @@ 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.001176 + Fee to the baker: ꜩ0.001243 Expected counter: 2 - Gas limit: 8897 + Gas limit: 9567 Storage limit: 0 bytes Balance updates: - [PUBLIC_KEY_HASH] ... -ꜩ0.001176 - payload fees(the block proposer) ....... +ꜩ0.001176 + [PUBLIC_KEY_HASH] ... -ꜩ0.001243 + payload fees(the block proposer) ....... +ꜩ0.001243 Smart contract rollup refutation timeout: Address: [SMART_ROLLUP_HASH] First staker (Alice): [PUBLIC_KEY_HASH] Second staker (Bob): [PUBLIC_KEY_HASH] This smart contract rollup refutation timeout was successfully applied - Consumed gas: 8796.496 + Consumed gas: 9466.498 Refutation game status: Game ended: [PUBLIC_KEY_HASH] lost because: timeout Balance updates: Frozen_bonds([PUBLIC_KEY_HASH],[SMART_ROLLUP_HASH]) ... -ꜩ10000 diff --git a/tezt/tests/expected/sc_rollup.ml/Alpha- arith - recover bond of stakers.out b/tezt/tests/expected/sc_rollup.ml/Alpha- arith - recover bond of stakers.out index 6395078e070a..5d91e21d1f47 100644 --- a/tezt/tests/expected/sc_rollup.ml/Alpha- arith - recover bond of stakers.out +++ b/tezt/tests/expected/sc_rollup.ml/Alpha- arith - recover bond of stakers.out @@ -35,7 +35,7 @@ This sequence of operations was run: ./octez-client --wait none publish commitment from '[PUBLIC_KEY_HASH]' for sc rollup '[SMART_ROLLUP_HASH]' with compressed state '[SC_ROLLUP_PVM_STATE_HASH]' at inbox level 12 and predecessor '[SC_ROLLUP_COMMITMENT_HASH]' and number of ticks 1 Node is bootstrapped. -Estimated gas: 7842.075 units (will add 100 for safety) +Estimated gas: 8528.079 units (will add 100 for safety) Estimated storage: no bytes added Operation successfully injected in the node. Operation hash is '[OPERATION_HASH]' @@ -46,13 +46,13 @@ 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.001115 + Fee to the baker: ꜩ0.001183 Expected counter: 2 - Gas limit: 7943 + Gas limit: 8629 Storage limit: 0 bytes Balance updates: - [PUBLIC_KEY_HASH] ... -ꜩ0.001115 - payload fees(the block proposer) ....... +ꜩ0.001115 + [PUBLIC_KEY_HASH] ... -ꜩ0.001183 + payload fees(the block proposer) ....... +ꜩ0.001183 Smart contract rollup commitment publishing: Address: [SMART_ROLLUP_HASH] Commitment: @@ -61,7 +61,7 @@ This sequence of operations was run: predecessor: [SC_ROLLUP_COMMITMENT_HASH] number_of_ticks: 1 This smart contract rollup commitment publishing was successfully applied - Consumed gas: 7842.075 + Consumed gas: 8528.079 Hash of commit: [SC_ROLLUP_COMMITMENT_HASH] Commitment published at level: 14 Balance updates: @@ -71,7 +71,7 @@ This sequence of operations was run: ./octez-client --wait none publish commitment from '[PUBLIC_KEY_HASH]' for sc rollup '[SMART_ROLLUP_HASH]' with compressed state '[SC_ROLLUP_PVM_STATE_HASH]' at inbox level 12 and predecessor '[SC_ROLLUP_COMMITMENT_HASH]' and number of ticks 1 Node is bootstrapped. -Estimated gas: 7612.051 units (will add 100 for safety) +Estimated gas: 8298.055 units (will add 100 for safety) Estimated storage: no bytes added Operation successfully injected in the node. Operation hash is '[OPERATION_HASH]' @@ -82,13 +82,13 @@ 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.001092 + Fee to the baker: ꜩ0.00116 Expected counter: 1 - Gas limit: 7713 + Gas limit: 8399 Storage limit: 0 bytes Balance updates: - [PUBLIC_KEY_HASH] ... -ꜩ0.001092 - payload fees(the block proposer) ....... +ꜩ0.001092 + [PUBLIC_KEY_HASH] ... -ꜩ0.00116 + payload fees(the block proposer) ....... +ꜩ0.00116 Smart contract rollup commitment publishing: Address: [SMART_ROLLUP_HASH] Commitment: @@ -97,7 +97,7 @@ This sequence of operations was run: predecessor: [SC_ROLLUP_COMMITMENT_HASH] number_of_ticks: 1 This smart contract rollup commitment publishing was successfully applied - Consumed gas: 7612.051 + Consumed gas: 8298.055 Hash of commit: [SC_ROLLUP_COMMITMENT_HASH] Commitment published at level: 14 Balance updates: @@ -135,7 +135,7 @@ This sequence of operations was run: ./octez-client --wait none recover bond of '[PUBLIC_KEY_HASH]' for sc rollup '[SMART_ROLLUP_HASH]' from '[PUBLIC_KEY_HASH]' --fee 1 Node is bootstrapped. -Estimated gas: 4350.470 units (will add 100 for safety) +Estimated gas: 5445.472 units (will add 100 for safety) Estimated storage: no bytes added Operation successfully injected in the node. Operation hash is '[OPERATION_HASH]' @@ -148,7 +148,7 @@ This sequence of operations was run: From: [PUBLIC_KEY_HASH] Fee to the baker: ꜩ1 Expected counter: 4 - Gas limit: 4451 + Gas limit: 5546 Storage limit: 0 bytes Balance updates: [PUBLIC_KEY_HASH] ... -ꜩ1 @@ -160,12 +160,12 @@ This sequence of operations was run: Balance updates: Frozen_bonds([PUBLIC_KEY_HASH],[SMART_ROLLUP_HASH]) ... -ꜩ10000 [PUBLIC_KEY_HASH] ...................................................... +ꜩ10000 - Consumed gas: 4350.470 + Consumed gas: 5445.472 ./octez-client --wait none recover bond of '[PUBLIC_KEY_HASH]' for sc rollup '[SMART_ROLLUP_HASH]' from '[PUBLIC_KEY_HASH]' --fee 1 Node is bootstrapped. -Estimated gas: 4350.470 units (will add 100 for safety) +Estimated gas: 5445.472 units (will add 100 for safety) Estimated storage: no bytes added Operation successfully injected in the node. Operation hash is '[OPERATION_HASH]' @@ -178,7 +178,7 @@ This sequence of operations was run: From: [PUBLIC_KEY_HASH] Fee to the baker: ꜩ1 Expected counter: 5 - Gas limit: 4451 + Gas limit: 5546 Storage limit: 0 bytes Balance updates: [PUBLIC_KEY_HASH] ... -ꜩ1 @@ -190,5 +190,5 @@ This sequence of operations was run: Balance updates: Frozen_bonds([PUBLIC_KEY_HASH],[SMART_ROLLUP_HASH]) ... -ꜩ10000 [PUBLIC_KEY_HASH] ...................................................... +ꜩ10000 - Consumed gas: 4350.470 + Consumed gas: 5445.472 diff --git a/tezt/tests/sc_rollup.ml b/tezt/tests/sc_rollup.ml index 75a9475ca045..553f79daade0 100644 --- a/tezt/tests/sc_rollup.ml +++ b/tezt/tests/sc_rollup.ml @@ -3039,9 +3039,16 @@ let test_no_cementation_if_parent_not_lcc_or_if_disputed_commit = (* [operator1] starts a dispute. *) let module M = Operation.Manager in let* () = + let refutation = + M.Start {player_commitment_hash = c32; opponent_commitment_hash = c31} + in bake_operation_via_rpc client @@ M.make ~source:operator2 - @@ M.sc_rollup_refute ~sc_rollup ~opponent:operator1.public_key_hash () + @@ M.sc_rollup_refute + ~sc_rollup + ~opponent:operator1.public_key_hash + ~refutation + () in (* [operator1] will not play and will be timeout-ed. *) let timeout_period = constants.timeout_period_in_blocks in @@ -3087,9 +3094,12 @@ let test_valid_dispute_dissection = let source = operator2 in let opponent = operator1.public_key_hash in let* () = + let refutation = + M.Start {player_commitment_hash = c32; opponent_commitment_hash = c31} + in bake_operation_via_rpc client @@ M.make ~source - @@ M.sc_rollup_refute ~sc_rollup ~opponent () + @@ M.sc_rollup_refute ~sc_rollup ~opponent ~refutation () in (* Construct a valid dissection with valid initial hash of size [sc_rollup.number_of_sections_in_dissection]. The state hash below is @@ -3107,7 +3117,7 @@ let test_valid_dispute_dissection = in (* Inject a valid dissection move *) let refutation = - M.{choice_tick = 0; refutation_step = Dissection (aux 0 [])} + M.Move {choice_tick = 0; refutation_step = Dissection (aux 0 [])} in let* () = @@ -3133,7 +3143,7 @@ let test_timeout = @@ fun client _node ~sc_rollup ~operator1 ~operator2 commits level0 level1 -> (* These are the commitments on the rollup. See [test_forking_scenario] to visualize the tree structure. *) - let c1, c2, _c31, _c32, _c311, _c321 = commits in + let c1, c2, c31, c32, _c311, _c321 = commits in (* A helper function to cement a sequence of commitments. *) let cement = cement_commitments client sc_rollup in let* constants = get_sc_rollup_constants client in @@ -3154,9 +3164,16 @@ let test_timeout = let module M = Operation.Manager in (* [operator2] starts a dispute, but won't be playing then. *) let* () = + let refutation = + M.Start {player_commitment_hash = c32; opponent_commitment_hash = c31} + in bake_operation_via_rpc client @@ M.make ~source:operator2 - @@ M.sc_rollup_refute ~sc_rollup ~opponent:operator1.public_key_hash () + @@ M.sc_rollup_refute + ~sc_rollup + ~opponent:operator1.public_key_hash + ~refutation + () in (* Get exactly to the block where we are able to timeout. *) let* () = @@ -3259,7 +3276,7 @@ let test_refutation_reward_and_punishment ~kind = let* () = repeat d (fun () -> Client.bake_for_and_wait client) in (* [operator1] stakes on a commitment. *) - let* _ = + let* operator1_commitment = publish_dummy_commitment ~inbox_level ~predecessor:c0 @@ -3279,7 +3296,7 @@ let test_refutation_reward_and_punishment ~kind = ~error_msg:"expecting frozen balance for operator1: %R, got %L") ; (* [operator2] stakes on a commitment. *) - let* _ = + let* operator2_commitment = publish_dummy_commitment ~inbox_level ~predecessor:c0 @@ -3300,9 +3317,20 @@ let test_refutation_reward_and_punishment ~kind = let module M = Operation.Manager in (* [operator1] starts a dispute, but will never play. *) let* () = + let refutation = + M.Start + { + player_commitment_hash = operator1_commitment; + opponent_commitment_hash = operator2_commitment; + } + in bake_operation_via_rpc client @@ M.make ~source:operator1 - @@ M.sc_rollup_refute ~sc_rollup ~opponent:operator2.public_key_hash () + @@ M.sc_rollup_refute + ~sc_rollup + ~opponent:operator2.public_key_hash + ~refutation + () in (* Get exactly to the block where we are able to timeout. *) let* () = -- GitLab