diff --git a/.env.example b/.env.example index 3e7ca31..daad78f 100644 --- a/.env.example +++ b/.env.example @@ -17,7 +17,7 @@ TOR=TRUE # NOSTR # nostr private key to which to receive tokens to -NOSTR_PRIVATE_KEY=nostr_privatekey_here_hex_or_bech32_nsec +# NOSTR_PRIVATE_KEY=nostr_privatekey_here_hex_or_bech32_nsec # nostr relays (comma separated list) NOSTR_RELAYS=["wss://nostr-pub.wellorder.net"] @@ -38,7 +38,6 @@ MINT_INFO_CONTACT=[["email","contact@me.com"], ["twitter","@me"], ["nostr", "np MINT_INFO_MOTD="Message to users" MINT_PRIVATE_KEY=supersecretprivatekey -MINT_DATABASE=data/mint # increment derivation path to rotate to a new keyset MINT_DERIVATION_PATH="0/0/0/0" diff --git a/.gitignore b/.gitignore index c894509..21e0d77 100644 --- a/.gitignore +++ b/.gitignore @@ -133,6 +133,7 @@ tor.pid # Default data directory /data +/test_data # MacOS .DS_Store diff --git a/Makefile b/Makefile index 613e020..3e17cb2 100644 --- a/Makefile +++ b/Makefile @@ -32,8 +32,6 @@ package: python setup.py sdist bdist_wheel test: - LIGHTNING=false \ - TOR=false \ poetry run pytest tests --cov-report xml --cov cashu install: diff --git a/cashu/wallet/api/router.py b/cashu/wallet/api/router.py index 667dd1a..7ad7e9d 100644 --- a/cashu/wallet/api/router.py +++ b/cashu/wallet/api/router.py @@ -38,32 +38,35 @@ from .responses import ( router: APIRouter = APIRouter() -def create_wallet( - url=settings.mint_url, dir=settings.cashu_dir, name=settings.wallet_name -): - return Wallet( - url=url, - db=os.path.join(dir, name), - name=name, +async def mint_wallet(mint_url: Optional[str] = None): + wallet: Wallet = await Wallet.with_db( + mint_url or settings.mint_url, + db=os.path.join(settings.cashu_dir, settings.wallet_name), + name=settings.wallet_name, ) - - -async def load_mint(wallet: Wallet, mint: Optional[str] = None): - if mint: - wallet = create_wallet(mint) - await init_wallet(wallet) await wallet.load_mint() return wallet -wallet = create_wallet() +wallet: Wallet = Wallet( + settings.mint_url, + db=os.path.join(settings.cashu_dir, settings.wallet_name), + name=settings.wallet_name, +) @router.on_event("startup") async def start_wallet(): + global wallet + wallet = await Wallet.with_db( + settings.mint_url, + db=os.path.join(settings.cashu_dir, settings.wallet_name), + name=settings.wallet_name, + ) + if settings.tor and not TorProxy().check_platform(): raise Exception("tor not working.") - await init_wallet(wallet) + await wallet.load_mint() @router.post("/pay", name="Pay lightning invoice", response_model=PayResponse) @@ -78,7 +81,7 @@ async def pay( raise Exception("lightning not enabled.") global wallet - wallet = await load_mint(wallet, mint) + wallet = await mint_wallet(mint) total_amount, fee_reserve_sat = await wallet.get_pay_amount_with_fees(invoice) assert total_amount > 0, "amount has to be larger than zero." @@ -116,7 +119,7 @@ async def invoice( print(f"Requesting split with {n_splits}*{split} sat tokens.") global wallet - wallet = await load_mint(wallet, mint) + wallet = await mint_wallet(mint) if not settings.lightning: await wallet.mint(amount, split=optional_split) return InvoiceResponse( @@ -150,8 +153,8 @@ async def swap( ): if not settings.lightning: raise Exception("lightning not supported") - incoming_wallet = await load_mint(wallet, mint=incoming_mint) - outgoing_wallet = await load_mint(wallet, mint=outgoing_mint) + incoming_wallet = await mint_wallet(incoming_mint) + outgoing_wallet = await mint_wallet(outgoing_mint) if incoming_wallet.url == outgoing_wallet.url: raise Exception("mints for swap have to be different") @@ -159,7 +162,7 @@ async def swap( invoice = await incoming_wallet.request_mint(amount) # pay invoice from outgoing mint - await outgoing_wallet.load_proofs() + await outgoing_wallet.load_proofs(reload=True) total_amount, fee_reserve_sat = await outgoing_wallet.get_pay_amount_with_fees( invoice.pr ) @@ -174,7 +177,7 @@ async def swap( # mint token in incoming mint await incoming_wallet.mint(amount, hash=invoice.hash) - await incoming_wallet.load_proofs() + await incoming_wallet.load_proofs(reload=True) mint_balances = await incoming_wallet.balance_per_minturl() return SwapResponse( outgoing_mint=outgoing_mint, @@ -191,7 +194,7 @@ async def swap( response_model=BalanceResponse, ) async def balance(): - await wallet.load_proofs() + await wallet.load_proofs(reload=True) keyset_balances = wallet.balance_per_keyset() mint_balances = await wallet.balance_per_minturl() return BalanceResponse( @@ -229,6 +232,7 @@ async def receive_command( nostr: bool = Query(default=False, description="Receive tokens via nostr"), all: bool = Query(default=False, description="Receive all pending tokens"), ): + wallet = await mint_wallet() initial_balance = wallet.available_balance if token: tokenObj: TokenV3 = deserialize_token_from_string(token) @@ -269,7 +273,7 @@ async def burn( ): global wallet if not delete: - wallet = await load_mint(wallet, mint) + wallet = await mint_wallet(mint) if not (all or token or force or delete) or (token and all): raise Exception( "enter a token or use --all to burn all pending tokens, --force to check all tokens" diff --git a/tests/conftest.py b/tests/conftest.py index a7d4c56..1404216 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -16,42 +16,36 @@ from cashu.lightning.fake import FakeWallet from cashu.mint import migrations as migrations_mint from cashu.mint.ledger import Ledger -SERVER_ENDPOINT = "http://localhost:3337" +SERVER_PORT = 3337 +SERVER_ENDPOINT = f"http://localhost:{SERVER_PORT}" + +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 = False +settings.tor = False +settings.mint_lightning_backend = "FakeWallet" +settings.mint_database = "./test_data/test_mint" +settings.mint_derivation_path = "0/0/0/0" +settings.mint_private_key = "TEST_PRIVATE_KEY" + +shutil.rmtree(settings.cashu_dir, ignore_errors=True) +Path(settings.cashu_dir).mkdir(parents=True, exist_ok=True) class UvicornServer(multiprocessing.Process): - def __init__(self, config: Config, private_key: str = "TEST_PRIVATE_KEY"): + def __init__(self, config: Config): super().__init__() self.server = Server(config=config) self.config = config - self.private_key = private_key def stop(self): self.terminate() def run(self, *args, **kwargs): - settings.lightning = False - settings.mint_lightning_backend = "FakeWallet" - settings.mint_database = "data/test_mint" - settings.mint_private_key = self.private_key - settings.mint_derivation_path = "0/0/0/0" - - dirpath = Path(settings.mint_database) - if dirpath.exists() and dirpath.is_dir(): - shutil.rmtree(dirpath) - - dirpath = Path("data/wallet1") - if dirpath.exists() and dirpath.is_dir(): - shutil.rmtree(dirpath) - - dirpath = Path("data/wallet2") - if dirpath.exists() and dirpath.is_dir(): - shutil.rmtree(dirpath) - - dirpath = Path("data/wallet3") - if dirpath.exists() and dirpath.is_dir(): - shutil.rmtree(dirpath) - self.server.run() @@ -62,13 +56,13 @@ async def ledger(): await ledger.load_used_proofs() await ledger.init_keysets() - db_file = "data/mint/test.sqlite3" + db_file = "test_data/mint/test.sqlite3" if os.path.exists(db_file): os.remove(db_file) ledger = Ledger( - db=Database("test", "data/mint"), - seed="TEST_PRIVATE_KEY", - derivation_path="0/0/0/0", + db=Database("test", "test_data/mint"), + seed=settings.mint_private_key, + derivation_path=settings.mint_derivation_path, lightning=FakeWallet(), ) await start_mint_init(ledger) @@ -77,12 +71,10 @@ async def ledger(): @pytest.fixture(autouse=True, scope="session") def mint(): - settings.mint_listen_port = 3337 - settings.mint_url = "http://localhost:3337" config = uvicorn.Config( "cashu.mint.app:app", port=settings.mint_listen_port, - host="127.0.0.1", + host=settings.mint_listen_host, ) server = UvicornServer(config=config) diff --git a/tests/test_cli.py b/tests/test_cli.py index f84223f..b2d2e65 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -16,14 +16,13 @@ def cli_prefix(): async def init_wallet(): wallet = await Wallet.with_db( url=settings.mint_host, - db="data/test_cli_wallet", + db="test_data/test_cli_wallet", name="wallet", ) await wallet.load_proofs() return wallet -@pytest.mark.asyncio def test_info(cli_prefix): runner = CliRunner() result = runner.invoke( @@ -37,7 +36,6 @@ def test_info(cli_prefix): assert result.exit_code == 0 -@pytest.mark.asyncio def test_info_with_mint(cli_prefix): runner = CliRunner() result = runner.invoke( @@ -51,7 +49,6 @@ def test_info_with_mint(cli_prefix): assert result.exit_code == 0 -@pytest.mark.asyncio def test_info_with_mnemonic(cli_prefix): runner = CliRunner() result = runner.invoke( @@ -65,7 +62,6 @@ def test_info_with_mnemonic(cli_prefix): assert result.exit_code == 0 -@pytest.mark.asyncio def test_balance(cli_prefix): runner = CliRunner() result = runner.invoke( @@ -80,7 +76,6 @@ def test_balance(cli_prefix): assert result.exit_code == 0 -@pytest.mark.asyncio def test_invoice(mint, cli_prefix): runner = CliRunner() result = runner.invoke( @@ -96,7 +91,6 @@ def test_invoice(mint, cli_prefix): assert result.exit_code == 0 -@pytest.mark.asyncio def test_invoice_with_split(mint, cli_prefix): runner = CliRunner() result = runner.invoke( @@ -108,7 +102,6 @@ def test_invoice_with_split(mint, cli_prefix): # assert wallet.proof_amounts.count(1) >= 10 -@pytest.mark.asyncio def test_wallets(cli_prefix): runner = CliRunner() result = runner.invoke( @@ -123,7 +116,6 @@ def test_wallets(cli_prefix): assert result.exit_code == 0 -@pytest.mark.asyncio def test_send(mint, cli_prefix): runner = CliRunner() result = runner.invoke( @@ -136,7 +128,6 @@ def test_send(mint, cli_prefix): assert "cashuA" in result.output, "output does not have a token" -@pytest.mark.asyncio def test_send_without_split(mint, cli_prefix): runner = CliRunner() result = runner.invoke( @@ -149,7 +140,6 @@ def test_send_without_split(mint, cli_prefix): assert "cashuA" in result.output, "output does not have a token" -@pytest.mark.asyncio def test_send_without_split_but_wrong_amount(mint, cli_prefix): runner = CliRunner() result = runner.invoke( @@ -159,7 +149,6 @@ def test_send_without_split_but_wrong_amount(mint, cli_prefix): assert "No proof with this amount found" in str(result.exception) -@pytest.mark.asyncio def test_receive_tokenv3(mint, cli_prefix): runner = CliRunner() token = ( @@ -181,10 +170,10 @@ def test_receive_tokenv3(mint, cli_prefix): print(result.output) -@pytest.mark.asyncio def test_receive_tokenv3_no_mint(mint, cli_prefix): - # this test works only if the previous test succeeds because we simulate the case 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 + # this test works only if the previous test succeeds because we simulate the case + # 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" @@ -205,7 +194,6 @@ def test_receive_tokenv3_no_mint(mint, cli_prefix): print(result.output) -@pytest.mark.asyncio def test_receive_tokenv2(mint, cli_prefix): runner = CliRunner() token = ( @@ -223,7 +211,6 @@ def test_receive_tokenv2(mint, cli_prefix): print(result.output) -@pytest.mark.asyncio def test_receive_tokenv1(mint, cli_prefix): runner = CliRunner() token = ( @@ -240,7 +227,6 @@ def test_receive_tokenv1(mint, cli_prefix): print(result.output) -@pytest.mark.asyncio() def test_nostr_send(mint, cli_prefix): runner = CliRunner() result = runner.invoke( diff --git a/tests/test_mint.py b/tests/test_mint.py index 1be52a1..2a5e22d 100644 --- a/tests/test_mint.py +++ b/tests/test_mint.py @@ -8,8 +8,6 @@ from cashu.core.helpers import calculate_number_of_blank_outputs from cashu.core.settings import settings from cashu.mint.ledger import Ledger -SERVER_ENDPOINT = "http://localhost:3338" - async def assert_err(f, msg): """Compute f() and expect an error message 'msg'.""" diff --git a/tests/test_wallet.py b/tests/test_wallet.py index fdcf664..dc2d850 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -50,7 +50,7 @@ async def reset_wallet_db(wallet: Wallet): async def wallet1(mint): wallet1 = await Wallet1.with_db( url=SERVER_ENDPOINT, - db="data/wallet1", + db="test_data/wallet1", name="wallet1", ) await wallet1.load_mint() @@ -62,7 +62,7 @@ async def wallet1(mint): async def wallet2(mint): wallet2 = await Wallet2.with_db( url=SERVER_ENDPOINT, - db="data/wallet2", + db="test_data/wallet2", name="wallet2", ) await wallet2.load_mint() @@ -72,13 +72,13 @@ async def wallet2(mint): @pytest_asyncio.fixture(scope="function") async def wallet3(mint): - dirpath = Path("data/wallet3") + dirpath = Path("test_data/wallet3") if dirpath.exists() and dirpath.is_dir(): shutil.rmtree(dirpath) wallet3 = await Wallet1.with_db( url=SERVER_ENDPOINT, - db="data/wallet3", + db="test_data/wallet3", name="wallet3", ) await wallet3.db.execute("DELETE FROM proofs") @@ -313,6 +313,7 @@ async def test_p2sh_receive_with_wrong_wallet(wallet1: Wallet, wallet2: Wallet): await assert_err(wallet2.redeem(send_proofs), "lock not found.") # wrong receiver +@pytest.mark.asyncio async def test_token_state(wallet1: Wallet): await wallet1.mint(64) assert wallet1.balance == 64 @@ -321,6 +322,7 @@ async def test_token_state(wallet1: Wallet): assert resp.dict()["pending"] +@pytest.mark.asyncio async def test_bump_secret_derivation(wallet3: Wallet): await wallet3._init_private_key( "half depart obvious quality work element tank gorilla view sugar picture humble" diff --git a/tests/test_wallet_api.py b/tests/test_wallet_api.py index bda4868..799f834 100644 --- a/tests/test_wallet_api.py +++ b/tests/test_wallet_api.py @@ -12,8 +12,8 @@ from tests.conftest import SERVER_ENDPOINT async def wallet(mint): wallet = await Wallet.with_db( url=SERVER_ENDPOINT, - db="data/test_wallet_api", - name="wallet_api", + db="test_data/wallet", + name="wallet", ) await wallet.load_mint() wallet.status() @@ -89,7 +89,7 @@ async def test_receive_all(wallet: Wallet): with TestClient(app) as client: response = client.post("/receive?all=true") assert response.status_code == 200 - assert response.json()["initial_balance"] + assert response.json()["initial_balance"] == 0 assert response.json()["balance"] @@ -100,7 +100,7 @@ async def test_burn_all(wallet: Wallet): assert response.status_code == 200 response = client.post("/burn?all=true") assert response.status_code == 200 - assert response.json()["balance"] + assert response.json()["balance"] == 0 @pytest.mark.asyncio diff --git a/tests/test_wallet_p2pk.py b/tests/test_wallet_p2pk.py index e92a850..50101af 100644 --- a/tests/test_wallet_p2pk.py +++ b/tests/test_wallet_p2pk.py @@ -34,7 +34,9 @@ def assert_amt(proofs: List[Proof], expected: int): @pytest_asyncio.fixture(scope="function") async def wallet1(mint): - wallet1 = await Wallet1.with_db(SERVER_ENDPOINT, "data/wallet_p2pk_1", "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() @@ -43,7 +45,9 @@ async def wallet1(mint): @pytest_asyncio.fixture(scope="function") async def wallet2(mint): - wallet2 = await Wallet2.with_db(SERVER_ENDPOINT, "data/wallet_p2pk_2", "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() @@ -95,7 +99,7 @@ async def test_p2pk_short_locktime_receive_with_wrong_private_key( pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # receiver side # sender side secret_lock = await wallet1.create_p2pk_lock( - pubkey_wallet2, locktime_seconds=4 + pubkey_wallet2, locktime_seconds=2 ) # sender side _, send_proofs = await wallet1.split_to_send( wallet1.proofs, 8, secret_lock=secret_lock @@ -107,7 +111,7 @@ async def test_p2pk_short_locktime_receive_with_wrong_private_key( wallet2.redeem(send_proofs), "Mint Error: no valid signature provided for input.", ) - await asyncio.sleep(6) + await asyncio.sleep(2) # should succeed because even with the wrong private key we # can redeem the tokens after the locktime await wallet2.redeem(send_proofs_copy) @@ -122,7 +126,7 @@ async def test_p2pk_locktime_with_refund_pubkey(wallet1: Wallet, wallet2: Wallet assert garbage_pubkey secret_lock = await wallet1.create_p2pk_lock( garbage_pubkey.serialize().hex(), # create lock to unspendable pubkey - locktime_seconds=4, # locktime + locktime_seconds=2, # locktime tags=Tags([["refund", pubkey_wallet2]]), # refund pubkey ) # sender side _, send_proofs = await wallet1.split_to_send( @@ -134,7 +138,7 @@ async def test_p2pk_locktime_with_refund_pubkey(wallet1: Wallet, wallet2: Wallet wallet2.redeem(send_proofs), "Mint Error: no valid signature provided for input.", ) - await asyncio.sleep(6) + await asyncio.sleep(2) # we can now redeem because of the refund locktime await wallet2.redeem(send_proofs_copy) @@ -150,7 +154,7 @@ async def test_p2pk_locktime_with_wrong_refund_pubkey(wallet1: Wallet, wallet2: assert garbage_pubkey_2 secret_lock = await wallet1.create_p2pk_lock( garbage_pubkey.serialize().hex(), # create lock to unspendable pubkey - locktime_seconds=4, # locktime + locktime_seconds=2, # locktime tags=Tags([["refund", garbage_pubkey_2.serialize().hex()]]), # refund pubkey ) # sender side _, send_proofs = await wallet1.split_to_send( @@ -162,7 +166,7 @@ async def test_p2pk_locktime_with_wrong_refund_pubkey(wallet1: Wallet, wallet2: wallet2.redeem(send_proofs), "Mint Error: no valid signature provided for input.", ) - await asyncio.sleep(6) + await asyncio.sleep(2) # we still can't redeem it because we used garbage_pubkey_2 as a refund pubkey await assert_err( wallet2.redeem(send_proofs_copy), diff --git a/tests/test_wallet_p2sh.py b/tests/test_wallet_p2sh.py index ebfe2a1..9540204 100644 --- a/tests/test_wallet_p2sh.py +++ b/tests/test_wallet_p2sh.py @@ -33,7 +33,9 @@ def assert_amt(proofs: List[Proof], expected: int): @pytest_asyncio.fixture(scope="function") async def wallet1(mint): - wallet1 = await Wallet1.with_db(SERVER_ENDPOINT, "data/wallet_p2sh_1", "wallet1") + wallet1 = await Wallet1.with_db( + SERVER_ENDPOINT, "test_data/wallet_p2sh_1", "wallet1" + ) await migrate_databases(wallet1.db, migrations) await wallet1.load_mint() wallet1.status() @@ -42,7 +44,9 @@ async def wallet1(mint): @pytest_asyncio.fixture(scope="function") async def wallet2(mint): - wallet2 = await Wallet2.with_db(SERVER_ENDPOINT, "data/wallet_p2sh_2", "wallet2") + wallet2 = await Wallet2.with_db( + SERVER_ENDPOINT, "test_data/wallet_p2sh_2", "wallet2" + ) await migrate_databases(wallet2.db, migrations) wallet2.private_key = PrivateKey(secrets.token_bytes(32), raw=True) await wallet2.load_mint()