Skip to content

Commit

Permalink
Merge pull request #3 from conduition/dlc2
Browse files Browse the repository at this point in the history
refactor response types/structure to match spec
  • Loading branch information
lollerfirst authored Aug 26, 2024
2 parents 18c5a34 + be81d78 commit 9c5bf34
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 43 deletions.
29 changes: 24 additions & 5 deletions cashu/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
"""
Expand All @@ -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
Expand Down
13 changes: 8 additions & 5 deletions cashu/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@
BlindedMessage_Deprecated,
BlindedSignature,
DiscreetLogContract,
DlcFundingProof,
DlcFundingAck,
DlcFundingError,
DlcPayout,
DlcPayoutForm,
DlcSettlement,
DlcSettlementAck,
DlcSettlementError,
MeltQuote,
MintQuote,
Proof,
Expand Down Expand Up @@ -337,17 +340,17 @@ 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 -------

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):
Expand Down
35 changes: 19 additions & 16 deletions cashu/mint/db/write.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@
from ...core.base import (
DiscreetLogContract,
DlcBadInput,
DlcFundingProof,
DlcFundingAck,
DlcFundingError,
DlcSettlement,
DlcSettlementAck,
DlcSettlementError,
MeltQuote,
MeltQuoteState,
MintQuote,
Expand Down Expand Up @@ -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 [], []
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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:
Expand All @@ -306,23 +309,23 @@ 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"
))
continue

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)}"
))
Expand Down
24 changes: 15 additions & 9 deletions cashu/mint/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
BlindedSignature,
DiscreetLogContract,
DlcBadInput,
DlcFundingAck,
DlcFundingError,
DlcFundingProof,
DlcSettlement,
DlcSettlementError,
MeltQuote,
MeltQuoteState,
Method,
Expand Down Expand Up @@ -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}")
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
))
Expand All @@ -1207,15 +1213,15 @@ 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
assert settlement.outcome and settlement.merkle_proof, "outcome or merkle proof not provided"
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)
))
Expand Down
2 changes: 1 addition & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,6 @@ def mint():

server = UvicornServer(config=config)
server.start()
time.sleep(1)
time.sleep(5)
yield server
server.stop()
14 changes: 7 additions & 7 deletions tests/test_dlc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"


Expand Down Expand Up @@ -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]),
Expand Down Expand Up @@ -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]),
Expand All @@ -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."
assert len(response.settled) > 0, "Response contains zero settlements."

0 comments on commit 9c5bf34

Please sign in to comment.