Goal
Reject inbound ValidatorRegistration partial-signature messages whose envelope
(send) slot is at or after the Ethereum Gloas fork. SIP-94 §5 deprecates the
ValidatorRegistration duty at Gloas: proposer preferences replace relay
registrations, and the relay path is gone with blinded blocks. This is the
receive-side half of that deprecation; the emit-side half (stop signing/broadcasting
post-Gloas) is #1065.
Add a fourth branch to validate_role_for_fork (message_validator/src/lib.rs:849)
and a new RoleNotActiveAfterEthFork error variant. Do NOT remove
Role::ValidatorRegistration (wire byte [4, 0, 0, 0], msgid.rs:34),
PartialSignatureKind::ValidatorRegistration = 4 (partial_sig.rs:34), or
BEACON_ROLE_VALIDATOR_REGISTRATION = BeaconRole(5) (consensus.rs:490, the
BNRoleValidatorRegistration the SIP names): SIP-94 §5 reserves those numeric values
for pre-Gloas operation and backward-compat decoding.
Context / motivation
validate_role_for_fork already gates role-by-fork in both directions and on both
the SSV Fork axis and the Ethereum ForkName axis:
RoleNotActiveBeforeFork (SSV): AggregatorCommittee before Boole.
RoleNotActiveAfterFork (SSV): Aggregator / SyncCommittee deprecated after Boole.
RoleNotActiveBeforeEthFork (eth): PTCAttester before Gloas.
The ValidatorRegistration deprecation is the missing symmetric case:
RoleNotActiveAfterEthFork on the eth axis. It reuses the exact PTCAttester
mechanism (spec.fork_name_at_epoch(epoch).gloas_enabled(), lib.rs:877) with inverted
polarity: reject when Gloas IS enabled.
validate_role_for_fork runs on the partial-sig path at partial_signature.rs:36
with messages.slot (the current send slot), so a single branch there covers every
inbound ValidatorRegistration message (the role is non-QBFT; partial-sig is its only
message type). The gate is defense-in-depth: honest post-Gloas operators emit nothing
(#1065); a Gloas-or-later-slot VR message is a buggy/stale/malicious sender.
Boundary safety: a VR message legitimately sent in the last pre-Gloas slot has a
pre-Gloas envelope slot, so gloas_enabled() is false and it is still accepted. Only
Gloas-or-later envelope slots are rejected.
Suggested approach
message_validator/src/lib.rs:
-
Add error variant next to RoleNotActiveBeforeEthFork (~lib.rs:226):
RoleNotActiveAfterEthFork { role: Role, current_fork: ForkName, deprecated_since_fork: ForkName }.
-
Add a branch in validate_role_for_fork (after the PTCAttester block, ~lib.rs:885):
// SIP-94 §5: the ValidatorRegistration duty is deprecated at Gloas. Proposer
// preferences replace relay registrations and the relay path is gone with
// blinded blocks. The variant + wire byte are retained for pre-Gloas decode.
if role == Role::ValidatorRegistration {
let current_fork = validation_context.spec.fork_name_at_epoch(epoch);
if current_fork.gloas_enabled() {
return Err(ValidationFailure::RoleNotActiveAfterEthFork {
role,
current_fork,
deprecated_since_fork: ForkName::Gloas,
});
}
}
-
Update the validate_role_for_fork doc comment (lib.rs:843-848) to list the VR
Gloas deprecation.
-
No change to From<&ValidationFailure> for MessageAcceptance (lib.rs:233): the new
variant is not in the Ignore list, so it falls through _ => Reject (lib.rs:257),
matching RoleNotActiveBeforeEthFork. "Invalid" in the SIP = REJECT.
No changes to msgid.rs / partial_sig.rs / message_counts.rs / duty_limit /
message_lateness: post-Gloas VR is rejected up front, and pre-Gloas VR flows through
the existing arms unchanged.
Acceptance criteria
- Pre-Gloas slot:
validate_role_for_fork returns Ok(()) for Role::ValidatorRegistration
(existing behavior, unchanged).
- Gloas-or-later slot:
validate_role_for_fork returns
RoleNotActiveAfterEthFork { deprecated_since_fork: ForkName::Gloas, .. }, and
end-to-end validate_partial_signature_message REJECTs the message.
- Exactly at
GLOAS_FORK_EPOCH slot 0: rejected (fork is enabled from its first slot).
Role::ValidatorRegistration wire byte stays [4, 0, 0, 0],
PartialSignatureKind::ValidatorRegistration stays 4, and
BEACON_ROLE_VALIDATOR_REGISTRATION stays BeaconRole(5) (msgid.rs / partial_sig.rs
/ consensus.rs untouched; reserved wire values per SIP-94 §5).
cargo check --workspace green; pre-existing ValidatorRegistration / partial-sig
tests unaffected (default spec_with_gloas(None) keeps them pre-Gloas).
Tests
Mirror test_ptc_attester_rejected_before_gloas (partial_signature.rs:1745), inverting polarity:
test_validator_registration_accepted_before_gloas: default spec (no Gloas),
validate_role_for_fork(Slot::new(0), &ctx) is Ok.
test_validator_registration_rejected_after_gloas: ctx.spec = spec_with_gloas(Some(0))
(partial_signature.rs:495), assert RoleNotActiveAfterEthFork.
test_validator_registration_rejected_at_gloas_boundary: spec_with_gloas(Some(N)),
message slot in epoch N, assert rejected end-to-end through
validate_partial_signature_message.
Use the tester-subagent before writing these tests (project rule).
Notes
Emit-side half of the SIP-94 §5 deprecation is #1065 (RegistrationService fork-gate).
Together #1065 + this issue fully implement the deprecation. Numeric values are
retained per SIP-94 §5, same treatment as RunnerRole values 1 and 3 in §3.
Issues are directionally correct, not prescriptive; verify symbols at PR time.
Goal
Reject inbound
ValidatorRegistrationpartial-signature messages whose envelope(send) slot is at or after the Ethereum Gloas fork. SIP-94 §5 deprecates the
ValidatorRegistrationduty at Gloas: proposer preferences replace relayregistrations, and the relay path is gone with blinded blocks. This is the
receive-side half of that deprecation; the emit-side half (stop signing/broadcasting
post-Gloas) is #1065.
Add a fourth branch to
validate_role_for_fork(message_validator/src/lib.rs:849)and a new
RoleNotActiveAfterEthForkerror variant. Do NOT removeRole::ValidatorRegistration(wire byte[4, 0, 0, 0],msgid.rs:34),PartialSignatureKind::ValidatorRegistration = 4(partial_sig.rs:34), orBEACON_ROLE_VALIDATOR_REGISTRATION = BeaconRole(5)(consensus.rs:490, theBNRoleValidatorRegistrationthe SIP names): SIP-94 §5 reserves those numeric valuesfor pre-Gloas operation and backward-compat decoding.
Context / motivation
validate_role_for_forkalready gates role-by-fork in both directions and on boththe SSV
Forkaxis and the EthereumForkNameaxis:RoleNotActiveBeforeFork(SSV):AggregatorCommitteebefore Boole.RoleNotActiveAfterFork(SSV):Aggregator/SyncCommitteedeprecated after Boole.RoleNotActiveBeforeEthFork(eth):PTCAttesterbefore Gloas.The
ValidatorRegistrationdeprecation is the missing symmetric case:RoleNotActiveAfterEthForkon the eth axis. It reuses the exactPTCAttestermechanism (
spec.fork_name_at_epoch(epoch).gloas_enabled(), lib.rs:877) with invertedpolarity: reject when Gloas IS enabled.
validate_role_for_forkruns on the partial-sig path atpartial_signature.rs:36with
messages.slot(the current send slot), so a single branch there covers everyinbound
ValidatorRegistrationmessage (the role is non-QBFT; partial-sig is its onlymessage type). The gate is defense-in-depth: honest post-Gloas operators emit nothing
(#1065); a Gloas-or-later-slot VR message is a buggy/stale/malicious sender.
Boundary safety: a VR message legitimately sent in the last pre-Gloas slot has a
pre-Gloas envelope slot, so
gloas_enabled()is false and it is still accepted. OnlyGloas-or-later envelope slots are rejected.
Suggested approach
message_validator/src/lib.rs:Add error variant next to
RoleNotActiveBeforeEthFork(~lib.rs:226):RoleNotActiveAfterEthFork { role: Role, current_fork: ForkName, deprecated_since_fork: ForkName }.Add a branch in
validate_role_for_fork(after the PTCAttester block, ~lib.rs:885):// SIP-94 §5: the ValidatorRegistration duty is deprecated at Gloas. Proposer
// preferences replace relay registrations and the relay path is gone with
// blinded blocks. The variant + wire byte are retained for pre-Gloas decode.
if role == Role::ValidatorRegistration {
let current_fork = validation_context.spec.fork_name_at_epoch(epoch);
if current_fork.gloas_enabled() {
return Err(ValidationFailure::RoleNotActiveAfterEthFork {
role,
current_fork,
deprecated_since_fork: ForkName::Gloas,
});
}
}
Update the
validate_role_for_forkdoc comment (lib.rs:843-848) to list the VRGloas deprecation.
No change to
From<&ValidationFailure> for MessageAcceptance(lib.rs:233): the newvariant is not in the
Ignorelist, so it falls through_ => Reject(lib.rs:257),matching
RoleNotActiveBeforeEthFork. "Invalid" in the SIP = REJECT.No changes to
msgid.rs/partial_sig.rs/message_counts.rs/duty_limit/message_lateness: post-Gloas VR is rejected up front, and pre-Gloas VR flows throughthe existing arms unchanged.
Acceptance criteria
validate_role_for_forkreturnsOk(())forRole::ValidatorRegistration(existing behavior, unchanged).
validate_role_for_forkreturnsRoleNotActiveAfterEthFork { deprecated_since_fork: ForkName::Gloas, .. }, andend-to-end
validate_partial_signature_messageREJECTs the message.GLOAS_FORK_EPOCHslot 0: rejected (fork is enabled from its first slot).Role::ValidatorRegistrationwire byte stays[4, 0, 0, 0],PartialSignatureKind::ValidatorRegistrationstays4, andBEACON_ROLE_VALIDATOR_REGISTRATIONstaysBeaconRole(5)(msgid.rs/partial_sig.rs/
consensus.rsuntouched; reserved wire values per SIP-94 §5).cargo check --workspacegreen; pre-existingValidatorRegistration/ partial-sigtests unaffected (default
spec_with_gloas(None)keeps them pre-Gloas).Tests
Mirror
test_ptc_attester_rejected_before_gloas(partial_signature.rs:1745), inverting polarity:test_validator_registration_accepted_before_gloas: default spec (no Gloas),validate_role_for_fork(Slot::new(0), &ctx)isOk.test_validator_registration_rejected_after_gloas:ctx.spec = spec_with_gloas(Some(0))(
partial_signature.rs:495), assertRoleNotActiveAfterEthFork.test_validator_registration_rejected_at_gloas_boundary:spec_with_gloas(Some(N)),message slot in epoch N, assert rejected end-to-end through
validate_partial_signature_message.Use the
tester-subagentbefore writing these tests (project rule).Notes
Emit-side half of the SIP-94 §5 deprecation is #1065 (RegistrationService fork-gate).
Together #1065 + this issue fully implement the deprecation. Numeric values are
retained per SIP-94 §5, same treatment as
RunnerRolevalues 1 and 3 in §3.Issues are directionally correct, not prescriptive; verify symbols at PR time.