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

routerrpc: add validation to MPP params #9603

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

NishantBansal2003
Copy link
Contributor

Change Description

Fixes: #8916
Adds validation to ensure that MPP parameters are compatible with the payment amount before attempting the payment. This prevents payments from entering a path finding loop that would eventually timeout.

Steps to Test

Added itest to validate that a payment with multipath parameters exceeding the allowed maximum amount fails with the expected error.

Pull Request Checklist

Testing

  • Your PR passes all CI checks.
  • Tests covering the positive and negative (error paths) are included.
  • Bug fixes contain tests triggering the bug to prevent regressions.

Code Style and Documentation

📝 Please see our Contribution Guidelines for further guidance.

Copy link
Contributor

coderabbitai bot commented Mar 13, 2025

Important

Review skipped

Auto reviews are limited to specific labels.

🏷️ Labels to auto review (1)
  • llm-review

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ 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>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • 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 testing code 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 gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

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 using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai plan to trigger planning for file edits and PR creation.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • 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/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Member

@yyforyongyu yyforyongyu left a comment

Choose a reason for hiding this comment

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

Thanks for the PR! Left a few comments re the format and test.

FeeLimitMsat: noFeeLimitMsat,
PaymentRequest: payReq,
MaxParts: 10,
MaxShardSizeMsat: 30_000_000,
Copy link
Member

Choose a reason for hiding this comment

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

why do we need to set MaxShardSizeMsat here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

To validate that when the mpp params permit sending the full payment amount, the payment is successfully settled. I'll add a comment in the test to make this clear.

Copy link
Collaborator

@ziggie1984 ziggie1984 Mar 19, 2025

Choose a reason for hiding this comment

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

Instead of just adding here the MaxShardSizeMsat please create a separate test which tests exactly the validation. This separates the test cases nicely and can be build integrating your test above testValidateMPPParams. First trying a lower shard amount and then increasing it and succeeding.

// testValidateMPPParams validates that a payment with multipath parameters
// exceeding the allowed maximum amount fails with the expected error.
func testValidateMPPParams(
ht *lntest.HarnessTest, mts *mppTestScenario, paymentAmt btcutil.Amount,
Copy link
Member

Choose a reason for hiding this comment

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

@@ -53,6 +85,9 @@ func testSendMultiPathPayment(ht *lntest.HarnessTest) {
mts.alice, mts.dave, expectedPolicy, chanPointAliceDave, false,
)

// Validate that multipath payment parameters are enforced correctly.
testValidateMPPParams(ht, mts, paymentAmt)
Copy link
Member

Choose a reason for hiding this comment

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

Also check this in testSendToRouteMultiPath?

Copy link
Member

@yyforyongyu yyforyongyu left a comment

Choose a reason for hiding this comment

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

LGTM🌹

@saubyk saubyk requested a review from ziggie1984 March 19, 2025 16:22
Copy link
Collaborator

@ziggie1984 ziggie1984 left a comment

Choose a reason for hiding this comment

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

Thanks for the fix, left some comments.

// exceeding the allowed maximum amount fails with the expected error.
func testValidateMPPParams(ht *lntest.HarnessTest, mts *mppTestScenario,
paymentAmt btcutil.Amount) {
// Create an invoice from Bob for the payment.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: New line above

FeeLimitMsat: noFeeLimitMsat,
PaymentRequest: payReq,
MaxParts: 10,
MaxShardSizeMsat: 30_000_000,
Copy link
Collaborator

@ziggie1984 ziggie1984 Mar 19, 2025

Choose a reason for hiding this comment

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

Instead of just adding here the MaxShardSizeMsat please create a separate test which tests exactly the validation. This separates the test cases nicely and can be build integrating your test above testValidateMPPParams. First trying a lower shard amount and then increasing it and succeeding.

@NishantBansal2003 NishantBansal2003 force-pushed the validate-mpp branch 3 times, most recently from ae6dfb4 to 791dd97 Compare March 20, 2025 06:43
// payment amount. In other words, the parameters are invalid if
// they do not permit sending the full payment amount.
if rpcPayReq.MaxShardSizeMsat > 0 {
if payIntent.MaxParts > MaxPartsUpperLimit {
Copy link
Member

Choose a reason for hiding this comment

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

this check should be moved to where we assign payIntent.MaxParts at L863 so we can exit early.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's put it at L873, since the MPP will only be used when MaxShardSizeMsat > 0. So, if MaxShardSizeMsat is not being set, then there is no issue with the value of MaxParts?

// Validate that the MPP parameters are compatible with the
// payment amount. In other words, the parameters are invalid if
// they do not permit sending the full payment amount.
if rpcPayReq.MaxShardSizeMsat > 0 {
Copy link
Member

Choose a reason for hiding this comment

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

think we should check payIntent.MaxShardAmt for clarity.

// \ /
// \__ Dave ____/
//
paymentAmt := mts.setupSendPaymentCase()
Copy link
Member

Choose a reason for hiding this comment

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

For a simple test like this, I'd say we use ht.CreateSimpleNetwork instead as we are not testing the routing behavior here.

//
paymentAmt := mts.setupSendPaymentCase()

// First, test that Alice's payment to Bob fails when the multipath
Copy link
Member

Choose a reason for hiding this comment

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

The comment needs to be updated.


success := ht.Run(test.name, func(t *testing.T) {
// Bob creates an invoice for the payment.
payReqs, _, _ := ht.CreatePayReqs(mts.bob, paymentAmt,
Copy link
Member

Choose a reason for hiding this comment

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

I think we can instead create a single invoice for the whole test case as we know only the last send payment will succeed.

require.Equal(ht, hex.EncodeToString(invoices[0].RPreimage),
payment.PaymentPreimage, "preimage doesn't match")

// Verify that Bob records the invoice as settled for the full amount.
Copy link
Member

Choose a reason for hiding this comment

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

Can use ht.AssertInvoiceSettled instead.

Copy link
Collaborator

@ziggie1984 ziggie1984 left a comment

Choose a reason for hiding this comment

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

I think we should just add a unit-test instead of adding a itest.

Moreover let's remove the minRequired parts check yy has a valid point.

@@ -36,6 +36,10 @@ const (
// TODO(roasbeef): make this value dynamic based on expected number of
// attempts for given amount.
DefaultMaxParts = 16

// MaxPartsUpperLimit defines the maximum allowable number of splits
// for MPP when the user is attempting to send a payment.
Copy link
Collaborator

Choose a reason for hiding this comment

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

it's MPP/AMP

// fails with the expected error when the payment amount exceeds the allowed
// maximum, and that when the multipath parameters permit the full payment
// amount, the payment is successfully settled.
func testValidateMPPParams(ht *lntest.HarnessTest) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Personally I think a itest for this change might be a bit overkill, can we not just test the extractIntentFromSendRequest via a unit-test in router_backend_test.go ?

@NishantBansal2003 NishantBansal2003 changed the title routerrpc+itest: add validation to MPP params routerrpc: add validation to MPP params Mar 21, 2025
Copy link
Collaborator

@ziggie1984 ziggie1984 left a comment

Choose a reason for hiding this comment

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

Nice, left some comments.

Comment on lines 481 to 482
maxPartsInput uint32
shardAmountMsat uint64
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: Why not keep the naming here from the above code ?

maxShardAmt, maxParts ?

valid bool
expectedErrorMsg string
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

Missing godoc, would be great if you could properly test the whole extract func. otherwise create a comment which specifies that you are in specific only testing the shard multipart payment logic.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have added tests for the entire extract function.

}

for _, testCase := range testCases {
testCase := testCase
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this iis not necessary anymore with the go compiler option loopvar

for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
runExtractIntentTest(t, backend, testCase)
Copy link
Collaborator

Choose a reason for hiding this comment

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

add t.Parallel() to speed up testing.

Copy link
Member

@yyforyongyu yyforyongyu left a comment

Choose a reason for hiding this comment

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

Nice test!

Copy link
Contributor

@MPins MPins left a comment

Choose a reason for hiding this comment

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

Well done 👍

Left some comments that might help make the test a bit cleaner.

@@ -475,3 +476,348 @@ func testUnmarshalAMP(t *testing.T, test unmarshalAMPTest) {
t.Fatalf("test case has non-standard outcome")
}
}

type ExtractIntentTestCase struct {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: this struct doesn't need to be exported, so it can start with a lowercase letter.

Comment on lines +539 to +541
backend: &RouterBackend{
MaxTotalTimelock: 1000,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Setting MaxTotalTimelock is not needed.

Comment on lines 552 to 554
backend: &RouterBackend{
MaxTotalTimelock: 1000,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Setting MaxTotalTimelock is not needed.

Comment on lines 565 to 577
backend: &RouterBackend{
MaxTotalTimelock: 1000,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Setting MaxTotalTimelock is not needed.

Comment on lines 578 to 601
backend: &RouterBackend{
MaxTotalTimelock: 1000,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Setting MaxTotalTimelock is not needed.

@saubyk saubyk added this to the v0.19.0 milestone Mar 28, 2025
@NishantBansal2003 NishantBansal2003 force-pushed the validate-mpp branch 2 times, most recently from 1adc9fd to e92ae2a Compare March 28, 2025 16:22
Copy link
Contributor

@MPins MPins left a comment

Choose a reason for hiding this comment

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

Looking closer, I found some missing tests and suggest a few naming changes to make it clearer what each test is actually doing.

Besides the suggested additions I made, we could also add a test for the case where the invoice has an amount specified and the payer is not allowed to override it. However, to test that properly, we would need a non-expired invoice. I don't think it's strictly necessary, though.

"outgoing_chan_ids are mutually exclusive",
},
{
name: "Invalid last hop pubkey",
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I would change the name of the test to "Invalid last hop pubkey length".

name: "Invalid last hop pubkey",
backend: &RouterBackend{},
sendReq: &SendPaymentRequest{
OutgoingChanId: 38484,
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Setting OutgoingChanId is not needed.

"mutually exclusive",
},
{
name: "Dest custom records with type below minimum",
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I would change the name of the test to "Dest custom records with type below minimum range"

Comment on lines 572 to 573
name: "Custom record entry with TLV type below " +
"minimum",
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I would change the name of the test to "Custom record entry with TLV type below minimum range"

"payment_request cannot appear together",
},
{
name: "Invalid payment request",
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I would change the name of the test to "Invalid payment request length"

"support AMP payments",
},
{
name: "Invalid payment hash",
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: I would change the name of the test to "Invalid payment hash length"

},
valid: false,
expectedErrorMsg: "invalid vertex length",
},
Copy link
Contributor

Choose a reason for hiding this comment

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

You can add the test below to increase the code coverage:

		{
			name: "total time lock exceeds max allowed",
			backend: &RouterBackend{
				MaxTotalTimelock: 1000,
			},
			sendReq: &SendPaymentRequest{
				CltvLimit: 1001,
			},
			valid: false,
			expectedErrorMsg: "total time lock of 1001 exceeds "+
			"max allowed 1000",
		},

valid: false,
expectedErrorMsg: "sat and msat arguments are " +
"mutually exclusive",
},
Copy link
Contributor

Choose a reason for hiding this comment

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

You can add the test below to increase the code coverage:

		{
			name: "Fee limit cannot be negative",
			backend: &RouterBackend{},
			sendReq: &SendPaymentRequest{
				FeeLimitSat:  -1,
			},
			valid: false,
			expectedErrorMsg: "amount cannot be negative",
		},

@NishantBansal2003
Copy link
Contributor Author

we could also add a test for the case where the invoice has an amount specified and the payer is not allowed to override it. However, to test that properly, we would need a non-expired invoice.

Yes, we require a non-expired invoice, which is why I was unable to add a test for it. Perhaps, in such cases, "itest" would suffice?

Copy link
Contributor

@MPins MPins left a comment

Choose a reason for hiding this comment

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

LGTM

@MPins
Copy link
Contributor

MPins commented Mar 30, 2025

suffice
Maybe creating an iTest is too much just to cover this use case.

@ziggie1984
Copy link
Collaborator

you can use: make unit-cover case=TestExtractIntentFromSendRequest and check the result coverage file for extractIntentFromSendRequest function to check if everything is covered using: go tool cover -html=coverage.txt, thats a pretty nice check on your tests.

Adds validation to ensure that MPP parameters are compatible
with the payment amount before attempting the payment. This
prevents payments from entering a path finding loop that
would eventually timeout.
Signed-off-by: Nishant Bansal <[email protected]>
Signed-off-by: Nishant Bansal <[email protected]>
@NishantBansal2003
Copy link
Contributor Author

you can use: make unit-cover case=TestExtractIntentFromSendRequest and check the result coverage file for extractIntentFromSendRequest function to check if everything is covered using: go tool cover -html=coverage.txt, thats a pretty nice check on your tests.

Thanks, this helped! I have also updated the tests for the latest changes.

@lightninglabs-deploy
Copy link

@ziggie1984: review reminder

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.

[feature] - routing: add additional validation to MPP related send payment params
6 participants