Skip to content

Commit

Permalink
Use PostRestoreRequest for all restore operations (#483)
Browse files Browse the repository at this point in the history
* use PostRestoreRequest for all restore operations

* refactor: unit method verification
  • Loading branch information
callebtc authored Mar 21, 2024
1 parent df2c81e commit f462134
Show file tree
Hide file tree
Showing 8 changed files with 71 additions and 31 deletions.
6 changes: 6 additions & 0 deletions cashu/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,12 @@ class CheckFeesResponse_deprecated(BaseModel):
# ------- API: RESTORE -------


class PostRestoreRequest(BaseModel):
outputs: List[BlindedMessage] = Field(
..., max_items=settings.mint_max_request_length
)


class PostRestoreResponse(BaseModel):
outputs: List[BlindedMessage] = []
signatures: List[BlindedSignature] = []
Expand Down
38 changes: 24 additions & 14 deletions cashu/mint/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,11 @@ async def mint_quote(self, quote_request: PostMintQuoteRequest) -> MintQuote:
)
if settings.mint_peg_out_only:
raise NotAllowedError("Mint does not allow minting new tokens.")
unit = Unit[quote_request.unit]
method = Method.bolt11

unit, method = self._verify_and_get_unit_method(
quote_request.unit, Method.bolt11.name
)

if settings.mint_max_balance:
balance = await self.get_balance()
if balance + quote_request.amount > settings.mint_max_balance:
Expand Down Expand Up @@ -387,10 +390,10 @@ async def get_mint_quote(self, quote_id: str) -> MintQuote:
MintQuote: Mint quote object.
"""
quote = await self.crud.get_mint_quote(quote_id=quote_id, db=self.db)
assert quote, "quote not found"
assert quote.method == Method.bolt11.name, "only bolt11 supported"
unit = Unit[quote.unit]
method = Method[quote.method]
if not quote:
raise Exception("quote not found")

unit, method = self._verify_and_get_unit_method(quote.unit, quote.method)

if not quote.paid:
assert quote.checking_id, "quote has no checking id"
Expand Down Expand Up @@ -471,8 +474,10 @@ async def melt_quote(
Returns:
PostMeltQuoteResponse: Melt quote response.
"""
unit = Unit[melt_quote.unit]
method = Method.bolt11
unit, method = self._verify_and_get_unit_method(
melt_quote.unit, Method.bolt11.name
)

# NOTE: we normalize the request to lowercase to avoid case sensitivity
# This works with Lightning but might not work with other methods
request = melt_quote.request.lower()
Expand Down Expand Up @@ -557,10 +562,12 @@ async def get_melt_quote(
MeltQuote: Melt quote object.
"""
melt_quote = await self.crud.get_melt_quote(quote_id=quote_id, db=self.db)
assert melt_quote, "quote not found"
assert melt_quote.method == Method.bolt11.name, "only bolt11 supported"
unit = Unit[melt_quote.unit]
method = Method[melt_quote.method]
if not melt_quote:
raise Exception("quote not found")

unit, method = self._verify_and_get_unit_method(
melt_quote.unit, melt_quote.method
)

# we only check the state with the backend if there is no associated internal
# mint quote for this melt quote
Expand Down Expand Up @@ -664,8 +671,11 @@ async def melt(
"""
# get melt quote and check if it was already paid
melt_quote = await self.get_melt_quote(quote_id=quote)
method = Method[melt_quote.method]
unit = Unit[melt_quote.unit]

unit, method = self._verify_and_get_unit_method(
melt_quote.unit, melt_quote.method
)

assert not melt_quote.paid, "melt quote already paid"

# make sure that the outputs (for fee return) are in the same unit as the quote
Expand Down
8 changes: 4 additions & 4 deletions cashu/mint/protocols.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Dict, Protocol
from typing import Dict, Mapping, Protocol

from ..core.base import MintKeyset, Unit
from ..core.base import Method, MintKeyset, Unit
from ..core.db import Database
from ..lightning.base import LightningBackend
from ..mint.crud import LedgerCrud
Expand All @@ -11,8 +11,8 @@ class SupportsKeysets(Protocol):
keysets: Dict[str, MintKeyset]


class SupportLightning(Protocol):
lightning: Dict[Unit, LightningBackend]
class SupportsBackends(Protocol):
backends: Mapping[Method, Mapping[Unit, LightningBackend]] = {}


class SupportsDb(Protocol):
Expand Down
5 changes: 3 additions & 2 deletions cashu/mint/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
PostMintQuoteResponse,
PostMintRequest,
PostMintResponse,
PostRestoreRequest,
PostRestoreResponse,
PostSplitRequest,
PostSplitResponse,
Expand Down Expand Up @@ -358,14 +359,14 @@ async def check_state(
@router.post(
"/v1/restore",
name="Restore",
summary="Restores a blinded signature from a secret",
summary="Restores blind signature for a set of outputs.",
response_model=PostRestoreResponse,
response_description=(
"Two lists with the first being the list of the provided outputs that "
"have an associated blinded signature which is given in the second list."
),
)
async def restore(payload: PostMintRequest) -> PostRestoreResponse:
async def restore(payload: PostRestoreRequest) -> PostRestoreResponse:
assert payload.outputs, Exception("no outputs provided.")
outputs, signatures = await ledger.restore(payload.outputs)
return PostRestoreResponse(outputs=outputs, signatures=signatures)
3 changes: 2 additions & 1 deletion cashu/mint/router_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
PostMintQuoteRequest,
PostMintRequest_deprecated,
PostMintResponse_deprecated,
PostRestoreRequest,
PostRestoreResponse,
PostSplitRequest_Deprecated,
PostSplitResponse_Deprecated,
Expand Down Expand Up @@ -357,7 +358,7 @@ async def check_spendable_deprecated(
),
deprecated=True,
)
async def restore(payload: PostMintRequest_deprecated) -> PostRestoreResponse:
async def restore(payload: PostRestoreRequest) -> PostRestoreResponse:
assert payload.outputs, Exception("no outputs provided.")
outputs, promises = await ledger.restore(payload.outputs)
return PostRestoreResponse(outputs=outputs, signatures=promises)
34 changes: 28 additions & 6 deletions cashu/mint/verification.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
from typing import Dict, List, Literal, Optional, Union
from typing import Dict, List, Literal, Optional, Tuple, Union

from loguru import logger

from ..core.base import (
BlindedMessage,
BlindedSignature,
Method,
MintKeyset,
Proof,
Unit,
)
from ..core.crypto import b_dhke
from ..core.crypto.secp import PublicKey
Expand All @@ -19,19 +21,23 @@
TransactionError,
)
from ..core.settings import settings
from ..lightning.base import LightningBackend
from ..mint.crud import LedgerCrud
from .conditions import LedgerSpendingConditions
from .protocols import SupportsDb, SupportsKeysets
from .protocols import SupportsBackends, SupportsDb, SupportsKeysets


class LedgerVerification(LedgerSpendingConditions, SupportsKeysets, SupportsDb):
class LedgerVerification(
LedgerSpendingConditions, SupportsKeysets, SupportsDb, SupportsBackends
):
"""Verification functions for the ledger."""

keyset: MintKeyset
keysets: Dict[str, MintKeyset]
spent_proofs: Dict[str, Proof]
crud: LedgerCrud
db: Database
lightning: Dict[Unit, LightningBackend]

async def verify_inputs_and_outputs(
self, *, proofs: List[Proof], outputs: Optional[List[BlindedMessage]] = None
Expand Down Expand Up @@ -240,6 +246,22 @@ def _verify_equation_balanced(
"""
sum_inputs = sum(self._verify_amount(p.amount) for p in proofs)
sum_outputs = sum(self._verify_amount(p.amount) for p in outs)
assert sum_outputs - sum_inputs == 0, TransactionError(
"inputs do not have same amount as outputs."
)
if not sum_outputs - sum_inputs == 0:
raise TransactionError("inputs do not have same amount as outputs.")

def _verify_and_get_unit_method(
self, unit_str: str, method_str: str
) -> Tuple[Unit, Method]:
"""Verify that the unit is supported by the ledger."""
method = Method[method_str]
unit = Unit[unit_str]

if not any([unit == k.unit for k in self.keysets.values()]):
raise NotAllowedError(f"unit '{unit.name}' not supported in any keyset.")

if not self.backends.get(method) or unit not in self.backends[method]:
raise NotAllowedError(
f"no support for method '{method.name}' with unit '{unit.name}'."
)

return unit, method
4 changes: 2 additions & 2 deletions tests/test_mint_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
MintMeltMethodSetting,
PostCheckStateRequest,
PostCheckStateResponse,
PostMintRequest,
PostRestoreRequest,
PostRestoreResponse,
SpentState,
)
Expand Down Expand Up @@ -430,7 +430,7 @@ async def test_api_restore(ledger: Ledger, wallet: Wallet):
)
outputs, rs = wallet._construct_outputs([64], secrets, rs)

payload = PostMintRequest(outputs=outputs, quote="placeholder")
payload = PostRestoreRequest(outputs=outputs)
response = httpx.post(
f"{BASE_URL}/v1/restore",
json=payload.dict(),
Expand Down
4 changes: 2 additions & 2 deletions tests/test_mint_api_deprecated.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from cashu.core.base import (
CheckSpendableRequest_deprecated,
CheckSpendableResponse_deprecated,
PostMintRequest,
PostRestoreRequest,
PostRestoreResponse,
Proof,
)
Expand Down Expand Up @@ -340,7 +340,7 @@ async def test_api_restore(ledger: Ledger, wallet: Wallet):
)
outputs, rs = wallet._construct_outputs([64], secrets, rs)

payload = PostMintRequest(outputs=outputs, quote="placeholder")
payload = PostRestoreRequest(outputs=outputs)
response = httpx.post(
f"{BASE_URL}/restore",
json=payload.dict(),
Expand Down

0 comments on commit f462134

Please sign in to comment.