rostrum/rpc/
server.rs

1use crate::chaindef::BlockHash;
2use crate::def::{PROTOCOL_VERSION_MAX, PROTOCOL_VERSION_MIN};
3use crate::discovery::discoverer::PeerDiscoverer;
4use crate::discovery::peer::{CandidatePeer, ServerPeer};
5use crate::errors::rpc_invalid_params;
6use crate::query::Query;
7use crate::rpc::parseutil::{parse_version_str, str_from_value};
8use anyhow::{Context, Result};
9use ip_rfc;
10use serde_json::Value;
11use std::net::ToSocketAddrs;
12use std::sync::Arc;
13
14use version_compare::Version;
15
16use super::parseutil::hash_from_value;
17
18/**
19 * This file contains implementations and tests of RPC calls starting with
20 * server.*
21 */
22
23// The default argument to server.version
24const SPEC_DEFAULT_VERSION: &str = "1.4";
25// Limit how many peer server candidates that can be provided to us per add_peer request
26const MAX_CANDIDATES_PER_REQ: usize = 10;
27
28pub struct ServerRPC {
29    server_donation_address: Option<String>,
30}
31
32/**
33 * Implements `server.*` RPC calls
34 */
35impl ServerRPC {
36    pub fn new(server_donation_address: Option<String>) -> ServerRPC {
37        ServerRPC {
38            server_donation_address,
39        }
40    }
41
42    /**
43     * Return a server donation address.
44     */
45    pub fn server_donation_address(&self) -> Result<Value> {
46        Ok(json!(self.server_donation_address))
47    }
48}
49
50fn best_match(client_min: &Version, client_max: &Version) -> String {
51    let our_min = Version::from(PROTOCOL_VERSION_MIN).unwrap();
52    let our_max = Version::from(PROTOCOL_VERSION_MAX).unwrap();
53
54    if *client_max >= our_max {
55        return our_max.as_str().into();
56    }
57
58    if *client_max <= our_min {
59        return our_min.as_str().into();
60    }
61
62    if *client_min >= our_min && *client_max <= our_max {
63        return client_max.as_str().into();
64    }
65
66    our_min.as_str().into()
67}
68
69fn best_match_response(version: &str, client_min: &Version, client_max: &Version) -> Value {
70    json!([version, best_match(client_min, client_max)])
71}
72
73pub fn parse_version(version: &str) -> Result<Version> {
74    let version =
75        Version::from(version).context(rpc_invalid_params("invalid version string".to_string()))?;
76    Ok(version)
77}
78
79pub fn server_version(params: &[Value], server_version: &str) -> Result<Value> {
80    // default to spec default on missing argument
81    let default_version = json!(SPEC_DEFAULT_VERSION);
82    let val = params.get(1).unwrap_or(&default_version);
83
84    if let Ok(versionstr) = str_from_value(Some(val), "version") {
85        let version = parse_version(&versionstr)?;
86        return Ok(best_match_response(server_version, &version, &version));
87    }
88
89    if let Some(minmax_list) = val.as_array() {
90        let min = str_from_value(Some(&minmax_list[0]), "version")?;
91        let min = parse_version(&min)?;
92        let max = str_from_value(Some(&minmax_list[1]), "version")?;
93        let max = parse_version(&max)?;
94        return Ok(best_match_response(server_version, &min, &max));
95    }
96
97    Err(rpc_invalid_params(
98        "invalid value in version argument".to_string(),
99    ))
100}
101
102pub async fn server_banner(query: &Arc<Query>) -> Result<Value> {
103    Ok(json!(query.get_banner().await?))
104}
105
106pub async fn server_peers_subscribe(discoverer: &PeerDiscoverer) -> Value {
107    json!(discoverer
108        .get_peers()
109        .await
110        .iter()
111        .map(|p| {
112            let ip = p.ip().to_string();
113            let host = p.hostname().unwrap_or(ip.clone());
114
115            let mut properties: Vec<String> = Vec::default();
116            if let Some(port) = p.tcp_port() {
117                properties.push(format!("t{}", port));
118            }
119            if let Some(port) = p.ssl_port() {
120                properties.push(format!("s{}", port));
121            }
122            properties.push(format!("v{}", p.version()));
123            json!(vec![json!(ip), json!(host), json!(properties)])
124        })
125        .collect::<Vec<Value>>())
126}
127
128pub fn server_features(query: &Arc<Query>) -> Result<Value> {
129    Ok(json!(query.announcer().server_features()))
130}
131
132pub async fn server_add_peer(
133    params: &[Value],
134    addr: &std::net::SocketAddr,
135    genesis_hash: &BlockHash,
136    discoverer: &PeerDiscoverer,
137) -> Result<Value> {
138    debug!("Add peer attempt by server peer {addr}");
139
140    // Basic validation
141    if !ip_rfc::global(&addr.ip()) {
142        debug!("Rejecting server peer '{addr}': Peer does not have a globally routable IP.");
143        return Ok(json!(false));
144    }
145
146    let peer_features = params.first().context("argument missing")?;
147    let peer_hosts = peer_features.get("hosts").unwrap_or(&Value::Null);
148    if peer_hosts.is_null() {
149        // Empty hosts list means that the peer does NOT want to peer up.
150        debug!("Rejecting server peer '{addr}': No hosts provided");
151        return Ok(json!(false));
152    }
153
154    let peer_genesis: BlockHash = hash_from_value(peer_features.get("genesis_hash"))?;
155    if peer_genesis != *genesis_hash {
156        bail!("Genesis mismatch {peer_genesis} != {genesis_hash}");
157    }
158
159    let peer_version = peer_features
160        .get("protocol_max")
161        .context("No protocol version provided")?
162        .as_str()
163        .context("Protocol version not a string")?;
164
165    let peer_version = parse_version_str(peer_version).context("invalid version")?;
166
167    // Validate hosts and add as candidate
168    for (i, (claimed_hostname, ports)) in peer_hosts
169        .as_object()
170        .context("Unable to parse hosts")?
171        .into_iter()
172        .enumerate()
173    {
174        if i >= MAX_CANDIDATES_PER_REQ {
175            break;
176        }
177        let tcp_port = match ports.get("tcp_port").and_then(|p| p.as_u64()) {
178            Some(p) => {
179                if p > u16::MAX as u64 {
180                    bail!("Invalid TCP port {p}");
181                }
182                Some(p as u16)
183            }
184            None => None,
185        };
186
187        let ssl_port = match ports.get("ssl_port").and_then(|p| p.as_u64()) {
188            Some(p) => {
189                if p > u16::MAX as u64 {
190                    bail!("Invalid SSL port {p}");
191                }
192                Some(p as u16)
193            }
194            None => None,
195        };
196
197        if tcp_port.is_none() && ssl_port.is_none() {
198            bail!("TCP port or SSL port not provided");
199        }
200
201        let resolve_test = format!(
202            "{claimed_hostname}:{}",
203            tcp_port.unwrap_or_else(|| ssl_port.unwrap())
204        );
205        let hostname = match resolve_test.to_socket_addrs() {
206            Ok(resolves) => {
207                // Verify that the hostname resolves to the IP address the peer connected from,
208                // otherwise ignore hostname. Could be a working server with a misconfigured hostname.
209                if !resolves
210                    .into_iter()
211                    .any(|host_addr| host_addr.ip() == addr.ip())
212                {
213                    trace!("Hostname '{claimed_hostname}' does not resolve to the address peer is connected from");
214                    None
215                } else {
216                    Some(claimed_hostname)
217                }
218            }
219            Err(e) => {
220                trace!("Hostname '{claimed_hostname}' resolve error: {e}");
221                None
222            }
223        };
224
225        discoverer
226            .add_candidate(CandidatePeer {
227                tcp_port: tcp_port as Option<u16>,
228                ssl_port,
229                hostname: hostname.cloned(),
230                ip: addr.ip(),
231                version: Some(peer_version.clone()),
232            })
233            .await?;
234    }
235
236    Ok(json!(true))
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    #[test]
244    fn test_server_version_noarg() {
245        let resp = server_version(&[], "dummy server").unwrap();
246        let resp = resp.as_array().unwrap();
247
248        assert!(resp[0].is_string());
249        assert_eq!(resp[1].as_str().unwrap(), SPEC_DEFAULT_VERSION);
250    }
251
252    #[test]
253    fn test_server_version_strarg() {
254        let clientver = json!("bestclient 1.0");
255        let resp = server_version(&[clientver.clone(), json!("1.3")], "dummy server").unwrap();
256        assert_eq!(resp[1].as_str().unwrap(), PROTOCOL_VERSION_MIN);
257        let resp = server_version(&[clientver.clone(), json!("13.3.7")], "dummy server").unwrap();
258        assert_eq!(resp[1].as_str().unwrap(), PROTOCOL_VERSION_MAX);
259    }
260
261    #[test]
262    fn test_server_version_minmax() {
263        let clientver = json!("bestclient 1.0");
264        // client max is higher than our max, we should return our max
265        let resp = server_version(
266            &[clientver.clone(), json!(["1.4", "13.3.7"])],
267            "dummy server",
268        )
269        .unwrap();
270        assert_eq!(resp[1].as_str().unwrap(), PROTOCOL_VERSION_MAX);
271
272        // client max is lower than our min, we shoud return our min
273        let resp =
274            server_version(&[clientver.clone(), json!(["1.2", "1.3"])], "dummy server").unwrap();
275        assert_eq!(resp[1].as_str().unwrap(), PROTOCOL_VERSION_MIN);
276
277        // client max is somewhere between our max and min, return same version
278        let client_max = "1.4.1";
279        let resp = server_version(
280            &[clientver.clone(), json!([PROTOCOL_VERSION_MIN, client_max])],
281            "dummy server",
282        )
283        .unwrap();
284        assert_eq!(resp[1].as_str().unwrap(), client_max);
285    }
286
287    #[test]
288    fn test_donation_address() {
289        let rpc = ServerRPC::new(None);
290        let result: Option<String> = None;
291        assert_eq!(rpc.server_donation_address().unwrap(), json!(result));
292        let rpc = ServerRPC::new(Some(
293            "bitcoincash:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqu08dsyxz98whc".to_string(),
294        ));
295        assert_eq!(
296            rpc.server_donation_address().unwrap(),
297            json!("bitcoincash:qqqqqqqqqqqqqqqqqqqqqqqqqqqqqu08dsyxz98whc")
298        );
299    }
300}