From: Matt Corallo <649246+TheBlueMatt@users.noreply.github.com> Date: Mon, 14 Aug 2023 18:47:11 +0000 (+0000) Subject: Merge pull request #116 from wpaulino/anchors X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=0be8a1b35092b7ff8f1ac0a2efb8708e5a18a80c;hp=d69af92ec08c03204796cecb35f6905374af785e;p=ldk-sample Merge pull request #116 from wpaulino/anchors Add support for opening anchor outputs channels --- diff --git a/src/bitcoind_client.rs b/src/bitcoind_client.rs index 228f27a..a093e6d 100644 --- a/src/bitcoind_client.rs +++ b/src/bitcoind_client.rs @@ -1,13 +1,19 @@ use crate::convert::{ - BlockchainInfo, FeeResponse, FundedTx, MempoolMinFeeResponse, NewAddress, RawTx, SignedTx, + BlockchainInfo, FeeResponse, FundedTx, ListUnspentResponse, MempoolMinFeeResponse, NewAddress, + RawTx, SignedTx, }; use crate::disk::FilesystemLogger; +use crate::hex_utils; use base64; +use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR; use bitcoin::blockdata::transaction::Transaction; -use bitcoin::consensus::encode; +use bitcoin::consensus::{encode, Decodable, Encodable}; use bitcoin::hash_types::{BlockHash, Txid}; -use bitcoin::util::address::Address; +use bitcoin::hashes::Hash; +use bitcoin::util::address::{Address, Payload, WitnessVersion}; +use bitcoin::{OutPoint, Script, TxOut, WPubkeyHash, XOnlyPublicKey}; use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator}; +use lightning::events::bump_transaction::{Utxo, WalletSource}; use lightning::log_error; use lightning::routing::utxo::{UtxoLookup, UtxoResult}; use lightning::util::logger::Logger; @@ -250,6 +256,13 @@ impl BitcoindClient { .await .unwrap() } + + pub async fn list_unspent(&self) -> ListUnspentResponse { + self.bitcoind_rpc_client + .call_method::("listunspent", &vec![]) + .await + .unwrap() + } } impl FeeEstimator for BitcoindClient { @@ -308,3 +321,55 @@ impl UtxoLookup for BitcoindClient { todo!(); } } + +impl WalletSource for BitcoindClient { + fn list_confirmed_utxos(&self) -> Result, ()> { + let utxos = tokio::task::block_in_place(move || { + self.handle.block_on(async move { self.list_unspent().await }).0 + }); + Ok(utxos + .into_iter() + .filter_map(|utxo| { + let outpoint = OutPoint { txid: utxo.txid, vout: utxo.vout }; + match utxo.address.payload { + Payload::WitnessProgram { version, ref program } => match version { + WitnessVersion::V0 => WPubkeyHash::from_slice(program) + .map(|wpkh| Utxo::new_v0_p2wpkh(outpoint, utxo.amount, &wpkh)) + .ok(), + // TODO: Add `Utxo::new_v1_p2tr` upstream. + WitnessVersion::V1 => XOnlyPublicKey::from_slice(program) + .map(|_| Utxo { + outpoint, + output: TxOut { + value: utxo.amount, + script_pubkey: Script::new_witness_program(version, program), + }, + satisfaction_weight: 1 /* empty script_sig */ * WITNESS_SCALE_FACTOR as u64 + + 1 /* witness items */ + 1 /* schnorr sig len */ + 64, /* schnorr sig */ + }) + .ok(), + _ => None, + }, + _ => None, + } + }) + .collect()) + } + + fn get_change_script(&self) -> Result { + tokio::task::block_in_place(move || { + Ok(self.handle.block_on(async move { self.get_new_address().await.script_pubkey() })) + }) + } + + fn sign_tx(&self, tx: Transaction) -> Result { + let mut tx_bytes = Vec::new(); + let _ = tx.consensus_encode(&mut tx_bytes).map_err(|_| ()); + let tx_hex = hex_utils::hex_str(&tx_bytes); + let signed_tx = tokio::task::block_in_place(move || { + self.handle.block_on(async move { self.sign_raw_transaction_with_wallet(tx_hex).await }) + }); + let signed_tx_bytes = hex_utils::to_vec(&signed_tx.hex).ok_or(())?; + Transaction::consensus_decode(&mut signed_tx_bytes.as_slice()).map_err(|_| ()) + } +} diff --git a/src/cli.rs b/src/cli.rs index a53a020..10dce92 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -93,7 +93,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(); @@ -119,20 +119,25 @@ pub(crate) async fn poll_for_user_input( 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() @@ -455,7 +460,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"); @@ -638,7 +643,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,6 +654,7 @@ fn open_channel( }, channel_handshake_config: ChannelHandshakeConfig { announced_channel, + negotiate_anchors_zero_fee_htlc_tx: with_anchors, ..Default::default() }, ..Default::default() diff --git a/src/convert.rs b/src/convert.rs index 84b033e..7f1bf10 100644 --- a/src/convert.rs +++ b/src/convert.rs @@ -1,7 +1,8 @@ use bitcoin::hashes::hex::FromHex; -use bitcoin::BlockHash; +use bitcoin::{Address, BlockHash, Txid}; use lightning_block_sync::http::JsonResponse; use std::convert::TryInto; +use std::str::FromStr; pub struct FundedTx { pub changepos: i64, @@ -116,3 +117,33 @@ impl TryInto for JsonResponse { }) } } + +pub struct ListUnspentUtxo { + pub txid: Txid, + pub vout: u32, + pub amount: u64, + pub address: Address, +} + +pub struct ListUnspentResponse(pub Vec); + +impl TryInto for JsonResponse { + type Error = std::io::Error; + fn try_into(self) -> Result { + let utxos = self + .0 + .as_array() + .unwrap() + .iter() + .map(|utxo| ListUnspentUtxo { + txid: Txid::from_str(&utxo["txid"].as_str().unwrap().to_string()).unwrap(), + vout: utxo["vout"].as_u64().unwrap() as u32, + amount: bitcoin::Amount::from_btc(utxo["amount"].as_f64().unwrap()) + .unwrap() + .to_sat(), + address: Address::from_str(&utxo["address"].as_str().unwrap().to_string()).unwrap(), + }) + .collect(); + Ok(ListUnspentResponse(utxos)) + } +} diff --git a/src/main.rs b/src/main.rs index 938df12..9fd1190 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,6 +16,7 @@ use bitcoin_bech32::WitnessProgram; use disk::{INBOUND_PAYMENTS_FNAME, OUTBOUND_PAYMENTS_FNAME}; use lightning::chain::{chainmonitor, ChannelMonitorUpdateStatus}; use lightning::chain::{Filter, Watch}; +use lightning::events::bump_transaction::{BumpTransactionEventHandler, Wallet}; use lightning::events::{Event, PaymentFailureReason, PaymentPurpose}; use lightning::ln::channelmanager::{self, RecentPaymentDetails}; use lightning::ln::channelmanager::{ @@ -141,10 +142,17 @@ pub(crate) type NetworkGraph = gossip::NetworkGraph>; type OnionMessenger = SimpleArcOnionMessenger; +pub(crate) type BumpTxEventHandler = BumpTransactionEventHandler< + Arc, + Arc, Arc>>, + Arc, + Arc, +>; + async fn handle_ldk_events( channel_manager: &Arc, bitcoind_client: &BitcoindClient, network_graph: &NetworkGraph, keys_manager: &KeysManager, - inbound_payments: Arc>, + bump_tx_event_handler: &BumpTxEventHandler, inbound_payments: Arc>, outbound_payments: Arc>, persister: &Arc, network: Network, event: Event, ) { @@ -278,8 +286,34 @@ async fn handle_ldk_events( } persister.persist(OUTBOUND_PAYMENTS_FNAME, &*outbound).unwrap(); } - Event::OpenChannelRequest { .. } => { - // Unreachable, we don't set manually_accept_inbound_channels + Event::OpenChannelRequest { + ref temporary_channel_id, ref counterparty_node_id, .. + } => { + let mut random_bytes = [0u8; 16]; + random_bytes.copy_from_slice(&keys_manager.get_secure_random_bytes()[..16]); + let user_channel_id = u128::from_be_bytes(random_bytes); + let res = channel_manager.accept_inbound_channel( + temporary_channel_id, + counterparty_node_id, + user_channel_id, + ); + + if let Err(e) = res { + print!( + "\nEVENT: Failed to accept inbound channel ({}) from {}: {:?}", + hex_utils::hex_str(&temporary_channel_id[..]), + hex_utils::hex_str(&counterparty_node_id.serialize()), + e, + ); + } else { + print!( + "\nEVENT: Accepted inbound channel ({}) from {}", + hex_utils::hex_str(&temporary_channel_id[..]), + hex_utils::hex_str(&counterparty_node_id.serialize()), + ); + } + print!("> "); + io::stdout().flush().unwrap(); } Event::PaymentPathSuccessful { .. } => {} Event::PaymentPathFailed { .. } => {} @@ -429,7 +463,7 @@ async fn handle_ldk_events( // the funding transaction either confirms, or this event is generated. } Event::HTLCIntercepted { .. } => {} - Event::BumpTransaction(_) => {} + Event::BumpTransaction(event) => bump_tx_event_handler.handle_event(&event), } } @@ -532,6 +566,13 @@ async fn start_ldk() { let cur = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap(); let keys_manager = Arc::new(KeysManager::new(&keys_seed, cur.as_secs(), cur.subsec_nanos())); + let bump_tx_event_handler = Arc::new(BumpTransactionEventHandler::new( + Arc::clone(&broadcaster), + Arc::new(Wallet::new(Arc::clone(&bitcoind_client), Arc::clone(&logger))), + Arc::clone(&keys_manager), + Arc::clone(&logger), + )); + // Step 7: Read ChannelMonitor state from disk let mut channelmonitors = persister.read_channelmonitors(keys_manager.clone(), keys_manager.clone()).unwrap(); @@ -566,6 +607,8 @@ async fn start_ldk() { // Step 11: Initialize the ChannelManager let mut user_config = UserConfig::default(); user_config.channel_handshake_limits.force_announced_channel_preference = false; + user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx = true; + user_config.manually_accept_inbound_channels = true; let mut restarting_node = true; let (channel_manager_blockhash, channel_manager) = { if let Ok(mut f) = fs::File::open(format!("{}/manager", ldk_data_dir.clone())) { @@ -778,6 +821,7 @@ async fn start_ldk() { let bitcoind_client_event_listener = Arc::clone(&bitcoind_client_event_listener); let network_graph_event_listener = Arc::clone(&network_graph_event_listener); let keys_manager_event_listener = Arc::clone(&keys_manager_event_listener); + let bump_tx_event_handler = Arc::clone(&bump_tx_event_handler); let inbound_payments_event_listener = Arc::clone(&inbound_payments_event_listener); let outbound_payments_event_listener = Arc::clone(&outbound_payments_event_listener); let persister_event_listener = Arc::clone(&persister_event_listener); @@ -787,6 +831,7 @@ async fn start_ldk() { &bitcoind_client_event_listener, &network_graph_event_listener, &keys_manager_event_listener, + &bump_tx_event_handler, inbound_payments_event_listener, outbound_payments_event_listener, &persister_event_listener,