Skip to content

Commit

Permalink
Balance Views Grouped By Keyset (#652)
Browse files Browse the repository at this point in the history
* balance view per keyset + relative changes.
    Still lacking: settings changes

* import unit

* fix 0 balance

* settings

---------

Co-authored-by: callebtc <[email protected]>
  • Loading branch information
lollerfirst and callebtc authored Feb 17, 2025
1 parent 23a706d commit 0c40bbb
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 9 deletions.
14 changes: 11 additions & 3 deletions cashu/mint/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ async def update_keyset(
@abstractmethod
async def get_balance(
self,
keyset: MintKeyset,
db: Database,
conn: Optional[Connection] = None,
) -> int: ...
Expand Down Expand Up @@ -668,15 +669,22 @@ async def store_keyset(

async def get_balance(
self,
keyset: MintKeyset,
db: Database,
conn: Optional[Connection] = None,
) -> int:
row = await (conn or db).fetchone(
f"""
SELECT * from {db.table_with_schema('balance')}
"""
SELECT balance FROM {db.table_with_schema('balance')}
WHERE keyset = :keyset
""",
{
"keyset": keyset.id,
},
)
assert row, "Balance not found"

if row is None:
return 0

# sqlalchemy index of first element
key = next(iter(row))
Expand Down
13 changes: 9 additions & 4 deletions cashu/mint/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,9 +322,9 @@ def get_keyset(self, keyset_id: Optional[str] = None) -> Dict[int, str]:
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:
async def get_balance(self, keyset: MintKeyset) -> int:
"""Returns the balance of the mint."""
return await self.crud.get_balance(db=self.db)
return await self.crud.get_balance(keyset=keyset, db=self.db)

# ------- ECASH -------

Expand Down Expand Up @@ -451,8 +451,13 @@ async def mint_quote(self, quote_request: PostMintQuoteRequest) -> MintQuote:
):
raise NotAllowedError("Backend does not support descriptions.")

if settings.mint_max_balance:
balance = await self.get_balance()
# MINT_MAX_BALANCE refers to sat (for now)
if settings.mint_max_balance and unit == Unit.sat:
# get next active keyset for unit
active_keyset: MintKeyset = next(
filter(lambda k: k.active and k.unit == unit, self.keysets.values())
)
balance = await self.get_balance(active_keyset)
if balance + quote_request.amount > settings.mint_max_balance:
raise NotAllowedError("Mint has reached maximum balance.")

Expand Down
40 changes: 40 additions & 0 deletions cashu/mint/migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -868,3 +868,43 @@ async def m025_add_amounts_to_keysets(db: Database):
await conn.execute(
f"UPDATE {db.table_with_schema('keysets')} SET amounts = '[]'"
)


async def m026_keyset_specific_balance_views(db: Database):
async with db.connect() as conn:
await drop_balance_views(db, conn)
await conn.execute(
f"""
CREATE VIEW {db.table_with_schema('balance_issued')} AS
SELECT id AS keyset, COALESCE(s, 0) AS balance FROM (
SELECT id, SUM(amount) AS s
FROM {db.table_with_schema('promises')}
WHERE amount > 0
GROUP BY id
);
"""
)
await conn.execute(
f"""
CREATE VIEW {db.table_with_schema('balance_redeemed')} AS
SELECT id AS keyset, COALESCE(s, 0) AS balance FROM (
SELECT id, SUM(amount) AS s
FROM {db.table_with_schema('proofs_used')}
WHERE amount > 0
GROUP BY id
);
"""
)
await conn.execute(
f"""
CREATE VIEW {db.table_with_schema('balance')} AS
SELECT keyset, s_issued - s_used AS balance FROM (
SELECT bi.keyset AS keyset,
bi.balance AS s_issued,
COALESCE(bu.balance, 0) AS s_used
FROM {db.table_with_schema('balance_issued')} bi
LEFT OUTER JOIN {db.table_with_schema('balance_redeemed')} bu
ON bi.keyset = bu.keyset
);
"""
)
8 changes: 6 additions & 2 deletions tests/test_mint.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest

from cashu.core.base import BlindedMessage, Proof
from cashu.core.base import BlindedMessage, MintKeyset, Proof, Unit
from cashu.core.crypto.b_dhke import step1_alice
from cashu.core.helpers import calculate_number_of_blank_outputs
from cashu.core.models import PostMintQuoteRequest
Expand Down Expand Up @@ -218,7 +218,11 @@ async def test_generate_change_promises_returns_empty_if_no_outputs(ledger: Ledg

@pytest.mark.asyncio
async def test_get_balance(ledger: Ledger):
balance = await ledger.get_balance()
unit = Unit["sat"]
active_keyset: MintKeyset = next(
filter(lambda k: k.active and k.unit == unit, ledger.keysets.values())
)
balance = await ledger.get_balance(active_keyset)
assert balance == 0


Expand Down

0 comments on commit 0c40bbb

Please sign in to comment.