import {COMPOUNDING_WITHDRAWAL_PREFIX, GENESIS_SLOT, MIN_ACTIVATION_BALANCE} from "@lodestar/params"; import {ValidatorIndex, ssz} from "@lodestar/types"; import {G2_POINT_AT_INFINITY} from "../constants/constants.js"; import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js"; import {hasEth1WithdrawalCredential} from "./capella.js"; export function hasCompoundingWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean { return withdrawalCredentials[0] === COMPOUNDING_WITHDRAWAL_PREFIX; } export function hasExecutionWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean { return ( hasCompoundingWithdrawalCredential(withdrawalCredentials) || hasEth1WithdrawalCredential(withdrawalCredentials) ); } export function switchToCompoundingValidator( state: CachedBeaconStateElectra | CachedBeaconStateGloas, index: ValidatorIndex ): void { const validator = state.validators.get(index); // directly modifying the byte leads to ssz missing the modification resulting into // wrong root compute, although slicing can be avoided but anyway this is not going // to be a hot path so its better to clean slice and avoid side effects const newWithdrawalCredentials = Uint8Array.prototype.slice.call( validator.withdrawalCredentials, 0, validator.withdrawalCredentials.length ); newWithdrawalCredentials[0] = COMPOUNDING_WITHDRAWAL_PREFIX; validator.withdrawalCredentials = newWithdrawalCredentials; queueExcessActiveBalance(state, index); } export function queueExcessActiveBalance( state: CachedBeaconStateElectra | CachedBeaconStateGloas, index: ValidatorIndex ): void { const balance = state.balances.get(index); if (balance > MIN_ACTIVATION_BALANCE) { const validator = state.validators.getReadonly(index); const excessBalance = balance - MIN_ACTIVATION_BALANCE; state.balances.set(index, MIN_ACTIVATION_BALANCE); const pendingDeposit = ssz.electra.PendingDeposit.toViewDU({ pubkey: validator.pubkey, withdrawalCredentials: validator.withdrawalCredentials, amount: excessBalance, // Use bls.G2_POINT_AT_INFINITY as a signature field placeholder signature: G2_POINT_AT_INFINITY, // Use GENESIS_SLOT to distinguish from a pending deposit request slot: GENESIS_SLOT, }); state.pendingDeposits.push(pendingDeposit); } } /** * Since we share pubkey2index, pubkey maybe added by other epoch transition but we don't have that validator in this state */ export function isPubkeyKnown(state: CachedBeaconStateElectra | CachedBeaconStateGloas, pubkey: Uint8Array): boolean { return isValidatorKnown(state, state.epochCtx.getValidatorIndex(pubkey)); } /** * Since we share pubkey2index, validatorIndex maybe not null but we don't have that validator in this state */ export function isValidatorKnown( state: CachedBeaconStateElectra | CachedBeaconStateGloas, index: ValidatorIndex | null ): index is ValidatorIndex { return index !== null && index < state.validators.length; }