Skip to content

Commit

Permalink
feat: calculate midterm by projected frame ref epoch for fork
Browse files Browse the repository at this point in the history
  • Loading branch information
vgorkavenko committed Jan 9, 2025
1 parent 30e0a1e commit f0a217a
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 11 deletions.
1 change: 1 addition & 0 deletions src/services/bunker.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def is_bunker_mode(
logger.info({"msg": "Bunker ON. CL rebase is negative"})
return True

MidtermSlashingPenalty.cl_spec = self.w3.cc.get_config_spec()
high_midterm_slashing_penalty = MidtermSlashingPenalty.is_high_midterm_slashing_penalty(
blockstamp, frame_config, chain_config, all_validators, lido_validators, current_report_cl_rebase, last_report_ref_slot
)
Expand Down
63 changes: 52 additions & 11 deletions src/services/bunker_cases/midterm_slashing_penalty.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,23 @@
MIN_VALIDATOR_WITHDRAWABILITY_DELAY,
PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX,
EFFECTIVE_BALANCE_INCREMENT,
MAX_EFFECTIVE_BALANCE,
)
from src.modules.submodules.types import FrameConfig, ChainConfig
from src.providers.consensus.types import Validator
from src.providers.consensus.types import Validator, BeaconSpecResponse
from src.types import EpochNumber, Gwei, ReferenceBlockStamp, FrameNumber, SlotNumber
from src.utils.validator_state import calculate_total_active_effective_balance
from src.web3py.extensions.lido_validators import LidoValidator


logger = logging.getLogger(__name__)

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


class MidtermSlashingPenalty:

cl_spec: BeaconSpecResponse

@staticmethod
def is_high_midterm_slashing_penalty(
blockstamp: ReferenceBlockStamp,
Expand Down Expand Up @@ -131,11 +135,11 @@ def get_lido_validators_with_future_midterm_epoch(
ref_epoch: EpochNumber,
frame_config: FrameConfig,
lido_validators: list[LidoValidator],
) -> dict[FrameNumber, list[LidoValidator]]:
) -> SlashedValidatorsFrameBuckets:
"""
Put validators to frame buckets by their midterm penalty epoch to calculate penalties impact in each frame
"""
buckets: dict[FrameNumber, list[LidoValidator]] = defaultdict(list[LidoValidator])
buckets: SlashedValidatorsFrameBuckets = defaultdict(list[LidoValidator])
for validator in lido_validators:
if not validator.validator.slashed:
# We need only slashed validators
Expand All @@ -145,7 +149,8 @@ def get_lido_validators_with_future_midterm_epoch(
# We need midterm penalties only from future frames
continue
frame_number = MidtermSlashingPenalty.get_frame_by_epoch(midterm_penalty_epoch, frame_config)
buckets[frame_number].append(validator)
frame_ref_epoch = MidtermSlashingPenalty.get_ref_epoch_by_frame(frame_number, frame_config)
buckets[(frame_number, frame_ref_epoch)].append(validator)

return buckets

Expand All @@ -154,23 +159,26 @@ def get_future_midterm_penalty_sum_in_frames(
ref_epoch: EpochNumber,
all_slashed_validators: list[Validator],
total_balance: Gwei,
per_frame_validators: dict[FrameNumber, list[LidoValidator]],
per_frame_validators: SlashedValidatorsFrameBuckets,
) -> 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, frame_ref_epoch), validators_in_future_frame in per_frame_validators.items():
per_frame_midterm_penalty_sum[frame_number] = MidtermSlashingPenalty.predict_midterm_penalty_in_frame(
ref_epoch,
frame_ref_epoch,
all_slashed_validators,
total_balance,
validators_in_future_frame
)

return per_frame_midterm_penalty_sum

@staticmethod
@classmethod
def predict_midterm_penalty_in_frame(
ref_epoch: EpochNumber,
cls,
report_ref_epoch: EpochNumber,
frame_ref_epoch: EpochNumber,
all_slashed_validators: list[Validator],
total_balance: Gwei,
midterm_penalized_validators_in_frame: list[LidoValidator]
Expand All @@ -180,15 +188,43 @@ def predict_midterm_penalty_in_frame(
for validator in midterm_penalized_validators_in_frame:
midterm_penalty_epoch = MidtermSlashingPenalty.get_midterm_penalty_epoch(validator)
bound_slashed_validators = MidtermSlashingPenalty.get_bound_with_midterm_epoch_slashed_validators(
ref_epoch, all_slashed_validators, EpochNumber(midterm_penalty_epoch)
report_ref_epoch, all_slashed_validators, EpochNumber(midterm_penalty_epoch)
)
penalty_in_frame += MidtermSlashingPenalty.get_validator_midterm_penalty(

if frame_ref_epoch < int(cls.cl_spec.ELECTRA_FORK_EPOCH):
penalty_in_frame += MidtermSlashingPenalty.get_validator_midterm_penalty(
validator, len(bound_slashed_validators), total_balance
)
continue

penalty_in_frame += MidtermSlashingPenalty.get_validator_midterm_penalty_electra(
validator, bound_slashed_validators, total_balance
)
return Gwei(penalty_in_frame)

@staticmethod
def get_validator_midterm_penalty(
validator: LidoValidator,
bound_slashed_validators_count: int,
total_balance: Gwei
) -> Gwei:
"""
Calculate midterm penalty for particular validator
https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#slashings
"""
# We don't know which balance was at slashing epoch, so we make a pessimistic assumption that it was 32 ETH
slashings = Gwei(bound_slashed_validators_count * MAX_EFFECTIVE_BALANCE)
adjusted_total_slashing_balance = min(
slashings * PROPORTIONAL_SLASHING_MULTIPLIER_BELLATRIX, total_balance
)
effective_balance = int(validator.validator.effective_balance)
penalty_numerator = effective_balance // EFFECTIVE_BALANCE_INCREMENT * adjusted_total_slashing_balance
penalty = penalty_numerator // total_balance * EFFECTIVE_BALANCE_INCREMENT

return Gwei(penalty)

@staticmethod
def get_validator_midterm_penalty_electra(
validator: LidoValidator,
bound_slashed_validators: list[Validator],
total_balance: Gwei,
Expand Down Expand Up @@ -250,6 +286,11 @@ def get_frame_by_epoch(epoch: EpochNumber, frame_config: FrameConfig) -> FrameNu
"""Get oracle report frame index by epoch"""
return FrameNumber((epoch - frame_config.initial_epoch) // frame_config.epochs_per_frame)

@staticmethod
def get_ref_epoch_by_frame(frame_number: FrameNumber, frame_config: FrameConfig) -> EpochNumber:
"""Get frame reference epoch"""
return EpochNumber(frame_number * frame_config.epochs_per_frame + frame_config.initial_epoch)

@staticmethod
def get_midterm_penalty_epoch(validator: Validator) -> EpochNumber:
"""https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#slashings"""
Expand Down

0 comments on commit f0a217a

Please sign in to comment.