Files
nutshell/tests/mint/test_mint_melt.py
lollerfirst 4a4b7f79f7 fix: unique ('PENDING', checking_ID) for melt quotes (#800)
* fix: only one melt_quote with shared checking_id is allowed to be in a pending state.

fix mypy

add comprehensive tests

remove SQL unique index

remove test db constraint

fix lock statement

remove `test_regtest_pay_mpp_incomplete_payment`

format

* remove `test_set_melt_quote_pending_with_outputs`

* client self-rug mitigation

* fix

* format

* DB level check: error if payment reference paid or pending

* fix test

* comments

* restore

* restore

---------

Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
2025-10-28 11:47:23 +01:00

569 lines
23 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 LightningPaymentFailedError
from cashu.core.models import PostMeltQuoteRequest, PostMintQuoteRequest
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 (
get_real_invoice,
is_fake,
is_regtest,
pay_if_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=ledger.keyset.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(..., rollback_unknown=True) should check the payment status and update the db
quote2 = await ledger.get_melt_quote(quote_id=quote.quote, rollback_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):
mint_quote = await wallet.request_mint(64)
await ledger.get_mint_quote(mint_quote.quote) # fakewallet: set the quote to paid
await wallet.mint(64, quote_id=mint_quote.quote)
# 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):
mint_quote = await wallet.request_mint(64)
await ledger.get_mint_quote(mint_quote.quote) # fakewallet: set the quote to paid
await wallet.mint(64, quote_id=mint_quote.quote)
# 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 LightningPaymentFailedError")
except LightningPaymentFailedError:
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 LightningPaymentFailedError")
except LightningPaymentFailedError:
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 LightningPaymentFailedError")
except LightningPaymentFailedError:
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 LightningPaymentFailedError")
except LightningPaymentFailedError:
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
):
mint_quote = await wallet.request_mint(64)
await ledger.get_mint_quote(mint_quote.quote) # fakewallet: set the quote to paid
await wallet.mint(64, quote_id=mint_quote.quote)
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
):
mint_quote = await wallet.request_mint(64)
await ledger.get_mint_quote(mint_quote.quote) # fakewallet: set the quote to paid
await wallet.mint(64, quote_id=mint_quote.quote)
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])
@pytest.mark.asyncio
@pytest.mark.skipif(is_regtest, reason="only fake wallet")
async def test_melt_lightning_pay_invoice_exception_exception(
ledger: Ledger, wallet: Wallet
):
"""Simulates the case where pay_invoice and get_payment_status raise an exception (due to network issues for example)."""
settings.mint_disable_melt_on_error = True
mint_quote = await wallet.request_mint(64)
await ledger.get_mint_quote(mint_quote.quote) # fakewallet: set the quote to paid
await wallet.mint(64, quote_id=mint_quote.quote)
# 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_exception = True
settings.fakewallet_pay_invoice_state_exception = True
# we expect a pending melt quote because something has gone wrong (for example has lost connection to backend)
resp = await ledger.melt(proofs=wallet.proofs, quote=quote_id)
assert resp.state == MeltQuoteState.pending.value
# the mint should be locked now and not allow any other melts until it is restarted
quote_id = (
await ledger.melt_quote(
PostMeltQuoteRequest(unit="sat", request=invoice_62_sat)
)
).quote
await assert_err(
ledger.melt(proofs=wallet.proofs, quote=quote_id),
"Melt is disabled. Please contact the operator.",
)
@pytest.mark.asyncio
@pytest.mark.skipif(is_regtest, reason="only fake wallet")
async def test_mint_melt_different_units(ledger: Ledger, wallet: Wallet):
"""Mint and melt different units."""
# load the wallet
mint_quote = await wallet.request_mint(64)
await wallet.mint(64, quote_id=mint_quote.quote)
amount = 32
# mint quote in sat
sat_mint_quote = await ledger.mint_quote(
quote_request=PostMintQuoteRequest(amount=amount, unit="sat")
)
sat_invoice = sat_mint_quote.request
assert sat_mint_quote.paid is False
# melt quote in usd
usd_melt_quote = await ledger.melt_quote(
PostMeltQuoteRequest(unit="usd", request=sat_invoice)
)
assert usd_melt_quote.paid is False
# pay melt quote with usd
await ledger.melt(proofs=wallet.proofs, quote=usd_melt_quote.quote)
output_amounts = [32]
secrets, rs, derivation_paths = await wallet.generate_n_secrets(len(output_amounts))
outputs, rs = wallet._construct_outputs(output_amounts, secrets, rs)
# mint in sat
mint_resp = await ledger.mint(outputs=outputs, quote_id=sat_mint_quote.quote)
assert len(mint_resp) == len(outputs)
# Tests for unique pending melt quote checking_id constraint
@pytest.mark.asyncio
async def test_set_melt_quote_pending_without_checking_id(ledger: Ledger):
"""Test that setting a melt quote as pending without a checking_id raises an error."""
from cashu.core.errors import TransactionError
quote = MeltQuote(
quote="quote_id_no_checking",
method="bolt11",
request="lnbc123",
checking_id="temp_id",
unit="sat",
amount=100,
fee_reserve=1,
state=MeltQuoteState.unpaid,
)
await ledger.crud.store_melt_quote(quote=quote, db=ledger.db)
# Set checking_id to empty to simulate the error condition
quote.checking_id = ""
try:
await ledger.db_write._set_melt_quote_pending(quote=quote)
raise AssertionError("Expected TransactionError")
except TransactionError as e:
assert "Melt quote doesn't have checking ID" in str(e)
@pytest.mark.asyncio
async def test_set_melt_quote_pending_prevents_duplicate_checking_id(ledger: Ledger):
"""Test that setting a melt quote as pending fails if another quote with same checking_id is already pending."""
from cashu.core.errors import TransactionError
checking_id = "test_checking_id_duplicate"
quote1 = MeltQuote(
quote="quote_id_dup_first",
method="bolt11",
request="lnbc123",
checking_id=checking_id,
unit="sat",
amount=100,
fee_reserve=1,
state=MeltQuoteState.unpaid,
)
quote2 = MeltQuote(
quote="quote_id_dup_second",
method="bolt11",
request="lnbc456",
checking_id=checking_id,
unit="sat",
amount=200,
fee_reserve=2,
state=MeltQuoteState.unpaid,
)
await ledger.crud.store_melt_quote(quote=quote1, db=ledger.db)
await ledger.crud.store_melt_quote(quote=quote2, db=ledger.db)
# Set the first quote as pending
await ledger.db_write._set_melt_quote_pending(quote=quote1)
# Verify the first quote is pending
quote1_db = await ledger.crud.get_melt_quote(quote_id="quote_id_dup_first", db=ledger.db)
assert quote1_db.state == MeltQuoteState.pending
# Attempt to set the second quote as pending should fail
try:
await ledger.db_write._set_melt_quote_pending(quote=quote2)
raise AssertionError("Expected TransactionError")
except TransactionError as e:
assert "Melt quote already paid or pending." in str(e)
# Verify the second quote is still unpaid
quote2_db = await ledger.crud.get_melt_quote(quote_id="quote_id_dup_second", db=ledger.db)
assert quote2_db.state == MeltQuoteState.unpaid
@pytest.mark.asyncio
async def test_set_melt_quote_pending_allows_different_checking_id(ledger: Ledger):
"""Test that setting melt quotes as pending succeeds when they have different checking_ids."""
checking_id_1 = "test_checking_id_allow_1"
checking_id_2 = "test_checking_id_allow_2"
quote1 = MeltQuote(
quote="quote_id_allow_1",
method="bolt11",
request="lnbc123",
checking_id=checking_id_1,
unit="sat",
amount=100,
fee_reserve=1,
state=MeltQuoteState.unpaid,
)
quote2 = MeltQuote(
quote="quote_id_allow_2",
method="bolt11",
request="lnbc456",
checking_id=checking_id_2,
unit="sat",
amount=200,
fee_reserve=2,
state=MeltQuoteState.unpaid,
)
await ledger.crud.store_melt_quote(quote=quote1, db=ledger.db)
await ledger.crud.store_melt_quote(quote=quote2, db=ledger.db)
# Set both quotes as pending - should succeed
await ledger.db_write._set_melt_quote_pending(quote=quote1)
await ledger.db_write._set_melt_quote_pending(quote=quote2)
# Verify both quotes are pending
quote1_db = await ledger.crud.get_melt_quote(quote_id="quote_id_allow_1", db=ledger.db)
quote2_db = await ledger.crud.get_melt_quote(quote_id="quote_id_allow_2", db=ledger.db)
assert quote1_db.state == MeltQuoteState.pending
assert quote2_db.state == MeltQuoteState.pending
@pytest.mark.asyncio
async def test_set_melt_quote_pending_after_unset(ledger: Ledger):
"""Test that a quote can be set as pending again after being unset."""
checking_id = "test_checking_id_unset_test"
quote1 = MeltQuote(
quote="quote_id_unset_first",
method="bolt11",
request="lnbc123",
checking_id=checking_id,
unit="sat",
amount=100,
fee_reserve=1,
state=MeltQuoteState.unpaid,
)
quote2 = MeltQuote(
quote="quote_id_unset_second",
method="bolt11",
request="lnbc456",
checking_id=checking_id,
unit="sat",
amount=200,
fee_reserve=2,
state=MeltQuoteState.unpaid,
)
await ledger.crud.store_melt_quote(quote=quote1, db=ledger.db)
await ledger.crud.store_melt_quote(quote=quote2, db=ledger.db)
# Set the first quote as pending
quote1_pending = await ledger.db_write._set_melt_quote_pending(quote=quote1)
assert quote1_pending.state == MeltQuoteState.pending
# Unset the first quote (mark as paid)
await ledger.db_write._unset_melt_quote_pending(quote=quote1_pending, state=MeltQuoteState.paid)
# Verify the first quote is no longer pending
quote1_db = await ledger.crud.get_melt_quote(quote_id="quote_id_unset_first", db=ledger.db)
assert quote1_db.state == MeltQuoteState.paid
# Now the second quote should still
assert_err(ledger.db_write._set_melt_quote_pending(quote=quote2), "Melt quote already paid or pending.")
# Verify the second quote is unpaid
quote2_db = await ledger.crud.get_melt_quote(quote_id="quote_id_unset_second", db=ledger.db)
assert quote2_db.state == MeltQuoteState.unpaid
@pytest.mark.asyncio
@pytest.mark.skipif(is_fake, reason="only regtest")
async def test_mint_pay_with_duplicate_checking_id(wallet):
mint_quote1 = await wallet.request_mint(1024)
mint_quote2 = await wallet.request_mint(1024)
await pay_if_regtest(mint_quote1.request)
await pay_if_regtest(mint_quote2.request)
proofs1 = await wallet.mint(amount=1024, quote_id=mint_quote1.quote)
proofs2 = await wallet.mint(amount=1024, quote_id=mint_quote2.quote)
invoice = get_real_invoice(64)['payment_request']
# Get two melt quotes for the same invoice
melt_quote1 = await wallet.melt_quote(invoice)
melt_quote2 = await wallet.melt_quote(invoice)
response1 = await wallet.melt(
proofs=proofs1, invoice=invoice, fee_reserve_sat=melt_quote1.fee_reserve, quote_id=melt_quote1.quote
)
assert response1.state == 'PAID'
assert_err(wallet.melt(
proofs=proofs2, invoice=invoice, fee_reserve_sat=melt_quote2.fee_reserve, quote_id=melt_quote2.quote
), "Melt quote already paid or pending.")