From f02ac8d3eeaa0eaa3a0f36fdd5e0388e0cdf1121 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Sat, 17 Sep 2022 23:26:51 +0300 Subject: [PATCH] burn all spent tokens with cashu burn -a --- mint/app.py | 2 +- mint/ledger.py | 15 +++++++++------ wallet/cashu.py | 16 +++++++++++++--- wallet/crud.py | 14 ++++++++++++++ wallet/wallet.py | 35 ++++++++++++++++++++--------------- 5 files changed, 57 insertions(+), 25 deletions(-) diff --git a/mint/app.py b/mint/app.py index 85add1b..360f318 100644 --- a/mint/app.py +++ b/mint/app.py @@ -144,7 +144,7 @@ async def melt(payload: MeltPayload): @app.post("/check") async def check_spendable(payload: CheckPayload): - return {"spendable": await ledger.check_spendable(payload.proofs)} + return await ledger.check_spendable(payload.proofs) @app.post("/split") diff --git a/mint/ledger.py b/mint/ledger.py index bf47080..d80aa10 100644 --- a/mint/ledger.py +++ b/mint/ledger.py @@ -13,9 +13,14 @@ from core.secp import PrivateKey, PublicKey from core.settings import LIGHTNING, MAX_ORDER from core.split import amount_split from lightning import WALLET -from mint.crud import (get_lightning_invoice, get_proofs_used, - invalidate_proof, store_lightning_invoice, - store_promise, update_lightning_invoice) +from mint.crud import ( + get_lightning_invoice, + get_proofs_used, + invalidate_proof, + store_lightning_invoice, + store_promise, + update_lightning_invoice, +) class Ledger: @@ -219,9 +224,7 @@ class Ledger: async def check_spendable(self, proofs: List[Proof]): """Checks if all provided proofs are valid and still spendable (i.e. have not been spent).""" - if not all([self._check_spendable(p) for p in proofs]): - return False - return True + return {i: self._check_spendable(p) for i, p in enumerate(proofs)} async def split( self, proofs: List[Proof], amount: int, output_data: List[BlindedMessage] diff --git a/wallet/cashu.py b/wallet/cashu.py index bf18f48..bc28284 100755 --- a/wallet/cashu.py +++ b/wallet/cashu.py @@ -16,6 +16,7 @@ from core.helpers import fee_reserve from core.migrations import migrate_databases from core.settings import LIGHTNING, MINT_URL from wallet import migrations +from wallet.crud import get_reserved_proofs from wallet.wallet import Wallet as Wallet @@ -121,14 +122,23 @@ async def receive(ctx, token: str): @cli.command("burn", help="Burn spent tokens.") -@click.argument("token", type=str) +@click.argument("token", required=False, type=str) +@click.option("--all", "-a", default=False, is_flag=True, help="Burn all spent tokens.") @click.pass_context @coro -async def burn(ctx, token: str): +async def burn(ctx, token: str, all: bool): wallet: Wallet = ctx.obj["WALLET"] await init_wallet(wallet) + if not (all or token) or (token and all): + print("Error: enter a token or use --all to burn all pending tokens.") + return + if all: + proofs = await get_reserved_proofs(wallet.db) + else: + proofs = [ + Proof.from_dict(p) for p in json.loads(base64.urlsafe_b64decode(token)) + ] wallet.status() - proofs = [Proof.from_dict(p) for p in json.loads(base64.urlsafe_b64decode(token))] await wallet.invalidate(proofs) wallet.status() diff --git a/wallet/crud.py b/wallet/crud.py index edc234a..555715f 100644 --- a/wallet/crud.py +++ b/wallet/crud.py @@ -38,6 +38,20 @@ async def get_proofs( return [Proof.from_row(r) for r in rows] +async def get_reserved_proofs( + db: Database, + conn: Optional[Connection] = None, +): + + rows = await (conn or db).fetchall( + """ + SELECT * from proofs + WHERE reserved + """ + ) + return [Proof.from_row(r) for r in rows] + + async def invalidate_proof( proof: Proof, db: Database, diff --git a/wallet/wallet.py b/wallet/wallet.py index 4d1fd2d..c34a946 100644 --- a/wallet/wallet.py +++ b/wallet/wallet.py @@ -4,13 +4,19 @@ from typing import List import requests import core.b_dhke as b_dhke -from core.base import (BlindedMessage, BlindedSignature, CheckPayload, - MeltPayload, MintPayloads, Proof, SplitPayload) +from core.base import ( + BlindedMessage, + BlindedSignature, + CheckPayload, + MeltPayload, + MintPayloads, + Proof, + SplitPayload, +) from core.db import Database from core.secp import PublicKey from core.split import amount_split -from wallet.crud import (get_proofs, invalidate_proof, store_proof, - update_proof_reserved) +from wallet.crud import get_proofs, invalidate_proof, store_proof, update_proof_reserved class LedgerAPI: @@ -113,9 +119,8 @@ class LedgerAPI: self.url + "/check", json=payload.dict(), ).json() - if "spendable" in return_dict and return_dict["spendable"]: - return True - return False + + return return_dict async def pay_lightning(self, proofs: List[Proof], amount: int, invoice: str): payload = MeltPayload(proofs=proofs, amount=amount, invoice=invoice) @@ -197,14 +202,14 @@ class Wallet(LedgerAPI): return await super().check_spendable(proofs) async def invalidate(self, proofs): - # first we make sure that the server has invalidated these proofs - if await self.check_spendable(proofs): - raise Exception("tokens not yet spent.") - - # TODO: check with server if they were redeemed already - for proof in proofs: - await invalidate_proof(proof, db=self.db) - invalidate_secrets = [p["secret"] for p in proofs] + """Invalidates all spendable tokens supplied in proofs.""" + spendables = await self.check_spendable(proofs) + invalidated_proofs = [] + for idx, spendable in spendables.items(): + if not spendable: + invalidated_proofs.append(proofs[int(idx)]) + await invalidate_proof(proofs[int(idx)], db=self.db) + invalidate_secrets = [p["secret"] for p in invalidated_proofs] self.proofs = list( filter(lambda p: p["secret"] not in invalidate_secrets, self.proofs) )