import {FAR_FUTURE_EPOCH, MIN_ACTIVATION_BALANCE, PENDING_CONSOLIDATIONS_LIMIT} from "@lodestar/params"; import {electra, ssz} from "@lodestar/types"; import {CachedBeaconStateElectra} from "../types.js"; import {hasEth1WithdrawalCredential} from "../util/capella.js"; import {hasExecutionWithdrawalCredential, isPubkeyKnown, switchToCompoundingValidator} from "../util/electra.js"; import {computeConsolidationEpochAndUpdateChurn} from "../util/epoch.js"; import {getConsolidationChurnLimit, isActiveValidator} from "../util/validator.js"; // TODO Electra: Clean up necessary as there is a lot of overlap with isValidSwitchToCompoundRequest export function processConsolidationRequest( state: CachedBeaconStateElectra, 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; } // 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; } // Verify that source != target, so a consolidation cannot be used as an exit. if (sourceIndex === targetIndex) { 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 withdrawal credentials if ( !hasExecutionWithdrawalCredential(sourceValidator.withdrawalCredentials) || !hasExecutionWithdrawalCredential(targetValidator.withdrawalCredentials) ) { return; } if (Buffer.compare(sourceWithdrawalAddress, sourceAddress) !== 0) { 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; } // 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); // Churn any target excess active balance of target and raise its max if (hasEth1WithdrawalCredential(targetValidator.withdrawalCredentials)) { switchToCompoundingValidator(state, targetIndex); } } /** * Determine if we should set consolidation target validator to compounding credential */ function isValidSwitchToCompoundRequest( state: CachedBeaconStateElectra, 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 (Buffer.compare(sourceWithdrawalAddress, sourceAddress) !== 0) { 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; }