mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 10:34:20 +01:00
* 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
279 lines
8.9 KiB
Python
279 lines
8.9 KiB
Python
from typing import List
|
|
|
|
import pytest
|
|
|
|
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):
|
|
"""Compute f() and expect an error message 'msg'."""
|
|
try:
|
|
await f
|
|
except Exception as exc:
|
|
assert exc.args[0] == msg, Exception(
|
|
f"Expected error: {msg}, got: {exc.args[0]}"
|
|
)
|
|
|
|
|
|
def assert_amt(proofs: List[Proof], expected: int):
|
|
"""Assert amounts the proofs contain."""
|
|
assert [p.amount for p in proofs] == expected
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_pubkeys(ledger: Ledger):
|
|
assert ledger.keyset.public_keys
|
|
assert (
|
|
ledger.keyset.public_keys[1].serialize().hex()
|
|
== "02194603ffa36356f4a56b7df9371fc3192472351453ec7398b8da8117e7c3e104"
|
|
)
|
|
assert (
|
|
ledger.keyset.public_keys[2 ** (settings.max_order - 1)].serialize().hex()
|
|
== "023c84c0895cc0e827b348ea0a62951ca489a5e436f3ea7545f3c1d5f1bea1c866"
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_privatekeys(ledger: Ledger):
|
|
assert ledger.keyset.private_keys
|
|
assert (
|
|
ledger.keyset.private_keys[1].serialize()
|
|
== "8300050453f08e6ead1296bb864e905bd46761beed22b81110fae0751d84604d"
|
|
)
|
|
assert (
|
|
ledger.keyset.private_keys[2 ** (settings.max_order - 1)].serialize()
|
|
== "b0477644cb3d82ffcc170bc0a76e0409727232e87c5ae51d64a259936228c7be"
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_keysets(ledger: Ledger):
|
|
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
|
|
async def test_get_keyset(ledger: Ledger):
|
|
keyset = ledger.get_keyset()
|
|
assert isinstance(keyset, dict)
|
|
assert len(keyset) == settings.max_order
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_mint(ledger: Ledger):
|
|
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(outputs=blinded_messages_mock, quote_id=quote.quote)
|
|
assert len(promises)
|
|
assert promises[0].amount == 8
|
|
assert (
|
|
promises[0].C_
|
|
== "031422eeffb25319e519c68de000effb294cb362ef713a7cf4832cea7b0452ba6e"
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_mint_invalid_blinded_message(ledger: Ledger):
|
|
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(outputs=blinded_messages_mock_invalid_key, quote_id=quote.quote),
|
|
"invalid public key",
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_generate_promises(ledger: Ledger):
|
|
blinded_messages_mock = [
|
|
BlindedMessage(
|
|
amount=8,
|
|
B_="02634a2c2b34bec9e8a4aba4361f6bf202d7fa2365379b0840afe249a7a9d71239",
|
|
id="009a1f293253e41e",
|
|
)
|
|
]
|
|
promises = await ledger._generate_promises(blinded_messages_mock)
|
|
assert (
|
|
promises[0].C_
|
|
== "031422eeffb25319e519c68de000effb294cb362ef713a7cf4832cea7b0452ba6e"
|
|
)
|
|
assert promises[0].amount == 8
|
|
assert promises[0].id == "009a1f293253e41e"
|
|
|
|
# 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_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
|
|
# token amount works: `n_blank_outputs != n_returned_promises != 4`.
|
|
invoice_amount = 100_000
|
|
fee_reserve = 2_000
|
|
total_provided = invoice_amount + fee_reserve
|
|
actual_fee = 100
|
|
|
|
expected_returned_promises = 7 # Amounts = [4, 8, 32, 64, 256, 512, 1024]
|
|
expected_returned_fees = 1900
|
|
|
|
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(),
|
|
id="009a1f293253e41e",
|
|
)
|
|
for b, _ in blinded_msgs
|
|
]
|
|
|
|
promises = await ledger._generate_change_promises(
|
|
total_provided, invoice_amount, actual_fee, outputs
|
|
)
|
|
|
|
assert len(promises) == expected_returned_promises
|
|
assert sum([promise.amount for promise in promises]) == expected_returned_fees
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_generate_change_promises_legacy_wallet(ledger: Ledger):
|
|
# Check if mint handles a legacy wallet implementation (always sends 4 blank
|
|
# outputs) as well.
|
|
invoice_amount = 100_000
|
|
fee_reserve = 2_000
|
|
total_provided = invoice_amount + fee_reserve
|
|
actual_fee = 100
|
|
|
|
expected_returned_promises = 4 # Amounts = [64, 256, 512, 1024]
|
|
expected_returned_fees = 1856
|
|
|
|
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(),
|
|
id="009a1f293253e41e",
|
|
)
|
|
for b, _ in blinded_msgs
|
|
]
|
|
|
|
promises = await ledger._generate_change_promises(
|
|
total_provided, invoice_amount, actual_fee, outputs
|
|
)
|
|
|
|
assert len(promises) == expected_returned_promises
|
|
assert sum([promise.amount for promise in promises]) == expected_returned_fees
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_generate_change_promises_returns_empty_if_no_outputs(ledger: Ledger):
|
|
invoice_amount = 100_000
|
|
fee_reserve = 1_000
|
|
total_provided = invoice_amount + fee_reserve
|
|
actual_fee_msat = 100_000
|
|
outputs = None
|
|
|
|
promises = await ledger._generate_change_promises(
|
|
total_provided, invoice_amount, actual_fee_msat, outputs
|
|
)
|
|
assert len(promises) == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_balance(ledger: Ledger):
|
|
balance = await ledger.get_balance()
|
|
assert balance == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_maximum_balance(ledger: Ledger):
|
|
settings.mint_max_balance = 1000
|
|
await ledger.mint_quote(PostMintQuoteRequest(amount=8, unit="sat"))
|
|
await assert_err(
|
|
ledger.mint_quote(PostMintQuoteRequest(amount=8000, unit="sat")),
|
|
"Mint has reached maximum balance.",
|
|
)
|
|
settings.mint_max_balance = 0
|