Skip to content
Closed
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
163 changes: 157 additions & 6 deletions specs/_features/maxeb_increase/capella.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,10 @@ def floorlog2(x: int) -> uint64:
MAX_BLS_TO_EXECUTION_CHANGES = 16
MAX_WITHDRAWALS_PER_PAYLOAD = uint64(16)
MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP = 16384

DOMAIN_CONSOLIDATION = DomainType('0x0B000000')
MAX_CONSOLIDATIONS = 4
PENDING_CONSOLIDATIONS_LIMIT = uint64(1048576) # MAX_CONSOLIDATIONS * SLOTS_PER_EPOCH * 8192
UNSET_CONSOLIDATED_TO = ValidatorIndex(2**64 - 1)

class Configuration(NamedTuple):
PRESET_BASE: str
Expand Down Expand Up @@ -321,6 +324,8 @@ class Validator(Container):
activation_epoch: Epoch
exit_epoch: Epoch
withdrawable_epoch: Epoch # When validator can withdraw funds
# TODO: may compress into some other validator field
consolidated_to: ValidatorIndex


class AttestationData(Container):
Expand Down Expand Up @@ -426,6 +431,22 @@ class SignedVoluntaryExit(Container):
signature: BLSSignature


class Consolidation(Container):
source_index: ValidatorIndex
target_index: ValidatorIndex


class SignedConsolidation(Container):
message: Consolidation
signature: BLSSignature


class PendingConsolidation(Container):
source_index: ValidatorIndex
target_index: ValidatorIndex
epoch: Epoch


class SignedBeaconBlockHeader(Container):
message: BeaconBlockHeader
signature: BLSSignature
Expand Down Expand Up @@ -636,6 +657,8 @@ class BeaconBlockBody(Container):
execution_payload: ExecutionPayload
# Capella operations
bls_to_execution_changes: List[SignedBLSToExecutionChange, MAX_BLS_TO_EXECUTION_CHANGES] # [New in Capella]
# MAXEB operations
consolidations: List[SignedConsolidation, MAX_CONSOLIDATIONS] # [New in MAXEB]


class BeaconBlock(Container):
Expand Down Expand Up @@ -707,6 +730,7 @@ class BeaconState(Container):
historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT] # [New in Capella]
pending_balance_deposits: List[PendingBalanceDeposit]
pending_partial_withdrawals: List[PartialWithdrawal]
pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT]


@dataclass(eq=True, frozen=True)
Expand Down Expand Up @@ -1327,6 +1351,7 @@ def process_epoch(state: BeaconState) -> None:
process_slashings(state)
process_eth1_data_reset(state)
process_pending_balance_deposits(state)
process_pending_consolidations(state)
process_effective_balance_updates(state)
process_slashings_reset(state)
process_randao_mixes_reset(state)
Expand Down Expand Up @@ -1627,6 +1652,86 @@ def process_pending_balance_deposits(state: BeaconState) -> None:
state.pending_balance_deposits = state.pending_balance_deposits[next_pending_deposit_index:]


def apply_pending_consolidation(state: BeaconState, pending_consolidation: PendingConsolidation) -> None:
current_epoch = get_current_epoch(state)

# Resolve the target of consolidation
target_validator = state.validators[pending_consolidation.target_index]
source_validator = state.validators[pending_consolidation.source_index]

# Abort consolidation if the target has been consolidated
if target_validator.consolidated_to != UNSET_CONSOLIDATED_TO:
return

# Apply consolidation
source_validator.consolidated_to = pending_consolidation.target_index

# Don't move the balance and slash the target if the source was slashed
Comment thread
fradamt marked this conversation as resolved.
if is_slashed(source_validator):
if is_slashed_proposer(source_validator):
penalty = PROPOSER_EQUIVOCATION_PENALTY_FACTOR * EFFECTIVE_BALANCE_INCREMENT
decrease_balance(state, target_validator, penalty)
initiate_validator_exit(state, pending_consolidation.target_index)
target_validator.slashed = add_flag(target_validator.slashed, SLASHED_PROPOSER_FLAG_INDEX)

if is_slashed_attester(source_validator):
if is_attester_slashable_validator(target_validator, current_epoch):
slash_validator(state, pending_consolidation.target_index)

return

# Don't move the balance and slash the source if the target was slashed
if is_slashed(target_validator):
if is_slashed_proposer(target_validator):
penalty = PROPOSER_EQUIVOCATION_PENALTY_FACTOR * EFFECTIVE_BALANCE_INCREMENT
decrease_balance(state, source_validator, penalty)
initiate_validator_exit(state, pending_consolidation.source_index)
source_validator.slashed = add_flag(source_validator.slashed, SLASHED_PROPOSER_FLAG_INDEX)

if is_slashed_attester(target_validator):
if is_attester_slashable_validator(source_validator, current_epoch):
slash_validator(state, pending_consolidation.source_index)

return

# Don't move the balance if the source withdrew
if source_validator.withdrawable_epoch < current_epoch:
return
Comment thread
fradamt marked this conversation as resolved.

# Move active balance
active_balance_ceil = MIN_ACTIVATION_BALANCE if has_eth1_withdrawal_credential(source_validator) else MAX_EFFECTIVE_BALANCE
active_balance = min(state.balances[pending_consolidation.source_index], active_balance_ceil)
state.balances[pending_consolidation.target_index] += active_balance
# Excess balance above current active balance ceil will be withdrawn
state.balances[pending_consolidation.source_index] = state.balances[pending_consolidation.source_index] - active_balance

# Update target exit and withdrawable epochs if the target exited
if target_validator.exit_epoch != FAR_FUTURE_EPOCH:
target_validator.exit_epoch = compute_exit_epoch_and_update_churn(state, active_balance)
target_validator.withdrawable_epoch = Epoch(target_validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY)

# Balance is not exiting the active set, do not apply churn
if source_validator.exit_epoch > current_epoch:
source_validator.exit_epoch = current_epoch
Comment on lines +1714 to +1715
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if source_validator.exit_epoch > current_epoch:
source_validator.exit_epoch = current_epoch
source_validator.exit_epoch = min(source_validator.exit_epoch, compute_activation_exit_epoch(current_epoch))

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


# Reset withdrawable epoch when it is closer than expected
withdrawable_epoch = Epoch(source_validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY)
if source_validator.withdrawable_epoch < withdrawable_epoch:
source_validator.withdrawable_epoch = withdrawable_epoch


def process_pending_consolidations(state: BeaconState) -> None:
next_pending_consolidation = 0
for pending_consolidation in state.pending_consolidations:
if pending_consolidation.epoch >= state.finalized_checkpoint.epoch:
break

apply_pending_consolidation(state, pending_consolidation)
next_pending_consolidation += 1

state.pending_consolidations = state.pending_consolidations[next_pending_consolidation:]


def process_effective_balance_updates(state: BeaconState) -> None:
# Update effective balances with hysteresis
for index, validator in enumerate(state.validators):
Expand Down Expand Up @@ -1735,6 +1840,7 @@ def for_ops(operations: Sequence[Any], fn: Callable[[BeaconState, Any], None]) -
for_ops(body.voluntary_exits, process_voluntary_exit)
for_ops(body.bls_to_execution_changes, process_bls_to_execution_change) # [New in Capella]
for_ops(body.execution_payload.withdraw_request, process_execution_layer_withdraw_request)
for_ops(body.consolidations, process_consolidation)


def process_execution_layer_withdraw_request(
Expand Down Expand Up @@ -1784,11 +1890,13 @@ def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSla
# Verify header slots match
assert header_1.slot == header_2.slot
# Verify header proposer indices match
assert header_1.proposer_index == header_2.proposer_index
header_1_proposer_index = resolve_consolidated_to(state, header_1.proposer_index)
header_2_proposer_index = resolve_consolidated_to(state, header_2.proposer_index)
assert header_1_proposer_index == header_2_proposer_index
# Verify the headers are different
assert header_1 != header_2
# Verify the proposer is slashable
proposer = state.validators[header_1.proposer_index]
proposer = state.validators[header_1_proposer_index]
assert proposer.activation_epoch <= get_current_epoch(state) and not is_slashed_proposer(proposer)
# Verify signatures
for signed_header in (proposer_slashing.signed_header_1, proposer_slashing.signed_header_2):
Expand All @@ -1798,8 +1906,8 @@ def process_proposer_slashing(state: BeaconState, proposer_slashing: ProposerSla

# Apply penalty
penalty = PROPOSER_EQUIVOCATION_PENALTY_FACTOR * EFFECTIVE_BALANCE_INCREMENT
decrease_balance(state, header_1.proposer_index, penalty)
initiate_validator_exit(state, header_1.proposer_index)
decrease_balance(state, header_1_proposer_index, penalty)
initiate_validator_exit(state, header_1_proposer_index)
proposer.slashed = add_flag(proposer.slashed, SLASHED_PROPOSER_FLAG_INDEX)

# Apply proposer and whistleblower rewards
Expand All @@ -1815,7 +1923,8 @@ def process_attester_slashing(state: BeaconState, attester_slashing: AttesterSla
assert is_valid_indexed_attestation(state, attestation_2)

slashed_any = False
indices = set(attestation_1.attesting_indices).intersection(attestation_2.attesting_indices)
indices = set([resolve_consolidated_to(i) for i in attestation_1.attesting_indices]).intersection(
[resolve_consolidated_to(i) for i in attestation_2.attesting_indices])
for index in sorted(indices):
if is_attester_slashable_validator(state.validators[index], get_current_epoch(state)):
slash_validator(state, index)
Expand Down Expand Up @@ -1939,6 +2048,44 @@ def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVolu
initiate_validator_exit(state, voluntary_exit.validator_index)


def resolve_consolidated_to(state: BeaconState, index: ValidatorIndex) -> ValidatorIndex:
if state.validators[index].consolidated_to != UNSET_CONSOLIDATED_TO:
# Recursively resolve consolidated index
return resolve_consolidated_to(state, state.validators[index].consolidated_to)
else:
return index


def process_consolidation(state: BeaconState, signed_consolidation: SignedConsolidation) -> None:
consolidation = signed_consolidation.message
target_validator = state.validators[consolidation.target_index]
source_validator = state.validators[consolidation.source_index]

# Verify the source and the target are active and not yet exited
assert is_active_validator(source_validator)
assert is_active_validator(target_validator)
assert source_validator.exit_epoch == FAR_FUTURE_EPOCH
assert target_validator.exit_epoch == FAR_FUTURE_EPOCH

# Verify the source and the target have Execution layer withdrawal credentials
assert source_validator.withdrawal_credentials[:1] in (ETH1_ADDRESS_WITHDRAWAL_PREFIX, COMPOUNDING_WITHDRAWAL_PREFIX)
assert target_validator.withdrawal_credentials[:1] in (ETH1_ADDRESS_WITHDRAWAL_PREFIX, COMPOUNDING_WITHDRAWAL_PREFIX)
# Verify the same withdrawal address
assert source_validator.withdrawal_credentials[1:] == target_validator.withdrawal_credentials[1:]

# Verify consolidation is signed by the source and the target
domain = compute_domain(DOMAIN_CONSOLIDATION, genesis_validators_root=state.genesis_validators_root)
signing_root = compute_signing_root(consolidation, domain)
pubkeys = [source_validator.pubkey, target_validator.pubkey]
assert bls.FastAggregateVerify(pubkeys, signing_root, signed_consolidation.signature)

# Queue consolidation for further processing
consolidation_epoch = compute_activation_exit_epoch(get_current_epoch(state))
state.pending_consolidations.append(PendingConsolidation(source_index = consolidation.source_index,
target_index = consolidation.target_index,
epoch = consolidation_epoch))


def is_previous_epoch_justified(store: Store) -> bool:
current_slot = get_current_slot(store)
current_epoch = compute_epoch_at_slot(current_slot)
Expand Down Expand Up @@ -2976,6 +3123,10 @@ def is_slashed_attester(validator: Validator) -> bool:
return has_flag(ParticipationFlags(validator.slashed), SLASHED_ATTESTER_FLAG_INDEX)


def is_slashed(validator: Validator) -> bool:
return is_slashed_attester(validator) or is_slashed_proposer(validator)


def get_next_sync_committee_indices(state: BeaconState) -> Sequence[ValidatorIndex]:
"""
Return the sync committee indices, with possible duplicates, for the next sync committee.
Expand Down