From 96946a2b54c70621230d1fbcd364425c3f19c8f8 Mon Sep 17 00:00:00 2001 From: Mathias Bourgoin Date: Thu, 14 Mar 2024 11:32:03 +0100 Subject: [PATCH 01/10] Proto/AI/Tests: Add logs --- .../test/helpers/scenario_bake.ml | 5 +++- .../test/helpers/scenario_base.ml | 2 ++ .../lib_protocol/test/helpers/scenario_op.ml | 10 ++++--- .../test/helpers/slashing_helpers.ml | 18 +++++++++++ .../test/helpers/tez_staking_helpers.ml | 30 +++++++++++++++++++ 5 files changed, 60 insertions(+), 5 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 8f6561dd2504..babed3510679 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_bake.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_bake.ml @@ -156,10 +156,13 @@ let bake ?baker : t -> t tzresult Lwt.t = let baker_name, {contract = baker_contract; _} = State.find_account_from_pkh baker 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 ~color:time_color - "Baking level %d with %s" + "Baking level %d (cycle %ld) with %s" (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 = diff --git a/src/proto_alpha/lib_protocol/test/helpers/scenario_base.ml b/src/proto_alpha/lib_protocol/test/helpers/scenario_base.ml index 052e61534b8f..e98b0491d3f5 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_base.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_base.ml @@ -40,6 +40,8 @@ let set_baker baker : (t, t) scenarios = (** Exclude a list of delegates from baking *) let exclude_bakers bakers : (t, t) scenarios = + log ~color:event_color "Excluding bakers: [ %s ]" (String.concat ", " bakers) + --> let open Lwt_result_syntax in exec_state (fun (_block, state) -> let bakers_pkh = 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 1bab0cafdbc4..bb810b8a22a3 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_op.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_op.ml @@ -355,9 +355,10 @@ let get_pending_slashed_pct_for_delegate (block, state) delegate = let update_state_denunciation (block, state) {State.culprit; denounced; evidence = _; misbehaviour} = let open Lwt_result_syntax in - if denounced then + if denounced then ( (* If the double signing has already been denounced, a second denunciation should fail *) - return (state, denounced) + Log.info ~color:event_color "Denunciation already included" ; + return (state, denounced)) else let*? block_level = Context.get_level (B block) in let next_level = @@ -368,9 +369,10 @@ let update_state_denunciation (block, state) in let ds_level = Protocol.Raw_level_repr.to_int32 misbehaviour.level in let ds_cycle = cycle_from_level block.constants.blocks_per_cycle ds_level in - if Cycle.(ds_cycle > inclusion_cycle) then + if Cycle.(ds_cycle > inclusion_cycle) then ( (* The denunciation is trying to be included too early *) - return (state, denounced) + Log.info ~color:event_color "Denunciation too early" ; + return (state, denounced)) else if Cycle.( add ds_cycle Protocol.Constants_repr.max_slashing_period diff --git a/src/proto_alpha/lib_protocol/test/helpers/slashing_helpers.ml b/src/proto_alpha/lib_protocol/test/helpers/slashing_helpers.ml index 31b0ee322a49..441d149b0cf3 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/slashing_helpers.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/slashing_helpers.ml @@ -140,6 +140,12 @@ let apply_slashing_account all_denunciations_to_apply in let culprit_name = find_account_name_from_pkh_exn culprit account_map in let rewarded_name = find_account_name_from_pkh_exn rewarded account_map in + Log.info + "Slashing %a for %a" + Signature.Public_key_hash.pp + culprit + Misbehaviour_repr.pp + misbehaviour ; let* slashed_pct = match misbehaviour.kind with | Double_baking -> @@ -156,15 +162,25 @@ let apply_slashing_account all_denunciations_to_apply in let slash_culprit ({frozen_deposits; unstaked_frozen; frozen_rights; _} as acc) = + Log.info + "Slashing %a for %a with frozen deposits: { %a }" + Signature.Public_key_hash.pp + acc.pkh + Misbehaviour_repr.pp + misbehaviour + Frozen_tez.pp + frozen_deposits ; let base_rights = CycleMap.find slashed_cycle frozen_rights |> Option.value ~default:Tez.zero in + Log.info "Base rights: %a" Tez.pp base_rights ; let frozen_deposits, burnt_frozen, rewarded_frozen = Frozen_tez.slash state.constants base_rights slashed_pct frozen_deposits in let slashed_pct_q = Protocol.Percentage.to_q slashed_pct in let slashed_pct = Q.(100 // 1 * slashed_pct_q |> to_int) in + Log.info "Slashed %d%% of frozen deposits@." slashed_pct ; let unstaked_frozen, slashed_unstaked = Unstaked_frozen.slash state.constants @@ -182,6 +198,7 @@ let apply_slashing_account all_denunciations_to_apply fail_account_not_found "apply_slashing" culprit_name) in let slashed_culprit_account, total_slashed = slash_culprit culprit_account in + Log.info "Slashed %a@." Signature.Public_key_hash.pp culprit_account.pkh ; let account_map = update_account ~f:(fun _ -> slashed_culprit_account) @@ -199,6 +216,7 @@ let apply_slashing_account all_denunciations_to_apply let total_burnt_amount = List.map fst total_slashed |> List.fold_left Tez.( +! ) Tez.zero in + Log.info "Total burnt amount: %a" Tez.pp total_burnt_amount ; return (account_map, total_burnt_amount) let apply_slashing_state all_denunciations_to_apply diff --git a/src/proto_alpha/lib_protocol/test/helpers/tez_staking_helpers.ml b/src/proto_alpha/lib_protocol/test/helpers/tez_staking_helpers.ml index 5be0ceb470cf..1620357c230b 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/tez_staking_helpers.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/tez_staking_helpers.ml @@ -56,6 +56,20 @@ module Frozen_tez = struct co_current : Partial_tez.t String.Map.t; } + let pp fmt {delegate; initial; self_current; co_current} = + Format.fprintf + fmt + "Delegate: %s, Initial: %a, Self_current: %a, Co_current: %a" + delegate + Tez.pp + initial + Tez.pp + self_current + (fun fmt -> + String.Map.iter (fun k v -> + Format.fprintf fmt "%s: %a, " k Partial_tez.pp v)) + co_current + let zero = { delegate = ""; @@ -228,6 +242,11 @@ module Frozen_tez = struct ({a with initial = Tez.(a.initial -! amount)}, amount) let slash cst base_amount (pct : Protocol.Percentage.t) a = + Log.info + "Slashing frozen tez for delegate %s with percentage %a" + a.delegate + Q.pp_print + @@ Protocol.Percentage.to_q pct ; let pct_q = Protocol.Percentage.to_q pct in let total_current = total_current a in let slashed_amount = @@ -242,6 +261,17 @@ module Frozen_tez = struct Tez.mul_q slashed_amount Q.(1 // rat) |> Tez.of_q ~round:`Down in let burnt_amount = Tez.(slashed_amount -! rewarded_amount) in + Log.info + "Total current: %a, slashed amount: %a, rewarded amount: %a, burnt \ + amount: %a" + Tez.pp + total_current + Tez.pp + slashed_amount + Tez.pp + rewarded_amount + Tez.pp + burnt_amount ; let a = sub_tez_from_all_current burnt_amount a in let a = sub_tez_from_all_current rewarded_amount a in (a, burnt_amount, rewarded_amount) -- GitLab From 3d6846d71905ea5fc0cfb02cbb34a072baa484c1 Mon Sep 17 00:00:00 2001 From: Mathias Bourgoin Date: Thu, 14 Mar 2024 16:25:33 +0100 Subject: [PATCH 02/10] Proto/AI/Tests: don't slash empty accounts --- .../lib_protocol/test/helpers/tez_staking_helpers.ml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/proto_alpha/lib_protocol/test/helpers/tez_staking_helpers.ml b/src/proto_alpha/lib_protocol/test/helpers/tez_staking_helpers.ml index 1620357c230b..59389f1d0921 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/tez_staking_helpers.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/tez_staking_helpers.ml @@ -272,9 +272,11 @@ module Frozen_tez = struct rewarded_amount Tez.pp burnt_amount ; - let a = sub_tez_from_all_current burnt_amount a in - let a = sub_tez_from_all_current rewarded_amount a in - (a, burnt_amount, rewarded_amount) + if total_current > Tez.zero then + let a = sub_tez_from_all_current burnt_amount a in + let a = sub_tez_from_all_current rewarded_amount a in + (a, burnt_amount, rewarded_amount) + else (a, Tez.zero, Tez.zero) end (** Representation of Unstaked frozen deposits *) -- GitLab From 89d3495f1f2caa7707578788f58a85049da30b7a Mon Sep 17 00:00:00 2001 From: Mathias Bourgoin Date: Thu, 14 Mar 2024 12:10:23 +0100 Subject: [PATCH 03/10] Proto/AI/Tests: Fix frozen rights computing in test framework --- .../test/helpers/scenario_bake.ml | 12 ++----- .../lib_protocol/test/helpers/state.ml | 13 +++++++ .../test/helpers/state_account.ml | 34 +++++++++++++++---- 3 files changed, 43 insertions(+), 16 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 babed3510679..173790c34d63 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_bake.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_bake.ml @@ -16,6 +16,8 @@ let apply_end_cycle current_cycle previous_block block state : State.t tzresult Lwt.t = let open Lwt_result_syntax in Log.debug ~color:time_color "Ending cycle %a" Cycle.pp current_cycle ; + (* Sets initial frozen for future cycle *) + let* state = update_map_es ~f:(update_frozen_rights_cycle block) state in (* Apply all slashes *) let* state = Slashing_helpers.apply_all_slashes_at_cycle_end @@ -23,16 +25,6 @@ let apply_end_cycle current_cycle previous_block block state : previous_block state in - (* Sets initial frozen for future cycle *) - let state = - update_map - ~f: - (update_frozen_rights_cycle - (Cycle.add - current_cycle - (state.constants.consensus_rights_delay + 1))) - state - in (* Apply autostaking *) let*? state = State_ai_flags.Autostake.run_at_cycle_end block state in (* Apply parameter changes *) diff --git a/src/proto_alpha/lib_protocol/test/helpers/state.ml b/src/proto_alpha/lib_protocol/test/helpers/state.ml index 5ecf1bec09a4..ffd2808c1474 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/state.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/state.ml @@ -95,6 +95,19 @@ let update_map ?(log_updates = []) ~(f : account_map -> account_map) (state : t) log_updates ; new_state +let update_map_es ?(log_updates = []) + ~(f : account_map -> account_map tzresult Lwt.t) (state : t) : + t tzresult Lwt.t = + let open Lwt_result_syntax in + let log_updates = List.sort_uniq String.compare log_updates in + let* account_map = f state.account_map in + let new_state = {state with account_map} in + List.iter + (fun x -> + log_debug_balance_update x state.account_map new_state.account_map) + log_updates ; + return new_state + let apply_burn amount src_name (state : t) : t = let f = apply_burn amount src_name in let state = update_map ~log_updates:[src_name] ~f state in diff --git a/src/proto_alpha/lib_protocol/test/helpers/state_account.ml b/src/proto_alpha/lib_protocol/test/helpers/state_account.ml index e831bfb24350..1ac8b96ff4fa 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/state_account.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/state_account.ml @@ -369,10 +369,32 @@ let apply_finalize staker_name account_map = (* Given cycle is the cycle for which the rights are computed, usually current + consensus rights delay *) -let update_frozen_rights_cycle cycle account_map = - String.Map.map - (fun ({frozen_deposits; frozen_rights; _} as acc) -> - let total_frozen = Frozen_tez.total_current frozen_deposits in - let frozen_rights = CycleMap.add cycle total_frozen frozen_rights in - {acc with frozen_rights}) +let update_frozen_rights_cycle block account_map = + let open Lwt_result_syntax in + String.Map.fold_es + (fun key acc acc_map -> + match acc.delegate with + | None -> return acc_map + | Some delegate_name -> + let delegate_pkh = + match String.Map.find delegate_name account_map with + | None -> + fail_account_not_found + "update_frozen_rights_cycle" + delegate_name + | Some delegate -> delegate.pkh + in + if delegate_pkh = acc.pkh then + (* Account is a delegate *) + let cycle = Block.current_cycle block in + let* frozen_deposits = + Context.Delegate.initial_frozen_deposits (B block) acc.pkh + in + + let frozen_rights = + CycleMap.add cycle frozen_deposits acc.frozen_rights + in + return (String.Map.add key {acc with frozen_rights} acc_map) + else return acc_map) + account_map account_map -- GitLab From 70d325c8cfe08880cad06f35fae5f2a959d0b64c Mon Sep 17 00:00:00 2001 From: Mathias Bourgoin Date: Thu, 14 Mar 2024 12:12:07 +0100 Subject: [PATCH 04/10] Proto/AI/Tests: Don't precompute and reject denunciations over 100% --- .../lib_protocol/test/helpers/scenario_op.ml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) 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 bb810b8a22a3..44b9abcfd610 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_op.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_op.ml @@ -377,20 +377,15 @@ let update_state_denunciation (block, state) Cycle.( add ds_cycle Protocol.Constants_repr.max_slashing_period <= inclusion_cycle) - then + then ( (* The denunciation is too late and gets refused. *) - return (state, denounced) - else if get_pending_slashed_pct_for_delegate (block, state) culprit >= 100 - then - (* Culprit has been slashed too much, a denunciation is not added to the list. - TODO: is the double signing treated as included, or can it be included in the - following cycle? *) - return (state, denounced) + Log.info ~color:event_color "Denunciation too late" ; + return (state, denounced)) else (* for simplicity's sake (lol), the block producer and the payload producer are the same We also assume that the current state baking policy will be used for the next block *) let* rewarded, _, _, _ = - Block.get_next_baker ?policy:state.baking_policy block + Block.get_next_baker ?policy:state.State.baking_policy block in let culprit_name, culprit_account = State.find_account_from_pkh culprit state -- GitLab From 3a597c44a49761cb73d20f612b6a02ea29903acc Mon Sep 17 00:00:00 2001 From: Mathias Bourgoin Date: Thu, 14 Mar 2024 12:12:37 +0100 Subject: [PATCH 05/10] Proto/AI/Tests: Order denunciations per level+round before slashing --- .../lib_protocol/test/helpers/slashing_helpers.ml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/proto_alpha/lib_protocol/test/helpers/slashing_helpers.ml b/src/proto_alpha/lib_protocol/test/helpers/slashing_helpers.ml index 441d149b0cf3..0d665fcedeb9 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/slashing_helpers.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/slashing_helpers.ml @@ -247,6 +247,14 @@ let apply_all_slashes_at_cycle_end current_cycle (block_before_slash : Block.t) let to_slash_later, to_slash_now = State_ai_flags.Delayed_slashing.partition_slashes state current_cycle in + (* Sort to_slash_now by level+round *) + let to_slash_now = + List.sort + (fun (_, item1) (_, item2) -> + Denunciations_repr.compare_item_except_hash item1 item2) + to_slash_now + in + let* state, total_burnt = List.fold_left_es (fun (acc_state, acc_total) x -> -- GitLab From eb1b02c25bb3f64a5aca7c8113d07a96f8a7787b Mon Sep 17 00:00:00 2001 From: Mathias Bourgoin Date: Thu, 14 Mar 2024 12:27:54 +0100 Subject: [PATCH 06/10] Proto/AI/Tests: create test_scenario_slashing_stakers.ml --- .../test/integration/test_scenario_slashing_stakers.ml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing_stakers.ml diff --git a/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing_stakers.ml b/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing_stakers.ml new file mode 100644 index 000000000000..0a966492a536 --- /dev/null +++ b/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing_stakers.ml @@ -0,0 +1,6 @@ +(*****************************************************************************) +(* *) +(* SPDX-License-Identifier: MIT *) +(* Copyright (c) 2024 Nomadic Labs, *) +(* *) +(*****************************************************************************) -- GitLab From 1fa5a1856910f21249b4a143484f2d717fac54b7 Mon Sep 17 00:00:00 2001 From: Mathias Bourgoin Date: Thu, 14 Mar 2024 16:00:57 +0100 Subject: [PATCH 07/10] Proto/AI/Tests: Move simple_slash into slashing_stakers tests --- .../integration/test_scenario_slashing.ml | 78 +--------------- .../test_scenario_slashing_stakers.ml | 90 +++++++++++++++++++ 2 files changed, 91 insertions(+), 77 deletions(-) diff --git a/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing.ml b/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing.ml index cfe84fc5f941..a86eb6e1d681 100644 --- a/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing.ml +++ b/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing.ml @@ -39,82 +39,6 @@ open Scenario_constants let fs = Format.asprintf -let test_simple_slash = - let any_slash delegate = - Tag "double baking" --> double_bake delegate - |+ Tag "double attesting" - --> double_attest ~other_bakers:("bootstrap2", "bootstrap3") delegate - |+ Tag "double preattesting" - --> double_preattest ~other_bakers:("bootstrap2", "bootstrap3") delegate - in - init_constants () - --> set S.Adaptive_issuance.autostaking_enable false - --> activate_ai `Zero_threshold - --> branch_flag S.Adaptive_issuance.ns_enable - --> begin_test ["delegate"; "bootstrap1"; "bootstrap2"; "bootstrap3"] - --> (Tag "No AI" --> next_cycle - |+ Tag "Yes AI" --> next_block --> wait_ai_activation) - --> any_slash "delegate" - --> snapshot_balances "before slash" ["delegate"] - --> ((Tag "denounce same cycle" - --> make_denunciations () - (* delegate can be forbidden in this case, so we set another baker *) - --> exclude_bakers ["delegate"] - |+ Tag "denounce next cycle" --> next_cycle --> make_denunciations () - (* delegate can be forbidden in this case, so we set another baker *) - --> exclude_bakers ["delegate"]) - --> (Empty - |+ Tag "another slash" --> any_slash "bootstrap1" - --> make_denunciations () - (* bootstrap1 can be forbidden in this case, so we set another baker *) - --> exclude_bakers ["delegate"; "bootstrap1"]) - --> check_snapshot_balances "before slash" - --> exec_unit (check_pending_slashings ~loc:__LOC__) - --> next_cycle - --> assert_failure - (exec_unit (fun (_block, state) -> - if State_ai_flags.Delayed_slashing.enabled state then - failwith "ns_enable = true: slash not applied yet" - else Lwt_result_syntax.return_unit) - --> check_snapshot_balances "before slash") - --> exec_unit (check_pending_slashings ~loc:__LOC__) - --> next_cycle - |+ Tag "denounce too late" --> next_cycle --> next_cycle - --> assert_failure - ~expected_error:(fun (_block, state) -> - let ds = state.State.double_signings in - let ds = match ds with [a] -> a | _ -> assert false in - let level = - Protocol.Alpha_context.Raw_level.Internal_for_tests.from_repr - ds.misbehaviour.level - in - let last_cycle = - Cycle.add - (Block.current_cycle_of_level - ~blocks_per_cycle:state.State.constants.blocks_per_cycle - ~current_level: - (Protocol.Raw_level_repr.to_int32 - ds.misbehaviour.level)) - (Protocol.Constants_repr.max_slashing_period - 1) - in - let (kind : Protocol.Alpha_context.Misbehaviour.kind) = - (* This conversion would not be needed if - Misbehaviour_repr.kind were moved to a - separate file that doesn't have under/over - Alpha_context versions. *) - match ds.misbehaviour.kind with - | Double_baking -> Double_baking - | Double_attesting -> Double_attesting - | Double_preattesting -> Double_preattesting - in - [ - Environment.Ecoproto_error - (Protocol.Validate_errors.Anonymous.Outdated_denunciation - {kind; level; last_cycle}); - ]) - (make_denunciations ()) - --> check_snapshot_balances "before slash") - let check_is_forbidden baker = assert_failure (next_block_with_baker baker) let check_is_not_forbidden baker = @@ -403,7 +327,7 @@ let test_slash_rounding = let tests = tests_of_scenarios @@ [ - ("Test simple slashing", test_simple_slash); + ("Test multiple misbehaviors", test_multiple_misbehaviors); ("Test slashed is forbidden", test_delegate_forbidden); ("Test slash with unstake", test_slash_unstake); (* TODO: make sure this test passes with blocks_per_cycle:8l diff --git a/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing_stakers.ml b/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing_stakers.ml index 0a966492a536..d561ae8331bf 100644 --- a/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing_stakers.ml +++ b/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing_stakers.ml @@ -4,3 +4,93 @@ (* Copyright (c) 2024 Nomadic Labs, *) (* *) (*****************************************************************************) + +(** Testing + ------- + Component: Adaptive Issuance, Slashing + Invocation: dune exec src/proto_alpha/lib_protocol/test/integration/main.exe \ + -- --file test_scenario_slashing_stakers.ml + Subject: Test slashing scenarios in the protocol. +*) + +open Adaptive_issuance_helpers +open State_account +open Tez_helpers.Ez_tez +open Scenario +open Scenario_constants + +let test_simple_slash = + let any_slash delegate = + Tag "double baking" --> double_bake delegate + |+ Tag "double attesting" + --> double_attest ~other_bakers:("bootstrap2", "bootstrap3") delegate + |+ Tag "double preattesting" + --> double_preattest ~other_bakers:("bootstrap2", "bootstrap3") delegate + in + init_constants () + --> set S.Adaptive_issuance.autostaking_enable false + --> activate_ai `Zero_threshold + --> branch_flag S.Adaptive_issuance.ns_enable + --> begin_test ["delegate"; "bootstrap1"; "bootstrap2"; "bootstrap3"] + --> (Tag "No AI" --> next_cycle + |+ Tag "Yes AI" --> next_block --> wait_ai_activation) + --> any_slash "delegate" + --> snapshot_balances "before slash" ["delegate"] + --> ((Tag "denounce same cycle" + --> make_denunciations () + (* delegate can be forbidden in this case, so we set another baker *) + --> exclude_bakers ["delegate"] + |+ Tag "denounce next cycle" --> next_cycle --> make_denunciations () + (* delegate can be forbidden in this case, so we set another baker *) + --> exclude_bakers ["delegate"]) + --> (Empty + |+ Tag "another slash" --> any_slash "bootstrap1" + --> make_denunciations () + (* bootstrap1 can be forbidden in this case, so we set another baker *) + --> exclude_bakers ["delegate"; "bootstrap1"]) + --> check_snapshot_balances "before slash" + --> exec_unit (check_pending_slashings ~loc:__LOC__) + --> next_cycle + --> assert_failure + (exec_unit (fun (_block, state) -> + if State_ai_flags.Delayed_slashing.enabled state then + failwith "ns_enable = true: slash not applied yet" + else Lwt_result_syntax.return_unit) + --> check_snapshot_balances "before slash") + --> exec_unit (check_pending_slashings ~loc:__LOC__) + --> next_cycle + |+ Tag "denounce too late" --> next_cycle --> next_cycle + --> assert_failure + ~expected_error:(fun (_block, state) -> + let ds = state.State.double_signings in + let ds = match ds with [a] -> a | _ -> assert false in + let level = + Protocol.Alpha_context.Raw_level.Internal_for_tests.from_repr + ds.misbehaviour.level + in + let last_cycle = + Cycle.add + (Block.current_cycle_of_level + ~blocks_per_cycle:state.State.constants.blocks_per_cycle + ~current_level: + (Protocol.Raw_level_repr.to_int32 + ds.misbehaviour.level)) + (Protocol.Constants_repr.max_slashing_period - 1) + in + let (kind : Protocol.Alpha_context.Misbehaviour.kind) = + (* This conversion would not be needed if + Misbehaviour_repr.kind were moved to a + separate file that doesn't have under/over + Alpha_context versions. *) + match ds.misbehaviour.kind with + | Double_baking -> Double_baking + | Double_attesting -> Double_attesting + | Double_preattesting -> Double_preattesting + in + [ + Environment.Ecoproto_error + (Protocol.Validate_errors.Anonymous.Outdated_denunciation + {kind; level; last_cycle}); + ]) + (make_denunciations ()) + --> check_snapshot_balances "before slash") -- GitLab From ce642ea5c63d6358422003977658205e22cbab58 Mon Sep 17 00:00:00 2001 From: Mathias Bourgoin Date: Thu, 14 Mar 2024 16:14:37 +0100 Subject: [PATCH 08/10] Proto/AI/Tests: add stakers into simple_slashing tests --- manifest/product_octez.ml | 1 + .../lib_protocol/test/integration/dune | 1 + .../test_scenario_slashing_stakers.ml | 141 +++++++++++++++--- 3 files changed, 121 insertions(+), 22 deletions(-) diff --git a/manifest/product_octez.ml b/manifest/product_octez.ml index 548d114a8ed2..b3f163c834db 100644 --- a/manifest/product_octez.ml +++ b/manifest/product_octez.ml @@ -5033,6 +5033,7 @@ end = struct ("test_scenario_rewards", N.(number >= 020)); ("test_scenario_autostaking", N.(number >= 020)); ("test_scenario_slashing", N.(number >= 020)); + ("test_scenario_slashing_stakers", 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 3d165f9443f3..0eff50c01d45 100644 --- a/src/proto_alpha/lib_protocol/test/integration/dune +++ b/src/proto_alpha/lib_protocol/test/integration/dune @@ -36,6 +36,7 @@ test_scenario_rewards test_scenario_autostaking test_scenario_slashing + test_scenario_slashing_stakers test_liquidity_baking test_storage_functions test_storage diff --git a/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing_stakers.ml b/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing_stakers.ml index d561ae8331bf..c30ed26eec26 100644 --- a/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing_stakers.ml +++ b/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing_stakers.ml @@ -7,10 +7,10 @@ (** Testing ------- - Component: Adaptive Issuance, Slashing + Component: Adaptive Issuance, Slashing with Stakers Invocation: dune exec src/proto_alpha/lib_protocol/test/integration/main.exe \ -- --file test_scenario_slashing_stakers.ml - Subject: Test slashing scenarios in the protocol. + Subject: Test slashing scenarios in the protocol with stakers. *) open Adaptive_issuance_helpers @@ -19,35 +19,124 @@ open Tez_helpers.Ez_tez open Scenario open Scenario_constants +let fs = Format.asprintf + +let slashed_staker_1 = "staker1" + +let slashed_staker_2 = "staker2" + +let never_slashed_staker = "staker3" + +let first_slashed_delegate = "delegate" + +let second_slashed_delegate = "bootstrap1" + +let never_slashed_delegate1 = "bootstrap2" + +let never_slashed_delegate2 = "bootstrap3" + +(** Setup starting state for test with + - 4 delegates + - potentialy three stakers (2 delegating to first_slashed_delegate and 1 to + never_slashed_delegate1 respectively) + - AI enabled + - ns_enable enabled/disabled + - alternative parameters for first_slashed_delegate and + never_slashed_delegate1 +*) +let init_with_stakers () = + let init_params l e = + {limit_of_staking_over_baking = l; edge_of_baking_over_staking = e} + in + (* Same edge for both delegates to avoid test branches explosion *) + let set_delegate_params dlgt1 dlgt2 = + (Tag "edge 1" + --> set_delegate_params dlgt1 (init_params Q.one Q.one) + --> set_delegate_params dlgt2 (init_params Q.one Q.one) + |+ Tag "edge 1/3" + --> set_delegate_params dlgt1 (init_params Q.one Q.(1 // 3)) + --> set_delegate_params dlgt2 (init_params Q.one Q.(1 // 3))) + --> wait_n_cycles 4 + in + let add_staker name delegate amount staked_amount = + add_account_with_funds name ~funder:delegate (Amount (Tez.of_mutez amount)) + --> set_delegate name (Some delegate) + --> stake name staked_amount + in + let add_stakers = + Tag "with stakers" + --> add_staker + slashed_staker_1 + first_slashed_delegate + 1_000_000_000_000L + Half + --> add_staker slashed_staker_2 first_slashed_delegate 3_333_333L Half + --> add_staker + never_slashed_staker + never_slashed_delegate1 + 1_000_000_000L + Half + |+ Empty + in + init_constants () + --> set S.Adaptive_issuance.autostaking_enable false + --> activate_ai `Force + --> branch_flag S.Adaptive_issuance.ns_enable + --> begin_test + [ + first_slashed_delegate; + second_slashed_delegate; + never_slashed_delegate1; + never_slashed_delegate2; + ] + --> set_delegate_params first_slashed_delegate never_slashed_delegate1 + --> add_stakers + +(** Starts with four delegates, misbhehaves with one, denounce it, and + potentially do it again with another delegate. + Alternative scenarios include: + - all three misbehaviors + - delegates with/without stakers to observe sharing of slashing and rewards, + - denunciations be made in the same cycle, in the next or too late + - several staking parameters + *) let test_simple_slash = + let open Lwt_result_syntax in let any_slash delegate = Tag "double baking" --> double_bake delegate |+ Tag "double attesting" - --> double_attest ~other_bakers:("bootstrap2", "bootstrap3") delegate + --> double_attest + ~other_bakers:(never_slashed_delegate1, never_slashed_delegate2) + delegate |+ Tag "double preattesting" - --> double_preattest ~other_bakers:("bootstrap2", "bootstrap3") delegate + --> double_preattest + ~other_bakers:(never_slashed_delegate1, never_slashed_delegate2) + delegate in - init_constants () - --> set S.Adaptive_issuance.autostaking_enable false - --> activate_ai `Zero_threshold - --> branch_flag S.Adaptive_issuance.ns_enable - --> begin_test ["delegate"; "bootstrap1"; "bootstrap2"; "bootstrap3"] - --> (Tag "No AI" --> next_cycle - |+ Tag "Yes AI" --> next_block --> wait_ai_activation) - --> any_slash "delegate" - --> snapshot_balances "before slash" ["delegate"] - --> ((Tag "denounce same cycle" - --> make_denunciations () - (* delegate can be forbidden in this case, so we set another baker *) - --> exclude_bakers ["delegate"] + init_with_stakers () + --> any_slash first_slashed_delegate + --> log "make denunciations" + --> snapshot_balances "before slash" [first_slashed_delegate] + --> ((Tag "denounce same cycle" --> make_denunciations () + (* delegate can be forbidden in this case, so we exclude it from list of potential bakers *) + --> exclude_bakers [first_slashed_delegate] |+ Tag "denounce next cycle" --> next_cycle --> make_denunciations () (* delegate can be forbidden in this case, so we set another baker *) - --> exclude_bakers ["delegate"]) + --> exclude_bakers [first_slashed_delegate]) + --> (Tag "denouncer with maybe staker" + --> set_baker never_slashed_delegate1 + (* ensure denunciation is included by never_slashed_delegate1 *) + |+ Tag "denouncer without staker" + --> set_baker never_slashed_delegate2) + --> next_block + --> (* only exclude "delegate" *) exclude_bakers [first_slashed_delegate] --> (Empty - |+ Tag "another slash" --> any_slash "bootstrap1" + |+ Tag "another slash" + --> any_slash second_slashed_delegate --> make_denunciations () - (* bootstrap1 can be forbidden in this case, so we set another baker *) - --> exclude_bakers ["delegate"; "bootstrap1"]) + (* bootstrap1 can be forbidden in this case, so we exclude it from list of potential bakers *) + --> exclude_bakers + [first_slashed_delegate; second_slashed_delegate]) --> check_snapshot_balances "before slash" --> exec_unit (check_pending_slashings ~loc:__LOC__) --> next_cycle @@ -55,7 +144,7 @@ let test_simple_slash = (exec_unit (fun (_block, state) -> if State_ai_flags.Delayed_slashing.enabled state then failwith "ns_enable = true: slash not applied yet" - else Lwt_result_syntax.return_unit) + else return_unit) --> check_snapshot_balances "before slash") --> exec_unit (check_pending_slashings ~loc:__LOC__) --> next_cycle @@ -94,3 +183,11 @@ let test_simple_slash = ]) (make_denunciations ()) --> check_snapshot_balances "before slash") + +let tests = tests_of_scenarios @@ [("Test simple slashing", test_simple_slash)] + +let () = + register_tests + ~__FILE__ + ~tags:["protocol"; "scenario"; "slashing"; "stakers"] + tests -- GitLab From 1d5dc689e7585cf44a5bf49e91c22517861fcf36 Mon Sep 17 00:00:00 2001 From: Mathias Bourgoin Date: Thu, 14 Mar 2024 15:55:45 +0100 Subject: [PATCH 09/10] Proto/AI/Tests: add test with many misbehaviors --- .../lib_protocol/test/helpers/scenario_op.ml | 187 +++++++++++++----- .../integration/test_scenario_slashing.ml | 79 ++++++++ 2 files changed, 216 insertions(+), 50 deletions(-) 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 44b9abcfd610..e93f0999700c 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_op.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_op.ml @@ -222,51 +222,99 @@ let op_double_baking ?(correct_order = true) bh1 bh2 ctxt = let bh1, bh2 = order_block_hashes ~correct_order bh1 bh2 in Op.double_baking ctxt bh1 bh2 -let double_bake_ delegate_name (block, state) = +(** [double_bake_op delegate_names (block, state)] performs a double baking with + the given delegate names. The first delegate in the list bakes the new main + branch. All delegates (including the first) will bake two other blocks at + the same level/different round. *) +let double_bake_op delegate_names (block, state) = let open Lwt_result_syntax in - Log.info ~color:event_color "Double baking with %s" delegate_name ; - let delegate = State.find_account delegate_name state in - let* operation = - Adaptive_issuance_helpers.unstake (B block) delegate.contract Tez.one_mutez - in - let* forked_block = - Block.bake ~policy:(By_account delegate.pkh) ~operation block - in - (* includes pending operations *) - let* main_branch, state = bake ~baker:delegate_name (block, state) in - let evidence = op_double_baking main_branch.header forked_block.header in - let*? misbehaviour = - Slashing_helpers.Misbehaviour_repr.from_duplicate_block main_branch + Log.info + ~color:event_color + "Double baking with (%s)" + (String.concat ", " delegate_names) ; + let delegates = + List.map + (fun delegate_name -> State.find_account delegate_name state) + delegate_names in - let dss = - {State.culprit = delegate.pkh; denounced = false; evidence; misbehaviour} + let* main_branch, state = + bake + ~baker:(WithExceptions.Option.get ~loc:__LOC__ @@ List.hd delegate_names) + (block, state) in - let state = - {state with double_signings = dss :: state.State.double_signings} + let* state = + List.fold_left_es + (fun state delegate -> + let* operation = + Adaptive_issuance_helpers.unstake + (B block) + delegate.contract + Tez.one_mutez + in + let* forked_block1 = + Block.bake ~policy:(By_account delegate.pkh) block + in + let* forked_block2 = + Block.bake ~policy:(By_account delegate.pkh) ~operation block + in + (* includes pending operations *) + let evidence = + op_double_baking forked_block1.header forked_block2.header + in + let*? misbehaviour = + Slashing_helpers.Misbehaviour_repr.from_duplicate_block forked_block1 + in + let dss = + { + State.culprit = delegate.pkh; + denounced = false; + evidence; + misbehaviour; + } + in + return + { + state with + State.double_signings = dss :: state.State.double_signings; + }) + state + delegates in return (main_branch, state) (* Note: advances one block *) let double_bake delegate_name : (t, t) scenarios = - exec (double_bake_ delegate_name) + exec (double_bake_op [delegate_name]) + +let double_bake_many delegate_names : (t, t) scenarios = + exec (double_bake_op delegate_names) -(* [other_bakers] can be used to force using specific bakers to avoid - reusing forbidden ones *) -let double_attest_op ?other_bakers ~op ~op_evidence ~kind delegate_name +(** [double_attest_op ?other_bakers ~op ~op_evidence ~kind delegate_names + (block, state)] performs a double (pre)attestation with the given delegate + names. Starting at block level `n`, it creates two 2-block branches and all + delegates will (pre)attest the two blocks at level `n+2`. [other_bakers] can + be used to force using specific bakers to avoid reusing forbidden ones *) +let double_attest_op ?other_bakers ~op ~op_evidence ~kind delegate_names (block, state) = let open Lwt_result_syntax in Log.info ~color:event_color - "Double %s with %s" + "Double %s with %a" (match kind with | Protocol.Misbehaviour_repr.Double_preattesting -> "preattesting" | Double_attesting -> "attesting" | Double_baking -> assert false) - delegate_name ; - let delegate = State.find_account delegate_name state in + (Format.pp_print_list ~pp_sep:Format.pp_print_space Format.pp_print_string) + delegate_names ; + let delegates = + List.map + (fun delegate_name -> State.find_account delegate_name state) + delegate_names + in let* baker, _, _, _ = Block.get_next_baker ?policy:state.baking_policy block in + Log.info "Baker: %a" Signature.Public_key_hash.pp baker ; let* other_baker1, other_baker2 = match other_bakers with | Some (ob1, ob2) -> @@ -280,28 +328,36 @@ let double_attest_op ?other_bakers ~op ~op_evidence ~kind delegate_name other_baker2 else other_baker1 in + Log.info "Other baker: %a" Signature.Public_key_hash.pp other_baker ; + Log.info "Bake 1 block with %a" Signature.Public_key_hash.pp baker ; let* forked_block = Block.bake ~policy:(By_account other_baker) block in + Log.info "Bake 1 block " ; let* forked_block = Block.bake ?policy:state.baking_policy forked_block in + Log.info "Baked two blocks" ; (* includes pending operations *) let* block, state = bake (block, state) in let* main_branch, state = bake (block, state) in - let* attestation_a = op ~delegate:delegate.pkh forked_block in - let* attestation_b = op ~delegate:delegate.pkh main_branch in - let evidence = op_evidence attestation_a attestation_b in - let dss = - { - State.culprit = delegate.pkh; - denounced = false; - evidence; - misbehaviour = - Slashing_helpers.Misbehaviour_repr.from_duplicate_operation - attestation_a; - } - in - let state = - {state with double_signings = dss :: state.State.double_signings} - in - return (main_branch, state) + List.fold_left_es + (fun (main_branch, state) delegate -> + let* attestation_a = op ~delegate:delegate.pkh forked_block in + let* attestation_b = op ~delegate:delegate.pkh main_branch in + let evidence = op_evidence attestation_a attestation_b in + let dss = + { + State.culprit = delegate.pkh; + denounced = false; + evidence; + misbehaviour = + Slashing_helpers.Misbehaviour_repr.from_duplicate_operation + attestation_a; + } + in + let state : State.t = + {state with double_signings = dss :: state.State.double_signings} + in + return (main_branch, state)) + (main_branch, state) + delegates let double_attest_ = double_attest_op @@ -310,8 +366,11 @@ let double_attest_ = ~kind:Double_attesting (* Note: advances two blocks *) +let double_attest_many ?other_bakers delegate_names : (t, t) scenarios = + exec (double_attest_ ?other_bakers delegate_names) + let double_attest ?other_bakers delegate_name : (t, t) scenarios = - exec (double_attest_ ?other_bakers delegate_name) + double_attest_many ?other_bakers [delegate_name] let double_preattest_ = double_attest_op @@ -320,8 +379,11 @@ let double_preattest_ = ~kind:Double_preattesting (* Note: advances two blocks *) +let double_preattest_many ?other_bakers delegate_names : (t, t) scenarios = + exec (double_preattest_ ?other_bakers delegate_names) + let double_preattest ?other_bakers delegate_name : (t, t) scenarios = - exec (double_preattest_ ?other_bakers delegate_name) + double_preattest_many ?other_bakers [delegate_name] let cycle_from_level blocks_per_cycle level = let current_cycle = Int32.div level blocks_per_cycle in @@ -423,8 +485,12 @@ let update_state_denunciation (block, state) in return (state, true) -let make_denunciations_ ?(filter = fun {State.denounced; _} -> not denounced) - (block, state) = +(** [make_denunciations_op ?single ?rev ?filter ()] denounces all double signers + in the state. If [single] is set, only one denunciation is made. If [rev] is + set, the denunciations are made in reverse order. If [filter] is set, only the + double signers for which the filter returns true are denounced. *) +let make_denunciations_op ?(single = false) ?(rev = false) + ?(filter = fun {State.denounced; _} -> not denounced) (block, state) = let open Lwt_result_syntax in let* () = check_pending_slashings ~loc:__LOC__ (block, state) in let make_op state ({State.evidence; _} as dss) = @@ -436,22 +502,43 @@ let make_denunciations_ ?(filter = fun {State.denounced; _} -> not denounced) let rec make_op_list dss_list state r_op r_dss = match dss_list with | d :: t -> ( + let open State in let* new_op = make_op state d in match new_op with | None -> make_op_list t state r_op (d :: r_dss) | Some (op, p_dss, new_state) -> - make_op_list t new_state (op :: r_op) (p_dss :: r_dss)) - | [] -> return @@ (state, List.rev r_op, List.rev r_dss) + Log.info + ~color:event_color + "Denouncing %a for %s at level %a round %a" + Signature.Public_key_hash.pp + d.culprit + (match d.misbehaviour.kind with + | Double_baking -> "double baking" + | Double_attesting -> "double attesting" + | Double_preattesting -> "double preattesting") + Protocol.Raw_level_repr.pp + d.misbehaviour.level + Protocol.Round_repr.pp + d.misbehaviour.round ; + if single then + return @@ (new_state, op :: r_op, List.rev @@ (p_dss :: t)) + else make_op_list t new_state (op :: r_op) (p_dss :: r_dss)) + | [] -> return @@ (state, r_op, r_dss) in let* state, operations, double_signings = - make_op_list state.double_signings state [] [] + make_op_list + (if rev then state.double_signings else List.rev state.double_signings) + state + [] + [] in let state = {state with double_signings} in return (state, operations) (* Important note: do not change the baking policy behaviour once denunciations are made, until the operations are included in a block (by default the next block) *) -let make_denunciations ?filter () = exec_op (make_denunciations_ ?filter) +let make_denunciations ?single ?rev ?filter () = + exec_op (make_denunciations_op ?single ?rev ?filter) (** Create an account and give an initial balance funded by [funder] *) let add_account_with_funds name ~funder amount = diff --git a/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing.ml b/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing.ml index a86eb6e1d681..9fc4070ec711 100644 --- a/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing.ml +++ b/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing.ml @@ -39,6 +39,85 @@ open Scenario_constants let fs = Format.asprintf +(** Test multiple misbehaviors + - Test a single delegate misbehaving multiple times + - Test multiple delegates misbehaving + - Test multiple delegates misbehaving multiple times + - Test denunciation at once or in a staggered way + - Test denunciation in chronological order or reverse order + - Spread misbehaviors/denunciations over multiple cycles + *) +let test_multiple_misbehaviors = + (* Denounce all misbehaviours or 14 one by one in chronological or reverse + order *) + let make_denunciations () = + Tag "denounce chronologically" + --> log "denounce chronologically" + --> (Tag "all at once" --> make_denunciations ~rev:false () + |+ Tag "one by one" + --> loop + 12 + (make_denunciations + ~filter:(fun {denounced; _} -> not denounced) + ~single:true + ~rev:false + ())) + |+ Tag "denounce reverse" --> log "denounce reverse" + --> (Tag "all at once" --> make_denunciations ~rev:true () + |+ Tag "one by one" + --> loop + 12 + (make_denunciations + ~filter:(fun {denounced; _} -> not denounced) + ~single:true + ~rev:true + ())) + in + (* Misbehaviors scenarios *) + let misbehave i delegate1 delegate2 = + (Tag "single delegate" + (* A single delegate misbehaves several times before being denunced *) + --> loop + i + (double_attest delegate1 --> double_preattest delegate1 + --> double_bake delegate1 --> double_attest delegate1 + --> double_preattest delegate1) + --> exclude_bakers [delegate1] + |+ Tag "multiple delegates" + (* Two delegates double bake sequentially *) + --> loop + i + (loop + 3 + (double_bake delegate1 --> double_bake delegate2 --> next_block)) + --> exclude_bakers [delegate1; delegate2] + |+ Tag "double misbehaviors" + (* Two delegates misbehave in parallel for multiple levels *) + --> loop + i + (double_attest_many [delegate1; delegate2] + --> double_attest_many [delegate1; delegate2] + --> double_preattest_many [delegate1; delegate2] + --> double_bake_many [delegate1; delegate2]) + --> exclude_bakers [delegate1; delegate2]) + --> make_denunciations () + in + init_constants ~blocks_per_cycle:24l ~reward_per_block:0L () + --> set S.Adaptive_issuance.autostaking_enable false + --> (Tag "No AI" --> activate_ai `No |+ Tag "Yes AI" --> activate_ai `Force) + --> branch_flag S.Adaptive_issuance.ns_enable + --> begin_test ["delegate"; "bootstrap1"; "bootstrap2"; "bootstrap3"] + --> next_cycle + --> (* various make misbehaviors spread over 1 or two cycles *) + List.fold_left + (fun acc i -> + acc + |+ Tag (string_of_int i ^ " misbehavior loops") + --> misbehave i "delegate" "bootstrap1" + --> next_cycle) + Empty + [1; 3] + let check_is_forbidden baker = assert_failure (next_block_with_baker baker) let check_is_not_forbidden baker = -- GitLab From fcff02ca688ac1cd0c09799f3a99354effc39c67 Mon Sep 17 00:00:00 2001 From: Mathias Bourgoin Date: Thu, 14 Mar 2024 16:03:16 +0100 Subject: [PATCH 10/10] Proto/AI/Tests: Reactivate existing tests --- .../integration/test_scenario_slashing.ml | 138 ++++++++---------- 1 file changed, 59 insertions(+), 79 deletions(-) diff --git a/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing.ml b/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing.ml index 9fc4070ec711..dff4e0cd3c2b 100644 --- a/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing.ml +++ b/src/proto_alpha/lib_protocol/test/integration/test_scenario_slashing.ml @@ -124,38 +124,52 @@ let check_is_not_forbidden baker = let open Lwt_result_syntax in exec (fun ((block, state) as input) -> let baker = State.find_account baker state in - let*! _ = Block.bake ~policy:(By_account baker.pkh) block in + let* _ = Block.bake ~policy:(By_account baker.pkh) block in return input) +(** Tests forbidding delegates ensuring: + - delegates are not forbidden until a denunciation is made (allowing for + multiple misbehaviours) + - a single misbehaviour is enough to be denunced and forbidden + - delegates are unforbidden after a certain amount of time + - delegates are not forbidden if denounced for an outdated misbehaviour +*) let test_delegate_forbidden = - init_constants ~blocks_per_cycle:30l () + let crd (_, state) = state.State.constants.consensus_rights_delay in + init_constants ~blocks_per_cycle:32l () --> set S.Adaptive_issuance.autostaking_enable false --> activate_ai `No --> branch_flag S.Adaptive_issuance.ns_enable --> begin_test ["delegate"; "bootstrap1"; "bootstrap2"] --> set_baker "bootstrap1" - --> (Tag "Many double bakes" - --> loop_action 14 (double_bake_ "delegate") - --> (Tag "14 double bakes are not enough to forbid a delegate" - (* 7*14 = 98 *) - --> make_denunciations () - --> check_is_not_forbidden "delegate" - |+ Tag "15 double bakes is one too many" - (* 7*15 = 105 > 100 *) - --> double_bake "delegate" + --> (Tag "Is not forbidden until first denunciation" + --> loop 14 (double_bake "delegate") + --> exclude_bakers ["delegate"] + --> (* ensure delegate is not forbidden until the denunciations are done *) + check_is_not_forbidden "delegate" + --> make_denunciations () + --> (* delegate is forbidden directly after the first denunciation *) + check_is_forbidden "delegate" + |+ Tag "Is forbidden after single misbehavior" + --> double_attest "delegate" + --> (Tag "very early first denounce" + --> exclude_bakers ["delegate"] --> make_denunciations () - --> check_is_forbidden "delegate") - |+ Tag "Is forbidden after first denunciation" + |+ Tag "in next cycle" --> next_cycle + --> exclude_bakers ["delegate"] + --> make_denunciations ()) + --> check_is_forbidden "delegate" + |+ Tag "Is unforbidden after CONSENSUS_RIGHTS_DELAY after slash cycles" --> double_attest "delegate" - --> (Tag "very early first denounce" --> make_denunciations () - --> (Tag "in same cycle" --> Empty - |+ Tag "next cycle" --> next_cycle) - --> check_is_forbidden "delegate") - |+ Tag "Is unforbidden after 7 cycles" --> double_attest "delegate" - --> make_denunciations () --> exclude_bakers ["delegate"] + --> make_denunciations () --> check_is_forbidden "delegate" - --> stake "delegate" Half + --> next_cycle (* slash occured *) --> stake "delegate" Half + --> wait_n_cycles_f crd + --> check_is_not_forbidden "delegate" + |+ Tag "Is not forbidden after a denunciation is outdated" + --> double_attest "delegate" --> wait_n_cycles 2 + --> assert_failure (make_denunciations ()) --> check_is_not_forbidden "delegate" |+ Tag "Two double attestations, in consecutive cycles, denounce out of \ @@ -212,13 +226,16 @@ let test_slash_monotonous_stake = --> (Tag "Double Bake" --> scenario ~offending_op:double_bake ~op:stake ~early_d:true |+ Tag "Double attest" - --> scenario ~offending_op:double_attest ~op:stake ~early_d:true) + --> scenario + ~offending_op:(fun s -> double_attest s) + ~op:stake + ~early_d:true) |+ Tag "denounce late" --> (Tag "Double Bake" --> scenario ~offending_op:double_bake ~op:stake ~early_d:false |+ Tag "Double attest" --> scenario - ~offending_op:double_attest + ~offending_op:(fun s -> double_attest s) ~op:stake ~early_d:false) --> make_denunciations ()) @@ -226,13 +243,18 @@ let test_slash_monotonous_stake = --> (Tag "Double Bake" --> scenario ~offending_op:double_bake ~op:unstake ~early_d:true |+ Tag "Double attest" - --> scenario ~offending_op:double_attest ~op:unstake ~early_d:true) + --> scenario + ~offending_op:(fun s -> double_attest s) + ~op:unstake + ~early_d:true) |+ Tag "denounce late" --> (Tag "Double Bake" --> scenario ~offending_op:double_bake ~op:unstake ~early_d:false |+ Tag "Double attest" - --> scenario ~offending_op:double_attest ~op:unstake ~early_d:false - ) + --> scenario + ~offending_op:(fun s -> double_attest s) + ~op:unstake + ~early_d:false) --> make_denunciations () let test_slash_timing = @@ -240,18 +262,23 @@ let test_slash_timing = --> set S.Adaptive_issuance.autostaking_enable false --> activate_ai `No --> branch_flag S.Adaptive_issuance.ns_enable - --> begin_test ["delegate"] --> next_cycle + --> begin_test ["delegate"; "bootstrap1"] + --> next_cycle --> (Tag "stake" --> stake "delegate" Half |+ Tag "unstake" --> unstake "delegate" Half) --> (Tag "with a first slash" --> double_bake "delegate" + --> exclude_bakers ["delegate"] --> make_denunciations () |+ Tag "without another slash" --> Empty) + --> stake "delegate" Half --> List.fold_left (fun acc i -> acc |+ Tag (string_of_int i ^ " cycles lag") --> wait_n_cycles i) - Empty + (wait_n_cycles 2) [3; 4; 5; 6] - --> double_bake "delegate" --> make_denunciations () --> next_cycle + --> double_bake "delegate" + --> exclude_bakers ["delegate"] + --> make_denunciations () --> next_cycle let init_scenario_with_delegators delegate_name faucet_name delegators_list = let rec init_delegators = function @@ -275,48 +302,7 @@ let init_scenario_with_delegators delegate_name faucet_name delegators_list = --> set_baker faucet_name --> set_delegate_params "delegate" init_params --> init_delegators delegators_list - --> next_block --> wait_delegate_parameters_activation - -let test_many_slashes = - let rec stake_unstake_for = function - | [] -> Empty - | staker :: t -> - stake staker Half --> unstake staker Half --> stake_unstake_for t - in - let slash delegate = double_bake delegate --> make_denunciations () in - Tag "double bake" - --> (Tag "solo delegate" - --> init_scenario_with_delegators - "delegate" - "faucet" - [("delegator", 1_234_567_891L)] - --> loop - 10 - (stake_unstake_for ["delegate"] --> slash "delegate" --> next_cycle) - ) -(* |+ Tag "delegate with one staker" - --> init_scenario_with_delegators - "delegate" - "faucet" - [("staker", 1_234_356_891L)] - --> loop - 10 - (stake_unstake_for ["delegate"; "staker"] - --> slash "delegate" --> next_cycle) - |+ Tag "delegate with three stakers" - --> init_scenario_with_delegators - "delegate" - "faucet" - [ - ("staker1", 1_234_356_891L); - ("staker2", 1_234_356_890L); - ("staker3", 1_723_333_111L); - ] - --> loop - 10 - (stake_unstake_for - ["delegate"; "staker1"; "staker2"; "staker3"] - --> slash "delegate" --> next_cycle))*) + --> wait_delegate_parameters_activation let test_no_shortcut_for_cheaters = let amount = Amount (Tez.of_mutez 333_000_000_000L) in @@ -375,8 +361,8 @@ let test_slash_correct_amount_after_stake_from_unstake = let test_mini_slash = init_constants () --> set S.Adaptive_issuance.autostaking_enable false - --> (Tag "Yes AI" --> activate_ai `Force |+ Tag "No AI" --> activate_ai `No) - --> begin_test ["delegate"; "baker"] + --> (Tag "Yes AI" --> activate_ai `Force --> begin_test ["delegate"; "baker"] + |+ Tag "No AI" --> activate_ai `No --> begin_test ["delegate"; "baker"]) --> unstake "delegate" (Amount Tez.one_mutez) --> set_baker "baker" --> next_cycle --> (Tag "5% slash" --> double_bake "delegate" --> make_denunciations () @@ -412,13 +398,7 @@ let tests = (* TODO: make sure this test passes with blocks_per_cycle:8l https://gitlab.com/tezos/tezos/-/issues/6904 *) ("Test slashes with simple varying stake", test_slash_monotonous_stake); - (* This test has been deactivated following the changes of the - forbidding mechanism that now forbids delegates right after the - first denunciation, it should be fixed and reactivated - https://gitlab.com/tezos/tezos/-/issues/6904 *) - (* ( "Test multiple slashes with multiple stakes/unstakes", *) - (* test_many_slashes ); *) - (* ("Test slash timing", test_slash_timing); *) + ("Test slash timing", test_slash_timing); ( "Test stake from unstake deactivated when slashed", test_no_shortcut_for_cheaters ); ( "Test stake from unstake reduce initial amount", -- GitLab