Files
nutshell/cashu/mint/migrations.py
callebtc a518274f7e Nutshell cleanup wishlist (#332)
* fix keys

* fix tests

* backwards compatible api upgrade

* upgrade seems to work

* fix tests

* add deprecated api functions

* add more tests of backwards compat

* add test serialization for nut00

* remove a redundant test

* move mint and melt to new api

* mypy works

* CI: mypy --check-untyped-defs

* add deprecated router

* add hints and remove logs

* fix tests

* cleanup

* use new mint and melt endpoints

* tests passing?

* fix mypy

* make format

* make format

* make format

* commit

* errors gone

* save

* adjust the API

* store quotes in db

* make mypy happy

* add fakewallet settings

* remove LIGHTNING=True and pass quote id for melt

* format

* tests passing

* add CoreLightningRestWallet

* add macaroon loader

* add correct config

* preimage -> proof

* move wallet.status() to cli.helpers.print_status()

* remove statuses from tests

* remove

* make format

* Use httpx in deprecated wallet

* fix cln interface

* create invoice before quote

* internal transactions and deprecated api testing

* fix tests

* add deprecated API tests

* fastapi type hints break things

* fix duplicate wallet error

* make format

* update poetry in CI to 1.7.1

* precommit restore

* remove bolt11

* oops

* default poetry

* store fee reserve for melt quotes and refactor melt()

* works?

* make format

* test

* finally

* fix deprecated models

* rename v1 endpoints to bolt11

* raise restore and check to v1, bump version to 0.15.0

* add version byte to keyset id

* remove redundant fields in json

* checks

* generate bip32 keyset wip

* migrate old keysets

* load duplicate keys

* duplicate old keysets

* revert router changes

* add deprecated /check and /restore endpoints

* try except invalidate

* parse unit from derivation path, adjust keyset id calculation with bytes

* remove keyest id from functions again and rely on self.keyset_id

* mosts tests work

* mint loads multiple derivation paths

* make format

* properly print units

* fix tests

* wallet works with multiple units

* add strike wallet and choose backend dynamically

* fix mypy

* add get_payment_quote to lightning backends

* make format

* fix startup

* fix lnbitswallet

* fix tests

* LightningWallet -> LightningBackend

* remove comments

* make format

* remove msat conversion

* add Amount type

* fix regtest

* use melt_quote as argument for pay_invoice

* test old api

* fees in sats

* fix deprecated fees

* fixes

* print balance correctly

* internally index keyset response by int

* add pydantic validation to input models

* add timestamps to mint db

* store timestamps for invoices, promises, proofs_used

* fix wallet migration

* rotate keys correctly for testing

* remove print

* update latest keyset

* fix tests

* fix test

* make format

* make format with correct black version

* remove nsat and cheese

* test against deprecated mint

* fix tests?

* actually use env var

* mint run with env vars

* moar test

* cleanup

* simplify tests, load all keys

* try out testing with internal invoices

* fix internal melt test

* fix test

* deprecated checkfees expects appropriate fees

* adjust comment

* drop lightning table

* split migration for testing for now, remove it later

* remove unused lightning table

* skip_private_key -> skip_db_read

* throw error on migration error

* reorder

* fix migrations

* fix lnbits fee return value negative

* fix typo

* comments

* add type

* make format

* split must use correct amount

* fix tests

* test deprecated api with internal/external melts

* do not split if not necessary

* refactor

* fix test

* make format with new black

* cleanup and add comments

* add quote state check endpoints

* fix deprecated wallet response

* split -> swap endpoint

* make format

* add expiry to quotes, get quote endpoints, and adjust to nut review comments

* allow overpayment of melt

* add lightning wallet tests

* commiting to save

* fix tests a bit

* make format

* remove comments

* get mint info

* check_spendable default False, and return payment quote checking id

* make format

* bump version in pyproject

* update to /v1/checkstate

* make format

* fix mint api checks

* return witness on /v1/checkstate

* no failfast

* try fail-fast: false in ci.yaml

* fix db lookup

* clean up literals
2024-01-08 00:57:15 +01:00

307 lines
10 KiB
Python

import time
from ..core.db import Connection, Database, table_with_schema
from ..core.settings import settings
async def m000_create_migrations_table(conn: Connection):
await conn.execute(f"""
CREATE TABLE IF NOT EXISTS {table_with_schema(conn, 'dbversions')} (
db TEXT PRIMARY KEY,
version INT NOT NULL
)
""")
async def m001_initial(db: Database):
async with db.connect() as conn:
await conn.execute(f"""
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'promises')} (
amount {db.big_int} NOT NULL,
B_b TEXT NOT NULL,
C_b TEXT NOT NULL,
UNIQUE (B_b)
);
""")
await conn.execute(f"""
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_used')} (
amount {db.big_int} NOT NULL,
C TEXT NOT NULL,
secret TEXT NOT NULL,
UNIQUE (secret)
);
""")
await conn.execute(f"""
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'invoices')} (
amount {db.big_int} NOT NULL,
pr TEXT NOT NULL,
hash TEXT NOT NULL,
issued BOOL NOT NULL,
UNIQUE (hash)
);
""")
async def m002_add_balance_views(db: Database):
async with db.connect() as conn:
await conn.execute(f"""
CREATE VIEW {table_with_schema(db, 'balance_issued')} AS
SELECT COALESCE(SUM(s), 0) AS balance FROM (
SELECT SUM(amount) AS s
FROM {table_with_schema(db, 'promises')}
WHERE amount > 0
) AS balance_issued;
""")
await conn.execute(f"""
CREATE VIEW {table_with_schema(db, 'balance_redeemed')} AS
SELECT COALESCE(SUM(s), 0) AS balance FROM (
SELECT SUM(amount) AS s
FROM {table_with_schema(db, 'proofs_used')}
WHERE amount > 0
) AS balance_redeemed;
""")
await conn.execute(f"""
CREATE VIEW {table_with_schema(db, 'balance')} AS
SELECT s_issued - s_used FROM (
SELECT bi.balance AS s_issued, bu.balance AS s_used
FROM {table_with_schema(db, 'balance_issued')} bi
CROSS JOIN {table_with_schema(db, 'balance_redeemed')} bu
) AS balance;
""")
async def m003_mint_keysets(db: Database):
"""
Stores mint keysets from different mints and epochs.
"""
async with db.connect() as conn:
await conn.execute(f"""
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'keysets')} (
id TEXT NOT NULL,
derivation_path TEXT,
valid_from TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
valid_to TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
first_seen TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
active BOOL DEFAULT TRUE,
UNIQUE (derivation_path)
);
""")
await conn.execute(f"""
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'mint_pubkeys')} (
id TEXT NOT NULL,
amount INTEGER NOT NULL,
pubkey TEXT NOT NULL,
UNIQUE (id, pubkey)
);
""")
async def m004_keysets_add_version(db: Database):
"""
Column that remembers with which version
"""
async with db.connect() as conn:
await conn.execute(
f"ALTER TABLE {table_with_schema(db, 'keysets')} ADD COLUMN version TEXT"
)
async def m005_pending_proofs_table(db: Database) -> None:
"""
Store pending proofs.
"""
async with db.connect() as conn:
await conn.execute(f"""
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_pending')} (
amount INTEGER NOT NULL,
C TEXT NOT NULL,
secret TEXT NOT NULL,
UNIQUE (secret)
);
""")
async def m006_invoices_add_payment_hash(db: Database):
"""
Column that remembers the payment_hash as we're using
the column hash as a random identifier now
(see https://github.com/cashubtc/nuts/pull/14).
"""
async with db.connect() as conn:
await conn.execute(
f"ALTER TABLE {table_with_schema(db, 'invoices')} ADD COLUMN payment_hash"
" TEXT"
)
await conn.execute(
f"UPDATE {table_with_schema(db, 'invoices')} SET payment_hash = hash"
)
async def m007_proofs_and_promises_store_id(db: Database):
"""
Column that stores the id of the proof or promise.
"""
async with db.connect() as conn:
await conn.execute(
f"ALTER TABLE {table_with_schema(db, 'proofs_used')} ADD COLUMN id TEXT"
)
await conn.execute(
f"ALTER TABLE {table_with_schema(db, 'proofs_pending')} ADD COLUMN id TEXT"
)
await conn.execute(
f"ALTER TABLE {table_with_schema(db, 'promises')} ADD COLUMN id TEXT"
)
async def m008_promises_dleq(db: Database):
"""
Add columns for DLEQ proof to promises table.
"""
async with db.connect() as conn:
await conn.execute(
f"ALTER TABLE {table_with_schema(db, 'promises')} ADD COLUMN e TEXT"
)
await conn.execute(
f"ALTER TABLE {table_with_schema(db, 'promises')} ADD COLUMN s TEXT"
)
async def m009_add_out_to_invoices(db: Database):
# column in invoices for marking whether the invoice is incoming (out=False) or outgoing (out=True)
async with db.connect() as conn:
# we have to drop the balance views first and recreate them later
await conn.execute(f"DROP VIEW IF EXISTS {table_with_schema(db, 'balance')}")
await conn.execute(
f"DROP VIEW IF EXISTS {table_with_schema(db, 'balance_issued')}"
)
await conn.execute(
f"DROP VIEW IF EXISTS {table_with_schema(db, 'balance_redeemed')}"
)
# rename column pr to bolt11
await conn.execute(
f"ALTER TABLE {table_with_schema(db, 'invoices')} RENAME COLUMN pr TO"
" bolt11"
)
# rename column hash to payment_hash
await conn.execute(
f"ALTER TABLE {table_with_schema(db, 'invoices')} RENAME COLUMN hash TO id"
)
# recreate balance views
await m002_add_balance_views(db)
async with db.connect() as conn:
await conn.execute(
f"ALTER TABLE {table_with_schema(db, 'invoices')} ADD COLUMN out BOOL"
)
async def m010_add_index_to_proofs_used(db: Database):
# create index on proofs_used table for secret
async with db.connect() as conn:
await conn.execute(
"CREATE INDEX IF NOT EXISTS"
" proofs_used_secret_idx ON"
f" {table_with_schema(db, 'proofs_used')} (secret)"
)
async def m011_add_quote_tables(db: Database):
async with db.connect() as conn:
# add column "created" to tables invoices, promises, proofs_used, proofs_pending
tables = ["invoices", "promises", "proofs_used", "proofs_pending"]
for table in tables:
await conn.execute(
f"ALTER TABLE {table_with_schema(db, table)} ADD COLUMN created"
" TIMESTAMP"
)
await conn.execute(
f"UPDATE {table_with_schema(db, table)} SET created ="
f" '{int(time.time())}'"
)
# add column "witness" to table proofs_used
await conn.execute(
f"ALTER TABLE {table_with_schema(db, 'proofs_used')} ADD COLUMN witness"
" TEXT"
)
# add columns "seed" and "unit" to table keysets
await conn.execute(
f"ALTER TABLE {table_with_schema(db, 'keysets')} ADD COLUMN seed TEXT"
)
await conn.execute(
f"ALTER TABLE {table_with_schema(db, 'keysets')} ADD COLUMN unit TEXT"
)
# fill columns "seed" and "unit" in table keysets
await conn.execute(
f"UPDATE {table_with_schema(db, 'keysets')} SET seed ="
f" '{settings.mint_private_key}', unit = 'sat'"
)
await conn.execute(f"""
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'mint_quotes')} (
quote TEXT NOT NULL,
method TEXT NOT NULL,
request TEXT NOT NULL,
checking_id TEXT NOT NULL,
unit TEXT NOT NULL,
amount INTEGER NOT NULL,
paid BOOL NOT NULL,
issued BOOL NOT NULL,
created_time TIMESTAMP,
paid_time TIMESTAMP,
UNIQUE (quote)
);
""")
await conn.execute(f"""
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'melt_quotes')} (
quote TEXT NOT NULL,
method TEXT NOT NULL,
request TEXT NOT NULL,
checking_id TEXT NOT NULL,
unit TEXT NOT NULL,
amount INTEGER NOT NULL,
fee_reserve INTEGER,
paid BOOL NOT NULL,
created_time TIMESTAMP,
paid_time TIMESTAMP,
fee_paid INTEGER,
proof TEXT,
UNIQUE (quote)
);
""")
await conn.execute(
f"INSERT INTO {table_with_schema(db, 'mint_quotes')} (quote, method,"
" request, checking_id, unit, amount, paid, issued, created_time,"
" paid_time) SELECT id, 'bolt11', bolt11, payment_hash, 'sat', amount,"
f" False, issued, created, 0 FROM {table_with_schema(db, 'invoices')} "
)
# drop table invoices
await conn.execute(f"DROP TABLE {table_with_schema(db, 'invoices')}")