1#![allow(clippy::result_unit_err)]
2pub mod error;
41pub mod json_rpc;
42pub mod methods;
43pub mod utils;
44
45use bitcoin_hashes::hex::FromHex;
46use std::convert::{TryFrom, TryInto};
47use tracing::debug;
48
49use error::Error;
51pub use json_rpc::Message;
52pub use methods::{client_to_server, server_to_client, Method, MethodError, ParsingMethodError};
53use utils::{Extranonce, HexU32Be};
54
55pub trait IsServer<'a> {
61 fn handle_message(
64 &mut self,
65 client_id: Option<usize>,
66 msg: json_rpc::Message,
67 ) -> Result<Option<json_rpc::Response>, Error<'a>>
68 where
69 Self: std::marker::Sized,
70 {
71 match msg {
72 Message::StandardRequest(_) => {
73 self.handle_request(client_id, msg)
75 }
76 Message::Notification(_) => {
77 self.handle_request(client_id, msg)
79 }
80 _ => {
81 Err(Error::InvalidJsonRpcMessageKind)
83 }
84 }
85 }
86
87 fn handle_request(
89 &mut self,
90 client_id: Option<usize>,
91 msg: json_rpc::Message,
92 ) -> Result<Option<json_rpc::Response>, Error<'a>>
93 where
94 Self: std::marker::Sized,
95 {
96 let request = msg.try_into()?;
97
98 match request {
99 methods::Client2Server::SuggestDifficulty() => Ok(None),
101 methods::Client2Server::Authorize(authorize) => {
102 let authorized = self.handle_authorize(client_id, &authorize);
103 if authorized {
104 self.authorize(client_id, &authorize.name);
105 }
106 Ok(Some(authorize.respond(authorized)))
107 }
108 methods::Client2Server::Configure(configure) => {
109 debug!("{:?}", configure);
110 self.set_version_rolling_mask(client_id, configure.version_rolling_mask());
111 self.set_version_rolling_min_bit(
112 client_id,
113 configure.version_rolling_min_bit_count(),
114 );
115 let (version_rolling, min_diff) = self.handle_configure(client_id, &configure);
116 Ok(Some(configure.respond(version_rolling, min_diff)))
117 }
118 methods::Client2Server::ExtranonceSubscribe(_) => {
119 self.handle_extranonce_subscribe();
120 Ok(None)
121 }
122 methods::Client2Server::Submit(submit) => {
123 let has_valid_version_bits = match &submit.version_bits {
124 Some(a) => {
125 if let Some(version_rolling_mask) = self.version_rolling_mask(client_id) {
126 version_rolling_mask.check_mask(a)
127 } else {
128 false
129 }
130 }
131 None => self.version_rolling_mask(client_id).is_none(),
132 };
133
134 let is_valid_submission = self.is_authorized(client_id, &submit.user_name)
135 && self.extranonce2_size(client_id) == submit.extra_nonce2.len()
136 && has_valid_version_bits;
137
138 if is_valid_submission {
139 let accepted = self.handle_submit(client_id, &submit);
140 Ok(Some(submit.respond(accepted)))
141 } else {
142 Err(Error::InvalidSubmission)
143 }
144 }
145 methods::Client2Server::Subscribe(subscribe) => {
146 let subscriptions = self.handle_subscribe(client_id, &subscribe);
147 let extra_n1 = self.set_extranonce1(client_id, None);
148 let extra_n2_size = self.set_extranonce2_size(client_id, None);
149 Ok(Some(subscribe.respond(
150 subscriptions,
151 extra_n1,
152 extra_n2_size,
153 )))
154 }
155 }
156 }
157
158 fn handle_configure(
161 &mut self,
162 client_id: Option<usize>,
163 request: &client_to_server::Configure,
164 ) -> (Option<server_to_client::VersionRollingParams>, Option<bool>);
165
166 fn handle_subscribe(
185 &self,
186 client_id: Option<usize>,
187 request: &client_to_server::Subscribe,
188 ) -> Vec<(String, String)>;
189
190 fn handle_authorize(
196 &self,
197 client_id: Option<usize>,
198 request: &client_to_server::Authorize,
199 ) -> bool;
200
201 fn handle_submit(
204 &self,
205 client_id: Option<usize>,
206 request: &client_to_server::Submit<'a>,
207 ) -> bool;
208
209 fn handle_extranonce_subscribe(&self);
211
212 fn is_authorized(&self, client_id: Option<usize>, name: &str) -> bool;
213
214 fn authorize(&mut self, client_id: Option<usize>, name: &str);
215
216 fn set_extranonce1(
218 &mut self,
219 client_id: Option<usize>,
220 extranonce1: Option<Extranonce<'a>>,
221 ) -> Extranonce<'a>;
222
223 fn extranonce1(&self, client_id: Option<usize>) -> Extranonce<'a>;
224
225 fn set_extranonce2_size(
227 &mut self,
228 client_id: Option<usize>,
229 extra_nonce2_size: Option<usize>,
230 ) -> usize;
231
232 fn extranonce2_size(&self, client_id: Option<usize>) -> usize;
233
234 fn version_rolling_mask(&self, client_id: Option<usize>) -> Option<HexU32Be>;
235
236 fn set_version_rolling_mask(&mut self, client_id: Option<usize>, mask: Option<HexU32Be>);
237
238 fn set_version_rolling_min_bit(&mut self, client_id: Option<usize>, mask: Option<HexU32Be>);
239
240 fn update_extranonce(
241 &mut self,
242 client_id: Option<usize>,
243 extra_nonce1: Extranonce<'a>,
244 extra_nonce2_size: usize,
245 ) -> Result<json_rpc::Message, Error<'a>> {
246 self.set_extranonce1(client_id, Some(extra_nonce1.clone()));
247 self.set_extranonce2_size(client_id, Some(extra_nonce2_size));
248
249 Ok(server_to_client::SetExtranonce {
250 extra_nonce1,
251 extra_nonce2_size,
252 }
253 .into())
254 }
255 fn notify(&mut self, client_id: Option<usize>) -> Result<json_rpc::Message, Error<'_>>;
259
260 fn handle_set_difficulty(
261 &mut self,
262 _client_id: Option<usize>,
263 value: f64,
264 ) -> Result<json_rpc::Message, Error<'_>> {
265 let set_difficulty = server_to_client::SetDifficulty { value };
266 Ok(set_difficulty.into())
267 }
268}
269
270pub trait IsClient<'a> {
271 fn handle_message(
277 &mut self,
278 server_id: Option<usize>,
279 msg: json_rpc::Message,
280 ) -> Result<Option<json_rpc::Message>, Error<'a>>
281 where
282 Self: std::marker::Sized,
283 {
284 let method: Result<Method<'a>, MethodError<'a>> = msg.try_into();
285
286 match method {
287 Ok(m) => match m {
288 Method::Server2ClientResponse(response) => {
289 let response = self.update_response(server_id, response)?;
290 self.handle_response(server_id, response)
291 }
292 Method::Server2Client(request) => self.handle_request(server_id, request),
293 Method::Client2Server(_) => Err(Error::InvalidReceiver(m.into())),
294 Method::ErrorMessage(msg) => self.handle_error_message(server_id, msg),
295 },
296 Err(e) => Err(e.into()),
297 }
298 }
299
300 fn update_response(
301 &mut self,
302 server_id: Option<usize>,
303 response: methods::Server2ClientResponse<'a>,
304 ) -> Result<methods::Server2ClientResponse<'a>, Error<'a>> {
305 match &response {
306 methods::Server2ClientResponse::GeneralResponse(general) => {
307 let is_authorize = self.id_is_authorize(server_id, &general.id);
308 let is_submit = self.id_is_submit(server_id, &general.id);
309 match (is_authorize, is_submit) {
310 (Some(prev_name), false) => {
311 let authorize = general.clone().into_authorize(prev_name);
312 Ok(methods::Server2ClientResponse::Authorize(authorize))
313 }
314 (None, false) => Ok(methods::Server2ClientResponse::Submit(
315 general.clone().into_submit(),
316 )),
317 _ => Err(Error::UnknownID(general.id)),
318 }
319 }
320 _ => Ok(response),
321 }
322 }
323
324 fn handle_request(
326 &mut self,
327 server_id: Option<usize>,
328 request: methods::Server2Client<'a>,
329 ) -> Result<Option<json_rpc::Message>, Error<'a>>
330 where
331 Self: std::marker::Sized,
332 {
333 match request {
334 methods::Server2Client::Notify(notify) => {
335 self.handle_notify(server_id, notify)?;
336 Ok(None)
337 }
338 methods::Server2Client::SetDifficulty(mut set_diff) => {
339 self.handle_set_difficulty(server_id, &mut set_diff)?;
340 Ok(None)
341 }
342 methods::Server2Client::SetExtranonce(mut set_extra_nonce) => {
343 self.handle_set_extranonce(server_id, &mut set_extra_nonce)?;
344 Ok(None)
345 }
346 methods::Server2Client::SetVersionMask(mut set_version_mask) => {
347 self.handle_set_version_mask(server_id, &mut set_version_mask)?;
348 Ok(None)
349 }
350 }
351 }
352
353 fn handle_response(
354 &mut self,
355 server_id: Option<usize>,
356 response: methods::Server2ClientResponse<'a>,
357 ) -> Result<Option<json_rpc::Message>, Error<'a>>
358 where
359 Self: std::marker::Sized,
360 {
361 match response {
362 methods::Server2ClientResponse::Configure(mut configure) => {
363 self.handle_configure(server_id, &mut configure)?;
364 self.set_version_rolling_mask(server_id, configure.version_rolling_mask());
365 self.set_version_rolling_min_bit(server_id, configure.version_rolling_min_bit());
366 self.set_status(server_id, ClientStatus::Configured);
367
368 debug!("NOTICE: Subscribe extranonce is hardcoded by server");
373 let subscribe = self
374 .subscribe(
375 server_id,
376 configure.id,
377 Some(Extranonce::try_from(
378 Vec::<u8>::from_hex("08000002").map_err(Error::HexError)?,
379 )?),
380 )
381 .ok();
382 Ok(subscribe)
383 }
384 methods::Server2ClientResponse::Subscribe(subscribe) => {
385 self.handle_subscribe(server_id, &subscribe)?;
386 self.set_extranonce1(server_id, subscribe.extra_nonce1);
387 self.set_extranonce2_size(server_id, subscribe.extra_nonce2_size);
388 self.set_status(server_id, ClientStatus::Subscribed);
389 Ok(None)
390 }
391 methods::Server2ClientResponse::Authorize(authorize) => {
392 if authorize.is_ok() {
393 self.authorize_user_name(server_id, authorize.user_name());
394 };
395 Ok(None)
396 }
397 methods::Server2ClientResponse::Submit(_) => Ok(None),
398 methods::Server2ClientResponse::GeneralResponse(_) => panic!(),
400 methods::Server2ClientResponse::SetDifficulty(_) => Ok(None),
401 }
402 }
403
404 fn handle_error_message(
405 &mut self,
406 server_id: Option<usize>,
407 message: Message,
408 ) -> Result<Option<json_rpc::Message>, Error<'a>>;
409
410 fn id_is_authorize(&mut self, server_id: Option<usize>, id: &u64) -> Option<String>;
413
414 fn id_is_submit(&mut self, server_id: Option<usize>, id: &u64) -> bool;
416
417 fn handle_notify(
418 &mut self,
419 server_id: Option<usize>,
420 notify: server_to_client::Notify<'a>,
421 ) -> Result<(), Error<'a>>;
422
423 fn handle_configure(
424 &mut self,
425 server_id: Option<usize>,
426 conf: &mut server_to_client::Configure,
427 ) -> Result<(), Error<'a>>;
428
429 fn handle_set_difficulty(
430 &mut self,
431 server_id: Option<usize>,
432 m: &mut server_to_client::SetDifficulty,
433 ) -> Result<(), Error<'a>>;
434
435 fn handle_set_extranonce(
436 &mut self,
437 server_id: Option<usize>,
438 m: &mut server_to_client::SetExtranonce,
439 ) -> Result<(), Error<'a>>;
440
441 fn handle_set_version_mask(
442 &mut self,
443 server_id: Option<usize>,
444 m: &mut server_to_client::SetVersionMask,
445 ) -> Result<(), Error<'a>>;
446
447 fn handle_subscribe(
448 &mut self,
449 server_id: Option<usize>,
450 subscribe: &server_to_client::Subscribe<'a>,
451 ) -> Result<(), Error<'a>>;
452
453 fn set_extranonce1(&mut self, server_id: Option<usize>, extranonce1: Extranonce<'a>);
454
455 fn extranonce1(&self, server_id: Option<usize>) -> Extranonce<'a>;
456
457 fn set_extranonce2_size(&mut self, server_id: Option<usize>, extra_nonce2_size: usize);
458
459 fn extranonce2_size(&self, server_id: Option<usize>) -> usize;
460
461 fn version_rolling_mask(&self, server_id: Option<usize>) -> Option<HexU32Be>;
462
463 fn set_version_rolling_mask(&mut self, server_id: Option<usize>, mask: Option<HexU32Be>);
464
465 fn set_version_rolling_min_bit(&mut self, server_id: Option<usize>, min: Option<HexU32Be>);
466
467 fn version_rolling_min_bit(&mut self, server_id: Option<usize>) -> Option<HexU32Be>;
468
469 fn set_status(&mut self, server_id: Option<usize>, status: ClientStatus);
470
471 fn signature(&self, server_id: Option<usize>) -> String;
472
473 fn status(&self, server_id: Option<usize>) -> ClientStatus;
474
475 fn last_notify(&self, server_id: Option<usize>) -> Option<server_to_client::Notify<'_>>;
476
477 #[allow(clippy::ptr_arg)]
479 fn is_authorized(&self, server_id: Option<usize>, name: &String) -> bool;
480
481 fn authorize_user_name(&mut self, server_id: Option<usize>, name: String);
483
484 fn configure(&mut self, server_id: Option<usize>, id: u64) -> json_rpc::Message {
485 if self.version_rolling_min_bit(server_id).is_none()
486 && self.version_rolling_mask(server_id).is_none()
487 {
488 client_to_server::Configure::void(id).into()
489 } else {
490 client_to_server::Configure::new(
491 id,
492 self.version_rolling_mask(server_id),
493 self.version_rolling_min_bit(server_id),
494 )
495 .into()
496 }
497 }
498
499 fn subscribe(
500 &mut self,
501 server_id: Option<usize>,
502 id: u64,
503 extranonce1: Option<Extranonce<'a>>,
504 ) -> Result<json_rpc::Message, Error<'a>> {
505 match self.status(server_id) {
506 ClientStatus::Init => Err(Error::IncorrectClientStatus("mining.subscribe".to_string())),
507 _ => Ok(client_to_server::Subscribe {
508 id,
509 agent_signature: self.signature(server_id),
510 extranonce1,
511 }
512 .try_into()?),
513 }
514 }
515
516 fn authorize(
517 &mut self,
518 server_id: Option<usize>,
519 id: u64,
520 name: String,
521 password: String,
522 ) -> Result<json_rpc::Message, Error<'_>> {
523 match self.status(server_id) {
524 ClientStatus::Init => Err(Error::IncorrectClientStatus("mining.authorize".to_string())),
525 _ => Ok(client_to_server::Authorize { id, name, password }.into()),
526 }
527 }
528
529 #[allow(clippy::too_many_arguments)]
530 fn submit(
531 &mut self,
532 server_id: Option<usize>,
533 id: u64,
534 user_name: String,
535 extra_nonce2: Extranonce<'a>,
536 time: i64,
537 nonce: i64,
538 version_bits: Option<HexU32Be>,
539 ) -> Result<json_rpc::Message, Error<'a>> {
540 match self.status(server_id) {
541 ClientStatus::Init => Err(Error::IncorrectClientStatus("mining.submit".to_string())),
542 _ => {
543 if let Some(notify) = self.last_notify(server_id) {
544 if !self.is_authorized(server_id, &user_name) {
545 return Err(Error::UnauthorizedClient(user_name));
546 }
547 Ok(client_to_server::Submit {
548 job_id: notify.job_id,
549 user_name,
550 extra_nonce2,
551 time: HexU32Be(time as u32),
552 nonce: HexU32Be(nonce as u32),
553 version_bits,
554 id,
555 }
556 .into())
557 } else {
558 Err(Error::IncorrectClientStatus(
559 "No Notify instance found".to_string(),
560 ))
561 }
562 }
563 }
564 }
565}
566
567#[derive(Debug, Copy, Clone, PartialEq, Eq)]
568pub enum ClientStatus {
569 Init,
570 Configured,
571 Subscribed,
572}
573
574#[cfg(test)]
575mod tests {
576 use super::*;
577 use std::collections::HashSet;
578
579 struct TestServer<'a> {
581 authorized_users: HashSet<String>,
582 extranonce1: Extranonce<'a>,
583 extranonce2_size: usize,
584 version_rolling_mask: Option<HexU32Be>,
585 version_rolling_min_bit: Option<HexU32Be>,
586 }
587
588 impl<'a> TestServer<'a> {
589 fn new(extranonce1: Extranonce<'a>, extranonce2_size: usize) -> Self {
590 Self {
591 authorized_users: HashSet::new(),
592 extranonce1,
593 extranonce2_size,
594 version_rolling_mask: None,
595 version_rolling_min_bit: None,
596 }
597 }
598 }
599
600 impl<'a> IsServer<'a> for TestServer<'a> {
601 fn handle_configure(
602 &mut self,
603 _client_id: Option<usize>,
604 _request: &client_to_server::Configure,
605 ) -> (Option<server_to_client::VersionRollingParams>, Option<bool>) {
606 (None, None)
607 }
608
609 fn handle_subscribe(
610 &self,
611 _client_id: Option<usize>,
612 _request: &client_to_server::Subscribe,
613 ) -> Vec<(String, String)> {
614 vec![("mining.notify".to_string(), "1".to_string())]
615 }
616
617 fn handle_authorize(
618 &self,
619 _client_id: Option<usize>,
620 _request: &client_to_server::Authorize,
621 ) -> bool {
622 true
623 }
624
625 fn notify(&mut self, _client_id: Option<usize>) -> Result<json_rpc::Message, Error<'_>> {
626 Ok(json_rpc::Message::StandardRequest(
627 json_rpc::StandardRequest {
628 id: 1,
629 method: "mining.notify".to_string(),
630 params: serde_json::json!([]),
631 },
632 ))
633 }
634
635 fn handle_submit(
636 &self,
637 _client_id: Option<usize>,
638 _request: &client_to_server::Submit<'a>,
639 ) -> bool {
640 true
641 }
642
643 fn handle_extranonce_subscribe(&self) {}
644
645 fn is_authorized(&self, _client_id: Option<usize>, name: &str) -> bool {
646 self.authorized_users.contains(name)
647 }
648
649 fn authorize(&mut self, _client_id: Option<usize>, name: &str) {
650 self.authorized_users.insert(name.to_string());
651 }
652
653 fn set_extranonce1(
654 &mut self,
655 _client_id: Option<usize>,
656 extranonce1: Option<Extranonce<'a>>,
657 ) -> Extranonce<'a> {
658 if let Some(extranonce1) = extranonce1 {
659 self.extranonce1 = extranonce1;
660 }
661 self.extranonce1.clone()
662 }
663
664 fn extranonce1(&self, _client_id: Option<usize>) -> Extranonce<'a> {
665 self.extranonce1.clone()
666 }
667
668 fn set_extranonce2_size(
669 &mut self,
670 _client_id: Option<usize>,
671 extra_nonce2_size: Option<usize>,
672 ) -> usize {
673 if let Some(extra_nonce2_size) = extra_nonce2_size {
674 self.extranonce2_size = extra_nonce2_size;
675 }
676 self.extranonce2_size
677 }
678
679 fn extranonce2_size(&self, _client_id: Option<usize>) -> usize {
680 self.extranonce2_size
681 }
682
683 fn version_rolling_mask(&self, _client_id: Option<usize>) -> Option<HexU32Be> {
684 None
685 }
686
687 fn set_version_rolling_mask(&mut self, _client_id: Option<usize>, mask: Option<HexU32Be>) {
688 self.version_rolling_mask = mask;
689 }
690
691 fn set_version_rolling_min_bit(
692 &mut self,
693 _client_id: Option<usize>,
694 mask: Option<HexU32Be>,
695 ) {
696 self.version_rolling_min_bit = mask;
697 }
698 }
699
700 #[test]
701 fn test_server_handle_invalid_message() {
702 let extranonce1 = Extranonce::try_from(Vec::<u8>::from_hex("08000002").unwrap()).unwrap();
703 let mut server = TestServer::new(extranonce1, 4);
704
705 let request_message = json_rpc::Message::StandardRequest(json_rpc::StandardRequest {
707 id: 42,
708 method: "mining.subscribe_bad".to_string(),
709 params: serde_json::json!([]),
710 });
711
712 let result = server.handle_message(None, request_message);
713
714 assert!(result.is_err());
715 match result.unwrap_err() {
716 Error::Method(inner) => match *inner {
717 MethodError::MethodNotFound(_) => {}
718 other => panic!("Expected MethodNotFound error, got {:?}", other),
719 },
720 other => panic!("Expected Error::Method, got {:?}", other),
721 }
722 }
723
724 #[test]
725 fn version_mask_invalid_len() {
726 let raw = serde_json::json!([
727 "mining.set_version_mask",
728 ["123456789"] ]);
730
731 let msg: Result<Message, _> = serde_json::from_value(raw);
732
733 if let Ok(msg) = msg {
734 let result = Method::try_from(msg);
735 assert!(result.is_err(), "Expected error for invalid hex length");
736
737 match result.unwrap_err() {
738 MethodError::ParsingMethodError((ParsingMethodError::InvalidHexLen(_), _)) => {}
739 other => panic!("Expected InvalidHexLen, got {:?}", other),
740 }
741 } else {
742 panic!("Message parsing failed unexpectedly");
743 }
744 }
745}