Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into multinut
Browse files Browse the repository at this point in the history
callebtc committed May 22, 2024
2 parents c5832ef + 52fbfc4 commit 422c030
Showing 44 changed files with 1,680 additions and 674 deletions.
3 changes: 2 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -87,7 +87,8 @@ LIGHTNING_FEE_PERCENT=1.0
LIGHTNING_RESERVE_FEE_MIN=2000

# Limits

# Max mint balance in satoshis
# MINT_MAX_BALANCE=1000000
# Max peg-in amount in satoshis
# MINT_MAX_PEG_IN=100000
# Max peg-out amount in satoshis
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
@@ -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: 2 additions & 2 deletions .github/workflows/docker.yaml
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@ name: Docker Build

on:
release:
types: [published]
types: [released]

jobs:
build-and-push:
@@ -42,7 +42,7 @@ jobs:
uses: docker/build-push-action@v5
with:
context: .
push: ${{ github.event_name == 'release' }}
push: ${{ github.event_name == 'release' && github.event.action == 'released' }}
tags: ${{ secrets.DOCKER_USERNAME }}/${{ github.event.repository.name }}:${{ steps.get_tag.outputs.tag }}
platforms: linux/amd64,linux/arm64
cache-from: type=local,src=/tmp/.buildx-cache
4 changes: 2 additions & 2 deletions .github/workflows/pypi.yaml
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@ name: Pip package
on:
push:
release:
types: [published]
types: [released]

jobs:
build-and-push:
@@ -30,6 +30,6 @@ jobs:
pip install --upgrade dist/*.whl
- name: Upload to PyPI on release
if: github.event_name == 'release'
if: github.event_name == 'release' && github.event.action == 'released'
run: |
poetry publish -u __token__ -p ${{ secrets.PYPI_API_TOKEN }}
4 changes: 0 additions & 4 deletions .github/workflows/regtest.yml
Original file line number Diff line number Diff line change
@@ -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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -109,11 +109,11 @@ vim .env
To use the wallet with the [public test mint](#test-instance), you need to change the appropriate entries in the `.env` file.

#### Test instance
*Warning: this instance is just for demonstration only. The server could vanish at any moment so consider any Satoshis you deposit a donation.*
*Warning: this instance is just for demonstration purposes and development only. The satoshis are not real.*

Change the appropriate `.env` file settings to
```bash
MINT_URL=https://8333.space:3338
MINT_URL=https://testnut.cashu.space
```

# Using Cashu
@@ -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
81 changes: 44 additions & 37 deletions cashu/core/base.py
Original file line number Diff line number Diff line change
@@ -101,12 +101,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)
@@ -161,20 +161,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)

@@ -194,10 +187,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 -------
@@ -333,6 +330,19 @@ class GetInfoResponse_deprecated(BaseModel):
parameter: Optional[dict] = None


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


# ------- API: KEYS -------


@@ -399,7 +409,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
)

@@ -447,7 +457,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
)

@@ -476,7 +486,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
)

@@ -648,7 +658,6 @@ def __init__(
valid_to=None,
first_seen=None,
active=True,
use_deprecated_id=False, # BACKWARDS COMPATIBILITY < 0.15.0
):
self.valid_from = valid_from
self.valid_to = valid_to
@@ -663,19 +672,10 @@ def __init__(
else:
self.id = id

# BEGIN BACKWARDS COMPATIBILITY < 0.15.0
if use_deprecated_id:
logger.warning(
"Using deprecated keyset id derivation for backwards compatibility <"
" 0.15.0"
)
self.id = derive_keyset_id_deprecated(self.public_keys)
# END BACKWARDS COMPATIBILITY < 0.15.0

self.unit = Unit[unit]

logger.trace(f"Derived keyset id {self.id} from public keys.")
if id and id != self.id and use_deprecated_id:
if id and id != self.id:
logger.warning(
f"WARNING: Keyset id {self.id} does not match the given id {id}."
" Overwriting."
@@ -730,8 +730,6 @@ class MintKeyset:
first_seen: Optional[str] = None
version: Optional[str] = None

duplicate_keyset_id: Optional[str] = None # BACKWARDS COMPATIBILITY < 0.15.0

def __init__(
self,
*,
@@ -812,6 +810,12 @@ def generate_keys(self):
assert self.seed, "seed not set"
assert self.derivation_path, "derivation path not set"

# we compute the keyset id from the public keys only if it is not
# loaded from the database. This is to allow for backwards compatibility
# with old keysets with new id's and vice versa. This code can be removed
# if there are only new keysets in the mint (> 0.15.0)
id_in_db = self.id

if self.version_tuple < (0, 12):
# WARNING: Broken key derivation for backwards compatibility with < 0.12
self.private_keys = derive_keys_backwards_compatible_insecure_pre_0_12(
@@ -822,19 +826,22 @@ def generate_keys(self):
f"WARNING: Using weak key derivation for keyset {self.id} (backwards"
" compatibility < 0.12)"
)
self.id = derive_keyset_id_deprecated(self.public_keys) # type: ignore
# load from db or derive
self.id = id_in_db or derive_keyset_id_deprecated(self.public_keys) # type: ignore
elif self.version_tuple < (0, 15):
self.private_keys = derive_keys_sha256(self.seed, self.derivation_path)
logger.trace(
f"WARNING: Using non-bip32 derivation for keyset {self.id} (backwards"
" compatibility < 0.15)"
)
self.public_keys = derive_pubkeys(self.private_keys) # type: ignore
self.id = derive_keyset_id_deprecated(self.public_keys) # type: ignore
# load from db or derive
self.id = id_in_db or derive_keyset_id_deprecated(self.public_keys) # type: ignore
else:
self.private_keys = derive_keys(self.seed, self.derivation_path)
self.public_keys = derive_pubkeys(self.private_keys) # type: ignore
self.id = derive_keyset_id(self.public_keys) # type: ignore
# load from db or derive
self.id = id_in_db or derive_keyset_id(self.public_keys) # type: ignore


# ------- TOKEN -------
4 changes: 3 additions & 1 deletion cashu/core/errors.py
Original file line number Diff line number Diff line change
@@ -63,7 +63,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)


12 changes: 2 additions & 10 deletions cashu/core/settings.py
Original file line number Diff line number Diff line change
@@ -8,7 +8,7 @@

env = Env()

VERSION = "0.15.1"
VERSION = "0.15.3"


def find_env_file():
@@ -58,14 +58,6 @@ class MintSettings(CashuSettings):

mint_database: str = Field(default="data/mint")
mint_test_database: str = Field(default="test_data/test_mint")
mint_duplicate_keysets: bool = Field(
default=True,
title="Duplicate keysets",
description=(
"Whether to duplicate keysets for backwards compatibility before v1 API"
" (Nutshell 0.15.0)."
),
)


class MintBackends(MintSettings):
@@ -125,6 +117,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)


@@ -133,7 +126,6 @@ class MintInformation(CashuSettings):
mint_info_description: str = Field(default=None)
mint_info_description_long: str = Field(default=None)
mint_info_contact: List[List[str]] = Field(default=[["", ""]])
mint_info_nuts: List[str] = Field(default=["NUT-07", "NUT-08", "NUT-09"])
mint_info_motd: str = Field(default=None)


14 changes: 9 additions & 5 deletions cashu/lightning/corelightningrest.py
Original file line number Diff line number Diff line change
@@ -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(
2 changes: 1 addition & 1 deletion cashu/lightning/fake.py
Original file line number Diff line number Diff line change
@@ -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:
12 changes: 11 additions & 1 deletion cashu/lightning/lnbits.py
Original file line number Diff line number Diff line change
@@ -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"],
)
Loading

0 comments on commit 422c030

Please sign in to comment.