mirror of
https://github.com/aljazceru/nutshell.git
synced 2026-01-06 02:14:21 +01:00
Mint: table locks (#566)
* 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
This commit is contained in:
@@ -1,10 +1,18 @@
|
||||
import asyncio
|
||||
from typing import List, Optional
|
||||
import random
|
||||
from typing import List, Optional, Union
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from ...core.base import Proof, ProofSpentState, ProofState
|
||||
from ...core.db import Connection, Database, get_db_connection
|
||||
from ...core.base import (
|
||||
MeltQuote,
|
||||
MeltQuoteState,
|
||||
MintQuote,
|
||||
MintQuoteState,
|
||||
Proof,
|
||||
ProofSpentState,
|
||||
ProofState,
|
||||
)
|
||||
from ...core.db import Connection, Database
|
||||
from ...core.errors import (
|
||||
TransactionError,
|
||||
)
|
||||
@@ -16,9 +24,6 @@ class DbWriteHelper:
|
||||
db: Database
|
||||
crud: LedgerCrud
|
||||
events: LedgerEventManager
|
||||
proofs_pending_lock: asyncio.Lock = (
|
||||
asyncio.Lock()
|
||||
) # holds locks for proofs_pending database
|
||||
|
||||
def __init__(
|
||||
self, db: Database, crud: LedgerCrud, events: LedgerEventManager
|
||||
@@ -41,20 +46,28 @@ class DbWriteHelper:
|
||||
Exception: At least one proof already in pending table.
|
||||
"""
|
||||
# first we check whether these proofs are pending already
|
||||
async with self.proofs_pending_lock:
|
||||
async with get_db_connection(self.db) as conn:
|
||||
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)
|
||||
try:
|
||||
for p in proofs:
|
||||
await self.crud.set_proof_pending(
|
||||
proof=p, db=self.db, quote_id=quote_id, conn=conn
|
||||
)
|
||||
await self.events.submit(
|
||||
ProofState(Y=p.Y, state=ProofSpentState.pending)
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to set proofs pending: {e}")
|
||||
raise TransactionError("Failed to set proofs pending.")
|
||||
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.
|
||||
@@ -66,14 +79,16 @@ class DbWriteHelper:
|
||||
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.proofs_pending_lock:
|
||||
async with get_db_connection(self.db) as conn:
|
||||
for p in proofs:
|
||||
await self.crud.unset_proof_pending(proof=p, db=self.db, conn=conn)
|
||||
if not spent:
|
||||
await self.events.submit(
|
||||
ProofState(Y=p.Y, state=ProofSpentState.unspent)
|
||||
)
|
||||
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
|
||||
@@ -86,12 +101,120 @@ class DbWriteHelper:
|
||||
Raises:
|
||||
Exception: At least one of the proofs is in the pending table.
|
||||
"""
|
||||
if not (
|
||||
len(
|
||||
await self.crud.get_proofs_pending(
|
||||
Ys=[p.Y for p in proofs], db=self.db, conn=conn
|
||||
)
|
||||
)
|
||||
== 0
|
||||
):
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user