@@ -17,9 +17,8 @@ use crate::lsps5::msgs::{
17
17
SetWebhookRequest , SetWebhookResponse , WebhookNotification , WebhookNotificationMethod ,
18
18
} ;
19
19
use crate :: message_queue:: MessageQueue ;
20
- use crate :: prelude:: hash_map:: Entry ;
21
20
use crate :: prelude:: * ;
22
- use crate :: sync:: { Arc , Mutex } ;
21
+ use crate :: sync:: { Arc , Mutex , RwLock , RwLockWriteGuard } ;
23
22
use crate :: utils:: time:: TimeProvider ;
24
23
25
24
use bitcoin:: secp256k1:: PublicKey ;
@@ -117,7 +116,7 @@ where
117
116
TP :: Target : TimeProvider ,
118
117
{
119
118
config : LSPS5ServiceConfig ,
120
- webhooks : Mutex < HashMap < PublicKey , HashMap < LSPS5AppName , Webhook > > > ,
119
+ per_peer_state : RwLock < HashMap < PublicKey , PeerState > > ,
121
120
event_queue : Arc < EventQueue > ,
122
121
pending_messages : Arc < MessageQueue > ,
123
122
time_provider : TP ,
@@ -140,7 +139,7 @@ where
140
139
assert ! ( config. max_webhooks_per_client > 0 , "`max_webhooks_per_client` must be > 0" ) ;
141
140
Self {
142
141
config,
143
- webhooks : Mutex :: new ( new_hash_map ( ) ) ,
142
+ per_peer_state : RwLock :: new ( new_hash_map ( ) ) ,
144
143
event_queue,
145
144
pending_messages,
146
145
time_provider,
@@ -150,18 +149,30 @@ where
150
149
}
151
150
}
152
151
153
- fn check_prune_stale_webhooks ( & self ) {
152
+ fn check_prune_stale_webhooks < ' a > (
153
+ & self , outer_state_lock : & mut RwLockWriteGuard < ' a , HashMap < PublicKey , PeerState > > ,
154
+ ) {
155
+ let mut last_pruning = self . last_pruning . lock ( ) . unwrap ( ) ;
154
156
let now =
155
157
LSPSDateTime :: new_from_duration_since_epoch ( self . time_provider . duration_since_epoch ( ) ) ;
156
- let should_prune = {
157
- let last_pruning = self . last_pruning . lock ( ) . unwrap ( ) ;
158
- last_pruning. as_ref ( ) . map_or ( true , |last_time| {
159
- now. duration_since ( & last_time) > PRUNE_STALE_WEBHOOKS_INTERVAL_DAYS
160
- } )
161
- } ;
158
+
159
+ let should_prune = last_pruning. as_ref ( ) . map_or ( true , |last_time| {
160
+ now. duration_since ( & last_time) > PRUNE_STALE_WEBHOOKS_INTERVAL_DAYS
161
+ } ) ;
162
162
163
163
if should_prune {
164
- self . prune_stale_webhooks ( ) ;
164
+ let now = LSPSDateTime :: new_from_duration_since_epoch (
165
+ self . time_provider . duration_since_epoch ( ) ,
166
+ ) ;
167
+
168
+ outer_state_lock. retain ( |client_id, peer_state| {
169
+ if self . client_has_open_channel ( client_id) {
170
+ // Don't prune clients with open channels
171
+ return true ;
172
+ }
173
+ !peer_state. prune_stale_webhooks ( now)
174
+ } ) ;
175
+ * last_pruning = Some ( now) ;
165
176
}
166
177
}
167
178
@@ -171,58 +182,53 @@ where
171
182
) -> Result < ( ) , LightningError > {
172
183
let mut message_queue_notifier = self . pending_messages . notifier ( ) ;
173
184
174
- self . check_prune_stale_webhooks ( ) ;
185
+ let mut outer_state_lock = self . per_peer_state . write ( ) . unwrap ( ) ;
186
+ self . check_prune_stale_webhooks ( & mut outer_state_lock) ;
175
187
176
- let mut webhooks = self . webhooks . lock ( ) . unwrap ( ) ;
188
+ let peer_state_lock =
189
+ outer_state_lock. entry ( counterparty_node_id) . or_insert_with ( PeerState :: default) ;
177
190
178
- let client_webhooks = webhooks. entry ( counterparty_node_id) . or_insert_with ( new_hash_map) ;
179
191
let now =
180
192
LSPSDateTime :: new_from_duration_since_epoch ( self . time_provider . duration_since_epoch ( ) ) ;
181
193
182
- let num_webhooks = client_webhooks . len ( ) ;
194
+ let num_webhooks = peer_state_lock . app_names ( ) . len ( ) ;
183
195
let mut no_change = false ;
184
- match client_webhooks. entry ( params. app_name . clone ( ) ) {
185
- Entry :: Occupied ( mut entry) => {
186
- no_change = entry. get ( ) . url == params. webhook ;
187
- let last_used = if no_change { entry. get ( ) . last_used } else { now } ;
188
- let last_notification_sent = entry. get ( ) . last_notification_sent ;
189
- entry. insert ( Webhook {
190
- _app_name : params. app_name . clone ( ) ,
191
- url : params. webhook . clone ( ) ,
192
- _counterparty_node_id : counterparty_node_id,
193
- last_used,
194
- last_notification_sent,
195
- } ) ;
196
- } ,
197
- Entry :: Vacant ( entry) => {
198
- if num_webhooks >= self . config . max_webhooks_per_client as usize {
199
- let error = LSPS5ProtocolError :: TooManyWebhooks ;
200
- let msg = LSPS5Message :: Response (
201
- request_id,
202
- LSPS5Response :: SetWebhookError ( error. clone ( ) . into ( ) ) ,
203
- )
204
- . into ( ) ;
205
- message_queue_notifier. enqueue ( & counterparty_node_id, msg) ;
206
- return Err ( LightningError {
207
- err : error. message ( ) . into ( ) ,
208
- action : ErrorAction :: IgnoreAndLog ( Level :: Info ) ,
209
- } ) ;
210
- }
211
196
212
- entry. insert ( Webhook {
213
- _app_name : params. app_name . clone ( ) ,
214
- url : params. webhook . clone ( ) ,
215
- _counterparty_node_id : counterparty_node_id,
216
- last_used : now,
217
- last_notification_sent : None ,
197
+ if let Some ( webhook) = peer_state_lock. webhook_mut ( & params. app_name . clone ( ) ) {
198
+ no_change = webhook. url == params. webhook ;
199
+ if !no_change {
200
+ webhook. last_used = now
201
+ }
202
+ } else {
203
+ if num_webhooks >= self . config . max_webhooks_per_client as usize {
204
+ let error = LSPS5ProtocolError :: TooManyWebhooks ;
205
+ let msg = LSPS5Message :: Response (
206
+ request_id,
207
+ LSPS5Response :: SetWebhookError ( error. clone ( ) . into ( ) ) ,
208
+ )
209
+ . into ( ) ;
210
+ message_queue_notifier. enqueue ( & counterparty_node_id, msg) ;
211
+ return Err ( LightningError {
212
+ err : error. message ( ) . into ( ) ,
213
+ action : ErrorAction :: IgnoreAndLog ( Level :: Info ) ,
218
214
} ) ;
219
- } ,
215
+ }
216
+
217
+ let webhook = Webhook {
218
+ _app_name : params. app_name . clone ( ) ,
219
+ url : params. webhook . clone ( ) ,
220
+ _counterparty_node_id : counterparty_node_id,
221
+ last_used : now,
222
+ last_notification_sent : None ,
223
+ } ;
224
+
225
+ peer_state_lock. insert_webhook ( params. app_name . clone ( ) , webhook) ;
220
226
}
221
227
222
228
if !no_change {
223
229
self . send_webhook_registered_notification (
224
230
counterparty_node_id,
225
- params. app_name ,
231
+ params. app_name . clone ( ) ,
226
232
params. webhook ,
227
233
)
228
234
. map_err ( |e| {
@@ -242,7 +248,7 @@ where
242
248
let msg = LSPS5Message :: Response (
243
249
request_id,
244
250
LSPS5Response :: SetWebhook ( SetWebhookResponse {
245
- num_webhooks : client_webhooks . len ( ) as u32 ,
251
+ num_webhooks : peer_state_lock . app_names ( ) . len ( ) as u32 ,
246
252
max_webhooks : self . config . max_webhooks_per_client ,
247
253
no_change,
248
254
} ) ,
@@ -258,14 +264,11 @@ where
258
264
) -> Result < ( ) , LightningError > {
259
265
let mut message_queue_notifier = self . pending_messages . notifier ( ) ;
260
266
261
- self . check_prune_stale_webhooks ( ) ;
262
-
263
- let webhooks = self . webhooks . lock ( ) . unwrap ( ) ;
267
+ let mut outer_state_lock = self . per_peer_state . write ( ) . unwrap ( ) ;
268
+ self . check_prune_stale_webhooks ( & mut outer_state_lock) ;
264
269
265
- let app_names = webhooks
266
- . get ( & counterparty_node_id)
267
- . map ( |client_webhooks| client_webhooks. keys ( ) . cloned ( ) . collect :: < Vec < _ > > ( ) )
268
- . unwrap_or_else ( Vec :: new) ;
270
+ let app_names =
271
+ outer_state_lock. get ( & counterparty_node_id) . map ( |p| p. app_names ( ) ) . unwrap_or_default ( ) ;
269
272
270
273
let max_webhooks = self . config . max_webhooks_per_client ;
271
274
@@ -282,12 +285,11 @@ where
282
285
) -> Result < ( ) , LightningError > {
283
286
let mut message_queue_notifier = self . pending_messages . notifier ( ) ;
284
287
285
- self . check_prune_stale_webhooks ( ) ;
288
+ let mut outer_state_lock = self . per_peer_state . write ( ) . unwrap ( ) ;
289
+ self . check_prune_stale_webhooks ( & mut outer_state_lock) ;
286
290
287
- let mut webhooks = self . webhooks . lock ( ) . unwrap ( ) ;
288
-
289
- if let Some ( client_webhooks) = webhooks. get_mut ( & counterparty_node_id) {
290
- if client_webhooks. remove ( & params. app_name ) . is_some ( ) {
291
+ if let Some ( peer_state) = outer_state_lock. get_mut ( & counterparty_node_id) {
292
+ if peer_state. remove_webhook ( & params. app_name ) {
291
293
let response = RemoveWebhookResponse { } ;
292
294
let msg =
293
295
LSPS5Message :: Response ( request_id, LSPS5Response :: RemoveWebhook ( response) )
@@ -408,11 +410,13 @@ where
408
410
fn send_notifications_to_client_webhooks (
409
411
& self , client_id : PublicKey , notification : WebhookNotification ,
410
412
) -> Result < ( ) , LSPS5ProtocolError > {
411
- let mut webhooks = self . webhooks . lock ( ) . unwrap ( ) ;
413
+ let mut outer_state_lock = self . per_peer_state . write ( ) . unwrap ( ) ;
414
+ self . check_prune_stale_webhooks ( & mut outer_state_lock) ;
412
415
413
- let client_webhooks = match webhooks. get_mut ( & client_id) {
414
- Some ( webhooks) if !webhooks. is_empty ( ) => webhooks,
415
- _ => return Ok ( ( ) ) ,
416
+ let peer_state = if let Some ( peer_state) = outer_state_lock. get_mut ( & client_id) {
417
+ peer_state
418
+ } else {
419
+ return Ok ( ( ) ) ;
416
420
} ;
417
421
418
422
let now =
@@ -421,7 +425,7 @@ where
421
425
// We must avoid sending multiple notifications of the same method
422
426
// (other than lsps5.webhook_registered) close in time.
423
427
if notification. method != WebhookNotificationMethod :: LSPS5WebhookRegistered {
424
- let rate_limit_applies = client_webhooks . iter ( ) . any ( |( _, webhook) | {
428
+ let rate_limit_applies = peer_state . webhooks ( ) . iter ( ) . any ( |( _, webhook) | {
425
429
webhook. last_notification_sent . as_ref ( ) . map_or ( false , |last_sent| {
426
430
now. duration_since ( & last_sent) < NOTIFICATION_COOLDOWN_TIME
427
431
} )
@@ -432,7 +436,7 @@ where
432
436
}
433
437
}
434
438
435
- for ( app_name, webhook) in client_webhooks . iter_mut ( ) {
439
+ for ( app_name, webhook) in peer_state . webhooks_mut ( ) . iter_mut ( ) {
436
440
webhook. last_used = now;
437
441
webhook. last_notification_sent = Some ( now) ;
438
442
self . send_notification (
@@ -490,26 +494,6 @@ where
490
494
. map_err ( |_| LSPS5ProtocolError :: UnknownError )
491
495
}
492
496
493
- fn prune_stale_webhooks ( & self ) {
494
- let now =
495
- LSPSDateTime :: new_from_duration_since_epoch ( self . time_provider . duration_since_epoch ( ) ) ;
496
- let mut webhooks = self . webhooks . lock ( ) . unwrap ( ) ;
497
-
498
- webhooks. retain ( |client_id, client_webhooks| {
499
- if !self . client_has_open_channel ( client_id) {
500
- client_webhooks. retain ( |_, webhook| {
501
- now. duration_since ( & webhook. last_used ) < MIN_WEBHOOK_RETENTION_DAYS
502
- } ) ;
503
- !client_webhooks. is_empty ( )
504
- } else {
505
- true
506
- }
507
- } ) ;
508
-
509
- let mut last_pruning = self . last_pruning . lock ( ) . unwrap ( ) ;
510
- * last_pruning = Some ( now) ;
511
- }
512
-
513
497
fn client_has_open_channel ( & self , client_id : & PublicKey ) -> bool {
514
498
self . channel_manager
515
499
. get_cm ( )
@@ -519,20 +503,16 @@ where
519
503
}
520
504
521
505
pub ( crate ) fn peer_connected ( & self , counterparty_node_id : & PublicKey ) {
522
- let mut webhooks = self . webhooks . lock ( ) . unwrap ( ) ;
523
- if let Some ( client_webhooks) = webhooks. get_mut ( counterparty_node_id) {
524
- for webhook in client_webhooks. values_mut ( ) {
525
- webhook. last_notification_sent = None ;
526
- }
506
+ let mut outer_state_lock = self . per_peer_state . write ( ) . unwrap ( ) ;
507
+ if let Some ( peer_state) = outer_state_lock. get_mut ( counterparty_node_id) {
508
+ peer_state. reset_notification_cooldown ( ) ;
527
509
}
528
510
}
529
511
530
512
pub ( crate ) fn peer_disconnected ( & self , counterparty_node_id : & PublicKey ) {
531
- let mut webhooks = self . webhooks . lock ( ) . unwrap ( ) ;
532
- if let Some ( client_webhooks) = webhooks. get_mut ( counterparty_node_id) {
533
- for webhook in client_webhooks. values_mut ( ) {
534
- webhook. last_notification_sent = None ;
535
- }
513
+ let mut outer_state_lock = self . per_peer_state . write ( ) . unwrap ( ) ;
514
+ if let Some ( peer_state) = outer_state_lock. get_mut ( counterparty_node_id) {
515
+ peer_state. reset_notification_cooldown ( ) ;
536
516
}
537
517
}
538
518
}
@@ -578,3 +558,66 @@ where
578
558
}
579
559
}
580
560
}
561
+
562
+ #[ derive( Debug , Default ) ]
563
+ struct PeerState {
564
+ webhooks : Vec < ( LSPS5AppName , Webhook ) > ,
565
+ }
566
+
567
+ impl PeerState {
568
+ fn webhook_mut ( & mut self , name : & LSPS5AppName ) -> Option < & mut Webhook > {
569
+ self . webhooks . iter_mut ( ) . find_map ( |( n, h) | if n == name { Some ( h) } else { None } )
570
+ }
571
+
572
+ fn webhooks ( & self ) -> & Vec < ( LSPS5AppName , Webhook ) > {
573
+ & self . webhooks
574
+ }
575
+
576
+ fn webhooks_mut ( & mut self ) -> & mut Vec < ( LSPS5AppName , Webhook ) > {
577
+ & mut self . webhooks
578
+ }
579
+
580
+ fn app_names ( & self ) -> Vec < LSPS5AppName > {
581
+ self . webhooks . iter ( ) . map ( |( n, _) | n) . cloned ( ) . collect ( )
582
+ }
583
+
584
+ fn insert_webhook ( & mut self , name : LSPS5AppName , hook : Webhook ) -> bool {
585
+ for ( n, h) in self . webhooks . iter_mut ( ) {
586
+ if * n == name {
587
+ * h = hook;
588
+ return true ;
589
+ }
590
+ }
591
+
592
+ self . webhooks . push ( ( name, hook) ) ;
593
+ false
594
+ }
595
+
596
+ fn remove_webhook ( & mut self , name : & LSPS5AppName ) -> bool {
597
+ let mut removed = false ;
598
+ self . webhooks . retain ( |( n, _) | {
599
+ if n != name {
600
+ true
601
+ } else {
602
+ removed = true ;
603
+ false
604
+ }
605
+ } ) ;
606
+ removed
607
+ }
608
+
609
+ fn reset_notification_cooldown ( & mut self ) {
610
+ for ( _, h) in self . webhooks . iter_mut ( ) {
611
+ h. last_notification_sent = None ;
612
+ }
613
+ }
614
+
615
+ // Returns whether the entire state is empty and can be pruned.
616
+ fn prune_stale_webhooks ( & mut self , now : LSPSDateTime ) -> bool {
617
+ self . webhooks . retain ( |( _, webhook) | {
618
+ now. duration_since ( & webhook. last_used ) < MIN_WEBHOOK_RETENTION_DAYS
619
+ } ) ;
620
+
621
+ self . webhooks . is_empty ( )
622
+ }
623
+ }
0 commit comments