Skip to content

exporter: /decideds is fork-blind for AGGREGATOR / SYNC_COMMITTEE_CONTRIBUTION post-Boole (#2968 item 3) #2987

Description

@momosh-ssv

Item 3 of the #2968 exporter back-compat umbrella. Surfaced by @ovidiu-ssv-labs during the #2975 review (Finding 2) — full mechanism analysis there: #2975 (review) (review of 2026-08-10).

Problem

After #2975, isCommitteeDutyAtSlot (exporter/validator.go) is the only Boole-aware routing point in exporter/. TraceDecidedsCore still routes on a hardcoded role switch with no slot/fork input (exporter/decided.go:41-46):

switch role {
case spectypes.BNRoleAttester, spectypes.BNRoleSyncCommittee:
    roleParticipantsIdx, roleErrs = e.getCommitteeDecidedsForRole(slot, indices, role)
default:
    roleParticipantsIdx, roleErrs = e.getValidatorDecidedsForRole(slot, indices, role)
}

Post-Boole, AGGREGATOR / SYNC_COMMITTEE_CONTRIBUTION duties run under the RoleAggregatorCommittee runner and their traces are stored on the committee path, so the default arm reads an index that is empty for these roles. /v1/exporter/decideds then returns an empty participants list with a 200 — silent data loss, no error, no note.

Notably, the store layer already supports the committee-side lookup for these roles (committeeRunnerRolesForBeaconRoles maps them to RoleAggregatorCommittee, and TestCollector_GetCommitteeDecideds_RoleFiltering asserts it works) — nothing in production routes to it.

Suggested fix

Route the decideds switch through the same fork-aware predicate the traces path uses:

if e.isCommitteeDutyAtSlot(role, slot) {
    roleParticipantsIdx, roleErrs = e.getCommitteeDecidedsForRole(slot, indices, role)
} else {
    roleParticipantsIdx, roleErrs = e.getValidatorDecidedsForRole(slot, indices, role)
}

Behavior-preserving pre-Boole, correct post-Boole. Note decided.go nests role-outer/slot-inner (inverse of validator.go), so the predicate must be evaluated inside the slot loop. Add a straddling-range test mirroring TestValidatorTracesCore_StraddlingFork.

Should land before Boole activates on mainnet.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions