mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-19 10:04:19 +01:00
Mint: Add LndRest and regtest tests (#359)
* update * working * test with lnd * update action * cache poetry * add lndrest * enable regtest * add regtests.yml * poetry version * add helpers * save * run legend regtest fork * actually start * use bash * give rights * remove cache? * change order * tests succeed with lndrestwallet * check if wallet is set * settings for regtest * fix fakewallet test * remove wacky balance check * adjust permissions * try with sudo * adjust example * remove eclair
This commit is contained in:
@@ -44,13 +44,18 @@ MINT_DERIVATION_PATH="0/0/0/0"
|
||||
MINT_DATABASE=data/mint
|
||||
|
||||
# Lightning
|
||||
# Supported: LNbitsWallet, FakeWallet
|
||||
# Supported: LndRestWallet, LNbitsWallet, FakeWallet
|
||||
MINT_LIGHTNING_BACKEND=LNbitsWallet
|
||||
|
||||
# for use with LNbitsWallet
|
||||
MINT_LNBITS_ENDPOINT=https://legend.lnbits.com
|
||||
MINT_LNBITS_KEY=yourkeyasdasdasd
|
||||
|
||||
# LndRestWallet
|
||||
MINT_LND_REST_ENDPOINT=https://127.0.0.1:8086
|
||||
MINT_LND_REST_CERT="/home/lnd/.lnd/tls.cert"
|
||||
MINT_LND_REST_MACAROON="/home/lnd/.lnd/data/chain/bitcoin/regtest/admin.macaroon"
|
||||
|
||||
# fee to reserve in percent of the amount
|
||||
LIGHTNING_FEE_PERCENT=1.0
|
||||
# minimum fee to reserve
|
||||
|
||||
27
.github/actions/prepare/action.yml
vendored
Normal file
27
.github/actions/prepare/action.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: prepare
|
||||
|
||||
inputs:
|
||||
python-version:
|
||||
description: "Python Version"
|
||||
required: true
|
||||
default: "3.10"
|
||||
poetry-version:
|
||||
description: "Poetry Version"
|
||||
default: "1.5.1"
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Set up Poetry ${{ inputs.poetry-version }}
|
||||
uses: abatilo/actions-poetry@v2
|
||||
with:
|
||||
poetry-version: ${{ inputs.poetry-version }}
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
cache: "poetry"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
poetry install --extras pgsql
|
||||
shell: bash
|
||||
34
.github/workflows/checks.yml
vendored
34
.github/workflows/checks.yml
vendored
@@ -1,24 +1,28 @@
|
||||
name: checks
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
python-version:
|
||||
default: "3.10.4"
|
||||
type: string
|
||||
poetry-version:
|
||||
default: "1.5.1"
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
formatting:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.10.4"]
|
||||
poetry-version: ["1.5.1"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Poetry ${{ matrix.poetry-version }}
|
||||
- name: Set up Poetry ${{ inputs.poetry-version }}
|
||||
uses: abatilo/actions-poetry@v2
|
||||
with:
|
||||
poetry-version: ${{ matrix.poetry-version }}
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
poetry-version: ${{ inputs.poetry-version }}
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
python-version: ${{ inputs.python-version }}
|
||||
cache: "poetry"
|
||||
- name: Install packages
|
||||
run: poetry install
|
||||
@@ -26,20 +30,16 @@ jobs:
|
||||
run: make black-check
|
||||
mypy:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.10.4"]
|
||||
poetry-version: ["1.5.1"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Poetry ${{ matrix.poetry-version }}
|
||||
- name: Set up Poetry ${{ inputs.poetry-version }}
|
||||
uses: abatilo/actions-poetry@v2
|
||||
with:
|
||||
poetry-version: ${{ matrix.poetry-version }}
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
poetry-version: ${{ inputs.poetry-version }}
|
||||
- name: Set up Python ${{ inputs.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
python-version: ${{ inputs.python-version }}
|
||||
cache: "poetry"
|
||||
- name: Install packages
|
||||
run: poetry install
|
||||
|
||||
33
.github/workflows/ci.yml
vendored
Normal file
33
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: Nutshell CI
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
checks:
|
||||
uses: ./.github/workflows/checks.yml
|
||||
tests:
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
python-version: ["3.9", "3.10"]
|
||||
poetry-version: ["1.5.1"]
|
||||
# db-url: ["", "postgres://cashu:cashu@localhost:5432/test"] # TODO: Postgres test not working
|
||||
db-url: [""]
|
||||
backend-wallet-class: ["FakeWallet"]
|
||||
uses: ./.github/workflows/tests.yml
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
poetry-version: ${{ matrix.poetry-version }}
|
||||
regtest:
|
||||
uses: ./.github/workflows/regtest.yml
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.10"]
|
||||
poetry-version: ["1.5.1"]
|
||||
backend-wallet-class: ["LndRestWallet", "LNbitsWallet"]
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
backend-wallet-class: ${{ matrix.backend-wallet-class }}
|
||||
74
.github/workflows/regtest.yml
vendored
Normal file
74
.github/workflows/regtest.yml
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
name: regtest
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
python-version:
|
||||
default: "3.10.4"
|
||||
type: string
|
||||
poetry-version:
|
||||
default: "1.5.1"
|
||||
type: string
|
||||
os-version:
|
||||
default: "ubuntu-latest"
|
||||
type: string
|
||||
db-url:
|
||||
default: ""
|
||||
type: string
|
||||
backend-wallet-class:
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
regtest:
|
||||
runs-on: ${{ inputs.os-version }}
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: ./.github/actions/prepare
|
||||
with:
|
||||
python-version: ${{ inputs.python-version }}
|
||||
poetry-version: ${{ inputs.poetry-version }}
|
||||
|
||||
- name: Setup Regtest
|
||||
run: |
|
||||
git clone https://github.com/callebtc/cashu-regtest-enviroment.git regtest
|
||||
cd regtest
|
||||
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
|
||||
MINT_HOST: localhost
|
||||
MINT_PORT: 3337
|
||||
MINT_DATABASE: ${{ inputs.db-url }}
|
||||
TOR: false
|
||||
MINT_LIGHTNING_BACKEND: ${{ inputs.backend-wallet-class }}
|
||||
MINT_LNBITS_ENDPOINT: http://localhost:5001
|
||||
MINT_LNBITS_KEY: d08a3313322a4514af75d488bcc27eee
|
||||
MINT_LND_REST_ENDPOINT: https://localhost:8081/
|
||||
MINT_LND_REST_CERT: ./regtest/data/lnd-3/tls.cert
|
||||
MINT_LND_REST_MACAROON: ./regtest/data/lnd-3/data/chain/bitcoin/regtest/admin.macaroon
|
||||
# LND_GRPC_ENDPOINT: localhost
|
||||
# LND_GRPC_PORT: 10009
|
||||
# LND_GRPC_CERT: docker/data/lnd-3/tls.cert
|
||||
# LND_GRPC_MACAROON: docker/data/lnd-3/data/chain/bitcoin/regtest/admin.macaroon
|
||||
# CORELIGHTNING_RPC: ./docker/data/clightning-1/regtest/lightning-rpc
|
||||
# CORELIGHTNING_REST_URL: https://localhost:3001
|
||||
# CORELIGHTNING_REST_MACAROON: ./docker/data/clightning-2-rest/access.macaroon
|
||||
# CORELIGHTNING_REST_CERT: ./docker/data/clightning-2-rest/certificate.pem
|
||||
run: |
|
||||
sudo chmod -R 777 .
|
||||
make test
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }}
|
||||
file: ./coverage.xml
|
||||
44
.github/workflows/tests.yml
vendored
44
.github/workflows/tests.yml
vendored
@@ -1,10 +1,24 @@
|
||||
name: tests
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
python-version:
|
||||
default: "3.10.4"
|
||||
type: string
|
||||
poetry-version:
|
||||
default: "1.5.1"
|
||||
type: string
|
||||
db-url:
|
||||
default: ""
|
||||
type: string
|
||||
os:
|
||||
default: "ubuntu-latest"
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
poetry:
|
||||
runs-on: ${{ matrix.os }}
|
||||
runs-on: ${{ inputs.os }}
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:latest
|
||||
@@ -19,34 +33,16 @@ jobs:
|
||||
--health-interval 10s
|
||||
--health-timeout 5s
|
||||
--health-retries 5
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
python-version: ["3.10.4"]
|
||||
poetry-version: ["1.5.1"]
|
||||
# db-url: ["", "postgres://cashu:cashu@localhost:5432/test"] # TODO: Postgres test not working
|
||||
db-url: [""]
|
||||
steps:
|
||||
- name: Checkout repository and submodules
|
||||
uses: actions/checkout@v2
|
||||
- uses: ./.github/actions/prepare
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up Poetry ${{ matrix.poetry-version }}
|
||||
uses: abatilo/actions-poetry@v2
|
||||
with:
|
||||
poetry-version: ${{ matrix.poetry-version }}
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
cache: "poetry"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
poetry install --extras pgsql
|
||||
shell: bash
|
||||
python-version: ${{ inputs.python-version }}
|
||||
poetry-version: ${{ inputs.poetry-version }}
|
||||
- name: Run tests
|
||||
env:
|
||||
LIGHTNING: false
|
||||
MINT_LIGHTNING_BACKEND: FakeWallet
|
||||
WALLET_NAME: test_wallet
|
||||
MINT_HOST: localhost
|
||||
MINT_PORT: 3337
|
||||
|
||||
11
Makefile
11
Makefile
@@ -29,8 +29,19 @@ package:
|
||||
python setup.py sdist bdist_wheel
|
||||
|
||||
test:
|
||||
PYTHONUNBUFFERED=1 \
|
||||
DEBUG=true \
|
||||
poetry run pytest tests --cov-report xml --cov cashu
|
||||
|
||||
test-lndrest:
|
||||
PYTHONUNBUFFERED=1 \
|
||||
DEBUG=true \
|
||||
MINT_LIGHTNING_BACKEND=LndRestWallet \
|
||||
MINT_LND_REST_ENDPOINT=https://localhost:8081/ \
|
||||
MINT_LND_REST_CERT=../cashu-regtest-enviroment/data/lnd-3/tls.cert \
|
||||
MINT_LND_REST_MACAROON=../cashu-regtest-enviroment/data/lnd-3/data/chain/bitcoin/regtest/admin.macaroon \
|
||||
poetry run pytest tests/test_cli.py --cov-report xml --cov cashu
|
||||
|
||||
install:
|
||||
make clean
|
||||
python setup.py sdist bdist_wheel
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
|
||||
from environs import Env # type: ignore
|
||||
from pydantic import BaseSettings, Extra, Field
|
||||
@@ -99,8 +99,17 @@ class WalletSettings(CashuSettings):
|
||||
locktime_delta_seconds: int = Field(default=86400) # 1 day
|
||||
|
||||
|
||||
class LndRestFundingSource(MintSettings):
|
||||
mint_lnd_rest_endpoint: Optional[str] = Field(default=None)
|
||||
mint_lnd_rest_cert: Optional[str] = Field(default=None)
|
||||
mint_lnd_rest_macaroon: Optional[str] = Field(default=None)
|
||||
mint_lnd_rest_admin_macaroon: Optional[str] = Field(default=None)
|
||||
mint_lnd_rest_invoice_macaroon: Optional[str] = Field(default=None)
|
||||
|
||||
|
||||
class Settings(
|
||||
EnvSettings,
|
||||
LndRestFundingSource,
|
||||
MintSettings,
|
||||
MintInformation,
|
||||
WalletSettings,
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
# type: ignore
|
||||
from ..core.settings import settings
|
||||
from .fake import FakeWallet # noqa: F401
|
||||
from .lnbits import LNbitsWallet # noqa: F401
|
||||
from .lndrest import LndRestWallet # noqa: F401
|
||||
|
||||
if settings.mint_lightning_backend is None:
|
||||
raise Exception("MINT_LIGHTNING_BACKEND not configured")
|
||||
|
||||
271
cashu/lightning/lndrest.py
Normal file
271
cashu/lightning/lndrest.py
Normal file
@@ -0,0 +1,271 @@
|
||||
import asyncio
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
from typing import AsyncGenerator, Dict, Optional
|
||||
|
||||
import httpx
|
||||
from loguru import logger
|
||||
|
||||
from ..core.settings import settings
|
||||
from .base import (
|
||||
InvoiceResponse,
|
||||
PaymentResponse,
|
||||
PaymentStatus,
|
||||
StatusResponse,
|
||||
Wallet,
|
||||
)
|
||||
|
||||
|
||||
def load_macaroon(macaroon: str) -> str:
|
||||
"""Returns hex version of a macaroon encoded in base64 or the file path.
|
||||
|
||||
:param macaroon: Macaroon encoded in base64 or file path.
|
||||
:type macaroon: str
|
||||
:return: Hex version of macaroon.
|
||||
:rtype: str
|
||||
"""
|
||||
|
||||
# if the macaroon is a file path, load it and return hex version
|
||||
if macaroon.split(".")[-1] == "macaroon":
|
||||
with open(macaroon, "rb") as f:
|
||||
macaroon_bytes = f.read()
|
||||
return macaroon_bytes.hex()
|
||||
else:
|
||||
# if macaroon is a provided string
|
||||
# check if it is hex, if so, return
|
||||
try:
|
||||
bytes.fromhex(macaroon)
|
||||
return macaroon
|
||||
except ValueError:
|
||||
pass
|
||||
# convert the bas64 macaroon to hex
|
||||
try:
|
||||
macaroon = base64.b64decode(macaroon).hex()
|
||||
except Exception:
|
||||
pass
|
||||
return macaroon
|
||||
|
||||
|
||||
class LndRestWallet(Wallet):
|
||||
"""https://api.lightning.community/rest/index.html#lnd-rest-api-reference"""
|
||||
|
||||
def __init__(self):
|
||||
endpoint = settings.mint_lnd_rest_endpoint
|
||||
cert = settings.mint_lnd_rest_cert
|
||||
|
||||
macaroon = (
|
||||
settings.mint_lnd_rest_macaroon
|
||||
or settings.mint_lnd_rest_admin_macaroon
|
||||
or settings.mint_lnd_rest_invoice_macaroon
|
||||
)
|
||||
|
||||
if not endpoint:
|
||||
raise Exception("cannot initialize lndrest: no endpoint")
|
||||
|
||||
if not macaroon:
|
||||
raise Exception("cannot initialize lndrest: no macaroon")
|
||||
|
||||
if not cert:
|
||||
logger.warning(
|
||||
"no certificate for lndrest provided, this only works if you have a"
|
||||
" publicly issued certificate"
|
||||
)
|
||||
|
||||
endpoint = endpoint[:-1] if endpoint.endswith("/") else endpoint
|
||||
endpoint = (
|
||||
f"https://{endpoint}" if not endpoint.startswith("http") else endpoint
|
||||
)
|
||||
self.endpoint = endpoint
|
||||
self.macaroon = load_macaroon(macaroon)
|
||||
|
||||
# if no cert provided it should be public so we set verify to True
|
||||
# and it will still check for validity of certificate and fail if its not valid
|
||||
# even on startup
|
||||
self.cert = cert or True
|
||||
|
||||
self.auth = {"Grpc-Metadata-macaroon": self.macaroon}
|
||||
self.client = httpx.AsyncClient(
|
||||
base_url=self.endpoint, headers=self.auth, verify=self.cert
|
||||
)
|
||||
|
||||
async def status(self) -> StatusResponse:
|
||||
try:
|
||||
r = await self.client.get("/v1/balance/channels")
|
||||
r.raise_for_status()
|
||||
except (httpx.ConnectError, httpx.RequestError) as exc:
|
||||
return StatusResponse(
|
||||
error_message=f"Unable to connect to {self.endpoint}. {exc}",
|
||||
balance_msat=0,
|
||||
)
|
||||
|
||||
try:
|
||||
data = r.json()
|
||||
if r.is_error:
|
||||
raise Exception
|
||||
except Exception:
|
||||
return StatusResponse(error_message=r.text[:200], balance_msat=0)
|
||||
|
||||
return StatusResponse(
|
||||
error_message=None, balance_msat=int(data["balance"]) * 1000
|
||||
)
|
||||
|
||||
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 = {"value": amount, "private": True, "memo": memo or ""}
|
||||
if kwargs.get("expiry"):
|
||||
data["expiry"] = kwargs["expiry"]
|
||||
if description_hash:
|
||||
data["description_hash"] = base64.b64encode(description_hash).decode(
|
||||
"ascii"
|
||||
)
|
||||
elif unhashed_description:
|
||||
data["description_hash"] = base64.b64encode(
|
||||
hashlib.sha256(unhashed_description).digest()
|
||||
).decode("ascii")
|
||||
|
||||
r = await self.client.post(url="/v1/invoices", json=data)
|
||||
|
||||
if r.is_error:
|
||||
error_message = r.text
|
||||
try:
|
||||
error_message = r.json()["error"]
|
||||
except Exception:
|
||||
pass
|
||||
return InvoiceResponse(
|
||||
ok=False,
|
||||
checking_id=None,
|
||||
payment_request=None,
|
||||
error_message=error_message,
|
||||
)
|
||||
|
||||
data = r.json()
|
||||
payment_request = data["payment_request"]
|
||||
payment_hash = base64.b64decode(data["r_hash"]).hex()
|
||||
checking_id = payment_hash
|
||||
|
||||
return InvoiceResponse(
|
||||
ok=True,
|
||||
checking_id=checking_id,
|
||||
payment_request=payment_request,
|
||||
error_message=None,
|
||||
)
|
||||
|
||||
async def pay_invoice(self, bolt11: str, fee_limit_msat: int) -> PaymentResponse:
|
||||
# set the fee limit for the payment
|
||||
lnrpcFeeLimit = dict()
|
||||
lnrpcFeeLimit["fixed_msat"] = f"{fee_limit_msat}"
|
||||
|
||||
r = await self.client.post(
|
||||
url="/v1/channels/transactions",
|
||||
json={"payment_request": bolt11, "fee_limit": lnrpcFeeLimit},
|
||||
timeout=None,
|
||||
)
|
||||
|
||||
if r.is_error or r.json().get("payment_error"):
|
||||
error_message = r.json().get("payment_error") or r.text
|
||||
return PaymentResponse(
|
||||
ok=False,
|
||||
checking_id=None,
|
||||
fee_msat=None,
|
||||
preimage=None,
|
||||
error_message=error_message,
|
||||
)
|
||||
|
||||
data = r.json()
|
||||
checking_id = base64.b64decode(data["payment_hash"]).hex()
|
||||
fee_msat = int(data["payment_route"]["total_fees_msat"])
|
||||
preimage = base64.b64decode(data["payment_preimage"]).hex()
|
||||
return PaymentResponse(
|
||||
ok=True,
|
||||
checking_id=checking_id,
|
||||
fee_msat=fee_msat,
|
||||
preimage=preimage,
|
||||
error_message=None,
|
||||
)
|
||||
|
||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||
r = await self.client.get(url=f"/v1/invoice/{checking_id}")
|
||||
|
||||
if r.is_error or not r.json().get("settled"):
|
||||
# this must also work when checking_id is not a hex recognizable by lnd
|
||||
# it will return an error and no "settled" attribute on the object
|
||||
return PaymentStatus(paid=None)
|
||||
|
||||
return PaymentStatus(paid=True)
|
||||
|
||||
async def get_payment_status(self, checking_id: str) -> PaymentStatus:
|
||||
"""
|
||||
This routine checks the payment status using routerpc.TrackPaymentV2.
|
||||
"""
|
||||
# convert checking_id from hex to base64 and some LND magic
|
||||
try:
|
||||
checking_id = base64.urlsafe_b64encode(bytes.fromhex(checking_id)).decode(
|
||||
"ascii"
|
||||
)
|
||||
except ValueError:
|
||||
return PaymentStatus(paid=None)
|
||||
|
||||
url = f"/v2/router/track/{checking_id}"
|
||||
|
||||
# check payment.status:
|
||||
# https://api.lightning.community/?python=#paymentpaymentstatus
|
||||
statuses = {
|
||||
"UNKNOWN": None,
|
||||
"IN_FLIGHT": None,
|
||||
"SUCCEEDED": True,
|
||||
"FAILED": False,
|
||||
}
|
||||
|
||||
async with self.client.stream("GET", url, timeout=None) as r:
|
||||
async for json_line in r.aiter_lines():
|
||||
try:
|
||||
line = json.loads(json_line)
|
||||
if line.get("error"):
|
||||
logger.error(
|
||||
line["error"]["message"]
|
||||
if "message" in line["error"]
|
||||
else line["error"]
|
||||
)
|
||||
return PaymentStatus(paid=None)
|
||||
payment = line.get("result")
|
||||
if payment is not None and payment.get("status"):
|
||||
return PaymentStatus(
|
||||
paid=statuses[payment["status"]],
|
||||
fee_msat=payment.get("fee_msat"),
|
||||
preimage=payment.get("payment_preimage"),
|
||||
)
|
||||
else:
|
||||
return PaymentStatus(paid=None)
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
return PaymentStatus(paid=None)
|
||||
|
||||
async def paid_invoices_stream(self) -> AsyncGenerator[str, None]:
|
||||
while True:
|
||||
try:
|
||||
url = "/v1/invoices/subscribe"
|
||||
async with self.client.stream("GET", url, timeout=None) as r:
|
||||
async for line in r.aiter_lines():
|
||||
try:
|
||||
inv = json.loads(line)["result"]
|
||||
if not inv["settled"]:
|
||||
continue
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
payment_hash = base64.b64decode(inv["r_hash"]).hex()
|
||||
yield payment_hash
|
||||
except Exception as exc:
|
||||
logger.error(
|
||||
f"lost connection to lnd invoices stream: '{exc}', retrying in 5"
|
||||
" seconds"
|
||||
)
|
||||
await asyncio.sleep(5)
|
||||
@@ -210,9 +210,17 @@ async def pay(ctx: Context, invoice: str, yes: bool):
|
||||
help="Split minted tokens with a specific amount.",
|
||||
type=int,
|
||||
)
|
||||
@click.option(
|
||||
"--no-check",
|
||||
"-n",
|
||||
default=False,
|
||||
is_flag=True,
|
||||
help="Do not check if invoice is paid.",
|
||||
type=bool,
|
||||
)
|
||||
@click.pass_context
|
||||
@coro
|
||||
async def invoice(ctx: Context, amount: int, id: str, split: int):
|
||||
async def invoice(ctx: Context, amount: int, id: str, split: int, no_check: bool):
|
||||
wallet: Wallet = ctx.obj["WALLET"]
|
||||
await wallet.load_mint()
|
||||
wallet.status()
|
||||
@@ -236,9 +244,11 @@ async def invoice(ctx: Context, amount: int, id: str, split: int):
|
||||
print(f"Invoice: {invoice.bolt11}")
|
||||
print("")
|
||||
print(
|
||||
"If you abort this you can use this command to recheck the"
|
||||
f" invoice:\ncashu invoice {amount} --id {invoice.id}"
|
||||
"You can use this command to check the invoice: cashu invoice"
|
||||
f" {amount} --id {invoice.id}"
|
||||
)
|
||||
if no_check:
|
||||
return
|
||||
check_until = time.time() + 5 * 60 # check for five minutes
|
||||
print("")
|
||||
print(
|
||||
|
||||
0
tests/__init__.py
Normal file
0
tests/__init__.py
Normal file
@@ -28,7 +28,7 @@ settings.mint_listen_port = SERVER_PORT
|
||||
settings.mint_url = SERVER_ENDPOINT
|
||||
settings.lightning = True
|
||||
settings.tor = False
|
||||
settings.mint_lightning_backend = "FakeWallet"
|
||||
settings.mint_lightning_backend = settings.mint_lightning_backend or "FakeWallet"
|
||||
settings.mint_database = "./test_data/test_mint"
|
||||
settings.mint_derivation_path = "0/0/0/0"
|
||||
settings.mint_private_key = "TEST_PRIVATE_KEY"
|
||||
|
||||
184
tests/helpers.py
Normal file
184
tests/helpers.py
Normal file
@@ -0,0 +1,184 @@
|
||||
import hashlib
|
||||
import importlib
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
import time
|
||||
from subprocess import PIPE, Popen, TimeoutExpired
|
||||
from typing import Tuple
|
||||
|
||||
from loguru import logger
|
||||
|
||||
from cashu.core.settings import settings
|
||||
|
||||
|
||||
def get_random_string(N: int = 10):
|
||||
return "".join(
|
||||
random.SystemRandom().choice(string.ascii_uppercase + string.digits)
|
||||
for _ in range(N)
|
||||
)
|
||||
|
||||
|
||||
async def get_random_invoice_data():
|
||||
return {"out": False, "amount": 10, "memo": f"test_memo_{get_random_string(10)}"}
|
||||
|
||||
|
||||
wallets_module = importlib.import_module("cashu.lightning")
|
||||
wallet_class = getattr(wallets_module, settings.mint_lightning_backend)
|
||||
WALLET = wallet_class()
|
||||
is_fake: bool = WALLET.__class__.__name__ == "FakeWallet"
|
||||
is_regtest: bool = not is_fake
|
||||
|
||||
|
||||
docker_lightning_cli = [
|
||||
"docker",
|
||||
"exec",
|
||||
"cashu-lnd-1-1",
|
||||
"lncli",
|
||||
"--network",
|
||||
"regtest",
|
||||
"--rpcserver=lnd-1",
|
||||
]
|
||||
|
||||
docker_bitcoin_cli = [
|
||||
"docker",
|
||||
"exec",
|
||||
"cashu-bitcoind-1-1bitcoin-cli",
|
||||
"-rpcuser=lnbits",
|
||||
"-rpcpassword=lnbits",
|
||||
"-regtest",
|
||||
]
|
||||
|
||||
|
||||
docker_lightning_unconnected_cli = [
|
||||
"docker",
|
||||
"exec",
|
||||
"cashu-lnd-2-1",
|
||||
"lncli",
|
||||
"--network",
|
||||
"regtest",
|
||||
"--rpcserver=lnd-2",
|
||||
]
|
||||
|
||||
|
||||
def run_cmd(cmd: list) -> str:
|
||||
timeout = 20
|
||||
process = Popen(cmd, stdout=PIPE, stderr=PIPE)
|
||||
|
||||
def process_communication(comm):
|
||||
stdout, stderr = comm
|
||||
output = stdout.decode("utf-8").strip()
|
||||
error = stderr.decode("utf-8").strip()
|
||||
return output, error
|
||||
|
||||
try:
|
||||
now = time.time()
|
||||
output, error = process_communication(process.communicate(timeout=timeout))
|
||||
took = time.time() - now
|
||||
logger.debug(f"ran command output: {output}, error: {error}, took: {took}s")
|
||||
return output
|
||||
except TimeoutExpired:
|
||||
process.kill()
|
||||
output, error = process_communication(process.communicate())
|
||||
logger.error(f"timeout command: {cmd}, output: {output}, error: {error}")
|
||||
raise
|
||||
|
||||
|
||||
def run_cmd_json(cmd: list) -> dict:
|
||||
output = run_cmd(cmd)
|
||||
try:
|
||||
return json.loads(output) if output else {}
|
||||
except json.decoder.JSONDecodeError:
|
||||
logger.error(f"failed to decode json from cmd `{cmd}`: {output}")
|
||||
raise
|
||||
|
||||
|
||||
def get_hold_invoice(sats: int) -> Tuple[str, dict]:
|
||||
preimage = os.urandom(32)
|
||||
preimage_hash = hashlib.sha256(preimage).hexdigest()
|
||||
cmd = docker_lightning_cli.copy()
|
||||
cmd.extend(["addholdinvoice", preimage_hash, str(sats)])
|
||||
json = run_cmd_json(cmd)
|
||||
return preimage.hex(), json
|
||||
|
||||
|
||||
def settle_invoice(preimage: str) -> str:
|
||||
cmd = docker_lightning_cli.copy()
|
||||
cmd.extend(["settleinvoice", preimage])
|
||||
return run_cmd(cmd)
|
||||
|
||||
|
||||
def cancel_invoice(preimage_hash: str) -> str:
|
||||
cmd = docker_lightning_cli.copy()
|
||||
cmd.extend(["cancelinvoice", preimage_hash])
|
||||
return run_cmd(cmd)
|
||||
|
||||
|
||||
def get_real_invoice(sats: int) -> dict:
|
||||
cmd = docker_lightning_cli.copy()
|
||||
cmd.extend(["addinvoice", str(sats)])
|
||||
return run_cmd_json(cmd)
|
||||
|
||||
|
||||
def pay_real_invoice(invoice: str) -> str:
|
||||
cmd = docker_lightning_cli.copy()
|
||||
cmd.extend(["payinvoice", "--force", invoice])
|
||||
return run_cmd(cmd)
|
||||
|
||||
|
||||
def mine_blocks(blocks: int = 1) -> str:
|
||||
cmd = docker_bitcoin_cli.copy()
|
||||
cmd.extend(["-generate", str(blocks)])
|
||||
return run_cmd(cmd)
|
||||
|
||||
|
||||
def get_unconnected_node_uri() -> str:
|
||||
cmd = docker_lightning_unconnected_cli.copy()
|
||||
cmd.append("getinfo")
|
||||
info = run_cmd_json(cmd)
|
||||
pubkey = info["identity_pubkey"]
|
||||
return f"{pubkey}@lnd-2:9735"
|
||||
|
||||
|
||||
def create_onchain_address(address_type: str = "bech32") -> str:
|
||||
cmd = docker_bitcoin_cli.copy()
|
||||
cmd.extend(["getnewaddress", address_type])
|
||||
return run_cmd(cmd)
|
||||
|
||||
|
||||
def pay_onchain(address: str, sats: int) -> str:
|
||||
btc = sats * 0.00000001
|
||||
cmd = docker_bitcoin_cli.copy()
|
||||
cmd.extend(["sendtoaddress", address, str(btc)])
|
||||
return run_cmd(cmd)
|
||||
|
||||
|
||||
# def clean_database(settings):
|
||||
# if DB_TYPE == POSTGRES:
|
||||
# db_url = make_url(settings.lnbits_database_url)
|
||||
|
||||
# conn = psycopg2.connect(settings.lnbits_database_url)
|
||||
# conn.autocommit = True
|
||||
# with conn.cursor() as cur:
|
||||
# try:
|
||||
# cur.execute("DROP DATABASE lnbits_test")
|
||||
# except psycopg2.errors.InvalidCatalogName:
|
||||
# pass
|
||||
# cur.execute("CREATE DATABASE lnbits_test")
|
||||
|
||||
# db_url.database = "lnbits_test"
|
||||
# settings.lnbits_database_url = str(db_url)
|
||||
|
||||
# core.db.__init__("database")
|
||||
|
||||
# conn.close()
|
||||
# else:
|
||||
# # FIXME: do this once mock data is removed from test data folder
|
||||
# # os.remove(settings.lnbits_data_folder + "/database.sqlite3")
|
||||
# pass
|
||||
|
||||
|
||||
def pay_if_regtest(bolt11: str):
|
||||
if is_regtest:
|
||||
pay_real_invoice(bolt11)
|
||||
@@ -7,6 +7,7 @@ from cashu.core.base import TokenV3
|
||||
from cashu.core.settings import settings
|
||||
from cashu.wallet.cli.cli import cli
|
||||
from cashu.wallet.wallet import Wallet
|
||||
from tests.helpers import is_fake, pay_if_regtest
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True, scope="session")
|
||||
@@ -14,6 +15,16 @@ def cli_prefix():
|
||||
yield ["--wallet", "test_cli_wallet", "--host", settings.mint_url, "--tests"]
|
||||
|
||||
|
||||
def get_bolt11_and_invoice_id_from_invoice_command(output: str) -> (str, str):
|
||||
invoice = [
|
||||
line.split(" ")[1] for line in output.split("\n") if line.startswith("Invoice")
|
||||
][0]
|
||||
invoice_id = [
|
||||
line.split(" ")[-1] for line in output.split("\n") if line.startswith("You can")
|
||||
][0]
|
||||
return invoice, invoice_id
|
||||
|
||||
|
||||
async def init_wallet():
|
||||
wallet = await Wallet.with_db(
|
||||
url=settings.mint_host,
|
||||
@@ -77,7 +88,8 @@ def test_balance(cli_prefix):
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_invoice(mint, cli_prefix):
|
||||
@pytest.mark.skipif(not is_fake, reason="only on fakewallet")
|
||||
def test_invoice_automatic_fakewallet(mint, cli_prefix):
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
@@ -87,20 +99,60 @@ def test_invoice(mint, cli_prefix):
|
||||
print("INVOICE")
|
||||
print(result.output)
|
||||
wallet = asyncio.run(init_wallet())
|
||||
# assert wallet.available_balance >= 1000
|
||||
assert wallet.available_balance >= 1000
|
||||
assert f"Balance: {wallet.available_balance} sat" in result.output
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_invoice(mint, cli_prefix):
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[*cli_prefix, "invoice", "-n", "1000"],
|
||||
)
|
||||
|
||||
assert result.exception is None
|
||||
|
||||
invoice, invoice_id = get_bolt11_and_invoice_id_from_invoice_command(result.output)
|
||||
pay_if_regtest(invoice)
|
||||
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[*cli_prefix, "invoice", "1000", "--id", invoice_id],
|
||||
)
|
||||
assert result.exception is None
|
||||
|
||||
wallet = asyncio.run(init_wallet())
|
||||
assert wallet.available_balance >= 1000
|
||||
assert result.exit_code == 0
|
||||
|
||||
|
||||
def test_invoice_with_split(mint, cli_prefix):
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[*cli_prefix, "invoice", "10", "-s", "1"],
|
||||
[
|
||||
*cli_prefix,
|
||||
"invoice",
|
||||
"10",
|
||||
"-s",
|
||||
"1",
|
||||
"-n",
|
||||
],
|
||||
)
|
||||
assert result.exception is None
|
||||
# wallet = asyncio.run(init_wallet())
|
||||
# assert wallet.proof_amounts.count(1) >= 10
|
||||
|
||||
invoice, invoice_id = get_bolt11_and_invoice_id_from_invoice_command(result.output)
|
||||
pay_if_regtest(invoice)
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[*cli_prefix, "invoice", "10", "-s", "1", "--id", invoice_id],
|
||||
)
|
||||
assert result.exception is None
|
||||
|
||||
assert result.exception is None
|
||||
wallet = asyncio.run(init_wallet())
|
||||
assert wallet.proof_amounts.count(1) >= 10
|
||||
|
||||
|
||||
def test_wallets(cli_prefix):
|
||||
|
||||
@@ -5,6 +5,7 @@ from cashu.mint.ledger import Ledger
|
||||
from cashu.wallet.wallet import Wallet
|
||||
from cashu.wallet.wallet import Wallet as Wallet1
|
||||
from tests.conftest import SERVER_ENDPOINT
|
||||
from tests.helpers import pay_if_regtest
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
@@ -23,8 +24,10 @@ async def wallet1(mint):
|
||||
async def test_melt(wallet1: Wallet, ledger: Ledger):
|
||||
# mint twice so we have enough to pay the second invoice back
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
assert wallet1.balance == 128
|
||||
total_amount, fee_reserve_sat = await wallet1.get_pay_amount_with_fees(
|
||||
@@ -41,6 +44,7 @@ async def test_melt(wallet1: Wallet, ledger: Ledger):
|
||||
@pytest.mark.asyncio
|
||||
async def test_split(wallet1: Wallet, ledger: Ledger):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
|
||||
keep_proofs, send_proofs = await wallet1.split_to_send(wallet1.proofs, 10)
|
||||
@@ -57,6 +61,7 @@ async def test_split(wallet1: Wallet, ledger: Ledger):
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_proof_state(wallet1: Wallet, ledger: Ledger):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
|
||||
keep_proofs, send_proofs = await wallet1.split_to_send(wallet1.proofs, 10)
|
||||
|
||||
@@ -14,6 +14,7 @@ from cashu.wallet.wallet import Wallet
|
||||
from cashu.wallet.wallet import Wallet as Wallet1
|
||||
from cashu.wallet.wallet import Wallet as Wallet2
|
||||
from tests.conftest import SERVER_ENDPOINT
|
||||
from tests.helpers import get_real_invoice, is_regtest, pay_if_regtest
|
||||
|
||||
|
||||
async def assert_err(f, msg: Union[str, CashuError]):
|
||||
@@ -138,6 +139,7 @@ async def test_get_keyset_ids(wallet1: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
async def test_mint(wallet1: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
assert wallet1.balance == 64
|
||||
|
||||
@@ -158,6 +160,7 @@ async def test_mint(wallet1: Wallet):
|
||||
async def test_mint_amounts(wallet1: Wallet):
|
||||
"""Mint predefined amounts"""
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
amts = [1, 1, 1, 2, 2, 4, 16]
|
||||
await wallet1.mint(amount=sum(amts), split=amts, id=invoice.id)
|
||||
assert wallet1.balance == 27
|
||||
@@ -187,6 +190,7 @@ async def test_mint_amounts_wrong_order(wallet1: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
async def test_split(wallet1: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
assert wallet1.balance == 64
|
||||
p1, p2 = await wallet1.split(wallet1.proofs, 20)
|
||||
@@ -202,6 +206,7 @@ async def test_split(wallet1: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
async def test_split_to_send(wallet1: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
keep_proofs, spendable_proofs = await wallet1.split_to_send(
|
||||
wallet1.proofs, 32, set_reserved=True
|
||||
@@ -217,6 +222,7 @@ async def test_split_to_send(wallet1: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
async def test_split_more_than_balance(wallet1: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
await assert_err(
|
||||
wallet1.split(wallet1.proofs, 128),
|
||||
@@ -230,8 +236,10 @@ async def test_split_more_than_balance(wallet1: Wallet):
|
||||
async def test_melt(wallet1: Wallet):
|
||||
# mint twice so we have enough to pay the second invoice back
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
assert wallet1.balance == 128
|
||||
|
||||
@@ -243,38 +251,46 @@ async def test_melt(wallet1: Wallet):
|
||||
assert fee_reserve_sat == 2
|
||||
_, send_proofs = await wallet1.split_to_send(wallet1.proofs, total_amount)
|
||||
|
||||
invoice_to_pay = invoice.bolt11
|
||||
invoice_payment_hash = str(invoice.payment_hash)
|
||||
if is_regtest:
|
||||
invoice_dict = get_real_invoice(64)
|
||||
invoice_to_pay = invoice_dict["payment_request"]
|
||||
invoice_payment_hash = str(invoice_dict["r_hash"])
|
||||
|
||||
melt_response = await wallet1.pay_lightning(
|
||||
send_proofs, invoice=invoice.bolt11, fee_reserve_sat=fee_reserve_sat
|
||||
send_proofs, invoice=invoice_to_pay, fee_reserve_sat=fee_reserve_sat
|
||||
)
|
||||
|
||||
assert melt_response.change
|
||||
assert len(melt_response.change) == 1
|
||||
assert melt_response.change, "No change returned"
|
||||
assert len(melt_response.change) == 1, "More than one change returned"
|
||||
# NOTE: we assume that we will get a token back from the same keyset as the ones we melted
|
||||
# this could be wrong if we melted tokens from an old keyset but the returned ones are
|
||||
# from a newer one.
|
||||
assert melt_response.change[0].id == send_proofs[0].id
|
||||
assert melt_response.change[0].id == send_proofs[0].id, "Wrong keyset returned"
|
||||
|
||||
# verify that proofs in proofs_used db have the same melt_id as the invoice in the db
|
||||
assert invoice.payment_hash
|
||||
assert invoice.payment_hash, "No payment hash in invoice"
|
||||
invoice_db = await get_lightning_invoice(
|
||||
db=wallet1.db, payment_hash=invoice.payment_hash, out=True
|
||||
db=wallet1.db, payment_hash=invoice_payment_hash, out=True
|
||||
)
|
||||
assert invoice_db
|
||||
assert invoice_db, "No invoice in db"
|
||||
proofs_used = await get_proofs(
|
||||
db=wallet1.db, melt_id=invoice_db.id, table="proofs_used"
|
||||
)
|
||||
|
||||
assert len(proofs_used) == len(send_proofs)
|
||||
assert all([p.melt_id == invoice_db.id for p in proofs_used])
|
||||
assert len(proofs_used) == len(send_proofs), "Not all proofs used"
|
||||
assert all([p.melt_id == invoice_db.id for p in proofs_used]), "Wrong melt_id"
|
||||
|
||||
# the payment was without fees so we need to remove it from the total amount
|
||||
assert wallet1.balance == 128 - (total_amount - fee_reserve_sat)
|
||||
assert wallet1.balance == 64
|
||||
assert wallet1.balance == 128 - (total_amount - fee_reserve_sat), "Wrong balance"
|
||||
assert wallet1.balance == 64, "Wrong balance"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_split_to_send_more_than_balance(wallet1: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
await assert_err(
|
||||
wallet1.split_to_send(wallet1.proofs, 128, set_reserved=True),
|
||||
@@ -287,6 +303,7 @@ async def test_split_to_send_more_than_balance(wallet1: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
async def test_double_spend(wallet1: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
doublespend = await wallet1.mint(64, id=invoice.id)
|
||||
await wallet1.split(wallet1.proofs, 20)
|
||||
await assert_err(
|
||||
@@ -300,6 +317,7 @@ async def test_double_spend(wallet1: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
async def test_duplicate_proofs_double_spent(wallet1: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
doublespend = await wallet1.mint(64, id=invoice.id)
|
||||
await assert_err(
|
||||
wallet1.split(wallet1.proofs + doublespend, 20),
|
||||
@@ -312,6 +330,7 @@ async def test_duplicate_proofs_double_spent(wallet1: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_and_redeem(wallet1: Wallet, wallet2: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
_, spendable_proofs = await wallet1.split_to_send(
|
||||
wallet1.proofs, 32, set_reserved=True
|
||||
@@ -330,6 +349,7 @@ async def test_send_and_redeem(wallet1: Wallet, wallet2: Wallet):
|
||||
async def test_invalidate_unspent_proofs(wallet1: Wallet):
|
||||
"""Try to invalidate proofs that have not been spent yet. Should not work!"""
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
await wallet1.invalidate(wallet1.proofs)
|
||||
assert wallet1.balance == 64
|
||||
@@ -339,6 +359,7 @@ async def test_invalidate_unspent_proofs(wallet1: Wallet):
|
||||
async def test_invalidate_unspent_proofs_without_checking(wallet1: Wallet):
|
||||
"""Try to invalidate proofs that have not been spent yet but force no check."""
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
await wallet1.invalidate(wallet1.proofs, check_spendable=False)
|
||||
assert wallet1.balance == 0
|
||||
@@ -347,6 +368,7 @@ async def test_invalidate_unspent_proofs_without_checking(wallet1: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
async def test_split_invalid_amount(wallet1: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
await assert_err(
|
||||
wallet1.split(wallet1.proofs, -1),
|
||||
@@ -357,6 +379,7 @@ async def test_split_invalid_amount(wallet1: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
async def test_token_state(wallet1: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
assert wallet1.balance == 64
|
||||
resp = await wallet1.check_proof_state(wallet1.proofs)
|
||||
|
||||
@@ -8,6 +8,7 @@ from cashu.lightning.base import InvoiceResponse, PaymentStatus
|
||||
from cashu.wallet.api.app import app
|
||||
from cashu.wallet.wallet import Wallet
|
||||
from tests.conftest import SERVER_ENDPOINT
|
||||
from tests.helpers import is_regtest
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
@@ -22,6 +23,7 @@ async def wallet(mint):
|
||||
yield wallet
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_regtest, reason="regtest")
|
||||
@pytest.mark.asyncio
|
||||
async def test_invoice(wallet: Wallet):
|
||||
with TestClient(app) as client:
|
||||
@@ -40,6 +42,7 @@ async def test_invoice(wallet: Wallet):
|
||||
print("paid")
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_regtest, reason="regtest")
|
||||
@pytest.mark.asyncio
|
||||
async def test_balance():
|
||||
with TestClient(app) as client:
|
||||
@@ -50,6 +53,7 @@ async def test_balance():
|
||||
assert response.json()["mints"]
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_regtest, reason="regtest")
|
||||
@pytest.mark.asyncio
|
||||
async def test_send(wallet: Wallet):
|
||||
with TestClient(app) as client:
|
||||
@@ -58,6 +62,7 @@ async def test_send(wallet: Wallet):
|
||||
assert response.json()["balance"]
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_regtest, reason="regtest")
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_without_split(wallet: Wallet):
|
||||
with TestClient(app) as client:
|
||||
@@ -66,6 +71,7 @@ async def test_send_without_split(wallet: Wallet):
|
||||
assert response.json()["balance"]
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_regtest, reason="regtest")
|
||||
@pytest.mark.asyncio
|
||||
async def test_send_without_split_but_wrong_amount(wallet: Wallet):
|
||||
with TestClient(app) as client:
|
||||
@@ -73,6 +79,7 @@ async def test_send_without_split_but_wrong_amount(wallet: Wallet):
|
||||
assert response.status_code == 400
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_regtest, reason="regtest")
|
||||
@pytest.mark.asyncio
|
||||
async def test_pending():
|
||||
with TestClient(app) as client:
|
||||
@@ -80,6 +87,7 @@ async def test_pending():
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_regtest, reason="regtest")
|
||||
@pytest.mark.asyncio
|
||||
async def test_receive_all(wallet: Wallet):
|
||||
with TestClient(app) as client:
|
||||
@@ -89,6 +97,7 @@ async def test_receive_all(wallet: Wallet):
|
||||
assert response.json()["balance"]
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_regtest, reason="regtest")
|
||||
@pytest.mark.asyncio
|
||||
async def test_burn_all(wallet: Wallet):
|
||||
with TestClient(app) as client:
|
||||
@@ -99,6 +108,7 @@ async def test_burn_all(wallet: Wallet):
|
||||
assert response.json()["balance"]
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_regtest, reason="regtest")
|
||||
@pytest.mark.asyncio
|
||||
async def test_pay():
|
||||
with TestClient(app) as client:
|
||||
@@ -113,6 +123,7 @@ async def test_pay():
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_regtest, reason="regtest")
|
||||
@pytest.mark.asyncio
|
||||
async def test_lock():
|
||||
with TestClient(app) as client:
|
||||
@@ -120,6 +131,7 @@ async def test_lock():
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_regtest, reason="regtest")
|
||||
@pytest.mark.asyncio
|
||||
async def test_locks():
|
||||
with TestClient(app) as client:
|
||||
@@ -127,6 +139,7 @@ async def test_locks():
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_regtest, reason="regtest")
|
||||
@pytest.mark.asyncio
|
||||
async def test_invoices():
|
||||
with TestClient(app) as client:
|
||||
@@ -134,6 +147,7 @@ async def test_invoices():
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_regtest, reason="regtest")
|
||||
@pytest.mark.asyncio
|
||||
async def test_wallets():
|
||||
with TestClient(app) as client:
|
||||
@@ -141,6 +155,7 @@ async def test_wallets():
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_regtest, reason="regtest")
|
||||
@pytest.mark.asyncio
|
||||
async def test_info():
|
||||
with TestClient(app) as client:
|
||||
@@ -149,6 +164,7 @@ async def test_info():
|
||||
assert response.json()["version"]
|
||||
|
||||
|
||||
@pytest.mark.skipif(is_regtest, reason="regtest")
|
||||
@pytest.mark.asyncio
|
||||
async def test_flow(wallet: Wallet):
|
||||
with TestClient(app) as client:
|
||||
|
||||
@@ -15,6 +15,7 @@ from cashu.wallet.wallet import Wallet
|
||||
from cashu.wallet.wallet import Wallet as Wallet1
|
||||
from cashu.wallet.wallet import Wallet as Wallet2
|
||||
from tests.conftest import SERVER_ENDPOINT
|
||||
from tests.helpers import pay_if_regtest
|
||||
|
||||
|
||||
async def assert_err(f, msg):
|
||||
@@ -59,6 +60,7 @@ async def wallet2(mint):
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_htlc_secret(wallet1: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
preimage = "00000000000000000000000000000000"
|
||||
preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||
@@ -69,6 +71,7 @@ async def test_create_htlc_secret(wallet1: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
async def test_htlc_split(wallet1: Wallet, wallet2: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
preimage = "00000000000000000000000000000000"
|
||||
preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||
@@ -81,6 +84,7 @@ async def test_htlc_split(wallet1: Wallet, wallet2: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
async def test_htlc_redeem_with_preimage(wallet1: Wallet, wallet2: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
preimage = "00000000000000000000000000000000"
|
||||
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||
@@ -94,6 +98,7 @@ async def test_htlc_redeem_with_preimage(wallet1: Wallet, wallet2: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
async def test_htlc_redeem_with_wrong_preimage(wallet1: Wallet, wallet2: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
preimage = "00000000000000000000000000000000"
|
||||
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||
@@ -111,6 +116,7 @@ async def test_htlc_redeem_with_wrong_preimage(wallet1: Wallet, wallet2: Wallet)
|
||||
@pytest.mark.asyncio
|
||||
async def test_htlc_redeem_with_no_signature(wallet1: Wallet, wallet2: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
preimage = "00000000000000000000000000000000"
|
||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||
@@ -130,6 +136,7 @@ async def test_htlc_redeem_with_no_signature(wallet1: Wallet, wallet2: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
async def test_htlc_redeem_with_wrong_signature(wallet1: Wallet, wallet2: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
preimage = "00000000000000000000000000000000"
|
||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||
@@ -153,6 +160,7 @@ async def test_htlc_redeem_with_wrong_signature(wallet1: Wallet, wallet2: Wallet
|
||||
@pytest.mark.asyncio
|
||||
async def test_htlc_redeem_with_correct_signature(wallet1: Wallet, wallet2: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
preimage = "00000000000000000000000000000000"
|
||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||
@@ -174,6 +182,7 @@ async def test_htlc_redeem_hashlock_wrong_signature_timelock_correct_signature(
|
||||
wallet1: Wallet, wallet2: Wallet
|
||||
):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
preimage = "00000000000000000000000000000000"
|
||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||
@@ -207,6 +216,7 @@ async def test_htlc_redeem_hashlock_wrong_signature_timelock_wrong_signature(
|
||||
wallet1: Wallet, wallet2: Wallet
|
||||
):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
preimage = "00000000000000000000000000000000"
|
||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||
|
||||
@@ -16,6 +16,7 @@ from cashu.wallet.wallet import Wallet
|
||||
from cashu.wallet.wallet import Wallet as Wallet1
|
||||
from cashu.wallet.wallet import Wallet as Wallet2
|
||||
from tests.conftest import SERVER_ENDPOINT
|
||||
from tests.helpers import pay_if_regtest
|
||||
|
||||
|
||||
async def assert_err(f, msg):
|
||||
@@ -60,6 +61,7 @@ async def wallet2(mint):
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_p2pk_pubkey(wallet1: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
pubkey = await wallet1.create_p2pk_pubkey()
|
||||
PublicKey(bytes.fromhex(pubkey), raw=True)
|
||||
@@ -68,6 +70,7 @@ async def test_create_p2pk_pubkey(wallet1: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
async def test_p2pk(wallet1: Wallet, wallet2: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||
# p2pk test
|
||||
@@ -81,6 +84,7 @@ async def test_p2pk(wallet1: Wallet, wallet2: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
async def test_p2pk_sig_all(wallet1: Wallet, wallet2: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||
# p2pk test
|
||||
@@ -96,6 +100,7 @@ async def test_p2pk_sig_all(wallet1: Wallet, wallet2: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
async def test_p2pk_receive_with_wrong_private_key(wallet1: Wallet, wallet2: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # receiver side
|
||||
# sender side
|
||||
@@ -116,6 +121,7 @@ async def test_p2pk_short_locktime_receive_with_wrong_private_key(
|
||||
wallet1: Wallet, wallet2: Wallet
|
||||
):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # receiver side
|
||||
# sender side
|
||||
@@ -141,6 +147,7 @@ async def test_p2pk_short_locktime_receive_with_wrong_private_key(
|
||||
@pytest.mark.asyncio
|
||||
async def test_p2pk_locktime_with_refund_pubkey(wallet1: Wallet, wallet2: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # receiver side
|
||||
# sender side
|
||||
@@ -169,6 +176,7 @@ async def test_p2pk_locktime_with_refund_pubkey(wallet1: Wallet, wallet2: Wallet
|
||||
@pytest.mark.asyncio
|
||||
async def test_p2pk_locktime_with_wrong_refund_pubkey(wallet1: Wallet, wallet2: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
await wallet2.create_p2pk_pubkey() # receiver side
|
||||
# sender side
|
||||
@@ -204,6 +212,7 @@ async def test_p2pk_locktime_with_second_refund_pubkey(
|
||||
wallet1: Wallet, wallet2: Wallet
|
||||
):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey() # receiver side
|
||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # receiver side
|
||||
@@ -235,6 +244,7 @@ async def test_p2pk_locktime_with_second_refund_pubkey(
|
||||
@pytest.mark.asyncio
|
||||
async def test_p2pk_multisig_2_of_2(wallet1: Wallet, wallet2: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||
@@ -256,6 +266,7 @@ async def test_p2pk_multisig_2_of_2(wallet1: Wallet, wallet2: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
async def test_p2pk_multisig_duplicate_signature(wallet1: Wallet, wallet2: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||
@@ -279,6 +290,7 @@ async def test_p2pk_multisig_duplicate_signature(wallet1: Wallet, wallet2: Walle
|
||||
@pytest.mark.asyncio
|
||||
async def test_p2pk_multisig_quorum_not_met_1_of_2(wallet1: Wallet, wallet2: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||
@@ -299,6 +311,7 @@ async def test_p2pk_multisig_quorum_not_met_1_of_2(wallet1: Wallet, wallet2: Wal
|
||||
@pytest.mark.asyncio
|
||||
async def test_p2pk_multisig_quorum_not_met_2_of_3(wallet1: Wallet, wallet2: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||
@@ -323,6 +336,7 @@ async def test_p2pk_multisig_quorum_not_met_2_of_3(wallet1: Wallet, wallet2: Wal
|
||||
@pytest.mark.asyncio
|
||||
async def test_p2pk_multisig_with_duplicate_publickey(wallet1: Wallet, wallet2: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||
# p2pk test
|
||||
@@ -340,6 +354,7 @@ async def test_p2pk_multisig_with_wrong_first_private_key(
|
||||
wallet1: Wallet, wallet2: Wallet
|
||||
):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
await wallet1.create_p2pk_pubkey()
|
||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||
|
||||
@@ -12,6 +12,7 @@ from cashu.wallet.wallet import Wallet
|
||||
from cashu.wallet.wallet import Wallet as Wallet1
|
||||
from cashu.wallet.wallet import Wallet as Wallet2
|
||||
from tests.conftest import SERVER_ENDPOINT
|
||||
from tests.helpers import pay_if_regtest
|
||||
|
||||
|
||||
async def assert_err(f, msg: Union[str, CashuError]):
|
||||
@@ -147,6 +148,7 @@ async def test_generate_secrets_from_to(wallet3: Wallet):
|
||||
async def test_restore_wallet_after_mint(wallet3: Wallet):
|
||||
await reset_wallet_db(wallet3)
|
||||
invoice = await wallet3.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet3.mint(64, id=invoice.id)
|
||||
assert wallet3.balance == 64
|
||||
await reset_wallet_db(wallet3)
|
||||
@@ -177,6 +179,7 @@ async def test_restore_wallet_after_split_to_send(wallet3: Wallet):
|
||||
await reset_wallet_db(wallet3)
|
||||
|
||||
invoice = await wallet3.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet3.mint(64, id=invoice.id)
|
||||
assert wallet3.balance == 64
|
||||
|
||||
@@ -199,6 +202,7 @@ async def test_restore_wallet_after_send_and_receive(wallet3: Wallet, wallet2: W
|
||||
)
|
||||
await reset_wallet_db(wallet3)
|
||||
invoice = await wallet3.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet3.mint(64, id=invoice.id)
|
||||
assert wallet3.balance == 64
|
||||
|
||||
@@ -239,6 +243,7 @@ async def test_restore_wallet_after_send_and_self_receive(wallet3: Wallet):
|
||||
await reset_wallet_db(wallet3)
|
||||
|
||||
invoice = await wallet3.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet3.mint(64, id=invoice.id)
|
||||
assert wallet3.balance == 64
|
||||
|
||||
@@ -265,6 +270,7 @@ async def test_restore_wallet_after_send_twice(
|
||||
await reset_wallet_db(wallet3)
|
||||
|
||||
invoice = await wallet3.request_mint(2)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet3.mint(2, id=invoice.id)
|
||||
box.add(wallet3.proofs)
|
||||
assert wallet3.balance == 2
|
||||
@@ -319,6 +325,7 @@ async def test_restore_wallet_after_send_and_self_receive_nonquadratic_value(
|
||||
await reset_wallet_db(wallet3)
|
||||
|
||||
invoice = await wallet3.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet3.mint(64, id=invoice.id)
|
||||
box.add(wallet3.proofs)
|
||||
assert wallet3.balance == 64
|
||||
|
||||
Reference in New Issue
Block a user