@@ -30,9 +30,9 @@ use graduated_rebalancer::{LightningBalance, ReceivedLightningPayment};
3030use std:: collections:: HashMap ;
3131use std:: fmt:: Debug ;
3232use std:: pin:: Pin ;
33- use std:: sync:: Arc ;
33+ use std:: sync:: { Arc , Mutex } ;
3434use std:: time:: SystemTime ;
35- use tokio:: sync:: watch;
35+ use tokio:: sync:: { Notify , watch} ;
3636
3737#[ derive( Debug , Clone , Copy ) ]
3838pub ( crate ) struct LightningWalletBalance {
@@ -46,11 +46,27 @@ pub(crate) struct LightningWalletImpl {
4646 store : Arc < dyn DynStore > ,
4747 payment_receipt_flag : watch:: Receiver < ( ) > ,
4848 channel_pending_receipt_flag : watch:: Receiver < u128 > ,
49- splice_pending_receipt_flag : watch :: Receiver < u128 > ,
49+ splice_pending_inbox : Arc < SplicePendingInbox > ,
5050 lsp_node_id : PublicKey ,
5151 lsp_socket_addr : SocketAddress ,
5252}
5353
54+ /// One pending `SplicePending` event per `user_channel_id`, consumed by
55+ /// `await_splice_pending`. A queue rather than a `watch` so each consumer takes its own event:
56+ /// `watch` would let a second splice on the same channel observe the previous splice's stale
57+ /// outpoint instead of waiting for the new `SplicePending`.
58+ pub ( crate ) struct SplicePendingInbox {
59+ pub ( crate ) pending : Mutex < HashMap < u128 , OutPoint > > ,
60+ pub ( crate ) notify : Notify ,
61+ }
62+
63+ impl SplicePendingInbox {
64+ pub ( crate ) fn deliver ( & self , channel_id : u128 , funding_txo : OutPoint ) {
65+ self . pending . lock ( ) . unwrap ( ) . insert ( channel_id, funding_txo) ;
66+ self . notify . notify_waiters ( ) ;
67+ }
68+ }
69+
5470pub ( crate ) struct LightningWallet {
5571 pub ( crate ) inner : Arc < LightningWalletImpl > ,
5672}
@@ -176,14 +192,17 @@ impl LightningWallet {
176192 Arc :: new ( builder. build_with_store ( node_entropy, LdkNodeStore ( Arc :: clone ( & store) ) ) ?) ;
177193 let ( payment_receipt_sender, payment_receipt_flag) = watch:: channel ( ( ) ) ;
178194 let ( channel_pending_sender, channel_pending_receipt_flag) = watch:: channel ( 0 ) ;
179- let ( splice_pending_sender, splice_pending_receipt_flag) = watch:: channel ( 0 ) ;
195+ let splice_pending_inbox = Arc :: new ( SplicePendingInbox {
196+ pending : Mutex :: new ( HashMap :: new ( ) ) ,
197+ notify : Notify :: new ( ) ,
198+ } ) ;
180199 let ev_handler = Arc :: new ( LdkEventHandler {
181200 event_queue,
182201 ldk_node : Arc :: clone ( & ldk_node) ,
183202 tx_metadata,
184203 payment_receipt_sender,
185204 channel_pending_sender,
186- splice_pending_sender ,
205+ splice_pending_inbox : Arc :: clone ( & splice_pending_inbox ) ,
187206 logger : Arc :: clone ( & logger) ,
188207 } ) ;
189208 let inner = Arc :: new ( LightningWalletImpl {
@@ -192,7 +211,7 @@ impl LightningWallet {
192211 store,
193212 payment_receipt_flag,
194213 channel_pending_receipt_flag,
195- splice_pending_receipt_flag ,
214+ splice_pending_inbox ,
196215 lsp_node_id,
197216 lsp_socket_addr,
198217 } ) ;
@@ -222,10 +241,19 @@ impl LightningWallet {
222241 flag. wait_for ( |t| t == & channel_id) . await . expect ( "channel pending not received" ) ;
223242 }
224243
225- pub ( crate ) async fn await_splice_pending ( & self , channel_id : u128 ) {
226- let mut flag = self . inner . splice_pending_receipt_flag . clone ( ) ;
227- flag. mark_unchanged ( ) ;
228- flag. wait_for ( |t| t == & channel_id) . await . expect ( "splice pending not received" ) ;
244+ pub ( crate ) async fn await_splice_pending ( & self , channel_id : u128 ) -> OutPoint {
245+ let inbox = & self . inner . splice_pending_inbox ;
246+ loop {
247+ // Register interest BEFORE checking the queue so a `notify_waiters` racing with the
248+ // check still wakes us up.
249+ let notified = inbox. notify . notified ( ) ;
250+ tokio:: pin!( notified) ;
251+ notified. as_mut ( ) . enable ( ) ;
252+ if let Some ( txo) = inbox. pending . lock ( ) . unwrap ( ) . remove ( & channel_id) {
253+ return txo;
254+ }
255+ notified. await ;
256+ }
229257 }
230258
231259 pub ( crate ) fn get_on_chain_address ( & self ) -> Result < Address , NodeError > {
@@ -343,53 +371,28 @@ impl LightningWallet {
343371 amount_sats,
344372 ) ?;
345373
346- loop {
374+ let funding_txo =
347375 self . await_splice_pending ( chan. user_channel_id . 0 ) . await ;
348- let channels = self . inner . ldk_node . list_channels ( ) ;
349- let new_chan = channels
350- . iter ( )
351- . find ( |c| c. user_channel_id == chan. user_channel_id ) ;
352- match new_chan {
353- Some ( c) => {
354- if c. funding_txo
355- . is_some_and ( |f| f != chan. funding_txo . unwrap ( ) )
356- {
357- let funding_txo = c. funding_txo . unwrap ( ) ;
358-
359- let id = PaymentId ( funding_txo. txid . to_byte_array ( ) ) ;
360- let details = PaymentDetails {
361- id,
362- kind : PaymentKind :: Onchain {
363- txid : funding_txo. txid ,
364- status : ConfirmationStatus :: Unconfirmed , // todo how do we update this?
365- } ,
366- amount_msat : Some ( amount_sats * 1_000 ) ,
367- fee_paid_msat : Some ( 69 ) , // todo get real fee
368- direction : PaymentDirection :: Outbound ,
369- status : PaymentStatus :: Succeeded ,
370- latest_update_timestamp : SystemTime :: now ( )
371- . duration_since ( SystemTime :: UNIX_EPOCH )
372- . unwrap ( )
373- . as_secs ( ) ,
374- } ;
375-
376- store:: write_splice_out (
377- self . inner . store . as_ref ( ) ,
378- & details,
379- )
380- . await ;
381- return Ok ( id) ;
382- }
383- } ,
384- None => {
385- log_error ! (
386- self . inner. logger,
387- "Channel disappeared while awaiting splice out"
388- ) ;
389- return Err ( NodeError :: WalletOperationFailed ) ;
390- } ,
391- }
392- }
376+
377+ let id = PaymentId ( funding_txo. txid . to_byte_array ( ) ) ;
378+ let details = PaymentDetails {
379+ id,
380+ kind : PaymentKind :: Onchain {
381+ txid : funding_txo. txid ,
382+ status : ConfirmationStatus :: Unconfirmed , // todo how do we update this?
383+ } ,
384+ amount_msat : Some ( amount_sats * 1_000 ) ,
385+ fee_paid_msat : Some ( 69 ) , // todo get real fee
386+ direction : PaymentDirection :: Outbound ,
387+ status : PaymentStatus :: Succeeded ,
388+ latest_update_timestamp : SystemTime :: now ( )
389+ . duration_since ( SystemTime :: UNIX_EPOCH )
390+ . unwrap ( )
391+ . as_secs ( ) ,
392+ } ;
393+
394+ store:: write_splice_out ( self . inner . store . as_ref ( ) , & details) . await ;
395+ Ok ( id)
393396 } ,
394397 }
395398 }
@@ -587,26 +590,10 @@ impl graduated_rebalancer::LightningWallet for LightningWallet {
587590 fn await_splice_pending (
588591 & self , channel_id : u128 ,
589592 ) -> Pin < Box < dyn Future < Output = OutPoint > + Send + ' _ > > {
590- Box :: pin ( async move {
591- // todo since we can't see if we have any active splices, we just await the next splice pending event
592- // this is kinda race-y hopefully we can fix
593- self . await_splice_pending ( channel_id) . await ;
594- loop {
595- let channels = self . inner . ldk_node . list_channels ( ) ;
596- let chan = channels
597- . into_iter ( )
598- . find ( |c| c. user_channel_id . 0 == channel_id && c. funding_txo . is_some ( ) ) ;
599- match chan {
600- Some ( c) => {
601- return c. funding_txo . expect ( "channel has no funding txo" ) ;
602- } ,
603- None => {
604- self . await_splice_pending ( channel_id) . await ;
605- // Wait for the next channel pending event
606- } ,
607- }
608- }
609- } )
593+ // `ChannelDetails.funding_txo` from `list_channels` still reports the old funding
594+ // outpoint between `SplicePending` and `SpliceLocked`, so we return the new outpoint
595+ // from the event itself rather than reading it back from the channel.
596+ Box :: pin ( async move { self . await_splice_pending ( channel_id) . await } )
610597 }
611598}
612599
0 commit comments