Skip to content

Instantly share code, notes, and snippets.

@ydm
Created May 14, 2025 13:13
Show Gist options
  • Save ydm/6dca3f65bd82efcfa3a7fe2e2c491ac5 to your computer and use it in GitHub Desktop.
Save ydm/6dca3f65bd82efcfa3a7fe2e2c491ac5 to your computer and use it in GitHub Desktop.
def get_balance_churn_limit(state: BeaconState) -> Gwei:
"""
Return the churn limit for the current epoch.
"""
churn = max(
MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA,
get_total_active_balance(state) // 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(MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT, get_balance_churn_limit(state))
def process_deposit(state: BeaconState, deposit: Deposit) -> None:
# Verify the Merkle branch
assert is_valid_merkle_branch(
leaf=hash_tree_root(deposit.data),
branch=deposit.proof,
depth=DEPOSIT_CONTRACT_TREE_DEPTH + 1, # Add 1 for the List length mix-in
index=state.eth1_deposit_index,
root=state.eth1_data.deposit_root,
)
# Deposits must be processed in order
state.eth1_deposit_index += 1
# [Modified in Electra:EIP7251]
apply_deposit(
state=state,
pubkey=deposit.data.pubkey,
withdrawal_credentials=deposit.data.withdrawal_credentials,
amount=deposit.data.amount,
signature=deposit.data.signature,
)
def apply_deposit(state: BeaconState,
pubkey: BLSPubkey,
withdrawal_credentials: Bytes32,
amount: uint64,
signature: BLSSignature) -> None:
validator_pubkeys = [v.pubkey for v in state.validators]
if pubkey not in validator_pubkeys:
# Verify the deposit signature (proof of possession) which is not checked by the deposit contract
if is_valid_deposit_signature(pubkey, withdrawal_credentials, amount, signature):
add_validator_to_registry(state, pubkey, withdrawal_credentials, Gwei(0)) # [Modified in Electra:EIP7251]
else:
return
# Increase balance by deposit amount
# [Modified in Electra:EIP7251]
state.pending_deposits.append(PendingDeposit(
pubkey=pubkey,
withdrawal_credentials=withdrawal_credentials,
amount=amount,
signature=signature,
slot=GENESIS_SLOT # Use GENESIS_SLOT to distinguish from a pending deposit request
))
def process_deposit_request(state: BeaconState, deposit_request: DepositRequest) -> None:
# Set deposit request start index
if state.deposit_requests_start_index == UNSET_DEPOSIT_REQUESTS_START_INDEX:
state.deposit_requests_start_index = deposit_request.index
# Create pending deposit
state.pending_deposits.append(PendingDeposit(
pubkey=deposit_request.pubkey,
withdrawal_credentials=deposit_request.withdrawal_credentials,
amount=deposit_request.amount,
signature=deposit_request.signature,
slot=state.slot,
))
def process_pending_deposits(state: BeaconState) -> None:
next_epoch = Epoch(get_current_epoch(state) + 1)
available_for_processing = state.deposit_balance_to_consume + get_activation_exit_churn_limit(state)
processed_amount = 0
next_deposit_index = 0
deposits_to_postpone = []
is_churn_limit_reached = False
finalized_slot = compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)
for deposit in state.pending_deposits:
# Do not process deposit requests if Eth1 bridge deposits are not yet applied.
if (
# Is deposit request
deposit.slot > GENESIS_SLOT and
# There are pending Eth1 bridge deposits
state.eth1_deposit_index < state.deposit_requests_start_index
):
break
# Check if deposit has been finalized, otherwise, stop processing.
if deposit.slot > finalized_slot:
break
# Check if number of processed deposits has not reached the limit, otherwise, stop processing.
if next_deposit_index >= MAX_PENDING_DEPOSITS_PER_EPOCH: # MAX_PENDING_DEPOSITS_PER_EPOCH = 16
break
# Read validator state
is_validator_exited = False
is_validator_withdrawn = False
validator_pubkeys = [v.pubkey for v in state.validators]
if deposit.pubkey in validator_pubkeys:
validator = state.validators[ValidatorIndex(validator_pubkeys.index(deposit.pubkey))]
is_validator_exited = validator.exit_epoch < FAR_FUTURE_EPOCH
is_validator_withdrawn = validator.withdrawable_epoch < next_epoch
if is_validator_withdrawn:
# Deposited balance will never become active. Increase balance but do not consume churn
apply_pending_deposit(state, deposit)
elif is_validator_exited:
# Validator is exiting, postpone the deposit until after withdrawable epoch
deposits_to_postpone.append(deposit)
else:
# Check if deposit fits in the churn, otherwise, do no more deposit processing in this epoch.
is_churn_limit_reached = processed_amount + deposit.amount > available_for_processing
if is_churn_limit_reached:
break
# Consume churn and apply deposit.
processed_amount += deposit.amount
apply_pending_deposit(state, deposit)
# Regardless of how the deposit was handled, we move on in the queue.
next_deposit_index += 1
state.pending_deposits = state.pending_deposits[next_deposit_index:] + deposits_to_postpone
# Accumulate churn only if the churn limit has been hit.
if is_churn_limit_reached:
state.deposit_balance_to_consume = available_for_processing - processed_amount
else:
state.deposit_balance_to_consume = Gwei(0)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment