Non-blocking hardening follow-ups from the #2941 (Boole → stage convergence) review, grouped by whether they should ship with the Boole fork or after. Items keep their original numbers; permalinks pin the reviewed revision.
Status (2026-08-11): Both "ship with the fork" items are still outstanding on stage — item 1's fix (#2973) is open/unmerged, and item 5 (spec re-pin) is blocked on the spec being tagged. Boole is still unscheduled on all shared networks (mainnet/holesky/hoodi pin Boole = math.MaxUint64; only local-testnet uses 0), so the pre-fork window item 1 warns about is still open. Among the "ship after" items, item 2 is now resolved (below); items 3, 4, 6, 7, and 8 remain open.
Ship with the Boole fork
Deferring these past the fork loses their value or blocks a clean mainnet cut.
1. A custom config can schedule Boole without rotating the domain (highest priority)
unmarshalFromConfig defaults an omitted next_domain_type to DomainType, so a custom network that sets forks: {boole: N} gets identical pre- and post-fork domains — QBFT message IDs never rotate at the boundary. Built-in configs are guarded by TestBuiltinNetworkDomainsAreUnique; custom ones have no runtime check.
Fix implemented in #2973 — not yet merged (still open against stage). On startup Network.Validate() returns an error and refuses to start (it does not merely warn) when BooleForkScheduled() is set and NextDomainType == DomainType, gated on the fork not yet being active. Land it before Boole is scheduled on any shared or custom network.
5. ssv-spec is pinned to a pseudo-version
go.mod and ssvsigner/go.mod pin ssv-spec v1.2.3-0.20260305184636-289c93aa4c12 — a commit, not a tag.
Action: re-pin both to the tagged spec release before mainnet (depends on the spec being tagged first).
Ship after the Boole fork
Hardening / housekeeping; none affect fork correctness on the built-in networks.
2. Boole topics are whitelisted even when no fork is scheduled
commons.Topics registers all 128 Boole topics in the subscription-filter whitelist unconditionally, even where Boole is unscheduled (e.g. mainnet). Pre-fork nothing validates on them, so exposure is limited to peers forming meshes on dead topics.
Resolved — superseded by #2971 (merged). #2971 reworked this function to be slot-aware but deliberately keeps "always include the Boole set", with an in-code rationale (Boole subscriptions can legitimately open ahead of the fork). The proposed BooleForkScheduled() gate was consciously not adopted, so this item can be closed as superseded. Low severity.
3. Pre-fork decode cap for partial-signature messages grew ~5×
maxPartialSignatureMessages = 5048 is the post-fork (AggregatorCommittee) worst case, but the derived maxEncodedPartialSignatureSize is enforced fork-independently in validatePartialSignatureMessage — the pre-fork acceptance cap jumps ~151 KB → ~763 KB. Spec-derived and drift-guarded, and gossip scoring bounds abuse.
Action: optionally make the cap fork-aware (1000 pre-fork, 5048 from the fork) if pre-fork DoS surface is a concern.
4. Unchecked type assertions when registering committee runners
createRunner asserts r.(*runner.CommitteeRunner) / r.(*runner.AggregatorCommitteeRunner) unchecked. The invariant holds today, but a future CreateRunnerFn returning a mismatched type would panic with a bare runtime error.
Action: use checked assertions and return an error (or a descriptive logger.Panic, matching the default branch).
6. local-testnet activates Boole from genesis
LocalTestnetSSV sets Boole: 0, flipping default local-dev clusters (make docker-all / docker-debug) to post-fork behavior from slot 0 — presumably intentional.
Action: confirm intent and note the local-dev flip in release notes.
7. Role-string mappers rely on doc-comment lockstep only
ssvtypes.RunnerRoleToString and message.RunnerRoleToString are independent mappers that must agree (utils.FormatRunnerRole delegates to the first), but the contract lives only in doc comments — no test asserts it.
Action: add a lockstep test asserting message.RunnerRoleToString(r) == utils.FormatRunnerRole(r) for every role, or unify on one mapper.
8. Codecov project status left informational
codecov.yml sets informational: true to avoid phantom coverage drops on the stacked convergence PRs.
Action: revert once the stack (#2971–#2975) has landed and baselines are stable.
Non-blocking hardening follow-ups from the #2941 (Boole → stage convergence) review, grouped by whether they should ship with the Boole fork or after. Items keep their original numbers; permalinks pin the reviewed revision.
Status (2026-08-11): Both "ship with the fork" items are still outstanding on
stage— item 1's fix (#2973) is open/unmerged, and item 5 (spec re-pin) is blocked on the spec being tagged. Boole is still unscheduled on all shared networks (mainnet/holesky/hoodipinBoole = math.MaxUint64; onlylocal-testnetuses0), so the pre-fork window item 1 warns about is still open. Among the "ship after" items, item 2 is now resolved (below); items 3, 4, 6, 7, and 8 remain open.Ship with the Boole fork
Deferring these past the fork loses their value or blocks a clean mainnet cut.
1. A custom config can schedule Boole without rotating the domain (highest priority)
unmarshalFromConfigdefaults an omittednext_domain_typetoDomainType, so a custom network that setsforks: {boole: N}gets identical pre- and post-fork domains — QBFT message IDs never rotate at the boundary. Built-in configs are guarded byTestBuiltinNetworkDomainsAreUnique; custom ones have no runtime check.Fix implemented in #2973 — not yet merged (still open against
stage). On startupNetwork.Validate()returns an error and refuses to start (it does not merely warn) whenBooleForkScheduled()is set andNextDomainType == DomainType, gated on the fork not yet being active. Land it before Boole is scheduled on any shared or custom network.5. ssv-spec is pinned to a pseudo-version
go.modandssvsigner/go.modpinssv-spec v1.2.3-0.20260305184636-289c93aa4c12— a commit, not a tag.Action: re-pin both to the tagged spec release before mainnet (depends on the spec being tagged first).
Ship after the Boole fork
Hardening / housekeeping; none affect fork correctness on the built-in networks.
2. Boole topics are whitelisted even when no fork is scheduled
commons.Topicsregisters all 128 Boole topics in the subscription-filter whitelist unconditionally, even where Boole is unscheduled (e.g. mainnet). Pre-fork nothing validates on them, so exposure is limited to peers forming meshes on dead topics.Resolved — superseded by #2971 (merged). #2971 reworked this function to be slot-aware but deliberately keeps "always include the Boole set", with an in-code rationale (Boole subscriptions can legitimately open ahead of the fork). The proposed
BooleForkScheduled()gate was consciously not adopted, so this item can be closed as superseded. Low severity.3. Pre-fork decode cap for partial-signature messages grew ~5×
maxPartialSignatureMessages = 5048is the post-fork (AggregatorCommittee) worst case, but the derivedmaxEncodedPartialSignatureSizeis enforced fork-independently invalidatePartialSignatureMessage— the pre-fork acceptance cap jumps ~151 KB → ~763 KB. Spec-derived and drift-guarded, and gossip scoring bounds abuse.Action: optionally make the cap fork-aware (1000 pre-fork, 5048 from the fork) if pre-fork DoS surface is a concern.
4. Unchecked type assertions when registering committee runners
createRunnerassertsr.(*runner.CommitteeRunner)/r.(*runner.AggregatorCommitteeRunner)unchecked. The invariant holds today, but a futureCreateRunnerFnreturning a mismatched type would panic with a bare runtime error.Action: use checked assertions and return an error (or a descriptive
logger.Panic, matching thedefaultbranch).6.
local-testnetactivates Boole from genesisLocalTestnetSSVsetsBoole: 0, flipping default local-dev clusters (make docker-all/docker-debug) to post-fork behavior from slot 0 — presumably intentional.Action: confirm intent and note the local-dev flip in release notes.
7. Role-string mappers rely on doc-comment lockstep only
ssvtypes.RunnerRoleToStringandmessage.RunnerRoleToStringare independent mappers that must agree (utils.FormatRunnerRoledelegates to the first), but the contract lives only in doc comments — no test asserts it.Action: add a lockstep test asserting
message.RunnerRoleToString(r) == utils.FormatRunnerRole(r)for every role, or unify on one mapper.8. Codecov project status left informational
codecov.ymlsetsinformational: trueto avoid phantom coverage drops on the stacked convergence PRs.Action: revert once the stack (#2971–#2975) has landed and baselines are stable.