import {FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, PENDING_CONSOLIDATIONS_LIMIT} from "@lodestar/params"; import {electra, ssz} from "@lodestar/types"; import {byteArrayEquals} from "@lodestar/utils"; import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js"; import {hasEth1WithdrawalCredential} from "../util/capella.js"; import { hasCompoundingWithdrawalCredential, hasExecutionWithdrawalCredential, isPubkeyKnown, switchToCompoundingValidator, } from "../util/electra.js"; import {computeConsolidationEpochAndUpdateChurn} from "../util/epoch.js"; import {getConsolidationChurnLimit, getPendingBalanceToWithdraw, isActiveValidator} from "../util/validator.js"; // TODO Electra: Clean up necessary as there is a lot of overlap with isValidSwitchToCompoundRequest export function processConsolidationRequest( state: CachedBeaconStateElectra | CachedBeaconStateGloas, consolidationRequest: electra.ConsolidationRequest ): void { const {sourcePubkey, targetPubkey, sourceAddress} = consolidationRequest; if (!isPubkeyKnown(state, sourcePubkey) || !isPubkeyKnown(state, targetPubkey)) { return; } const sourceIndex = state.epochCtx.getValidatorIndex(sourcePubkey); const targetIndex = state.epochCtx.getValidatorIndex(targetPubkey); if (sourceIndex === null || targetIndex === null) { return; } if (isValidSwitchToCompoundRequest(state, consolidationRequest)) { switchToCompoundingValidator(state, sourceIndex); // Early return since we have already switched validator to compounding return; } // Verify that source != target, so a consolidation cannot be used as an exit. if (sourceIndex === targetIndex) { return; } // If the pending consolidations queue is full, consolidation requests are ignored if (state.pendingConsolidations.length >= PENDING_CONSOLIDATIONS_LIMIT) { return; } // If there is too little available consolidation churn limit, consolidation requests are ignored if (getConsolidationChurnLimit(state.epochCtx) <= MIN_ACTIVATION_BALANCE) { return; } const sourceValidator = state.validators.get(sourceIndex); const targetValidator = state.validators.getReadonly(targetIndex); const sourceWithdrawalAddress = sourceValidator.withdrawalCredentials.subarray(12); const currentEpoch = state.epochCtx.epoch; // Verify source withdrawal credentials const hasCorrectCredential = hasExecutionWithdrawalCredential(sourceValidator.withdrawalCredentials); const isCorrectSourceAddress = byteArrayEquals(sourceWithdrawalAddress, sourceAddress); if (!(hasCorrectCredential && isCorrectSourceAddress)) { return; } // Verify that target has compounding withdrawal credentials if (!hasCompoundingWithdrawalCredential(targetValidator.withdrawalCredentials)) { return; } // Verify the source and the target are active if (!isActiveValidator(sourceValidator, currentEpoch) || !isActiveValidator(targetValidator, currentEpoch)) { return; } // Verify exits for source and target have not been initiated if (sourceValidator.exitEpoch !== FAR_FUTURE_EPOCH || targetValidator.exitEpoch !== FAR_FUTURE_EPOCH) { return; } // Verify the source has been active long enough if (currentEpoch < sourceValidator.activationEpoch + state.config.SHARD_COMMITTEE_PERIOD) { return; } // Verify the source has no pending withdrawals in the queue if (getPendingBalanceToWithdraw(state, sourceIndex) > 0) { return; } // Initiate source validator exit and append pending consolidation // TODO Electra: See if we can get rid of big int const exitEpoch = computeConsolidationEpochAndUpdateChurn(state, BigInt(sourceValidator.effectiveBalance)); sourceValidator.exitEpoch = exitEpoch; sourceValidator.withdrawableEpoch = exitEpoch + state.config.MIN_VALIDATOR_WITHDRAWABILITY_DELAY; const pendingConsolidation = ssz.electra.PendingConsolidation.toViewDU({ sourceIndex, targetIndex, }); state.pendingConsolidations.push(pendingConsolidation); } /** * Determine if we should set consolidation target validator to compounding credential */ function isValidSwitchToCompoundRequest( state: CachedBeaconStateElectra | CachedBeaconStateGloas, consolidationRequest: electra.ConsolidationRequest ): boolean { const {sourcePubkey, targetPubkey, sourceAddress} = consolidationRequest; const sourceIndex = state.epochCtx.getValidatorIndex(sourcePubkey); const targetIndex = state.epochCtx.getValidatorIndex(targetPubkey); // Verify pubkey exists if (sourceIndex === null) { // this check is mainly to make the compiler happy, pubkey is checked by the consumer already return false; } // Switch to compounding requires source and target be equal if (sourceIndex !== targetIndex) { return false; } const sourceValidator = state.validators.getReadonly(sourceIndex); const sourceWithdrawalAddress = sourceValidator.withdrawalCredentials.subarray(12); // Verify request has been authorized if (!byteArrayEquals(sourceWithdrawalAddress, sourceAddress)) { return false; } // Verify source withdrawal credentials if (!hasEth1WithdrawalCredential(sourceValidator.withdrawalCredentials)) { return false; } // Verify the source is active if (!isActiveValidator(sourceValidator, state.epochCtx.epoch)) { return false; } // Verify exit for source has not been initiated if (sourceValidator.exitEpoch !== FAR_FUTURE_EPOCH) { return false; } return true; }