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

consolidation by moving balance upon exit #14

Merged
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
137 changes: 127 additions & 10 deletions specs/_features/maxeb_increase/capella.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,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 @@ -250,7 +252,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 @@ -265,6 +268,7 @@ class Configuration(NamedTuple):
CAPELLA_FORK_VERSION: Version
CAPELLA_FORK_EPOCH: Epoch



config = Configuration(
PRESET_BASE="mainnet",
Expand All @@ -278,7 +282,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 @@ -425,6 +430,19 @@ class SignedVoluntaryExit(Container):
message: VoluntaryExit
signature: BLSSignature

class Consolidation(Container):
source_index: ValidatorIndex
mkalinin marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -636,6 +654,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 @@ -679,8 +699,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 @@ -707,6 +729,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 @@ -1041,20 +1064,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 @@ -1163,7 +1198,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 @@ -1179,6 +1214,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 @@ -1327,6 +1378,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 @@ -1614,7 +1666,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 @@ -1627,14 +1679,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):
mkalinin marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -1735,6 +1817,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 @@ -1939,6 +2022,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
mkalinin marked this conversation as resolved.
Show resolved Hide resolved
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