Skip to content

Commit 7e4abff

Browse files
committed
Implement Anchor channel event handling
When Anchor outputs need to be spent LDK will generate `BumpTransactionEvent`s. Here, we add the corresponding event-handling and PSBT-signing support.
1 parent fc98e32 commit 7e4abff

File tree

5 files changed

+192
-20
lines changed

5 files changed

+192
-20
lines changed

src/builder.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -693,12 +693,11 @@ fn build_with_store_internal(
693693
// for inbound channels.
694694
let mut user_config = UserConfig::default();
695695
user_config.channel_handshake_limits.force_announced_channel_preference = false;
696-
697-
if !config.trusted_peers_0conf.is_empty() {
698-
// Manually accept inbound channels if we expect 0conf channel requests, avoid
699-
// generating the events otherwise.
700-
user_config.manually_accept_inbound_channels = true;
701-
}
696+
user_config.manually_accept_inbound_channels = true;
697+
// Note the channel_handshake_config will be overwritten in `connect_open_channel`, but we
698+
// still set a default here.
699+
user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx =
700+
config.anchor_channels_config.is_some();
702701

703702
if liquidity_source_config.and_then(|lsc| lsc.lsps2_service.as_ref()).is_some() {
704703
// Generally allow claiming underpaying HTLCs as the LSP will skim off some fee. We'll

src/event.rs

Lines changed: 41 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use crate::types::{DynStore, Sweeper, Wallet};
22

33
use crate::{
4-
hex_utils, ChannelManager, Config, Error, NetworkGraph, PeerInfo, PeerStore, UserChannelId,
4+
hex_utils, BumpTransactionEventHandler, ChannelManager, Config, Error, NetworkGraph, PeerInfo,
5+
PeerStore, UserChannelId,
56
};
67

78
use crate::connection::ConnectionManager;
@@ -15,9 +16,10 @@ use crate::io::{
1516
EVENT_QUEUE_PERSISTENCE_KEY, EVENT_QUEUE_PERSISTENCE_PRIMARY_NAMESPACE,
1617
EVENT_QUEUE_PERSISTENCE_SECONDARY_NAMESPACE,
1718
};
18-
use crate::logger::{log_error, log_info, Logger};
19+
use crate::logger::{log_debug, log_error, log_info, Logger};
1920

2021
use lightning::chain::chaininterface::ConfirmationTarget;
22+
use lightning::events::bump_transaction::BumpTransactionEvent;
2123
use lightning::events::{ClosureReason, PaymentPurpose};
2224
use lightning::events::{Event as LdkEvent, PaymentFailureReason};
2325
use lightning::impl_writeable_tlv_based_enum;
@@ -317,6 +319,7 @@ where
317319
{
318320
event_queue: Arc<EventQueue<L>>,
319321
wallet: Arc<Wallet>,
322+
bump_tx_event_handler: Arc<BumpTransactionEventHandler>,
320323
channel_manager: Arc<ChannelManager>,
321324
connection_manager: Arc<ConnectionManager<L>>,
322325
output_sweeper: Arc<Sweeper>,
@@ -333,15 +336,17 @@ where
333336
L::Target: Logger,
334337
{
335338
pub fn new(
336-
event_queue: Arc<EventQueue<L>>, wallet: Arc<Wallet>, channel_manager: Arc<ChannelManager>,
337-
connection_manager: Arc<ConnectionManager<L>>, output_sweeper: Arc<Sweeper>,
338-
network_graph: Arc<NetworkGraph>, payment_store: Arc<PaymentStore<L>>,
339-
peer_store: Arc<PeerStore<L>>, runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>,
340-
logger: L, config: Arc<Config>,
339+
event_queue: Arc<EventQueue<L>>, wallet: Arc<Wallet>,
340+
bump_tx_event_handler: Arc<BumpTransactionEventHandler>,
341+
channel_manager: Arc<ChannelManager>, connection_manager: Arc<ConnectionManager<L>>,
342+
output_sweeper: Arc<Sweeper>, network_graph: Arc<NetworkGraph>,
343+
payment_store: Arc<PaymentStore<L>>, peer_store: Arc<PeerStore<L>>,
344+
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>, logger: L, config: Arc<Config>,
341345
) -> Self {
342346
Self {
343347
event_queue,
344348
wallet,
349+
bump_tx_event_handler,
345350
channel_manager,
346351
connection_manager,
347352
output_sweeper,
@@ -1018,7 +1023,6 @@ where
10181023
},
10191024
LdkEvent::DiscardFunding { .. } => {},
10201025
LdkEvent::HTLCIntercepted { .. } => {},
1021-
LdkEvent::BumpTransaction(_) => {},
10221026
LdkEvent::InvoiceRequestFailed { payment_id } => {
10231027
log_error!(
10241028
self.logger,
@@ -1062,6 +1066,35 @@ where
10621066
});
10631067
}
10641068
},
1069+
LdkEvent::BumpTransaction(bte) => {
1070+
let (channel_id, counterparty_node_id) = match bte {
1071+
BumpTransactionEvent::ChannelClose {
1072+
ref channel_id,
1073+
ref counterparty_node_id,
1074+
..
1075+
} => (channel_id, counterparty_node_id),
1076+
BumpTransactionEvent::HTLCResolution {
1077+
ref channel_id,
1078+
ref counterparty_node_id,
1079+
..
1080+
} => (channel_id, counterparty_node_id),
1081+
};
1082+
1083+
if let Some(anchor_channels_config) = self.config.anchor_channels_config.as_ref() {
1084+
if anchor_channels_config
1085+
.trusted_peers_no_reserve
1086+
.contains(counterparty_node_id)
1087+
{
1088+
log_debug!(self.logger,
1089+
"Ignoring BumpTransactionEvent for channel {} due to trusted counterparty {}",
1090+
channel_id, counterparty_node_id
1091+
);
1092+
return;
1093+
}
1094+
}
1095+
1096+
self.bump_tx_event_handler.handle_event(&bte);
1097+
},
10651098
}
10661099
}
10671100
}

src/lib.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,15 @@ use payment::store::PaymentStore;
133133
use payment::{Bolt11Payment, Bolt12Payment, OnchainPayment, PaymentDetails, SpontaneousPayment};
134134
use peer_store::{PeerInfo, PeerStore};
135135
use types::{
136-
Broadcaster, ChainMonitor, ChannelManager, DynStore, FeeEstimator, KeysManager, NetworkGraph,
137-
PeerManager, Router, Scorer, Sweeper, Wallet,
136+
Broadcaster, BumpTransactionEventHandler, ChainMonitor, ChannelManager, DynStore, FeeEstimator,
137+
KeysManager, NetworkGraph, PeerManager, Router, Scorer, Sweeper, Wallet,
138138
};
139139
pub use types::{ChannelDetails, PeerDetails, UserChannelId};
140140

141141
use logger::{log_error, log_info, log_trace, FilesystemLogger, Logger};
142142

143143
use lightning::chain::{BestBlock, Confirm};
144+
use lightning::events::bump_transaction::Wallet as LdkWallet;
144145
use lightning::ln::channelmanager::PaymentId;
145146
use lightning::ln::msgs::SocketAddress;
146147

@@ -620,9 +621,17 @@ impl Node {
620621
}
621622
});
622623

624+
let bump_tx_event_handler = Arc::new(BumpTransactionEventHandler::new(
625+
Arc::clone(&self.tx_broadcaster),
626+
Arc::new(LdkWallet::new(Arc::clone(&self.wallet), Arc::clone(&self.logger))),
627+
Arc::clone(&self.keys_manager),
628+
Arc::clone(&self.logger),
629+
));
630+
623631
let event_handler = Arc::new(EventHandler::new(
624632
Arc::clone(&self.event_queue),
625633
Arc::clone(&self.wallet),
634+
bump_tx_event_handler,
626635
Arc::clone(&self.channel_manager),
627636
Arc::clone(&self.connection_manager),
628637
Arc::clone(&self.output_sweeper),

src/types.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,14 @@ pub(crate) type Sweeper = OutputSweeper<
135135
Arc<KeysManager>,
136136
>;
137137

138+
pub(crate) type BumpTransactionEventHandler =
139+
lightning::events::bump_transaction::BumpTransactionEventHandler<
140+
Arc<Broadcaster>,
141+
Arc<lightning::events::bump_transaction::Wallet<Arc<Wallet>, Arc<FilesystemLogger>>>,
142+
Arc<KeysManager>,
143+
Arc<FilesystemLogger>,
144+
>;
145+
138146
/// A local, potentially user-provided, identifier of a channel.
139147
///
140148
/// By default, this will be randomly generated for the user to ensure local uniqueness.

src/wallet.rs

Lines changed: 127 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use crate::Error;
44

55
use lightning::chain::chaininterface::{BroadcasterInterface, ConfirmationTarget, FeeEstimator};
66

7+
use lightning::events::bump_transaction::{Utxo, WalletSource};
78
use lightning::ln::msgs::{DecodeError, UnsignedGossipMessage};
89
use lightning::ln::script::ShutdownScript;
910
use lightning::sign::{
@@ -19,8 +20,14 @@ use bdk::wallet::AddressIndex;
1920
use bdk::FeeRate;
2021
use bdk::{SignOptions, SyncOptions};
2122

23+
use bitcoin::address::{Payload, WitnessVersion};
2224
use bitcoin::bech32::u5;
25+
use bitcoin::blockdata::constants::WITNESS_SCALE_FACTOR;
2326
use bitcoin::blockdata::locktime::absolute::LockTime;
27+
use bitcoin::hash_types::WPubkeyHash;
28+
use bitcoin::hashes::Hash;
29+
use bitcoin::key::XOnlyPublicKey;
30+
use bitcoin::psbt::PartiallySignedTransaction;
2431
use bitcoin::secp256k1::ecdh::SharedSecret;
2532
use bitcoin::secp256k1::ecdsa::{RecoverableSignature, Signature};
2633
use bitcoin::secp256k1::{PublicKey, Scalar, Secp256k1, SecretKey, Signing};
@@ -242,6 +249,123 @@ where
242249
}
243250
}
244251

252+
impl<D, B: Deref, E: Deref, L: Deref> WalletSource for Wallet<D, B, E, L>
253+
where
254+
D: BatchDatabase,
255+
B::Target: BroadcasterInterface,
256+
E::Target: FeeEstimator,
257+
L::Target: Logger,
258+
{
259+
fn list_confirmed_utxos(&self) -> Result<Vec<Utxo>, ()> {
260+
let locked_wallet = self.inner.lock().unwrap();
261+
let mut utxos = Vec::new();
262+
let confirmed_txs: Vec<bdk::TransactionDetails> = locked_wallet
263+
.list_transactions(false)
264+
.map_err(|e| {
265+
log_error!(self.logger, "Failed to retrieve transactions from wallet: {}", e);
266+
})?
267+
.into_iter()
268+
.filter(|t| t.confirmation_time.is_some())
269+
.collect();
270+
let unspent_confirmed_utxos = locked_wallet
271+
.list_unspent()
272+
.map_err(|e| {
273+
log_error!(
274+
self.logger,
275+
"Failed to retrieve unspent transactions from wallet: {}",
276+
e
277+
);
278+
})?
279+
.into_iter()
280+
.filter(|u| confirmed_txs.iter().find(|t| t.txid == u.outpoint.txid).is_some());
281+
282+
for u in unspent_confirmed_utxos {
283+
let payload = Payload::from_script(&u.txout.script_pubkey).map_err(|e| {
284+
log_error!(self.logger, "Failed to retrieve script payload: {}", e);
285+
})?;
286+
287+
match payload {
288+
Payload::WitnessProgram(program) => match program.version() {
289+
WitnessVersion::V0 if program.program().len() == 20 => {
290+
let wpkh =
291+
WPubkeyHash::from_slice(program.program().as_bytes()).map_err(|e| {
292+
log_error!(self.logger, "Failed to retrieve script payload: {}", e);
293+
})?;
294+
let utxo = Utxo::new_v0_p2wpkh(u.outpoint, u.txout.value, &wpkh);
295+
utxos.push(utxo);
296+
},
297+
WitnessVersion::V1 => {
298+
XOnlyPublicKey::from_slice(program.program().as_bytes()).map_err(|e| {
299+
log_error!(self.logger, "Failed to retrieve script payload: {}", e);
300+
})?;
301+
302+
let utxo = Utxo {
303+
outpoint: u.outpoint,
304+
output: TxOut {
305+
value: u.txout.value,
306+
script_pubkey: ScriptBuf::new_witness_program(&program),
307+
},
308+
satisfaction_weight: 1 /* empty script_sig */ * WITNESS_SCALE_FACTOR as u64 +
309+
1 /* witness items */ + 1 /* schnorr sig len */ + 64, /* schnorr sig */
310+
};
311+
utxos.push(utxo);
312+
},
313+
_ => {
314+
log_error!(
315+
self.logger,
316+
"Unexpected witness version or length. Version: {}, Length: {}",
317+
program.version(),
318+
program.program().len()
319+
);
320+
},
321+
},
322+
_ => {
323+
log_error!(
324+
self.logger,
325+
"Tried to use a non-witness script. This must never happen."
326+
);
327+
panic!("Tried to use a non-witness script. This must never happen.");
328+
},
329+
}
330+
}
331+
332+
Ok(utxos)
333+
}
334+
335+
fn get_change_script(&self) -> Result<ScriptBuf, ()> {
336+
let locked_wallet = self.inner.lock().unwrap();
337+
let address_info = locked_wallet.get_address(AddressIndex::LastUnused).map_err(|e| {
338+
log_error!(self.logger, "Failed to retrieve new address from wallet: {}", e);
339+
})?;
340+
341+
Ok(address_info.address.script_pubkey())
342+
}
343+
344+
fn sign_psbt(&self, mut psbt: PartiallySignedTransaction) -> Result<Transaction, ()> {
345+
let locked_wallet = self.inner.lock().unwrap();
346+
347+
// While BDK populates both `witness_utxo` and `non_witness_utxo` fields, LDK does not. As
348+
// BDK by default doesn't trust the witness UTXO to account for the Segwit bug, we must
349+
// disable it here as otherwise we fail to sign.
350+
let mut sign_options = SignOptions::default();
351+
sign_options.trust_witness_utxo = true;
352+
353+
match locked_wallet.sign(&mut psbt, sign_options) {
354+
Ok(_finalized) => {
355+
// BDK will fail to finalize for all LDK-provided inputs of the PSBT. Unfortunately
356+
// we can't check more fine grained if it succeeded for all the other inputs here,
357+
// so we just ignore the returned `finalized` bool.
358+
},
359+
Err(err) => {
360+
log_error!(self.logger, "Failed to sign transaction: {}", err);
361+
return Err(());
362+
},
363+
}
364+
365+
Ok(psbt.extract_tx())
366+
}
367+
}
368+
245369
/// Similar to [`KeysManager`], but overrides the destination and shutdown scripts so they are
246370
/// directly spendable by the BDK wallet.
247371
pub struct WalletKeysManager<D, B: Deref, E: Deref, L: Deref>
@@ -407,11 +531,10 @@ where
407531
})?;
408532

409533
match address.payload {
410-
bitcoin::address::Payload::WitnessProgram(program) => {
411-
ShutdownScript::new_witness_program(&program).map_err(|e| {
534+
Payload::WitnessProgram(program) => ShutdownScript::new_witness_program(&program)
535+
.map_err(|e| {
412536
log_error!(self.logger, "Invalid shutdown script: {:?}", e);
413-
})
414-
},
537+
}),
415538
_ => {
416539
log_error!(
417540
self.logger,

0 commit comments

Comments
 (0)