mirror of
https://github.com/aljazceru/nutshell.git
synced 2026-01-28 04:54:21 +01:00
Nutshell cleanup wishlist (#332)
* fix keys * fix tests * backwards compatible api upgrade * upgrade seems to work * fix tests * add deprecated api functions * add more tests of backwards compat * add test serialization for nut00 * remove a redundant test * move mint and melt to new api * mypy works * CI: mypy --check-untyped-defs * add deprecated router * add hints and remove logs * fix tests * cleanup * use new mint and melt endpoints * tests passing? * fix mypy * make format * make format * make format * commit * errors gone * save * adjust the API * store quotes in db * make mypy happy * add fakewallet settings * remove LIGHTNING=True and pass quote id for melt * format * tests passing * add CoreLightningRestWallet * add macaroon loader * add correct config * preimage -> proof * move wallet.status() to cli.helpers.print_status() * remove statuses from tests * remove * make format * Use httpx in deprecated wallet * fix cln interface * create invoice before quote * internal transactions and deprecated api testing * fix tests * add deprecated API tests * fastapi type hints break things * fix duplicate wallet error * make format * update poetry in CI to 1.7.1 * precommit restore * remove bolt11 * oops * default poetry * store fee reserve for melt quotes and refactor melt() * works? * make format * test * finally * fix deprecated models * rename v1 endpoints to bolt11 * raise restore and check to v1, bump version to 0.15.0 * add version byte to keyset id * remove redundant fields in json * checks * generate bip32 keyset wip * migrate old keysets * load duplicate keys * duplicate old keysets * revert router changes * add deprecated /check and /restore endpoints * try except invalidate * parse unit from derivation path, adjust keyset id calculation with bytes * remove keyest id from functions again and rely on self.keyset_id * mosts tests work * mint loads multiple derivation paths * make format * properly print units * fix tests * wallet works with multiple units * add strike wallet and choose backend dynamically * fix mypy * add get_payment_quote to lightning backends * make format * fix startup * fix lnbitswallet * fix tests * LightningWallet -> LightningBackend * remove comments * make format * remove msat conversion * add Amount type * fix regtest * use melt_quote as argument for pay_invoice * test old api * fees in sats * fix deprecated fees * fixes * print balance correctly * internally index keyset response by int * add pydantic validation to input models * add timestamps to mint db * store timestamps for invoices, promises, proofs_used * fix wallet migration * rotate keys correctly for testing * remove print * update latest keyset * fix tests * fix test * make format * make format with correct black version * remove nsat and cheese * test against deprecated mint * fix tests? * actually use env var * mint run with env vars * moar test * cleanup * simplify tests, load all keys * try out testing with internal invoices * fix internal melt test * fix test * deprecated checkfees expects appropriate fees * adjust comment * drop lightning table * split migration for testing for now, remove it later * remove unused lightning table * skip_private_key -> skip_db_read * throw error on migration error * reorder * fix migrations * fix lnbits fee return value negative * fix typo * comments * add type * make format * split must use correct amount * fix tests * test deprecated api with internal/external melts * do not split if not necessary * refactor * fix test * make format with new black * cleanup and add comments * add quote state check endpoints * fix deprecated wallet response * split -> swap endpoint * make format * add expiry to quotes, get quote endpoints, and adjust to nut review comments * allow overpayment of melt * add lightning wallet tests * commiting to save * fix tests a bit * make format * remove comments * get mint info * check_spendable default False, and return payment quote checking id * make format * bump version in pyproject * update to /v1/checkstate * make format * fix mint api checks * return witness on /v1/checkstate * no failfast * try fail-fast: false in ci.yaml * fix db lookup * clean up literals
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import asyncio
|
||||
import multiprocessing
|
||||
import os
|
||||
import shutil
|
||||
@@ -9,35 +10,50 @@ import pytest_asyncio
|
||||
import uvicorn
|
||||
from uvicorn import Config, Server
|
||||
|
||||
from cashu.core.base import Method, Unit
|
||||
from cashu.core.db import Database
|
||||
from cashu.core.migrations import migrate_databases
|
||||
from cashu.core.settings import settings
|
||||
from cashu.lightning.fake import FakeWallet
|
||||
from cashu.mint import migrations as migrations_mint
|
||||
from cashu.mint.crud import LedgerCrud
|
||||
from cashu.mint.crud import LedgerCrudSqlite
|
||||
from cashu.mint.ledger import Ledger
|
||||
|
||||
SERVER_PORT = 3337
|
||||
SERVER_ENDPOINT = f"http://localhost:{SERVER_PORT}"
|
||||
|
||||
settings.debug = True
|
||||
settings.debug = False
|
||||
settings.cashu_dir = "./test_data/"
|
||||
settings.mint_host = "localhost"
|
||||
settings.mint_port = SERVER_PORT
|
||||
settings.mint_host = "0.0.0.0"
|
||||
settings.mint_listen_port = SERVER_PORT
|
||||
settings.mint_url = SERVER_ENDPOINT
|
||||
settings.lightning = True
|
||||
settings.tor = False
|
||||
settings.wallet_unit = "sat"
|
||||
settings.mint_lightning_backend = settings.mint_lightning_backend or "FakeWallet"
|
||||
settings.fakewallet_brr = True
|
||||
settings.fakewallet_delay_payment = False
|
||||
settings.fakewallet_stochastic_invoice = False
|
||||
settings.mint_database = "./test_data/test_mint"
|
||||
settings.mint_derivation_path = "0/0/0/0"
|
||||
settings.mint_derivation_path = "m/0'/0'/0'"
|
||||
settings.mint_derivation_path_list = []
|
||||
settings.mint_private_key = "TEST_PRIVATE_KEY"
|
||||
settings.mint_max_balance = 0
|
||||
|
||||
assert "test" in settings.cashu_dir
|
||||
shutil.rmtree(settings.cashu_dir, ignore_errors=True)
|
||||
Path(settings.cashu_dir).mkdir(parents=True, exist_ok=True)
|
||||
|
||||
from cashu.mint.startup import lightning_backend # noqa
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def event_loop():
|
||||
policy = asyncio.get_event_loop_policy()
|
||||
loop = policy.new_event_loop()
|
||||
yield loop
|
||||
loop.close()
|
||||
|
||||
|
||||
class UvicornServer(multiprocessing.Process):
|
||||
def __init__(self, config: Config):
|
||||
@@ -52,33 +68,7 @@ class UvicornServer(multiprocessing.Process):
|
||||
self.server.run()
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def ledger():
|
||||
async def start_mint_init(ledger: Ledger):
|
||||
await migrate_databases(ledger.db, migrations_mint)
|
||||
if settings.mint_cache_secrets:
|
||||
await ledger.load_used_proofs()
|
||||
await ledger.init_keysets()
|
||||
|
||||
database_name = "mint"
|
||||
|
||||
if not settings.mint_database.startswith("postgres"):
|
||||
# clear sqlite database
|
||||
db_file = os.path.join(settings.mint_database, database_name + ".sqlite3")
|
||||
if os.path.exists(db_file):
|
||||
os.remove(db_file)
|
||||
|
||||
ledger = Ledger(
|
||||
db=Database(database_name, settings.mint_database),
|
||||
seed=settings.mint_private_key,
|
||||
derivation_path=settings.mint_derivation_path,
|
||||
lightning=FakeWallet(),
|
||||
crud=LedgerCrud(),
|
||||
)
|
||||
await start_mint_init(ledger)
|
||||
yield ledger
|
||||
|
||||
|
||||
# # This fixture is used for tests that require API access to the mint
|
||||
@pytest.fixture(autouse=True, scope="session")
|
||||
def mint():
|
||||
config = uvicorn.Config(
|
||||
@@ -92,3 +82,33 @@ def mint():
|
||||
time.sleep(1)
|
||||
yield server
|
||||
server.stop()
|
||||
|
||||
|
||||
# This fixture is used for all other tests
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def ledger():
|
||||
async def start_mint_init(ledger: Ledger):
|
||||
await migrate_databases(ledger.db, migrations_mint)
|
||||
if settings.mint_cache_secrets:
|
||||
await ledger.load_used_proofs()
|
||||
await ledger.init_keysets()
|
||||
|
||||
if not settings.mint_database.startswith("postgres"):
|
||||
# clear sqlite database
|
||||
db_file = os.path.join(settings.mint_database, "mint.sqlite3")
|
||||
if os.path.exists(db_file):
|
||||
os.remove(db_file)
|
||||
|
||||
backends = {
|
||||
Method.bolt11: {Unit.sat: lightning_backend},
|
||||
}
|
||||
ledger = Ledger(
|
||||
db=Database("mint", settings.mint_database),
|
||||
seed=settings.mint_private_key,
|
||||
derivation_path=settings.mint_derivation_path,
|
||||
backends=backends,
|
||||
crud=LedgerCrudSqlite(),
|
||||
)
|
||||
await start_mint_init(ledger)
|
||||
yield ledger
|
||||
print("teardown")
|
||||
|
||||
@@ -29,7 +29,7 @@ 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
|
||||
|
||||
is_deprecated_api_only = settings.debug_mint_only_deprecated
|
||||
|
||||
docker_lightning_cli = [
|
||||
"docker",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import asyncio
|
||||
import base64
|
||||
import json
|
||||
from typing import Tuple
|
||||
|
||||
import pytest
|
||||
@@ -27,8 +29,9 @@ def get_bolt11_and_invoice_id_from_invoice_command(output: str) -> Tuple[str, st
|
||||
|
||||
|
||||
async def init_wallet():
|
||||
settings.debug = False
|
||||
wallet = await Wallet.with_db(
|
||||
url=settings.mint_host,
|
||||
url=settings.mint_url,
|
||||
db="test_data/test_cli_wallet",
|
||||
name="wallet",
|
||||
)
|
||||
@@ -56,7 +59,7 @@ def test_info_with_mint(cli_prefix):
|
||||
[*cli_prefix, "info", "--mint"],
|
||||
)
|
||||
assert result.exception is None
|
||||
print("INFO -M")
|
||||
print("INFO --MINT")
|
||||
print(result.output)
|
||||
assert "Mint name" in result.output
|
||||
assert result.exit_code == 0
|
||||
@@ -69,7 +72,7 @@ def test_info_with_mnemonic(cli_prefix):
|
||||
[*cli_prefix, "info", "--mnemonic"],
|
||||
)
|
||||
assert result.exception is None
|
||||
print("INFO -M")
|
||||
print("INFO --MNEMONIC")
|
||||
print(result.output)
|
||||
assert "Mnemonic" in result.output
|
||||
assert result.exit_code == 0
|
||||
@@ -177,7 +180,7 @@ def test_send(mint, cli_prefix):
|
||||
[*cli_prefix, "send", "10"],
|
||||
)
|
||||
assert result.exception is None
|
||||
print(result.output)
|
||||
print("test_send", result.output)
|
||||
token_str = result.output.split("\n")[0]
|
||||
assert "cashuA" in token_str, "output does not have a token"
|
||||
token = TokenV3.deserialize(token_str)
|
||||
@@ -191,7 +194,7 @@ def test_send_with_dleq(mint, cli_prefix):
|
||||
[*cli_prefix, "send", "10", "--dleq"],
|
||||
)
|
||||
assert result.exception is None
|
||||
print(result.output)
|
||||
print("test_send_with_dleq", result.output)
|
||||
token_str = result.output.split("\n")[0]
|
||||
assert "cashuA" in token_str, "output does not have a token"
|
||||
token = TokenV3.deserialize(token_str)
|
||||
@@ -205,7 +208,7 @@ def test_send_legacy(mint, cli_prefix):
|
||||
[*cli_prefix, "send", "10", "--legacy"],
|
||||
)
|
||||
assert result.exception is None
|
||||
print(result.output)
|
||||
print("test_send_legacy", result.output)
|
||||
# this is the legacy token in the output
|
||||
token_str = result.output.split("\n")[4]
|
||||
assert token_str.startswith("eyJwcm9v"), "output is not as expected"
|
||||
@@ -219,7 +222,7 @@ def test_send_without_split(mint, cli_prefix):
|
||||
)
|
||||
assert result.exception is None
|
||||
print("SEND")
|
||||
print(result.output)
|
||||
print("test_send_without_split", result.output)
|
||||
assert "cashuA" in result.output, "output does not have a token"
|
||||
|
||||
|
||||
@@ -234,12 +237,7 @@ def test_send_without_split_but_wrong_amount(mint, cli_prefix):
|
||||
|
||||
def test_receive_tokenv3(mint, cli_prefix):
|
||||
runner = CliRunner()
|
||||
token = (
|
||||
"cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjFjQ05JQVoyWC93MSIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogIld6TEF2VW53SDlRaFYwQU1rMy1oYWciLC"
|
||||
"AiQyI6ICIwMmZlMzUxYjAyN2FlMGY1ZDkyN2U2ZjFjMTljMjNjNTc3NzRhZTI2M2UyOGExN2E2MTUxNjY1ZjU3NWNhNjMyNWMifSwgeyJpZCI6ICIxY0NOSUFaMlgvdzEiLCAiYW"
|
||||
"1vdW50IjogOCwgInNlY3JldCI6ICJDamFTeTcyR2dVOGwzMGV6bE5zZnVBIiwgIkMiOiAiMDNjMzM0OTJlM2ZlNjI4NzFhMWEzMDhiNWUyYjVhZjBkNWI1Mjk5YzI0YmVkNDI2Zj"
|
||||
"Q1YzZmNDg5N2QzZjc4NGQ5In1dLCAibWludCI6ICJodHRwOi8vbG9jYWxob3N0OjMzMzcifV19"
|
||||
)
|
||||
token = "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjAwOWExZjI5MzI1M2U0MWUiLCAiYW1vdW50IjogMiwgInNlY3JldCI6ICI0NzlkY2E0MzUzNzU4MTM4N2Q1ODllMDU1MGY0Y2Q2MjFmNjE0MDM1MGY5M2Q4ZmI1OTA2YjJlMGRiNmRjYmI3IiwgIkMiOiAiMDM1MGQ0ZmI0YzdiYTMzNDRjMWRjYWU1ZDExZjNlNTIzZGVkOThmNGY4ODdkNTQwZmYyMDRmNmVlOWJjMjkyZjQ1In0sIHsiaWQiOiAiMDA5YTFmMjkzMjUzZTQxZSIsICJhbW91bnQiOiA4LCAic2VjcmV0IjogIjZjNjAzNDgwOGQyNDY5N2IyN2YxZTEyMDllNjdjNjVjNmE2MmM2Zjc3NGI4NWVjMGQ5Y2Y3MjE0M2U0NWZmMDEiLCAiQyI6ICIwMjZkNDlhYTE0MmFlNjM1NWViZTJjZGQzYjFhOTdmMjE1MDk2NTlkMDE3YWU0N2FjNDY3OGE4NWVkY2E4MGMxYmQifV0sICJtaW50IjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzNyJ9XX0=" # noqa
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[
|
||||
@@ -258,12 +256,29 @@ def test_receive_tokenv3_no_mint(mint, cli_prefix):
|
||||
# where the mint URL is not in the token therefore, we need to know the mint keyset
|
||||
# already and have the mint URL in the db
|
||||
runner = CliRunner()
|
||||
token = (
|
||||
"cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjFjQ05JQVoyWC93MSIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogIi1oM0ZXMFFoX1FYLW9ac1V2c0RuNlEiLC"
|
||||
"AiQyI6ICIwMzY5Mzc4MzdlYjg5ZWI4NjMyNWYwOWUyOTIxMWQxYTI4OTRlMzQ2YmM1YzQwZTZhMThlNTk5ZmVjNjEwOGRmMGIifSwgeyJpZCI6ICIxY0NOSUFaMlgvdzEiLCAiYW"
|
||||
"1vdW50IjogOCwgInNlY3JldCI6ICI3d0VhNUgzZGhSRGRNZl94c1k3c3JnIiwgIkMiOiAiMDJiZmZkM2NlZDkxNjUyMzcxMDg2NjQxMzJiMjgxYjBhZjY1ZTNlZWVkNTY3MmFkZj"
|
||||
"M0Y2VhNzE5ODhhZWM1NWI1In1dfV19"
|
||||
)
|
||||
token_dict = {
|
||||
"token": [
|
||||
{
|
||||
"proofs": [
|
||||
{
|
||||
"id": "009a1f293253e41e",
|
||||
"amount": 2,
|
||||
"secret": "ea3420987e1ecd71de58e4ff00e8a94d1f1f9333dad98e923e3083d21bf314e2",
|
||||
"C": "0204eb99cf27105b4de4029478376d6f71e9e3d5af1cc28a652c028d1bcd6537cc",
|
||||
},
|
||||
{
|
||||
"id": "009a1f293253e41e",
|
||||
"amount": 8,
|
||||
"secret": "3447975db92f43b269290e05b91805df7aa733f622e55d885a2cab78e02d4a72",
|
||||
"C": "0286c78750d414bc067178cbac0f3551093cea47d213ebf356899c972448ee6255",
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
token = "cashuA" + base64.b64encode(json.dumps(token_dict).encode()).decode()
|
||||
print("RECEIVE")
|
||||
print(token)
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[
|
||||
@@ -273,18 +288,37 @@ def test_receive_tokenv3_no_mint(mint, cli_prefix):
|
||||
],
|
||||
)
|
||||
assert result.exception is None
|
||||
print("RECEIVE")
|
||||
print(result.output)
|
||||
|
||||
|
||||
def test_receive_tokenv2(mint, cli_prefix):
|
||||
runner = CliRunner()
|
||||
token = (
|
||||
"eyJwcm9vZnMiOiBbeyJpZCI6ICIxY0NOSUFaMlgvdzEiLCAiYW1vdW50IjogMiwgInNlY3JldCI6ICJhUmREbzlFdW9yZUVfOW90enRNVVpnIiwgIkMiOiAiMDNhMzY5ZmUy"
|
||||
"N2IxYmVmOTg4MzA3NDQyN2RjMzc1NmU0NThlMmMwYjQ1NWMwYmVmZGM4ZjVmNTA3YmM5MGQxNmU3In0sIHsiaWQiOiAiMWNDTklBWjJYL3cxIiwgImFtb3VudCI6IDgsICJzZWNy"
|
||||
"ZXQiOiAiTEZQbFp6Ui1MWHFfYXFDMGhUeDQyZyIsICJDIjogIjAzNGNiYzQxYWY0ODIxMGFmNjVmYjVjOWIzOTNkMjhmMmQ5ZDZhOWE5MzI2YmI3MzQ2YzVkZmRmMTU5MDk1MzI2"
|
||||
"YyJ9XSwgIm1pbnRzIjogW3sidXJsIjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzNyIsICJpZHMiOiBbIjFjQ05JQVoyWC93MSJdfV19"
|
||||
)
|
||||
token_dict = {
|
||||
"proofs": [
|
||||
{
|
||||
"id": "009a1f293253e41e",
|
||||
"amount": 2,
|
||||
"secret": (
|
||||
"a1efb610726b342aec209375397fee86a0b88732779ce218e99132f9a975db2a"
|
||||
),
|
||||
"C": (
|
||||
"03057e5fe352bac785468ffa51a1ecf0f75af24d2d27ab1fd00164672a417d9523"
|
||||
),
|
||||
},
|
||||
{
|
||||
"id": "009a1f293253e41e",
|
||||
"amount": 8,
|
||||
"secret": (
|
||||
"b065a17938bc79d6224dc381873b8b7f3a46267e8b00d9ce59530354d9d81ae4"
|
||||
),
|
||||
"C": (
|
||||
"021e83773f5eb66f837a5721a067caaa8d7018ef0745b4302f4e2c6cac8806dc69"
|
||||
),
|
||||
},
|
||||
],
|
||||
"mints": [{"url": "http://localhost:3337", "ids": ["009a1f293253e41e"]}],
|
||||
}
|
||||
token = base64.b64encode(json.dumps(token_dict).encode()).decode()
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[*cli_prefix, "receive", token],
|
||||
@@ -296,11 +330,25 @@ def test_receive_tokenv2(mint, cli_prefix):
|
||||
|
||||
def test_receive_tokenv1(mint, cli_prefix):
|
||||
runner = CliRunner()
|
||||
token = (
|
||||
"W3siaWQiOiAiMWNDTklBWjJYL3cxIiwgImFtb3VudCI6IDIsICJzZWNyZXQiOiAiRnVsc2dzMktQV1FMcUlLX200SzgwQSIsICJDIjogIjAzNTc4OThlYzlhMjIxN2VhYWIx"
|
||||
"ZDc3YmM1Mzc2OTUwMjJlMjU2YTljMmMwNjc0ZDJlM2FiM2JiNGI0ZDMzMWZiMSJ9LCB7ImlkIjogIjFjQ05JQVoyWC93MSIsICJhbW91bnQiOiA4LCAic2VjcmV0IjogInJlRDBD"
|
||||
"azVNS2xBTUQ0dWk2OEtfbEEiLCAiQyI6ICIwMjNkODNkNDE0MDU0NWQ1NTg4NjUyMzU5YjJhMjFhODljODY1ZGIzMzAyZTkzMTZkYTM5NjA0YTA2ZDYwYWQzOGYifV0="
|
||||
)
|
||||
token_dict = [
|
||||
{
|
||||
"id": "009a1f293253e41e",
|
||||
"amount": 2,
|
||||
"secret": (
|
||||
"bc0360c041117969ef7b8add48d0981c669619aa5743cccce13d4a771c9e164d"
|
||||
),
|
||||
"C": "026fd492f933e9240f36fb2559a7327f47b3441b895a5f8f0b1d6825fee73438f0",
|
||||
},
|
||||
{
|
||||
"id": "009a1f293253e41e",
|
||||
"amount": 8,
|
||||
"secret": (
|
||||
"cf83bd8df35bb104d3818511c1653e9ebeb2b645a36fd071b2229aa2c3044acd"
|
||||
),
|
||||
"C": "0279606f3dfd7784757c6320b17e1bf2211f284318814c12bfaa40680e017abd34",
|
||||
},
|
||||
]
|
||||
token = base64.b64encode(json.dumps(token_dict).encode()).decode()
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
[*cli_prefix, "receive", token],
|
||||
|
||||
@@ -9,7 +9,7 @@ def test_get_output_split():
|
||||
assert amount_split(13) == [1, 4, 8]
|
||||
|
||||
|
||||
def test_tokenv3_get_amount():
|
||||
def test_tokenv3_deserialize_get_attributes():
|
||||
token_str = (
|
||||
"cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIkplaFpMVTZuQ3BSZCIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogIjBFN2lDazRkVmxSZjVQRjFnNFpWMnci"
|
||||
"LCAiQyI6ICIwM2FiNTgwYWQ5NTc3OGVkNTI5NmY4YmVlNjU1ZGJkN2Q2NDJmNWQzMmRlOGUyNDg0NzdlMGI0ZDZhYTg2M2ZjZDUifSwgeyJpZCI6ICJKZWhaTFU2bkNwUmQiLCAiYW"
|
||||
@@ -18,16 +18,6 @@ def test_tokenv3_get_amount():
|
||||
)
|
||||
token = TokenV3.deserialize(token_str)
|
||||
assert token.get_amount() == 10
|
||||
|
||||
|
||||
def test_tokenv3_get_proofs():
|
||||
token_str = (
|
||||
"cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIkplaFpMVTZuQ3BSZCIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogIjBFN2lDazRkVmxSZjVQRjFnNFpWMnci"
|
||||
"LCAiQyI6ICIwM2FiNTgwYWQ5NTc3OGVkNTI5NmY4YmVlNjU1ZGJkN2Q2NDJmNWQzMmRlOGUyNDg0NzdlMGI0ZDZhYTg2M2ZjZDUifSwgeyJpZCI6ICJKZWhaTFU2bkNwUmQiLCAiYW"
|
||||
"1vdW50IjogOCwgInNlY3JldCI6ICJzNklwZXh3SGNxcXVLZDZYbW9qTDJnIiwgIkMiOiAiMDIyZDAwNGY5ZWMxNmE1OGFkOTAxNGMyNTliNmQ2MTRlZDM2ODgyOWYwMmMzODc3M2M0"
|
||||
"NzIyMWY0OTYxY2UzZjIzIn1dLCAibWludCI6ICJodHRwOi8vbG9jYWxob3N0OjMzMzgifV19"
|
||||
)
|
||||
token = TokenV3.deserialize(token_str)
|
||||
assert len(token.get_proofs()) == 2
|
||||
|
||||
|
||||
@@ -117,6 +107,43 @@ def test_tokenv3_deserialize_with_memo():
|
||||
assert token.memo == "Test memo"
|
||||
|
||||
|
||||
def test_serialize_example_token_nut00():
|
||||
token_dict = {
|
||||
"token": [
|
||||
{
|
||||
"mint": "https://8333.space:3338",
|
||||
"proofs": [
|
||||
{
|
||||
"id": "9bb9d58392cd823e",
|
||||
"amount": 2,
|
||||
"secret": "EhpennC9qB3iFlW8FZ_pZw",
|
||||
"C": "02c020067db727d586bc3183aecf97fcb800c3f4cc4759f69c626c9db5d8f5b5d4",
|
||||
},
|
||||
{
|
||||
"id": "9bb9d58392cd823e",
|
||||
"amount": 8,
|
||||
"secret": "TmS6Cv0YT5PU_5ATVKnukw",
|
||||
"C": "02ac910bef28cbe5d7325415d5c263026f15f9b967a079ca9779ab6e5c2db133a7",
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
"memo": "Thank you.",
|
||||
}
|
||||
tokenObj = TokenV3.parse_obj(token_dict)
|
||||
assert (
|
||||
tokenObj.serialize()
|
||||
== "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjliYjlkNTgzOTJjZDg"
|
||||
"yM2UiLCAiYW1vdW50IjogMiwgInNlY3JldCI6ICJFaHBlbm5DOXFCM2lGbFc4Rlpf"
|
||||
"cFp3IiwgIkMiOiAiMDJjMDIwMDY3ZGI3MjdkNTg2YmMzMTgzYWVjZjk3ZmNiODAwY"
|
||||
"zNmNGNjNDc1OWY2OWM2MjZjOWRiNWQ4ZjViNWQ0In0sIHsiaWQiOiAiOWJiOWQ1OD"
|
||||
"M5MmNkODIzZSIsICJhbW91bnQiOiA4LCAic2VjcmV0IjogIlRtUzZDdjBZVDVQVV8"
|
||||
"1QVRWS251a3ciLCAiQyI6ICIwMmFjOTEwYmVmMjhjYmU1ZDczMjU0MTVkNWMyNjMw"
|
||||
"MjZmMTVmOWI5NjdhMDc5Y2E5Nzc5YWI2ZTVjMmRiMTMzYTcifV0sICJtaW50IjogI"
|
||||
"mh0dHBzOi8vODMzMy5zcGFjZTozMzM4In1dLCAibWVtbyI6ICJUaGFuayB5b3UuIn0="
|
||||
)
|
||||
|
||||
|
||||
def test_calculate_number_of_blank_outputs():
|
||||
# Example from NUT-08 specification.
|
||||
fee_reserve_sat = 1000
|
||||
|
||||
@@ -2,11 +2,12 @@ from typing import List
|
||||
|
||||
import pytest
|
||||
|
||||
from cashu.core.base import BlindedMessage, Proof
|
||||
from cashu.core.base import BlindedMessage, PostMintQuoteRequest, Proof
|
||||
from cashu.core.crypto.b_dhke import step1_alice
|
||||
from cashu.core.helpers import calculate_number_of_blank_outputs
|
||||
from cashu.core.settings import settings
|
||||
from cashu.mint.ledger import Ledger
|
||||
from tests.helpers import pay_if_regtest
|
||||
|
||||
|
||||
async def assert_err(f, msg):
|
||||
@@ -29,11 +30,11 @@ async def test_pubkeys(ledger: Ledger):
|
||||
assert ledger.keyset.public_keys
|
||||
assert (
|
||||
ledger.keyset.public_keys[1].serialize().hex()
|
||||
== "03190ebc0c3e2726a5349904f572a2853ea021b0128b269b8b6906501d262edaa8"
|
||||
== "02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104"
|
||||
)
|
||||
assert (
|
||||
ledger.keyset.public_keys[2 ** (settings.max_order - 1)].serialize().hex()
|
||||
== "032dc008b88b85fdc2301a499bfaaef774c191a6307d8c9434838fc2eaa2e48d51"
|
||||
== "023c84c0895cc0e827b348ea0a62951ca489a5e436f3ea7545f3c1d5f1bea1c866"
|
||||
)
|
||||
|
||||
|
||||
@@ -42,19 +43,30 @@ async def test_privatekeys(ledger: Ledger):
|
||||
assert ledger.keyset.private_keys
|
||||
assert (
|
||||
ledger.keyset.private_keys[1].serialize()
|
||||
== "67de62e1bf8b5ccf88dbad6768b7d13fa0f41433b0a89caf915039505f2e00a7"
|
||||
== "8300050453f08e6ead1296bb864e905bd46761beed22b81110fae0751d84604d"
|
||||
)
|
||||
assert (
|
||||
ledger.keyset.private_keys[2 ** (settings.max_order - 1)].serialize()
|
||||
== "3b1340c703b02028a11025302d2d9e68d2a6dd721ab1a2770f0942d15eacb8d0"
|
||||
== "b0477644cb3d82ffcc170bc0a76e0409727232e87c5ae51d64a259936228c7be"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_keysets(ledger: Ledger):
|
||||
assert len(ledger.keysets.keysets)
|
||||
assert len(ledger.keysets.get_ids())
|
||||
assert ledger.keyset.id == "1cCNIAZ2X/w1"
|
||||
assert len(ledger.keysets)
|
||||
assert len(list(ledger.keysets.keys()))
|
||||
assert ledger.keyset.id == "009a1f293253e41e"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_keysets_backwards_compatibility_pre_v0_15(ledger: Ledger):
|
||||
"""Backwards compatibility test for keysets pre v0.15.0
|
||||
We expect two instances of the same keyset but with different IDs.
|
||||
First one is the new hex ID, second one is the old base64 ID.
|
||||
"""
|
||||
assert len(ledger.keysets) == 2
|
||||
assert list(ledger.keysets.keys()) == ["009a1f293253e41e", "eGnEWtdJ0PIM"]
|
||||
assert ledger.keyset.id == "009a1f293253e41e"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -66,33 +78,37 @@ async def test_get_keyset(ledger: Ledger):
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mint(ledger: Ledger):
|
||||
invoice, id = await ledger.request_mint(8)
|
||||
quote = await ledger.mint_quote(PostMintQuoteRequest(amount=8, unit="sat"))
|
||||
pay_if_regtest(quote.request)
|
||||
blinded_messages_mock = [
|
||||
BlindedMessage(
|
||||
amount=8,
|
||||
B_="02634a2c2b34bec9e8a4aba4361f6bf202d7fa2365379b0840afe249a7a9d71239",
|
||||
id="009a1f293253e41e",
|
||||
)
|
||||
]
|
||||
promises = await ledger.mint(blinded_messages_mock, id=id)
|
||||
promises = await ledger.mint(outputs=blinded_messages_mock, quote_id=quote.quote)
|
||||
assert len(promises)
|
||||
assert promises[0].amount == 8
|
||||
assert (
|
||||
promises[0].C_
|
||||
== "037074c4f53e326ee14ed67125f387d160e0e729351471b69ad41f7d5d21071e15"
|
||||
== "031422eeffb25319e519c68de000effb294cb362ef713a7cf4832cea7b0452ba6e"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mint_invalid_blinded_message(ledger: Ledger):
|
||||
invoice, id = await ledger.request_mint(8)
|
||||
quote = await ledger.mint_quote(PostMintQuoteRequest(amount=8, unit="sat"))
|
||||
pay_if_regtest(quote.request)
|
||||
blinded_messages_mock_invalid_key = [
|
||||
BlindedMessage(
|
||||
amount=8,
|
||||
B_="02634a2c2b34bec9e8a4aba4361f6bff02d7fa2365379b0840afe249a7a9d71237",
|
||||
id="009a1f293253e41e",
|
||||
)
|
||||
]
|
||||
await assert_err(
|
||||
ledger.mint(blinded_messages_mock_invalid_key, id=id),
|
||||
ledger.mint(outputs=blinded_messages_mock_invalid_key, quote_id=quote.quote),
|
||||
"invalid public key",
|
||||
)
|
||||
|
||||
@@ -103,14 +119,16 @@ async def test_generate_promises(ledger: Ledger):
|
||||
BlindedMessage(
|
||||
amount=8,
|
||||
B_="02634a2c2b34bec9e8a4aba4361f6bf202d7fa2365379b0840afe249a7a9d71239",
|
||||
id="009a1f293253e41e",
|
||||
)
|
||||
]
|
||||
promises = await ledger._generate_promises(blinded_messages_mock)
|
||||
assert (
|
||||
promises[0].C_
|
||||
== "037074c4f53e326ee14ed67125f387d160e0e729351471b69ad41f7d5d21071e15"
|
||||
== "031422eeffb25319e519c68de000effb294cb362ef713a7cf4832cea7b0452ba6e"
|
||||
)
|
||||
assert promises[0].amount == 8
|
||||
assert promises[0].id == "009a1f293253e41e"
|
||||
|
||||
# DLEQ proof present
|
||||
assert promises[0].dleq
|
||||
@@ -118,6 +136,55 @@ async def test_generate_promises(ledger: Ledger):
|
||||
assert promises[0].dleq.e
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_promises_deprecated_keyset_id(ledger: Ledger):
|
||||
blinded_messages_mock = [
|
||||
BlindedMessage(
|
||||
amount=8,
|
||||
B_="02634a2c2b34bec9e8a4aba4361f6bf202d7fa2365379b0840afe249a7a9d71239",
|
||||
id="eGnEWtdJ0PIM",
|
||||
)
|
||||
]
|
||||
promises = await ledger._generate_promises(blinded_messages_mock)
|
||||
assert (
|
||||
promises[0].C_
|
||||
== "031422eeffb25319e519c68de000effb294cb362ef713a7cf4832cea7b0452ba6e"
|
||||
)
|
||||
assert promises[0].amount == 8
|
||||
assert promises[0].id == "eGnEWtdJ0PIM"
|
||||
|
||||
# DLEQ proof present
|
||||
assert promises[0].dleq
|
||||
assert promises[0].dleq.s
|
||||
assert promises[0].dleq.e
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_promises_keyset_backwards_compatibility_pre_v0_15(
|
||||
ledger: Ledger,
|
||||
):
|
||||
"""Backwards compatibility test for keysets pre v0.15.0
|
||||
We want to generate promises using the old keyset ID.
|
||||
We expect the promise to have the old base64 ID.
|
||||
"""
|
||||
blinded_messages_mock = [
|
||||
BlindedMessage(
|
||||
amount=8,
|
||||
B_="02634a2c2b34bec9e8a4aba4361f6bf202d7fa2365379b0840afe249a7a9d71239",
|
||||
id="eGnEWtdJ0PIM",
|
||||
)
|
||||
]
|
||||
promises = await ledger._generate_promises(
|
||||
blinded_messages_mock, keyset=ledger.keysets["eGnEWtdJ0PIM"]
|
||||
)
|
||||
assert (
|
||||
promises[0].C_
|
||||
== "031422eeffb25319e519c68de000effb294cb362ef713a7cf4832cea7b0452ba6e"
|
||||
)
|
||||
assert promises[0].amount == 8
|
||||
assert promises[0].id == "eGnEWtdJ0PIM"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_generate_change_promises(ledger: Ledger):
|
||||
# Example slightly adapted from NUT-08 because we want to ensure the dynamic change
|
||||
@@ -125,7 +192,7 @@ async def test_generate_change_promises(ledger: Ledger):
|
||||
invoice_amount = 100_000
|
||||
fee_reserve = 2_000
|
||||
total_provided = invoice_amount + fee_reserve
|
||||
actual_fee_msat = 100_000
|
||||
actual_fee = 100
|
||||
|
||||
expected_returned_promises = 7 # Amounts = [4, 8, 32, 64, 256, 512, 1024]
|
||||
expected_returned_fees = 1900
|
||||
@@ -133,11 +200,16 @@ async def test_generate_change_promises(ledger: Ledger):
|
||||
n_blank_outputs = calculate_number_of_blank_outputs(fee_reserve)
|
||||
blinded_msgs = [step1_alice(str(n)) for n in range(n_blank_outputs)]
|
||||
outputs = [
|
||||
BlindedMessage(amount=1, B_=b.serialize().hex()) for b, _ in blinded_msgs
|
||||
BlindedMessage(
|
||||
amount=1,
|
||||
B_=b.serialize().hex(),
|
||||
id="009a1f293253e41e",
|
||||
)
|
||||
for b, _ in blinded_msgs
|
||||
]
|
||||
|
||||
promises = await ledger._generate_change_promises(
|
||||
total_provided, invoice_amount, actual_fee_msat, outputs
|
||||
total_provided, invoice_amount, actual_fee, outputs
|
||||
)
|
||||
|
||||
assert len(promises) == expected_returned_promises
|
||||
@@ -151,7 +223,7 @@ async def test_generate_change_promises_legacy_wallet(ledger: Ledger):
|
||||
invoice_amount = 100_000
|
||||
fee_reserve = 2_000
|
||||
total_provided = invoice_amount + fee_reserve
|
||||
actual_fee_msat = 100_000
|
||||
actual_fee = 100
|
||||
|
||||
expected_returned_promises = 4 # Amounts = [64, 256, 512, 1024]
|
||||
expected_returned_fees = 1856
|
||||
@@ -159,11 +231,16 @@ async def test_generate_change_promises_legacy_wallet(ledger: Ledger):
|
||||
n_blank_outputs = 4
|
||||
blinded_msgs = [step1_alice(str(n)) for n in range(n_blank_outputs)]
|
||||
outputs = [
|
||||
BlindedMessage(amount=1, B_=b.serialize().hex()) for b, _ in blinded_msgs
|
||||
BlindedMessage(
|
||||
amount=1,
|
||||
B_=b.serialize().hex(),
|
||||
id="009a1f293253e41e",
|
||||
)
|
||||
for b, _ in blinded_msgs
|
||||
]
|
||||
|
||||
promises = await ledger._generate_change_promises(
|
||||
total_provided, invoice_amount, actual_fee_msat, outputs
|
||||
total_provided, invoice_amount, actual_fee, outputs
|
||||
)
|
||||
|
||||
assert len(promises) == expected_returned_promises
|
||||
@@ -193,9 +270,9 @@ async def test_get_balance(ledger: Ledger):
|
||||
@pytest.mark.asyncio
|
||||
async def test_maximum_balance(ledger: Ledger):
|
||||
settings.mint_max_balance = 1000
|
||||
invoice, id = await ledger.request_mint(8)
|
||||
await ledger.mint_quote(PostMintQuoteRequest(amount=8, unit="sat"))
|
||||
await assert_err(
|
||||
ledger.request_mint(8000),
|
||||
ledger.mint_quote(PostMintQuoteRequest(amount=8000, unit="sat")),
|
||||
"Mint has reached maximum balance.",
|
||||
)
|
||||
settings.mint_max_balance = 0
|
||||
|
||||
@@ -1,71 +1,371 @@
|
||||
import bolt11
|
||||
import httpx
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from cashu.core.base import CheckSpendableRequest, CheckSpendableResponse, Proof
|
||||
from cashu.core.base import (
|
||||
PostCheckStateRequest,
|
||||
PostCheckStateResponse,
|
||||
SpentState,
|
||||
)
|
||||
from cashu.core.settings import settings
|
||||
from cashu.mint.ledger import Ledger
|
||||
from cashu.wallet.wallet import Wallet
|
||||
from tests.helpers import get_real_invoice, is_fake, is_regtest, pay_if_regtest
|
||||
|
||||
BASE_URL = "http://localhost:3337"
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def wallet(ledger: Ledger):
|
||||
wallet1 = await Wallet.with_db(
|
||||
url=BASE_URL,
|
||||
db="test_data/wallet_mint_api",
|
||||
name="wallet_mint_api",
|
||||
)
|
||||
await wallet1.load_mint()
|
||||
yield wallet1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_info(ledger):
|
||||
response = httpx.get(f"{BASE_URL}/info")
|
||||
@pytest.mark.skipif(
|
||||
settings.debug_mint_only_deprecated,
|
||||
reason="settings.debug_mint_only_deprecated is set",
|
||||
)
|
||||
async def test_info(ledger: Ledger):
|
||||
response = httpx.get(f"{BASE_URL}/v1/info")
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
assert ledger.pubkey
|
||||
assert response.json()["pubkey"] == ledger.pubkey.serialize().hex()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_keys(ledger):
|
||||
response = httpx.get(f"{BASE_URL}/keys")
|
||||
@pytest.mark.skipif(
|
||||
settings.debug_mint_only_deprecated,
|
||||
reason="settings.debug_mint_only_deprecated is set",
|
||||
)
|
||||
async def test_api_keys(ledger: Ledger):
|
||||
response = httpx.get(f"{BASE_URL}/v1/keys")
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
assert response.json() == {
|
||||
str(k): v.serialize().hex() for k, v in ledger.keyset.public_keys.items()
|
||||
assert ledger.keyset.public_keys
|
||||
expected = {
|
||||
"keysets": [
|
||||
{
|
||||
"id": keyset.id,
|
||||
"unit": keyset.unit.name,
|
||||
"keys": {
|
||||
str(k): v.serialize().hex() for k, v in keyset.public_keys.items() # type: ignore
|
||||
},
|
||||
}
|
||||
for keyset in ledger.keysets.values()
|
||||
]
|
||||
}
|
||||
assert response.json() == expected
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_keysets(ledger):
|
||||
response = httpx.get(f"{BASE_URL}/keysets")
|
||||
@pytest.mark.skipif(
|
||||
settings.debug_mint_only_deprecated,
|
||||
reason="settings.debug_mint_only_deprecated is set",
|
||||
)
|
||||
async def test_api_keysets(ledger: Ledger):
|
||||
response = httpx.get(f"{BASE_URL}/v1/keysets")
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
assert response.json()["keysets"] == list(ledger.keysets.keysets.keys())
|
||||
expected = {
|
||||
"keysets": [
|
||||
{
|
||||
"id": "009a1f293253e41e",
|
||||
"unit": "sat",
|
||||
"active": True,
|
||||
},
|
||||
# for backwards compatibility of the new keyset ID format,
|
||||
# we also return the same keyset with the old base64 ID
|
||||
{
|
||||
"id": "eGnEWtdJ0PIM",
|
||||
"unit": "sat",
|
||||
"active": True,
|
||||
},
|
||||
]
|
||||
}
|
||||
assert response.json() == expected
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_keyset_keys(ledger):
|
||||
response = httpx.get(
|
||||
f"{BASE_URL}/keys/{'1cCNIAZ2X/w1'.replace('/', '_').replace('+', '-')}"
|
||||
@pytest.mark.skipif(
|
||||
settings.debug_mint_only_deprecated,
|
||||
reason="settings.debug_mint_only_deprecated is set",
|
||||
)
|
||||
async def test_api_keyset_keys(ledger: Ledger):
|
||||
response = httpx.get(f"{BASE_URL}/v1/keys/009a1f293253e41e")
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
assert ledger.keyset.public_keys
|
||||
expected = {
|
||||
"keysets": [
|
||||
{
|
||||
"id": "009a1f293253e41e",
|
||||
"unit": "sat",
|
||||
"keys": {
|
||||
str(k): v.serialize().hex()
|
||||
for k, v in ledger.keysets["009a1f293253e41e"].public_keys.items() # type: ignore
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
assert response.json() == expected
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
settings.debug_mint_only_deprecated,
|
||||
reason="settings.debug_mint_only_deprecated is set",
|
||||
)
|
||||
async def test_api_keyset_keys_old_keyset_id(ledger: Ledger):
|
||||
response = httpx.get(f"{BASE_URL}/v1/keys/eGnEWtdJ0PIM")
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
assert ledger.keyset.public_keys
|
||||
expected = {
|
||||
"keysets": [
|
||||
{
|
||||
"id": "eGnEWtdJ0PIM",
|
||||
"unit": "sat",
|
||||
"keys": {
|
||||
str(k): v.serialize().hex()
|
||||
for k, v in ledger.keysets["eGnEWtdJ0PIM"].public_keys.items() # type: ignore
|
||||
},
|
||||
}
|
||||
]
|
||||
}
|
||||
assert response.json() == expected
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
settings.debug_mint_only_deprecated,
|
||||
reason="settings.debug_mint_only_deprecated is set",
|
||||
)
|
||||
async def test_split(ledger: Ledger, wallet: Wallet):
|
||||
invoice = await wallet.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet.mint(64, id=invoice.id)
|
||||
assert wallet.balance == 64
|
||||
secrets, rs, derivation_paths = await wallet.generate_n_secrets(2)
|
||||
outputs, rs = wallet._construct_outputs([32, 32], secrets, rs)
|
||||
# outputs = wallet._construct_outputs([32, 32], ["a", "b"], ["c", "d"])
|
||||
inputs_payload = [p.to_dict() for p in wallet.proofs]
|
||||
outputs_payload = [o.dict() for o in outputs]
|
||||
payload = {"inputs": inputs_payload, "outputs": outputs_payload}
|
||||
response = httpx.post(f"{BASE_URL}/v1/swap", json=payload)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
result = response.json()
|
||||
assert len(result["signatures"]) == 2
|
||||
assert result["signatures"][0]["amount"] == 32
|
||||
assert result["signatures"][1]["amount"] == 32
|
||||
assert result["signatures"][0]["id"] == "009a1f293253e41e"
|
||||
assert result["signatures"][0]["dleq"]
|
||||
assert "e" in result["signatures"][0]["dleq"]
|
||||
assert "s" in result["signatures"][0]["dleq"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
settings.debug_mint_only_deprecated,
|
||||
reason="settings.debug_mint_only_deprecated is set",
|
||||
)
|
||||
async def test_mint_quote(ledger: Ledger):
|
||||
response = httpx.post(
|
||||
f"{BASE_URL}/v1/mint/quote/bolt11",
|
||||
json={"unit": "sat", "amount": 100},
|
||||
)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
assert response.json() == {
|
||||
str(k): v.serialize().hex() for k, v in ledger.keyset.public_keys.items()
|
||||
}
|
||||
result = response.json()
|
||||
assert result["quote"]
|
||||
assert result["request"]
|
||||
invoice = bolt11.decode(result["request"])
|
||||
assert invoice.amount_msat == 100 * 1000
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_mint_validation(ledger):
|
||||
response = httpx.get(f"{BASE_URL}/mint?amount=-21")
|
||||
assert "detail" in response.json()
|
||||
response = httpx.get(f"{BASE_URL}/mint?amount=0")
|
||||
assert "detail" in response.json()
|
||||
response = httpx.get(f"{BASE_URL}/mint?amount=2100000000000001")
|
||||
assert "detail" in response.json()
|
||||
response = httpx.get(f"{BASE_URL}/mint?amount=1")
|
||||
assert "detail" not in response.json()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_check_state(ledger):
|
||||
proofs = [
|
||||
Proof(id="1234", amount=0, secret="asdasdasd", C="asdasdasd"),
|
||||
Proof(id="1234", amount=0, secret="asdasdasd1", C="asdasdasd1"),
|
||||
]
|
||||
payload = CheckSpendableRequest(proofs=proofs)
|
||||
@pytest.mark.skipif(
|
||||
settings.debug_mint_only_deprecated,
|
||||
reason="settings.debug_mint_only_deprecated is set",
|
||||
)
|
||||
async def test_mint(ledger: Ledger, wallet: Wallet):
|
||||
invoice = await wallet.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
quote_id = invoice.id
|
||||
secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(10000, 10001)
|
||||
outputs, rs = wallet._construct_outputs([32, 32], secrets, rs)
|
||||
outputs_payload = [o.dict() for o in outputs]
|
||||
response = httpx.post(
|
||||
f"{BASE_URL}/check",
|
||||
f"{BASE_URL}/v1/mint/bolt11",
|
||||
json={"quote": quote_id, "outputs": outputs_payload},
|
||||
timeout=None,
|
||||
)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
result = response.json()
|
||||
assert len(result["signatures"]) == 2
|
||||
assert result["signatures"][0]["amount"] == 32
|
||||
assert result["signatures"][1]["amount"] == 32
|
||||
assert result["signatures"][0]["id"] == "009a1f293253e41e"
|
||||
assert result["signatures"][0]["dleq"]
|
||||
assert "e" in result["signatures"][0]["dleq"]
|
||||
assert "s" in result["signatures"][0]["dleq"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
settings.debug_mint_only_deprecated,
|
||||
reason="settings.debug_mint_only_deprecated is set",
|
||||
)
|
||||
@pytest.mark.skipif(
|
||||
is_regtest,
|
||||
reason="regtest",
|
||||
)
|
||||
async def test_melt_quote_internal(ledger: Ledger, wallet: Wallet):
|
||||
# internal invoice
|
||||
invoice = await wallet.request_mint(64)
|
||||
request = invoice.bolt11
|
||||
response = httpx.post(
|
||||
f"{BASE_URL}/v1/melt/quote/bolt11",
|
||||
json={"unit": "sat", "request": request},
|
||||
)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
result = response.json()
|
||||
assert result["quote"]
|
||||
assert result["amount"] == 64
|
||||
# TODO: internal invoice, fee should be 0
|
||||
assert result["fee_reserve"] == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
settings.debug_mint_only_deprecated,
|
||||
reason="settings.debug_mint_only_deprecated is set",
|
||||
)
|
||||
@pytest.mark.skipif(
|
||||
is_fake,
|
||||
reason="only works on regtest",
|
||||
)
|
||||
async def test_melt_quote_external(ledger: Ledger, wallet: Wallet):
|
||||
# internal invoice
|
||||
invoice_dict = get_real_invoice(64)
|
||||
request = invoice_dict["payment_request"]
|
||||
response = httpx.post(
|
||||
f"{BASE_URL}/v1/melt/quote/bolt11",
|
||||
json={"unit": "sat", "request": request},
|
||||
)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
result = response.json()
|
||||
assert result["quote"]
|
||||
assert result["amount"] == 64
|
||||
# external invoice, fee should be 2
|
||||
assert result["fee_reserve"] == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
settings.debug_mint_only_deprecated,
|
||||
reason="settings.debug_mint_only_deprecated is set",
|
||||
)
|
||||
async def test_melt_internal(ledger: Ledger, wallet: Wallet):
|
||||
# internal invoice
|
||||
invoice = await wallet.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet.mint(64, id=invoice.id)
|
||||
assert wallet.balance == 64
|
||||
|
||||
# create invoice to melt to
|
||||
invoice = await wallet.request_mint(64)
|
||||
invoice_payment_request = invoice.bolt11
|
||||
|
||||
quote = await wallet.melt_quote(invoice_payment_request)
|
||||
assert quote.amount == 64
|
||||
assert quote.fee_reserve == 0
|
||||
|
||||
inputs_payload = [p.to_dict() for p in wallet.proofs]
|
||||
|
||||
# outputs for change
|
||||
secrets, rs, derivation_paths = await wallet.generate_n_secrets(1)
|
||||
outputs, rs = wallet._construct_outputs([2], secrets, rs)
|
||||
outputs_payload = [o.dict() for o in outputs]
|
||||
|
||||
response = httpx.post(
|
||||
f"{BASE_URL}/v1/melt/bolt11",
|
||||
json={
|
||||
"quote": quote.quote,
|
||||
"inputs": inputs_payload,
|
||||
"outputs": outputs_payload,
|
||||
},
|
||||
timeout=None,
|
||||
)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
result = response.json()
|
||||
assert result.get("payment_preimage") is not None
|
||||
assert result["paid"] is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
settings.debug_mint_only_deprecated,
|
||||
reason="settings.debug_mint_only_deprecated is set",
|
||||
)
|
||||
@pytest.mark.skipif(
|
||||
is_fake,
|
||||
reason="only works on regtest",
|
||||
)
|
||||
async def test_melt_external(ledger: Ledger, wallet: Wallet):
|
||||
# internal invoice
|
||||
invoice = await wallet.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet.mint(64, id=invoice.id)
|
||||
assert wallet.balance == 64
|
||||
|
||||
invoice_dict = get_real_invoice(62)
|
||||
invoice_payment_request = invoice_dict["payment_request"]
|
||||
|
||||
quote = await wallet.melt_quote(invoice_payment_request)
|
||||
assert quote.amount == 62
|
||||
assert quote.fee_reserve == 2
|
||||
|
||||
keep, send = await wallet.split_to_send(wallet.proofs, 64)
|
||||
inputs_payload = [p.to_dict() for p in send]
|
||||
|
||||
# outputs for change
|
||||
secrets, rs, derivation_paths = await wallet.generate_n_secrets(1)
|
||||
outputs, rs = wallet._construct_outputs([2], secrets, rs)
|
||||
outputs_payload = [o.dict() for o in outputs]
|
||||
|
||||
response = httpx.post(
|
||||
f"{BASE_URL}/v1/melt/bolt11",
|
||||
json={
|
||||
"quote": quote.quote,
|
||||
"inputs": inputs_payload,
|
||||
"outputs": outputs_payload,
|
||||
},
|
||||
timeout=None,
|
||||
)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
result = response.json()
|
||||
assert result.get("payment_preimage") is not None
|
||||
assert result["paid"] is True
|
||||
assert result["change"]
|
||||
# we get back 2 sats because Lightning was free to pay on regtest
|
||||
assert result["change"][0]["amount"] == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
settings.debug_mint_only_deprecated,
|
||||
reason="settings.debug_mint_only_deprecated is set",
|
||||
)
|
||||
async def test_api_check_state(ledger: Ledger):
|
||||
payload = PostCheckStateRequest(secrets=["asdasdasd", "asdasdasd1"])
|
||||
response = httpx.post(
|
||||
f"{BASE_URL}/v1/checkstate",
|
||||
json=payload.dict(),
|
||||
)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
states = CheckSpendableResponse.parse_obj(response.json())
|
||||
assert states.spendable
|
||||
assert len(states.spendable) == 2
|
||||
assert states.pending
|
||||
assert len(states.pending) == 2
|
||||
response = PostCheckStateResponse.parse_obj(response.json())
|
||||
assert response
|
||||
assert len(response.states) == 2
|
||||
assert response.states[0].state == SpentState.unspent
|
||||
|
||||
289
tests/test_mint_api_deprecated.py
Normal file
289
tests/test_mint_api_deprecated.py
Normal file
@@ -0,0 +1,289 @@
|
||||
import httpx
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from cashu.core.base import (
|
||||
CheckSpendableRequest_deprecated,
|
||||
CheckSpendableResponse_deprecated,
|
||||
Proof,
|
||||
)
|
||||
from cashu.core.settings import settings
|
||||
from cashu.mint.ledger import Ledger
|
||||
from cashu.wallet.wallet import Wallet
|
||||
from tests.helpers import get_real_invoice, is_fake, is_regtest, pay_if_regtest
|
||||
|
||||
BASE_URL = "http://localhost:3337"
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def wallet(ledger: Ledger):
|
||||
wallet1 = await Wallet.with_db(
|
||||
url=BASE_URL,
|
||||
db="test_data/wallet_mint_api_deprecated",
|
||||
name="wallet_mint_api_deprecated",
|
||||
)
|
||||
await wallet1.load_mint()
|
||||
yield wallet1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_info(ledger: Ledger):
|
||||
response = httpx.get(f"{BASE_URL}/info")
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
assert ledger.pubkey
|
||||
assert response.json()["pubkey"] == ledger.pubkey.serialize().hex()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_keys(ledger: Ledger):
|
||||
response = httpx.get(f"{BASE_URL}/keys")
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
assert ledger.keyset.public_keys
|
||||
assert response.json() == {
|
||||
str(k): v.serialize().hex() for k, v in ledger.keyset.public_keys.items()
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_keysets(ledger: Ledger):
|
||||
response = httpx.get(f"{BASE_URL}/keysets")
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
assert ledger.keyset.public_keys
|
||||
assert response.json()["keysets"] == list(ledger.keysets.keys())
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_keyset_keys(ledger: Ledger):
|
||||
response = httpx.get(f"{BASE_URL}/keys/009a1f293253e41e")
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
assert ledger.keyset.public_keys
|
||||
assert response.json() == {
|
||||
str(k): v.serialize().hex() for k, v in ledger.keyset.public_keys.items()
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_split(ledger: Ledger, wallet: Wallet):
|
||||
invoice = await wallet.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet.mint(64, id=invoice.id)
|
||||
assert wallet.balance == 64
|
||||
secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(20000, 20001)
|
||||
outputs, rs = wallet._construct_outputs([32, 32], secrets, rs)
|
||||
# outputs = wallet._construct_outputs([32, 32], ["a", "b"], ["c", "d"])
|
||||
inputs_payload = [p.to_dict() for p in wallet.proofs]
|
||||
outputs_payload = [o.dict() for o in outputs]
|
||||
# strip "id" from outputs_payload, which is not used in the deprecated split endpoint
|
||||
for o in outputs_payload:
|
||||
o.pop("id")
|
||||
payload = {"proofs": inputs_payload, "outputs": outputs_payload}
|
||||
response = httpx.post(f"{BASE_URL}/split", json=payload, timeout=None)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
result = response.json()
|
||||
assert result["promises"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_split_deprecated_with_amount(ledger: Ledger, wallet: Wallet):
|
||||
invoice = await wallet.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet.mint(64, id=invoice.id)
|
||||
assert wallet.balance == 64
|
||||
secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(80000, 80001)
|
||||
outputs, rs = wallet._construct_outputs([32, 32], secrets, rs)
|
||||
# outputs = wallet._construct_outputs([32, 32], ["a", "b"], ["c", "d"])
|
||||
inputs_payload = [p.to_dict() for p in wallet.proofs]
|
||||
outputs_payload = [o.dict() for o in outputs]
|
||||
# strip "id" from outputs_payload, which is not used in the deprecated split endpoint
|
||||
for o in outputs_payload:
|
||||
o.pop("id")
|
||||
# we supply an amount here, which should cause the very old deprecated split endpoint to be used
|
||||
payload = {"proofs": inputs_payload, "outputs": outputs_payload, "amount": 32}
|
||||
response = httpx.post(f"{BASE_URL}/split", json=payload, timeout=None)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
result = response.json()
|
||||
# old deprecated output format
|
||||
assert result["fst"]
|
||||
assert result["snd"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_mint_validation(ledger):
|
||||
response = httpx.get(f"{BASE_URL}/mint?amount=-21")
|
||||
assert "detail" in response.json()
|
||||
response = httpx.get(f"{BASE_URL}/mint?amount=0")
|
||||
assert "detail" in response.json()
|
||||
response = httpx.get(f"{BASE_URL}/mint?amount=2100000000000001")
|
||||
assert "detail" in response.json()
|
||||
response = httpx.get(f"{BASE_URL}/mint?amount=1")
|
||||
assert "detail" not in response.json()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mint(ledger: Ledger, wallet: Wallet):
|
||||
invoice = await wallet.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
quote_id = invoice.id
|
||||
secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(10000, 10001)
|
||||
outputs, rs = wallet._construct_outputs([32, 32], secrets, rs)
|
||||
outputs_payload = [o.dict() for o in outputs]
|
||||
response = httpx.post(
|
||||
f"{BASE_URL}/mint",
|
||||
json={"outputs": outputs_payload},
|
||||
params={"hash": quote_id},
|
||||
timeout=None,
|
||||
)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
result = response.json()
|
||||
assert len(result["promises"]) == 2
|
||||
assert result["promises"][0]["amount"] == 32
|
||||
assert result["promises"][1]["amount"] == 32
|
||||
if settings.debug_mint_only_deprecated:
|
||||
assert result["promises"][0]["id"] == "eGnEWtdJ0PIM"
|
||||
else:
|
||||
assert result["promises"][0]["id"] == "009a1f293253e41e"
|
||||
assert result["promises"][0]["dleq"]
|
||||
assert "e" in result["promises"][0]["dleq"]
|
||||
assert "s" in result["promises"][0]["dleq"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_melt_internal(ledger: Ledger, wallet: Wallet):
|
||||
# internal invoice
|
||||
invoice = await wallet.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet.mint(64, id=invoice.id)
|
||||
assert wallet.balance == 64
|
||||
|
||||
# create invoice to melt to
|
||||
invoice = await wallet.request_mint(64)
|
||||
|
||||
invoice_payment_request = invoice.bolt11
|
||||
|
||||
quote = await wallet.melt_quote(invoice_payment_request)
|
||||
assert quote.amount == 64
|
||||
assert quote.fee_reserve == 0
|
||||
|
||||
inputs_payload = [p.to_dict() for p in wallet.proofs]
|
||||
|
||||
# outputs for change
|
||||
secrets, rs, derivation_paths = await wallet.generate_n_secrets(1)
|
||||
outputs, rs = wallet._construct_outputs([2], secrets, rs)
|
||||
outputs_payload = [o.dict() for o in outputs]
|
||||
|
||||
response = httpx.post(
|
||||
f"{BASE_URL}/melt",
|
||||
json={
|
||||
"pr": invoice_payment_request,
|
||||
"proofs": inputs_payload,
|
||||
"outputs": outputs_payload,
|
||||
},
|
||||
timeout=None,
|
||||
)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
result = response.json()
|
||||
assert result.get("preimage") is not None
|
||||
assert result["paid"] is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
is_fake,
|
||||
reason="only works on regtest",
|
||||
)
|
||||
async def test_melt_external(ledger: Ledger, wallet: Wallet):
|
||||
# internal invoice
|
||||
invoice = await wallet.request_mint(64)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet.mint(64, id=invoice.id)
|
||||
assert wallet.balance == 64
|
||||
|
||||
# create invoice to melt to
|
||||
# use 2 sat less because we need to pay the fee
|
||||
invoice_dict = get_real_invoice(62)
|
||||
invoice_payment_request = invoice_dict["payment_request"]
|
||||
|
||||
quote = await wallet.melt_quote(invoice_payment_request)
|
||||
assert quote.amount == 62
|
||||
assert quote.fee_reserve == 2
|
||||
|
||||
inputs_payload = [p.to_dict() for p in wallet.proofs]
|
||||
|
||||
# outputs for change
|
||||
secrets, rs, derivation_paths = await wallet.generate_n_secrets(1)
|
||||
outputs, rs = wallet._construct_outputs([2], secrets, rs)
|
||||
outputs_payload = [o.dict() for o in outputs]
|
||||
|
||||
response = httpx.post(
|
||||
f"{BASE_URL}/melt",
|
||||
json={
|
||||
"pr": invoice_payment_request,
|
||||
"proofs": inputs_payload,
|
||||
"outputs": outputs_payload,
|
||||
},
|
||||
timeout=None,
|
||||
)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
result = response.json()
|
||||
assert result.get("preimage") is not None
|
||||
assert result["paid"] is True
|
||||
assert result["change"]
|
||||
# we get back 2 sats because Lightning was free to pay on regtest
|
||||
assert result["change"][0]["amount"] == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_checkfees(ledger: Ledger, wallet: Wallet):
|
||||
# internal invoice
|
||||
invoice = await wallet.request_mint(64)
|
||||
response = httpx.post(
|
||||
f"{BASE_URL}/checkfees",
|
||||
json={
|
||||
"pr": invoice.bolt11,
|
||||
},
|
||||
timeout=None,
|
||||
)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
result = response.json()
|
||||
# internal invoice, so no fee
|
||||
assert result["fee"] == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(not is_regtest, reason="only works on regtest")
|
||||
async def test_checkfees_external(ledger: Ledger, wallet: Wallet):
|
||||
# external invoice
|
||||
invoice_dict = get_real_invoice(62)
|
||||
invoice_payment_request = invoice_dict["payment_request"]
|
||||
response = httpx.post(
|
||||
f"{BASE_URL}/checkfees",
|
||||
json={"pr": invoice_payment_request},
|
||||
timeout=None,
|
||||
)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
result = response.json()
|
||||
# external invoice, so fee
|
||||
assert result["fee"] == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
settings.debug_mint_only_deprecated,
|
||||
reason="settings.debug_mint_only_deprecated is set",
|
||||
)
|
||||
async def test_api_check_state(ledger: Ledger):
|
||||
proofs = [
|
||||
Proof(id="1234", amount=0, secret="asdasdasd", C="asdasdasd"),
|
||||
Proof(id="1234", amount=0, secret="asdasdasd1", C="asdasdasd1"),
|
||||
]
|
||||
payload = CheckSpendableRequest_deprecated(proofs=proofs)
|
||||
response = httpx.post(
|
||||
f"{BASE_URL}/check",
|
||||
json=payload.dict(),
|
||||
)
|
||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||
states = CheckSpendableResponse_deprecated.parse_obj(response.json())
|
||||
assert states.spendable
|
||||
assert len(states.spendable) == 2
|
||||
assert states.pending
|
||||
assert len(states.pending) == 2
|
||||
@@ -1,11 +1,13 @@
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from cashu.core.base import PostMeltQuoteRequest, PostMintQuoteRequest
|
||||
from cashu.core.helpers import sum_proofs
|
||||
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
|
||||
from tests.helpers import get_real_invoice, is_fake, is_regtest, pay_if_regtest
|
||||
|
||||
|
||||
async def assert_err(f, msg):
|
||||
@@ -20,36 +22,120 @@ async def assert_err(f, msg):
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def wallet1(mint):
|
||||
async def wallet1(ledger: Ledger):
|
||||
wallet1 = await Wallet1.with_db(
|
||||
url=SERVER_ENDPOINT,
|
||||
db="test_data/wallet1",
|
||||
name="wallet1",
|
||||
)
|
||||
await wallet1.load_mint()
|
||||
wallet1.status()
|
||||
yield wallet1
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_melt(wallet1: Wallet, ledger: Ledger):
|
||||
@pytest.mark.skipif(is_regtest, reason="only works with FakeWallet")
|
||||
async def test_melt_internal(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)
|
||||
invoice2 = await wallet1.request_mint(64)
|
||||
pay_if_regtest(invoice2.bolt11)
|
||||
await wallet1.mint(64, id=invoice2.id)
|
||||
invoice = await wallet1.request_mint(128)
|
||||
await wallet1.mint(128, id=invoice.id)
|
||||
assert wallet1.balance == 128
|
||||
total_amount, fee_reserve_sat = await wallet1.get_pay_amount_with_fees(
|
||||
invoice2.bolt11
|
||||
|
||||
# create a mint quote so that we can melt to it internally
|
||||
invoice_to_pay = await wallet1.request_mint(64)
|
||||
invoice_payment_request = invoice_to_pay.bolt11
|
||||
|
||||
melt_quote = await ledger.melt_quote(
|
||||
PostMeltQuoteRequest(request=invoice_payment_request, unit="sat")
|
||||
)
|
||||
melt_fees = await ledger.get_melt_fees(invoice2.bolt11)
|
||||
assert melt_fees == fee_reserve_sat
|
||||
assert not melt_quote.paid
|
||||
assert melt_quote.amount == 64
|
||||
assert melt_quote.fee_reserve == 0
|
||||
|
||||
melt_quote_pre_payment = await ledger.get_melt_quote(melt_quote.quote)
|
||||
assert not melt_quote_pre_payment.paid, "melt quote should not be paid"
|
||||
|
||||
keep_proofs, send_proofs = await wallet1.split_to_send(wallet1.proofs, 64)
|
||||
await ledger.melt(proofs=send_proofs, quote=melt_quote.quote)
|
||||
|
||||
melt_quote_post_payment = await ledger.get_melt_quote(melt_quote.quote)
|
||||
assert melt_quote_post_payment.paid, "melt quote should be paid"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(is_fake, reason="only works with Regtest")
|
||||
async def test_melt_external(wallet1: Wallet, ledger: Ledger):
|
||||
# mint twice so we have enough to pay the second invoice back
|
||||
invoice = await wallet1.request_mint(128)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(128, id=invoice.id)
|
||||
assert wallet1.balance == 128
|
||||
|
||||
invoice_dict = get_real_invoice(64)
|
||||
invoice_payment_request = invoice_dict["payment_request"]
|
||||
|
||||
mint_quote = await wallet1.get_pay_amount_with_fees(invoice_payment_request)
|
||||
total_amount = mint_quote.amount + mint_quote.fee_reserve
|
||||
keep_proofs, send_proofs = await wallet1.split_to_send(wallet1.proofs, total_amount)
|
||||
melt_quote = await ledger.melt_quote(
|
||||
PostMeltQuoteRequest(request=invoice_payment_request, unit="sat")
|
||||
)
|
||||
|
||||
await ledger.melt(send_proofs, invoice2.bolt11, outputs=None)
|
||||
melt_quote_pre_payment = await ledger.get_melt_quote(melt_quote.quote)
|
||||
assert not melt_quote_pre_payment.paid, "melt quote should not be paid"
|
||||
|
||||
assert not melt_quote.paid, "melt quote should not be paid"
|
||||
await ledger.melt(proofs=send_proofs, quote=melt_quote.quote)
|
||||
|
||||
melt_quote_post_payment = await ledger.get_melt_quote(melt_quote.quote)
|
||||
assert melt_quote_post_payment.paid, "melt quote should be paid"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(is_regtest, reason="only works with FakeWallet")
|
||||
async def test_mint_internal(wallet1: Wallet, ledger: Ledger):
|
||||
invoice = await wallet1.request_mint(128)
|
||||
|
||||
mint_quote = await ledger.get_mint_quote(invoice.id)
|
||||
|
||||
assert mint_quote.paid, "mint quote should be paid"
|
||||
|
||||
output_amounts = [128]
|
||||
secrets, rs, derivation_paths = await wallet1.generate_n_secrets(
|
||||
len(output_amounts)
|
||||
)
|
||||
outputs, rs = wallet1._construct_outputs(output_amounts, secrets, rs)
|
||||
await ledger.mint(outputs=outputs, quote_id=invoice.id)
|
||||
|
||||
await assert_err(
|
||||
ledger.mint(outputs=outputs, quote_id=invoice.id),
|
||||
"outputs have already been signed before.",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(is_fake, reason="only works with Regtest")
|
||||
async def test_mint_external(wallet1: Wallet, ledger: Ledger):
|
||||
quote = await ledger.mint_quote(PostMintQuoteRequest(amount=128, unit="sat"))
|
||||
|
||||
mint_quote = await ledger.get_mint_quote(quote.quote)
|
||||
assert not mint_quote.paid, "mint quote not should be paid"
|
||||
|
||||
await assert_err(
|
||||
wallet1.mint(128, id=quote.quote),
|
||||
"quote not paid",
|
||||
)
|
||||
|
||||
pay_if_regtest(quote.request)
|
||||
|
||||
mint_quote = await ledger.get_mint_quote(quote.quote)
|
||||
assert mint_quote.paid, "mint quote should be paid"
|
||||
|
||||
output_amounts = [128]
|
||||
secrets, rs, derivation_paths = await wallet1.generate_n_secrets(
|
||||
len(output_amounts)
|
||||
)
|
||||
outputs, rs = wallet1._construct_outputs(output_amounts, secrets, rs)
|
||||
await ledger.mint(outputs=outputs, quote_id=quote.quote)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -120,14 +206,13 @@ async def test_split_with_input_more_than_outputs(wallet1: Wallet, ledger: Ledge
|
||||
|
||||
# make sure we can still spend our tokens
|
||||
keep_proofs, send_proofs = await wallet1.split(inputs, 10)
|
||||
print(keep_proofs, send_proofs)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_split_twice_with_same_outputs(wallet1: Wallet, ledger: Ledger):
|
||||
invoice = await wallet1.request_mint(128)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(128, [64, 64], id=invoice.id)
|
||||
await wallet1.mint(128, split=[64, 64], id=invoice.id)
|
||||
inputs1 = wallet1.proofs[:1]
|
||||
inputs2 = wallet1.proofs[1:]
|
||||
|
||||
@@ -164,14 +249,14 @@ async def test_mint_with_same_outputs_twice(wallet1: Wallet, ledger: Ledger):
|
||||
len(output_amounts)
|
||||
)
|
||||
outputs, rs = wallet1._construct_outputs(output_amounts, secrets, rs)
|
||||
await ledger.mint(outputs, id=invoice.id)
|
||||
await ledger.mint(outputs=outputs, quote_id=invoice.id)
|
||||
|
||||
# now try to mint with the same outputs again
|
||||
invoice2 = await wallet1.request_mint(128)
|
||||
pay_if_regtest(invoice2.bolt11)
|
||||
|
||||
await assert_err(
|
||||
ledger.mint(outputs, id=invoice2.id),
|
||||
ledger.mint(outputs=outputs, quote_id=invoice2.id),
|
||||
"outputs have already been signed before.",
|
||||
)
|
||||
|
||||
@@ -191,16 +276,79 @@ async def test_melt_with_same_outputs_twice(wallet1: Wallet, ledger: Ledger):
|
||||
# we use the outputs once for minting
|
||||
invoice2 = await wallet1.request_mint(128)
|
||||
pay_if_regtest(invoice2.bolt11)
|
||||
await ledger.mint(outputs, id=invoice2.id)
|
||||
await ledger.mint(outputs=outputs, quote_id=invoice2.id)
|
||||
|
||||
# use the same outputs for melting
|
||||
invoice3 = await wallet1.request_mint(128)
|
||||
mint_quote = await ledger.mint_quote(PostMintQuoteRequest(unit="sat", amount=128))
|
||||
melt_quote = await ledger.melt_quote(
|
||||
PostMeltQuoteRequest(unit="sat", request=mint_quote.request)
|
||||
)
|
||||
await assert_err(
|
||||
ledger.melt(wallet1.proofs, invoice3.bolt11, outputs=outputs),
|
||||
ledger.melt(proofs=wallet1.proofs, quote=melt_quote.quote, outputs=outputs),
|
||||
"outputs have already been signed before.",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_melt_with_less_inputs_than_invoice(wallet1: Wallet, ledger: Ledger):
|
||||
invoice = await wallet1.request_mint(32)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(32, id=invoice.id)
|
||||
|
||||
# outputs for fee return
|
||||
output_amounts = [1, 1, 1, 1]
|
||||
secrets, rs, derivation_paths = await wallet1.generate_n_secrets(
|
||||
len(output_amounts)
|
||||
)
|
||||
outputs, rs = wallet1._construct_outputs(output_amounts, secrets, rs)
|
||||
|
||||
# create a mint quote to pay
|
||||
mint_quote = await ledger.mint_quote(PostMintQuoteRequest(unit="sat", amount=128))
|
||||
# prepare melt quote
|
||||
melt_quote = await ledger.melt_quote(
|
||||
PostMeltQuoteRequest(unit="sat", request=mint_quote.request)
|
||||
)
|
||||
|
||||
assert melt_quote.amount + melt_quote.fee_reserve > sum_proofs(wallet1.proofs)
|
||||
|
||||
# try to pay with not enough inputs
|
||||
await assert_err(
|
||||
ledger.melt(proofs=wallet1.proofs, quote=melt_quote.quote, outputs=outputs),
|
||||
"not enough inputs provided for melt",
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_melt_with_more_inputs_than_invoice(wallet1: Wallet, ledger: Ledger):
|
||||
invoice = await wallet1.request_mint(130)
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(130, split=[64, 64, 2], id=invoice.id)
|
||||
|
||||
# outputs for fee return
|
||||
output_amounts = [1, 1, 1, 1]
|
||||
secrets, rs, derivation_paths = await wallet1.generate_n_secrets(
|
||||
len(output_amounts)
|
||||
)
|
||||
outputs, rs = wallet1._construct_outputs(output_amounts, secrets, rs)
|
||||
|
||||
# create a mint quote to pay
|
||||
mint_quote = await ledger.mint_quote(PostMintQuoteRequest(unit="sat", amount=128))
|
||||
# prepare melt quote
|
||||
melt_quote = await ledger.melt_quote(
|
||||
PostMeltQuoteRequest(unit="sat", request=mint_quote.request)
|
||||
)
|
||||
# fees are 0 because it's internal
|
||||
assert melt_quote.fee_reserve == 0
|
||||
|
||||
# make sure we have more inputs than the melt quote needs
|
||||
assert sum_proofs(wallet1.proofs) >= melt_quote.amount + melt_quote.fee_reserve
|
||||
payment_proof, return_outputs = await ledger.melt(
|
||||
proofs=wallet1.proofs, quote=melt_quote.quote, outputs=outputs
|
||||
)
|
||||
# we get 2 sats back because we overpaid
|
||||
assert sum([o.amount for o in return_outputs]) == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_check_proof_state(wallet1: Wallet, ledger: Ledger):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
@@ -209,6 +357,7 @@ async def test_check_proof_state(wallet1: Wallet, ledger: Ledger):
|
||||
|
||||
keep_proofs, send_proofs = await wallet1.split_to_send(wallet1.proofs, 10)
|
||||
|
||||
spendable, pending = await ledger.check_proof_state(proofs=send_proofs)
|
||||
assert sum(spendable) == len(send_proofs)
|
||||
assert sum(pending) == 0
|
||||
proof_states = await ledger.check_proofs_state(
|
||||
secrets=[p.secret for p in send_proofs]
|
||||
)
|
||||
assert all([p.state.value == "UNSPENT" for p in proof_states])
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import copy
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
from typing import List, Union
|
||||
|
||||
import pytest
|
||||
@@ -10,12 +8,12 @@ from cashu.core.base import Proof
|
||||
from cashu.core.errors import CashuError, KeysetNotFoundError
|
||||
from cashu.core.helpers import sum_proofs
|
||||
from cashu.core.settings import settings
|
||||
from cashu.wallet.crud import get_keyset, get_lightning_invoice, get_proofs
|
||||
from cashu.wallet.crud import get_keysets, get_lightning_invoice, get_proofs
|
||||
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
|
||||
from tests.helpers import get_real_invoice, is_fake, is_regtest, pay_if_regtest
|
||||
|
||||
|
||||
async def assert_err(f, msg: Union[str, CashuError]):
|
||||
@@ -56,47 +54,32 @@ async def wallet1(mint):
|
||||
name="wallet1",
|
||||
)
|
||||
await wallet1.load_mint()
|
||||
wallet1.status()
|
||||
yield wallet1
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def wallet2(mint):
|
||||
async def wallet2():
|
||||
wallet2 = await Wallet2.with_db(
|
||||
url=SERVER_ENDPOINT,
|
||||
db="test_data/wallet2",
|
||||
name="wallet2",
|
||||
)
|
||||
await wallet2.load_mint()
|
||||
wallet2.status()
|
||||
yield wallet2
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def wallet3(mint):
|
||||
dirpath = Path("test_data/wallet3")
|
||||
if dirpath.exists() and dirpath.is_dir():
|
||||
shutil.rmtree(dirpath)
|
||||
|
||||
wallet3 = await Wallet1.with_db(
|
||||
url=SERVER_ENDPOINT,
|
||||
db="test_data/wallet3",
|
||||
name="wallet3",
|
||||
)
|
||||
await wallet3.db.execute("DELETE FROM proofs")
|
||||
await wallet3.db.execute("DELETE FROM proofs_used")
|
||||
await wallet3.load_mint()
|
||||
wallet3.status()
|
||||
yield wallet3
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_keys(wallet1: Wallet):
|
||||
assert wallet1.keysets[wallet1.keyset_id].public_keys
|
||||
assert len(wallet1.keysets[wallet1.keyset_id].public_keys) == settings.max_order
|
||||
keyset = await wallet1._get_keys(wallet1.url)
|
||||
keysets = await wallet1._get_keys()
|
||||
keyset = keysets[0]
|
||||
assert keyset.id is not None
|
||||
assert keyset.id == "1cCNIAZ2X/w1"
|
||||
# assert keyset.id_deprecated == "eGnEWtdJ0PIM"
|
||||
if settings.debug_mint_only_deprecated:
|
||||
assert keyset.id == "eGnEWtdJ0PIM"
|
||||
else:
|
||||
assert keyset.id == "009a1f293253e41e"
|
||||
assert isinstance(keyset.id, str)
|
||||
assert len(keyset.id) > 0
|
||||
|
||||
@@ -106,13 +89,14 @@ async def test_get_keyset(wallet1: Wallet):
|
||||
assert wallet1.keysets[wallet1.keyset_id].public_keys
|
||||
assert len(wallet1.keysets[wallet1.keyset_id].public_keys) == settings.max_order
|
||||
# let's get the keys first so we can get a keyset ID that we use later
|
||||
keys1 = await wallet1._get_keys(wallet1.url)
|
||||
keysets = await wallet1._get_keys()
|
||||
keyset = keysets[0]
|
||||
# gets the keys of a specific keyset
|
||||
assert keys1.id is not None
|
||||
assert keys1.public_keys is not None
|
||||
keys2 = await wallet1._get_keys_of_keyset(wallet1.url, keys1.id)
|
||||
assert keyset.id is not None
|
||||
assert keyset.public_keys is not None
|
||||
keys2 = await wallet1._get_keys_of_keyset(keyset.id)
|
||||
assert keys2.public_keys is not None
|
||||
assert len(keys1.public_keys) == len(keys2.public_keys)
|
||||
assert len(keyset.public_keys) == len(keys2.public_keys)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -130,32 +114,39 @@ async def test_get_keyset_from_db(wallet1: Wallet):
|
||||
assert keyset1.id == keyset2.id
|
||||
|
||||
# load it directly from the db
|
||||
keyset3 = await get_keyset(db=wallet1.db, id=keyset1.id)
|
||||
assert keyset3
|
||||
keysets_local = await get_keysets(db=wallet1.db, id=keyset1.id)
|
||||
assert keysets_local[0]
|
||||
keyset3 = keysets_local[0]
|
||||
assert keyset1.public_keys == keyset3.public_keys
|
||||
assert keyset1.id == keyset3.id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_info(wallet1: Wallet):
|
||||
info = await wallet1._get_info(wallet1.url)
|
||||
info = await wallet1._get_info()
|
||||
assert info.name
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_nonexistent_keyset(wallet1: Wallet):
|
||||
await assert_err(
|
||||
wallet1._get_keys_of_keyset(wallet1.url, "nonexistent"),
|
||||
wallet1._get_keys_of_keyset("nonexistent"),
|
||||
KeysetNotFoundError(),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_keyset_ids(wallet1: Wallet):
|
||||
keyset = await wallet1._get_keyset_ids(wallet1.url)
|
||||
assert isinstance(keyset, list)
|
||||
assert len(keyset) > 0
|
||||
assert keyset[-1] == wallet1.keyset_id
|
||||
keysets = await wallet1._get_keyset_ids()
|
||||
assert isinstance(keysets, list)
|
||||
assert len(keysets) > 0
|
||||
assert wallet1.keyset_id in keysets
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_request_mint(wallet1: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
assert invoice.payment_hash
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -181,9 +172,9 @@ async def test_mint(wallet1: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
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]
|
||||
invoice = await wallet1.request_mint(sum(amts))
|
||||
pay_if_regtest(invoice.bolt11)
|
||||
await wallet1.mint(amount=sum(amts), split=amts, id=invoice.id)
|
||||
assert wallet1.balance == 27
|
||||
assert wallet1.proof_amounts == amts
|
||||
@@ -192,9 +183,11 @@ async def test_mint_amounts(wallet1: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
async def test_mint_amounts_wrong_sum(wallet1: Wallet):
|
||||
"""Mint predefined amounts"""
|
||||
|
||||
amts = [1, 1, 1, 2, 2, 4, 16]
|
||||
invoice = await wallet1.request_mint(sum(amts))
|
||||
await assert_err(
|
||||
wallet1.mint(amount=sum(amts) + 1, split=amts),
|
||||
wallet1.mint(amount=sum(amts) + 1, split=amts, id=invoice.id),
|
||||
"split must sum to amount",
|
||||
)
|
||||
|
||||
@@ -203,8 +196,9 @@ async def test_mint_amounts_wrong_sum(wallet1: Wallet):
|
||||
async def test_mint_amounts_wrong_order(wallet1: Wallet):
|
||||
"""Mint amount that is not part in 2^n"""
|
||||
amts = [1, 2, 3]
|
||||
invoice = await wallet1.request_mint(sum(amts))
|
||||
await assert_err(
|
||||
wallet1.mint(amount=sum(amts), split=[1, 2, 3]),
|
||||
wallet1.mint(amount=sum(amts), split=[1, 2, 3], id=invoice.id),
|
||||
f"Can only mint amounts with 2^n up to {2**settings.max_order}.",
|
||||
)
|
||||
|
||||
@@ -257,42 +251,54 @@ async def test_split_more_than_balance(wallet1: Wallet):
|
||||
@pytest.mark.asyncio
|
||||
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)
|
||||
topup_invoice = await wallet1.request_mint(128)
|
||||
pay_if_regtest(topup_invoice.bolt11)
|
||||
await wallet1.mint(128, id=topup_invoice.id)
|
||||
assert wallet1.balance == 128
|
||||
|
||||
total_amount, fee_reserve_sat = await wallet1.get_pay_amount_with_fees(
|
||||
invoice.bolt11
|
||||
)
|
||||
assert total_amount == 66
|
||||
|
||||
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)
|
||||
invoice_payment_request = ""
|
||||
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"])
|
||||
invoice_payment_request = invoice_dict["payment_request"]
|
||||
|
||||
if is_fake:
|
||||
invoice = await wallet1.request_mint(64)
|
||||
invoice_payment_hash = str(invoice.payment_hash)
|
||||
invoice_payment_request = invoice.bolt11
|
||||
|
||||
quote = await wallet1.get_pay_amount_with_fees(invoice_payment_request)
|
||||
total_amount = quote.amount + quote.fee_reserve
|
||||
|
||||
if is_regtest:
|
||||
# we expect a fee reserve of 2 sat for regtest
|
||||
assert total_amount == 66
|
||||
assert quote.fee_reserve == 2
|
||||
if is_fake:
|
||||
# we expect a fee reserve of 0 sat for fake
|
||||
assert total_amount == 64
|
||||
assert quote.fee_reserve == 0
|
||||
|
||||
_, send_proofs = await wallet1.split_to_send(wallet1.proofs, total_amount)
|
||||
|
||||
melt_response = await wallet1.pay_lightning(
|
||||
send_proofs, invoice=invoice_to_pay, fee_reserve_sat=fee_reserve_sat
|
||||
proofs=send_proofs,
|
||||
invoice=invoice_payment_request,
|
||||
fee_reserve_sat=quote.fee_reserve,
|
||||
quote_id=quote.quote,
|
||||
)
|
||||
|
||||
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, "Wrong keyset returned"
|
||||
if is_regtest:
|
||||
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, "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, "No payment hash in invoice"
|
||||
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
|
||||
)
|
||||
@@ -305,7 +311,7 @@ async def test_melt(wallet1: Wallet):
|
||||
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), "Wrong balance"
|
||||
assert wallet1.balance == 128 - (total_amount - quote.fee_reserve), "Wrong balance"
|
||||
assert wallet1.balance == 64, "Wrong balance"
|
||||
|
||||
|
||||
@@ -368,23 +374,23 @@ async def test_send_and_redeem(wallet1: Wallet, wallet2: Wallet):
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalidate_unspent_proofs(wallet1: Wallet):
|
||||
async def test_invalidate_all_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
|
||||
assert wallet1.balance == 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_invalidate_unspent_proofs_without_checking(wallet1: Wallet):
|
||||
async def test_invalidate_unspent_proofs_with_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
|
||||
await wallet1.invalidate(wallet1.proofs, check_spendable=True)
|
||||
assert wallet1.balance == 64
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -405,5 +411,21 @@ async def test_token_state(wallet1: Wallet):
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
assert wallet1.balance == 64
|
||||
resp = await wallet1.check_proof_state(wallet1.proofs)
|
||||
assert resp.dict()["spendable"]
|
||||
assert resp.dict()["pending"]
|
||||
assert resp.states[0].state.value == "UNSPENT"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_load_mint_keys_specific_keyset(wallet1: Wallet):
|
||||
await wallet1._load_mint_keys()
|
||||
if settings.debug_mint_only_deprecated:
|
||||
assert list(wallet1.keysets.keys()) == ["eGnEWtdJ0PIM"]
|
||||
else:
|
||||
assert list(wallet1.keysets.keys()) == ["009a1f293253e41e", "eGnEWtdJ0PIM"]
|
||||
await wallet1._load_mint_keys(keyset_id=wallet1.keyset_id)
|
||||
await wallet1._load_mint_keys(keyset_id="009a1f293253e41e")
|
||||
# expect deprecated keyset id to be present
|
||||
await wallet1._load_mint_keys(keyset_id="eGnEWtdJ0PIM")
|
||||
await assert_err(
|
||||
wallet1._load_mint_keys(keyset_id="nonexistent"),
|
||||
KeysetNotFoundError(),
|
||||
)
|
||||
|
||||
@@ -12,14 +12,13 @@ from tests.helpers import is_regtest
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def wallet(mint):
|
||||
async def wallet():
|
||||
wallet = await Wallet.with_db(
|
||||
url=SERVER_ENDPOINT,
|
||||
db="test_data/wallet",
|
||||
name="wallet",
|
||||
)
|
||||
await wallet.load_mint()
|
||||
wallet.status()
|
||||
yield wallet
|
||||
|
||||
|
||||
|
||||
@@ -35,25 +35,23 @@ def assert_amt(proofs: List[Proof], expected: int):
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def wallet1(mint):
|
||||
async def wallet1():
|
||||
wallet1 = await Wallet1.with_db(
|
||||
SERVER_ENDPOINT, "test_data/wallet_p2pk_1", "wallet1"
|
||||
)
|
||||
await migrate_databases(wallet1.db, migrations)
|
||||
await wallet1.load_mint()
|
||||
wallet1.status()
|
||||
yield wallet1
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def wallet2(mint):
|
||||
async def wallet2():
|
||||
wallet2 = await Wallet2.with_db(
|
||||
SERVER_ENDPOINT, "test_data/wallet_p2pk_2", "wallet2"
|
||||
)
|
||||
await migrate_databases(wallet2.db, migrations)
|
||||
wallet2.private_key = PrivateKey(secrets.token_bytes(32), raw=True)
|
||||
await wallet2.load_mint()
|
||||
wallet2.status()
|
||||
yield wallet2
|
||||
|
||||
|
||||
|
||||
130
tests/test_wallet_lightning.py
Normal file
130
tests/test_wallet_lightning.py
Normal file
@@ -0,0 +1,130 @@
|
||||
from typing import List, Union
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from cashu.core.base import Proof
|
||||
from cashu.core.errors import CashuError
|
||||
from cashu.wallet.lightning import LightningWallet
|
||||
from tests.conftest import SERVER_ENDPOINT
|
||||
from tests.helpers import get_real_invoice, is_fake, is_regtest, pay_if_regtest
|
||||
|
||||
|
||||
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")
|
||||
|
||||
|
||||
def assert_amt(proofs: List[Proof], expected: int):
|
||||
"""Assert amounts the proofs contain."""
|
||||
assert [p.amount for p in proofs] == expected
|
||||
|
||||
|
||||
async def reset_wallet_db(wallet: LightningWallet):
|
||||
await wallet.db.execute("DELETE FROM proofs")
|
||||
await wallet.db.execute("DELETE FROM proofs_used")
|
||||
await wallet.db.execute("DELETE FROM keysets")
|
||||
await wallet._load_mint()
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def wallet():
|
||||
wallet = await LightningWallet.with_db(
|
||||
url=SERVER_ENDPOINT,
|
||||
db="test_data/wallet1",
|
||||
name="wallet1",
|
||||
)
|
||||
await wallet.async_init()
|
||||
yield wallet
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_invoice(wallet: LightningWallet):
|
||||
invoice = await wallet.create_invoice(64)
|
||||
assert invoice.payment_request
|
||||
assert invoice.payment_request.startswith("ln")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(is_regtest, reason="only works with FakeWallet")
|
||||
async def test_check_invoice_internal(wallet: LightningWallet):
|
||||
# fill wallet
|
||||
invoice = await wallet.create_invoice(64)
|
||||
assert invoice.payment_request
|
||||
assert invoice.checking_id
|
||||
status = await wallet.get_invoice_status(invoice.checking_id)
|
||||
assert status.paid
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(is_fake, reason="only works with Regtest")
|
||||
async def test_check_invoice_external(wallet: LightningWallet):
|
||||
# fill wallet
|
||||
invoice = await wallet.create_invoice(64)
|
||||
assert invoice.payment_request
|
||||
assert invoice.checking_id
|
||||
status = await wallet.get_invoice_status(invoice.checking_id)
|
||||
assert not status.paid
|
||||
pay_if_regtest(invoice.payment_request)
|
||||
status = await wallet.get_invoice_status(invoice.checking_id)
|
||||
assert status.paid
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(is_regtest, reason="only works with FakeWallet")
|
||||
async def test_pay_invoice_internal(wallet: LightningWallet):
|
||||
# fill wallet
|
||||
invoice = await wallet.create_invoice(64)
|
||||
assert invoice.payment_request
|
||||
assert invoice.checking_id
|
||||
await wallet.get_invoice_status(invoice.checking_id)
|
||||
assert wallet.available_balance >= 64
|
||||
|
||||
# pay invoice
|
||||
invoice2 = await wallet.create_invoice(16)
|
||||
assert invoice2.payment_request
|
||||
status = await wallet.pay_invoice(invoice2.payment_request)
|
||||
|
||||
assert status.ok
|
||||
|
||||
# check payment
|
||||
assert invoice2.checking_id
|
||||
status = await wallet.get_payment_status(invoice2.checking_id)
|
||||
assert status.paid
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(is_fake, reason="only works with Regtest")
|
||||
async def test_pay_invoice_external(wallet: LightningWallet):
|
||||
# fill wallet
|
||||
invoice = await wallet.create_invoice(64)
|
||||
assert invoice.payment_request
|
||||
assert invoice.checking_id
|
||||
pay_if_regtest(invoice.payment_request)
|
||||
status = await wallet.get_invoice_status(invoice.checking_id)
|
||||
assert status.paid
|
||||
assert wallet.available_balance >= 64
|
||||
|
||||
# pay invoice
|
||||
invoice_real = get_real_invoice(16)
|
||||
status = await wallet.pay_invoice(invoice_real["payment_request"])
|
||||
|
||||
assert status.ok
|
||||
|
||||
# check payment
|
||||
assert status.checking_id
|
||||
status = await wallet.get_payment_status(status.checking_id)
|
||||
assert status.paid
|
||||
@@ -1,12 +1,13 @@
|
||||
import asyncio
|
||||
import copy
|
||||
import json
|
||||
import secrets
|
||||
from typing import List
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from cashu.core.base import Proof
|
||||
from cashu.core.base import Proof, SpentState
|
||||
from cashu.core.crypto.secp import PrivateKey, PublicKey
|
||||
from cashu.core.migrations import migrate_databases
|
||||
from cashu.core.p2pk import SigFlags
|
||||
@@ -16,7 +17,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
|
||||
from tests.helpers import is_deprecated_api_only, pay_if_regtest
|
||||
|
||||
|
||||
async def assert_err(f, msg):
|
||||
@@ -36,25 +37,23 @@ def assert_amt(proofs: List[Proof], expected: int):
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def wallet1(mint):
|
||||
async def wallet1():
|
||||
wallet1 = await Wallet1.with_db(
|
||||
SERVER_ENDPOINT, "test_data/wallet_p2pk_1", "wallet1"
|
||||
)
|
||||
await migrate_databases(wallet1.db, migrations)
|
||||
await wallet1.load_mint()
|
||||
wallet1.status()
|
||||
yield wallet1
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def wallet2(mint):
|
||||
async def wallet2():
|
||||
wallet2 = await Wallet2.with_db(
|
||||
SERVER_ENDPOINT, "test_data/wallet_p2pk_2", "wallet2"
|
||||
)
|
||||
await migrate_databases(wallet2.db, migrations)
|
||||
wallet2.private_key = PrivateKey(secrets.token_bytes(32), raw=True)
|
||||
await wallet2.load_mint()
|
||||
wallet2.status()
|
||||
yield wallet2
|
||||
|
||||
|
||||
@@ -80,6 +79,16 @@ async def test_p2pk(wallet1: Wallet, wallet2: Wallet):
|
||||
)
|
||||
await wallet2.redeem(send_proofs)
|
||||
|
||||
proof_states = await wallet2.check_proof_state(send_proofs)
|
||||
assert all([p.state == SpentState.spent for p in proof_states.states])
|
||||
|
||||
if not is_deprecated_api_only:
|
||||
for state in proof_states.states:
|
||||
assert state.witness is not None
|
||||
witness_obj = json.loads(state.witness)
|
||||
assert len(witness_obj["signatures"]) == 1
|
||||
assert len(witness_obj["signatures"][0]) == 128
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_p2pk_sig_all(wallet1: Wallet, wallet2: Wallet):
|
||||
@@ -222,9 +231,9 @@ async def test_p2pk_locktime_with_second_refund_pubkey(
|
||||
secret_lock = await wallet1.create_p2pk_lock(
|
||||
garbage_pubkey.serialize().hex(), # create lock to unspendable pubkey
|
||||
locktime_seconds=2, # locktime
|
||||
tags=Tags(
|
||||
[["refund", pubkey_wallet2, pubkey_wallet1]]
|
||||
), # multiple refund pubkeys
|
||||
tags=Tags([
|
||||
["refund", pubkey_wallet2, pubkey_wallet1]
|
||||
]), # multiple refund pubkeys
|
||||
) # sender side
|
||||
_, send_proofs = await wallet1.split_to_send(
|
||||
wallet1.proofs, 8, secret_lock=secret_lock
|
||||
@@ -379,9 +388,9 @@ async def test_p2pk_multisig_with_wrong_first_private_key(
|
||||
|
||||
|
||||
def test_tags():
|
||||
tags = Tags(
|
||||
[["key1", "value1"], ["key2", "value2", "value2_1"], ["key2", "value3"]]
|
||||
)
|
||||
tags = Tags([
|
||||
["key1", "value1"], ["key2", "value2", "value2_1"], ["key2", "value3"]
|
||||
])
|
||||
assert tags.get_tag("key1") == "value1"
|
||||
assert tags["key1"] == "value1"
|
||||
assert tags.get_tag("key2") == "value2"
|
||||
|
||||
@@ -8,6 +8,7 @@ import pytest_asyncio
|
||||
from cashu.core.base import Proof
|
||||
from cashu.core.crypto.secp import PrivateKey
|
||||
from cashu.core.errors import CashuError
|
||||
from cashu.core.settings import settings
|
||||
from cashu.wallet.wallet import Wallet
|
||||
from cashu.wallet.wallet import Wallet as Wallet1
|
||||
from cashu.wallet.wallet import Wallet as Wallet2
|
||||
@@ -46,31 +47,29 @@ async def reset_wallet_db(wallet: Wallet):
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def wallet1(mint):
|
||||
async def wallet1():
|
||||
wallet1 = await Wallet1.with_db(
|
||||
url=SERVER_ENDPOINT,
|
||||
db="test_data/wallet1",
|
||||
name="wallet1",
|
||||
)
|
||||
await wallet1.load_mint()
|
||||
wallet1.status()
|
||||
yield wallet1
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def wallet2(mint):
|
||||
async def wallet2():
|
||||
wallet2 = await Wallet2.with_db(
|
||||
url=SERVER_ENDPOINT,
|
||||
db="test_data/wallet2",
|
||||
name="wallet2",
|
||||
)
|
||||
await wallet2.load_mint()
|
||||
wallet2.status()
|
||||
yield wallet2
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def wallet3(mint):
|
||||
async def wallet3():
|
||||
dirpath = Path("test_data/wallet3")
|
||||
if dirpath.exists() and dirpath.is_dir():
|
||||
shutil.rmtree(dirpath)
|
||||
@@ -83,11 +82,14 @@ async def wallet3(mint):
|
||||
await wallet3.db.execute("DELETE FROM proofs")
|
||||
await wallet3.db.execute("DELETE FROM proofs_used")
|
||||
await wallet3.load_mint()
|
||||
wallet3.status()
|
||||
yield wallet3
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.skipif(
|
||||
settings.debug_mint_only_deprecated,
|
||||
reason="settings.debug_mint_only_deprecated is set",
|
||||
)
|
||||
async def test_bump_secret_derivation(wallet3: Wallet):
|
||||
await wallet3._init_private_key(
|
||||
"half depart obvious quality work element tank gorilla view sugar picture"
|
||||
@@ -95,23 +97,27 @@ async def test_bump_secret_derivation(wallet3: Wallet):
|
||||
)
|
||||
secrets1, rs1, derivation_paths1 = await wallet3.generate_n_secrets(5)
|
||||
secrets2, rs2, derivation_paths2 = await wallet3.generate_secrets_from_to(0, 4)
|
||||
assert wallet3.keyset_id == "1cCNIAZ2X/w1"
|
||||
assert wallet3.keyset_id == "009a1f293253e41e"
|
||||
assert secrets1 == secrets2
|
||||
assert [r.private_key for r in rs1] == [r.private_key for r in rs2]
|
||||
assert derivation_paths1 == derivation_paths2
|
||||
for s in secrets1:
|
||||
print('"' + s + '",')
|
||||
assert secrets1 == [
|
||||
"9d32fc57e6fa2942d05ee475d28ba6a56839b8cb8a3f174b05ed0ed9d3a420f6",
|
||||
"1c0f2c32e7438e7cc992612049e9dfcdbffd454ea460901f24cc429921437802",
|
||||
"327c606b761af03cbe26fa13c4b34a6183b868c52cda059fe57fdddcb4e1e1e7",
|
||||
"53476919560398b56c0fdc5dd92cf8628b1e06de6f2652b0f7d6e8ac319de3b7",
|
||||
"b2f5d632229378a716be6752fc79ac8c2b43323b820859a7956f2dfe5432b7b4",
|
||||
"485875df74771877439ac06339e284c3acfcd9be7abf3bc20b516faeadfe77ae",
|
||||
"8f2b39e8e594a4056eb1e6dbb4b0c38ef13b1b2c751f64f810ec04ee35b77270",
|
||||
"bc628c79accd2364fd31511216a0fab62afd4a18ff77a20deded7b858c9860c8",
|
||||
"59284fd1650ea9fa17db2b3acf59ecd0f2d52ec3261dd4152785813ff27a33bf",
|
||||
"576c23393a8b31cc8da6688d9c9a96394ec74b40fdaf1f693a6bb84284334ea0",
|
||||
]
|
||||
for d in derivation_paths1:
|
||||
print('"' + d + '",')
|
||||
assert derivation_paths1 == [
|
||||
"m/129372'/0'/2004500376'/0'",
|
||||
"m/129372'/0'/2004500376'/1'",
|
||||
"m/129372'/0'/2004500376'/2'",
|
||||
"m/129372'/0'/2004500376'/3'",
|
||||
"m/129372'/0'/2004500376'/4'",
|
||||
"m/129372'/0'/864559728'/0'",
|
||||
"m/129372'/0'/864559728'/1'",
|
||||
"m/129372'/0'/864559728'/2'",
|
||||
"m/129372'/0'/864559728'/3'",
|
||||
"m/129372'/0'/864559728'/4'",
|
||||
]
|
||||
|
||||
|
||||
@@ -191,7 +197,7 @@ async def test_restore_wallet_after_split_to_send(wallet3: Wallet):
|
||||
assert wallet3.balance == 0
|
||||
await wallet3.restore_promises_from_to(0, 100)
|
||||
assert wallet3.balance == 64 * 2
|
||||
await wallet3.invalidate(wallet3.proofs)
|
||||
await wallet3.invalidate(wallet3.proofs, check_spendable=True)
|
||||
assert wallet3.balance == 64
|
||||
|
||||
|
||||
@@ -216,7 +222,7 @@ async def test_restore_wallet_after_send_and_receive(wallet3: Wallet, wallet2: W
|
||||
assert wallet3.balance == 0
|
||||
await wallet3.restore_promises_from_to(0, 100)
|
||||
assert wallet3.balance == 64 + 2 * 32
|
||||
await wallet3.invalidate(wallet3.proofs)
|
||||
await wallet3.invalidate(wallet3.proofs, check_spendable=True)
|
||||
assert wallet3.balance == 32
|
||||
|
||||
|
||||
@@ -257,7 +263,7 @@ async def test_restore_wallet_after_send_and_self_receive(wallet3: Wallet):
|
||||
assert wallet3.balance == 0
|
||||
await wallet3.restore_promises_from_to(0, 100)
|
||||
assert wallet3.balance == 64 + 2 * 32 + 32
|
||||
await wallet3.invalidate(wallet3.proofs)
|
||||
await wallet3.invalidate(wallet3.proofs, check_spendable=True)
|
||||
assert wallet3.balance == 64
|
||||
|
||||
|
||||
@@ -290,7 +296,7 @@ async def test_restore_wallet_after_send_twice(
|
||||
await wallet3.restore_promises_from_to(0, 10)
|
||||
box.add(wallet3.proofs)
|
||||
assert wallet3.balance == 5
|
||||
await wallet3.invalidate(wallet3.proofs)
|
||||
await wallet3.invalidate(wallet3.proofs, check_spendable=True)
|
||||
assert wallet3.balance == 2
|
||||
|
||||
# again
|
||||
@@ -310,7 +316,7 @@ async def test_restore_wallet_after_send_twice(
|
||||
await wallet3.restore_promises_from_to(0, 15)
|
||||
box.add(wallet3.proofs)
|
||||
assert wallet3.balance == 7
|
||||
await wallet3.invalidate(wallet3.proofs)
|
||||
await wallet3.invalidate(wallet3.proofs, check_spendable=True)
|
||||
assert wallet3.balance == 2
|
||||
|
||||
|
||||
@@ -345,7 +351,7 @@ async def test_restore_wallet_after_send_and_self_receive_nonquadratic_value(
|
||||
await wallet3.restore_promises_from_to(0, 20)
|
||||
box.add(wallet3.proofs)
|
||||
assert wallet3.balance == 138
|
||||
await wallet3.invalidate(wallet3.proofs)
|
||||
await wallet3.invalidate(wallet3.proofs, check_spendable=True)
|
||||
assert wallet3.balance == 64
|
||||
|
||||
# again
|
||||
@@ -362,5 +368,5 @@ async def test_restore_wallet_after_send_and_self_receive_nonquadratic_value(
|
||||
assert wallet3.balance == 0
|
||||
await wallet3.restore_promises_from_to(0, 50)
|
||||
assert wallet3.balance == 182
|
||||
await wallet3.invalidate(wallet3.proofs)
|
||||
await wallet3.invalidate(wallet3.proofs, check_spendable=True)
|
||||
assert wallet3.balance == 64
|
||||
|
||||
Reference in New Issue
Block a user