Skip to content

Commit e4e69e9

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 788ed65 commit e4e69e9

File tree

5 files changed

+187
-19
lines changed

5 files changed

+187
-19
lines changed

src/builder.rs

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

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

src/event.rs

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::types::{DynStore, Sweeper, Wallet};
22
use crate::{
3-
hex_utils, ChannelManager, Config, Error, NetworkGraph, PeerInfo, PeerStore, UserChannelId,
3+
hex_utils, BumpTransactionEventHandler, ChannelManager, Config, Error, NetworkGraph, PeerInfo,
4+
PeerStore, UserChannelId,
45
};
56

67
use crate::payment::payment_store::{
@@ -12,9 +13,10 @@ use crate::io::{
1213
EVENT_QUEUE_PERSISTENCE_KEY, EVENT_QUEUE_PERSISTENCE_PRIMARY_NAMESPACE,
1314
EVENT_QUEUE_PERSISTENCE_SECONDARY_NAMESPACE,
1415
};
15-
use crate::logger::{log_error, log_info, Logger};
16+
use crate::logger::{log_debug, log_error, log_info, Logger};
1617

1718
use lightning::chain::chaininterface::ConfirmationTarget;
19+
use lightning::events::bump_transaction::BumpTransactionEvent;
1820
use lightning::events::{ClosureReason, PaymentPurpose};
1921
use lightning::events::{Event as LdkEvent, PaymentFailureReason};
2022
use lightning::impl_writeable_tlv_based_enum;
@@ -314,6 +316,7 @@ where
314316
{
315317
event_queue: Arc<EventQueue<L>>,
316318
wallet: Arc<Wallet>,
319+
bump_tx_event_handler: Arc<BumpTransactionEventHandler>,
317320
channel_manager: Arc<ChannelManager>,
318321
output_sweeper: Arc<Sweeper>,
319322
network_graph: Arc<NetworkGraph>,
@@ -329,14 +332,17 @@ where
329332
L::Target: Logger,
330333
{
331334
pub fn new(
332-
event_queue: Arc<EventQueue<L>>, wallet: Arc<Wallet>, channel_manager: Arc<ChannelManager>,
333-
output_sweeper: Arc<Sweeper>, network_graph: Arc<NetworkGraph>,
334-
payment_store: Arc<PaymentStore<L>>, peer_store: Arc<PeerStore<L>>,
335-
runtime: Arc<RwLock<Option<tokio::runtime::Runtime>>>, logger: L, config: Arc<Config>,
335+
event_queue: Arc<EventQueue<L>>, wallet: Arc<Wallet>,
336+
bump_tx_event_handler: Arc<BumpTransactionEventHandler>,
337+
channel_manager: Arc<ChannelManager>, 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>,
336341
) -> Self {
337342
Self {
338343
event_queue,
339344
wallet,
345+
bump_tx_event_handler,
340346
channel_manager,
341347
output_sweeper,
342348
network_graph,
@@ -930,7 +936,35 @@ where
930936
},
931937
LdkEvent::DiscardFunding { .. } => {},
932938
LdkEvent::HTLCIntercepted { .. } => {},
933-
LdkEvent::BumpTransaction(_) => {},
939+
LdkEvent::BumpTransaction(bte) => {
940+
let (channel_id, counterparty_node_id) = match bte {
941+
BumpTransactionEvent::ChannelClose {
942+
ref channel_id,
943+
ref counterparty_node_id,
944+
..
945+
} => (channel_id, counterparty_node_id),
946+
BumpTransactionEvent::HTLCResolution {
947+
ref channel_id,
948+
ref counterparty_node_id,
949+
..
950+
} => (channel_id, counterparty_node_id),
951+
};
952+
953+
if let Some(anchor_channels_config) = self.config.anchor_channels_config.as_ref() {
954+
if anchor_channels_config
955+
.trusted_peers_no_reserve
956+
.contains(counterparty_node_id)
957+
{
958+
log_debug!(self.logger,
959+
"Ignoring BumpTransactionEvent for channel {} due to trusted counterparty {}",
960+
channel_id, counterparty_node_id
961+
);
962+
return;
963+
}
964+
}
965+
966+
self.bump_tx_event_handler.handle_event(&bte);
967+
},
934968
LdkEvent::InvoiceRequestFailed { .. } => {},
935969
LdkEvent::InvoiceGenerated { .. } => {},
936970
LdkEvent::ConnectionNeeded { .. } => {},

src/lib.rs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,15 @@ use payment::payment_store::PaymentStore;
133133
use payment::{Bolt11Payment, 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, ChannelType, 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

@@ -617,9 +618,17 @@ impl Node {
617618
}
618619
});
619620

621+
let bump_tx_event_handler = Arc::new(BumpTransactionEventHandler::new(
622+
Arc::clone(&self.tx_broadcaster),
623+
Arc::new(LdkWallet::new(Arc::clone(&self.wallet), Arc::clone(&self.logger))),
624+
Arc::clone(&self.keys_manager),
625+
Arc::clone(&self.logger),
626+
));
627+
620628
let event_handler = Arc::new(EventHandler::new(
621629
Arc::clone(&self.event_queue),
622630
Arc::clone(&self.wallet),
631+
bump_tx_event_handler,
623632
Arc::clone(&self.channel_manager),
624633
Arc::clone(&self.output_sweeper),
625634
Arc::clone(&self.network_graph),

src/types.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,14 @@ pub(crate) type Sweeper = OutputSweeper<
143143
Arc<FilesystemLogger>,
144144
>;
145145

146+
pub(crate) type BumpTransactionEventHandler =
147+
lightning::events::bump_transaction::BumpTransactionEventHandler<
148+
Arc<Broadcaster>,
149+
Arc<lightning::events::bump_transaction::Wallet<Arc<Wallet>, Arc<FilesystemLogger>>>,
150+
Arc<KeysManager>,
151+
Arc<FilesystemLogger>,
152+
>;
153+
146154
/// A local, potentially user-provided, identifier of a channel.
147155
///
148156
/// By default, this will be randomly generated for the user to ensure local uniqueness.

src/wallet.rs

Lines changed: 122 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};
@@ -245,6 +252,118 @@ where
245252
}
246253
}
247254

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

404523
match address.payload {
405-
bitcoin::address::Payload::WitnessProgram(program) => {
406-
ShutdownScript::new_witness_program(&program).map_err(|e| {
524+
Payload::WitnessProgram(program) => ShutdownScript::new_witness_program(&program)
525+
.map_err(|e| {
407526
log_error!(self.logger, "Invalid shutdown script: {:?}", e);
408-
})
409-
},
527+
}),
410528
_ => {
411529
log_error!(
412530
self.logger,

0 commit comments

Comments
 (0)