Skip to content

Commit

Permalink
CLN multinut test fix (#602)
Browse files Browse the repository at this point in the history
  • Loading branch information
lollerfirst authored Jul 30, 2024
1 parent e14dc6d commit 77ba356
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 13 deletions.
5 changes: 4 additions & 1 deletion cashu/lightning/clnrest.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
class CLNRestWallet(LightningBackend):
supported_units = set([Unit.sat, Unit.msat])
unit = Unit.sat
supports_mpp = False # settings.mint_clnrest_enable_mpp
supports_mpp = settings.mint_clnrest_enable_mpp
supports_incoming_payment_stream: bool = True

def __init__(self, unit: Unit = Unit.sat, **kwargs):
Expand Down Expand Up @@ -195,11 +195,14 @@ async def pay_invoice(
}

# Handle Multi-Mint payout where we must only pay part of the invoice amount
logger.trace(f"{quote_amount_msat = }, {invoice.amount_msat = }")
if quote_amount_msat != invoice.amount_msat:
logger.trace("Detected Multi-Nut payment")
if self.supports_mpp:
post_data["partial_msat"] = quote_amount_msat
else:
error_message = "mint does not support MPP"
logger.error(error_message)
return PaymentResponse(
ok=False,
checking_id=None,
Expand Down
14 changes: 14 additions & 0 deletions tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,16 @@ async def get_random_invoice_data():
"--rpcserver=lnd-2",
]

def docker_clightning_cli(index):
return [
"docker",
"exec",
f"cashu-clightning-{index}-1",
"lightning-cli",
"--network",
"regtest",
"--keywords",
]

def run_cmd(cmd: list) -> str:
timeout = 20
Expand Down Expand Up @@ -161,6 +171,10 @@ def pay_real_invoice(invoice: str) -> str:
cmd.extend(["payinvoice", "--force", invoice])
return run_cmd(cmd)

def partial_pay_real_invoice(invoice: str, amount: int, node: int) -> str:
cmd = docker_clightning_cli(node)
cmd.extend(["pay", f"bolt11={invoice}", f"partial_msat={amount*1000}"])
return run_cmd(cmd)

def mine_blocks(blocks: int = 1) -> str:
cmd = docker_bitcoin_cli.copy()
Expand Down
33 changes: 21 additions & 12 deletions tests/test_wallet_regtest_mpp.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
import asyncio
import threading
from typing import List

import pytest
import pytest_asyncio

from cashu.core.base import Method, Proof
from cashu.lightning.clnrest import CLNRestWallet
from cashu.mint.ledger import Ledger
from cashu.wallet.wallet import Wallet
from tests.conftest import SERVER_ENDPOINT
from tests.helpers import (
get_real_invoice,
is_fake,
partial_pay_real_invoice,
pay_if_regtest,
)

Expand Down Expand Up @@ -42,32 +45,33 @@ async def test_regtest_pay_mpp(wallet: Wallet, ledger: Ledger):
proofs1 = await wallet.mint(128, id=topup_invoice.id)
assert wallet.balance == 128

topup_invoice = await wallet.request_mint(128)
await pay_if_regtest(topup_invoice.bolt11)
proofs2 = await wallet.mint(128, id=topup_invoice.id)
assert wallet.balance == 256

# this is the invoice we want to pay in two parts
invoice_dict = get_real_invoice(64)
invoice_payment_request = invoice_dict["payment_request"]

async def pay_mpp(amount: int, proofs: List[Proof], delay: float = 0.0):
await asyncio.sleep(delay)
async def _mint_pay_mpp(invoice: str, amount: int, proofs: List[Proof]):
# wallet pays 32 sat of the invoice
quote = await wallet.melt_quote(invoice_payment_request, amount=amount)
quote = await wallet.melt_quote(invoice, amount=amount)
assert quote.amount == amount
await wallet.melt(
proofs,
invoice_payment_request,
invoice,
fee_reserve_sat=quote.fee_reserve,
quote_id=quote.quote,
)
def mint_pay_mpp(invoice: str, amount: int, proofs: List[Proof]):
asyncio.run(_mint_pay_mpp(invoice, amount, proofs))

# call pay_mpp twice in parallel to pay the full invoice
# we delay the second payment so that the wallet doesn't derive the same blindedmessages twice due to a race condition
await asyncio.gather(pay_mpp(32, proofs1), pay_mpp(32, proofs2, delay=0.5))
t1 = threading.Thread(target=mint_pay_mpp, args=(invoice_payment_request, 32, proofs1))
t2 = threading.Thread(target=partial_pay_real_invoice, args=(invoice_payment_request, 32, 1))

t1.start()
t2.start()
t1.join()
t2.join()

assert wallet.balance <= 256 - 64
assert wallet.balance <= 256 - 32


@pytest.mark.asyncio
Expand All @@ -76,6 +80,11 @@ async def test_regtest_pay_mpp_incomplete_payment(wallet: Wallet, ledger: Ledger
# make sure that mpp is supported by the bolt11-sat backend
if not ledger.backends[Method["bolt11"]][wallet.unit].supports_mpp:
pytest.skip("backend does not support mpp")

# This test cannot be done with CLN because we only have one mint
# and CLN hates multiple partial payment requests
if isinstance(ledger.backends[Method["bolt11"]][wallet.unit], CLNRestWallet):
pytest.skip("CLN cannot perform this test")

# make sure wallet knows the backend supports mpp
assert wallet.mint_info.supports_mpp("bolt11", wallet.unit)
Expand Down

0 comments on commit 77ba356

Please sign in to comment.