From 23086bada07dbf7f0e95298de3c919c80a96ac1b Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Mon, 18 Mar 2024 16:19:01 +0100 Subject: [PATCH 1/4] Proto/tests: track last active cycle of accounts --- .../test/helpers/account_helpers.ml | 5 ++++- .../test/helpers/scenario_bake.ml | 16 +++++++++++++--- .../lib_protocol/test/helpers/scenario_op.ml | 19 +++++++++++++++++-- .../test/helpers/state_ai_flags.ml | 15 +++++++++++++++ 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/proto_alpha/lib_protocol/test/helpers/account_helpers.ml b/src/proto_alpha/lib_protocol/test/helpers/account_helpers.ml index 620f200a85ee..ee6ce8b9fb4b 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/account_helpers.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/account_helpers.ml @@ -50,6 +50,7 @@ type account_state = { is unmodified if at that time, the account is not a delegate or is a deactivated delegate. *) slashed_cycles : Cycle.t list; + last_active_cycle : Cycle.t; } let init_account ?delegate ~pkh ~contract ~parameters ?(liquid = Tez.zero) @@ -58,7 +59,7 @@ let init_account ?delegate ~pkh ~contract ~parameters ?(liquid = Tez.zero) ?(unstaked_finalizable = Unstaked_finalizable.zero) ?(staking_delegator_numerator = Z.zero) ?(staking_delegate_denominator = Z.zero) ?(frozen_rights = CycleMap.empty) - ?(slashed_cycles = []) () = + ?(slashed_cycles = []) ?(last_active_cycle = Cycle.root) () = { pkh; contract; @@ -73,6 +74,7 @@ let init_account ?delegate ~pkh ~contract ~parameters ?(liquid = Tez.zero) staking_delegate_denominator; frozen_rights; slashed_cycles; + last_active_cycle; } type account_map = account_state String.Map.t @@ -117,6 +119,7 @@ let balance_of_account account_name (account_map : account_map) = staking_delegate_denominator; frozen_rights = _; slashed_cycles = _; + last_active_cycle = _; } -> let balance = { diff --git a/src/proto_alpha/lib_protocol/test/helpers/scenario_bake.ml b/src/proto_alpha/lib_protocol/test/helpers/scenario_bake.ml index c83c10b832dc..fa90942efb64 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_bake.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_bake.ml @@ -14,7 +14,7 @@ open Scenario_base (** Applies when baking the last block of a cycle *) let apply_end_cycle current_cycle previous_block block state : State.t tzresult Lwt.t = - let open Lwt_result_syntax in + let open Lwt_result_wrap_syntax in Log.debug ~color:time_color "Ending cycle %a" Cycle.pp current_cycle ; (* Apply all slashes *) let* state = @@ -165,9 +165,20 @@ let bake ?baker : t -> t tzresult Lwt.t = Some (Block.By_account pkh) in let* baker, _, _, _ = Block.get_next_baker ?policy block in - let baker_name, {contract = baker_contract; _} = + let baker_name, ({contract = baker_contract; _} as baker_acc) = State.find_account_from_pkh baker state in + let current_cycle = Block.current_cycle block in + (* update baker activity *) + let state = + State.update_map + ~f:(fun acc_map -> + String.Map.add + baker_name + {baker_acc with last_active_cycle = current_cycle} + acc_map) + state + in let* level = Plugin.RPC.current_level Block.rpc_ctxt block in assert (Protocol.Alpha_context.Cycle.(level.cycle = Block.current_cycle block)) ; Log.info @@ -176,7 +187,6 @@ let bake ?baker : t -> t tzresult Lwt.t = (Int32.to_int (Int32.succ Block.(block.header.shell.level))) (Protocol.Alpha_context.Cycle.to_int32 level.cycle) baker_name ; - let current_cycle = Block.current_cycle block in let adaptive_issuance_vote = if state.force_ai_vote_yes then Protocol.Alpha_context.Per_block_votes.Per_block_vote_on diff --git a/src/proto_alpha/lib_protocol/test/helpers/scenario_op.ml b/src/proto_alpha/lib_protocol/test/helpers/scenario_op.ml index 4d9ef0ad945b..aec95c26a42e 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_op.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_op.ml @@ -111,7 +111,7 @@ let set_delegate src_name delegate_name_opt : (t, t) scenarios = let is_not_changing_delegate = Option.equal String.equal delegate_name_opt src.delegate in - let cycle = Block.current_cycle block in + let current_cycle = Block.current_cycle block in let* operation = Op.delegation ~fee:Tez.zero (B block) src.contract delegate_pkh_opt in @@ -120,11 +120,26 @@ let set_delegate src_name delegate_name_opt : (t, t) scenarios = if Q.(equal balance.staked_b zero) || is_not_changing_delegate then state else - let state = State.apply_unstake cycle Tez.max_tez src_name state in + let state = + State.apply_unstake current_cycle Tez.max_tez src_name state + in (* Changing delegate applies finalize if unstake happened *) State.apply_finalize src_name state in let state = State.update_delegate src_name delegate_name_opt state in + (* update delegate activation status *) + let state = + (* if self delegating *) + if Option.equal String.equal delegate_name_opt (Some src_name) then + State.update_map + ~f:(fun acc_map -> + String.Map.add + src_name + {src with last_active_cycle = current_cycle} + acc_map) + state + else state + in return (state, [operation])) (** Stake operation *) diff --git a/src/proto_alpha/lib_protocol/test/helpers/state_ai_flags.ml b/src/proto_alpha/lib_protocol/test/helpers/state_ai_flags.ml index 994f84f5981d..17d84d8c1743 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/state_ai_flags.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/state_ai_flags.ml @@ -107,15 +107,30 @@ module Autostake = struct staking_delegate_denominator = _; frozen_rights = _; slashed_cycles = _; + last_active_cycle; } : account_state) state = let open Result_syntax in + (* TODO: use Protocol.Constants_storage.tolerated_inactivity_period *) + let tolerated_inactivity_period = + (2 * state.constants.consensus_rights_delay) + 1 + in if Some name <> delegate then ( Log.debug "Model Autostaking: %s <> %s, noop@." name (Option.value ~default:"None" delegate) ; return state) + else if + Cycle.(old_cycle = add last_active_cycle tolerated_inactivity_period) + then + return + @@ update_map + ~f:(apply_unstake (Cycle.succ old_cycle) Tez.max_tez name) + state + else if + Cycle.(old_cycle > add last_active_cycle tolerated_inactivity_period) + then return state else let* current_liquid_delegated = liquid_delegated ~name state in let current_frozen = Frozen_tez.total_current frozen_deposits in -- GitLab From 6f65140f5f3a75f84036f70192f884a8678cd783 Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Mon, 18 Mar 2024 16:23:10 +0100 Subject: [PATCH 2/4] Proto/tests: add new file for deactivation tests --- manifest/product_octez.ml | 1 + src/proto_alpha/lib_protocol/test/integration/dune | 1 + .../test/integration/test_scenario_deactivation.ml | 14 ++++++++++++++ 3 files changed, 16 insertions(+) create mode 100644 src/proto_alpha/lib_protocol/test/integration/test_scenario_deactivation.ml diff --git a/manifest/product_octez.ml b/manifest/product_octez.ml index 49cdda36189b..06844b424b36 100644 --- a/manifest/product_octez.ml +++ b/manifest/product_octez.ml @@ -5041,6 +5041,7 @@ end = struct ("test_scenario_autostaking", N.(number >= 020)); ("test_scenario_slashing", N.(number >= 020)); ("test_scenario_slashing_stakers", N.(number >= 020)); + ("test_scenario_deactivation", N.(number >= 020)); ("test_liquidity_baking", true); ("test_storage_functions", true); ("test_storage", true); diff --git a/src/proto_alpha/lib_protocol/test/integration/dune b/src/proto_alpha/lib_protocol/test/integration/dune index 0eff50c01d45..7cdf9f80af8d 100644 --- a/src/proto_alpha/lib_protocol/test/integration/dune +++ b/src/proto_alpha/lib_protocol/test/integration/dune @@ -37,6 +37,7 @@ test_scenario_autostaking test_scenario_slashing test_scenario_slashing_stakers + test_scenario_deactivation test_liquidity_baking test_storage_functions test_storage diff --git a/src/proto_alpha/lib_protocol/test/integration/test_scenario_deactivation.ml b/src/proto_alpha/lib_protocol/test/integration/test_scenario_deactivation.ml new file mode 100644 index 000000000000..ba3c9a7d27a7 --- /dev/null +++ b/src/proto_alpha/lib_protocol/test/integration/test_scenario_deactivation.ml @@ -0,0 +1,14 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2024 Nomadic Labs, *) +(* *) +(*****************************************************************************) + +(** Testing + ------- + Component: Protocol, Consensus, Deactivation + Invocation: dune exec src/proto_alpha/lib_protocol/test/integration/main.exe \ + -- --file test_scenario_deactivation.ml + Subject: Test deactivation in the protocol. +*) -- GitLab From 245647a15679c12ca2220d06eafe0db8b2756966 Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Mon, 18 Mar 2024 17:29:47 +0100 Subject: [PATCH 3/4] Proto/tests: refactor some functions --- .../test/helpers/scenario_bake.ml | 35 +++++-------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/src/proto_alpha/lib_protocol/test/helpers/scenario_bake.ml b/src/proto_alpha/lib_protocol/test/helpers/scenario_bake.ml index fa90942efb64..9b70f7a47f35 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_bake.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_bake.ml @@ -309,15 +309,9 @@ let exec_op f = --> next_block (** Waiting functions *) -let rec wait_n_cycles n = - if n <= 0 then noop - else if n = 1 then next_cycle - else wait_n_cycles (n - 1) --> next_cycle +let wait_n_cycles n = loop n next_cycle -let rec wait_n_blocks n = - if n <= 0 then noop - else if n = 1 then next_block - else wait_n_blocks (n - 1) --> next_block +let wait_n_blocks n = loop n next_block let wait_cycle_f_es (condition : t -> t -> bool tzresult Lwt.t) : (t, t) scenarios = @@ -338,14 +332,8 @@ let wait_cycle_f_es (condition : t -> t -> bool tzresult Lwt.t) : *) let wait_cycle_f (condition : t -> t -> bool) : (t, t) scenarios = let open Lwt_result_syntax in - exec (fun init_t -> - let rec bake_while t = - if condition init_t t then return t - else - let* t = next_cycle_ t in - bake_while t - in - bake_while init_t) + let condition a b = return @@ condition a b in + wait_cycle_f_es condition (** Wait until we are in a cycle satisfying the given condition. Fails if AI_activation is requested and AI is not set to be activated in the future. *) @@ -442,16 +430,6 @@ let wait_ai_activation = let wait_delegate_parameters_activation = wait_cycle_until `delegate_parameters_activation -let wait_n_cycles_f (n_cycles : t -> int) = - let condition ((init_block, _init_state) as t_init) - ((current_block, _current_state) as _t_current) = - let n = n_cycles t_init in - let init_cycle = Block.current_cycle init_block in - let current_cycle = Block.current_cycle current_block in - Cycle.(current_cycle >= add init_cycle n) - in - wait_cycle_f condition - let wait_n_cycles_f_es (n_cycles : t -> int tzresult Lwt.t) = let open Lwt_result_syntax in let condition ((init_block, _init_state) as t_init) @@ -462,3 +440,8 @@ let wait_n_cycles_f_es (n_cycles : t -> int tzresult Lwt.t) = return Cycle.(current_cycle >= add init_cycle n) in wait_cycle_f_es condition + +let wait_n_cycles_f (n_cycles : t -> int) = + let open Lwt_result_syntax in + let n_cycles n = return @@ n_cycles n in + wait_n_cycles_f_es n_cycles -- GitLab From 50a10b5d508279e941e357f6c9545d52319ae6f9 Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Tue, 19 Mar 2024 09:56:56 +0100 Subject: [PATCH 4/4] Proto/tests: add new deactivation tests --- .../integration/test_scenario_deactivation.ml | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/src/proto_alpha/lib_protocol/test/integration/test_scenario_deactivation.ml b/src/proto_alpha/lib_protocol/test/integration/test_scenario_deactivation.ml index ba3c9a7d27a7..8d6a09f3f4c1 100644 --- a/src/proto_alpha/lib_protocol/test/integration/test_scenario_deactivation.ml +++ b/src/proto_alpha/lib_protocol/test/integration/test_scenario_deactivation.ml @@ -12,3 +12,147 @@ -- --file test_scenario_deactivation.ml Subject: Test deactivation in the protocol. *) + +open State_account +open Tez_helpers.Ez_tez +open Scenario + +let check_is_active ~loc src_name = + let open Lwt_result_syntax in + exec_unit @@ fun (block, state) -> + let src = State.find_account src_name state in + let* b = Context.Delegate.deactivated (B block) src.pkh in + Assert.is_true ~loc (not b) + +let check_is_not_active ~loc src_name = + let open Lwt_result_syntax in + exec_unit @@ fun (block, state) -> + let src = State.find_account src_name state in + let* b = Context.Delegate.deactivated (B block) src.pkh in + Assert.is_true ~loc b + +(** Test that a delegate gets deactivated after a set period of time if it is not baking. + Test that with autostaking, the frozen funds are completely unstaked, which get + finalizable (but not finalized) after a set period of time. + Test that these finalizable funds can indeed be finalized. *) +let test_simple_scenario_with_autostaking = + init_constants () + --> set S.Adaptive_issuance.autostaking_enable true + --> activate_ai `No + --> begin_test ["delegate"; "baker"] + --> set_baker "baker" + --> wait_n_cycles_f (fun (_, state) -> + (2 * state.State.constants.consensus_rights_delay) + 1) + --> check_balance_field "delegate" `Staked (Tez.of_mutez 200_000_000_000L) + --> check_is_active ~loc:__LOC__ "delegate" + --> next_cycle + --> check_is_not_active ~loc:__LOC__ "delegate" + --> check_balance_field "delegate" `Staked Tez.zero + --> check_balance_field + "delegate" + `Unstaked_frozen_total + (Tez.of_mutez 200_000_000_000L) + --> wait_n_cycles_f Test_scenario_stake.unstake_wait + --> check_balance_field "delegate" `Unstaked_frozen_total Tez.zero + --> check_balance_field + "delegate" + `Unstaked_finalizable + (Tez.of_mutez 200_000_000_000L) + --> (Tag "Reactivate" + --> set_delegate "delegate" (Some "delegate") + --> check_is_active ~loc:__LOC__ "delegate" + --> next_cycle + --> check_balance_field "delegate" `Unstaked_finalizable Tez.zero + --> check_balance_field + "delegate" + `Staked + (Tez.of_mutez 200_000_000_000L) + |+ Tag "manual finalize unstake" + --> finalize_unstake "delegate" + --> check_balance_field "delegate" `Unstaked_finalizable Tez.zero + --> check_balance_field "delegate" `Staked Tez.zero + --> check_balance_field + "delegate" + `Liquid + (Tez.of_mutez 4_000_000_000_000L)) + +(** Test that a delegate gets deactivated after a set period of time if it is not baking. + Test that with AI, the frozen funds stay frozen, and the delegate can still issue AI + operations without reactivating. *) +let test_simple_scenario_with_ai = + init_constants () --> activate_ai `Force + --> begin_test ["delegate"; "baker"] + --> check_balance_field "delegate" `Staked (Tez.of_mutez 200_000_000_000L) + --> set_baker "baker" + --> wait_n_cycles_f (fun (_, state) -> + (2 * state.State.constants.consensus_rights_delay) + 1) + --> check_balance_field "delegate" `Staked (Tez.of_mutez 200_000_000_000L) + --> check_is_active ~loc:__LOC__ "delegate" + --> next_cycle + --> check_is_not_active ~loc:__LOC__ "delegate" + --> check_balance_field "delegate" `Staked (Tez.of_mutez 200_000_000_000L) + --> check_balance_field "delegate" `Unstaked_frozen_total Tez.zero + --> unstake "delegate" All + --> wait_n_cycles_f Test_scenario_stake.unstake_wait + --> finalize_unstake "delegate" + --> check_balance_field "delegate" `Unstaked_finalizable Tez.zero + --> check_balance_field "delegate" `Staked Tez.zero + --> check_balance_field "delegate" `Liquid (Tez.of_mutez 4_000_000_000_000L) + --> check_is_not_active ~loc:__LOC__ "delegate" + --> stake "delegate" Half --> next_cycle + --> check_is_not_active ~loc:__LOC__ "delegate" + +(** Test that a delegate can be deactivated by setting its frozen funds to 0. + Test that a delegate can be activated while having no rights. + Test that a delegate can be deactivated while having rights, and test that it can + still bake while deactivated, hence reactivating *) +let test_baking_deactivation = + init_constants () --> activate_ai `Force + --> begin_test ["delegate"; "baker"] + --> unstake "delegate" All + --> wait_n_cycles_f (fun (_, state) -> + (2 * state.State.constants.consensus_rights_delay) + 1) + --> check_is_active ~loc:__LOC__ "delegate" + --> next_cycle + --> check_is_not_active ~loc:__LOC__ "delegate" + (* Reactivate and wait for rights *) + --> stake "delegate" Half + --> set_delegate "delegate" (Some "delegate") + (* No rights yet, but active *) + --> assert_failure (next_block_with_baker "delegate") + --> check_is_active ~loc:__LOC__ "delegate" + --> wait_n_cycles_f (fun (_, state) -> + state.State.constants.consensus_rights_delay + 1) + --> check_is_active ~loc:__LOC__ "delegate" + --> next_block_with_baker "delegate" + (* Get deactivated by doing nothing *) + --> set_baker "baker" + --> wait_n_cycles_f (fun (_, state) -> + state.State.constants.consensus_rights_delay + 1) + --> check_is_active ~loc:__LOC__ "delegate" + --> next_cycle + --> check_is_not_active ~loc:__LOC__ "delegate" + (* The delegate still has enough rights to bake... *) + --> exec_unit (fun (block, state) -> + let dlgt = State.find_account "delegate" state in + let current_cycle = Block.current_cycle block in + let rights = + CycleMap.find current_cycle dlgt.frozen_rights + |> Option.value ~default:Tez.zero + in + Assert.not_equal_tez ~loc:__LOC__ Tez.zero rights) + --> next_block_with_baker "delegate" + --> check_is_active ~loc:__LOC__ "delegate" + +let tests = + tests_of_scenarios + [ + ( "Test simple deactivation scenario with autostaking", + test_simple_scenario_with_autostaking ); + ("Test simple deactivation scenario with ai", test_simple_scenario_with_ai); + ( "Test deactivation and reactivation scenarios with baking", + test_baking_deactivation ); + ] + +let () = + register_tests ~__FILE__ ~tags:["protocol"; "scenario"; "deactivation"] tests -- GitLab