lightning-liquidity: Pre-/Refactors to prepare for persistence - #4008

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
tnull:2025-08-liquidity-persistence-prefactors
Aug 18, 2025
Merged

lightning-liquidity: Pre-/Refactors to prepare for persistence#4008
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
tnull:2025-08-liquidity-persistence-prefactors

Conversation

@tnull

@tnulltnull commented Aug 13, 2025

Copy link
Copy Markdown
Contributor

Before we can introduce persistence to the lightning-liquidity crate, we make a number of pre-/refactors to make our lives easier. We split this out here to keep PR sizes manageable and to introducing too many conflicts with concurrent work.

In this PR, we move some LSPS2/LSPS5 state data to dedicated types, which will allow use to use our serialization macros in the next step. We also simplify the last_notification_sent tracking in LSPS5 (now only tracking a single timestamp for all notification methods), which was requested on a previous PR. We furthermore now reset the notification cooldown on peer disconnection (useful in case we somehow notified last while the peer was connected), and move to prune the LSPS5 service state only on peer_{dis}connected, which should be more than enough.

(cc @martinsaposnic)

.. which streamlines the `PaymentQueue` API a bit, but most importantly
can more easily get persisted using macros in the next step.
@tnull
tnull requested a review from TheBlueMattAugust 13, 2025 09:04
@tnulltnull self-assigned this Aug 13, 2025
@tnulltnull added lightning-liquidity weekly goal Someone wants to land this this week labels Aug 13, 2025
@ldk-reviews-bot

ldk-reviews-bot commented Aug 13, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @martinsaposnic as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnulltnull moved this to Goal: Merge in Weekly GoalsAug 13, 2025
@codecov

codecovBot commented Aug 13, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.57576% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.86%. Comparing base (3b16c77) to head (f95ba69).
⚠️ Report is 21 commits behind head on main.

Files with missing linesPatch %Lines
lightning-liquidity/src/lsps5/service.rs97.52%3 Missing ⚠️
lightning-liquidity/src/manager.rs66.66%0 Missing and 1 partial ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4008 +/- ##
=======================================
Coverage 88.85% 88.86% =======================================
Files 175 175 Lines 127682 127758 +76 Branches 127682 127758 +76 =======================================
+ Hits 113449 113527 +78 + Misses 11675 11669 -6 - Partials 2558 2562 +4 
FlagCoverage Δ
fuzzing21.75% <0.00%> (-0.11%)⬇️
tests88.69% <97.57%> (+<0.01%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from 7447f32 to 1d490f2CompareAugust 13, 2025 12:23
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
}

// Returns whether the entire state is empty and can be pruned.
fn prune_stale_webhooks(&mut self, now: LSPSDateTime) -> bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function name is kind of confusing, it sounds like an action but it's not

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function name is kind of confusing, it sounds like an action but it's not

It is an action though, as it drops stale webhooks?

if let Some(webhook) = peer_state_lock.webhook_mut(&params.app_name.clone()) {
no_change = webhook.url == params.webhook;
if !no_change {
webhook.last_used = now

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in here you need to set the webhook.url to params.webhook. if not, the update webhook functionality will be broken.

unfortunately, right now the tests are not testing the webhook update feature. they only test that the notification is sent with the updated url, but they don't test that the url is actually updated and persisted :(

here is a regression test that passes on main but fails on this branch

#[test]fnwebhook_update_affects_future_notifications(){let mock_time_provider = Arc::new(MockTimeProvider::new(1000));let time_provider = Arc::<MockTimeProvider>::clone(&mock_time_provider);let chanmon_cfgs = create_chanmon_cfgs(2);let node_cfgs = create_node_cfgs(2,&chanmon_cfgs);let node_chanmgrs = create_node_chanmgrs(2,&node_cfgs,&[None,None]);let nodes = create_network(2,&node_cfgs,&node_chanmgrs);let(lsps_nodes, _) = lsps5_test_setup(nodes, time_provider);letLSPSNodes{ service_node, client_node } = lsps_nodes;let service_node_id = service_node.inner.node.get_our_node_id();let client_node_id = client_node.inner.node.get_our_node_id();let client_handler = client_node.liquidity_manager.lsps5_client_handler().unwrap();let service_handler = service_node.liquidity_manager.lsps5_service_handler().unwrap();let app = "UpdateTestApp";let url_v1 = "https://example.org/v1";let url_v2 = "https://example.org/v2";// register v1
client_handler.set_webhook(service_node_id, app.into(), url_v1.into()).unwrap();let req = get_lsps_message!(client_node, service_node_id);
service_node.liquidity_manager.handle_custom_message(req, client_node_id).unwrap();let _ = service_node.liquidity_manager.next_event().unwrap();// initial webhook_registeredlet resp = get_lsps_message!(service_node, client_node_id);
client_node.liquidity_manager.handle_custom_message(resp, service_node_id).unwrap();let _ = client_node.liquidity_manager.next_event().unwrap();// update to v2
client_handler.set_webhook(service_node_id, app.into(), url_v2.into()).unwrap();let upd_req = get_lsps_message!(client_node, service_node_id);
service_node.liquidity_manager.handle_custom_message(upd_req, client_node_id).unwrap();let update_event = service_node.liquidity_manager.next_event().unwrap();match update_event {LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification{
url, ..
}) => {assert_eq!(url.as_str(), url_v2);},
_ => panic!("Expected webhook_registered for update"),}let upd_resp = get_lsps_message!(service_node, client_node_id);
client_node.liquidity_manager.handle_custom_message(upd_resp, service_node_id).unwrap();let _ = client_node.liquidity_manager.next_event().unwrap();// Advance past cooldown and send a notification again
mock_time_provider.advance_time(NOTIFICATION_COOLDOWN_TIME.as_secs() + 1);
service_handler.notify_payment_incoming(client_node_id).unwrap();let ev = service_node.liquidity_manager.next_event().unwrap();match ev {LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification{
url,
notification,
..
}) => {assert_eq!(notification.method,WebhookNotificationMethod::LSPS5PaymentIncoming);assert_eq!(url.as_str(), url_v2,"Should target updated URL");},
_ => panic!("Expected SendWebhookNotification after update"),}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you also need to set last_notification_sent to None so you don't carry the old cooldown to the new url

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so you don't carry the old cooldown to the new url

we can add a test that asserts that a notification can be sent immediately after updating a webhook

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in here you need to set the webhook.url to params.webhook. if not, the update webhook functionality will be broken.

unfortunately, right now the tests are not testing the webhook update feature. they only test that the notification is sent with the updated url, but they don't test that the url is actually updated and persisted :(

here is a regression test that passes on main but fails on this branch

Ah, good catch, that's indeed a behavior change. I added a fix and included the test, thanks for that.

@martinsaposnic

Copy link
Copy Markdown
Contributor

@tnull left a few small comments but otherwise looks good!

Comment threadlightning-liquidity/src/lsps5/service.rs
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from 1d490f2 to e952cbdCompareAugust 14, 2025 07:47
@tnull

Copy link
Copy Markdown
ContributorAuthor

Addressed pending comments.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all LGTM, feel free to squash.

@martinsaposnic

martinsaposnic commented Aug 17, 2025

Copy link
Copy Markdown
Contributor

Sorry for the delay here. Fixups look good. No further comments 👍

While bLIP-55 describes that the service should wait at least some
cooldown between sending notifications per individual `method`, there is
nothing that keeps us from simplifying our approach to apply the
cooldown to *any* notifications sent, especially since we just reduced
the cooldown period to 1 minute elsewhere. Here, we therefore simplify
the `last_notification_sent` field to just be a `Option<LSPSDateTime>`.
If we happened to send a notification while the client is connected to
us, we would previously only reset the cooldown once the client connects
again.
While theoretically it would be preferable to never set the
`last_notification_sent` field to begin with if the client is connected
to us, allowing the service handler to query the peer connection state
would be unnecessarily complex. Here, we therefore simply opt to also
reset the `last_notification_sent` state once the peer disconnects from
us.
Going forward, we'll add serialization logic for LSPS5 types. To contain
the persisted state a bit better (and to align the model with LSPS1/2),
we refactor the `LSPS5ServiceHandler` to hold a `PeerState` object.
Previously, we'd constantly check whether or not we can prune stale
webhooks. While not wrong, it lead to a bunch of ~unnecessary
operations, especially given that we only prune once a day currently.
Here we move pruning to `peer_connected`/`peer_disconnected`, which is
similar to what we do for LSPS2, and should still be more than enough.
We add the license header to all files in `lightning-liquidity` where it
was absent.
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from e952cbd to f95ba69CompareAugust 18, 2025 07:03
@tnull

Copy link
Copy Markdown
ContributorAuthor

This all LGTM, feel free to squash.

Squashed without further changes.

@TheBlueMatt
TheBlueMatt merged commit e1a31e1 into lightningdevkit:mainAug 18, 2025
24 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsAug 18, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lightning-liquidityweekly goalSomeone wants to land this this week

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@tnull@ldk-reviews-bot@martinsaposnic@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

lightning-liquidity: Pre-/Refactors to prepare for persistence - #4008

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
tnull:2025-08-liquidity-persistence-prefactors
Aug 18, 2025
Merged

lightning-liquidity: Pre-/Refactors to prepare for persistence#4008
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
tnull:2025-08-liquidity-persistence-prefactors

Conversation

@tnull

@tnulltnull commented Aug 13, 2025

Copy link
Copy Markdown
Contributor

Before we can introduce persistence to the lightning-liquidity crate, we make a number of pre-/refactors to make our lives easier. We split this out here to keep PR sizes manageable and to introducing too many conflicts with concurrent work.

In this PR, we move some LSPS2/LSPS5 state data to dedicated types, which will allow use to use our serialization macros in the next step. We also simplify the last_notification_sent tracking in LSPS5 (now only tracking a single timestamp for all notification methods), which was requested on a previous PR. We furthermore now reset the notification cooldown on peer disconnection (useful in case we somehow notified last while the peer was connected), and move to prune the LSPS5 service state only on peer_{dis}connected, which should be more than enough.

(cc @martinsaposnic)

.. which streamlines the `PaymentQueue` API a bit, but most importantly
can more easily get persisted using macros in the next step.
@tnull
tnull requested a review from TheBlueMattAugust 13, 2025 09:04
@tnulltnull self-assigned this Aug 13, 2025
@tnulltnull added lightning-liquidity weekly goal Someone wants to land this this week labels Aug 13, 2025
@ldk-reviews-bot

ldk-reviews-bot commented Aug 13, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @martinsaposnic as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnulltnull moved this to Goal: Merge in Weekly GoalsAug 13, 2025
@codecov

codecovBot commented Aug 13, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.57576% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.86%. Comparing base (3b16c77) to head (f95ba69).
⚠️ Report is 21 commits behind head on main.

Files with missing linesPatch %Lines
lightning-liquidity/src/lsps5/service.rs97.52%3 Missing ⚠️
lightning-liquidity/src/manager.rs66.66%0 Missing and 1 partial ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4008 +/- ##
=======================================
Coverage 88.85% 88.86% =======================================
Files 175 175 Lines 127682 127758 +76 Branches 127682 127758 +76 =======================================
+ Hits 113449 113527 +78 + Misses 11675 11669 -6 - Partials 2558 2562 +4 
FlagCoverage Δ
fuzzing21.75% <0.00%> (-0.11%)⬇️
tests88.69% <97.57%> (+<0.01%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from 7447f32 to 1d490f2CompareAugust 13, 2025 12:23
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
}

// Returns whether the entire state is empty and can be pruned.
fn prune_stale_webhooks(&mut self, now: LSPSDateTime) -> bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function name is kind of confusing, it sounds like an action but it's not

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function name is kind of confusing, it sounds like an action but it's not

It is an action though, as it drops stale webhooks?

if let Some(webhook) = peer_state_lock.webhook_mut(&params.app_name.clone()) {
no_change = webhook.url == params.webhook;
if !no_change {
webhook.last_used = now

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in here you need to set the webhook.url to params.webhook. if not, the update webhook functionality will be broken.

unfortunately, right now the tests are not testing the webhook update feature. they only test that the notification is sent with the updated url, but they don't test that the url is actually updated and persisted :(

here is a regression test that passes on main but fails on this branch

#[test]fnwebhook_update_affects_future_notifications(){let mock_time_provider = Arc::new(MockTimeProvider::new(1000));let time_provider = Arc::<MockTimeProvider>::clone(&mock_time_provider);let chanmon_cfgs = create_chanmon_cfgs(2);let node_cfgs = create_node_cfgs(2,&chanmon_cfgs);let node_chanmgrs = create_node_chanmgrs(2,&node_cfgs,&[None,None]);let nodes = create_network(2,&node_cfgs,&node_chanmgrs);let(lsps_nodes, _) = lsps5_test_setup(nodes, time_provider);letLSPSNodes{ service_node, client_node } = lsps_nodes;let service_node_id = service_node.inner.node.get_our_node_id();let client_node_id = client_node.inner.node.get_our_node_id();let client_handler = client_node.liquidity_manager.lsps5_client_handler().unwrap();let service_handler = service_node.liquidity_manager.lsps5_service_handler().unwrap();let app = "UpdateTestApp";let url_v1 = "https://example.org/v1";let url_v2 = "https://example.org/v2";// register v1
client_handler.set_webhook(service_node_id, app.into(), url_v1.into()).unwrap();let req = get_lsps_message!(client_node, service_node_id);
service_node.liquidity_manager.handle_custom_message(req, client_node_id).unwrap();let _ = service_node.liquidity_manager.next_event().unwrap();// initial webhook_registeredlet resp = get_lsps_message!(service_node, client_node_id);
client_node.liquidity_manager.handle_custom_message(resp, service_node_id).unwrap();let _ = client_node.liquidity_manager.next_event().unwrap();// update to v2
client_handler.set_webhook(service_node_id, app.into(), url_v2.into()).unwrap();let upd_req = get_lsps_message!(client_node, service_node_id);
service_node.liquidity_manager.handle_custom_message(upd_req, client_node_id).unwrap();let update_event = service_node.liquidity_manager.next_event().unwrap();match update_event {LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification{
url, ..
}) => {assert_eq!(url.as_str(), url_v2);},
_ => panic!("Expected webhook_registered for update"),}let upd_resp = get_lsps_message!(service_node, client_node_id);
client_node.liquidity_manager.handle_custom_message(upd_resp, service_node_id).unwrap();let _ = client_node.liquidity_manager.next_event().unwrap();// Advance past cooldown and send a notification again
mock_time_provider.advance_time(NOTIFICATION_COOLDOWN_TIME.as_secs() + 1);
service_handler.notify_payment_incoming(client_node_id).unwrap();let ev = service_node.liquidity_manager.next_event().unwrap();match ev {LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification{
url,
notification,
..
}) => {assert_eq!(notification.method,WebhookNotificationMethod::LSPS5PaymentIncoming);assert_eq!(url.as_str(), url_v2,"Should target updated URL");},
_ => panic!("Expected SendWebhookNotification after update"),}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you also need to set last_notification_sent to None so you don't carry the old cooldown to the new url

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so you don't carry the old cooldown to the new url

we can add a test that asserts that a notification can be sent immediately after updating a webhook

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in here you need to set the webhook.url to params.webhook. if not, the update webhook functionality will be broken.

unfortunately, right now the tests are not testing the webhook update feature. they only test that the notification is sent with the updated url, but they don't test that the url is actually updated and persisted :(

here is a regression test that passes on main but fails on this branch

Ah, good catch, that's indeed a behavior change. I added a fix and included the test, thanks for that.

@martinsaposnic

Copy link
Copy Markdown
Contributor

@tnull left a few small comments but otherwise looks good!

Comment threadlightning-liquidity/src/lsps5/service.rs
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from 1d490f2 to e952cbdCompareAugust 14, 2025 07:47
@tnull

Copy link
Copy Markdown
ContributorAuthor

Addressed pending comments.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all LGTM, feel free to squash.

@martinsaposnic

martinsaposnic commented Aug 17, 2025

Copy link
Copy Markdown
Contributor

Sorry for the delay here. Fixups look good. No further comments 👍

While bLIP-55 describes that the service should wait at least some
cooldown between sending notifications per individual `method`, there is
nothing that keeps us from simplifying our approach to apply the
cooldown to *any* notifications sent, especially since we just reduced
the cooldown period to 1 minute elsewhere. Here, we therefore simplify
the `last_notification_sent` field to just be a `Option<LSPSDateTime>`.
If we happened to send a notification while the client is connected to
us, we would previously only reset the cooldown once the client connects
again.
While theoretically it would be preferable to never set the
`last_notification_sent` field to begin with if the client is connected
to us, allowing the service handler to query the peer connection state
would be unnecessarily complex. Here, we therefore simply opt to also
reset the `last_notification_sent` state once the peer disconnects from
us.
Going forward, we'll add serialization logic for LSPS5 types. To contain
the persisted state a bit better (and to align the model with LSPS1/2),
we refactor the `LSPS5ServiceHandler` to hold a `PeerState` object.
Previously, we'd constantly check whether or not we can prune stale
webhooks. While not wrong, it lead to a bunch of ~unnecessary
operations, especially given that we only prune once a day currently.
Here we move pruning to `peer_connected`/`peer_disconnected`, which is
similar to what we do for LSPS2, and should still be more than enough.
We add the license header to all files in `lightning-liquidity` where it
was absent.
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from e952cbd to f95ba69CompareAugust 18, 2025 07:03
@tnull

Copy link
Copy Markdown
ContributorAuthor

This all LGTM, feel free to squash.

Squashed without further changes.

@TheBlueMatt
TheBlueMatt merged commit e1a31e1 into lightningdevkit:mainAug 18, 2025
24 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsAug 18, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lightning-liquidityweekly goalSomeone wants to land this this week

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@tnull@ldk-reviews-bot@martinsaposnic@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

lightning-liquidity: Pre-/Refactors to prepare for persistence - #4008

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
tnull:2025-08-liquidity-persistence-prefactors
Aug 18, 2025
Merged

lightning-liquidity: Pre-/Refactors to prepare for persistence#4008
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
tnull:2025-08-liquidity-persistence-prefactors

Conversation

@tnull

@tnulltnull commented Aug 13, 2025

Copy link
Copy Markdown
Contributor

Before we can introduce persistence to the lightning-liquidity crate, we make a number of pre-/refactors to make our lives easier. We split this out here to keep PR sizes manageable and to introducing too many conflicts with concurrent work.

In this PR, we move some LSPS2/LSPS5 state data to dedicated types, which will allow use to use our serialization macros in the next step. We also simplify the last_notification_sent tracking in LSPS5 (now only tracking a single timestamp for all notification methods), which was requested on a previous PR. We furthermore now reset the notification cooldown on peer disconnection (useful in case we somehow notified last while the peer was connected), and move to prune the LSPS5 service state only on peer_{dis}connected, which should be more than enough.

(cc @martinsaposnic)

.. which streamlines the `PaymentQueue` API a bit, but most importantly
can more easily get persisted using macros in the next step.
@tnull
tnull requested a review from TheBlueMattAugust 13, 2025 09:04
@tnulltnull self-assigned this Aug 13, 2025
@tnulltnull added lightning-liquidity weekly goal Someone wants to land this this week labels Aug 13, 2025
@ldk-reviews-bot

ldk-reviews-bot commented Aug 13, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @martinsaposnic as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnulltnull moved this to Goal: Merge in Weekly GoalsAug 13, 2025
@codecov

codecovBot commented Aug 13, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.57576% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.86%. Comparing base (3b16c77) to head (f95ba69).
⚠️ Report is 21 commits behind head on main.

Files with missing linesPatch %Lines
lightning-liquidity/src/lsps5/service.rs97.52%3 Missing ⚠️
lightning-liquidity/src/manager.rs66.66%0 Missing and 1 partial ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4008 +/- ##
=======================================
Coverage 88.85% 88.86% =======================================
Files 175 175 Lines 127682 127758 +76 Branches 127682 127758 +76 =======================================
+ Hits 113449 113527 +78 + Misses 11675 11669 -6 - Partials 2558 2562 +4 
FlagCoverage Δ
fuzzing21.75% <0.00%> (-0.11%)⬇️
tests88.69% <97.57%> (+<0.01%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from 7447f32 to 1d490f2CompareAugust 13, 2025 12:23
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
}

// Returns whether the entire state is empty and can be pruned.
fn prune_stale_webhooks(&mut self, now: LSPSDateTime) -> bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function name is kind of confusing, it sounds like an action but it's not

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function name is kind of confusing, it sounds like an action but it's not

It is an action though, as it drops stale webhooks?

if let Some(webhook) = peer_state_lock.webhook_mut(&params.app_name.clone()) {
no_change = webhook.url == params.webhook;
if !no_change {
webhook.last_used = now

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in here you need to set the webhook.url to params.webhook. if not, the update webhook functionality will be broken.

unfortunately, right now the tests are not testing the webhook update feature. they only test that the notification is sent with the updated url, but they don't test that the url is actually updated and persisted :(

here is a regression test that passes on main but fails on this branch

#[test]fnwebhook_update_affects_future_notifications(){let mock_time_provider = Arc::new(MockTimeProvider::new(1000));let time_provider = Arc::<MockTimeProvider>::clone(&mock_time_provider);let chanmon_cfgs = create_chanmon_cfgs(2);let node_cfgs = create_node_cfgs(2,&chanmon_cfgs);let node_chanmgrs = create_node_chanmgrs(2,&node_cfgs,&[None,None]);let nodes = create_network(2,&node_cfgs,&node_chanmgrs);let(lsps_nodes, _) = lsps5_test_setup(nodes, time_provider);letLSPSNodes{ service_node, client_node } = lsps_nodes;let service_node_id = service_node.inner.node.get_our_node_id();let client_node_id = client_node.inner.node.get_our_node_id();let client_handler = client_node.liquidity_manager.lsps5_client_handler().unwrap();let service_handler = service_node.liquidity_manager.lsps5_service_handler().unwrap();let app = "UpdateTestApp";let url_v1 = "https://example.org/v1";let url_v2 = "https://example.org/v2";// register v1
client_handler.set_webhook(service_node_id, app.into(), url_v1.into()).unwrap();let req = get_lsps_message!(client_node, service_node_id);
service_node.liquidity_manager.handle_custom_message(req, client_node_id).unwrap();let _ = service_node.liquidity_manager.next_event().unwrap();// initial webhook_registeredlet resp = get_lsps_message!(service_node, client_node_id);
client_node.liquidity_manager.handle_custom_message(resp, service_node_id).unwrap();let _ = client_node.liquidity_manager.next_event().unwrap();// update to v2
client_handler.set_webhook(service_node_id, app.into(), url_v2.into()).unwrap();let upd_req = get_lsps_message!(client_node, service_node_id);
service_node.liquidity_manager.handle_custom_message(upd_req, client_node_id).unwrap();let update_event = service_node.liquidity_manager.next_event().unwrap();match update_event {LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification{
url, ..
}) => {assert_eq!(url.as_str(), url_v2);},
_ => panic!("Expected webhook_registered for update"),}let upd_resp = get_lsps_message!(service_node, client_node_id);
client_node.liquidity_manager.handle_custom_message(upd_resp, service_node_id).unwrap();let _ = client_node.liquidity_manager.next_event().unwrap();// Advance past cooldown and send a notification again
mock_time_provider.advance_time(NOTIFICATION_COOLDOWN_TIME.as_secs() + 1);
service_handler.notify_payment_incoming(client_node_id).unwrap();let ev = service_node.liquidity_manager.next_event().unwrap();match ev {LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification{
url,
notification,
..
}) => {assert_eq!(notification.method,WebhookNotificationMethod::LSPS5PaymentIncoming);assert_eq!(url.as_str(), url_v2,"Should target updated URL");},
_ => panic!("Expected SendWebhookNotification after update"),}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you also need to set last_notification_sent to None so you don't carry the old cooldown to the new url

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so you don't carry the old cooldown to the new url

we can add a test that asserts that a notification can be sent immediately after updating a webhook

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in here you need to set the webhook.url to params.webhook. if not, the update webhook functionality will be broken.

unfortunately, right now the tests are not testing the webhook update feature. they only test that the notification is sent with the updated url, but they don't test that the url is actually updated and persisted :(

here is a regression test that passes on main but fails on this branch

Ah, good catch, that's indeed a behavior change. I added a fix and included the test, thanks for that.

@martinsaposnic

Copy link
Copy Markdown
Contributor

@tnull left a few small comments but otherwise looks good!

Comment threadlightning-liquidity/src/lsps5/service.rs
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from 1d490f2 to e952cbdCompareAugust 14, 2025 07:47
@tnull

Copy link
Copy Markdown
ContributorAuthor

Addressed pending comments.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all LGTM, feel free to squash.

@martinsaposnic

martinsaposnic commented Aug 17, 2025

Copy link
Copy Markdown
Contributor

Sorry for the delay here. Fixups look good. No further comments 👍

While bLIP-55 describes that the service should wait at least some
cooldown between sending notifications per individual `method`, there is
nothing that keeps us from simplifying our approach to apply the
cooldown to *any* notifications sent, especially since we just reduced
the cooldown period to 1 minute elsewhere. Here, we therefore simplify
the `last_notification_sent` field to just be a `Option<LSPSDateTime>`.
If we happened to send a notification while the client is connected to
us, we would previously only reset the cooldown once the client connects
again.
While theoretically it would be preferable to never set the
`last_notification_sent` field to begin with if the client is connected
to us, allowing the service handler to query the peer connection state
would be unnecessarily complex. Here, we therefore simply opt to also
reset the `last_notification_sent` state once the peer disconnects from
us.
Going forward, we'll add serialization logic for LSPS5 types. To contain
the persisted state a bit better (and to align the model with LSPS1/2),
we refactor the `LSPS5ServiceHandler` to hold a `PeerState` object.
Previously, we'd constantly check whether or not we can prune stale
webhooks. While not wrong, it lead to a bunch of ~unnecessary
operations, especially given that we only prune once a day currently.
Here we move pruning to `peer_connected`/`peer_disconnected`, which is
similar to what we do for LSPS2, and should still be more than enough.
We add the license header to all files in `lightning-liquidity` where it
was absent.
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from e952cbd to f95ba69CompareAugust 18, 2025 07:03
@tnull

Copy link
Copy Markdown
ContributorAuthor

This all LGTM, feel free to squash.

Squashed without further changes.

@TheBlueMatt
TheBlueMatt merged commit e1a31e1 into lightningdevkit:mainAug 18, 2025
24 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsAug 18, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lightning-liquidityweekly goalSomeone wants to land this this week

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@tnull@ldk-reviews-bot@martinsaposnic@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

lightning-liquidity: Pre-/Refactors to prepare for persistence - #4008

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
tnull:2025-08-liquidity-persistence-prefactors
Aug 18, 2025
Merged

lightning-liquidity: Pre-/Refactors to prepare for persistence#4008
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
tnull:2025-08-liquidity-persistence-prefactors

Conversation

@tnull

@tnulltnull commented Aug 13, 2025

Copy link
Copy Markdown
Contributor

Before we can introduce persistence to the lightning-liquidity crate, we make a number of pre-/refactors to make our lives easier. We split this out here to keep PR sizes manageable and to introducing too many conflicts with concurrent work.

In this PR, we move some LSPS2/LSPS5 state data to dedicated types, which will allow use to use our serialization macros in the next step. We also simplify the last_notification_sent tracking in LSPS5 (now only tracking a single timestamp for all notification methods), which was requested on a previous PR. We furthermore now reset the notification cooldown on peer disconnection (useful in case we somehow notified last while the peer was connected), and move to prune the LSPS5 service state only on peer_{dis}connected, which should be more than enough.

(cc @martinsaposnic)

.. which streamlines the `PaymentQueue` API a bit, but most importantly
can more easily get persisted using macros in the next step.
@tnull
tnull requested a review from TheBlueMattAugust 13, 2025 09:04
@tnulltnull self-assigned this Aug 13, 2025
@tnulltnull added lightning-liquidity weekly goal Someone wants to land this this week labels Aug 13, 2025
@ldk-reviews-bot

ldk-reviews-bot commented Aug 13, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @martinsaposnic as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnulltnull moved this to Goal: Merge in Weekly GoalsAug 13, 2025
@codecov

codecovBot commented Aug 13, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.57576% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.86%. Comparing base (3b16c77) to head (f95ba69).
⚠️ Report is 21 commits behind head on main.

Files with missing linesPatch %Lines
lightning-liquidity/src/lsps5/service.rs97.52%3 Missing ⚠️
lightning-liquidity/src/manager.rs66.66%0 Missing and 1 partial ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4008 +/- ##
=======================================
Coverage 88.85% 88.86% =======================================
Files 175 175 Lines 127682 127758 +76 Branches 127682 127758 +76 =======================================
+ Hits 113449 113527 +78 + Misses 11675 11669 -6 - Partials 2558 2562 +4 
FlagCoverage Δ
fuzzing21.75% <0.00%> (-0.11%)⬇️
tests88.69% <97.57%> (+<0.01%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from 7447f32 to 1d490f2CompareAugust 13, 2025 12:23
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
}

// Returns whether the entire state is empty and can be pruned.
fn prune_stale_webhooks(&mut self, now: LSPSDateTime) -> bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function name is kind of confusing, it sounds like an action but it's not

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function name is kind of confusing, it sounds like an action but it's not

It is an action though, as it drops stale webhooks?

if let Some(webhook) = peer_state_lock.webhook_mut(&params.app_name.clone()) {
no_change = webhook.url == params.webhook;
if !no_change {
webhook.last_used = now

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in here you need to set the webhook.url to params.webhook. if not, the update webhook functionality will be broken.

unfortunately, right now the tests are not testing the webhook update feature. they only test that the notification is sent with the updated url, but they don't test that the url is actually updated and persisted :(

here is a regression test that passes on main but fails on this branch

#[test]fnwebhook_update_affects_future_notifications(){let mock_time_provider = Arc::new(MockTimeProvider::new(1000));let time_provider = Arc::<MockTimeProvider>::clone(&mock_time_provider);let chanmon_cfgs = create_chanmon_cfgs(2);let node_cfgs = create_node_cfgs(2,&chanmon_cfgs);let node_chanmgrs = create_node_chanmgrs(2,&node_cfgs,&[None,None]);let nodes = create_network(2,&node_cfgs,&node_chanmgrs);let(lsps_nodes, _) = lsps5_test_setup(nodes, time_provider);letLSPSNodes{ service_node, client_node } = lsps_nodes;let service_node_id = service_node.inner.node.get_our_node_id();let client_node_id = client_node.inner.node.get_our_node_id();let client_handler = client_node.liquidity_manager.lsps5_client_handler().unwrap();let service_handler = service_node.liquidity_manager.lsps5_service_handler().unwrap();let app = "UpdateTestApp";let url_v1 = "https://example.org/v1";let url_v2 = "https://example.org/v2";// register v1
client_handler.set_webhook(service_node_id, app.into(), url_v1.into()).unwrap();let req = get_lsps_message!(client_node, service_node_id);
service_node.liquidity_manager.handle_custom_message(req, client_node_id).unwrap();let _ = service_node.liquidity_manager.next_event().unwrap();// initial webhook_registeredlet resp = get_lsps_message!(service_node, client_node_id);
client_node.liquidity_manager.handle_custom_message(resp, service_node_id).unwrap();let _ = client_node.liquidity_manager.next_event().unwrap();// update to v2
client_handler.set_webhook(service_node_id, app.into(), url_v2.into()).unwrap();let upd_req = get_lsps_message!(client_node, service_node_id);
service_node.liquidity_manager.handle_custom_message(upd_req, client_node_id).unwrap();let update_event = service_node.liquidity_manager.next_event().unwrap();match update_event {LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification{
url, ..
}) => {assert_eq!(url.as_str(), url_v2);},
_ => panic!("Expected webhook_registered for update"),}let upd_resp = get_lsps_message!(service_node, client_node_id);
client_node.liquidity_manager.handle_custom_message(upd_resp, service_node_id).unwrap();let _ = client_node.liquidity_manager.next_event().unwrap();// Advance past cooldown and send a notification again
mock_time_provider.advance_time(NOTIFICATION_COOLDOWN_TIME.as_secs() + 1);
service_handler.notify_payment_incoming(client_node_id).unwrap();let ev = service_node.liquidity_manager.next_event().unwrap();match ev {LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification{
url,
notification,
..
}) => {assert_eq!(notification.method,WebhookNotificationMethod::LSPS5PaymentIncoming);assert_eq!(url.as_str(), url_v2,"Should target updated URL");},
_ => panic!("Expected SendWebhookNotification after update"),}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you also need to set last_notification_sent to None so you don't carry the old cooldown to the new url

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so you don't carry the old cooldown to the new url

we can add a test that asserts that a notification can be sent immediately after updating a webhook

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in here you need to set the webhook.url to params.webhook. if not, the update webhook functionality will be broken.

unfortunately, right now the tests are not testing the webhook update feature. they only test that the notification is sent with the updated url, but they don't test that the url is actually updated and persisted :(

here is a regression test that passes on main but fails on this branch

Ah, good catch, that's indeed a behavior change. I added a fix and included the test, thanks for that.

@martinsaposnic

Copy link
Copy Markdown
Contributor

@tnull left a few small comments but otherwise looks good!

Comment threadlightning-liquidity/src/lsps5/service.rs
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from 1d490f2 to e952cbdCompareAugust 14, 2025 07:47
@tnull

Copy link
Copy Markdown
ContributorAuthor

Addressed pending comments.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all LGTM, feel free to squash.

@martinsaposnic

martinsaposnic commented Aug 17, 2025

Copy link
Copy Markdown
Contributor

Sorry for the delay here. Fixups look good. No further comments 👍

While bLIP-55 describes that the service should wait at least some
cooldown between sending notifications per individual `method`, there is
nothing that keeps us from simplifying our approach to apply the
cooldown to *any* notifications sent, especially since we just reduced
the cooldown period to 1 minute elsewhere. Here, we therefore simplify
the `last_notification_sent` field to just be a `Option<LSPSDateTime>`.
If we happened to send a notification while the client is connected to
us, we would previously only reset the cooldown once the client connects
again.
While theoretically it would be preferable to never set the
`last_notification_sent` field to begin with if the client is connected
to us, allowing the service handler to query the peer connection state
would be unnecessarily complex. Here, we therefore simply opt to also
reset the `last_notification_sent` state once the peer disconnects from
us.
Going forward, we'll add serialization logic for LSPS5 types. To contain
the persisted state a bit better (and to align the model with LSPS1/2),
we refactor the `LSPS5ServiceHandler` to hold a `PeerState` object.
Previously, we'd constantly check whether or not we can prune stale
webhooks. While not wrong, it lead to a bunch of ~unnecessary
operations, especially given that we only prune once a day currently.
Here we move pruning to `peer_connected`/`peer_disconnected`, which is
similar to what we do for LSPS2, and should still be more than enough.
We add the license header to all files in `lightning-liquidity` where it
was absent.
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from e952cbd to f95ba69CompareAugust 18, 2025 07:03
@tnull

Copy link
Copy Markdown
ContributorAuthor

This all LGTM, feel free to squash.

Squashed without further changes.

@TheBlueMatt
TheBlueMatt merged commit e1a31e1 into lightningdevkit:mainAug 18, 2025
24 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsAug 18, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lightning-liquidityweekly goalSomeone wants to land this this week

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@tnull@ldk-reviews-bot@martinsaposnic@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

lightning-liquidity: Pre-/Refactors to prepare for persistence - #4008

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
tnull:2025-08-liquidity-persistence-prefactors
Aug 18, 2025
Merged

lightning-liquidity: Pre-/Refactors to prepare for persistence#4008
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
tnull:2025-08-liquidity-persistence-prefactors

Conversation

@tnull

@tnulltnull commented Aug 13, 2025

Copy link
Copy Markdown
Contributor

Before we can introduce persistence to the lightning-liquidity crate, we make a number of pre-/refactors to make our lives easier. We split this out here to keep PR sizes manageable and to introducing too many conflicts with concurrent work.

In this PR, we move some LSPS2/LSPS5 state data to dedicated types, which will allow use to use our serialization macros in the next step. We also simplify the last_notification_sent tracking in LSPS5 (now only tracking a single timestamp for all notification methods), which was requested on a previous PR. We furthermore now reset the notification cooldown on peer disconnection (useful in case we somehow notified last while the peer was connected), and move to prune the LSPS5 service state only on peer_{dis}connected, which should be more than enough.

(cc @martinsaposnic)

.. which streamlines the `PaymentQueue` API a bit, but most importantly
can more easily get persisted using macros in the next step.
@tnull
tnull requested a review from TheBlueMattAugust 13, 2025 09:04
@tnulltnull self-assigned this Aug 13, 2025
@tnulltnull added lightning-liquidity weekly goal Someone wants to land this this week labels Aug 13, 2025
@ldk-reviews-bot

ldk-reviews-bot commented Aug 13, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @martinsaposnic as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnulltnull moved this to Goal: Merge in Weekly GoalsAug 13, 2025
@codecov

codecovBot commented Aug 13, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.57576% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.86%. Comparing base (3b16c77) to head (f95ba69).
⚠️ Report is 21 commits behind head on main.

Files with missing linesPatch %Lines
lightning-liquidity/src/lsps5/service.rs97.52%3 Missing ⚠️
lightning-liquidity/src/manager.rs66.66%0 Missing and 1 partial ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4008 +/- ##
=======================================
Coverage 88.85% 88.86% =======================================
Files 175 175 Lines 127682 127758 +76 Branches 127682 127758 +76 =======================================
+ Hits 113449 113527 +78 + Misses 11675 11669 -6 - Partials 2558 2562 +4 
FlagCoverage Δ
fuzzing21.75% <0.00%> (-0.11%)⬇️
tests88.69% <97.57%> (+<0.01%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from 7447f32 to 1d490f2CompareAugust 13, 2025 12:23
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
}

// Returns whether the entire state is empty and can be pruned.
fn prune_stale_webhooks(&mut self, now: LSPSDateTime) -> bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function name is kind of confusing, it sounds like an action but it's not

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function name is kind of confusing, it sounds like an action but it's not

It is an action though, as it drops stale webhooks?

if let Some(webhook) = peer_state_lock.webhook_mut(&params.app_name.clone()) {
no_change = webhook.url == params.webhook;
if !no_change {
webhook.last_used = now

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in here you need to set the webhook.url to params.webhook. if not, the update webhook functionality will be broken.

unfortunately, right now the tests are not testing the webhook update feature. they only test that the notification is sent with the updated url, but they don't test that the url is actually updated and persisted :(

here is a regression test that passes on main but fails on this branch

#[test]fnwebhook_update_affects_future_notifications(){let mock_time_provider = Arc::new(MockTimeProvider::new(1000));let time_provider = Arc::<MockTimeProvider>::clone(&mock_time_provider);let chanmon_cfgs = create_chanmon_cfgs(2);let node_cfgs = create_node_cfgs(2,&chanmon_cfgs);let node_chanmgrs = create_node_chanmgrs(2,&node_cfgs,&[None,None]);let nodes = create_network(2,&node_cfgs,&node_chanmgrs);let(lsps_nodes, _) = lsps5_test_setup(nodes, time_provider);letLSPSNodes{ service_node, client_node } = lsps_nodes;let service_node_id = service_node.inner.node.get_our_node_id();let client_node_id = client_node.inner.node.get_our_node_id();let client_handler = client_node.liquidity_manager.lsps5_client_handler().unwrap();let service_handler = service_node.liquidity_manager.lsps5_service_handler().unwrap();let app = "UpdateTestApp";let url_v1 = "https://example.org/v1";let url_v2 = "https://example.org/v2";// register v1
client_handler.set_webhook(service_node_id, app.into(), url_v1.into()).unwrap();let req = get_lsps_message!(client_node, service_node_id);
service_node.liquidity_manager.handle_custom_message(req, client_node_id).unwrap();let _ = service_node.liquidity_manager.next_event().unwrap();// initial webhook_registeredlet resp = get_lsps_message!(service_node, client_node_id);
client_node.liquidity_manager.handle_custom_message(resp, service_node_id).unwrap();let _ = client_node.liquidity_manager.next_event().unwrap();// update to v2
client_handler.set_webhook(service_node_id, app.into(), url_v2.into()).unwrap();let upd_req = get_lsps_message!(client_node, service_node_id);
service_node.liquidity_manager.handle_custom_message(upd_req, client_node_id).unwrap();let update_event = service_node.liquidity_manager.next_event().unwrap();match update_event {LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification{
url, ..
}) => {assert_eq!(url.as_str(), url_v2);},
_ => panic!("Expected webhook_registered for update"),}let upd_resp = get_lsps_message!(service_node, client_node_id);
client_node.liquidity_manager.handle_custom_message(upd_resp, service_node_id).unwrap();let _ = client_node.liquidity_manager.next_event().unwrap();// Advance past cooldown and send a notification again
mock_time_provider.advance_time(NOTIFICATION_COOLDOWN_TIME.as_secs() + 1);
service_handler.notify_payment_incoming(client_node_id).unwrap();let ev = service_node.liquidity_manager.next_event().unwrap();match ev {LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification{
url,
notification,
..
}) => {assert_eq!(notification.method,WebhookNotificationMethod::LSPS5PaymentIncoming);assert_eq!(url.as_str(), url_v2,"Should target updated URL");},
_ => panic!("Expected SendWebhookNotification after update"),}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you also need to set last_notification_sent to None so you don't carry the old cooldown to the new url

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so you don't carry the old cooldown to the new url

we can add a test that asserts that a notification can be sent immediately after updating a webhook

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in here you need to set the webhook.url to params.webhook. if not, the update webhook functionality will be broken.

unfortunately, right now the tests are not testing the webhook update feature. they only test that the notification is sent with the updated url, but they don't test that the url is actually updated and persisted :(

here is a regression test that passes on main but fails on this branch

Ah, good catch, that's indeed a behavior change. I added a fix and included the test, thanks for that.

@martinsaposnic

Copy link
Copy Markdown
Contributor

@tnull left a few small comments but otherwise looks good!

Comment threadlightning-liquidity/src/lsps5/service.rs
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from 1d490f2 to e952cbdCompareAugust 14, 2025 07:47
@tnull

Copy link
Copy Markdown
ContributorAuthor

Addressed pending comments.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all LGTM, feel free to squash.

@martinsaposnic

martinsaposnic commented Aug 17, 2025

Copy link
Copy Markdown
Contributor

Sorry for the delay here. Fixups look good. No further comments 👍

While bLIP-55 describes that the service should wait at least some
cooldown between sending notifications per individual `method`, there is
nothing that keeps us from simplifying our approach to apply the
cooldown to *any* notifications sent, especially since we just reduced
the cooldown period to 1 minute elsewhere. Here, we therefore simplify
the `last_notification_sent` field to just be a `Option<LSPSDateTime>`.
If we happened to send a notification while the client is connected to
us, we would previously only reset the cooldown once the client connects
again.
While theoretically it would be preferable to never set the
`last_notification_sent` field to begin with if the client is connected
to us, allowing the service handler to query the peer connection state
would be unnecessarily complex. Here, we therefore simply opt to also
reset the `last_notification_sent` state once the peer disconnects from
us.
Going forward, we'll add serialization logic for LSPS5 types. To contain
the persisted state a bit better (and to align the model with LSPS1/2),
we refactor the `LSPS5ServiceHandler` to hold a `PeerState` object.
Previously, we'd constantly check whether or not we can prune stale
webhooks. While not wrong, it lead to a bunch of ~unnecessary
operations, especially given that we only prune once a day currently.
Here we move pruning to `peer_connected`/`peer_disconnected`, which is
similar to what we do for LSPS2, and should still be more than enough.
We add the license header to all files in `lightning-liquidity` where it
was absent.
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from e952cbd to f95ba69CompareAugust 18, 2025 07:03
@tnull

Copy link
Copy Markdown
ContributorAuthor

This all LGTM, feel free to squash.

Squashed without further changes.

@TheBlueMatt
TheBlueMatt merged commit e1a31e1 into lightningdevkit:mainAug 18, 2025
24 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsAug 18, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lightning-liquidityweekly goalSomeone wants to land this this week

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@tnull@ldk-reviews-bot@martinsaposnic@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

lightning-liquidity: Pre-/Refactors to prepare for persistence - #4008

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
tnull:2025-08-liquidity-persistence-prefactors
Aug 18, 2025
Merged

lightning-liquidity: Pre-/Refactors to prepare for persistence#4008
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
tnull:2025-08-liquidity-persistence-prefactors

Conversation

@tnull

@tnulltnull commented Aug 13, 2025

Copy link
Copy Markdown
Contributor

Before we can introduce persistence to the lightning-liquidity crate, we make a number of pre-/refactors to make our lives easier. We split this out here to keep PR sizes manageable and to introducing too many conflicts with concurrent work.

In this PR, we move some LSPS2/LSPS5 state data to dedicated types, which will allow use to use our serialization macros in the next step. We also simplify the last_notification_sent tracking in LSPS5 (now only tracking a single timestamp for all notification methods), which was requested on a previous PR. We furthermore now reset the notification cooldown on peer disconnection (useful in case we somehow notified last while the peer was connected), and move to prune the LSPS5 service state only on peer_{dis}connected, which should be more than enough.

(cc @martinsaposnic)

.. which streamlines the `PaymentQueue` API a bit, but most importantly
can more easily get persisted using macros in the next step.
@tnull
tnull requested a review from TheBlueMattAugust 13, 2025 09:04
@tnulltnull self-assigned this Aug 13, 2025
@tnulltnull added lightning-liquidity weekly goal Someone wants to land this this week labels Aug 13, 2025
@ldk-reviews-bot

ldk-reviews-bot commented Aug 13, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @martinsaposnic as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnulltnull moved this to Goal: Merge in Weekly GoalsAug 13, 2025
@codecov

codecovBot commented Aug 13, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.57576% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.86%. Comparing base (3b16c77) to head (f95ba69).
⚠️ Report is 21 commits behind head on main.

Files with missing linesPatch %Lines
lightning-liquidity/src/lsps5/service.rs97.52%3 Missing ⚠️
lightning-liquidity/src/manager.rs66.66%0 Missing and 1 partial ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4008 +/- ##
=======================================
Coverage 88.85% 88.86% =======================================
Files 175 175 Lines 127682 127758 +76 Branches 127682 127758 +76 =======================================
+ Hits 113449 113527 +78 + Misses 11675 11669 -6 - Partials 2558 2562 +4 
FlagCoverage Δ
fuzzing21.75% <0.00%> (-0.11%)⬇️
tests88.69% <97.57%> (+<0.01%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from 7447f32 to 1d490f2CompareAugust 13, 2025 12:23
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
}

// Returns whether the entire state is empty and can be pruned.
fn prune_stale_webhooks(&mut self, now: LSPSDateTime) -> bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function name is kind of confusing, it sounds like an action but it's not

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function name is kind of confusing, it sounds like an action but it's not

It is an action though, as it drops stale webhooks?

if let Some(webhook) = peer_state_lock.webhook_mut(&params.app_name.clone()) {
no_change = webhook.url == params.webhook;
if !no_change {
webhook.last_used = now

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in here you need to set the webhook.url to params.webhook. if not, the update webhook functionality will be broken.

unfortunately, right now the tests are not testing the webhook update feature. they only test that the notification is sent with the updated url, but they don't test that the url is actually updated and persisted :(

here is a regression test that passes on main but fails on this branch

#[test]fnwebhook_update_affects_future_notifications(){let mock_time_provider = Arc::new(MockTimeProvider::new(1000));let time_provider = Arc::<MockTimeProvider>::clone(&mock_time_provider);let chanmon_cfgs = create_chanmon_cfgs(2);let node_cfgs = create_node_cfgs(2,&chanmon_cfgs);let node_chanmgrs = create_node_chanmgrs(2,&node_cfgs,&[None,None]);let nodes = create_network(2,&node_cfgs,&node_chanmgrs);let(lsps_nodes, _) = lsps5_test_setup(nodes, time_provider);letLSPSNodes{ service_node, client_node } = lsps_nodes;let service_node_id = service_node.inner.node.get_our_node_id();let client_node_id = client_node.inner.node.get_our_node_id();let client_handler = client_node.liquidity_manager.lsps5_client_handler().unwrap();let service_handler = service_node.liquidity_manager.lsps5_service_handler().unwrap();let app = "UpdateTestApp";let url_v1 = "https://example.org/v1";let url_v2 = "https://example.org/v2";// register v1
client_handler.set_webhook(service_node_id, app.into(), url_v1.into()).unwrap();let req = get_lsps_message!(client_node, service_node_id);
service_node.liquidity_manager.handle_custom_message(req, client_node_id).unwrap();let _ = service_node.liquidity_manager.next_event().unwrap();// initial webhook_registeredlet resp = get_lsps_message!(service_node, client_node_id);
client_node.liquidity_manager.handle_custom_message(resp, service_node_id).unwrap();let _ = client_node.liquidity_manager.next_event().unwrap();// update to v2
client_handler.set_webhook(service_node_id, app.into(), url_v2.into()).unwrap();let upd_req = get_lsps_message!(client_node, service_node_id);
service_node.liquidity_manager.handle_custom_message(upd_req, client_node_id).unwrap();let update_event = service_node.liquidity_manager.next_event().unwrap();match update_event {LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification{
url, ..
}) => {assert_eq!(url.as_str(), url_v2);},
_ => panic!("Expected webhook_registered for update"),}let upd_resp = get_lsps_message!(service_node, client_node_id);
client_node.liquidity_manager.handle_custom_message(upd_resp, service_node_id).unwrap();let _ = client_node.liquidity_manager.next_event().unwrap();// Advance past cooldown and send a notification again
mock_time_provider.advance_time(NOTIFICATION_COOLDOWN_TIME.as_secs() + 1);
service_handler.notify_payment_incoming(client_node_id).unwrap();let ev = service_node.liquidity_manager.next_event().unwrap();match ev {LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification{
url,
notification,
..
}) => {assert_eq!(notification.method,WebhookNotificationMethod::LSPS5PaymentIncoming);assert_eq!(url.as_str(), url_v2,"Should target updated URL");},
_ => panic!("Expected SendWebhookNotification after update"),}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you also need to set last_notification_sent to None so you don't carry the old cooldown to the new url

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so you don't carry the old cooldown to the new url

we can add a test that asserts that a notification can be sent immediately after updating a webhook

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in here you need to set the webhook.url to params.webhook. if not, the update webhook functionality will be broken.

unfortunately, right now the tests are not testing the webhook update feature. they only test that the notification is sent with the updated url, but they don't test that the url is actually updated and persisted :(

here is a regression test that passes on main but fails on this branch

Ah, good catch, that's indeed a behavior change. I added a fix and included the test, thanks for that.

@martinsaposnic

Copy link
Copy Markdown
Contributor

@tnull left a few small comments but otherwise looks good!

Comment threadlightning-liquidity/src/lsps5/service.rs
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from 1d490f2 to e952cbdCompareAugust 14, 2025 07:47
@tnull

Copy link
Copy Markdown
ContributorAuthor

Addressed pending comments.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all LGTM, feel free to squash.

@martinsaposnic

martinsaposnic commented Aug 17, 2025

Copy link
Copy Markdown
Contributor

Sorry for the delay here. Fixups look good. No further comments 👍

While bLIP-55 describes that the service should wait at least some
cooldown between sending notifications per individual `method`, there is
nothing that keeps us from simplifying our approach to apply the
cooldown to *any* notifications sent, especially since we just reduced
the cooldown period to 1 minute elsewhere. Here, we therefore simplify
the `last_notification_sent` field to just be a `Option<LSPSDateTime>`.
If we happened to send a notification while the client is connected to
us, we would previously only reset the cooldown once the client connects
again.
While theoretically it would be preferable to never set the
`last_notification_sent` field to begin with if the client is connected
to us, allowing the service handler to query the peer connection state
would be unnecessarily complex. Here, we therefore simply opt to also
reset the `last_notification_sent` state once the peer disconnects from
us.
Going forward, we'll add serialization logic for LSPS5 types. To contain
the persisted state a bit better (and to align the model with LSPS1/2),
we refactor the `LSPS5ServiceHandler` to hold a `PeerState` object.
Previously, we'd constantly check whether or not we can prune stale
webhooks. While not wrong, it lead to a bunch of ~unnecessary
operations, especially given that we only prune once a day currently.
Here we move pruning to `peer_connected`/`peer_disconnected`, which is
similar to what we do for LSPS2, and should still be more than enough.
We add the license header to all files in `lightning-liquidity` where it
was absent.
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from e952cbd to f95ba69CompareAugust 18, 2025 07:03
@tnull

Copy link
Copy Markdown
ContributorAuthor

This all LGTM, feel free to squash.

Squashed without further changes.

@TheBlueMatt
TheBlueMatt merged commit e1a31e1 into lightningdevkit:mainAug 18, 2025
24 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsAug 18, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lightning-liquidityweekly goalSomeone wants to land this this week

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@tnull@ldk-reviews-bot@martinsaposnic@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

lightning-liquidity: Pre-/Refactors to prepare for persistence - #4008

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
tnull:2025-08-liquidity-persistence-prefactors
Aug 18, 2025
Merged

lightning-liquidity: Pre-/Refactors to prepare for persistence#4008
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
tnull:2025-08-liquidity-persistence-prefactors

Conversation

@tnull

@tnulltnull commented Aug 13, 2025

Copy link
Copy Markdown
Contributor

Before we can introduce persistence to the lightning-liquidity crate, we make a number of pre-/refactors to make our lives easier. We split this out here to keep PR sizes manageable and to introducing too many conflicts with concurrent work.

In this PR, we move some LSPS2/LSPS5 state data to dedicated types, which will allow use to use our serialization macros in the next step. We also simplify the last_notification_sent tracking in LSPS5 (now only tracking a single timestamp for all notification methods), which was requested on a previous PR. We furthermore now reset the notification cooldown on peer disconnection (useful in case we somehow notified last while the peer was connected), and move to prune the LSPS5 service state only on peer_{dis}connected, which should be more than enough.

(cc @martinsaposnic)

.. which streamlines the `PaymentQueue` API a bit, but most importantly
can more easily get persisted using macros in the next step.
@tnull
tnull requested a review from TheBlueMattAugust 13, 2025 09:04
@tnulltnull self-assigned this Aug 13, 2025
@tnulltnull added lightning-liquidity weekly goal Someone wants to land this this week labels Aug 13, 2025
@ldk-reviews-bot

ldk-reviews-bot commented Aug 13, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @martinsaposnic as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnulltnull moved this to Goal: Merge in Weekly GoalsAug 13, 2025
@codecov

codecovBot commented Aug 13, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.57576% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.86%. Comparing base (3b16c77) to head (f95ba69).
⚠️ Report is 21 commits behind head on main.

Files with missing linesPatch %Lines
lightning-liquidity/src/lsps5/service.rs97.52%3 Missing ⚠️
lightning-liquidity/src/manager.rs66.66%0 Missing and 1 partial ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4008 +/- ##
=======================================
Coverage 88.85% 88.86% =======================================
Files 175 175 Lines 127682 127758 +76 Branches 127682 127758 +76 =======================================
+ Hits 113449 113527 +78 + Misses 11675 11669 -6 - Partials 2558 2562 +4 
FlagCoverage Δ
fuzzing21.75% <0.00%> (-0.11%)⬇️
tests88.69% <97.57%> (+<0.01%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from 7447f32 to 1d490f2CompareAugust 13, 2025 12:23
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
}

// Returns whether the entire state is empty and can be pruned.
fn prune_stale_webhooks(&mut self, now: LSPSDateTime) -> bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function name is kind of confusing, it sounds like an action but it's not

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function name is kind of confusing, it sounds like an action but it's not

It is an action though, as it drops stale webhooks?

if let Some(webhook) = peer_state_lock.webhook_mut(&params.app_name.clone()) {
no_change = webhook.url == params.webhook;
if !no_change {
webhook.last_used = now

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in here you need to set the webhook.url to params.webhook. if not, the update webhook functionality will be broken.

unfortunately, right now the tests are not testing the webhook update feature. they only test that the notification is sent with the updated url, but they don't test that the url is actually updated and persisted :(

here is a regression test that passes on main but fails on this branch

#[test]fnwebhook_update_affects_future_notifications(){let mock_time_provider = Arc::new(MockTimeProvider::new(1000));let time_provider = Arc::<MockTimeProvider>::clone(&mock_time_provider);let chanmon_cfgs = create_chanmon_cfgs(2);let node_cfgs = create_node_cfgs(2,&chanmon_cfgs);let node_chanmgrs = create_node_chanmgrs(2,&node_cfgs,&[None,None]);let nodes = create_network(2,&node_cfgs,&node_chanmgrs);let(lsps_nodes, _) = lsps5_test_setup(nodes, time_provider);letLSPSNodes{ service_node, client_node } = lsps_nodes;let service_node_id = service_node.inner.node.get_our_node_id();let client_node_id = client_node.inner.node.get_our_node_id();let client_handler = client_node.liquidity_manager.lsps5_client_handler().unwrap();let service_handler = service_node.liquidity_manager.lsps5_service_handler().unwrap();let app = "UpdateTestApp";let url_v1 = "https://example.org/v1";let url_v2 = "https://example.org/v2";// register v1
client_handler.set_webhook(service_node_id, app.into(), url_v1.into()).unwrap();let req = get_lsps_message!(client_node, service_node_id);
service_node.liquidity_manager.handle_custom_message(req, client_node_id).unwrap();let _ = service_node.liquidity_manager.next_event().unwrap();// initial webhook_registeredlet resp = get_lsps_message!(service_node, client_node_id);
client_node.liquidity_manager.handle_custom_message(resp, service_node_id).unwrap();let _ = client_node.liquidity_manager.next_event().unwrap();// update to v2
client_handler.set_webhook(service_node_id, app.into(), url_v2.into()).unwrap();let upd_req = get_lsps_message!(client_node, service_node_id);
service_node.liquidity_manager.handle_custom_message(upd_req, client_node_id).unwrap();let update_event = service_node.liquidity_manager.next_event().unwrap();match update_event {LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification{
url, ..
}) => {assert_eq!(url.as_str(), url_v2);},
_ => panic!("Expected webhook_registered for update"),}let upd_resp = get_lsps_message!(service_node, client_node_id);
client_node.liquidity_manager.handle_custom_message(upd_resp, service_node_id).unwrap();let _ = client_node.liquidity_manager.next_event().unwrap();// Advance past cooldown and send a notification again
mock_time_provider.advance_time(NOTIFICATION_COOLDOWN_TIME.as_secs() + 1);
service_handler.notify_payment_incoming(client_node_id).unwrap();let ev = service_node.liquidity_manager.next_event().unwrap();match ev {LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification{
url,
notification,
..
}) => {assert_eq!(notification.method,WebhookNotificationMethod::LSPS5PaymentIncoming);assert_eq!(url.as_str(), url_v2,"Should target updated URL");},
_ => panic!("Expected SendWebhookNotification after update"),}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you also need to set last_notification_sent to None so you don't carry the old cooldown to the new url

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so you don't carry the old cooldown to the new url

we can add a test that asserts that a notification can be sent immediately after updating a webhook

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in here you need to set the webhook.url to params.webhook. if not, the update webhook functionality will be broken.

unfortunately, right now the tests are not testing the webhook update feature. they only test that the notification is sent with the updated url, but they don't test that the url is actually updated and persisted :(

here is a regression test that passes on main but fails on this branch

Ah, good catch, that's indeed a behavior change. I added a fix and included the test, thanks for that.

@martinsaposnic

Copy link
Copy Markdown
Contributor

@tnull left a few small comments but otherwise looks good!

Comment threadlightning-liquidity/src/lsps5/service.rs
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from 1d490f2 to e952cbdCompareAugust 14, 2025 07:47
@tnull

Copy link
Copy Markdown
ContributorAuthor

Addressed pending comments.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all LGTM, feel free to squash.

@martinsaposnic

martinsaposnic commented Aug 17, 2025

Copy link
Copy Markdown
Contributor

Sorry for the delay here. Fixups look good. No further comments 👍

While bLIP-55 describes that the service should wait at least some
cooldown between sending notifications per individual `method`, there is
nothing that keeps us from simplifying our approach to apply the
cooldown to *any* notifications sent, especially since we just reduced
the cooldown period to 1 minute elsewhere. Here, we therefore simplify
the `last_notification_sent` field to just be a `Option<LSPSDateTime>`.
If we happened to send a notification while the client is connected to
us, we would previously only reset the cooldown once the client connects
again.
While theoretically it would be preferable to never set the
`last_notification_sent` field to begin with if the client is connected
to us, allowing the service handler to query the peer connection state
would be unnecessarily complex. Here, we therefore simply opt to also
reset the `last_notification_sent` state once the peer disconnects from
us.
Going forward, we'll add serialization logic for LSPS5 types. To contain
the persisted state a bit better (and to align the model with LSPS1/2),
we refactor the `LSPS5ServiceHandler` to hold a `PeerState` object.
Previously, we'd constantly check whether or not we can prune stale
webhooks. While not wrong, it lead to a bunch of ~unnecessary
operations, especially given that we only prune once a day currently.
Here we move pruning to `peer_connected`/`peer_disconnected`, which is
similar to what we do for LSPS2, and should still be more than enough.
We add the license header to all files in `lightning-liquidity` where it
was absent.
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from e952cbd to f95ba69CompareAugust 18, 2025 07:03
@tnull

Copy link
Copy Markdown
ContributorAuthor

This all LGTM, feel free to squash.

Squashed without further changes.

@TheBlueMatt
TheBlueMatt merged commit e1a31e1 into lightningdevkit:mainAug 18, 2025
24 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsAug 18, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lightning-liquidityweekly goalSomeone wants to land this this week

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@tnull@ldk-reviews-bot@martinsaposnic@TheBlueMatt
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

lightning-liquidity: Pre-/Refactors to prepare for persistence - #4008

Merged
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
tnull:2025-08-liquidity-persistence-prefactors
Aug 18, 2025
Merged

lightning-liquidity: Pre-/Refactors to prepare for persistence#4008
TheBlueMatt merged 7 commits into
lightningdevkit:mainfrom
tnull:2025-08-liquidity-persistence-prefactors

Conversation

@tnull

@tnulltnull commented Aug 13, 2025

Copy link
Copy Markdown
Contributor

Before we can introduce persistence to the lightning-liquidity crate, we make a number of pre-/refactors to make our lives easier. We split this out here to keep PR sizes manageable and to introducing too many conflicts with concurrent work.

In this PR, we move some LSPS2/LSPS5 state data to dedicated types, which will allow use to use our serialization macros in the next step. We also simplify the last_notification_sent tracking in LSPS5 (now only tracking a single timestamp for all notification methods), which was requested on a previous PR. We furthermore now reset the notification cooldown on peer disconnection (useful in case we somehow notified last while the peer was connected), and move to prune the LSPS5 service state only on peer_{dis}connected, which should be more than enough.

(cc @martinsaposnic)

.. which streamlines the `PaymentQueue` API a bit, but most importantly
can more easily get persisted using macros in the next step.
@tnull
tnull requested a review from TheBlueMattAugust 13, 2025 09:04
@tnulltnull self-assigned this Aug 13, 2025
@tnulltnull added lightning-liquidity weekly goal Someone wants to land this this week labels Aug 13, 2025
@ldk-reviews-bot

ldk-reviews-bot commented Aug 13, 2025

Copy link
Copy Markdown

👋 Thanks for assigning @martinsaposnic as a reviewer!
I'll wait for their review and will help manage the review process.
Once they submit their review, I'll check if a second reviewer would be helpful.

@tnulltnull moved this to Goal: Merge in Weekly GoalsAug 13, 2025
@codecov

codecovBot commented Aug 13, 2025

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.57576% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 88.86%. Comparing base (3b16c77) to head (f95ba69).
⚠️ Report is 21 commits behind head on main.

Files with missing linesPatch %Lines
lightning-liquidity/src/lsps5/service.rs97.52%3 Missing ⚠️
lightning-liquidity/src/manager.rs66.66%0 Missing and 1 partial ⚠️
Additional details and impacted files
@@ Coverage Diff @@## main #4008 +/- ##
=======================================
Coverage 88.85% 88.86% =======================================
Files 175 175 Lines 127682 127758 +76 Branches 127682 127758 +76 =======================================
+ Hits 113449 113527 +78 + Misses 11675 11669 -6 - Partials 2558 2562 +4 
FlagCoverage Δ
fuzzing21.75% <0.00%> (-0.11%)⬇️
tests88.69% <97.57%> (+<0.01%)⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from 7447f32 to 1d490f2CompareAugust 13, 2025 12:23
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
Comment threadlightning-liquidity/src/lsps5/service.rs Outdated
}

// Returns whether the entire state is empty and can be pruned.
fn prune_stale_webhooks(&mut self, now: LSPSDateTime) -> bool {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function name is kind of confusing, it sounds like an action but it's not

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this function name is kind of confusing, it sounds like an action but it's not

It is an action though, as it drops stale webhooks?

if let Some(webhook) = peer_state_lock.webhook_mut(&params.app_name.clone()) {
no_change = webhook.url == params.webhook;
if !no_change {
webhook.last_used = now

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in here you need to set the webhook.url to params.webhook. if not, the update webhook functionality will be broken.

unfortunately, right now the tests are not testing the webhook update feature. they only test that the notification is sent with the updated url, but they don't test that the url is actually updated and persisted :(

here is a regression test that passes on main but fails on this branch

#[test]fnwebhook_update_affects_future_notifications(){let mock_time_provider = Arc::new(MockTimeProvider::new(1000));let time_provider = Arc::<MockTimeProvider>::clone(&mock_time_provider);let chanmon_cfgs = create_chanmon_cfgs(2);let node_cfgs = create_node_cfgs(2,&chanmon_cfgs);let node_chanmgrs = create_node_chanmgrs(2,&node_cfgs,&[None,None]);let nodes = create_network(2,&node_cfgs,&node_chanmgrs);let(lsps_nodes, _) = lsps5_test_setup(nodes, time_provider);letLSPSNodes{ service_node, client_node } = lsps_nodes;let service_node_id = service_node.inner.node.get_our_node_id();let client_node_id = client_node.inner.node.get_our_node_id();let client_handler = client_node.liquidity_manager.lsps5_client_handler().unwrap();let service_handler = service_node.liquidity_manager.lsps5_service_handler().unwrap();let app = "UpdateTestApp";let url_v1 = "https://example.org/v1";let url_v2 = "https://example.org/v2";// register v1
client_handler.set_webhook(service_node_id, app.into(), url_v1.into()).unwrap();let req = get_lsps_message!(client_node, service_node_id);
service_node.liquidity_manager.handle_custom_message(req, client_node_id).unwrap();let _ = service_node.liquidity_manager.next_event().unwrap();// initial webhook_registeredlet resp = get_lsps_message!(service_node, client_node_id);
client_node.liquidity_manager.handle_custom_message(resp, service_node_id).unwrap();let _ = client_node.liquidity_manager.next_event().unwrap();// update to v2
client_handler.set_webhook(service_node_id, app.into(), url_v2.into()).unwrap();let upd_req = get_lsps_message!(client_node, service_node_id);
service_node.liquidity_manager.handle_custom_message(upd_req, client_node_id).unwrap();let update_event = service_node.liquidity_manager.next_event().unwrap();match update_event {LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification{
url, ..
}) => {assert_eq!(url.as_str(), url_v2);},
_ => panic!("Expected webhook_registered for update"),}let upd_resp = get_lsps_message!(service_node, client_node_id);
client_node.liquidity_manager.handle_custom_message(upd_resp, service_node_id).unwrap();let _ = client_node.liquidity_manager.next_event().unwrap();// Advance past cooldown and send a notification again
mock_time_provider.advance_time(NOTIFICATION_COOLDOWN_TIME.as_secs() + 1);
service_handler.notify_payment_incoming(client_node_id).unwrap();let ev = service_node.liquidity_manager.next_event().unwrap();match ev {LiquidityEvent::LSPS5Service(LSPS5ServiceEvent::SendWebhookNotification{
url,
notification,
..
}) => {assert_eq!(notification.method,WebhookNotificationMethod::LSPS5PaymentIncoming);assert_eq!(url.as_str(), url_v2,"Should target updated URL");},
_ => panic!("Expected SendWebhookNotification after update"),}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you also need to set last_notification_sent to None so you don't carry the old cooldown to the new url

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so you don't carry the old cooldown to the new url

we can add a test that asserts that a notification can be sent immediately after updating a webhook

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in here you need to set the webhook.url to params.webhook. if not, the update webhook functionality will be broken.

unfortunately, right now the tests are not testing the webhook update feature. they only test that the notification is sent with the updated url, but they don't test that the url is actually updated and persisted :(

here is a regression test that passes on main but fails on this branch

Ah, good catch, that's indeed a behavior change. I added a fix and included the test, thanks for that.

@martinsaposnic

Copy link
Copy Markdown
Contributor

@tnull left a few small comments but otherwise looks good!

Comment threadlightning-liquidity/src/lsps5/service.rs
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from 1d490f2 to e952cbdCompareAugust 14, 2025 07:47
@tnull

Copy link
Copy Markdown
ContributorAuthor

Addressed pending comments.

@TheBlueMattTheBlueMatt left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This all LGTM, feel free to squash.

@martinsaposnic

martinsaposnic commented Aug 17, 2025

Copy link
Copy Markdown
Contributor

Sorry for the delay here. Fixups look good. No further comments 👍

While bLIP-55 describes that the service should wait at least some
cooldown between sending notifications per individual `method`, there is
nothing that keeps us from simplifying our approach to apply the
cooldown to *any* notifications sent, especially since we just reduced
the cooldown period to 1 minute elsewhere. Here, we therefore simplify
the `last_notification_sent` field to just be a `Option<LSPSDateTime>`.
If we happened to send a notification while the client is connected to
us, we would previously only reset the cooldown once the client connects
again.
While theoretically it would be preferable to never set the
`last_notification_sent` field to begin with if the client is connected
to us, allowing the service handler to query the peer connection state
would be unnecessarily complex. Here, we therefore simply opt to also
reset the `last_notification_sent` state once the peer disconnects from
us.
Going forward, we'll add serialization logic for LSPS5 types. To contain
the persisted state a bit better (and to align the model with LSPS1/2),
we refactor the `LSPS5ServiceHandler` to hold a `PeerState` object.
Previously, we'd constantly check whether or not we can prune stale
webhooks. While not wrong, it lead to a bunch of ~unnecessary
operations, especially given that we only prune once a day currently.
Here we move pruning to `peer_connected`/`peer_disconnected`, which is
similar to what we do for LSPS2, and should still be more than enough.
We add the license header to all files in `lightning-liquidity` where it
was absent.
@tnull
tnullforce-pushed the 2025-08-liquidity-persistence-prefactors branch from e952cbd to f95ba69CompareAugust 18, 2025 07:03
@tnull

Copy link
Copy Markdown
ContributorAuthor

This all LGTM, feel free to squash.

Squashed without further changes.

@TheBlueMatt
TheBlueMatt merged commit e1a31e1 into lightningdevkit:mainAug 18, 2025
24 checks passed
@github-project-automationgithub-project-automationBot moved this from Goal: Merge to Done in Weekly GoalsAug 18, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lightning-liquidityweekly goalSomeone wants to land this this week

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants

@tnull@ldk-reviews-bot@martinsaposnic@TheBlueMatt