Просмотр исходного кода

rpc: Add support for multiple listeners per server

x 4 дней назад
Родитель
Сommit
f5e0d154b7

+ 12 - 12
bin/darkfid/darkfid_config.toml

@@ -41,7 +41,7 @@ skip_fees = false
 ## Testnet JSON-RPC settings
 [network_config."testnet".rpc]
 # JSON-RPC listen URL
-rpc_listen = "tcp://127.0.0.1:18345"
+rpc_listen = ["tcp://127.0.0.1:18345"]
 
 # Disabled RPC methods
 #rpc_disabled_methods = []
@@ -49,7 +49,7 @@ rpc_listen = "tcp://127.0.0.1:18345"
 ## Testnet management JSON-RPC settings
 [network_config."testnet".management_rpc]
 # JSON-RPC listen URL
-rpc_listen = "tcp://127.0.0.1:18346"
+rpc_listen = ["tcp://127.0.0.1:18346"]
 
 # Disabled RPC methods
 #rpc_disabled_methods = []
@@ -57,7 +57,7 @@ rpc_listen = "tcp://127.0.0.1:18346"
 ## Testnet JSON-RPC settings for stratum mining requests (optional)
 #[network_config."testnet".stratum_rpc]
 # JSON-RPC listen URL (stratum mining)
-#rpc_listen = "tcp://127.0.0.1:18347"
+#rpc_listen = ["tcp://127.0.0.1:18347"]
 
 # Disabled RPC methods (stratum mining)
 #rpc_disabled_methods = []
@@ -65,7 +65,7 @@ rpc_listen = "tcp://127.0.0.1:18346"
 ## Testnet JSON-RPC settings for p2pool merge mining requests (optional)
 #[network_config."testnet".mm_rpc]
 # JSON-RPC listen URL (merge mining)
-#rpc_listen = "http+tcp://127.0.0.1:18348"
+#rpc_listen = ["http+tcp://127.0.0.1:18348"]
 
 # Disabled RPC methods (merge mining)
 #rpc_disabled_methods = []
@@ -236,7 +236,7 @@ skip_fees = false
 ## Mainnet JSON-RPC settings
 [network_config."mainnet".rpc]
 # JSON-RPC listen URL
-rpc_listen = "tcp://127.0.0.1:8345"
+rpc_listen = ["tcp://127.0.0.1:8345"]
 
 # Disabled RPC methods
 #rpc_disabled_methods = []
@@ -244,7 +244,7 @@ rpc_listen = "tcp://127.0.0.1:8345"
 ## Mainnet management JSON-RPC settings
 [network_config."mainnet".management_rpc]
 # JSON-RPC listen URL
-rpc_listen = "tcp://127.0.0.1:8346"
+rpc_listen = ["tcp://127.0.0.1:8346"]
 
 # Disabled RPC methods
 #rpc_disabled_methods = []
@@ -252,7 +252,7 @@ rpc_listen = "tcp://127.0.0.1:8346"
 ## Mainnet JSON-RPC settings for stratum mining requests (optional)
 #[network_config."mainnet".stratum_rpc]
 # JSON-RPC listen URL (stratum mining)
-#rpc_listen = "tcp://127.0.0.1:8347"
+#rpc_listen = ["tcp://127.0.0.1:8347"]
 
 # Disabled RPC methods (stratum mining)
 #rpc_disabled_methods = []
@@ -260,7 +260,7 @@ rpc_listen = "tcp://127.0.0.1:8346"
 ## Mainnet JSON-RPC settings for p2pool merge mining requests (optional)
 #[network_config."mainnet".mm_rpc]
 # JSON-RPC listen URL (merge mining)
-#rpc_listen = "http+tcp://127.0.0.1:8348"
+#rpc_listen = ["http+tcp://127.0.0.1:8348"]
 
 # Disabled RPC methods (merge mining)
 #rpc_disabled_methods = []
@@ -428,7 +428,7 @@ skip_fees = false
 ## Localnet JSON-RPC settings
 [network_config."localnet".rpc]
 # JSON-RPC listen URL
-rpc_listen = "tcp://127.0.0.1:28345"
+rpc_listen = ["tcp://127.0.0.1:28345"]
 
 # Disabled RPC methods
 #rpc_disabled_methods = []
@@ -436,7 +436,7 @@ rpc_listen = "tcp://127.0.0.1:28345"
 ## Localnet management JSON-RPC settings
 [network_config."localnet".management_rpc]
 # JSON-RPC listen URL
-rpc_listen = "tcp://127.0.0.1:28346"
+rpc_listen = ["tcp://127.0.0.1:28346"]
 
 # Disabled RPC methods
 #rpc_disabled_methods = []
@@ -444,7 +444,7 @@ rpc_listen = "tcp://127.0.0.1:28346"
 ## Localnet JSON-RPC settings for stratum mining requests (optional)
 #[network_config."localnet".stratum_rpc]
 # JSON-RPC listen URL (stratum mining)
-#rpc_listen = "tcp://127.0.0.1:28347"
+#rpc_listen = ["tcp://127.0.0.1:28347"]
 
 # Disabled RPC methods (stratum mining)
 #rpc_disabled_methods = []
@@ -452,7 +452,7 @@ rpc_listen = "tcp://127.0.0.1:28346"
 ## Localnet JSON-RPC settings for p2pool merge mining requests (optional)
 #[network_config."localnet".mm_rpc]
 # JSON-RPC listen URL (merge mining)
-#rpc_listen = "http+tcp://127.0.0.1:28348"
+#rpc_listen = ["http+tcp://127.0.0.1:28348"]
 
 # Disabled RPC methods (merge mining)
 #rpc_disabled_methods = []

+ 2 - 2
bin/darkfid/src/tests/mod.rs

@@ -280,11 +280,11 @@ fn darkfid_programmatic_control() -> Result<()> {
                     checkpoint: None,
                 };
                 let rpc_settings = RpcSettings {
-                    listen: Url::parse("tcp://127.0.0.1:18245").unwrap(),
+                    listen: vec![Url::parse("tcp://127.0.0.1:18245").unwrap()],
                     ..RpcSettings::default()
                 };
                 let management_rpc_settings = RpcSettings {
-                    listen: Url::parse("tcp://127.0.0.1:18246").unwrap(),
+                    listen: vec![Url::parse("tcp://127.0.0.1:18246").unwrap()],
                     ..RpcSettings::default()
                 };
 

+ 1 - 1
bin/darkirc/darkirc_config.toml

@@ -75,7 +75,7 @@ autojoin = [
 ## JSON-RPC settings
 [rpc]
 ## JSON-RPC listen URL
-rpc_listen = "tcp://127.0.0.1:9605"
+rpc_listen = ["tcp://127.0.0.1:9605"]
 
 ## Disabled RPC methods
 rpc_disabled_methods = ["p2p.get_info"]

+ 2 - 2
bin/fud/fud/fud_config.toml

@@ -60,7 +60,7 @@ btc_electrum_nodes = [
 # JSON-RPC settings
 [rpc]
 # JSON-RPC listen URL
-rpc_listen = "tcp://127.0.0.1:9705"
+rpc_listen = ["tcp://127.0.0.1:9705"]
 
 # Disabled RPC methods
 #rpc_disabled_methods = []
@@ -68,7 +68,7 @@ rpc_listen = "tcp://127.0.0.1:9705"
 # Management JSON-RPC settings
 [management_rpc]
 # JSON-RPC listen URL
-rpc_listen = "tcp://127.0.0.1:9706"
+rpc_listen = ["tcp://127.0.0.1:9706"]
 
 # Disabled RPC methods
 #rpc_disabled_methods = []

+ 2 - 2
bin/fud/fud/src/main.rs

@@ -117,7 +117,7 @@ async fn realmain(args: Args, ex: Arc<Executor<'static>>) -> Result<()> {
     );
 
     let rpc_settings: RpcSettings = args.rpc.into();
-    info!(target: "fud", "Starting main JSON-RPC server on {}", rpc_settings.listen);
+    info!(target: "fud", "Starting main JSON-RPC server on {:?}", rpc_settings.listen);
     let rpc_interface = Arc::new(DefaultRpcInterface::new(fud.clone(), event_sub));
     let rpc_task = StoppableTask::new();
     let rpc_interface_ = rpc_interface.clone();
@@ -138,7 +138,7 @@ async fn realmain(args: Args, ex: Arc<Executor<'static>>) -> Result<()> {
         parse_management_rpc(&get_config_path(args.config.clone(), CONFIG_FILE)?)?;
     if let Some(management_rpc_settings) = management_rpc_settings {
         let management_rpc_settings: RpcSettings = management_rpc_settings.into();
-        info!(target: "fud", "Starting management JSON-RPC server on {}", management_rpc_settings.listen);
+        info!(target: "fud", "Starting management JSON-RPC server on {:?}", management_rpc_settings.listen);
         let management_rpc_interface = Arc::new(ManagementRpcInterface::new(fud.clone(), dnet_sub));
         management_rpc_task = Some(StoppableTask::new());
         let management_rpc_interface_ = management_rpc_interface.clone();

+ 1 - 1
bin/lilith/lilith_config.toml

@@ -9,7 +9,7 @@
 ## JSON-RPC settings
 [rpc]
 # JSON-RPC listen URL
-rpc_listen = "tcp://127.0.0.1:18927"
+rpc_listen = ["tcp://127.0.0.1:18927"]
 
 # Disabled RPC methods
 #rpc_disabled_methods = []

+ 1 - 1
bin/lilith/src/main.rs

@@ -495,7 +495,7 @@ async fn realmain(args: Args, ex: Arc<Executor<'static>>) -> Result<()> {
 
     // JSON-RPC server
     let rpc_settings: RpcSettings = args.rpc.into();
-    info!(target: "lilith", "Starting JSON-RPC server on {}", rpc_settings.listen);
+    info!(target: "lilith", "Starting JSON-RPC server on {:?}", rpc_settings.listen);
     let lilith_ = lilith.clone();
     let rpc_task = StoppableTask::new();
     rpc_task.clone().start(

+ 1 - 1
bin/tau/taud/taud_config.toml

@@ -46,7 +46,7 @@ write_public_key = "7iHFdGxe1DYyBtPLVFb7fXL3Kk495WtwRWaNxhYQTU9x"
 # JSON-RPC settings
 [rpc]
 # JSON-RPC listen URL
-rpc_listen = "tcp://127.0.0.1:9805"
+rpc_listen = ["tcp://127.0.0.1:9805"]
 
 # Disabled RPC methods
 rpc_disabled_methods = ["p2p.get_info"]

+ 1 - 1
example/dchat/dchatd/dchatd_config.toml

@@ -3,7 +3,7 @@
 # JSON-RPC settings
 [rpc]
 # JSON-RPC listen URL
-rpc_listen = "tcp://127.0.0.1:51054"
+rpc_listen = ["tcp://127.0.0.1:51054"]
 
 # Disabled RPC methods
 #rpc_disabled_methods = ["p2p.get_info"]

+ 1 - 1
example/dchat/dchatd/src/main.rs

@@ -131,7 +131,7 @@ async fn realmain(args: Args, ex: Arc<smol::Executor<'static>>) -> Result<()> {
 
     // ANCHOR: rpc
     let rpc_settings: RpcSettings = args.rpc.into();
-    info!("Starting JSON-RPC server on port {}", rpc_settings.listen);
+    info!("Starting JSON-RPC server on {:?}", rpc_settings.listen);
     let msgs: DchatMsgsBuffer = Arc::new(Mutex::new(vec![DchatMsg { msg: String::new() }]));
     let rpc_connections = Mutex::new(HashSet::new());
     let dchat = Arc::new(Dchat::new(p2p.clone(), msgs.clone(), rpc_connections, dnet_sub));

+ 8 - 2
example/p2pdebug/src/main.rs

@@ -27,7 +27,7 @@ use url::Url;
 
 use darkfi::{
     cli_desc, net,
-    rpc::server::listen_and_serve,
+    rpc::{server::listen_and_serve, settings::RpcSettings},
     util::cli::{get_log_config, get_log_level},
     Result,
 };
@@ -247,7 +247,13 @@ impl MockP2p {
         let rpc_interface =
             Arc::new(rpc::JsonRpcInterface { addr: rpc_addr.clone(), p2p: p2p.clone() });
         let _ex = executor.clone();
-        executor.spawn(async move { listen_and_serve(rpc_addr, rpc_interface, _ex).await }).detach();
+        let rpc_settings =
+            RpcSettings { listen: vec![rpc_addr], ..RpcSettings::default() };
+        executor
+            .spawn(async move {
+                listen_and_serve(rpc_settings, rpc_interface, None, _ex).await
+            })
+            .detach();
 
         p2p.clone().start(executor.clone()).await?;
         p2p.run(executor).await

+ 14 - 0
src/net/transport/mod.rs

@@ -374,6 +374,20 @@ pub struct Listener {
 }
 
 impl Listener {
+    /// Instantiate and bind listeners for every provided endpoint.
+    pub async fn listen_all(
+        endpoints: impl IntoIterator<Item = Url>,
+        datastore: Option<String>,
+        require_tls_client_cert: bool,
+    ) -> io::Result<Vec<Box<dyn PtListener>>> {
+        let mut listeners = Vec::new();
+        for endpoint in endpoints {
+            let listener = Self::new(endpoint, datastore.clone(), require_tls_client_cert).await?;
+            listeners.push(listener.listen().await?);
+        }
+        Ok(listeners)
+    }
+
     /// Instantiate a new [`Listener`] with the given [`Url`] and datastore path.
     /// Must contain a scheme, host string, and a port.
     pub async fn new(

+ 39 - 21
src/rpc/server.rs

@@ -19,6 +19,7 @@
 use std::{collections::HashSet, future::Future, io::ErrorKind, sync::Arc};
 
 use async_trait::async_trait;
+use futures::future::try_join_all;
 use parking_lot::Mutex as SyncMutex;
 use smol::{
     io::{BufReader, ReadHalf, WriteHalf},
@@ -432,10 +433,14 @@ async fn run_accept_loop<'a, T: 'a>(
                 let rh_ = rh.clone();
                 verbose!(target: "rpc::server", "[RPC] Server accepted conn from {url}");
 
-                // Enforce the connection limit here, before mark_connection,
-                // so the active count never crosses the limit.
+                // Check and reserve a connection slot under the same lock so
+                // simultaneous accepts on different listeners cannot exceed
+                // the server-wide limit.
+                let task = StoppableTask::new();
+                let mut connections = rh.connections_mut().await;
                 if let Some(limit) = conn_limit {
-                    if rh.active_connections().await >= limit {
+                    if connections.len() >= limit {
+                        drop(connections);
                         debug!(
                             target: "rpc::server::run_accept_loop",
                             "[RPC] Connection limit ({limit}) reached, rejecting {url}",
@@ -465,7 +470,6 @@ async fn run_accept_loop<'a, T: 'a>(
                 let reader = Arc::new(Mutex::new(BufReader::new(reader)));
                 let writer = Arc::new(Mutex::new(writer));
 
-                let task = StoppableTask::new();
                 let task_ = task.clone();
                 let ex_ = ex.clone();
                 let tasks = Arc::new(ConnectionTasks::default());
@@ -473,7 +477,6 @@ async fn run_accept_loop<'a, T: 'a>(
 
                 // Register before starting so a connection that closes
                 // immediately cannot finish before it is tracked.
-                let mut connections = rh.connections_mut().await;
                 connections.insert(task.clone());
                 task.clone().start(
                     accept_with_tasks(
@@ -563,7 +566,7 @@ async fn run_accept_loop<'a, T: 'a>(
     }
 }
 
-/// Start a JSON-RPC server bound to the givven accept URL and use the
+/// Start a JSON-RPC server bound to the given accept URLs and use the
 /// given [`RequestHandler`] to handle incoming requests.
 ///
 /// The supported network schemes can be prefixed with `http+` to serve
@@ -574,17 +577,32 @@ pub async fn listen_and_serve<'a, T: 'a>(
     conn_limit: Option<usize>,
     ex: Arc<smol::Executor<'a>>,
 ) -> Result<()> {
-    // Figure out if we're using HTTP and rewrite the URL accordingly.
-    let mut listen_url = settings.listen.clone();
-    if settings.listen.scheme().starts_with("http+") {
-        let scheme = settings.listen.scheme().strip_prefix("http+").unwrap();
-        let url_str = settings.listen.as_str().replace(settings.listen.scheme(), scheme);
-        listen_url = url_str.parse()?;
+    let mut listen_urls = Vec::with_capacity(settings.listen.len());
+    let mut listener_settings = Vec::with_capacity(settings.listen.len());
+
+    for endpoint in &settings.listen {
+        let use_http = endpoint.scheme().starts_with("http+");
+        let mut listen_url = endpoint.clone();
+        if use_http {
+            let scheme = endpoint.scheme().strip_prefix("http+").unwrap();
+            listen_url.set_scheme(scheme).map_err(|_| Error::UrlParse(endpoint.to_string()))?;
+        }
+
+        listen_urls.push(listen_url);
+        let mut endpoint_settings = settings.clone();
+        endpoint_settings.listen = vec![endpoint.clone()];
+        listener_settings.push(endpoint_settings);
     }
 
-    let listener = Listener::new(listen_url, None, false).await?.listen().await?;
+    // Bind every socket before starting any accept loop. If one address is
+    // invalid or unavailable, no partially-running RPC server is left behind.
+    let listeners = Listener::listen_all(listen_urls, None, false).await?;
+    let accept_loops =
+        listeners.into_iter().zip(listener_settings).map(|(listener, endpoint_settings)| {
+            run_accept_loop(listener, rh.clone(), conn_limit, endpoint_settings, ex.clone())
+        });
 
-    run_accept_loop(listener, rh, conn_limit, settings, ex.clone()).await
+    try_join_all(accept_loops).await.map(|_| ())
 }
 
 #[cfg(test)]
@@ -630,7 +648,7 @@ mod tests {
             let listener = TcpListener::bind("127.0.0.1:0").await?;
             let sockaddr = listener.local_addr()?;
             let settings = RpcSettings {
-                listen: Url::parse(&format!("tcp://127.0.0.1:{}", sockaddr.port()))?,
+                listen: vec![Url::parse(&format!("tcp://127.0.0.1:{}", sockaddr.port()))?],
                 disabled_methods: vec![],
             };
             drop(listener);
@@ -660,17 +678,17 @@ mod tests {
             msleep(500).await;
 
             // Connect a client
-            let rpc_client0 = RpcClient::new(settings.listen.clone(), executor.clone()).await?;
+            let rpc_client0 = RpcClient::new(settings.listen[0].clone(), executor.clone()).await?;
             msleep(500).await;
             assert!(rpc_server.active_connections().await == 1);
 
             // Connect another client
-            let rpc_client1 = RpcClient::new(settings.listen.clone(), executor.clone()).await?;
+            let rpc_client1 = RpcClient::new(settings.listen[0].clone(), executor.clone()).await?;
             msleep(500).await;
             assert!(rpc_server.active_connections().await == 2);
 
             // And another one
-            let _rpc_client2 = RpcClient::new(settings.listen.clone(), executor.clone()).await?;
+            let _rpc_client2 = RpcClient::new(settings.listen[0].clone(), executor.clone()).await?;
             msleep(500).await;
             assert!(rpc_server.active_connections().await == 3);
 
@@ -686,7 +704,7 @@ mod tests {
 
             // The Listener should be stopped when we stop the server task.
             server_task.stop().await;
-            assert!(RpcClient::new(settings.listen, executor.clone()).await.is_err());
+            assert!(RpcClient::new(settings.listen[0].clone(), executor.clone()).await.is_err());
 
             // After the server is stopped, the connections tasks should also be stopped
             assert!(rpc_server.active_connections().await == 0);
@@ -703,7 +721,7 @@ mod tests {
             let listener = TcpListener::bind("127.0.0.1:0").await?;
             let sockaddr = listener.local_addr()?;
             let settings = RpcSettings {
-                listen: Url::parse(&format!("tcp://127.0.0.1:{}", sockaddr.port()))?,
+                listen: vec![Url::parse(&format!("tcp://127.0.0.1:{}", sockaddr.port()))?],
                 disabled_methods: vec![],
             };
             drop(listener);
@@ -733,7 +751,7 @@ mod tests {
 
             for _ in 0..32 {
                 let client =
-                    Arc::new(RpcClient::new(settings.listen.clone(), executor.clone()).await?);
+                    Arc::new(RpcClient::new(settings.listen[0].clone(), executor.clone()).await?);
                 let client_ = client.clone();
                 let subscriber_task = executor.spawn(async move {
                     client_

+ 36 - 7
src/rpc/settings.rs

@@ -16,12 +16,39 @@
  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
  */
 
+use serde::{Deserialize, Deserializer};
 use structopt::StructOpt;
 use url::Url;
 
-#[derive(Clone)]
+fn default_listen_addrs() -> Vec<Url> {
+    vec![Url::parse("tcp://127.0.0.1:22222").unwrap()]
+}
+
+/// Deserialize both the historical single URL and the preferred URL array.
+///
+/// Keeping the single-URL form working avoids invalidating existing daemon
+/// configurations while allowing RPC to use the same array syntax as P2P.
+fn deserialize_listen_addrs<'de, D>(deserializer: D) -> Result<Vec<Url>, D::Error>
+where
+    D: Deserializer<'de>,
+{
+    #[derive(serde::Deserialize)]
+    #[serde(untagged)]
+    enum ListenAddrs {
+        One(Url),
+        Many(Vec<Url>),
+    }
+
+    Ok(match ListenAddrs::deserialize(deserializer)? {
+        ListenAddrs::One(url) => vec![url],
+        ListenAddrs::Many(urls) => urls,
+    })
+}
+
+#[derive(Clone, Debug)]
 pub struct RpcSettings {
-    pub listen: Url,
+    /// RPC server listen addresses.
+    pub listen: Vec<Url>,
     pub disabled_methods: Vec<String>,
 }
 
@@ -29,14 +56,15 @@ impl RpcSettings {
     pub fn is_method_disabled(&self, method: &String) -> bool {
         self.disabled_methods.contains(method)
     }
+
     pub fn use_http(&self) -> bool {
-        self.listen.scheme().starts_with("http+")
+        self.listen.first().is_some_and(|endpoint| endpoint.scheme().starts_with("http+"))
     }
 }
 
 impl Default for RpcSettings {
     fn default() -> Self {
-        Self { listen: Url::parse("tcp://127.0.0.1:22222").unwrap(), disabled_methods: vec![] }
+        Self { listen: default_listen_addrs(), disabled_methods: vec![] }
     }
 }
 
@@ -45,9 +73,10 @@ impl Default for RpcSettings {
 #[structopt()]
 #[serde(rename = "rpc")]
 pub struct RpcSettingsOpt {
-    /// RPC server listen address
-    #[structopt(long, default_value = "tcp://127.0.0.1:22222")]
-    pub rpc_listen: Url,
+    /// RPC server listen addresses
+    #[serde(default = "default_listen_addrs", deserialize_with = "deserialize_listen_addrs")]
+    #[structopt(long, default_value = "tcp://127.0.0.1:22222", use_delimiter = true)]
+    pub rpc_listen: Vec<Url>,
 
     /// Disabled JSON-RPC methods
     #[structopt(long, use_delimiter = true)]

+ 4 - 4
tests/jsonrpc.rs

@@ -102,7 +102,7 @@ fn jsonrpc_reqrep() -> Result<()> {
         let listener = TcpListener::bind("127.0.0.1:0").await?;
         let sockaddr = listener.local_addr()?;
         let rpc_settings = RpcSettings {
-            listen: Url::parse(&format!("tcp://127.0.0.1:{}", sockaddr.port()))?,
+            listen: vec![Url::parse(&format!("tcp://127.0.0.1:{}", sockaddr.port()))?],
             ..RpcSettings::default()
         };
         drop(listener);
@@ -128,7 +128,7 @@ fn jsonrpc_reqrep() -> Result<()> {
 
         msleep(500).await;
 
-        let client = RpcClient::new(rpc_settings.listen, executor.clone()).await?;
+        let client = RpcClient::new(rpc_settings.listen[0].clone(), executor.clone()).await?;
         let req = JsonRequest::new("ping", vec![].into());
         let rep = client.request(req).await?;
 
@@ -155,7 +155,7 @@ fn http_jsonrpc_reqrep() -> Result<()> {
         let listener = TcpListener::bind("127.0.0.1:0").await?;
         let sockaddr = listener.local_addr()?;
         let rpc_settings = RpcSettings {
-            listen: Url::parse(&format!("http+tcp://127.0.0.1:{}", sockaddr.port()))?,
+            listen: vec![Url::parse(&format!("http+tcp://127.0.0.1:{}", sockaddr.port()))?],
             ..RpcSettings::default()
         };
         drop(listener);
@@ -181,7 +181,7 @@ fn http_jsonrpc_reqrep() -> Result<()> {
 
         msleep(500).await;
 
-        let client = RpcClient::new(rpc_settings.listen, executor.clone()).await?;
+        let client = RpcClient::new(rpc_settings.listen[0].clone(), executor.clone()).await?;
         let req = JsonRequest::new("ping", vec![].into());
         let rep = client.request(req).await?;