Files
nutshell/tests/test_mint_melt.py
callebtc d8d3037cc5 WIP: New melt flow (#622)
* `PaymentResult`

* ledger: rely on PaymentResult instead of paid flag. Double check for payments marked pending.

* `None` is `PENDING`

* make format

* reflected changes API tests where `PaymentStatus` is used + reflected changes in lnbits

* reflect changes in blink backend and tests

* fix lnbits get_payment_status

* remove paid flag

* fix mypy

* remove more paid flags

* fix strike mypy

* green

* shorten all state checks

* fix

* fix some tests

* gimme 

* fix............

* fix lnbits

* fix error

* lightning refactor

* add more regtest tests

* add tests for pending state and failure

* shorten checks

* use match case for startup check - and remember modified checking_id from pay_invoice

* fix strike pending return

* new tests?

* refactor startup routine into get_melt_quote

* test with purge

* refactor blink

* cleanup responses

* blink: return checking_id on failure

* fix lndgrpc try except

* add more testing for melt branches

* speed things up a bit

* remove comments

* remove comments

* block pending melt quotes

* remove comments

---------

Co-authored-by: lollerfirst <lollerfirst@gmail.com>
2024-09-24 14:55:35 +02:00

298 lines
13 KiB
Python

from typing import List, Tuple
import pytest
import pytest_asyncio
from cashu.core.base import MeltQuote, MeltQuoteState, Proof
from cashu.core.errors import LightningError
from cashu.core.models import PostMeltQuoteRequest
from cashu.core.settings import settings
from cashu.lightning.base import PaymentResult
from cashu.mint.ledger import Ledger
from cashu.wallet.wallet import Wallet
from tests.conftest import SERVER_ENDPOINT
from tests.helpers import (
is_regtest,
)
SEED = "TEST_PRIVATE_KEY"
DERIVATION_PATH = "m/0'/0'/0'"
DECRYPTON_KEY = "testdecryptionkey"
ENCRYPTED_SEED = "U2FsdGVkX1_7UU_-nVBMBWDy_9yDu4KeYb7MH8cJTYQGD4RWl82PALH8j-HKzTrI"
async def assert_err(f, msg):
"""Compute f() and expect an error message 'msg'."""
try:
await f
except Exception as exc:
assert exc.args[0] == msg, Exception(
f"Expected error: {msg}, got: {exc.args[0]}"
)
def assert_amt(proofs: List[Proof], expected: int):
"""Assert amounts the proofs contain."""
assert [p.amount for p in proofs] == expected
@pytest_asyncio.fixture(scope="function")
async def wallet(ledger: Ledger):
wallet1 = await Wallet.with_db(
url=SERVER_ENDPOINT,
db="test_data/wallet_mint_api_deprecated",
name="wallet_mint_api_deprecated",
)
await wallet1.load_mint()
yield wallet1
async def create_pending_melts(
ledger: Ledger, check_id: str = "checking_id"
) -> Tuple[Proof, MeltQuote]:
"""Helper function for startup tests for fakewallet. Creates fake pending melt
quote and fake proofs that are in the pending table that look like they're being
used to pay the pending melt quote."""
quote_id = "quote_id"
quote = MeltQuote(
quote=quote_id,
method="bolt11",
request="asdasd",
checking_id=check_id,
unit="sat",
state=MeltQuoteState.pending,
amount=100,
fee_reserve=1,
)
await ledger.crud.store_melt_quote(
quote=quote,
db=ledger.db,
)
pending_proof = Proof(amount=123, C="asdasd", secret="asdasd", id=quote_id)
await ledger.crud.set_proof_pending(
db=ledger.db,
proof=pending_proof,
quote_id=quote_id,
)
# expect a pending melt quote
melt_quotes = await ledger.crud.get_all_melt_quotes_from_pending_proofs(
db=ledger.db
)
assert melt_quotes
return pending_proof, quote
@pytest.mark.asyncio
@pytest.mark.skipif(is_regtest, reason="only fake wallet")
async def test_fakewallet_pending_quote_get_melt_quote_success(ledger: Ledger):
"""Startup routine test. Expects that a pending proofs are removed form the pending db
after the startup routine determines that the associated melt quote was paid."""
pending_proof, quote = await create_pending_melts(ledger)
states = await ledger.db_read.get_proofs_states([pending_proof.Y])
assert states[0].pending
settings.fakewallet_payment_state = PaymentResult.SETTLED.name
# get_melt_quote should check the payment status and update the db
quote2 = await ledger.get_melt_quote(quote_id=quote.quote)
assert quote2.state == MeltQuoteState.paid
# expect that no pending tokens are in db anymore
melt_quotes = await ledger.crud.get_all_melt_quotes_from_pending_proofs(
db=ledger.db
)
assert not melt_quotes
# expect that proofs are spent
states = await ledger.db_read.get_proofs_states([pending_proof.Y])
assert states[0].spent
@pytest.mark.asyncio
@pytest.mark.skipif(is_regtest, reason="only fake wallet")
async def test_fakewallet_pending_quote_get_melt_quote_pending(ledger: Ledger):
"""Startup routine test. Expects that a pending proofs are removed form the pending db
after the startup routine determines that the associated melt quote was paid."""
pending_proof, quote = await create_pending_melts(ledger)
states = await ledger.db_read.get_proofs_states([pending_proof.Y])
assert states[0].pending
settings.fakewallet_payment_state = PaymentResult.PENDING.name
# get_melt_quote should check the payment status and update the db
quote2 = await ledger.get_melt_quote(quote_id=quote.quote)
assert quote2.state == MeltQuoteState.pending
# expect that pending tokens are still in db
melt_quotes = await ledger.crud.get_all_melt_quotes_from_pending_proofs(
db=ledger.db
)
assert melt_quotes
# expect that proofs are pending
states = await ledger.db_read.get_proofs_states([pending_proof.Y])
assert states[0].pending
@pytest.mark.asyncio
@pytest.mark.skipif(is_regtest, reason="only fake wallet")
async def test_fakewallet_pending_quote_get_melt_quote_failed(ledger: Ledger):
"""Startup routine test. Expects that a pending proofs are removed form the pending db
after the startup routine determines that the associated melt quote was paid."""
pending_proof, quote = await create_pending_melts(ledger)
states = await ledger.db_read.get_proofs_states([pending_proof.Y])
assert states[0].pending
settings.fakewallet_payment_state = PaymentResult.FAILED.name
# get_melt_quote should check the payment status and update the db
quote2 = await ledger.get_melt_quote(quote_id=quote.quote)
assert quote2.state == MeltQuoteState.unpaid
# expect that pending tokens are still in db
melt_quotes = await ledger.crud.get_all_melt_quotes_from_pending_proofs(
db=ledger.db
)
assert not melt_quotes
# expect that proofs are pending
states = await ledger.db_read.get_proofs_states([pending_proof.Y])
assert states[0].unspent
@pytest.mark.asyncio
@pytest.mark.skipif(is_regtest, reason="only fake wallet")
async def test_fakewallet_pending_quote_get_melt_quote_unknown(ledger: Ledger):
"""Startup routine test. Expects that a pending proofs are removed form the pending db
after the startup routine determines that the associated melt quote was paid."""
pending_proof, quote = await create_pending_melts(ledger)
states = await ledger.db_read.get_proofs_states([pending_proof.Y])
assert states[0].pending
settings.fakewallet_payment_state = PaymentResult.UNKNOWN.name
# get_melt_quote(..., purge_unknown=True) should check the payment status and update the db
quote2 = await ledger.get_melt_quote(quote_id=quote.quote, purge_unknown=True)
assert quote2.state == MeltQuoteState.unpaid
# expect that pending tokens are still in db
melt_quotes = await ledger.crud.get_all_melt_quotes_from_pending_proofs(
db=ledger.db
)
assert not melt_quotes
# expect that proofs are pending
states = await ledger.db_read.get_proofs_states([pending_proof.Y])
assert states[0].unspent
@pytest.mark.asyncio
@pytest.mark.skipif(is_regtest, reason="only fake wallet")
async def test_melt_lightning_pay_invoice_settled(ledger: Ledger, wallet: Wallet):
invoice = await wallet.request_mint(64)
await ledger.get_mint_quote(invoice.id) # fakewallet: set the quote to paid
await wallet.mint(64, id=invoice.id)
# invoice_64_sat = "lnbcrt640n1pn0r3tfpp5e30xac756gvd26cn3tgsh8ug6ct555zrvl7vsnma5cwp4g7auq5qdqqcqzzsxqyz5vqsp5xfhtzg0y3mekv6nsdnj43c346smh036t4f8gcfa2zwpxzwcryqvs9qxpqysgqw5juev8y3zxpdu0mvdrced5c6a852f9x7uh57g6fgjgcg5muqzd5474d7xgh770frazel67eejfwelnyr507q46hxqehala880rhlqspw07ta0"
invoice_62_sat = "lnbcrt620n1pn0r3vepp5zljn7g09fsyeahl4rnhuy0xax2puhua5r3gspt7ttlfrley6valqdqqcqzzsxqyz5vqsp577h763sel3q06tfnfe75kvwn5pxn344sd5vnays65f9wfgx4fpzq9qxpqysgqg3re9afz9rwwalytec04pdhf9mvh3e2k4r877tw7dr4g0fvzf9sny5nlfggdy6nduy2dytn06w50ls34qfldgsj37x0ymxam0a687mspp0ytr8"
quote_id = (
await ledger.melt_quote(
PostMeltQuoteRequest(unit="sat", request=invoice_62_sat)
)
).quote
# quote = await ledger.get_melt_quote(quote_id)
settings.fakewallet_payment_state = PaymentResult.SETTLED.name
settings.fakewallet_pay_invoice_state = PaymentResult.SETTLED.name
melt_response = await ledger.melt(proofs=wallet.proofs, quote=quote_id)
assert melt_response.state == MeltQuoteState.paid.value
@pytest.mark.asyncio
@pytest.mark.skipif(is_regtest, reason="only fake wallet")
async def test_melt_lightning_pay_invoice_failed_failed(ledger: Ledger, wallet: Wallet):
invoice = await wallet.request_mint(64)
await ledger.get_mint_quote(invoice.id) # fakewallet: set the quote to paid
await wallet.mint(64, id=invoice.id)
# invoice_64_sat = "lnbcrt640n1pn0r3tfpp5e30xac756gvd26cn3tgsh8ug6ct555zrvl7vsnma5cwp4g7auq5qdqqcqzzsxqyz5vqsp5xfhtzg0y3mekv6nsdnj43c346smh036t4f8gcfa2zwpxzwcryqvs9qxpqysgqw5juev8y3zxpdu0mvdrced5c6a852f9x7uh57g6fgjgcg5muqzd5474d7xgh770frazel67eejfwelnyr507q46hxqehala880rhlqspw07ta0"
invoice_62_sat = "lnbcrt620n1pn0r3vepp5zljn7g09fsyeahl4rnhuy0xax2puhua5r3gspt7ttlfrley6valqdqqcqzzsxqyz5vqsp577h763sel3q06tfnfe75kvwn5pxn344sd5vnays65f9wfgx4fpzq9qxpqysgqg3re9afz9rwwalytec04pdhf9mvh3e2k4r877tw7dr4g0fvzf9sny5nlfggdy6nduy2dytn06w50ls34qfldgsj37x0ymxam0a687mspp0ytr8"
quote_id = (
await ledger.melt_quote(
PostMeltQuoteRequest(unit="sat", request=invoice_62_sat)
)
).quote
# quote = await ledger.get_melt_quote(quote_id)
settings.fakewallet_payment_state = PaymentResult.FAILED.name
settings.fakewallet_pay_invoice_state = PaymentResult.FAILED.name
try:
await ledger.melt(proofs=wallet.proofs, quote=quote_id)
raise AssertionError("Expected LightningError")
except LightningError:
pass
settings.fakewallet_payment_state = PaymentResult.UNKNOWN.name
settings.fakewallet_pay_invoice_state = PaymentResult.FAILED.name
try:
await ledger.melt(proofs=wallet.proofs, quote=quote_id)
raise AssertionError("Expected LightningError")
except LightningError:
pass
settings.fakewallet_payment_state = PaymentResult.FAILED.name
settings.fakewallet_pay_invoice_state = PaymentResult.UNKNOWN.name
try:
await ledger.melt(proofs=wallet.proofs, quote=quote_id)
raise AssertionError("Expected LightningError")
except LightningError:
pass
settings.fakewallet_payment_state = PaymentResult.UNKNOWN.name
settings.fakewallet_pay_invoice_state = PaymentResult.UNKNOWN.name
try:
await ledger.melt(proofs=wallet.proofs, quote=quote_id)
raise AssertionError("Expected LightningError")
except LightningError:
pass
@pytest.mark.asyncio
@pytest.mark.skipif(is_regtest, reason="only fake wallet")
async def test_melt_lightning_pay_invoice_failed_settled(
ledger: Ledger, wallet: Wallet
):
invoice = await wallet.request_mint(64)
await ledger.get_mint_quote(invoice.id) # fakewallet: set the quote to paid
await wallet.mint(64, id=invoice.id)
invoice_62_sat = "lnbcrt620n1pn0r3vepp5zljn7g09fsyeahl4rnhuy0xax2puhua5r3gspt7ttlfrley6valqdqqcqzzsxqyz5vqsp577h763sel3q06tfnfe75kvwn5pxn344sd5vnays65f9wfgx4fpzq9qxpqysgqg3re9afz9rwwalytec04pdhf9mvh3e2k4r877tw7dr4g0fvzf9sny5nlfggdy6nduy2dytn06w50ls34qfldgsj37x0ymxam0a687mspp0ytr8"
quote_id = (
await ledger.melt_quote(
PostMeltQuoteRequest(unit="sat", request=invoice_62_sat)
)
).quote
settings.fakewallet_pay_invoice_state = PaymentResult.FAILED.name
settings.fakewallet_payment_state = PaymentResult.SETTLED.name
melt_response = await ledger.melt(proofs=wallet.proofs, quote=quote_id)
assert melt_response.state == MeltQuoteState.pending.value
# expect that proofs are pending
states = await ledger.db_read.get_proofs_states([p.Y for p in wallet.proofs])
assert all([s.pending for s in states])
@pytest.mark.asyncio
@pytest.mark.skipif(is_regtest, reason="only fake wallet")
async def test_melt_lightning_pay_invoice_failed_pending(
ledger: Ledger, wallet: Wallet
):
invoice = await wallet.request_mint(64)
await ledger.get_mint_quote(invoice.id) # fakewallet: set the quote to paid
await wallet.mint(64, id=invoice.id)
invoice_62_sat = "lnbcrt620n1pn0r3vepp5zljn7g09fsyeahl4rnhuy0xax2puhua5r3gspt7ttlfrley6valqdqqcqzzsxqyz5vqsp577h763sel3q06tfnfe75kvwn5pxn344sd5vnays65f9wfgx4fpzq9qxpqysgqg3re9afz9rwwalytec04pdhf9mvh3e2k4r877tw7dr4g0fvzf9sny5nlfggdy6nduy2dytn06w50ls34qfldgsj37x0ymxam0a687mspp0ytr8"
quote_id = (
await ledger.melt_quote(
PostMeltQuoteRequest(unit="sat", request=invoice_62_sat)
)
).quote
settings.fakewallet_pay_invoice_state = PaymentResult.FAILED.name
settings.fakewallet_payment_state = PaymentResult.PENDING.name
melt_response = await ledger.melt(proofs=wallet.proofs, quote=quote_id)
assert melt_response.state == MeltQuoteState.pending.value
# expect that proofs are pending
states = await ledger.db_read.get_proofs_states([p.Y for p in wallet.proofs])
assert all([s.pending for s in states])