minerd/
rpc.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
/* This file is part of DarkFi (https://dark.fi)
 *
 * Copyright (C) 2020-2024 Dyne.org foundation
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

use std::collections::HashSet;

use log::{debug, error, info};
use num_bigint::BigUint;
use smol::lock::MutexGuard;

use darkfi::{
    blockchain::BlockInfo,
    rpc::{
        jsonrpc::{ErrorCode, JsonError, JsonRequest, JsonResponse, JsonResult},
        server::RequestHandler,
        util::JsonValue,
    },
    system::{sleep, StoppableTaskPtr},
    util::encoding::base64,
    validator::pow::mine_block,
};
use darkfi_sdk::num_traits::Num;
use darkfi_serial::{async_trait, deserialize_async};

use crate::{
    error::{server_error, RpcError},
    MinerNode,
};

#[async_trait]
impl RequestHandler<()> for MinerNode {
    async fn handle_request(&self, req: JsonRequest) -> JsonResult {
        debug!(target: "minerd::rpc", "--> {}", req.stringify().unwrap());

        match req.method.as_str() {
            "ping" => self.pong(req.id, req.params).await,
            "abort" => self.abort(req.id, req.params).await,
            "mine" => self.mine(req.id, req.params).await,
            _ => JsonError::new(ErrorCode::MethodNotFound, None, req.id).into(),
        }
    }

    async fn connections_mut(&self) -> MutexGuard<'life0, HashSet<StoppableTaskPtr>> {
        self.rpc_connections.lock().await
    }
}

impl MinerNode {
    // RPCAPI:
    // Signals miner daemon to abort mining pending request.
    // Returns `true` on success.
    //
    // --> {"jsonrpc": "2.0", "method": "abort", "params": [], "id": 42}
    // <-- {"jsonrpc": "2.0", "result": "true", "id": 42}
    async fn abort(&self, id: u16, _params: JsonValue) -> JsonResult {
        if let Some(e) = self.abort_pending(id).await {
            return e
        };
        JsonResponse::new(JsonValue::Boolean(true), id).into()
    }

    // RPCAPI:
    // Mine provided block for requested mine target, and return the corresponding nonce value.
    //
    // --> {"jsonrpc": "2.0", "method": "mine", "params": ["target", "block"], "id": 42}
    // --> {"jsonrpc": "2.0", "result": "nonce", "id": 42}
    async fn mine(&self, id: u16, params: JsonValue) -> JsonResult {
        // Verify parameters
        if !params.is_array() {
            return JsonError::new(ErrorCode::InvalidParams, None, id).into()
        }
        let params = params.get::<Vec<JsonValue>>().unwrap();
        if params.len() != 2 || !params[0].is_string() || !params[1].is_string() {
            return JsonError::new(ErrorCode::InvalidParams, None, id).into()
        }

        // Parse parameters
        let Ok(target) = BigUint::from_str_radix(params[0].get::<String>().unwrap(), 10) else {
            error!(target: "minerd::rpc", "Failed to parse target");
            return server_error(RpcError::TargetParseError, id, None)
        };
        let Some(block_bytes) = base64::decode(params[1].get::<String>().unwrap()) else {
            error!(target: "minerd::rpc", "Failed to parse block bytes");
            return server_error(RpcError::BlockParseError, id, None)
        };
        let Ok(mut block) = deserialize_async::<BlockInfo>(&block_bytes).await else {
            error!(target: "minerd::rpc", "Failed to parse block");
            return server_error(RpcError::BlockParseError, id, None)
        };
        let block_hash = block.hash();
        info!(target: "minerd::rpc", "Received request to mine block {} for target: {}", block_hash, target);

        // Check if another request is being processed
        if let Some(e) = self.abort_pending(id).await {
            return e
        };

        // Mine provided block
        info!(target: "minerd::rpc", "Mining block {} for target: {}", block_hash, target);
        if let Err(e) = mine_block(&target, &mut block, self.threads, &self.stop_signal.clone()) {
            error!(target: "minerd::rpc", "Failed mining block {} with error: {}", block_hash, e);
            return server_error(RpcError::MiningFailed, id, None)
        }

        // Return block nonce
        JsonResponse::new(JsonValue::Number(block.header.nonce as f64), id).into()
    }

    /// Auxiliary function to abort pending request.
    async fn abort_pending(&self, id: u16) -> Option<JsonResult> {
        // Check if a pending request is being processed
        info!(target: "minerd::rpc", "Checking if a pending request is being processed...");
        if self.stop_signal.receiver_count() == 0 {
            info!(target: "minerd::rpc", "No pending requests!");
            return None
        }

        info!(target: "minerd::rpc", "Pending request is in progress, sending stop signal...");
        // Send stop signal to worker
        if self.sender.send(()).await.is_err() {
            error!(target: "minerd::rpc", "Failed to stop pending request");
            return Some(server_error(RpcError::StopFailed, id, None))
        }

        // Wait for worker to terminate
        info!(target: "minerd::rpc", "Waiting for request to terminate...");
        while self.stop_signal.receiver_count() > 1 {
            sleep(1).await;
        }
        info!(target: "minerd::rpc", "Pending request terminated!");

        // Consume channel item so its empty again
        if self.stop_signal.recv().await.is_err() {
            error!(target: "minerd::rpc", "Failed to cleanup stop signal channel");
            return Some(server_error(RpcError::StopFailed, id, None))
        }

        None
    }
}