Skip to main content

dig_peer_protocol/
opcodes.rs

1//! The complete DIG opcode namespace — the `200..=225` band, in one place.
2//!
3//! DIG extends Chia's `ProtocolMessageTypes` (which stops at `RespondCostInfo = 107`) with a
4//! band that starts at **200**, leaving a 100-value gap for future upstream additions. The band
5//! has two halves:
6//!
7//! | Range | Half | Where it is defined |
8//! |---|---|---|
9//! | `200..=219` | **consensus** — the DIG L2 gossip opcodes | [`DigMessageType`], one variant each |
10//! | `220..=` | **free** — directed / broadcast application protocols | the constants below |
11//!
12//! The consensus half is an enum because each opcode has a fixed body type and a gossip
13//! strategy; the free half is plain constants because each opcode's body is owned by the
14//! application protocol that defines it, not by this crate.
15//!
16//! These values are **canonical**: a second implementation must match them byte for byte, and
17//! no assigned value ever moves (§5.1, additive only).
18
19use crate::DigMessageType;
20
21/// First opcode of the DIG band. Everything below this belongs to Chia.
22pub const DIG_BAND_START: u8 = 200;
23
24/// First opcode of the **free** half of the DIG band — application protocols, not L2 consensus.
25pub const FREE_BAND_START: u8 = 220;
26
27/// Wire opcode for a directed **dig-message** envelope (WU6, epic #796).
28///
29/// Carries a `dig-message` directed envelope as OPAQUE bytes in [`DigMessage::data`]. The
30/// transport (dig-gossip) never seals, opens, or parses it; end-to-end sealing to the
31/// recipient's DID key is `dig-message`'s job.
32///
33/// [`DigMessage::data`]: crate::DigMessage::data
34pub const DIG_MESSAGE: u8 = 220;
35
36/// Wire opcode for a **store-melted** broadcast (epic #1316).
37///
38/// Announces that a dig-store's on-chain coin has been melted, so peers stop hosting its `.dig`
39/// content. A public all-peers flood: signed and mTLS-authenticated, but NOT recipient-sealed —
40/// store deletion is addressed to everyone (the §5.4 public-broadcast carve-out).
41pub const STORE_MELTED: u8 = 221;
42
43/// Wire opcode for a **holdings-announce** broadcast (#1428, spec #1394).
44///
45/// Announces a batch of signed holdings add/remove deltas so peers learn which content a
46/// provider holds; this feeds dig-dht's holder set. Public flood, same carve-out as
47/// [`STORE_MELTED`].
48pub const HOLDINGS_ANNOUNCE: u8 = 222;
49
50/// Wire opcode for a **profile-root announce** broadcast (epic #3008).
51///
52/// Body is exactly 64 bytes: `store_id ‖ root`, two 32-byte hashes with no framing, announcing
53/// the sender's current profile-SMT root for that store. A public all-peers flood, same §5.4
54/// carve-out as [`STORE_MELTED`].
55///
56/// **Deliberately unsigned.** The authority for a profile root is the on-chain root, not the
57/// announcing peer, so the receiver compares any announced root against chain before trusting
58/// it. A forged announce therefore costs an attacker one wasted [`PROFILE_BODY_REQUEST`] that
59/// then fails that compare — signing would buy no additional guarantee while adding a signature
60/// verification to every message of the highest-volume broadcast in the band.
61pub const PROFILE_ROOT_ANNOUNCE: u8 = 223;
62
63/// Wire opcode for a directed **profile-body request** (epic #3008).
64///
65/// Body is exactly 64 bytes: `store_id ‖ root`, asking one peer for the profile body behind a
66/// root learned from a [`PROFILE_ROOT_ANNOUNCE`]. Answered with [`PROFILE_BODY`].
67pub const PROFILE_BODY_REQUEST: u8 = 224;
68
69/// Wire opcode for a directed **profile-body** response (epic #3008).
70///
71/// Body is `store_id ‖ root ‖ len:u32be ‖ body` — the two 32-byte hashes the request named,
72/// then a big-endian length prefix and that many bytes of profile body. The receiver rehashes
73/// the body and compares against `root`, which is why the announce that started the exchange
74/// needs no signature.
75pub const PROFILE_BODY: u8 = 225;
76
77/// Every opcode DIG has assigned, ascending — the 20 consensus opcodes plus the 6 free-band ones.
78///
79/// This is the list a peer link dispatches on and the list a conformance test checks against
80/// Chia's namespace for collisions.
81pub const ALL_DIG_OPCODES: [u8; 26] = [
82    DigMessageType::NewAttestation as u8,
83    DigMessageType::NewCheckpointProposal as u8,
84    DigMessageType::NewCheckpointSignature as u8,
85    DigMessageType::RequestCheckpointSignatures as u8,
86    DigMessageType::RespondCheckpointSignatures as u8,
87    DigMessageType::RequestStatus as u8,
88    DigMessageType::RespondStatus as u8,
89    DigMessageType::NewCheckpointSubmission as u8,
90    DigMessageType::ValidatorAnnounce as u8,
91    DigMessageType::RequestBlockTransactions as u8,
92    DigMessageType::RespondBlockTransactions as u8,
93    DigMessageType::ReconciliationSketch as u8,
94    DigMessageType::ReconciliationResponse as u8,
95    DigMessageType::StemTransaction as u8,
96    DigMessageType::PlumtreeLazyAnnounce as u8,
97    DigMessageType::PlumtreePrune as u8,
98    DigMessageType::PlumtreeGraft as u8,
99    DigMessageType::PlumtreeRequestByHash as u8,
100    DigMessageType::RegisterPeer as u8,
101    DigMessageType::RegisterAck as u8,
102    DIG_MESSAGE,
103    STORE_MELTED,
104    HOLDINGS_ANNOUNCE,
105    PROFILE_ROOT_ANNOUNCE,
106    PROFILE_BODY_REQUEST,
107    PROFILE_BODY,
108];
109
110/// Whether `opcode` belongs to the DIG band rather than Chia's namespace.
111///
112/// This is a *band* test, not an *assigned* test: an unassigned value such as `250` is still
113/// DIG's to allocate, and a link must route it to DIG handling (where it is rejected as unknown)
114/// rather than to a Chia decoder that would reject the whole connection.
115#[must_use]
116pub const fn is_dig_opcode(opcode: u8) -> bool {
117    opcode >= DIG_BAND_START
118}
119
120#[cfg(test)]
121mod tests {
122    use super::{
123        is_dig_opcode, ALL_DIG_OPCODES, DIG_BAND_START, DIG_MESSAGE, FREE_BAND_START,
124        HOLDINGS_ANNOUNCE, PROFILE_BODY, PROFILE_BODY_REQUEST, PROFILE_ROOT_ANNOUNCE, STORE_MELTED,
125    };
126    use chia_protocol::ProtocolMessageTypes;
127    use chia_traits::Streamable;
128
129    /// No DIG opcode may ever collide with one Chia accepts — probed against the real decoder
130    /// over the whole `u8` space rather than against a transcribed copy of Chia's enum, so an
131    /// upstream addition that reached into the band would fail this test instead of silently
132    /// producing two meanings for one byte.
133    #[test]
134    fn dig_opcodes_are_disjoint_from_the_chia_namespace() {
135        for opcode in ALL_DIG_OPCODES {
136            assert!(
137                ProtocolMessageTypes::from_bytes(&[opcode]).is_err(),
138                "opcode {opcode} is claimed by both DIG and Chia"
139            );
140        }
141    }
142
143    /// The band is contiguous from 200 with no gaps and no duplicates: a gap would mean an
144    /// opcode was silently dropped from the list, a duplicate that two protocols share a byte.
145    #[test]
146    fn the_assigned_band_is_contiguous_from_200() {
147        let expected: Vec<u8> = (DIG_BAND_START..=PROFILE_BODY).collect();
148        assert_eq!(ALL_DIG_OPCODES.to_vec(), expected);
149    }
150
151    /// The free band starts exactly where the consensus band ends, and every assigned value is
152    /// pinned — these are cross-repo canonical constants that must not drift.
153    #[test]
154    fn free_band_constants_are_pinned() {
155        assert_eq!(FREE_BAND_START, 220);
156        assert_eq!(DIG_MESSAGE, 220);
157        assert_eq!(STORE_MELTED, 221);
158        assert_eq!(HOLDINGS_ANNOUNCE, 222);
159        assert_eq!(PROFILE_ROOT_ANNOUNCE, 223);
160        assert_eq!(PROFILE_BODY_REQUEST, 224);
161        assert_eq!(PROFILE_BODY, 225);
162    }
163
164    /// The three profile-SMT opcodes are a single indivisible allocation: each one must be
165    /// present in the dispatch list, in order, or the sync protocol is only half-routable. A
166    /// list missing just the middle value still passes a naive "highest value is 225" check,
167    /// so this asserts the exact contiguous triple as a slice.
168    #[test]
169    fn the_profile_sync_triple_is_assigned_together() {
170        let tail = &ALL_DIG_OPCODES[ALL_DIG_OPCODES.len() - 3..];
171        assert_eq!(
172            tail,
173            [PROFILE_ROOT_ANNOUNCE, PROFILE_BODY_REQUEST, PROFILE_BODY]
174        );
175    }
176
177    /// The band predicate is pinned from BOTH sides: 199 is Chia's, 200 is DIG's.
178    #[test]
179    fn band_predicate_is_pinned_from_both_sides() {
180        assert!(!is_dig_opcode(DIG_BAND_START - 1));
181        assert!(is_dig_opcode(DIG_BAND_START));
182        assert!(is_dig_opcode(u8::MAX));
183    }
184}