Add a String to Monero ConnectionErrors debugging the issue

We're reaching this in CI so there must be some issue present.
This commit is contained in:
Luke Parker
2023-11-03 05:45:31 -04:00
parent a2089c61fb
commit 4c9e3b085b
4 changed files with 45 additions and 28 deletions

View File

@@ -26,8 +26,8 @@ mod binaries {
let hash = loop { let hash = loop {
match rpc.get_block_hash(block_i).await { match rpc.get_block_hash(block_i).await {
Ok(hash) => break hash, Ok(hash) => break hash,
Err(RpcError::ConnectionError) => { Err(RpcError::ConnectionError(e)) => {
println!("get_block_hash ConnectionError"); println!("get_block_hash ConnectionError: {e}");
continue; continue;
} }
Err(e) => panic!("couldn't get block {block_i}'s hash: {e:?}"), Err(e) => panic!("couldn't get block {block_i}'s hash: {e:?}"),
@@ -42,8 +42,8 @@ mod binaries {
let res: BlockResponse = loop { let res: BlockResponse = loop {
match rpc.json_rpc_call("get_block", Some(json!({ "hash": hex::encode(hash) }))).await { match rpc.json_rpc_call("get_block", Some(json!({ "hash": hex::encode(hash) }))).await {
Ok(res) => break res, Ok(res) => break res,
Err(RpcError::ConnectionError) => { Err(RpcError::ConnectionError(e)) => {
println!("get_block ConnectionError"); println!("get_block ConnectionError: {e}");
continue; continue;
} }
Err(e) => panic!("couldn't get block {block_i} via block.hash(): {e:?}"), Err(e) => panic!("couldn't get block {block_i} via block.hash(): {e:?}"),
@@ -85,8 +85,8 @@ mod binaries {
.await .await
{ {
Ok(txs) => break txs, Ok(txs) => break txs,
Err(RpcError::ConnectionError) => { Err(RpcError::ConnectionError(e)) => {
println!("get_transactions ConnectionError"); println!("get_transactions ConnectionError: {e}");
continue; continue;
} }
Err(e) => panic!("couldn't call get_transactions: {e:?}"), Err(e) => panic!("couldn't call get_transactions: {e:?}"),
@@ -190,8 +190,8 @@ mod binaries {
.await .await
{ {
Ok(outs) => break outs, Ok(outs) => break outs,
Err(RpcError::ConnectionError) => { Err(RpcError::ConnectionError(e)) => {
println!("get_outs ConnectionError"); println!("get_outs ConnectionError: {e}");
continue; continue;
} }
Err(e) => panic!("couldn't connect to RPC to get outs: {e:?}"), Err(e) => panic!("couldn't connect to RPC to get outs: {e:?}"),

View File

@@ -1,7 +1,11 @@
use core::str::FromStr;
use async_trait::async_trait; use async_trait::async_trait;
use digest_auth::AuthContext; use digest_auth::AuthContext;
use hyper::{header::HeaderValue, Request, service::Service, client::connect::HttpConnector, Client}; use hyper::{
Uri, header::HeaderValue, Request, service::Service, client::connect::HttpConnector, Client,
};
use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder};
use crate::rpc::{RpcError, RpcConnection, Rpc}; use crate::rpc::{RpcError, RpcConnection, Rpc};
@@ -41,7 +45,7 @@ impl HttpRpc {
let url_clone = url; let url_clone = url;
let split_url = url_clone.split('@').collect::<Vec<_>>(); let split_url = url_clone.split('@').collect::<Vec<_>>();
if split_url.len() != 2 { if split_url.len() != 2 {
Err(RpcError::ConnectionError)?; Err(RpcError::ConnectionError("invalid amount of login specifications".to_string()))?;
} }
let mut userpass = split_url[0]; let mut userpass = split_url[0];
url = split_url[1].to_string(); url = split_url[1].to_string();
@@ -50,20 +54,20 @@ impl HttpRpc {
if userpass.contains("://") { if userpass.contains("://") {
let split_userpass = userpass.split("://").collect::<Vec<_>>(); let split_userpass = userpass.split("://").collect::<Vec<_>>();
if split_userpass.len() != 2 { if split_userpass.len() != 2 {
Err(RpcError::ConnectionError)?; Err(RpcError::ConnectionError("invalid amount of protocol specifications".to_string()))?;
} }
url = split_userpass[0].to_string() + "://" + &url; url = split_userpass[0].to_string() + "://" + &url;
userpass = split_userpass[1]; userpass = split_userpass[1];
} }
let split_userpass = userpass.split(':').collect::<Vec<_>>(); let split_userpass = userpass.split(':').collect::<Vec<_>>();
if split_userpass.len() != 2 { if split_userpass.len() > 2 {
Err(RpcError::ConnectionError)?; Err(RpcError::ConnectionError("invalid amount of passwords".to_string()))?;
} }
Authentication::Authenticated( Authentication::Authenticated(
https_builder, https_builder,
split_userpass[0].to_string(), split_userpass[0].to_string(),
split_userpass[1].to_string(), split_userpass.get(1).unwrap_or(&"").to_string(),
) )
} else { } else {
Authentication::Unauthenticated(Client::builder().build(https_builder)) Authentication::Unauthenticated(Client::builder().build(https_builder))
@@ -93,23 +97,28 @@ impl HttpRpc {
Authentication::Unauthenticated(client) => client Authentication::Unauthenticated(client) => client
.request(request(self.url.clone() + "/" + route)) .request(request(self.url.clone() + "/" + route))
.await .await
.map_err(|_| RpcError::ConnectionError)?, .map_err(|e| RpcError::ConnectionError(e.to_string()))?,
Authentication::Authenticated(https_builder, user, pass) => { Authentication::Authenticated(https_builder, user, pass) => {
let connection = https_builder let connection = https_builder
.clone() .clone()
.call(self.url.parse().map_err(|_| RpcError::ConnectionError)?) .call(
self
.url
.parse()
.map_err(|e: <Uri as FromStr>::Err| RpcError::ConnectionError(e.to_string()))?,
)
.await .await
.map_err(|_| RpcError::ConnectionError)?; .map_err(|e| RpcError::ConnectionError(e.to_string()))?;
let (mut requester, connection) = hyper::client::conn::http1::handshake(connection) let (mut requester, connection) = hyper::client::conn::http1::handshake(connection)
.await .await
.map_err(|_| RpcError::ConnectionError)?; .map_err(|e| RpcError::ConnectionError(e.to_string()))?;
let connection_task = tokio::spawn(connection); let connection_task = tokio::spawn(connection);
connection_task_handle = Some(connection_task.abort_handle()); connection_task_handle = Some(connection_task.abort_handle());
let mut response = requester let mut response = requester
.send_request(request("/".to_string() + route)) .send_request(request("/".to_string() + route))
.await .await
.map_err(|_| RpcError::ConnectionError)?; .map_err(|e| RpcError::ConnectionError(e.to_string()))?;
// Only provide authentication if this daemon actually expects it // Only provide authentication if this daemon actually expects it
if let Some(header) = response.headers().get("www-authenticate") { if let Some(header) = response.headers().get("www-authenticate") {
let mut request = request("/".to_string() + route); let mut request = request("/".to_string() + route);
@@ -135,11 +144,13 @@ impl HttpRpc {
); );
// Wait for the connection to be ready again // Wait for the connection to be ready again
requester.ready().await.map_err(|_| RpcError::ConnectionError)?; requester.ready().await.map_err(|e| RpcError::ConnectionError(e.to_string()))?;
// Make the request with the response challenge // Make the request with the response challenge
response = response = requester
requester.send_request(request).await.map_err(|_| RpcError::ConnectionError)?; .send_request(request)
.await
.map_err(|e| RpcError::ConnectionError(e.to_string()))?;
} }
response response
@@ -164,13 +175,13 @@ impl HttpRpc {
let mut body = response.into_body(); let mut body = response.into_body();
while res.len() < length { while res.len() < length {
let Some(data) = body.data().await else { break }; let Some(data) = body.data().await else { break };
res.extend(data.map_err(|_| RpcError::ConnectionError)?.as_ref()); res.extend(data.map_err(|e| RpcError::ConnectionError(e.to_string()))?.as_ref());
} }
*/ */
let res = hyper::body::to_bytes(response.into_body()) let res = hyper::body::to_bytes(response.into_body())
.await .await
.map_err(|_| RpcError::ConnectionError)? .map_err(|e| RpcError::ConnectionError(e.to_string()))?
.to_vec(); .to_vec();
if let Some(connection_task) = connection_task_handle { if let Some(connection_task) = connection_task_handle {
@@ -188,6 +199,6 @@ impl RpcConnection for HttpRpc {
// TODO: Make this timeout configurable // TODO: Make this timeout configurable
tokio::time::timeout(core::time::Duration::from_secs(30), self.inner_post(route, body)) tokio::time::timeout(core::time::Duration::from_secs(30), self.inner_post(route, body))
.await .await
.map_err(|_| RpcError::ConnectionError)? .map_err(|e| RpcError::ConnectionError(e.to_string()))?
} }
} }

View File

@@ -57,8 +57,8 @@ struct TransactionsResponse {
pub enum RpcError { pub enum RpcError {
#[cfg_attr(feature = "std", error("internal error ({0})"))] #[cfg_attr(feature = "std", error("internal error ({0})"))]
InternalError(&'static str), InternalError(&'static str),
#[cfg_attr(feature = "std", error("connection error"))] #[cfg_attr(feature = "std", error("connection error ({0})"))]
ConnectionError, ConnectionError(String),
#[cfg_attr(feature = "std", error("invalid node ({0})"))] #[cfg_attr(feature = "std", error("invalid node ({0})"))]
InvalidNode(&'static str), InvalidNode(&'static str),
#[cfg_attr(feature = "std", error("unsupported protocol version ({0})"))] #[cfg_attr(feature = "std", error("unsupported protocol version ({0})"))]

View File

@@ -182,6 +182,9 @@ impl PartialEq for Monero {
impl Eq for Monero {} impl Eq for Monero {}
fn map_rpc_err(err: RpcError) -> NetworkError { fn map_rpc_err(err: RpcError) -> NetworkError {
if let RpcError::ConnectionError(e) = &err {
log::debug!("Monero ConnectionError: {e}");
}
if let RpcError::InvalidNode(reason) = &err { if let RpcError::InvalidNode(reason) = &err {
log::error!("Monero RpcError::InvalidNode({reason})"); log::error!("Monero RpcError::InvalidNode({reason})");
} }
@@ -599,7 +602,10 @@ impl Network for Monero {
async fn publish_transaction(&self, tx: &Self::Transaction) -> Result<(), NetworkError> { async fn publish_transaction(&self, tx: &Self::Transaction) -> Result<(), NetworkError> {
match self.rpc.publish_transaction(tx).await { match self.rpc.publish_transaction(tx).await {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(RpcError::ConnectionError) => Err(NetworkError::ConnectionError)?, Err(RpcError::ConnectionError(e)) => {
log::debug!("Monero ConnectionError: {e}");
Err(NetworkError::ConnectionError)?
}
// TODO: Distinguish already in pool vs double spend (other signing attempt succeeded) vs // TODO: Distinguish already in pool vs double spend (other signing attempt succeeded) vs
// invalid transaction // invalid transaction
Err(e) => panic!("failed to publish TX {}: {e}", hex::encode(tx.hash())), Err(e) => panic!("failed to publish TX {}: {e}", hex::encode(tx.hash())),