Skip to content

Commit

Permalink
Merge pull request #14 from fradamt/consolidate-through-exit
Browse files Browse the repository at this point in the history
consolidation by moving balance upon exit
  • Loading branch information
mkalinin authored Jan 24, 2024
2 parents c9f1f50 + 419baf2 commit 53582d0
Showing 1 changed file with 127 additions and 10 deletions.
137 changes: 127 additions & 10 deletions specs/_features/maxeb_increase/capella.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,9 @@ 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 = 1
PENDING_CONSOLIDATIONS_LIMIT = uint64(262144) # MAX_CONSOLIDATIONS * SLOTS_PER_EPOCH * 8192

class Configuration(NamedTuple):
PRESET_BASE: str
Expand All @@ -247,7 +249,8 @@ class Configuration(NamedTuple):
SHARD_COMMITTEE_PERIOD: uint64
ETH1_FOLLOW_DISTANCE: uint64
EJECTION_BALANCE: Gwei
MIN_PER_EPOCH_CHURN_LIMIT: uint64
MIN_PER_EPOCH_CHURN_LIMIT: Gwei
MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: Gwei
CHURN_LIMIT_QUOTIENT: uint64
PROPOSER_SCORE_BOOST: uint64
INACTIVITY_SCORE_BIAS: uint64
Expand All @@ -262,6 +265,7 @@ class Configuration(NamedTuple):
CAPELLA_FORK_VERSION: Version
CAPELLA_FORK_EPOCH: Epoch



config = Configuration(
PRESET_BASE="mainnet",
Expand All @@ -275,7 +279,8 @@ class Configuration(NamedTuple):
SHARD_COMMITTEE_PERIOD=uint64(256),
ETH1_FOLLOW_DISTANCE=uint64(2048),
EJECTION_BALANCE=Gwei(16000000000),
MIN_PER_EPOCH_CHURN_LIMIT=uint64(4),
MIN_PER_EPOCH_CHURN_LIMIT=uint64(128),
MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT=uint64(256),
CHURN_LIMIT_QUOTIENT=uint64(65536),
PROPOSER_SCORE_BOOST=uint64(40),
INACTIVITY_SCORE_BIAS=uint64(4),
Expand Down Expand Up @@ -422,6 +427,19 @@ class SignedVoluntaryExit(Container):
message: VoluntaryExit
signature: BLSSignature

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


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

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

class SignedBeaconBlockHeader(Container):
message: BeaconBlockHeader
Expand Down Expand Up @@ -633,6 +651,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 @@ -676,8 +696,10 @@ class BeaconState(Container):
validators: List[Validator, VALIDATOR_REGISTRY_LIMIT]
balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT]
deposit_balance_to_consume: Gwei
exit_balance_to_consume: Gwei # Should be initialized with get_validator_churn_limit(state)
exit_balance_to_consume: Gwei # Should be initialized with get_churn_limit(state)
earliest_exit_epoch: Epoch # Should be initialized with the max([v.exit_epoch for v in state.validators if v.exit_epoch != FAR_FUTURE_EPOCH]) + 1
consolidation_balance_to_consume: Gwei # Should be initialized with get_consolidation_churn_limit(state)
earliest_consolidation_epoch: Epoch
# Randomness
randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR]
# Slashings
Expand All @@ -704,6 +726,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 @@ -1038,20 +1061,32 @@ def get_randao_mix(state: BeaconState, epoch: Epoch) -> Bytes32:
return state.randao_mixes[epoch % EPOCHS_PER_HISTORICAL_VECTOR]




def get_active_validator_indices(state: BeaconState, epoch: Epoch) -> Sequence[ValidatorIndex]:
"""
Return the sequence of active validator indices at ``epoch``.
"""
return [ValidatorIndex(i) for i, v in enumerate(state.validators) if is_active_validator(v, epoch)]


def get_validator_churn_limit(state: BeaconState) -> Gwei:
def get_churn_limit(state: BeaconState) -> Gwei:
"""
Return the validator churn limit for the current epoch.
Return the churn limit for the current epoch.
"""
churn = max(config.MIN_PER_EPOCH_CHURN_LIMIT * MIN_ACTIVATION_BALANCE, get_total_active_balance(state) // config.CHURN_LIMIT_QUOTIENT)
churn = max(config.MIN_PER_EPOCH_CHURN_LIMIT,
get_total_active_balance(state) // config.CHURN_LIMIT_QUOTIENT)
return churn - churn % EFFECTIVE_BALANCE_INCREMENT

def get_activation_exit_churn_limit(state: BeaconState) -> Gwei:
"""
Return the churn limit for the current epoch dedicated to activations and exits.
"""
return min(config.MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT, get_churn_limit(state))

def get_consolidation_churn_limit(state: BeaconState) -> Gwei:
return get_churn_limit(state) - get_activation_exit_churn_limit(state)


def get_seed(state: BeaconState, epoch: Epoch, domain_type: DomainType) -> Bytes32:
"""
Expand Down Expand Up @@ -1160,7 +1195,7 @@ def decrease_balance(state: BeaconState, index: ValidatorIndex, delta: Gwei) ->

def compute_exit_epoch_and_update_churn(state: BeaconState, exit_balance: Gwei) -> Epoch:
earliest_exit_epoch = compute_activation_exit_epoch(get_current_epoch(state))
per_epoch_churn = get_validator_churn_limit(state)
per_epoch_churn = get_activation_exit_churn_limit(state)
# New epoch for exits.
if state.earliest_exit_epoch < earliest_exit_epoch:
state.earliest_exit_epoch = earliest_exit_epoch
Expand All @@ -1176,6 +1211,22 @@ def compute_exit_epoch_and_update_churn(state: BeaconState, exit_balance: Gwei)
state.exit_balance_to_consume = per_epoch_churn - remainder
return state.earliest_exit_epoch

def compute_consolidation_epoch_and_update_churn(state: BeaconState, consolidation_balance: Gwei) -> Epoch:
earliest_consolidation_epoch = compute_activation_exit_epoch(get_current_epoch(state))
per_epoch_consolidation_churn = get_consolidation_churn_limit(state)
# New epoch for consolidations.
if state.earliest_consolidation_epoch < earliest_consolidation_epoch:
state.earliest_consolidation_epoch = earliest_consolidation_epoch
state.consolidation_balance_to_consume = per_epoch_consolidation_churn
# Consolidation fits in the current earliest consolidation epoch.
if consolidation_balance <= state.consolidation_balance_to_consume:
state.consolidation_balance_to_consume -= consolidation_balance
else: # Consolidation doesn't fit in the current earliest epoch.
balance_to_process = consolidation_balance - state.consolidation_balance_to_consume
additional_epochs, remainder = divmod(balance_to_process, per_epoch_consolidation_churn)
state.earliest_consolidation_epoch += additional_epochs + 1
state.consolidation_balance_to_consume = per_epoch_consolidation_churn - remainder
return state.earliest_consolidation_epoch

def initiate_validator_exit(state: BeaconState, index: ValidatorIndex) -> None:
"""
Expand Down Expand Up @@ -1324,6 +1375,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 @@ -1611,7 +1663,7 @@ def process_eth1_data_reset(state: BeaconState) -> None:


def process_pending_balance_deposits(state: BeaconState) -> None:
state.deposit_balance_to_consume += get_validator_churn_limit(state)
state.deposit_balance_to_consume += get_activation_exit_churn_limit(state)
next_pending_deposit_index = 0
for pending_balance_deposit in state.pending_balance_deposits:
if state.deposit_balance_to_consume < pending_balance_deposit.amount:
Expand All @@ -1624,14 +1676,44 @@ def process_pending_balance_deposits(state: BeaconState) -> None:
state.pending_balance_deposits = state.pending_balance_deposits[next_pending_deposit_index:]


def get_active_balance(state: BeaconState, validator: Validator) -> Gwei:
active_balance_ceil = MIN_ACTIVATION_BALANCE if has_eth1_withdrawal_credential(validator) else MAX_EFFECTIVE_BALANCE
return min(state.balances[validator.index], active_balance_ceil)

def apply_pending_consolidation(state: BeaconState, pending_consolidation: PendingConsolidation) -> None:
source_validator = state.validators[pending_consolidation.source_index]
target_validator = state.validators[pending_consolidation.target_index]
# Move active balance to target. Excess balance will be withdrawn.
active_balance = get_active_balance(state, source_validator)
state.balances[source_validator.index] -= active_balance
state.balances[target_validator.index] += active_balance


def process_pending_consolidations(state: BeaconState) -> None:
next_pending_consolidation = 0
for pending_consolidation in state.pending_consolidations:
source_validator = state.validators[pending_consolidation.source_index]
if source_validator.withdrawable_epoch > get_current_epoch(state):
break

if not source_validator.slashed:
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):
balance = state.balances[index]
HYSTERESIS_INCREMENT = uint64(EFFECTIVE_BALANCE_INCREMENT // HYSTERESIS_QUOTIENT)
DOWNWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_DOWNWARD_MULTIPLIER
UPWARD_THRESHOLD = HYSTERESIS_INCREMENT * HYSTERESIS_UPWARD_MULTIPLIER
EFFECTIVE_BALANCE_LIMIT = MAX_EFFECTIVE_BALANCE if has_compounding_withdrawal_credential(validator) else MIN_ACTIVATION_BALANCE
EFFECTIVE_BALANCE_LIMIT = MAX_EFFECTIVE_BALANCE if has_compounding_withdrawal_credential(validator) else MIN_ACTIVATION_BALANCE
if (
balance + DOWNWARD_THRESHOLD < validator.effective_balance
or validator.effective_balance + UPWARD_THRESHOLD < balance
Expand Down Expand Up @@ -1732,6 +1814,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 @@ -1928,6 +2011,40 @@ def process_voluntary_exit(state: BeaconState, signed_voluntary_exit: SignedVolu
initiate_validator_exit(state, voluntary_exit.validator_index)


def process_consolidation(state: BeaconState, signed_consolidation: SignedConsolidation) -> None:
assert(len(state.pending_consolidations) < PENDING_CONSOLIDATIONS_LIMIT)
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
assert is_active_validator(source_validator)
assert is_active_validator(target_validator)
# Verify exits for source and target have not been initiated
assert source_validator.exit_epoch == FAR_FUTURE_EPOCH
assert target_validator.exit_epoch == FAR_FUTURE_EPOCH
# Consolidations must specify an epoch when they become valid; they are not valid before then
assert get_current_epoch(state) >= consolidation.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)

# Initiate source validator exit and append pending consolidation
active_balance = get_active_balance(state, source_validator)
source_validator.exit_epoch = compute_consolidation_epoch_and_update_churn(state, active_balance)
source_validator.withdrawable_epoch = Epoch(source_validator.exit_epoch + config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY)
state.pending_consolidations.append(PendingConsolidation(source_index = source_validator.index,
target_index = target_validator.index))


def is_previous_epoch_justified(store: Store) -> bool:
current_slot = get_current_slot(store)
current_epoch = compute_epoch_at_slot(current_slot)
Expand Down

0 comments on commit 53582d0

Please sign in to comment.