-
Notifications
You must be signed in to change notification settings - Fork 402
LSPS5 implementation #3662
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
LSPS5 implementation #3662
Conversation
👋 Thanks for assigning @tnull as a reviewer! |
This is a huge PR, but it wasn’t obvious to me how to split it in a way that would still make sense (I did split it into small commits to make it easier to review.). I’m open to suggestions if you have ideas on how this could be structured differently. |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3662 +/- ##
==========================================
- Coverage 89.13% 89.11% -0.03%
==========================================
Files 157 161 +4
Lines 123851 125617 +1766
Branches 123851 125617 +1766
==========================================
+ Hits 110395 111943 +1548
- Misses 10779 10929 +150
- Partials 2677 2745 +68 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, thank you for looking into this! I did a first pass, and it looks pretty amazing already!
Before going too much into further details, here are a few general comments upfront:
-
I'm generally no fan of introducing additional dependencies here, an in particular not
reqwest
andtokio
. I think following the pattern so farBroadcastNotifications
could be a request that the user handles with any HTTP client they want and then could call back intoLSPS5ServiceHandler
. Alternatively, we could also use a trait similar to the currentHTTPClient
, but I don't think we want to keep the default implementation. Note that the blockingreqwest
variant wraps atokio
runtime internally, and therefore should never (1, 2, ...) be used together. I guess technically we could consider a defaultasync
version of the trait that usesasync
reqwest
, but I would prefer to simply have well-documented trait on our end that the user can implement however they choose to. Also note that stackingtokio
runtimes is heavily discouraged in general, so assuming our users would themselves use atokio
runtime, we shouldn't wrap one inLSPS5ServiceHandler
. -
Note that
lightning-liquidity
is optionally no-std
compliant, so please don't rely onstd
wherever possible, often it's just a matter of usingcore
instead and importing the respective types fromcrate::prelude
. If you really find yourselves needing to usestd
, make sure it's feature gated behindfeature = "std"
and we provide an alternative for users that don't support it. -
Minor: Regarding formatting we're using tabs, not spaces. Feel free to run
./contrib/run-rustfmt.sh
after each commit to run our formatting scripts. -
This PR in its current scope is great, just want to note that eventually we need to add persistence for the state. As we haven't fully fleshed out the persistence strategy for
lightning-liquidity
in general yet, it's actually preferred to defer this to a follow-up, but just wanted to mention it. Also note that some changes toMessageQueue
/EventQueue
will happen inlightning-liquidity
: IntroduceEventQueue
notifier and wake BP for message processing #3509, but will ping you to rebase once that has been merged. (just sidenotes here).
I hope these initial points make sense, let me know if you have any questions, or once you made corresponding changes and think this is ready for the next round of review!
👋 The first review has been submitted! Do you think this PR is ready for a second reviewer? If so, click here to assign a second reviewer. |
Thanks for asking! I'm totally fine to keep this (with its current scope) in a single PR, as long as we keep the commit history pretty clean to allow continuing review to happen commit-by-commit. To this end, please make sure add any fixup commits clearly marked (e.g. via a |
Btw, I'm not sure if you're familiar with the previous attempt of implementing LSPS5: #3499 Given this is a clean slate, not sure how much there is to learn, but still might be worth a look. Also not sure if @johncantrell97 would be interested in reviewing this PR, too, as he's familiar with the codebase and LSPS5. |
1d4b47c
to
edf5346
Compare
@tnull, ready for the next review round!
CI is failing because of the usage of the |
9809682
to
af5929e
Compare
🔔 1st Reminder Hey @tnull @valentinewallace! This PR has been waiting for your review. |
1 similar comment
🔔 1st Reminder Hey @tnull @valentinewallace! This PR has been waiting for your review. |
29fa657
to
819614e
Compare
@tnull thanks a lot for the review! All review comments have been addressed in fixup commits. Let me know what you think! Side note. There are a few things I’m still not fully convinced about:
|
🔔 1st Reminder Hey @tnull! This PR has been waiting for your review. |
🔔 2nd Reminder Hey @tnull! This PR has been waiting for your review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@martinsaposnic This needs a rebase now that #3509 was merged. In particular, you'll need to switch to the new event queue, etc.
Let me know once it's rebased. Will do another full pass then, but should be ready for a second reviewer soon.
- Add 'time' feature flag to allow disabling time-dependent functionality - Include 'time' in default features - Allow users to disable SystemTime::now without disabling all std features - Follows pattern established in other crates (e.g., lightning-transaction-sync) - Improves compatibility with WASM environments
- do new_from_duration_since_epoch (instead of From<Duration>) - Avoid doing ambiguous timestamp types - Add abs_diff function to use on client / service
Adds a new url_utils.rs module that provides: - A lightweight URL parser specialized for LSPS5 webhook validation - An implementation focusing on scheme and host extraction - RFC-compliant scheme validation - Tests for various URL scenarios This implementation allows validating webhook URLs without depending on the external url crate
- Replace String fields with UntrustedString wrapper - Change parse to take owned String and return Err(()) - Simplify error handling, drop free‑form error messages - Add is_https(), url_length(), is_public() helpers - Make url() public, remove scheme()/host() getters - Clean up parsing logic to avoid extra allocations - Update tests to use .to_string() and new API - Add proptest for robust property‑based testing - Remove redundant comments and dead code
- Define LSPS5Request and LSPS5Response enums for webhook registration, listing, and removal. - Implement WebhookNotification and associated helper constructors for different notification types. - Implement serialization/deserialization support with comprehensive tests. - Improve LSPS5 message types, validation, and testing - Replace generic String types with strongly-typed Lsps5AppName and Lsps5WebhookUrl with built-in length and format validation - Restructure imports to follow one-per-line convention - Add constants for notification method strings - Make WebhookNotificationMethod enum more consistent with LSPS5 prefix - Use explicit serde_json::json and serde_json::Value instead of imports - Improve code documentation with proper ticks and references - Add comprehensive test vectors from the BLIP-0055 specification
- Use UntrustedString for LSPS5AppName and LSPS5WebhookUrl - Move URL validation to url_utils for consistency - Use chars().count() for app name length check - Remove jsonrpc field from WebhookNotification struct - Always serialize jsonrpc as 2.0 and Visitor for deserialization - Avoid heap allocations in WebhookNotification deserialization - Update tests for new constructors and error types
- Introduce LSPS5ServiceEvent for LSPS-side webhook events including registration, listing, removal, and notification. - Define LSPS5ClientEvent for handling webhook outcomes on the client (Lightning node) side. - Outline WebhookNotificationParams enum to support notification-specific parameters. - Improve LSPS5 event documentation and field naming - Rename client/lsp fields to counterparty_node_id for consistent terminology - Replace generic String types with more specific Lsps5AppName and Lsps5WebhookUrl - Add comprehensive documentation for all events and fields - Include format specifications (UTF-8, ISO8601) and size constraints - Add request_id field to all relevant events for consistent request tracking - Provide detailed descriptions of error codes and their meanings - Use complete sentences in documentation comments
- Replaces raw error code/message pairs with a structured LSPS5Error type - Changes String timestamp fields to use the proper LSPSDateTime type - Improves documentation with references to MAX_APP_NAME_LENGTH constant
Implements the LSPS5 webhook registration service that allows LSPs to notify clients of important events via webhooks. This service handles webhook registration, listing, removal, and notification delivery according to the LSPS5 specification. Some details: - A generic HttpClient trait is defined so users can provide their own HTTP implementation - A generic TimeProvider trait is defined with a DefaultTimeProvider that uses std functionality - Uses URL utils to validate webhook URLs according to LSPS5 requirements - Uses secure message signing logic from the lightning::util::message_signing module - Works with the events and messages defined in earlier commits - Tests will be provided in a future commit
- Use LSPSDateTime for webhook timestamps for future LDK serialization - Make MIN_WEBHOOK_RETENTION_DAYS and PRUNE_STALE_WEBHOOKS_INTERVAL_DAYS Durations - Remove unnecessary Arc from Mutex fields (webhooks, last_pruning) - Use unwrap() for Mutex locks as per project convention - Move pruning interval to a const - Set last_pruning in prune_stale_webhooks after pruning - Remove unnecessary reassignments and error handling on locks - Use new_with_custom_time_provider for handler construction - Set PROTOCOL_NUMBER to 5 for LSPS5 - Rename _new_with_custom_time_provider to new_with_custom_time_provider - Make notification_cooldown_hours a Duration - Minor doc and formatting improvements
Implements the client-side functionality for LSPS5 webhook registration, allowing Lightning clients to register, list, and remove webhooks with LSPs. This client handler processes responses and verifies webhook notification signatures. Key features: - Full client API for webhook registration operations - Per-peer state tracking for pending requests - Automatic request timeout and cleanup - Security validation for webhook URLs - Notification signature verification - Add store_signature and check_signature to prevent replay attacks - Some tests are provided but more will come in a future commit This implementation pairs with the service-side LSPS5 webhook handler to complete the webhook registration protocol according to the LSPS5 specification.
- Use Duration type consistently for time-related configurations instead of u64 - Replace raw durations with LSPSDateTime for timestamp tracking and comparison - Do unwrap directly on mutex locks - Clean up code organization (imports, visibility modifiers) - Use the new LSPS5Error
Fully integrates the LSPS5 webhook components into the lightning-liquidity framework, enabling usage through the LiquidityManager. It includes - Registering LSPS5 events in the event system - Adding LSPS5 module to the main library exports - Updating LSPS0 serialization to handle LSPS5 messages - Adding LSPS5 configuration options to client and service config structures - Implementing message handling for LSPS5 requests and responses - Adding accessor methods for LSPS5 client and service handlers With this change, LSPS5 webhook functionality can now be accessed through the standard LiquidityManager interface, following the same pattern as other LSPS protocols.
…orrectness of the signing logic
@tnull rebase done! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excuse the delay here! I have yet to take another closer look at the tests and parts of the service, but at this point this PR should be ready for another set of eyes.
I'll ping @johncantrell97 as he's familiar with LSPS5 and agreed to review this PR.
Please feel free to squash the fixups.
@@ -1,6 +1,5 @@ | |||
use super::LiquidityEvent; | |||
use crate::sync::{Arc, Mutex}; | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Please leave this line in, it was there for a reason.
|
||
/// Default time provider using the system clock. | ||
#[derive(Clone, Debug)] | ||
#[cfg(feature = "time")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, as noted on the TimeProvider
PR, I think we should either feature-gate this on all(feature = "std", feature = "time")
, or simply have time
depend on std
in the first place.
fn default() -> Self { | ||
Self { | ||
max_webhooks_per_client: DEFAULT_MAX_WEBHOOKS_PER_CLIENT, | ||
signing_key: SecretKey::from_slice(&[1; 32]).expect("Static key should be valid"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should provide a default value for signing key. We will probably need to drop the Default
implementation alltogether.
} | ||
|
||
/// Handle a set_webhook request. | ||
pub fn handle_set_webhook( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this should be pub
, same with the other internal methods here.
Please also drop the doc comments on the internal methods while you're at it. The comments for the public API methods could be expanded though to include additional context and instructions now how they would be used.
"Service handler received LSPS5 response message. This should never happen." | ||
); | ||
Err(LightningError { | ||
err: format!("Service handler received LSPS5 response message from node {:?}. This should never happen.", counterparty_node_id), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Formatting is off here.
action: ErrorAction::IgnoreAndLog(Level::Error), | ||
}); | ||
|
||
self.with_peer_state(*counterparty_node_id, |peer_state| { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Phew, can we reduce the indentation here? Maybe at least move the closure to a variable before giving it to with_peer_state
? Also, we might be able to avoid the match
above by early-aborting in the Request
case?
pub fn verify_notification_signature( | ||
&self, counterparty_node_id: PublicKey, signature_timestamp: &LSPSDateTime, | ||
signature: &str, notification: &WebhookNotification, | ||
) -> Result<bool, LightningError> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As previously mentioned, I don't think we should expose LightningError
as a return value in our public API. Let's rather introduce well-defined error enums instead, probably one for the client and one for the service side.
} else { | ||
#[cfg(feature = "time")] | ||
{ | ||
LSPS5ClientHandler::new( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather then doing this, let's just go the same way as we did for LSPS5ServiceHandler
: add two separate constructors new
and new_with_custom_time_provider
to LiquidityManager
, where the former is gated on time
/std
and reuses the latter. This would also allow us to require the time_provider
field, i.e., drop the Option
, no?
@@ -4,6 +4,7 @@ | |||
#![allow(unused_imports)] | |||
#![allow(unused_macros)] | |||
|
|||
use bitcoin::secp256k1::SecretKey; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Let's move this down to the other bitcoin
imports.
@@ -672,3 +684,52 @@ fn advance_chain(node: &mut Node, num_blocks: u32) { | |||
} | |||
} | |||
} | |||
|
|||
pub(crate) fn get_client_and_service( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems specific to LSPS5, so probably should live in lsps5_integration_tests.rs
rather than the common
mod?
A complete implementation for LSPS5 (spec defined here lightning/blips#55)
Reviewing commit by commit is recommended (~40% of the added lines are tests)
Notes:
- Will rebase fromDONElightning-liquidity
: IntroduceEventQueue
notifier and wake BP for message processing #3509 once that PR is merged