From cb4e9014b5ab5cfca40c7f0666d04ca369d5ad1a Mon Sep 17 00:00:00 2001 From: Sylvain Ribstein Date: Mon, 18 Jul 2022 17:24:46 +0200 Subject: [PATCH 1/3] proto/scoru: timeout is different for each player --- .../lib_parameters/default_parameters.ml | 3 - .../constants_parametric_repr.mli | 17 +++++- .../lib_protocol/sc_rollup_errors.ml | 27 +++++++-- .../lib_protocol/sc_rollup_game_repr.ml | 12 ++++ .../lib_protocol/sc_rollup_game_repr.mli | 18 ++++++ .../sc_rollup_refutation_storage.ml | 57 +++++++++++++++---- .../sc_rollup_refutation_storage.mli | 3 +- src/proto_alpha/lib_protocol/storage.ml | 4 +- src/proto_alpha/lib_protocol/storage.mli | 2 +- 9 files changed, 119 insertions(+), 24 deletions(-) diff --git a/src/proto_alpha/lib_parameters/default_parameters.ml b/src/proto_alpha/lib_parameters/default_parameters.ml index 60b736269b88..6b39500e98bd 100644 --- a/src/proto_alpha/lib_parameters/default_parameters.ml +++ b/src/proto_alpha/lib_parameters/default_parameters.ml @@ -52,9 +52,6 @@ let sc_rollup_max_outbox_messages_per_level = 100 It suffers from the same risk of censorship as {!sc_rollup_challenge_windows_in_blocks} so we use the same value. - - TODO: https://gitlab.com/tezos/tezos/-/issues/2902 - This constant needs to be refined. *) let sc_rollup_timeout_period_in_blocks = 20_160 diff --git a/src/proto_alpha/lib_protocol/constants_parametric_repr.mli b/src/proto_alpha/lib_protocol/constants_parametric_repr.mli index 20db61d4065f..22d62377f6d4 100644 --- a/src/proto_alpha/lib_protocol/constants_parametric_repr.mli +++ b/src/proto_alpha/lib_protocol/constants_parametric_repr.mli @@ -102,7 +102,22 @@ type sc_rollup = { max_outbox_messages_per_level : int; (* The default number of required sections in a dissection *) number_of_sections_in_dissection : int; - (* The timeout period for a player in a refutation game. *) + (* The timeout period for a player in a refutation game. + + Timeout logic is similar to a chess clock. Each player starts with the same + timeout = [timeout_period_in_blocks]. Each game move updates the timeout of + the current player by decreasing it by the amount of time she took to play, + i.e. number of blocks since the opponent last move. See + {!Sc_rollup_game_repr.timeout} and + {!Sc_rollup_refutation_storage.game_move} to see the implementation. + + Because of that [timeout_period_in_blocks] must be at least half the upper + bound number of blocks needed for a game to finish. This bound is + correlated to the maximum distance allowed between the first and last tick + of a dissection. For example, when the maximum distance allowed is half the + total distance [(last_tick - last_tick) / 2] then bound is [Log^2 + (Int32.max_int) + 2 = 33]. See {!Sc_rollup_game_repr.check_dissection} for + more information on the dissection logic. *) timeout_period_in_blocks : int; } diff --git a/src/proto_alpha/lib_protocol/sc_rollup_errors.ml b/src/proto_alpha/lib_protocol/sc_rollup_errors.ml index 3e693a2e6e2a..29ba9dbc68ab 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_errors.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_errors.ml @@ -52,7 +52,9 @@ type error += [ `Refuter of Signature.public_key_hash | `Defender of Signature.public_key_hash | `Both of Signature.public_key_hash * Signature.public_key_hash ] - | (* `Temporary *) Sc_rollup_timeout_level_not_reached + | (* `Temporary *) + Sc_rollup_timeout_level_not_reached of + Raw_level_repr.t * Signature.public_key_hash | (* `Temporary *) Sc_rollup_max_number_of_messages_reached_for_commitment_period | (* `Permanent *) Sc_rollup_add_zero_messages @@ -138,10 +140,25 @@ let () = ~id:"Sc_rollup_timeout_level_not_reached" ~title:"Attempt to timeout game too early" ~description - ~pp:(fun ppf () -> Format.fprintf ppf "%s" description) - Data_encoding.unit - (function Sc_rollup_timeout_level_not_reached -> Some () | _ -> None) - (fun () -> Sc_rollup_timeout_level_not_reached) ; + ~pp:(fun ppf (timeout, staker) -> + Format.fprintf + ppf + "%s. The player %a has %a left blocks to play." + description + Signature.Public_key_hash.pp_short + staker + Raw_level_repr.pp + timeout) + Data_encoding.( + obj2 + (req "level_timeout" Raw_level_repr.encoding) + (req "staker" Signature.Public_key_hash.encoding)) + (function + | Sc_rollup_timeout_level_not_reached (timeout, staker) -> + Some (timeout, staker) + | _ -> None) + (fun (timeout, staker) -> + Sc_rollup_timeout_level_not_reached (timeout, staker)) ; let description = "Refutation game already started, must play with is_opening_move = false." in 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 233a311529cf..8c079025826c 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_game_repr.ml @@ -860,3 +860,15 @@ module Internal_for_tests = struct let check_dissection = check_dissection end + +type timeout = {alice : int; bob : int; last_turn_level : Raw_level_repr.t} + +let timeout_encoding = + let open Data_encoding in + conv + (fun {alice; bob; last_turn_level} -> (alice, bob, last_turn_level)) + (fun (alice, bob, last_turn_level) -> {alice; bob; last_turn_level}) + (obj3 + (req "alice" int31) + (req "bob" int31) + (req "last_turn_level" Raw_level_repr.encoding)) 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 0a84c2b54256..0f21b768222c 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_game_repr.mli +++ b/src/proto_alpha/lib_protocol/sc_rollup_game_repr.mli @@ -371,6 +371,24 @@ val outcome_encoding : outcome Data_encoding.t being provided this returns an [outcome]. *) val play : t -> refutation -> (outcome, t) Either.t Lwt.t +(** 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 + blocks. + + Timeout logic is similar to a chess clock. Each player starts with the same + timeout. Each game move updates the timeout of the current player by + decreasing it by the amount of time she took to play, i.e. number of blocks + since the opponent last move. See {!Sc_rollup_refutation_storage.game_move} + to see the implementation. +*) +type timeout = { + alice : int; (** Timeout of [Alice]. *) + bob : int; (** Timeout of [Bob]. *) + last_turn_level : Raw_level_repr.t; (** Block level of the last turn move. *) +} + +val timeout_encoding : timeout Data_encoding.t + module Internal_for_tests : sig (** Checks that the tick count chosen by the current move is one of the ones in the current dissection. Returns a tuple containing 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 082596c7498a..e14938f87656 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml @@ -38,12 +38,43 @@ type point = { type conflict_point = point * point -let timeout_level ctxt = - let level = Raw_context.current_level ctxt in +(** [initial_timeout ctxt] set the initial timeout of players. The initial + timeout of each player is equal to [sc_rollup_timeout_period_in_blocks]. *) +let initial_timeout ctxt = + let last_turn_level = (Raw_context.current_level ctxt).level in let timeout_period_in_blocks = Constants_storage.sc_rollup_timeout_period_in_blocks ctxt in - Raw_level_repr.add level.level timeout_period_in_blocks + Sc_rollup_game_repr. + { + alice = timeout_period_in_blocks; + bob = timeout_period_in_blocks; + last_turn_level; + } + +(** [update_timeout ctxt rollup game idx] update the timeout left for the + current player [game.turn]. Her new timeout is equal to [nb_of_block_left - + (current_level - last_turn_level)] where [nb_of_block_left] is her current + timeout. *) +let update_timeout ctxt rollup (game : Sc_rollup_game_repr.t) idx = + let open Lwt_tzresult_syntax in + let* ctxt, timeout = Store.Game_timeout.get (ctxt, rollup) idx in + let current_level = (Raw_context.current_level ctxt).level in + let sub_block_left nb_of_block_left = + nb_of_block_left + - Int32.to_int (Raw_level_repr.diff current_level timeout.last_turn_level) + in + let new_timeout = + match game.turn with + | Alice -> + let nb_of_block_left = sub_block_left timeout.alice in + {timeout with last_turn_level = current_level; alice = nb_of_block_left} + | Bob -> + let nb_of_block_left = sub_block_left timeout.bob in + {timeout with last_turn_level = current_level; bob = nb_of_block_left} + in + let* ctxt, _ = Store.Game_timeout.update (ctxt, rollup) idx new_timeout in + return ctxt let get_ongoing_game ctxt rollup staker1 staker2 = let open Lwt_tzresult_syntax in @@ -238,7 +269,7 @@ let start_game ctxt rollup ~player:refuter ~opponent:defender = in let* ctxt, _ = Store.Game.init (ctxt, rollup) stakers game in let* ctxt, _ = - Store.Game_timeout.init (ctxt, rollup) stakers (timeout_level ctxt) + Store.Game_timeout.init (ctxt, rollup) stakers (initial_timeout ctxt) in let* ctxt, _ = Store.Opponent.init (ctxt, rollup) refuter defender in let* ctxt, _ = Store.Opponent.init (ctxt, rollup) defender refuter in @@ -260,20 +291,26 @@ let game_move ctxt rollup ~player ~opponent refutation = | Either.Left outcome -> return (Some outcome, ctxt) | Either.Right new_game -> let* ctxt, _ = Store.Game.update (ctxt, rollup) idx new_game in - let* ctxt, _ = - Store.Game_timeout.update (ctxt, rollup) idx (timeout_level ctxt) - in + let* ctxt = update_timeout ctxt rollup game idx in return (None, ctxt) let timeout ctxt rollup stakers = let open Lwt_tzresult_syntax in let level = (Raw_context.current_level ctxt).level in let* game, ctxt = get_game ctxt rollup stakers in - let* ctxt, timeout_level = Store.Game_timeout.get (ctxt, rollup) stakers in + let* ctxt, timeout = Store.Game_timeout.get (ctxt, rollup) stakers in let* () = + let block_left_before_timeout = + match game.turn with Alice -> timeout.alice | Bob -> timeout.bob + in + let level_of_timeout = + Raw_level_repr.add timeout.last_turn_level block_left_before_timeout + in fail_unless - Raw_level_repr.(level > timeout_level) - Sc_rollup_timeout_level_not_reached + Raw_level_repr.(level > level_of_timeout) + (Sc_rollup_timeout_level_not_reached + ( level_of_timeout, + match game.turn with Alice -> stakers.alice | Bob -> stakers.bob )) in return (Sc_rollup_game_repr.{loser = game.turn; reason = Timeout}, 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 db19144362b6..8b95abe7e1e1 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli +++ b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli @@ -84,8 +84,7 @@ val start_game : that [player] is the player whose turn it is; if so, it applies [refutation] using the [play] function. - If the result is a new game, this is stored and the timeout level is - updated. + If the result is a new game, this is stored and the timeout is updated. If the result is an [outcome], this will be returned. diff --git a/src/proto_alpha/lib_protocol/storage.ml b/src/proto_alpha/lib_protocol/storage.ml index 8d587d84d443..10f10fc1fe0d 100644 --- a/src/proto_alpha/lib_protocol/storage.ml +++ b/src/proto_alpha/lib_protocol/storage.ml @@ -1762,9 +1762,9 @@ module Sc_rollup = struct end)) (Make_index (Sc_rollup_game_repr.Index)) (struct - type t = Raw_level_repr.t + type t = Sc_rollup_game_repr.timeout - let encoding = Raw_level_repr.encoding + let encoding = Sc_rollup_game_repr.timeout_encoding end) module Opponent = diff --git a/src/proto_alpha/lib_protocol/storage.mli b/src/proto_alpha/lib_protocol/storage.mli index 0852cb969ecf..3968316767d2 100644 --- a/src/proto_alpha/lib_protocol/storage.mli +++ b/src/proto_alpha/lib_protocol/storage.mli @@ -817,7 +817,7 @@ module Sc_rollup : sig module Game_timeout : Non_iterable_indexed_carbonated_data_storage with type key = Sc_rollup_game_repr.Index.t - and type value = Raw_level_repr.t + and type value = Sc_rollup_game_repr.timeout and type t = Raw_context.t * Sc_rollup_repr.t (** [Opponent] stores the current opponent of the staker. This is -- GitLab From ad02bc679504b94e2d6eb417cff3450e960e5be8 Mon Sep 17 00:00:00 2001 From: Sylvain Ribstein Date: Wed, 20 Jul 2022 14:19:24 +0200 Subject: [PATCH 2/3] proto/scoru: add timeout RPC to get current value --- src/proto_alpha/lib_plugin/RPC.ml | 33 +++++++++++++++++++ .../lib_protocol/alpha_context.mli | 7 ++++ .../sc_rollup_refutation_storage.ml | 9 +++++ .../sc_rollup_refutation_storage.mli | 8 +++++ 4 files changed, 57 insertions(+) diff --git a/src/proto_alpha/lib_plugin/RPC.ml b/src/proto_alpha/lib_plugin/RPC.ml index e74efd09c5c2..11a1fefef01b 100644 --- a/src/proto_alpha/lib_plugin/RPC.ml +++ b/src/proto_alpha/lib_plugin/RPC.ml @@ -1927,6 +1927,24 @@ module Sc_rollup = struct ~output RPC_path.(path /: Sc_rollup.Address.rpc_arg / "conflicts") + let timeout = + let query = + let open RPC_query in + query (fun x y -> + Sc_rollup.Staker.(of_b58check_exn x, of_b58check_exn y)) + |+ field "staker1" RPC_arg.string "" (fun (x, _) -> + Format.asprintf "%a" Sc_rollup.Staker.pp x) + |+ field "staker2" RPC_arg.string "" (fun (_, x) -> + Format.asprintf "%a" Sc_rollup.Staker.pp x) + |> seal + in + let output = Data_encoding.option Sc_rollup.Game.timeout_encoding in + RPC_service.get_service + ~description:"Returns the timeout of players." + ~query + ~output + RPC_path.(path /: Sc_rollup.Address.rpc_arg / "timeout") + let timeout_reached = let query = let open RPC_query in @@ -2067,6 +2085,20 @@ module Sc_rollup = struct rollup staker) + let register_timeout () = + Registration.register1 + ~chunked:false + S.timeout + (fun context rollup (staker1, staker2) () -> + let open Lwt_tzresult_syntax in + let index = Sc_rollup.Game.Index.make staker1 staker2 in + let*! res = + Sc_rollup.Refutation_storage.get_timeout context rollup index + in + match res with + | Ok (timeout, _context) -> return_some timeout + | Error _ -> return_none) + let register_timeout_reached () = Registration.register1 ~chunked:false @@ -2105,6 +2137,7 @@ module Sc_rollup = struct register_root () ; register_ongoing_refutation_game () ; register_conflicts () ; + register_timeout () ; register_timeout_reached () ; register_can_be_cemented () diff --git a/src/proto_alpha/lib_protocol/alpha_context.mli b/src/proto_alpha/lib_protocol/alpha_context.mli index 070c86660305..296da2ae1cd3 100644 --- a/src/proto_alpha/lib_protocol/alpha_context.mli +++ b/src/proto_alpha/lib_protocol/alpha_context.mli @@ -3421,6 +3421,10 @@ module Sc_rollup : sig val play : t -> refutation -> (outcome, t) Either.t Lwt.t + type timeout = {alice : int; bob : int; last_turn_level : Raw_level_repr.t} + + val timeout_encoding : timeout Data_encoding.t + module Internal_for_tests : sig val check_dissection : default_number_of_sections:int -> @@ -3495,6 +3499,9 @@ module Sc_rollup : sig Game.refutation -> (Game.outcome option * context) tzresult Lwt.t + val get_timeout : + context -> t -> Game.Index.t -> (Game.timeout * context) tzresult Lwt.t + val timeout : context -> t -> Game.Index.t -> (Game.outcome * context) tzresult Lwt.t 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 e14938f87656..33403e15db99 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml +++ b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.ml @@ -294,6 +294,15 @@ let game_move ctxt rollup ~player ~opponent refutation = let* ctxt = update_timeout ctxt rollup game idx in return (None, ctxt) +let get_timeout ctxt rollup stakers = + let open Lwt_tzresult_syntax in + let* ctxt, timeout_opt = + Storage.Sc_rollup.Game_timeout.find (ctxt, rollup) stakers + in + match timeout_opt with + | Some timeout -> return (timeout, ctxt) + | None -> fail Sc_rollup_no_game + let timeout ctxt rollup stakers = let open Lwt_tzresult_syntax in let level = (Raw_context.current_level ctxt).level in 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 8b95abe7e1e1..026ce3889bcb 100644 --- a/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli +++ b/src/proto_alpha/lib_protocol/sc_rollup_refutation_storage.mli @@ -146,6 +146,14 @@ val timeout : Sc_rollup_game_repr.Index.t -> (Sc_rollup_game_repr.outcome * Raw_context.t) tzresult Lwt.t +(** [get_timeout ctxt rollup stakers] returns the current timeout values of both + players. *) +val get_timeout : + Raw_context.t -> + Sc_rollup_repr.t -> + Sc_rollup_game_repr.Index.t -> + (Sc_rollup_game_repr.timeout * Raw_context.t) tzresult Lwt.t + (** [apply_outcome ctxt rollup outcome] takes an [outcome] produced by [timeout] or [game_move] and performs the necessary end-of-game cleanup: remove the game itself from the store and punish the losing -- GitLab From 41bb4a5b9638dd9f71d0e4bd73fdd99ff7ae201b Mon Sep 17 00:00:00 2001 From: Sylvain Ribstein Date: Wed, 20 Jul 2022 14:22:49 +0200 Subject: [PATCH 3/3] test/scoru: test timeout of players --- .../lib_protocol/test/helpers/account.ml | 4 + .../lib_protocol/test/helpers/account.mli | 3 + .../lib_protocol/test/helpers/context.ml | 9 + .../lib_protocol/test/helpers/context.mli | 6 + .../integration/operations/test_sc_rollup.ml | 158 +++++++++++++++++- 5 files changed, 179 insertions(+), 1 deletion(-) diff --git a/src/proto_alpha/lib_protocol/test/helpers/account.ml b/src/proto_alpha/lib_protocol/test/helpers/account.ml index df85819387f6..a157f16435bb 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/account.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/account.ml @@ -123,3 +123,7 @@ let new_commitment ?seed () = Lwt.return ( (Environment.wrap_tzresult @@ Tez.(one *? 4_000L)) >|? fun amount -> (unactivated_account, {blinded_public_key_hash = bpkh; amount}) ) + +let pkh_of_contract_exn = function + | Contract.Implicit pkh -> pkh + | Originated _ -> assert false diff --git a/src/proto_alpha/lib_protocol/test/helpers/account.mli b/src/proto_alpha/lib_protocol/test/helpers/account.mli index d7064b862fcf..6d3aeb79ca41 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/account.mli +++ b/src/proto_alpha/lib_protocol/test/helpers/account.mli @@ -68,3 +68,6 @@ val commitment_secret : Blinded_public_key_hash.activation_code val new_commitment : ?seed:Bytes.t -> unit -> (account * Commitment.t) tzresult Lwt.t + +(** Fails if the contract is not an implicit one *) +val pkh_of_contract_exn : Contract.t -> Signature.Public_key_hash.t diff --git a/src/proto_alpha/lib_protocol/test/helpers/context.ml b/src/proto_alpha/lib_protocol/test/helpers/context.ml index 85018eb7f9cd..b99cbef8a1ac 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/context.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/context.ml @@ -393,6 +393,15 @@ module Sc_rollup = struct sc_rollup () () + + let timeout ctxt sc_rollup stakers = + Environment.RPC_context.make_call1 + Plugin.RPC.Sc_rollup.S.timeout + rpc_ctxt + ctxt + sc_rollup + stakers + () end type (_, _) tup = diff --git a/src/proto_alpha/lib_protocol/test/helpers/context.mli b/src/proto_alpha/lib_protocol/test/helpers/context.mli index 416b32da3cc7..989d8480e16f 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/context.mli +++ b/src/proto_alpha/lib_protocol/test/helpers/context.mli @@ -224,6 +224,12 @@ module Sc_rollup : sig val genesis_info : t -> Sc_rollup.t -> Sc_rollup.Commitment.genesis_info tzresult Lwt.t + + val timeout : + t -> + Sc_rollup.t -> + Signature.Public_key_hash.t * Signature.Public_key_hash.t -> + Sc_rollup.Game.timeout option tzresult Lwt.t end type (_, _) tup = 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 50fb11c88545..24a0e83814b2 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 @@ -80,7 +80,8 @@ let assert_equal_z ~loc x y = (** [context_init tup] initializes a context for testing in which the [sc_rollup_enable] constant is set to true. It returns the created context and contracts. *) -let context_init ?(sc_rollup_challenge_window_in_blocks = 10) tup = +let context_init ?(sc_rollup_challenge_window_in_blocks = 10) + ?(timeout_period_in_blocks = 10) tup = Context.init_with_constants_gen tup { @@ -91,6 +92,7 @@ let context_init ?(sc_rollup_challenge_window_in_blocks = 10) tup = Context.default_test_constants.sc_rollup with enable = true; challenge_window_in_blocks = sc_rollup_challenge_window_in_blocks; + timeout_period_in_blocks; }; } @@ -1421,6 +1423,155 @@ let test_inbox_max_number_of_messages_per_commitment_period () = let* _incr = Incremental.add_operation ~expect_apply_failure incr op in return_unit +(** [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. +*) +let test_timeout () = + let* block, (account1, account2, account3) = context_init Context.T3 in + let pkh1 = Account.pkh_of_contract_exn account1 in + let pkh2 = Account.pkh_of_contract_exn account2 in + let* block, rollup = sc_originate block account1 "unit" in + let* constants = Context.get_constants (B block) in + let Constants.Parametric.{timeout_period_in_blocks; _} = + constants.parametric.sc_rollup + in + let* genesis_info = Context.Sc_rollup.genesis_info (B block) rollup in + let* dummy_commitment = dummy_commitment (B block) rollup in + let commitment1 = + { + dummy_commitment with + number_of_ticks = number_of_ticks_exn 4l; + compressed_state = + Sc_rollup.State_hash.context_hash_to_state_hash + (Context_hash.hash_string ["first"]); + } + in + let commitment2 = + { + dummy_commitment with + number_of_ticks = number_of_ticks_exn 4l; + compressed_state = + Sc_rollup.State_hash.context_hash_to_state_hash + (Context_hash.hash_string ["second"]); + } + in + let add_op block op = + let* incr = Incremental.begin_construction block in + let* incr = Incremental.add_operation incr op in + Incremental.finalize_block incr + in + let add_publish block account commitment = + let* publish = Op.sc_rollup_publish (B block) account rollup commitment in + add_op block publish + in + let* block = add_publish block account1 commitment1 in + let* block = add_publish block account2 commitment2 in + let* start_game_op = + Op.sc_rollup_refute (B block) account1 rollup pkh2 None + in + let* block = add_op block start_game_op in + let* block = Block.bake_n (timeout_period_in_blocks - 1) block in + let game_index = Sc_rollup.Game.Index.make pkh1 pkh2 in + (* Testing to send a timeout before it's allowed. There is one block left + before timeout is allowed. *) + let* _incr = + let*? current_level = + Context.get_level (B block) >|? fun lvl -> + Raw_level.to_int32 lvl |> Raw_level_repr.of_int32_exn + in + let expected_block_left = 1l in + let expect_apply_failure = function + | Environment.Ecoproto_error + (Sc_rollup_errors.Sc_rollup_timeout_level_not_reached + (level_timeout, staker) as e) + :: _ -> + Assert.test_error_encodings e ; + let block_left = Raw_level_repr.diff level_timeout current_level in + if block_left = expected_block_left && pkh1 = staker then return_unit + else + failwith + "It should have failed with [Sc_rollup_timeout_level_not_reached \ + (%ld, %a)] but got [Sc_rollup_timeout_level_not_reached (%ld, \ + %a)]" + expected_block_left + Signature.Public_key_hash.pp + pkh1 + block_left + Signature.Public_key_hash.pp + staker + | _ -> + failwith + "It should have failed with [Sc_rollup_timeout_level_not_reached \ + (%ld, %a)]" + expected_block_left + Signature.Public_key_hash.pp + pkh1 + in + let* timeout = Op.sc_rollup_timeout (B block) account3 rollup game_index in + let* incr = Incremental.begin_construction block in + Incremental.add_operation ~expect_apply_failure incr timeout + in + let* refute = + let tick = + WithExceptions.Option.get ~loc:__LOC__ (Sc_rollup.Tick.of_int 0) + in + let* {compressed_state; _} = + Context.Sc_rollup.commitment (B block) rollup genesis_info.commitment_hash + in + let first_chunk = + Sc_rollup.Game.{state_hash = Some compressed_state; tick} + in + let* rest = + List.init_es ~when_negative_length:[] 4 (fun i -> + let state_hash = None in + let tick = + WithExceptions.Option.get + ~loc:__LOC__ + (Sc_rollup.Tick.of_int (i + 1)) + in + return Sc_rollup.Game.{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) + in + let* block = add_op block refute in + let* pkh1_timeout, pkh2_timeout = + let+ timeout = Context.Sc_rollup.timeout (B block) rollup (pkh1, pkh2) in + let timeout = WithExceptions.Option.get ~loc:__LOC__ timeout in + if game_index.alice = pkh1 then (timeout.alice, timeout.bob) + else (timeout.bob, timeout.alice) + in + let* () = Assert.equal_int ~loc:__LOC__ pkh1_timeout 0 in + let* () = + Assert.equal_int ~loc:__LOC__ pkh2_timeout timeout_period_in_blocks + in + let* block = Block.bake_n timeout_period_in_blocks block in + let* timeout = Op.sc_rollup_timeout (B block) account3 rollup game_index in + let* incr = Incremental.begin_construction block in + let* incr = Incremental.add_operation incr timeout in + match Incremental.rev_tickets incr with + | [ + Operation_metadata + { + contents = + Single_result + (Manager_operation_result + { + operation_result = + Applied + (Sc_rollup_timeout_result + {game_status = Ended (Timeout, looser); _}); + _; + }); + }; + ] + when looser = pkh2 -> + return_unit + | _ -> assert false + let tests = [ Tztest.tztest @@ -1501,4 +1652,9 @@ let tests = "inbox max number of messages during commitment period" `Quick test_inbox_max_number_of_messages_per_commitment_period; + Tztest.tztest + "Test that a player can't timeout another player before timeout period \ + and related timeout value." + `Quick + test_timeout; ] -- GitLab