cn_stratum/
message.rs

1// copyright 2017 Kaz Wesley
2
3//! Serialization for the JSON-RPC-based `CryptoNote` pool protocol
4
5use crate::hexbytes;
6
7use arrayvec::ArrayString;
8use serde::Deserializer;
9use serde_derive::{Deserialize, Serialize};
10
11use std::error::Error;
12use std::fmt::{self, Display, Formatter};
13
14////////// COMMON
15
16/// `WorkerId` can be any JSON string of up to 64 bytes. It is opaque to
17/// the worker.
18#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
19pub struct WorkerId(ArrayString<[u8; 64]>);
20
21/// Server-defined Job identifier
22#[derive(Debug, Serialize, Deserialize, Clone, Copy, PartialEq)]
23pub struct JobId(ArrayString<[u8; 64]>);
24
25////////// server -> worker
26
27// Input is either 32-bit or 64-bit little-endian hex string, not necessarily padded.
28// Inputs of 8 hex chars or less are in a compact format.
29pub fn deserialize_target<'de, D>(deserializer: D) -> Result<u64, D::Error>
30where
31    D: Deserializer<'de>,
32{
33    let (mut val, hexlen) = hexbytes::hex64le_to_int(deserializer)?;
34    // unpack compact format
35    // XXX: this is what other miners do. It doesn't seem right...
36    if hexlen <= 8 {
37        val |= val << 0x20;
38    }
39    Ok(val)
40}
41
42/// Description of what hash to try to find.
43#[derive(Debug, Deserialize, Clone)]
44pub struct Job {
45    #[serde(deserialize_with = "hexbytes::hex_to_varbyte")]
46    blob: Vec<u8>,
47    job_id: JobId,
48    #[serde(deserialize_with = "deserialize_target")]
49    target: u64,
50    #[serde(default)]
51    algo: Option<String>,
52    #[serde(default)]
53    variant: u32, // xmrig proxy sends this for compat with obsolete xmrig
54}
55
56impl Job {
57    /// Borrow the payload
58    pub fn blob(&self) -> &[u8] {
59        &self.blob
60    }
61
62    pub(crate) fn id(&self) -> JobId {
63        self.job_id
64    }
65
66    /// The goal hash
67    pub fn target(&self) -> u64 {
68        self.target
69    }
70
71    /// Algo-switching extension for some upstreams
72    pub fn algo(&self) -> Option<&str> {
73        self.algo.as_ref().map(|x| x.as_ref())
74    }
75}
76
77impl PartialEq<Job> for Job {
78    fn eq(&self, other: &Job) -> bool {
79        self.job_id == other.job_id
80    }
81}
82
83#[derive(Debug, Deserialize)]
84#[serde(tag = "method", content = "params", rename_all = "lowercase")]
85pub enum ClientCommand {
86    Job(Job),
87}
88
89#[derive(Debug, Deserialize)]
90pub struct ErrorReply {
91    code: i64,
92    message: String,
93}
94
95impl Display for ErrorReply {
96    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
97        write!(f, "{:?}", &self)
98    }
99}
100
101impl Error for ErrorReply {
102    fn description(&self) -> &str {
103        &self.message
104    }
105}
106
107#[derive(Debug, Deserialize)]
108pub struct JsonMessage<T> {
109    #[serde(default)]
110    pub jsonrpc: Option<String>,
111    #[serde(default)]
112    pub status: Option<String>,
113    #[serde(flatten)]
114    pub body: T,
115}
116
117/// Initial job assignment and reply to subsequent job requests
118#[derive(Debug, Deserialize)]
119pub struct JobAssignment {
120    #[serde(rename = "id")]
121    worker_id: WorkerId,
122    job: Job,
123    #[serde(default)]
124    status: Option<String>,
125    #[serde(default)]
126    extensions: Vec<String>,
127}
128
129impl JobAssignment {
130    /// Server-defined token identifying our connection
131    pub fn worker_id(&self) -> WorkerId {
132        self.worker_id
133    }
134    /// Return the new Job itself.
135    pub fn into_job(self) -> Job {
136        self.job
137    }
138    /// Optional messague, usually something friendly like "OK"
139    pub fn status(&self) -> Option<&str> {
140        self.status.as_ref().map(|x| x.as_ref())
141    }
142    /// Protocol extensions supported by the server
143    pub fn extensions(&self) -> impl Iterator<Item = &str> {
144        self.extensions.iter().map(|x| x.as_ref())
145    }
146}
147
148#[derive(Debug, Deserialize)]
149#[serde(untagged)]
150pub enum PoolReply {
151    /// reply to getjob (not implemented) and login
152    Job(Box<JobAssignment>),
153    /// reply to submit
154    Status { status: String },
155}
156
157/// Message received from pool (reply or job notification).
158#[derive(Debug, Deserialize)]
159#[serde(untagged)]
160pub enum PoolEvent<ReqId> {
161    ClientCommand(ClientCommand),
162    PoolReply {
163        id: ReqId,
164        error: Option<ErrorReply>,
165        result: Option<PoolReply>,
166    },
167}
168
169////////// worker -> server
170
171#[derive(Debug, Serialize)]
172pub struct Share {
173    #[serde(rename = "id")]
174    pub worker_id: WorkerId,
175    pub job_id: JobId,
176    #[serde(serialize_with = "hexbytes::u32_to_hex_padded")]
177    pub nonce: u32,
178    #[serde(serialize_with = "hexbytes::byte32_to_hex")]
179    pub result: [u8; 32],
180    pub algo: String,
181}
182
183#[derive(Debug, Serialize)]
184pub struct Credentials {
185    pub login: String,
186    pub pass: String,
187    pub agent: String,
188    pub algo: Vec<String>,
189}
190
191#[derive(Debug, Serialize)]
192#[serde(tag = "method", content = "params", rename_all = "lowercase")]
193pub enum PoolCommand {
194    Submit(Share),
195    Login(Credentials),
196    KeepAlived { id: WorkerId },
197}
198
199/// Message sent from client to pool.
200///
201/// `ReqId` can be any JSON value. If you are sending the requests, you
202/// can serialize with a specific type like u32, and should be able to
203/// expect the same type to come back in replies. If you are receiving
204/// the requests, you should use a generic type like
205/// `serde_json::Value`.
206#[derive(Debug, Serialize)]
207pub struct PoolRequest<ReqId> {
208    pub id: ReqId,
209    #[serde(flatten)]
210    pub command: PoolCommand,
211}