use crate::chaindef::BlockHash;
use crate::def::{PROTOCOL_VERSION_MAX, PROTOCOL_VERSION_MIN};
use crate::discovery::discoverer::PeerDiscoverer;
use crate::discovery::peer::ServerPeer;
use crate::discovery::utilparser::parse_candidates_from_features;
use crate::edition::Edition;
use crate::errors::rpc_invalid_params;
use crate::query::Query;
use crate::rpc::parseutil::str_from_value;
use anyhow::{Context, Result};
use ip_rfc;
use serde_json::Value;
use std::sync::Arc;
use version_compare::Version;
use bitcoincash::network::constants::Network;
const SPEC_DEFAULT_VERSION: &str = "1.4";
pub struct ServerRPC {
server_donation_address: Option<String>,
network: Network,
}
impl ServerRPC {
pub fn new(server_donation_address: Option<String>, network: Network) -> ServerRPC {
ServerRPC {
server_donation_address,
network,
}
}
pub fn server_donation_address(&self) -> Result<Value> {
Ok(json!(self.server_donation_address))
}
pub async fn server_add_peer(
&self,
params: &[Value],
addr: &std::net::SocketAddr,
genesis_hash: &BlockHash,
discoverer: &PeerDiscoverer,
) -> Result<Value> {
debug!("discover: Add peer attempt by server peer {addr}");
if self.network != Network::Regtest && !ip_rfc::global(&addr.ip()) {
debug!("Rejecting server peer '{addr}': Peer does not have a globally routable IP.");
return Ok(json!(false));
}
let peer_features = params.first().context("argument missing")?;
let mut candidates = match parse_candidates_from_features(
peer_features,
addr.ip(),
Some(genesis_hash),
)
.await
{
Ok(c) => c,
Err(e) => {
trace!("discover: Rejecting peer {}: {}", addr, e);
return Ok(json!(false));
}
};
let mut added = false;
for c in candidates.drain(..) {
let hostname = c.pretty_hostname();
trace!("discover: Adding candidate {hostname}");
if let Err(e) = discoverer.add_candidate(c).await {
trace!("discover: rejecting candidate {hostname}: {e}");
return Ok(json!(added));
}
added = true;
}
Ok(json!(true))
}
}
fn best_match(client_min: &Version, client_max: &Version) -> String {
let our_min = Version::from(PROTOCOL_VERSION_MIN).unwrap();
let our_max = Version::from(PROTOCOL_VERSION_MAX).unwrap();
if *client_max >= our_max {
return our_max.as_str().into();
}
if *client_max <= our_min {
return our_min.as_str().into();
}
if *client_min >= our_min && *client_max <= our_max {
return client_max.as_str().into();
}
our_min.as_str().into()
}
fn best_match_response(version: &str, client_min: &Version, client_max: &Version) -> Value {
json!([version, best_match(client_min, client_max)])
}
pub fn parse_version(version: &str) -> Result<Version<'_>> {
let version =
Version::from(version).context(rpc_invalid_params("invalid version string".to_string()))?;
Ok(version)
}
pub fn parse_edition(params: &[Value]) -> Result<Edition> {
if let Some(edition_val) = params.get(2) {
if let Ok(edition_str) = str_from_value(Some(edition_val), "edition") {
return Edition::parse(&edition_str).ok_or_else(|| {
rpc_invalid_params(format!("unsupported edition: {}", edition_str))
});
}
}
Ok(Edition::Original)
}
pub fn server_version(params: &[Value], server_version: &str) -> (Edition, Result<Value>) {
let edition = match parse_edition(params) {
Ok(e) => e,
Err(e) => return (Edition::Original, Err(e)),
};
let default_version = json!(SPEC_DEFAULT_VERSION);
let val = params.get(1).unwrap_or(&default_version);
if let Ok(versionstr) = str_from_value(Some(val), "version") {
let version = match parse_version(&versionstr) {
Ok(v) => v,
Err(e) => return (edition, Err(e)),
};
return (
edition,
Ok(best_match_response(server_version, &version, &version)),
);
}
if let Some(minmax_list) = val.as_array() {
let min = match str_from_value(Some(&minmax_list[0]), "version") {
Ok(v) => v,
Err(e) => return (edition, Err(e)),
};
let min = match parse_version(&min) {
Ok(v) => v,
Err(e) => return (edition, Err(e)),
};
let max = match str_from_value(Some(&minmax_list[1]), "version") {
Ok(v) => v,
Err(e) => return (edition, Err(e)),
};
let max = match parse_version(&max) {
Ok(v) => v,
Err(e) => return (edition, Err(e)),
};
return (edition, Ok(best_match_response(server_version, &min, &max)));
}
(
edition,
Err(rpc_invalid_params(
"invalid value in version argument".to_string(),
)),
)
}
pub async fn server_banner(query: &Arc<Query>) -> Result<Value> {
Ok(json!(query.get_banner().await?))
}
pub async fn server_peers_subscribe(discoverer: &PeerDiscoverer) -> Value {
json!(discoverer
.get_peers()
.await
.iter()
.map(|p| {
let ip = p.ip().to_string();
let host = p.hostname().unwrap_or(ip.clone());
let mut properties: Vec<String> = Vec::default();
if let Some(port) = p.tcp_port() {
properties.push(format!("t{}", port));
}
if let Some(port) = p.ssl_port() {
properties.push(format!("s{}", port));
}
properties.push(format!("v{}", p.version()));
json!(vec![json!(ip), json!(host), json!(properties)])
})
.collect::<Vec<Value>>())
}
pub fn server_features(query: &Arc<Query>) -> Result<Value> {
Ok(json!(query.announcer().server_features()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_edition_missing() {
let params: &[Value] = &[];
let result = parse_edition(params).unwrap();
assert_eq!(result, Edition::Original);
}
#[test]
fn test_parse_edition_null() {
let params = [json!(null)];
let result = parse_edition(params.as_slice()).unwrap();
assert_eq!(result, Edition::Original);
}
#[test]
fn test_parse_edition_valid() {
let p1 = json!("dummy");
let p2 = json!("dummy");
let p3 = json!("2027-pre");
let arr: Vec<Value> = vec![p1, p2, p3];
let result = parse_edition(&arr).unwrap();
assert_eq!(result, Edition::Edition2027Pre);
let p1 = json!("dummy");
let p2 = json!("dummy");
let p3 = json!("original");
let arr: Vec<Value> = vec![p1, p2, p3];
let result = parse_edition(&arr).unwrap();
assert_eq!(result, Edition::Original);
}
#[test]
fn test_parse_edition_invalid() {
let p1 = json!("dummy");
let p2 = json!("dummy");
let p3 = json!("invalid-edition");
let arr: Vec<Value> = vec![p1, p2, p3];
let result = parse_edition(&arr);
assert!(result.is_err());
}
#[test]
fn test_server_version_edition_not_set_on_error() {
let (edition, result) = server_version(
&[json!("client"), json!(42), json!("2027-pre")],
"dummy server",
);
assert!(result.is_err());
assert_eq!(edition, Edition::Edition2027Pre);
}
#[test]
fn test_server_version_noarg() {
let (_, resp) = server_version(&[], "dummy server");
let resp = resp.unwrap();
let resp = resp.as_array().unwrap();
assert!(resp[0].is_string());
assert_eq!(resp[1].as_str().unwrap(), SPEC_DEFAULT_VERSION);
}
#[test]
fn test_server_version_strarg() {
let clientver = json!("bestclient 1.0");
let (_, resp) = server_version(&[clientver.clone(), json!("1.3")], "dummy server");
let resp = resp.unwrap();
assert_eq!(resp[1].as_str().unwrap(), PROTOCOL_VERSION_MIN);
let (_, resp) = server_version(&[clientver.clone(), json!("13.3.7")], "dummy server");
let resp = resp.unwrap();
assert_eq!(resp[1].as_str().unwrap(), PROTOCOL_VERSION_MAX);
}
#[test]
fn test_server_version_minmax() {
let clientver = json!("bestclient 1.0");
let (_, resp) = server_version(
&[clientver.clone(), json!(["1.4", "13.3.7"])],
"dummy server",
);
let resp = resp.unwrap();
assert_eq!(resp[1].as_str().unwrap(), PROTOCOL_VERSION_MAX);
let (_, resp) = server_version(&[clientver.clone(), json!(["1.2", "1.3"])], "dummy server");
let resp = resp.unwrap();
assert_eq!(resp[1].as_str().unwrap(), PROTOCOL_VERSION_MIN);
let client_max = "1.4.1";
let (_, resp) = server_version(
&[clientver.clone(), json!([PROTOCOL_VERSION_MIN, client_max])],
"dummy server",
);
let resp = resp.unwrap();
assert_eq!(resp[1].as_str().unwrap(), client_max);
}
#[test]
fn test_donation_address() {
let rpc = ServerRPC::new(None, Network::Bitcoin);
let result: Option<String> = None;
assert_eq!(rpc.server_donation_address().unwrap(), json!(result));
let rpc = ServerRPC::new(
Some("bitcoincash:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqu08dsyxz98whc".to_string()),
Network::Bitcoin,
);
assert_eq!(
rpc.server_donation_address().unwrap(),
json!("bitcoincash:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqu08dsyxz98whc")
);
}
}