From 385801a7054881168d7b5a4201ef19fb088d997d Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 3 Oct 2023 16:54:27 +0200 Subject: [PATCH 1/3] refactor wallet restore tests --- cashu/wallet/wallet.py | 2 +- tests/test_wallet.py | 310 +----------------------------- tests/test_wallet_restore.py | 359 +++++++++++++++++++++++++++++++++++ 3 files changed, 362 insertions(+), 309 deletions(-) create mode 100644 tests/test_wallet_restore.py diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index 15a7a4bf..c761e507 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -1368,7 +1368,7 @@ async def restore_promises( outputs (List[BlindedMessage]): Outputs for which we request promises secrets (List[str]): Secrets generated for the outputs rs (List[PrivateKey]): Random blinding factors generated for the outputs - derivation_paths (List[str]): Derivation paths for the secrets + derivation_paths (List[str]): Derivation paths used for the secrets necessary to unblind the promises Returns: List[Proof]: List of restored proofs diff --git a/tests/test_wallet.py b/tests/test_wallet.py index d401be53..b8519332 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -1,12 +1,11 @@ import shutil from pathlib import Path -from typing import Dict, List, Union +from typing import List, Union import pytest import pytest_asyncio from cashu.core.base import Proof -from cashu.core.crypto.secp import PrivateKey, PublicKey from cashu.core.errors import CashuError, KeysetNotFoundError from cashu.core.helpers import sum_proofs from cashu.core.settings import settings @@ -94,6 +93,7 @@ async def test_get_keys(wallet1: Wallet): assert len(wallet1.keys.public_keys) == settings.max_order keyset = await wallet1._get_keys(wallet1.url) assert keyset.id is not None + assert keyset.id == "1cCNIAZ2X/w1" assert isinstance(keyset.id, str) assert len(keyset.id) > 0 @@ -312,40 +312,6 @@ async def test_split_invalid_amount(wallet1: Wallet): ) -@pytest.mark.asyncio -async def test_create_p2pk_pubkey(wallet1: Wallet): - invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) - pubkey = await wallet1.create_p2pk_pubkey() - PublicKey(bytes.fromhex(pubkey), raw=True) - - -@pytest.mark.asyncio -async def test_p2sh(wallet1: Wallet, wallet2: Wallet): - invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) - _ = await wallet1.create_p2sh_address_and_store() # receiver side - _, send_proofs = await wallet1.split_to_send(wallet1.proofs, 8) # sender side - - frst_proofs, scnd_proofs = await wallet2.redeem(send_proofs) # receiver side - assert len(frst_proofs) == 0 - assert len(scnd_proofs) == 1 - assert sum_proofs(scnd_proofs) == 8 - assert wallet2.balance == 8 - - -@pytest.mark.asyncio -async def test_p2sh_receive_with_wrong_wallet(wallet1: Wallet, wallet2: Wallet): - invoice = await wallet1.request_mint(64) - await wallet1.mint(64, hash=invoice.hash) - wallet1_address = await wallet1.create_p2sh_address_and_store() # receiver side - secret_lock = await wallet1.create_p2sh_lock(wallet1_address) # sender side - _, send_proofs = await wallet1.split_to_send( - wallet1.proofs, 8, secret_lock - ) # sender side - await assert_err(wallet2.redeem(send_proofs), "lock not found.") # wrong receiver - - @pytest.mark.asyncio async def test_token_state(wallet1: Wallet): invoice = await wallet1.request_mint(64) @@ -354,275 +320,3 @@ async def test_token_state(wallet1: Wallet): resp = await wallet1.check_proof_state(wallet1.proofs) assert resp.dict()["spendable"] 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" - ) - secrets1, rs1, derivation_paths1 = await wallet3.generate_n_secrets(5) - secrets2, rs2, derivation_paths2 = await wallet3.generate_secrets_from_to(0, 4) - assert secrets1 == secrets2 - assert [r.private_key for r in rs1] == [r.private_key for r in rs2] - assert derivation_paths1 == derivation_paths2 - assert secrets1 == [ - "9bfb12704297fe90983907d122838940755fcce370ce51e9e00a4275a347c3fe", - "dbc5e05f2b1f24ec0e2ab6e8312d5e13f57ada52594d4caf429a697d9c742490", - "06a29fa8081b3a620b50b473fc80cde9a575c3b94358f3513c03007f8b66321e", - "652d08c804bd2c5f2c1f3e3d8895860397df394b30473753227d766affd15e89", - "654e5997f8a20402f7487296b6f7e463315dd52fc6f6cc5a4e35c7f6ccac77e0", - ] - 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'", - ] - - -@pytest.mark.asyncio -async def test_bump_secret_derivation_two_steps(wallet3: Wallet): - await wallet3._init_private_key( - "half depart obvious quality work element tank gorilla view sugar picture" - " humble" - ) - secrets1_1, rs1_1, derivation_paths1 = await wallet3.generate_n_secrets(2) - secrets1_2, rs1_2, derivation_paths2 = await wallet3.generate_n_secrets(3) - secrets1 = secrets1_1 + secrets1_2 - rs1 = rs1_1 + rs1_2 - secrets2, rs2, derivation_paths = await wallet3.generate_secrets_from_to(0, 4) - assert secrets1 == secrets2 - assert [r.private_key for r in rs1] == [r.private_key for r in rs2] - - -@pytest.mark.asyncio -async def test_generate_secrets_from_to(wallet3: Wallet): - await wallet3._init_private_key( - "half depart obvious quality work element tank gorilla view sugar picture" - " humble" - ) - secrets1, rs1, derivation_paths1 = await wallet3.generate_secrets_from_to(0, 4) - assert len(secrets1) == 5 - secrets2, rs2, derivation_paths2 = await wallet3.generate_secrets_from_to(2, 4) - assert len(secrets2) == 3 - assert secrets1[2:] == secrets2 - assert [r.private_key for r in rs1[2:]] == [r.private_key for r in rs2] - - -@pytest.mark.asyncio -async def test_restore_wallet_after_mint(wallet3: Wallet): - await reset_wallet_db(wallet3) - invoice = await wallet3.request_mint(64) - await wallet3.mint(64, hash=invoice.hash) - assert wallet3.balance == 64 - await reset_wallet_db(wallet3) - await wallet3.load_proofs() - wallet3.proofs = [] - assert wallet3.balance == 0 - await wallet3.restore_promises_from_to(0, 20) - assert wallet3.balance == 64 - - -@pytest.mark.asyncio -async def test_restore_wallet_with_invalid_mnemonic(wallet3: Wallet): - await assert_err( - wallet3._init_private_key( - "half depart obvious quality work element tank gorilla view sugar picture" - " picture" - ), - "Invalid mnemonic", - ) - - -@pytest.mark.asyncio -async def test_restore_wallet_after_split_to_send(wallet3: Wallet): - await wallet3._init_private_key( - "half depart obvious quality work element tank gorilla view sugar picture" - " humble" - ) - await reset_wallet_db(wallet3) - - invoice = await wallet3.request_mint(64) - await wallet3.mint(64, hash=invoice.hash) - assert wallet3.balance == 64 - - _, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 32, set_reserved=True) # type: ignore - - await reset_wallet_db(wallet3) - await wallet3.load_proofs() - wallet3.proofs = [] - assert wallet3.balance == 0 - await wallet3.restore_promises_from_to(0, 100) - assert wallet3.balance == 64 * 2 - await wallet3.invalidate(wallet3.proofs) - assert wallet3.balance == 64 - - -@pytest.mark.asyncio -async def test_restore_wallet_after_send_and_receive(wallet3: Wallet, wallet2: Wallet): - await wallet3._init_private_key( - "hello rug want adapt talent together lunar method bean expose beef position" - ) - await reset_wallet_db(wallet3) - invoice = await wallet3.request_mint(64) - await wallet3.mint(64, hash=invoice.hash) - assert wallet3.balance == 64 - - _, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 32, set_reserved=True) # type: ignore - - await wallet2.redeem(spendable_proofs) - - await reset_wallet_db(wallet3) - await wallet3.load_proofs(reload=True) - assert wallet3.proofs == [] - assert wallet3.balance == 0 - await wallet3.restore_promises_from_to(0, 100) - assert wallet3.balance == 64 + 2 * 32 - await wallet3.invalidate(wallet3.proofs) - assert wallet3.balance == 32 - - -class ProofBox: - proofs: Dict[str, Proof] = {} - - def add(self, proofs: List[Proof]) -> None: - for proof in proofs: - if proof.secret in self.proofs: - if self.proofs[proof.secret].C != proof.C: - print("Proofs are not equal") - print(self.proofs[proof.secret]) - print(proof) - else: - self.proofs[proof.secret] = proof - - -@pytest.mark.asyncio -async def test_restore_wallet_after_send_and_self_receive(wallet3: Wallet): - await wallet3._init_private_key( - "lucky broken tell exhibit shuffle tomato ethics virus rabbit spread measure" - " text" - ) - await reset_wallet_db(wallet3) - - invoice = await wallet3.request_mint(64) - await wallet3.mint(64, hash=invoice.hash) - assert wallet3.balance == 64 - - _, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 32, set_reserved=True) # type: ignore - - await wallet3.redeem(spendable_proofs) - - await reset_wallet_db(wallet3) - await wallet3.load_proofs(reload=True) - assert wallet3.proofs == [] - assert wallet3.balance == 0 - await wallet3.restore_promises_from_to(0, 100) - assert wallet3.balance == 64 + 2 * 32 + 32 - await wallet3.invalidate(wallet3.proofs) - assert wallet3.balance == 64 - - -@pytest.mark.asyncio -async def test_restore_wallet_after_send_twice( - wallet3: Wallet, -): - box = ProofBox() - wallet3.private_key = PrivateKey() - await reset_wallet_db(wallet3) - - invoice = await wallet3.request_mint(2) - await wallet3.mint(2, hash=invoice.hash) - box.add(wallet3.proofs) - assert wallet3.balance == 2 - - keep_proofs, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 1, set_reserved=True) # type: ignore - box.add(wallet3.proofs) - assert wallet3.available_balance == 1 - await wallet3.redeem(spendable_proofs) - box.add(wallet3.proofs) - assert wallet3.available_balance == 2 - assert wallet3.balance == 2 - - await reset_wallet_db(wallet3) - await wallet3.load_proofs(reload=True) - assert wallet3.proofs == [] - assert wallet3.balance == 0 - await wallet3.restore_promises_from_to(0, 10) - box.add(wallet3.proofs) - assert wallet3.balance == 5 - await wallet3.invalidate(wallet3.proofs) - assert wallet3.balance == 2 - - # again - - _, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 1, set_reserved=True) # type: ignore - box.add(wallet3.proofs) - - assert wallet3.available_balance == 1 - await wallet3.redeem(spendable_proofs) - box.add(wallet3.proofs) - assert wallet3.available_balance == 2 - - await reset_wallet_db(wallet3) - await wallet3.load_proofs(reload=True) - assert wallet3.proofs == [] - assert wallet3.balance == 0 - await wallet3.restore_promises_from_to(0, 15) - box.add(wallet3.proofs) - assert wallet3.balance == 7 - await wallet3.invalidate(wallet3.proofs) - assert wallet3.balance == 2 - - -@pytest.mark.asyncio -async def test_restore_wallet_after_send_and_self_receive_nonquadratic_value( - wallet3: Wallet, -): - box = ProofBox() - await wallet3._init_private_key( - "casual demise flight cradle feature hub link slim remember anger front asthma" - ) - await reset_wallet_db(wallet3) - - invoice = await wallet3.request_mint(64) - await wallet3.mint(64, hash=invoice.hash) - box.add(wallet3.proofs) - assert wallet3.balance == 64 - - keep_proofs, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 10, set_reserved=True) # type: ignore - box.add(wallet3.proofs) - - assert wallet3.available_balance == 64 - 10 - await wallet3.redeem(spendable_proofs) - box.add(wallet3.proofs) - assert wallet3.available_balance == 64 - - await reset_wallet_db(wallet3) - await wallet3.load_proofs(reload=True) - assert wallet3.proofs == [] - assert wallet3.balance == 0 - await wallet3.restore_promises_from_to(0, 20) - box.add(wallet3.proofs) - assert wallet3.balance == 138 - await wallet3.invalidate(wallet3.proofs) - assert wallet3.balance == 64 - - # again - - _, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 12, set_reserved=True) # type: ignore - - assert wallet3.available_balance == 64 - 12 - await wallet3.redeem(spendable_proofs) - assert wallet3.available_balance == 64 - - await reset_wallet_db(wallet3) - await wallet3.load_proofs(reload=True) - assert wallet3.proofs == [] - assert wallet3.balance == 0 - await wallet3.restore_promises_from_to(0, 50) - assert wallet3.balance == 182 - await wallet3.invalidate(wallet3.proofs) - assert wallet3.balance == 64 diff --git a/tests/test_wallet_restore.py b/tests/test_wallet_restore.py new file mode 100644 index 00000000..bcd1837f --- /dev/null +++ b/tests/test_wallet_restore.py @@ -0,0 +1,359 @@ +import shutil +from pathlib import Path +from typing import Dict, List, Union + +import pytest +import pytest_asyncio + +from cashu.core.base import Proof +from cashu.core.crypto.secp import PrivateKey +from cashu.core.errors import CashuError +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 + + +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: Wallet): + 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 wallet1(mint): + 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): + 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_bump_secret_derivation(wallet3: Wallet): + await wallet3._init_private_key( + "half depart obvious quality work element tank gorilla view sugar picture" + " humble" + ) + 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 secrets1 == secrets2 + assert [r.private_key for r in rs1] == [r.private_key for r in rs2] + assert derivation_paths1 == derivation_paths2 + assert secrets1 == [ + "9bfb12704297fe90983907d122838940755fcce370ce51e9e00a4275a347c3fe", + "dbc5e05f2b1f24ec0e2ab6e8312d5e13f57ada52594d4caf429a697d9c742490", + "06a29fa8081b3a620b50b473fc80cde9a575c3b94358f3513c03007f8b66321e", + "652d08c804bd2c5f2c1f3e3d8895860397df394b30473753227d766affd15e89", + "654e5997f8a20402f7487296b6f7e463315dd52fc6f6cc5a4e35c7f6ccac77e0", + ] + 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'", + ] + + +@pytest.mark.asyncio +async def test_bump_secret_derivation_two_steps(wallet3: Wallet): + await wallet3._init_private_key( + "half depart obvious quality work element tank gorilla view sugar picture" + " humble" + ) + secrets1_1, rs1_1, derivation_paths1 = await wallet3.generate_n_secrets(2) + secrets1_2, rs1_2, derivation_paths2 = await wallet3.generate_n_secrets(3) + secrets1 = secrets1_1 + secrets1_2 + rs1 = rs1_1 + rs1_2 + secrets2, rs2, derivation_paths = await wallet3.generate_secrets_from_to(0, 4) + assert secrets1 == secrets2 + assert [r.private_key for r in rs1] == [r.private_key for r in rs2] + + +@pytest.mark.asyncio +async def test_generate_secrets_from_to(wallet3: Wallet): + await wallet3._init_private_key( + "half depart obvious quality work element tank gorilla view sugar picture" + " humble" + ) + secrets1, rs1, derivation_paths1 = await wallet3.generate_secrets_from_to(0, 4) + assert len(secrets1) == 5 + secrets2, rs2, derivation_paths2 = await wallet3.generate_secrets_from_to(2, 4) + assert len(secrets2) == 3 + assert secrets1[2:] == secrets2 + assert [r.private_key for r in rs1[2:]] == [r.private_key for r in rs2] + + +@pytest.mark.asyncio +async def test_restore_wallet_after_mint(wallet3: Wallet): + await reset_wallet_db(wallet3) + invoice = await wallet3.request_mint(64) + await wallet3.mint(64, hash=invoice.hash) + assert wallet3.balance == 64 + await reset_wallet_db(wallet3) + await wallet3.load_proofs() + wallet3.proofs = [] + assert wallet3.balance == 0 + await wallet3.restore_promises_from_to(0, 20) + assert wallet3.balance == 64 + + +@pytest.mark.asyncio +async def test_restore_wallet_with_invalid_mnemonic(wallet3: Wallet): + await assert_err( + wallet3._init_private_key( + "half depart obvious quality work element tank gorilla view sugar picture" + " picture" + ), + "Invalid mnemonic", + ) + + +@pytest.mark.asyncio +async def test_restore_wallet_after_split_to_send(wallet3: Wallet): + await wallet3._init_private_key( + "half depart obvious quality work element tank gorilla view sugar picture" + " humble" + ) + await reset_wallet_db(wallet3) + + invoice = await wallet3.request_mint(64) + await wallet3.mint(64, hash=invoice.hash) + assert wallet3.balance == 64 + + _, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 32, set_reserved=True) # type: ignore + + await reset_wallet_db(wallet3) + await wallet3.load_proofs() + wallet3.proofs = [] + assert wallet3.balance == 0 + await wallet3.restore_promises_from_to(0, 100) + assert wallet3.balance == 64 * 2 + await wallet3.invalidate(wallet3.proofs) + assert wallet3.balance == 64 + + +@pytest.mark.asyncio +async def test_restore_wallet_after_send_and_receive(wallet3: Wallet, wallet2: Wallet): + await wallet3._init_private_key( + "hello rug want adapt talent together lunar method bean expose beef position" + ) + await reset_wallet_db(wallet3) + invoice = await wallet3.request_mint(64) + await wallet3.mint(64, hash=invoice.hash) + assert wallet3.balance == 64 + + _, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 32, set_reserved=True) # type: ignore + + await wallet2.redeem(spendable_proofs) + + await reset_wallet_db(wallet3) + await wallet3.load_proofs(reload=True) + assert wallet3.proofs == [] + assert wallet3.balance == 0 + await wallet3.restore_promises_from_to(0, 100) + assert wallet3.balance == 64 + 2 * 32 + await wallet3.invalidate(wallet3.proofs) + assert wallet3.balance == 32 + + +class ProofBox: + proofs: Dict[str, Proof] = {} + + def add(self, proofs: List[Proof]) -> None: + for proof in proofs: + if proof.secret in self.proofs: + if self.proofs[proof.secret].C != proof.C: + print("Proofs are not equal") + print(self.proofs[proof.secret]) + print(proof) + else: + self.proofs[proof.secret] = proof + + +@pytest.mark.asyncio +async def test_restore_wallet_after_send_and_self_receive(wallet3: Wallet): + await wallet3._init_private_key( + "lucky broken tell exhibit shuffle tomato ethics virus rabbit spread measure" + " text" + ) + await reset_wallet_db(wallet3) + + invoice = await wallet3.request_mint(64) + await wallet3.mint(64, hash=invoice.hash) + assert wallet3.balance == 64 + + _, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 32, set_reserved=True) # type: ignore + + await wallet3.redeem(spendable_proofs) + + await reset_wallet_db(wallet3) + await wallet3.load_proofs(reload=True) + assert wallet3.proofs == [] + assert wallet3.balance == 0 + await wallet3.restore_promises_from_to(0, 100) + assert wallet3.balance == 64 + 2 * 32 + 32 + await wallet3.invalidate(wallet3.proofs) + assert wallet3.balance == 64 + + +@pytest.mark.asyncio +async def test_restore_wallet_after_send_twice( + wallet3: Wallet, +): + box = ProofBox() + wallet3.private_key = PrivateKey() + await reset_wallet_db(wallet3) + + invoice = await wallet3.request_mint(2) + await wallet3.mint(2, hash=invoice.hash) + box.add(wallet3.proofs) + assert wallet3.balance == 2 + + keep_proofs, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 1, set_reserved=True) # type: ignore + box.add(wallet3.proofs) + assert wallet3.available_balance == 1 + await wallet3.redeem(spendable_proofs) + box.add(wallet3.proofs) + assert wallet3.available_balance == 2 + assert wallet3.balance == 2 + + await reset_wallet_db(wallet3) + await wallet3.load_proofs(reload=True) + assert wallet3.proofs == [] + assert wallet3.balance == 0 + await wallet3.restore_promises_from_to(0, 10) + box.add(wallet3.proofs) + assert wallet3.balance == 5 + await wallet3.invalidate(wallet3.proofs) + assert wallet3.balance == 2 + + # again + + _, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 1, set_reserved=True) # type: ignore + box.add(wallet3.proofs) + + assert wallet3.available_balance == 1 + await wallet3.redeem(spendable_proofs) + box.add(wallet3.proofs) + assert wallet3.available_balance == 2 + + await reset_wallet_db(wallet3) + await wallet3.load_proofs(reload=True) + assert wallet3.proofs == [] + assert wallet3.balance == 0 + await wallet3.restore_promises_from_to(0, 15) + box.add(wallet3.proofs) + assert wallet3.balance == 7 + await wallet3.invalidate(wallet3.proofs) + assert wallet3.balance == 2 + + +@pytest.mark.asyncio +async def test_restore_wallet_after_send_and_self_receive_nonquadratic_value( + wallet3: Wallet, +): + box = ProofBox() + await wallet3._init_private_key( + "casual demise flight cradle feature hub link slim remember anger front asthma" + ) + await reset_wallet_db(wallet3) + + invoice = await wallet3.request_mint(64) + await wallet3.mint(64, hash=invoice.hash) + box.add(wallet3.proofs) + assert wallet3.balance == 64 + + keep_proofs, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 10, set_reserved=True) # type: ignore + box.add(wallet3.proofs) + + assert wallet3.available_balance == 64 - 10 + await wallet3.redeem(spendable_proofs) + box.add(wallet3.proofs) + assert wallet3.available_balance == 64 + + await reset_wallet_db(wallet3) + await wallet3.load_proofs(reload=True) + assert wallet3.proofs == [] + assert wallet3.balance == 0 + await wallet3.restore_promises_from_to(0, 20) + box.add(wallet3.proofs) + assert wallet3.balance == 138 + await wallet3.invalidate(wallet3.proofs) + assert wallet3.balance == 64 + + # again + + _, spendable_proofs = await wallet3.split_to_send(wallet3.proofs, 12, set_reserved=True) # type: ignore + + assert wallet3.available_balance == 64 - 12 + await wallet3.redeem(spendable_proofs) + assert wallet3.available_balance == 64 + + await reset_wallet_db(wallet3) + await wallet3.load_proofs(reload=True) + assert wallet3.proofs == [] + assert wallet3.balance == 0 + await wallet3.restore_promises_from_to(0, 50) + assert wallet3.balance == 182 + await wallet3.invalidate(wallet3.proofs) + assert wallet3.balance == 64 From b35a7b1c1a17e75bd1cd9fe9184a7857fa25eb84 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 3 Oct 2023 17:55:13 +0200 Subject: [PATCH 2/3] fix secret derivation --- cashu/wallet/secrets.py | 4 ++-- tests/test_wallet_restore.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cashu/wallet/secrets.py b/cashu/wallet/secrets.py index 07732682..45601758 100644 --- a/cashu/wallet/secrets.py +++ b/cashu/wallet/secrets.py @@ -163,7 +163,7 @@ async def generate_n_secrets( await self.generate_determinstic_secret(s) for s in secret_counters ] # secrets are supplied as str - secrets = [hashlib.sha256(s[0]).hexdigest() for s in secrets_rs_derivationpaths] + secrets = [s[0].hex() for s in secrets_rs_derivationpaths] # rs are supplied as PrivateKey rs = [PrivateKey(privkey=s[1], raw=True) for s in secrets_rs_derivationpaths] @@ -194,7 +194,7 @@ async def generate_secrets_from_to( await self.generate_determinstic_secret(s) for s in secret_counters ] # secrets are supplied as str - secrets = [hashlib.sha256(s[0]).hexdigest() for s in secrets_rs_derivationpaths] + secrets = [s[0].hex() for s in secrets_rs_derivationpaths] # rs are supplied as PrivateKey rs = [PrivateKey(privkey=s[1], raw=True) for s in secrets_rs_derivationpaths] derivation_paths = [s[2] for s in secrets_rs_derivationpaths] diff --git a/tests/test_wallet_restore.py b/tests/test_wallet_restore.py index bcd1837f..7a52d84a 100644 --- a/tests/test_wallet_restore.py +++ b/tests/test_wallet_restore.py @@ -99,11 +99,11 @@ async def test_bump_secret_derivation(wallet3: Wallet): assert [r.private_key for r in rs1] == [r.private_key for r in rs2] assert derivation_paths1 == derivation_paths2 assert secrets1 == [ - "9bfb12704297fe90983907d122838940755fcce370ce51e9e00a4275a347c3fe", - "dbc5e05f2b1f24ec0e2ab6e8312d5e13f57ada52594d4caf429a697d9c742490", - "06a29fa8081b3a620b50b473fc80cde9a575c3b94358f3513c03007f8b66321e", - "652d08c804bd2c5f2c1f3e3d8895860397df394b30473753227d766affd15e89", - "654e5997f8a20402f7487296b6f7e463315dd52fc6f6cc5a4e35c7f6ccac77e0", + "9d32fc57e6fa2942d05ee475d28ba6a56839b8cb8a3f174b05ed0ed9d3a420f6", + "1c0f2c32e7438e7cc992612049e9dfcdbffd454ea460901f24cc429921437802", + "327c606b761af03cbe26fa13c4b34a6183b868c52cda059fe57fdddcb4e1e1e7", + "53476919560398b56c0fdc5dd92cf8628b1e06de6f2652b0f7d6e8ac319de3b7", + "b2f5d632229378a716be6752fc79ac8c2b43323b820859a7956f2dfe5432b7b4", ] assert derivation_paths1 == [ "m/129372'/0'/2004500376'/0'", From 7e6041080f43d91f80d445646969d866b213b5f4 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 3 Oct 2023 18:31:50 +0200 Subject: [PATCH 3/3] selfpay to refresh tokens --- cashu/wallet/cli/cli.py | 27 +++++++++++++++++++++++++++ cashu/wallet/cli/cli_helpers.py | 3 +-- tests/test_cli.py | 11 +++++++++++ 3 files changed, 39 insertions(+), 2 deletions(-) diff --git a/cashu/wallet/cli/cli.py b/cashu/wallet/cli/cli.py index bdbe5167..56e5e01f 100644 --- a/cashu/wallet/cli/cli.py +++ b/cashu/wallet/cli/cli.py @@ -809,3 +809,30 @@ async def restore(ctx: Context, to: int, batch: int): await wallet.restore_wallet_from_mnemonic(mnemonic, to=to, batch=batch) await wallet.load_proofs() wallet.status() + + +@cli.command("selfpay", help="Refresh tokens.") +# @click.option("--all", default=False, is_flag=True, help="Execute on all available mints.") +@click.pass_context +@coro +async def selfpay(ctx: Context, all: bool = False): + wallet = await get_mint_wallet(ctx, force_select=True) + await wallet.load_mint() + + # get balance on this mint + mint_balance_dict = await wallet.balance_per_minturl() + mint_balance = mint_balance_dict[wallet.url]["available"] + # send balance once to mark as reserved + await wallet.split_to_send(wallet.proofs, mint_balance, None, set_reserved=True) + # load all reserved proofs (including the one we just sent) + reserved_proofs = await get_reserved_proofs(wallet.db) + if not len(reserved_proofs): + print("No balance on this mint.") + return + + token = await wallet.serialize_proofs(reserved_proofs) + print(f"Selfpay token for mint {wallet.url}:") + print("") + print(token) + tokenObj = TokenV3.deserialize(token) + await receive(wallet, tokenObj) diff --git a/cashu/wallet/cli/cli_helpers.py b/cashu/wallet/cli/cli_helpers.py index e7d12b21..d2b58c7e 100644 --- a/cashu/wallet/cli/cli_helpers.py +++ b/cashu/wallet/cli/cli_helpers.py @@ -46,12 +46,11 @@ async def get_mint_wallet(ctx: Context, force_select: bool = False): mint_url = wallet.url # load this mint_url into a wallet - mint_wallet = Wallet( + mint_wallet = await Wallet.with_db( mint_url, os.path.join(settings.cashu_dir, ctx.obj["WALLET_NAME"]), name=wallet.name, ) - # await mint_wallet.load_mint() await mint_wallet.load_proofs(reload=True) return mint_wallet diff --git a/tests/test_cli.py b/tests/test_cli.py index bc1208fd..9bb1f86b 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -284,3 +284,14 @@ def test_pending(cli_prefix): assert result.exception is None print(result.output) assert result.exit_code == 0 + + +def test_selfpay(cli_prefix): + runner = CliRunner() + result = runner.invoke( + cli, + [*cli_prefix, "selfpay"], + ) + assert result.exception is None + print(result.output) + assert result.exit_code == 0