Skip to content

feat(lightning): add CLN NUT-15 MPP partial payment support#1018

Open
christoph865 wants to merge 3 commits into
cashubtc:mainfrom
christoph865:fix/cln-mpp-nut15
Open

feat(lightning): add CLN NUT-15 MPP partial payment support#1018
christoph865 wants to merge 3 commits into
cashubtc:mainfrom
christoph865:fix/cln-mpp-nut15

Conversation

@christoph865

Copy link
Copy Markdown

Summary:

Add CLN NUT-15 MPP support for partial melts.
Route amount mismatches to partial payments when MPP is enabled.
Return a clear error when MPP is required but disabled.
Add focused CLN MPP tests in tests/lightning/test_clnrest_mpp.py, tests/lightning/test_lightning_backends_mocked.py, and tests/lightning/conftest.py.

Validation:

poetry run pytest -q tests/lightning
poetry run pytest -q test_clnrest_mpp.py test_lightning_backends_mocked.py -k "clnrest or mpp"

Closes #536

@a1denvalu3

Copy link
Copy Markdown
Collaborator

If the aim of the PR is to add support for multinut payments to the wallet, then why does the diff show changes to clnrest.py?

@christoph865

Copy link
Copy Markdown
Author

The intention of the PR is to add multinut payment support for wallet flows on CLN-backed mints. Although the feature is wallet-facing, the actual outgoing Lightning payment is executed by the configured backend. For CLN-backed mints, that backend is clnrest.py, so supporting the wallet flow end-to-end also requires clnrest.py to handle the NUT-15 partial-payment path. That is why the diff includes changes there.

@edgarmuyomba

Copy link
Copy Markdown
Contributor

Hi @christoph865
#536 appears to have been already handled by #551 . I think it was only still open accidentally.
Could you kindly review the existing MPP implementation in clnrest.py file and confirm if this PR adds something not already covered.

@christoph865

Copy link
Copy Markdown
Author

Hi @edgarmuyomba, thanks for pointing this out.

After re-checking clnrest.py against current main, you are right that the core CLN MPP behavior appears to have already been introduced via #551 (including partial payment handling with partial_msat).
So this PR does not introduce the initial CLN MPP implementation itself.

What this PR mainly adds on top is:

  • focused CLN MPP test coverage in test_clnrest_mpp.py
  • fixture setup for lightning tests in conftest.py
  • a small mocked-backend expectation update in test_lightning_backends_mocked.py
  • cleanup/refactoring/logging improvements in clnrest.py
    if you prefer, I can narrow this PR to test-only changes, or close it if you consider it redundant.

@edgarmuyomba

Copy link
Copy Markdown
Contributor

yea, I think you should narrow the PR down to test-only changes.
definitely not redundant

@christoph865

Copy link
Copy Markdown
Author

Done. Narrowed to test-only changes. The diff now adds test_clnrest_mpp.py covering the three MPP code paths in pay_invoice(): partial payment routed correctly, MPP disabled returns error, full payment unaffected.

Comment thread tests/lightning/test_clnrest_mpp.py Outdated
Comment on lines +91 to +138
@pytest.mark.asyncio
async def test_mpp_disabled_returns_error(wallet: CLNRestWallet):
wallet.supports_mpp = False

with patch("cashu.lightning.clnrest.decode") as mock_decode:
mock_invoice = Mock(spec=Bolt11)
mock_invoice.payment_hash = "hash123"
mock_invoice.amount_msat = 1000000
mock_decode.return_value = mock_invoice

quote = create_melt_quote(amount=600000, unit="msat")

result = await wallet.pay_invoice(quote, 1000)

assert result.result == PaymentResult.FAILED
assert result.error_message is not None
assert "does not support MPP" in result.error_message


@pytest.mark.asyncio
async def test_full_payment_no_mpp(wallet: CLNRestWallet):
with patch("cashu.lightning.clnrest.decode") as mock_decode:
mock_invoice = Mock(spec=Bolt11)
mock_invoice.payment_hash = "full_payment_hash"
mock_invoice.amount_msat = 1000000
mock_decode.return_value = mock_invoice

quote = create_melt_quote(amount=1000000, unit="msat")

wallet.client.post = AsyncMock(
return_value=Mock(
is_error=False,
json=lambda: {
"payment_hash": "full_payment_hash",
"payment_preimage": "preimage123",
"amount_sent_msat": 1000100,
"amount_msat": 1000000,
"status": "complete",
},
)
)

result = await wallet.pay_invoice(quote, 1000)

assert result.result == PaymentResult.SETTLED
assert result.preimage == "preimage123"
call_data = wallet.client.post.call_args.kwargs["data"]
assert "partial_msat" not in call_data, "partial_msat must not be sent for full payments"

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.

Nicely done on trimming the PR. Looking at the existing tests though, there's already an established pattern for testing Lightning backends in tests/lightning/test_lightning_backends_mocked.py, including a test for CLNRest without MPP support.
These new files duplicate some of the tests there with a different approach as well.
Could you have a look at the existing file and follow the pattern there to avoid fragmenting the testing convention?

@christoph865

Copy link
Copy Markdown
Author

Done. Moved the MPP tests into test_lightning_backends_mocked.py following the existing pattern (object.__new__, inner Client class, SimpleNamespace, _response()). Removed test_clnrest_mpp.py and conftest.py. The diff now only modifies test_lightning_backends_mocked.py, adding test_clnrest_pay_invoice_mpp_sends_partial_msat and test_clnrest_pay_invoice_full_amount_no_partial_msat.

@edgarmuyomba edgarmuyomba left a comment

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.

Approach is clear and changes are well focused.
LGTM!

@codecov

codecov Bot commented Jun 8, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 75.11%. Comparing base (c913bab) to head (7be69e6).
⚠️ Report is 39 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1018      +/-   ##
==========================================
+ Coverage   75.02%   75.11%   +0.09%     
==========================================
  Files         111      110       -1     
  Lines       12239    12093     -146     
==========================================
- Hits         9182     9084      -98     
+ Misses       3057     3009      -48     

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

Feature request: add NUT-15 Multinut payments to CLNRestWallet

3 participants