mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-03 17:40:34 +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",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "directories"
|
||||
version = "4.0.1"
|
||||
|
@ -2206,7 +2219,7 @@ dependencies = [
|
|||
"glob",
|
||||
"hex",
|
||||
"home",
|
||||
"md-5",
|
||||
"md-5 0.10.5",
|
||||
"num_cpus",
|
||||
"once_cell",
|
||||
"path-slash",
|
||||
|
@ -4423,6 +4436,17 @@ dependencies = [
|
|||
"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]]
|
||||
name = "md-5"
|
||||
version = "0.10.5"
|
||||
|
@ -4638,6 +4662,7 @@ dependencies = [
|
|||
"blake2",
|
||||
"curve25519-dalek 3.2.0",
|
||||
"dalek-ff-group",
|
||||
"digest_auth",
|
||||
"dleq",
|
||||
"flexible-transcript",
|
||||
"group",
|
||||
|
|
|
@ -46,6 +46,7 @@ serde_json = "1.0"
|
|||
base58-monero = "1"
|
||||
monero-epee-bin-serde = "1.0"
|
||||
|
||||
digest_auth = "0.3"
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
|
||||
[build-dependencies]
|
||||
|
|
|
@ -7,7 +7,8 @@ use curve25519_dalek::edwards::{EdwardsPoint, CompressedEdwardsY};
|
|||
use serde::{Serialize, Deserialize, de::DeserializeOwned};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use reqwest;
|
||||
use digest_auth::AuthContext;
|
||||
use reqwest::{Client, RequestBuilder};
|
||||
|
||||
use crate::{
|
||||
Protocol,
|
||||
|
@ -72,11 +73,47 @@ fn rpc_point(point: &str) -> Result<EdwardsPoint, RpcError> {
|
|||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Rpc(String);
|
||||
pub struct Rpc {
|
||||
client: Client,
|
||||
userpass: Option<(String, String)>,
|
||||
url: String,
|
||||
}
|
||||
|
||||
impl Rpc {
|
||||
pub fn new(daemon: String) -> Rpc {
|
||||
Rpc(daemon)
|
||||
/// Create a new RPC connection.
|
||||
/// 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.
|
||||
|
@ -87,8 +124,7 @@ impl Rpc {
|
|||
method: &str,
|
||||
params: Option<Params>,
|
||||
) -> Result<Response, RpcError> {
|
||||
let client = reqwest::Client::new();
|
||||
let mut builder = client.post(self.0.clone() + "/" + method);
|
||||
let mut builder = self.client.post(self.url.clone() + "/" + method);
|
||||
if let Some(params) = params.as_ref() {
|
||||
builder = builder.json(params);
|
||||
}
|
||||
|
@ -115,16 +151,35 @@ impl Rpc {
|
|||
method: &str,
|
||||
params: Vec<u8>,
|
||||
) -> Result<Response, RpcError> {
|
||||
let client = reqwest::Client::new();
|
||||
let builder = client.post(self.0.clone() + "/" + method).body(params);
|
||||
let builder = self.client.post(self.url.clone() + "/" + method).body(params.clone());
|
||||
self.call_tail(method, builder.header("Content-Type", "application/octet-stream")).await
|
||||
}
|
||||
|
||||
async fn call_tail<Response: DeserializeOwned + Debug>(
|
||||
&self,
|
||||
method: &str,
|
||||
builder: reqwest::RequestBuilder,
|
||||
mut builder: RequestBuilder,
|
||||
) -> 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)?;
|
||||
|
||||
Ok(if !method.ends_with(".bin") {
|
||||
|
|
|
@ -11,7 +11,7 @@ use monero_serai::{
|
|||
};
|
||||
|
||||
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
|
||||
if rpc.get_height().await.unwrap() != 1 {
|
||||
|
|
|
@ -70,7 +70,7 @@ pub struct Monero {
|
|||
|
||||
impl 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 {
|
||||
|
|
Loading…
Reference in a new issue