Skip to main content

dig_peer_protocol/
lib.rs

1//! # dig-peer-protocol
2//!
3//! The DIG Network peer wire — a **native** protocol, plus the narrow chia surface a DIG node
4//! needs to also speak to chia full nodes.
5//!
6//! ## The native DIG wire
7//!
8//! [`DigMessage`] is the envelope for every DIG message: a raw `u8` opcode, an optional
9//! correlation id, and a [`Bytes`] payload, encoded by this crate and nothing else. It carries
10//! the `200..=222` DIG opcode band, which `chia_protocol::Message` structurally cannot express —
11//! its `ProtocolMessageTypes` is a closed `#[repr(u8)]` enum with no `Unknown(u8)`, so a DIG
12//! opcode is neither constructible nor decodable through it, and one inbound DIG frame drops a
13//! whole `chia-sdk-client` connection rather than that one frame.
14//!
15//! DIG answered that with vendored forks of `chia-protocol` and `chia-sdk-client`. This crate
16//! replaces the forks: [`DigLink`] is a websocket peer link written directly against the wire
17//! format, and the DIG types it carries — [`Bytes`], [`NodeType`], [`DigMessage`],
18//! [`DigMessageType`], [`RegisterPeer`], [`RegisterAck`] — are DIG's own.
19//!
20//! ## What is deliberately still chia, and why
21//!
22//! Decoupling from `chia-protocol` is not the same as decoupling from every crate whose name
23//! starts with `chia`. Two are kept ON PURPOSE. **Do not "finish the decoupling" by removing
24//! them** — they were assessed and retained:
25//!
26//! - **`chia-traits` ([`Streamable`]) and `chia_streamable_macro` ([`macro@streamable`])** — a
27//!   serialization trait and a derive macro. Neither has the property this crate is escaping:
28//!   there is no closed enum and no private-field wire authority in either. They serialize the
29//!   *bodies* of DIG messages, and they do it with an encoding that is already live on the
30//!   network. Replacing them would mean owning a serializer — new surface, and a fresh
31//!   byte-identity risk — to buy nothing that matters.
32//! - **`chia-protocol`'s `ChiaProtocolMessage` and `TimestampedPeerInfo`, and
33//!   `chia-sdk-client`** — these serve genuine *chia* traffic. A DIG node talks to chia full
34//!   nodes too: [`DigLink`]'s typed `send`/`request` derive a chia opcode from
35//!   `ChiaProtocolMessage`, [`RespondPeersIntroducer`] is chia opcode 64, and
36//!   [`OpcodeRateLimits`] re-keys chia's own published rate-limit table so a chia opcode is
37//!   limited exactly as a stock peer would limit it. Chia types for chia traffic is the design,
38//!   not a leftover.
39//!
40//! There is no blanket `pub use chia_protocol::*`. A glob re-export is how chia types reach
41//! consumers that never asked for them, and it made a chia version bump a breaking change to
42//! every downstream crate. What a chia-full-node path needs is named explicitly below.
43//!
44//! ## Feature flags
45//!
46//! | Flag | Forwards to | Effect |
47//! |------|-------------|--------|
48//! | `native-tls` | `chia-sdk-client/native-tls` | OS-native TLS; enables `Client`, `ClientState`, `Connector`, `create_native_tls_connector`, `DigLink::connect` |
49//! | `rustls` | `chia-sdk-client/rustls` | Pure-Rust TLS; enables `Client`, `ClientState`, `Connector`, `create_rustls_connector`, `DigLink::connect` |
50//!
51//! Neither is enabled by default. Without one, the TLS-dependent items above are unavailable;
52//! [`DigLink::from_websocket`] and [`DigLink::from_server_websocket`] stay available, since
53//! adopting an already-established socket needs no TLS backend of its own.
54
55// ============================================================================
56// Re-export: chia-protocol — NAMED, for chia-full-node paths only
57// ============================================================================
58// Explicitly not a glob. `ChiaProtocolMessage` is what `DigLink`'s typed send/request bound
59// their generics on, and `TimestampedPeerInfo` is a field of chia opcode 64; a consumer needing
60// any other chia wire type depends on `chia-protocol` directly and says so in its own manifest.
61pub use chia_protocol::{ChiaProtocolMessage, ProtocolMessageTypes, TimestampedPeerInfo};
62
63// ============================================================================
64// Re-export: chia-sdk-client (peer IO, TLS, rate limiting)
65// ============================================================================
66// Backend-agnostic types — always available.
67pub use chia_sdk_client::{
68    load_ssl_cert, ClientError, Network, Peer, PeerOptions, RateLimit, RateLimiter, RateLimits,
69    V2_RATE_LIMITS,
70};
71
72// `Client`, `ClientState`, and `Connector` require a TLS backend in `chia-sdk-client`.
73// Enable either the `native-tls` or `rustls` feature to use them.
74#[cfg(any(feature = "native-tls", feature = "rustls"))]
75pub use chia_sdk_client::{Client, ClientState, Connector};
76
77#[cfg(feature = "native-tls")]
78pub use chia_sdk_client::create_native_tls_connector;
79
80#[cfg(feature = "rustls")]
81pub use chia_sdk_client::create_rustls_connector;
82
83// ============================================================================
84// Re-export: chia-ssl (certificate types)
85// ============================================================================
86pub use chia_ssl::ChiaCertificate;
87
88// ============================================================================
89// Re-export: chia-traits (serialization)
90// ============================================================================
91pub use chia_traits::Streamable;
92
93// ============================================================================
94// Re-export: chia_streamable_macro (proc macro for wire structs)
95// ============================================================================
96pub use chia_streamable_macro::streamable;
97
98// ============================================================================
99// DIG extensions
100// ============================================================================
101mod bytes;
102mod dig_message;
103mod dig_message_type;
104mod error;
105mod introducer_wire;
106mod link;
107mod node_type;
108mod opcodes;
109mod rate_limit;
110mod request_map;
111
112pub use bytes::Bytes;
113pub use dig_message::DigMessage;
114pub use dig_message_type::{DigMessageType, UnknownDigMessageType};
115pub use error::LinkError;
116pub use introducer_wire::{
117    RegisterAck, RegisterPeer, RequestPeersIntroducer, RespondPeersIntroducer,
118};
119pub use link::{DigLink, LinkOptions};
120pub use node_type::{NodeType, UnknownNodeType};
121pub use opcodes::{
122    is_dig_opcode, ALL_DIG_OPCODES, DIG_BAND_START, DIG_MESSAGE, FREE_BAND_START,
123    HOLDINGS_ANNOUNCE, STORE_MELTED,
124};
125pub use rate_limit::{Admission, OpcodeRateLimiter, OpcodeRateLimits};
126
127#[cfg(test)]
128mod dig_message_opcode_tests {
129    use super::{DigMessage, DigMessageType, DIG_MESSAGE};
130
131    /// The opcode frames a real [`DigMessage`] and survives a wire round-trip with its
132    /// `msg_type` intact — the canonical value (220) exercised through the actual encoder.
133    #[test]
134    fn dig_message_opcode_frames_and_round_trips() {
135        let msg = DigMessage::new(DIG_MESSAGE, Some(9), vec![1, 2, 3].into());
136        let back = DigMessage::from_bytes(&msg.to_bytes()).expect("round-trip");
137        assert_eq!(back.msg_type, 220);
138        assert_eq!(back.msg_type, DIG_MESSAGE);
139        assert_eq!(back.data.as_ref(), &[1, 2, 3]);
140    }
141
142    /// 220 is in the free band: it is NOT a consensus `DigMessageType` discriminant, so a
143    /// consensus-band decode of the opcode fails — the two bands can never collide.
144    #[test]
145    fn dig_message_opcode_is_not_a_consensus_type() {
146        assert!(DigMessageType::try_from(DIG_MESSAGE).is_err());
147    }
148}