Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Balance Views Grouped By Keyset #652

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -660,15 +661,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 @@ -293,9 +293,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 @@ -418,8 +418,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: 39 additions & 1 deletion cashu/mint/migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,6 @@ async def m021_add_change_and_expiry_to_melt_quotes(db: Database):
f"ALTER TABLE {db.table_with_schema('melt_quotes')} ADD COLUMN expiry TIMESTAMP"
)


async def m022_quote_set_states_to_values(db: Database):
async with db.connect() as conn:
for melt_quote_states in MeltQuoteState:
Expand All @@ -838,3 +837,42 @@ async def m022_quote_set_states_to_values(db: Database):
await conn.execute(
f"UPDATE {db.table_with_schema('mint_quotes')} SET state = '{mint_quote_states.value}' WHERE state = '{mint_quote_states.name}'"
)

async def m023_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
Loading