Update to latest RL + fix step numbers + nodeinfo + listpeers commands
[ldk-sample] / src / cli.rs
1 use crate::disk;
2 use crate::hex_utils;
3 use crate::{
4         ChannelManager, FilesystemLogger, HTLCStatus, MillisatAmount, PaymentInfo, PaymentInfoStorage,
5         PeerManager,
6 };
7 use bitcoin::network::constants::Network;
8 use bitcoin::secp256k1::key::PublicKey;
9 use lightning::chain;
10 use lightning::chain::keysinterface::KeysManager;
11 use lightning::ln::features::InvoiceFeatures;
12 use lightning::ln::{PaymentHash, PaymentSecret};
13 use lightning::routing::network_graph::NetGraphMsgHandler;
14 use lightning::routing::router;
15 use lightning::routing::router::RouteHintHop;
16 use lightning::util::config::UserConfig;
17 use lightning_invoice::{utils, Currency, Invoice};
18 use std::env;
19 use std::io;
20 use std::io::{BufRead, Write};
21 use std::net::{SocketAddr, TcpStream};
22 use std::ops::Deref;
23 use std::path::Path;
24 use std::str::FromStr;
25 use std::sync::Arc;
26 use std::time::Duration;
27 use tokio::sync::mpsc;
28
29 pub(crate) struct LdkUserInfo {
30         pub(crate) bitcoind_rpc_username: String,
31         pub(crate) bitcoind_rpc_password: String,
32         pub(crate) bitcoind_rpc_port: u16,
33         pub(crate) bitcoind_rpc_host: String,
34         pub(crate) ldk_storage_dir_path: String,
35         pub(crate) ldk_peer_listening_port: u16,
36         pub(crate) network: Network,
37 }
38
39 pub(crate) fn parse_startup_args() -> Result<LdkUserInfo, ()> {
40         if env::args().len() < 3 {
41                 println!("ldk-tutorial-node requires 3 arguments: `cargo run <bitcoind-rpc-username>:<bitcoind-rpc-password>@<bitcoind-rpc-host>:<bitcoind-rpc-port> ldk_storage_directory_path [<ldk-incoming-peer-listening-port>] [bitcoin-network]`");
42                 return Err(());
43         }
44         let bitcoind_rpc_info = env::args().skip(1).next().unwrap();
45         let bitcoind_rpc_info_parts: Vec<&str> = bitcoind_rpc_info.split("@").collect();
46         if bitcoind_rpc_info_parts.len() != 2 {
47                 println!("ERROR: bad bitcoind RPC URL provided");
48                 return Err(());
49         }
50         let rpc_user_and_password: Vec<&str> = bitcoind_rpc_info_parts[0].split(":").collect();
51         if rpc_user_and_password.len() != 2 {
52                 println!("ERROR: bad bitcoind RPC username/password combo provided");
53                 return Err(());
54         }
55         let bitcoind_rpc_username = rpc_user_and_password[0].to_string();
56         let bitcoind_rpc_password = rpc_user_and_password[1].to_string();
57         let bitcoind_rpc_path: Vec<&str> = bitcoind_rpc_info_parts[1].split(":").collect();
58         if bitcoind_rpc_path.len() != 2 {
59                 println!("ERROR: bad bitcoind RPC path provided");
60                 return Err(());
61         }
62         let bitcoind_rpc_host = bitcoind_rpc_path[0].to_string();
63         let bitcoind_rpc_port = bitcoind_rpc_path[1].parse::<u16>().unwrap();
64
65         let ldk_storage_dir_path = env::args().skip(2).next().unwrap();
66
67         let mut ldk_peer_port_set = true;
68         let ldk_peer_listening_port: u16 = match env::args().skip(3).next().map(|p| p.parse()) {
69                 Some(Ok(p)) => p,
70                 Some(Err(e)) => panic!("{}", e),
71                 None => {
72                         ldk_peer_port_set = false;
73                         9735
74                 }
75         };
76
77         let arg_idx = match ldk_peer_port_set {
78                 true => 4,
79                 false => 3,
80         };
81         let network: Network = match env::args().skip(arg_idx).next().as_ref().map(String::as_str) {
82                 Some("testnet") => Network::Testnet,
83                 Some("regtest") => Network::Regtest,
84                 Some(_) => panic!("Unsupported network provided. Options are: `regtest`, `testnet`"),
85                 None => Network::Testnet,
86         };
87         Ok(LdkUserInfo {
88                 bitcoind_rpc_username,
89                 bitcoind_rpc_password,
90                 bitcoind_rpc_host,
91                 bitcoind_rpc_port,
92                 ldk_storage_dir_path,
93                 ldk_peer_listening_port,
94                 network,
95         })
96 }
97
98 pub(crate) async fn poll_for_user_input(
99         peer_manager: Arc<PeerManager>, channel_manager: Arc<ChannelManager>,
100         keys_manager: Arc<KeysManager>,
101         router: Arc<NetGraphMsgHandler<Arc<dyn chain::Access + Send + Sync>, Arc<FilesystemLogger>>>,
102         inbound_payments: PaymentInfoStorage, outbound_payments: PaymentInfoStorage,
103         event_notifier: mpsc::Sender<()>, ldk_data_dir: String, logger: Arc<FilesystemLogger>,
104         network: Network,
105 ) {
106         println!("LDK startup successful. To view available commands: \"help\".\nLDK logs are available at <your-supplied-ldk-data-dir-path>/.ldk/logs");
107         let stdin = io::stdin();
108         print!("> ");
109         io::stdout().flush().unwrap(); // Without flushing, the `>` doesn't print
110         for line in stdin.lock().lines() {
111                 let _ = event_notifier.try_send(());
112                 let line = line.unwrap();
113                 let mut words = line.split_whitespace();
114                 if let Some(word) = words.next() {
115                         match word {
116                                 "help" => help(),
117                                 "openchannel" => {
118                                         let peer_pubkey_and_ip_addr = words.next();
119                                         let channel_value_sat = words.next();
120                                         if peer_pubkey_and_ip_addr.is_none() || channel_value_sat.is_none() {
121                                                 println!("ERROR: openchannel has 2 required arguments: `openchannel pubkey@host:port channel_amt_satoshis` [--public]");
122                                                 print!("> ");
123                                                 io::stdout().flush().unwrap();
124                                                 continue;
125                                         }
126                                         let peer_pubkey_and_ip_addr = peer_pubkey_and_ip_addr.unwrap();
127                                         let (pubkey, peer_addr) =
128                                                 match parse_peer_info(peer_pubkey_and_ip_addr.to_string()) {
129                                                         Ok(info) => info,
130                                                         Err(e) => {
131                                                                 println!("{:?}", e.into_inner().unwrap());
132                                                                 print!("> ");
133                                                                 io::stdout().flush().unwrap();
134                                                                 continue;
135                                                         }
136                                                 };
137
138                                         let chan_amt_sat: Result<u64, _> = channel_value_sat.unwrap().parse();
139                                         if chan_amt_sat.is_err() {
140                                                 println!("ERROR: channel amount must be a number");
141                                                 print!("> ");
142                                                 io::stdout().flush().unwrap();
143                                                 continue;
144                                         }
145
146                                         if connect_peer_if_necessary(
147                                                 pubkey,
148                                                 peer_addr,
149                                                 peer_manager.clone(),
150                                                 event_notifier.clone(),
151                                         )
152                                         .is_err()
153                                         {
154                                                 print!("> ");
155                                                 io::stdout().flush().unwrap();
156                                                 continue;
157                                         };
158
159                                         let announce_channel = match words.next() {
160                                                 Some("--public") | Some("--public=true") => true,
161                                                 Some("--public=false") => false,
162                                                 Some(_) => {
163                                                         println!("ERROR: invalid `--public` command format. Valid formats: `--public`, `--public=true` `--public=false`");
164                                                         print!("> ");
165                                                         io::stdout().flush().unwrap();
166                                                         continue;
167                                                 }
168                                                 None => false,
169                                         };
170
171                                         if open_channel(
172                                                 pubkey,
173                                                 chan_amt_sat.unwrap(),
174                                                 announce_channel,
175                                                 channel_manager.clone(),
176                                         )
177                                         .is_ok()
178                                         {
179                                                 let peer_data_path = format!("{}/channel_peer_data", ldk_data_dir.clone());
180                                                 let _ = disk::persist_channel_peer(
181                                                         Path::new(&peer_data_path),
182                                                         peer_pubkey_and_ip_addr,
183                                                 );
184                                         }
185                                 }
186                                 "sendpayment" => {
187                                         let invoice_str = words.next();
188                                         if invoice_str.is_none() {
189                                                 println!("ERROR: sendpayment requires an invoice: `sendpayment <invoice>`");
190                                                 print!("> ");
191                                                 io::stdout().flush().unwrap();
192                                                 continue;
193                                         }
194
195                                         let invoice = match Invoice::from_str(invoice_str.unwrap()) {
196                                                 Ok(inv) => inv,
197                                                 Err(e) => {
198                                                         println!("ERROR: invalid invoice: {:?}", e);
199                                                         print!("> ");
200                                                         io::stdout().flush().unwrap();
201                                                         continue;
202                                                 }
203                                         };
204                                         let mut route_hints = invoice.routes().clone();
205                                         let mut last_hops = Vec::new();
206                                         for hint in route_hints.drain(..) {
207                                                 last_hops.push(hint[hint.len() - 1].clone());
208                                         }
209
210                                         let amt_pico_btc = invoice.amount_pico_btc();
211                                         if amt_pico_btc.is_none() {
212                                                 println!("ERROR: invalid invoice: must contain amount to pay");
213                                                 print!("> ");
214                                                 io::stdout().flush().unwrap();
215                                                 continue;
216                                         }
217                                         let amt_msat = amt_pico_btc.unwrap() / 10;
218
219                                         let payee_pubkey = invoice.recover_payee_pub_key();
220                                         let final_cltv = invoice.min_final_cltv_expiry() as u32;
221
222                                         let mut payment_hash = PaymentHash([0; 32]);
223                                         payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]);
224
225                                         let payment_secret = match invoice.payment_secret() {
226                                                 Some(secret) => {
227                                                         let mut payment_secret = PaymentSecret([0; 32]);
228                                                         payment_secret.0.copy_from_slice(&secret.0);
229                                                         Some(payment_secret)
230                                                 }
231                                                 None => None,
232                                         };
233
234                                         let invoice_features = match invoice.features() {
235                                                 Some(feat) => Some(feat.clone()),
236                                                 None => None,
237                                         };
238
239                                         send_payment(
240                                                 payee_pubkey,
241                                                 amt_msat,
242                                                 final_cltv,
243                                                 payment_hash,
244                                                 payment_secret,
245                                                 invoice_features,
246                                                 last_hops,
247                                                 router.clone(),
248                                                 channel_manager.clone(),
249                                                 outbound_payments.clone(),
250                                                 logger.clone(),
251                                         );
252                                 }
253                                 "getinvoice" => {
254                                         let amt_str = words.next();
255                                         if amt_str.is_none() {
256                                                 println!("ERROR: getinvoice requires an amount in millisatoshis");
257                                                 print!("> ");
258                                                 io::stdout().flush().unwrap();
259                                                 continue;
260                                         }
261
262                                         let amt_msat: Result<u64, _> = amt_str.unwrap().parse();
263                                         if amt_msat.is_err() {
264                                                 println!("ERROR: getinvoice provided payment amount was not a number");
265                                                 print!("> ");
266                                                 io::stdout().flush().unwrap();
267                                                 continue;
268                                         }
269                                         get_invoice(
270                                                 amt_msat.unwrap(),
271                                                 inbound_payments.clone(),
272                                                 channel_manager.clone(),
273                                                 keys_manager.clone(),
274                                                 network,
275                                         );
276                                 }
277                                 "connectpeer" => {
278                                         let peer_pubkey_and_ip_addr = words.next();
279                                         if peer_pubkey_and_ip_addr.is_none() {
280                                                 println!("ERROR: connectpeer requires peer connection info: `connectpeer pubkey@host:port`");
281                                                 print!("> ");
282                                                 io::stdout().flush().unwrap();
283                                                 continue;
284                                         }
285                                         let (pubkey, peer_addr) =
286                                                 match parse_peer_info(peer_pubkey_and_ip_addr.unwrap().to_string()) {
287                                                         Ok(info) => info,
288                                                         Err(e) => {
289                                                                 println!("{:?}", e.into_inner().unwrap());
290                                                                 print!("> ");
291                                                                 io::stdout().flush().unwrap();
292                                                                 continue;
293                                                         }
294                                                 };
295                                         if connect_peer_if_necessary(
296                                                 pubkey,
297                                                 peer_addr,
298                                                 peer_manager.clone(),
299                                                 event_notifier.clone(),
300                                         )
301                                         .is_ok()
302                                         {
303                                                 println!("SUCCESS: connected to peer {}", pubkey);
304                                         }
305                                 }
306                                 "listchannels" => list_channels(channel_manager.clone()),
307                                 "listpayments" => {
308                                         list_payments(inbound_payments.clone(), outbound_payments.clone())
309                                 }
310                                 "closechannel" => {
311                                         let channel_id_str = words.next();
312                                         if channel_id_str.is_none() {
313                                                 println!("ERROR: closechannel requires a channel ID: `closechannel <channel_id>`");
314                                                 print!("> ");
315                                                 io::stdout().flush().unwrap();
316                                                 continue;
317                                         }
318                                         let channel_id_vec = hex_utils::to_vec(channel_id_str.unwrap());
319                                         if channel_id_vec.is_none() {
320                                                 println!("ERROR: couldn't parse channel_id as hex");
321                                                 print!("> ");
322                                                 io::stdout().flush().unwrap();
323                                                 continue;
324                                         }
325                                         let mut channel_id = [0; 32];
326                                         channel_id.copy_from_slice(&channel_id_vec.unwrap());
327                                         close_channel(channel_id, channel_manager.clone());
328                                 }
329                                 "forceclosechannel" => {
330                                         let channel_id_str = words.next();
331                                         if channel_id_str.is_none() {
332                                                 println!("ERROR: forceclosechannel requires a channel ID: `forceclosechannel <channel_id>`");
333                                                 print!("> ");
334                                                 io::stdout().flush().unwrap();
335                                                 continue;
336                                         }
337                                         let channel_id_vec = hex_utils::to_vec(channel_id_str.unwrap());
338                                         if channel_id_vec.is_none() {
339                                                 println!("ERROR: couldn't parse channel_id as hex");
340                                                 print!("> ");
341                                                 io::stdout().flush().unwrap();
342                                                 continue;
343                                         }
344                                         let mut channel_id = [0; 32];
345                                         channel_id.copy_from_slice(&channel_id_vec.unwrap());
346                                         force_close_channel(channel_id, channel_manager.clone());
347                                 }
348                                 "nodeinfo" => node_info(channel_manager.clone(), peer_manager.clone()),
349                                 "listpeers" => list_peers(peer_manager.clone()),
350                                 _ => println!("Unknown command. See `\"help\" for available commands."),
351                         }
352                 }
353                 print!("> ");
354                 io::stdout().flush().unwrap();
355         }
356 }
357
358 fn help() {
359         println!("openchannel pubkey@host:port <channel_amt_satoshis>");
360         println!("sendpayment <invoice>");
361         println!("getinvoice <amt_in_millisatoshis>");
362         println!("connectpeer pubkey@host:port");
363         println!("listchannels");
364         println!("listpayments");
365         println!("closechannel <channel_id>");
366         println!("forceclosechannel <channel_id>");
367 }
368
369 fn node_info(channel_manager: Arc<ChannelManager>, peer_manager: Arc<PeerManager>) {
370         println!("\t{{");
371         println!("\t\t node_pubkey: {}", channel_manager.get_our_node_id());
372         println!("\t\t num_channels: {}", channel_manager.list_channels().len());
373         println!("\t\t num_usable_channels: {}", channel_manager.list_usable_channels().len());
374         println!("\t\t num_peers: {}", peer_manager.get_peer_node_ids().len());
375         println!("\t}},");
376 }
377
378 fn list_peers(peer_manager: Arc<PeerManager>) {
379         println!("\t{{");
380         for pubkey in peer_manager.get_peer_node_ids() {
381                 println!("\t\t pubkey: {}", pubkey);
382         }
383         println!("\t}},");
384 }
385
386 fn list_channels(channel_manager: Arc<ChannelManager>) {
387         print!("[");
388         for chan_info in channel_manager.list_channels() {
389                 println!("");
390                 println!("\t{{");
391                 println!("\t\tchannel_id: {},", hex_utils::hex_str(&chan_info.channel_id[..]));
392                 println!(
393                         "\t\tpeer_pubkey: {},",
394                         hex_utils::hex_str(&chan_info.remote_network_id.serialize())
395                 );
396                 let mut pending_channel = false;
397                 match chan_info.short_channel_id {
398                         Some(id) => println!("\t\tshort_channel_id: {},", id),
399                         None => {
400                                 pending_channel = true;
401                         }
402                 }
403                 println!("\t\tpending_open: {},", pending_channel);
404                 println!("\t\tchannel_value_satoshis: {},", chan_info.channel_value_satoshis);
405                 println!("\t\tchannel_can_send_payments: {},", chan_info.is_live);
406                 println!("\t}},");
407         }
408         println!("]");
409 }
410
411 fn list_payments(inbound_payments: PaymentInfoStorage, outbound_payments: PaymentInfoStorage) {
412         let inbound = inbound_payments.lock().unwrap();
413         let outbound = outbound_payments.lock().unwrap();
414         print!("[");
415         for (payment_hash, payment_info) in inbound.deref() {
416                 println!("");
417                 println!("\t{{");
418                 println!("\t\tamount_millisatoshis: {},", payment_info.amt_msat);
419                 println!("\t\tpayment_hash: {},", hex_utils::hex_str(&payment_hash.0));
420                 println!("\t\thtlc_direction: inbound,");
421                 println!(
422                         "\t\thtlc_status: {},",
423                         match payment_info.status {
424                                 HTLCStatus::Pending => "pending",
425                                 HTLCStatus::Succeeded => "succeeded",
426                                 HTLCStatus::Failed => "failed",
427                         }
428                 );
429
430                 println!("\t}},");
431         }
432
433         for (payment_hash, payment_info) in outbound.deref() {
434                 println!("");
435                 println!("\t{{");
436                 println!("\t\tamount_millisatoshis: {},", payment_info.amt_msat);
437                 println!("\t\tpayment_hash: {},", hex_utils::hex_str(&payment_hash.0));
438                 println!("\t\thtlc_direction: outbound,");
439                 println!(
440                         "\t\thtlc_status: {},",
441                         match payment_info.status {
442                                 HTLCStatus::Pending => "pending",
443                                 HTLCStatus::Succeeded => "succeeded",
444                                 HTLCStatus::Failed => "failed",
445                         }
446                 );
447
448                 println!("\t}},");
449         }
450         println!("]");
451 }
452
453 pub(crate) fn connect_peer_if_necessary(
454         pubkey: PublicKey, peer_addr: SocketAddr, peer_manager: Arc<PeerManager>,
455         event_notifier: mpsc::Sender<()>,
456 ) -> Result<(), ()> {
457         for node_pubkey in peer_manager.get_peer_node_ids() {
458                 if node_pubkey == pubkey {
459                         return Ok(());
460                 }
461         }
462         match TcpStream::connect_timeout(&peer_addr, Duration::from_secs(10)) {
463                 Ok(stream) => {
464                         let peer_mgr = peer_manager.clone();
465                         let event_ntfns = event_notifier.clone();
466                         tokio::spawn(async move {
467                                 lightning_net_tokio::setup_outbound(peer_mgr, event_ntfns, pubkey, stream).await;
468                         });
469                         let mut peer_connected = false;
470                         while !peer_connected {
471                                 for node_pubkey in peer_manager.get_peer_node_ids() {
472                                         if node_pubkey == pubkey {
473                                                 peer_connected = true;
474                                         }
475                                 }
476                         }
477                 }
478                 Err(e) => {
479                         println!("ERROR: failed to connect to peer: {:?}", e);
480                         return Err(());
481                 }
482         }
483         Ok(())
484 }
485
486 fn open_channel(
487         peer_pubkey: PublicKey, channel_amt_sat: u64, announce_channel: bool,
488         channel_manager: Arc<ChannelManager>,
489 ) -> Result<(), ()> {
490         let mut config = UserConfig::default();
491         if announce_channel {
492                 config.channel_options.announced_channel = true;
493         }
494         // lnd's max to_self_delay is 2016, so we want to be compatible.
495         config.peer_channel_config_limits.their_to_self_delay = 2016;
496         match channel_manager.create_channel(peer_pubkey, channel_amt_sat, 0, 0, None) {
497                 Ok(_) => {
498                         println!("EVENT: initiated channel with peer {}. ", peer_pubkey);
499                         return Ok(());
500                 }
501                 Err(e) => {
502                         println!("ERROR: failed to open channel: {:?}", e);
503                         return Err(());
504                 }
505         }
506 }
507
508 fn send_payment(
509         payee: PublicKey, amt_msat: u64, final_cltv: u32, payment_hash: PaymentHash,
510         payment_secret: Option<PaymentSecret>, payee_features: Option<InvoiceFeatures>,
511         route_hints: Vec<RouteHintHop>,
512         router: Arc<NetGraphMsgHandler<Arc<dyn chain::Access + Send + Sync>, Arc<FilesystemLogger>>>,
513         channel_manager: Arc<ChannelManager>, payment_storage: PaymentInfoStorage,
514         logger: Arc<FilesystemLogger>,
515 ) {
516         let network_graph = router.network_graph.read().unwrap();
517         let first_hops = channel_manager.list_usable_channels();
518         let payer_pubkey = channel_manager.get_our_node_id();
519
520         let route = router::get_route(
521                 &payer_pubkey,
522                 &network_graph,
523                 &payee,
524                 payee_features,
525                 Some(&first_hops.iter().collect::<Vec<_>>()),
526                 &route_hints.iter().collect::<Vec<_>>(),
527                 amt_msat,
528                 final_cltv,
529                 logger,
530         );
531         if let Err(e) = route {
532                 println!("ERROR: failed to find route: {}", e.err);
533                 return;
534         }
535         let status = match channel_manager.send_payment(&route.unwrap(), payment_hash, &payment_secret)
536         {
537                 Ok(()) => {
538                         println!("EVENT: initiated sending {} msats to {}", amt_msat, payee);
539                         HTLCStatus::Pending
540                 }
541                 Err(e) => {
542                         println!("ERROR: failed to send payment: {:?}", e);
543                         HTLCStatus::Failed
544                 }
545         };
546         let mut payments = payment_storage.lock().unwrap();
547         payments.insert(
548                 payment_hash,
549                 PaymentInfo {
550                         preimage: None,
551                         secret: payment_secret,
552                         status,
553                         amt_msat: MillisatAmount(Some(amt_msat)),
554                 },
555         );
556 }
557
558 fn get_invoice(
559         amt_msat: u64, payment_storage: PaymentInfoStorage, channel_manager: Arc<ChannelManager>,
560         keys_manager: Arc<KeysManager>, network: Network,
561 ) {
562         let mut payments = payment_storage.lock().unwrap();
563         let currency = match network {
564                 Network::Bitcoin => Currency::Bitcoin,
565                 Network::Testnet => Currency::BitcoinTestnet,
566                 Network::Regtest => Currency::Regtest,
567                 Network::Signet => panic!("Signet unsupported"),
568         };
569         let invoice = match utils::create_invoice_from_channelmanager(
570                 &channel_manager,
571                 keys_manager,
572                 currency,
573                 Some(amt_msat),
574                 "ldk-tutorial-node".to_string(),
575         ) {
576                 Ok(inv) => {
577                         println!("SUCCESS: generated invoice: {}", inv);
578                         inv
579                 }
580                 Err(e) => {
581                         println!("ERROR: failed to create invoice: {:?}", e);
582                         return;
583                 }
584         };
585
586         let mut payment_hash = PaymentHash([0; 32]);
587         payment_hash.0.copy_from_slice(&invoice.payment_hash().as_ref()[0..32]);
588         payments.insert(
589                 payment_hash,
590                 PaymentInfo {
591                         preimage: None,
592                         // We can't add payment secrets to invoices until we support features in invoices.
593                         // Otherwise lnd errors with "destination hop doesn't understand payment addresses"
594                         // (for context, lnd calls payment secrets "payment addresses").
595                         secret: Some(invoice.payment_secret().unwrap().clone()),
596                         status: HTLCStatus::Pending,
597                         amt_msat: MillisatAmount(Some(amt_msat)),
598                 },
599         );
600 }
601
602 fn close_channel(channel_id: [u8; 32], channel_manager: Arc<ChannelManager>) {
603         match channel_manager.close_channel(&channel_id) {
604                 Ok(()) => println!("EVENT: initiating channel close"),
605                 Err(e) => println!("ERROR: failed to close channel: {:?}", e),
606         }
607 }
608
609 fn force_close_channel(channel_id: [u8; 32], channel_manager: Arc<ChannelManager>) {
610         match channel_manager.force_close_channel(&channel_id) {
611                 Ok(()) => println!("EVENT: initiating channel force-close"),
612                 Err(e) => println!("ERROR: failed to force-close channel: {:?}", e),
613         }
614 }
615
616 pub(crate) fn parse_peer_info(
617         peer_pubkey_and_ip_addr: String,
618 ) -> Result<(PublicKey, SocketAddr), std::io::Error> {
619         let mut pubkey_and_addr = peer_pubkey_and_ip_addr.split("@");
620         let pubkey = pubkey_and_addr.next();
621         let peer_addr_str = pubkey_and_addr.next();
622         if peer_addr_str.is_none() || peer_addr_str.is_none() {
623                 return Err(std::io::Error::new(
624                         std::io::ErrorKind::Other,
625                         "ERROR: incorrectly formatted peer
626         info. Should be formatted as: `pubkey@host:port`",
627                 ));
628         }
629
630         let peer_addr: Result<SocketAddr, _> = peer_addr_str.unwrap().parse();
631         if peer_addr.is_err() {
632                 return Err(std::io::Error::new(
633                         std::io::ErrorKind::Other,
634                         "ERROR: couldn't parse pubkey@host:port into a socket address",
635                 ));
636         }
637
638         let pubkey = hex_utils::to_compressed_pubkey(pubkey.unwrap());
639         if pubkey.is_none() {
640                 return Err(std::io::Error::new(
641                         std::io::ErrorKind::Other,
642                         "ERROR: unable to parse given pubkey for node",
643                 ));
644         }
645
646         Ok((pubkey.unwrap(), peer_addr.unwrap()))
647 }