From 8a98a138f71ac1d6b1d8b3f5de829ccf5be3ebec Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Fri, 4 Jul 2025 17:04:59 +0200 Subject: [PATCH 01/10] Proto/tests/scenario: add option to disable default checks A temporary solution to handle incomplete reward test cases --- .../test/helpers/scenario_bake.ml | 29 +++++++++---------- .../test/helpers/scenario_begin.ml | 15 ++++++++-- 2 files changed, 26 insertions(+), 18 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 cd8e793ebf98..11f756297e5e 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_bake.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_bake.ml @@ -49,7 +49,7 @@ let apply_new_cycle new_cycle state : State.t = apply_unslashable_for_all new_cycle state (** After baking and applying rewards in state *) -let check_all_balances block state : unit tzresult Lwt.t = +let check_all_balances _full_metadata (block, state) : unit tzresult Lwt.t = let open Lwt_result_syntax in let State.{account_map; total_supply; _} = state in let* actual_total_supply = Context.get_total_supply (B block) in @@ -79,7 +79,7 @@ let check_all_balances block state : unit tzresult Lwt.t = Assert.join_errors r1 r2 (** Misc checks at block end *) -let check_misc block state : unit tzresult Lwt.t = +let check_misc _full_metadata (block, state) : unit tzresult Lwt.t = let open Lwt_result_syntax in let State.{account_map; _} = state in String.Map.fold_s @@ -169,7 +169,7 @@ let check_misc block state : unit tzresult Lwt.t = account_map Result.return_unit -let check_issuance_rpc block : unit tzresult Lwt.t = +let check_issuance_rpc _metadata (block, _state) : unit tzresult Lwt.t = let open Lwt_result_syntax in (* We assume one block per minute *) let* rewards_per_block = Context.get_issuance_per_minute (B block) in @@ -285,7 +285,6 @@ let finalize_payload_ ?payload_round ?baker : t -> t_incr tzresult Lwt.t = Protocol.Alpha_context.Per_block_votes.Per_block_vote_on else Per_block_vote_pass in - let* () = check_issuance_rpc block in let* block' = Block.bake ?policy ~adaptive_issuance_vote block in let* i = Incremental.begin_construction @@ -348,8 +347,16 @@ let finalize_block_ : t_incr -> t tzresult Lwt.t = if not (Block.last_block_of_cycle block) then return state else apply_end_cycle current_cycle previous_block block state in - let* () = check_all_balances block state in - let* () = check_misc block state in + let* () = + List.iter_es + (fun f -> f metadata (block, state)) + state.check_finalized_block_perm + in + let* () = + List.iter_es + (fun f -> f metadata (block, state)) + state.check_finalized_block_temp + in (* Dawn of a new cycle: update finalizables *) (* Note: this is done after the checks, because it is not observable by RPCs by calling the previous block (which is still in the previous cycle *) @@ -363,16 +370,6 @@ let finalize_block_ : t_incr -> t tzresult Lwt.t = |> Int32.to_int) ; return @@ apply_new_cycle new_future_current_cycle state) in - let* () = - List.iter_es - (fun f -> f metadata (block, state)) - state.check_finalized_block_perm - in - let* () = - List.iter_es - (fun f -> f metadata (block, state)) - state.check_finalized_block_temp - in let state = { state with diff --git a/src/proto_alpha/lib_protocol/test/helpers/scenario_begin.ml b/src/proto_alpha/lib_protocol/test/helpers/scenario_begin.ml index a424ad1f2c63..d1d707be2c99 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_begin.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_begin.ml @@ -83,11 +83,19 @@ let init_constants ?(default = Test) ?(reward_per_block = 0L) (** Initialize the test, given some initial parameters *) let begin_test ?algo ?(burn_rewards = false) ?(force_attest_all = false) ?(force_preattest_all = false) ?(check_finalized_block_perm = []) - delegates_name_list : (constants, t) scenarios = + ?(disable_default_checks = false) delegates_name_list : + (constants, t) scenarios = exec (fun (constants : constants) -> let open Lwt_result_syntax in let bootstrap = "__bootstrap__" in let delegates_name_list = bootstrap :: delegates_name_list in + (* Do not disable default checks, unless for a good reason *) + let check_finalized_block_perm = + if disable_default_checks then check_finalized_block_perm + else + [check_all_balances; check_misc; check_issuance_rpc] + @ check_finalized_block_perm + in (* Override threshold value if activate *) let n = List.length delegates_name_list in let* block, delegates = Context.init_with_constants_n ?algo constants n in @@ -164,5 +172,8 @@ let begin_test ?algo ?(burn_rewards = false) ?(force_attest_all = false) grandparent = block; } in - let* () = check_all_balances block state in + let* () = + if not disable_default_checks then check_all_balances () (block, state) + else return_unit + in return (block, state)) -- GitLab From c5517ca87ee9efd6d1994fb42036e79ce260911b Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Mon, 7 Jul 2025 13:28:50 +0200 Subject: [PATCH 02/10] Proto/tests/scenario: add end of cycle attestation rewards checks --- .../test/helpers/scenario_attestation.ml | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) 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 56d2c1ca2500..8e5103b63181 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_attestation.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_attestation.ml @@ -162,6 +162,78 @@ let check_attestation_aggregate_metadata ?(check_not_found = false) ~kind Signature.Public_key_hash.pp) (List.map fst committee_expect) +let check_attestation_rewards ?(check_not_found = false) delegate_name : + Block.full_metadata -> t -> unit tzresult Lwt.t = + fun (_block_header_metadata, _op_metadata) (block, state) -> + let open Lwt_result_syntax in + Log.debug ~color:low_debug_color "Check metadata: attestation rewards" ; + let* () = + if Block.last_block_of_cycle block then return_unit + else + failwith + "check_attestation_rewards must be called at the end of a cycle \ + (current block position in cycle: %d, expecting %d)" + (Int32.to_int (Block.cycle_position block)) + (Int32.to_int state.constants.blocks_per_cycle - 1) + in + (* In the block metadata, there is no exact correspondence between attestation rewards + and the receivers. For now, we do a simple check. + TODO: make a check function that covers all end-of-cycle rewards. This should + also include staking rewards, and rewards of amount zero should not appear. *) + let id_or_not, error_suffix = + if check_not_found then + ( not, + "balance increased, suggesting rewards were distributed, which is not \ + expected." ) + else + ( Fun.id, + "balance did not increase, suggesting rewards were not distributed, \ + which is not expected." ) + in + let delegate = State.find_account delegate_name state in + let* previous_balance = + Context.Contract.full_balance (B state.grandparent) delegate.contract + in + let* current_balance = + Context.Contract.full_balance (B block) delegate.contract + in + if id_or_not @@ Tez.(current_balance > previous_balance) then return_unit + else failwith "Check attestation rewards: %s's %s" delegate_name error_suffix + +let check_missed_attestation_rewards delegate_name ?(check_not_found = false) : + Block.full_metadata -> t -> unit tzresult Lwt.t = + fun (block_header_metadata, _op_metadata) (block, state) -> + let open Lwt_result_syntax in + Log.debug ~color:low_debug_color "Check metadata: missed attestation rewards" ; + let* () = + if Block.last_block_of_cycle block then return_unit + else + failwith + "check_missed_attestation_rewards must be called at the end of a cycle \ + (current block position in cycle: %d, expecting %d)" + (Int32.to_int (Block.cycle_position block)) + (Int32.to_int state.constants.blocks_per_cycle - 1) + in + let id_or_not, error_prefix = + if check_not_found then (not, "Not expected but found in metadata") + else (Fun.id, "Expected but not found in metadata") + in + let delegate = State.find_account delegate_name state in + if + id_or_not + @@ List.exists + (function + | Alpha_context.Receipt.Balance_update_item + ( Lost_attesting_rewards (pkh, _participation, _revelation), + Credited _, + Block_application ) -> + Signature.Public_key_hash.equal delegate.pkh pkh + | _ -> false) + block_header_metadata.balance_updates + then return_unit + else + failwith "%s: missed attestation receipt for %s" error_prefix delegate_name + let attest_with ?dal_content (delegate_name : string) : (t, t) scenarios = exec (fun (block, state) -> let open Lwt_result_wrap_syntax in -- GitLab From 44467a6ad6ee085be5be154a247077428324fa01 Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Mon, 7 Jul 2025 13:29:27 +0200 Subject: [PATCH 03/10] Proto/tests/scenario: get delegates/stakers from state --- .../lib_protocol/test/helpers/state.ml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/proto_alpha/lib_protocol/test/helpers/state.ml b/src/proto_alpha/lib_protocol/test/helpers/state.ml index a27ed03b0748..f1efa43ebf55 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/state.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/state.ml @@ -79,6 +79,27 @@ let find_account_from_pkh (pkh : Signature.public_key_hash) (state : t) : assert false | Some (name, acc) -> (name, acc) +let get_delegates_of ~name state = + String.Map.fold + (fun _delegator account acc -> + match account.delegate with + | Some delegate when String.equal delegate name -> account :: acc + | _ -> acc) + state.account_map + [] + +let get_stakers_of ~name state = + String.Map.fold + (fun _delegator account acc -> + match account.delegate with + | Some delegate + when String.equal delegate name + && Z.(account.staking_delegator_numerator > zero) -> + account :: acc + | _ -> acc) + state.account_map + [] + let liquid_delegated ~name state = let open Result_syntax in String.Map.fold_e -- GitLab From fb4ad16ac8e585f892a3fec24ef2e500ff690e92 Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Mon, 7 Jul 2025 15:53:03 +0200 Subject: [PATCH 04/10] Proto/tests/scenario: remove hidden bootstrap account Make it explicit, becasue oftentimes useless --- .../lib_protocol/test/helpers/scenario_begin.ml | 3 +-- .../test/integration/test_scenario_rewards.ml | 5 +---- .../test/integration/test_scenario_slashing.ml | 2 +- .../lib_protocol/test/integration/test_scenario_stake.ml | 9 +++++---- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/proto_alpha/lib_protocol/test/helpers/scenario_begin.ml b/src/proto_alpha/lib_protocol/test/helpers/scenario_begin.ml index d1d707be2c99..681b181529af 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_begin.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_begin.ml @@ -87,8 +87,7 @@ let begin_test ?algo ?(burn_rewards = false) ?(force_attest_all = false) (constants, t) scenarios = exec (fun (constants : constants) -> let open Lwt_result_syntax in - let bootstrap = "__bootstrap__" in - let delegates_name_list = bootstrap :: delegates_name_list in + assert (not @@ List.is_empty delegates_name_list) ; (* Do not disable default checks, unless for a good reason *) let check_finalized_block_perm = if disable_default_checks then check_finalized_block_perm diff --git a/src/proto_alpha/lib_protocol/test/integration/test_scenario_rewards.ml b/src/proto_alpha/lib_protocol/test/integration/test_scenario_rewards.ml index 8f5e6d32c098..2d12a8ba91b9 100644 --- a/src/proto_alpha/lib_protocol/test/integration/test_scenario_rewards.ml +++ b/src/proto_alpha/lib_protocol/test/integration/test_scenario_rewards.ml @@ -153,7 +153,6 @@ let test_overstake_different_limits = --> set_baker "delegate" (* same rights to have same block distribution *) --> unstake "faucet" (Amount (Tez.of_mutez 190_000_000_000L)) - --> unstake "__bootstrap__" (Amount (Tez.of_mutez 190_000_000_000L)) --> set_limit 5. --> wait_delegate_parameters_activation --> add_account_with_funds "staker1" @@ -238,7 +237,6 @@ let test_static_decreasing = --> begin_test ~burn_rewards:true ["delegate"] (* We stake about 50% of the total supply *) --> stake "delegate" (Amount (Tez.of_mutez 1_800_000_000_000L)) - --> stake "__bootstrap__" (Amount (Tez.of_mutez 1_800_000_000_000L)) --> (Tag "increase stake, decrease rate" --> next_cycle --> loop rate_var_lag (stake "delegate" delta --> next_cycle) --> loop 10 cycle_stake @@ -279,7 +277,6 @@ let test_static_timing = --> begin_test ~burn_rewards:true ["delegate"] (* We stake about 50% of the total supply *) --> stake "delegate" (Amount (Tez.of_mutez 1_800_000_000_000L)) - --> stake "__bootstrap__" (Amount (Tez.of_mutez 1_800_000_000_000L)) --> wait_n_cycles_f (fun x -> consensus_rights_delay x + 1) --> save_current_rate --> (Tag "increase stake" --> stake "delegate" delta @@ -302,7 +299,7 @@ let begin_test_with_rewards_checks ~init_limit = set_delegate_params name params in init_constants ~reward_per_block:1_000_000_000L () - --> begin_test ["delegate"; "faucet"] + --> begin_test ["delegate"; "faucet"; "bootstrap"] --> set_baker "delegate" --> add_account_with_funds "staker" 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 f6376f38348c..3e6fd52bf7e3 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 @@ -498,7 +498,7 @@ let test_mega_slash = (fun acc name -> acc --> unstake name (Amount (Tez.of_mutez 190_000_000_000L))) Empty - ("__bootstrap__" :: delegates) + delegates --> set_baker "baker" (* Activate staking for delegate *) --> set_delegate_params diff --git a/src/proto_alpha/lib_protocol/test/integration/test_scenario_stake.ml b/src/proto_alpha/lib_protocol/test/integration/test_scenario_stake.ml index 1020e09dfdc0..80fe96ee5ab0 100644 --- a/src/proto_alpha/lib_protocol/test/integration/test_scenario_stake.ml +++ b/src/proto_alpha/lib_protocol/test/integration/test_scenario_stake.ml @@ -52,7 +52,8 @@ let init_staker_delegate_or_external = in let begin_test ~self_stake = let name = if self_stake then "staker" else "delegate" in - init_constants () --> begin_test [name] + init_constants () + --> begin_test [name; "bootstrap"] --> set_delegate_params name init_params in (Tag "self stake" --> begin_test ~self_stake:true @@ -732,14 +733,14 @@ let test_pseudotokens_roundings = in (* Any number works, we'll be in AI later anyways *) init_constants ~reward_per_block:1_000_000_007L () - --> begin_test ["delegate"; "faucet"] ~force_attest_all:true + --> begin_test ["delegate"; "faucet"; "bootstrap"] ~force_attest_all:true --> set_baker "faucet" --> set_delegate_params "delegate" init_params (* delegate stake = 10_000 tz *) --> unstake "delegate" (Amount (Tez.of_mutez 190_000_000_000L)) (* Set other's stake to avoid lack of attestation slots *) --> unstake "faucet" (Amount (Tez.of_mutez 190_000_000_000L)) - --> unstake "__bootstrap__" (Amount (Tez.of_mutez 190_000_000_000L)) + --> unstake "bootstrap" (Amount (Tez.of_mutez 190_000_000_000L)) --> check_balance_field "delegate" `Staked (Tez.of_mutez 10_000_000_000L) --> add_account_with_funds "staker" @@ -954,7 +955,7 @@ let unstake_all = } in init_constants ~reward_per_block:1_000_000L () - --> begin_test ~force_attest_all:true ["delegate"] + --> begin_test ~force_attest_all:true ["delegate"; "bootstrap"] --> set_delegate_params "delegate" init_params --> add_account_with_funds "staker" -- GitLab From c13aeb8e43634488f3bfa738982f8ebbccf50875 Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Mon, 7 Jul 2025 15:55:06 +0200 Subject: [PATCH 05/10] Proto/tests/scenario: simple attestation rewards checks with tz4/aggregation variants --- .../test/helpers/scenario_bake.ml | 5 ++ .../test/helpers/scenario_base.ml | 2 + .../consensus/test_scenario_attestation.ml | 73 +++++++++++++++++++ 3 files changed, 80 insertions(+) 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 11f756297e5e..8baad0a1b156 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_bake.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_bake.ml @@ -481,6 +481,11 @@ let next_cycle_ input = (** Bake until the end of a cycle *) let next_cycle = exec next_cycle_ +let dawn_of_next_cycle = + exec (fun input -> + Log.info ~color:action_color "[Dawn of next cycle]" ; + bake_until_dawn_of_next_cycle input) + (** Executes an operation: f should return a new state and a list of operations, which are then applied *) let exec_op f = let open Lwt_result_syntax in 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 142737652ea1..9821b6ae8566 100644 --- a/src/proto_alpha/lib_protocol/test/helpers/scenario_base.ml +++ b/src/proto_alpha/lib_protocol/test/helpers/scenario_base.ml @@ -318,3 +318,5 @@ let with_metadata f (block, state) = match state.previous_metadata with | None -> assert false | Some metadata -> f metadata (block, state) + +let exec_metadata f = exec_unit (with_metadata f) diff --git a/src/proto_alpha/lib_protocol/test/integration/consensus/test_scenario_attestation.ml b/src/proto_alpha/lib_protocol/test/integration/consensus/test_scenario_attestation.ml index 4e403817da7d..4c174e30f444 100644 --- a/src/proto_alpha/lib_protocol/test/integration/consensus/test_scenario_attestation.ml +++ b/src/proto_alpha/lib_protocol/test/integration/consensus/test_scenario_attestation.ml @@ -157,6 +157,75 @@ let test_preattest_aggreg = --> check_delegate_didnt_preattest "delegate1" --> check_delegate_didnt_preattest "delegate2" +let init_constants_for_attestation_rewards = + init_constants () + --> set + S.issuance_weights + { + base_total_issued_per_minute = Tez_helpers.of_mutez 1_000_000_007L; + baking_reward_fixed_portion_weight = 0; + baking_reward_bonus_weight = 0; + attesting_reward_weight = 1; + seed_nonce_revelation_tip_weight = 0; + vdf_revelation_tip_weight = 0; + dal_rewards_weight = 0; + } + +let test_attestation_rewards = + init_constants_for_attestation_rewards + (* Default checks are disabled because rewards have been changed *) + --> (Tag "not tz4" + --> begin_test + ["delegate"] + ~disable_default_checks:true + ~force_attest_all:true + |+ Tag "tz4 (solo)" + --> begin_test + ["delegate"] + ~algo:Bls + ~disable_default_checks:true + ~force_attest_all:true + |+ Tag "tz4 (with others)" + --> begin_test + ["delegate"; "bozo1"; "bozo2"] + ~algo:Bls + ~disable_default_checks:true + ~force_attest_all:true) + --> dawn_of_next_cycle + --> exec_metadata (check_attestation_rewards "delegate") + --> exec_metadata + (check_missed_attestation_rewards ~check_not_found:true "delegate") + +let test_missed_attestations_rewards = + init_constants_for_attestation_rewards + (* Default checks are disabled because rewards have been changed *) + --> begin_test ["delegate"] ~disable_default_checks:true + --> snapshot_balances "init" ["delegate"] + --> next_block + --> (Tag "attest once" --> attest_with "delegate" |+ Tag "no attest" --> noop) + --> dawn_of_next_cycle + --> exec_metadata (check_missed_attestation_rewards "delegate") + (* Check balance of "delegate" hasn't changed *) + --> exec_metadata (check_attestation_rewards ~check_not_found:true "delegate") + +let test_missed_attestations_rewards_tz4 = + init_constants_for_attestation_rewards + (* Default checks are disabled because rewards have been changed *) + --> begin_test + ["delegate"; "bozo1"; "bozo2"] + ~algo:Bls + ~disable_default_checks:true + --> snapshot_balances "init" ["delegate"] + --> next_block + --> (Tag "attest once (solo)" --> attest_aggreg_with ["delegate"] + |+ Tag "attest once (with others)" + --> attest_aggreg_with ["delegate"; "bozo1"; "bozo2"] + |+ Tag "no attest" --> noop) + --> dawn_of_next_cycle + --> exec_metadata (check_missed_attestation_rewards "delegate") + (* Check balance of "delegate" hasn't changed *) + --> exec_metadata (check_attestation_rewards ~check_not_found:true "delegate") + let tests = tests_of_scenarios @@ [ @@ -167,6 +236,10 @@ let tests = ("Test preattest all", test_preattest_all); ("Test attest aggreg", test_attest_aggreg); ("Test preattest aggreg", test_preattest_aggreg); + ("Test attestation rewards", test_attestation_rewards); + ("Test missed attestation rewards", test_missed_attestations_rewards); + ( "Test missed attestation rewards (tz4)", + test_missed_attestations_rewards_tz4 ); ] let () = -- GitLab From ea3d5486f90db279a6b09a870e03f49e61f5c9fd Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Wed, 9 Jul 2025 15:04:38 +0200 Subject: [PATCH 06/10] 023_PtSeouLo/Proto/tests/scenario: add option to disable default checks Porting to proto 023_PtSeouLo 8a98a138f71ac1d6b1d8b3f5de829ccf5be3ebec - Proto/tests/scenario: add option to disable default checks --- .../test/helpers/scenario_bake.ml | 29 +++++++++---------- .../test/helpers/scenario_begin.ml | 15 ++++++++-- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_bake.ml b/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_bake.ml index cd8e793ebf98..11f756297e5e 100644 --- a/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_bake.ml +++ b/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_bake.ml @@ -49,7 +49,7 @@ let apply_new_cycle new_cycle state : State.t = apply_unslashable_for_all new_cycle state (** After baking and applying rewards in state *) -let check_all_balances block state : unit tzresult Lwt.t = +let check_all_balances _full_metadata (block, state) : unit tzresult Lwt.t = let open Lwt_result_syntax in let State.{account_map; total_supply; _} = state in let* actual_total_supply = Context.get_total_supply (B block) in @@ -79,7 +79,7 @@ let check_all_balances block state : unit tzresult Lwt.t = Assert.join_errors r1 r2 (** Misc checks at block end *) -let check_misc block state : unit tzresult Lwt.t = +let check_misc _full_metadata (block, state) : unit tzresult Lwt.t = let open Lwt_result_syntax in let State.{account_map; _} = state in String.Map.fold_s @@ -169,7 +169,7 @@ let check_misc block state : unit tzresult Lwt.t = account_map Result.return_unit -let check_issuance_rpc block : unit tzresult Lwt.t = +let check_issuance_rpc _metadata (block, _state) : unit tzresult Lwt.t = let open Lwt_result_syntax in (* We assume one block per minute *) let* rewards_per_block = Context.get_issuance_per_minute (B block) in @@ -285,7 +285,6 @@ let finalize_payload_ ?payload_round ?baker : t -> t_incr tzresult Lwt.t = Protocol.Alpha_context.Per_block_votes.Per_block_vote_on else Per_block_vote_pass in - let* () = check_issuance_rpc block in let* block' = Block.bake ?policy ~adaptive_issuance_vote block in let* i = Incremental.begin_construction @@ -348,8 +347,16 @@ let finalize_block_ : t_incr -> t tzresult Lwt.t = if not (Block.last_block_of_cycle block) then return state else apply_end_cycle current_cycle previous_block block state in - let* () = check_all_balances block state in - let* () = check_misc block state in + let* () = + List.iter_es + (fun f -> f metadata (block, state)) + state.check_finalized_block_perm + in + let* () = + List.iter_es + (fun f -> f metadata (block, state)) + state.check_finalized_block_temp + in (* Dawn of a new cycle: update finalizables *) (* Note: this is done after the checks, because it is not observable by RPCs by calling the previous block (which is still in the previous cycle *) @@ -363,16 +370,6 @@ let finalize_block_ : t_incr -> t tzresult Lwt.t = |> Int32.to_int) ; return @@ apply_new_cycle new_future_current_cycle state) in - let* () = - List.iter_es - (fun f -> f metadata (block, state)) - state.check_finalized_block_perm - in - let* () = - List.iter_es - (fun f -> f metadata (block, state)) - state.check_finalized_block_temp - in let state = { state with diff --git a/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_begin.ml b/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_begin.ml index a424ad1f2c63..d1d707be2c99 100644 --- a/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_begin.ml +++ b/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_begin.ml @@ -83,11 +83,19 @@ let init_constants ?(default = Test) ?(reward_per_block = 0L) (** Initialize the test, given some initial parameters *) let begin_test ?algo ?(burn_rewards = false) ?(force_attest_all = false) ?(force_preattest_all = false) ?(check_finalized_block_perm = []) - delegates_name_list : (constants, t) scenarios = + ?(disable_default_checks = false) delegates_name_list : + (constants, t) scenarios = exec (fun (constants : constants) -> let open Lwt_result_syntax in let bootstrap = "__bootstrap__" in let delegates_name_list = bootstrap :: delegates_name_list in + (* Do not disable default checks, unless for a good reason *) + let check_finalized_block_perm = + if disable_default_checks then check_finalized_block_perm + else + [check_all_balances; check_misc; check_issuance_rpc] + @ check_finalized_block_perm + in (* Override threshold value if activate *) let n = List.length delegates_name_list in let* block, delegates = Context.init_with_constants_n ?algo constants n in @@ -164,5 +172,8 @@ let begin_test ?algo ?(burn_rewards = false) ?(force_attest_all = false) grandparent = block; } in - let* () = check_all_balances block state in + let* () = + if not disable_default_checks then check_all_balances () (block, state) + else return_unit + in return (block, state)) -- GitLab From f676b893ce49a6e7a1ebc4cff99242a16b10a657 Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Wed, 9 Jul 2025 15:04:47 +0200 Subject: [PATCH 07/10] 023_PtSeouLo/Proto/tests/scenario: add end of cycle attestation rewards checks Porting to proto 023_PtSeouLo c5517ca87ee9efd6d1994fb42036e79ce260911b - Proto/tests/scenario: add end of cycle attestation rewards checks --- .../test/helpers/scenario_attestation.ml | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_attestation.ml b/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_attestation.ml index 56d2c1ca2500..8e5103b63181 100644 --- a/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_attestation.ml +++ b/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_attestation.ml @@ -162,6 +162,78 @@ let check_attestation_aggregate_metadata ?(check_not_found = false) ~kind Signature.Public_key_hash.pp) (List.map fst committee_expect) +let check_attestation_rewards ?(check_not_found = false) delegate_name : + Block.full_metadata -> t -> unit tzresult Lwt.t = + fun (_block_header_metadata, _op_metadata) (block, state) -> + let open Lwt_result_syntax in + Log.debug ~color:low_debug_color "Check metadata: attestation rewards" ; + let* () = + if Block.last_block_of_cycle block then return_unit + else + failwith + "check_attestation_rewards must be called at the end of a cycle \ + (current block position in cycle: %d, expecting %d)" + (Int32.to_int (Block.cycle_position block)) + (Int32.to_int state.constants.blocks_per_cycle - 1) + in + (* In the block metadata, there is no exact correspondence between attestation rewards + and the receivers. For now, we do a simple check. + TODO: make a check function that covers all end-of-cycle rewards. This should + also include staking rewards, and rewards of amount zero should not appear. *) + let id_or_not, error_suffix = + if check_not_found then + ( not, + "balance increased, suggesting rewards were distributed, which is not \ + expected." ) + else + ( Fun.id, + "balance did not increase, suggesting rewards were not distributed, \ + which is not expected." ) + in + let delegate = State.find_account delegate_name state in + let* previous_balance = + Context.Contract.full_balance (B state.grandparent) delegate.contract + in + let* current_balance = + Context.Contract.full_balance (B block) delegate.contract + in + if id_or_not @@ Tez.(current_balance > previous_balance) then return_unit + else failwith "Check attestation rewards: %s's %s" delegate_name error_suffix + +let check_missed_attestation_rewards delegate_name ?(check_not_found = false) : + Block.full_metadata -> t -> unit tzresult Lwt.t = + fun (block_header_metadata, _op_metadata) (block, state) -> + let open Lwt_result_syntax in + Log.debug ~color:low_debug_color "Check metadata: missed attestation rewards" ; + let* () = + if Block.last_block_of_cycle block then return_unit + else + failwith + "check_missed_attestation_rewards must be called at the end of a cycle \ + (current block position in cycle: %d, expecting %d)" + (Int32.to_int (Block.cycle_position block)) + (Int32.to_int state.constants.blocks_per_cycle - 1) + in + let id_or_not, error_prefix = + if check_not_found then (not, "Not expected but found in metadata") + else (Fun.id, "Expected but not found in metadata") + in + let delegate = State.find_account delegate_name state in + if + id_or_not + @@ List.exists + (function + | Alpha_context.Receipt.Balance_update_item + ( Lost_attesting_rewards (pkh, _participation, _revelation), + Credited _, + Block_application ) -> + Signature.Public_key_hash.equal delegate.pkh pkh + | _ -> false) + block_header_metadata.balance_updates + then return_unit + else + failwith "%s: missed attestation receipt for %s" error_prefix delegate_name + let attest_with ?dal_content (delegate_name : string) : (t, t) scenarios = exec (fun (block, state) -> let open Lwt_result_wrap_syntax in -- GitLab From d66272e397a8ad3b4ee81b2178eb71e2ca944077 Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Wed, 9 Jul 2025 15:04:54 +0200 Subject: [PATCH 08/10] 023_PtSeouLo/Proto/tests/scenario: get delegates/stakers from state Porting to proto 023_PtSeouLo 44467a6ad6ee085be5be154a247077428324fa01 - Proto/tests/scenario: get delegates/stakers from state --- .../lib_protocol/test/helpers/state.ml | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/proto_023_PtSeouLo/lib_protocol/test/helpers/state.ml b/src/proto_023_PtSeouLo/lib_protocol/test/helpers/state.ml index a27ed03b0748..f1efa43ebf55 100644 --- a/src/proto_023_PtSeouLo/lib_protocol/test/helpers/state.ml +++ b/src/proto_023_PtSeouLo/lib_protocol/test/helpers/state.ml @@ -79,6 +79,27 @@ let find_account_from_pkh (pkh : Signature.public_key_hash) (state : t) : assert false | Some (name, acc) -> (name, acc) +let get_delegates_of ~name state = + String.Map.fold + (fun _delegator account acc -> + match account.delegate with + | Some delegate when String.equal delegate name -> account :: acc + | _ -> acc) + state.account_map + [] + +let get_stakers_of ~name state = + String.Map.fold + (fun _delegator account acc -> + match account.delegate with + | Some delegate + when String.equal delegate name + && Z.(account.staking_delegator_numerator > zero) -> + account :: acc + | _ -> acc) + state.account_map + [] + let liquid_delegated ~name state = let open Result_syntax in String.Map.fold_e -- GitLab From 91296caafbc44a58af0b58d6f9605ea71acf0bdf Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Wed, 9 Jul 2025 15:05:00 +0200 Subject: [PATCH 09/10] 023_PtSeouLo/Proto/tests/scenario: remove hidden bootstrap account Porting to proto 023_PtSeouLo fb4ad16ac8e585f892a3fec24ef2e500ff690e92 - Proto/tests/scenario: remove hidden bootstrap account --- .../lib_protocol/test/helpers/scenario_begin.ml | 3 +-- .../test/integration/test_scenario_rewards.ml | 5 +---- .../test/integration/test_scenario_slashing.ml | 2 +- .../lib_protocol/test/integration/test_scenario_stake.ml | 9 +++++---- 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_begin.ml b/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_begin.ml index d1d707be2c99..681b181529af 100644 --- a/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_begin.ml +++ b/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_begin.ml @@ -87,8 +87,7 @@ let begin_test ?algo ?(burn_rewards = false) ?(force_attest_all = false) (constants, t) scenarios = exec (fun (constants : constants) -> let open Lwt_result_syntax in - let bootstrap = "__bootstrap__" in - let delegates_name_list = bootstrap :: delegates_name_list in + assert (not @@ List.is_empty delegates_name_list) ; (* Do not disable default checks, unless for a good reason *) let check_finalized_block_perm = if disable_default_checks then check_finalized_block_perm diff --git a/src/proto_023_PtSeouLo/lib_protocol/test/integration/test_scenario_rewards.ml b/src/proto_023_PtSeouLo/lib_protocol/test/integration/test_scenario_rewards.ml index 4bd28698b1ad..cd71f850f1e9 100644 --- a/src/proto_023_PtSeouLo/lib_protocol/test/integration/test_scenario_rewards.ml +++ b/src/proto_023_PtSeouLo/lib_protocol/test/integration/test_scenario_rewards.ml @@ -153,7 +153,6 @@ let test_overstake_different_limits = --> set_baker "delegate" (* same rights to have same block distribution *) --> unstake "faucet" (Amount (Tez.of_mutez 190_000_000_000L)) - --> unstake "__bootstrap__" (Amount (Tez.of_mutez 190_000_000_000L)) --> set_limit 5. --> wait_delegate_parameters_activation --> add_account_with_funds "staker1" @@ -238,7 +237,6 @@ let test_static_decreasing = --> begin_test ~burn_rewards:true ["delegate"] (* We stake about 50% of the total supply *) --> stake "delegate" (Amount (Tez.of_mutez 1_800_000_000_000L)) - --> stake "__bootstrap__" (Amount (Tez.of_mutez 1_800_000_000_000L)) --> (Tag "increase stake, decrease rate" --> next_cycle --> loop rate_var_lag (stake "delegate" delta --> next_cycle) --> loop 10 cycle_stake @@ -279,7 +277,6 @@ let test_static_timing = --> begin_test ~burn_rewards:true ["delegate"] (* We stake about 50% of the total supply *) --> stake "delegate" (Amount (Tez.of_mutez 1_800_000_000_000L)) - --> stake "__bootstrap__" (Amount (Tez.of_mutez 1_800_000_000_000L)) --> wait_n_cycles_f (fun x -> consensus_rights_delay x + 1) --> save_current_rate --> (Tag "increase stake" --> stake "delegate" delta @@ -302,7 +299,7 @@ let begin_test_with_rewards_checks ~init_limit = set_delegate_params name params in init_constants ~reward_per_block:1_000_000_000L () - --> begin_test ["delegate"; "faucet"] + --> begin_test ["delegate"; "faucet"; "bootstrap"] --> set_baker "delegate" --> add_account_with_funds "staker" diff --git a/src/proto_023_PtSeouLo/lib_protocol/test/integration/test_scenario_slashing.ml b/src/proto_023_PtSeouLo/lib_protocol/test/integration/test_scenario_slashing.ml index 70c4181b17c4..9ad39608e3b3 100644 --- a/src/proto_023_PtSeouLo/lib_protocol/test/integration/test_scenario_slashing.ml +++ b/src/proto_023_PtSeouLo/lib_protocol/test/integration/test_scenario_slashing.ml @@ -498,7 +498,7 @@ let test_mega_slash = (fun acc name -> acc --> unstake name (Amount (Tez.of_mutez 190_000_000_000L))) Empty - ("__bootstrap__" :: delegates) + delegates --> set_baker "baker" (* Activate staking for delegate *) --> set_delegate_params diff --git a/src/proto_023_PtSeouLo/lib_protocol/test/integration/test_scenario_stake.ml b/src/proto_023_PtSeouLo/lib_protocol/test/integration/test_scenario_stake.ml index 92db50385144..adcfea3fc01c 100644 --- a/src/proto_023_PtSeouLo/lib_protocol/test/integration/test_scenario_stake.ml +++ b/src/proto_023_PtSeouLo/lib_protocol/test/integration/test_scenario_stake.ml @@ -52,7 +52,8 @@ let init_staker_delegate_or_external = in let begin_test ~self_stake = let name = if self_stake then "staker" else "delegate" in - init_constants () --> begin_test [name] + init_constants () + --> begin_test [name; "bootstrap"] --> set_delegate_params name init_params in (Tag "self stake" --> begin_test ~self_stake:true @@ -732,14 +733,14 @@ let test_pseudotokens_roundings = in (* Any number works, we'll be in AI later anyways *) init_constants ~reward_per_block:1_000_000_007L () - --> begin_test ["delegate"; "faucet"] ~force_attest_all:true + --> begin_test ["delegate"; "faucet"; "bootstrap"] ~force_attest_all:true --> set_baker "faucet" --> set_delegate_params "delegate" init_params (* delegate stake = 10_000 tz *) --> unstake "delegate" (Amount (Tez.of_mutez 190_000_000_000L)) (* Set other's stake to avoid lack of attestation slots *) --> unstake "faucet" (Amount (Tez.of_mutez 190_000_000_000L)) - --> unstake "__bootstrap__" (Amount (Tez.of_mutez 190_000_000_000L)) + --> unstake "bootstrap" (Amount (Tez.of_mutez 190_000_000_000L)) --> check_balance_field "delegate" `Staked (Tez.of_mutez 10_000_000_000L) --> add_account_with_funds "staker" @@ -954,7 +955,7 @@ let unstake_all = } in init_constants ~reward_per_block:1_000_000L () - --> begin_test ~force_attest_all:true ["delegate"] + --> begin_test ~force_attest_all:true ["delegate"; "bootstrap"] --> set_delegate_params "delegate" init_params --> add_account_with_funds "staker" -- GitLab From e7a0b2af2e086780407db984f9a5fb89dbb30a2a Mon Sep 17 00:00:00 2001 From: Lucas Randazzo Date: Wed, 9 Jul 2025 15:05:08 +0200 Subject: [PATCH 10/10] 023_PtSeouLo/Proto/tests/scenario: simple attestation rewards checks Porting to proto 023_PtSeouLo c13aeb8e43634488f3bfa738982f8ebbccf50875 - Proto/tests/scenario: simple attestation rewards checks --- .../test/helpers/scenario_bake.ml | 5 ++ .../test/helpers/scenario_base.ml | 2 + .../consensus/test_scenario_attestation.ml | 73 +++++++++++++++++++ 3 files changed, 80 insertions(+) diff --git a/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_bake.ml b/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_bake.ml index 11f756297e5e..8baad0a1b156 100644 --- a/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_bake.ml +++ b/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_bake.ml @@ -481,6 +481,11 @@ let next_cycle_ input = (** Bake until the end of a cycle *) let next_cycle = exec next_cycle_ +let dawn_of_next_cycle = + exec (fun input -> + Log.info ~color:action_color "[Dawn of next cycle]" ; + bake_until_dawn_of_next_cycle input) + (** Executes an operation: f should return a new state and a list of operations, which are then applied *) let exec_op f = let open Lwt_result_syntax in diff --git a/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_base.ml b/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_base.ml index 142737652ea1..9821b6ae8566 100644 --- a/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_base.ml +++ b/src/proto_023_PtSeouLo/lib_protocol/test/helpers/scenario_base.ml @@ -318,3 +318,5 @@ let with_metadata f (block, state) = match state.previous_metadata with | None -> assert false | Some metadata -> f metadata (block, state) + +let exec_metadata f = exec_unit (with_metadata f) diff --git a/src/proto_023_PtSeouLo/lib_protocol/test/integration/consensus/test_scenario_attestation.ml b/src/proto_023_PtSeouLo/lib_protocol/test/integration/consensus/test_scenario_attestation.ml index 4e403817da7d..4c174e30f444 100644 --- a/src/proto_023_PtSeouLo/lib_protocol/test/integration/consensus/test_scenario_attestation.ml +++ b/src/proto_023_PtSeouLo/lib_protocol/test/integration/consensus/test_scenario_attestation.ml @@ -157,6 +157,75 @@ let test_preattest_aggreg = --> check_delegate_didnt_preattest "delegate1" --> check_delegate_didnt_preattest "delegate2" +let init_constants_for_attestation_rewards = + init_constants () + --> set + S.issuance_weights + { + base_total_issued_per_minute = Tez_helpers.of_mutez 1_000_000_007L; + baking_reward_fixed_portion_weight = 0; + baking_reward_bonus_weight = 0; + attesting_reward_weight = 1; + seed_nonce_revelation_tip_weight = 0; + vdf_revelation_tip_weight = 0; + dal_rewards_weight = 0; + } + +let test_attestation_rewards = + init_constants_for_attestation_rewards + (* Default checks are disabled because rewards have been changed *) + --> (Tag "not tz4" + --> begin_test + ["delegate"] + ~disable_default_checks:true + ~force_attest_all:true + |+ Tag "tz4 (solo)" + --> begin_test + ["delegate"] + ~algo:Bls + ~disable_default_checks:true + ~force_attest_all:true + |+ Tag "tz4 (with others)" + --> begin_test + ["delegate"; "bozo1"; "bozo2"] + ~algo:Bls + ~disable_default_checks:true + ~force_attest_all:true) + --> dawn_of_next_cycle + --> exec_metadata (check_attestation_rewards "delegate") + --> exec_metadata + (check_missed_attestation_rewards ~check_not_found:true "delegate") + +let test_missed_attestations_rewards = + init_constants_for_attestation_rewards + (* Default checks are disabled because rewards have been changed *) + --> begin_test ["delegate"] ~disable_default_checks:true + --> snapshot_balances "init" ["delegate"] + --> next_block + --> (Tag "attest once" --> attest_with "delegate" |+ Tag "no attest" --> noop) + --> dawn_of_next_cycle + --> exec_metadata (check_missed_attestation_rewards "delegate") + (* Check balance of "delegate" hasn't changed *) + --> exec_metadata (check_attestation_rewards ~check_not_found:true "delegate") + +let test_missed_attestations_rewards_tz4 = + init_constants_for_attestation_rewards + (* Default checks are disabled because rewards have been changed *) + --> begin_test + ["delegate"; "bozo1"; "bozo2"] + ~algo:Bls + ~disable_default_checks:true + --> snapshot_balances "init" ["delegate"] + --> next_block + --> (Tag "attest once (solo)" --> attest_aggreg_with ["delegate"] + |+ Tag "attest once (with others)" + --> attest_aggreg_with ["delegate"; "bozo1"; "bozo2"] + |+ Tag "no attest" --> noop) + --> dawn_of_next_cycle + --> exec_metadata (check_missed_attestation_rewards "delegate") + (* Check balance of "delegate" hasn't changed *) + --> exec_metadata (check_attestation_rewards ~check_not_found:true "delegate") + let tests = tests_of_scenarios @@ [ @@ -167,6 +236,10 @@ let tests = ("Test preattest all", test_preattest_all); ("Test attest aggreg", test_attest_aggreg); ("Test preattest aggreg", test_preattest_aggreg); + ("Test attestation rewards", test_attestation_rewards); + ("Test missed attestation rewards", test_missed_attestations_rewards); + ( "Test missed attestation rewards (tz4)", + test_missed_attestations_rewards_tz4 ); ] let () = -- GitLab