mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 10:34:20 +01:00
[Refactor] Mint: remove output.id optional (#504)
* remove output.id optional * asserts * wip * wip * working
This commit is contained in:
@@ -172,7 +172,7 @@ This command runs the mint on your local computer. Skip this step if you want to
|
||||
## Docker
|
||||
|
||||
```
|
||||
docker run -d -p 3338:3338 --name nutshell -e MINT_BACKEND_BOLT11_SAT=FakeWallet -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.15.2 poetry run mint
|
||||
docker run -d -p 3338:3338 --name nutshell -e MINT_BACKEND_BOLT11_SAT=FakeWallet -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.15.3 poetry run mint
|
||||
```
|
||||
|
||||
## From this repository
|
||||
|
||||
@@ -101,12 +101,12 @@ class Proof(BaseModel):
|
||||
time_created: Union[None, str] = ""
|
||||
time_reserved: Union[None, str] = ""
|
||||
derivation_path: Union[None, str] = "" # derivation path of the proof
|
||||
mint_id: Union[None, str] = (
|
||||
None # holds the id of the mint operation that created this proof
|
||||
)
|
||||
melt_id: Union[None, str] = (
|
||||
None # holds the id of the melt operation that destroyed this proof
|
||||
)
|
||||
mint_id: Union[
|
||||
None, str
|
||||
] = None # holds the id of the mint operation that created this proof
|
||||
melt_id: Union[
|
||||
None, str
|
||||
] = None # holds the id of the melt operation that destroyed this proof
|
||||
|
||||
def __init__(self, **data):
|
||||
super().__init__(**data)
|
||||
@@ -161,20 +161,13 @@ class Proof(BaseModel):
|
||||
return HTLCWitness.from_witness(self.witness).preimage
|
||||
|
||||
|
||||
class Proofs(BaseModel):
|
||||
# NOTE: not used in Pydantic validation
|
||||
__root__: List[Proof]
|
||||
|
||||
|
||||
class BlindedMessage(BaseModel):
|
||||
"""
|
||||
Blinded message or blinded secret or "output" which is to be signed by the mint
|
||||
"""
|
||||
|
||||
amount: int
|
||||
id: Optional[
|
||||
str
|
||||
] # DEPRECATION: Only Optional for backwards compatibility with old clients < 0.15 for deprecated API route.
|
||||
id: str
|
||||
B_: str # Hex-encoded blinded message
|
||||
witness: Union[str, None] = None # witnesses (used for P2PK with SIG_ALL)
|
||||
|
||||
@@ -204,11 +197,6 @@ class BlindedSignature(BaseModel):
|
||||
)
|
||||
|
||||
|
||||
class BlindedMessages(BaseModel):
|
||||
# NOTE: not used in Pydantic validation
|
||||
__root__: List[BlindedMessage] = []
|
||||
|
||||
|
||||
# ------- LIGHTNING INVOICE -------
|
||||
|
||||
|
||||
@@ -341,6 +329,19 @@ class GetInfoResponse_deprecated(BaseModel):
|
||||
parameter: Optional[dict] = None
|
||||
|
||||
|
||||
class BlindedMessage_Deprecated(BaseModel):
|
||||
# Same as BlindedMessage, but without the id field
|
||||
amount: int
|
||||
B_: str # Hex-encoded blinded message
|
||||
id: Optional[str] = None
|
||||
witness: Union[str, None] = None # witnesses (used for P2PK with SIG_ALL)
|
||||
|
||||
@property
|
||||
def p2pksigs(self) -> List[str]:
|
||||
assert self.witness, "Witness missing in output"
|
||||
return P2PKWitness.from_witness(self.witness).signatures
|
||||
|
||||
|
||||
# ------- API: KEYS -------
|
||||
|
||||
|
||||
@@ -407,7 +408,7 @@ class GetMintResponse_deprecated(BaseModel):
|
||||
|
||||
|
||||
class PostMintRequest_deprecated(BaseModel):
|
||||
outputs: List[BlindedMessage] = Field(
|
||||
outputs: List[BlindedMessage_Deprecated] = Field(
|
||||
..., max_items=settings.mint_max_request_length
|
||||
)
|
||||
|
||||
@@ -454,7 +455,7 @@ class PostMeltResponse(BaseModel):
|
||||
class PostMeltRequest_deprecated(BaseModel):
|
||||
proofs: List[Proof] = Field(..., max_items=settings.mint_max_request_length)
|
||||
pr: str = Field(..., max_length=settings.mint_max_request_length)
|
||||
outputs: Union[List[BlindedMessage], None] = Field(
|
||||
outputs: Union[List[BlindedMessage_Deprecated], None] = Field(
|
||||
None, max_items=settings.mint_max_request_length
|
||||
)
|
||||
|
||||
@@ -483,7 +484,7 @@ class PostSplitResponse(BaseModel):
|
||||
class PostSplitRequest_Deprecated(BaseModel):
|
||||
proofs: List[Proof] = Field(..., max_items=settings.mint_max_request_length)
|
||||
amount: Optional[int] = None
|
||||
outputs: List[BlindedMessage] = Field(
|
||||
outputs: List[BlindedMessage_Deprecated] = Field(
|
||||
..., max_items=settings.mint_max_request_length
|
||||
)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ from pydantic import BaseSettings, Extra, Field
|
||||
|
||||
env = Env()
|
||||
|
||||
VERSION = "0.15.2"
|
||||
VERSION = "0.15.3"
|
||||
|
||||
|
||||
def find_env_file():
|
||||
@@ -58,7 +58,7 @@ class MintSettings(CashuSettings):
|
||||
|
||||
mint_database: str = Field(default="data/mint")
|
||||
mint_test_database: str = Field(default="test_data/test_mint")
|
||||
mint_duplicate_keysets: bool = Field(
|
||||
mint_duplicate_old_keysets: bool = Field(
|
||||
default=True,
|
||||
title="Duplicate keysets",
|
||||
description=(
|
||||
|
||||
@@ -34,6 +34,7 @@ from ..core.crypto.keys import (
|
||||
from ..core.crypto.secp import PrivateKey, PublicKey
|
||||
from ..core.db import Connection, Database, get_db_connection
|
||||
from ..core.errors import (
|
||||
CashuError,
|
||||
KeysetError,
|
||||
KeysetNotFoundError,
|
||||
LightningError,
|
||||
@@ -72,7 +73,8 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
derivation_path="",
|
||||
crud=LedgerCrudSqlite(),
|
||||
):
|
||||
assert seed, "seed not set"
|
||||
if not seed:
|
||||
raise Exception("seed not set")
|
||||
|
||||
# decrypt seed if seed_decryption_key is set
|
||||
try:
|
||||
@@ -189,7 +191,8 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
Returns:
|
||||
MintKeyset: Keyset
|
||||
"""
|
||||
assert derivation_path, "derivation path not set"
|
||||
if not derivation_path:
|
||||
raise Exception("derivation path not set")
|
||||
seed = seed or self.seed
|
||||
tmp_keyset_local = MintKeyset(
|
||||
seed=seed,
|
||||
@@ -230,7 +233,8 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
|
||||
# BEGIN BACKWARDS COMPATIBILITY < 0.15.0
|
||||
# set the deprecated id
|
||||
assert keyset.public_keys
|
||||
if not keyset.public_keys:
|
||||
raise KeysetError("no public keys for this keyset")
|
||||
keyset.duplicate_keyset_id = derive_keyset_id_deprecated(keyset.public_keys)
|
||||
# END BACKWARDS COMPATIBILITY < 0.15.0
|
||||
|
||||
@@ -268,17 +272,22 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
logger.info(f"Current keyset: {self.keyset.id}")
|
||||
|
||||
# check that we have a least one active keyset
|
||||
assert any([k.active for k in self.keysets.values()]), "No active keyset found."
|
||||
if not any([k.active for k in self.keysets.values()]):
|
||||
raise KeysetError("No active keyset found.")
|
||||
|
||||
# BEGIN BACKWARDS COMPATIBILITY < 0.15.0
|
||||
# we duplicate new keysets and compute their old keyset id, and
|
||||
# we duplicate old keysets and compute their new keyset id
|
||||
if (
|
||||
duplicate_keysets is None and settings.mint_duplicate_keysets
|
||||
) or duplicate_keysets:
|
||||
if duplicate_keysets is not False and (
|
||||
settings.mint_duplicate_old_keysets or duplicate_keysets
|
||||
):
|
||||
for _, keyset in copy.copy(self.keysets).items():
|
||||
# if keyset.version_tuple >= (0, 15, 3) and not duplicate_keysets:
|
||||
# # we do not duplicate keysets from version 0.15.3 and above if not forced by duplicate_keysets
|
||||
# continue
|
||||
keyset_copy = copy.copy(keyset)
|
||||
assert keyset_copy.public_keys
|
||||
if not keyset_copy.public_keys:
|
||||
raise KeysetError("no public keys for this keyset")
|
||||
if keyset.version_tuple >= (0, 15):
|
||||
keyset_copy.id = derive_keyset_id_deprecated(
|
||||
keyset_copy.public_keys
|
||||
@@ -296,7 +305,8 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
if keyset_id and keyset_id not in self.keysets:
|
||||
raise KeysetNotFoundError()
|
||||
keyset = self.keysets[keyset_id] if keyset_id else self.keyset
|
||||
assert keyset.public_keys, KeysetError("no public keys for this keyset")
|
||||
if not keyset.public_keys:
|
||||
raise KeysetError("no public keys for this keyset")
|
||||
return {a: p.serialize().hex() for a, p in keyset.public_keys.items()}
|
||||
|
||||
async def get_balance(self) -> int:
|
||||
@@ -400,7 +410,8 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
MintQuote: Mint quote object.
|
||||
"""
|
||||
logger.trace("called request_mint")
|
||||
assert quote_request.amount > 0, "amount must be positive"
|
||||
if not quote_request.amount > 0:
|
||||
raise TransactionError("amount must be positive")
|
||||
if settings.mint_max_peg_in and quote_request.amount > settings.mint_max_peg_in:
|
||||
raise NotAllowedError(
|
||||
f"Maximum mint amount is {settings.mint_max_peg_in} sat."
|
||||
@@ -426,9 +437,8 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
f" {invoice_response.checking_id}"
|
||||
)
|
||||
|
||||
assert (
|
||||
invoice_response.payment_request and invoice_response.checking_id
|
||||
), LightningError("could not fetch bolt11 payment request from backend")
|
||||
if not (invoice_response.payment_request and invoice_response.checking_id):
|
||||
raise LightningError("could not fetch bolt11 payment request from backend")
|
||||
|
||||
# get invoice expiry time
|
||||
invoice_obj = bolt11.decode(invoice_response.payment_request)
|
||||
@@ -478,7 +488,8 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
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"
|
||||
if not quote.checking_id:
|
||||
raise CashuError("quote has no checking id")
|
||||
logger.trace(f"Lightning: checking invoice {quote.checking_id}")
|
||||
status: PaymentStatus = await self.backends[method][
|
||||
unit
|
||||
@@ -518,18 +529,27 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
await self._verify_outputs(outputs)
|
||||
sum_amount_outputs = sum([b.amount for b in outputs])
|
||||
|
||||
output_units = set([k.unit for k in [self.keysets[o.id] for o in outputs]])
|
||||
if not len(output_units) == 1:
|
||||
raise TransactionError("outputs have different units")
|
||||
output_unit = list(output_units)[0]
|
||||
|
||||
self.locks[quote_id] = (
|
||||
self.locks.get(quote_id) or asyncio.Lock()
|
||||
) # create a new lock if it doesn't exist
|
||||
async with self.locks[quote_id]:
|
||||
quote = await self.get_mint_quote(quote_id=quote_id)
|
||||
assert quote.paid, QuoteNotPaidError()
|
||||
assert not quote.issued, "quote already issued"
|
||||
assert (
|
||||
quote.amount == sum_amount_outputs
|
||||
), "amount to mint does not match quote amount"
|
||||
if quote.expiry:
|
||||
assert quote.expiry > int(time.time()), "quote expired"
|
||||
|
||||
if not quote.paid:
|
||||
raise QuoteNotPaidError()
|
||||
if quote.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:
|
||||
raise TransactionError("amount to mint does not match quote amount")
|
||||
if quote.expiry and quote.expiry > int(time.time()):
|
||||
raise TransactionError("quote expired")
|
||||
|
||||
promises = await self._generate_promises(outputs)
|
||||
logger.trace("generated promises")
|
||||
@@ -571,12 +591,19 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
request=request, db=self.db
|
||||
)
|
||||
if mint_quote:
|
||||
assert request == mint_quote.request, "bolt11 requests do not match"
|
||||
assert mint_quote.unit == melt_quote.unit, "units do not match"
|
||||
assert mint_quote.method == method.name, "methods do not match"
|
||||
assert not mint_quote.paid, "mint quote already paid"
|
||||
assert not mint_quote.issued, "mint quote already issued"
|
||||
assert mint_quote.checking_id, "mint quote has no checking id"
|
||||
if not request == mint_quote.request:
|
||||
raise TransactionError("bolt11 requests do not match")
|
||||
if not mint_quote.unit == melt_quote.unit:
|
||||
raise TransactionError("units do not match")
|
||||
if not mint_quote.method == method.name:
|
||||
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 not mint_quote.checking_id:
|
||||
raise TransactionError("mint quote has no checking id")
|
||||
|
||||
payment_quote = PaymentQuoteResponse(
|
||||
checking_id=mint_quote.checking_id,
|
||||
amount=Amount(unit, mint_quote.amount),
|
||||
@@ -589,20 +616,20 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
else:
|
||||
# not internal, get payment quote by backend
|
||||
payment_quote = await self.backends[method][unit].get_payment_quote(request)
|
||||
assert payment_quote.checking_id, "quote has no checking id"
|
||||
if not payment_quote.checking_id:
|
||||
raise TransactionError("quote has no checking id")
|
||||
# make sure the backend returned the amount with a correct unit
|
||||
assert (
|
||||
payment_quote.amount.unit == unit
|
||||
), "payment quote amount units do not match"
|
||||
if not payment_quote.amount.unit == unit:
|
||||
raise TransactionError("payment quote amount units do not match")
|
||||
# fee from the backend must be in the same unit as the amount
|
||||
assert (
|
||||
payment_quote.fee.unit == unit
|
||||
), "payment quote fee units do not match"
|
||||
if not payment_quote.fee.unit == unit:
|
||||
raise TransactionError("payment quote fee units do not match")
|
||||
|
||||
# We assume that the request is a bolt11 invoice, this works since we
|
||||
# support only the bol11 method for now.
|
||||
invoice_obj = bolt11.decode(melt_quote.request)
|
||||
assert invoice_obj.amount_msat, "invoice has no amount."
|
||||
if not invoice_obj.amount_msat:
|
||||
raise TransactionError("invoice has no amount.")
|
||||
# we set the expiry of this quote to the expiry of the bolt11 invoice
|
||||
expiry = None
|
||||
if invoice_obj.expiry is not None:
|
||||
@@ -703,23 +730,28 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
if not mint_quote:
|
||||
return melt_quote
|
||||
# we settle the transaction internally
|
||||
assert not melt_quote.paid, "melt quote already paid"
|
||||
if melt_quote.paid:
|
||||
raise TransactionError("melt quote already paid")
|
||||
|
||||
# verify amounts from bolt11 invoice
|
||||
bolt11_request = melt_quote.request
|
||||
invoice_obj = bolt11.decode(bolt11_request)
|
||||
assert invoice_obj.amount_msat, "invoice has no amount."
|
||||
# invoice_amount_sat = math.ceil(invoice_obj.amount_msat / 1000)
|
||||
# assert (
|
||||
# Amount(Unit[melt_quote.unit], mint_quote.amount).to(Unit.sat).amount
|
||||
# == invoice_amount_sat
|
||||
# ), "amounts do not match"
|
||||
assert mint_quote.amount == melt_quote.amount, "amounts do not match"
|
||||
assert bolt11_request == mint_quote.request, "bolt11 requests do not match"
|
||||
assert mint_quote.unit == melt_quote.unit, "units do not match"
|
||||
assert mint_quote.method == melt_quote.method, "methods do not match"
|
||||
assert not mint_quote.paid, "mint quote already paid"
|
||||
assert not mint_quote.issued, "mint quote already issued"
|
||||
|
||||
if not invoice_obj.amount_msat:
|
||||
raise TransactionError("invoice has no amount.")
|
||||
if not mint_quote.amount == melt_quote.amount:
|
||||
raise TransactionError("amounts do not match")
|
||||
if not bolt11_request == mint_quote.request:
|
||||
raise TransactionError("bolt11 requests do not match")
|
||||
if not mint_quote.unit == melt_quote.unit:
|
||||
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")
|
||||
|
||||
logger.info(
|
||||
f"Settling bolt11 payment internally: {melt_quote.quote} ->"
|
||||
f" {mint_quote.quote} ({melt_quote.amount} {melt_quote.unit})"
|
||||
@@ -764,25 +796,25 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
melt_quote.unit, melt_quote.method
|
||||
)
|
||||
|
||||
assert not melt_quote.paid, "melt quote already paid"
|
||||
if melt_quote.paid:
|
||||
raise TransactionError("melt quote already paid")
|
||||
|
||||
# make sure that the outputs (for fee return) are in the same unit as the quote
|
||||
if outputs:
|
||||
await self._verify_outputs(outputs, skip_amount_check=True)
|
||||
assert outputs[0].id, "output id not set"
|
||||
outputs_unit = self.keysets[outputs[0].id].unit
|
||||
assert melt_quote.unit == outputs_unit.name, (
|
||||
f"output unit {outputs_unit.name} does not match quote unit"
|
||||
f" {melt_quote.unit}"
|
||||
)
|
||||
if not melt_quote.unit == outputs_unit.name:
|
||||
raise TransactionError(
|
||||
f"output unit {outputs_unit.name} does not match quote unit {melt_quote.unit}"
|
||||
)
|
||||
|
||||
# verify that the amount of the input proofs is equal to the amount of the quote
|
||||
total_provided = sum_proofs(proofs)
|
||||
total_needed = melt_quote.amount + (melt_quote.fee_reserve or 0)
|
||||
assert total_provided >= total_needed, (
|
||||
f"not enough inputs provided for melt. Provided: {total_provided}, needed:"
|
||||
f" {total_needed}"
|
||||
)
|
||||
if not total_provided >= total_needed:
|
||||
raise TransactionError(
|
||||
f"not enough inputs provided for melt. Provided: {total_provided}, needed: {total_needed}"
|
||||
)
|
||||
|
||||
# verify that the amount of the proofs is not larger than the maximum allowed
|
||||
if settings.mint_max_peg_out and total_provided > settings.mint_max_peg_out:
|
||||
@@ -831,7 +863,6 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
# prepare change to compensate wallet for overpaid fees
|
||||
return_promises: List[BlindedSignature] = []
|
||||
if outputs:
|
||||
assert outputs[0].id, "output id not set"
|
||||
return_promises = await self._generate_change_promises(
|
||||
input_amount=total_provided,
|
||||
output_amount=melt_quote.amount,
|
||||
@@ -871,22 +902,21 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
Tuple[List[BlindSignature],List[BlindSignature]]: Promises on both sides of the split.
|
||||
"""
|
||||
logger.trace("split called")
|
||||
# explicitly check that amount of inputs is equal to amount of outputs
|
||||
# note: we check this again in verify_inputs_and_outputs but only if any
|
||||
# outputs are provided at all. To make sure of that before calling
|
||||
# verify_inputs_and_outputs, we check it here.
|
||||
self._verify_equation_balanced(proofs, outputs)
|
||||
# verify spending inputs, outputs, and spending conditions
|
||||
await self.verify_inputs_and_outputs(proofs=proofs, outputs=outputs)
|
||||
|
||||
await self._set_proofs_pending(proofs)
|
||||
try:
|
||||
# explicitly check that amount of inputs is equal to amount of outputs
|
||||
# note: we check this again in verify_inputs_and_outputs but only if any
|
||||
# outputs are provided at all. To make sure of that before calling
|
||||
# verify_inputs_and_outputs, we check it here.
|
||||
self._verify_equation_balanced(proofs, outputs)
|
||||
# verify spending inputs, outputs, and spending conditions
|
||||
await self.verify_inputs_and_outputs(proofs=proofs, outputs=outputs)
|
||||
|
||||
# Mark proofs as used and prepare new promises
|
||||
async with get_db_connection(self.db) as conn:
|
||||
# we do this in a single db transaction
|
||||
promises = await self._generate_promises(outputs, keyset, conn)
|
||||
await self._invalidate_proofs(proofs=proofs, conn=conn)
|
||||
promises = await self._generate_promises(outputs, keyset, conn)
|
||||
|
||||
except Exception as e:
|
||||
logger.trace(f"split failed: {e}")
|
||||
@@ -949,15 +979,16 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
] = []
|
||||
for output in outputs:
|
||||
B_ = PublicKey(bytes.fromhex(output.B_), raw=True)
|
||||
assert output.id, "output id not set"
|
||||
keyset = keyset or self.keysets[output.id]
|
||||
|
||||
assert output.id in self.keysets, f"keyset {output.id} not found"
|
||||
assert output.id in [
|
||||
if output.id not in self.keysets:
|
||||
raise TransactionError(f"keyset {output.id} not found")
|
||||
if output.id not in [
|
||||
keyset.id,
|
||||
keyset.duplicate_keyset_id,
|
||||
], "keyset id does not match output id"
|
||||
assert keyset.active, "keyset is not active"
|
||||
]:
|
||||
raise TransactionError("keyset id does not match output id")
|
||||
if not keyset.active:
|
||||
raise TransactionError("keyset is not active")
|
||||
keyset_id = output.id
|
||||
logger.trace(f"Generating promise with keyset {keyset_id}.")
|
||||
private_key_amount = keyset.private_keys[output.amount]
|
||||
@@ -995,7 +1026,8 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
|
||||
async def load_used_proofs(self) -> None:
|
||||
"""Load all used proofs from database."""
|
||||
assert settings.mint_cache_secrets, "MINT_CACHE_SECRETS must be set to TRUE"
|
||||
if not settings.mint_cache_secrets:
|
||||
raise Exception("MINT_CACHE_SECRETS must be set to TRUE")
|
||||
logger.debug("Loading used proofs into memory")
|
||||
spent_proofs_list = await self.crud.get_spent_proofs(db=self.db) or []
|
||||
logger.debug(f"Loaded {len(spent_proofs_list)} used proofs")
|
||||
@@ -1082,11 +1114,12 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
|
||||
Raises:
|
||||
Exception: At least one of the proofs is in the pending table.
|
||||
"""
|
||||
assert (
|
||||
if not (
|
||||
len(
|
||||
await self.crud.get_proofs_pending(
|
||||
Ys=[p.Y for p in proofs], db=self.db, conn=conn
|
||||
)
|
||||
)
|
||||
== 0
|
||||
), TransactionError("proofs are pending.")
|
||||
):
|
||||
raise TransactionError("proofs are pending.")
|
||||
|
||||
@@ -4,6 +4,7 @@ from fastapi import APIRouter, Request
|
||||
from loguru import logger
|
||||
|
||||
from ..core.base import (
|
||||
BlindedMessage,
|
||||
BlindedSignature,
|
||||
CheckFeesRequest_deprecated,
|
||||
CheckFeesResponse_deprecated,
|
||||
@@ -177,10 +178,14 @@ async def mint_deprecated(
|
||||
|
||||
# BEGIN BACKWARDS COMPATIBILITY < 0.15
|
||||
# Mint expects "id" in outputs to know which keyset to use to sign them.
|
||||
for output in payload.outputs:
|
||||
if not output.id:
|
||||
# use the deprecated version of the current keyset
|
||||
output.id = ledger.keyset.duplicate_keyset_id
|
||||
# use the deprecated version of the current keyset
|
||||
assert ledger.keyset.duplicate_keyset_id
|
||||
outputs: list[BlindedMessage] = [
|
||||
BlindedMessage(
|
||||
id=o.id or ledger.keyset.duplicate_keyset_id, **o.dict(exclude={"id"})
|
||||
)
|
||||
for o in payload.outputs
|
||||
]
|
||||
# END BACKWARDS COMPATIBILITY < 0.15
|
||||
|
||||
# BEGIN: backwards compatibility < 0.12 where we used to lookup payments with payment_hash
|
||||
@@ -189,7 +194,7 @@ async def mint_deprecated(
|
||||
assert hash, "hash must be set."
|
||||
# END: backwards compatibility < 0.12
|
||||
|
||||
promises = await ledger.mint(outputs=payload.outputs, quote_id=hash)
|
||||
promises = await ledger.mint(outputs=outputs, quote_id=hash)
|
||||
blinded_signatures = PostMintResponse_deprecated(promises=promises)
|
||||
|
||||
logger.trace(f"< POST /mint: {blinded_signatures}")
|
||||
@@ -221,15 +226,18 @@ async def melt_deprecated(
|
||||
logger.trace(f"> POST /melt: {payload}")
|
||||
# BEGIN BACKWARDS COMPATIBILITY < 0.14: add "id" to outputs
|
||||
if payload.outputs:
|
||||
for output in payload.outputs:
|
||||
if not output.id:
|
||||
output.id = ledger.keyset.id
|
||||
outputs: list[BlindedMessage] = [
|
||||
BlindedMessage(id=o.id or ledger.keyset.id, **o.dict(exclude={"id"}))
|
||||
for o in payload.outputs
|
||||
]
|
||||
else:
|
||||
outputs = []
|
||||
# END BACKWARDS COMPATIBILITY < 0.14
|
||||
quote = await ledger.melt_quote(
|
||||
PostMeltQuoteRequest(request=payload.pr, unit="sat")
|
||||
)
|
||||
preimage, change_promises = await ledger.melt(
|
||||
proofs=payload.proofs, quote=quote.quote, outputs=payload.outputs
|
||||
proofs=payload.proofs, quote=quote.quote, outputs=outputs
|
||||
)
|
||||
resp = PostMeltResponse_deprecated(
|
||||
paid=True, preimage=preimage, change=change_promises
|
||||
@@ -290,12 +298,12 @@ async def split_deprecated(
|
||||
logger.trace(f"> POST /split: {payload}")
|
||||
assert payload.outputs, Exception("no outputs provided.")
|
||||
# BEGIN BACKWARDS COMPATIBILITY < 0.14: add "id" to outputs
|
||||
if payload.outputs:
|
||||
for output in payload.outputs:
|
||||
if not output.id:
|
||||
output.id = ledger.keyset.id
|
||||
outputs: list[BlindedMessage] = [
|
||||
BlindedMessage(id=o.id or ledger.keyset.id, **o.dict(exclude={"id"}))
|
||||
for o in payload.outputs
|
||||
]
|
||||
# END BACKWARDS COMPATIBILITY < 0.14
|
||||
promises = await ledger.split(proofs=payload.proofs, outputs=payload.outputs)
|
||||
promises = await ledger.split(proofs=payload.proofs, outputs=outputs)
|
||||
|
||||
if payload.amount:
|
||||
# BEGIN backwards compatibility < 0.13
|
||||
|
||||
@@ -8,6 +8,7 @@ from loguru import logger
|
||||
|
||||
from ..core.base import (
|
||||
BlindedMessage,
|
||||
BlindedMessage_Deprecated,
|
||||
BlindedSignature,
|
||||
CheckFeesRequest_deprecated,
|
||||
CheckFeesResponse_deprecated,
|
||||
@@ -271,7 +272,8 @@ class LedgerAPIDeprecated(SupportsHttpxClient, SupportsMintURL):
|
||||
Raises:
|
||||
Exception: If the minting fails
|
||||
"""
|
||||
outputs_payload = PostMintRequest_deprecated(outputs=outputs)
|
||||
outputs_deprecated = [BlindedMessage_Deprecated(**o.dict()) for o in outputs]
|
||||
outputs_payload = PostMintRequest_deprecated(outputs=outputs_deprecated)
|
||||
|
||||
def _mintrequest_include_fields(outputs: List[BlindedMessage]):
|
||||
"""strips away fields from the model that aren't necessary for the /mint"""
|
||||
@@ -307,7 +309,14 @@ class LedgerAPIDeprecated(SupportsHttpxClient, SupportsMintURL):
|
||||
Accepts proofs and a lightning invoice to pay in exchange.
|
||||
"""
|
||||
logger.warning("Using deprecated API call: POST /melt")
|
||||
payload = PostMeltRequest_deprecated(proofs=proofs, pr=invoice, outputs=outputs)
|
||||
outputs_deprecated = (
|
||||
[BlindedMessage_Deprecated(**o.dict()) for o in outputs]
|
||||
if outputs
|
||||
else None
|
||||
)
|
||||
payload = PostMeltRequest_deprecated(
|
||||
proofs=proofs, pr=invoice, outputs=outputs_deprecated
|
||||
)
|
||||
|
||||
def _meltrequest_include_fields(proofs: List[Proof]):
|
||||
"""strips away fields from the model that aren't necessary for the /melt"""
|
||||
@@ -336,7 +345,10 @@ class LedgerAPIDeprecated(SupportsHttpxClient, SupportsMintURL):
|
||||
) -> List[BlindedSignature]:
|
||||
"""Consume proofs and create new promises based on amount split."""
|
||||
logger.warning("Using deprecated API call: Calling split. POST /split")
|
||||
split_payload = PostSplitRequest_Deprecated(proofs=proofs, outputs=outputs)
|
||||
outputs_deprecated = [BlindedMessage_Deprecated(**o.dict()) for o in outputs]
|
||||
split_payload = PostSplitRequest_Deprecated(
|
||||
proofs=proofs, outputs=outputs_deprecated
|
||||
)
|
||||
|
||||
# construct payload
|
||||
def _splitrequest_include_fields(proofs: List[Proof]):
|
||||
@@ -403,7 +415,8 @@ class LedgerAPIDeprecated(SupportsHttpxClient, SupportsMintURL):
|
||||
Asks the mint to restore promises corresponding to outputs.
|
||||
"""
|
||||
logger.warning("Using deprecated API call: POST /restore")
|
||||
payload = PostMintRequest_deprecated(outputs=outputs)
|
||||
outputs_deprecated = [BlindedMessage_Deprecated(**o.dict()) for o in outputs]
|
||||
payload = PostMintRequest_deprecated(outputs=outputs_deprecated)
|
||||
resp = await self.httpx.post(join(self.url, "/restore"), json=payload.dict())
|
||||
self.raise_on_error(resp)
|
||||
response_dict = resp.json()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[tool.poetry]
|
||||
name = "cashu"
|
||||
version = "0.15.2"
|
||||
version = "0.15.3"
|
||||
description = "Ecash wallet and mint"
|
||||
authors = ["calle <callebtc@protonmail.com>"]
|
||||
license = "MIT"
|
||||
|
||||
2
setup.py
2
setup.py
@@ -13,7 +13,7 @@ entry_points = {"console_scripts": ["cashu = cashu.wallet.cli.cli:cli"]}
|
||||
|
||||
setuptools.setup(
|
||||
name="cashu",
|
||||
version="0.15.2",
|
||||
version="0.15.3",
|
||||
description="Ecash wallet and mint",
|
||||
long_description=long_description,
|
||||
long_description_content_type="text/markdown",
|
||||
|
||||
@@ -65,7 +65,7 @@ async def test_init_keysets_with_duplicates(ledger: Ledger):
|
||||
@pytest.mark.asyncio
|
||||
async def test_init_keysets_with_duplicates_via_settings(ledger: Ledger):
|
||||
ledger.keysets = {}
|
||||
settings.mint_duplicate_keysets = True
|
||||
settings.mint_duplicate_old_keysets = True
|
||||
await ledger.init_keysets()
|
||||
assert len(ledger.keysets) == 2
|
||||
|
||||
@@ -80,7 +80,7 @@ async def test_init_keysets_without_duplicates(ledger: Ledger):
|
||||
@pytest.mark.asyncio
|
||||
async def test_init_keysets_without_duplicates_via_settings(ledger: Ledger):
|
||||
ledger.keysets = {}
|
||||
settings.mint_duplicate_keysets = False
|
||||
settings.mint_duplicate_old_keysets = False
|
||||
await ledger.init_keysets()
|
||||
assert len(ledger.keysets) == 1
|
||||
|
||||
|
||||
@@ -355,7 +355,7 @@ async def test_duplicate_proofs_double_spent(wallet1: Wallet):
|
||||
doublespend = await wallet1.mint(64, id=invoice.id)
|
||||
await assert_err(
|
||||
wallet1.split(wallet1.proofs + doublespend, 20),
|
||||
"Mint Error: Failed to set proofs pending.",
|
||||
"Mint Error: duplicate proofs.",
|
||||
)
|
||||
assert wallet1.balance == 64
|
||||
assert wallet1.available_balance == 64
|
||||
|
||||
Reference in New Issue
Block a user