mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 18:44:20 +01:00
* clean up db * db: table lock * db.table_with_schema * fix encrypt.py * postgres nowait * add timeout to lock * melt quote state in db * kinda working * kinda working with postgres * remove dispose * getting there * porperly clean up db for tests * faster tests * configure connection pooling * try github with connection pool * invoice dispatcher does not lock db * fakewallet: pay_if_regtest waits * pay fakewallet invoices * add more * faster * slower * pay_if_regtest async * do not lock the invoice dispatcher * test: do I get disk I/O errors if we disable the invoice_callback_dispatcher? * fix fake so it workss without a callback dispatchert * test on github * readd tasks * refactor * increase time for lock invoice disatcher * try avoiding a race * remove task * github actions: test regtest with postgres * mint per module * no connection pool for testing * enable pool * do not resend paid event * reuse connection * close db connections * sessions * enable debug * dispose engine * disable connection pool for tests * enable connection pool for postgres only * clean up shutdown routine * remove wait for lightning fakewallet lightning invoice * cancel invoice listener tasks on shutdown * fakewallet conftest: decrease outgoing delay * delay payment and set postgres only if needed * disable fail fast for regtest * clean up regtest.yml * change order of tests_db.py * row-specific mint_quote locking * refactor * fix lock statement * refactor swap * refactor * remove psycopg2 * add connection string example to .env.example * remove unnecessary pay * shorter sleep in test_wallet_subscription_swap
221 lines
8.7 KiB
Python
221 lines
8.7 KiB
Python
import random
|
|
from typing import List, Optional, Union
|
|
|
|
from loguru import logger
|
|
|
|
from ...core.base import (
|
|
MeltQuote,
|
|
MeltQuoteState,
|
|
MintQuote,
|
|
MintQuoteState,
|
|
Proof,
|
|
ProofSpentState,
|
|
ProofState,
|
|
)
|
|
from ...core.db import Connection, Database
|
|
from ...core.errors import (
|
|
TransactionError,
|
|
)
|
|
from ..crud import LedgerCrud
|
|
from ..events.events import LedgerEventManager
|
|
|
|
|
|
class DbWriteHelper:
|
|
db: Database
|
|
crud: LedgerCrud
|
|
events: LedgerEventManager
|
|
|
|
def __init__(
|
|
self, db: Database, crud: LedgerCrud, events: LedgerEventManager
|
|
) -> None:
|
|
self.db = db
|
|
self.crud = crud
|
|
self.events = events
|
|
|
|
async def _set_proofs_pending(
|
|
self, proofs: List[Proof], quote_id: Optional[str] = None
|
|
) -> None:
|
|
"""If none of the proofs is in the pending table (_validate_proofs_pending), adds proofs to
|
|
the list of pending proofs or removes them. Used as a mutex for proofs.
|
|
|
|
Args:
|
|
proofs (List[Proof]): Proofs to add to pending table.
|
|
quote_id (Optional[str]): Melt quote ID. If it is not set, we assume the pending tokens to be from a swap.
|
|
|
|
Raises:
|
|
Exception: At least one proof already in pending table.
|
|
"""
|
|
# first we check whether these proofs are pending already
|
|
random_id = random.randint(0, 1000000)
|
|
try:
|
|
logger.debug("trying to set proofs pending")
|
|
logger.trace(f"get_connection: random_id: {random_id}")
|
|
async with self.db.get_connection(
|
|
lock_table="proofs_pending",
|
|
lock_timeout=1,
|
|
) as conn:
|
|
logger.trace(f"get_connection: got connection {random_id}")
|
|
await self._validate_proofs_pending(proofs, conn)
|
|
for p in proofs:
|
|
logger.trace(f"crud: setting proof {p.Y} as PENDING")
|
|
await self.crud.set_proof_pending(
|
|
proof=p, db=self.db, quote_id=quote_id, conn=conn
|
|
)
|
|
logger.trace(f"crud: set proof {p.Y} as PENDING")
|
|
except Exception as e:
|
|
logger.error(f"Failed to set proofs pending: {e}")
|
|
raise TransactionError(f"Failed to set proofs pending: {str(e)}")
|
|
logger.trace("_set_proofs_pending released lock")
|
|
for p in proofs:
|
|
await self.events.submit(ProofState(Y=p.Y, state=ProofSpentState.pending))
|
|
|
|
async def _unset_proofs_pending(self, proofs: List[Proof], spent=True) -> None:
|
|
"""Deletes proofs from pending table.
|
|
|
|
Args:
|
|
proofs (List[Proof]): Proofs to delete.
|
|
spent (bool): Whether the proofs have been spent or not. Defaults to True.
|
|
This should be False if the proofs were NOT invalidated before calling this function.
|
|
It is used to emit the unspent state for the proofs (otherwise the spent state is emitted
|
|
by the _invalidate_proofs function when the proofs are spent).
|
|
"""
|
|
async with self.db.get_connection() as conn:
|
|
for p in proofs:
|
|
logger.trace(f"crud: un-setting proof {p.Y} as PENDING")
|
|
await self.crud.unset_proof_pending(proof=p, db=self.db, conn=conn)
|
|
|
|
if not spent:
|
|
for p in proofs:
|
|
await self.events.submit(
|
|
ProofState(Y=p.Y, state=ProofSpentState.unspent)
|
|
)
|
|
|
|
async def _validate_proofs_pending(
|
|
self, proofs: List[Proof], conn: Optional[Connection] = None
|
|
) -> None:
|
|
"""Checks if any of the provided proofs is in the pending proofs table.
|
|
|
|
Args:
|
|
proofs (List[Proof]): Proofs to check.
|
|
|
|
Raises:
|
|
Exception: At least one of the proofs is in the pending table.
|
|
"""
|
|
logger.trace("crud: validating proofs pending")
|
|
pending_proofs = await self.crud.get_proofs_pending(
|
|
Ys=[p.Y for p in proofs], db=self.db, conn=conn
|
|
)
|
|
if not (len(pending_proofs) == 0):
|
|
raise TransactionError("proofs are pending.")
|
|
|
|
async def _set_mint_quote_pending(self, quote_id: str) -> MintQuote:
|
|
"""Sets the mint quote as pending.
|
|
|
|
Args:
|
|
quote (MintQuote): Mint quote to set as pending.
|
|
"""
|
|
quote: Union[MintQuote, None] = None
|
|
async with self.db.get_connection(
|
|
lock_table="mint_quotes", lock_select_statement=f"quote='{quote_id}'"
|
|
) as conn:
|
|
# get mint quote from db and check if it is already pending
|
|
quote = await self.crud.get_mint_quote(
|
|
quote_id=quote_id, db=self.db, conn=conn
|
|
)
|
|
if not quote:
|
|
raise TransactionError("Mint quote not found.")
|
|
if quote.state == MintQuoteState.pending:
|
|
raise TransactionError("Mint quote already pending.")
|
|
if not quote.state == MintQuoteState.paid:
|
|
raise TransactionError("Mint quote is not paid yet.")
|
|
# set the quote as pending
|
|
quote.state = MintQuoteState.pending
|
|
logger.trace(f"crud: setting quote {quote_id} as PENDING")
|
|
await self.crud.update_mint_quote(quote=quote, db=self.db, conn=conn)
|
|
if quote is None:
|
|
raise TransactionError("Mint quote not found.")
|
|
return quote
|
|
|
|
async def _unset_mint_quote_pending(
|
|
self, quote_id: str, state: MintQuoteState
|
|
) -> MintQuote:
|
|
"""Unsets the mint quote as pending.
|
|
|
|
Args:
|
|
quote (MintQuote): Mint quote to unset as pending.
|
|
state (MintQuoteState): New state of the mint quote.
|
|
"""
|
|
quote: Union[MintQuote, None] = None
|
|
async with self.db.get_connection(lock_table="mint_quotes") as conn:
|
|
# get mint quote from db and check if it is pending
|
|
quote = await self.crud.get_mint_quote(
|
|
quote_id=quote_id, db=self.db, conn=conn
|
|
)
|
|
if not quote:
|
|
raise TransactionError("Mint quote not found.")
|
|
if quote.state != MintQuoteState.pending:
|
|
raise TransactionError(
|
|
f"Mint quote not pending: {quote.state.value}. Cannot set as {state.value}."
|
|
)
|
|
# set the quote as pending
|
|
quote.state = state
|
|
logger.trace(f"crud: setting quote {quote_id} as {state.value}")
|
|
await self.crud.update_mint_quote(quote=quote, db=self.db, conn=conn)
|
|
if quote is None:
|
|
raise TransactionError("Mint quote not found.")
|
|
|
|
await self.events.submit(quote)
|
|
return quote
|
|
|
|
async def _set_melt_quote_pending(self, quote: MeltQuote) -> MeltQuote:
|
|
"""Sets the melt quote as pending.
|
|
|
|
Args:
|
|
quote (MeltQuote): Melt quote to set as pending.
|
|
"""
|
|
quote_copy = quote.copy()
|
|
async with self.db.get_connection(
|
|
lock_table="melt_quotes",
|
|
lock_select_statement=f"checking_id='{quote.checking_id}'",
|
|
) as conn:
|
|
# get melt quote from db and check if it is already pending
|
|
quote_db = await self.crud.get_melt_quote(
|
|
checking_id=quote.checking_id, db=self.db, conn=conn
|
|
)
|
|
if not quote_db:
|
|
raise TransactionError("Melt quote not found.")
|
|
if quote_db.state == MeltQuoteState.pending:
|
|
raise TransactionError("Melt quote already pending.")
|
|
# set the quote as pending
|
|
quote_copy.state = MeltQuoteState.pending
|
|
await self.crud.update_melt_quote(quote=quote_copy, db=self.db, conn=conn)
|
|
|
|
await self.events.submit(quote_copy)
|
|
return quote_copy
|
|
|
|
async def _unset_melt_quote_pending(
|
|
self, quote: MeltQuote, state: MeltQuoteState
|
|
) -> MeltQuote:
|
|
"""Unsets the melt quote as pending.
|
|
|
|
Args:
|
|
quote (MeltQuote): Melt quote to unset as pending.
|
|
state (MeltQuoteState): New state of the melt quote.
|
|
"""
|
|
quote_copy = quote.copy()
|
|
async with self.db.get_connection(lock_table="melt_quotes") as conn:
|
|
# get melt quote from db and check if it is pending
|
|
quote_db = await self.crud.get_melt_quote(
|
|
checking_id=quote.checking_id, db=self.db, conn=conn
|
|
)
|
|
if not quote_db:
|
|
raise TransactionError("Melt quote not found.")
|
|
if quote_db.state != MeltQuoteState.pending:
|
|
raise TransactionError("Melt quote not pending.")
|
|
# set the quote as pending
|
|
quote_copy.state = state
|
|
await self.crud.update_melt_quote(quote=quote_copy, db=self.db, conn=conn)
|
|
|
|
await self.events.submit(quote_copy)
|
|
return quote_copy
|