Skip to content
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

Fix Route serialization round-trip #2897

Merged
merged 4 commits into from
Feb 16, 2024

Conversation

TheBlueMatt
Copy link
Collaborator

When the max_total_routing_fee_msat parameter was added to RouteParameters, the serialization used map to get the max fee, accidentally writing an Option<Option<u64>>, but then read it as an Option<u64>. Thus, any Routes with a route_params written will fail to be read back.

Luckily, this is an incredibly rarely-used bit of code, so only one user managed to hit it.

Copy link

coderabbitai bot commented Feb 16, 2024

Warning

Rate Limit Exceeded

@TheBlueMatt has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 28 minutes and 40 seconds before requesting another review.

How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.
Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.
Please see our FAQ for further information.

Commits Files that changed from the base of the PR and between e32020c and 24d02ff.

Walkthrough

The recent modifications aim to refine the handling of routing fees within the Route struct by adjusting the access method to max_total_routing_fee_msat. Additionally, the changes enhance testing robustness in test_utils by introducing a serialization round-trip check for successfully found routes, ensuring reliability and efficiency in route handling and testing processes.

Changes

File(s) Change Summary
.../routing/router.rs Modified access to max_total_routing_fee_msat in Route using and_then instead of map.
.../util/test_utils.rs Updated TestRouter to handle find_route result, adding a serialization round-trip test for routes.

🐇✨
To the code we hop and skip,
A little change, a mighty leap.
Through routes and tests, we weave and dart,
With every line, we craft with heart.
So here's to changes, small and bright,
Guiding our code from dusk till light.
🌟🐾

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share

Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit-tests for this file.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit tests for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai generate interesting stats about this repository from git and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit tests.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (invoked as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • The JSON schema for the configuration file is available here.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/coderabbit-overrides.v2.json

CodeRabbit Discord Community

Join our Discord Community to get help, request features, and share feedback.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 3fd4b39 and 2c2f39a.
Files selected for processing (2)
  • lightning/src/routing/router.rs (1 hunks)
  • lightning/src/util/test_utils.rs (1 hunks)
Additional comments: 2
lightning/src/util/test_utils.rs (1)
  • 154-165: The added code segment in TestRouter's find_route method correctly implements the round-trip serialization test for routes. This change addresses the PR's objective to enhance testing for serialization issues. The serialization and deserialization process is tested by encoding the route and then decoding it, asserting equality to ensure the round-trip maintains integrity. This is a crucial addition for catching serialization issues early and preventing similar bugs in the future.

However, there are a few points to consider:

  • The use of unwrap in tests is generally acceptable for prototyping or if you're certain the operation won't fail. However, in a library context, especially one as critical as a routing component in a payment network, it might be beneficial to handle errors gracefully even in tests to ensure test failures are clear and actionable.
  • The comment on line 161 has a typo: "hwere" should be corrected to "where".
  • It's good practice to include a brief explanation or reference to the specific issue this test is designed to catch, ensuring future maintainers understand the context and importance of this test case.

Overall, the change is aligned with the PR's objectives and significantly improves the robustness of the serialization process for Route objects.

However, consider refining error handling in tests and correcting the typo in the comment for clarity and maintainability.

lightning/src/routing/router.rs (1)
  • 413-413: The change from using map to and_then for accessing max_total_routing_fee_msat in route_params is a crucial fix for the serialization issue described. This adjustment ensures that max_total_routing_fee_msat is correctly serialized as Option<u64>, addressing the problem of unintended nesting. It's important to verify that this change aligns well with the overall serialization and deserialization processes of Route objects to ensure no unintended side effects occur.

@TheBlueMatt TheBlueMatt force-pushed the 2024-02-fix-route-ser branch 2 times, most recently from c847773 to eda574d Compare February 16, 2024 05:49
Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

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

Ugh, my bad :(


self.router.find_route(payer, params, first_hops, inflight_htlcs)
if let Ok(route) = &route_res {
Copy link
Contributor

Choose a reason for hiding this comment

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

Wonder if this needs to happen for all our tests. Wouldn't it suffice (and be quicker) to add a single test case for this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Maybe, but this gets us a lot more coverage, and potentially gets us coverage of new fields in the future "for free", vs a freestanding test does not.

Copy link
Contributor

@tnull tnull left a comment

Choose a reason for hiding this comment

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

Seems apart from the bug in max_total_routing_fee_msat serialization there is also another issue with serializing the BlindedTails: currently, we seem to write one tail per hop, but expect to read one tail per path (which makes sense). Not sure if there was a reason for the prior scheme, but writing once per path seems to fix it for me, see below (cc @valentinewallace).

There is also a minor test failure due to a BOLT12 test expecting to find an empty route, which we however refuse to read. Expecting to find the empty route might also be a mistake here? (cc @jkczyz)

This diff makes makes tests pass for me:

diff --git a/lightning/src/ln/outbound_payment.rs b/lightning/src/ln/outbound_payment.rs
index c260bac85..be029ae8d 100644
--- a/lightning/src/ln/outbound_payment.rs
+++ b/lightning/src/ln/outbound_payment.rs
@@ -2314,14 +2314,6 @@ mod tests {
                );
                assert!(outbound_payments.has_pending_payments());

-               let route_params = RouteParameters::from_payment_params_and_value(
-                       PaymentParameters::from_bolt12_invoice(&invoice),
-                       invoice.amount_msats(),
-               );
-               router.expect_find_route(
-                       route_params.clone(), Ok(Route { paths: vec![], route_params: Some(route_params) })
-               );
-
                assert_eq!(
                        outbound_payments.send_payment_for_bolt12_invoice(
                                &invoice, payment_id, &&router, vec![], || InFlightHtlcs::new(), &&keys_manager,
@@ -2332,7 +2324,7 @@ mod tests {
                assert!(!outbound_payments.has_pending_payments());

                let payment_hash = invoice.payment_hash();
-               let reason = Some(PaymentFailureReason::UnexpectedError);
+               let reason = Some(PaymentFailureReason::RouteNotFound);

                assert!(!pending_events.lock().unwrap().is_empty());
                assert_eq!(
diff --git a/lightning/src/routing/router.rs b/lightning/src/routing/router.rs
index d8a5ba122..5b555b3b2 100644
--- a/lightning/src/routing/router.rs
+++ b/lightning/src/routing/router.rs
@@ -507,18 +507,10 @@ impl Writeable for Route {
                let mut blinded_tails = Vec::new();
                for path in self.paths.iter() {
                        (path.hops.len() as u8).write(writer)?;
-                       for (idx, hop) in path.hops.iter().enumerate() {
+                       for hop in &path.hops {
                                hop.write(writer)?;
-                               if let Some(blinded_tail) = &path.blinded_tail {
-                                       if blinded_tails.is_empty() {
-                                               blinded_tails = Vec::with_capacity(path.hops.len());
-                                               for _ in 0..idx {
-                                                       blinded_tails.push(None);
-                                               }
-                                       }
-                                       blinded_tails.push(Some(blinded_tail));
-                               } else if !blinded_tails.is_empty() { blinded_tails.push(None); }
                        }
+                       blinded_tails.push(&path.blinded_tail);
                }
                write_tlv_fields!(writer, {
                        // For compatibility with LDK versions prior to 0.0.117, we take the individual

@tnull
Copy link
Contributor

tnull commented Feb 16, 2024

One thought: since we wrote bogus data anyways and broke serialization, could we now make the best of it and have Route take a full RouteParameters instead of the individual fields to avoid the the weird "reconstruct-from-parts" logic?

@TheBlueMatt
Copy link
Collaborator Author

One thought: since we wrote bogus data anyways and broke serialization, could we now make the best of it and have Route take a full RouteParameters instead of the individual fields to avoid the the weird "reconstruct-from-parts" logic?

Sadly, I think the one user who does use this logic may want that :(

@jkczyz
Copy link
Contributor

jkczyz commented Feb 16, 2024

There is also a minor test failure due to a BOLT12 test expecting to find an empty route, which we however refuse to read. Expecting to find the empty route might also be a mistake here? (cc @jkczyz)

fails_paying_for_bolt12_invoice isn't exhaustively testing the possible failures, but was meant to test anything after receiving an Ok result from the router whereas fails_finding_route_for_bolt12_invoice hits the Err case. We can probably just drop fails_paying_for_bolt12_invoice and test those error cases directly via send_payment_with_route. I'm not sure how well the coverage is there.

`fails_paying_for_bolt12_invoice` tests that we fail to send a
payment if the router returns `Ok` but includes a bogus route (one
with 0-length paths). While this marginally increases our test
coverage, in the next commit we'll be testing that all routes
round-trip serialization, which fails here as bogus routes are not
supported in deserialization.

Because this isn't particularly critical test coverage, we simply
opt to drop the test entirely here.
`Route`'s blinded_path serialization logic writes a blinded path
`Option` per path hop, however on read we (correctly) only read one
blinded path `Option` per path. This causes serialization of
`Route`s with blinded paths to fail to round-trip.

Here we fix this by writing blinded paths per path.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between e32020c and ed66196.
Files selected for processing (3)
  • lightning/src/ln/outbound_payment.rs (1 hunks)
  • lightning/src/routing/router.rs (1 hunks)
  • lightning/src/util/test_utils.rs (2 hunks)
Files skipped from review due to trivial changes (1)
  • lightning/src/ln/outbound_payment.rs
Files skipped from review as they are similar to previous changes (2)
  • lightning/src/routing/router.rs
  • lightning/src/util/test_utils.rs

@TheBlueMatt
Copy link
Collaborator Author

Note that we can't swap to just blinded_tails.push(&path.blinded_tail); as that results in writing an even TLV always which breaks downgrade.

@codecov-commenter
Copy link

codecov-commenter commented Feb 16, 2024

Codecov Report

Attention: 3 lines in your changes are missing coverage. Please review.

Comparison is base (3fd4b39) 89.11% compared to head (ed66196) 89.12%.
Report is 4 commits behind head on main.

❗ Current head ed66196 differs from pull request most recent head 24d02ff. Consider uploading reports for the commit 24d02ff to get more accurate results

Files Patch % Lines
lightning/src/routing/router.rs 75.00% 2 Missing and 1 partial ⚠️

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

Additional details and impacted files
@@           Coverage Diff            @@
##             main    #2897    +/-   ##
========================================
  Coverage   89.11%   89.12%            
========================================
  Files         115      115            
  Lines       94232    94354   +122     
  Branches    94232    94354   +122     
========================================
+ Hits        83978    84095   +117     
- Misses       7781     7787     +6     
+ Partials     2473     2472     -1     

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

lightning/src/util/test_utils.rs Outdated Show resolved Hide resolved
lightning/src/util/test_utils.rs Outdated Show resolved Hide resolved
When the `max_total_routing_fee_msat` parameter was added to
`RouteParameters`, the serialization used `map` to get the max fee,
accidentally writing an `Option<Option<u64>>`, but then read it as
an `Option<u64>`. Thus, any `Route`s with a `route_params` written
will fail to be read back.

Luckily, this is an incredibly rarely-used bit of code, so only one
user managed to hit it.
This adds testing for the previous two commits by testing that all
routes generated in testing are able to survive a serialization
round-trip.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Review Status

Actionable comments generated: 0

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between ed66196 and 38a01ed.
Files selected for processing (1)
  • lightning/src/util/test_utils.rs (2 hunks)
Files skipped from review as they are similar to previous changes (1)
  • lightning/src/util/test_utils.rs

@TheBlueMatt
Copy link
Collaborator Author

Redid the commit to make testing a separate commit to aid backporting.

@TheBlueMatt TheBlueMatt merged commit 6fa1cb2 into lightningdevkit:main Feb 16, 2024
11 of 15 checks passed
TheBlueMatt added a commit that referenced this pull request Apr 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants