Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion packages/beacon-node/src/api/impl/beacon/pool/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,18 @@ export function getBeaconPoolApi({
},

async submitPoolVoluntaryExit({signedVoluntaryExit}) {
// Validate the voluntary exit
await validateApiVoluntaryExit(chain, signedVoluntaryExit);
// Insert into the operation pool
// The opPool will handle validation and delayed broadcasting
chain.opPool.insertVoluntaryExit(signedVoluntaryExit);
// Emit event immediately so monitoring/logging can track it
chain.emitter.emit(routes.events.EventType.voluntaryExit, signedVoluntaryExit);
await network.publishVoluntaryExit(signedVoluntaryExit);
// Note: Network publishing is now handled by the opPool when conditions are met
// instead of publishing immediately here
Comment on lines +202 to +203
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see those changes implemented in the PR

logger.info("Voluntary exit accepted and added to pool", {
validatorIndex: signedVoluntaryExit.message.validatorIndex,
});
},

async submitPoolBLSToExecutionChange({blsToExecutionChanges}) {
Expand Down
49 changes: 47 additions & 2 deletions packages/beacon-node/src/chain/validation/voluntaryExit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ export async function validateApiVoluntaryExit(
voluntaryExit: phase0.SignedVoluntaryExit
): Promise<void> {
const prioritizeBls = true;
return validateVoluntaryExit(chain, voluntaryExit, prioritizeBls);
// For API submissions, we validate signature and permanent conditions
// Transient conditions will be checked by the opPool before broadcasting
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Transient conditions will be checked by the opPool before broadcasting

doesn't seem to be implemented

return validateVoluntaryExitForApi(chain, voluntaryExit, prioritizeBls);
}

export async function validateGossipVoluntaryExit(
Expand All @@ -28,6 +30,35 @@ export async function validateGossipVoluntaryExit(
return validateVoluntaryExit(chain, voluntaryExit);
}

async function validateVoluntaryExitForApi(
chain: IBeaconChain,
voluntaryExit: phase0.SignedVoluntaryExit,
prioritizeBls = false
): Promise<void> {
// [IGNORE] The voluntary exit is the first valid voluntary exit received for the validator with index
// signed_voluntary_exit.message.validator_index.
if (chain.opPool.hasSeenVoluntaryExit(voluntaryExit.message.validatorIndex)) {
throw new VoluntaryExitError(GossipAction.IGNORE, {
code: VoluntaryExitErrorCode.ALREADY_EXISTS,
});
}

// Get current state for validation
const state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.validateGossipVoluntaryExit);

// Validate signature - this is a permanent check
const signatureSet = getVoluntaryExitSignatureSet(state, voluntaryExit);
if (!(await chain.bls.verifySignatureSets([signatureSet], {batchable: true, priority: prioritizeBls}))) {
throw new VoluntaryExitError(GossipAction.REJECT, {
code: VoluntaryExitErrorCode.INVALID_SIGNATURE,
});
}
}

/**
* Full validation for gossip voluntary exits.
* Checks all conditions including transient ones.
*/
async function validateVoluntaryExit(
chain: IBeaconChain,
voluntaryExit: phase0.SignedVoluntaryExit,
Expand All @@ -50,7 +81,7 @@ async function validateVoluntaryExit(
// relevant on periods of many skipped slots.
const state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.validateGossipVoluntaryExit);

// [REJECT] All of the conditions within process_voluntary_exit pass validation.
// Check all conditions here:
// verifySignature = false, verified in batch below
const validity = getVoluntaryExitValidity(chain.config.getForkSeq(state.slot), state, voluntaryExit, false);
if (validity !== VoluntaryExitValidity.valid) {
Expand All @@ -66,3 +97,17 @@ async function validateVoluntaryExit(
});
}
}

export async function validateVoluntaryExitTransientConditions(
chain: IBeaconChain,
voluntaryExit: phase0.SignedVoluntaryExit
): Promise<boolean> {
try {
const state = await chain.getHeadStateAtCurrentEpoch(RegenCaller.validateGossipVoluntaryExit);
// Check all transient conditions (verifySignature = false since we already verified it)
const validity = getVoluntaryExitValidity(chain.config.getForkSeq(state.slot), state, voluntaryExit, false);
return validity === VoluntaryExitValidity.valid;
} catch (_e) {
return false;
}
}