Skip to content

Commit

Permalink
Database shenanigans
Browse files Browse the repository at this point in the history
  • Loading branch information
lollerfirst committed Jul 30, 2024
1 parent 4ce0b7b commit a5b147b
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 10 deletions.
7 changes: 7 additions & 0 deletions cashu/core/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,4 +106,11 @@ class DlcVerificationFail(CashuError):
def __init__(self, **kwargs):
super().__init__(self.detail, self.code)
self.bad_inputs = kwargs['bad_inputs']

class DlcAlreadyRegisteredError(CashuError):
detail = "dlc already registered"
code = 30001

def __init__(self, **kwargs):
super().__init__(self.detail, self.code)

52 changes: 52 additions & 0 deletions cashu/mint/crud.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional
from .base import DiscreteLogContract

from ..core.base import (
BlindedSignature,
Expand Down Expand Up @@ -243,6 +244,23 @@ async def update_melt_quote(
) -> None:
...

@abstractmethod
async def get_registered_dlc(
self,
dlc_root: str,
db: Database,
conn: Optional[Connection] = None,
) -> DiscreteLogContract:
...

@abstractmethod
async def store_dlc(
self,
dlc: DiscreteLogContract,
db: Database,
conn: Optional[Connection] = None,
) -> None:
...

class LedgerCrudSqlite(LedgerCrud):
"""Implementation of LedgerCrud for sqlite.
Expand Down Expand Up @@ -741,3 +759,37 @@ async def get_proofs_used(
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 []

async def get_registered_dlc(
self,
dlc_root: str,
db: Database,
conn: Optional[Connection] = None,
) -> Optional[DiscreteLogContract]:
query = f"""
SELECT * from {db.table_with_schema('dlc')}
WHERE dlc_root = :dlc_root
"""
result = await (conn or db).fetchone(query, {"dlc_root": dlc_root})
return result

async def store_dlc(
self,
dlc: DiscreteLogContract,
db: Database,
conn: Optional[Connection] = None,
) -> None:
query = f"""
INSERT INTO {db.table_with_schema('dlc')}
(dlc_root, settled, funding_amount, unit)
VALUES (:dlc_root, :settled, :funding_amount, :unit)
"""
await (conn or db).execute(
query,
{
"dlc_root": dlc.dlc_root,
"settled": dlc.settled,
"funding_amount": dlc.funding_amount,
"unit": dlc.unit,
},
)
9 changes: 8 additions & 1 deletion cashu/mint/db/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from ...core.base import Proof, ProofSpentState, ProofState
from ...core.db import Connection, Database
from ...core.errors import TokenAlreadySpentError
from ...core.errors import TokenAlreadySpentError, DlcAlreadyRegisteredError
from ..crud import LedgerCrud


Expand Down Expand Up @@ -91,3 +91,10 @@ async def _verify_proofs_spendable(
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()

async def _verify_dlc_registrable(
self, dlc_root: str, conn: Optional[Connection] = None,
):
async with self.db.get_connection(conn) as conn:
if await self.crud.get_registered_dlc(dlc_root, self.db, conn) is not None:
raise DlcAlreadyRegisteredError()
51 changes: 50 additions & 1 deletion cashu/mint/db/write.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List, Optional, Union
from typing import List, Optional, Union, Tuple

from loguru import logger

Expand All @@ -10,10 +10,15 @@
Proof,
ProofSpentState,
ProofState,
DiscreteLogContract,
DlcFundingProof,
DlcBadInput,
)
from ...core.db import Connection, Database
from ...core.errors import (
TransactionError,
TokenAlreadySpentError,
DlcAlreadyRegisteredError,
)
from ..crud import LedgerCrud
from ..events.events import LedgerEventManager
Expand Down Expand Up @@ -223,3 +228,47 @@ async def _unset_melt_quote_pending(

await self.events.submit(quote_copy)
return quote_copy

async def _verify_proofs_and_dlc_registrations(
self,
registrations: List[Tuple[DiscreteLogContract, DlcFundingProof]],
is_atomic: bool,
) -> Tuple[List[Tuple[DiscreteLogContract, DlcFundingProof]], List[DlcFundingProof]]:
ok: List[Tuple[DiscreteLogContract, DlcFundingProof]] = []
errors: List[DlcFundingProof]= []
logger.trace("_verify_proofs_and_dlc_registrations acquiring lock")
async with self.db.get_connection(lock_table="proofs_used") as conn:
for registration in registrations:
reg = registration[0]
logger.trace("checking whether proofs are already spent")
try:
assert reg.inputs
await self.db_read._verify_proofs_spendable(reg.inputs, conn)
await self.db_read._verify_dlc_registrable(reg.dlc_root, conn)
ok.append(registration)
except (TokenAlreadySpentError, DlcAlreadyRegisteredError) as e:
logger.trace(f"Proofs already spent for registration {reg.dlc_root}")
errors.append(DlcFundingProof(
dlc_root=reg.dlc_root,
bad_inputs=[DlcBadInput(
index=-1,
detail=e.detail
)]
))

# Do not continue if errors on atomic
if is_atomic and len(errors) > 0:
return (ok, errors)

for registration in ok:
reg = registration[0]
assert reg.inputs
for p in reg.inputs:
logger.trace(f"Invalidating proof {p.Y}")
await self.crud.invalidate_proof(
proof=p, db=self.db, conn=conn
)
logger.trace(f"Registering DLC {reg.dlc_root}")
await self.crud.store_dlc(reg, self.db, conn)
logger.trace("_verify_proofs_and_dlc_registrations lock released")
return (ok, errors)
32 changes: 24 additions & 8 deletions cashu/mint/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
PostMeltQuoteResponse,
PostMintQuoteRequest,
PostDlcRegistrationRequest,
PostDlcRegistrationResponse,
)
from ..core.settings import settings
from ..core.split import amount_split
Expand Down Expand Up @@ -1094,9 +1095,9 @@ async def _generate_promises(
signatures.append(signature)
return signatures

async def register_dlc(self, request: PostDlcRegistrationRequest):
async def register_dlc(self, request: PostDlcRegistrationRequest) -> PostDlcRegistrationResponse:
logger.trace("register called")
is_atomic = request.atomic
is_atomic = request.atomic or False
funded: List[Tuple[DiscreteLogContract, DlcFundingProof]] = []
errors: List[DlcFundingProof] = []
for registration in request.registrations:
Expand All @@ -1114,11 +1115,11 @@ async def register_dlc(self, request: PostDlcRegistrationRequest):
# We use the funding proof private key
'''
signature = sign_dlc(
registration.dlc_root,
registration.funding_amount,
registration.unit,
self.funding_proof_private_key
)
registration.dlc_root,
registration.funding_amount,
registration.unit,
self.funding_proof_private_key
)
funding_proof = DlcFundingProof(
dlc_root=registration.dlc_root,
signature=signature.hex()
Expand All @@ -1127,6 +1128,7 @@ async def register_dlc(self, request: PostDlcRegistrationRequest):
settled=False,
dlc_root=registration.dlc_root,
funding_amount=amount_provided,
inputs=registration.inputs,
unit=registration.unit,
)
funded.append((dlc, funding_proof))
Expand All @@ -1147,4 +1149,18 @@ async def register_dlc(self, request: PostDlcRegistrationRequest):
errors.append(DlcFundingProof(
dlc_root=registration.dlc_root,
bad_inputs=e.bad_inputs,
))
))
# If `atomic` register and there are errors, abort
if is_atomic and len(errors) > 0:
return PostDlcRegistrationResponse(errors=errors)
# Database dance:
funded, db_errors = await self.db_write._verify_proofs_and_dlc_registrations(funded, is_atomic)
errors += db_errors
if is_atomic and len(errors) > 0:
return PostDlcRegistrationResponse(errors=errors)

# ALL OK
return PostDlcRegistrationResponse(
funded=[f[1] for f in funded],
errors=errors if len(errors) > 0 else None,
)
1 change: 1 addition & 0 deletions cashu/mint/migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,7 @@ async def m022_add_dlc_table(db: Database):
dlc_root TEXT NOT NULL,
settled BOOL NOT NULL DEFAULT FALSE,
funding_amount {db.big_int} NOT NULL,
unit TEXT NOT NULL,
debts TEXT,
UNIQUE (dlc_root),
Expand Down

0 comments on commit a5b147b

Please sign in to comment.