X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=src%2Fcli.rs;h=a70d772769e931bed7e7bfd5312b97136ec646b6;hb=HEAD;hp=a53a0200e2b36e448f1a5b0f7ced31cdd08fc1d2;hpb=55dc47ff1c12f05c524fab2a191999dfe5e4fb8b;p=ldk-sample diff --git a/src/cli.rs b/src/cli.rs index a53a020..fe1b286 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,27 +1,29 @@ use crate::disk::{self, INBOUND_PAYMENTS_FNAME, OUTBOUND_PAYMENTS_FNAME}; use crate::hex_utils; use crate::{ - ChannelManager, HTLCStatus, MillisatAmount, NetworkGraph, OnionMessenger, PaymentInfo, - PaymentInfoStorage, PeerManager, + ChannelManager, HTLCStatus, InboundPaymentInfoStorage, MillisatAmount, NetworkGraph, + OnionMessenger, OutboundPaymentInfoStorage, PaymentInfo, PeerManager, }; use bitcoin::hashes::sha256::Hash as Sha256; use bitcoin::hashes::Hash; use bitcoin::network::constants::Network; use bitcoin::secp256k1::PublicKey; use lightning::ln::channelmanager::{PaymentId, RecipientOnionFields, Retry}; -use lightning::ln::msgs::NetAddress; -use lightning::ln::{PaymentHash, PaymentPreimage}; -use lightning::onion_message::OnionMessagePath; -use lightning::onion_message::{CustomOnionMessageContents, Destination, OnionMessageContents}; +use lightning::ln::msgs::SocketAddress; +use lightning::ln::{ChannelId, PaymentHash, PaymentPreimage}; +use lightning::offers::offer::{self, Offer}; +use lightning::onion_message::messenger::Destination; +use lightning::onion_message::packet::OnionMessageContents; use lightning::routing::gossip::NodeId; use lightning::routing::router::{PaymentParameters, RouteParameters}; use lightning::sign::{EntropySource, KeysManager}; use lightning::util::config::{ChannelHandshakeConfig, ChannelHandshakeLimits, UserConfig}; -use lightning::util::persist::KVStorePersister; +use lightning::util::persist::KVStore; use lightning::util::ser::{Writeable, Writer}; -use lightning_invoice::payment::pay_invoice; +use lightning_invoice::payment::payment_parameters_from_invoice; +use lightning_invoice::payment::payment_parameters_from_zero_amount_invoice; use lightning_invoice::{utils, Bolt11Invoice, Currency}; -use lightning_persister::FilesystemPersister; +use lightning_persister::fs_store::FilesystemStore; use std::env; use std::io; use std::io::Write; @@ -38,17 +40,18 @@ pub(crate) struct LdkUserInfo { pub(crate) bitcoind_rpc_host: String, pub(crate) ldk_storage_dir_path: String, pub(crate) ldk_peer_listening_port: u16, - pub(crate) ldk_announced_listen_addr: Vec, + pub(crate) ldk_announced_listen_addr: Vec, pub(crate) ldk_announced_node_name: [u8; 32], pub(crate) network: Network, } +#[derive(Debug)] struct UserOnionMessageContents { tlv_type: u64, data: Vec, } -impl CustomOnionMessageContents for UserOnionMessageContents { +impl OnionMessageContents for UserOnionMessageContents { fn tlv_type(&self) -> u64 { self.tlv_type } @@ -60,19 +63,19 @@ impl Writeable for UserOnionMessageContents { } } -pub(crate) async fn poll_for_user_input( +pub(crate) fn poll_for_user_input( peer_manager: Arc, channel_manager: Arc, keys_manager: Arc, network_graph: Arc, - onion_messenger: Arc, inbound_payments: Arc>, - outbound_payments: Arc>, ldk_data_dir: String, network: Network, - logger: Arc, persister: Arc, + onion_messenger: Arc, inbound_payments: Arc>, + outbound_payments: Arc>, ldk_data_dir: String, + network: Network, logger: Arc, fs_store: Arc, ) { println!( "LDK startup successful. Enter \"help\" to view available commands. Press Ctrl-D to quit." ); println!("LDK logs are available at /.ldk/logs"); println!("Local Node ID is {}.", channel_manager.get_our_node_id()); - loop { + 'read_command: loop { print!("> "); io::stdout().flush().unwrap(); // Without flushing, the `>` doesn't print let mut line = String::new(); @@ -93,7 +96,7 @@ pub(crate) async fn poll_for_user_input( let peer_pubkey_and_ip_addr = words.next(); let channel_value_sat = words.next(); if peer_pubkey_and_ip_addr.is_none() || channel_value_sat.is_none() { - println!("ERROR: openchannel has 2 required arguments: `openchannel pubkey@host:port channel_amt_satoshis` [--public]"); + println!("ERROR: openchannel has 2 required arguments: `openchannel pubkey@host:port channel_amt_satoshis` [--public] [--with-anchors]"); continue; } let peer_pubkey_and_ip_addr = peer_pubkey_and_ip_addr.unwrap(); @@ -103,7 +106,7 @@ pub(crate) async fn poll_for_user_input( Err(e) => { println!("{:?}", e.into_inner().unwrap()); continue; - } + }, }; let chan_amt_sat: Result = channel_value_sat.unwrap().parse(); @@ -112,27 +115,36 @@ pub(crate) async fn poll_for_user_input( continue; } - if connect_peer_if_necessary(pubkey, peer_addr, peer_manager.clone()) - .await + if tokio::runtime::Handle::current() + .block_on(connect_peer_if_necessary( + pubkey, + peer_addr, + peer_manager.clone(), + )) .is_err() { continue; }; - let announce_channel = match words.next() { - Some("--public") | Some("--public=true") => true, - Some("--public=false") => false, - Some(_) => { - println!("ERROR: invalid `--public` command format. Valid formats: `--public`, `--public=true` `--public=false`"); - continue; + let (mut announce_channel, mut with_anchors) = (false, false); + while let Some(word) = words.next() { + match word { + "--public" | "--public=true" => announce_channel = true, + "--public=false" => announce_channel = false, + "--with-anchors" | "--with-anchors=true" => with_anchors = true, + "--with-anchors=false" => with_anchors = false, + _ => { + println!("ERROR: invalid boolean flag format. Valid formats: `--option`, `--option=true` `--option=false`"); + continue; + }, } - None => false, - }; + } if open_channel( pubkey, chan_amt_sat.unwrap(), announce_channel, + with_anchors, channel_manager.clone(), ) .is_ok() @@ -143,7 +155,7 @@ pub(crate) async fn poll_for_user_input( peer_pubkey_and_ip_addr, ); } - } + }, "sendpayment" => { let invoice_str = words.next(); if invoice_str.is_none() { @@ -151,21 +163,91 @@ pub(crate) async fn poll_for_user_input( continue; } - let invoice = match Bolt11Invoice::from_str(invoice_str.unwrap()) { - Ok(inv) => inv, - Err(e) => { - println!("ERROR: invalid invoice: {:?}", e); + let mut user_provided_amt: Option = None; + if let Some(amt_msat_str) = words.next() { + match amt_msat_str.parse() { + Ok(amt) => user_provided_amt = Some(amt), + Err(e) => { + println!("ERROR: couldn't parse amount_msat: {}", e); + continue; + }, + }; + } + + if let Ok(offer) = Offer::from_str(invoice_str.unwrap()) { + let random_bytes = keys_manager.get_secure_random_bytes(); + let payment_id = PaymentId(random_bytes); + + let amt_msat = match (offer.amount(), user_provided_amt) { + (Some(offer::Amount::Bitcoin { amount_msats }), _) => *amount_msats, + (_, Some(amt)) => amt, + (amt, _) => { + println!("ERROR: Cannot process non-Bitcoin-denominated offer value {:?}", amt); + continue; + }, + }; + if user_provided_amt.is_some() && user_provided_amt != Some(amt_msat) { + println!("Amount didn't match offer of {}msat", amt_msat); continue; } - }; - send_payment( - &channel_manager, - &invoice, - &mut outbound_payments.lock().unwrap(), - persister.clone(), - ); - } + while user_provided_amt.is_none() { + print!("Paying offer for {} msat. Continue (Y/N)? >", amt_msat); + io::stdout().flush().unwrap(); + + if let Err(e) = io::stdin().read_line(&mut line) { + println!("ERROR: {}", e); + break 'read_command; + } + + if line.len() == 0 { + // We hit EOF / Ctrl-D + break 'read_command; + } + + if line.starts_with("Y") { + break; + } + if line.starts_with("N") { + continue 'read_command; + } + } + + outbound_payments.lock().unwrap().payments.insert( + payment_id, + PaymentInfo { + preimage: None, + secret: None, + status: HTLCStatus::Pending, + amt_msat: MillisatAmount(Some(amt_msat)), + }, + ); + fs_store + .write("", "", OUTBOUND_PAYMENTS_FNAME, &outbound_payments.encode()) + .unwrap(); + + let retry = Retry::Timeout(Duration::from_secs(10)); + let amt = Some(amt_msat); + let pay = channel_manager + .pay_for_offer(&offer, None, amt, None, payment_id, retry, None); + if pay.is_err() { + println!("ERROR: Failed to pay: {:?}", pay); + } + } else { + match Bolt11Invoice::from_str(invoice_str.unwrap()) { + Ok(invoice) => send_payment( + &channel_manager, + &invoice, + user_provided_amt, + &mut outbound_payments.lock().unwrap(), + Arc::clone(&fs_store), + ), + Err(e) => { + println!("ERROR: invalid invoice: {:?}", e); + }, + } + } + }, "keysend" => { let dest_pubkey = match words.next() { Some(dest) => match hex_utils::to_compressed_pubkey(dest) { @@ -173,26 +255,26 @@ pub(crate) async fn poll_for_user_input( None => { println!("ERROR: couldn't parse destination pubkey"); continue; - } + }, }, None => { println!("ERROR: keysend requires a destination pubkey: `keysend `"); continue; - } + }, }; let amt_msat_str = match words.next() { Some(amt) => amt, None => { println!("ERROR: keysend requires an amount in millisatoshis: `keysend `"); continue; - } + }, }; let amt_msat: u64 = match amt_msat_str.parse() { Ok(amt) => amt, Err(e) => { println!("ERROR: couldn't parse amount_msat: {}", e); continue; - } + }, }; keysend( &channel_manager, @@ -200,9 +282,37 @@ pub(crate) async fn poll_for_user_input( amt_msat, &*keys_manager, &mut outbound_payments.lock().unwrap(), - persister.clone(), + Arc::clone(&fs_store), ); - } + }, + "getoffer" => { + let offer_builder = channel_manager.create_offer_builder(String::new()); + if let Err(e) = offer_builder { + println!("ERROR: Failed to initiate offer building: {:?}", e); + continue; + } + + let amt_str = words.next(); + let offer = if amt_str.is_some() { + let amt_msat: Result = amt_str.unwrap().parse(); + if amt_msat.is_err() { + println!("ERROR: getoffer provided payment amount was not a number"); + continue; + } + offer_builder.unwrap().amount_msats(amt_msat.unwrap()).build() + } else { + offer_builder.unwrap().build() + }; + + if offer.is_err() { + println!("ERROR: Failed to build offer: {:?}", offer.unwrap_err()); + } else { + // Note that unlike BOLT11 invoice creation we don't bother to add a + // pending inbound payment here, as offers can be reused and don't + // correspond with individual payments. + println!("{}", offer.unwrap()); + } + }, "getinvoice" => { let amt_str = words.next(); if amt_str.is_none() { @@ -238,8 +348,10 @@ pub(crate) async fn poll_for_user_input( expiry_secs.unwrap(), Arc::clone(&logger), ); - persister.persist(INBOUND_PAYMENTS_FNAME, &*inbound_payments).unwrap(); - } + fs_store + .write("", "", INBOUND_PAYMENTS_FNAME, &inbound_payments.encode()) + .unwrap(); + }, "connectpeer" => { let peer_pubkey_and_ip_addr = words.next(); if peer_pubkey_and_ip_addr.is_none() { @@ -252,15 +364,19 @@ pub(crate) async fn poll_for_user_input( Err(e) => { println!("{:?}", e.into_inner().unwrap()); continue; - } + }, }; - if connect_peer_if_necessary(pubkey, peer_addr, peer_manager.clone()) - .await + if tokio::runtime::Handle::current() + .block_on(connect_peer_if_necessary( + pubkey, + peer_addr, + peer_manager.clone(), + )) .is_ok() { println!("SUCCESS: connected to peer {}", pubkey); } - } + }, "disconnectpeer" => { let peer_pubkey = words.next(); if peer_pubkey.is_none() { @@ -274,7 +390,7 @@ pub(crate) async fn poll_for_user_input( Err(e) => { println!("ERROR: {}", e.to_string()); continue; - } + }, }; if do_disconnect_peer( @@ -286,7 +402,7 @@ pub(crate) async fn poll_for_user_input( { println!("SUCCESS: disconnected from peer {}", peer_pubkey); } - } + }, "listchannels" => list_channels(&channel_manager, &network_graph), "listpayments" => list_payments( &inbound_payments.lock().unwrap(), @@ -316,18 +432,18 @@ pub(crate) async fn poll_for_user_input( None => { println!("ERROR: couldn't parse peer_pubkey"); continue; - } + }, }; let peer_pubkey = match PublicKey::from_slice(&peer_pubkey_vec) { Ok(peer_pubkey) => peer_pubkey, Err(_) => { println!("ERROR: couldn't parse peer_pubkey"); continue; - } + }, }; close_channel(channel_id, peer_pubkey, channel_manager.clone()); - } + }, "forceclosechannel" => { let channel_id_str = words.next(); if channel_id_str.is_none() { @@ -352,18 +468,18 @@ pub(crate) async fn poll_for_user_input( None => { println!("ERROR: couldn't parse peer_pubkey"); continue; - } + }, }; let peer_pubkey = match PublicKey::from_slice(&peer_pubkey_vec) { Ok(peer_pubkey) => peer_pubkey, Err(_) => { println!("ERROR: couldn't parse peer_pubkey"); continue; - } + }, }; force_close_channel(channel_id, peer_pubkey, channel_manager.clone()); - } + }, "nodeinfo" => node_info(&channel_manager, &peer_manager), "listpeers" => list_peers(peer_manager.clone()), "signmessage" => { @@ -379,7 +495,7 @@ pub(crate) async fn poll_for_user_input( &keys_manager.get_node_secret_key() ) ); - } + }, "sendonionmessage" => { let path_pks_str = words.next(); if path_pks_str.is_none() { @@ -397,7 +513,7 @@ pub(crate) async fn poll_for_user_input( println!("ERROR: couldn't parse peer_pubkey"); errored = true; break; - } + }, }; let node_pubkey = match PublicKey::from_slice(&node_pubkey_vec) { Ok(peer_pubkey) => peer_pubkey, @@ -405,7 +521,7 @@ pub(crate) async fn poll_for_user_input( println!("ERROR: couldn't parse peer_pubkey"); errored = true; break; - } + }, }; intermediate_nodes.push(node_pubkey); } @@ -417,26 +533,27 @@ pub(crate) async fn poll_for_user_input( _ => { println!("Need an integral message type above 64"); continue; - } + }, }; let data = match words.next().map(|s| hex_utils::to_vec(s)) { Some(Some(data)) => data, _ => { println!("Need a hex data string"); continue; - } + }, }; let destination = Destination::Node(intermediate_nodes.pop().unwrap()); - let message_path = OnionMessagePath { intermediate_nodes, destination }; match onion_messenger.send_onion_message( - message_path, - OnionMessageContents::Custom(UserOnionMessageContents { tlv_type, data }), + UserOnionMessageContents { tlv_type, data }, + destination, None, ) { - Ok(()) => println!("SUCCESS: forwarded onion message to first hop"), + Ok(success) => { + println!("SUCCESS: forwarded onion message to first hop {:?}", success) + }, Err(e) => println!("ERROR: failed to send onion message: {:?}", e), } - } + }, "quit" | "exit" => break, _ => println!("Unknown command. See `\"help\" for available commands."), } @@ -455,7 +572,7 @@ fn help() { println!(" help\tShows a list of commands."); println!(" quit\tClose the application."); println!("\n Channels:"); - println!(" openchannel pubkey@host:port [--public]"); + println!(" openchannel pubkey@host:port [--public] [--with-anchors]"); println!(" closechannel "); println!(" forceclosechannel "); println!(" listchannels"); @@ -464,11 +581,12 @@ fn help() { println!(" disconnectpeer "); println!(" listpeers"); println!("\n Payments:"); - println!(" sendpayment "); + println!(" sendpayment []"); println!(" keysend "); println!(" listpayments"); println!("\n Invoices:"); println!(" getinvoice "); + println!(" getoffer []"); println!("\n Other:"); println!(" signmessage "); println!( @@ -502,7 +620,7 @@ fn list_channels(channel_manager: &Arc, network_graph: &Arc, network_graph: &Arc, network_graph: &Arc Err(()), } } @@ -638,7 +758,7 @@ fn do_disconnect_peer( } fn open_channel( - peer_pubkey: PublicKey, channel_amt_sat: u64, announced_channel: bool, + peer_pubkey: PublicKey, channel_amt_sat: u64, announced_channel: bool, with_anchors: bool, channel_manager: Arc, ) -> Result<(), ()> { let config = UserConfig { @@ -649,31 +769,62 @@ fn open_channel( }, channel_handshake_config: ChannelHandshakeConfig { announced_channel, + negotiate_anchors_zero_fee_htlc_tx: with_anchors, ..Default::default() }, ..Default::default() }; - match channel_manager.create_channel(peer_pubkey, channel_amt_sat, 0, 0, Some(config)) { + match channel_manager.create_channel(peer_pubkey, channel_amt_sat, 0, 0, None, Some(config)) { Ok(_) => { println!("EVENT: initiated channel with peer {}. ", peer_pubkey); return Ok(()); - } + }, Err(e) => { println!("ERROR: failed to open channel: {:?}", e); return Err(()); - } + }, } } fn send_payment( - channel_manager: &ChannelManager, invoice: &Bolt11Invoice, - outbound_payments: &mut PaymentInfoStorage, persister: Arc, + channel_manager: &ChannelManager, invoice: &Bolt11Invoice, required_amount_msat: Option, + outbound_payments: &mut OutboundPaymentInfoStorage, fs_store: Arc, ) { - let payment_hash = PaymentHash((*invoice.payment_hash()).into_inner()); + let payment_id = PaymentId((*invoice.payment_hash()).to_byte_array()); let payment_secret = Some(*invoice.payment_secret()); + let zero_amt_invoice = + invoice.amount_milli_satoshis().is_none() || invoice.amount_milli_satoshis() == Some(0); + let pay_params_opt = if zero_amt_invoice { + if let Some(amt_msat) = required_amount_msat { + payment_parameters_from_zero_amount_invoice(invoice, amt_msat) + } else { + println!("Need an amount for the given 0-value invoice"); + print!("> "); + return; + } + } else { + if required_amount_msat.is_some() && invoice.amount_milli_satoshis() != required_amount_msat + { + println!( + "Amount didn't match invoice value of {}msat", + invoice.amount_milli_satoshis().unwrap_or(0) + ); + print!("> "); + return; + } + payment_parameters_from_invoice(invoice) + }; + let (payment_hash, recipient_onion, route_params) = match pay_params_opt { + Ok(res) => res, + Err(e) => { + println!("Failed to parse invoice: {:?}", e); + print!("> "); + return; + }, + }; outbound_payments.payments.insert( - payment_hash, + payment_id, PaymentInfo { preimage: None, secret: payment_secret, @@ -681,36 +832,43 @@ fn send_payment( amt_msat: MillisatAmount(invoice.amount_milli_satoshis()), }, ); - persister.persist(OUTBOUND_PAYMENTS_FNAME, &*outbound_payments).unwrap(); - match pay_invoice(invoice, Retry::Timeout(Duration::from_secs(10)), channel_manager) { - Ok(_payment_id) => { + fs_store.write("", "", OUTBOUND_PAYMENTS_FNAME, &outbound_payments.encode()).unwrap(); + + match channel_manager.send_payment( + payment_hash, + recipient_onion, + payment_id, + route_params, + Retry::Timeout(Duration::from_secs(10)), + ) { + Ok(_) => { let payee_pubkey = invoice.recover_payee_pub_key(); let amt_msat = invoice.amount_milli_satoshis().unwrap(); println!("EVENT: initiated sending {} msats to {}", amt_msat, payee_pubkey); print!("> "); - } + }, Err(e) => { println!("ERROR: failed to send payment: {:?}", e); print!("> "); - outbound_payments.payments.get_mut(&payment_hash).unwrap().status = HTLCStatus::Failed; - persister.persist(OUTBOUND_PAYMENTS_FNAME, &*outbound_payments).unwrap(); - } + outbound_payments.payments.get_mut(&payment_id).unwrap().status = HTLCStatus::Failed; + fs_store.write("", "", OUTBOUND_PAYMENTS_FNAME, &outbound_payments.encode()).unwrap(); + }, }; } fn keysend( channel_manager: &ChannelManager, payee_pubkey: PublicKey, amt_msat: u64, entropy_source: &E, - outbound_payments: &mut PaymentInfoStorage, persister: Arc, + outbound_payments: &mut OutboundPaymentInfoStorage, fs_store: Arc, ) { let payment_preimage = PaymentPreimage(entropy_source.get_secure_random_bytes()); - let payment_hash = PaymentHash(Sha256::hash(&payment_preimage.0[..]).into_inner()); + let payment_id = PaymentId(Sha256::hash(&payment_preimage.0[..]).to_byte_array()); - let route_params = RouteParameters { - payment_params: PaymentParameters::for_keysend(payee_pubkey, 40, false), - final_value_msat: amt_msat, - }; + let route_params = RouteParameters::from_payment_params_and_value( + PaymentParameters::for_keysend(payee_pubkey, 40, false), + amt_msat, + ); outbound_payments.payments.insert( - payment_hash, + payment_id, PaymentInfo { preimage: None, secret: None, @@ -718,37 +876,37 @@ fn keysend( amt_msat: MillisatAmount(Some(amt_msat)), }, ); - persister.persist(OUTBOUND_PAYMENTS_FNAME, &*outbound_payments).unwrap(); + fs_store.write("", "", OUTBOUND_PAYMENTS_FNAME, &outbound_payments.encode()).unwrap(); match channel_manager.send_spontaneous_payment_with_retry( Some(payment_preimage), RecipientOnionFields::spontaneous_empty(), - PaymentId(payment_hash.0), + payment_id, route_params, Retry::Timeout(Duration::from_secs(10)), ) { Ok(_payment_hash) => { println!("EVENT: initiated sending {} msats to {}", amt_msat, payee_pubkey); print!("> "); - } + }, Err(e) => { println!("ERROR: failed to send payment: {:?}", e); print!("> "); - outbound_payments.payments.get_mut(&payment_hash).unwrap().status = HTLCStatus::Failed; - persister.persist(OUTBOUND_PAYMENTS_FNAME, &*outbound_payments).unwrap(); - } + outbound_payments.payments.get_mut(&payment_id).unwrap().status = HTLCStatus::Failed; + fs_store.write("", "", OUTBOUND_PAYMENTS_FNAME, &outbound_payments.encode()).unwrap(); + }, }; } fn get_invoice( - amt_msat: u64, inbound_payments: &mut PaymentInfoStorage, channel_manager: &ChannelManager, - keys_manager: Arc, network: Network, expiry_secs: u32, - logger: Arc, + amt_msat: u64, inbound_payments: &mut InboundPaymentInfoStorage, + channel_manager: &ChannelManager, keys_manager: Arc, network: Network, + expiry_secs: u32, logger: Arc, ) { let currency = match network { Network::Bitcoin => Currency::Bitcoin, - Network::Testnet => Currency::BitcoinTestnet, Network::Regtest => Currency::Regtest, Network::Signet => Currency::Signet, + Network::Testnet | _ => Currency::BitcoinTestnet, }; let invoice = match utils::create_invoice_from_channelmanager( channel_manager, @@ -763,14 +921,14 @@ fn get_invoice( Ok(inv) => { println!("SUCCESS: generated invoice: {}", inv); inv - } + }, Err(e) => { println!("ERROR: failed to create invoice: {:?}", e); return; - } + }, }; - let payment_hash = PaymentHash(invoice.payment_hash().clone().into_inner()); + let payment_hash = PaymentHash(invoice.payment_hash().to_byte_array()); inbound_payments.payments.insert( payment_hash, PaymentInfo { @@ -785,7 +943,7 @@ fn get_invoice( fn close_channel( channel_id: [u8; 32], counterparty_node_id: PublicKey, channel_manager: Arc, ) { - match channel_manager.close_channel(&channel_id, &counterparty_node_id) { + match channel_manager.close_channel(&ChannelId(channel_id), &counterparty_node_id) { Ok(()) => println!("EVENT: initiating channel close"), Err(e) => println!("ERROR: failed to close channel: {:?}", e), } @@ -794,7 +952,9 @@ fn close_channel( fn force_close_channel( channel_id: [u8; 32], counterparty_node_id: PublicKey, channel_manager: Arc, ) { - match channel_manager.force_close_broadcasting_latest_txn(&channel_id, &counterparty_node_id) { + match channel_manager + .force_close_broadcasting_latest_txn(&ChannelId(channel_id), &counterparty_node_id) + { Ok(()) => println!("EVENT: initiating channel force-close"), Err(e) => println!("ERROR: failed to force-close channel: {:?}", e), }