From 33331023276857fe4b6f694d2dd8cb6094aeecb1 Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Tue, 7 Mar 2023 17:49:27 +0100 Subject: [PATCH] lightning: add fakewallet (#134) * lightning: add fakewallet * make format * fix mypy * make backend configurable * weird mypy --- .env.example | 3 ++ cashu/core/settings.py | 1 + cashu/lightning/__init__.py | 6 +-- cashu/lightning/fake.py | 100 ++++++++++++++++++++++++++++++++++++ cashu/lightning/lnbits.py | 1 - cashu/mint/startup.py | 17 ++++-- mypy.ini | 7 ++- 7 files changed, 127 insertions(+), 8 deletions(-) create mode 100644 cashu/lightning/fake.py diff --git a/.env.example b/.env.example index 1011499..910a718 100644 --- a/.env.example +++ b/.env.example @@ -21,6 +21,9 @@ TOR=TRUE MINT_PRIVATE_KEY=supersecretprivatekey +# Supported: LNbitsWallet, FakeWallet +MINT_LIGHTNING_BACKEND=LNbitsWallet + MINT_SERVER_HOST=127.0.0.1 MINT_SERVER_PORT=3338 diff --git a/cashu/core/settings.py b/cashu/core/settings.py index 94f8c17..523fa87 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -44,6 +44,7 @@ MINT_URL = env.str("MINT_URL", default=None) MINT_HOST = env.str("MINT_HOST", default="8333.space") MINT_PORT = env.int("MINT_PORT", default=3338) +MINT_LIGHTNING_BACKEND = env.str("MINT_LIGHTNING_BACKEND", default="FakeWallet") MINT_DATABASE = env.str("MINT_DATABASE", default="data/mint") if not MINT_URL: diff --git a/cashu/lightning/__init__.py b/cashu/lightning/__init__.py index 54af20c..2661f50 100644 --- a/cashu/lightning/__init__.py +++ b/cashu/lightning/__init__.py @@ -1,3 +1,3 @@ -# from cashu.lightning.lnbits import LNbitsWallet - -# WALLET = LNbitsWallet() +# type: ignore +from .fake import FakeWallet +from .lnbits import LNbitsWallet diff --git a/cashu/lightning/fake.py b/cashu/lightning/fake.py new file mode 100644 index 0000000..d6ec8b0 --- /dev/null +++ b/cashu/lightning/fake.py @@ -0,0 +1,100 @@ +import asyncio +import hashlib +import random +from datetime import datetime +from typing import AsyncGenerator, Dict, Optional, Set + +from cashu.core.bolt11 import Invoice, decode, encode + +from .base import ( + InvoiceResponse, + PaymentResponse, + PaymentStatus, + StatusResponse, + Wallet, +) + + +class FakeWallet(Wallet): + """https://github.com/lnbits/lnbits""" + + queue: asyncio.Queue = asyncio.Queue(0) + paid_invoices: Set[str] = set() + secret: str = "FAKEWALLET SECRET" + privkey: str = hashlib.pbkdf2_hmac( + "sha256", + secret.encode(), + ("FakeWallet").encode(), + 2048, + 32, + ).hex() + + async def status(self) -> StatusResponse: + return StatusResponse(None, 1337) + + async def create_invoice( + self, + amount: int, + memo: Optional[str] = None, + description_hash: Optional[bytes] = None, + unhashed_description: Optional[bytes] = None, + **kwargs, + ) -> InvoiceResponse: + data: Dict = { + "out": False, + "amount": amount * 1000, + "currency": "bc", + "privkey": self.privkey, + "memo": memo, + "description_hash": b"", + "description": "", + "fallback": None, + "expires": kwargs.get("expiry"), + "timestamp": datetime.now().timestamp(), + "route": None, + "tags_set": [], + } + if description_hash: + data["tags_set"] = ["h"] + data["description_hash"] = description_hash + elif unhashed_description: + data["tags_set"] = ["d"] + data["description_hash"] = hashlib.sha256(unhashed_description).digest() + else: + data["tags_set"] = ["d"] + data["memo"] = memo + data["description"] = memo + randomHash = ( + self.privkey[:6] + + hashlib.sha256(str(random.getrandbits(256)).encode()).hexdigest()[6:] + ) + data["paymenthash"] = randomHash + payment_request = encode(data) + checking_id = randomHash + + return InvoiceResponse(True, checking_id, payment_request) + + async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse: + invoice = decode(bolt11) + + if invoice.payment_hash[:6] == self.privkey[:6]: + await self.queue.put(invoice) + self.paid_invoices.add(invoice.payment_hash) + return PaymentResponse(True, invoice.payment_hash, 0) + else: + return PaymentResponse( + ok=False, error_message="Only internal invoices can be used!" + ) + + async def get_invoice_status(self, checking_id: str) -> PaymentStatus: + + paid = checking_id in self.paid_invoices + return PaymentStatus(paid or None) + + async def get_payment_status(self, _: str) -> PaymentStatus: + return PaymentStatus(None) + + async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: + while True: + value: Invoice = await self.queue.get() + yield value.payment_hash diff --git a/cashu/lightning/lnbits.py b/cashu/lightning/lnbits.py index 1dce0f8..7dbf54a 100644 --- a/cashu/lightning/lnbits.py +++ b/cashu/lightning/lnbits.py @@ -1,5 +1,4 @@ # type: ignore -from os import getenv from typing import Dict, Optional import requests diff --git a/cashu/mint/startup.py b/cashu/mint/startup.py index b136762..53f1ca7 100644 --- a/cashu/mint/startup.py +++ b/cashu/mint/startup.py @@ -1,22 +1,32 @@ # startup routine of the standalone app. These are the steps that need # to be taken by external apps importing the cashu mint. -import asyncio +import importlib from loguru import logger from cashu.core.db import Database from cashu.core.migrations import migrate_databases -from cashu.core.settings import CASHU_DIR, LIGHTNING, MINT_DATABASE, MINT_PRIVATE_KEY +from cashu.core.settings import ( + CASHU_DIR, + LIGHTNING, + MINT_DATABASE, + MINT_LIGHTNING_BACKEND, + MINT_PRIVATE_KEY, +) +from cashu.lightning.fake import FakeWallet # type: ignore from cashu.lightning.lnbits import LNbitsWallet # type: ignore from cashu.mint import migrations from cashu.mint.ledger import Ledger +wallets_module = importlib.import_module("cashu.lightning") +LIGHTNING_BACKEND = getattr(wallets_module, MINT_LIGHTNING_BACKEND)() + ledger = Ledger( db=Database("mint", MINT_DATABASE), seed=MINT_PRIVATE_KEY, derivation_path="0/0/0/0", - lightning=LNbitsWallet(), + lightning=LIGHTNING_BACKEND, ) @@ -27,6 +37,7 @@ async def start_mint_init(): await ledger.init_keysets() if LIGHTNING: + logger.info(f"Using backend: {MINT_LIGHTNING_BACKEND}") error_message, balance = await ledger.lightning.status() if error_message: logger.warning( diff --git a/mypy.ini b/mypy.ini index 41463ae..f997474 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,2 +1,7 @@ [mypy] -exclude = /cashu/nostr/ \ No newline at end of file +python_version = 3.9 +# disallow_untyped_defs = True +ignore_missing_imports = True + +[mypy-cashu.nostr.*] +ignore_errors = True \ No newline at end of file