Summary
Post-Boole, go-ssv's discovery filter cannot match Anchor. Anchor advertises the active domain in its main domaintype key, while checkPeer compares that against the static ssvConfig.DomainType, so the comparison never succeeds. Anchor fixed the reverse direction in sigp/anchor#1187, so cross-client edges do form, but only Anchor can ever create one.
We would like to ask you to revisit #2952 item 4 against a condition it did not weigh. Background and the full audit are in sigp/anchor#1185.
Evidence from hoodi-stage
hi-h-sv-net2 (operator 417) restarted post-Boole on 2026-07-28 and stalled at 3 peers against target_peers=69 with zero go-ssv peers. On 2026-07-29 00:18:46 UTC we put the #1187 build on that node alone, keeping operators 415/416/418 on v1.3.0 as a control since they still held warm pre-fork connections.
|
before |
after |
| peers (in / out) |
3 (1 / 2) |
7 (1 / 6) |
| distinct go-ssv ENRs surfaced by discovery |
0 |
7 |
| connected go-ssv peers |
0 |
4 |
Deriving peer IDs from each discovered ENR's secp256k1 entry and matching them against Handshake completed lines: of the 7 connections, 3 are Anchor and 4 are go-ssv, and Anchor dialed all four go-ssv peers. The single inbound connection is another Anchor node. We attempted 23 distinct dials in the window and no go-ssv node dialed us. No trusted or static peers are configured on these hosts, so all of it came through normal discv5.
Every go-ssv ENR carried domaintype=00003114 with next_domaintype=00003115, and no Anchor ENR carried a next domain. So the stage fleet is populating NextDomainType correctly and #1187 consumes it.
The ask
At network/discovery/dv5_service.go:279 (PR #2941 head 7de8ab0f), widen the comparison to accept either configured domain:
// Accept a candidate on either configured domain, so peers that advertise the
// post-fork domain in their main key remain discoverable across the transition.
domainMatches := nodeDomainType == dvs.ssvConfig.DomainType ||
(dvs.ssvConfig.NextDomainType != (spectypes.DomainType{}) &&
nodeDomainType == dvs.ssvConfig.NextDomainType)
if !domainMatches {
recordPeerSkipped(ctx, skipReasonDomainTypeMismatch)
return newPeerSkipError(skipReasonDomainTypeMismatch,
fmt.Errorf("domain type %x matches neither %x nor %x",
nodeDomainType, dvs.ssvConfig.DomainType, dvs.ssvConfig.NextDomainType))
}
- No structural change.
ssvConfig.NextDomainType is on the same struct (networkconfig/ssv.go:40) and already used at :498 and :571. One production call site, no Options change, no slot clock.
- Strictly widens an accept-set, so it cannot partition a homogeneous fleet, and no existing discovery test result changes.
- The zero-value guard is load-bearing.
UnmarshalJSON defaults NextDomainType to DomainType only for parsed configs (ssv.go:125-127); the struct literals in the discovery tests leave it [4]byte{}, where a bare || would start accepting an all-zero domain.
It also makes records.KeyNextDomainType do something: today it is written at :498 and :571 and, as far as we can tell, never read anywhere in the non-test tree.
The fuller version is your own isDomainCompatible from boole-fork, which uses the runtime CurrentDomainType() and the transition window. That is the better end state, but it needs DiscV5Service to hold the whole *networkconfig.Network for the slot clock, so we are not asking for it here.
Why we think it is worth revisiting
#2952 item 4 closed deliberately, with a condition attached:
only port isDomainCompatible if full mixed-fleet interop during testnet migration is required
The case weighed there was interop with older boole-fork binaries during a migration. That is transient, and we agree it would not justify the change on its own. Anchor is a permanent second implementation that advertises the active domain rather than a static one, so this is the steady state rather than a window that closes.
Mainnet exposure
Mainnet is pre-Boole, so both clients are on Alan, discovery is symmetric, and that symmetry currently supplies most of our connectivity:
| operator |
peers |
target |
in |
out |
inbound share |
| 2042 |
56 |
69 |
43 |
13 |
77% |
| 2043 |
70 |
72 |
56 |
14 |
80% |
| 2044 |
40 |
69 |
27 |
13 |
68% |
| 2045 |
60 |
69 |
45 |
15 |
75% |
| 948 |
22 |
63 |
18 |
4 |
82% |
Our nodes sustain only 13-15 outbound connections each, and 4 on operator 948. Anchor also stops running discovery once connected_peers >= target_peers, where the target is min(60 + 3*subnets, 150), and operator 2043 is already at 70 of 72. On a densely peered network that combination leaves a restarted go-ssv node with no route back: it cannot discover us, and we may have stopped looking.
If Boole activates on mainnet with the current filter, the inbound path stops being replenished. Warm connections survive the transition, as they did on hoodi-stage in July, so it would look clean at first and then degrade with restarts and ordinary churn.
Summary
Post-Boole, go-ssv's discovery filter cannot match Anchor. Anchor advertises the active domain in its main
domaintypekey, whilecheckPeercompares that against the staticssvConfig.DomainType, so the comparison never succeeds. Anchor fixed the reverse direction in sigp/anchor#1187, so cross-client edges do form, but only Anchor can ever create one.We would like to ask you to revisit #2952 item 4 against a condition it did not weigh. Background and the full audit are in sigp/anchor#1185.
Evidence from hoodi-stage
hi-h-sv-net2(operator 417) restarted post-Boole on 2026-07-28 and stalled at 3 peers againsttarget_peers=69with zero go-ssv peers. On 2026-07-29 00:18:46 UTC we put the #1187 build on that node alone, keeping operators 415/416/418 on v1.3.0 as a control since they still held warm pre-fork connections.Deriving peer IDs from each discovered ENR's
secp256k1entry and matching them againstHandshake completedlines: of the 7 connections, 3 are Anchor and 4 are go-ssv, and Anchor dialed all four go-ssv peers. The single inbound connection is another Anchor node. We attempted 23 distinct dials in the window and no go-ssv node dialed us. No trusted or static peers are configured on these hosts, so all of it came through normal discv5.Every go-ssv ENR carried
domaintype=00003114withnext_domaintype=00003115, and no Anchor ENR carried a next domain. So the stage fleet is populatingNextDomainTypecorrectly and #1187 consumes it.The ask
At
network/discovery/dv5_service.go:279(PR #2941 head7de8ab0f), widen the comparison to accept either configured domain:ssvConfig.NextDomainTypeis on the same struct (networkconfig/ssv.go:40) and already used at:498and:571. One production call site, noOptionschange, no slot clock.UnmarshalJSONdefaultsNextDomainTypetoDomainTypeonly for parsed configs (ssv.go:125-127); the struct literals in the discovery tests leave it[4]byte{}, where a bare||would start accepting an all-zero domain.It also makes
records.KeyNextDomainTypedo something: today it is written at:498and:571and, as far as we can tell, never read anywhere in the non-test tree.The fuller version is your own
isDomainCompatiblefromboole-fork, which uses the runtimeCurrentDomainType()and the transition window. That is the better end state, but it needsDiscV5Serviceto hold the whole*networkconfig.Networkfor the slot clock, so we are not asking for it here.Why we think it is worth revisiting
#2952 item 4 closed deliberately, with a condition attached:
The case weighed there was interop with older
boole-forkbinaries during a migration. That is transient, and we agree it would not justify the change on its own. Anchor is a permanent second implementation that advertises the active domain rather than a static one, so this is the steady state rather than a window that closes.Mainnet exposure
Mainnet is pre-Boole, so both clients are on Alan, discovery is symmetric, and that symmetry currently supplies most of our connectivity:
Our nodes sustain only 13-15 outbound connections each, and 4 on operator 948. Anchor also stops running discovery once
connected_peers >= target_peers, where the target ismin(60 + 3*subnets, 150), and operator 2043 is already at 70 of 72. On a densely peered network that combination leaves a restarted go-ssv node with no route back: it cannot discover us, and we may have stopped looking.If Boole activates on mainnet with the current filter, the inbound path stops being replenished. Warm connections survive the transition, as they did on hoodi-stage in July, so it would look clean at first and then degrade with restarts and ordinary churn.