docs(vendor): the chia fork rebase is a cross-repo cascade; no fork can be deleted - #62
docs(vendor): the chia fork rebase is a cross-repo cascade; no fork can be deleted#62MichaelTaylor3d wants to merge 3 commits into
Conversation
Records the encoded `Message` envelope for RegisterPeer (218), RegisterAck (219) and DigMessage (220), the raw discriminant of every DIG opcode, and a decode round trip through the vendored enum. Committed as its own change, against the PRE-rebase tree, so the vectors measure the rebase rather than re-record whatever it produces.
Measured, rather than assumed, before touching a vendored tree: - Bumping `vendor/chia-protocol` to 0.36.1 does not fail. `[patch.crates-io]` substitutes a package only where the patched version satisfies the existing requirement, and dig-gossip reaches chia-protocol through `dig-peer-protocol`, which pins `^0.26`. Cargo drops the patch and resolves pristine upstream, reporting a warning with `cargo metadata` exiting 0. - Upstream has not claimed 200-222 at 0.26.0, 0.36.1 or 0.47.0 — it stops at 107 — so there is no collision and no renumber pressure on the live wire. - None of the three forks can be deleted. Upstream chia-sdk-client 0.34.0 has none of the three fork items, and upstream `TlsAcceptorBuilder` still cannot require a client certificate, which makes the native-tls patch security-load-bearing on the default feature set. Also formats the golden-vector test committed in aead743.
5acb9f6 to
1f3ae0f
Compare
… trap Carries forward the one durable lesson from the abandoned rebase investigation (PR #62): [patch.crates-io] substitutes only where the patched version SATISFIES the requirement, so a mismatched patch is dropped with a warning and EXIT=0 — the failure has no shape. Rewritten for the post-deletion world, where it now explains why the remaining native-tls patch is guarded in publish.yml rather than by the compiler: nothing stops compiling if it is stripped, the acceptor just stops requiring a client certificate. Also kept from #62: upstream ProtocolMessageTypes stops at RespondCostInfo = 107 at 0.26.0, 0.36.1 and 0.47.0, so the DIG 200-222 band collides with nothing. Not carried forward: #62's verdicts that chia-protocol and chia-sdk-client could not be deleted. The redesign it named as a prerequisite is exactly what was done. The new entry records the mechanism — DigMessage's raw u8 msg_type, which makes the opcode band expressible with no enum to extend — and that the wire was pinned by golden vectors before the refactor and is byte-identical after. Also corrects the #1371 entry, which credited from_server_websocket to the vendored chia-sdk-client; it now lives on DigLink, and is one of the reasons the fork could go. README's PeerConnection field and Cargo.toml's dependency-shape rationale follow the same rename (Cargo.toml comments only — no dependency, version or patch line is touched). Refs dig_ecosystem#2228, dig_ecosystem#2647 Co-Authored-By: Claude <noreply@anthropic.com>
… trap Carries forward the one durable lesson from the abandoned rebase investigation (PR #62): [patch.crates-io] substitutes only where the patched version SATISFIES the requirement, so a mismatched patch is dropped with a warning and EXIT=0 — the failure has no shape. Rewritten for the post-deletion world, where it now explains why the remaining native-tls patch is guarded in publish.yml rather than by the compiler: nothing stops compiling if it is stripped, the acceptor just stops requiring a client certificate. Also kept from #62: upstream ProtocolMessageTypes stops at RespondCostInfo = 107 at 0.26.0, 0.36.1 and 0.47.0, so the DIG 200-222 band collides with nothing. Not carried forward: #62's verdicts that chia-protocol and chia-sdk-client could not be deleted. The redesign it named as a prerequisite is exactly what was done. The new entry records the mechanism — DigMessage's raw u8 msg_type, which makes the opcode band expressible with no enum to extend — and that the wire was pinned by golden vectors before the refactor and is byte-identical after. Also corrects the #1371 entry, which credited from_server_websocket to the vendored chia-sdk-client; it now lives on DigLink, and is one of the reasons the fork could go. README's PeerConnection field and Cargo.toml's dependency-shape rationale follow the same rename (Cargo.toml comments only — no dependency, version or patch line is touched). Refs dig_ecosystem#2228, dig_ecosystem#2647 Co-Authored-By: Claude <noreply@anthropic.com>
… trap Carries forward the one durable lesson from the abandoned rebase investigation (PR #62): [patch.crates-io] substitutes only where the patched version SATISFIES the requirement, so a mismatched patch is dropped with a warning and EXIT=0 — the failure has no shape. Rewritten for the post-deletion world, where it now explains why the remaining native-tls patch is guarded in publish.yml rather than by the compiler: nothing stops compiling if it is stripped, the acceptor just stops requiring a client certificate. Also kept from #62: upstream ProtocolMessageTypes stops at RespondCostInfo = 107 at 0.26.0, 0.36.1 and 0.47.0, so the DIG 200-222 band collides with nothing. Not carried forward: #62's verdicts that chia-protocol and chia-sdk-client could not be deleted. The redesign it named as a prerequisite is exactly what was done. The new entry records the mechanism — DigMessage's raw u8 msg_type, which makes the opcode band expressible with no enum to extend — and that the wire was pinned by golden vectors before the refactor and is byte-identical after. Also corrects the #1371 entry, which credited from_server_websocket to the vendored chia-sdk-client; it now lives on DigLink, and is one of the reasons the fork could go. README's PeerConnection field and Cargo.toml's dependency-shape rationale follow the same rename (Cargo.toml comments only — no dependency, version or patch line is touched). Refs dig_ecosystem#2228, dig_ecosystem#2647 Co-Authored-By: Claude <noreply@anthropic.com>
Closing unmerged — this branch's central finding has been falsified by PR #63This PR concluded that no vendored fork could be deleted ("three noes, each measured") and recorded the What changed#63 deletes
This PR's verdict was correct about the constraint and wrong about the conclusion: it treated "this Why this branch cannot merge in either order
What was salvaged, and where it now lives
One fragment is genuinely unique and still true, and is being ported into #63 rather than lost: the The branch is not deleted, so every byte above remains recoverable at Follow-ups this PR correctly identified, which stay open
|
`test_register_introducer_connect_fail` dials `wss://127.0.0.1:7/ws` with nothing listening and accepted only `ClientError | IntroducerError`. A refused TCP connect is unambiguously "never reached the peer", which is what the settled design assigns to `LinkError` — so the code was right and the expectation was stale. This is the same defect `1e29f2e` fixed in `dsc_004_tests::test_query_introducer_connect_fail`, and it takes that commit's shape exactly, including the DSC-005 acceptance row. Two things are worth recording about why it survived that commit: - The fail-fast nextest run stopped inside `dsc_004` and never reached `dsc_005`, so the twin sat in the 446 tests that never executed. It surfaced on the first run after `--no-fail-fast` landed. - A local `--all-features` run reports it PASSING. That configuration enables `native-tls` and `rustls` together, which takes a different connector path than CI's `--no-default-features --features native-tls,...`. An `--all-features` green is not evidence for this crate. This is not a relaxation. The prohibition on this branch is against accepting either variant for a handshake-POLICY rejection, where `ClientError` is the only correct answer; `test_query_introducer_handshake_wrong_network` still demands it specifically and still passes. Verified in CI's exact feature set: `13 tests run: 13 passed, 0 skipped`. Also salvages the one non-duplicated section of PR #62 before it is discarded: why upstream `native-tls` cannot replace the vendored patch, and the single consumer configuration (dig-node, `default-features = false, features = ["rustls", "relay"]`) in which the patch is not load-bearing — verified against dig-node's manifest. Refs dig_ecosystem#2228 Co-Authored-By: Claude <noreply@anthropic.com>
…rks (#63) * test(wire): pin golden frame vectors for opcodes 200/217/218/219/220 dig-gossip's wire is vendored byte-identical into dig-relay (GPL-2.0) and runs on a live peer network, so the migration off the vendored chia-protocol fork (dig_ecosystem#2228) must not move a single byte. Pin the encoded frames BEFORE any refactor so the claim is measurable rather than asserted: a vector added afterwards would only record whatever the new code produces. Each vector distinguishes the real encoding from the nearest wrong one -- both Option<u16> correlation-id states, a payload with distinguishable ends so a reversed body or wrong length-prefix width is visible, a non-default NodeType discriminant, and both RegisterAck outcomes. Refs: dig_ecosystem#2228 Co-Authored-By: Claude <noreply@anthropic.com> * refactor(deps)!: bump dig-peer-protocol to 0.4 and import chia types directly dig-peer-protocol 0.4.0 replaced its blanket `pub use chia_protocol::*` with three named re-exports, so the ~20 genuine chia full-node types dig-gossip used through that glob (Bytes32, Handshake, RequestPeers, SpendBundle, ...) now come from a direct chia-protocol import. That is the intended shape -- chia types for chia peers -- not a workaround. Handshake.node_type is chia_protocol::NodeType while every DIG-side surface speaks dig_peer_protocol::NodeType. Bridge them explicitly rather than casting: both are closed enums over the same seven roles, so the conversion is total in each direction and an exhaustive match makes a future divergence in either crate a build failure instead of a silently wrong role. The transport type (Message -> DigMessage) is deliberately left for the next commit; it is the transport migration proper, not an import fix. Refs: dig_ecosystem#2228 Co-Authored-By: Claude <noreply@anthropic.com> * refactor(wire): frame DIG opcodes as DigMessage instead of the forked enum DigMessage.msg_type is a raw wire byte, so a DIG opcode no longer needs a ProtocolMessageTypes variant to name it -- which is the entire reason the vendored chia-protocol fork existed on this path. frame_envelope, frame_dig_message, frame_holdings_announce and frame_store_melted now build DigMessage directly, and frame_dig_message loses its from_bytes round-trip through the enum. message_wire_len stops returning Result: DigMessage::to_bytes is infallible because msg_type is already a byte and data is already serialized. The golden vectors pinned in 5119249 must still pass byte-identically; that is what makes this a refactor rather than a wire change. Refs: dig_ecosystem#2228 Co-Authored-By: Claude <noreply@anthropic.com> * refactor(transport): migrate the connection layer to DigLink DOES NOT COMPILE (2 errors) -- unfinished plumbing, not a flawed approach. Salvaged from a lane killed by a session limit so the work is not lost. Swaps chia_sdk_client::Peer for dig_peer_protocol::DigLink, which is what retires BOTH vendored crates: DigLink carries chia typed traffic via opcode_of::<T>() AND the DIG band via send_dig/send_message, and supplies the from_server_websocket the vendored fork exists to provide. Refs #2619. * refactor(transport): carry both bands over DigLink, retiring the Peer coupling chia_sdk_client::Peer decodes every inbound frame with chia_protocol::Message:: from_bytes, whose ProtocolMessageTypes stops at RespondCostInfo = 107 -- so it rejects DIG opcodes 200-222 before dig-gossip's own code runs. That runtime decode, not any compile-time type, is what kept the vendored chia-protocol fork alive. DigLink frames a raw opcode byte and so carries the DIG band natively, while still sending Chia-typed bodies via opcode_of::<T>(); one link per connection serves both bands. Inbound rate limiting moves from chia_sdk_client::RateLimiter, which is keyed by ProtocolMessageTypes and therefore cannot express a DIG opcode at all, to dig_peer_protocol::OpcodeRateLimiter, which re-keys Chia's own V2_RATE_LIMITS onto the wire byte and so preserves every Chia bound unchanged. Chia-band opcodes are derived from ProtocolMessageTypes in one small module rather than written as literals, keeping chia-protocol the single authority for Chia opcode numbering. Refs: dig_ecosystem#2228 Co-Authored-By: Claude <noreply@anthropic.com> * refactor(discovery): take introducer 218/219 bodies from dig-peer-protocol RegisterPeer and RegisterAck were declared here with #[streamable(message)], which makes the proc-macro emit a ProtocolMessageTypes::RegisterPeer path -- a variant that exists only in the forked chia-protocol. That one attribute was the second thing anchoring the fork. dig-peer-protocol declares the same bodies with plain #[streamable] plus to_dig_message/from_dig_message, carrying the opcode as a raw byte. No enum variant is needed, and the bodies now have one definition shared across repos instead of one per repo. Registration correspondingly moves from the Chia-typed request_infallible to request_dig. Adds the CON-005 guard battery for the rate-limit re-key: an entry that fails to carry from ProtocolMessageTypes onto the wire byte does not error, it silently falls through to the far looser default_settings, which is a DoS surface no compiler can see. Bounds are pinned as absolute literals from both sides, with a control proving an untabled opcode really is looser -- without it the battery would pass even if every opcode were capped identically. All 9 golden wire vectors pass byte-identically. The wire has not moved. Refs: dig_ecosystem#2228 Co-Authored-By: Claude <noreply@anthropic.com> * feat(deps)!: delete the vendored chia-protocol and chia-sdk-client forks Both forks existed for one reason: Chia's ProtocolMessageTypes stops at RespondCostInfo = 107 and cannot name a DIG opcode (200-222). chia-protocol was forked to add the 23 variants; chia-sdk-client was forked because its Peer reader decodes with chia_protocol::Message::from_bytes, which rejects any frame whose opcode has no variant -- and because upstream has no server-side constructor for an accepted websocket. dig_peer_protocol::DigLink frames a raw opcode byte and ships from_server_websocket, so neither reason survives. Both vendored trees are deleted and both [patch.crates-io] entries removed. native-tls STAYS patched. It is unrelated to this fork and is security load-bearing: upstream TlsAcceptorBuilder exposes no way to require a client certificate, so dropping it would still compile while silently accepting inbound peers with no client cert at all (CON-009). Verified from the resolved graph rather than from a green build, because a mismatched [patch] disappears with a warning and exit 0: cargo metadata reports no unused-patch warnings, no vendored chia-* package, and exactly one version each of chia-protocol (0.26.0) and chia-sdk-client (0.28.0), both from the registry. native-tls remains the sole vendored entry. BREAKING CHANGE: the peer transport is dig_peer_protocol::DigLink rather than chia_sdk_client::Peer; inbound channels carry DigMessage rather than chia_protocol::Message; classify_broadcast takes a wire byte; message_wire_len is infallible; GossipError gains a LinkError variant. Refs: dig_ecosystem#2228 Co-Authored-By: Claude <noreply@anthropic.com> * test(transport): port integration tests to the DigLink transport The chia-protocol/chia-sdk-client fork deletion changed the inbound and outbound transport types the integration harness builds against. Port the test suite to DigLink's surface and bump the version to 0.23.0 (0.x breaking bump for the transport change). Co-Authored-By: Claude <noreply@anthropic.com> * test(connection): key the in-crate unit tests by raw DIG opcode Upstream `chia_protocol::ProtocolMessageTypes` is a closed enum ending at `RespondCostInfo = 107`, so the fork-only `HoldingsAnnounce` (222) and `StoreMelted` (221) variants no longer exist. Port every `#[cfg(test)]` module in `src/` onto the raw `u8` opcode constants and the `dig-peer-protocol` 0.4.0 `OpcodeRateLimiter` API. Two guards were silently defused by the enum's disappearance and are repaired, not merely recompiled: - `public_flood_opcode_set_is_exactly_221_and_222` filtered each opcode through `ProtocolMessageTypes::from_bytes`, which now FAILS for 221 and 222 — so the loop skipped exactly the two opcodes the test is named after and asserted the empty set. It now walks all 256 raw opcodes with no decode filter, and additionally names the resulting set in full. - `every_220_band_opcode_is_classified` had the same filter over the 220-band, which now matches nothing at all. It enumerates `dig_peer_protocol::ALL_DIG_OPCODES` — the canonical namespace list — and asserts the band it checks is non-empty. The staggered-construction fixture in `dig_rate_limiter` is preserved: the Chia half is now an `OpcodeRateLimiter` built at the window start against the published `Handshake` row (5/window), the DIG half ~1 s later, so an elapsed-since-construction window still fails observably. Co-Authored-By: Claude <noreply@anthropic.com> * style(types): drop the unused Streamable import in peer.rs `DigMessage::to_bytes` is an inherent, infallible method, so the trait import is dead and fails the `-D warnings` gate. Co-Authored-By: Claude <noreply@anthropic.com> * style(limits): drop five no-op u8 casts left by the opcode port `DigMessage::msg_type` is already a raw `u8` since the port off `ProtocolMessageTypes`, so these `as u8` casts are no-ops that trip `clippy::unnecessary_cast` under `-D warnings`. Co-Authored-By: Claude <noreply@anthropic.com> * build(deps): re-pin dig-peer-protocol to 0.5 0.5.0 adds `impl From<&RateLimits> for OpcodeRateLimits`, which the CON-005 tests need. A 0.x minor is semver-incompatible, so the old "0.4" requirement could never resolve it. Co-Authored-By: Claude <noreply@anthropic.com> * test(con-005): port the rate-limit tests onto OpcodeRateLimiter `dig-peer-protocol` 0.5.0 replaces chia's `ProtocolMessageTypes`-keyed `RateLimiter` with `OpcodeRateLimiter`, keyed by the raw wire opcode, so the CON-005 tests move onto it: `OpcodeRateLimits::from(&limits)` re-keys the same chia table the old fixtures used, and `allow` replaces `handle_message`. The one semantic difference — `admit` does not charge a refused message, where chia's inbound limiter did — changes no assertion here: every test exhausts its budget with ADMITTED frames and then asserts the next one is refused, so nothing depends on a rejection consuming budget. The charge-on-reject property still lives in dig-gossip's own `DigRateLimiter`, which keeps the `incoming` flag. `test_rate_limit_blocks_oversized_message` gets stronger rather than merely ported: the bound is pinned from both sides (at `max_size` admits, one byte over refuses) and the refusal is asserted as `Unsendable`, not just "not admitted" — a size refusal survives every window roll, so reporting it as `Deferred` would send a retrying caller to wait on a budget that can never clear. `Admission` is re-exported to make that assertable. Also clears the warnings the previously-failing target masked: unused imports across seven test files and two more no-op `as u8` casts. Co-Authored-By: Claude <noreply@anthropic.com> * test(wire): pin the golden vectors in the decode direction too The nine vectors were encode-only, which is the wrong half of the contract. The vendored `chia-protocol` fork existed *because* `Message::from_bytes` rejected DIG opcodes; this PR replaces that decode path, and dig-relay vendors the wire byte-identically and sends frames dig-gossip must accept. Proving we still emit the same bytes says nothing about whether we still read theirs. Each hex literal is now a `const` consumed by BOTH an encode and a decode test, so the two directions cannot drift apart — an expectation captured from a decoder run could not fail when the run is wrong. Adds a negative vector: a frame whose opcode has no `ProtocolMessageTypes` variant must decode successfully, which is the entire reason `DigMessage` keys on a raw `u8`. It is paired with an assertion that `ProtocolMessageTypes::from_bytes` genuinely rejects that opcode — without it the vector would pass just as happily on an opcode the forked enum accepts, and so could not distinguish "decodes DIG opcodes" from "has not happened to reject one yet". Also applies rustfmt, which had drifted on this branch and in several test files the fmt gate would have failed on. Co-Authored-By: Claude <noreply@anthropic.com> * fix(con-001): a refused dial is a LinkError now the dial is DigLink's `test_outbound_connect_failure` was the only failing test left in the suite: since the outbound dial moved off `chia-sdk-client` onto `DigLink`, a refused TCP connection arrives as `GossipError::LinkError(WebSocket(Io(ConnectionRefused)))`, not `ClientError`. The truthful contract is the new one, so the CON-001 spec row moves with it rather than the mapping being papered over: `ClientError` still carries handshake-POLICY failures (network-id mismatch, incompatible protocol version), and keeping the two variants distinct is what lets a caller tell "never reached the peer" from "the peer refused us". The test therefore asserts the specific variant — `is_err()` would pass on a handshake rejection that never touched the wire. Co-Authored-By: Claude <noreply@anthropic.com> * docs(ci): state the real crates.io publish blocker after the chia fork deletion The publish guard's comments and echo text listed three vendored forks in [patch.crates-io] (chia-protocol, chia-sdk-client, native-tls) and attributed the block to 19 compile errors from cargo publish stripping them. Two of those forks were deleted in dig_ecosystem#2228; only native-tls remains. The publish is still correctly blocked, but for a different and more dangerous reason: cargo publish strips [patch.crates-io], so a published dig-gossip would build against upstream native-tls, compile cleanly, and silently accept inbound peers presenting no client certificate (CON-009). The failure mode is a silent security regression, not a build break. Comments and echo text only — the guard step, its condition and its exit 1 are unchanged. Refs dig_ecosystem#2228, dig_ecosystem#2647 Co-Authored-By: Claude <noreply@anthropic.com> * docs(spec): correct the Section 1 crate-reuse claims to the DigLink transport Sections 1 through 1.7 described the peer connection as chia-sdk-client's `Peer`, reached by `connect_peer()`, with rate limiting by `RateLimiter` + `V2_RATE_LIMITS` and payloads delivered as `chia-protocol::Message`. None of that is the contract any more: dig-gossip depends on `dig-peer-protocol`, whose `DigLink` frames a raw u8 opcode, and delivers `DigMessage`. The Chia types are still used, but as re-exports rather than direct dependencies. SPEC.md is normative, so an independent reimplementation built against these sentences would have named types that dig-gossip does not use. Refs dig_ecosystem#2228 Co-Authored-By: Claude <noreply@anthropic.com> * docs(spec): retire the vendored-fork claims and the Peer/connect_peer API surface Sections 2, 5, 8 and 10 still described a vendored `chia-protocol`, a `vendor/` thin-wrapper policy, and an API built on `chia-sdk-client`'s `Peer`, `PeerOptions`, `RateLimiter` and `connect_peer()`. The vendored Chia forks were deleted in dig_ecosystem#2228; the peer link is `dig_peer_protocol::DigLink`, inbound uses `DigLink::from_server_websocket()`, and the framing encoder writes a raw u8 opcode instead of mirroring a discriminant onto `ProtocolMessageTypes`. Corrected in this commit: - 2.1 / 10.2 re-export blocks, which named types the crate does not re-export - 2.4 PeerConnection, whose `peer` field is a `DigLink` - 2.3 / 8.x frame and payload signatures, which take and return `DigMessage` - 5.1 / 5.2 connection flows, incl. why outbound mirrors rather than calls upstream's `connect.rs` (it must capture the remote SPKI DER) - 5.3's `Peer::from_server_websocket` and the native-tls patch rationale - 8.1's "vendored `chia-protocol`" clone note and `vendor/` policy reference - the dispatch-authority paragraph's "mirrors onto the vendored `ProtocolMessageTypes`" claim - 10.3 / 10.4 feature-flag and dependency snippets The wire is unchanged: all nine golden hex vectors are byte-identical across the refactor, so no on-wire statement in this document moved. Refs dig_ecosystem#2228 Co-Authored-By: Claude <noreply@anthropic.com> * docs(requirements): retarget the crate-structure and prompt specs at dig-peer-protocol STR-001, STR-003, STR-004, IMPLEMENTATION_ORDER and prompt/start.md still required a direct chia-sdk-client dependency, feature forwards to chia-sdk-client/*, and re-exports of Peer/PeerOptions/RateLimiter. The manifest and the str_001 tests already assert the opposite: dig-peer-protocol at 0.4 with default-features = false, and both TLS features forwarding through it. STR-001 gains an acceptance criterion that [patch.crates-io] contains native-tls and nothing else, so a re-vendored Chia crate is a spec violation rather than a silent regression. Refs dig_ecosystem#2228 Co-Authored-By: Claude <noreply@anthropic.com> * docs(requirements): retarget the domain specs at DigLink and the DIG opcode band The connection, crate_api, discovery, relay, privacy and erlay domain specs still specified chia-sdk-client's Peer, PeerOptions, connect_peer(), Peer::from_websocket() and RateLimiter, and two TRACKING.yaml files cited vendor/chia-protocol and vendor/chia-sdk-client paths that no longer exist. Corrections of substance, beyond the renames: - CON-001 no longer specifies calling connect_peer(). The crate deliberately mirrors upstream's connect.rs instead, because upstream discards the parsed Handshake and never exposes the remote TLS SPKI DER, and API-005 needs both. - CON-002 states why inbound needs from_server_websocket: from_websocket types the stream as the client-oriented MaybeTlsStream. - CON-005 keys both rate-limit bounds by the raw wire opcode; the previous text had the Chia bound keyed by ProtocolMessageTypes. - DSC-005 tracking no longer claims a vendored chia-protocol supplies the 218/219 enum variants. They travel as raw DigMessage msg_type bytes. - CON-009 tracking names vendor/native-tls as the only remaining vendored crate. All TRACKING.yaml and registry files re-validated as YAML. Refs dig_ecosystem#2228 Co-Authored-By: Claude <noreply@anthropic.com> * docs(devlog): record the fork deletion and salvage the patch-not-used trap Carries forward the one durable lesson from the abandoned rebase investigation (PR #62): [patch.crates-io] substitutes only where the patched version SATISFIES the requirement, so a mismatched patch is dropped with a warning and EXIT=0 — the failure has no shape. Rewritten for the post-deletion world, where it now explains why the remaining native-tls patch is guarded in publish.yml rather than by the compiler: nothing stops compiling if it is stripped, the acceptor just stops requiring a client certificate. Also kept from #62: upstream ProtocolMessageTypes stops at RespondCostInfo = 107 at 0.26.0, 0.36.1 and 0.47.0, so the DIG 200-222 band collides with nothing. Not carried forward: #62's verdicts that chia-protocol and chia-sdk-client could not be deleted. The redesign it named as a prerequisite is exactly what was done. The new entry records the mechanism — DigMessage's raw u8 msg_type, which makes the opcode band expressible with no enum to extend — and that the wire was pinned by golden vectors before the refactor and is byte-identical after. Also corrects the #1371 entry, which credited from_server_websocket to the vendored chia-sdk-client; it now lives on DigLink, and is one of the reasons the fork could go. README's PeerConnection field and Cargo.toml's dependency-shape rationale follow the same rename (Cargo.toml comments only — no dependency, version or patch line is touched). Refs dig_ecosystem#2228, dig_ecosystem#2647 Co-Authored-By: Claude <noreply@anthropic.com> * docs(manifest): drop the last vendored-fork references from the comment blocks Three dependency comments still pointed at `chia_sdk_client::Peer` and one called the rustls backend 'the vendored chia-sdk-client outbound connector'. The type is `dig_peer_protocol::DigLink` and chia-sdk-client is now an ordinary transitive crates.io dependency. Comments only: the diff contains no dependency, version, feature or [patch.crates-io] line. Refs dig_ecosystem#2228 Co-Authored-By: Claude <noreply@anthropic.com> * docs(connection): describe the DigLink transport, not the deleted forks listener.rs and outbound.rs still documented `chia-sdk-client` and `vendor/chia-sdk-client` as the transport, and outbound.rs linked a docs.rs page for a function neither crate exposes here. Port the reasoning to what the code now does. Co-Authored-By: Claude <noreply@anthropic.com> * docs(discovery): fix references to deleted vendor directories Four doc comments referenced vendored chia-protocol and chia-sdk-client directories that were deleted during the DPP 0.4.0 adoption. Update them to describe what the code actually does now: - introducer_client.rs:19 — point to the real dial reference (src/connection/outbound.rs) instead of vendor path; note that chia-sdk-client remains a legitimate crates.io transitive dependency - introducer_register_wire.rs:13 — replace "fork keeps it alive" with "DIG opcodes now travel as raw msg_type bytes, making the fork removable" - discovery/mod.rs:13 — drop vendor-path reference; explain that opcodes 218/219 re-export from dig-peer-protocol - gossip_handle.rs:988 — replace "(see vendor/chia-sdk-client patch)" with "a property of the DigLink receiver" Code behavior unchanged; comment text only. Co-Authored-By: Claude <noreply@anthropic.com> * test(dsc-004): handle LinkError in connect_fail test The introducer client now uses DigLink for the dial, so a refused TCP connect surfaces as GossipError::LinkError, not ClientError. This matches the CON-001 pattern where the dial's typed connection-level error is LinkError; ClientError remains reserved for handshake-policy failures (dig_ecosystem#2228). Add LinkError to the match in test_query_introducer_connect_fail and update DSC-004 acceptance criteria and test plan to clarify the expected error variant. Co-Authored-By: Claude <noreply@anthropic.com> * docs(manifest): the DIG extension band runs to 222, not 219 220 is `DIG_MESSAGE`, 221 `STORE_MELTED`, 222 `HOLDINGS_ANNOUNCE`, and all three are live. dig-relay vendors this wire byte-identically, so an understated band in a dependency comment is exactly the kind of number that gets copied into a second implementation. Comment only — the dependency line is unchanged. Co-Authored-By: Claude <noreply@anthropic.com> * docs(tests): update stale references to vendor paths and legacy transport APIs Replace stale doc/comment text referencing deleted `vendor/chia-protocol` and `vendor/chia-sdk-client` paths; update references to the old `connect_peer()` function (which does not exist) with descriptions of the actual transport establishment via TLS + handshake in `dig_peer_protocol::DigLink`. Update `send_protocol_message` reference to the actual `send_message` method. Facts: - chia_sdk_client::Peer → dig_peer_protocol::DigLink (the connection type) - connect_peer() does not exist (outbound dial is in src/connection/outbound.rs) - send_protocol_message → send_message - vendor/chia-protocol/, vendor/chia-sdk-client/ paths no longer exist Files changed: 8 test files with doc/comment-only updates. No test logic, assertions, fixtures, or use statements changed. Co-Authored-By: Claude <noreply@anthropic.com> * docs(handshake): cite the real outbound dial, not the retired connect_peer() The SPEC traceability block quoted `connect_peer()`, a function no crate in this tree exposes. SPEC.md no longer uses that name either, so the citations pointed at language that had been rewritten. Co-Authored-By: Claude <noreply@anthropic.com> * fix(connection): report a handshake-policy rejection as ClientError on both dial legs Recovered WIP from a lane that stopped before pushing. Introduces `DialError`, the two-armed union a dial returns so a policy verdict is not downgraded into a transport error on the way out. Before this, the outbound leg and the introducer client had only `LinkError` available in their return type, so `ClientError::WrongNetwork` / `WrongNodeType` were rendered as `LinkError::Io(Custom(<formatted string>))`. That put "never reached the peer" and "the peer failed our policy" in one variant distinguishable only by matching the error string, which is not a contract, and it contradicted the settled decision on this PR that the two stay distinct because they carry opposite retry semantics. The inbound listener already returned `ClientError` for a policy rejection, so this brings the outbound leg into line rather than inventing a new shape. WIP: not yet compiled or tested on this branch; pushed first so the work survives. Co-Authored-By: Claude <noreply@anthropic.com> * ci: run the suite with --no-fail-fast so a count is a total, not a floor `cargo nextest` stops at the first failing target by default, and the run then reports a pass count that is indistinguishable from a complete one. On this branch a red head stopped at 531 of 977: 446 tests never executed, and because the run aborted before finishing, `--fail-under-lines 80` never measured the coverage floor either. That is worse than a plain failure. "Unmeasured" and "measured and under" are different verdicts, and the job was rendering the first as though it were a count you could reason about. Every suite total quoted from such a run is a lower bound with no upper bound attached. `--no-fail-fast` makes the job finish counting before it fails. It does not weaken the gate: nextest still exits non-zero on any real failure, `--retries 2` still classifies transient loopback flakes, and the coverage floor now gets a complete measurement to apply itself to. Added to the rustls `--lib` job for the same reason. Refs dig_ecosystem#2228 Co-Authored-By: Claude <noreply@anthropic.com> * docs(spec): correct the dependency, transport-type and rate-limiter claims SPEC.md is the contract an independent reimplementation is built against, so each of these sentences was a real defect rather than a cosmetic one. * Dependency routing. SPEC.md claimed dig-peer-protocol was "the single dependency" reaching the Chia ecosystem, and that the Chia crates were "reached through the re-exports above", while its own table eight lines later marked chia-protocol and chia-traits as direct reuse. Cargo.toml declares both directly and lib.rs re-exports the wire types straight from chia_protocol, so the prose was the wrong half. Also corrects chia-ssl, which the table called direct reuse but which is not in Cargo.toml at all, and the Cargo.toml comment making the same over-claim. * Superseded transport type. The crate transports dig_peer_protocol::DigMessage; chia_protocol::Message appears zero times under src/. A reimplementer reading the old sentence would build on Chia's closed #[repr(u8)] discriminant, which is precisely the type this branch exists to stop using, and would hit the decode rejection the vendored fork was created to work around. * Rate limiter. Four code sites and three CON-005 rows asserted the inbound gate charges refused frames on both halves. Only DigRateLimiter is inbound-shaped today; OpcodeRateLimiter exposes no such flag in dig-peer-protocol 0.5, and OpcodeRateLimits' fields are private so there is no local substitute. Each site now states what is true and marks the gap with TODO(dig_ecosystem#2228) rather than asserting a security property the code does not have. * Rename collateral. A Peer -> DigLink rename ran through prose it should not have, mangling quoted SPEC headings ("Peer identity from mTLS", "Peer sharing via gossip", "Message cache") into names those sections do not have. Co-Authored-By: Claude <noreply@anthropic.com> * test(dsc-005): a refused dial is a LinkError, matching the dsc-004 twin `test_register_introducer_connect_fail` dials `wss://127.0.0.1:7/ws` with nothing listening and accepted only `ClientError | IntroducerError`. A refused TCP connect is unambiguously "never reached the peer", which is what the settled design assigns to `LinkError` — so the code was right and the expectation was stale. This is the same defect `1e29f2e` fixed in `dsc_004_tests::test_query_introducer_connect_fail`, and it takes that commit's shape exactly, including the DSC-005 acceptance row. Two things are worth recording about why it survived that commit: - The fail-fast nextest run stopped inside `dsc_004` and never reached `dsc_005`, so the twin sat in the 446 tests that never executed. It surfaced on the first run after `--no-fail-fast` landed. - A local `--all-features` run reports it PASSING. That configuration enables `native-tls` and `rustls` together, which takes a different connector path than CI's `--no-default-features --features native-tls,...`. An `--all-features` green is not evidence for this crate. This is not a relaxation. The prohibition on this branch is against accepting either variant for a handshake-POLICY rejection, where `ClientError` is the only correct answer; `test_query_introducer_handshake_wrong_network` still demands it specifically and still passes. Verified in CI's exact feature set: `13 tests run: 13 passed, 0 skipped`. Also salvages the one non-duplicated section of PR #62 before it is discarded: why upstream `native-tls` cannot replace the vendored patch, and the single consumer configuration (dig-node, `default-features = false, features = ["rustls", "relay"]`) in which the patch is not load-bearing — verified against dig-node's manifest. Refs dig_ecosystem#2228 Co-Authored-By: Claude <noreply@anthropic.com> * fix: return MissingHandshake typed client errors Co-authored-by: MichaelTaylor3d <5665004+MichaelTaylor3d@users.noreply.github.com> * fix(keepalive): send the liveness probe uncorrelated (#2767) Two dig-gossip peers tore each other's link down. Both allocate correlation ids from a counter starting at zero and both keepalive loops start at handshake on a shared interval, so the first probes carried the same id. DigLink matches inbound frames on correlation id before forwarding, so each side's waiter received the peer's RequestPeers, the peer's request never reached the forwarder, its auto-reply never fired, and both sides tore down at the staleness check while logging a timeout that named the wrong cause. The probe now goes out with id: None and liveness is observed on the application inbound broadcast, which skips the id-match arm entirely. An unavailable broadcast is liveness-neutral: the round is skipped and the peer is kept, the correct direction for a probe whose only action is to disconnect. Co-Authored-By: Claude <noreply@anthropic.com> * test(keepalive): pin the uncorrelated probe against a parked correlation waiter (#2767) Co-Authored-By: Claude <noreply@anthropic.com> * test(keepalive): regression + fail-open coverage for the probe collision (#2767) Co-Authored-By: Claude <noreply@anthropic.com> * fix(dial): classify a non-Handshake first frame as policy, not transport (#2228) An opcode outside ProtocolMessageTypes cannot be expressed as a typed ClientError, so it takes a dig-gossip-owned DialError::UnknownOpcode carrying the raw byte rather than a formatted string. Also removes the false --no-default-features coherence claim in introducer_client and the stubs that existed only to serve it: no TLS backend does not build today (dig_ecosystem#2225), and makes the crates.io publish guard unconditional so dispatch is not an escape hatch around the stripped native-tls patch (dig_ecosystem#2647). Co-Authored-By: Claude <noreply@anthropic.com> * docs(con-004): record that the keepalive probe must be uncorrelated (#2767) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
DO NOT MERGE — investigation result. The rebase this branch was opened for cannot be done in this repo.
Headline
Nothing was rebased and nothing was deleted, because measurement refuted both options. The valuable output is the evidence, now recorded in
DEVELOPMENT_LOG.mdand the three vendor READMEs so the next attempt does not re-derive it.The rebase is a cross-repo cascade, not a local bump
Setting
vendor/chia-protocolto 0.36.1 produced the most dangerous shape a build failure can take: none at all.[patch.crates-io]substitutes a package only where the patched version SATISFIES the existing requirement. dig-gossip reacheschia-protocolthroughdig-peer-protocol, which pinschia-protocol = "0.26"andchia-sdk-client = "0.28"— still true at the latest published 0.3.0. A 0.36.1 patch satisfies neither, so Cargo drops the patch and resolves pristine upstream, with a warning and exit 0.Ordering a real rebase requires:
dig-peer-protocolmoves and republishes (release-first, §4.1) → dig-gossip's own directchia-protocol/chia_streamable_macropins → the vendored trees. That is at minimum two repos.The only thing that makes the dropped patch loud is that
ProtocolMessageTypes::RegisterPeerstops existing and the tree stops compiling — includingtests/wire_golden_vectors.rs. Treat that compile break as the guard it is.Can each fork die instead? Three noes, each measured
chia-protocol#[repr(u8)], not#[non_exhaustive], andMessage.msg_typeis typed as it, sofrom_bytesrejects a DIG opcode.dig_peer_protocol::DigMessageis a raw-u8escape hatch, but adopting it de-types the inbound decode, the broadcast classifier and the rate-limit keying — a redesign that only moves the delta into thechia-sdk-clientfork we keep anyway.chia-sdk-clientsend_protocol_message, nofrom_server_websocket(still concreteSplitSink/SplitStream), no inbound-RequestPeersrouting.native-tlsnative-tlsis the DEFAULT feature andnative_tls_acceptorcompiles underall(feature = "native-tls", not(feature = "rustls")). UpstreamTlsAcceptorBuilderexposes onlymin_protocol_version/max_protocol_version/accept_alpn/build— no way to require a client cert. Dropping it would silently accept inbound peers with no client certificate, defeating CON-009.The
RateLimits::dig_wireitem sometimes listed as a fourth delta no longer exists — removed in dig_ecosystem#2228, nowconnection::dig_rate_limiter.Upstream does not collide with 200–222
ProtocolMessageTypesstops atRespondCostInfo = 107at 0.26.0, 0.36.1 and 0.47.0 alike. No collision, no forced renumber, no wire break. The vendored version can stay where it is indefinitely without risking the live wire.Blast radius
git diff --stat origin/main...HEADtouches zero files undersrc/and zero vendored.rs. One version bump, one new test file, four docs files. No symbol was edited, so no wire, no consumer and no dig-relay vendored copy is affected.Golden vectors
6/6 green (
register_peer,register_ack,dig_messagewith and without an id, the zero-length prefix case, and the round trip through the vendored decoder). Stated honestly: they are a baseline, not a proof of a rebase — no vendored code moved, so they measure only that the pre-rebase wire is recorded. Their value is to the next attempt.Gates
cargo fmt --all --check— exit 0 (this branch also formats the test file the prior commit left unformatted).cargo clippy --all-targets --all-features -- -D warnings— exit 0.cargo test --test wire_golden_vectors— exit 0, 6 passed.con_1691,str_001,str_002); they are untouched by this branch and CI runs them on ubuntu with--test-threads=1 --retries 2, green on main. CI on this PR is the authoritative gate.Version: 0.22.2 → 0.22.3 (docs + a test, no behaviour change).