1use std::{collections::HashMap, sync::Arc};
20
21use darkfi::{
22 async_daemonize, cli_desc,
23 rpc::{
24 jsonrpc::{JsonRequest, JsonResponse},
25 util::JsonValue,
26 },
27 Error, Result,
28};
29use log::{debug, error, info};
30use serde::Deserialize;
31use smol::{stream::StreamExt, Executor};
32use structopt::StructOpt;
33use structopt_toml::StructOptToml;
34use surf::StatusCode;
35use url::Url;
36
37const CONFIG_FILE: &str = "darkfi_mmproxy.toml";
38const CONFIG_FILE_CONTENTS: &str = include_str!("../darkfi_mmproxy.toml");
39
40mod monerod;
42use monerod::MonerodRequest;
43
44#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
45#[serde(default)]
46#[structopt(name = "darkfi-mmproxy", about = cli_desc!())]
47struct Args {
48 #[structopt(short, parse(from_occurrences))]
49 verbose: u8,
51
52 #[structopt(short, long)]
53 config: Option<String>,
55
56 #[structopt(long)]
57 log: Option<String>,
59
60 #[structopt(flatten)]
61 mmproxy: MmproxyArgs,
62
63 #[structopt(flatten)]
64 monerod: MonerodArgs,
65}
66
67#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
68#[structopt()]
69struct MmproxyArgs {
70 #[structopt(long, default_value = "http://127.0.0.1:3333")]
71 mmproxy_rpc: Url,
73}
74
75#[derive(Clone, Debug, Deserialize, StructOpt, StructOptToml)]
76#[structopt()]
77struct MonerodArgs {
78 #[structopt(long, default_value = "mainnet")]
79 monero_network: String,
81
82 #[structopt(long, default_value = "http://127.0.0.1:18081")]
83 monero_rpc: Url,
85}
86
87struct MiningProxy {
89 monero_network: monero::Network,
91 monero_rpc: Url,
93}
94
95impl MiningProxy {
96 async fn new(monerod: MonerodArgs) -> Result<Self> {
98 let monero_network = match monerod.monero_network.to_lowercase().as_str() {
99 "mainnet" => monero::Network::Mainnet,
100 "testnet" => monero::Network::Testnet,
101 _ => {
102 error!("Invalid Monero network \"{}\"", monerod.monero_network);
103 return Err(Error::Custom(format!(
104 "Invalid Monero network \"{}\"",
105 monerod.monero_network
106 )))
107 }
108 };
109
110 let self_ = Self { monero_network, monero_rpc: monerod.monero_rpc };
113
114 let req = JsonRequest::new("getinfo", vec![].into());
115 let rep: JsonResponse = match self_.monero_request(MonerodRequest::Post(req)).await {
116 Ok(v) => JsonResponse::try_from(&v)?,
117 Err(e) => {
118 error!("Failed connecting to monerod RPC: {}", e);
119 return Err(e)
120 }
121 };
122
123 let Some(result) = rep.result.get::<HashMap<String, JsonValue>>() else {
124 error!("Invalid response from monerod RPC");
125 return Err(Error::Custom("Invalid response from monerod RPC".to_string()))
126 };
127
128 let nettype = result.get("nettype").unwrap().get::<String>().unwrap();
129
130 let mut xmr_is_mainnet = false;
131 let mut xmr_is_testnet = false;
132
133 match nettype.as_str() {
134 "mainnet" | "fakechain" => xmr_is_mainnet = true,
136 "testnet" => xmr_is_testnet = true,
137 _ => unimplemented!("Missing handler for network {}", nettype),
138 }
139
140 if xmr_is_mainnet && monero_network != monero::Network::Mainnet {
141 error!("mmproxy requested testnet, but monerod is mainnet");
142 return Err(Error::Custom("Monero network mismatch".to_string()))
143 }
144
145 if xmr_is_testnet && monero_network != monero::Network::Testnet {
146 error!("mmproxy requested mainnet, but monerod is testnet");
147 return Err(Error::Custom("Monero network mismatch".to_string()))
148 }
149
150 Ok(self_)
151 }
152}
153
154async_daemonize!(realmain);
155async fn realmain(args: Args, ex: Arc<Executor<'static>>) -> Result<()> {
156 info!("Starting DarkFi x Monero merge mining proxy");
157
158 let mmproxy = Arc::new(MiningProxy::new(args.monerod).await?);
159 let mut app = tide::with_state(mmproxy);
160
161 app.at("/getheight").get(|req: tide::Request<Arc<MiningProxy>>| async move {
163 debug!(target: "monerod::getheight", "--> /getheight");
164 let mmproxy = req.state();
165 let return_data = mmproxy.monerod_get_height().await?;
166 let return_data = return_data.stringify()?;
167 debug!(target: "monerod::getheight", "<-- {}", return_data);
168 Ok(return_data)
169 });
170
171 app.at("/getinfo").get(|req: tide::Request<Arc<MiningProxy>>| async move {
173 debug!(target: "monerod::getinfo", "--> /getinfo");
174 let mmproxy = req.state();
175 let return_data = mmproxy.monerod_get_info().await?;
176 let return_data = return_data.stringify()?;
177 debug!(target: "monerod::getinfo", "<-- {}", return_data);
178 Ok(return_data)
179 });
180
181 app.at("/json_rpc").post(|mut req: tide::Request<Arc<MiningProxy>>| async move {
183 let body_string = match req.body_string().await {
184 Ok(v) => v,
185 Err(e) => {
186 error!(target: "monerod::json_rpc", "Failed reading request body: {}", e);
187 return Err(surf::Error::new(StatusCode::BadRequest, Error::Custom(e.to_string())))
188 }
189 };
190 debug!(target: "monerod::json_rpc", "--> {}", body_string);
191
192 let json_str: JsonValue = match body_string.parse() {
193 Ok(v) => v,
194 Err(e) => {
195 error!(target: "monerod::json_rpc", "Failed parsing JSON body: {}", e);
196 return Err(surf::Error::new(StatusCode::BadRequest, Error::Custom(e.to_string())))
197 }
198 };
199
200 let JsonValue::Object(ref request) = json_str else {
201 return Err(surf::Error::new(
202 StatusCode::BadRequest,
203 Error::Custom("Invalid JSONRPC request".to_string()),
204 ))
205 };
206
207 if !request.contains_key("method") || !request["method"].is_string() {
208 return Err(surf::Error::new(
209 StatusCode::BadRequest,
210 Error::Custom("Invalid JSONRPC request".to_string()),
211 ))
212 }
213
214 let mmproxy = req.state();
215
216 let return_data: JsonValue = match request["method"].get::<String>().unwrap().as_str() {
218 "getblocktemplate" => mmproxy.monerod_getblocktemplate(&json_str).await?,
219 "submitblock" => mmproxy.monerod_submit_block(&json_str).await?,
220 _ => {
221 return Err(surf::Error::new(
222 StatusCode::BadRequest,
223 Error::Custom("Invalid JSONRPC request".to_string()),
224 ))
225 }
226 };
227
228 let return_data = return_data.stringify()?;
229 debug!(target: "monerod::json_rpc", "<-- {}", return_data);
230 Ok(return_data)
231 });
232
233 ex.spawn(async move { app.listen(args.mmproxy.mmproxy_rpc).await.unwrap() }).detach();
234 info!("Merge mining proxy ready, waiting for connections");
235
236 let (signals_handler, signals_task) = SignalHandler::new(ex)?;
238 signals_handler.wait_termination(signals_task).await?;
239 info!("Caught termination signal, cleaning up and exiting");
240
241 Ok(())
242}