Merge pull request #5 from callebtc/deployment/dockerfile

Deployment/dockerfile
This commit is contained in:
calle
2022-09-15 03:00:37 +03:00
committed by GitHub
9 changed files with 201 additions and 94 deletions

11
Dockerfile Normal file
View File

@@ -0,0 +1,11 @@
FROM python:3.9-slim
RUN apt-get update
RUN apt-get install -y curl python3-dev autoconf g++
RUN curl -sSL https://install.python-poetry.org | python3 -
ENV PATH="/root/.local/bin:$PATH"
WORKDIR /app
COPY . .
RUN poetry config virtualenvs.create false
RUN poetry install --no-dev --no-root
EXPOSE 3338
CMD ["poetry", "run", "mint", "--port", "3338", "--host", "0.0.0.0"]

View File

@@ -49,7 +49,7 @@ To use the wallet with the [public test mint](#test-instance), you need to chang
## Run a mint yourself
This runs the mint on your local computer. Skip this step if you want to use the [public test mint](#test-instance) instead.
```bash
poetry run uvicorn mint.app:app --port 3338
poetry run mint
```
## Use wallet
@@ -59,7 +59,7 @@ poetry run uvicorn mint.app:app --port 3338
This command will return a Lightning invoice and a payment hash. You have to pay the invoice before you can receive the tokens. Note: Minting tokens involves two steps: requesting a mint, and actually minting tokens (see below).
```bash
poetry run ./cashu --wallet=wallet --mint=420
poetry run cashu mint 420
```
Returns:
```bash
@@ -73,7 +73,7 @@ Balance: 0
#### Mint tokens
After paying the invoice, copy the `hash` value from above and add it to the command
```bash
poetry run ./cashu --wallet=wallet --mint=420 --hash=009d6eb02da8769b37602ac6d98ecafc1d65bd2408114a5d50e60da200bc85c5
poetry run cashu mint 420 --hash=009d6eb02da8769b37602ac6d98ecafc1d65bd2408114a5d50e60da200bc85c5
```
You should see your balance update accordingly:
```bash
@@ -81,10 +81,15 @@ Balance: 0
Balance: 420
```
#### Check balance
```bash
poetry run cashu balance
```
#### Send tokens
To send tokens to another user, enter
```bash
poetry run ./cashu --send=69
poetry run cashu send 69
```
You should see the encoded token. Copy the token and send it to another user such as via email or a messenger. The token looks like this:
```bash
@@ -94,7 +99,7 @@ W3siYW1vdW50IjogMSwgIkMiOiB7IngiOiAzMzg0Mzg0NDYzNzAwMTY1NDA2MTQxMDY3Mzg1MDg5MjA2
#### Receive tokens
To receive tokens, another user enters:
```bash
poetry run ./cashu --receive=W3siYW1vdW50IjogMSwgIkMiOi...
poetry run cashu receive W3siYW1vdW50IjogMSwgIkMiOi...
```
You should see the balance increase:
```bash
@@ -102,10 +107,10 @@ wallet balance: 0
wallet balance: 69
```
#### Invalidate tokens
The sending user needs to invalidate their tokens from above, otherwise they will try to double spend them (which won't work because the server keeps a list of all spent tokens):
#### Burn tokens
The sending user needs to burn (invalidate) their tokens from above, otherwise they will try to double spend them (which won't work because the server keeps a list of all spent tokens):
```bash
poetry run ./cashu --invalidate=W3siYW1vdW50IjogMSwgIkMiOi...
poetry run cashu burn W3siYW1vdW50IjogMSwgIkMiOi...
```
Returns:
```bash

75
cashu
View File

@@ -1,75 +0,0 @@
#!/usr/bin/env python
import asyncio
import base64
import json
from functools import wraps
import click
from bech32 import bech32_decode, bech32_encode, convertbits
from wallet.migrations import m001_initial
from wallet.wallet import Wallet as Wallet
from core.settings import MINT_URL
# https://github.com/pallets/click/issues/85#issuecomment-503464628
def coro(f):
@wraps(f)
def wrapper(*args, **kwargs):
return asyncio.run(f(*args, **kwargs))
return wrapper
@click.command("mint")
@click.option("--host", default=MINT_URL, help="Mint hostname.")
@click.option("--wallet", default="wallet", help="Wallet to use.")
@click.option("--mint", default=0, help="Mint tokens.")
@click.option("--hash", default="", help="Hash of the paid invoice.")
@click.option("--send", default=0, help="Send tokens.")
@click.option("--receive", default="", help="Receive tokens.")
@click.option("--invalidate", default="", help="Invalidate tokens.")
@coro
async def main(host, wallet, mint, hash, send, receive, invalidate):
wallet = Wallet(host, f"data/{wallet}", wallet)
await m001_initial(db=wallet.db)
await wallet.load_proofs()
if mint and not hash:
print(f"Balance: {wallet.balance}")
r = await wallet.request_mint(mint)
print(r)
if mint and hash:
print(f"Balance: {wallet.balance}")
await wallet.mint(mint, hash)
print(f"Balance: {wallet.balance}")
if send:
wallet.status()
_, send_proofs = await wallet.split(wallet.proofs, send)
print(base64.urlsafe_b64encode(json.dumps(send_proofs).encode()).decode())
if receive:
wallet.status()
proofs = json.loads(base64.urlsafe_b64decode(receive))
_, _ = await wallet.redeem(proofs)
wallet.status()
if invalidate:
wallet.status()
proofs = json.loads(base64.urlsafe_b64decode(invalidate))
await wallet.invalidate(proofs)
wallet.status()
if __name__ == "__main__":
main()
@click.command("send")
@click.option("--send", default=1, help="Mint tokens.")
@coro
async def send(send):
print("asd")
# w1_fst_proofs, w1_snd_proofs = await wallet.split(proofs, 20)
return "asd"

1
cashu.py Executable file
View File

@@ -0,0 +1 @@
# for mysterious reasons, this file needs to exist for `poetry run cashu` to work

View File

@@ -3,13 +3,15 @@ import logging
import sys
from typing import Union
import click
import uvicorn
from ecc.curve import Point, secp256k1
from fastapi import FastAPI
from loguru import logger
import core.settings as settings
from core.base import MintPayloads, SplitPayload
from core.settings import MINT_PRIVATE_KEY
from core.settings import MINT_PRIVATE_KEY, MINT_SERVER_HOST, MINT_SERVER_PORT
from lightning import WALLET
from mint.ledger import Ledger
from mint.migrations import m001_initial
@@ -148,5 +150,50 @@ async def split(payload: SplitPayload):
return {"error": str(exc)}
# if __name__ == "__main__":
# uvicorn.run(app, host="127.0.0.1", port=5049)
@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()

View File

@@ -13,14 +13,9 @@ from core.db import Database
from core.settings import MAX_ORDER
from core.split import amount_split
from lightning import WALLET
from mint.crud import (
get_lightning_invoice,
get_proofs_used,
invalidate_proof,
store_lightning_invoice,
store_promise,
update_lightning_invoice,
)
from mint.crud import (get_lightning_invoice, get_proofs_used,
invalidate_proof, store_lightning_invoice,
store_promise, update_lightning_invoice)
class Ledger:

View File

@@ -39,3 +39,7 @@ isort = "^5.10.1"
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
mint = "mint.app:main"
cashu = "wallet.cashu:cli"

118
wallet/cashu.py Executable file
View File

@@ -0,0 +1,118 @@
#!/usr/bin/env python
import asyncio
import base64
import json
from functools import wraps
import click
from bech32 import bech32_decode, bech32_encode, convertbits
from core.settings import MINT_URL
from wallet.migrations import m001_initial
from wallet.wallet import Wallet as Wallet
async def init_wallet(wallet: Wallet):
"""Performs migrations and loads proofs from db."""
await m001_initial(db=wallet.db)
await wallet.load_proofs()
class NaturalOrderGroup(click.Group):
"""For listing commands in help in order of definition"""
def list_commands(self, ctx):
return self.commands.keys()
@click.group(cls=NaturalOrderGroup)
@click.option("--host", "-h", default=MINT_URL, help="Mint address.")
@click.option("--wallet", "-w", "walletname", default="wallet", help="Wallet to use.")
@click.pass_context
def cli(
ctx,
host: str,
walletname: str,
):
ctx.ensure_object(dict)
ctx.obj["HOST"] = host
ctx.obj["WALLET_NAME"] = walletname
ctx.obj["WALLET"] = Wallet(ctx.obj["HOST"], f"data/{walletname}", walletname)
pass
# https://github.com/pallets/click/issues/85#issuecomment-503464628
def coro(f):
@wraps(f)
def wrapper(*args, **kwargs):
return asyncio.run(f(*args, **kwargs))
return wrapper
@cli.command("mint", help="Mint tokens.")
@click.argument("amount", type=int)
@click.option("--hash", default="", help="Hash of the paid invoice.", type=str)
@click.pass_context
@coro
async def mint(ctx, amount: int, hash: str):
wallet: Wallet = ctx.obj["WALLET"]
await m001_initial(db=wallet.db)
await wallet.load_proofs()
if amount and not hash:
print(f"Balance: {wallet.balance}")
r = await wallet.request_mint(amount)
print(r)
if amount and hash:
print(f"Balance: {wallet.balance}")
await wallet.mint(amount, hash)
print(f"Balance: {wallet.balance}")
@cli.command("balance", help="See balance.")
@click.pass_context
@coro
async def receive(ctx):
wallet: Wallet = ctx.obj["WALLET"]
await init_wallet(wallet)
wallet.status()
@cli.command("send", help="Send tokens.")
@click.argument("amount", type=int)
@click.pass_context
@coro
async def send(ctx, amount: int):
wallet: Wallet = ctx.obj["WALLET"]
await init_wallet(wallet)
wallet.status()
_, send_proofs = await wallet.split(wallet.proofs, amount)
print(base64.urlsafe_b64encode(json.dumps(send_proofs).encode()).decode())
@cli.command("receive", help="Receive tokens.")
@click.argument("token", type=str)
@click.pass_context
@coro
async def receive(ctx, token: str):
wallet: Wallet = ctx.obj["WALLET"]
await init_wallet(wallet)
wallet.status()
proofs = json.loads(base64.urlsafe_b64decode(token))
_, _ = await wallet.redeem(proofs)
wallet.status()
@cli.command("burn", help="Burn spent tokens.")
@click.argument("token", type=str)
@click.pass_context
@coro
async def receive(ctx, token: str):
wallet: Wallet = ctx.obj["WALLET"]
await init_wallet(wallet)
wallet.status()
proofs = json.loads(base64.urlsafe_b64decode(token))
await wallet.invalidate(proofs)
wallet.status()

View File

@@ -139,6 +139,7 @@ class Wallet(LedgerAPI):
return await self.split(proofs, sum(p["amount"] for p in proofs))
async def split(self, proofs, amount):
assert len(proofs) > 0, ValueError("no proofs provided.")
fst_proofs, snd_proofs = super().split(proofs, amount)
if len(fst_proofs) == 0 and len(snd_proofs) == 0:
raise Exception("received no splits")