Fix receive -a to receive all pending tokens (#578)

* catch error if receive -a doesnt work with a certain mint

* receive pending tokens from multiple mints

* receive pending from all mints and catch exceptions
This commit is contained in:
callebtc
2024-07-11 12:55:36 +02:00
committed by GitHub
parent 26b94951fc
commit 1660005bef
3 changed files with 101 additions and 22 deletions

View File

@@ -36,6 +36,7 @@ from ..cli.cli_helpers import (
get_unit_wallet,
print_balance,
print_mint_balances,
receive_all_pending,
verify_mint,
)
from ..helpers import (
@@ -601,7 +602,7 @@ async def receive_cli(
all: bool,
):
wallet: Wallet = ctx.obj["WALLET"]
# receive a specific token
if token:
token_obj = deserialize_token_from_string(token)
# verify that we trust the mint in this tokens
@@ -615,30 +616,16 @@ async def receive_cli(
await verify_mint(mint_wallet, mint_url)
receive_wallet = await receive(mint_wallet, token_obj)
ctx.obj["WALLET"] = receive_wallet
# receive tokens via nostr
elif nostr:
await receive_nostr(wallet)
# exit on keypress
input("Enter any text to exit.")
print("Exiting.")
os._exit(0)
# receive all pending outgoing tokens back to the wallet
elif all:
reserved_proofs = await get_reserved_proofs(wallet.db)
if len(reserved_proofs):
for key, value in groupby(reserved_proofs, key=itemgetter("send_id")): # type: ignore
proofs = list(value)
token = await wallet.serialize_proofs(proofs)
token_obj = TokenV4.deserialize(token)
# verify that we trust the mint of this token
# ask the user if they want to trust the mint
mint_url = token_obj.mint
mint_wallet = Wallet(
mint_url,
os.path.join(settings.cashu_dir, wallet.name),
unit=token_obj.unit,
)
await verify_mint(mint_wallet, mint_url)
receive_wallet = await receive(wallet, token_obj)
ctx.obj["WALLET"] = receive_wallet
await receive_all_pending(ctx, wallet)
else:
print("Error: enter token or use either flag --nostr or --all.")
return
@@ -767,7 +754,9 @@ async def pending(ctx: Context, legacy, number: int, offset: int):
)
print(f"Legacy token: {token_legacy}\n")
print("--------------------------\n")
print("To remove all spent tokens use: cashu burn -a")
print("To remove all pending tokens that are already spent use: cashu burn -a")
print("To remove a specific pending token use: cashu burn <token>")
print("To receive all pending tokens use: cashu receive -a")
@cli.command("lock", help="Generate receiving lock.")

View File

@@ -1,4 +1,7 @@
#!/usr/bin/env python
import os
from itertools import groupby
from operator import itemgetter
import click
from click import Context
@@ -6,8 +9,15 @@ from loguru import logger
from ...core.base import Unit
from ...core.settings import settings
from ...wallet.crud import get_keysets
from ...wallet.crud import (
get_keysets,
get_reserved_proofs,
)
from ...wallet.wallet import Wallet as Wallet
from ..helpers import (
deserialize_token_from_string,
receive,
)
async def print_balance(ctx: Context):
@@ -170,3 +180,40 @@ async def verify_mint(mint_wallet: Wallet, url: str):
)
else:
logger.debug(f"We know mint {url} already")
async def receive_all_pending(ctx: Context, wallet: Wallet):
reserved_proofs = await get_reserved_proofs(wallet.db)
if not len(reserved_proofs):
print("No pending proofs to receive.")
return
for key, value in groupby(reserved_proofs, key=itemgetter("send_id")): # type: ignore
mint_url = None
token_obj = None
try:
proofs = list(value)
mint_url, unit = await wallet._get_proofs_mint_unit(proofs)
mint_wallet = await Wallet.with_db(
url=mint_url,
db=os.path.join(settings.cashu_dir, wallet.name),
name=wallet.name,
unit=unit.name,
)
# verify that we trust the mint of this token
# ask the user if they want to trust the mint
await verify_mint(mint_wallet, mint_url)
token = await mint_wallet.serialize_proofs(proofs)
token_obj = deserialize_token_from_string(token)
mint_url = token_obj.mint
receive_wallet = await receive(mint_wallet, token_obj)
ctx.obj["WALLET"] = receive_wallet
except Exception as e:
if mint_url and token_obj:
unit = Unit[token_obj.unit]
print(
f"Could not receive {unit.str(token_obj.amount)} from mint {mint_url}: {str(e)}"
)
else:
print(f"Could not receive token: {str(e)}")
continue

View File

@@ -1,5 +1,5 @@
from itertools import groupby
from typing import Dict, List, Optional
from typing import Dict, List, Optional, Tuple
from loguru import logger
@@ -91,6 +91,44 @@ class WalletProofs(SupportsDb, SupportsKeysets):
)
return mint_urls
async def _get_proofs_keysets(self, proofs: List[Proof]) -> Dict[str, WalletKeyset]:
keyset_ids = self._get_proofs_keyset_ids(proofs)
keysets_dict = {}
async with self.db.get_connection() as conn:
for keyset_id in keyset_ids:
keyset = await get_keysets(id=keyset_id, db=self.db, conn=conn)
if len(keyset) == 1:
keysets_dict[keyset_id] = keyset[0]
return keysets_dict
async def _get_proofs_mint_unit(self, proofs: List[Proof]) -> Tuple[str, Unit]:
"""Helper function that extracts the mint URL and unit from a list of proofs. It raises an exception if the proofs are from multiple mints or units.
Args:
proofs (List[Proof]): List of proofs to extract the mint URL and unit from.
Raises:
Exception: If the proofs are from multiple mints or units.
Exception: If the proofs are from an unknown mint or keyset.
Returns:
Tuple[str, Unit]: Mint URL and `Unit` of the proofs
"""
proofs_keysets = await self._get_proofs_keysets(proofs)
mint_urls = [k.mint_url for k in proofs_keysets.values()]
if not mint_urls:
raise Exception("Proofs from unknown mint or keyset.")
if len(set(mint_urls)) != 1:
raise Exception("Proofs from multiple mints.")
mint_url = mint_urls[0]
if not mint_url:
raise Exception("No mint URL found for keyset")
proofs_units = [k.unit for k in proofs_keysets.values()]
if len(set(proofs_units)) != 1:
raise Exception("Proofs from multiple units.")
unit = proofs_units[0]
return mint_url, unit
async def serialize_proofs(
self,
proofs: List[Proof],
@@ -136,6 +174,8 @@ class WalletProofs(SupportsDb, SupportsKeysets):
# extract all keysets IDs from proofs
keyset_ids = self._get_proofs_keyset_ids(proofs)
keysets = {k.id: k for k in self.keysets.values() if k.id in keyset_ids}
if not keysets:
raise ValueError("No keysets found for proofs")
assert (
len(set([k.unit for k in keysets.values()])) == 1
), "All keysets must have the same unit"
@@ -170,7 +210,10 @@ class WalletProofs(SupportsDb, SupportsKeysets):
# get all keysets from proofs
keyset_ids = set(self._get_proofs_keyset_ids(proofs))
keysets = [self.keysets[i] for i in keyset_ids]
try:
keysets = [self.keysets[i] for i in keyset_ids]
except KeyError:
raise ValueError("Keysets of proofs are not loaded in wallet")
# we make sure that all proofs are from keysets of the same mint
if len(set([k.mint_url for k in keysets])) > 1:
raise ValueError("TokenV4 can only contain proofs from a single mint URL")