Wallet: fix secret derivation & new CLI command cashu selfpay (#331)

* refactor wallet restore tests

* fix secret derivation

* selfpay to refresh tokens
This commit is contained in:
callebtc
2023-10-03 18:34:12 +02:00
committed by GitHub
parent 0959314786
commit 1149533e82
7 changed files with 403 additions and 313 deletions

View File

@@ -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.restore_wallet_from_mnemonic(mnemonic, to=to, batch=batch)
await wallet.load_proofs() await wallet.load_proofs()
wallet.status() 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)

View File

@@ -46,12 +46,11 @@ async def get_mint_wallet(ctx: Context, force_select: bool = False):
mint_url = wallet.url mint_url = wallet.url
# load this mint_url into a wallet # load this mint_url into a wallet
mint_wallet = Wallet( mint_wallet = await Wallet.with_db(
mint_url, mint_url,
os.path.join(settings.cashu_dir, ctx.obj["WALLET_NAME"]), os.path.join(settings.cashu_dir, ctx.obj["WALLET_NAME"]),
name=wallet.name, name=wallet.name,
) )
# await mint_wallet.load_mint()
await mint_wallet.load_proofs(reload=True) await mint_wallet.load_proofs(reload=True)
return mint_wallet return mint_wallet

View File

@@ -163,7 +163,7 @@ class WalletSecrets(SupportsDb, SupportsKeysets):
await self.generate_determinstic_secret(s) for s in secret_counters await self.generate_determinstic_secret(s) for s in secret_counters
] ]
# secrets are supplied as str # 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 are supplied as PrivateKey
rs = [PrivateKey(privkey=s[1], raw=True) for s in secrets_rs_derivationpaths] rs = [PrivateKey(privkey=s[1], raw=True) for s in secrets_rs_derivationpaths]
@@ -194,7 +194,7 @@ class WalletSecrets(SupportsDb, SupportsKeysets):
await self.generate_determinstic_secret(s) for s in secret_counters await self.generate_determinstic_secret(s) for s in secret_counters
] ]
# secrets are supplied as str # 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 are supplied as PrivateKey
rs = [PrivateKey(privkey=s[1], raw=True) for s in secrets_rs_derivationpaths] rs = [PrivateKey(privkey=s[1], raw=True) for s in secrets_rs_derivationpaths]
derivation_paths = [s[2] for s in secrets_rs_derivationpaths] derivation_paths = [s[2] for s in secrets_rs_derivationpaths]

View File

@@ -1368,7 +1368,7 @@ class Wallet(LedgerAPI, WalletP2PK, WalletHTLC, WalletSecrets):
outputs (List[BlindedMessage]): Outputs for which we request promises outputs (List[BlindedMessage]): Outputs for which we request promises
secrets (List[str]): Secrets generated for the outputs secrets (List[str]): Secrets generated for the outputs
rs (List[PrivateKey]): Random blinding factors 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: Returns:
List[Proof]: List of restored proofs List[Proof]: List of restored proofs

View File

@@ -284,3 +284,14 @@ def test_pending(cli_prefix):
assert result.exception is None assert result.exception is None
print(result.output) print(result.output)
assert result.exit_code == 0 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

View File

@@ -1,12 +1,11 @@
import shutil import shutil
from pathlib import Path from pathlib import Path
from typing import Dict, List, Union from typing import List, Union
import pytest import pytest
import pytest_asyncio import pytest_asyncio
from cashu.core.base import Proof from cashu.core.base import Proof
from cashu.core.crypto.secp import PrivateKey, PublicKey
from cashu.core.errors import CashuError, KeysetNotFoundError from cashu.core.errors import CashuError, KeysetNotFoundError
from cashu.core.helpers import sum_proofs from cashu.core.helpers import sum_proofs
from cashu.core.settings import settings 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 assert len(wallet1.keys.public_keys) == settings.max_order
keyset = await wallet1._get_keys(wallet1.url) keyset = await wallet1._get_keys(wallet1.url)
assert keyset.id is not None assert keyset.id is not None
assert keyset.id == "1cCNIAZ2X/w1"
assert isinstance(keyset.id, str) assert isinstance(keyset.id, str)
assert len(keyset.id) > 0 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 @pytest.mark.asyncio
async def test_token_state(wallet1: Wallet): async def test_token_state(wallet1: Wallet):
invoice = await wallet1.request_mint(64) 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) resp = await wallet1.check_proof_state(wallet1.proofs)
assert resp.dict()["spendable"] assert resp.dict()["spendable"]
assert resp.dict()["pending"] 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

View File

@@ -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 == [
"9d32fc57e6fa2942d05ee475d28ba6a56839b8cb8a3f174b05ed0ed9d3a420f6",
"1c0f2c32e7438e7cc992612049e9dfcdbffd454ea460901f24cc429921437802",
"327c606b761af03cbe26fa13c4b34a6183b868c52cda059fe57fdddcb4e1e1e7",
"53476919560398b56c0fdc5dd92cf8628b1e06de6f2652b0f7d6e8ac319de3b7",
"b2f5d632229378a716be6752fc79ac8c2b43323b820859a7956f2dfe5432b7b4",
]
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