1use super::noise::{CipherState, Session, MAC_LEN, MAX_CIPHERTEXT_CHUNK, MAX_PLAINTEXT_CHUNK};
7use anyhow::{bail, ensure, Context, Result};
8use sha2::{Digest, Sha256};
9use std::time::Duration;
10use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt};
11
12pub const HEADER_LEN: usize = 6;
14pub const ENCRYPTED_HEADER_LEN: usize = HEADER_LEN + MAC_LEN;
16pub const CHANNEL_MSG_BIT: u16 = 0x8000;
18pub const IO_TIMEOUT: Duration = Duration::from_secs(30);
19pub const PROTOCOL_VERSION: u16 = 2;
21pub const MINING_PROTOCOL: u8 = 0;
23
24pub const REQUIRES_STANDARD_JOBS: u32 = 1 << 0;
26pub const REQUIRES_WORK_SELECTION: u32 = 1 << 1;
27pub const REQUIRES_VERSION_ROLLING: u32 = 1 << 2;
28pub const VERSION_ROLLING_MASK: u32 = 0x1fff_e000;
30pub const MAX_FUTURE_NTIME: u32 = 7200;
32pub const EXTRANONCE_PREFIX_LEN: usize = 4;
34pub const MAX_EXTRANONCE: usize = 32;
36pub const MAX_COINBASE_MESSAGE: usize = 32;
38pub const DIFF1: f64 = 26959535291011309493156476344723991336010898738574164086137773096960.0;
40
41pub mod msg {
42 pub const SETUP_CONNECTION: u8 = 0x00;
43 pub const SETUP_CONNECTION_SUCCESS: u8 = 0x01;
44 pub const SETUP_CONNECTION_ERROR: u8 = 0x02;
45 pub const CHANNEL_ENDPOINT_CHANGED: u8 = 0x03;
46 pub const RECONNECT: u8 = 0x04;
47 pub const OPEN_STANDARD_MINING_CHANNEL: u8 = 0x10;
48 pub const OPEN_STANDARD_MINING_CHANNEL_SUCCESS: u8 = 0x11;
49 pub const OPEN_MINING_CHANNEL_ERROR: u8 = 0x12;
50 pub const OPEN_EXTENDED_MINING_CHANNEL: u8 = 0x13;
51 pub const OPEN_EXTENDED_MINING_CHANNEL_SUCCESS: u8 = 0x14;
52 pub const NEW_MINING_JOB: u8 = 0x15;
53 pub const UPDATE_CHANNEL: u8 = 0x16;
54 pub const UPDATE_CHANNEL_ERROR: u8 = 0x17;
55 pub const CLOSE_CHANNEL: u8 = 0x18;
56 pub const SET_EXTRANONCE_PREFIX: u8 = 0x19;
57 pub const SUBMIT_SHARES_STANDARD: u8 = 0x1a;
58 pub const SUBMIT_SHARES_EXTENDED: u8 = 0x1b;
59 pub const SUBMIT_SHARES_SUCCESS: u8 = 0x1c;
60 pub const SUBMIT_SHARES_ERROR: u8 = 0x1d;
61 pub const NEW_EXTENDED_MINING_JOB: u8 = 0x1f;
62 pub const SET_NEW_PREV_HASH: u8 = 0x20;
63 pub const SET_TARGET: u8 = 0x21;
64 pub const SET_CUSTOM_MINING_JOB: u8 = 0x22;
65 pub const SET_GROUP_CHANNEL: u8 = 0x25;
66}
67
68pub fn channel_bit(msg_type: u8) -> bool {
70 matches!(msg_type, 0x03 | 0x15..=0x24)
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct Frame {
76 pub extension_type: u16,
77 pub msg_type: u8,
78 pub payload: Vec<u8>,
79}
80
81impl Frame {
82 pub fn extension(&self) -> u16 {
84 self.extension_type & !CHANNEL_MSG_BIT
85 }
86
87 pub fn header(&self) -> [u8; HEADER_LEN] {
88 let mut h = [0u8; HEADER_LEN];
89 h[0..2].copy_from_slice(&self.extension_type.to_le_bytes());
90 h[2] = self.msg_type;
91 h[3..6].copy_from_slice(&(self.payload.len() as u32).to_le_bytes()[..3]);
92 h
93 }
94}
95
96pub fn ciphertext_len(plaintext: usize) -> usize {
98 let rest = plaintext % MAX_PLAINTEXT_CHUNK;
99 plaintext / MAX_PLAINTEXT_CHUNK * MAX_CIPHERTEXT_CHUNK
100 + if rest > 0 { rest + MAC_LEN } else { 0 }
101}
102
103pub fn seal(cipher: &mut CipherState, frame: &Frame) -> Result<Vec<u8>> {
105 ensure!(
106 frame.payload.len() < 1 << 24,
107 "a payload is at most 2^24-1 bytes"
108 );
109 let mut out = cipher.encrypt(&[], &frame.header())?;
110 for chunk in frame.payload.chunks(MAX_PLAINTEXT_CHUNK) {
111 out.extend_from_slice(&cipher.encrypt(&[], chunk)?);
112 }
113 Ok(out)
114}
115
116pub struct FrameReader<R> {
118 reader: R,
119 cipher: CipherState,
120 max_payload: usize,
121}
122
123impl<R: AsyncRead + Unpin> FrameReader<R> {
124 pub fn new(reader: R, cipher: CipherState, max_payload: usize) -> Self {
125 Self {
126 reader,
127 cipher,
128 max_payload,
129 }
130 }
131
132 pub async fn next(&mut self, idle: Duration) -> Result<Option<Frame>> {
136 let mut header_ct = [0u8; ENCRYPTED_HEADER_LEN];
137 let first = tokio::time::timeout(idle, self.reader.read(&mut header_ct[..1]))
138 .await
139 .context("Stratum V2 peer idle")??;
140 if first == 0 {
141 return Ok(None);
142 }
143 tokio::time::timeout(IO_TIMEOUT, self.reader.read_exact(&mut header_ct[1..]))
144 .await
145 .context("Stratum V2 frame header deadline")??;
146 let header = self
147 .cipher
148 .decrypt(&[], &header_ct)
149 .context("frame header did not authenticate")?;
150 let extension_type = u16::from_le_bytes([header[0], header[1]]);
151 let msg_type = header[2];
152 let length = u32::from_le_bytes([header[3], header[4], header[5], 0]) as usize;
153 ensure!(
154 length <= self.max_payload,
155 "frame declares a {length}-byte payload, more than the {} this side accepts",
156 self.max_payload
157 );
158 let mut ct = vec![0u8; ciphertext_len(length)];
159 tokio::time::timeout(IO_TIMEOUT, self.reader.read_exact(&mut ct))
160 .await
161 .context("Stratum V2 frame payload deadline")??;
162 let mut payload = Vec::with_capacity(length);
163 for chunk in ct.chunks(MAX_CIPHERTEXT_CHUNK) {
164 payload.extend_from_slice(
165 &self
166 .cipher
167 .decrypt(&[], chunk)
168 .context("frame payload did not authenticate")?,
169 );
170 }
171 ensure!(payload.len() == length, "payload length mismatch");
172 Ok(Some(Frame {
173 extension_type,
174 msg_type,
175 payload,
176 }))
177 }
178}
179
180pub struct FrameWriter<W> {
182 writer: W,
183 cipher: CipherState,
184}
185
186impl<W: AsyncWrite + Unpin> FrameWriter<W> {
187 pub fn new(writer: W, cipher: CipherState) -> Self {
188 Self { writer, cipher }
189 }
190
191 pub async fn send(&mut self, message: &Message) -> Result<usize> {
192 let bytes = seal(&mut self.cipher, &message.encode())?;
193 tokio::time::timeout(IO_TIMEOUT, self.writer.write_all(&bytes))
194 .await
195 .context("Stratum V2 write deadline")??;
196 Ok(bytes.len())
197 }
198
199 pub async fn shutdown(&mut self) {
200 let _ = self.writer.shutdown().await;
201 }
202}
203
204pub fn transport<R: AsyncRead + Unpin, W: AsyncWrite + Unpin>(
206 reader: R,
207 writer: W,
208 session: Session,
209 max_payload: usize,
210) -> (FrameReader<R>, FrameWriter<W>) {
211 (
212 FrameReader::new(reader, session.recv, max_payload),
213 FrameWriter::new(writer, session.send),
214 )
215}
216
217pub struct Reader<'a> {
219 buf: &'a [u8],
220 pos: usize,
221}
222
223impl<'a> Reader<'a> {
224 pub fn new(buf: &'a [u8]) -> Self {
225 Self { buf, pos: 0 }
226 }
227
228 fn take(&mut self, n: usize) -> Result<&'a [u8]> {
229 ensure!(
230 self.buf.len() - self.pos >= n,
231 "message ends {} bytes into a {n}-byte field",
232 self.buf.len() - self.pos
233 );
234 let s = &self.buf[self.pos..self.pos + n];
235 self.pos += n;
236 Ok(s)
237 }
238
239 pub fn u8(&mut self) -> Result<u8> {
240 Ok(self.take(1)?[0])
241 }
242 pub fn u16(&mut self) -> Result<u16> {
243 Ok(u16::from_le_bytes(self.take(2)?.try_into()?))
244 }
245 pub fn u24(&mut self) -> Result<u32> {
246 let b = self.take(3)?;
247 Ok(u32::from_le_bytes([b[0], b[1], b[2], 0]))
248 }
249 pub fn u32(&mut self) -> Result<u32> {
250 Ok(u32::from_le_bytes(self.take(4)?.try_into()?))
251 }
252 pub fn u64(&mut self) -> Result<u64> {
253 Ok(u64::from_le_bytes(self.take(8)?.try_into()?))
254 }
255 pub fn f32(&mut self) -> Result<f32> {
257 let v = f32::from_le_bytes(self.take(4)?.try_into()?);
258 ensure!(
259 v.is_finite() && v.is_sign_positive(),
260 "F32 field is {v}, which the protocol forbids"
261 );
262 Ok(v)
263 }
264 pub fn bool(&mut self) -> Result<bool> {
265 Ok(self.u8()? & 1 == 1)
266 }
267 pub fn u256(&mut self) -> Result<[u8; 32]> {
268 Ok(self.take(32)?.try_into()?)
269 }
270 fn bytes_u8_prefix(&mut self, max: usize, what: &str) -> Result<Vec<u8>> {
271 let n = self.u8()? as usize;
272 ensure!(n <= max, "{what} declares {n} bytes, at most {max} allowed");
273 Ok(self.take(n)?.to_vec())
274 }
275 pub fn str0_255(&mut self) -> Result<Vec<u8>> {
276 self.bytes_u8_prefix(255, "STR0_255")
277 }
278 pub fn b0_32(&mut self) -> Result<Vec<u8>> {
279 self.bytes_u8_prefix(32, "B0_32")
280 }
281 pub fn b0_255(&mut self) -> Result<Vec<u8>> {
282 self.bytes_u8_prefix(255, "B0_255")
283 }
284 pub fn b0_64k(&mut self) -> Result<Vec<u8>> {
285 let n = self.u16()? as usize;
286 Ok(self.take(n)?.to_vec())
287 }
288 pub fn option_u32(&mut self) -> Result<Option<u32>> {
289 match self.u8()? {
290 0 => Ok(None),
291 1 => Ok(Some(self.u32()?)),
292 n => bail!("OPTION declares {n} elements, at most 1 allowed"),
293 }
294 }
295 pub fn seq0_255_u256(&mut self) -> Result<Vec<[u8; 32]>> {
296 let n = self.u8()? as usize;
297 (0..n).map(|_| self.u256()).collect()
298 }
299 pub fn seq0_64k_u32(&mut self) -> Result<Vec<u32>> {
300 let n = self.u16()? as usize;
301 (0..n).map(|_| self.u32()).collect()
302 }
303 pub fn finish(&self) -> Result<()> {
305 ensure!(
306 self.pos == self.buf.len(),
307 "{} unexpected bytes after the message",
308 self.buf.len() - self.pos
309 );
310 Ok(())
311 }
312}
313
314#[derive(Default)]
315pub struct Writer(pub Vec<u8>);
316
317impl Writer {
318 pub fn u8(&mut self, v: u8) -> &mut Self {
319 self.0.push(v);
320 self
321 }
322 pub fn u16(&mut self, v: u16) -> &mut Self {
323 self.0.extend_from_slice(&v.to_le_bytes());
324 self
325 }
326 pub fn u32(&mut self, v: u32) -> &mut Self {
327 self.0.extend_from_slice(&v.to_le_bytes());
328 self
329 }
330 pub fn u64(&mut self, v: u64) -> &mut Self {
331 self.0.extend_from_slice(&v.to_le_bytes());
332 self
333 }
334 pub fn f32(&mut self, v: f32) -> &mut Self {
335 self.0.extend_from_slice(&v.to_le_bytes());
336 self
337 }
338 pub fn bool(&mut self, v: bool) -> &mut Self {
339 self.u8(u8::from(v))
340 }
341 pub fn u256(&mut self, v: &[u8; 32]) -> &mut Self {
342 self.0.extend_from_slice(v);
343 self
344 }
345 pub fn short_bytes(&mut self, v: &[u8]) -> &mut Self {
347 let v = &v[..v.len().min(255)];
348 self.u8(v.len() as u8);
349 self.0.extend_from_slice(v);
350 self
351 }
352 pub fn b0_64k(&mut self, v: &[u8]) -> &mut Self {
353 let v = &v[..v.len().min(65535)];
354 self.u16(v.len() as u16);
355 self.0.extend_from_slice(v);
356 self
357 }
358 pub fn option_u32(&mut self, v: Option<u32>) -> &mut Self {
359 match v {
360 Some(v) => self.u8(1).u32(v),
361 None => self.u8(0),
362 }
363 }
364 pub fn seq0_255_u256(&mut self, v: &[[u8; 32]]) -> &mut Self {
365 let v = &v[..v.len().min(255)];
366 self.u8(v.len() as u8);
367 for h in v {
368 self.u256(h);
369 }
370 self
371 }
372 pub fn seq0_64k_u32(&mut self, v: &[u32]) -> &mut Self {
373 let v = &v[..v.len().min(65535)];
374 self.u16(v.len() as u16);
375 for n in v {
376 self.u32(*n);
377 }
378 self
379 }
380}
381
382#[derive(Debug, Clone, PartialEq)]
385pub enum Message {
386 SetupConnection {
387 protocol: u8,
388 min_version: u16,
389 max_version: u16,
390 flags: u32,
391 endpoint_host: Vec<u8>,
392 endpoint_port: u16,
393 vendor: Vec<u8>,
394 hardware_version: Vec<u8>,
395 firmware: Vec<u8>,
396 device_id: Vec<u8>,
397 },
398 SetupConnectionSuccess {
399 used_version: u16,
400 flags: u32,
401 },
402 SetupConnectionError {
403 flags: u32,
404 error_code: Vec<u8>,
405 },
406 ChannelEndpointChanged {
407 channel_id: u32,
408 },
409 Reconnect {
410 new_host: Vec<u8>,
411 new_port: u16,
412 },
413 OpenStandardMiningChannel {
414 request_id: u32,
415 user_identity: Vec<u8>,
416 nominal_hash_rate: f32,
417 max_target: [u8; 32],
418 },
419 OpenStandardMiningChannelSuccess {
420 request_id: u32,
421 channel_id: u32,
422 target: [u8; 32],
423 extranonce_prefix: Vec<u8>,
424 group_channel_id: u32,
425 },
426 OpenExtendedMiningChannel {
427 request_id: u32,
428 user_identity: Vec<u8>,
429 nominal_hash_rate: f32,
430 max_target: [u8; 32],
431 min_extranonce_size: u16,
432 },
433 OpenExtendedMiningChannelSuccess {
434 request_id: u32,
435 channel_id: u32,
436 target: [u8; 32],
437 extranonce_size: u16,
438 extranonce_prefix: Vec<u8>,
439 group_channel_id: u32,
440 },
441 OpenMiningChannelError {
442 request_id: u32,
443 error_code: Vec<u8>,
444 },
445 UpdateChannel {
446 channel_id: u32,
447 nominal_hash_rate: f32,
448 max_target: [u8; 32],
449 },
450 UpdateChannelError {
451 channel_id: u32,
452 error_code: Vec<u8>,
453 },
454 CloseChannel {
455 channel_id: u32,
456 reason_code: Vec<u8>,
457 },
458 SetExtranoncePrefix {
459 channel_id: u32,
460 extranonce_prefix: Vec<u8>,
461 },
462 SubmitSharesStandard {
463 channel_id: u32,
464 sequence_number: u32,
465 job_id: u32,
466 nonce: u32,
467 ntime: u32,
468 version: u32,
469 },
470 SubmitSharesExtended {
471 channel_id: u32,
472 sequence_number: u32,
473 job_id: u32,
474 nonce: u32,
475 ntime: u32,
476 version: u32,
477 extranonce: Vec<u8>,
478 },
479 SubmitSharesSuccess {
480 channel_id: u32,
481 last_sequence_number: u32,
482 new_submits_accepted_count: u32,
483 new_shares_sum: u64,
484 },
485 SubmitSharesError {
486 channel_id: u32,
487 sequence_number: u32,
488 error_code: Vec<u8>,
489 },
490 NewMiningJob {
491 channel_id: u32,
492 job_id: u32,
493 min_ntime: Option<u32>,
494 version: u32,
495 merkle_root: [u8; 32],
496 },
497 NewExtendedMiningJob {
498 channel_id: u32,
499 job_id: u32,
500 min_ntime: Option<u32>,
501 version: u32,
502 version_rolling_allowed: bool,
503 merkle_path: Vec<[u8; 32]>,
504 coinbase_tx_prefix: Vec<u8>,
505 coinbase_tx_suffix: Vec<u8>,
506 },
507 SetNewPrevHash {
508 channel_id: u32,
509 job_id: u32,
510 prev_hash: [u8; 32],
511 min_ntime: u32,
512 nbits: u32,
513 },
514 SetTarget {
515 channel_id: u32,
516 maximum_target: [u8; 32],
517 },
518 SetGroupChannel {
519 group_channel_id: u32,
520 channel_ids: Vec<u32>,
521 },
522}
523
524impl Message {
525 pub fn msg_type(&self) -> u8 {
526 use Message::*;
527 match self {
528 SetupConnection { .. } => msg::SETUP_CONNECTION,
529 SetupConnectionSuccess { .. } => msg::SETUP_CONNECTION_SUCCESS,
530 SetupConnectionError { .. } => msg::SETUP_CONNECTION_ERROR,
531 ChannelEndpointChanged { .. } => msg::CHANNEL_ENDPOINT_CHANGED,
532 Reconnect { .. } => msg::RECONNECT,
533 OpenStandardMiningChannel { .. } => msg::OPEN_STANDARD_MINING_CHANNEL,
534 OpenStandardMiningChannelSuccess { .. } => msg::OPEN_STANDARD_MINING_CHANNEL_SUCCESS,
535 OpenExtendedMiningChannel { .. } => msg::OPEN_EXTENDED_MINING_CHANNEL,
536 OpenExtendedMiningChannelSuccess { .. } => msg::OPEN_EXTENDED_MINING_CHANNEL_SUCCESS,
537 OpenMiningChannelError { .. } => msg::OPEN_MINING_CHANNEL_ERROR,
538 UpdateChannel { .. } => msg::UPDATE_CHANNEL,
539 UpdateChannelError { .. } => msg::UPDATE_CHANNEL_ERROR,
540 CloseChannel { .. } => msg::CLOSE_CHANNEL,
541 SetExtranoncePrefix { .. } => msg::SET_EXTRANONCE_PREFIX,
542 SubmitSharesStandard { .. } => msg::SUBMIT_SHARES_STANDARD,
543 SubmitSharesExtended { .. } => msg::SUBMIT_SHARES_EXTENDED,
544 SubmitSharesSuccess { .. } => msg::SUBMIT_SHARES_SUCCESS,
545 SubmitSharesError { .. } => msg::SUBMIT_SHARES_ERROR,
546 NewMiningJob { .. } => msg::NEW_MINING_JOB,
547 NewExtendedMiningJob { .. } => msg::NEW_EXTENDED_MINING_JOB,
548 SetNewPrevHash { .. } => msg::SET_NEW_PREV_HASH,
549 SetTarget { .. } => msg::SET_TARGET,
550 SetGroupChannel { .. } => msg::SET_GROUP_CHANNEL,
551 }
552 }
553
554 pub fn name(&self) -> &'static str {
556 name_of(self.msg_type())
557 }
558
559 pub fn encode(&self) -> Frame {
560 use Message::*;
561 let mut w = Writer::default();
562 match self {
563 SetupConnection {
564 protocol,
565 min_version,
566 max_version,
567 flags,
568 endpoint_host,
569 endpoint_port,
570 vendor,
571 hardware_version,
572 firmware,
573 device_id,
574 } => {
575 w.u8(*protocol)
576 .u16(*min_version)
577 .u16(*max_version)
578 .u32(*flags)
579 .short_bytes(endpoint_host)
580 .u16(*endpoint_port)
581 .short_bytes(vendor)
582 .short_bytes(hardware_version)
583 .short_bytes(firmware)
584 .short_bytes(device_id);
585 }
586 SetupConnectionSuccess {
587 used_version,
588 flags,
589 } => {
590 w.u16(*used_version).u32(*flags);
591 }
592 SetupConnectionError { flags, error_code } => {
593 w.u32(*flags).short_bytes(error_code);
594 }
595 ChannelEndpointChanged { channel_id } => {
596 w.u32(*channel_id);
597 }
598 Reconnect { new_host, new_port } => {
599 w.short_bytes(new_host).u16(*new_port);
600 }
601 OpenStandardMiningChannel {
602 request_id,
603 user_identity,
604 nominal_hash_rate,
605 max_target,
606 } => {
607 w.u32(*request_id)
608 .short_bytes(user_identity)
609 .f32(*nominal_hash_rate)
610 .u256(max_target);
611 }
612 OpenStandardMiningChannelSuccess {
613 request_id,
614 channel_id,
615 target,
616 extranonce_prefix,
617 group_channel_id,
618 } => {
619 w.u32(*request_id)
620 .u32(*channel_id)
621 .u256(target)
622 .short_bytes(extranonce_prefix)
623 .u32(*group_channel_id);
624 }
625 OpenExtendedMiningChannel {
626 request_id,
627 user_identity,
628 nominal_hash_rate,
629 max_target,
630 min_extranonce_size,
631 } => {
632 w.u32(*request_id)
633 .short_bytes(user_identity)
634 .f32(*nominal_hash_rate)
635 .u256(max_target)
636 .u16(*min_extranonce_size);
637 }
638 OpenExtendedMiningChannelSuccess {
639 request_id,
640 channel_id,
641 target,
642 extranonce_size,
643 extranonce_prefix,
644 group_channel_id,
645 } => {
646 w.u32(*request_id)
647 .u32(*channel_id)
648 .u256(target)
649 .u16(*extranonce_size)
650 .short_bytes(extranonce_prefix)
651 .u32(*group_channel_id);
652 }
653 OpenMiningChannelError {
654 request_id,
655 error_code,
656 } => {
657 w.u32(*request_id).short_bytes(error_code);
658 }
659 UpdateChannel {
660 channel_id,
661 nominal_hash_rate,
662 max_target,
663 } => {
664 w.u32(*channel_id).f32(*nominal_hash_rate).u256(max_target);
665 }
666 UpdateChannelError {
667 channel_id,
668 error_code,
669 } => {
670 w.u32(*channel_id).short_bytes(error_code);
671 }
672 CloseChannel {
673 channel_id,
674 reason_code,
675 } => {
676 w.u32(*channel_id).short_bytes(reason_code);
677 }
678 SetExtranoncePrefix {
679 channel_id,
680 extranonce_prefix,
681 } => {
682 w.u32(*channel_id).short_bytes(extranonce_prefix);
683 }
684 SubmitSharesStandard {
685 channel_id,
686 sequence_number,
687 job_id,
688 nonce,
689 ntime,
690 version,
691 } => {
692 w.u32(*channel_id)
693 .u32(*sequence_number)
694 .u32(*job_id)
695 .u32(*nonce)
696 .u32(*ntime)
697 .u32(*version);
698 }
699 SubmitSharesExtended {
700 channel_id,
701 sequence_number,
702 job_id,
703 nonce,
704 ntime,
705 version,
706 extranonce,
707 } => {
708 w.u32(*channel_id)
709 .u32(*sequence_number)
710 .u32(*job_id)
711 .u32(*nonce)
712 .u32(*ntime)
713 .u32(*version)
714 .short_bytes(extranonce);
715 }
716 SubmitSharesSuccess {
717 channel_id,
718 last_sequence_number,
719 new_submits_accepted_count,
720 new_shares_sum,
721 } => {
722 w.u32(*channel_id)
723 .u32(*last_sequence_number)
724 .u32(*new_submits_accepted_count)
725 .u64(*new_shares_sum);
726 }
727 SubmitSharesError {
728 channel_id,
729 sequence_number,
730 error_code,
731 } => {
732 w.u32(*channel_id)
733 .u32(*sequence_number)
734 .short_bytes(error_code);
735 }
736 NewMiningJob {
737 channel_id,
738 job_id,
739 min_ntime,
740 version,
741 merkle_root,
742 } => {
743 w.u32(*channel_id)
744 .u32(*job_id)
745 .option_u32(*min_ntime)
746 .u32(*version)
747 .u256(merkle_root);
748 }
749 NewExtendedMiningJob {
750 channel_id,
751 job_id,
752 min_ntime,
753 version,
754 version_rolling_allowed,
755 merkle_path,
756 coinbase_tx_prefix,
757 coinbase_tx_suffix,
758 } => {
759 w.u32(*channel_id)
760 .u32(*job_id)
761 .option_u32(*min_ntime)
762 .u32(*version)
763 .bool(*version_rolling_allowed)
764 .seq0_255_u256(merkle_path)
765 .b0_64k(coinbase_tx_prefix)
766 .b0_64k(coinbase_tx_suffix);
767 }
768 SetNewPrevHash {
769 channel_id,
770 job_id,
771 prev_hash,
772 min_ntime,
773 nbits,
774 } => {
775 w.u32(*channel_id)
776 .u32(*job_id)
777 .u256(prev_hash)
778 .u32(*min_ntime)
779 .u32(*nbits);
780 }
781 SetTarget {
782 channel_id,
783 maximum_target,
784 } => {
785 w.u32(*channel_id).u256(maximum_target);
786 }
787 SetGroupChannel {
788 group_channel_id,
789 channel_ids,
790 } => {
791 w.u32(*group_channel_id).seq0_64k_u32(channel_ids);
792 }
793 }
794 let msg_type = self.msg_type();
795 Frame {
796 extension_type: if channel_bit(msg_type) {
797 CHANNEL_MSG_BIT
798 } else {
799 0
800 },
801 msg_type,
802 payload: w.0,
803 }
804 }
805
806 pub fn decode(frame: &Frame) -> Result<Message> {
809 use Message::*;
810 ensure!(
811 frame.extension() == 0,
812 "extension 0x{:04x} is not a core protocol message",
813 frame.extension()
814 );
815 let mut r = Reader::new(&frame.payload);
816 let m = match frame.msg_type {
817 msg::SETUP_CONNECTION => SetupConnection {
818 protocol: r.u8()?,
819 min_version: r.u16()?,
820 max_version: r.u16()?,
821 flags: r.u32()?,
822 endpoint_host: r.str0_255()?,
823 endpoint_port: r.u16()?,
824 vendor: r.str0_255()?,
825 hardware_version: r.str0_255()?,
826 firmware: r.str0_255()?,
827 device_id: r.str0_255()?,
828 },
829 msg::SETUP_CONNECTION_SUCCESS => SetupConnectionSuccess {
830 used_version: r.u16()?,
831 flags: r.u32()?,
832 },
833 msg::SETUP_CONNECTION_ERROR => SetupConnectionError {
834 flags: r.u32()?,
835 error_code: r.str0_255()?,
836 },
837 msg::CHANNEL_ENDPOINT_CHANGED => ChannelEndpointChanged {
838 channel_id: r.u32()?,
839 },
840 msg::RECONNECT => Reconnect {
841 new_host: r.str0_255()?,
842 new_port: r.u16()?,
843 },
844 msg::OPEN_STANDARD_MINING_CHANNEL => OpenStandardMiningChannel {
845 request_id: r.u32()?,
846 user_identity: r.str0_255()?,
847 nominal_hash_rate: r.f32()?,
848 max_target: r.u256()?,
849 },
850 msg::OPEN_STANDARD_MINING_CHANNEL_SUCCESS => OpenStandardMiningChannelSuccess {
851 request_id: r.u32()?,
852 channel_id: r.u32()?,
853 target: r.u256()?,
854 extranonce_prefix: r.b0_32()?,
855 group_channel_id: r.u32()?,
856 },
857 msg::OPEN_EXTENDED_MINING_CHANNEL => OpenExtendedMiningChannel {
858 request_id: r.u32()?,
859 user_identity: r.str0_255()?,
860 nominal_hash_rate: r.f32()?,
861 max_target: r.u256()?,
862 min_extranonce_size: r.u16()?,
863 },
864 msg::OPEN_EXTENDED_MINING_CHANNEL_SUCCESS => OpenExtendedMiningChannelSuccess {
865 request_id: r.u32()?,
866 channel_id: r.u32()?,
867 target: r.u256()?,
868 extranonce_size: r.u16()?,
869 extranonce_prefix: r.b0_32()?,
870 group_channel_id: r.u32()?,
871 },
872 msg::OPEN_MINING_CHANNEL_ERROR => OpenMiningChannelError {
873 request_id: r.u32()?,
874 error_code: r.str0_255()?,
875 },
876 msg::UPDATE_CHANNEL => UpdateChannel {
877 channel_id: r.u32()?,
878 nominal_hash_rate: r.f32()?,
879 max_target: r.u256()?,
880 },
881 msg::UPDATE_CHANNEL_ERROR => UpdateChannelError {
882 channel_id: r.u32()?,
883 error_code: r.str0_255()?,
884 },
885 msg::CLOSE_CHANNEL => CloseChannel {
886 channel_id: r.u32()?,
887 reason_code: r.str0_255()?,
888 },
889 msg::SET_EXTRANONCE_PREFIX => SetExtranoncePrefix {
890 channel_id: r.u32()?,
891 extranonce_prefix: r.b0_32()?,
892 },
893 msg::SUBMIT_SHARES_STANDARD => SubmitSharesStandard {
894 channel_id: r.u32()?,
895 sequence_number: r.u32()?,
896 job_id: r.u32()?,
897 nonce: r.u32()?,
898 ntime: r.u32()?,
899 version: r.u32()?,
900 },
901 msg::SUBMIT_SHARES_EXTENDED => SubmitSharesExtended {
902 channel_id: r.u32()?,
903 sequence_number: r.u32()?,
904 job_id: r.u32()?,
905 nonce: r.u32()?,
906 ntime: r.u32()?,
907 version: r.u32()?,
908 extranonce: r.b0_32()?,
909 },
910 msg::SUBMIT_SHARES_SUCCESS => SubmitSharesSuccess {
911 channel_id: r.u32()?,
912 last_sequence_number: r.u32()?,
913 new_submits_accepted_count: r.u32()?,
914 new_shares_sum: r.u64()?,
915 },
916 msg::SUBMIT_SHARES_ERROR => SubmitSharesError {
917 channel_id: r.u32()?,
918 sequence_number: r.u32()?,
919 error_code: r.str0_255()?,
920 },
921 msg::NEW_MINING_JOB => NewMiningJob {
922 channel_id: r.u32()?,
923 job_id: r.u32()?,
924 min_ntime: r.option_u32()?,
925 version: r.u32()?,
926 merkle_root: r.u256()?,
927 },
928 msg::NEW_EXTENDED_MINING_JOB => NewExtendedMiningJob {
929 channel_id: r.u32()?,
930 job_id: r.u32()?,
931 min_ntime: r.option_u32()?,
932 version: r.u32()?,
933 version_rolling_allowed: r.bool()?,
934 merkle_path: r.seq0_255_u256()?,
935 coinbase_tx_prefix: r.b0_64k()?,
936 coinbase_tx_suffix: r.b0_64k()?,
937 },
938 msg::SET_NEW_PREV_HASH => SetNewPrevHash {
939 channel_id: r.u32()?,
940 job_id: r.u32()?,
941 prev_hash: r.u256()?,
942 min_ntime: r.u32()?,
943 nbits: r.u32()?,
944 },
945 msg::SET_TARGET => SetTarget {
946 channel_id: r.u32()?,
947 maximum_target: r.u256()?,
948 },
949 msg::SET_GROUP_CHANNEL => SetGroupChannel {
950 group_channel_id: r.u32()?,
951 channel_ids: r.seq0_64k_u32()?,
952 },
953 other => bail!(
954 "message type 0x{other:02x} ({}) is not handled",
955 name_of(other)
956 ),
957 };
958 r.finish()
959 .with_context(|| format!("{} is malformed", name_of(frame.msg_type)))?;
960 Ok(m)
961 }
962}
963
964pub fn name_of(msg_type: u8) -> &'static str {
965 match msg_type {
966 msg::SETUP_CONNECTION => "SetupConnection",
967 msg::SETUP_CONNECTION_SUCCESS => "SetupConnection.Success",
968 msg::SETUP_CONNECTION_ERROR => "SetupConnection.Error",
969 msg::CHANNEL_ENDPOINT_CHANGED => "ChannelEndpointChanged",
970 msg::RECONNECT => "Reconnect",
971 msg::OPEN_STANDARD_MINING_CHANNEL => "OpenStandardMiningChannel",
972 msg::OPEN_STANDARD_MINING_CHANNEL_SUCCESS => "OpenStandardMiningChannel.Success",
973 msg::OPEN_MINING_CHANNEL_ERROR => "OpenMiningChannel.Error",
974 msg::OPEN_EXTENDED_MINING_CHANNEL => "OpenExtendedMiningChannel",
975 msg::OPEN_EXTENDED_MINING_CHANNEL_SUCCESS => "OpenExtendedMiningChannel.Success",
976 msg::NEW_MINING_JOB => "NewMiningJob",
977 msg::UPDATE_CHANNEL => "UpdateChannel",
978 msg::UPDATE_CHANNEL_ERROR => "UpdateChannel.Error",
979 msg::CLOSE_CHANNEL => "CloseChannel",
980 msg::SET_EXTRANONCE_PREFIX => "SetExtranoncePrefix",
981 msg::SUBMIT_SHARES_STANDARD => "SubmitSharesStandard",
982 msg::SUBMIT_SHARES_EXTENDED => "SubmitSharesExtended",
983 msg::SUBMIT_SHARES_SUCCESS => "SubmitShares.Success",
984 msg::SUBMIT_SHARES_ERROR => "SubmitShares.Error",
985 msg::NEW_EXTENDED_MINING_JOB => "NewExtendedMiningJob",
986 msg::SET_NEW_PREV_HASH => "SetNewPrevHash",
987 msg::SET_TARGET => "SetTarget",
988 msg::SET_CUSTOM_MINING_JOB => "SetCustomMiningJob",
989 0x23 => "SetCustomMiningJob.Success",
990 0x24 => "SetCustomMiningJob.Error",
991 msg::SET_GROUP_CHANNEL => "SetGroupChannel",
992 _ => "unknown",
993 }
994}
995
996pub fn text(bytes: &[u8]) -> String {
998 crate::utils::sanitize::strip_controls(&String::from_utf8_lossy(bytes))
999}
1000
1001pub fn valid_error_code(code: &str) -> bool {
1003 !code.is_empty() && code.len() <= 255 && code.bytes().all(|b| (0x20..0x7f).contains(&b))
1004}
1005
1006pub fn sha256d(data: &[u8]) -> [u8; 32] {
1007 Sha256::digest(Sha256::digest(data)).into()
1008}
1009
1010pub fn header(
1012 version: u32,
1013 prev_hash: &[u8; 32],
1014 merkle_root: &[u8; 32],
1015 ntime: u32,
1016 nbits: u32,
1017 nonce: u32,
1018) -> [u8; 80] {
1019 let mut h = [0u8; 80];
1020 h[0..4].copy_from_slice(&version.to_le_bytes());
1021 h[4..36].copy_from_slice(prev_hash);
1022 h[36..68].copy_from_slice(merkle_root);
1023 h[68..72].copy_from_slice(&ntime.to_le_bytes());
1024 h[72..76].copy_from_slice(&nbits.to_le_bytes());
1025 h[76..80].copy_from_slice(&nonce.to_le_bytes());
1026 h
1027}
1028
1029pub fn meets(hash: &[u8; 32], target: &[u8; 32]) -> bool {
1031 for i in (0..32).rev() {
1032 if hash[i] != target[i] {
1033 return hash[i] < target[i];
1034 }
1035 }
1036 true
1037}
1038
1039pub fn le_value(n: &[u8; 32]) -> f64 {
1041 n.iter()
1042 .rev()
1043 .fold(0f64, |acc, b| acc * 256.0 + f64::from(*b))
1044}
1045
1046pub fn difficulty(hash: &[u8; 32]) -> f64 {
1048 let v = le_value(hash);
1049 if v == 0.0 {
1050 f64::MAX
1051 } else {
1052 DIFF1 / v
1053 }
1054}
1055
1056pub fn target_difficulty(target: &[u8; 32]) -> f64 {
1058 difficulty(target)
1059}
1060
1061pub fn target_for(difficulty: f64) -> [u8; 32] {
1063 if !(difficulty.is_finite() && difficulty > 0.0) {
1064 return [0xff; 32];
1065 }
1066 let t = DIFF1 / difficulty;
1067 if t.is_nan() || t >= 2f64.powi(256) {
1068 return [0xff; 32];
1069 }
1070 if t < 1.0 {
1071 let mut one = [0u8; 32];
1072 one[0] = 1;
1073 return one;
1074 }
1075 let bits = t.to_bits();
1077 let exp = ((bits >> 52) & 0x7ff) as i32 - 1075;
1078 let mantissa = (bits & ((1u64 << 52) - 1)) | (1u64 << 52);
1079 let mut out = [0u8; 32];
1080 if exp < 0 {
1081 let v = mantissa >> (-exp);
1082 out[..8].copy_from_slice(&v.to_le_bytes());
1083 return out;
1084 }
1085 let (byte_shift, bit_shift) = ((exp / 8) as usize, (exp % 8) as u32);
1086 let wide = u128::from(mantissa) << bit_shift;
1087 for (i, b) in wide.to_le_bytes().iter().enumerate() {
1088 if let Some(slot) = out.get_mut(byte_shift + i) {
1089 *slot = *b;
1090 }
1091 }
1092 out
1093}
1094
1095pub fn min_target(a: [u8; 32], b: [u8; 32]) -> [u8; 32] {
1097 if meets(&a, &b) {
1098 a
1099 } else {
1100 b
1101 }
1102}
1103
1104pub fn display_hex(hash: &[u8; 32]) -> String {
1106 let mut h = *hash;
1107 h.reverse();
1108 hex::encode(h)
1109}
1110
1111pub fn parse_display_hash(s: Option<&str>) -> Result<[u8; 32]> {
1113 let Some(s) = s.filter(|s| !s.is_empty()) else {
1114 return Ok([0u8; 32]);
1115 };
1116 let mut b: [u8; 32] = hex::decode(s)
1117 .ok()
1118 .and_then(|v| v.try_into().ok())
1119 .context("prev_hash must be 64 hex digits")?;
1120 b.reverse();
1121 Ok(b)
1122}
1123
1124pub fn merkle_root(coinbase: &[u8], path: &[[u8; 32]]) -> [u8; 32] {
1126 let mut root = sha256d(coinbase);
1127 for leaf in path {
1128 let mut both = [0u8; 64];
1129 both[..32].copy_from_slice(&root);
1130 both[32..].copy_from_slice(leaf);
1131 root = sha256d(&both);
1132 }
1133 root
1134}
1135
1136fn push(script: &mut Vec<u8>, data: &[u8]) {
1138 script.push(data.len() as u8);
1139 script.extend_from_slice(data);
1140}
1141
1142fn height_push(height: u32) -> Vec<u8> {
1144 let mut n = height.to_le_bytes().to_vec();
1145 while n.len() > 1 && n[n.len() - 1] == 0 {
1146 n.pop();
1147 }
1148 if n[n.len() - 1] & 0x80 != 0 {
1149 n.push(0);
1150 }
1151 n
1152}
1153
1154pub fn coinbase_halves(height: u32, message: &[u8], extranonce_len: usize) -> (Vec<u8>, Vec<u8>) {
1159 let message = &message[..message.len().min(MAX_COINBASE_MESSAGE)];
1160 let mut script = Vec::new();
1161 push(&mut script, &height_push(height));
1162 if !message.is_empty() {
1163 push(&mut script, message);
1164 }
1165 script.push(extranonce_len as u8);
1166 let script_len = script.len() + extranonce_len;
1167 let mut prefix = Vec::new();
1168 prefix.extend_from_slice(&2u32.to_le_bytes());
1169 prefix.push(1);
1170 prefix.extend_from_slice(&[0u8; 32]);
1171 prefix.extend_from_slice(&u32::MAX.to_le_bytes());
1172 prefix.push(script_len as u8);
1173 prefix.extend_from_slice(&script);
1174 let mut suffix = Vec::new();
1175 suffix.extend_from_slice(&u32::MAX.to_le_bytes());
1176 suffix.push(1);
1177 suffix.extend_from_slice(&0u64.to_le_bytes());
1178 let out_script = [&[0x6a, 6][..], b"netget"].concat();
1179 suffix.push(out_script.len() as u8);
1180 suffix.extend_from_slice(&out_script);
1181 suffix.extend_from_slice(&0u32.to_le_bytes());
1182 (prefix, suffix)
1183}
1184
1185pub fn version_allowed(job_version: u32, share_version: u32, rolling: bool) -> bool {
1187 if rolling {
1188 (job_version ^ share_version) & !VERSION_ROLLING_MASK == 0
1189 } else {
1190 job_version == share_version
1191 }
1192}
1193
1194pub fn now_secs() -> u32 {
1195 crate::utils::clock::SystemTime::now()
1196 .duration_since(crate::utils::clock::UNIX_EPOCH)
1197 .map(|d| d.as_secs() as u32)
1198 .unwrap_or(0)
1199}
1200
1201pub fn flag_names(flags: u32) -> Vec<&'static str> {
1203 let mut out = Vec::new();
1204 if flags & REQUIRES_STANDARD_JOBS != 0 {
1205 out.push("REQUIRES_STANDARD_JOBS");
1206 }
1207 if flags & REQUIRES_WORK_SELECTION != 0 {
1208 out.push("REQUIRES_WORK_SELECTION");
1209 }
1210 if flags & REQUIRES_VERSION_ROLLING != 0 {
1211 out.push("REQUIRES_VERSION_ROLLING");
1212 }
1213 if flags & !0b111 != 0 {
1214 out.push("UNKNOWN");
1215 }
1216 out
1217}
1218
1219pub async fn accept_handshake<S: AsyncRead + AsyncWrite + Unpin>(
1222 stream: &mut S,
1223 static_key: &super::noise::EllKey,
1224 authority: &super::noise::Authority,
1225 validity_secs: u32,
1226 deadline: Duration,
1227) -> Result<Session> {
1228 let mut first = [0u8; super::noise::INITIATOR_MESSAGE_LEN];
1229 tokio::time::timeout(deadline, stream.read_exact(&mut first))
1230 .await
1231 .context("Stratum V2 handshake deadline")?
1232 .context("connection closed during the Noise handshake")?;
1233 let (reply, session) =
1234 super::noise::respond(&first, static_key, authority, validity_secs, now_secs())?;
1235 tokio::time::timeout(IO_TIMEOUT, stream.write_all(&reply))
1236 .await
1237 .context("Stratum V2 write deadline")??;
1238 Ok(session)
1239}
1240
1241pub async fn connect_handshake<S: AsyncRead + AsyncWrite + Unpin>(
1244 stream: &mut S,
1245 authority: &[u8; 32],
1246 deadline: Duration,
1247) -> Result<(Session, super::noise::Certificate)> {
1248 let (initiator, first) = super::noise::Initiator::start()?;
1249 tokio::time::timeout(IO_TIMEOUT, stream.write_all(&first))
1250 .await
1251 .context("Stratum V2 write deadline")??;
1252 let mut reply = [0u8; super::noise::RESPONDER_MESSAGE_LEN];
1253 tokio::time::timeout(deadline, stream.read_exact(&mut reply))
1254 .await
1255 .context("Stratum V2 handshake deadline")?
1256 .context("the pool closed the connection during the Noise handshake")?;
1257 initiator.finish(&reply, authority, now_secs())
1258}