Skip to content

Commit

Permalink
tests almost passing
Browse files Browse the repository at this point in the history
  • Loading branch information
callebtc committed Apr 5, 2024
1 parent 0022287 commit 41a85df
Show file tree
Hide file tree
Showing 10 changed files with 128 additions and 80 deletions.
5 changes: 5 additions & 0 deletions cashu/core/json_rpc/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ class JSONRPCUnubscribeParams(BaseModel):
subId: str


class JSONRPCNotficationParams(BaseModel):
subId: str
payload: dict


class JSONRRPCSubscribeResponse(BaseModel):
status: JSONRPCStatus
subId: str
12 changes: 3 additions & 9 deletions cashu/lightning/blink.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
# type: ignore
import asyncio
import json
import math
from typing import Dict, Optional, Union
from typing import AsyncGenerator, Dict, Optional, Union

import bolt11
import httpx
Expand Down Expand Up @@ -450,10 +449,5 @@ async def get_payment_quote(self, bolt11: str) -> PaymentQuoteResponse:
amount=amount.to(self.unit, round="up"),
)


async def main():
pass


if __name__ == "__main__":
asyncio.run(main())
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
raise NotImplementedError("paid_invoices_stream not implemented")
44 changes: 29 additions & 15 deletions cashu/lightning/fake.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,21 @@ async def status(self) -> StatusResponse:
return StatusResponse(error_message=None, balance=1337)

async def mark_invoice_paid(self, invoice: Bolt11) -> None:
await asyncio.sleep(1)
await asyncio.sleep(2)
self.paid_invoices_incoming.append(invoice)
await self.paid_invoices_queue.put(invoice)

def create_dummy_bolt11(self, payment_hash: str) -> Bolt11:
tags = Tags()
tags.add(TagChar.payment_hash, payment_hash)
tags.add(TagChar.payment_secret, urandom(32).hex())
return Bolt11(
currency="bc",
amount_msat=MilliSatoshi(1337),
date=int(datetime.now().timestamp()),
tags=tags,
)

async def create_invoice(
self,
amount: Amount,
Expand Down Expand Up @@ -119,7 +130,8 @@ async def create_invoice(

payment_request = encode(bolt11, self.privkey)

asyncio.create_task(self.mark_invoice_paid(bolt11))
if settings.fakewallet_brr:
asyncio.create_task(self.mark_invoice_paid(bolt11))

return InvoiceResponse(
ok=True, checking_id=payment_hash, payment_request=payment_request
Expand Down Expand Up @@ -149,25 +161,27 @@ async def pay_invoice(self, quote: MeltQuote, fee_limit: int) -> PaymentResponse
)

async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
is_created_invoice = any(
[checking_id == i.payment_hash for i in self.created_invoices]
)
if not is_created_invoice:
return PaymentStatus(paid=None)
invoice = next(
i for i in self.created_invoices if i.payment_hash == checking_id
)
# is_created_invoice = any(
# [checking_id == i.payment_hash for i in self.created_invoices]
# )
# if not is_created_invoice:
# return PaymentStatus(paid=None)
# invoice = next(
# i for i in self.created_invoices if i.payment_hash == checking_id
# )
paid = False
if is_created_invoice or (
settings.fakewallet_brr
or (settings.fakewallet_stochastic_invoice and random.random() > 0.7)
if settings.fakewallet_brr or (
settings.fakewallet_stochastic_invoice and random.random() > 0.7
):
paid = True

# invoice is paid but not in paid_invoices_incoming yet
# so we add it to the paid_invoices_queue
if paid and invoice not in self.paid_invoices_incoming:
await self.paid_invoices_queue.put(invoice)
# if paid and invoice not in self.paid_invoices_incoming:
if paid:
await self.paid_invoices_queue.put(
self.create_dummy_bolt11(payment_hash=checking_id)
)
return PaymentStatus(paid=paid)

async def get_payment_status(self, _: str) -> PaymentStatus:
Expand Down
5 changes: 4 additions & 1 deletion cashu/lightning/lnbits.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# type: ignore
from typing import Optional
from typing import AsyncGenerator, Optional

import httpx
from bolt11 import (
Expand Down Expand Up @@ -179,3 +179,6 @@ async def get_payment_quote(self, bolt11: str) -> PaymentQuoteResponse:
fee=fees.to(self.unit, round="up"),
amount=amount.to(self.unit, round="up"),
)

async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
raise NotImplementedError("paid_invoices_stream not implemented")
5 changes: 4 additions & 1 deletion cashu/lightning/strike.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# type: ignore
import secrets
from typing import Dict, Optional
from typing import AsyncGenerator, Dict, Optional

import httpx

Expand Down Expand Up @@ -195,3 +195,6 @@ async def get_payment_status(self, checking_id: str) -> PaymentStatus:
fee_msat=data["details"]["fee"],
preimage=data["preimage"],
)

async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
raise NotImplementedError("paid_invoices_stream not implemented")
8 changes: 3 additions & 5 deletions cashu/mint/events/client_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
JSONRPCErrorCode,
JSONRPCErrorResponse,
JSONRPCMethods,
JSONRPCNotficationParams,
JSONRPCNotification,
JSONRPCRequest,
JSONRPCResponse,
Expand Down Expand Up @@ -108,12 +109,9 @@ async def _handle_request(self, data: JSONRPCRequest) -> JSONRPCResponse:

async def _send_obj(self, data: dict, subId: str):
logger.info(f"Sending object: {data}")
method = JSONRPCMethods.SUBSCRIBE.value
data.update({"subId": subId})
params = data
resp = JSONRPCNotification(
method=method,
params=params,
method=JSONRPCMethods.SUBSCRIBE.value,
params=JSONRPCNotficationParams(subId=subId, payload=data).dict(),
)
await self._send_msg(resp)

Expand Down
103 changes: 69 additions & 34 deletions cashu/wallet/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
from operator import itemgetter
from os import listdir
from os.path import isdir, join
from typing import Optional
from typing import Optional, Union

import click
from click import Context
from loguru import logger

from ...core.base import Invoice, TokenV3, Unit
from ...core.base import Invoice, MintQuote, TokenV3, Unit
from ...core.helpers import sum_proofs
from ...core.json_rpc.base import JSONRPCNotficationParams
from ...core.logging import configure_logger
from ...core.settings import settings
from ...nostr.client.client import NostrClient
Expand Down Expand Up @@ -44,6 +45,7 @@
send,
)
from ..nostr import receive_nostr, send_nostr
from ..subscriptions import SubscriptionManager


class NaturalOrderGroup(click.Group):
Expand Down Expand Up @@ -261,22 +263,49 @@ async def invoice(ctx: Context, amount: float, id: str, split: int, no_check: bo
)

paid = False

def mint_invoice_callback(msg):
nonlocal ctx, wallet, amount, optional_split, paid
print("Received callback for invoice.")
asyncio.run(wallet.mint(int(amount), split=optional_split, id=invoice.id))
print(" Invoice callback paid.")
print("")
asyncio.run(print_balance(ctx))
os._exit(0)
invoice_nonlocal: Union[None, Invoice] = None
subscription_nonlocal: Union[None, SubscriptionManager] = None

def mint_invoice_callback(msg: JSONRPCNotficationParams):
nonlocal \
ctx, \
wallet, \
amount, \
optional_split, \
paid, \
invoice_nonlocal, \
subscription_nonlocal
logger.trace(f"Received callback: {msg}")
if paid:
return
try:
quote = MintQuote.parse_obj(msg.payload)
except Exception:
return
logger.debug(f"Received callback for quote: {quote}")
if (
quote.paid
and not quote.issued
and quote.request == invoice.bolt11
and msg.subId in subscription.callback_map.keys()
):
try:
asyncio.run(
wallet.mint(int(amount), split=optional_split, id=invoice.id)
)
except Exception as e:
print(f"Error during mint: {str(e)}")
return
# set paid so we won't react to any more callbacks
paid = True

# user requests an invoice
if amount and not id:
mint_supports_websockets = True
invoice, subscription = await wallet.request_mint_subscription(
amount, callback=mint_invoice_callback
)
invoice_nonlocal, subscription_nonlocal = invoice, subscription
if invoice.bolt11:
print("")
print(f"Pay invoice to mint {wallet.unit.str(amount)}:")
Expand All @@ -299,33 +328,39 @@ def mint_invoice_callback(msg):
while not paid:
await asyncio.sleep(0.1)

if not mint_supports_websockets:
check_until = time.time() + 5 * 60 # check for five minutes
paid = False
while time.time() < check_until and not paid:
await asyncio.sleep(10)
try:
# await wallet.mint(amount, split=optional_split, id=invoice.id)
paid = True
print(" Invoice paid.")
subscription.close()
except Exception as e:
# TODO: user error codes!
if "not paid" in str(e):
print(".", end="", flush=True)
continue
else:
print(f"Error: {str(e)}")
if not paid:
print("\n")
print(
"Invoice is not paid yet, stopping check. Use the command above to"
" recheck after the invoice has been paid."
)
# we still check manually every 10 seconds
check_until = time.time() + 5 * 60 # check for five minutes
paid = False
while time.time() < check_until and not paid:
await asyncio.sleep(10)
try:
# await wallet.mint(amount, split=optional_split, id=invoice.id)
paid = True
except Exception as e:
# TODO: user error codes!
if "not paid" in str(e):
print(".", end="", flush=True)
continue
else:
print(f"Error: {str(e)}")
if not paid:
print("\n")
print(
"Invoice is not paid yet, stopping check. Use the command above to"
" recheck after the invoice has been paid."
)

# user paid invoice and want to check it
elif amount and id:
await wallet.mint(amount, split=optional_split, id=id)

# close open subscriptions so we can exit
try:
subscription.close()
except Exception:
pass
print(" Invoice paid.")

print("")
await print_balance(ctx)
return
Expand Down
18 changes: 4 additions & 14 deletions cashu/wallet/subscriptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from ..core.crypto.keys import random_hash
from ..core.json_rpc.base import (
JSONRPCNotficationParams,
JSONRPCNotification,
JSONRPCRequest,
JSONRPCResponse,
Expand Down Expand Up @@ -42,8 +43,9 @@ def _on_message(self, ws, message):

try:
msg = JSONRPCNotification.parse_raw(message)
params = JSONRPCNotficationParams.parse_obj(msg.params)
logger.debug(f"Received notification: {msg}")
self.callback_map[msg.params["subId"]](msg)
self.callback_map[params.subId](params)
return
except Exception:
pass
Expand All @@ -54,21 +56,9 @@ def connect(self):
self.websocket.run_forever(ping_interval=10, ping_timeout=5)

def close(self):
self.websocket.keep_running = False
self.websocket.close()

# async def listen(self):
# while True:
# await asyncio.sleep(0)
# try:
# msg = self.websocket.recv()
# print(msg)
# except WebSocketConnectionClosedException:
# print("Connection closed")
# break
# except Exception as e:
# print(f"Error receiving message: {e}")
# break

def wait_until_connected(self):
while not self.websocket.sock or not self.websocket.sock.connected:
time.sleep(0.025)
Expand Down
4 changes: 3 additions & 1 deletion cashu/wallet/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -823,7 +823,9 @@ async def request_mint_subscription(
"""
mint_qoute = await super().mint_quote(amount)
subscriptions = SubscriptionManager(self.url)
threading.Thread(target=subscriptions.connect).start()
threading.Thread(
target=subscriptions.connect, name="SubscriptionManager", daemon=True
).start()
subscriptions.subscribe(filters=[mint_qoute.quote], callback=callback)
# return the invoice
decoded_invoice = bolt11.decode(mint_qoute.request)
Expand Down
4 changes: 4 additions & 0 deletions tests/test_wallet_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ def test_invoice_with_split(mint, cli_prefix):
wallet = asyncio.run(init_wallet())
assert wallet.proof_amounts.count(1) >= 10


@pytest.mark.skipif(not is_fake, reason="only on fakewallet")
def test_invoices_with_minting(cli_prefix):
# arrange
Expand Down Expand Up @@ -223,6 +224,7 @@ def test_invoices_without_minting(cli_prefix):
assert get_invoice_from_invoices_command(result.output)["ID"] == invoice.id
assert get_invoice_from_invoices_command(result.output)["Paid"] == str(invoice.paid)


@pytest.mark.skipif(not is_fake, reason="only on fakewallet")
def test_invoices_with_onlypaid_option(cli_prefix):
# arrange
Expand Down Expand Up @@ -263,6 +265,7 @@ def test_invoices_with_onlypaid_option_without_minting(cli_prefix):
assert result.exit_code == 0
assert "No invoices found." in result.output


@pytest.mark.skipif(not is_fake, reason="only on fakewallet")
def test_invoices_with_onlyunpaid_option(cli_prefix):
# arrange
Expand Down Expand Up @@ -322,6 +325,7 @@ def test_invoices_with_both_onlypaid_and_onlyunpaid_options(cli_prefix):
in result.output
)


@pytest.mark.skipif(not is_fake, reason="only on fakewallet")
def test_invoices_with_pending_option(cli_prefix):
# arrange
Expand Down

0 comments on commit 41a85df

Please sign in to comment.