Merge origin/main: resolve conflicts and add 10s timeout to welist_http_client
This commit is contained in:
@@ -26,7 +26,9 @@ use openssl::pkey::PKey;
|
||||
use openssl::sign::Signer;
|
||||
use reqwest::Client as rClient;
|
||||
use std::fs;
|
||||
use std::net::SocketAddr;
|
||||
use std::time::Instant;
|
||||
use url::Url;
|
||||
|
||||
const LOCKTIME_THRESHOLD: i64 = 5000000;
|
||||
const VERSION: &str = "0.0.2";
|
||||
@@ -331,7 +333,9 @@ async fn main_result(cfg: &MyConfig, network_params: &NetworkParams) -> Result<(
|
||||
if let Err(e) = send_stats_report(cfg, bcinfo).await {
|
||||
error!("send_stats_report failed: {}", e);
|
||||
}
|
||||
let _ = calculate_stats(&db, network_params.db_field.clone()).await;
|
||||
if let Err(e) = calculate_stats(&db, network_params.db_field.clone()).await {
|
||||
warn!("calculate_stats failed: {e}");
|
||||
}
|
||||
}
|
||||
Err(erx) => {
|
||||
error!("impossible to get client: {}, retrying on next block", erx);
|
||||
@@ -421,6 +425,84 @@ ON CONFLICT(chain) DO UPDATE SET
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
/// Parse the `(host, port)` pair from a base URL like `https://host[:port]`.
|
||||
///
|
||||
/// Falls back to the scheme's well-known default port (443 for `https`,
|
||||
/// 80 for plain `http`), or to 443 when the scheme is unknown.
|
||||
fn parse_host_port(base_url: &str) -> Option<(String, u16)> {
|
||||
let url = Url::parse(base_url).ok()?;
|
||||
let host = url
|
||||
.host_str()?
|
||||
.trim_start_matches('[')
|
||||
.trim_end_matches(']')
|
||||
.to_string();
|
||||
let port = url.port_or_known_default().unwrap_or(443);
|
||||
Some((host, port))
|
||||
}
|
||||
|
||||
/// Resolve `host:port` and return the first IPv6 (AAAA) address, if any.
|
||||
///
|
||||
/// Returns `None` when the host has no IPv6 address.
|
||||
async fn resolve_first_ipv6(host: &str, port: u16) -> Option<SocketAddr> {
|
||||
use std::net::ToSocketAddrs;
|
||||
let host = host.to_string();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
format!("{}:{}", host, port)
|
||||
.to_socket_addrs()
|
||||
.ok()
|
||||
.and_then(|mut addrs| addrs.find(|a| a.is_ipv6()))
|
||||
})
|
||||
.await
|
||||
.ok()
|
||||
.flatten()
|
||||
}
|
||||
|
||||
/// Build the HTTP client used for welist reports.
|
||||
///
|
||||
/// When `BAL_PUSHER_PREFER_IPV6` is truthy, the welist host is resolved and
|
||||
/// the client is pinned to its first IPv6 address (the original hostname is
|
||||
/// still used for the `Host` header and TLS SNI). This works around networks
|
||||
/// where the IPv4 route to the welist host is broken while IPv6 works: the
|
||||
/// default connector may otherwise pick the broken family and the request
|
||||
/// stalls. When the variable is unset (the default), behavior is unchanged.
|
||||
async fn welist_http_client(welist_url: &str) -> rClient {
|
||||
let prefer_ipv6 = env::var("BAL_PUSHER_PREFER_IPV6")
|
||||
.unwrap_or("false".to_string())
|
||||
.parse::<bool>()
|
||||
.unwrap_or(false);
|
||||
if !prefer_ipv6 {
|
||||
return new_welist_client();
|
||||
}
|
||||
let (host, port) = match parse_host_port(welist_url) {
|
||||
Some(hp) => hp,
|
||||
None => {
|
||||
warn!("BAL_PUSHER_PREFER_IPV6: cannot parse '{welist_url}', using default resolver");
|
||||
return new_welist_client();
|
||||
}
|
||||
};
|
||||
match resolve_first_ipv6(&host, port).await {
|
||||
Some(addr) => {
|
||||
debug!("BAL_PUSHER_PREFER_IPV6: pinning {host} to {addr}");
|
||||
rClient::builder()
|
||||
.timeout(Duration::from_secs(10))
|
||||
.resolve(&host, addr)
|
||||
.build()
|
||||
.unwrap_or_else(|_| new_welist_client())
|
||||
}
|
||||
None => {
|
||||
debug!("BAL_PUSHER_PREFER_IPV6: no IPv6 address for {host}, using default resolver");
|
||||
new_welist_client()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn new_welist_client() -> rClient {
|
||||
rClient::builder()
|
||||
.timeout(Duration::from_secs(10))
|
||||
.build()
|
||||
.unwrap_or_else(|_| rClient::new())
|
||||
}
|
||||
|
||||
async fn send_stats_report(
|
||||
cfg: &MyConfig,
|
||||
bcinfo: GetBlockchainInfoResult,
|
||||
@@ -440,10 +522,7 @@ async fn send_stats_report(
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
let client = rClient::builder()
|
||||
.timeout(Duration::from_secs(10))
|
||||
.build()
|
||||
.unwrap_or_else(|_| rClient::new());
|
||||
let client = welist_http_client(&welist_url).await;
|
||||
let url = format!("{}/ping", welist_url);
|
||||
debug!("welist url: {}", url);
|
||||
let chain = bcinfo.chain.to_string().to_lowercase();
|
||||
@@ -720,3 +799,45 @@ fn seq_to_str(seq: &[u8]) -> String {
|
||||
}
|
||||
"Unknown".to_string()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_host_port_https_default_port() {
|
||||
assert_eq!(
|
||||
parse_host_port("https://welist.bitcoin-after.life"),
|
||||
Some(("welist.bitcoin-after.life".to_string(), 443))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_host_port_explicit_port_and_path() {
|
||||
assert_eq!(
|
||||
parse_host_port("https://example.com:8443/ping"),
|
||||
Some(("example.com".to_string(), 8443))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_host_port_http_default_port() {
|
||||
assert_eq!(
|
||||
parse_host_port("http://example.com"),
|
||||
Some(("example.com".to_string(), 80))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_host_port_ipv6_literal_brackets_stripped() {
|
||||
assert_eq!(
|
||||
parse_host_port("https://[2a13:2c0::1]:443"),
|
||||
Some(("2a13:2c0::1".to_string(), 443))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_host_port_invalid_url() {
|
||||
assert_eq!(parse_host_port("not a url"), None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user