darkfi_mmproxy/
monerod.rsuse std::{collections::HashMap, str::FromStr};
use darkfi::{
rpc::{
jsonrpc::{JsonRequest, JsonResponse},
util::JsonValue,
},
Error, Result,
};
use log::{debug, error, info};
use monero::blockdata::transaction::{ExtraField, RawExtraField, SubField::MergeMining};
use super::MiningProxy;
pub(crate) enum MonerodRequest {
Get(String),
Post(JsonRequest),
}
impl MiningProxy {
pub(crate) async fn monero_request(&self, req: MonerodRequest) -> Result<JsonValue> {
let mut rep = match req {
MonerodRequest::Get(method) => {
let endpoint = format!("{}{}", self.monero_rpc, method);
match surf::get(&endpoint).await {
Ok(v) => v,
Err(e) => {
let e = format!("Failed sending monerod GET request: {}", e);
error!(target: "monerod::monero_request", "{}", e);
return Err(Error::Custom(e))
}
}
}
MonerodRequest::Post(data) => {
let endpoint = format!("{}json_rpc", self.monero_rpc);
let client = surf::Client::new();
match client
.get(endpoint)
.header("Content-Type", "application/json")
.body(data.stringify().unwrap())
.send()
.await
{
Ok(v) => v,
Err(e) => {
let e = format!("Failed sending monerod POST request: {}", e);
error!(target: "monerod::monero_request", "{}", e);
return Err(Error::Custom(e))
}
}
}
};
let json_rep: JsonValue = match rep.body_string().await {
Ok(v) => match v.parse() {
Ok(v) => v,
Err(e) => {
let e = format!("Failed parsing JSON string from monerod response: {}", e);
error!(target: "monerod::monero_request", "{}", e);
return Err(Error::Custom(e))
}
},
Err(e) => {
let e = format!("Failed parsing body string from monerod response: {}", e);
error!(target: "monerod::monero_request", "{}", e);
return Err(Error::Custom(e))
}
};
Ok(json_rep)
}
pub async fn monerod_get_height(&self) -> Result<JsonValue> {
info!(target: "monerod::getheight", "Proxying /getheight request");
let rep = self.monero_request(MonerodRequest::Get("getheight".to_string())).await?;
Ok(rep)
}
pub async fn monerod_get_info(&self) -> Result<JsonValue> {
info!(target: "monerod::getinfo", "Proxying /getinfo request");
let rep = self.monero_request(MonerodRequest::Get("getinfo".to_string())).await?;
Ok(rep)
}
pub async fn monerod_submit_block(&self, req: &JsonValue) -> Result<JsonValue> {
info!(target: "monerod::submitblock", "Proxying submitblock request");
let request = JsonRequest::try_from(req)?;
if !request.params.is_array() {
return Err(Error::Custom("Invalid request".to_string()))
}
for block in request.params.get::<Vec<JsonValue>>().unwrap() {
let Some(block) = block.get::<String>() else {
return Err(Error::Custom("Invalid request".to_string()))
};
debug!(
target: "monerod::submitblock", "{:#?}",
monero::consensus::deserialize::<monero::Block>(&hex::decode(block).unwrap()).unwrap(),
);
}
let response = self.monero_request(MonerodRequest::Post(request)).await?;
Ok(response)
}
pub async fn monerod_getblocktemplate(&self, req: &JsonValue) -> Result<JsonValue> {
info!(target: "monerod::getblocktemplate", "Proxying getblocktemplate request");
let mut request = JsonRequest::try_from(req)?;
if !request.params.is_object() {
return Err(Error::Custom("Invalid request".to_string()))
}
let params: &mut HashMap<String, JsonValue> = request.params.get_mut().unwrap();
if !params.contains_key("wallet_address") {
return Err(Error::Custom("Invalid request".to_string()))
}
let Some(wallet_address) = params["wallet_address"].get::<String>() else {
return Err(Error::Custom("Invalid request".to_string()))
};
let Ok(wallet_address) = monero::Address::from_str(wallet_address) else {
return Err(Error::Custom("Invalid request".to_string()))
};
if wallet_address.network != self.monero_network {
return Err(Error::Custom("Monero network address mismatch".to_string()))
}
if wallet_address.addr_type != monero::AddressType::Standard {
return Err(Error::Custom("Non-standard Monero address".to_string()))
}
let mm_tag = MergeMining(monero::VarInt(32), monero::Hash([0_u8; 32]));
let tx_extra: RawExtraField = ExtraField(vec![mm_tag]).into();
debug!(target: "monerod::getblocktemplate", "Inserting \"reserve_size\":{}", tx_extra.0.len());
params.insert("reserve_size".to_string(), (tx_extra.0.len() as f64).into());
params.remove("extra_nonce");
let gbt_response = self.monero_request(MonerodRequest::Post(request)).await?;
debug!(target: "monerod::getblocktemplate", "Got {}", gbt_response.stringify()?);
let mut gbt_response = JsonResponse::try_from(&gbt_response)?;
let gbt_result: &mut HashMap<String, JsonValue> = gbt_response.result.get_mut().unwrap();
let mut block_template = monero::consensus::deserialize::<monero::Block>(
&hex::decode(gbt_result["blocktemplate_blob"].get::<String>().unwrap()).unwrap(),
)
.unwrap();
block_template.miner_tx.prefix.extra = tx_extra;
gbt_result.insert(
"blocktemplate_blob".to_string(),
hex::encode(monero::consensus::serialize(&block_template)).into(),
);
gbt_result.insert(
"blockhashing_blob".to_string(),
hex::encode(block_template.serialize_hashable()).into(),
);
Ok((&gbt_response).into())
}
}