[Refactor] Mint: remove output.id optional (#504)

* remove output.id optional

* asserts

* wip

* wip

* working
This commit is contained in:
callebtc
2024-04-10 12:23:53 +02:00
committed by GitHub
parent 3b2f1aa6f4
commit 19de10bfea
10 changed files with 180 additions and 125 deletions

View File

@@ -172,7 +172,7 @@ This command runs the mint on your local computer. Skip this step if you want to
## Docker ## 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 ## From this repository

View File

@@ -101,12 +101,12 @@ class Proof(BaseModel):
time_created: Union[None, str] = "" time_created: Union[None, str] = ""
time_reserved: Union[None, str] = "" time_reserved: Union[None, str] = ""
derivation_path: Union[None, str] = "" # derivation path of the proof derivation_path: Union[None, str] = "" # derivation path of the proof
mint_id: Union[None, str] = ( mint_id: Union[
None # holds the id of the mint operation that created this proof None, str
) ] = None # holds the id of the mint operation that created this proof
melt_id: Union[None, str] = ( melt_id: Union[
None # holds the id of the melt operation that destroyed this proof None, str
) ] = None # holds the id of the melt operation that destroyed this proof
def __init__(self, **data): def __init__(self, **data):
super().__init__(**data) super().__init__(**data)
@@ -161,20 +161,13 @@ class Proof(BaseModel):
return HTLCWitness.from_witness(self.witness).preimage return HTLCWitness.from_witness(self.witness).preimage
class Proofs(BaseModel):
# NOTE: not used in Pydantic validation
__root__: List[Proof]
class BlindedMessage(BaseModel): class BlindedMessage(BaseModel):
""" """
Blinded message or blinded secret or "output" which is to be signed by the mint Blinded message or blinded secret or "output" which is to be signed by the mint
""" """
amount: int amount: int
id: Optional[ id: str
str
] # DEPRECATION: Only Optional for backwards compatibility with old clients < 0.15 for deprecated API route.
B_: str # Hex-encoded blinded message B_: str # Hex-encoded blinded message
witness: Union[str, None] = None # witnesses (used for P2PK with SIG_ALL) 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 ------- # ------- LIGHTNING INVOICE -------
@@ -341,6 +329,19 @@ class GetInfoResponse_deprecated(BaseModel):
parameter: Optional[dict] = None 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 ------- # ------- API: KEYS -------
@@ -407,7 +408,7 @@ class GetMintResponse_deprecated(BaseModel):
class PostMintRequest_deprecated(BaseModel): class PostMintRequest_deprecated(BaseModel):
outputs: List[BlindedMessage] = Field( outputs: List[BlindedMessage_Deprecated] = Field(
..., max_items=settings.mint_max_request_length ..., max_items=settings.mint_max_request_length
) )
@@ -454,7 +455,7 @@ class PostMeltResponse(BaseModel):
class PostMeltRequest_deprecated(BaseModel): class PostMeltRequest_deprecated(BaseModel):
proofs: List[Proof] = Field(..., max_items=settings.mint_max_request_length) proofs: List[Proof] = Field(..., max_items=settings.mint_max_request_length)
pr: str = Field(..., max_length=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 None, max_items=settings.mint_max_request_length
) )
@@ -483,7 +484,7 @@ class PostSplitResponse(BaseModel):
class PostSplitRequest_Deprecated(BaseModel): class PostSplitRequest_Deprecated(BaseModel):
proofs: List[Proof] = Field(..., max_items=settings.mint_max_request_length) proofs: List[Proof] = Field(..., max_items=settings.mint_max_request_length)
amount: Optional[int] = None amount: Optional[int] = None
outputs: List[BlindedMessage] = Field( outputs: List[BlindedMessage_Deprecated] = Field(
..., max_items=settings.mint_max_request_length ..., max_items=settings.mint_max_request_length
) )

View File

@@ -8,7 +8,7 @@ from pydantic import BaseSettings, Extra, Field
env = Env() env = Env()
VERSION = "0.15.2" VERSION = "0.15.3"
def find_env_file(): def find_env_file():
@@ -58,7 +58,7 @@ class MintSettings(CashuSettings):
mint_database: str = Field(default="data/mint") mint_database: str = Field(default="data/mint")
mint_test_database: str = Field(default="test_data/test_mint") mint_test_database: str = Field(default="test_data/test_mint")
mint_duplicate_keysets: bool = Field( mint_duplicate_old_keysets: bool = Field(
default=True, default=True,
title="Duplicate keysets", title="Duplicate keysets",
description=( description=(

View File

@@ -34,6 +34,7 @@ from ..core.crypto.keys import (
from ..core.crypto.secp import PrivateKey, PublicKey from ..core.crypto.secp import PrivateKey, PublicKey
from ..core.db import Connection, Database, get_db_connection from ..core.db import Connection, Database, get_db_connection
from ..core.errors import ( from ..core.errors import (
CashuError,
KeysetError, KeysetError,
KeysetNotFoundError, KeysetNotFoundError,
LightningError, LightningError,
@@ -72,7 +73,8 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
derivation_path="", derivation_path="",
crud=LedgerCrudSqlite(), crud=LedgerCrudSqlite(),
): ):
assert seed, "seed not set" if not seed:
raise Exception("seed not set")
# decrypt seed if seed_decryption_key is set # decrypt seed if seed_decryption_key is set
try: try:
@@ -189,7 +191,8 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
Returns: Returns:
MintKeyset: Keyset MintKeyset: Keyset
""" """
assert derivation_path, "derivation path not set" if not derivation_path:
raise Exception("derivation path not set")
seed = seed or self.seed seed = seed or self.seed
tmp_keyset_local = MintKeyset( tmp_keyset_local = MintKeyset(
seed=seed, seed=seed,
@@ -230,7 +233,8 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
# BEGIN BACKWARDS COMPATIBILITY < 0.15.0 # BEGIN BACKWARDS COMPATIBILITY < 0.15.0
# set the deprecated id # 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) keyset.duplicate_keyset_id = derive_keyset_id_deprecated(keyset.public_keys)
# END BACKWARDS COMPATIBILITY < 0.15.0 # END BACKWARDS COMPATIBILITY < 0.15.0
@@ -268,17 +272,22 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
logger.info(f"Current keyset: {self.keyset.id}") logger.info(f"Current keyset: {self.keyset.id}")
# check that we have a least one active keyset # 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 # BEGIN BACKWARDS COMPATIBILITY < 0.15.0
# we duplicate new keysets and compute their old keyset id, and # we duplicate new keysets and compute their old keyset id, and
# we duplicate old keysets and compute their new keyset id # we duplicate old keysets and compute their new keyset id
if ( if duplicate_keysets is not False and (
duplicate_keysets is None and settings.mint_duplicate_keysets settings.mint_duplicate_old_keysets or duplicate_keysets
) or duplicate_keysets: ):
for _, keyset in copy.copy(self.keysets).items(): 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) 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): if keyset.version_tuple >= (0, 15):
keyset_copy.id = derive_keyset_id_deprecated( keyset_copy.id = derive_keyset_id_deprecated(
keyset_copy.public_keys keyset_copy.public_keys
@@ -296,7 +305,8 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
if keyset_id and keyset_id not in self.keysets: if keyset_id and keyset_id not in self.keysets:
raise KeysetNotFoundError() raise KeysetNotFoundError()
keyset = self.keysets[keyset_id] if keyset_id else self.keyset 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()} return {a: p.serialize().hex() for a, p in keyset.public_keys.items()}
async def get_balance(self) -> int: async def get_balance(self) -> int:
@@ -400,7 +410,8 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
MintQuote: Mint quote object. MintQuote: Mint quote object.
""" """
logger.trace("called request_mint") 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: if settings.mint_max_peg_in and quote_request.amount > settings.mint_max_peg_in:
raise NotAllowedError( raise NotAllowedError(
f"Maximum mint amount is {settings.mint_max_peg_in} sat." f"Maximum mint amount is {settings.mint_max_peg_in} sat."
@@ -426,9 +437,8 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
f" {invoice_response.checking_id}" f" {invoice_response.checking_id}"
) )
assert ( if not (invoice_response.payment_request and invoice_response.checking_id):
invoice_response.payment_request and invoice_response.checking_id raise LightningError("could not fetch bolt11 payment request from backend")
), LightningError("could not fetch bolt11 payment request from backend")
# get invoice expiry time # get invoice expiry time
invoice_obj = bolt11.decode(invoice_response.payment_request) 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) unit, method = self._verify_and_get_unit_method(quote.unit, quote.method)
if not quote.paid: 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}") logger.trace(f"Lightning: checking invoice {quote.checking_id}")
status: PaymentStatus = await self.backends[method][ status: PaymentStatus = await self.backends[method][
unit unit
@@ -518,18 +529,27 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
await self._verify_outputs(outputs) await self._verify_outputs(outputs)
sum_amount_outputs = sum([b.amount for b in 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[quote_id] = (
self.locks.get(quote_id) or asyncio.Lock() self.locks.get(quote_id) or asyncio.Lock()
) # create a new lock if it doesn't exist ) # create a new lock if it doesn't exist
async with self.locks[quote_id]: async with self.locks[quote_id]:
quote = await self.get_mint_quote(quote_id=quote_id) quote = await self.get_mint_quote(quote_id=quote_id)
assert quote.paid, QuoteNotPaidError()
assert not quote.issued, "quote already issued" if not quote.paid:
assert ( raise QuoteNotPaidError()
quote.amount == sum_amount_outputs if quote.issued:
), "amount to mint does not match quote amount" raise TransactionError("quote already issued")
if quote.expiry: if not quote.unit == output_unit.name:
assert quote.expiry > int(time.time()), "quote expired" 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) promises = await self._generate_promises(outputs)
logger.trace("generated promises") logger.trace("generated promises")
@@ -571,12 +591,19 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
request=request, db=self.db request=request, db=self.db
) )
if mint_quote: if mint_quote:
assert request == mint_quote.request, "bolt11 requests do not match" if not request == mint_quote.request:
assert mint_quote.unit == melt_quote.unit, "units do not match" raise TransactionError("bolt11 requests do not match")
assert mint_quote.method == method.name, "methods do not match" if not mint_quote.unit == melt_quote.unit:
assert not mint_quote.paid, "mint quote already paid" raise TransactionError("units do not match")
assert not mint_quote.issued, "mint quote already issued" if not mint_quote.method == method.name:
assert mint_quote.checking_id, "mint quote has no checking id" 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( payment_quote = PaymentQuoteResponse(
checking_id=mint_quote.checking_id, checking_id=mint_quote.checking_id,
amount=Amount(unit, mint_quote.amount), amount=Amount(unit, mint_quote.amount),
@@ -589,20 +616,20 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
else: else:
# not internal, get payment quote by backend # not internal, get payment quote by backend
payment_quote = await self.backends[method][unit].get_payment_quote(request) 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 # make sure the backend returned the amount with a correct unit
assert ( if not payment_quote.amount.unit == unit:
payment_quote.amount.unit == unit raise TransactionError("payment quote amount units do not match")
), "payment quote amount units do not match"
# fee from the backend must be in the same unit as the amount # fee from the backend must be in the same unit as the amount
assert ( if not payment_quote.fee.unit == unit:
payment_quote.fee.unit == unit raise TransactionError("payment quote fee units do not match")
), "payment quote fee units do not match"
# We assume that the request is a bolt11 invoice, this works since we # We assume that the request is a bolt11 invoice, this works since we
# support only the bol11 method for now. # support only the bol11 method for now.
invoice_obj = bolt11.decode(melt_quote.request) 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 # we set the expiry of this quote to the expiry of the bolt11 invoice
expiry = None expiry = None
if invoice_obj.expiry is not None: if invoice_obj.expiry is not None:
@@ -703,23 +730,28 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
if not mint_quote: if not mint_quote:
return melt_quote return melt_quote
# we settle the transaction internally # 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 # verify amounts from bolt11 invoice
bolt11_request = melt_quote.request bolt11_request = melt_quote.request
invoice_obj = bolt11.decode(bolt11_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) if not invoice_obj.amount_msat:
# assert ( raise TransactionError("invoice has no amount.")
# Amount(Unit[melt_quote.unit], mint_quote.amount).to(Unit.sat).amount if not mint_quote.amount == melt_quote.amount:
# == invoice_amount_sat raise TransactionError("amounts do not match")
# ), "amounts do not match" if not bolt11_request == mint_quote.request:
assert mint_quote.amount == melt_quote.amount, "amounts do not match" raise TransactionError("bolt11 requests do not match")
assert bolt11_request == mint_quote.request, "bolt11 requests do not match" if not mint_quote.unit == melt_quote.unit:
assert mint_quote.unit == melt_quote.unit, "units do not match" raise TransactionError("units do not match")
assert mint_quote.method == melt_quote.method, "methods do not match" if not mint_quote.method == melt_quote.method:
assert not mint_quote.paid, "mint quote already paid" raise TransactionError("methods do not match")
assert not mint_quote.issued, "mint quote already issued" if mint_quote.paid:
raise TransactionError("mint quote already paid")
if mint_quote.issued:
raise TransactionError("mint quote already issued")
logger.info( logger.info(
f"Settling bolt11 payment internally: {melt_quote.quote} ->" f"Settling bolt11 payment internally: {melt_quote.quote} ->"
f" {mint_quote.quote} ({melt_quote.amount} {melt_quote.unit})" f" {mint_quote.quote} ({melt_quote.amount} {melt_quote.unit})"
@@ -764,25 +796,25 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
melt_quote.unit, melt_quote.method 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 # make sure that the outputs (for fee return) are in the same unit as the quote
if outputs: if outputs:
await self._verify_outputs(outputs, skip_amount_check=True) 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 outputs_unit = self.keysets[outputs[0].id].unit
assert melt_quote.unit == outputs_unit.name, ( if not melt_quote.unit == outputs_unit.name:
f"output unit {outputs_unit.name} does not match quote unit" raise TransactionError(
f" {melt_quote.unit}" 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 # verify that the amount of the input proofs is equal to the amount of the quote
total_provided = sum_proofs(proofs) total_provided = sum_proofs(proofs)
total_needed = melt_quote.amount + (melt_quote.fee_reserve or 0) total_needed = melt_quote.amount + (melt_quote.fee_reserve or 0)
assert total_provided >= total_needed, ( if not total_provided >= total_needed:
f"not enough inputs provided for melt. Provided: {total_provided}, needed:" raise TransactionError(
f" {total_needed}" 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 # 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: 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 # prepare change to compensate wallet for overpaid fees
return_promises: List[BlindedSignature] = [] return_promises: List[BlindedSignature] = []
if outputs: if outputs:
assert outputs[0].id, "output id not set"
return_promises = await self._generate_change_promises( return_promises = await self._generate_change_promises(
input_amount=total_provided, input_amount=total_provided,
output_amount=melt_quote.amount, 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. Tuple[List[BlindSignature],List[BlindSignature]]: Promises on both sides of the split.
""" """
logger.trace("split called") 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) await self._set_proofs_pending(proofs)
try: 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 # Mark proofs as used and prepare new promises
async with get_db_connection(self.db) as conn: async with get_db_connection(self.db) as conn:
# we do this in a single db transaction # we do this in a single db transaction
promises = await self._generate_promises(outputs, keyset, conn)
await self._invalidate_proofs(proofs=proofs, conn=conn) await self._invalidate_proofs(proofs=proofs, conn=conn)
promises = await self._generate_promises(outputs, keyset, conn)
except Exception as e: except Exception as e:
logger.trace(f"split failed: {e}") logger.trace(f"split failed: {e}")
@@ -949,15 +979,16 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
] = [] ] = []
for output in outputs: for output in outputs:
B_ = PublicKey(bytes.fromhex(output.B_), raw=True) B_ = PublicKey(bytes.fromhex(output.B_), raw=True)
assert output.id, "output id not set"
keyset = keyset or self.keysets[output.id] keyset = keyset or self.keysets[output.id]
if output.id not in self.keysets:
assert output.id in self.keysets, f"keyset {output.id} not found" raise TransactionError(f"keyset {output.id} not found")
assert output.id in [ if output.id not in [
keyset.id, keyset.id,
keyset.duplicate_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 keyset_id = output.id
logger.trace(f"Generating promise with keyset {keyset_id}.") logger.trace(f"Generating promise with keyset {keyset_id}.")
private_key_amount = keyset.private_keys[output.amount] private_key_amount = keyset.private_keys[output.amount]
@@ -995,7 +1026,8 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
async def load_used_proofs(self) -> None: async def load_used_proofs(self) -> None:
"""Load all used proofs from database.""" """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") logger.debug("Loading used proofs into memory")
spent_proofs_list = await self.crud.get_spent_proofs(db=self.db) or [] spent_proofs_list = await self.crud.get_spent_proofs(db=self.db) or []
logger.debug(f"Loaded {len(spent_proofs_list)} used proofs") logger.debug(f"Loaded {len(spent_proofs_list)} used proofs")
@@ -1082,11 +1114,12 @@ class Ledger(LedgerVerification, LedgerSpendingConditions):
Raises: Raises:
Exception: At least one of the proofs is in the pending table. Exception: At least one of the proofs is in the pending table.
""" """
assert ( if not (
len( len(
await self.crud.get_proofs_pending( await self.crud.get_proofs_pending(
Ys=[p.Y for p in proofs], db=self.db, conn=conn Ys=[p.Y for p in proofs], db=self.db, conn=conn
) )
) )
== 0 == 0
), TransactionError("proofs are pending.") ):
raise TransactionError("proofs are pending.")

View File

@@ -4,6 +4,7 @@ from fastapi import APIRouter, Request
from loguru import logger from loguru import logger
from ..core.base import ( from ..core.base import (
BlindedMessage,
BlindedSignature, BlindedSignature,
CheckFeesRequest_deprecated, CheckFeesRequest_deprecated,
CheckFeesResponse_deprecated, CheckFeesResponse_deprecated,
@@ -177,10 +178,14 @@ async def mint_deprecated(
# BEGIN BACKWARDS COMPATIBILITY < 0.15 # BEGIN BACKWARDS COMPATIBILITY < 0.15
# Mint expects "id" in outputs to know which keyset to use to sign them. # Mint expects "id" in outputs to know which keyset to use to sign them.
for output in payload.outputs: # use the deprecated version of the current keyset
if not output.id: assert ledger.keyset.duplicate_keyset_id
# use the deprecated version of the current keyset outputs: list[BlindedMessage] = [
output.id = ledger.keyset.duplicate_keyset_id BlindedMessage(
id=o.id or ledger.keyset.duplicate_keyset_id, **o.dict(exclude={"id"})
)
for o in payload.outputs
]
# END BACKWARDS COMPATIBILITY < 0.15 # END BACKWARDS COMPATIBILITY < 0.15
# BEGIN: backwards compatibility < 0.12 where we used to lookup payments with payment_hash # 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." assert hash, "hash must be set."
# END: backwards compatibility < 0.12 # 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) blinded_signatures = PostMintResponse_deprecated(promises=promises)
logger.trace(f"< POST /mint: {blinded_signatures}") logger.trace(f"< POST /mint: {blinded_signatures}")
@@ -221,15 +226,18 @@ async def melt_deprecated(
logger.trace(f"> POST /melt: {payload}") logger.trace(f"> POST /melt: {payload}")
# BEGIN BACKWARDS COMPATIBILITY < 0.14: add "id" to outputs # BEGIN BACKWARDS COMPATIBILITY < 0.14: add "id" to outputs
if payload.outputs: if payload.outputs:
for output in payload.outputs: outputs: list[BlindedMessage] = [
if not output.id: BlindedMessage(id=o.id or ledger.keyset.id, **o.dict(exclude={"id"}))
output.id = ledger.keyset.id for o in payload.outputs
]
else:
outputs = []
# END BACKWARDS COMPATIBILITY < 0.14 # END BACKWARDS COMPATIBILITY < 0.14
quote = await ledger.melt_quote( quote = await ledger.melt_quote(
PostMeltQuoteRequest(request=payload.pr, unit="sat") PostMeltQuoteRequest(request=payload.pr, unit="sat")
) )
preimage, change_promises = await ledger.melt( 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( resp = PostMeltResponse_deprecated(
paid=True, preimage=preimage, change=change_promises paid=True, preimage=preimage, change=change_promises
@@ -290,12 +298,12 @@ async def split_deprecated(
logger.trace(f"> POST /split: {payload}") logger.trace(f"> POST /split: {payload}")
assert payload.outputs, Exception("no outputs provided.") assert payload.outputs, Exception("no outputs provided.")
# BEGIN BACKWARDS COMPATIBILITY < 0.14: add "id" to outputs # BEGIN BACKWARDS COMPATIBILITY < 0.14: add "id" to outputs
if payload.outputs: outputs: list[BlindedMessage] = [
for output in payload.outputs: BlindedMessage(id=o.id or ledger.keyset.id, **o.dict(exclude={"id"}))
if not output.id: for o in payload.outputs
output.id = ledger.keyset.id ]
# END BACKWARDS COMPATIBILITY < 0.14 # 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: if payload.amount:
# BEGIN backwards compatibility < 0.13 # BEGIN backwards compatibility < 0.13

View File

@@ -8,6 +8,7 @@ from loguru import logger
from ..core.base import ( from ..core.base import (
BlindedMessage, BlindedMessage,
BlindedMessage_Deprecated,
BlindedSignature, BlindedSignature,
CheckFeesRequest_deprecated, CheckFeesRequest_deprecated,
CheckFeesResponse_deprecated, CheckFeesResponse_deprecated,
@@ -271,7 +272,8 @@ class LedgerAPIDeprecated(SupportsHttpxClient, SupportsMintURL):
Raises: Raises:
Exception: If the minting fails 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]): def _mintrequest_include_fields(outputs: List[BlindedMessage]):
"""strips away fields from the model that aren't necessary for the /mint""" """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. Accepts proofs and a lightning invoice to pay in exchange.
""" """
logger.warning("Using deprecated API call: POST /melt") 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]): def _meltrequest_include_fields(proofs: List[Proof]):
"""strips away fields from the model that aren't necessary for the /melt""" """strips away fields from the model that aren't necessary for the /melt"""
@@ -336,7 +345,10 @@ class LedgerAPIDeprecated(SupportsHttpxClient, SupportsMintURL):
) -> List[BlindedSignature]: ) -> List[BlindedSignature]:
"""Consume proofs and create new promises based on amount split.""" """Consume proofs and create new promises based on amount split."""
logger.warning("Using deprecated API call: Calling split. POST /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 # construct payload
def _splitrequest_include_fields(proofs: List[Proof]): def _splitrequest_include_fields(proofs: List[Proof]):
@@ -403,7 +415,8 @@ class LedgerAPIDeprecated(SupportsHttpxClient, SupportsMintURL):
Asks the mint to restore promises corresponding to outputs. Asks the mint to restore promises corresponding to outputs.
""" """
logger.warning("Using deprecated API call: POST /restore") 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()) resp = await self.httpx.post(join(self.url, "/restore"), json=payload.dict())
self.raise_on_error(resp) self.raise_on_error(resp)
response_dict = resp.json() response_dict = resp.json()

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "cashu" name = "cashu"
version = "0.15.2" version = "0.15.3"
description = "Ecash wallet and mint" description = "Ecash wallet and mint"
authors = ["calle <callebtc@protonmail.com>"] authors = ["calle <callebtc@protonmail.com>"]
license = "MIT" license = "MIT"

View File

@@ -13,7 +13,7 @@ entry_points = {"console_scripts": ["cashu = cashu.wallet.cli.cli:cli"]}
setuptools.setup( setuptools.setup(
name="cashu", name="cashu",
version="0.15.2", version="0.15.3",
description="Ecash wallet and mint", description="Ecash wallet and mint",
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",

View File

@@ -65,7 +65,7 @@ async def test_init_keysets_with_duplicates(ledger: Ledger):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_init_keysets_with_duplicates_via_settings(ledger: Ledger): async def test_init_keysets_with_duplicates_via_settings(ledger: Ledger):
ledger.keysets = {} ledger.keysets = {}
settings.mint_duplicate_keysets = True settings.mint_duplicate_old_keysets = True
await ledger.init_keysets() await ledger.init_keysets()
assert len(ledger.keysets) == 2 assert len(ledger.keysets) == 2
@@ -80,7 +80,7 @@ async def test_init_keysets_without_duplicates(ledger: Ledger):
@pytest.mark.asyncio @pytest.mark.asyncio
async def test_init_keysets_without_duplicates_via_settings(ledger: Ledger): async def test_init_keysets_without_duplicates_via_settings(ledger: Ledger):
ledger.keysets = {} ledger.keysets = {}
settings.mint_duplicate_keysets = False settings.mint_duplicate_old_keysets = False
await ledger.init_keysets() await ledger.init_keysets()
assert len(ledger.keysets) == 1 assert len(ledger.keysets) == 1

View File

@@ -355,7 +355,7 @@ async def test_duplicate_proofs_double_spent(wallet1: Wallet):
doublespend = await wallet1.mint(64, id=invoice.id) doublespend = await wallet1.mint(64, id=invoice.id)
await assert_err( await assert_err(
wallet1.split(wallet1.proofs + doublespend, 20), wallet1.split(wallet1.proofs + doublespend, 20),
"Mint Error: Failed to set proofs pending.", "Mint Error: duplicate proofs.",
) )
assert wallet1.balance == 64 assert wallet1.balance == 64
assert wallet1.available_balance == 64 assert wallet1.available_balance == 64