Files
nutshell/tests/helpers.py
callebtc 6a0a370ba5 Mint: table locks (#566)
* clean up db

* db: table lock

* db.table_with_schema

* fix encrypt.py

* postgres nowait

* add timeout to lock

* melt quote state in db

* kinda working

* kinda working with postgres

* remove dispose

* getting there

* porperly clean up db for tests

* faster tests

* configure connection pooling

* try github with connection pool

* invoice dispatcher does not lock db

* fakewallet: pay_if_regtest waits

* pay fakewallet invoices

* add more

* faster

* slower

* pay_if_regtest async

* do not lock the invoice dispatcher

* test: do I get disk I/O errors if we disable the invoice_callback_dispatcher?

* fix fake so it workss without a callback dispatchert

* test on github

* readd tasks

* refactor

* increase time for lock invoice disatcher

* try avoiding a race

* remove task

* github actions: test regtest with postgres

* mint per module

* no connection pool for testing

* enable pool

* do not resend paid event

* reuse connection

* close db connections

* sessions

* enable debug

* dispose engine

* disable connection pool for tests

* enable connection pool for postgres only

* clean up shutdown routine

* remove wait for lightning fakewallet lightning invoice

* cancel invoice listener tasks on shutdown

* fakewallet conftest: decrease outgoing delay

* delay payment and set postgres only if needed

* disable fail fast for regtest

* clean up regtest.yml

* change order of tests_db.py

* row-specific mint_quote locking

* refactor

* fix lock statement

* refactor swap

* refactor

* remove psycopg2

* add connection string example to .env.example

* remove unnecessary pay

* shorter sleep in test_wallet_subscription_swap
2024-07-08 18:05:57 +02:00

198 lines
5.3 KiB
Python

import asyncio
import hashlib
import importlib
import json
import os
import random
import string
import time
from subprocess import PIPE, Popen, TimeoutExpired
from typing import List, Tuple, Union
from loguru import logger
from cashu.core.errors import CashuError
from cashu.core.settings import settings
async def assert_err(f, msg: Union[str, CashuError]):
"""Compute f() and expect an error message 'msg'."""
try:
await f
except Exception as exc:
error_message: str = str(exc.args[0])
if isinstance(msg, CashuError):
if msg.detail not in error_message:
raise Exception(
f"CashuError. Expected error: {msg.detail}, got: {error_message}"
)
return
if msg not in error_message:
raise Exception(f"Expected error: {msg}, got: {error_message}")
return
raise Exception(f"Expected error: {msg}, got no error")
async def assert_err_multiple(f, msgs: List[str]):
"""Compute f() and expect an error message 'msg'."""
try:
await f
except Exception as exc:
for msg in msgs:
if msg in str(exc.args[0]):
return
raise Exception(f"Expected error: {msgs}, got: {exc.args[0]}")
raise Exception(f"Expected error: {msgs}, got no error")
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_backend_bolt11_sat)
WALLET = wallet_class()
is_fake: bool = WALLET.__class__.__name__ == "FakeWallet"
is_regtest: bool = not is_fake
is_deprecated_api_only = settings.debug_mint_only_deprecated
is_github_actions = os.getenv("GITHUB_ACTIONS") == "true"
is_postgres = settings.mint_database.startswith("postgres")
SLEEP_TIME = 1 if not is_github_actions else 2
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)
async def pay_if_regtest(bolt11: str):
if is_regtest:
pay_real_invoice(bolt11)
if is_fake:
await asyncio.sleep(settings.fakewallet_delay_incoming_payment or 0)
await asyncio.sleep(0.1)