mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-20 17:54:38 +00:00
Support an authenticated Monero RPC
Closes https://github.com/serai-dex/serai/issues/143.
This commit is contained in:
parent
b05a223b69
commit
6f9cf510da
5 changed files with 93 additions and 12 deletions
27
Cargo.lock
generated
27
Cargo.lock
generated
|
@ -1594,6 +1594,19 @@ dependencies = [
|
||||||
"subtle",
|
"subtle",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "digest_auth"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fa30657988b2ced88f68fe490889e739bf98d342916c33ed3100af1d6f1cbc9c"
|
||||||
|
dependencies = [
|
||||||
|
"digest 0.9.0",
|
||||||
|
"hex",
|
||||||
|
"md-5 0.9.1",
|
||||||
|
"rand 0.8.5",
|
||||||
|
"sha2 0.9.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "directories"
|
name = "directories"
|
||||||
version = "4.0.1"
|
version = "4.0.1"
|
||||||
|
@ -2206,7 +2219,7 @@ dependencies = [
|
||||||
"glob",
|
"glob",
|
||||||
"hex",
|
"hex",
|
||||||
"home",
|
"home",
|
||||||
"md-5",
|
"md-5 0.10.5",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"path-slash",
|
"path-slash",
|
||||||
|
@ -4423,6 +4436,17 @@ dependencies = [
|
||||||
"rawpointer",
|
"rawpointer",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "md-5"
|
||||||
|
version = "0.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15"
|
||||||
|
dependencies = [
|
||||||
|
"block-buffer 0.9.0",
|
||||||
|
"digest 0.9.0",
|
||||||
|
"opaque-debug 0.3.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "md-5"
|
name = "md-5"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
|
@ -4638,6 +4662,7 @@ dependencies = [
|
||||||
"blake2",
|
"blake2",
|
||||||
"curve25519-dalek 3.2.0",
|
"curve25519-dalek 3.2.0",
|
||||||
"dalek-ff-group",
|
"dalek-ff-group",
|
||||||
|
"digest_auth",
|
||||||
"dleq",
|
"dleq",
|
||||||
"flexible-transcript",
|
"flexible-transcript",
|
||||||
"group",
|
"group",
|
||||||
|
|
|
@ -46,6 +46,7 @@ serde_json = "1.0"
|
||||||
base58-monero = "1"
|
base58-monero = "1"
|
||||||
monero-epee-bin-serde = "1.0"
|
monero-epee-bin-serde = "1.0"
|
||||||
|
|
||||||
|
digest_auth = "0.3"
|
||||||
reqwest = { version = "0.11", features = ["json"] }
|
reqwest = { version = "0.11", features = ["json"] }
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
|
|
|
@ -7,7 +7,8 @@ use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY};
|
||||||
use serde::{Serialize, Deserialize, de::DeserializeOwned};
|
use serde::{Serialize, Deserialize, de::DeserializeOwned};
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
|
|
||||||
use reqwest;
|
use digest_auth::AuthContext;
|
||||||
|
use reqwest::{Client, RequestBuilder};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Protocol,
|
Protocol,
|
||||||
|
@ -72,11 +73,47 @@ fn rpc_point(point: &str) -> Result<EdwardsPoint, RpcError> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Rpc(String);
|
pub struct Rpc {
|
||||||
|
client: Client,
|
||||||
|
userpass: Option<(String, String)>,
|
||||||
|
url: String,
|
||||||
|
}
|
||||||
|
|
||||||
impl Rpc {
|
impl Rpc {
|
||||||
pub fn new(daemon: String) -> Rpc {
|
/// Create a new RPC connection.
|
||||||
Rpc(daemon)
|
/// A daemon requiring authentication can be used via including the username and password in the
|
||||||
|
/// URL.
|
||||||
|
pub fn new(mut url: String) -> Result<Rpc, RpcError> {
|
||||||
|
// Parse out the username and password
|
||||||
|
let userpass = if url.contains('@') {
|
||||||
|
let url_clone = url.clone();
|
||||||
|
let split_url = url_clone.split('@').collect::<Vec<_>>();
|
||||||
|
if split_url.len() != 2 {
|
||||||
|
Err(RpcError::InvalidNode)?;
|
||||||
|
}
|
||||||
|
let mut userpass = split_url[0];
|
||||||
|
url = split_url[1].to_string();
|
||||||
|
|
||||||
|
// If there was additionally a protocol string, restore that to the daemon URL
|
||||||
|
if userpass.contains("://") {
|
||||||
|
let split_userpass = userpass.split("://").collect::<Vec<_>>();
|
||||||
|
if split_userpass.len() != 2 {
|
||||||
|
Err(RpcError::InvalidNode)?;
|
||||||
|
}
|
||||||
|
url = split_userpass[0].to_string() + "://" + &url;
|
||||||
|
userpass = split_userpass[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
let split_userpass = userpass.split(':').collect::<Vec<_>>();
|
||||||
|
if split_userpass.len() != 2 {
|
||||||
|
Err(RpcError::InvalidNode)?;
|
||||||
|
}
|
||||||
|
Some((split_userpass[0].to_string(), split_userpass[1].to_string()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Rpc { client: Client::new(), userpass, url })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform a RPC call to the specified method with the provided parameters.
|
/// Perform a RPC call to the specified method with the provided parameters.
|
||||||
|
@ -87,8 +124,7 @@ impl Rpc {
|
||||||
method: &str,
|
method: &str,
|
||||||
params: Option<Params>,
|
params: Option<Params>,
|
||||||
) -> Result<Response, RpcError> {
|
) -> Result<Response, RpcError> {
|
||||||
let client = reqwest::Client::new();
|
let mut builder = self.client.post(self.url.clone() + "/" + method);
|
||||||
let mut builder = client.post(self.0.clone() + "/" + method);
|
|
||||||
if let Some(params) = params.as_ref() {
|
if let Some(params) = params.as_ref() {
|
||||||
builder = builder.json(params);
|
builder = builder.json(params);
|
||||||
}
|
}
|
||||||
|
@ -115,16 +151,35 @@ impl Rpc {
|
||||||
method: &str,
|
method: &str,
|
||||||
params: Vec<u8>,
|
params: Vec<u8>,
|
||||||
) -> Result<Response, RpcError> {
|
) -> Result<Response, RpcError> {
|
||||||
let client = reqwest::Client::new();
|
let builder = self.client.post(self.url.clone() + "/" + method).body(params.clone());
|
||||||
let builder = client.post(self.0.clone() + "/" + method).body(params);
|
|
||||||
self.call_tail(method, builder.header("Content-Type", "application/octet-stream")).await
|
self.call_tail(method, builder.header("Content-Type", "application/octet-stream")).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn call_tail<Response: DeserializeOwned + Debug>(
|
async fn call_tail<Response: DeserializeOwned + Debug>(
|
||||||
&self,
|
&self,
|
||||||
method: &str,
|
method: &str,
|
||||||
builder: reqwest::RequestBuilder,
|
mut builder: RequestBuilder,
|
||||||
) -> Result<Response, RpcError> {
|
) -> Result<Response, RpcError> {
|
||||||
|
if let Some((user, pass)) = &self.userpass {
|
||||||
|
let req = self.client.post(&self.url).send().await.map_err(|_| RpcError::InvalidNode)?;
|
||||||
|
// Only provide authentication if this daemon actually expects it
|
||||||
|
if let Some(header) = req.headers().get("www-authenticate") {
|
||||||
|
builder = builder.header(
|
||||||
|
"Authorization",
|
||||||
|
digest_auth::parse(header.to_str().map_err(|_| RpcError::InvalidNode)?)
|
||||||
|
.map_err(|_| RpcError::InvalidNode)?
|
||||||
|
.respond(&AuthContext::new_post::<_, _, _, &[u8]>(
|
||||||
|
user,
|
||||||
|
pass,
|
||||||
|
"/".to_string() + method,
|
||||||
|
None,
|
||||||
|
))
|
||||||
|
.map_err(|_| RpcError::InvalidNode)?
|
||||||
|
.to_header_string(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let res = builder.send().await.map_err(|_| RpcError::ConnectionError)?;
|
let res = builder.send().await.map_err(|_| RpcError::ConnectionError)?;
|
||||||
|
|
||||||
Ok(if !method.ends_with(".bin") {
|
Ok(if !method.ends_with(".bin") {
|
||||||
|
|
|
@ -11,7 +11,7 @@ use monero_serai::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn rpc() -> Rpc {
|
pub async fn rpc() -> Rpc {
|
||||||
let rpc = Rpc::new("http://127.0.0.1:18081".to_string());
|
let rpc = Rpc::new("http://127.0.0.1:18081".to_string()).unwrap();
|
||||||
|
|
||||||
// Only run once
|
// Only run once
|
||||||
if rpc.get_height().await.unwrap() != 1 {
|
if rpc.get_height().await.unwrap() != 1 {
|
||||||
|
|
|
@ -70,7 +70,7 @@ pub struct Monero {
|
||||||
|
|
||||||
impl Monero {
|
impl Monero {
|
||||||
pub async fn new(url: String) -> Monero {
|
pub async fn new(url: String) -> Monero {
|
||||||
Monero { rpc: Rpc::new(url), view: additional_key::<Monero>(0).0 }
|
Monero { rpc: Rpc::new(url).unwrap(), view: additional_key::<Monero>(0).0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scanner(&self, spend: dfg::EdwardsPoint) -> Scanner {
|
fn scanner(&self, spend: dfg::EdwardsPoint) -> Scanner {
|
||||||
|
|
Loading…
Reference in a new issue