router stuff

This commit is contained in:
callebtc
2022-09-28 11:24:15 +02:00
parent ff7312c6d8
commit 21dff05f82
6 changed files with 240 additions and 221 deletions

View File

@@ -0,0 +1,6 @@
from core.settings import MINT_PRIVATE_KEY
from mint.ledger import Ledger
print("init")
ledger = Ledger(MINT_PRIVATE_KEY, "data/mint")

7
mint/__main__.py Normal file
View File

@@ -0,0 +1,7 @@
from .app import create_app, main
print("main")
app = create_app()
# main()

0
mint/api.py Normal file
View File

View File

@@ -1,221 +1,89 @@
import asyncio import asyncio
import logging import logging
import sys import sys
from typing import Union from typing import Union
import click
import uvicorn from fastapi import FastAPI
from fastapi import FastAPI from loguru import logger
from loguru import logger from secp256k1 import PublicKey
from secp256k1 import PublicKey
import core.settings as settings
import core.settings as settings from core.settings import (
from core.base import CheckPayload, MeltPayload, MintPayloads, SplitPayload CASHU_DIR,
from core.settings import ( )
CASHU_DIR, from lightning import WALLET
MINT_PRIVATE_KEY, from mint.ledger import Ledger
MINT_SERVER_HOST, from mint.migrations import m001_initial
MINT_SERVER_PORT,
) from . import ledger
from lightning import WALLET
from mint.ledger import Ledger
from mint.migrations import m001_initial def startup(app: FastAPI):
@app.on_event("startup")
ledger = Ledger(MINT_PRIVATE_KEY, "data/mint") async def load_ledger():
await asyncio.wait([m001_initial(ledger.db)])
await ledger.load_used_proofs()
def startup(app: FastAPI):
@app.on_event("startup") error_message, balance = await WALLET.status()
async def load_ledger(): if error_message:
await asyncio.wait([m001_initial(ledger.db)]) logger.warning(
await ledger.load_used_proofs() f"The backend for {WALLET.__class__.__name__} isn't working properly: '{error_message}'",
RuntimeWarning,
error_message, balance = await WALLET.status() )
if error_message:
logger.warning( logger.info(f"Lightning balance: {balance} sat")
f"The backend for {WALLET.__class__.__name__} isn't working properly: '{error_message}'", logger.info(f"Data dir: {CASHU_DIR}")
RuntimeWarning, logger.info("Mint started.")
)
logger.info(f"Lightning balance: {balance} sat") def create_app(config_object="core.settings") -> FastAPI:
logger.info(f"Data dir: {CASHU_DIR}") def configure_logger() -> None:
logger.info("Mint started.") class Formatter:
def __init__(self):
self.padding = 0
def create_app(config_object="core.settings") -> FastAPI: self.minimal_fmt: str = "<green>{time:YYYY-MM-DD HH:mm:ss.SS}</green> | <level>{level}</level> | <level>{message}</level>\n"
def configure_logger() -> None: if settings.DEBUG:
class Formatter: self.fmt: str = "<green>{time:YYYY-MM-DD HH:mm:ss.SS}</green> | <level>{level: <4}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | <level>{message}</level>\n"
def __init__(self): else:
self.padding = 0 self.fmt: str = self.minimal_fmt
self.minimal_fmt: str = "<green>{time:YYYY-MM-DD HH:mm:ss.SS}</green> | <level>{level}</level> | <level>{message}</level>\n"
if settings.DEBUG: def format(self, record):
self.fmt: str = "<green>{time:YYYY-MM-DD HH:mm:ss.SS}</green> | <level>{level: <4}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> | <level>{message}</level>\n" function = "{function}".format(**record)
else: if function == "emit": # uvicorn logs
self.fmt: str = self.minimal_fmt return self.minimal_fmt
return self.fmt
def format(self, record):
function = "{function}".format(**record) class InterceptHandler(logging.Handler):
if function == "emit": # uvicorn logs def emit(self, record):
return self.minimal_fmt try:
return self.fmt level = logger.level(record.levelname).name
except ValueError:
class InterceptHandler(logging.Handler): level = record.levelno
def emit(self, record): logger.log(level, record.getMessage())
try:
level = logger.level(record.levelname).name logger.remove()
except ValueError: log_level: str = "INFO"
level = record.levelno formatter = Formatter()
logger.log(level, record.getMessage()) logger.add(sys.stderr, level=log_level, format=formatter.format)
logger.remove() logging.getLogger("uvicorn").handlers = [InterceptHandler()]
log_level: str = "INFO" logging.getLogger("uvicorn.access").handlers = [InterceptHandler()]
formatter = Formatter()
logger.add(sys.stderr, level=log_level, format=formatter.format) configure_logger()
logging.getLogger("uvicorn").handlers = [InterceptHandler()] app = FastAPI(
logging.getLogger("uvicorn.access").handlers = [InterceptHandler()] title="Cashu Mint",
description="Ecash wallet and mint.",
configure_logger() license_info={
"name": "MIT License",
app = FastAPI( "url": "https://raw.githubusercontent.com/callebtc/cashu/main/LICENSE",
title="Cashu Mint", },
description="Ecash wallet and mint.", )
license_info={
"name": "MIT License", startup(app)
"url": "https://raw.githubusercontent.com/callebtc/cashu/main/LICENSE.md", return app
},
)
# if __name__ == "__main__":
startup(app) # main()
return app
app = create_app()
@app.get("/keys")
def keys():
"""Get the public keys of the mint"""
return ledger.get_pubkeys()
@app.get("/mint")
async def request_mint(amount: int = 0):
"""Request minting of tokens. Server responds with a Lightning invoice."""
payment_request, payment_hash = await ledger.request_mint(amount)
print(f"Lightning invoice: {payment_request}")
return {"pr": payment_request, "hash": payment_hash}
@app.post("/mint")
async def mint(payloads: MintPayloads, payment_hash: Union[str, None] = None):
"""
Requests the minting of tokens belonging to a paid payment request.
Parameters:
pr: payment_request of the Lightning paid invoice.
Body (JSON):
payloads: contains a list of blinded messages waiting to be signed.
NOTE:
- This needs to be replaced by the preimage otherwise someone knowing
the payment_request can request the tokens instead of the rightful
owner.
- The blinded message should ideally be provided to the server *before* payment
in the GET /mint endpoint so that the server knows to sign only these tokens
when the invoice is paid.
"""
amounts = []
B_s = []
for payload in payloads.blinded_messages:
amounts.append(payload.amount)
B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
try:
promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash)
return promises
except Exception as exc:
return {"error": str(exc)}
@app.post("/melt")
async def melt(payload: MeltPayload):
"""
Requests tokens to be destroyed and sent out via Lightning.
"""
ok, preimage = await ledger.melt(payload.proofs, payload.amount, payload.invoice)
return {"paid": ok, "preimage": preimage}
@app.post("/check")
async def check_spendable(payload: CheckPayload):
return await ledger.check_spendable(payload.proofs)
@app.post("/split")
async def split(payload: SplitPayload):
"""
Requetst a set of tokens with amount "total" to be split into two
newly minted sets with amount "split" and "total-split".
"""
proofs = payload.proofs
amount = payload.amount
output_data = payload.output_data.blinded_messages
try:
split_return = await ledger.split(proofs, amount, output_data)
except Exception as exc:
return {"error": str(exc)}
if not split_return:
"""There was a problem with the split"""
raise Exception("could not split tokens.")
fst_promises, snd_promises = split_return
return {"fst": fst_promises, "snd": snd_promises}
@click.command(
context_settings=dict(
ignore_unknown_options=True,
allow_extra_args=True,
)
)
@click.option("--port", default=MINT_SERVER_PORT, help="Port to listen on")
@click.option("--host", default=MINT_SERVER_HOST, help="Host to run mint on")
@click.option("--ssl-keyfile", default=None, help="Path to SSL keyfile")
@click.option("--ssl-certfile", default=None, help="Path to SSL certificate")
@click.pass_context
def main(
ctx,
port: int = MINT_SERVER_PORT,
host: str = MINT_SERVER_HOST,
ssl_keyfile: str = None,
ssl_certfile: str = None,
):
"""Launched with `poetry run mint` at root level"""
# this beautiful beast parses all command line arguments and passes them to the uvicorn server
d = dict()
for a in ctx.args:
item = a.split("=")
if len(item) > 1: # argument like --key=value
print(a, item)
d[item[0].strip("--").replace("-", "_")] = (
int(item[1]) # need to convert to int if it's a number
if item[1].isdigit()
else item[1]
)
else:
d[a.strip("--")] = True # argument like --key
config = uvicorn.Config(
"mint.app:app",
port=port,
host=host,
ssl_keyfile=ssl_keyfile,
ssl_certfile=ssl_certfile,
**d,
)
server = uvicorn.Server(config)
server.run()
if __name__ == "__main__":
main()

52
mint/main.py Normal file
View File

@@ -0,0 +1,52 @@
import click
import uvicorn
from core.settings import (
MINT_SERVER_HOST,
MINT_SERVER_PORT,
)
@click.command(
context_settings=dict(
ignore_unknown_options=True,
allow_extra_args=True,
)
)
@click.option("--port", default=MINT_SERVER_PORT, help="Port to listen on")
@click.option("--host", default=MINT_SERVER_HOST, help="Host to run mint on")
@click.option("--ssl-keyfile", default=None, help="Path to SSL keyfile")
@click.option("--ssl-certfile", default=None, help="Path to SSL certificate")
@click.pass_context
def main(
ctx,
port: int = MINT_SERVER_PORT,
host: str = MINT_SERVER_HOST,
ssl_keyfile: str = None,
ssl_certfile: str = None,
):
"""Launched with `poetry run mint` at root level"""
# this beautiful beast parses all command line arguments and passes them to the uvicorn server
d = dict()
for a in ctx.args:
item = a.split("=")
if len(item) > 1: # argument like --key=value
print(a, item)
d[item[0].strip("--").replace("-", "_")] = (
int(item[1]) # need to convert to int if it's a number
if item[1].isdigit()
else item[1]
)
else:
d[a.strip("--")] = True # argument like --key
config = uvicorn.Config(
"mint.__main__:app",
port=port,
host=host,
ssl_keyfile=ssl_keyfile,
ssl_certfile=ssl_certfile,
**d,
)
server = uvicorn.Server(config)
server.run()

86
mint/router.py Normal file
View File

@@ -0,0 +1,86 @@
from fastapi import APIRouter
from mint import ledger
from core.base import CheckPayload, MeltPayload, MintPayloads, SplitPayload
from typing import Union
from secp256k1 import PublicKey
router: APIRouter = APIRouter()
@router.get("/keys")
def keys():
"""Get the public keys of the mint"""
return ledger.get_pubkeys()
@router.get("/mint")
async def request_mint(amount: int = 0):
"""Request minting of tokens. Server responds with a Lightning invoice."""
payment_request, payment_hash = await ledger.request_mint(amount)
print(f"Lightning invoice: {payment_request}")
return {"pr": payment_request, "hash": payment_hash}
@router.post("/mint")
async def mint(payloads: MintPayloads, payment_hash: Union[str, None] = None):
"""
Requests the minting of tokens belonging to a paid payment request.
Parameters:
pr: payment_request of the Lightning paid invoice.
Body (JSON):
payloads: contains a list of blinded messages waiting to be signed.
NOTE:
- This needs to be replaced by the preimage otherwise someone knowing
the payment_request can request the tokens instead of the rightful
owner.
- The blinded message should ideally be provided to the server *before* payment
in the GET /mint endpoint so that the server knows to sign only these tokens
when the invoice is paid.
"""
amounts = []
B_s = []
for payload in payloads.blinded_messages:
amounts.append(payload.amount)
B_s.append(PublicKey(bytes.fromhex(payload.B_), raw=True))
try:
promises = await ledger.mint(B_s, amounts, payment_hash=payment_hash)
return promises
except Exception as exc:
return {"error": str(exc)}
@router.post("/melt")
async def melt(payload: MeltPayload):
"""
Requests tokens to be destroyed and sent out via Lightning.
"""
ok, preimage = await ledger.melt(payload.proofs, payload.amount, payload.invoice)
return {"paid": ok, "preimage": preimage}
@router.post("/check")
async def check_spendable(payload: CheckPayload):
return await ledger.check_spendable(payload.proofs)
@router.post("/split")
async def split(payload: SplitPayload):
"""
Requetst a set of tokens with amount "total" to be split into two
newly minted sets with amount "split" and "total-split".
"""
proofs = payload.proofs
amount = payload.amount
output_data = payload.output_data.blinded_messages
try:
split_return = await ledger.split(proofs, amount, output_data)
except Exception as exc:
return {"error": str(exc)}
if not split_return:
"""There was a problem with the split"""
raise Exception("could not split tokens.")
fst_promises, snd_promises = split_return
return {"fst": fst_promises, "snd": snd_promises}