diff --git a/README.md b/README.md index b6a44d14..d0b708cc 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/cashu/mint/crud.py b/cashu/mint/crud.py index cf376e28..0ef4af9d 100644 --- a/cashu/mint/crud.py +++ b/cashu/mint/crud.py @@ -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, @@ -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, @@ -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, @@ -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 [] diff --git a/cashu/mint/db/read.py b/cashu/mint/db/read.py index ac35ec80..681ebe21 100644 --- a/cashu/mint/db/read.py +++ b/cashu/mint/db/read.py @@ -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( diff --git a/cashu/mint/protocols.py b/cashu/mint/protocols.py index ff576d96..0e84ea3a 100644 --- a/cashu/mint/protocols.py +++ b/cashu/mint/protocols.py @@ -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 @@ -18,6 +20,8 @@ class SupportsBackends(Protocol): class SupportsDb(Protocol): db: Database + db_read: DbReadHelper + db_write: DbWriteHelper crud: LedgerCrud diff --git a/cashu/mint/verification.py b/cashu/mint/verification.py index f0ce828c..ae2c5725 100644 --- a/cashu/mint/verification.py +++ b/cashu/mint/verification.py @@ -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 @@ -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( @@ -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]): @@ -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)."""