diff --git a/cashu/core/base.py b/cashu/core/base.py index a3202610..b373da77 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -1242,9 +1242,16 @@ class DlcFundingProof(BaseModel): A dlc merkle root with its signature or a dlc merkle root with bad inputs. """ + keyset: str + signature: str + +class DlcFundingAck(BaseModel): dlc_root: str - signature: Optional[str] = None - bad_inputs: Optional[List[DlcBadInput]] = None # Used to specify potential errors + funding_proof: DlcFundingProof + +class DlcFundingError(BaseModel): + dlc_root: str + bad_inputs: Optional[List[DlcBadInput]] # Used to specify potential errors class DlcOutcome(BaseModel): """ @@ -1259,9 +1266,21 @@ class DlcSettlement(BaseModel): Data used to settle an outcome of a DLC """ dlc_root: str - outcome: Optional[DlcOutcome] = None - merkle_proof: Optional[List[str]] = None - details: Optional[str] = None + outcome: DlcOutcome + merkle_proof: List[str] + +class DlcSettlementAck(BaseModel): + """ + Used by the mint to indicate the success of a DLC's funding, settlement, etc. + """ + dlc_root: str + +class DlcSettlementError(BaseModel): + """ + Indicates to the client that a DLC operation (funding, settlement, etc) failed. + """ + dlc_root: str + details: str class DlcPayoutForm(BaseModel): dlc_root: str diff --git a/cashu/core/models.py b/cashu/core/models.py index 8acdb71b..3c4cae6e 100644 --- a/cashu/core/models.py +++ b/cashu/core/models.py @@ -7,10 +7,13 @@ BlindedMessage_Deprecated, BlindedSignature, DiscreetLogContract, - DlcFundingProof, + DlcFundingAck, + DlcFundingError, DlcPayout, DlcPayoutForm, DlcSettlement, + DlcSettlementAck, + DlcSettlementError, MeltQuote, MintQuote, Proof, @@ -337,8 +340,8 @@ class PostDlcRegistrationRequest(BaseModel): registrations: List[DiscreetLogContract] class PostDlcRegistrationResponse(BaseModel): - funded: List[DlcFundingProof] = [] - errors: Optional[List[DlcFundingProof]] = None + funded: List[DlcFundingAck] = [] + errors: Optional[List[DlcFundingError]] = None # ------- API: DLC SETTLEMENT ------- @@ -346,8 +349,8 @@ class PostDlcSettleRequest(BaseModel): settlements: List[DlcSettlement] class PostDlcSettleResponse(BaseModel): - settled: List[DlcSettlement] = [] - errors: Optional[List[DlcSettlement]] = None + settled: List[DlcSettlementAck] = [] + errors: Optional[List[DlcSettlementError]] = None # ------- API: DLC PAYOUT ------- class PostDlcPayoutRequest(BaseModel): diff --git a/cashu/mint/db/write.py b/cashu/mint/db/write.py index 2bd6ffd4..347b13c0 100644 --- a/cashu/mint/db/write.py +++ b/cashu/mint/db/write.py @@ -5,8 +5,11 @@ from ...core.base import ( DiscreetLogContract, DlcBadInput, - DlcFundingProof, + DlcFundingAck, + DlcFundingError, DlcSettlement, + DlcSettlementAck, + DlcSettlementError, MeltQuote, MeltQuoteState, MintQuote, @@ -232,20 +235,20 @@ async def _unset_melt_quote_pending( async def _verify_proofs_and_dlc_registrations( self, - registrations: List[Tuple[DiscreetLogContract, DlcFundingProof]], - ) -> Tuple[List[Tuple[DiscreetLogContract, DlcFundingProof]], List[DlcFundingProof]]: + registrations: List[Tuple[DiscreetLogContract, DlcFundingAck]], + ) -> Tuple[List[Tuple[DiscreetLogContract, DlcFundingAck]], List[DlcFundingError]]: """ Method to check if proofs are already spent or registrations already registered. If they are not, we set them as spent and registered respectively Args: - registrations (List[Tuple[DiscreetLogContract, DlcFundingProof]]): List of registrations. + registrations (List[Tuple[DiscreetLogContract, DlcFundingAck]]): List of registrations. Returns: - List[Tuple[DiscreetLogContract, DlcFundingProof]]: a list of registered DLCs - List[DlcFundingProof]: a list of errors + List[Tuple[DiscreetLogContract, DlcFundingAck]]: a list of registered DLCs + List[DlcFundingError]: a list of errors """ - checked: List[Tuple[DiscreetLogContract, DlcFundingProof]] = [] - registered: List[Tuple[DiscreetLogContract, DlcFundingProof]] = [] - errors: List[DlcFundingProof]= [] + checked: List[Tuple[DiscreetLogContract, DlcFundingAck]] = [] + registered: List[Tuple[DiscreetLogContract, DlcFundingAck]] = [] + errors: List[DlcFundingError]= [] if len(registrations) == 0: logger.trace("Received 0 registrations") return [], [] @@ -261,7 +264,7 @@ async def _verify_proofs_and_dlc_registrations( checked.append(registration) except (TokenAlreadySpentError, DlcAlreadyRegisteredError) as e: logger.trace(f"Proofs already spent for registration {reg.dlc_root}") - errors.append(DlcFundingProof( + errors.append(DlcFundingError( dlc_root=reg.dlc_root, bad_inputs=[DlcBadInput( index=-1, @@ -284,7 +287,7 @@ async def _verify_proofs_and_dlc_registrations( registered.append(registration) except Exception as e: logger.trace(f"Failed to register {reg.dlc_root}: {str(e)}") - errors.append(DlcFundingProof( + errors.append(DlcFundingError( dlc_root=reg.dlc_root, bad_inputs=[DlcBadInput( index=-1, @@ -297,7 +300,7 @@ async def _verify_proofs_and_dlc_registrations( async def _settle_dlc( self, settlements: List[DlcSettlement] - ) -> Tuple[List[DlcSettlement], List[DlcSettlement]]: + ) -> Tuple[List[DlcSettlementAck], List[DlcSettlementError]]: settled = [] errors = [] async with self.db.get_connection(lock_table="dlc") as conn: @@ -306,13 +309,13 @@ async def _settle_dlc( # We verify the dlc_root is in the DB dlc = await self.crud.get_registered_dlc(settlement.dlc_root, self.db, conn) if dlc is None: - errors.append(DlcSettlement( + errors.append(DlcSettlementError( dlc_root=settlement.dlc_root, details="no DLC with this root hash" )) continue if dlc.settled is True: - errors.append(DlcSettlement( + errors.append(DlcSettlementError( dlc_root=settlement.dlc_root, details="DLC already settled" )) @@ -320,9 +323,9 @@ async def _settle_dlc( assert settlement.outcome await self.crud.set_dlc_settled_and_debts(settlement.dlc_root, settlement.outcome.P, self.db, conn) - settled.append(settlement) + settled.append(DlcSettlementAck(dlc_root=settlement.dlc_root)) except Exception as e: - errors.append(DlcSettlement( + errors.append(DlcSettlementError( dlc_root=settlement.dlc_root, details=f"error with the DB: {str(e)}" )) diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 0e5278c3..d3926370 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -12,8 +12,11 @@ BlindedSignature, DiscreetLogContract, DlcBadInput, + DlcFundingAck, + DlcFundingError, DlcFundingProof, DlcSettlement, + DlcSettlementError, MeltQuote, MeltQuoteState, Method, @@ -1132,8 +1135,8 @@ async def register_dlc(self, request: PostDlcRegistrationRequest) -> PostDlcRegi PostDlcRegistrationResponse: Indicating the funded and registered DLCs as well as the errors. """ logger.trace("register called") - funded: List[Tuple[DiscreetLogContract, DlcFundingProof]] = [] - errors: List[DlcFundingProof] = [] + funded: List[Tuple[DiscreetLogContract, DlcFundingAck]] = [] + errors: List[DlcFundingError] = [] for registration in request.registrations: try: logger.trace(f"processing registration {registration.dlc_root}") @@ -1159,9 +1162,12 @@ async def register_dlc(self, request: PostDlcRegistrationRequest) -> PostDlcRegi registration.funding_amount, funding_privkey, ) - funding_proof = DlcFundingProof( + funding_ack = DlcFundingAck( dlc_root=registration.dlc_root, - signature=signature.hex(), + funding_proof=DlcFundingProof( + keyset=active_keyset_for_unit.id, + signature=signature.hex(), + ), ) dlc = DiscreetLogContract( settled=False, @@ -1170,12 +1176,12 @@ async def register_dlc(self, request: PostDlcRegistrationRequest) -> PostDlcRegi inputs=registration.inputs, unit=registration.unit, ) - funded.append((dlc, funding_proof)) + funded.append((dlc, funding_ack)) except (TransactionError, DlcVerificationFail) as e: logger.error(f"registration {registration.dlc_root} failed") # Generic Error if isinstance(e, TransactionError): - errors.append(DlcFundingProof( + errors.append(DlcFundingError( dlc_root=registration.dlc_root, bad_inputs=[DlcBadInput( index=-1, @@ -1184,7 +1190,7 @@ async def register_dlc(self, request: PostDlcRegistrationRequest) -> PostDlcRegi )) # DLC verification fail else: - errors.append(DlcFundingProof( + errors.append(DlcFundingError( dlc_root=registration.dlc_root, bad_inputs=e.bad_inputs, )) @@ -1207,7 +1213,7 @@ async def settle_dlc(self, request: PostDlcSettleRequest) -> PostDlcSettleRespon """ logger.trace("settle called") verified: List[DlcSettlement] = [] - errors: List[DlcSettlement] = [] + errors: List[DlcSettlementError] = [] for settlement in request.settlements: try: # Verify inclusion of payout structure and associated attestation in the DLC @@ -1215,7 +1221,7 @@ async def settle_dlc(self, request: PostDlcSettleRequest) -> PostDlcSettleRespon await self._verify_dlc_inclusion(settlement.dlc_root, settlement.outcome, settlement.merkle_proof) verified.append(settlement) except (DlcSettlementFail, AssertionError) as e: - errors.append(DlcSettlement( + errors.append(DlcSettlementError( dlc_root=settlement.dlc_root, details=e.detail if isinstance(e, DlcSettlementFail) else str(e) )) diff --git a/tests/conftest.py b/tests/conftest.py index 76d39d30..f985a287 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -139,6 +139,6 @@ def mint(): server = UvicornServer(config=config) server.start() - time.sleep(1) + time.sleep(5) yield server server.stop() diff --git a/tests/test_dlc.py b/tests/test_dlc.py index 0a7d3321..a07b894f 100644 --- a/tests/test_dlc.py +++ b/tests/test_dlc.py @@ -310,8 +310,8 @@ async def test_registration_vanilla_proofs(wallet: Wallet, ledger: Ledger): response = await ledger.register_dlc(request) assert len(response.funded) == 1, "Funding proofs len != 1" - funding_proof = response.funded[0] - assert verify_dlc_signature(dlc_root, 64, bytes.fromhex(funding_proof.signature), pubkey),\ + ack = response.funded[0] + assert verify_dlc_signature(dlc_root, 64, bytes.fromhex(ack.funding_proof.signature), pubkey),\ "Could not verify funding proof" @pytest.mark.asyncio @@ -345,8 +345,8 @@ async def test_registration_dlc_locked_proofs(wallet: Wallet, ledger: Ledger): assert response.errors is None, f"Funding proofs error: {response.errors[0].bad_inputs}" assert len(response.funded) == 1, "Funding proofs len != 1" - funding_proof = response.funded[0] - assert verify_dlc_signature(dlc_root, 64, bytes.fromhex(funding_proof.signature), pubkey), \ + ack = response.funded[0] + assert verify_dlc_signature(dlc_root, 64, bytes.fromhex(ack.funding_proof.signature), pubkey), \ "Could not verify funding proof" @@ -508,7 +508,7 @@ async def test_settle_dlc(wallet: Wallet, ledger: Ledger): request = PostDlcRegistrationRequest(registrations=[dlc]) response = await ledger.register_dlc(request) - assert response.errors is None, f"Funding proofs error: {response.errors[0].bad_inputs}" + assert response.errors is None, f"Funding proofs error: {response.errors[0].bad_inputs}" outcome = DlcOutcome( P=json.dumps(payouts[1]), @@ -558,7 +558,7 @@ async def test_settle_dlc_timeout(wallet: Wallet, ledger: Ledger): request = PostDlcRegistrationRequest(registrations=[dlc]) response = await ledger.register_dlc(request) - assert response.errors is None, f"Funding proofs error: {response.errors[0].bad_inputs}" + assert response.errors is None, f"Funding proofs error: {response.errors[0].bad_inputs}" outcome = DlcOutcome( P=json.dumps(payouts[2]), @@ -574,4 +574,4 @@ async def test_settle_dlc_timeout(wallet: Wallet, ledger: Ledger): response = await ledger.settle_dlc(request) assert response.errors is None, f"Response contains errors: {response.errors}" - assert len(response.settled) > 0, "Response contains zero settlements." \ No newline at end of file + assert len(response.settled) > 0, "Response contains zero settlements."