This commit is contained in:
callebtc
2022-09-21 17:07:25 +03:00
parent 895a0582a1
commit 80a696d097
10 changed files with 103 additions and 32 deletions

View File

@@ -1,5 +1,7 @@
DEBUG = true
CASHU_DIR=~/.cashu
# WALLET
MINT_HOST=127.0.0.1

View File

@@ -9,6 +9,7 @@ class Proof(BaseModel):
C: str
secret: str
reserved: bool = False # whether this proof is reserved for sending
send_id: str = "" # unique ID of send attempt
@classmethod
def from_row(cls, row: Row):
@@ -17,6 +18,7 @@ class Proof(BaseModel):
C=row[1],
secret=row[2],
reserved=row[3] or False,
send_id=row[4] or "",
)
@classmethod
@@ -26,6 +28,7 @@ class Proof(BaseModel):
C=d["C"],
secret=d["secret"],
reserved=d["reserved"] or False,
send_id=d["send_id"] or "",
)
def __getitem__(self, key):

View File

@@ -1,9 +1,15 @@
from pathlib import Path
from environs import Env # type: ignore
env = Env()
env.read_env()
DEBUG = env.bool("DEBUG", default=False)
CASHU_DIR = env.str("CASHU_DIR", default="~/.cashu")
CASHU_DIR = CASHU_DIR.replace("~", str(Path.home()))
assert len(CASHU_DIR), "CASHU_DIR not defined"
LIGHTNING = env.bool("LIGHTNING", default=True)
LIGHTNING_FEE_PERCENT = env.float("LIGHTNING_FEE_PERCENT", default=1.0)
assert LIGHTNING_FEE_PERCENT >= 0, "LIGHTNING_FEE_PERCENT must be at least 0"

View File

@@ -8,8 +8,13 @@ import requests
from core.settings import LNBITS_ENDPOINT, LNBITS_KEY
from .base import (InvoiceResponse, PaymentResponse, PaymentStatus,
StatusResponse, Wallet)
from .base import (
InvoiceResponse,
PaymentResponse,
PaymentStatus,
StatusResponse,
Wallet,
)
class LNbitsWallet(Wallet):

View File

@@ -11,7 +11,12 @@ from secp256k1 import PublicKey
import core.settings as settings
from core.base import CheckPayload, MeltPayload, MintPayloads, SplitPayload
from core.settings import MINT_PRIVATE_KEY, MINT_SERVER_HOST, MINT_SERVER_PORT
from core.settings import (
CASHU_DIR,
MINT_PRIVATE_KEY,
MINT_SERVER_HOST,
MINT_SERVER_PORT,
)
from lightning import WALLET
from mint.ledger import Ledger
from mint.migrations import m001_initial
@@ -33,7 +38,7 @@ def startup(app: FastAPI):
)
logger.info(f"Lightning balance: {balance} sat")
logger.info(f"Data dir: {CASHU_DIR}")
logger.info("Mint started.")

View File

@@ -1,7 +1,7 @@
import setuptools
from os import path
import setuptools
this_directory = path.abspath(path.dirname(__file__))
with open(path.join(this_directory, "README.md"), encoding="utf-8") as f:
long_description = f.read()

View File

@@ -5,7 +5,8 @@ import base64
import json
import math
from functools import wraps
from pathlib import Path
from itertools import groupby
from operator import itemgetter
import click
from bech32 import bech32_decode, bech32_encode, convertbits
@@ -15,7 +16,7 @@ from core.base import Proof
from core.bolt11 import Invoice
from core.helpers import fee_reserve
from core.migrations import migrate_databases
from core.settings import LIGHTNING, MINT_URL
from core.settings import CASHU_DIR, LIGHTNING, MINT_URL
from wallet import migrations
from wallet.crud import get_reserved_proofs
from wallet.wallet import Wallet as Wallet
@@ -46,9 +47,7 @@ def cli(
ctx.ensure_object(dict)
ctx.obj["HOST"] = host
ctx.obj["WALLET_NAME"] = walletname
ctx.obj["WALLET"] = Wallet(
ctx.obj["HOST"], f"{str(Path.home())}/.cashu/{walletname}", walletname
)
ctx.obj["WALLET"] = Wallet(ctx.obj["HOST"], f"{CASHU_DIR}/{walletname}", walletname)
pass
@@ -106,8 +105,8 @@ async def send(ctx, amount: int):
wallet.status()
_, send_proofs = await wallet.split_to_send(wallet.proofs, amount)
await wallet.set_reserved(send_proofs, reserved=True)
proofs_serialized = [p.dict() for p in send_proofs]
print(base64.urlsafe_b64encode(json.dumps(proofs_serialized).encode()).decode())
token = await wallet.serialize_proofs(send_proofs)
print(token)
wallet.status()
@@ -146,6 +145,25 @@ async def burn(ctx, token: str, all: bool):
wallet.status()
@cli.command("pending", help="Show pending tokens.")
@click.pass_context
@coro
async def pending(ctx):
wallet: Wallet = ctx.obj["WALLET"]
await init_wallet(wallet)
wallet.status()
reserved_proofs = await get_reserved_proofs(wallet.db)
if len(reserved_proofs):
sorted_proofs = sorted(reserved_proofs, key=itemgetter("send_id"))
for key, value in groupby(sorted_proofs, key=itemgetter("send_id")):
grouped_proofs = list(value)
token = await wallet.serialize_proofs(grouped_proofs)
print(
f"Amount: {sum([p['amount'] for p in grouped_proofs])} sat. ID: {key}"
)
print(token)
@cli.command("pay", help="Pay lightning invoice.")
@click.argument("invoice", type=str)
@click.pass_context

View File

@@ -1,4 +1,4 @@
import secrets
import time
from typing import Optional
from core.base import Proof
@@ -14,14 +14,10 @@ async def store_proof(
await (conn or db).execute(
"""
INSERT INTO proofs
(amount, C, secret)
VALUES (?, ?, ?)
(amount, C, secret, time_created)
VALUES (?, ?, ?, ?)
""",
(
proof.amount,
str(proof.C),
str(proof.secret),
),
(proof.amount, str(proof.C), str(proof.secret), int(time.time())),
)
@@ -69,24 +65,35 @@ async def invalidate_proof(
await (conn or db).execute(
"""
INSERT INTO proofs_used
(amount, C, secret)
VALUES (?, ?, ?)
(amount, C, secret, time_used)
VALUES (?, ?, ?, ?)
""",
(
proof.amount,
str(proof.C),
str(proof.secret),
),
(proof.amount, str(proof.C), str(proof.secret), int(time.time())),
)
async def update_proof_reserved(
proof: Proof,
reserved: bool,
db: Database,
send_id: str = None,
db: Database = None,
conn: Optional[Connection] = None,
):
clauses = []
values = []
clauses.append("reserved = ?")
values.append(reserved)
if send_id:
clauses.append("send_id = ?")
values.append(send_id)
if reserved:
# set the time of reserving
clauses.append("time_reserved = ?")
values.append(int(time.time()))
await (conn or db).execute(
"UPDATE proofs SET reserved = ? WHERE secret = ?",
(reserved, str(proof.secret)),
f"UPDATE proofs SET {', '.join(clauses)} WHERE secret = ?",
(*values, str(proof.secret)),
)

View File

@@ -68,3 +68,14 @@ async def m002_add_proofs_reserved(db):
"""
await db.execute("ALTER TABLE proofs ADD COLUMN reserved BOOL")
async def m003_add_proofs_sendid(db):
"""
Column with unique ID for each initiated send attempt
so proofs can be later grouped together for each send attempt.
"""
await db.execute("ALTER TABLE proofs ADD COLUMN send_id TEXT")
await db.execute("ALTER TABLE proofs ADD COLUMN time_created TIMESTAMP")
await db.execute("ALTER TABLE proofs ADD COLUMN time_reserved TIMESTAMP")
await db.execute("ALTER TABLE proofs_used ADD COLUMN time_used TIMESTAMP")

View File

@@ -1,4 +1,7 @@
import base64
import json
import random
import uuid
from typing import List
import requests
@@ -186,6 +189,14 @@ class Wallet(LedgerAPI):
raise Exception("could not pay invoice.")
return status["paid"]
@staticmethod
async def serialize_proofs(proofs: List[Proof]):
proofs_serialized = [p.dict() for p in proofs]
token = base64.urlsafe_b64encode(
json.dumps(proofs_serialized).encode()
).decode()
return token
async def split_to_send(self, proofs: List[Proof], amount):
"""Like self.split but only considers non-reserved tokens."""
if len([p for p in proofs if not p.reserved]) <= 0:
@@ -194,9 +205,12 @@ class Wallet(LedgerAPI):
async def set_reserved(self, proofs: List[Proof], reserved: bool):
"""Mark a proof as reserved to avoid reuse or delete marking."""
uuid_str = str(uuid.uuid1())
for proof in proofs:
proof.reserved = True
await update_proof_reserved(proof, reserved=reserved, db=self.db)
await update_proof_reserved(
proof, reserved=reserved, send_id=uuid_str, db=self.db
)
async def check_spendable(self, proofs):
return await super().check_spendable(proofs)