From bb6f5c2990dbbc62c068e0037fb74b04e5722e41 Mon Sep 17 00:00:00 2001 From: Elias Rohrer Date: Fri, 21 Apr 2023 15:33:42 +0200 Subject: [PATCH] Allow for trusted inbound 0conf channels --- bindings/ldk_node.udl | 1 + src/event.rs | 53 +++++++++++++++++++++++++++++++++--- src/lib.rs | 13 +++++++++ src/peer_store.rs | 4 +++ src/test/functional_tests.rs | 50 ++++++++++++++++++++++++++++++---- 5 files changed, 112 insertions(+), 9 deletions(-) diff --git a/bindings/ldk_node.udl b/bindings/ldk_node.udl index 9ceae441b..c78de15e7 100644 --- a/bindings/ldk_node.udl +++ b/bindings/ldk_node.udl @@ -6,6 +6,7 @@ dictionary Config { Network network; NetAddress? listening_address; u32 default_cltv_expiry_delta; + sequence peers_trusted_0conf; }; interface Builder { diff --git a/src/event.rs b/src/event.rs index 7e97fc49f..e4a1c294c 100644 --- a/src/event.rs +++ b/src/event.rs @@ -231,7 +231,7 @@ where payment_store: Arc>, runtime: Arc>>, logger: L, - _config: Arc, + config: Arc, } impl EventHandler @@ -242,7 +242,7 @@ where wallet: Arc>, event_queue: Arc>, channel_manager: Arc>, network_graph: Arc, keys_manager: Arc, payment_store: Arc>, - runtime: Arc>>, logger: L, _config: Arc, + runtime: Arc>>, logger: L, config: Arc, ) -> Self { Self { event_queue, @@ -253,7 +253,7 @@ where payment_store, logger, runtime, - _config, + config, } } @@ -544,7 +544,52 @@ where } } } - LdkEvent::OpenChannelRequest { .. } => {} + LdkEvent::OpenChannelRequest { + temporary_channel_id, + counterparty_node_id, + funding_satoshis, + channel_type: _, + push_msat: _, + } => { + let user_channel_id: u128 = rand::thread_rng().gen::(); + let allow_0conf = self.config.peers_trusted_0conf.contains(&counterparty_node_id); + let res = if allow_0conf { + self.channel_manager.accept_inbound_channel_from_trusted_peer_0conf( + &temporary_channel_id, + &counterparty_node_id, + user_channel_id, + ) + } else { + self.channel_manager.accept_inbound_channel( + &temporary_channel_id, + &counterparty_node_id, + user_channel_id, + ) + }; + + match res { + Ok(()) => { + log_info!( + self.logger, + "Accepting inbound{} channel of {}sats from{} peer {}", + if allow_0conf { " 0conf" } else { "" }, + funding_satoshis, + if allow_0conf { " trusted" } else { "" }, + counterparty_node_id, + ); + } + Err(e) => { + log_error!( + self.logger, + "Error while accepting inbound{} channel from{} peer {}: {:?}", + if allow_0conf { " 0conf" } else { "" }, + counterparty_node_id, + if allow_0conf { " trusted" } else { "" }, + e, + ); + } + } + } LdkEvent::PaymentForwarded { prev_channel_id, next_channel_id, diff --git a/src/lib.rs b/src/lib.rs index 3a0526f23..fbd0a0460 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -207,6 +207,7 @@ const WALLET_KEYS_SEED_LEN: usize = 64; /// | `network` | Network::Bitcoin | /// | `listening_address` | 0.0.0.0:9735 | /// | `default_cltv_expiry_delta` | 144 | +/// | `peers_trusted_0conf` | [] | /// pub struct Config { /// The path where the underlying LDK and BDK persist their data. @@ -217,6 +218,12 @@ pub struct Config { pub listening_address: Option, /// The default CLTV expiry delta to be used for payments. pub default_cltv_expiry_delta: u32, + /// A list of peers which we allow to establish zero confirmation channels to us. + /// + /// **Note:** Allowing payments via zero-confirmation channels is potentially insecure if the + /// funding transaction ends up never being confirmed on-chain. Zero-confirmation channels + /// should therefore only be accepted from trusted peers. + pub peers_trusted_0conf: Vec, } impl Default for Config { @@ -226,6 +233,7 @@ impl Default for Config { network: DEFAULT_NETWORK, listening_address: Some(DEFAULT_LISTENING_ADDR.parse().unwrap()), default_cltv_expiry_delta: DEFAULT_CLTV_EXPIRY_DELTA, + peers_trusted_0conf: Vec::new(), } } } @@ -519,6 +527,11 @@ impl Builder { // Initialize the ChannelManager let mut user_config = UserConfig::default(); user_config.channel_handshake_limits.force_announced_channel_preference = false; + if !config.peers_trusted_0conf.is_empty() { + // Manually accept inbound channels if we expect 0conf channel requests, avoid + // generating the events otherwise. + user_config.manually_accept_inbound_channels = true; + } let channel_manager = { if let Ok(mut reader) = kv_store .read(CHANNEL_MANAGER_PERSISTENCE_NAMESPACE, CHANNEL_MANAGER_PERSISTENCE_KEY) diff --git a/src/peer_store.rs b/src/peer_store.rs index d22560aa1..cae22d22f 100644 --- a/src/peer_store.rs +++ b/src/peer_store.rs @@ -32,6 +32,10 @@ where pub(crate) fn add_peer(&self, peer_info: PeerInfo) -> Result<(), Error> { let mut locked_peers = self.peers.write().unwrap(); + if locked_peers.contains_key(&peer_info.node_id) { + return Ok(()); + } + locked_peers.insert(peer_info.node_id, peer_info); self.persist_peers(&*locked_peers) } diff --git a/src/test/functional_tests.rs b/src/test/functional_tests.rs index 39da1a634..73e641655 100644 --- a/src/test/functional_tests.rs +++ b/src/test/functional_tests.rs @@ -1,8 +1,13 @@ +use crate::io::KVStore; use crate::test::utils::*; use crate::test::utils::{expect_event, random_config}; -use crate::{Builder, Error, Event, PaymentDirection, PaymentStatus}; +use crate::{Builder, Error, Event, Node, PaymentDirection, PaymentStatus}; use bitcoin::Amount; +use electrsd::bitcoind::BitcoinD; +use electrsd::ElectrsD; + +use std::sync::Arc; #[test] fn channel_full_cycle() { @@ -14,7 +19,6 @@ fn channel_full_cycle() { builder_a.set_esplora_server(esplora_url.clone()); let node_a = builder_a.build(); node_a.start().unwrap(); - let addr_a = node_a.new_funding_address().unwrap(); println!("\n== Node B =="); let config_b = random_config(); @@ -22,6 +26,39 @@ fn channel_full_cycle() { builder_b.set_esplora_server(esplora_url); let node_b = builder_b.build(); node_b.start().unwrap(); + + do_channel_full_cycle(node_a, node_b, &bitcoind, &electrsd, false); +} + +#[test] +fn channel_full_cycle_0conf() { + let (bitcoind, electrsd) = setup_bitcoind_and_electrsd(); + println!("== Node A =="); + let esplora_url = electrsd.esplora_url.as_ref().unwrap(); + let config_a = random_config(); + let builder_a = Builder::from_config(config_a); + builder_a.set_esplora_server(esplora_url.clone()); + let node_a = builder_a.build(); + node_a.start().unwrap(); + + println!("\n== Node B =="); + let mut config_b = random_config(); + config_b.peers_trusted_0conf.push(node_a.node_id()); + + let builder_b = Builder::from_config(config_b); + builder_b.set_esplora_server(esplora_url.clone()); + let node_b = builder_a.build(); + + node_b.start().unwrap(); + + do_channel_full_cycle(node_a, node_b, &bitcoind, &electrsd, true) +} + +fn do_channel_full_cycle( + node_a: Arc>, node_b: Arc>, bitcoind: &BitcoinD, electrsd: &ElectrsD, + allow_0conf: bool, +) { + let addr_a = node_a.new_funding_address().unwrap(); let addr_b = node_b.new_funding_address().unwrap(); let premine_amount_sat = 100_000; @@ -71,8 +108,11 @@ fn channel_full_cycle() { wait_for_tx(&electrsd, funding_txo.txid); - println!("\n .. generating blocks, syncing wallets .. "); - generate_blocks_and_wait(&bitcoind, &electrsd, 6); + if !allow_0conf { + println!("\n .. generating blocks .."); + generate_blocks_and_wait(&bitcoind, &electrsd, 6); + } + node_a.sync_wallets().unwrap(); node_b.sync_wallets().unwrap(); @@ -262,7 +302,7 @@ fn channel_open_fails_when_funds_insufficient() { node_b.listening_address().unwrap().into(), 120000, None, - true + true, ) ); }