From dbfed560d41ded4a285cd2a3998a2e4b2e6e437e Mon Sep 17 00:00:00 2001 From: Marina Polubelova Date: Fri, 8 Aug 2025 11:11:45 +0200 Subject: [PATCH 1/4] Proto/Tests: mv update_activity to scenario_activity --- .../test/helpers/account_helpers.ml | 46 ------------------- .../test/helpers/scenario_activity.ml | 40 ++++++++++++++++ .../test/helpers/scenario_attestation.ml | 7 +-- .../test/helpers/scenario_bake.ml | 6 +-- .../lib_protocol/test/helpers/scenario_op.ml | 5 +- 5 files changed, 44 insertions(+), 60 deletions(-) create mode 100644 src/proto_alpha/lib_protocol/test/helpers/scenario_activity.ml 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 4961b7dab5a5..b41470f1225c 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/account_helpers.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/account_helpers.ml @@ -560,52 +560,6 @@ let current_total_frozen_deposits_with_limits account_state = account_state.parameters.limit_of_staking_over_baking account_state.frozen_deposits -let update_activity constants current_cycle account = - match account.last_seen_activity with - | None -> - { - account with - last_seen_activity = - Some - (Cycle.add - current_cycle - constants - .Protocol.Alpha_context.Constants.Parametric - .consensus_rights_delay); - } - | Some activity_cycle -> - if - (* When a delegate is initialized or reactivated (either from - [set_delegate] or participating in the consensus again), we put - extra [consensus_rights_delay] cycles in the future to account - for its extended grace period *) - Cycle.( - add - activity_cycle - constants - .Protocol.Alpha_context.Constants.Parametric - .tolerated_inactivity_period - < current_cycle) - then - { - account with - last_seen_activity = - Some - Cycle.( - max - activity_cycle - (add - current_cycle - constants - .Protocol.Alpha_context.Constants.Parametric - .consensus_rights_delay)); - } - else - { - account with - last_seen_activity = Some Cycle.(max activity_cycle current_cycle); - } - let assert_balance_evolution ~loc ~for_accounts ~part ~name ~old_balance ~new_balance compare = let open Lwt_result_syntax in diff --git a/src/proto_alpha/lib_protocol/test/helpers/scenario_activity.ml b/src/proto_alpha/lib_protocol/test/helpers/scenario_activity.ml new file mode 100644 index 000000000000..264b2f858072 --- /dev/null +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_activity.ml @@ -0,0 +1,40 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2025 Nomadic Labs, *) +(* *) +(*****************************************************************************) + +open State_account +open State + +let update_activity_account constants current_activity_cycle account = + let consensus_rights_delay = + constants.Protocol.Alpha_context.Constants.Parametric.consensus_rights_delay + in + let tolerated_inactivity_period = constants.tolerated_inactivity_period in + let last_seen_activity = + (* When a delegate is initialized or reactivated (either from + [set_delegate] or participating in the consensus again), we put + extra [consensus_rights_delay] cycles in the future to account + for its extended grace period *) + match account.last_seen_activity with + | None -> Cycle.add current_activity_cycle consensus_rights_delay + | Some last_seen_activity_cycle -> + let updated = + if + Cycle.( + add last_seen_activity_cycle tolerated_inactivity_period + < current_activity_cycle) + then Cycle.add current_activity_cycle consensus_rights_delay + else current_activity_cycle + in + Cycle.max last_seen_activity_cycle updated + in + {account with last_seen_activity = Some last_seen_activity} + +let update_activity name state current_activity_cycle : State.t = + State.update_account_f + name + (update_activity_account state.constants current_activity_cycle) + state diff --git a/src/proto_alpha/lib_protocol/test/helpers/scenario_attestation.ml b/src/proto_alpha/lib_protocol/test/helpers/scenario_attestation.ml index 5a8f3338b3e1..8221481c98d5 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_attestation.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_attestation.ml @@ -13,12 +13,7 @@ open Scenario_base open Protocol let update_activity name block state : State.t = - State.update_account_f - name - (Account_helpers.update_activity - state.constants - (Block.cycle_of_next_block block)) - state + Scenario_activity.update_activity name state (Block.cycle_of_next_block block) type kind = Preattestation | Attestation 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 50e475f66b9c..8328e923c459 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_bake.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_bake.ml @@ -331,12 +331,10 @@ let finalize_block_ : t_incr -> t tzresult Lwt.t = let baker_name, _ = State.find_account_from_pkh baker.pkh state in (* Update baker activity *) let state = - State.update_account_f + Scenario_activity.update_activity baker_name - (Account_helpers.update_activity - state.constants - (Block.current_cycle block)) state + (Block.current_cycle block) in let* () = check_ai_launch_cycle_is_zero ~loc:__LOC__ block in let* state = State.apply_rewards ~baker:baker_name block state in 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 3bc5841f2f3e..c1095ea689ca 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_op.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_op.ml @@ -167,10 +167,7 @@ let set_delegate src_name delegate_name_opt : (t, t) scenarios = (* if self delegating *) if Option.equal String.equal delegate_name_opt (Some src_name) then let activity_cycle = current_cycle in - State.update_account_f - src_name - (Account_helpers.update_activity state.constants activity_cycle) - state + Scenario_activity.update_activity src_name state activity_cycle else state in return (state, [operation])) -- GitLab From de45d46cd62892a854b84da971201113b095edea Mon Sep 17 00:00:00 2001 From: Marina Polubelova Date: Fri, 8 Aug 2025 15:05:12 +0200 Subject: [PATCH 2/4] Tezt/Test: add protocol migration test for tolerated_inactivity_period --- tezt/tests/protocol_migration.ml | 132 ++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 1 deletion(-) diff --git a/tezt/tests/protocol_migration.ml b/tezt/tests/protocol_migration.ml index c23e10539476..eb47df74a434 100644 --- a/tezt/tests/protocol_migration.ml +++ b/tezt/tests/protocol_migration.ml @@ -2086,6 +2086,135 @@ let test_consensus_rights_delay_shortening () = unit done +let test_tolerated_inactivity_period () = + (* Given that [grace_period] for the current scenario is equal to 4 + cycles, we check a protocol migration near that cycle *) + let migrate_from = Option.get Protocol.(previous_protocol Alpha) in + let migrate_to = Protocol.Alpha in + + for migration_cycle = 4 to 6 do + Test.register + ~__FILE__ + ~title: + (Printf.sprintf + "protocol migration for tolerated_inactivity_period at the end of \ + cycle %d" + (migration_cycle - 1)) + ~tags:[team; "protocol"; "migration"; "tolerated_inactivity_period"] + @@ fun () -> + let parameter_file = Protocol.parameter_file migrate_from in + let parameters = JSON.parse_file parameter_file in + let blocks_per_cycle = JSON.(get "blocks_per_cycle" parameters |> as_int) in + let migration_level = migration_cycle * blocks_per_cycle in + let () = Local_helpers.print_parameters ~parameter_file ~migration_level in + + let* client, _node = + Local_helpers.activate_protocol + ~parameter_file + ~migrate_from + ~migrate_to + ~migration_level + in + let bake_until_cycle_with_and_check = + Local_helpers.bake_until_cycle_with_and_check + ~migration_level + ~migrate_from + ~migrate_to + ~blocks_per_cycle + in + (* [default_baker] never gets deactivated *) + let default_baker = Constant.bootstrap1.alias in + (* [funder] is used to fund all new accounts *) + let funder = Constant.bootstrap2.alias in + + (* [delegate_i] stops participating in cycle (i + 1) *) + let* delegates = + Local_helpers.create_delegates_and_stake + ~baker:default_baker + ~giver:funder + ~delegates: + (List.init 8 (fun i -> + ("delegate_" ^ Int.to_string i, Tez.of_int 300_000))) + client + in + let delegates_array = Array.of_list delegates in + let all_activated = List.init 8 (fun _i -> false) in + let all_deactivated = List.init 8 (fun _i -> true) in + + let check_deactivated_list expected_status_for_delegates = + Lwt_list.iteri_s + (fun i expected -> + let* () = + Local_helpers.check_deactivated delegates_array.(i) ~expected client + in + unit) + expected_status_for_delegates + in + + let* () = + Local_helpers.check_current_level_and_cycle ~level:4 ~cycle:0 client + in + (* grace_period = tolerated_inactivity_period + consensus_rights_delay = 2 + 2 = 4 *) + (* all delegates are marked as active with [grace_period] = 4 *) + let* () = check_deactivated_list all_activated in + + let* () = + Lwt_list.iter_s + (fun target_cycle -> + let delegates_i_and_higher i = + List.init (8 - i) (fun x -> delegates_array.(x + i).alias) + in + (* [delegate_i] cannot bake during cycle_{0,1,2} as they + have no baking rights, so we wait for cycle 3 to start + baking with new delegates; delegate_{0,1,2} never bake in + this testing scenario *) + let bakers, delegate = + if 3 <= target_cycle && target_cycle <= 7 then + ( delegates_i_and_higher target_cycle, + delegates_array.(target_cycle).alias ) + else ([], default_baker) + in + let expected_list = + match target_cycle with + | 3 | 4 -> all_activated + | 5 -> + (* D0, D1, D2 are deactivated at the end of C4 *) + List.init 8 (fun i -> if i <= 2 then true else false) + | 6 -> + (* D3 is deactivated at the end of C5 *) + List.init 8 (fun i -> if i <= 3 then true else false) + | 7 -> + (* D4 is deactivated at the end of C6 *) + List.init 8 (fun i -> if i <= 4 then true else false) + | 8 -> + (* D5 is deactivated at the end of C7 *) + List.init 8 (fun i -> if i <= 5 then true else false) + | 9 -> + (* D6 is deactivated at the end of C8 *) + List.init 8 (fun i -> if i <= 6 then true else false) + | 10 -> + (* D7 is deactivated at the end of C9 *) + all_deactivated + | _ -> failwith "unexpected input" + in + (* [default_baker] must be always active *) + let bakers = default_baker :: bakers in + let* () = + bake_until_cycle_with_and_check + ~bakers + ~target_cycle + ~delegate + ~check_last_block:(fun () -> check_deactivated_list expected_list) + ~check_next_block:(fun _ -> unit) + client + in + unit) + (* From cycle 3 to 10 *) + (List.init 8 (fun i -> i + 3)) + in + unit + done + let register ~migrate_from ~migrate_to = test_migration_for_whole_cycle ~migrate_from ~migrate_to ; test_migration_with_bakers ~migrate_from ~migrate_to () ; @@ -2097,4 +2226,5 @@ let register ~migrate_from ~migrate_to = test_reveal_migration () ; test_tz4_manager_operation ~with_empty_mempool:true ; test_tz4_manager_operation ~with_empty_mempool:false ; - test_consensus_rights_delay_shortening () + test_consensus_rights_delay_shortening () ; + test_tolerated_inactivity_period () -- GitLab From 6e66764bbe1138efc750ff6497a335ebf8d54e1e Mon Sep 17 00:00:00 2001 From: Marina Polubelova Date: Fri, 8 Aug 2025 11:31:43 +0200 Subject: [PATCH 3/4] Proto: update delegate deactivation table with the actual last activity cycle --- .../delegate_activation_storage.ml | 30 +++++++++++-------- .../delegate_activation_storage.mli | 9 +++++- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/proto_alpha/lib_protocol/delegate_activation_storage.ml b/src/proto_alpha/lib_protocol/delegate_activation_storage.ml index 0f064f495520..f1778351f5eb 100644 --- a/src/proto_alpha/lib_protocol/delegate_activation_storage.ml +++ b/src/proto_alpha/lib_protocol/delegate_activation_storage.ml @@ -37,20 +37,28 @@ let is_inactive ctxt delegate = ctxt (Contract_repr.Implicit delegate) in + let tolerance = Constants_storage.tolerated_inactivity_period ctxt in match cycle_opt with | Some last_active_cycle -> let ({Level_repr.cycle = current_cycle; _} : Level_repr.t) = Raw_context.current_level ctxt in - Cycle_repr.(last_active_cycle < current_cycle) + Cycle_repr.(add last_active_cycle tolerance < current_cycle) | None -> (* This case is only when called from `set_active`, when creating a contract. *) false let last_cycle_before_deactivation ctxt delegate = + let open Lwt_result_syntax in + let tolerance = Constants_storage.tolerated_inactivity_period ctxt in let contract = Contract_repr.Implicit delegate in - Storage.Contract.Delegate_last_cycle_before_deactivation.get ctxt contract + let+ cycle = + Storage.Contract.Delegate_last_cycle_before_deactivation.get ctxt contract + in + (* we give [tolerance] cycles to the delegate after its last active + cycle before it can be deactivated *) + Cycle_repr.add cycle tolerance let set_inactive ctxt delegate = Storage.Contract.Inactive_delegate.add ctxt (Contract_repr.Implicit delegate) @@ -59,14 +67,7 @@ let set_active ctxt delegate = let open Lwt_result_syntax in let* inactive = is_inactive ctxt delegate in let current_cycle = (Raw_context.current_level ctxt).cycle in - let tolerance = Constants_storage.tolerated_inactivity_period ctxt in let consensus_rights_delay = Constants_storage.consensus_rights_delay ctxt in - (* We allow a number of cycles before a delegate is deactivated as follows: - - if the delegate is active, we give it at least `tolerance` cycles - after the current cycle before to be deactivated. - - if the delegate is new or inactive, we give it additionally - `consensus_rights_delay` because the delegate needs this number of cycles to - receive rights, so `tolerance + consensus_rights_delay` in total. *) let delegate_contract = Contract_repr.Implicit delegate in let* current_last_active_cycle = Storage.Contract.Delegate_last_cycle_before_deactivation.find @@ -74,13 +75,16 @@ let set_active ctxt delegate = delegate_contract in let last_active_cycle = + (* if the delegate is new or inactive, we give it additionally + [consensus_rights_delay] because the delegate needs this number + of cycles to receive the rights *) match current_last_active_cycle with - | None -> Cycle_repr.add current_cycle (tolerance + consensus_rights_delay) + | None -> Cycle_repr.add current_cycle consensus_rights_delay | Some current_last_active_cycle -> - let delay = - if inactive then tolerance + consensus_rights_delay else tolerance + let updated = + if inactive then Cycle_repr.add current_cycle consensus_rights_delay + else current_cycle in - let updated = Cycle_repr.add current_cycle delay in Cycle_repr.max current_last_active_cycle updated in let*! ctxt = diff --git a/src/proto_alpha/lib_protocol/delegate_activation_storage.mli b/src/proto_alpha/lib_protocol/delegate_activation_storage.mli index 390d37cbef2f..ed30e8e2974d 100644 --- a/src/proto_alpha/lib_protocol/delegate_activation_storage.mli +++ b/src/proto_alpha/lib_protocol/delegate_activation_storage.mli @@ -29,7 +29,14 @@ This module is responsible for maintaining the following tables: - {!Storage.Contract.Inactive_delegate} - - {!Storage.Contract.Delegate_last_cycle_before_deactivation} *) + - {!Storage.Contract.Delegate_last_cycle_before_deactivation} + + Note that [Delegate_last_cycle_before_deactivation] represents the + last active cycle of a delegate without taking into account + [tolerated_inactivity_period]. When a delegate is initialized or + reactivated, we give it additionally [consensus_rights_delay] + cycles to account for the fact that it will not receive consensus + rights yet during the first [consensus_rights_delay] cycles *) val is_inactive : Raw_context.t -> Signature.Public_key_hash.t -> bool tzresult Lwt.t -- GitLab From 76528c4da0b3bafb9d2a73a13962e8611344d0bf Mon Sep 17 00:00:00 2001 From: Marina Polubelova Date: Fri, 8 Aug 2025 16:50:15 +0200 Subject: [PATCH 4/4] Tezt/Test: update tolerated_inactivity migration test --- tezt/tests/protocol_migration.ml | 83 ++++++++++++++++++++++++++------ 1 file changed, 69 insertions(+), 14 deletions(-) diff --git a/tezt/tests/protocol_migration.ml b/tezt/tests/protocol_migration.ml index eb47df74a434..b65b2b2231eb 100644 --- a/tezt/tests/protocol_migration.ml +++ b/tezt/tests/protocol_migration.ml @@ -2088,7 +2088,18 @@ let test_consensus_rights_delay_shortening () = let test_tolerated_inactivity_period () = (* Given that [grace_period] for the current scenario is equal to 4 - cycles, we check a protocol migration near that cycle *) + cycles, we check a protocol migration near that cycle. + + In proto T, we update the Delegate_last_cycle_before_deactivation + table with the actual last activity cycle without doing a + stitching during the protocol migration. It means that the + [grace_period] is off by: + + - 2 cycles for the delegates with the last activity in the last + cycle or last but one cycle of proto S + + - 1 cycle for the delegates with the last activity in the first + cycle of proto T *) let migrate_from = Option.get Protocol.(previous_protocol Alpha) in let migrate_to = Protocol.Alpha in @@ -2174,27 +2185,71 @@ let test_tolerated_inactivity_period () = delegates_array.(target_cycle).alias ) else ([], default_baker) in + + (* Migration at the end of C3: + - D0, D1, D2 are deactivated at the end of C4 -> C6 + - D3 is deactivated at the end of C5 -> C7 + -------------------------[proto migration] + - D4 is deactivated at the end of C6 -> C7 + - D5 is deactivated at the end of C7 + - D6 is deactivated at the end of C8 + - D7 is deactivated at the end of C9 + + Migration at the end of C4: + - D0, D1, D2 are deactivated at the end of C4 + - D3 is deactivated at the end of C5 -> C7 + - D4 is deactivated at the end of C6 -> C8 + -------------------------[proto migration] + - D5 is deactivated at the end of C7 -> C8 + - D6 is deactivated at the end of C8 + - D7 is deactivated at the end of C9 + + Migration at the end of C5: + - D0, D1, D2 are deactivated at the end of C4 + - D3 is deactivated at the end of C5 + - D4 is deactivated at the end of C6 -> C8 + - D5 is deactivated at the end of C7 -> C9 + -------------------------[proto migration] + - D6 is deactivated at the end of C8 -> C9 + - D7 is deactivated at the end of C9 + *) let expected_list = match target_cycle with | 3 | 4 -> all_activated | 5 -> - (* D0, D1, D2 are deactivated at the end of C4 *) - List.init 8 (fun i -> if i <= 2 then true else false) + let is_deactivated i = + if i <= 2 then migration_cycle >= 5 else false + in + List.init 8 is_deactivated | 6 -> - (* D3 is deactivated at the end of C5 *) - List.init 8 (fun i -> if i <= 3 then true else false) + let is_deactivated i = + if i <= 2 then migration_cycle >= 5 + else if i = 3 then migration_cycle >= 6 + else false + in + List.init 8 is_deactivated | 7 -> - (* D4 is deactivated at the end of C6 *) - List.init 8 (fun i -> if i <= 4 then true else false) + let is_deactivated i = + if i <= 2 then true + else if i = 3 then migration_cycle >= 6 + else false + in + List.init 8 is_deactivated | 8 -> - (* D5 is deactivated at the end of C7 *) - List.init 8 (fun i -> if i <= 5 then true else false) + let is_deactivated i = + if i <= 3 then true + else if i = 4 || i = 5 then migration_cycle = 4 + else false + in + List.init 8 is_deactivated | 9 -> - (* D6 is deactivated at the end of C8 *) - List.init 8 (fun i -> if i <= 6 then true else false) - | 10 -> - (* D7 is deactivated at the end of C9 *) - all_deactivated + let is_deactivated i = + if i = 5 || i = 6 then migration_cycle <= 5 + else if i = 7 then false + else true + in + List.init 8 is_deactivated + | 10 -> all_deactivated | _ -> failwith "unexpected input" in (* [default_baker] must be always active *) -- GitLab