Skip to content

Commit

Permalink
refactor db transactions (#571)
Browse files Browse the repository at this point in the history
  • Loading branch information
callebtc authored Jul 9, 2024
1 parent 51ae82b commit 539054a
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 54 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ These steps help you install Python via pyenv and Poetry. If you already have Po

```bash
# on ubuntu:
sudo apt install -y build-essential pkg-config libffi-dev libpq-dev zlib1g-dev libssl-dev python3-dev libsqlite3-dev ncurses-dev libbz2-dev libreadline-dev lzma-dev
sudo apt install -y build-essential pkg-config libffi-dev libpq-dev zlib1g-dev libssl-dev python3-dev libsqlite3-dev ncurses-dev libbz2-dev libreadline-dev lzma-dev liblzma-dev

# install python using pyenv
curl https://pyenv.run | bash
Expand Down
56 changes: 30 additions & 26 deletions cashu/mint/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,15 @@ async def get_keyset(
...

@abstractmethod
async def get_spent_proofs(
async def get_proofs_used(
self,
*,
Ys: List[str],
db: Database,
conn: Optional[Connection] = None,
) -> List[Proof]:
...

async def get_proof_used(
self,
*,
Y: str,
db: Database,
conn: Optional[Connection] = None,
) -> Optional[Proof]:
...

@abstractmethod
async def invalidate_proof(
self,
Expand Down Expand Up @@ -157,6 +149,16 @@ async def get_promise(
) -> Optional[BlindedSignature]:
...

@abstractmethod
async def get_promises(
self,
*,
db: Database,
b_s: List[str],
conn: Optional[Connection] = None,
) -> List[BlindedSignature]:
...

@abstractmethod
async def store_mint_quote(
self,
Expand Down Expand Up @@ -294,18 +296,21 @@ async def get_promise(
)
return BlindedSignature.from_row(row) if row else None

async def get_spent_proofs(
async def get_promises(
self,
*,
db: Database,
b_s: List[str],
conn: Optional[Connection] = None,
) -> List[Proof]:
) -> List[BlindedSignature]:
rows = await (conn or db).fetchall(
f"""
SELECT * from {db.table_with_schema('proofs_used')}
"""
SELECT * from {db.table_with_schema('promises')}
WHERE b_ IN ({','.join([':b_' + str(i) for i in range(len(b_s))])})
""",
{f"b_{i}": b_s[i] for i in range(len(b_s))},
)
return [Proof(**r) for r in rows] if rows else []
return [BlindedSignature.from_row(r) for r in rows] if rows else []

async def invalidate_proof(
self,
Expand Down Expand Up @@ -722,18 +727,17 @@ async def get_keyset(
)
return [MintKeyset(**row) for row in rows]

async def get_proof_used(
async def get_proofs_used(
self,
*,
Y: str,
Ys: List[str],
db: Database,
conn: Optional[Connection] = None,
) -> Optional[Proof]:
row = await (conn or db).fetchone(
f"""
SELECT * from {db.table_with_schema('proofs_used')}
WHERE y = :y
""",
{"y": Y},
)
return Proof(**row) if row else None
) -> List[Proof]:
query = f"""
SELECT * from {db.table_with_schema('proofs_used')}
WHERE y IN ({','.join([':y_' + str(i) for i in range(len(Ys))])})
"""
values = {f"y_{i}": Ys[i] for i in range(len(Ys))}
rows = await (conn or db).fetchall(query, values)
return [Proof(**r) for r in rows] if rows else []
6 changes: 2 additions & 4 deletions cashu/mint/db/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,8 @@ async def _get_proofs_spent(
proofs_spent_dict: Dict[str, Proof] = {}
# check used secrets in database
async with self.db.get_connection(conn) as conn:
for Y in Ys:
spent_proof = await self.crud.get_proof_used(db=self.db, Y=Y, conn=conn)
if spent_proof:
proofs_spent_dict[Y] = spent_proof
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(
Expand Down
4 changes: 4 additions & 0 deletions cashu/mint/protocols.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
from ..core.db import Database
from ..lightning.base import LightningBackend
from ..mint.crud import LedgerCrud
from .db.read import DbReadHelper
from .db.write import DbWriteHelper
from .events.events import LedgerEventManager


Expand All @@ -18,6 +20,8 @@ class SupportsBackends(Protocol):

class SupportsDb(Protocol):
db: Database
db_read: DbReadHelper
db_write: DbWriteHelper
crud: LedgerCrud


Expand Down
35 changes: 12 additions & 23 deletions cashu/mint/verification.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
from ..lightning.base import LightningBackend
from ..mint.crud import LedgerCrud
from .conditions import LedgerSpendingConditions
from .db.read import DbReadHelper
from .db.write import DbWriteHelper
from .protocols import SupportsBackends, SupportsDb, SupportsKeysets


Expand All @@ -37,6 +39,8 @@ class LedgerVerification(
keysets: Dict[str, MintKeyset]
crud: LedgerCrud
db: Database
db_read: DbReadHelper
db_write: DbWriteHelper
lightning: Dict[Unit, LightningBackend]

async def verify_inputs_and_outputs(
Expand Down Expand Up @@ -64,7 +68,10 @@ async def verify_inputs_and_outputs(
if not proofs:
raise TransactionError("no proofs provided.")
# Verify proofs are spendable
if not len(await self._get_proofs_spent([p.Y for p in proofs], conn)) == 0:
if (
not len(await self.db_read._get_proofs_spent([p.Y for p in proofs], conn))
== 0
):
raise TokenAlreadySpentError()
# Verify amounts of inputs
if not all([self._verify_amount(p.amount) for p in proofs]):
Expand Down Expand Up @@ -156,29 +163,11 @@ async def _check_outputs_issued_before(
Returns:
result (List[bool]): Whether outputs are already present in the database.
"""
result = []
async with self.db.get_connection(conn) as conn:
for output in outputs:
promise = await self.crud.get_promise(
b_=output.B_, db=self.db, conn=conn
)
result.append(False if promise is None else True)
return result

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=conn) as conn:
for Y in Ys:
spent_proof = await self.crud.get_proof_used(db=self.db, Y=Y, conn=conn)
if spent_proof:
proofs_spent_dict[Y] = spent_proof
return proofs_spent_dict
promises = await self.crud.get_promises(
b_s=[output.B_ for output in outputs], db=self.db, conn=conn
)
return [True if promise else False for promise in promises]

def _verify_secret_criteria(self, proof: Proof) -> Literal[True]:
"""Verifies that a secret is present and is not too long (DOS prevention)."""
Expand Down

0 comments on commit 539054a

Please sign in to comment.