Files
nutshell/cashu/mint/db/read.py
lollerfirst efdfecc182 Fix race condition (#586)
* `_set_proofs_pending` performs DB related "proofs are spendable" check inside the lock.

* move _verify_spent_proofs_and_set_pending to write.py

* edit logging

---------

Co-authored-by: callebtc <93376500+callebtc@users.noreply.github.com>
2024-07-17 14:04:17 +02:00

94 lines
3.8 KiB
Python

from typing import Dict, List, Optional
from ...core.base import Proof, ProofSpentState, ProofState
from ...core.db import Connection, Database
from ...core.errors import TokenAlreadySpentError
from ..crud import LedgerCrud
class DbReadHelper:
db: Database
crud: LedgerCrud
def __init__(self, db: Database, crud: LedgerCrud) -> None:
self.db = db
self.crud = crud
async def _get_proofs_pending(
self, Ys: List[str], conn: Optional[Connection] = None
) -> Dict[str, Proof]:
"""Returns a dictionary of only those proofs that are pending.
The key is the Y=h2c(secret) and the value is the proof.
"""
async with self.db.get_connection(conn) as conn:
proofs_pending = await self.crud.get_proofs_pending(
Ys=Ys, db=self.db, conn=conn
)
proofs_pending_dict = {p.Y: p for p in proofs_pending}
return proofs_pending_dict
async def _get_proofs_spent(
self, Ys: List[str], conn: Optional[Connection] = None
) -> Dict[str, Proof]:
"""Returns a dictionary of all proofs that are spent.
The key is the Y=h2c(secret) and the value is the proof.
"""
proofs_spent_dict: Dict[str, Proof] = {}
# check used secrets in database
async with self.db.get_connection(conn) as conn:
spent_proofs = await self.crud.get_proofs_used(db=self.db, Ys=Ys, conn=conn)
proofs_spent_dict = {p.Y: p for p in spent_proofs}
return proofs_spent_dict
async def get_proofs_states(
self, Ys: List[str], conn: Optional[Connection] = None
) -> List[ProofState]:
"""Checks if provided proofs are spend or are pending.
Used by wallets to check if their proofs have been redeemed by a receiver or they are still in-flight in a transaction.
Returns two lists that are in the same order as the provided proofs. Wallet must match the list
to the proofs they have provided in order to figure out which proof is spendable or pending
and which isn't.
Args:
Ys (List[str]): List of Y's of proofs to check
Returns:
List[bool]: List of which proof is still spendable (True if still spendable, else False)
List[bool]: List of which proof are pending (True if pending, else False)
"""
states: List[ProofState] = []
async with self.db.get_connection(conn) as conn:
proofs_spent = await self._get_proofs_spent(Ys, conn)
proofs_pending = await self._get_proofs_pending(Ys, conn)
for Y in Ys:
if Y not in proofs_spent and Y not in proofs_pending:
states.append(ProofState(Y=Y, state=ProofSpentState.unspent))
elif Y not in proofs_spent and Y in proofs_pending:
states.append(ProofState(Y=Y, state=ProofSpentState.pending))
else:
states.append(
ProofState(
Y=Y,
state=ProofSpentState.spent,
witness=proofs_spent[Y].witness,
)
)
return states
async def _verify_proofs_spendable(
self, proofs: List[Proof], conn: Optional[Connection] = None
):
"""Checks the database to see if any of the proofs are already spent.
Args:
proofs (List[Proof]): Proofs to verify
conn (Optional[Connection]): Database connection to use. Defaults to None.
Raises:
TokenAlreadySpentError: If any of the proofs are already spent
"""
async with self.db.get_connection(conn) as conn:
if not len(await self._get_proofs_spent([p.Y for p in proofs], conn)) == 0:
raise TokenAlreadySpentError()