diff --git a/packages/state-transition/src/block/processDepositRequest.ts b/packages/state-transition/src/block/processDepositRequest.ts index 1bd12e5cb282..fe48e27b74a1 100644 --- a/packages/state-transition/src/block/processDepositRequest.ts +++ b/packages/state-transition/src/block/processDepositRequest.ts @@ -1,7 +1,7 @@ import {FAR_FUTURE_EPOCH, ForkSeq, UNSET_DEPOSIT_REQUESTS_START_INDEX} from "@lodestar/params"; import {BLSPubkey, Bytes32, UintNum64, electra, ssz} from "@lodestar/types"; import {CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js"; -import {findBuilderIndexByPubkey, isBuilderWithdrawalCredential} from "../util/gloas.js"; +import {findBuilderIndexByPubkey, isBuilderWithdrawalCredential, isPendingValidator} from "../util/gloas.js"; import {computeEpochAtSlot, isValidatorKnown} from "../util/index.js"; import {isValidDepositSignature} from "./processDeposit.js"; @@ -93,8 +93,9 @@ export function processDepositRequest( const isValidator = isValidatorKnown(state, validatorIndex); const isBuilderPrefix = isBuilderWithdrawalCredential(withdrawalCredentials); - // Route to builder if it's an existing builder OR has builder prefix and is not a validator - if (isBuilder || (isBuilderPrefix && !isValidator)) { + // Route to builder if it's an existing builder OR has builder prefix and is not a validator/pending validator + // Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/beacon-chain.md#modified-process_deposit_request + if (isBuilder || (isBuilderPrefix && !isValidator && !isPendingValidator(state.config, stateGloas, pubkey))) { // Apply builder deposits immediately applyDepositForBuilder(stateGloas, pubkey, withdrawalCredentials, amount, signature, state.slot); return; diff --git a/packages/state-transition/src/util/gloas.ts b/packages/state-transition/src/util/gloas.ts index 1a3ce4206046..581a07b0f11c 100644 --- a/packages/state-transition/src/util/gloas.ts +++ b/packages/state-transition/src/util/gloas.ts @@ -1,3 +1,4 @@ +import {BeaconConfig} from "@lodestar/config"; import { BUILDER_INDEX_FLAG, BUILDER_PAYMENT_THRESHOLD_DENOMINATOR, @@ -8,14 +9,44 @@ import { MIN_DEPOSIT_AMOUNT, SLOTS_PER_EPOCH, } from "@lodestar/params"; -import {BuilderIndex, Epoch, ValidatorIndex, gloas} from "@lodestar/types"; +import {BLSPubkey, BuilderIndex, Epoch, ValidatorIndex, gloas} from "@lodestar/types"; import {AttestationData} from "@lodestar/types/phase0"; import {byteArrayEquals} from "@lodestar/utils"; +import {isValidDepositSignature} from "../block/processDeposit.js"; import {CachedBeaconStateGloas} from "../types.js"; import {getBlockRootAtSlot} from "./blockRoot.js"; import {computeEpochAtSlot} from "./epoch.js"; import {RootCache} from "./rootCache.js"; +/** + * Check if a pending deposit with a valid signature is in the queue for the given pubkey. + * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.3/specs/gloas/beacon-chain.md#new-is_pending_validator + * + * Note: This function naively revalidates deposit signatures on every call. + * Implementations SHOULD cache verification results to avoid repeated work. + * TODO GLOAS: Cache deposit signature validation results to avoid repeated BLS verification + */ +export function isPendingValidator(config: BeaconConfig, state: CachedBeaconStateGloas, pubkey: BLSPubkey): boolean { + for (let i = 0; i < state.pendingDeposits.length; i++) { + const pendingDeposit = state.pendingDeposits.getReadonly(i); + if (!byteArrayEquals(pendingDeposit.pubkey, pubkey)) { + continue; + } + if ( + isValidDepositSignature( + config, + pendingDeposit.pubkey, + pendingDeposit.withdrawalCredentials, + pendingDeposit.amount, + pendingDeposit.signature + ) + ) { + return true; + } + } + return false; +} + export function isBuilderWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean { return withdrawalCredentials[0] === BUILDER_WITHDRAWAL_PREFIX; }