mirror of
https://github.com/mimblewimble/mwixnet.git
synced 2025-02-01 08:51:09 +03:00
189 lines
5.2 KiB
Rust
189 lines
5.2 KiB
Rust
use std::sync::Arc;
|
|
|
|
use async_trait::async_trait;
|
|
use grin_api::json_rpc::{build_request, Response};
|
|
use grin_core::ser;
|
|
use grin_core::ser::ProtocolVersion;
|
|
use grin_wallet_util::OnionV3Address;
|
|
use serde_json;
|
|
use serde_json::json;
|
|
use thiserror::Error;
|
|
use tor_rtcompat::Runtime;
|
|
|
|
use grin_onion::crypto::dalek::{self, DalekPublicKey};
|
|
use grin_onion::onion::Onion;
|
|
|
|
use crate::config::ServerConfig;
|
|
use crate::servers::mix_rpc::{MixReq, MixResp};
|
|
use crate::tor::TorService;
|
|
use crate::{http, tor};
|
|
|
|
/// Error types for interacting with nodes
|
|
#[derive(Error, Debug)]
|
|
pub enum MixClientError {
|
|
#[error("Tor Error: {0:?}")]
|
|
Tor(tor::TorError),
|
|
#[error("Communication Error: {0:?}")]
|
|
CommError(http::HttpError),
|
|
#[error("Dalek Error: {0:?}")]
|
|
Dalek(dalek::DalekError),
|
|
#[error("Error decoding JSON response: {0:?}")]
|
|
DecodeResponseError(serde_json::Error),
|
|
#[error("Error in JSON-RPC response: {0:?}")]
|
|
ResponseError(grin_api::json_rpc::RpcError),
|
|
#[error("Custom client error: {0:?}")]
|
|
Custom(String),
|
|
}
|
|
|
|
/// A client for consuming a mix API
|
|
#[async_trait]
|
|
pub trait MixClient: Send + Sync {
|
|
/// Swaps the outputs provided and returns the final swapped outputs and kernels.
|
|
async fn mix_outputs(&self, onions: &Vec<Onion>) -> Result<MixResp, MixClientError>;
|
|
}
|
|
|
|
pub struct MixClientImpl<R: Runtime> {
|
|
config: ServerConfig,
|
|
tor: Arc<grin_util::Mutex<TorService<R>>>,
|
|
addr: OnionV3Address,
|
|
}
|
|
|
|
impl<R: Runtime> MixClientImpl<R> {
|
|
pub fn new(
|
|
config: ServerConfig,
|
|
tor: Arc<grin_util::Mutex<TorService<R>>>,
|
|
next_pubkey: DalekPublicKey,
|
|
) -> Self {
|
|
let addr = OnionV3Address::from_bytes(next_pubkey.as_ref().to_bytes());
|
|
MixClientImpl { config, tor, addr }
|
|
}
|
|
|
|
async fn async_send_json_request<D: serde::de::DeserializeOwned>(
|
|
&self,
|
|
addr: &OnionV3Address,
|
|
method: &str,
|
|
params: &serde_json::Value,
|
|
) -> Result<D, MixClientError> {
|
|
let url = format!("{}/v1", addr.to_http_str());
|
|
let request_str = serde_json::to_string(&build_request(method, params)).unwrap();
|
|
let hyper_request =
|
|
http::build_request(&url, &None, request_str).map_err(MixClientError::CommError)?;
|
|
|
|
let hyper_client = self.tor.lock().new_hyper_client();
|
|
let res = hyper_client.request(hyper_request).await.unwrap();
|
|
|
|
let body_bytes = hyper::body::to_bytes(res.into_body()).await.unwrap();
|
|
let res = String::from_utf8(body_bytes.to_vec()).unwrap();
|
|
|
|
let response: Response =
|
|
serde_json::from_str(&res).map_err(MixClientError::DecodeResponseError)?;
|
|
|
|
if let Some(ref e) = response.error {
|
|
return Err(MixClientError::ResponseError(e.clone()));
|
|
}
|
|
|
|
let result = match response.result.clone() {
|
|
Some(r) => serde_json::from_value(r).map_err(MixClientError::DecodeResponseError),
|
|
None => serde_json::from_value(serde_json::Value::Null)
|
|
.map_err(MixClientError::DecodeResponseError),
|
|
}?;
|
|
|
|
Ok(result)
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl<R: Runtime> MixClient for MixClientImpl<R> {
|
|
async fn mix_outputs(&self, onions: &Vec<Onion>) -> Result<MixResp, MixClientError> {
|
|
let serialized = ser::ser_vec(&onions, ProtocolVersion::local()).unwrap();
|
|
let sig =
|
|
dalek::sign(&self.config.key, serialized.as_slice()).map_err(MixClientError::Dalek)?;
|
|
let mix = MixReq::new(onions.clone(), sig);
|
|
|
|
self.async_send_json_request::<MixResp>(&self.addr, "mix", &json!([mix]))
|
|
.await
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub mod mock {
|
|
use std::collections::HashMap;
|
|
|
|
use async_trait::async_trait;
|
|
|
|
use grin_onion::onion::Onion;
|
|
|
|
use crate::servers::mix_rpc::MixResp;
|
|
|
|
use super::{MixClient, MixClientError};
|
|
|
|
pub struct MockMixClient {
|
|
results: HashMap<Vec<Onion>, MixResp>,
|
|
}
|
|
|
|
impl MockMixClient {
|
|
pub fn new() -> MockMixClient {
|
|
MockMixClient {
|
|
results: HashMap::new(),
|
|
}
|
|
}
|
|
|
|
pub fn set_response(&mut self, onions: &Vec<Onion>, r: MixResp) {
|
|
self.results.insert(onions.clone(), r);
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl MixClient for MockMixClient {
|
|
async fn mix_outputs(&self, onions: &Vec<Onion>) -> Result<MixResp, MixClientError> {
|
|
self.results
|
|
.get(onions)
|
|
.map(|r| Ok(r.clone()))
|
|
.unwrap_or(Err(MixClientError::Custom(
|
|
"No response set for input".into(),
|
|
)))
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub mod test_util {
|
|
use std::sync::Arc;
|
|
|
|
use async_trait::async_trait;
|
|
use grin_core::ser;
|
|
use grin_core::ser::ProtocolVersion;
|
|
|
|
use grin_onion::crypto::dalek::{self, DalekPublicKey};
|
|
use grin_onion::crypto::secp::SecretKey;
|
|
use grin_onion::onion::Onion;
|
|
|
|
use crate::servers::mix::MixServer;
|
|
use crate::servers::mix_rpc::MixResp;
|
|
|
|
use super::{MixClient, MixClientError};
|
|
|
|
/// Implementation of the 'MixClient' trait that calls a mix server implementation directly.
|
|
/// No JSON-RPC serialization or socket communication occurs.
|
|
#[derive(Clone)]
|
|
pub struct DirectMixClient {
|
|
pub key: SecretKey,
|
|
pub mix_server: Arc<dyn MixServer>,
|
|
}
|
|
|
|
#[async_trait]
|
|
impl MixClient for DirectMixClient {
|
|
async fn mix_outputs(&self, onions: &Vec<Onion>) -> Result<MixResp, MixClientError> {
|
|
let serialized = ser::ser_vec(&onions, ProtocolVersion::local()).unwrap();
|
|
let sig =
|
|
dalek::sign(&self.key, serialized.as_slice()).map_err(MixClientError::Dalek)?;
|
|
|
|
sig.verify(
|
|
&DalekPublicKey::from_secret(&self.key),
|
|
serialized.as_slice(),
|
|
)
|
|
.unwrap();
|
|
Ok(self.mix_server.mix_outputs(&onions, &sig).await.unwrap())
|
|
}
|
|
}
|
|
}
|