save lightning invoices

This commit is contained in:
callebtc
2022-10-18 11:16:30 +02:00
parent 990e46fbb0
commit c110715f28
6 changed files with 144 additions and 128 deletions

View File

@@ -12,15 +12,6 @@ class P2SHScript(BaseModel):
signature: str signature: str
address: Union[str, None] = None address: Union[str, None] = None
@classmethod
def from_row(cls, row: Row):
return cls(
address=row[0],
script=row[1],
signature=row[2],
used=row[3],
)
class Proof(BaseModel): class Proof(BaseModel):
id: str = "" id: str = ""
@@ -28,36 +19,10 @@ class Proof(BaseModel):
secret: str = "" secret: str = ""
C: str = "" C: str = ""
script: Union[P2SHScript, None] = None script: Union[P2SHScript, None] = None
reserved: bool = False # whether this proof is reserved for sending reserved: Union[None, bool] = False # whether this proof is reserved for sending
send_id: str = "" # unique ID of send attempt send_id: Union[None, str] = "" # unique ID of send attempt
time_created: str = "" time_created: Union[None, str] = ""
time_reserved: str = "" time_reserved: Union[None, str] = ""
@classmethod
def from_row(cls, row: Row):
return cls(
amount=row[0],
C=row[1],
secret=row[2],
reserved=row[3] or False,
send_id=row[4] or "",
time_created=row[5] or "",
time_reserved=row[6] or "",
id=row[7] or "",
)
@classmethod
def from_dict(cls, d: dict):
assert "amount" in d, "no amount in proof"
return cls(
amount=d.get("amount"),
C=d.get("C"),
secret=d.get("secret") or "",
reserved=d.get("reserved") or False,
send_id=d.get("send_id") or "",
time_created=d.get("time_created") or "",
time_reserved=d.get("time_reserved") or "",
)
def to_dict(self): def to_dict(self):
return dict(id=self.id, amount=self.amount, secret=self.secret, C=self.C) return dict(id=self.id, amount=self.amount, secret=self.secret, C=self.C)
@@ -81,17 +46,12 @@ class Proofs(BaseModel):
class Invoice(BaseModel): class Invoice(BaseModel):
amount: int amount: int
pr: str pr: str
hash: str hash: Union[None, str] = None
issued: bool = False preimage: Union[str, None] = None
issued: Union[None, bool] = False
@classmethod paid: Union[None, bool] = False
def from_row(cls, row: Row): time_created: Union[None, str, int, float] = ""
return cls( time_paid: Union[None, str, int, float] = ""
amount=int(row[0]),
pr=str(row[1]),
hash=str(row[2]),
issued=bool(row[3]),
)
class BlindedMessage(BaseModel): class BlindedMessage(BaseModel):
@@ -104,14 +64,6 @@ class BlindedSignature(BaseModel):
amount: int amount: int
C_: str C_: str
@classmethod
def from_dict(cls, d: dict):
return cls(
id=d.get("id"),
amount=d["amount"],
C_=d["C_"],
)
class MintRequest(BaseModel): class MintRequest(BaseModel):
blinded_messages: List[BlindedMessage] = [] blinded_messages: List[BlindedMessage] = []
@@ -173,16 +125,6 @@ class KeyBase(BaseModel):
amount: int amount: int
pubkey: str pubkey: str
@classmethod
def from_row(cls, row: Row):
if row is None:
return cls
return cls(
id=row[0],
amount=int(row[1]),
pubkey=row[2],
)
class WalletKeyset: class WalletKeyset:
id: str id: str
@@ -213,19 +155,6 @@ class WalletKeyset:
self.public_keys = pubkeys self.public_keys = pubkeys
self.id = derive_keyset_id(self.public_keys) self.id = derive_keyset_id(self.public_keys)
@classmethod
def from_row(cls, row: Row):
if row is None:
return cls
return cls(
id=row[0],
mint_url=row[1],
valid_from=row[2],
valid_to=row[3],
first_seen=row[4],
active=row[5],
)
class MintKeyset: class MintKeyset:
id: str id: str
@@ -266,20 +195,6 @@ class MintKeyset:
self.public_keys = derive_pubkeys(self.private_keys) self.public_keys = derive_pubkeys(self.private_keys)
self.id = derive_keyset_id(self.public_keys) self.id = derive_keyset_id(self.public_keys)
@classmethod
def from_row(cls, row: Row):
if row is None:
return cls
return cls(
id=row[0],
derivation_path=row[1],
valid_from=row[2],
valid_to=row[3],
first_seen=row[4],
active=row[5],
version=row[6],
)
def get_keybase(self): def get_keybase(self):
return { return {
k: KeyBase(id=self.id, amount=k, pubkey=v.serialize().hex()) k: KeyBase(id=self.id, amount=k, pubkey=v.serialize().hex())

View File

@@ -135,7 +135,7 @@ async def get_lightning_invoice(
""", """,
(hash,), (hash,),
) )
return Invoice.from_row(row) return Invoice(**row)
async def update_lightning_invoice( async def update_lightning_invoice(
@@ -204,4 +204,4 @@ async def get_keyset(
""", """,
tuple(values), tuple(values),
) )
return [MintKeyset.from_row(row) for row in rows] return [MintKeyset(**row) for row in rows]

View File

@@ -85,14 +85,14 @@ async def invoice(ctx, amount: int, hash: str):
if not LIGHTNING: if not LIGHTNING:
r = await wallet.mint(amount) r = await wallet.mint(amount)
elif amount and not hash: elif amount and not hash:
r = await wallet.request_mint(amount) invoice = await wallet.request_mint(amount)
if "pr" in r: if invoice.pr:
print(f"Pay invoice to mint {amount} sat:") print(f"Pay invoice to mint {amount} sat:")
print("") print("")
print(f"Invoice: {r['pr']}") print(f"Invoice: {invoice.pr}")
print("") print("")
print( print(
f"Execute this command if you abort the check:\ncashu invoice {amount} --hash {r['hash']}" f"Execute this command if you abort the check:\ncashu invoice {amount} --hash {invoice.hash}"
) )
check_until = time.time() + 5 * 60 # check for five minutes check_until = time.time() + 5 * 60 # check for five minutes
print("") print("")
@@ -105,7 +105,7 @@ async def invoice(ctx, amount: int, hash: str):
while time.time() < check_until and not paid: while time.time() < check_until and not paid:
time.sleep(3) time.sleep(3)
try: try:
await wallet.mint(amount, r["hash"]) await wallet.mint(amount, invoice.hash)
paid = True paid = True
print(" Invoice paid.") print(" Invoice paid.")
except Exception as e: except Exception as e:
@@ -221,7 +221,7 @@ async def receive(ctx, coin: str, lock: str):
signature = p2shscripts[0].signature signature = p2shscripts[0].signature
else: else:
script, signature = None, None script, signature = None, None
proofs = [Proof.from_dict(p) for p in json.loads(base64.urlsafe_b64decode(coin))] proofs = [Proof(**p) for p in json.loads(base64.urlsafe_b64decode(coin))]
_, _ = await wallet.redeem(proofs, scnd_script=script, scnd_siganture=signature) _, _ = await wallet.redeem(proofs, scnd_script=script, scnd_siganture=signature)
wallet.status() wallet.status()
@@ -250,9 +250,7 @@ async def burn(ctx, coin: str, all: bool, force: bool):
proofs = wallet.proofs proofs = wallet.proofs
else: else:
# check only the specified ones # check only the specified ones
proofs = [ proofs = [Proof(**p) for p in json.loads(base64.urlsafe_b64decode(coin))]
Proof.from_dict(p) for p in json.loads(base64.urlsafe_b64decode(coin))
]
wallet.status() wallet.status()
await wallet.invalidate(proofs) await wallet.invalidate(proofs)
wallet.status() wallet.status()

View File

@@ -1,7 +1,7 @@
import time import time
from typing import Any, List, Optional from typing import Any, List, Optional
from cashu.core.base import KeyBase, P2SHScript, Proof, WalletKeyset from cashu.core.base import Invoice, KeyBase, P2SHScript, Proof, WalletKeyset
from cashu.core.db import Connection, Database from cashu.core.db import Connection, Database
@@ -31,7 +31,7 @@ async def get_proofs(
SELECT * from proofs SELECT * from proofs
""" """
) )
return [Proof.from_row(r) for r in rows] return [Proof(**dict(r)) for r in rows]
async def get_reserved_proofs( async def get_reserved_proofs(
@@ -45,7 +45,7 @@ async def get_reserved_proofs(
WHERE reserved WHERE reserved
""" """
) )
return [Proof.from_row(r) for r in rows] return [Proof(**r) for r in rows]
async def invalidate_proof( async def invalidate_proof(
@@ -162,7 +162,7 @@ async def get_unused_locks(
""", """,
tuple(args), tuple(args),
) )
return [P2SHScript.from_row(r) for r in rows] return [P2SHScript(**r) for r in rows]
async def update_p2sh_used( async def update_p2sh_used(
@@ -233,4 +233,78 @@ async def get_keyset(
""", """,
tuple(values), tuple(values),
) )
return WalletKeyset.from_row(row) if row is not None else None return WalletKeyset(**row) if row is not None else None
async def store_lightning_invoice(
db: Database,
invoice: Invoice,
conn: Optional[Connection] = None,
):
await (conn or db).execute(
f"""
INSERT INTO invoices
(amount, pr, hash, preimage, paid, time_created, time_paid)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(
invoice.amount,
invoice.pr,
invoice.hash,
invoice.preimage,
invoice.paid,
invoice.time_created,
invoice.time_paid,
),
)
async def get_lightning_invoice(
db: Database,
hash: str = None,
conn: Optional[Connection] = None,
):
clauses = []
values: List[Any] = []
if hash:
clauses.append("hash = ?")
values.append(hash)
where = ""
if clauses:
where = f"WHERE {' AND '.join(clauses)}"
row = await (conn or db).fetchone(
f"""
SELECT * from invoices
{where}
""",
(hash,),
)
return Invoice(**row)
async def update_lightning_invoice(
db: Database,
hash: str,
paid: bool,
time_paid: int = None,
conn: Optional[Connection] = None,
):
clauses = []
values: List[Any] = []
clauses.append("paid = ?")
values.append(paid)
if time_paid:
clauses.append("time_paid = ?")
values.append(time_paid)
await (conn or db).execute(
f"UPDATE invoices SET {', '.join(clauses)} WHERE hash = ?",
(
*values,
hash,
),
)

View File

@@ -119,18 +119,28 @@ async def m005_wallet_keysets(db: Database):
); );
""" """
) )
# await db.execute(
# f"""
# CREATE TABLE IF NOT EXISTS mint_pubkeys (
# id TEXT NOT NULL,
# amount INTEGER NOT NULL,
# pubkey TEXT NOT NULL,
# UNIQUE (id, pubkey)
# );
# """
# )
await db.execute("ALTER TABLE proofs ADD COLUMN id TEXT") await db.execute("ALTER TABLE proofs ADD COLUMN id TEXT")
await db.execute("ALTER TABLE proofs_used ADD COLUMN id TEXT") await db.execute("ALTER TABLE proofs_used ADD COLUMN id TEXT")
async def m006_invoices(db: Database):
"""
Stores Lightning invoices.
"""
await db.execute(
f"""
CREATE TABLE IF NOT EXISTS invoices (
amount INTEGER NOT NULL,
pr TEXT NOT NULL,
hash TEXT,
preimage TEXT,
paid BOOL DEFAULT FALSE,
time_created TIMESTAMP DEFAULT {db.timestamp_now},
time_paid TIMESTAMP DEFAULT {db.timestamp_now},
UNIQUE (hash)
);
"""
)

View File

@@ -1,6 +1,7 @@
import base64 import base64
import json import json
import secrets as scrts import secrets as scrts
import time
import uuid import uuid
from itertools import groupby from itertools import groupby
from typing import Dict, List from typing import Dict, List
@@ -14,6 +15,7 @@ from cashu.core.base import (
BlindedSignature, BlindedSignature,
CheckFeesRequest, CheckFeesRequest,
CheckRequest, CheckRequest,
Invoice,
MeltRequest, MeltRequest,
MintRequest, MintRequest,
P2SHScript, P2SHScript,
@@ -38,8 +40,10 @@ from cashu.wallet.crud import (
invalidate_proof, invalidate_proof,
secret_used, secret_used,
store_keyset, store_keyset,
store_lightning_invoice,
store_p2sh, store_p2sh,
store_proof, store_proof,
update_lightning_invoice,
update_proof_reserved, update_proof_reserved,
) )
@@ -175,7 +179,7 @@ class LedgerAPI:
resp.raise_for_status() resp.raise_for_status()
return_dict = resp.json() return_dict = resp.json()
self.raise_on_error(return_dict) self.raise_on_error(return_dict)
return return_dict return Invoice(amount=amount, pr=return_dict["pr"], hash=return_dict["hash"])
async def mint(self, amounts, payment_hash=None): async def mint(self, amounts, payment_hash=None):
"""Mints new coins and returns a proof of promise.""" """Mints new coins and returns a proof of promise."""
@@ -192,7 +196,7 @@ class LedgerAPI:
promises_list = resp.json() promises_list = resp.json()
self.raise_on_error(promises_list) self.raise_on_error(promises_list)
promises = [BlindedSignature.from_dict(p) for p in promises_list] promises = [BlindedSignature(**p) for p in promises_list]
return self._construct_proofs(promises, secrets, rs) return self._construct_proofs(promises, secrets, rs)
async def split(self, proofs, amount, scnd_secret: str = None): async def split(self, proofs, amount, scnd_secret: str = None):
@@ -246,8 +250,8 @@ class LedgerAPI:
promises_dict = resp.json() promises_dict = resp.json()
self.raise_on_error(promises_dict) self.raise_on_error(promises_dict)
promises_fst = [BlindedSignature.from_dict(p) for p in promises_dict["fst"]] promises_fst = [BlindedSignature(**p) for p in promises_dict["fst"]]
promises_snd = [BlindedSignature.from_dict(p) for p in promises_dict["snd"]] promises_snd = [BlindedSignature(**p) for p in promises_dict["snd"]]
# Construct proofs from promises (i.e., unblind signatures) # Construct proofs from promises (i.e., unblind signatures)
frst_proofs = self._construct_proofs( frst_proofs = self._construct_proofs(
promises_fst, secrets[: len(promises_fst)], rs[: len(promises_fst)] promises_fst, secrets[: len(promises_fst)], rs[: len(promises_fst)]
@@ -340,7 +344,10 @@ class Wallet(LedgerAPI):
return {key: list(group) for key, group in groupby(proofs, lambda p: p.id)} return {key: list(group) for key, group in groupby(proofs, lambda p: p.id)}
async def request_mint(self, amount): async def request_mint(self, amount):
return super().request_mint(amount) invoice = super().request_mint(amount)
invoice.time_created = int(time.time())
await store_lightning_invoice(db=self.db, invoice=invoice)
return invoice
async def mint(self, amount: int, payment_hash: str = None): async def mint(self, amount: int, payment_hash: str = None):
split = amount_split(amount) split = amount_split(amount)
@@ -348,6 +355,10 @@ class Wallet(LedgerAPI):
if proofs == []: if proofs == []:
raise Exception("received no proofs.") raise Exception("received no proofs.")
await self._store_proofs(proofs) await self._store_proofs(proofs)
if payment_hash:
await update_lightning_invoice(
db=self.db, hash=payment_hash, paid=True, time_paid=int(time.time())
)
self.proofs += proofs self.proofs += proofs
return proofs return proofs
@@ -389,6 +400,14 @@ class Wallet(LedgerAPI):
status = await super().pay_lightning(proofs, invoice) status = await super().pay_lightning(proofs, invoice)
if status["paid"] == True: if status["paid"] == True:
await self.invalidate(proofs) await self.invalidate(proofs)
invoice_obj = Invoice(
amount=-sum_proofs(proofs),
pr=invoice,
preimage=status.get("preimage"),
paid=True,
time_paid=time.time(),
)
await store_lightning_invoice(db=self.db, invoice=invoice_obj)
else: else:
raise Exception("could not pay invoice.") raise Exception("could not pay invoice.")
return status["paid"] return status["paid"]