Skip to content

Commit

Permalink
Merge branch 'main' into add-fees
Browse files Browse the repository at this point in the history
  • Loading branch information
callebtc committed Apr 10, 2024
2 parents 7aa6099 + 19de10b commit 72ff760
Show file tree
Hide file tree
Showing 39 changed files with 1,481 additions and 373 deletions.
2 changes: 1 addition & 1 deletion .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1 +1 @@
custom: https://legend.lnbits.com/tipjar/794
custom: https://docs.cashu.space/contribute
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ jobs:
poetry-version: ["1.7.1"]
backend-wallet-class:
["LndRestWallet", "CoreLightningRestWallet", "LNbitsWallet"]
# mint-database: ["./test_data/test_mint", "postgres://cashu:cashu@localhost:5432/cashu"]
mint-database: ["./test_data/test_mint"]
with:
python-version: ${{ matrix.python-version }}
backend-wallet-class: ${{ matrix.backend-wallet-class }}
mint-database: "./test_data/test_mint"
mint-database: ${{ matrix.mint-database }}
4 changes: 0 additions & 4 deletions .github/workflows/regtest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,6 @@ jobs:
chmod -R 777 .
bash ./start.sh
- name: Create fake admin
if: ${{ inputs.backend-wallet-class == 'LNbitsWallet' }}
run: docker exec cashu-lnbits-1 poetry run python tools/create_fake_admin.py

- name: Run Tests
env:
WALLET_NAME: test_wallet
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ This command runs the mint on your local computer. Skip this step if you want to
## Docker

```
docker run -d -p 3338:3338 --name nutshell -e MINT_BACKEND_BOLT11_SAT=FakeWallet -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.15.2 poetry run mint
docker run -d -p 3338:3338 --name nutshell -e MINT_BACKEND_BOLT11_SAT=FakeWallet -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.15.3 poetry run mint
```

## From this repository
Expand Down
46 changes: 28 additions & 18 deletions cashu/core/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,12 @@ class Proof(BaseModel):
time_created: Union[None, str] = ""
time_reserved: Union[None, str] = ""
derivation_path: Union[None, str] = "" # derivation path of the proof
mint_id: Union[None, str] = (
None # holds the id of the mint operation that created this proof
)
melt_id: Union[None, str] = (
None # holds the id of the melt operation that destroyed this proof
)
mint_id: Union[
None, str
] = None # holds the id of the mint operation that created this proof
melt_id: Union[
None, str
] = None # holds the id of the melt operation that destroyed this proof

def __init__(self, **data):
super().__init__(**data)
Expand Down Expand Up @@ -175,20 +175,13 @@ def htlcpreimage(self) -> Union[str, None]:
return HTLCWitness.from_witness(self.witness).preimage


class Proofs(BaseModel):
# NOTE: not used in Pydantic validation
__root__: List[Proof]


class BlindedMessage(BaseModel):
"""
Blinded message or blinded secret or "output" which is to be signed by the mint
"""

amount: int
id: Optional[
str
] # DEPRECATION: Only Optional for backwards compatibility with old clients < 0.15 for deprecated API route.
id: str
B_: str # Hex-encoded blinded message
witness: Union[str, None] = None # witnesses (used for P2PK with SIG_ALL)

Expand All @@ -198,6 +191,19 @@ def p2pksigs(self) -> List[str]:
return P2PKWitness.from_witness(self.witness).signatures


class BlindedMessage_Deprecated(BaseModel):
# Same as BlindedMessage, but without the id field
amount: int
B_: str # Hex-encoded blinded message
id: Optional[str] = None
witness: Union[str, None] = None # witnesses (used for P2PK with SIG_ALL)

@property
def p2pksigs(self) -> List[str]:
assert self.witness, "Witness missing in output"
return P2PKWitness.from_witness(self.witness).signatures


class BlindedSignature(BaseModel):
"""
Blinded signature or "promise" which is the signature on a `BlindedMessage`
Expand All @@ -208,10 +214,14 @@ class BlindedSignature(BaseModel):
C_: str # Hex-encoded signature
dleq: Optional[DLEQ] = None # DLEQ proof


class BlindedMessages(BaseModel):
# NOTE: not used in Pydantic validation
__root__: List[BlindedMessage] = []
@classmethod
def from_row(cls, row: Row):
return cls(
id=row["id"],
amount=row["amount"],
C_=row["c_"],
dleq=DLEQ(e=row["dleq_e"], s=row["dleq_s"]),
)


# ------- LIGHTNING INVOICE -------
Expand Down
4 changes: 3 additions & 1 deletion cashu/core/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ class KeysetNotFoundError(KeysetError):
detail = "keyset not found"
code = 12001

def __init__(self):
def __init__(self, keyset_id: Optional[str] = None):
if keyset_id:
self.detail = f"{self.detail}: {keyset_id}"
super().__init__(self.detail, code=self.code)


Expand Down
16 changes: 11 additions & 5 deletions cashu/core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@

from pydantic import BaseModel, Field

from .base import BlindedMessage, BlindedSignature, Proof, ProofState
from .base import (
BlindedMessage,
BlindedMessage_Deprecated,
BlindedSignature,
Proof,
ProofState,
)
from .settings import settings

# ------- API -------
Expand Down Expand Up @@ -57,7 +63,7 @@ class KeysetsResponseKeyset(BaseModel):
id: str
unit: str
active: bool
input_fee_ppm: Optional[int]
input_fee_ppm: Optional[int] = None


class KeysetsResponse(BaseModel):
Expand Down Expand Up @@ -107,7 +113,7 @@ class GetMintResponse_deprecated(BaseModel):


class PostMintRequest_deprecated(BaseModel):
outputs: List[BlindedMessage] = Field(
outputs: List[BlindedMessage_Deprecated] = Field(
..., max_items=settings.mint_max_request_length
)

Expand Down Expand Up @@ -154,7 +160,7 @@ class PostMeltResponse(BaseModel):
class PostMeltRequest_deprecated(BaseModel):
proofs: List[Proof] = Field(..., max_items=settings.mint_max_request_length)
pr: str = Field(..., max_length=settings.mint_max_request_length)
outputs: Union[List[BlindedMessage], None] = Field(
outputs: Union[List[BlindedMessage_Deprecated], None] = Field(
None, max_items=settings.mint_max_request_length
)

Expand Down Expand Up @@ -183,7 +189,7 @@ class PostSplitResponse(BaseModel):
class PostSplitRequest_Deprecated(BaseModel):
proofs: List[Proof] = Field(..., max_items=settings.mint_max_request_length)
amount: Optional[int] = None
outputs: List[BlindedMessage] = Field(
outputs: List[BlindedMessage_Deprecated] = Field(
..., max_items=settings.mint_max_request_length
)

Expand Down
5 changes: 3 additions & 2 deletions cashu/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

env = Env()

VERSION = "0.15.1"
VERSION = "0.15.3"


def find_env_file():
Expand Down Expand Up @@ -62,7 +62,7 @@ class MintSettings(CashuSettings):
default={"sat": {"fee": 1, "batch": 10}}
)
mint_max_secret_length: int = Field(default=512)
mint_duplicate_keysets: bool = Field(
mint_duplicate_old_keysets: bool = Field(
default=True,
title="Duplicate keysets",
description=(
Expand Down Expand Up @@ -129,6 +129,7 @@ class FakeWalletSettings(MintSettings):
fakewallet_brr: bool = Field(default=True)
fakewallet_delay_payment: bool = Field(default=False)
fakewallet_stochastic_invoice: bool = Field(default=False)
fakewallet_payment_state: Optional[bool] = Field(default=None)
mint_cache_secrets: bool = Field(default=True)


Expand Down
14 changes: 9 additions & 5 deletions cashu/lightning/corelightningrest.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,17 +247,21 @@ async def get_payment_status(self, checking_id: str) -> PaymentStatus:
r.raise_for_status()
data = r.json()

if r.is_error or "error" in data or not data.get("pays"):
raise Exception("error in corelightning-rest response")
if not data.get("pays"):
# payment not found
logger.error(f"payment not found: {data.get('pays')}")
raise Exception("payment not found")

if r.is_error or "error" in data:
message = data.get("error") or data
raise Exception(f"error in corelightning-rest response: {message}")

pay = data["pays"][0]

fee_msat, preimage = None, None
if self.statuses[pay["status"]]:
# cut off "msat" and convert to int
fee_msat = -int(pay["amount_sent_msat"][:-4]) - int(
pay["amount_msat"][:-4]
)
fee_msat = -int(pay["amount_sent_msat"]) - int(pay["amount_msat"])
preimage = pay["preimage"]

return PaymentStatus(
Expand Down
2 changes: 1 addition & 1 deletion cashu/lightning/fake.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
return PaymentStatus(paid=paid or None)

async def get_payment_status(self, _: str) -> PaymentStatus:
return PaymentStatus(paid=None)
return PaymentStatus(paid=settings.fakewallet_payment_state)

async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
while True:
Expand Down
12 changes: 11 additions & 1 deletion cashu/lightning/lnbits.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,18 @@ async def get_payment_status(self, checking_id: str) -> PaymentStatus:
if "paid" not in data and "details" not in data:
return PaymentStatus(paid=None)

paid_value = None
if data["paid"]:
paid_value = True
elif not data["paid"] and data["details"]["pending"]:
paid_value = None
elif not data["paid"] and not data["details"]["pending"]:
paid_value = False
else:
raise ValueError(f"unexpected value for paid: {data['paid']}")

return PaymentStatus(
paid=data["paid"],
paid=paid_value,
fee=Amount(unit=Unit.msat, amount=abs(data["details"]["fee"])),
preimage=data["preimage"],
)
Expand Down
8 changes: 7 additions & 1 deletion cashu/lightning/lndrest.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,14 +217,20 @@ async def get_payment_status(self, checking_id: str) -> PaymentStatus:
async for json_line in r.aiter_lines():
try:
line = json.loads(json_line)

# check for errors
if line.get("error"):
logger.error(
message = (
line["error"]["message"]
if "message" in line["error"]
else line["error"]
)
logger.error(f"LND get_payment_status error: {message}")
return PaymentStatus(paid=None)

payment = line.get("result")

# payment exists
if payment is not None and payment.get("status"):
return PaymentStatus(
paid=statuses[payment["status"]],
Expand Down
6 changes: 3 additions & 3 deletions cashu/mint/conditions.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,9 +255,9 @@ def _verify_output_p2pk_spending_conditions(
# check if all secrets are P2PK
# NOTE: This is redundant, because P2PKSecret.from_secret() already checks for the kind
# Leaving it in for explicitness
if not all([
SecretKind(secret.kind) == SecretKind.P2PK for secret in p2pk_secrets
]):
if not all(
[SecretKind(secret.kind) == SecretKind.P2PK for secret in p2pk_secrets]
):
# not all secrets are P2PK
return True

Expand Down
Loading

0 comments on commit 72ff760

Please sign in to comment.