dig_constants/lib.rs
1//! DIG Network Constants
2//!
3//! Defines network parameters for the DIG L2 blockchain. This crate exists
4//! separately so that any DIG crate can import network constants without
5//! pulling in the full CLVM engine or other heavy dependencies.
6//!
7//! The core type is [`NetworkConstants`], which wraps `chia-consensus`'s
8//! `ConsensusConstants` with DIG-specific values (genesis challenge,
9//! AGG_SIG additional data, cost limits, etc.).
10//!
11//! # Usage
12//!
13//! ```rust,ignore
14//! use dig_constants::DIG_MAINNET;
15//!
16//! let genesis = DIG_MAINNET.genesis_challenge();
17//! let consensus = DIG_MAINNET.consensus();
18//! ```
19
20use chia_consensus::consensus_constants::ConsensusConstants;
21use chia_protocol::Bytes32;
22use hex_literal::hex;
23
24/// DIG network constants.
25///
26/// Wraps `chia-consensus::ConsensusConstants` with accessors for the fields
27/// that DIG validators and wallet code commonly need. The underlying
28/// `ConsensusConstants` is available via [`consensus()`](Self::consensus)
29/// for direct use with `chia-consensus` functions like `run_spendbundle()`.
30#[derive(Debug, Clone)]
31pub struct NetworkConstants {
32 inner: ConsensusConstants,
33}
34
35impl NetworkConstants {
36 /// The underlying `chia-consensus` constants, for passing directly to
37 /// `run_spendbundle()`, `validate_clvm_and_signature()`, etc.
38 pub fn consensus(&self) -> &ConsensusConstants {
39 &self.inner
40 }
41
42 /// DIG genesis challenge.
43 pub fn genesis_challenge(&self) -> Bytes32 {
44 self.inner.genesis_challenge
45 }
46
47 /// AGG_SIG_ME additional data (== genesis_challenge on Chia L1).
48 pub fn agg_sig_me_additional_data(&self) -> Bytes32 {
49 self.inner.agg_sig_me_additional_data
50 }
51
52 /// Maximum CLVM cost per block.
53 pub fn max_block_cost_clvm(&self) -> u64 {
54 self.inner.max_block_cost_clvm
55 }
56
57 /// Cost per byte of generator program.
58 pub fn cost_per_byte(&self) -> u64 {
59 self.inner.cost_per_byte
60 }
61
62 /// Maximum coin amount (u64::MAX).
63 pub fn max_coin_amount(&self) -> u64 {
64 self.inner.max_coin_amount
65 }
66}
67
68// =============================================================================
69// AGG_SIG additional data derivation
70//
71// On Chia L1, each AGG_SIG_* variant's additional_data is:
72// sha256(genesis_challenge || opcode_byte)
73// except AGG_SIG_ME which uses genesis_challenge directly.
74//
75// See: condition_tools.py:58-71
76// https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/condition_tools.py#L58
77// =============================================================================
78
79// ---------------------------------------------------------------------------
80// DIG Mainnet
81//
82// TODO: Replace placeholder genesis challenge with the real DIG mainnet value
83// once the network is launched. All agg_sig_*_additional_data values must be
84// recomputed as sha256(genesis_challenge || opcode_byte) at that time.
85// ---------------------------------------------------------------------------
86
87/// Placeholder DIG mainnet genesis challenge.
88///
89/// This MUST be replaced with the real value before mainnet launch.
90/// All `agg_sig_*_additional_data` fields are derived from this.
91const DIG_MAINNET_GENESIS_CHALLENGE: [u8; 32] =
92 hex!("0000000000000000000000000000000000000000000000000000000000000000");
93
94/// DIG mainnet constants.
95///
96/// Uses DIG's own genesis challenge and AGG_SIG domain separation.
97/// Proof-of-space and VDF fields are set to neutral values since DIG L2
98/// does not use Chia's proof-of-space consensus.
99pub const DIG_MAINNET: NetworkConstants = NetworkConstants {
100 inner: ConsensusConstants {
101 // -- DIG-specific values --
102 genesis_challenge: Bytes32::new(DIG_MAINNET_GENESIS_CHALLENGE),
103
104 // AGG_SIG additional data: derived from genesis_challenge.
105 // AGG_SIG_ME = genesis_challenge directly.
106 // Others = sha256(genesis_challenge || opcode_byte).
107 // Derivation: condition_tools.py:58-71
108 // https://github.com/Chia-Network/chia-blockchain/blob/main/chia/consensus/condition_tools.py#L58
109 // Opcode bytes: AGG_SIG_PARENT=43, PUZZLE=44, AMOUNT=45,
110 // PUZZLE_AMOUNT=46, PARENT_AMOUNT=47, PARENT_PUZZLE=48
111 // NOTE: Recompute ALL values when genesis_challenge is finalized.
112 agg_sig_me_additional_data: Bytes32::new(DIG_MAINNET_GENESIS_CHALLENGE),
113 agg_sig_parent_additional_data: Bytes32::new(hex!(
114 "978722459e638504a3c4ed25b0eae952f1cba668de5a44ccbb3b311eb6901218"
115 )),
116 agg_sig_puzzle_additional_data: Bytes32::new(hex!(
117 "b5b75cf3f16babd124b3c36ac239db038cf9384b6f4343ab65121e7994fa87e4"
118 )),
119 agg_sig_amount_additional_data: Bytes32::new(hex!(
120 "568b7e86b93e78c4a70a90902134266d5f666400d449c827c32422c14a8df42a"
121 )),
122 agg_sig_puzzle_amount_additional_data: Bytes32::new(hex!(
123 "73f82ca7a07025c76c91a3faf2e574ffa13759597fc5d9a0573b4df70245de2e"
124 )),
125 agg_sig_parent_amount_additional_data: Bytes32::new(hex!(
126 "3a2914bb834c69f745c1932450bad277f975b3e1b246d003e3a53e550cf74936"
127 )),
128 agg_sig_parent_puzzle_additional_data: Bytes32::new(hex!(
129 "4f59298d607f3143532ed694fb2f10454a684c1a29ef83e250bf5e234c6720b7"
130 )),
131
132 // DIG L2 cost limits
133 max_block_cost_clvm: 11_000_000_000, // per-spend limit, same as Chia L1
134 cost_per_byte: 12_000,
135 max_coin_amount: u64::MAX,
136
137 // Block generator limits
138 max_generator_size: 1_000_000,
139 max_generator_ref_list_size: 512,
140
141 // Hard fork heights — set to 0 to always use latest consensus rules.
142 // DIG L2 starts with all features enabled from block 0.
143 hard_fork_height: 0,
144 hard_fork2_height: 0,
145
146 // Pre-farm puzzle hashes — not used by DIG L2, set to zero.
147 genesis_pre_farm_pool_puzzle_hash: Bytes32::new([0u8; 32]),
148 genesis_pre_farm_farmer_puzzle_hash: Bytes32::new([0u8; 32]),
149
150 // -- Proof-of-space / VDF fields (not used by DIG L2) --
151 // These must be valid values since ConsensusConstants is passed to
152 // chia-consensus functions, but DIG does not use PoS consensus.
153 slot_blocks_target: 32,
154 min_blocks_per_challenge_block: 16,
155 max_sub_slot_blocks: 128,
156 num_sps_sub_slot: 64,
157 sub_slot_iters_starting: 1 << 27,
158 difficulty_constant_factor: 1 << 67,
159 difficulty_starting: 7,
160 difficulty_change_max_factor: 3,
161 sub_epoch_blocks: 384,
162 epoch_blocks: 4608,
163 significant_bits: 8,
164 discriminant_size_bits: 1024,
165 number_zero_bits_plot_filter_v1: 9,
166 number_zero_bits_plot_filter_v2: 9,
167 min_plot_size_v1: 32,
168 max_plot_size_v1: 50,
169 min_plot_size_v2: 28,
170 max_plot_size_v2: 32,
171 sub_slot_time_target: 600,
172 num_sp_intervals_extra: 3,
173 max_future_time2: 120,
174 number_of_timestamps: 11,
175 max_vdf_witness_size: 64,
176 mempool_block_buffer: 10,
177 weight_proof_threshold: 2,
178 blocks_cache_size: 4608 + (128 * 4),
179 weight_proof_recent_blocks: 1000,
180 max_block_count_per_requests: 32,
181 pool_sub_slot_iters: 37_600_000_000,
182 plot_filter_128_height: 0xffff_ffff,
183 plot_filter_64_height: 0xffff_ffff,
184 plot_filter_32_height: 0xffff_ffff,
185 plot_difficulty_initial: 2,
186 plot_difficulty_4_height: 0xffff_ffff,
187 plot_difficulty_5_height: 0xffff_ffff,
188 plot_difficulty_6_height: 0xffff_ffff,
189 plot_difficulty_7_height: 0xffff_ffff,
190 plot_difficulty_8_height: 0xffff_ffff,
191 },
192};
193
194// =============================================================================
195// NAT-traversal relay endpoint
196//
197// A DIG Node behind NAT cannot accept inbound dials, so it holds a constant
198// reservation with a publicly-reachable relay to stay discoverable. The
199// canonical public relay is `relay.dig.net`, serving the `RelayMessage`
200// WebSocket wire (RLY-001..RLY-007) on port 9450.
201//
202// This constant is the single source of truth for that endpoint so consumers
203// (`dig-node`, `dig-gossip`) don't each hardcode it. It MUST stay byte-identical
204// to `dig-node`'s `relay::DEFAULT_RELAY_URL` (the string a node actually dials
205// when `DIG_RELAY_URL` is unset) and to the `dig-relay` server's documented
206// client endpoint.
207// =============================================================================
208
209/// Canonical DIG NAT-traversal relay endpoint.
210///
211/// This is the WebSocket URL a DIG Node dials by default to obtain a relay
212/// reservation (so NAT'd peers stay reachable). It is the value used unless an
213/// operator overrides it via the `DIG_RELAY_URL` environment variable (or
214/// disables the reservation with `DIG_RELAY_URL=off`).
215///
216/// Format: `wss://<host>:<port>` — the relay protocol (`RelayMessage`,
217/// RLY-001..RLY-007) is JSON over a secure WebSocket. Mainnet uses the canonical
218/// public deployment `relay.dig.net` on port 9450.
219///
220/// Kept byte-identical to `dig-node`'s `relay::DEFAULT_RELAY_URL` and the
221/// `dig-relay` server's documented client endpoint.
222pub const DIG_RELAY_URL: &str = "wss://relay.dig.net:9450";
223
224// ---------------------------------------------------------------------------
225// DIG Testnet
226// ---------------------------------------------------------------------------
227
228/// Placeholder DIG testnet genesis challenge.
229const DIG_TESTNET_GENESIS_CHALLENGE: [u8; 32] =
230 hex!("0000000000000000000000000000000000000000000000000000000000000001");
231
232/// DIG testnet constants.
233///
234/// Same structure as mainnet but with a different genesis challenge.
235/// Useful for testing without risking mainnet state.
236pub const DIG_TESTNET: NetworkConstants = NetworkConstants {
237 inner: ConsensusConstants {
238 genesis_challenge: Bytes32::new(DIG_TESTNET_GENESIS_CHALLENGE),
239 // AGG_SIG_ME = genesis_challenge. Others = sha256(genesis || opcode_byte).
240 agg_sig_me_additional_data: Bytes32::new(DIG_TESTNET_GENESIS_CHALLENGE),
241 agg_sig_parent_additional_data: Bytes32::new(hex!(
242 "6ae3f62deccdc8d56baf955e45dad1d40332a7b8e4afbb38f07719a863658054"
243 )),
244 agg_sig_puzzle_additional_data: Bytes32::new(hex!(
245 "5d962189ce65d3b3799f032add1ab29ef94ebc0e349fe1db231752304cdd6904"
246 )),
247 agg_sig_amount_additional_data: Bytes32::new(hex!(
248 "3724d66f2da5614aa650517a2feb7d681807d5107441c9c72579e9a751b82d67"
249 )),
250 agg_sig_puzzle_amount_additional_data: Bytes32::new(hex!(
251 "fb6a54a5b51e9734a6ff72fe4105cd5db891d4dbaef9361586091f0d4486581b"
252 )),
253 agg_sig_parent_amount_additional_data: Bytes32::new(hex!(
254 "a5556086f1b58dfd5966bf1aac5257c7f60774ffe5a9e219919c56640993f68b"
255 )),
256 agg_sig_parent_puzzle_additional_data: Bytes32::new(hex!(
257 "3fcc94e67cf3975473b065f0b21a4e92dd3df498d8b4464d7da9582669ac4e48"
258 )),
259 // All other fields same as mainnet
260 max_block_cost_clvm: 11_000_000_000,
261 cost_per_byte: 12_000,
262 max_coin_amount: u64::MAX,
263 max_generator_size: 1_000_000,
264 max_generator_ref_list_size: 512,
265 hard_fork_height: 0,
266 hard_fork2_height: 0,
267 genesis_pre_farm_pool_puzzle_hash: Bytes32::new([0u8; 32]),
268 genesis_pre_farm_farmer_puzzle_hash: Bytes32::new([0u8; 32]),
269 slot_blocks_target: 32,
270 min_blocks_per_challenge_block: 16,
271 max_sub_slot_blocks: 128,
272 num_sps_sub_slot: 64,
273 sub_slot_iters_starting: 1 << 27,
274 difficulty_constant_factor: 1 << 67,
275 difficulty_starting: 7,
276 difficulty_change_max_factor: 3,
277 sub_epoch_blocks: 384,
278 epoch_blocks: 4608,
279 significant_bits: 8,
280 discriminant_size_bits: 1024,
281 number_zero_bits_plot_filter_v1: 9,
282 number_zero_bits_plot_filter_v2: 9,
283 min_plot_size_v1: 32,
284 max_plot_size_v1: 50,
285 min_plot_size_v2: 28,
286 max_plot_size_v2: 32,
287 sub_slot_time_target: 600,
288 num_sp_intervals_extra: 3,
289 max_future_time2: 120,
290 number_of_timestamps: 11,
291 max_vdf_witness_size: 64,
292 mempool_block_buffer: 10,
293 weight_proof_threshold: 2,
294 blocks_cache_size: 4608 + (128 * 4),
295 weight_proof_recent_blocks: 1000,
296 max_block_count_per_requests: 32,
297 pool_sub_slot_iters: 37_600_000_000,
298 plot_filter_128_height: 0xffff_ffff,
299 plot_filter_64_height: 0xffff_ffff,
300 plot_filter_32_height: 0xffff_ffff,
301 plot_difficulty_initial: 2,
302 plot_difficulty_4_height: 0xffff_ffff,
303 plot_difficulty_5_height: 0xffff_ffff,
304 plot_difficulty_6_height: 0xffff_ffff,
305 plot_difficulty_7_height: 0xffff_ffff,
306 plot_difficulty_8_height: 0xffff_ffff,
307 },
308};
309
310#[cfg(test)]
311mod tests {
312 use super::*;
313
314 /// The canonical relay endpoint must equal exactly what a DIG Node dials by
315 /// default. This pins the value byte-for-byte against `dig-node`'s
316 /// `relay::DEFAULT_RELAY_URL` (`wss://relay.dig.net:9450`) and the
317 /// `dig-relay` server's documented client endpoint. If either side ever
318 /// changes the scheme, host, or port, this guard fails so the shared
319 /// contract can't silently drift.
320 #[test]
321 fn dig_relay_url_is_canonical_endpoint() {
322 assert_eq!(DIG_RELAY_URL, "wss://relay.dig.net:9450");
323 }
324
325 /// The relay endpoint is a secure-WebSocket URL pointing at the canonical
326 /// public host on the relay protocol port.
327 #[test]
328 fn dig_relay_url_is_well_formed() {
329 assert!(
330 DIG_RELAY_URL.starts_with("wss://"),
331 "relay must use secure WebSocket"
332 );
333 assert!(
334 DIG_RELAY_URL.contains("relay.dig.net"),
335 "relay must point at the canonical host"
336 );
337 assert!(
338 DIG_RELAY_URL.ends_with(":9450"),
339 "relay must use the RelayMessage wire port 9450"
340 );
341 }
342}