@@ -329,10 +329,14 @@ where
329329 /// This builds a [`WebhookNotificationMethod::LSPS5PaymentIncoming`] webhook notification, signs it with your
330330 /// node key, and enqueues HTTP POSTs to all registered webhook URLs for that client.
331331 ///
332+ /// This may fail if a similar notification was sent too recently,
333+ /// violating the notification cooldown period defined in [`DEFAULT_NOTIFICATION_COOLDOWN_HOURS`].
334+ ///
332335 /// # Parameters
333336 /// - `client_id`: the client's node-ID whose webhooks should be invoked.
334337 ///
335338 /// [`WebhookNotificationMethod::LSPS5PaymentIncoming`]: super::msgs::WebhookNotificationMethod::LSPS5PaymentIncoming
339+ /// [`DEFAULT_NOTIFICATION_COOLDOWN_HOURS`]: super::service::DEFAULT_NOTIFICATION_COOLDOWN_HOURS
336340 pub fn notify_payment_incoming ( & self , client_id : PublicKey ) -> Result < ( ) , LSPS5ProtocolError > {
337341 let notification = WebhookNotification :: payment_incoming ( ) ;
338342 self . send_notifications_to_client_webhooks ( client_id, notification)
@@ -346,11 +350,15 @@ where
346350 /// the `timeout` block height, signs it, and enqueues HTTP POSTs to the client's
347351 /// registered webhooks.
348352 ///
353+ /// This may fail if a similar notification was sent too recently,
354+ /// violating the notification cooldown period defined in [`DEFAULT_NOTIFICATION_COOLDOWN_HOURS`].
355+ ///
349356 /// # Parameters
350357 /// - `client_id`: the client's node-ID whose webhooks should be invoked.
351358 /// - `timeout`: the block height at which the channel contract will expire.
352359 ///
353360 /// [`WebhookNotificationMethod::LSPS5ExpirySoon`]: super::msgs::WebhookNotificationMethod::LSPS5ExpirySoon
361+ /// [`DEFAULT_NOTIFICATION_COOLDOWN_HOURS`]: super::service::DEFAULT_NOTIFICATION_COOLDOWN_HOURS
354362 pub fn notify_expiry_soon (
355363 & self , client_id : PublicKey , timeout : u32 ,
356364 ) -> Result < ( ) , LSPS5ProtocolError > {
@@ -364,10 +372,14 @@ where
364372 /// liquidity for `client_id`. Builds a [`WebhookNotificationMethod::LSPS5LiquidityManagementRequest`] notification,
365373 /// signs it, and sends it to all of the client's registered webhook URLs.
366374 ///
375+ /// This may fail if a similar notification was sent too recently,
376+ /// violating the notification cooldown period defined in [`DEFAULT_NOTIFICATION_COOLDOWN_HOURS`].
377+ ///
367378 /// # Parameters
368379 /// - `client_id`: the client's node-ID whose webhooks should be invoked.
369380 ///
370381 /// [`WebhookNotificationMethod::LSPS5LiquidityManagementRequest`]: super::msgs::WebhookNotificationMethod::LSPS5LiquidityManagementRequest
382+ /// [`DEFAULT_NOTIFICATION_COOLDOWN_HOURS`]: super::service::DEFAULT_NOTIFICATION_COOLDOWN_HOURS
371383 pub fn notify_liquidity_management_request (
372384 & self , client_id : PublicKey ,
373385 ) -> Result < ( ) , LSPS5ProtocolError > {
@@ -381,10 +393,14 @@ where
381393 /// for `client_id` while the client is offline. Builds a [`WebhookNotificationMethod::LSPS5OnionMessageIncoming`]
382394 /// notification, signs it, and enqueues HTTP POSTs to each registered webhook.
383395 ///
396+ /// This may fail if a similar notification was sent too recently,
397+ /// violating the notification cooldown period defined in [`DEFAULT_NOTIFICATION_COOLDOWN_HOURS`].
398+ ///
384399 /// # Parameters
385400 /// - `client_id`: the client's node-ID whose webhooks should be invoked.
386401 ///
387402 /// [`WebhookNotificationMethod::LSPS5OnionMessageIncoming`]: super::msgs::WebhookNotificationMethod::LSPS5OnionMessageIncoming
403+ /// [`DEFAULT_NOTIFICATION_COOLDOWN_HOURS`]: super::service::DEFAULT_NOTIFICATION_COOLDOWN_HOURS
388404 pub fn notify_onion_message_incoming (
389405 & self , client_id : PublicKey ,
390406 ) -> Result < ( ) , LSPS5ProtocolError > {
@@ -405,23 +421,34 @@ where
405421 let now =
406422 LSPSDateTime :: new_from_duration_since_epoch ( self . time_provider . duration_since_epoch ( ) ) ;
407423
408- for ( app_name , webhook ) in client_webhooks . iter_mut ( ) {
409- if webhook
410- . last_notification_sent
411- . get ( & notification . method )
412- . map ( |last_sent| now . clone ( ) . abs_diff ( & last_sent ) )
413- . map_or ( true , |duration| duration >= DEFAULT_NOTIFICATION_COOLDOWN_HOURS . as_secs ( ) )
414- {
415- webhook . last_notification_sent . insert ( notification . method . clone ( ) , now. clone ( ) ) ;
416- webhook . last_used = now . clone ( ) ;
417- self . send_notification (
418- client_id ,
419- app_name . clone ( ) ,
420- webhook . url . clone ( ) ,
421- notification . clone ( ) ,
422- ) ? ;
424+ // We must avoid sending multiple notifications of the same method
425+ // (other than lsps5.webhook_registered) close in time.
426+ if notification . method != WebhookNotificationMethod :: LSPS5WebhookRegistered {
427+ let rate_limit_applies = client_webhooks . iter ( ) . any ( | ( _ , webhook ) | {
428+ webhook
429+ . last_notification_sent
430+ . get ( & notification . method )
431+ . map ( |last_sent| now. abs_diff ( & last_sent ) )
432+ . map_or ( false , |duration| {
433+ duration < DEFAULT_NOTIFICATION_COOLDOWN_HOURS . as_secs ( )
434+ } )
435+ } ) ;
436+
437+ if rate_limit_applies {
438+ return Err ( LSPS5ProtocolError :: SlowDownError ) ;
423439 }
424440 }
441+
442+ for ( app_name, webhook) in client_webhooks. iter_mut ( ) {
443+ webhook. last_notification_sent . insert ( notification. method . clone ( ) , now. clone ( ) ) ;
444+ webhook. last_used = now. clone ( ) ;
445+ self . send_notification (
446+ client_id,
447+ app_name. clone ( ) ,
448+ webhook. url . clone ( ) ,
449+ notification. clone ( ) ,
450+ ) ?;
451+ }
425452 Ok ( ( ) )
426453 }
427454
0 commit comments