mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-21 11:04:19 +01:00
* wip store balance * store balances in watchdog worker * move mint_auth_database setting * auth db * balances returned as Amount (instead of int) * add test for balance change on invoice receive * fix 1 test * cancel tasks on shutdown * watchdog can now abort * remove wallet api server * fix lndgrpc * fix lnbits balance * disable watchdog * balance lnbits msat * test db watcher with its own database connection * init superclass only once * wip: log balance in keysets table * check max balance using new keyset balance * fix test * fix another test * store fees in keysets * format * cleanup * shorter * add keyset migration to auth server * fix fakewallet * fix db tests * fix postgres problems during migration 26 (mint) * fix cln * ledger * working with pending * super fast watchdog, errors * test new pipeline * delete walletapi * delete unneeded files * revert workflows
160 lines
6.4 KiB
Python
160 lines
6.4 KiB
Python
import asyncio
|
|
from typing import List, Optional, Tuple
|
|
|
|
from loguru import logger
|
|
|
|
from cashu.core.db import Connection, Database
|
|
|
|
from ..core.base import Amount, MintBalanceLogEntry, Unit
|
|
from ..core.settings import settings
|
|
from ..lightning.base import LightningBackend
|
|
from .protocols import SupportsBackends, SupportsDb
|
|
|
|
|
|
class LedgerWatchdog(SupportsDb, SupportsBackends):
|
|
watcher_db: Database
|
|
abort_queue: asyncio.Queue = asyncio.Queue(0)
|
|
|
|
def __init__(self) -> None:
|
|
self.watcher_db = Database(self.db.name, self.db.db_location)
|
|
return
|
|
|
|
async def get_unit_balance_and_fees(
|
|
self,
|
|
unit: Unit,
|
|
db: Database,
|
|
conn: Optional[Connection] = None,
|
|
) -> Tuple[Amount, Amount]:
|
|
keysets = await self.crud.get_keyset(db=db, unit=unit.name, conn=conn)
|
|
balance = Amount(unit, 0)
|
|
fees_paid = Amount(unit, 0)
|
|
for keyset in keysets:
|
|
balance_update = await self.crud.get_balance(keyset, db=db, conn=conn)
|
|
balance += balance_update[0]
|
|
fees_paid += balance_update[1]
|
|
|
|
return balance, fees_paid
|
|
|
|
async def dispatch_watchdogs(self) -> List[asyncio.Task]:
|
|
tasks = []
|
|
for method, unitbackends in self.backends.items():
|
|
for unit, backend in unitbackends.items():
|
|
tasks.append(
|
|
asyncio.create_task(self.dispatch_backend_checker(unit, backend))
|
|
)
|
|
tasks.append(asyncio.create_task(self.monitor_abort_queue()))
|
|
return tasks
|
|
|
|
async def monitor_abort_queue(self):
|
|
while True:
|
|
await self.abort_queue.get()
|
|
if settings.mint_watchdog_ignore_mismatch:
|
|
logger.warning(
|
|
"Ignoring balance mismatch due to MINT_WATCHDOG_IGNORE_MISMATCH setting"
|
|
)
|
|
continue
|
|
logger.error(
|
|
"Shutting down the mint due to balance mismatch. Fix the balance mismatch and restart the mint or set MINT_WATCHDOG_IGNORE_MISMATCH=True to ignore the mismatch."
|
|
)
|
|
raise SystemExit
|
|
|
|
async def get_balance(self, unit: Unit) -> Tuple[Amount, Amount]:
|
|
"""Returns the balance of the mint for this unit."""
|
|
return await self.get_unit_balance_and_fees(unit=unit, db=self.db)
|
|
|
|
async def dispatch_backend_checker(
|
|
self, unit: Unit, backend: LightningBackend
|
|
) -> None:
|
|
logger.info(
|
|
f"Dispatching backend checker for unit: {unit.name} and backend: {backend.__class__.__name__}"
|
|
)
|
|
while True:
|
|
backend_status = await backend.status()
|
|
backend_balance = backend_status.balance
|
|
last_balance_log_entry: MintBalanceLogEntry | None = None
|
|
async with self.watcher_db.connect() as conn:
|
|
last_balance_log_entry = await self.crud.get_last_balance_log_entry(
|
|
unit=unit, db=self.watcher_db
|
|
)
|
|
keyset_balance, keyset_fees_paid = await self.get_unit_balance_and_fees(
|
|
unit, db=self.watcher_db, conn=conn
|
|
)
|
|
|
|
logger.debug(f"Last balance log entry: {last_balance_log_entry}")
|
|
logger.debug(
|
|
f"Backend balance {backend.__class__.__name__}: {backend_balance}"
|
|
)
|
|
logger.debug(
|
|
f"Unit balance {unit.name}: {keyset_balance}, fees paid: {keyset_fees_paid}"
|
|
)
|
|
|
|
ok = await self.check_balances_and_abort(
|
|
backend,
|
|
last_balance_log_entry,
|
|
backend_balance,
|
|
keyset_balance,
|
|
keyset_fees_paid,
|
|
)
|
|
|
|
if ok or settings.mint_watchdog_ignore_mismatch:
|
|
await self.crud.store_balance_log(
|
|
backend_balance,
|
|
keyset_balance,
|
|
keyset_fees_paid,
|
|
db=self.db,
|
|
conn=conn,
|
|
)
|
|
|
|
await asyncio.sleep(settings.mint_watchdog_balance_check_interval_seconds)
|
|
|
|
async def check_balances_and_abort(
|
|
self,
|
|
backend: LightningBackend,
|
|
last_balance_log_entry: MintBalanceLogEntry | None,
|
|
backend_balance: Amount,
|
|
keyset_balance: Amount,
|
|
keyset_fees_paid: Amount,
|
|
) -> bool:
|
|
"""Check if the backend balance and the mint balance match.
|
|
If they don't match, log a warning and raise an exception that will shut down the mint.
|
|
Returns True if the balances check succeeded, False otherwise.
|
|
|
|
Args:
|
|
backend (LightningBackend): Backend to check the balance against
|
|
last_balance_log_entry (MintBalanceLogEntry | None): Last balance log entry in the database
|
|
backend_balance (Amount): Balance of the backend
|
|
keyset_balance (Amount): Balance of the mint
|
|
|
|
Returns:
|
|
bool: True if the balances check succeeded, False otherwise
|
|
"""
|
|
if keyset_balance + keyset_fees_paid > backend_balance:
|
|
logger.warning(
|
|
f"Backend balance {backend.__class__.__name__}: {backend_balance} is smaller than issued unit balance {keyset_balance.unit}: {keyset_balance}"
|
|
)
|
|
await self.abort_queue.put(True)
|
|
return False
|
|
|
|
if last_balance_log_entry:
|
|
last_balance_delta = last_balance_log_entry.backend_balance - (
|
|
last_balance_log_entry.keyset_balance
|
|
+ last_balance_log_entry.keyset_fees_paid
|
|
)
|
|
current_balance_delta = backend_balance - (
|
|
keyset_balance + keyset_fees_paid
|
|
)
|
|
if last_balance_delta > current_balance_delta:
|
|
logger.warning(
|
|
f"Balance delta mismatch: before: {last_balance_delta} - now: {current_balance_delta}"
|
|
)
|
|
logger.warning(
|
|
f"Balances before: backend: {last_balance_log_entry.backend_balance}, issued ecash: {last_balance_log_entry.keyset_balance}, fees earned: {last_balance_log_entry.keyset_fees_paid}"
|
|
)
|
|
logger.warning(
|
|
f"Balances now: backend: {backend_balance}, issued ecash: {keyset_balance}, fees earned: {keyset_fees_paid}"
|
|
)
|
|
await self.abort_queue.put(True)
|
|
return False
|
|
|
|
return True
|