1use bitcoin_hashes::hex::ToHex;
2use serde_json::{
3 Value,
4 Value::{Array as JArrary, Null, Number as JNumber, String as JString},
5};
6use std::convert::{TryFrom, TryInto};
7
8use crate::{
9 error::Error,
10 json_rpc::{Message, Response, StandardRequest},
11 methods::ParsingMethodError,
12 utils::{Extranonce, HexU32Be},
13};
14
15#[cfg(test)]
16use quickcheck::{Arbitrary, Gen};
17
18#[cfg(test)]
19use quickcheck_macros;
20
21#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct Authorize {
27 pub id: u64,
28 pub name: String,
29 pub password: String,
30}
31
32impl Authorize {
33 pub fn respond(self, is_ok: bool) -> Response {
34 let result = serde_json::to_value(is_ok).unwrap();
36 Response {
37 id: self.id,
38 result,
39 error: None,
40 }
41 }
42}
43
44impl From<Authorize> for Message {
45 fn from(auth: Authorize) -> Self {
46 Message::StandardRequest(StandardRequest {
47 id: auth.id,
48 method: "mining.authorize".into(),
49 params: (&[auth.name, auth.password][..]).into(),
50 })
51 }
52}
53
54impl TryFrom<StandardRequest> for Authorize {
55 type Error = ParsingMethodError;
56
57 fn try_from(msg: StandardRequest) -> Result<Self, Self::Error> {
58 match msg.params.as_array() {
59 Some(params) => {
60 let (name, password) = match ¶ms[..] {
61 [JString(a), JString(b)] => (a.into(), b.into()),
62 _ => return Err(ParsingMethodError::wrong_args_from_value(msg.params)),
63 };
64 let id = msg.id;
65 Ok(Self { id, name, password })
66 }
67 None => Err(ParsingMethodError::not_array_from_value(msg.params)),
68 }
69 }
70}
71
72#[cfg(test)]
73impl Arbitrary for Authorize {
74 fn arbitrary(g: &mut Gen) -> Self {
75 Authorize {
76 name: String::arbitrary(g),
77 password: String::arbitrary(g),
78 id: u64::arbitrary(g),
79 }
80 }
81}
82
83#[cfg(test)]
84#[quickcheck_macros::quickcheck]
85fn from_to_json_rpc(auth: Authorize) -> bool {
86 let message = Into::<Message>::into(auth.clone());
87 let request = match message {
88 Message::StandardRequest(s) => s,
89 _ => panic!(),
90 };
91 auth == TryInto::<Authorize>::try_into(request).unwrap()
92}
93
94#[derive(Debug, Clone, Copy)]
100pub struct ExtranonceSubscribe();
101
102#[derive(Debug, Clone, PartialEq, Eq)]
118pub struct Submit<'a> {
119 pub user_name: String, pub job_id: String, pub extra_nonce2: Extranonce<'a>, pub time: HexU32Be, pub nonce: HexU32Be,
124 pub version_bits: Option<HexU32Be>,
125 pub id: u64,
126}
127impl Submit<'_> {
131 pub fn respond(self, is_ok: bool) -> Response {
132 let result = serde_json::to_value(is_ok).unwrap();
134 Response {
135 id: self.id,
136 result,
137 error: None,
138 }
139 }
140}
141
142impl From<Submit<'_>> for Message {
143 fn from(submit: Submit) -> Self {
144 let ex: String = submit.extra_nonce2.0.inner_as_ref().to_hex();
145 let mut params: Vec<Value> = vec![
146 submit.user_name.into(),
147 submit.job_id.into(),
148 ex.into(),
149 submit.time.into(),
150 submit.nonce.into(),
151 ];
152 if let Some(a) = submit.version_bits {
153 let a: String = a.into();
154 params.push(a.into());
155 };
156 Message::StandardRequest(StandardRequest {
157 id: submit.id,
158 method: "mining.submit".into(),
159 params: params.into(),
160 })
161 }
162}
163
164impl TryFrom<StandardRequest> for Submit<'_> {
165 type Error = ParsingMethodError;
166
167 #[allow(clippy::many_single_char_names)]
168 fn try_from(msg: StandardRequest) -> Result<Self, Self::Error> {
169 match msg.params.as_array() {
170 Some(params) => {
171 let (user_name, job_id, extra_nonce2, time, nonce, version_bits) = match ¶ms[..]
172 {
173 [JString(a), JString(b), JString(c), JNumber(d), JNumber(e), JString(f)] => (
174 a.into(),
175 b.into(),
176 Extranonce::try_from(hex::decode(c)?)?,
177 HexU32Be(d.as_u64().unwrap() as u32),
178 HexU32Be(e.as_u64().unwrap() as u32),
179 Some((f.as_str()).try_into()?),
180 ),
181 [JString(a), JString(b), JString(c), JString(d), JString(e), JString(f)] => (
182 a.into(),
183 b.into(),
184 Extranonce::try_from(hex::decode(c)?)?,
185 (d.as_str()).try_into()?,
186 (e.as_str()).try_into()?,
187 Some((f.as_str()).try_into()?),
188 ),
189 [JString(a), JString(b), JString(c), JNumber(d), JNumber(e)] => (
190 a.into(),
191 b.into(),
192 Extranonce::try_from(hex::decode(c)?)?,
193 HexU32Be(d.as_u64().unwrap() as u32),
194 HexU32Be(e.as_u64().unwrap() as u32),
195 None,
196 ),
197 [JString(a), JString(b), JString(c), JString(d), JString(e)] => (
198 a.into(),
199 b.into(),
200 Extranonce::try_from(hex::decode(c)?)?,
201 (d.as_str()).try_into()?,
202 (e.as_str()).try_into()?,
203 None,
204 ),
205 _ => return Err(ParsingMethodError::wrong_args_from_value(msg.params)),
206 };
207 let id = msg.id;
208 let res = crate::client_to_server::Submit {
209 user_name,
210 job_id,
211 extra_nonce2,
212 time,
213 nonce,
214 version_bits,
215 id,
216 };
217 Ok(res)
218 }
219 None => Err(ParsingMethodError::not_array_from_value(msg.params)),
220 }
221 }
222}
223
224#[cfg(test)]
225impl Arbitrary for Submit<'static> {
226 fn arbitrary(g: &mut Gen) -> Self {
227 let mut extra = Vec::<u8>::arbitrary(g);
228 extra.resize(32, 0);
229 println!("\nEXTRA: {extra:?}\n");
230 let bits = Option::<u32>::arbitrary(g);
231 println!("\nBITS: {bits:?}\n");
232 let extra: Extranonce = extra.try_into().unwrap();
233 let bits = bits.map(HexU32Be);
234 println!("\nBITS: {bits:?}\n");
235 Submit {
236 user_name: String::arbitrary(g),
237 job_id: String::arbitrary(g),
238 extra_nonce2: extra,
239 time: HexU32Be(u32::arbitrary(g)),
240 nonce: HexU32Be(u32::arbitrary(g)),
241 version_bits: bits,
242 id: u64::arbitrary(g),
243 }
244 }
245}
246
247#[cfg(test)]
248#[quickcheck_macros::quickcheck]
249fn submit_from_to_json_rpc(submit: Submit<'static>) -> bool {
250 let message = Into::<Message>::into(submit.clone());
251 println!("\nMESSAGE: {message:?}\n");
252 let request = match message {
253 Message::StandardRequest(s) => s,
254 _ => panic!(),
255 };
256 println!("\nREQUEST: {request:?}\n");
257 submit == TryInto::<Submit>::try_into(request).unwrap()
258}
259
260#[derive(Debug, Clone)]
269pub struct Subscribe<'a> {
270 pub id: u64,
271 pub agent_signature: String,
272 pub extranonce1: Option<Extranonce<'a>>,
273}
274
275impl<'a> Subscribe<'a> {
276 pub fn respond(
277 self,
278 subscriptions: Vec<(String, String)>,
279 extra_nonce1: Extranonce<'a>,
280 extra_nonce2_size: usize,
281 ) -> Response {
282 let response = crate::server_to_client::Subscribe {
283 subscriptions,
284 extra_nonce1,
285 extra_nonce2_size,
286 id: self.id,
287 };
288 match Message::from(response) {
289 Message::OkResponse(r) => r,
290 _ => unreachable!(),
291 }
292 }
293}
294
295impl<'a> TryFrom<Subscribe<'a>> for Message {
296 type Error = Error<'a>;
297
298 fn try_from(subscribe: Subscribe) -> Result<Self, Error> {
299 let params = match (subscribe.agent_signature, subscribe.extranonce1) {
300 (a, Some(b)) => vec![a, b.0.inner_as_ref().to_hex()],
301 (a, None) => vec![a],
302 };
303 Ok(Message::StandardRequest(StandardRequest {
304 id: subscribe.id,
305 method: "mining.subscribe".into(),
306 params: (¶ms[..]).into(),
307 }))
308 }
309}
310
311impl TryFrom<StandardRequest> for Subscribe<'_> {
312 type Error = ParsingMethodError;
313
314 fn try_from(msg: StandardRequest) -> Result<Self, Self::Error> {
315 match msg.params.as_array() {
316 Some(params) => {
317 let (agent_signature, extranonce1) = match ¶ms[..] {
318 [JString(a), Null, JString(_), Null] => (a.into(), None),
320 [JString(a), Null] => (a.into(), None),
322 [JString(a), JString(b)] => (a.into(), Some(Extranonce::try_from(b.as_str())?)),
323 [JString(a)] => (a.into(), None),
324 [] => ("".to_string(), None),
325 _ => return Err(ParsingMethodError::wrong_args_from_value(msg.params)),
326 };
327 let id = msg.id;
328 let res = Subscribe {
329 id,
330 agent_signature,
331 extranonce1,
332 };
333 Ok(res)
334 }
335 None => Err(ParsingMethodError::not_array_from_value(msg.params)),
336 }
337 }
338}
339
340#[derive(Debug, Clone)]
341pub struct Configure {
342 extensions: Vec<ConfigureExtension>,
343 id: u64,
344}
345
346impl Configure {
347 pub fn new(id: u64, mask: Option<HexU32Be>, min_bit_count: Option<HexU32Be>) -> Self {
348 let extension = ConfigureExtension::VersionRolling(VersionRollingParams {
349 mask,
350 min_bit_count,
351 });
352 Configure {
353 extensions: vec![extension],
354 id,
355 }
356 }
357
358 pub fn void(id: u64) -> Self {
359 Configure {
360 extensions: vec![],
361 id,
362 }
363 }
364
365 pub fn respond(
366 self,
367 version_rolling: Option<crate::server_to_client::VersionRollingParams>,
368 minimum_difficulty: Option<bool>,
369 ) -> Response {
370 let response = crate::server_to_client::Configure {
371 id: self.id,
372 version_rolling,
373 minimum_difficulty,
374 };
375 match Message::from(response) {
376 Message::OkResponse(r) => r,
377 _ => unreachable!(),
378 }
379 }
380
381 pub fn version_rolling_mask(&self) -> Option<HexU32Be> {
382 let mut res = None;
383 for ext in &self.extensions {
384 if let ConfigureExtension::VersionRolling(p) = ext {
385 res = Some(p.mask.clone().unwrap_or(HexU32Be(0x1FFFE000)));
386 };
387 }
388 res
389 }
390
391 pub fn version_rolling_min_bit_count(&self) -> Option<HexU32Be> {
392 let mut res = None;
393 for ext in &self.extensions {
394 if let ConfigureExtension::VersionRolling(p) = ext {
395 res = Some(p.min_bit_count.clone().unwrap_or(HexU32Be(0)));
397 };
398 }
399 res
400 }
401}
402
403impl From<Configure> for Message {
404 fn from(conf: Configure) -> Self {
405 let mut params = serde_json::Map::new();
406 let extension_names: Vec<Value> = conf
407 .extensions
408 .iter()
409 .map(|x| x.get_extension_name())
410 .collect();
411 for parameter in conf.extensions {
412 let mut parameter: serde_json::Map<String, Value> = parameter.into();
413 params.append(&mut parameter);
414 }
415 Message::StandardRequest(StandardRequest {
416 id: conf.id,
417 method: "mining.configure".into(),
418 params: vec![JArrary(extension_names), params.into()].into(),
419 })
420 }
421}
422
423impl TryFrom<StandardRequest> for Configure {
424 type Error = ParsingMethodError;
425
426 fn try_from(msg: StandardRequest) -> Result<Self, Self::Error> {
427 let extensions = ConfigureExtension::from_value(&msg.params)?;
428 let id = msg.id;
429 Ok(Self { extensions, id })
430 }
431}
432
433#[derive(Debug, Clone)]
434pub enum ConfigureExtension {
435 VersionRolling(VersionRollingParams),
436 MinimumDifficulty(u64),
437 SubcribeExtraNonce,
438 Info(InfoParams),
439}
440
441#[allow(clippy::unnecessary_unwrap)]
442impl ConfigureExtension {
443 pub fn from_value(val: &Value) -> Result<Vec<ConfigureExtension>, ParsingMethodError> {
444 let mut res = vec![];
445 let root = val
446 .as_array()
447 .ok_or_else(|| ParsingMethodError::not_array_from_value(val.clone()))?;
448 if root.is_empty() {
449 return Err(ParsingMethodError::Todo);
450 };
451
452 let version_rolling_mask = val.pointer("/1/version-rolling.mask");
453 let version_rolling_min_bit = val.pointer("/1/version-rolling.min-bit-count");
454 let info_connection_url = val.pointer("/1/info.connection-url");
455 let info_hw_version = val.pointer("/1/info.hw-version");
456 let info_sw_version = val.pointer("/1/info.sw-version");
457 let info_hw_id = val.pointer("/1/info.hw-id");
458 let minimum_difficulty_value = val.pointer("/1/minimum-difficulty.value");
459
460 if root[0]
461 .as_array()
462 .ok_or_else(|| ParsingMethodError::not_array_from_value(root[0].clone()))?
463 .contains(&JString("subscribe-extranonce".to_string()))
464 {
465 res.push(ConfigureExtension::SubcribeExtraNonce)
466 }
467 let (mask, min_bit_count) = match (version_rolling_mask, version_rolling_min_bit) {
468 (None, None) => (None, None),
469 (Some(JString(mask)), None) => {
471 let mask: HexU32Be = mask.as_str().try_into()?;
472 (Some(mask), None)
473 }
474 (Some(JString(mask)), Some(JString(min_bit))) => {
476 let mask: HexU32Be = mask.as_str().try_into()?;
477 let min_bit: HexU32Be = min_bit.as_str().try_into()?;
478 (Some(mask), Some(min_bit))
479 }
480 (Some(JString(mask)), Some(JNumber(min_bit))) => {
482 let mask: HexU32Be = mask.as_str().try_into()?;
483 let min_bit: HexU32Be = HexU32Be(min_bit.as_u64().unwrap() as u32);
485 (Some(mask), Some(min_bit))
486 }
487 (None, Some(_)) => return Err(ParsingMethodError::Todo),
489 (Some(_), None) => return Err(ParsingMethodError::Todo),
491 (Some(_), Some(_)) => return Err(ParsingMethodError::Todo),
493 };
494 if mask.is_some() || min_bit_count.is_some() {
495 let params = VersionRollingParams {
496 mask,
497 min_bit_count,
498 };
499 res.push(ConfigureExtension::VersionRolling(params));
500 }
501
502 if let Some(minimum_difficulty_value) = minimum_difficulty_value {
503 let min_diff = match minimum_difficulty_value {
504 JNumber(a) => a
505 .as_u64()
506 .ok_or_else(|| ParsingMethodError::not_unsigned_from_value(a.clone()))?,
507 _ => {
508 return Err(ParsingMethodError::unexpected_value_from_value(
509 minimum_difficulty_value.clone(),
510 ))
511 }
512 };
513
514 res.push(ConfigureExtension::MinimumDifficulty(min_diff));
515 };
516
517 if info_connection_url.is_some()
518 || info_hw_id.is_some()
519 || info_hw_version.is_some()
520 || info_sw_version.is_some()
521 {
522 let connection_url = if info_connection_url.is_some()
523 && info_connection_url.unwrap().as_str().is_some()
525 {
526 Some(info_connection_url.unwrap().as_str().unwrap().to_string())
528 } else if info_connection_url.is_some() {
529 return Err(ParsingMethodError::Todo);
530 } else {
531 None
532 };
533 let hw_id = if info_hw_id.is_some() && info_hw_id.unwrap().as_str().is_some() {
535 Some(info_hw_id.unwrap().as_str().unwrap().to_string())
537 } else if info_hw_id.is_some() {
538 return Err(ParsingMethodError::Todo);
539 } else {
540 None
541 };
542 let hw_version =
544 if info_hw_version.is_some() && info_hw_version.unwrap().as_str().is_some() {
545 Some(info_hw_version.unwrap().as_str().unwrap().to_string())
547 } else if info_hw_version.is_some() {
548 return Err(ParsingMethodError::Todo);
549 } else {
550 None
551 };
552 let sw_version =
553 if info_sw_version.is_some() && info_sw_version.unwrap().as_str().is_some() {
555 Some(info_sw_version.unwrap().as_str().unwrap().to_string())
557 } else if info_sw_version.is_some() {
558 return Err(ParsingMethodError::Todo);
559 } else {
560 None
561 };
562 let params = InfoParams {
563 connection_url,
564 hw_id,
565 hw_version,
566 sw_version,
567 };
568 res.push(ConfigureExtension::Info(params));
569 };
570 Ok(res)
571 }
572}
573
574impl ConfigureExtension {
575 pub fn get_extension_name(&self) -> Value {
576 match self {
577 ConfigureExtension::VersionRolling(_) => "version-rolling".into(),
578 ConfigureExtension::MinimumDifficulty(_) => "minimum-difficulty".into(),
579 ConfigureExtension::SubcribeExtraNonce => "subscribe-extranonce".into(),
580 ConfigureExtension::Info(_) => "info".into(),
581 }
582 }
583}
584
585impl From<ConfigureExtension> for serde_json::Map<String, Value> {
586 fn from(conf: ConfigureExtension) -> Self {
587 match conf {
588 ConfigureExtension::VersionRolling(a) => a.into(),
589 ConfigureExtension::SubcribeExtraNonce => serde_json::Map::new(),
590 ConfigureExtension::Info(a) => a.into(),
591 ConfigureExtension::MinimumDifficulty(a) => {
592 let mut map = serde_json::Map::new();
593 map.insert("minimum-difficulty".to_string(), a.into());
594 map
595 }
596 }
597 }
598}
599
600#[derive(Debug, Clone)]
601pub struct VersionRollingParams {
602 mask: Option<HexU32Be>,
603 min_bit_count: Option<HexU32Be>,
604}
605
606impl From<VersionRollingParams> for serde_json::Map<String, Value> {
607 fn from(conf: VersionRollingParams) -> Self {
608 let mut params = serde_json::Map::new();
609 match (conf.mask, conf.min_bit_count) {
610 (Some(mask), Some(min)) => {
611 let mask: String = mask.into();
612 let min: String = min.into();
613 params.insert("version-rolling.mask".to_string(), mask.into());
614 params.insert("version-rolling.min-bit-count".to_string(), min.into());
615 }
616 (Some(mask), None) => {
617 let mask: String = mask.into();
618 params.insert("version-rolling.mask".to_string(), mask.into());
619 }
620 (None, Some(min)) => {
621 let min: String = min.into();
622 params.insert("version-rolling.min-bit-count".to_string(), min.into());
623 }
624 (None, None) => (),
625 };
626 params
627 }
628}
629
630#[derive(Debug, Clone)]
631pub struct InfoParams {
632 connection_url: Option<String>,
633 #[allow(dead_code)]
634 hw_id: Option<String>,
635 #[allow(dead_code)]
636 hw_version: Option<String>,
637 #[allow(dead_code)]
638 sw_version: Option<String>,
639}
640
641impl From<InfoParams> for serde_json::Map<String, Value> {
642 fn from(info: InfoParams) -> Self {
643 let mut params = serde_json::Map::new();
644 if info.connection_url.is_some() {
645 params.insert(
646 "info.connection-url".to_string(),
647 info.connection_url.unwrap().into(),
649 );
650 }
651 params
652 }
653}
654
655#[test]
661fn test_version_extension_with_broken_bit_count() {
662 let client_message = r#"{"id":0,
663 "method": "mining.configure",
664 "params":[
665 ["version-rolling"],
666 {"version-rolling.mask":"1fffe000",
667 "version-rolling.min-bit-count":"16"}
668 ]
669 }"#;
670 let client_message: StandardRequest = serde_json::from_str(client_message).unwrap();
671 let server_configure = Configure::try_from(client_message).unwrap();
672 match &server_configure.extensions[0] {
673 ConfigureExtension::VersionRolling(params) => {
674 assert!(params.min_bit_count.as_ref().unwrap().0 == 0x16)
675 }
676 _ => panic!(),
677 };
678}
679#[test]
680fn test_version_extension_with_non_string_bit_count() {
681 let client_message = r#"{"id":0,
682 "method": "mining.configure",
683 "params":[
684 ["version-rolling"],
685 {"version-rolling.mask":"1fffe000",
686 "version-rolling.min-bit-count":16}
687 ]
688 }"#;
689 let client_message: StandardRequest = serde_json::from_str(client_message).unwrap();
690 let server_configure = Configure::try_from(client_message).unwrap();
691 match &server_configure.extensions[0] {
692 ConfigureExtension::VersionRolling(params) => {
693 assert!(params.min_bit_count.as_ref().unwrap().0 == 16)
694 }
695 _ => panic!(),
696 };
697}
698
699#[test]
700fn test_version_extension_with_no_bit_count() {
701 let client_message = r#"{"id":0,
702 "method": "mining.configure",
703 "params":[
704 ["version-rolling"],
705 {"version-rolling.mask":"ffffffff"}
706 ]
707 }"#;
708 let client_message: StandardRequest = serde_json::from_str(client_message).unwrap();
709 let server_configure = Configure::try_from(client_message).unwrap();
710 match &server_configure.extensions[0] {
711 ConfigureExtension::VersionRolling(params) => {
712 assert!(params.min_bit_count.as_ref().is_none());
713 }
714 _ => panic!(),
715 };
716}
717
718#[test]
719fn test_subscribe_with_odd_length_extranonce() {
720 let client_message = r#"{"id":1,
722 "method": "mining.subscribe",
723 "params":["test-agent", "abc"]
724 }"#;
725 let client_message: StandardRequest = serde_json::from_str(client_message).unwrap();
726 let subscribe = Subscribe::try_from(client_message).unwrap();
727
728 assert_eq!(subscribe.agent_signature, "test-agent");
730 assert!(subscribe.extranonce1.is_some());
731 let extranonce = subscribe.extranonce1.unwrap();
732 assert_eq!(extranonce.0.inner_as_ref(), &[0x0a, 0xbc]); }
734
735#[test]
736fn test_subscribe_with_even_length_extranonce() {
737 let client_message = r#"{"id":1,
739 "method": "mining.subscribe",
740 "params":["test-agent", "abcd"]
741 }"#;
742 let client_message: StandardRequest = serde_json::from_str(client_message).unwrap();
743 let subscribe = Subscribe::try_from(client_message).unwrap();
744
745 assert_eq!(subscribe.agent_signature, "test-agent");
746 assert!(subscribe.extranonce1.is_some());
747 let extranonce = subscribe.extranonce1.unwrap();
748 assert_eq!(extranonce.0.inner_as_ref(), &[0xab, 0xcd]); }