Commit d9c7a89
fix: v19.1 — restore fresh-bootstrap auto-creation of genesis_anchors.json
The v19.0 handshake-proof gate was too strict: it returned `Err` whenever
`verify_dilithium_heartbeat_signature_async` returned false, conflating two
genuinely different conditions:
(a) PK is in the consensus registry but the supplied signature does not
verify under it — a real identity-squat attempt, MUST drop.
(b) PK is NOT yet in the registry — a legitimate fresh-bootstrap peer
whose identity binding will be installed once its `VrfKeyAnnounce`
(carrying its own self-signature, verified inline) reaches us.
Treating (b) as a hard error broke fresh-cluster bootstrap end-to-end.
The very first cross-peer connection from each genesis node arrives
BEFORE that peer's PK has been cross-registered, so the connection was
dropped before VrfKeyAnnounce gossip could propagate. Without that gossip
the registry never reached 5 entries, `try_autowrite_genesis_anchors_locked`
never triggered, `genesis_anchors.json` was never written, and the
cluster sat at height=0 indefinitely (observed 4h+ stall on five-node
genesis testnet, ~13K rejections per node per error category).
This patch restores the universal L1 invariant that connection-level
handshake admits unknown-identity peers (TLS/QUIC is a transport, not
an identity oracle); identity binding happens through signed messages
carried OVER the connection, not embedded in the handshake itself.
Changes
-------
1. `verify_handshake_proof` (quic_transport.rs) — three-state contract:
* Ok(true) — proof present and verifies under registered PK
* Ok(false) — advisory admit (no proof / pre-init / PK absent)
* Err — proof present, PK present, signature mismatch
The PK-absence branch uses `qnet_consensus::has_consensus_pk` to
check membership separately from signature verification, so the
"PK not registered yet" condition no longer collapses into the
"signature invalid" failure path.
Receiver-side log messages updated from `no_dilithium_proof
reason=legacy_peer_phase_2A` to `advisory_admit
reason=pk_unknown_or_no_proof` since the same Ok(false) path now
covers three operational causes, not just legacy peers.
2. `verify_consensus_signature` Tier 3 (consensus_crypto.rs) — first-seen
policy for genesis identities aligned with `anchors_missing_boot_decision`:
* anchors loaded → strict reject (steady-state squat protection)
* anchors absent + QNET_BOOTSTRAP_FRESH=1 → admit TOFV (signature
math below the gate is the cryptographic floor — an attacker
without the SK cannot pass it)
* anchors absent + no opt-in → strict reject (misconfigured deploy
surfaces explicitly with the actual flag values in the log)
Decision logic extracted into `tier3_genesis_first_seen_admit`
pub(crate) helper so production verify-path and unit tests share a
single source of truth.
Security
--------
`Ok(false)` from the handshake admits a connection but does NOT
authenticate the peer. Every consensus-relevant message that flows
over the admitted connection still passes through full Dilithium3
verification:
* VrfKeyAnnounce inline self-signature verify (the registration
pathway itself is cryptographically gated)
* `verify_consensus_signature` for TimeoutVote / heartbeat / commit
* Tier 2 catches any later identity mismatch under a registered PK
The Tier 3 fresh-window admit is gated on the same operator-explicit
QNET_BOOTSTRAP_FRESH=1 flag that already governs whether the process
is allowed to start at all when anchors are absent. Operators who do
not set the flag get the same hard reject as before. Operators who
deploy `genesis_anchors.json` get strict mode regardless of the flag.
Scalability
-----------
Hot-path cost stays sub-1% of a core at 1000+ super-node mesh:
* one lock-free `has_consensus_pk` check per handshake (~50ns)
* at most one Dilithium3 verify per handshake (~3ms, only when proof
actually attached AND PK present)
* one env-var read + RwLock-read on Tier 3 hits (negligible vs the
Dilithium3 math that follows on the same call)
Tests
-----
* New `verify_returns_ok_false_for_unknown_pk_with_proof` in
qnet-integration confirms admit-on-PK-miss.
* New `tests_v19_1_tier3_fresh_window` module in qnet-consensus pins
the policy decision across all three states (strict-when-anchors-loaded,
admit-when-fresh-window-open, strict-when-misconfigured).
* All existing v19/v20 regression tests still pass: 73 in qnet-consensus
(was 70, +3 new), 143 in qnet-integration (was 142, +1 new), 0 fail.
Build
-----
cargo build --release clean in 16m 34s, 0 warnings.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent 12b543c commit d9c7a89
2 files changed
Lines changed: 303 additions & 46 deletions
File tree
- core/qnet-consensus/src
- development/qnet-integration/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1602 | 1602 | | |
1603 | 1603 | | |
1604 | 1604 | | |
1605 | | - | |
1606 | | - | |
1607 | | - | |
1608 | | - | |
| 1605 | + | |
1609 | 1606 | | |
1610 | | - | |
1611 | | - | |
1612 | | - | |
1613 | | - | |
1614 | | - | |
1615 | | - | |
| 1607 | + | |
| 1608 | + | |
| 1609 | + | |
| 1610 | + | |
| 1611 | + | |
| 1612 | + | |
| 1613 | + | |
| 1614 | + | |
| 1615 | + | |
| 1616 | + | |
| 1617 | + | |
| 1618 | + | |
| 1619 | + | |
| 1620 | + | |
| 1621 | + | |
| 1622 | + | |
| 1623 | + | |
| 1624 | + | |
| 1625 | + | |
| 1626 | + | |
| 1627 | + | |
| 1628 | + | |
| 1629 | + | |
| 1630 | + | |
| 1631 | + | |
| 1632 | + | |
| 1633 | + | |
| 1634 | + | |
| 1635 | + | |
| 1636 | + | |
| 1637 | + | |
| 1638 | + | |
| 1639 | + | |
| 1640 | + | |
| 1641 | + | |
| 1642 | + | |
| 1643 | + | |
| 1644 | + | |
| 1645 | + | |
| 1646 | + | |
| 1647 | + | |
| 1648 | + | |
| 1649 | + | |
| 1650 | + | |
| 1651 | + | |
| 1652 | + | |
| 1653 | + | |
| 1654 | + | |
1616 | 1655 | | |
1617 | 1656 | | |
1618 | 1657 | | |
1619 | 1658 | | |
1620 | 1659 | | |
1621 | | - | |
1622 | | - | |
1623 | | - | |
1624 | | - | |
1625 | | - | |
1626 | | - | |
1627 | | - | |
1628 | | - | |
1629 | | - | |
1630 | | - | |
1631 | | - | |
1632 | | - | |
1633 | | - | |
1634 | | - | |
| 1660 | + | |
| 1661 | + | |
| 1662 | + | |
| 1663 | + | |
| 1664 | + | |
| 1665 | + | |
| 1666 | + | |
| 1667 | + | |
| 1668 | + | |
| 1669 | + | |
| 1670 | + | |
| 1671 | + | |
| 1672 | + | |
| 1673 | + | |
| 1674 | + | |
| 1675 | + | |
| 1676 | + | |
| 1677 | + | |
| 1678 | + | |
| 1679 | + | |
| 1680 | + | |
| 1681 | + | |
| 1682 | + | |
| 1683 | + | |
| 1684 | + | |
| 1685 | + | |
| 1686 | + | |
| 1687 | + | |
| 1688 | + | |
| 1689 | + | |
1635 | 1690 | | |
1636 | 1691 | | |
1637 | 1692 | | |
| |||
2064 | 2119 | | |
2065 | 2120 | | |
2066 | 2121 | | |
| 2122 | + | |
| 2123 | + | |
| 2124 | + | |
| 2125 | + | |
| 2126 | + | |
| 2127 | + | |
| 2128 | + | |
| 2129 | + | |
| 2130 | + | |
| 2131 | + | |
| 2132 | + | |
| 2133 | + | |
| 2134 | + | |
| 2135 | + | |
| 2136 | + | |
| 2137 | + | |
| 2138 | + | |
| 2139 | + | |
| 2140 | + | |
| 2141 | + | |
| 2142 | + | |
| 2143 | + | |
| 2144 | + | |
| 2145 | + | |
| 2146 | + | |
| 2147 | + | |
| 2148 | + | |
| 2149 | + | |
| 2150 | + | |
| 2151 | + | |
| 2152 | + | |
| 2153 | + | |
| 2154 | + | |
| 2155 | + | |
| 2156 | + | |
| 2157 | + | |
| 2158 | + | |
| 2159 | + | |
| 2160 | + | |
| 2161 | + | |
| 2162 | + | |
| 2163 | + | |
| 2164 | + | |
| 2165 | + | |
| 2166 | + | |
| 2167 | + | |
| 2168 | + | |
| 2169 | + | |
| 2170 | + | |
| 2171 | + | |
| 2172 | + | |
| 2173 | + | |
| 2174 | + | |
| 2175 | + | |
| 2176 | + | |
| 2177 | + | |
| 2178 | + | |
| 2179 | + | |
| 2180 | + | |
| 2181 | + | |
| 2182 | + | |
| 2183 | + | |
| 2184 | + | |
| 2185 | + | |
| 2186 | + | |
| 2187 | + | |
| 2188 | + | |
| 2189 | + | |
| 2190 | + | |
| 2191 | + | |
| 2192 | + | |
| 2193 | + | |
| 2194 | + | |
| 2195 | + | |
| 2196 | + | |
| 2197 | + | |
| 2198 | + | |
| 2199 | + | |
| 2200 | + | |
| 2201 | + | |
| 2202 | + | |
| 2203 | + | |
| 2204 | + | |
| 2205 | + | |
| 2206 | + | |
| 2207 | + | |
| 2208 | + | |
| 2209 | + | |
| 2210 | + | |
0 commit comments