Skip to content

Commit

Permalink
Merge branch 'main' into mint-strike-btc-euro
Browse files Browse the repository at this point in the history
  • Loading branch information
callebtc committed Jun 26, 2024
2 parents 103cc0a + 6b38ef6 commit 030347d
Show file tree
Hide file tree
Showing 26 changed files with 330 additions and 84 deletions.
58 changes: 55 additions & 3 deletions cashu/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class DLEQWallet(BaseModel):
# ------- PROOFS -------


class SpentState(Enum):
class ProofSpentState(Enum):
unspent = "UNSPENT"
spent = "SPENT"
pending = "PENDING"
Expand All @@ -59,13 +59,13 @@ def __str__(self):

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

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -303,6 +313,7 @@ def from_row(cls, row: Row):
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"],
Expand All @@ -318,6 +329,27 @@ def identifier(self) -> str:
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
Expand All @@ -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
Expand All @@ -353,6 +386,7 @@ def from_row(cls, row: Row):
amount=row["amount"],
paid=row["paid"],
issued=row["issued"],
state=MintQuoteState[row["state"]],
created_time=created_time,
paid_time=paid_time,
)
Expand All @@ -366,6 +400,24 @@ def identifier(self) -> str:
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 -------

Expand Down
24 changes: 22 additions & 2 deletions cashu/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
BlindedMessage,
BlindedMessage_Deprecated,
BlindedSignature,
MeltQuote,
MintQuote,
Proof,
ProofState,
)
Expand Down Expand Up @@ -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 -------

Expand Down Expand Up @@ -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 -------

Expand Down
18 changes: 11 additions & 7 deletions cashu/mint/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,8 +433,8 @@ async def store_mint_quote(
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,
Expand All @@ -445,6 +445,7 @@ async def store_mint_quote(
quote.amount,
quote.issued,
quote.paid,
quote.state.name,
timestamp_from_seconds(db, quote.created_time),
timestamp_from_seconds(db, quote.paid_time),
),
Expand Down Expand Up @@ -510,10 +511,11 @@ async def update_mint_quote(
) -> 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,
),
Expand Down Expand Up @@ -546,8 +548,8 @@ async def store_melt_quote(
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,
Expand All @@ -558,6 +560,7 @@ async def store_melt_quote(
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,
Expand Down Expand Up @@ -608,10 +611,11 @@ async def update_melt_quote(
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,
Expand Down
8 changes: 4 additions & 4 deletions cashu/mint/db/read.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -54,14 +54,14 @@ async def get_proofs_states(self, Ys: List[str]) -> List[ProofState]:
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,
)
)
Expand Down
6 changes: 3 additions & 3 deletions cashu/mint/db/write.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

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,
Expand Down Expand Up @@ -50,7 +50,7 @@ async def _set_proofs_pending(
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}")
Expand All @@ -72,7 +72,7 @@ async def _unset_proofs_pending(self, proofs: List[Proof], spent=True) -> None:
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(
Expand Down
4 changes: 2 additions & 2 deletions cashu/mint/events/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ def remove_client(self, client: LedgerEventClientManager) -> None:

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
Expand Down
Loading

0 comments on commit 030347d

Please sign in to comment.