mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 18:44:20 +01:00
NUT-04 and NUT-05: Add state field to quotes (#560)
* wip adding states, tests failing * add state field to quotes * responses from quotes * store correct state * cleaner test * fix swap check * oops
This commit is contained in:
@@ -48,7 +48,7 @@ class DLEQWallet(BaseModel):
|
||||
# ------- PROOFS -------
|
||||
|
||||
|
||||
class SpentState(Enum):
|
||||
class ProofSpentState(Enum):
|
||||
unspent = "UNSPENT"
|
||||
spent = "SPENT"
|
||||
pending = "PENDING"
|
||||
@@ -59,13 +59,13 @@ class SpentState(Enum):
|
||||
|
||||
class ProofState(LedgerEvent):
|
||||
Y: str
|
||||
state: SpentState
|
||||
state: ProofSpentState
|
||||
witness: Optional[str] = None
|
||||
|
||||
@root_validator()
|
||||
def check_witness(cls, values):
|
||||
state, witness = values.get("state"), values.get("witness")
|
||||
if witness is not None and state != SpentState.spent:
|
||||
if witness is not None and state != ProofSpentState.spent:
|
||||
raise ValueError('Witness can only be set if the spent state is "SPENT"')
|
||||
return values
|
||||
|
||||
@@ -268,6 +268,15 @@ class Invoice(BaseModel):
|
||||
time_paid: Union[None, str, int, float] = ""
|
||||
|
||||
|
||||
class MeltQuoteState(Enum):
|
||||
unpaid = "UNPAID"
|
||||
pending = "PENDING"
|
||||
paid = "PAID"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class MeltQuote(LedgerEvent):
|
||||
quote: str
|
||||
method: str
|
||||
@@ -277,6 +286,7 @@ class MeltQuote(LedgerEvent):
|
||||
amount: int
|
||||
fee_reserve: int
|
||||
paid: bool
|
||||
state: MeltQuoteState
|
||||
created_time: Union[int, None] = None
|
||||
paid_time: Union[int, None] = None
|
||||
fee_paid: int = 0
|
||||
@@ -303,6 +313,7 @@ class MeltQuote(LedgerEvent):
|
||||
amount=row["amount"],
|
||||
fee_reserve=row["fee_reserve"],
|
||||
paid=row["paid"],
|
||||
state=MeltQuoteState[row["state"]],
|
||||
created_time=created_time,
|
||||
paid_time=paid_time,
|
||||
fee_paid=row["fee_paid"],
|
||||
@@ -318,6 +329,27 @@ class MeltQuote(LedgerEvent):
|
||||
def kind(self) -> JSONRPCSubscriptionKinds:
|
||||
return JSONRPCSubscriptionKinds.BOLT11_MELT_QUOTE
|
||||
|
||||
# method that is invoked when the `state` attribute is changed. to protect the state from being set to anything else if the current state is paid
|
||||
def __setattr__(self, name, value):
|
||||
# an unpaid quote can only be set to pending or paid
|
||||
if name == "state" and self.state == MeltQuoteState.unpaid:
|
||||
if value != MeltQuoteState.pending and value != MeltQuoteState.paid:
|
||||
raise Exception("Cannot change state of an unpaid quote.")
|
||||
# a paid quote can not be changed
|
||||
if name == "state" and self.state == MeltQuoteState.paid:
|
||||
raise Exception("Cannot change state of a paid quote.")
|
||||
super().__setattr__(name, value)
|
||||
|
||||
|
||||
class MintQuoteState(Enum):
|
||||
unpaid = "UNPAID"
|
||||
paid = "PAID"
|
||||
pending = "PENDING"
|
||||
issued = "ISSUED"
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
|
||||
class MintQuote(LedgerEvent):
|
||||
quote: str
|
||||
@@ -328,6 +360,7 @@ class MintQuote(LedgerEvent):
|
||||
amount: int
|
||||
paid: bool
|
||||
issued: bool
|
||||
state: MintQuoteState
|
||||
created_time: Union[int, None] = None
|
||||
paid_time: Union[int, None] = None
|
||||
expiry: Optional[int] = None
|
||||
@@ -353,6 +386,7 @@ class MintQuote(LedgerEvent):
|
||||
amount=row["amount"],
|
||||
paid=row["paid"],
|
||||
issued=row["issued"],
|
||||
state=MintQuoteState[row["state"]],
|
||||
created_time=created_time,
|
||||
paid_time=paid_time,
|
||||
)
|
||||
@@ -366,6 +400,24 @@ class MintQuote(LedgerEvent):
|
||||
def kind(self) -> JSONRPCSubscriptionKinds:
|
||||
return JSONRPCSubscriptionKinds.BOLT11_MINT_QUOTE
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
# un unpaid quote can only be set to paid
|
||||
if name == "state" and self.state == MintQuoteState.unpaid:
|
||||
if value != MintQuoteState.paid:
|
||||
raise Exception("Cannot change state of an unpaid quote.")
|
||||
# a paid quote can only be set to pending or issued
|
||||
if name == "state" and self.state == MintQuoteState.paid:
|
||||
if value != MintQuoteState.pending and value != MintQuoteState.issued:
|
||||
raise Exception(f"Cannot change state of a paid quote to {value}.")
|
||||
# a pending quote can only be set to paid or issued
|
||||
if name == "state" and self.state == MintQuoteState.pending:
|
||||
if value not in [MintQuoteState.paid, MintQuoteState.issued]:
|
||||
raise Exception("Cannot change state of a pending quote.")
|
||||
# an issued quote cannot be changed
|
||||
if name == "state" and self.state == MintQuoteState.issued:
|
||||
raise Exception("Cannot change state of an issued quote.")
|
||||
super().__setattr__(name, value)
|
||||
|
||||
|
||||
# ------- KEYSETS -------
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ from .base import (
|
||||
BlindedMessage,
|
||||
BlindedMessage_Deprecated,
|
||||
BlindedSignature,
|
||||
MeltQuote,
|
||||
MintQuote,
|
||||
Proof,
|
||||
ProofState,
|
||||
)
|
||||
@@ -98,9 +100,19 @@ class PostMintQuoteRequest(BaseModel):
|
||||
class PostMintQuoteResponse(BaseModel):
|
||||
quote: str # quote id
|
||||
request: str # input payment request
|
||||
paid: bool # whether the request has been paid
|
||||
paid: Optional[
|
||||
bool
|
||||
] # whether the request has been paid # DEPRECATED as per NUT PR #141
|
||||
state: str # state of the quote
|
||||
expiry: Optional[int] # expiry of the quote
|
||||
|
||||
@classmethod
|
||||
def from_mint_quote(self, mint_quote: MintQuote) -> "PostMintQuoteResponse":
|
||||
to_dict = mint_quote.dict()
|
||||
# turn state into string
|
||||
to_dict["state"] = mint_quote.state.value
|
||||
return PostMintQuoteResponse.parse_obj(to_dict)
|
||||
|
||||
|
||||
# ------- API: MINT -------
|
||||
|
||||
@@ -168,9 +180,17 @@ class PostMeltQuoteResponse(BaseModel):
|
||||
quote: str # quote id
|
||||
amount: int # input amount
|
||||
fee_reserve: int # input fee reserve
|
||||
paid: bool # whether the request has been paid
|
||||
paid: bool # whether the request has been paid # DEPRECATED as per NUT PR #136
|
||||
state: str # state of the quote
|
||||
expiry: Optional[int] # expiry of the quote
|
||||
|
||||
@classmethod
|
||||
def from_melt_quote(self, melt_quote: MeltQuote) -> "PostMeltQuoteResponse":
|
||||
to_dict = melt_quote.dict()
|
||||
# turn state into string
|
||||
to_dict["state"] = melt_quote.state.value
|
||||
return PostMeltQuoteResponse.parse_obj(to_dict)
|
||||
|
||||
|
||||
# ------- API: MELT -------
|
||||
|
||||
|
||||
@@ -433,8 +433,8 @@ class LedgerCrudSqlite(LedgerCrud):
|
||||
await (conn or db).execute(
|
||||
f"""
|
||||
INSERT INTO {table_with_schema(db, 'mint_quotes')}
|
||||
(quote, method, request, checking_id, unit, amount, issued, paid, created_time, paid_time)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
(quote, method, request, checking_id, unit, amount, issued, paid, state, created_time, paid_time)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
quote.quote,
|
||||
@@ -445,6 +445,7 @@ class LedgerCrudSqlite(LedgerCrud):
|
||||
quote.amount,
|
||||
quote.issued,
|
||||
quote.paid,
|
||||
quote.state.name,
|
||||
timestamp_from_seconds(db, quote.created_time),
|
||||
timestamp_from_seconds(db, quote.paid_time),
|
||||
),
|
||||
@@ -510,10 +511,11 @@ class LedgerCrudSqlite(LedgerCrud):
|
||||
) -> None:
|
||||
await (conn or db).execute(
|
||||
f"UPDATE {table_with_schema(db, 'mint_quotes')} SET issued = ?, paid = ?,"
|
||||
" paid_time = ? WHERE quote = ?",
|
||||
" state = ?, paid_time = ? WHERE quote = ?",
|
||||
(
|
||||
quote.issued,
|
||||
quote.paid,
|
||||
quote.state.name,
|
||||
timestamp_from_seconds(db, quote.paid_time),
|
||||
quote.quote,
|
||||
),
|
||||
@@ -546,8 +548,8 @@ class LedgerCrudSqlite(LedgerCrud):
|
||||
await (conn or db).execute(
|
||||
f"""
|
||||
INSERT INTO {table_with_schema(db, 'melt_quotes')}
|
||||
(quote, method, request, checking_id, unit, amount, fee_reserve, paid, created_time, paid_time, fee_paid, proof)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
(quote, method, request, checking_id, unit, amount, fee_reserve, paid, state, created_time, paid_time, fee_paid, proof)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
quote.quote,
|
||||
@@ -558,6 +560,7 @@ class LedgerCrudSqlite(LedgerCrud):
|
||||
quote.amount,
|
||||
quote.fee_reserve or 0,
|
||||
quote.paid,
|
||||
quote.state.name,
|
||||
timestamp_from_seconds(db, quote.created_time),
|
||||
timestamp_from_seconds(db, quote.paid_time),
|
||||
quote.fee_paid,
|
||||
@@ -608,10 +611,11 @@ class LedgerCrudSqlite(LedgerCrud):
|
||||
conn: Optional[Connection] = None,
|
||||
) -> None:
|
||||
await (conn or db).execute(
|
||||
f"UPDATE {table_with_schema(db, 'melt_quotes')} SET paid = ?, fee_paid = ?,"
|
||||
" paid_time = ?, proof = ? WHERE quote = ?",
|
||||
f"UPDATE {table_with_schema(db, 'melt_quotes')} SET paid = ?, state = ?,"
|
||||
" fee_paid = ?, paid_time = ?, proof = ? WHERE quote = ?",
|
||||
(
|
||||
quote.paid,
|
||||
quote.state.name,
|
||||
quote.fee_paid,
|
||||
timestamp_from_seconds(db, quote.paid_time),
|
||||
quote.proof,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
from typing import Dict, List
|
||||
|
||||
from ...core.base import Proof, ProofState, SpentState
|
||||
from ...core.base import Proof, ProofSpentState, ProofState
|
||||
from ...core.db import Database
|
||||
from ..crud import LedgerCrud
|
||||
|
||||
@@ -54,14 +54,14 @@ class DbReadHelper:
|
||||
proofs_pending = await self._get_proofs_pending(Ys)
|
||||
for Y in Ys:
|
||||
if Y not in proofs_spent and Y not in proofs_pending:
|
||||
states.append(ProofState(Y=Y, state=SpentState.unspent))
|
||||
states.append(ProofState(Y=Y, state=ProofSpentState.unspent))
|
||||
elif Y not in proofs_spent and Y in proofs_pending:
|
||||
states.append(ProofState(Y=Y, state=SpentState.pending))
|
||||
states.append(ProofState(Y=Y, state=ProofSpentState.pending))
|
||||
else:
|
||||
states.append(
|
||||
ProofState(
|
||||
Y=Y,
|
||||
state=SpentState.spent,
|
||||
state=ProofSpentState.spent,
|
||||
witness=proofs_spent[Y].witness,
|
||||
)
|
||||
)
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import List, Optional
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from ...core.base import Proof, ProofState, SpentState
|
||||
from ...core.base import Proof, ProofSpentState, ProofState
|
||||
from ...core.db import Connection, Database, get_db_connection
|
||||
from ...core.errors import (
|
||||
TransactionError,
|
||||
@@ -50,7 +50,7 @@ class DbWriteHelper:
|
||||
proof=p, db=self.db, quote_id=quote_id, conn=conn
|
||||
)
|
||||
await self.events.submit(
|
||||
ProofState(Y=p.Y, state=SpentState.pending)
|
||||
ProofState(Y=p.Y, state=ProofSpentState.pending)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to set proofs pending: {e}")
|
||||
@@ -72,7 +72,7 @@ class DbWriteHelper:
|
||||
await self.crud.unset_proof_pending(proof=p, db=self.db, conn=conn)
|
||||
if not spent:
|
||||
await self.events.submit(
|
||||
ProofState(Y=p.Y, state=SpentState.unspent)
|
||||
ProofState(Y=p.Y, state=ProofSpentState.unspent)
|
||||
)
|
||||
|
||||
async def _validate_proofs_pending(
|
||||
|
||||
@@ -38,9 +38,9 @@ class LedgerEventManager:
|
||||
|
||||
def serialize_event(self, event: LedgerEvent) -> dict:
|
||||
if isinstance(event, MintQuote):
|
||||
return_dict = PostMintQuoteResponse.parse_obj(event.dict()).dict()
|
||||
return_dict = PostMintQuoteResponse.from_mint_quote(event).dict()
|
||||
elif isinstance(event, MeltQuote):
|
||||
return_dict = PostMeltQuoteResponse.parse_obj(event.dict()).dict()
|
||||
return_dict = PostMeltQuoteResponse.from_melt_quote(event).dict()
|
||||
elif isinstance(event, ProofState):
|
||||
return_dict = event.dict(exclude_unset=True, exclude_none=True)
|
||||
return return_dict
|
||||
|
||||
@@ -11,12 +11,14 @@ from ..core.base import (
|
||||
BlindedMessage,
|
||||
BlindedSignature,
|
||||
MeltQuote,
|
||||
MeltQuoteState,
|
||||
Method,
|
||||
MintKeyset,
|
||||
MintQuote,
|
||||
MintQuoteState,
|
||||
Proof,
|
||||
ProofSpentState,
|
||||
ProofState,
|
||||
SpentState,
|
||||
Unit,
|
||||
)
|
||||
from ..core.crypto import b_dhke
|
||||
@@ -153,6 +155,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
||||
logger.info(f"Melt quote {quote.quote} state: paid")
|
||||
quote.paid_time = int(time.time())
|
||||
quote.paid = True
|
||||
quote.state = MeltQuoteState.paid
|
||||
if payment.fee:
|
||||
quote.fee_paid = payment.fee.to(Unit[quote.unit]).amount
|
||||
quote.proof = payment.preimage or ""
|
||||
@@ -302,7 +305,9 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
||||
proof=p, db=self.db, quote_id=quote_id, conn=conn
|
||||
)
|
||||
await self.events.submit(
|
||||
ProofState(Y=p.Y, state=SpentState.spent, witness=p.witness or None)
|
||||
ProofState(
|
||||
Y=p.Y, state=ProofSpentState.spent, witness=p.witness or None
|
||||
)
|
||||
)
|
||||
|
||||
async def _generate_change_promises(
|
||||
@@ -429,6 +434,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
||||
amount=quote_request.amount,
|
||||
issued=False,
|
||||
paid=False,
|
||||
state=MintQuoteState.unpaid,
|
||||
created_time=int(time.time()),
|
||||
expiry=expiry,
|
||||
)
|
||||
@@ -455,7 +461,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
||||
|
||||
unit, method = self._verify_and_get_unit_method(quote.unit, quote.method)
|
||||
|
||||
if not quote.paid:
|
||||
if quote.state == MintQuoteState.unpaid:
|
||||
if not quote.checking_id:
|
||||
raise CashuError("quote has no checking id")
|
||||
logger.trace(f"Lightning: checking invoice {quote.checking_id}")
|
||||
@@ -465,6 +471,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
||||
if status.paid:
|
||||
logger.trace(f"Setting quote {quote_id} as paid")
|
||||
quote.paid = True
|
||||
quote.state = MintQuoteState.paid
|
||||
quote.paid_time = int(time.time())
|
||||
await self.crud.update_mint_quote(quote=quote, db=self.db)
|
||||
await self.events.submit(quote)
|
||||
@@ -509,6 +516,12 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
||||
raise QuoteNotPaidError()
|
||||
if quote.issued:
|
||||
raise TransactionError("quote already issued")
|
||||
|
||||
if not quote.state == MintQuoteState.paid:
|
||||
raise QuoteNotPaidError()
|
||||
if quote.state == MintQuoteState.issued:
|
||||
raise TransactionError("quote already issued")
|
||||
|
||||
if not quote.unit == output_unit.name:
|
||||
raise TransactionError("quote unit does not match output unit")
|
||||
if not quote.amount == sum_amount_outputs:
|
||||
@@ -518,6 +531,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
||||
|
||||
logger.trace(f"crud: setting quote {quote_id} as issued")
|
||||
quote.issued = True
|
||||
quote.state = MintQuoteState.issued
|
||||
await self.crud.update_mint_quote(quote=quote, db=self.db)
|
||||
|
||||
promises = await self._generate_promises(outputs)
|
||||
@@ -549,6 +563,12 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
||||
raise TransactionError("mint quote already paid")
|
||||
if mint_quote.issued:
|
||||
raise TransactionError("mint quote already issued")
|
||||
|
||||
if mint_quote.state == MintQuoteState.issued:
|
||||
raise TransactionError("mint quote already issued")
|
||||
if mint_quote.state != MintQuoteState.unpaid:
|
||||
raise TransactionError("mint quote already paid")
|
||||
|
||||
if not mint_quote.checking_id:
|
||||
raise TransactionError("mint quote has no checking id")
|
||||
if melt_quote.is_mpp:
|
||||
@@ -658,6 +678,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
||||
unit=unit.name,
|
||||
amount=payment_quote.amount.to(unit).amount,
|
||||
paid=False,
|
||||
state=MeltQuoteState.unpaid,
|
||||
fee_reserve=payment_quote.fee.to(unit).amount,
|
||||
created_time=int(time.time()),
|
||||
expiry=expiry,
|
||||
@@ -670,6 +691,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
||||
amount=quote.amount,
|
||||
fee_reserve=quote.fee_reserve,
|
||||
paid=quote.paid,
|
||||
state=quote.state.value,
|
||||
expiry=quote.expiry,
|
||||
)
|
||||
|
||||
@@ -714,6 +736,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
||||
if status.paid:
|
||||
logger.trace(f"Setting quote {quote_id} as paid")
|
||||
melt_quote.paid = True
|
||||
melt_quote.state = MeltQuoteState.paid
|
||||
if status.fee:
|
||||
melt_quote.fee_paid = status.fee.to(unit).amount
|
||||
if status.preimage:
|
||||
@@ -753,6 +776,8 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
||||
# we settle the transaction internally
|
||||
if melt_quote.paid:
|
||||
raise TransactionError("melt quote already paid")
|
||||
if melt_quote.state != MeltQuoteState.unpaid:
|
||||
raise TransactionError("melt quote already paid")
|
||||
|
||||
# verify amounts from bolt11 invoice
|
||||
bolt11_request = melt_quote.request
|
||||
@@ -768,11 +793,15 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
||||
raise TransactionError("units do not match")
|
||||
if not mint_quote.method == melt_quote.method:
|
||||
raise TransactionError("methods do not match")
|
||||
|
||||
if mint_quote.paid:
|
||||
raise TransactionError("mint quote already paid")
|
||||
if mint_quote.issued:
|
||||
raise TransactionError("mint quote already issued")
|
||||
|
||||
if mint_quote.state != MintQuoteState.unpaid:
|
||||
raise TransactionError("mint quote already paid")
|
||||
|
||||
logger.info(
|
||||
f"Settling bolt11 payment internally: {melt_quote.quote} ->"
|
||||
f" {mint_quote.quote} ({melt_quote.amount} {melt_quote.unit})"
|
||||
@@ -780,9 +809,11 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
||||
|
||||
melt_quote.fee_paid = 0 # no internal fees
|
||||
melt_quote.paid = True
|
||||
melt_quote.state = MeltQuoteState.paid
|
||||
melt_quote.paid_time = int(time.time())
|
||||
|
||||
mint_quote.paid = True
|
||||
mint_quote.state = MintQuoteState.paid
|
||||
mint_quote.paid_time = melt_quote.paid_time
|
||||
|
||||
async with get_db_connection(self.db) as conn:
|
||||
@@ -821,7 +852,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
||||
melt_quote.unit, melt_quote.method
|
||||
)
|
||||
|
||||
if melt_quote.paid:
|
||||
if melt_quote.state != MeltQuoteState.unpaid:
|
||||
raise TransactionError("melt quote already paid")
|
||||
|
||||
# make sure that the outputs (for fee return) are in the same unit as the quote
|
||||
@@ -866,7 +897,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
||||
# settle the transaction internally if there is a mint quote with the same payment request
|
||||
melt_quote = await self.melt_mint_settle_internally(melt_quote, proofs)
|
||||
# quote not paid yet (not internal), pay it with the backend
|
||||
if not melt_quote.paid:
|
||||
if not melt_quote.paid and melt_quote.state == MeltQuoteState.unpaid:
|
||||
logger.debug(f"Lightning: pay invoice {melt_quote.request}")
|
||||
payment = await self.backends[method][unit].pay_invoice(
|
||||
melt_quote, melt_quote.fee_reserve * 1000
|
||||
@@ -887,6 +918,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
||||
melt_quote.proof = payment.preimage
|
||||
# set quote as paid
|
||||
melt_quote.paid = True
|
||||
melt_quote.state = MeltQuoteState.paid
|
||||
melt_quote.paid_time = int(time.time())
|
||||
await self.crud.update_melt_quote(quote=melt_quote, db=self.db)
|
||||
await self.events.submit(melt_quote)
|
||||
|
||||
@@ -773,3 +773,45 @@ async def m019_add_fee_to_keysets(db: Database):
|
||||
await conn.execute(
|
||||
f"UPDATE {table_with_schema(db, 'keysets')} SET input_fee_ppk = 0"
|
||||
)
|
||||
|
||||
|
||||
async def m020_add_state_to_mint_and_melt_quotes(db: Database):
|
||||
async with db.connect() as conn:
|
||||
await conn.execute(
|
||||
f"ALTER TABLE {table_with_schema(db, 'mint_quotes')} ADD COLUMN state TEXT"
|
||||
)
|
||||
await conn.execute(
|
||||
f"ALTER TABLE {table_with_schema(db, 'melt_quotes')} ADD COLUMN state TEXT"
|
||||
)
|
||||
|
||||
# get all melt and mint quotes and figure out the state to set using the `paid` column
|
||||
# and the `paid` and `issued` column respectively
|
||||
# mint quotes:
|
||||
async with db.connect() as conn:
|
||||
rows = await conn.fetchall(
|
||||
f"SELECT * FROM {table_with_schema(db, 'mint_quotes')}"
|
||||
)
|
||||
for row in rows:
|
||||
if row["issued"]:
|
||||
state = "issued"
|
||||
elif row["paid"]:
|
||||
state = "paid"
|
||||
else:
|
||||
state = "unpaid"
|
||||
await conn.execute(
|
||||
f"UPDATE {table_with_schema(db, 'mint_quotes')} SET state = '{state}' WHERE quote = '{row['quote']}'"
|
||||
)
|
||||
|
||||
# melt quotes:
|
||||
async with db.connect() as conn:
|
||||
rows = await conn.fetchall(
|
||||
f"SELECT * FROM {table_with_schema(db, 'melt_quotes')}"
|
||||
)
|
||||
for row in rows:
|
||||
if row["paid"]:
|
||||
state = "paid"
|
||||
else:
|
||||
state = "unpaid"
|
||||
await conn.execute(
|
||||
f"UPDATE {table_with_schema(db, 'melt_quotes')} SET state = '{state}' WHERE quote = '{row['quote']}'"
|
||||
)
|
||||
|
||||
@@ -161,6 +161,7 @@ async def mint_quote(
|
||||
request=quote.request,
|
||||
quote=quote.quote,
|
||||
paid=quote.paid,
|
||||
state=quote.state.value,
|
||||
expiry=quote.expiry,
|
||||
)
|
||||
logger.trace(f"< POST /v1/mint/quote/bolt11: {resp}")
|
||||
@@ -184,6 +185,7 @@ async def get_mint_quote(request: Request, quote: str) -> PostMintQuoteResponse:
|
||||
quote=mint_quote.quote,
|
||||
request=mint_quote.request,
|
||||
paid=mint_quote.paid,
|
||||
state=mint_quote.state.value,
|
||||
expiry=mint_quote.expiry,
|
||||
)
|
||||
logger.trace(f"< GET /v1/mint/quote/bolt11/{quote}")
|
||||
@@ -274,6 +276,7 @@ async def get_melt_quote(request: Request, quote: str) -> PostMeltQuoteResponse:
|
||||
amount=melt_quote.amount,
|
||||
fee_reserve=melt_quote.fee_reserve,
|
||||
paid=melt_quote.paid,
|
||||
state=melt_quote.state.value,
|
||||
expiry=melt_quote.expiry,
|
||||
)
|
||||
logger.trace(f"< GET /v1/melt/quote/bolt11/{quote}")
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Dict, List, Optional
|
||||
from fastapi import APIRouter, Request
|
||||
from loguru import logger
|
||||
|
||||
from ..core.base import BlindedMessage, BlindedSignature, SpentState
|
||||
from ..core.base import BlindedMessage, BlindedSignature, ProofSpentState
|
||||
from ..core.errors import CashuError
|
||||
from ..core.models import (
|
||||
CheckFeesRequest_deprecated,
|
||||
@@ -345,13 +345,13 @@ async def check_spendable_deprecated(
|
||||
spendableList: List[bool] = []
|
||||
pendingList: List[bool] = []
|
||||
for proof_state in proofs_state:
|
||||
if proof_state.state == SpentState.unspent:
|
||||
if proof_state.state == ProofSpentState.unspent:
|
||||
spendableList.append(True)
|
||||
pendingList.append(False)
|
||||
elif proof_state.state == SpentState.spent:
|
||||
elif proof_state.state == ProofSpentState.spent:
|
||||
spendableList.append(False)
|
||||
pendingList.append(False)
|
||||
elif proof_state.state == SpentState.pending:
|
||||
elif proof_state.state == ProofSpentState.pending:
|
||||
spendableList.append(True)
|
||||
pendingList.append(True)
|
||||
return CheckSpendableResponse_deprecated(
|
||||
|
||||
@@ -3,7 +3,7 @@ from typing import Mapping
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from ..core.base import Method, Unit
|
||||
from ..core.base import Method, MintQuoteState, Unit
|
||||
from ..core.db import Database
|
||||
from ..lightning.base import LightningBackend
|
||||
from ..mint.crud import LedgerCrud
|
||||
@@ -40,6 +40,7 @@ class LedgerTasks(SupportsDb, SupportsBackends, SupportsEvents):
|
||||
# set the quote as paid
|
||||
if not quote.paid:
|
||||
quote.paid = True
|
||||
quote.state = MintQuoteState.paid
|
||||
await self.crud.update_mint_quote(quote=quote, db=self.db)
|
||||
logger.trace(f"Quote {quote} set as paid and ")
|
||||
await self.events.submit(quote)
|
||||
|
||||
@@ -15,7 +15,7 @@ import click
|
||||
from click import Context
|
||||
from loguru import logger
|
||||
|
||||
from ...core.base import Invoice, Method, TokenV3, Unit
|
||||
from ...core.base import Invoice, Method, MintQuoteState, TokenV3, Unit
|
||||
from ...core.helpers import sum_proofs
|
||||
from ...core.json_rpc.base import JSONRPCNotficationParams
|
||||
from ...core.logging import configure_logger
|
||||
@@ -296,8 +296,10 @@ async def invoice(ctx: Context, amount: float, id: str, split: int, no_check: bo
|
||||
except Exception:
|
||||
return
|
||||
logger.debug(f"Received callback for quote: {quote}")
|
||||
# we need to sleep to give the callback map some time to be populated
|
||||
time.sleep(0.1)
|
||||
if (
|
||||
quote.paid
|
||||
(quote.paid or quote.state == MintQuoteState.paid.value)
|
||||
and quote.request == invoice.bolt11
|
||||
and msg.subId in subscription.callback_map.keys()
|
||||
):
|
||||
@@ -310,6 +312,9 @@ async def invoice(ctx: Context, amount: float, id: str, split: int, no_check: bo
|
||||
except Exception as e:
|
||||
print(f"Error during mint: {str(e)}")
|
||||
return
|
||||
else:
|
||||
logger.debug("Quote not paid yet.")
|
||||
return
|
||||
|
||||
# user requests an invoice
|
||||
if amount and not id:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import bolt11
|
||||
|
||||
from ...core.base import Amount, SpentState, Unit
|
||||
from ...core.base import Amount, ProofSpentState, Unit
|
||||
from ...core.helpers import sum_promises
|
||||
from ...core.settings import settings
|
||||
from ...lightning.base import (
|
||||
@@ -131,12 +131,12 @@ class LightningWallet(Wallet):
|
||||
if not proofs_states:
|
||||
return PaymentStatus(paid=False) # "states not fount"
|
||||
|
||||
if all([p.state == SpentState.pending for p in proofs_states.states]):
|
||||
if all([p.state == ProofSpentState.pending for p in proofs_states.states]):
|
||||
return PaymentStatus(paid=None) # "pending (with check)"
|
||||
if any([p.state == SpentState.spent for p in proofs_states.states]):
|
||||
if any([p.state == ProofSpentState.spent for p in proofs_states.states]):
|
||||
# NOTE: consider adding this check in wallet.py and mark the invoice as paid if all proofs are spent
|
||||
return PaymentStatus(paid=True) # "paid (with check)"
|
||||
if all([p.state == SpentState.unspent for p in proofs_states.states]):
|
||||
if all([p.state == ProofSpentState.unspent for p in proofs_states.states]):
|
||||
return PaymentStatus(paid=False) # "failed (with check)"
|
||||
return PaymentStatus(paid=None) # "undefined state"
|
||||
|
||||
|
||||
@@ -243,3 +243,81 @@ async def m012_add_fee_to_keysets(db: Database):
|
||||
# add column for storing the fee of a keyset
|
||||
await conn.execute("ALTER TABLE keysets ADD COLUMN input_fee_ppk INTEGER")
|
||||
await conn.execute("UPDATE keysets SET input_fee_ppk = 0")
|
||||
|
||||
|
||||
# # async def m020_add_state_to_mint_and_melt_quotes(db: Database):
|
||||
# # async with db.connect() as conn:
|
||||
# # await conn.execute(
|
||||
# # f"ALTER TABLE {table_with_schema(db, 'mint_quotes')} ADD COLUMN state TEXT"
|
||||
# # )
|
||||
# # await conn.execute(
|
||||
# # f"ALTER TABLE {table_with_schema(db, 'melt_quotes')} ADD COLUMN state TEXT"
|
||||
# # )
|
||||
|
||||
# # # get all melt and mint quotes and figure out the state to set using the `paid` column
|
||||
# # # and the `paid` and `issued` column respectively
|
||||
# # # mint quotes:
|
||||
# # async with db.connect() as conn:
|
||||
# # rows = await conn.fetchall(
|
||||
# # f"SELECT * FROM {table_with_schema(db, 'mint_quotes')}"
|
||||
# # )
|
||||
# # for row in rows:
|
||||
# # if row["issued"]:
|
||||
# # state = "issued"
|
||||
# # elif row["paid"]:
|
||||
# # state = "paid"
|
||||
# # else:
|
||||
# # state = "unpaid"
|
||||
# # await conn.execute(
|
||||
# # f"UPDATE {table_with_schema(db, 'mint_quotes')} SET state = '{state}' WHERE quote = '{row['quote']}'"
|
||||
# # )
|
||||
|
||||
# # # melt quotes:
|
||||
# # async with db.connect() as conn:
|
||||
# # rows = await conn.fetchall(
|
||||
# # f"SELECT * FROM {table_with_schema(db, 'melt_quotes')}"
|
||||
# # )
|
||||
# # for row in rows:
|
||||
# # if row["paid"]:
|
||||
# # state = "paid"
|
||||
# # else:
|
||||
# # state = "unpaid"
|
||||
# # await conn.execute(
|
||||
# # f"UPDATE {table_with_schema(db, 'melt_quotes')} SET state = '{state}' WHERE quote = '{row['quote']}'"
|
||||
# # )
|
||||
# # add the equivalent of the above migration for the wallet here. do not use table_with_schema. use the tables and columns
|
||||
# # as they are defined in the wallet db
|
||||
|
||||
|
||||
# async def m020_add_state_to_mint_and_melt_quotes(db: Database):
|
||||
# async with db.connect() as conn:
|
||||
# await conn.execute("ALTER TABLE mint_quotes ADD COLUMN state TEXT")
|
||||
# await conn.execute("ALTER TABLE melt_quotes ADD COLUMN state TEXT")
|
||||
|
||||
# # get all melt and mint quotes and figure out the state to set using the `paid` column
|
||||
# # and the `paid` and `issued` column respectively
|
||||
# # mint quotes:
|
||||
# async with db.connect() as conn:
|
||||
# rows = await conn.fetchall("SELECT * FROM mint_quotes")
|
||||
# for row in rows:
|
||||
# if row["issued"]:
|
||||
# state = "issued"
|
||||
# elif row["paid"]:
|
||||
# state = "paid"
|
||||
# else:
|
||||
# state = "unpaid"
|
||||
# await conn.execute(
|
||||
# f"UPDATE mint_quotes SET state = '{state}' WHERE quote = '{row['quote']}'"
|
||||
# )
|
||||
|
||||
# # melt quotes:
|
||||
# async with db.connect() as conn:
|
||||
# rows = await conn.fetchall("SELECT * FROM melt_quotes")
|
||||
# for row in rows:
|
||||
# if row["paid"]:
|
||||
# state = "paid"
|
||||
# else:
|
||||
# state = "unpaid"
|
||||
# await conn.execute(
|
||||
# f"UPDATE melt_quotes SET state = '{state}' WHERE quote = '{row['quote']}'"
|
||||
# )
|
||||
|
||||
@@ -47,14 +47,19 @@ class SubscriptionManager:
|
||||
|
||||
try:
|
||||
msg = JSONRPCNotification.parse_raw(message)
|
||||
params = JSONRPCNotficationParams.parse_obj(msg.params)
|
||||
logger.debug(f"Received notification: {msg}")
|
||||
self.callback_map[params.subId](params)
|
||||
except Exception as e:
|
||||
logger.error(f"Error parsing notification: {e}")
|
||||
return
|
||||
try:
|
||||
params = JSONRPCNotficationParams.parse_obj(msg.params)
|
||||
logger.trace(f"Notification params: {params}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error parsing notification params: {e}")
|
||||
return
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
logger.error(f"Error parsing message: {message}")
|
||||
self.callback_map[params.subId](params)
|
||||
return
|
||||
|
||||
def connect(self):
|
||||
self.websocket.run_forever(ping_interval=10, ping_timeout=5)
|
||||
|
||||
@@ -11,9 +11,10 @@ from loguru import logger
|
||||
from ..core.base import (
|
||||
BlindedMessage,
|
||||
BlindedSignature,
|
||||
MeltQuoteState,
|
||||
Proof,
|
||||
ProofSpentState,
|
||||
ProofState,
|
||||
SpentState,
|
||||
Unit,
|
||||
WalletKeyset,
|
||||
)
|
||||
@@ -390,6 +391,7 @@ class LedgerAPI(LedgerAPIDeprecated, object):
|
||||
amount=amount or invoice_obj.amount_msat // 1000,
|
||||
fee_reserve=ret.fee or 0,
|
||||
paid=False,
|
||||
state=MeltQuoteState.unpaid.value,
|
||||
expiry=invoice_obj.expiry,
|
||||
)
|
||||
# END backwards compatibility < 0.15.0
|
||||
@@ -509,11 +511,11 @@ class LedgerAPI(LedgerAPIDeprecated, object):
|
||||
states: List[ProofState] = []
|
||||
for spendable, pending, p in zip(ret.spendable, ret.pending, proofs):
|
||||
if spendable and not pending:
|
||||
states.append(ProofState(Y=p.Y, state=SpentState.unspent))
|
||||
states.append(ProofState(Y=p.Y, state=ProofSpentState.unspent))
|
||||
elif spendable and pending:
|
||||
states.append(ProofState(Y=p.Y, state=SpentState.pending))
|
||||
states.append(ProofState(Y=p.Y, state=ProofSpentState.pending))
|
||||
else:
|
||||
states.append(ProofState(Y=p.Y, state=SpentState.spent))
|
||||
states.append(ProofState(Y=p.Y, state=ProofSpentState.spent))
|
||||
ret = PostCheckStateResponse(states=states)
|
||||
return ret
|
||||
# END backwards compatibility < 0.15.0
|
||||
|
||||
@@ -15,7 +15,7 @@ from ..core.base import (
|
||||
DLEQWallet,
|
||||
Invoice,
|
||||
Proof,
|
||||
SpentState,
|
||||
ProofSpentState,
|
||||
Unit,
|
||||
WalletKeyset,
|
||||
)
|
||||
@@ -924,7 +924,7 @@ class Wallet(
|
||||
if check_spendable:
|
||||
proof_states = await self.check_proof_state(proofs)
|
||||
for i, state in enumerate(proof_states.states):
|
||||
if state.state == SpentState.spent:
|
||||
if state.state == ProofSpentState.spent:
|
||||
invalidated_proofs.append(proofs[i])
|
||||
else:
|
||||
invalidated_proofs = proofs
|
||||
|
||||
@@ -10,6 +10,7 @@ from ..core.base import (
|
||||
BlindedMessage,
|
||||
BlindedMessage_Deprecated,
|
||||
BlindedSignature,
|
||||
MintQuoteState,
|
||||
Proof,
|
||||
WalletKeyset,
|
||||
)
|
||||
@@ -252,6 +253,7 @@ class LedgerAPIDeprecated(SupportsHttpxClient, SupportsMintURL):
|
||||
quote=mint_response.hash,
|
||||
request=mint_response.pr,
|
||||
paid=False,
|
||||
state=MintQuoteState.unpaid.value,
|
||||
expiry=decoded_invoice.date + (decoded_invoice.expiry or 0),
|
||||
)
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ settings.tor = False
|
||||
settings.wallet_unit = "sat"
|
||||
settings.mint_backend_bolt11_sat = settings.mint_backend_bolt11_sat or "FakeWallet"
|
||||
settings.fakewallet_brr = True
|
||||
settings.fakewallet_delay_outgoing_payment = None
|
||||
settings.fakewallet_delay_outgoing_payment = 0
|
||||
settings.fakewallet_delay_incoming_payment = 1
|
||||
settings.fakewallet_stochastic_invoice = False
|
||||
assert (
|
||||
|
||||
@@ -3,7 +3,7 @@ import httpx
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from cashu.core.base import SpentState
|
||||
from cashu.core.base import ProofSpentState
|
||||
from cashu.core.models import (
|
||||
GetInfoResponse,
|
||||
MintMeltMethodSetting,
|
||||
@@ -403,7 +403,7 @@ async def test_api_check_state(ledger: Ledger):
|
||||
response = PostCheckStateResponse.parse_obj(response.json())
|
||||
assert response
|
||||
assert len(response.states) == 2
|
||||
assert response.states[0].state == SpentState.unspent
|
||||
assert response.states[0].state == ProofSpentState.unspent
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
@@ -5,7 +5,7 @@ import bolt11
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from cashu.core.base import MeltQuote, Proof, SpentState
|
||||
from cashu.core.base import MeltQuote, MeltQuoteState, Proof, ProofSpentState
|
||||
from cashu.core.crypto.aes import AESCipher
|
||||
from cashu.core.db import Database
|
||||
from cashu.core.settings import settings
|
||||
@@ -144,6 +144,7 @@ async def create_pending_melts(
|
||||
checking_id=check_id,
|
||||
unit="sat",
|
||||
paid=False,
|
||||
state=MeltQuoteState.unpaid,
|
||||
amount=100,
|
||||
fee_reserve=1,
|
||||
)
|
||||
@@ -172,7 +173,7 @@ async def test_startup_fakewallet_pending_quote_success(ledger: Ledger):
|
||||
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].state == SpentState.pending
|
||||
assert states[0].state == ProofSpentState.pending
|
||||
settings.fakewallet_payment_state = True
|
||||
# run startup routinge
|
||||
await ledger.startup_ledger()
|
||||
@@ -185,7 +186,7 @@ async def test_startup_fakewallet_pending_quote_success(ledger: Ledger):
|
||||
|
||||
# expect that proofs are spent
|
||||
states = await ledger.db_read.get_proofs_states([pending_proof.Y])
|
||||
assert states[0].state == SpentState.spent
|
||||
assert states[0].state == ProofSpentState.spent
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -198,7 +199,7 @@ async def test_startup_fakewallet_pending_quote_failure(ledger: Ledger):
|
||||
"""
|
||||
pending_proof, quote = await create_pending_melts(ledger)
|
||||
states = await ledger.db_read.get_proofs_states([pending_proof.Y])
|
||||
assert states[0].state == SpentState.pending
|
||||
assert states[0].state == ProofSpentState.pending
|
||||
settings.fakewallet_payment_state = False
|
||||
# run startup routinge
|
||||
await ledger.startup_ledger()
|
||||
@@ -211,7 +212,7 @@ async def test_startup_fakewallet_pending_quote_failure(ledger: Ledger):
|
||||
|
||||
# expect that proofs are unspent
|
||||
states = await ledger.db_read.get_proofs_states([pending_proof.Y])
|
||||
assert states[0].state == SpentState.unspent
|
||||
assert states[0].state == ProofSpentState.unspent
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -219,7 +220,7 @@ async def test_startup_fakewallet_pending_quote_failure(ledger: Ledger):
|
||||
async def test_startup_fakewallet_pending_quote_pending(ledger: Ledger):
|
||||
pending_proof, quote = await create_pending_melts(ledger)
|
||||
states = await ledger.db_read.get_proofs_states([pending_proof.Y])
|
||||
assert states[0].state == SpentState.pending
|
||||
assert states[0].state == ProofSpentState.pending
|
||||
settings.fakewallet_payment_state = None
|
||||
# run startup routinge
|
||||
await ledger.startup_ledger()
|
||||
@@ -232,7 +233,7 @@ async def test_startup_fakewallet_pending_quote_pending(ledger: Ledger):
|
||||
|
||||
# expect that proofs are still pending
|
||||
states = await ledger.db_read.get_proofs_states([pending_proof.Y])
|
||||
assert states[0].state == SpentState.pending
|
||||
assert states[0].state == ProofSpentState.pending
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -274,7 +275,7 @@ async def test_startup_regtest_pending_quote_pending(wallet: Wallet, ledger: Led
|
||||
|
||||
# expect that proofs are still pending
|
||||
states = await ledger.db_read.get_proofs_states([p.Y for p in send_proofs])
|
||||
assert all([s.state == SpentState.pending for s in states])
|
||||
assert all([s.state == ProofSpentState.pending for s in states])
|
||||
|
||||
# only now settle the invoice
|
||||
settle_invoice(preimage=preimage)
|
||||
@@ -308,7 +309,7 @@ async def test_startup_regtest_pending_quote_success(wallet: Wallet, ledger: Led
|
||||
await asyncio.sleep(SLEEP_TIME)
|
||||
# expect that proofs are pending
|
||||
states = await ledger.db_read.get_proofs_states([p.Y for p in send_proofs])
|
||||
assert all([s.state == SpentState.pending for s in states])
|
||||
assert all([s.state == ProofSpentState.pending for s in states])
|
||||
|
||||
settle_invoice(preimage=preimage)
|
||||
await asyncio.sleep(SLEEP_TIME)
|
||||
@@ -324,7 +325,7 @@ async def test_startup_regtest_pending_quote_success(wallet: Wallet, ledger: Led
|
||||
|
||||
# expect that proofs are spent
|
||||
states = await ledger.db_read.get_proofs_states([p.Y for p in send_proofs])
|
||||
assert all([s.state == SpentState.spent for s in states])
|
||||
assert all([s.state == ProofSpentState.spent for s in states])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -359,7 +360,7 @@ async def test_startup_regtest_pending_quote_failure(wallet: Wallet, ledger: Led
|
||||
|
||||
# expect that proofs are pending
|
||||
states = await ledger.db_read.get_proofs_states([p.Y for p in send_proofs])
|
||||
assert all([s.state == SpentState.pending for s in states])
|
||||
assert all([s.state == ProofSpentState.pending for s in states])
|
||||
|
||||
cancel_invoice(preimage_hash=preimage_hash)
|
||||
await asyncio.sleep(SLEEP_TIME)
|
||||
@@ -375,4 +376,4 @@ async def test_startup_regtest_pending_quote_failure(wallet: Wallet, ledger: Led
|
||||
|
||||
# expect that proofs are unspent
|
||||
states = await ledger.db_read.get_proofs_states([p.Y for p in send_proofs])
|
||||
assert all([s.state == SpentState.unspent for s in states])
|
||||
assert all([s.state == ProofSpentState.unspent for s in states])
|
||||
|
||||
@@ -2,7 +2,7 @@ import pytest
|
||||
import respx
|
||||
from httpx import Response
|
||||
|
||||
from cashu.core.base import Amount, MeltQuote, Unit
|
||||
from cashu.core.base import Amount, MeltQuote, MeltQuoteState, Unit
|
||||
from cashu.core.models import PostMeltQuoteRequest
|
||||
from cashu.core.settings import settings
|
||||
from cashu.lightning.blink import MINIMUM_FEE_MSAT, BlinkWallet # type: ignore
|
||||
@@ -99,6 +99,7 @@ async def test_blink_pay_invoice():
|
||||
amount=100,
|
||||
fee_reserve=12,
|
||||
paid=False,
|
||||
state=MeltQuoteState.unpaid,
|
||||
)
|
||||
payment = await blink.pay_invoice(quote, 1000)
|
||||
assert payment.ok
|
||||
@@ -131,6 +132,7 @@ async def test_blink_pay_invoice_failure():
|
||||
amount=100,
|
||||
fee_reserve=12,
|
||||
paid=False,
|
||||
state=MeltQuoteState.unpaid,
|
||||
)
|
||||
payment = await blink.pay_invoice(quote, 1000)
|
||||
assert not payment.ok
|
||||
|
||||
@@ -3,7 +3,7 @@ import asyncio
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from cashu.core.base import SpentState
|
||||
from cashu.core.base import ProofSpentState
|
||||
from cashu.mint.ledger import Ledger
|
||||
from cashu.wallet.wallet import Wallet
|
||||
from tests.conftest import SERVER_ENDPOINT
|
||||
@@ -63,7 +63,7 @@ async def test_regtest_pending_quote(wallet: Wallet, ledger: Ledger):
|
||||
|
||||
# expect that proofs are still pending
|
||||
states = await ledger.db_read.get_proofs_states([p.Y for p in send_proofs])
|
||||
assert all([s.state == SpentState.pending for s in states])
|
||||
assert all([s.state == ProofSpentState.pending for s in states])
|
||||
|
||||
# only now settle the invoice
|
||||
settle_invoice(preimage=preimage)
|
||||
@@ -71,7 +71,7 @@ async def test_regtest_pending_quote(wallet: Wallet, ledger: Ledger):
|
||||
|
||||
# expect that proofs are now spent
|
||||
states = await ledger.db_read.get_proofs_states([p.Y for p in send_proofs])
|
||||
assert all([s.state == SpentState.spent for s in states])
|
||||
assert all([s.state == ProofSpentState.spent for s in states])
|
||||
|
||||
# expect that no melt quote is pending
|
||||
melt_quotes = await ledger.crud.get_all_melt_quotes_from_pending_proofs(
|
||||
|
||||
@@ -7,7 +7,7 @@ from typing import List
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from cashu.core.base import Proof, SpentState
|
||||
from cashu.core.base import Proof, ProofSpentState
|
||||
from cashu.core.crypto.secp import PrivateKey, PublicKey
|
||||
from cashu.core.migrations import migrate_databases
|
||||
from cashu.core.p2pk import SigFlags
|
||||
@@ -80,7 +80,7 @@ async def test_p2pk(wallet1: Wallet, wallet2: Wallet):
|
||||
await wallet2.redeem(send_proofs)
|
||||
|
||||
proof_states = await wallet2.check_proof_state(send_proofs)
|
||||
assert all([p.state == SpentState.spent for p in proof_states.states])
|
||||
assert all([p.state == ProofSpentState.spent for p in proof_states.states])
|
||||
|
||||
if not is_deprecated_api_only:
|
||||
for state in proof_states.states:
|
||||
|
||||
@@ -4,7 +4,7 @@ import bolt11
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from cashu.core.base import SpentState
|
||||
from cashu.core.base import ProofSpentState
|
||||
from cashu.mint.ledger import Ledger
|
||||
from cashu.wallet.wallet import Wallet
|
||||
from tests.conftest import SERVER_ENDPOINT
|
||||
@@ -57,14 +57,14 @@ async def test_regtest_pending_quote(wallet: Wallet, ledger: Ledger):
|
||||
await asyncio.sleep(SLEEP_TIME)
|
||||
|
||||
states = await wallet.check_proof_state(send_proofs)
|
||||
assert all([s.state == SpentState.pending for s in states.states])
|
||||
assert all([s.state == ProofSpentState.pending for s in states.states])
|
||||
|
||||
settle_invoice(preimage=preimage)
|
||||
|
||||
await asyncio.sleep(SLEEP_TIME)
|
||||
|
||||
states = await wallet.check_proof_state(send_proofs)
|
||||
assert all([s.state == SpentState.spent for s in states.states])
|
||||
assert all([s.state == ProofSpentState.spent for s in states.states])
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -97,11 +97,11 @@ async def test_regtest_failed_quote(wallet: Wallet, ledger: Ledger):
|
||||
await asyncio.sleep(SLEEP_TIME)
|
||||
|
||||
states = await wallet.check_proof_state(send_proofs)
|
||||
assert all([s.state == SpentState.pending for s in states.states])
|
||||
assert all([s.state == ProofSpentState.pending for s in states.states])
|
||||
|
||||
cancel_invoice(preimage_hash=preimage_hash)
|
||||
|
||||
await asyncio.sleep(SLEEP_TIME)
|
||||
|
||||
states = await wallet.check_proof_state(send_proofs)
|
||||
assert all([s.state == SpentState.unspent for s in states.states])
|
||||
assert all([s.state == ProofSpentState.unspent for s in states.states])
|
||||
|
||||
@@ -3,7 +3,7 @@ import asyncio
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from cashu.core.base import Method, ProofState
|
||||
from cashu.core.base import Method, MintQuoteState, ProofSpentState, ProofState
|
||||
from cashu.core.json_rpc.base import JSONRPCNotficationParams
|
||||
from cashu.core.nuts import WEBSOCKETS_NUT
|
||||
from cashu.core.settings import settings
|
||||
@@ -51,20 +51,17 @@ async def test_wallet_subscription_mint(wallet: Wallet):
|
||||
wait = settings.fakewallet_delay_incoming_payment or 2
|
||||
await asyncio.sleep(wait + 2)
|
||||
|
||||
# TODO: check for pending and paid states according to: https://github.com/cashubtc/nuts/pull/136
|
||||
# TODO: we have three messages here, but the value "paid" only changes once
|
||||
# the mint sends an update when the quote is pending but the API does not express that yet
|
||||
|
||||
# first we expect the issued=False state to arrive
|
||||
|
||||
assert triggered
|
||||
assert len(msg_stack) == 3
|
||||
|
||||
assert msg_stack[0].payload["paid"] is False
|
||||
assert msg_stack[0].payload["state"] == MintQuoteState.unpaid.value
|
||||
|
||||
assert msg_stack[1].payload["paid"] is True
|
||||
assert msg_stack[1].payload["state"] == MintQuoteState.paid.value
|
||||
|
||||
assert msg_stack[2].payload["paid"] is True
|
||||
assert msg_stack[2].payload["state"] == MintQuoteState.issued.value
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -103,16 +100,16 @@ async def test_wallet_subscription_swap(wallet: Wallet):
|
||||
pending_stack = msg_stack[:n_subscriptions]
|
||||
for msg in pending_stack:
|
||||
proof_state = ProofState.parse_obj(msg.payload)
|
||||
assert proof_state.state.value == "UNSPENT"
|
||||
assert proof_state.state == ProofSpentState.unspent
|
||||
|
||||
# the second one is the PENDING state
|
||||
spent_stack = msg_stack[n_subscriptions : n_subscriptions * 2]
|
||||
for msg in spent_stack:
|
||||
proof_state = ProofState.parse_obj(msg.payload)
|
||||
assert proof_state.state.value == "PENDING"
|
||||
assert proof_state.state == ProofSpentState.pending
|
||||
|
||||
# the third one is the SPENT state
|
||||
spent_stack = msg_stack[n_subscriptions * 2 :]
|
||||
for msg in spent_stack:
|
||||
proof_state = ProofState.parse_obj(msg.payload)
|
||||
assert proof_state.state.value == "SPENT"
|
||||
assert proof_state.state == ProofSpentState.spent
|
||||
|
||||
Reference in New Issue
Block a user