Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test if midterm penalty can be decreased if pectra activation is checked on a last epoch before pectra. #643

Merged
merged 7 commits into from
Mar 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 5 additions & 11 deletions src/services/bunker_cases/midterm_slashing_penalty.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

logger = logging.getLogger(__name__)

type SlashedValidatorsFrameBuckets = dict[tuple[FrameNumber, EpochNumber], list[LidoValidator]]
type SlashedValidatorsFrameBuckets = dict[FrameNumber, list[LidoValidator]]


class MidtermSlashingPenalty:
Expand Down Expand Up @@ -159,9 +159,7 @@ def get_lido_validators_with_future_midterm_epoch(
# We need midterm penalties only from future frames
continue
frame_number = web3_converter.get_frame_by_epoch(midterm_penalty_epoch)
frame_ref_slot = SlotNumber(web3_converter.get_frame_first_slot(frame_number) - 1)
frame_ref_epoch = web3_converter.get_epoch_by_slot(frame_ref_slot)
buckets[(frame_number, frame_ref_epoch)].append(validator)
buckets[frame_number].append(validator)

return buckets

Expand All @@ -174,7 +172,7 @@ def get_future_midterm_penalty_sum_in_frames_pre_electra(
) -> dict[FrameNumber, Gwei]:
"""Calculate sum of midterm penalties in each frame"""
per_frame_midterm_penalty_sum: dict[FrameNumber, Gwei] = {}
for (frame_number, _), validators_in_future_frame in per_frame_validators.items():
for frame_number, validators_in_future_frame in per_frame_validators.items():
per_frame_midterm_penalty_sum[frame_number] = (
MidtermSlashingPenalty.predict_midterm_penalty_in_frame_pre_electra(
ref_epoch, all_slashed_validators, total_balance, validators_in_future_frame
Expand Down Expand Up @@ -212,11 +210,10 @@ def get_future_midterm_penalty_sum_in_frames_post_electra(
) -> dict[FrameNumber, Gwei]:
"""Calculate sum of midterm penalties in each frame"""
per_frame_midterm_penalty_sum: dict[FrameNumber, Gwei] = {}
for (frame_number, frame_ref_epoch), validators_in_future_frame in per_frame_validators.items():
for frame_number, validators_in_future_frame in per_frame_validators.items():
per_frame_midterm_penalty_sum[frame_number] = (
MidtermSlashingPenalty.predict_midterm_penalty_in_frame_post_electra(
ref_epoch,
frame_ref_epoch,
is_electra_activated,
slashings,
total_balance,
Expand All @@ -229,7 +226,6 @@ def get_future_midterm_penalty_sum_in_frames_post_electra(
@staticmethod
def predict_midterm_penalty_in_frame_post_electra(
report_ref_epoch: EpochNumber,
frame_ref_epoch: EpochNumber,
is_electra_activated: Callable[[EpochNumber], bool],
slashings: list[Gwei],
total_balance: Gwei,
Expand All @@ -239,9 +235,7 @@ def predict_midterm_penalty_in_frame_post_electra(
penalty_in_frame = 0
for validator in midterm_penalized_validators_in_frame:
midterm_penalty_epoch = MidtermSlashingPenalty.get_midterm_penalty_epoch(validator)
# all validators which were slashed in [midterm_penalty_epoch - EPOCHS_PER_SLASHINGS_VECTOR, midterm_penalty_epoch]

if is_electra_activated(frame_ref_epoch):
if is_electra_activated(midterm_penalty_epoch):
penalty_in_frame += MidtermSlashingPenalty.get_validator_midterm_penalty_electra(
validator, slashings, total_balance, midterm_penalty_epoch, report_ref_epoch
)
Expand Down
107 changes: 83 additions & 24 deletions tests/modules/accounting/bunker/test_bunker_midterm_penalty.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from unittest.mock import Mock

import pytest

from src.constants import (
Expand All @@ -14,6 +12,7 @@
from src.services.bunker_cases.midterm_slashing_penalty import MidtermSlashingPenalty
from src.types import EpochNumber, Gwei, ReferenceBlockStamp, SlotNumber, ValidatorIndex
from src.utils.web3converter import Web3Converter
from tests.factory.no_registry import ValidatorFactory, ValidatorStateFactory


def simple_blockstamp(
Expand Down Expand Up @@ -285,7 +284,7 @@ def test_get_possible_slashed_epochs(validator, ref_epoch, expected_result):
# the same midterm epoch
225,
simple_validators(0, 9, slashed=True),
{(18, 4049): simple_validators(0, 9, slashed=True)},
{18: simple_validators(0, 9, slashed=True)},
),
(
# midterm frames in past
Expand All @@ -301,8 +300,8 @@ def test_get_possible_slashed_epochs(validator, ref_epoch, expected_result):
*simple_validators(10, 59, slashed=True, withdrawable_epoch="8417"),
],
{
(18, 4049): simple_validators(0, 9, slashed=True),
(19, 4274): simple_validators(10, 59, slashed=True, withdrawable_epoch="8417"),
18: simple_validators(0, 9, slashed=True),
19: simple_validators(10, 59, slashed=True, withdrawable_epoch="8417"),
},
),
],
Expand Down Expand Up @@ -339,15 +338,15 @@ def test_get_per_frame_lido_validators_with_future_midterm_epoch(
(
# one is slashed
225,
{(18, 4050): simple_validators(0, 0, slashed=True)},
{18: simple_validators(0, 0, slashed=True)},
simple_validators(0, 0, slashed=True),
100,
{18: 0},
),
(
# all are slashed
225,
{(18, 4050): simple_validators(0, 99, slashed=True)},
{18: simple_validators(0, 99, slashed=True)},
simple_validators(0, 99, slashed=True),
100,
{18: 100 * 32 * 10**9},
Expand All @@ -356,8 +355,8 @@ def test_get_per_frame_lido_validators_with_future_midterm_epoch(
# slashed in different frames with determined slashing epochs
225,
{
(18, 4050): simple_validators(0, 9, slashed=True),
(19, 4725): simple_validators(10, 59, slashed=True, exit_epoch="8000", withdrawable_epoch="8417"),
18: simple_validators(0, 9, slashed=True),
19: simple_validators(10, 59, slashed=True, exit_epoch="8000", withdrawable_epoch="8417"),
},
[
*simple_validators(0, 9, slashed=True),
Expand All @@ -370,11 +369,11 @@ def test_get_per_frame_lido_validators_with_future_midterm_epoch(
# slashed in different epochs in different frames without determined shasling epochs
225,
{
(18, 4050): [
18: [
*simple_validators(0, 5),
*simple_validators(6, 9, slashed=True, exit_epoch="8192", withdrawable_epoch="8197"),
],
(19, 4725): [
19: [
*simple_validators(10, 29, slashed=True, exit_epoch="8000", withdrawable_epoch="8417"),
*simple_validators(30, 59, slashed=True, exit_epoch="8417", withdrawable_epoch="8419"),
],
Expand Down Expand Up @@ -416,7 +415,7 @@ def test_get_future_midterm_penalty_sum_in_frames_pre_electra(
# one is slashed before electra
225,
lambda epoch: epoch >= 4500,
{(18, 4049): simple_validators(0, 0, slashed=True)},
{18: simple_validators(0, 0, slashed=True)},
[*([0] * EPOCHS_PER_SLASHINGS_VECTOR)],
50000,
{18: 0},
Expand All @@ -425,7 +424,7 @@ def test_get_future_midterm_penalty_sum_in_frames_pre_electra(
# one is slashed after electra
225,
lambda epoch: epoch >= 225,
{(18, 4049): simple_validators(0, 0, slashed=True)},
{18: simple_validators(0, 0, slashed=True)},
[*([0] * 224), *([32 * 10**9]), *([0] * (EPOCHS_PER_SLASHINGS_VECTOR - 225))],
50000,
{18: 1_920_000},
Expand All @@ -434,7 +433,7 @@ def test_get_future_midterm_penalty_sum_in_frames_pre_electra(
# all are slashed before electra
225,
lambda epoch: epoch >= 4500,
{(18, 4049): simple_validators(0, 99, slashed=True)},
{18: simple_validators(0, 99, slashed=True)},
[*([32 * 10**9] * 100), *([0] * (EPOCHS_PER_SLASHINGS_VECTOR - 100))],
50000,
{18: 0},
Expand All @@ -443,18 +442,18 @@ def test_get_future_midterm_penalty_sum_in_frames_pre_electra(
# all are slashed after electra
350,
lambda epoch: epoch >= 225,
{(18, 4049): simple_validators(0, 99, slashed=True)},
{18: simple_validators(0, 99, slashed=True)},
[*([0] * 225), *([32 * 10**9] * 100), *([0] * (EPOCHS_PER_SLASHINGS_VECTOR - 325))],
50000,
{18: 19_200_000_000},
),
(
# slashed in different frames with determined slashing epochs in different forks
225,
lambda epoch: epoch >= 4500,
lambda epoch: epoch >= 4200,
{
(18, 4049): simple_validators(0, 0, slashed=True),
(19, 4724): simple_validators(10, 59, slashed=True, exit_epoch="8000", withdrawable_epoch="8417"),
18: simple_validators(0, 0, slashed=True),
19: simple_validators(10, 59, slashed=True, exit_epoch="8000", withdrawable_epoch="8417"),
},
[
*([0] * 100),
Expand All @@ -469,13 +468,13 @@ def test_get_future_midterm_penalty_sum_in_frames_pre_electra(
(
# slashed in different epochs in different frames without determined slashing epochs in different forks
225,
lambda epoch: epoch >= 4500,
lambda epoch: epoch >= 4200,
{
(18, 4049): [
18: [
*simple_validators(0, 5),
*simple_validators(6, 9, slashed=True, exit_epoch="8192", withdrawable_epoch="8197"),
],
(19, 4724): [
19: [
*simple_validators(10, 29, slashed=True, exit_epoch="8000", withdrawable_epoch="8417"),
*simple_validators(30, 59, slashed=True, exit_epoch="8417", withdrawable_epoch="8419"),
],
Expand Down Expand Up @@ -572,7 +571,7 @@ def test_predict_midterm_penalty_in_frame_pre_electra(
),
[
# BEFORE ELECTRA
(225, False, 100 * 32 * 10**9, [], [], 0),
(225, lambda _: False, 100 * 32 * 10**9, [], [], 0),
(
# one is slashed
225,
Expand Down Expand Up @@ -672,16 +671,76 @@ def test_predict_midterm_penalty_in_frame_post_electra(
):
result = MidtermSlashingPenalty.predict_midterm_penalty_in_frame_post_electra(
report_ref_epoch=EpochNumber(ref_epoch),
frame_ref_epoch=Mock(),
is_electra_activated=is_electra_activated,
total_balance=total_balance,
slashings=slashings,
midterm_penalized_validators_in_frame=validators_in_frame,
)

assert result == expected_result


def test_midterm_penalty_prediction_in_pectra_transition_can_be_greater_than_before_pectra():
epoch = EpochNumber(10)
slashings = [*([32 * 10**9] * EPOCHS_PER_SLASHINGS_VECTOR)]
total_balance = 100000 * 32 * 10**9

validators_in_frame = [
ValidatorFactory.build(
balance=32 * 10**9,
validator=ValidatorStateFactory.build(
activation_epoch=10,
withdrawable_epoch=(EPOCHS_PER_SLASHINGS_VECTOR // 2) + 50,
slashed=True,
effective_balance=32 * 10**9,
),
),
ValidatorFactory.build(
balance=32 * 10**9,
validator=ValidatorStateFactory.build(
activation_epoch=10,
withdrawable_epoch=(EPOCHS_PER_SLASHINGS_VECTOR // 2) + 30,
slashed=True,
effective_balance=32 * 10**9,
),
),
]

midterm_penalty_prediction_electra_not_activated = (
MidtermSlashingPenalty.predict_midterm_penalty_in_frame_post_electra(
report_ref_epoch=EpochNumber(epoch),
is_electra_activated=lambda _: False,
total_balance=total_balance,
slashings=slashings,
midterm_penalized_validators_in_frame=validators_in_frame,
)
)

midterm_penalty_prediction_electra_activated_second = (
MidtermSlashingPenalty.predict_midterm_penalty_in_frame_post_electra(
report_ref_epoch=EpochNumber(epoch),
is_electra_activated=lambda _: True,
total_balance=total_balance,
slashings=slashings,
midterm_penalized_validators_in_frame=validators_in_frame[:1],
)
)

midterm_penalty_prediction_electra_not_activated_first = (
MidtermSlashingPenalty.predict_midterm_penalty_in_frame_post_electra(
report_ref_epoch=EpochNumber(epoch),
is_electra_activated=lambda _: False,
total_balance=total_balance,
slashings=slashings,
midterm_penalized_validators_in_frame=validators_in_frame[1:],
)
)

assert (
midterm_penalty_prediction_electra_not_activated
<= midterm_penalty_prediction_electra_activated_second + midterm_penalty_prediction_electra_not_activated_first
)


# 50% active validators with 2048 EB and the rest part with 32 EB
half_electra = [
*simple_validators(0, 250_000, effective_balance=MAX_EFFECTIVE_BALANCE),
Expand Down
Loading