mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 10:34:20 +01:00
Mint: table locks (#566)
* clean up db * db: table lock * db.table_with_schema * fix encrypt.py * postgres nowait * add timeout to lock * melt quote state in db * kinda working * kinda working with postgres * remove dispose * getting there * porperly clean up db for tests * faster tests * configure connection pooling * try github with connection pool * invoice dispatcher does not lock db * fakewallet: pay_if_regtest waits * pay fakewallet invoices * add more * faster * slower * pay_if_regtest async * do not lock the invoice dispatcher * test: do I get disk I/O errors if we disable the invoice_callback_dispatcher? * fix fake so it workss without a callback dispatchert * test on github * readd tasks * refactor * increase time for lock invoice disatcher * try avoiding a race * remove task * github actions: test regtest with postgres * mint per module * no connection pool for testing * enable pool * do not resend paid event * reuse connection * close db connections * sessions * enable debug * dispose engine * disable connection pool for tests * enable connection pool for postgres only * clean up shutdown routine * remove wait for lightning fakewallet lightning invoice * cancel invoice listener tasks on shutdown * fakewallet conftest: decrease outgoing delay * delay payment and set postgres only if needed * disable fail fast for regtest * clean up regtest.yml * change order of tests_db.py * row-specific mint_quote locking * refactor * fix lock statement * refactor swap * refactor * remove psycopg2 * add connection string example to .env.example * remove unnecessary pay * shorter sleep in test_wallet_subscription_swap
This commit is contained in:
@@ -54,7 +54,10 @@ MINT_DERIVATION_PATH="m/0'/0'/0'"
|
|||||||
# In this example, we have 2 keysets for sat, 1 for msat and 1 for usd
|
# In this example, we have 2 keysets for sat, 1 for msat and 1 for usd
|
||||||
# MINT_DERIVATION_PATH_LIST=["m/0'/0'/0'", "m/0'/0'/1'", "m/0'/1'/0'", "m/0'/2'/0'"]
|
# MINT_DERIVATION_PATH_LIST=["m/0'/0'/0'", "m/0'/0'/1'", "m/0'/1'/0'", "m/0'/2'/0'"]
|
||||||
|
|
||||||
|
# To use SQLite, choose a directory to store the database
|
||||||
MINT_DATABASE=data/mint
|
MINT_DATABASE=data/mint
|
||||||
|
# To use PostgreSQL, set the connection string
|
||||||
|
# MINT_DATABASE=postgres://cashu:cashu@localhost:5432/cashu
|
||||||
|
|
||||||
# Funding source backends
|
# Funding source backends
|
||||||
# Supported: FakeWallet, LndRestWallet, CLNRestWallet, BlinkWallet, LNbitsWallet, StrikeWallet, CoreLightningRestWallet (deprecated)
|
# Supported: FakeWallet, LndRestWallet, CLNRestWallet, BlinkWallet, LNbitsWallet, StrikeWallet, CoreLightningRestWallet (deprecated)
|
||||||
|
|||||||
2
.github/actions/prepare/action.yml
vendored
2
.github/actions/prepare/action.yml
vendored
@@ -23,5 +23,5 @@ runs:
|
|||||||
cache: "poetry"
|
cache: "poetry"
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
poetry install --extras pgsql
|
poetry install
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@@ -28,13 +28,14 @@ jobs:
|
|||||||
regtest:
|
regtest:
|
||||||
uses: ./.github/workflows/regtest.yml
|
uses: ./.github/workflows/regtest.yml
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
python-version: ["3.10"]
|
python-version: ["3.10"]
|
||||||
poetry-version: ["1.7.1"]
|
poetry-version: ["1.7.1"]
|
||||||
backend-wallet-class:
|
backend-wallet-class:
|
||||||
["LndRestWallet", "CLNRestWallet", "CoreLightningRestWallet", "LNbitsWallet"]
|
["LndRestWallet", "CLNRestWallet", "CoreLightningRestWallet", "LNbitsWallet"]
|
||||||
# mint-database: ["./test_data/test_mint", "postgres://cashu:cashu@localhost:5432/cashu"]
|
mint-database: ["./test_data/test_mint", "postgres://cashu:cashu@localhost:5432/cashu"]
|
||||||
mint-database: ["./test_data/test_mint"]
|
# mint-database: ["./test_data/test_mint"]
|
||||||
with:
|
with:
|
||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
backend-wallet-class: ${{ matrix.backend-wallet-class }}
|
backend-wallet-class: ${{ matrix.backend-wallet-class }}
|
||||||
|
|||||||
22
.github/workflows/regtest.yml
vendored
22
.github/workflows/regtest.yml
vendored
@@ -23,21 +23,13 @@ jobs:
|
|||||||
regtest:
|
regtest:
|
||||||
runs-on: ${{ inputs.os-version }}
|
runs-on: ${{ inputs.os-version }}
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: postgres:latest
|
|
||||||
env:
|
|
||||||
POSTGRES_USER: cashu
|
|
||||||
POSTGRES_PASSWORD: cashu
|
|
||||||
POSTGRES_DB: cashu
|
|
||||||
ports:
|
|
||||||
- 5432:5432
|
|
||||||
options: >-
|
|
||||||
--health-cmd pg_isready
|
|
||||||
--health-interval 10s
|
|
||||||
--health-timeout 5s
|
|
||||||
--health-retries 5
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Start PostgreSQL service
|
||||||
|
if: contains(inputs.mint-database, 'postgres')
|
||||||
|
run: |
|
||||||
|
docker run -d --name postgres -e POSTGRES_USER=cashu -e POSTGRES_PASSWORD=cashu -e POSTGRES_DB=cashu -p 5432:5432 postgres:latest
|
||||||
|
until docker exec postgres pg_isready; do sleep 1; done
|
||||||
|
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
- uses: ./.github/actions/prepare
|
- uses: ./.github/actions/prepare
|
||||||
@@ -78,7 +70,7 @@ jobs:
|
|||||||
MINT_CLNREST_URL: https://localhost:3010
|
MINT_CLNREST_URL: https://localhost:3010
|
||||||
MINT_CLNREST_RUNE: ./regtest/data/clightning-2/rune
|
MINT_CLNREST_RUNE: ./regtest/data/clightning-2/rune
|
||||||
MINT_CLNREST_CERT: ./regtest/data/clightning-2/regtest/ca.pem
|
MINT_CLNREST_CERT: ./regtest/data/clightning-2/regtest/ca.pem
|
||||||
MINT_CLNENABLE_MPP: false
|
MINT_CLNREST_ENABLE_MPP: false
|
||||||
run: |
|
run: |
|
||||||
sudo chmod -R 777 .
|
sudo chmod -R 777 .
|
||||||
make test
|
make test
|
||||||
|
|||||||
19
.github/workflows/tests.yml
vendored
19
.github/workflows/tests.yml
vendored
@@ -23,21 +23,12 @@ jobs:
|
|||||||
poetry:
|
poetry:
|
||||||
name: Run (db ${{ inputs.mint-database }}, deprecated api ${{ inputs.mint-only-deprecated }})
|
name: Run (db ${{ inputs.mint-database }}, deprecated api ${{ inputs.mint-only-deprecated }})
|
||||||
runs-on: ${{ inputs.os }}
|
runs-on: ${{ inputs.os }}
|
||||||
services:
|
|
||||||
postgres:
|
|
||||||
image: postgres:latest
|
|
||||||
env:
|
|
||||||
POSTGRES_USER: cashu
|
|
||||||
POSTGRES_PASSWORD: cashu
|
|
||||||
POSTGRES_DB: cashu
|
|
||||||
ports:
|
|
||||||
- 5432:5432
|
|
||||||
options: >-
|
|
||||||
--health-cmd pg_isready
|
|
||||||
--health-interval 10s
|
|
||||||
--health-timeout 5s
|
|
||||||
--health-retries 5
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Start PostgreSQL service
|
||||||
|
if: contains(inputs.mint-database, 'postgres')
|
||||||
|
run: |
|
||||||
|
docker run -d --name postgres -e POSTGRES_USER=cashu -e POSTGRES_PASSWORD=cashu -e POSTGRES_DB=cashu -p 5432:5432 postgres:latest
|
||||||
|
until docker exec postgres pg_isready; do sleep 1; done
|
||||||
- name: Checkout repository and submodules
|
- name: Checkout repository and submodules
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
- uses: ./.github/actions/prepare
|
- uses: ./.github/actions/prepare
|
||||||
|
|||||||
@@ -83,8 +83,6 @@ pyenv local 3.10.4
|
|||||||
poetry install
|
poetry install
|
||||||
```
|
```
|
||||||
|
|
||||||
If you would like to use PostgreSQL as the mint database, use the command `poetry install --extras pgsql`.
|
|
||||||
|
|
||||||
#### Poetry: Update Cashu
|
#### Poetry: Update Cashu
|
||||||
To update Cashu to the newest version enter
|
To update Cashu to the newest version enter
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -344,11 +344,13 @@ class MeltQuote(LedgerEvent):
|
|||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
# an unpaid quote can only be set to pending or paid
|
# an unpaid quote can only be set to pending or paid
|
||||||
if name == "state" and self.state == MeltQuoteState.unpaid:
|
if name == "state" and self.state == MeltQuoteState.unpaid:
|
||||||
if value != MeltQuoteState.pending and value != MeltQuoteState.paid:
|
if value not in [MeltQuoteState.pending, MeltQuoteState.paid]:
|
||||||
raise Exception("Cannot change state of an unpaid quote.")
|
raise Exception(
|
||||||
|
f"Cannot change state of an unpaid melt quote to {value}."
|
||||||
|
)
|
||||||
# a paid quote can not be changed
|
# a paid quote can not be changed
|
||||||
if name == "state" and self.state == MeltQuoteState.paid:
|
if name == "state" and self.state == MeltQuoteState.paid:
|
||||||
raise Exception("Cannot change state of a paid quote.")
|
raise Exception("Cannot change state of a paid melt quote.")
|
||||||
super().__setattr__(name, value)
|
super().__setattr__(name, value)
|
||||||
|
|
||||||
|
|
||||||
@@ -415,18 +417,20 @@ class MintQuote(LedgerEvent):
|
|||||||
# un unpaid quote can only be set to paid
|
# un unpaid quote can only be set to paid
|
||||||
if name == "state" and self.state == MintQuoteState.unpaid:
|
if name == "state" and self.state == MintQuoteState.unpaid:
|
||||||
if value != MintQuoteState.paid:
|
if value != MintQuoteState.paid:
|
||||||
raise Exception("Cannot change state of an unpaid quote.")
|
raise Exception(
|
||||||
|
f"Cannot change state of an unpaid mint quote to {value}."
|
||||||
|
)
|
||||||
# a paid quote can only be set to pending or issued
|
# a paid quote can only be set to pending or issued
|
||||||
if name == "state" and self.state == MintQuoteState.paid:
|
if name == "state" and self.state == MintQuoteState.paid:
|
||||||
if value != MintQuoteState.pending and value != MintQuoteState.issued:
|
if value != MintQuoteState.pending and value != MintQuoteState.issued:
|
||||||
raise Exception(f"Cannot change state of a paid quote to {value}.")
|
raise Exception(f"Cannot change state of a paid mint quote to {value}.")
|
||||||
# a pending quote can only be set to paid or issued
|
# a pending quote can only be set to paid or issued
|
||||||
if name == "state" and self.state == MintQuoteState.pending:
|
if name == "state" and self.state == MintQuoteState.pending:
|
||||||
if value not in [MintQuoteState.paid, MintQuoteState.issued]:
|
if value not in [MintQuoteState.paid, MintQuoteState.issued]:
|
||||||
raise Exception("Cannot change state of a pending quote.")
|
raise Exception("Cannot change state of a pending mint quote.")
|
||||||
# an issued quote cannot be changed
|
# an issued quote cannot be changed
|
||||||
if name == "state" and self.state == MintQuoteState.issued:
|
if name == "state" and self.state == MintQuoteState.issued:
|
||||||
raise Exception("Cannot change state of an issued quote.")
|
raise Exception("Cannot change state of an issued mint quote.")
|
||||||
super().__setattr__(name, value)
|
super().__setattr__(name, value)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
338
cashu/core/db.py
338
cashu/core/db.py
@@ -1,14 +1,18 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import time
|
import time
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import text
|
||||||
from sqlalchemy_aio.base import AsyncConnection
|
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
||||||
from sqlalchemy_aio.strategy import ASYNCIO_STRATEGY # type: ignore
|
from sqlalchemy.orm import sessionmaker
|
||||||
|
from sqlalchemy.pool import NullPool, QueuePool
|
||||||
|
|
||||||
|
from cashu.core.settings import settings
|
||||||
|
|
||||||
POSTGRES = "POSTGRES"
|
POSTGRES = "POSTGRES"
|
||||||
COCKROACH = "COCKROACH"
|
COCKROACH = "COCKROACH"
|
||||||
@@ -57,9 +61,12 @@ class Compat:
|
|||||||
return "BIGINT"
|
return "BIGINT"
|
||||||
return "INT"
|
return "INT"
|
||||||
|
|
||||||
|
def table_with_schema(self, table: str):
|
||||||
|
return f"{self.references_schema if self.schema else ''}{table}"
|
||||||
|
|
||||||
|
|
||||||
class Connection(Compat):
|
class Connection(Compat):
|
||||||
def __init__(self, conn: AsyncConnection, txn, typ, name, schema):
|
def __init__(self, conn: AsyncSession, txn, typ, name, schema):
|
||||||
self.conn = conn
|
self.conn = conn
|
||||||
self.txn = txn
|
self.txn = txn
|
||||||
self.type = typ
|
self.type = typ
|
||||||
@@ -70,23 +77,23 @@ class Connection(Compat):
|
|||||||
if self.type in {POSTGRES, COCKROACH}:
|
if self.type in {POSTGRES, COCKROACH}:
|
||||||
query = query.replace("%", "%%")
|
query = query.replace("%", "%%")
|
||||||
query = query.replace("?", "%s")
|
query = query.replace("?", "%s")
|
||||||
return query
|
return text(query)
|
||||||
|
|
||||||
async def fetchall(self, query: str, values: tuple = ()) -> list:
|
async def fetchall(self, query: str, values: dict = {}) -> list:
|
||||||
result = await self.conn.execute(self.rewrite_query(query), values)
|
result = await self.conn.execute(self.rewrite_query(query), values)
|
||||||
return await result.fetchall()
|
return result.all()
|
||||||
|
|
||||||
async def fetchone(self, query: str, values: tuple = ()):
|
async def fetchone(self, query: str, values: dict = {}):
|
||||||
result = await self.conn.execute(self.rewrite_query(query), values)
|
result = await self.conn.execute(self.rewrite_query(query), values)
|
||||||
row = await result.fetchone()
|
return result.fetchone()
|
||||||
await result.close()
|
|
||||||
return row
|
|
||||||
|
|
||||||
async def execute(self, query: str, values: tuple = ()):
|
async def execute(self, query: str, values: dict = {}):
|
||||||
return await self.conn.execute(self.rewrite_query(query), values)
|
return await self.conn.execute(self.rewrite_query(query), values)
|
||||||
|
|
||||||
|
|
||||||
class Database(Compat):
|
class Database(Compat):
|
||||||
|
_connection: Optional[AsyncSession] = None
|
||||||
|
|
||||||
def __init__(self, db_name: str, db_location: str):
|
def __init__(self, db_name: str, db_location: str):
|
||||||
self.name = db_name
|
self.name = db_name
|
||||||
self.db_location = db_location
|
self.db_location = db_location
|
||||||
@@ -99,43 +106,20 @@ class Database(Compat):
|
|||||||
self.type = COCKROACH
|
self.type = COCKROACH
|
||||||
else:
|
else:
|
||||||
self.type = POSTGRES
|
self.type = POSTGRES
|
||||||
|
database_uri = database_uri.replace(
|
||||||
import psycopg2 # type: ignore
|
"postgres://", "postgresql+asyncpg://"
|
||||||
|
|
||||||
def _parse_timestamp(value, _):
|
|
||||||
f = "%Y-%m-%d %H:%M:%S.%f"
|
|
||||||
if "." not in value:
|
|
||||||
f = "%Y-%m-%d %H:%M:%S"
|
|
||||||
return time.mktime(datetime.datetime.strptime(value, f).timetuple())
|
|
||||||
|
|
||||||
psycopg2.extensions.register_type( # type: ignore
|
|
||||||
psycopg2.extensions.new_type( # type: ignore
|
|
||||||
psycopg2.extensions.DECIMAL.values, # type: ignore
|
|
||||||
"DEC2FLOAT",
|
|
||||||
lambda value, curs: float(value) if value is not None else None,
|
|
||||||
)
|
)
|
||||||
)
|
database_uri = database_uri.replace(
|
||||||
psycopg2.extensions.register_type( # type: ignore
|
"postgresql://", "postgresql+asyncpg://"
|
||||||
psycopg2.extensions.new_type( # type: ignore
|
|
||||||
(1082, 1083, 1266),
|
|
||||||
"DATE2INT",
|
|
||||||
lambda value, curs: (
|
|
||||||
time.mktime(value.timetuple()) if value is not None else None # type: ignore
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
# Disble prepared statement cache: https://docs.sqlalchemy.org/en/14/dialects/postgresql.html#prepared-statement-cache
|
||||||
|
database_uri += "?prepared_statement_cache_size=0"
|
||||||
# psycopg2.extensions.register_type(
|
|
||||||
# psycopg2.extensions.new_type(
|
|
||||||
# (1184, 1114), "TIMESTAMP2INT", _parse_timestamp
|
|
||||||
# )
|
|
||||||
# )
|
|
||||||
else:
|
else:
|
||||||
if not os.path.exists(self.db_location):
|
if not os.path.exists(self.db_location):
|
||||||
logger.info(f"Creating database directory: {self.db_location}")
|
logger.info(f"Creating database directory: {self.db_location}")
|
||||||
os.makedirs(self.db_location)
|
os.makedirs(self.db_location)
|
||||||
self.path = os.path.join(self.db_location, f"{self.name}.sqlite3")
|
self.path = os.path.join(self.db_location, f"{self.name}.sqlite3")
|
||||||
database_uri = f"sqlite:///{self.path}"
|
database_uri = f"sqlite+aiosqlite:///{self.path}?check_same_thread=false"
|
||||||
self.type = SQLITE
|
self.type = SQLITE
|
||||||
|
|
||||||
self.schema = self.name
|
self.schema = self.name
|
||||||
@@ -144,44 +128,167 @@ class Database(Compat):
|
|||||||
else:
|
else:
|
||||||
self.schema = None
|
self.schema = None
|
||||||
|
|
||||||
self.engine = create_engine(database_uri, strategy=ASYNCIO_STRATEGY)
|
kwargs = {}
|
||||||
self.lock = asyncio.Lock()
|
if not settings.db_connection_pool:
|
||||||
|
kwargs["poolclass"] = NullPool
|
||||||
|
elif self.type == POSTGRES:
|
||||||
|
kwargs["poolclass"] = QueuePool
|
||||||
|
kwargs["pool_size"] = 50
|
||||||
|
kwargs["max_overflow"] = 100
|
||||||
|
|
||||||
|
self.engine = create_async_engine(database_uri, **kwargs)
|
||||||
|
self.async_session = sessionmaker(
|
||||||
|
self.engine,
|
||||||
|
expire_on_commit=False,
|
||||||
|
class_=AsyncSession, # type: ignore
|
||||||
|
)
|
||||||
|
|
||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def connect(self):
|
async def get_connection(
|
||||||
await self.lock.acquire()
|
self,
|
||||||
try:
|
conn: Optional[Connection] = None,
|
||||||
async with self.engine.connect() as conn: # type: ignore
|
lock_table: Optional[str] = None,
|
||||||
async with conn.begin() as txn:
|
lock_select_statement: Optional[str] = None,
|
||||||
wconn = Connection(conn, txn, self.type, self.name, self.schema)
|
lock_timeout: Optional[float] = None,
|
||||||
|
):
|
||||||
|
"""Either yield the existing database connection (passthrough) or create a new one.
|
||||||
|
|
||||||
if self.schema:
|
Args:
|
||||||
if self.type in {POSTGRES, COCKROACH}:
|
conn (Optional[Connection], optional): Connection object. Defaults to None.
|
||||||
await wconn.execute(
|
lock_table (Optional[str], optional): Table to lock. Defaults to None.
|
||||||
f"CREATE SCHEMA IF NOT EXISTS {self.schema}"
|
lock_select_statement (Optional[str], optional): Lock select statement. Defaults to None.
|
||||||
)
|
lock_timeout (Optional[float], optional): Lock timeout. Defaults to None.
|
||||||
elif self.type == SQLITE:
|
|
||||||
await wconn.execute(
|
|
||||||
f"ATTACH '{self.path}' AS {self.schema}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
Yields:
|
||||||
|
Connection: Connection object.
|
||||||
|
"""
|
||||||
|
if conn is not None:
|
||||||
|
# Yield the existing connection
|
||||||
|
logger.trace("Reusing existing connection")
|
||||||
|
yield conn
|
||||||
|
else:
|
||||||
|
logger.trace("get_connection: Creating new connection")
|
||||||
|
async with self.connect(
|
||||||
|
lock_table, lock_select_statement, lock_timeout
|
||||||
|
) as new_conn:
|
||||||
|
yield new_conn
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
|
async def connect(
|
||||||
|
self,
|
||||||
|
lock_table: Optional[str] = None,
|
||||||
|
lock_select_statement: Optional[str] = None,
|
||||||
|
lock_timeout: Optional[float] = None,
|
||||||
|
):
|
||||||
|
async def _handle_lock_retry(retry_delay, timeout, start_time) -> float:
|
||||||
|
await asyncio.sleep(retry_delay)
|
||||||
|
retry_delay = min(retry_delay * 2, timeout - (time.time() - start_time))
|
||||||
|
return retry_delay
|
||||||
|
|
||||||
|
def _is_lock_exception(e):
|
||||||
|
if "database is locked" in str(e) or "could not obtain lock" in str(e):
|
||||||
|
logger.trace(f"Lock exception: {e}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
timeout = lock_timeout or 5 # default to 5 seconds
|
||||||
|
start_time = time.time()
|
||||||
|
retry_delay = 0.1
|
||||||
|
random_int = int(time.time() * 1000)
|
||||||
|
trial = 0
|
||||||
|
|
||||||
|
while time.time() - start_time < timeout:
|
||||||
|
trial += 1
|
||||||
|
session: AsyncSession = self.async_session() # type: ignore
|
||||||
|
try:
|
||||||
|
logger.trace(f"Connecting to database trial: {trial} ({random_int})")
|
||||||
|
async with session.begin() as txn: # type: ignore
|
||||||
|
logger.trace("Connected to database. Starting transaction")
|
||||||
|
wconn = Connection(session, txn, self.type, self.name, self.schema)
|
||||||
|
if lock_table:
|
||||||
|
await self.acquire_lock(
|
||||||
|
wconn, lock_table, lock_select_statement
|
||||||
|
)
|
||||||
|
logger.trace(
|
||||||
|
f"> Yielding connection. Lock: {lock_table} - trial {trial} ({random_int})"
|
||||||
|
)
|
||||||
yield wconn
|
yield wconn
|
||||||
finally:
|
logger.trace(
|
||||||
self.lock.release()
|
f"< Connection yielded. Unlock: {lock_table} - trial {trial} ({random_int})"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
if _is_lock_exception(e):
|
||||||
|
retry_delay = await _handle_lock_retry(
|
||||||
|
retry_delay, timeout, start_time
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
logger.error(f"Error in session trial: {trial} ({random_int}): {e}")
|
||||||
|
raise e
|
||||||
|
finally:
|
||||||
|
logger.trace(f"Closing session trial: {trial} ({random_int})")
|
||||||
|
await session.close()
|
||||||
|
# if not inherited:
|
||||||
|
# logger.trace("Closing session")
|
||||||
|
# await session.close()
|
||||||
|
# self._connection = None
|
||||||
|
raise Exception(
|
||||||
|
f"failed to acquire database lock on {lock_table} after {timeout}s and {trial} trials ({random_int})"
|
||||||
|
)
|
||||||
|
|
||||||
async def fetchall(self, query: str, values: tuple = ()) -> list:
|
async def acquire_lock(
|
||||||
|
self,
|
||||||
|
wconn: Connection,
|
||||||
|
lock_table: str,
|
||||||
|
lock_select_statement: Optional[str] = None,
|
||||||
|
):
|
||||||
|
"""Acquire a lock on a table or a row in a table.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
wconn (Connection): Connection object.
|
||||||
|
lock_table (str): Table to lock.
|
||||||
|
lock_select_statement (Optional[str], optional):
|
||||||
|
lock_timeout (Optional[float], optional):
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
Exception: _description_
|
||||||
|
"""
|
||||||
|
if lock_select_statement:
|
||||||
|
assert (
|
||||||
|
len(re.findall(r"^[^=]+='[^']+'$", lock_select_statement)) == 1
|
||||||
|
), "lock_select_statement must have exactly one {column}='{value}' pattern."
|
||||||
|
try:
|
||||||
|
logger.trace(
|
||||||
|
f"Acquiring lock on {lock_table} with statement {self.lock_table(lock_table, lock_select_statement)}"
|
||||||
|
)
|
||||||
|
await wconn.execute(self.lock_table(lock_table, lock_select_statement))
|
||||||
|
logger.trace(f"Success: Acquired lock on {lock_table}")
|
||||||
|
return
|
||||||
|
except Exception as e:
|
||||||
|
if (
|
||||||
|
(
|
||||||
|
self.type == POSTGRES
|
||||||
|
and "could not obtain lock on relation" in str(e)
|
||||||
|
)
|
||||||
|
or (self.type == COCKROACH and "already locked" in str(e))
|
||||||
|
or (self.type == SQLITE and "database is locked" in str(e))
|
||||||
|
):
|
||||||
|
logger.trace(f"Table {lock_table} is already locked: {e}")
|
||||||
|
else:
|
||||||
|
logger.trace(f"Failed to acquire lock on {lock_table}: {e}")
|
||||||
|
|
||||||
|
raise e
|
||||||
|
|
||||||
|
async def fetchall(self, query: str, values: dict = {}) -> list:
|
||||||
async with self.connect() as conn:
|
async with self.connect() as conn:
|
||||||
result = await conn.execute(query, values)
|
result = await conn.execute(query, values)
|
||||||
return await result.fetchall()
|
return result.all()
|
||||||
|
|
||||||
async def fetchone(self, query: str, values: tuple = ()):
|
async def fetchone(self, query: str, values: dict = {}):
|
||||||
async with self.connect() as conn:
|
async with self.connect() as conn:
|
||||||
result = await conn.execute(query, values)
|
result = await conn.execute(query, values)
|
||||||
row = await result.fetchone()
|
return result.fetchone()
|
||||||
await result.close()
|
|
||||||
return row
|
|
||||||
|
|
||||||
async def execute(self, query: str, values: tuple = ()):
|
async def execute(self, query: str, values: dict = {}):
|
||||||
async with self.connect() as conn:
|
async with self.connect() as conn:
|
||||||
return await conn.execute(query, values)
|
return await conn.execute(query, values)
|
||||||
|
|
||||||
@@ -189,60 +296,51 @@ class Database(Compat):
|
|||||||
async def reuse_conn(self, conn: Connection):
|
async def reuse_conn(self, conn: Connection):
|
||||||
yield conn
|
yield conn
|
||||||
|
|
||||||
|
def lock_table(
|
||||||
|
self,
|
||||||
|
table: str,
|
||||||
|
lock_select_statement: Optional[str] = None,
|
||||||
|
) -> str:
|
||||||
|
# with postgres, we can lock a row with a SELECT statement with FOR UPDATE NOWAIT
|
||||||
|
if lock_select_statement:
|
||||||
|
if self.type == POSTGRES:
|
||||||
|
return f"SELECT 1 FROM {self.table_with_schema(table)} WHERE {lock_select_statement} FOR UPDATE NOWAIT;"
|
||||||
|
|
||||||
# public functions for LNbits to use (we don't want to change the Database or Compat classes above)
|
if self.type == POSTGRES:
|
||||||
def table_with_schema(db: Union[Database, Connection], table: str):
|
return (
|
||||||
return f"{db.references_schema if db.schema else ''}{table}"
|
f"LOCK TABLE {self.table_with_schema(table)} IN EXCLUSIVE MODE NOWAIT;"
|
||||||
|
)
|
||||||
|
elif self.type == COCKROACH:
|
||||||
|
return f"LOCK TABLE {table};"
|
||||||
|
elif self.type == SQLITE:
|
||||||
|
return "BEGIN EXCLUSIVE TRANSACTION;"
|
||||||
|
return "<nothing>"
|
||||||
|
|
||||||
|
def timestamp_from_seconds(
|
||||||
def lock_table(db: Database, table: str) -> str:
|
self, seconds: Union[int, float, None]
|
||||||
if db.type == POSTGRES:
|
) -> Union[str, None]:
|
||||||
return f"LOCK TABLE {table_with_schema(db, table)} IN EXCLUSIVE MODE;"
|
if seconds is None:
|
||||||
elif db.type == COCKROACH:
|
return None
|
||||||
return f"LOCK TABLE {table};"
|
seconds = int(seconds)
|
||||||
elif db.type == SQLITE:
|
if self.type in {POSTGRES, COCKROACH}:
|
||||||
return "BEGIN EXCLUSIVE TRANSACTION;"
|
return datetime.datetime.fromtimestamp(seconds).strftime(
|
||||||
return "<nothing>"
|
"%Y-%m-%d %H:%M:%S"
|
||||||
|
)
|
||||||
|
elif self.type == SQLITE:
|
||||||
def timestamp_from_seconds(
|
return str(seconds)
|
||||||
db: Database, seconds: Union[int, float, None]
|
|
||||||
) -> Union[str, None]:
|
|
||||||
if seconds is None:
|
|
||||||
return None
|
return None
|
||||||
seconds = int(seconds)
|
|
||||||
if db.type in {POSTGRES, COCKROACH}:
|
|
||||||
return datetime.datetime.fromtimestamp(seconds).strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
elif db.type == SQLITE:
|
|
||||||
return str(seconds)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
def timestamp_now_str(self) -> str:
|
||||||
|
timestamp = self.timestamp_from_seconds(time.time())
|
||||||
|
if timestamp is None:
|
||||||
|
raise Exception("Timestamp is None")
|
||||||
|
return timestamp
|
||||||
|
|
||||||
def timestamp_now(db: Database) -> str:
|
def to_timestamp(self, timestamp_str: str) -> Union[str, datetime.datetime]:
|
||||||
timestamp = timestamp_from_seconds(db, time.time())
|
if not timestamp_str:
|
||||||
if timestamp is None:
|
timestamp_str = self.timestamp_now_str()
|
||||||
raise Exception("Timestamp is None")
|
if self.type in {POSTGRES, COCKROACH}:
|
||||||
return timestamp
|
return datetime.datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S")
|
||||||
|
elif self.type == SQLITE:
|
||||||
|
return timestamp_str
|
||||||
@asynccontextmanager
|
return "<nothing>"
|
||||||
async def get_db_connection(db: Database, conn: Optional[Connection] = None):
|
|
||||||
"""Either yield the existing database connection or create a new one.
|
|
||||||
|
|
||||||
Note: This should be implemented as Database.get_db_connection(self, conn) but
|
|
||||||
since we want to use it in LNbits, we can't change the Database class their.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
db (Database): Database object.
|
|
||||||
conn (Optional[Connection], optional): Connection object. Defaults to None.
|
|
||||||
|
|
||||||
Yields:
|
|
||||||
Connection: Connection object.
|
|
||||||
"""
|
|
||||||
if conn is not None:
|
|
||||||
# Yield the existing connection
|
|
||||||
yield conn
|
|
||||||
else:
|
|
||||||
# Create and yield a new connection
|
|
||||||
async with db.connect() as new_conn:
|
|
||||||
yield new_conn
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import time
|
|||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from ..core.db import COCKROACH, POSTGRES, SQLITE, Database, table_with_schema
|
from ..core.db import COCKROACH, POSTGRES, SQLITE, Database
|
||||||
from ..core.settings import settings
|
from ..core.settings import settings
|
||||||
|
|
||||||
|
|
||||||
@@ -47,10 +47,10 @@ async def migrate_databases(db: Database, migrations_module):
|
|||||||
async def set_migration_version(conn, db_name, version):
|
async def set_migration_version(conn, db_name, version):
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
INSERT INTO {table_with_schema(db, 'dbversions')} (db, version) VALUES (?, ?)
|
INSERT INTO {db.table_with_schema('dbversions')} (db, version) VALUES (:db, :version)
|
||||||
ON CONFLICT (db) DO UPDATE SET version = ?
|
ON CONFLICT (db) DO UPDATE SET version = :version
|
||||||
""",
|
""",
|
||||||
(db_name, version, version),
|
{"db": db_name, "version": version},
|
||||||
)
|
)
|
||||||
|
|
||||||
async def run_migration(db, migrations_module):
|
async def run_migration(db, migrations_module):
|
||||||
@@ -89,20 +89,21 @@ async def migrate_databases(db: Database, migrations_module):
|
|||||||
if conn.type == SQLITE:
|
if conn.type == SQLITE:
|
||||||
exists = await conn.fetchone(
|
exists = await conn.fetchone(
|
||||||
"SELECT * FROM sqlite_master WHERE type='table' AND"
|
"SELECT * FROM sqlite_master WHERE type='table' AND"
|
||||||
f" name='{table_with_schema(db, 'dbversions')}'"
|
f" name='{db.table_with_schema('dbversions')}'"
|
||||||
)
|
)
|
||||||
elif conn.type in {POSTGRES, COCKROACH}:
|
elif conn.type in {POSTGRES, COCKROACH}:
|
||||||
exists = await conn.fetchone(
|
exists = await conn.fetchone(
|
||||||
"SELECT * FROM information_schema.tables WHERE table_name ="
|
"SELECT * FROM information_schema.tables WHERE table_name ="
|
||||||
f" '{table_with_schema(db, 'dbversions')}'"
|
f" '{db.table_with_schema('dbversions')}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
if not exists:
|
if not exists:
|
||||||
await migrations_module.m000_create_migrations_table(conn)
|
await migrations_module.m000_create_migrations_table(conn)
|
||||||
|
|
||||||
rows = await (
|
result = await conn.execute(
|
||||||
await conn.execute(f"SELECT * FROM {table_with_schema(db, 'dbversions')}")
|
f"SELECT * FROM {db.table_with_schema('dbversions')}"
|
||||||
).fetchall()
|
)
|
||||||
|
rows = result.all()
|
||||||
current_versions = {row["db"]: row["version"] for row in rows}
|
current_versions = {row["db"]: row["version"] for row in rows}
|
||||||
matcher = re.compile(r"^m(\d\d\d)_")
|
matcher = re.compile(r"^m(\d\d\d)_")
|
||||||
await run_migration(db, migrations_module)
|
await run_migration(db, migrations_module)
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ class EnvSettings(CashuSettings):
|
|||||||
debug_profiling: bool = Field(default=False)
|
debug_profiling: bool = Field(default=False)
|
||||||
debug_mint_only_deprecated: bool = Field(default=False)
|
debug_mint_only_deprecated: bool = Field(default=False)
|
||||||
db_backup_path: Optional[str] = Field(default=None)
|
db_backup_path: Optional[str] = Field(default=None)
|
||||||
|
db_connection_pool: bool = Field(default=True)
|
||||||
|
|
||||||
|
|
||||||
class MintSettings(CashuSettings):
|
class MintSettings(CashuSettings):
|
||||||
@@ -131,8 +132,8 @@ class MintLimits(MintSettings):
|
|||||||
|
|
||||||
class FakeWalletSettings(MintSettings):
|
class FakeWalletSettings(MintSettings):
|
||||||
fakewallet_brr: bool = Field(default=True)
|
fakewallet_brr: bool = Field(default=True)
|
||||||
fakewallet_delay_outgoing_payment: Optional[int] = Field(default=3)
|
fakewallet_delay_outgoing_payment: Optional[float] = Field(default=3.0)
|
||||||
fakewallet_delay_incoming_payment: Optional[int] = Field(default=3)
|
fakewallet_delay_incoming_payment: Optional[float] = Field(default=3.0)
|
||||||
fakewallet_stochastic_invoice: bool = Field(default=False)
|
fakewallet_stochastic_invoice: bool = Field(default=False)
|
||||||
fakewallet_payment_state: Optional[bool] = Field(default=None)
|
fakewallet_payment_state: Optional[bool] = Field(default=None)
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import hashlib
|
import hashlib
|
||||||
import math
|
import math
|
||||||
import random
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from os import urandom
|
from os import urandom
|
||||||
from typing import AsyncGenerator, Dict, List, Optional
|
from typing import AsyncGenerator, Dict, List, Optional
|
||||||
@@ -57,8 +56,12 @@ class FakeWallet(LightningBackend):
|
|||||||
async def status(self) -> StatusResponse:
|
async def status(self) -> StatusResponse:
|
||||||
return StatusResponse(error_message=None, balance=1337)
|
return StatusResponse(error_message=None, balance=1337)
|
||||||
|
|
||||||
async def mark_invoice_paid(self, invoice: Bolt11) -> None:
|
async def mark_invoice_paid(self, invoice: Bolt11, delay=True) -> None:
|
||||||
if settings.fakewallet_delay_incoming_payment:
|
if invoice in self.paid_invoices_incoming:
|
||||||
|
return
|
||||||
|
if not settings.fakewallet_brr:
|
||||||
|
return
|
||||||
|
if settings.fakewallet_delay_incoming_payment and delay:
|
||||||
await asyncio.sleep(settings.fakewallet_delay_incoming_payment)
|
await asyncio.sleep(settings.fakewallet_delay_incoming_payment)
|
||||||
self.paid_invoices_incoming.append(invoice)
|
self.paid_invoices_incoming.append(invoice)
|
||||||
await self.paid_invoices_queue.put(invoice)
|
await self.paid_invoices_queue.put(invoice)
|
||||||
@@ -165,19 +168,13 @@ class FakeWallet(LightningBackend):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
async def get_invoice_status(self, checking_id: str) -> PaymentStatus:
|
||||||
paid = False
|
await self.mark_invoice_paid(self.create_dummy_bolt11(checking_id), delay=False)
|
||||||
if settings.fakewallet_brr or (
|
paid_chceking_ids = [i.payment_hash for i in self.paid_invoices_incoming]
|
||||||
settings.fakewallet_stochastic_invoice and random.random() > 0.7
|
if checking_id in paid_chceking_ids:
|
||||||
):
|
|
||||||
paid = True
|
paid = True
|
||||||
|
else:
|
||||||
|
paid = False
|
||||||
|
|
||||||
# invoice is paid but not in paid_invoices_incoming yet
|
|
||||||
# so we add it to the paid_invoices_queue
|
|
||||||
# if paid and invoice not in self.paid_invoices_incoming:
|
|
||||||
if paid:
|
|
||||||
await self.paid_invoices_queue.put(
|
|
||||||
self.create_dummy_bolt11(payment_hash=checking_id)
|
|
||||||
)
|
|
||||||
return PaymentStatus(paid=paid)
|
return PaymentStatus(paid=paid)
|
||||||
|
|
||||||
async def get_payment_status(self, _: str) -> PaymentStatus:
|
async def get_payment_status(self, _: str) -> PaymentStatus:
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from ..core.logging import configure_logger
|
|||||||
from ..core.settings import settings
|
from ..core.settings import settings
|
||||||
from .router import router
|
from .router import router
|
||||||
from .router_deprecated import router_deprecated
|
from .router_deprecated import router_deprecated
|
||||||
|
from .startup import shutdown_mint as shutdown_mint_init
|
||||||
from .startup import start_mint_init
|
from .startup import start_mint_init
|
||||||
|
|
||||||
if settings.debug_profiling:
|
if settings.debug_profiling:
|
||||||
@@ -103,3 +104,8 @@ else:
|
|||||||
@app.on_event("startup")
|
@app.on_event("startup")
|
||||||
async def startup_mint():
|
async def startup_mint():
|
||||||
await start_mint_init()
|
await start_mint_init()
|
||||||
|
|
||||||
|
|
||||||
|
@app.on_event("shutdown")
|
||||||
|
async def shutdown_mint():
|
||||||
|
await shutdown_mint_init()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Any, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from ..core.base import (
|
from ..core.base import (
|
||||||
BlindedSignature,
|
BlindedSignature,
|
||||||
@@ -12,9 +12,6 @@ from ..core.base import (
|
|||||||
from ..core.db import (
|
from ..core.db import (
|
||||||
Connection,
|
Connection,
|
||||||
Database,
|
Database,
|
||||||
table_with_schema,
|
|
||||||
timestamp_from_seconds,
|
|
||||||
timestamp_now,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -266,19 +263,19 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
f"""
|
f"""
|
||||||
INSERT INTO {table_with_schema(db, 'promises')}
|
INSERT INTO {db.table_with_schema('promises')}
|
||||||
(amount, b_, c_, dleq_e, dleq_s, id, created)
|
(amount, b_, c_, dleq_e, dleq_s, id, created)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
VALUES (:amount, :b_, :c_, :dleq_e, :dleq_s, :id, :created)
|
||||||
""",
|
""",
|
||||||
(
|
{
|
||||||
amount,
|
"amount": amount,
|
||||||
b_,
|
"b_": b_,
|
||||||
c_,
|
"c_": c_,
|
||||||
e,
|
"dleq_e": e,
|
||||||
s,
|
"dleq_s": s,
|
||||||
id,
|
"id": id,
|
||||||
timestamp_now(db),
|
"created": db.to_timestamp(db.timestamp_now_str()),
|
||||||
),
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_promise(
|
async def get_promise(
|
||||||
@@ -290,10 +287,10 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
) -> Optional[BlindedSignature]:
|
) -> Optional[BlindedSignature]:
|
||||||
row = await (conn or db).fetchone(
|
row = await (conn or db).fetchone(
|
||||||
f"""
|
f"""
|
||||||
SELECT * from {table_with_schema(db, 'promises')}
|
SELECT * from {db.table_with_schema('promises')}
|
||||||
WHERE b_ = ?
|
WHERE b_ = :b_
|
||||||
""",
|
""",
|
||||||
(str(b_),),
|
{"b_": str(b_)},
|
||||||
)
|
)
|
||||||
return BlindedSignature.from_row(row) if row else None
|
return BlindedSignature.from_row(row) if row else None
|
||||||
|
|
||||||
@@ -305,7 +302,7 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
) -> List[Proof]:
|
) -> List[Proof]:
|
||||||
rows = await (conn or db).fetchall(
|
rows = await (conn or db).fetchall(
|
||||||
f"""
|
f"""
|
||||||
SELECT * from {table_with_schema(db, 'proofs_used')}
|
SELECT * from {db.table_with_schema('proofs_used')}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
return [Proof(**r) for r in rows] if rows else []
|
return [Proof(**r) for r in rows] if rows else []
|
||||||
@@ -318,23 +315,22 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
quote_id: Optional[str] = None,
|
quote_id: Optional[str] = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
# we add the proof and secret to the used list
|
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
f"""
|
f"""
|
||||||
INSERT INTO {table_with_schema(db, 'proofs_used')}
|
INSERT INTO {db.table_with_schema('proofs_used')}
|
||||||
(amount, c, secret, y, id, witness, created, melt_quote)
|
(amount, c, secret, y, id, witness, created, melt_quote)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (:amount, :c, :secret, :y, :id, :witness, :created, :melt_quote)
|
||||||
""",
|
""",
|
||||||
(
|
{
|
||||||
proof.amount,
|
"amount": proof.amount,
|
||||||
proof.C,
|
"c": proof.C,
|
||||||
proof.secret,
|
"secret": proof.secret,
|
||||||
proof.Y,
|
"y": proof.Y,
|
||||||
proof.id,
|
"id": proof.id,
|
||||||
proof.witness,
|
"witness": proof.witness,
|
||||||
timestamp_now(db),
|
"created": db.to_timestamp(db.timestamp_now_str()),
|
||||||
quote_id,
|
"melt_quote": quote_id,
|
||||||
),
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_all_melt_quotes_from_pending_proofs(
|
async def get_all_melt_quotes_from_pending_proofs(
|
||||||
@@ -345,7 +341,7 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
) -> List[MeltQuote]:
|
) -> List[MeltQuote]:
|
||||||
rows = await (conn or db).fetchall(
|
rows = await (conn or db).fetchall(
|
||||||
f"""
|
f"""
|
||||||
SELECT * from {table_with_schema(db, 'melt_quotes')} WHERE quote in (SELECT DISTINCT melt_quote FROM {table_with_schema(db, 'proofs_pending')})
|
SELECT * from {db.table_with_schema('melt_quotes')} WHERE quote in (SELECT DISTINCT melt_quote FROM {db.table_with_schema('proofs_pending')})
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
return [MeltQuote.from_row(r) for r in rows]
|
return [MeltQuote.from_row(r) for r in rows]
|
||||||
@@ -359,10 +355,10 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
) -> List[Proof]:
|
) -> List[Proof]:
|
||||||
rows = await (conn or db).fetchall(
|
rows = await (conn or db).fetchall(
|
||||||
f"""
|
f"""
|
||||||
SELECT * from {table_with_schema(db, 'proofs_pending')}
|
SELECT * from {db.table_with_schema('proofs_pending')}
|
||||||
WHERE melt_quote = ?
|
WHERE melt_quote = :quote_id
|
||||||
""",
|
""",
|
||||||
(quote_id,),
|
{"quote_id": quote_id},
|
||||||
)
|
)
|
||||||
return [Proof(**r) for r in rows]
|
return [Proof(**r) for r in rows]
|
||||||
|
|
||||||
@@ -373,13 +369,12 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
db: Database,
|
db: Database,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> List[Proof]:
|
) -> List[Proof]:
|
||||||
rows = await (conn or db).fetchall(
|
query = f"""
|
||||||
f"""
|
SELECT * from {db.table_with_schema('proofs_pending')}
|
||||||
SELECT * from {table_with_schema(db, 'proofs_pending')}
|
WHERE y IN ({','.join([':y_' + str(i) for i in range(len(Ys))])})
|
||||||
WHERE y IN ({','.join(['?']*len(Ys))})
|
"""
|
||||||
""",
|
values = {f"y_{i}": Ys[i] for i in range(len(Ys))}
|
||||||
tuple(Ys),
|
rows = await (conn or db).fetchall(query, values)
|
||||||
)
|
|
||||||
return [Proof(**r) for r in rows]
|
return [Proof(**r) for r in rows]
|
||||||
|
|
||||||
async def set_proof_pending(
|
async def set_proof_pending(
|
||||||
@@ -390,23 +385,22 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
quote_id: Optional[str] = None,
|
quote_id: Optional[str] = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
# we add the proof and secret to the used list
|
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
f"""
|
f"""
|
||||||
INSERT INTO {table_with_schema(db, 'proofs_pending')}
|
INSERT INTO {db.table_with_schema('proofs_pending')}
|
||||||
(amount, c, secret, y, id, witness, created, melt_quote)
|
(amount, c, secret, y, id, witness, created, melt_quote)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (:amount, :c, :secret, :y, :id, :witness, :created, :melt_quote)
|
||||||
""",
|
""",
|
||||||
(
|
{
|
||||||
proof.amount,
|
"amount": proof.amount,
|
||||||
proof.C,
|
"c": proof.C,
|
||||||
proof.secret,
|
"secret": proof.secret,
|
||||||
proof.Y,
|
"y": proof.Y,
|
||||||
proof.id,
|
"id": proof.id,
|
||||||
proof.witness,
|
"witness": proof.witness,
|
||||||
timestamp_now(db),
|
"created": db.to_timestamp(db.timestamp_now_str()),
|
||||||
quote_id,
|
"melt_quote": quote_id,
|
||||||
),
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
async def unset_proof_pending(
|
async def unset_proof_pending(
|
||||||
@@ -418,10 +412,10 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
f"""
|
f"""
|
||||||
DELETE FROM {table_with_schema(db, 'proofs_pending')}
|
DELETE FROM {db.table_with_schema('proofs_pending')}
|
||||||
WHERE secret = ?
|
WHERE secret = :secret
|
||||||
""",
|
""",
|
||||||
(proof.secret,),
|
{"secret": proof.secret},
|
||||||
)
|
)
|
||||||
|
|
||||||
async def store_mint_quote(
|
async def store_mint_quote(
|
||||||
@@ -433,23 +427,27 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
f"""
|
f"""
|
||||||
INSERT INTO {table_with_schema(db, 'mint_quotes')}
|
INSERT INTO {db.table_with_schema('mint_quotes')}
|
||||||
(quote, method, request, checking_id, unit, amount, issued, paid, state, created_time, paid_time)
|
(quote, method, request, checking_id, unit, amount, issued, paid, state, created_time, paid_time)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (:quote, :method, :request, :checking_id, :unit, :amount, :issued, :paid, :state, :created_time, :paid_time)
|
||||||
""",
|
""",
|
||||||
(
|
{
|
||||||
quote.quote,
|
"quote": quote.quote,
|
||||||
quote.method,
|
"method": quote.method,
|
||||||
quote.request,
|
"request": quote.request,
|
||||||
quote.checking_id,
|
"checking_id": quote.checking_id,
|
||||||
quote.unit,
|
"unit": quote.unit,
|
||||||
quote.amount,
|
"amount": quote.amount,
|
||||||
quote.issued,
|
"issued": quote.issued,
|
||||||
quote.paid,
|
"paid": quote.paid,
|
||||||
quote.state.name,
|
"state": quote.state.name,
|
||||||
timestamp_from_seconds(db, quote.created_time),
|
"created_time": db.to_timestamp(
|
||||||
timestamp_from_seconds(db, quote.paid_time),
|
db.timestamp_from_seconds(quote.created_time) or ""
|
||||||
),
|
),
|
||||||
|
"paid_time": db.to_timestamp(
|
||||||
|
db.timestamp_from_seconds(quote.paid_time) or ""
|
||||||
|
),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_mint_quote(
|
async def get_mint_quote(
|
||||||
@@ -462,26 +460,25 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> Optional[MintQuote]:
|
) -> Optional[MintQuote]:
|
||||||
clauses = []
|
clauses = []
|
||||||
values: List[Any] = []
|
values: Dict[str, Any] = {}
|
||||||
if quote_id:
|
if quote_id:
|
||||||
clauses.append("quote = ?")
|
clauses.append("quote = :quote_id")
|
||||||
values.append(quote_id)
|
values["quote_id"] = quote_id
|
||||||
if checking_id:
|
if checking_id:
|
||||||
clauses.append("checking_id = ?")
|
clauses.append("checking_id = :checking_id")
|
||||||
values.append(checking_id)
|
values["checking_id"] = checking_id
|
||||||
if request:
|
if request:
|
||||||
clauses.append("request = ?")
|
clauses.append("request = :request")
|
||||||
values.append(request)
|
values["request"] = request
|
||||||
if not any(clauses):
|
if not any(clauses):
|
||||||
raise ValueError("No search criteria")
|
raise ValueError("No search criteria")
|
||||||
|
|
||||||
where = f"WHERE {' AND '.join(clauses)}"
|
where = f"WHERE {' AND '.join(clauses)}"
|
||||||
row = await (conn or db).fetchone(
|
row = await (conn or db).fetchone(
|
||||||
f"""
|
f"""
|
||||||
SELECT * from {table_with_schema(db, 'mint_quotes')}
|
SELECT * from {db.table_with_schema('mint_quotes')}
|
||||||
{where}
|
{where}
|
||||||
""",
|
""",
|
||||||
tuple(values),
|
values,
|
||||||
)
|
)
|
||||||
if row is None:
|
if row is None:
|
||||||
return None
|
return None
|
||||||
@@ -496,10 +493,10 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
) -> Optional[MintQuote]:
|
) -> Optional[MintQuote]:
|
||||||
row = await (conn or db).fetchone(
|
row = await (conn or db).fetchone(
|
||||||
f"""
|
f"""
|
||||||
SELECT * from {table_with_schema(db, 'mint_quotes')}
|
SELECT * from {db.table_with_schema('mint_quotes')}
|
||||||
WHERE request = ?
|
WHERE request = :request
|
||||||
""",
|
""",
|
||||||
(request,),
|
{"request": request},
|
||||||
)
|
)
|
||||||
return MintQuote.from_row(row) if row else None
|
return MintQuote.from_row(row) if row else None
|
||||||
|
|
||||||
@@ -511,15 +508,16 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
f"UPDATE {table_with_schema(db, 'mint_quotes')} SET issued = ?, paid = ?,"
|
f"UPDATE {db.table_with_schema('mint_quotes')} SET issued = :issued, paid = :paid, state = :state, paid_time = :paid_time WHERE quote = :quote",
|
||||||
" state = ?, paid_time = ? WHERE quote = ?",
|
{
|
||||||
(
|
"issued": quote.issued,
|
||||||
quote.issued,
|
"paid": quote.paid,
|
||||||
quote.paid,
|
"state": quote.state.name,
|
||||||
quote.state.name,
|
"paid_time": db.to_timestamp(
|
||||||
timestamp_from_seconds(db, quote.paid_time),
|
db.timestamp_from_seconds(quote.paid_time) or ""
|
||||||
quote.quote,
|
),
|
||||||
),
|
"quote": quote.quote,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
# async def update_mint_quote_paid(
|
# async def update_mint_quote_paid(
|
||||||
@@ -531,7 +529,7 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
# conn: Optional[Connection] = None,
|
# conn: Optional[Connection] = None,
|
||||||
# ) -> None:
|
# ) -> None:
|
||||||
# await (conn or db).execute(
|
# await (conn or db).execute(
|
||||||
# f"UPDATE {table_with_schema(db, 'mint_quotes')} SET paid = ? WHERE"
|
# f"UPDATE {db.table_with_schema('mint_quotes')} SET paid = ? WHERE"
|
||||||
# " quote = ?",
|
# " quote = ?",
|
||||||
# (
|
# (
|
||||||
# paid,
|
# paid,
|
||||||
@@ -548,27 +546,33 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
f"""
|
f"""
|
||||||
INSERT INTO {table_with_schema(db, 'melt_quotes')}
|
INSERT INTO {db.table_with_schema('melt_quotes')}
|
||||||
(quote, method, request, checking_id, unit, amount, fee_reserve, paid, state, created_time, paid_time, fee_paid, proof, change, expiry)
|
(quote, method, request, checking_id, unit, amount, fee_reserve, paid, state, created_time, paid_time, fee_paid, proof, change, expiry)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (:quote, :method, :request, :checking_id, :unit, :amount, :fee_reserve, :paid, :state, :created_time, :paid_time, :fee_paid, :proof, :change, :expiry)
|
||||||
""",
|
""",
|
||||||
(
|
{
|
||||||
quote.quote,
|
"quote": quote.quote,
|
||||||
quote.method,
|
"method": quote.method,
|
||||||
quote.request,
|
"request": quote.request,
|
||||||
quote.checking_id,
|
"checking_id": quote.checking_id,
|
||||||
quote.unit,
|
"unit": quote.unit,
|
||||||
quote.amount,
|
"amount": quote.amount,
|
||||||
quote.fee_reserve or 0,
|
"fee_reserve": quote.fee_reserve or 0,
|
||||||
quote.paid,
|
"paid": quote.paid,
|
||||||
quote.state.name,
|
"state": quote.state.name,
|
||||||
timestamp_from_seconds(db, quote.created_time),
|
"created_time": db.to_timestamp(
|
||||||
timestamp_from_seconds(db, quote.paid_time),
|
db.timestamp_from_seconds(quote.created_time) or ""
|
||||||
quote.fee_paid,
|
),
|
||||||
quote.payment_preimage,
|
"paid_time": db.to_timestamp(
|
||||||
json.dumps(quote.change) if quote.change else None,
|
db.timestamp_from_seconds(quote.paid_time) or ""
|
||||||
timestamp_from_seconds(db, quote.expiry),
|
),
|
||||||
),
|
"fee_paid": quote.fee_paid,
|
||||||
|
"proof": quote.payment_preimage,
|
||||||
|
"change": json.dumps(quote.change) if quote.change else None,
|
||||||
|
"expiry": db.to_timestamp(
|
||||||
|
db.timestamp_from_seconds(quote.expiry) or ""
|
||||||
|
),
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_melt_quote(
|
async def get_melt_quote(
|
||||||
@@ -581,26 +585,25 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> Optional[MeltQuote]:
|
) -> Optional[MeltQuote]:
|
||||||
clauses = []
|
clauses = []
|
||||||
values: List[Any] = []
|
values: Dict[str, Any] = {}
|
||||||
if quote_id:
|
if quote_id:
|
||||||
clauses.append("quote = ?")
|
clauses.append("quote = :quote_id")
|
||||||
values.append(quote_id)
|
values["quote_id"] = quote_id
|
||||||
if checking_id:
|
if checking_id:
|
||||||
clauses.append("checking_id = ?")
|
clauses.append("checking_id = :checking_id")
|
||||||
values.append(checking_id)
|
values["checking_id"] = checking_id
|
||||||
if request:
|
if request:
|
||||||
clauses.append("request = ?")
|
clauses.append("request = :request")
|
||||||
values.append(request)
|
values["request"] = request
|
||||||
if not any(clauses):
|
if not any(clauses):
|
||||||
raise ValueError("No search criteria")
|
raise ValueError("No search criteria")
|
||||||
where = f"WHERE {' AND '.join(clauses)}"
|
where = f"WHERE {' AND '.join(clauses)}"
|
||||||
|
|
||||||
row = await (conn or db).fetchone(
|
row = await (conn or db).fetchone(
|
||||||
f"""
|
f"""
|
||||||
SELECT * from {table_with_schema(db, 'melt_quotes')}
|
SELECT * from {db.table_with_schema('melt_quotes')}
|
||||||
{where}
|
{where}
|
||||||
""",
|
""",
|
||||||
tuple(values),
|
values,
|
||||||
)
|
)
|
||||||
if row is None:
|
if row is None:
|
||||||
return None
|
return None
|
||||||
@@ -614,17 +617,22 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
f"UPDATE {table_with_schema(db, 'melt_quotes')} SET paid = ?, state = ?,"
|
f"""
|
||||||
" fee_paid = ?, paid_time = ?, proof = ?, change = ? WHERE quote = ?",
|
UPDATE {db.table_with_schema('melt_quotes')} SET paid = :paid, state = :state, fee_paid = :fee_paid, paid_time = :paid_time, proof = :proof, change = :change WHERE quote = :quote
|
||||||
(
|
""",
|
||||||
quote.paid,
|
{
|
||||||
quote.state.name,
|
"paid": quote.paid,
|
||||||
quote.fee_paid,
|
"state": quote.state.name,
|
||||||
timestamp_from_seconds(db, quote.paid_time),
|
"fee_paid": quote.fee_paid,
|
||||||
quote.payment_preimage,
|
"paid_time": db.to_timestamp(
|
||||||
json.dumps([s.dict() for s in quote.change]) if quote.change else None,
|
db.timestamp_from_seconds(quote.paid_time) or ""
|
||||||
quote.quote,
|
),
|
||||||
),
|
"proof": quote.payment_preimage,
|
||||||
|
"change": json.dumps([s.dict() for s in quote.change])
|
||||||
|
if quote.change
|
||||||
|
else None,
|
||||||
|
"quote": quote.quote,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
async def store_keyset(
|
async def store_keyset(
|
||||||
@@ -634,26 +642,30 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
keyset: MintKeyset,
|
keyset: MintKeyset,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).execute( # type: ignore
|
await (conn or db).execute(
|
||||||
f"""
|
f"""
|
||||||
INSERT INTO {table_with_schema(db, 'keysets')}
|
INSERT INTO {db.table_with_schema('keysets')}
|
||||||
(id, seed, encrypted_seed, seed_encryption_method, derivation_path, valid_from, valid_to, first_seen, active, version, unit, input_fee_ppk)
|
(id, seed, encrypted_seed, seed_encryption_method, derivation_path, valid_from, valid_to, first_seen, active, version, unit, input_fee_ppk)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (:id, :seed, :encrypted_seed, :seed_encryption_method, :derivation_path, :valid_from, :valid_to, :first_seen, :active, :version, :unit, :input_fee_ppk)
|
||||||
""",
|
""",
|
||||||
(
|
{
|
||||||
keyset.id,
|
"id": keyset.id,
|
||||||
keyset.seed,
|
"seed": keyset.seed,
|
||||||
keyset.encrypted_seed,
|
"encrypted_seed": keyset.encrypted_seed,
|
||||||
keyset.seed_encryption_method,
|
"seed_encryption_method": keyset.seed_encryption_method,
|
||||||
keyset.derivation_path,
|
"derivation_path": keyset.derivation_path,
|
||||||
keyset.valid_from or timestamp_now(db),
|
"valid_from": db.to_timestamp(
|
||||||
keyset.valid_to or timestamp_now(db),
|
keyset.valid_from or db.timestamp_now_str()
|
||||||
keyset.first_seen or timestamp_now(db),
|
),
|
||||||
True,
|
"valid_to": db.to_timestamp(keyset.valid_to or db.timestamp_now_str()),
|
||||||
keyset.version,
|
"first_seen": db.to_timestamp(
|
||||||
keyset.unit.name,
|
keyset.first_seen or db.timestamp_now_str()
|
||||||
keyset.input_fee_ppk,
|
),
|
||||||
),
|
"active": True,
|
||||||
|
"version": keyset.version,
|
||||||
|
"unit": keyset.unit.name,
|
||||||
|
"input_fee_ppk": keyset.input_fee_ppk,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
async def get_balance(
|
async def get_balance(
|
||||||
@@ -663,7 +675,7 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
) -> int:
|
) -> int:
|
||||||
row = await (conn or db).fetchone(
|
row = await (conn or db).fetchone(
|
||||||
f"""
|
f"""
|
||||||
SELECT * from {table_with_schema(db, 'balance')}
|
SELECT * from {db.table_with_schema('balance')}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
assert row, "Balance not found"
|
assert row, "Balance not found"
|
||||||
@@ -681,32 +693,32 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> List[MintKeyset]:
|
) -> List[MintKeyset]:
|
||||||
clauses = []
|
clauses = []
|
||||||
values: List[Any] = []
|
values: Dict = {}
|
||||||
if active is not None:
|
if active is not None:
|
||||||
clauses.append("active = ?")
|
clauses.append("active = :active")
|
||||||
values.append(active)
|
values["active"] = active
|
||||||
if id is not None:
|
if id is not None:
|
||||||
clauses.append("id = ?")
|
clauses.append("id = :id")
|
||||||
values.append(id)
|
values["id"] = id
|
||||||
if derivation_path is not None:
|
if derivation_path is not None:
|
||||||
clauses.append("derivation_path = ?")
|
clauses.append("derivation_path = :derivation_path")
|
||||||
values.append(derivation_path)
|
values["derivation_path"] = derivation_path
|
||||||
if seed is not None:
|
if seed is not None:
|
||||||
clauses.append("seed = ?")
|
clauses.append("seed = :seed")
|
||||||
values.append(seed)
|
values["seed"] = seed
|
||||||
if unit is not None:
|
if unit is not None:
|
||||||
clauses.append("unit = ?")
|
clauses.append("unit = :unit")
|
||||||
values.append(unit)
|
values["unit"] = unit
|
||||||
where = ""
|
where = ""
|
||||||
if clauses:
|
if clauses:
|
||||||
where = f"WHERE {' AND '.join(clauses)}"
|
where = f"WHERE {' AND '.join(clauses)}"
|
||||||
|
|
||||||
rows = await (conn or db).fetchall( # type: ignore
|
rows = await (conn or db).fetchall( # type: ignore
|
||||||
f"""
|
f"""
|
||||||
SELECT * from {table_with_schema(db, 'keysets')}
|
SELECT * from {db.table_with_schema('keysets')}
|
||||||
{where}
|
{where}
|
||||||
""",
|
""",
|
||||||
tuple(values),
|
values,
|
||||||
)
|
)
|
||||||
return [MintKeyset(**row) for row in rows]
|
return [MintKeyset(**row) for row in rows]
|
||||||
|
|
||||||
@@ -719,9 +731,9 @@ class LedgerCrudSqlite(LedgerCrud):
|
|||||||
) -> Optional[Proof]:
|
) -> Optional[Proof]:
|
||||||
row = await (conn or db).fetchone(
|
row = await (conn or db).fetchone(
|
||||||
f"""
|
f"""
|
||||||
SELECT * from {table_with_schema(db, 'proofs_used')}
|
SELECT * from {db.table_with_schema('proofs_used')}
|
||||||
WHERE y = ?
|
WHERE y = :y
|
||||||
""",
|
""",
|
||||||
(Y,),
|
{"y": Y},
|
||||||
)
|
)
|
||||||
return Proof(**row) if row else None
|
return Proof(**row) if row else None
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from typing import Dict, List
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from ...core.base import Proof, ProofSpentState, ProofState
|
from ...core.base import Proof, ProofSpentState, ProofState
|
||||||
from ...core.db import Database
|
from ...core.db import Connection, Database
|
||||||
from ..crud import LedgerCrud
|
from ..crud import LedgerCrud
|
||||||
|
|
||||||
|
|
||||||
@@ -13,28 +13,37 @@ class DbReadHelper:
|
|||||||
self.db = db
|
self.db = db
|
||||||
self.crud = crud
|
self.crud = crud
|
||||||
|
|
||||||
async def _get_proofs_pending(self, Ys: List[str]) -> Dict[str, Proof]:
|
async def _get_proofs_pending(
|
||||||
|
self, Ys: List[str], conn: Optional[Connection] = None
|
||||||
|
) -> Dict[str, Proof]:
|
||||||
"""Returns a dictionary of only those proofs that are pending.
|
"""Returns a dictionary of only those proofs that are pending.
|
||||||
The key is the Y=h2c(secret) and the value is the proof.
|
The key is the Y=h2c(secret) and the value is the proof.
|
||||||
"""
|
"""
|
||||||
proofs_pending = await self.crud.get_proofs_pending(Ys=Ys, db=self.db)
|
async with self.db.get_connection(conn) as conn:
|
||||||
|
proofs_pending = await self.crud.get_proofs_pending(
|
||||||
|
Ys=Ys, db=self.db, conn=conn
|
||||||
|
)
|
||||||
proofs_pending_dict = {p.Y: p for p in proofs_pending}
|
proofs_pending_dict = {p.Y: p for p in proofs_pending}
|
||||||
return proofs_pending_dict
|
return proofs_pending_dict
|
||||||
|
|
||||||
async def _get_proofs_spent(self, Ys: List[str]) -> Dict[str, Proof]:
|
async def _get_proofs_spent(
|
||||||
|
self, Ys: List[str], conn: Optional[Connection] = None
|
||||||
|
) -> Dict[str, Proof]:
|
||||||
"""Returns a dictionary of all proofs that are spent.
|
"""Returns a dictionary of all proofs that are spent.
|
||||||
The key is the Y=h2c(secret) and the value is the proof.
|
The key is the Y=h2c(secret) and the value is the proof.
|
||||||
"""
|
"""
|
||||||
proofs_spent_dict: Dict[str, Proof] = {}
|
proofs_spent_dict: Dict[str, Proof] = {}
|
||||||
# check used secrets in database
|
# check used secrets in database
|
||||||
async with self.db.connect() as conn:
|
async with self.db.get_connection(conn) as conn:
|
||||||
for Y in Ys:
|
for Y in Ys:
|
||||||
spent_proof = await self.crud.get_proof_used(db=self.db, Y=Y, conn=conn)
|
spent_proof = await self.crud.get_proof_used(db=self.db, Y=Y, conn=conn)
|
||||||
if spent_proof:
|
if spent_proof:
|
||||||
proofs_spent_dict[Y] = spent_proof
|
proofs_spent_dict[Y] = spent_proof
|
||||||
return proofs_spent_dict
|
return proofs_spent_dict
|
||||||
|
|
||||||
async def get_proofs_states(self, Ys: List[str]) -> List[ProofState]:
|
async def get_proofs_states(
|
||||||
|
self, Ys: List[str], conn: Optional[Connection] = None
|
||||||
|
) -> List[ProofState]:
|
||||||
"""Checks if provided proofs are spend or are pending.
|
"""Checks if provided proofs are spend or are pending.
|
||||||
Used by wallets to check if their proofs have been redeemed by a receiver or they are still in-flight in a transaction.
|
Used by wallets to check if their proofs have been redeemed by a receiver or they are still in-flight in a transaction.
|
||||||
|
|
||||||
@@ -50,19 +59,20 @@ class DbReadHelper:
|
|||||||
List[bool]: List of which proof are pending (True if pending, else False)
|
List[bool]: List of which proof are pending (True if pending, else False)
|
||||||
"""
|
"""
|
||||||
states: List[ProofState] = []
|
states: List[ProofState] = []
|
||||||
proofs_spent = await self._get_proofs_spent(Ys)
|
async with self.db.get_connection(conn) as conn:
|
||||||
proofs_pending = await self._get_proofs_pending(Ys)
|
proofs_spent = await self._get_proofs_spent(Ys, conn)
|
||||||
for Y in Ys:
|
proofs_pending = await self._get_proofs_pending(Ys, conn)
|
||||||
if Y not in proofs_spent and Y not in proofs_pending:
|
for Y in Ys:
|
||||||
states.append(ProofState(Y=Y, state=ProofSpentState.unspent))
|
if Y not in proofs_spent and Y not in proofs_pending:
|
||||||
elif Y not in proofs_spent and Y in proofs_pending:
|
states.append(ProofState(Y=Y, state=ProofSpentState.unspent))
|
||||||
states.append(ProofState(Y=Y, state=ProofSpentState.pending))
|
elif Y not in proofs_spent and Y in proofs_pending:
|
||||||
else:
|
states.append(ProofState(Y=Y, state=ProofSpentState.pending))
|
||||||
states.append(
|
else:
|
||||||
ProofState(
|
states.append(
|
||||||
Y=Y,
|
ProofState(
|
||||||
state=ProofSpentState.spent,
|
Y=Y,
|
||||||
witness=proofs_spent[Y].witness,
|
state=ProofSpentState.spent,
|
||||||
|
witness=proofs_spent[Y].witness,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
|
||||||
return states
|
return states
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
import asyncio
|
import random
|
||||||
from typing import List, Optional
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from ...core.base import Proof, ProofSpentState, ProofState
|
from ...core.base import (
|
||||||
from ...core.db import Connection, Database, get_db_connection
|
MeltQuote,
|
||||||
|
MeltQuoteState,
|
||||||
|
MintQuote,
|
||||||
|
MintQuoteState,
|
||||||
|
Proof,
|
||||||
|
ProofSpentState,
|
||||||
|
ProofState,
|
||||||
|
)
|
||||||
|
from ...core.db import Connection, Database
|
||||||
from ...core.errors import (
|
from ...core.errors import (
|
||||||
TransactionError,
|
TransactionError,
|
||||||
)
|
)
|
||||||
@@ -16,9 +24,6 @@ class DbWriteHelper:
|
|||||||
db: Database
|
db: Database
|
||||||
crud: LedgerCrud
|
crud: LedgerCrud
|
||||||
events: LedgerEventManager
|
events: LedgerEventManager
|
||||||
proofs_pending_lock: asyncio.Lock = (
|
|
||||||
asyncio.Lock()
|
|
||||||
) # holds locks for proofs_pending database
|
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, db: Database, crud: LedgerCrud, events: LedgerEventManager
|
self, db: Database, crud: LedgerCrud, events: LedgerEventManager
|
||||||
@@ -41,20 +46,28 @@ class DbWriteHelper:
|
|||||||
Exception: At least one proof already in pending table.
|
Exception: At least one proof already in pending table.
|
||||||
"""
|
"""
|
||||||
# first we check whether these proofs are pending already
|
# first we check whether these proofs are pending already
|
||||||
async with self.proofs_pending_lock:
|
random_id = random.randint(0, 1000000)
|
||||||
async with get_db_connection(self.db) as conn:
|
try:
|
||||||
|
logger.debug("trying to set proofs pending")
|
||||||
|
logger.trace(f"get_connection: random_id: {random_id}")
|
||||||
|
async with self.db.get_connection(
|
||||||
|
lock_table="proofs_pending",
|
||||||
|
lock_timeout=1,
|
||||||
|
) as conn:
|
||||||
|
logger.trace(f"get_connection: got connection {random_id}")
|
||||||
await self._validate_proofs_pending(proofs, conn)
|
await self._validate_proofs_pending(proofs, conn)
|
||||||
try:
|
for p in proofs:
|
||||||
for p in proofs:
|
logger.trace(f"crud: setting proof {p.Y} as PENDING")
|
||||||
await self.crud.set_proof_pending(
|
await self.crud.set_proof_pending(
|
||||||
proof=p, db=self.db, quote_id=quote_id, conn=conn
|
proof=p, db=self.db, quote_id=quote_id, conn=conn
|
||||||
)
|
)
|
||||||
await self.events.submit(
|
logger.trace(f"crud: set proof {p.Y} as PENDING")
|
||||||
ProofState(Y=p.Y, state=ProofSpentState.pending)
|
except Exception as e:
|
||||||
)
|
logger.error(f"Failed to set proofs pending: {e}")
|
||||||
except Exception as e:
|
raise TransactionError(f"Failed to set proofs pending: {str(e)}")
|
||||||
logger.error(f"Failed to set proofs pending: {e}")
|
logger.trace("_set_proofs_pending released lock")
|
||||||
raise TransactionError("Failed to set proofs pending.")
|
for p in proofs:
|
||||||
|
await self.events.submit(ProofState(Y=p.Y, state=ProofSpentState.pending))
|
||||||
|
|
||||||
async def _unset_proofs_pending(self, proofs: List[Proof], spent=True) -> None:
|
async def _unset_proofs_pending(self, proofs: List[Proof], spent=True) -> None:
|
||||||
"""Deletes proofs from pending table.
|
"""Deletes proofs from pending table.
|
||||||
@@ -66,14 +79,16 @@ class DbWriteHelper:
|
|||||||
It is used to emit the unspent state for the proofs (otherwise the spent state is emitted
|
It is used to emit the unspent state for the proofs (otherwise the spent state is emitted
|
||||||
by the _invalidate_proofs function when the proofs are spent).
|
by the _invalidate_proofs function when the proofs are spent).
|
||||||
"""
|
"""
|
||||||
async with self.proofs_pending_lock:
|
async with self.db.get_connection() as conn:
|
||||||
async with get_db_connection(self.db) as conn:
|
for p in proofs:
|
||||||
for p in proofs:
|
logger.trace(f"crud: un-setting proof {p.Y} as PENDING")
|
||||||
await self.crud.unset_proof_pending(proof=p, db=self.db, conn=conn)
|
await self.crud.unset_proof_pending(proof=p, db=self.db, conn=conn)
|
||||||
if not spent:
|
|
||||||
await self.events.submit(
|
if not spent:
|
||||||
ProofState(Y=p.Y, state=ProofSpentState.unspent)
|
for p in proofs:
|
||||||
)
|
await self.events.submit(
|
||||||
|
ProofState(Y=p.Y, state=ProofSpentState.unspent)
|
||||||
|
)
|
||||||
|
|
||||||
async def _validate_proofs_pending(
|
async def _validate_proofs_pending(
|
||||||
self, proofs: List[Proof], conn: Optional[Connection] = None
|
self, proofs: List[Proof], conn: Optional[Connection] = None
|
||||||
@@ -86,12 +101,120 @@ class DbWriteHelper:
|
|||||||
Raises:
|
Raises:
|
||||||
Exception: At least one of the proofs is in the pending table.
|
Exception: At least one of the proofs is in the pending table.
|
||||||
"""
|
"""
|
||||||
if not (
|
logger.trace("crud: validating proofs pending")
|
||||||
len(
|
pending_proofs = await self.crud.get_proofs_pending(
|
||||||
await self.crud.get_proofs_pending(
|
Ys=[p.Y for p in proofs], db=self.db, conn=conn
|
||||||
Ys=[p.Y for p in proofs], db=self.db, conn=conn
|
)
|
||||||
)
|
if not (len(pending_proofs) == 0):
|
||||||
)
|
|
||||||
== 0
|
|
||||||
):
|
|
||||||
raise TransactionError("proofs are pending.")
|
raise TransactionError("proofs are pending.")
|
||||||
|
|
||||||
|
async def _set_mint_quote_pending(self, quote_id: str) -> MintQuote:
|
||||||
|
"""Sets the mint quote as pending.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
quote (MintQuote): Mint quote to set as pending.
|
||||||
|
"""
|
||||||
|
quote: Union[MintQuote, None] = None
|
||||||
|
async with self.db.get_connection(
|
||||||
|
lock_table="mint_quotes", lock_select_statement=f"quote='{quote_id}'"
|
||||||
|
) as conn:
|
||||||
|
# get mint quote from db and check if it is already pending
|
||||||
|
quote = await self.crud.get_mint_quote(
|
||||||
|
quote_id=quote_id, db=self.db, conn=conn
|
||||||
|
)
|
||||||
|
if not quote:
|
||||||
|
raise TransactionError("Mint quote not found.")
|
||||||
|
if quote.state == MintQuoteState.pending:
|
||||||
|
raise TransactionError("Mint quote already pending.")
|
||||||
|
if not quote.state == MintQuoteState.paid:
|
||||||
|
raise TransactionError("Mint quote is not paid yet.")
|
||||||
|
# set the quote as pending
|
||||||
|
quote.state = MintQuoteState.pending
|
||||||
|
logger.trace(f"crud: setting quote {quote_id} as PENDING")
|
||||||
|
await self.crud.update_mint_quote(quote=quote, db=self.db, conn=conn)
|
||||||
|
if quote is None:
|
||||||
|
raise TransactionError("Mint quote not found.")
|
||||||
|
return quote
|
||||||
|
|
||||||
|
async def _unset_mint_quote_pending(
|
||||||
|
self, quote_id: str, state: MintQuoteState
|
||||||
|
) -> MintQuote:
|
||||||
|
"""Unsets the mint quote as pending.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
quote (MintQuote): Mint quote to unset as pending.
|
||||||
|
state (MintQuoteState): New state of the mint quote.
|
||||||
|
"""
|
||||||
|
quote: Union[MintQuote, None] = None
|
||||||
|
async with self.db.get_connection(lock_table="mint_quotes") as conn:
|
||||||
|
# get mint quote from db and check if it is pending
|
||||||
|
quote = await self.crud.get_mint_quote(
|
||||||
|
quote_id=quote_id, db=self.db, conn=conn
|
||||||
|
)
|
||||||
|
if not quote:
|
||||||
|
raise TransactionError("Mint quote not found.")
|
||||||
|
if quote.state != MintQuoteState.pending:
|
||||||
|
raise TransactionError(
|
||||||
|
f"Mint quote not pending: {quote.state.value}. Cannot set as {state.value}."
|
||||||
|
)
|
||||||
|
# set the quote as pending
|
||||||
|
quote.state = state
|
||||||
|
logger.trace(f"crud: setting quote {quote_id} as {state.value}")
|
||||||
|
await self.crud.update_mint_quote(quote=quote, db=self.db, conn=conn)
|
||||||
|
if quote is None:
|
||||||
|
raise TransactionError("Mint quote not found.")
|
||||||
|
|
||||||
|
await self.events.submit(quote)
|
||||||
|
return quote
|
||||||
|
|
||||||
|
async def _set_melt_quote_pending(self, quote: MeltQuote) -> MeltQuote:
|
||||||
|
"""Sets the melt quote as pending.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
quote (MeltQuote): Melt quote to set as pending.
|
||||||
|
"""
|
||||||
|
quote_copy = quote.copy()
|
||||||
|
async with self.db.get_connection(
|
||||||
|
lock_table="melt_quotes",
|
||||||
|
lock_select_statement=f"checking_id='{quote.checking_id}'",
|
||||||
|
) as conn:
|
||||||
|
# get melt quote from db and check if it is already pending
|
||||||
|
quote_db = await self.crud.get_melt_quote(
|
||||||
|
checking_id=quote.checking_id, db=self.db, conn=conn
|
||||||
|
)
|
||||||
|
if not quote_db:
|
||||||
|
raise TransactionError("Melt quote not found.")
|
||||||
|
if quote_db.state == MeltQuoteState.pending:
|
||||||
|
raise TransactionError("Melt quote already pending.")
|
||||||
|
# set the quote as pending
|
||||||
|
quote_copy.state = MeltQuoteState.pending
|
||||||
|
await self.crud.update_melt_quote(quote=quote_copy, db=self.db, conn=conn)
|
||||||
|
|
||||||
|
await self.events.submit(quote_copy)
|
||||||
|
return quote_copy
|
||||||
|
|
||||||
|
async def _unset_melt_quote_pending(
|
||||||
|
self, quote: MeltQuote, state: MeltQuoteState
|
||||||
|
) -> MeltQuote:
|
||||||
|
"""Unsets the melt quote as pending.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
quote (MeltQuote): Melt quote to unset as pending.
|
||||||
|
state (MeltQuoteState): New state of the melt quote.
|
||||||
|
"""
|
||||||
|
quote_copy = quote.copy()
|
||||||
|
async with self.db.get_connection(lock_table="melt_quotes") as conn:
|
||||||
|
# get melt quote from db and check if it is pending
|
||||||
|
quote_db = await self.crud.get_melt_quote(
|
||||||
|
checking_id=quote.checking_id, db=self.db, conn=conn
|
||||||
|
)
|
||||||
|
if not quote_db:
|
||||||
|
raise TransactionError("Melt quote not found.")
|
||||||
|
if quote_db.state != MeltQuoteState.pending:
|
||||||
|
raise TransactionError("Melt quote not pending.")
|
||||||
|
# set the quote as pending
|
||||||
|
quote_copy.state = state
|
||||||
|
await self.crud.update_melt_quote(quote=quote_copy, db=self.db, conn=conn)
|
||||||
|
|
||||||
|
await self.events.submit(quote_copy)
|
||||||
|
return quote_copy
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ except ImportError:
|
|||||||
import asyncio
|
import asyncio
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
from cashu.core.db import Database, table_with_schema
|
from cashu.core.db import Database
|
||||||
from cashu.core.migrations import migrate_databases
|
from cashu.core.migrations import migrate_databases
|
||||||
from cashu.core.settings import settings
|
from cashu.core.settings import settings
|
||||||
from cashu.mint import migrations
|
from cashu.mint import migrations
|
||||||
@@ -95,7 +95,7 @@ async def migrate(no_dry_run):
|
|||||||
# get all keysets
|
# get all keysets
|
||||||
async with ledger.db.connect() as conn:
|
async with ledger.db.connect() as conn:
|
||||||
rows = await conn.fetchall(
|
rows = await conn.fetchall(
|
||||||
f"SELECT * FROM {table_with_schema(ledger.db, 'keysets')} WHERE seed IS NOT"
|
f"SELECT * FROM {ledger.db.table_with_schema('keysets')} WHERE seed IS NOT"
|
||||||
" NULL"
|
" NULL"
|
||||||
)
|
)
|
||||||
click.echo(f"Found {len(rows)} keysets in database.")
|
click.echo(f"Found {len(rows)} keysets in database.")
|
||||||
@@ -138,7 +138,7 @@ async def migrate(no_dry_run):
|
|||||||
for keyset_dict in keysets_migrate:
|
for keyset_dict in keysets_migrate:
|
||||||
click.echo(f"Updating keyset {keyset_dict['id']}")
|
click.echo(f"Updating keyset {keyset_dict['id']}")
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"UPDATE {table_with_schema(ledger.db, 'keysets')} SET seed='',"
|
f"UPDATE {ledger.db.table_with_schema('keysets')} SET seed='',"
|
||||||
" encrypted_seed = ?, seed_encryption_method = ? WHERE id = ?",
|
" encrypted_seed = ?, seed_encryption_method = ? WHERE id = ?",
|
||||||
(
|
(
|
||||||
keyset_dict["encrypted_seed"],
|
keyset_dict["encrypted_seed"],
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ from ..core.crypto.keys import (
|
|||||||
random_hash,
|
random_hash,
|
||||||
)
|
)
|
||||||
from ..core.crypto.secp import PrivateKey, PublicKey
|
from ..core.crypto.secp import PrivateKey, PublicKey
|
||||||
from ..core.db import Connection, Database, get_db_connection
|
from ..core.db import Connection, Database
|
||||||
from ..core.errors import (
|
from ..core.errors import (
|
||||||
CashuError,
|
CashuError,
|
||||||
KeysetError,
|
KeysetError,
|
||||||
@@ -68,6 +68,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
keysets: Dict[str, MintKeyset] = {}
|
keysets: Dict[str, MintKeyset] = {}
|
||||||
events = LedgerEventManager()
|
events = LedgerEventManager()
|
||||||
db_read: DbReadHelper
|
db_read: DbReadHelper
|
||||||
|
invoice_listener_tasks: List[asyncio.Task] = []
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -106,7 +107,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
async def startup_ledger(self):
|
async def startup_ledger(self):
|
||||||
await self._startup_ledger()
|
await self._startup_ledger()
|
||||||
await self._check_pending_proofs_and_melt_quotes()
|
await self._check_pending_proofs_and_melt_quotes()
|
||||||
await self.dispatch_listeners()
|
self.invoice_listener_tasks = await self.dispatch_listeners()
|
||||||
|
|
||||||
async def _startup_ledger(self):
|
async def _startup_ledger(self):
|
||||||
await self.init_keysets()
|
await self.init_keysets()
|
||||||
@@ -132,6 +133,11 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
|
|
||||||
logger.info(f"Data dir: {settings.cashu_dir}")
|
logger.info(f"Data dir: {settings.cashu_dir}")
|
||||||
|
|
||||||
|
async def shutdown_ledger(self):
|
||||||
|
await self.db.engine.dispose()
|
||||||
|
for task in self.invoice_listener_tasks:
|
||||||
|
task.cancel()
|
||||||
|
|
||||||
async def _check_pending_proofs_and_melt_quotes(self):
|
async def _check_pending_proofs_and_melt_quotes(self):
|
||||||
"""Startup routine that checks all pending proofs for their melt state and either invalidates
|
"""Startup routine that checks all pending proofs for their melt state and either invalidates
|
||||||
them for a successful melt or deletes them if the melt failed.
|
them for a successful melt or deletes them if the melt failed.
|
||||||
@@ -168,7 +174,6 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
await self.db_write._unset_proofs_pending(pending_proofs)
|
await self.db_write._unset_proofs_pending(pending_proofs)
|
||||||
elif payment.failed:
|
elif payment.failed:
|
||||||
logger.info(f"Melt quote {quote.quote} state: failed")
|
logger.info(f"Melt quote {quote.quote} state: failed")
|
||||||
|
|
||||||
# unset pending
|
# unset pending
|
||||||
await self.db_write._unset_proofs_pending(pending_proofs, spent=False)
|
await self.db_write._unset_proofs_pending(pending_proofs, spent=False)
|
||||||
elif payment.pending:
|
elif payment.pending:
|
||||||
@@ -298,9 +303,10 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
proofs (List[Proof]): Proofs to add to known secret table.
|
proofs (List[Proof]): Proofs to add to known secret table.
|
||||||
conn: (Optional[Connection], optional): Database connection to reuse. Will create a new one if not given. Defaults to None.
|
conn: (Optional[Connection], optional): Database connection to reuse. Will create a new one if not given. Defaults to None.
|
||||||
"""
|
"""
|
||||||
async with get_db_connection(self.db, conn) as conn:
|
async with self.db.get_connection(conn) as conn:
|
||||||
# store in db
|
# store in db
|
||||||
for p in proofs:
|
for p in proofs:
|
||||||
|
logger.trace(f"Invalidating proof {p.Y}")
|
||||||
await self.crud.invalidate_proof(
|
await self.crud.invalidate_proof(
|
||||||
proof=p, db=self.db, quote_id=quote_id, conn=conn
|
proof=p, db=self.db, quote_id=quote_id, conn=conn
|
||||||
)
|
)
|
||||||
@@ -469,12 +475,26 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
unit
|
unit
|
||||||
].get_invoice_status(quote.checking_id)
|
].get_invoice_status(quote.checking_id)
|
||||||
if status.paid:
|
if status.paid:
|
||||||
logger.trace(f"Setting quote {quote_id} as paid")
|
# change state to paid in one transaction, it could have been marked paid
|
||||||
quote.paid = True
|
# by the invoice listener in the mean time
|
||||||
quote.state = MintQuoteState.paid
|
async with self.db.get_connection(
|
||||||
quote.paid_time = int(time.time())
|
lock_table="mint_quotes",
|
||||||
await self.crud.update_mint_quote(quote=quote, db=self.db)
|
lock_select_statement=f"quote='{quote_id}'",
|
||||||
await self.events.submit(quote)
|
) as conn:
|
||||||
|
quote = await self.crud.get_mint_quote(
|
||||||
|
quote_id=quote_id, db=self.db, conn=conn
|
||||||
|
)
|
||||||
|
if not quote:
|
||||||
|
raise Exception("quote not found")
|
||||||
|
if quote.state == MintQuoteState.unpaid:
|
||||||
|
logger.trace(f"Setting quote {quote_id} as paid")
|
||||||
|
quote.paid = True
|
||||||
|
quote.state = MintQuoteState.paid
|
||||||
|
quote.paid_time = int(time.time())
|
||||||
|
await self.crud.update_mint_quote(
|
||||||
|
quote=quote, db=self.db, conn=conn
|
||||||
|
)
|
||||||
|
await self.events.submit(quote)
|
||||||
|
|
||||||
return quote
|
return quote
|
||||||
|
|
||||||
@@ -501,46 +521,39 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
Returns:
|
Returns:
|
||||||
List[BlindedSignature]: Signatures on the outputs.
|
List[BlindedSignature]: Signatures on the outputs.
|
||||||
"""
|
"""
|
||||||
logger.trace("called mint")
|
|
||||||
await self._verify_outputs(outputs)
|
await self._verify_outputs(outputs)
|
||||||
sum_amount_outputs = sum([b.amount for b in outputs])
|
sum_amount_outputs = sum([b.amount for b in outputs])
|
||||||
# we already know from _verify_outputs that all outputs have the same unit because they have the same keyset
|
# we already know from _verify_outputs that all outputs have the same unit because they have the same keyset
|
||||||
output_unit = self.keysets[outputs[0].id].unit
|
output_unit = self.keysets[outputs[0].id].unit
|
||||||
|
|
||||||
self.locks[quote_id] = (
|
quote = await self.get_mint_quote(quote_id)
|
||||||
self.locks.get(quote_id) or asyncio.Lock()
|
if quote.state == MintQuoteState.pending:
|
||||||
) # create a new lock if it doesn't exist
|
raise TransactionError("Mint quote already pending.")
|
||||||
async with self.locks[quote_id]:
|
if quote.state == MintQuoteState.issued:
|
||||||
quote = await self.get_mint_quote(quote_id=quote_id)
|
raise TransactionError("Mint quote already issued.")
|
||||||
if not quote.paid:
|
if not quote.state == MintQuoteState.paid:
|
||||||
raise QuoteNotPaidError()
|
raise QuoteNotPaidError()
|
||||||
if quote.issued:
|
previous_state = quote.state
|
||||||
raise TransactionError("quote already issued")
|
await self.db_write._set_mint_quote_pending(quote_id=quote_id)
|
||||||
|
try:
|
||||||
if not quote.state == MintQuoteState.paid:
|
|
||||||
raise QuoteNotPaidError()
|
|
||||||
if quote.state == MintQuoteState.issued:
|
|
||||||
raise TransactionError("quote already issued")
|
|
||||||
|
|
||||||
if not quote.unit == output_unit.name:
|
if not quote.unit == output_unit.name:
|
||||||
raise TransactionError("quote unit does not match output unit")
|
raise TransactionError("quote unit does not match output unit")
|
||||||
if not quote.amount == sum_amount_outputs:
|
if not quote.amount == sum_amount_outputs:
|
||||||
raise TransactionError("amount to mint does not match quote amount")
|
raise TransactionError("amount to mint does not match quote amount")
|
||||||
if quote.expiry and quote.expiry > int(time.time()):
|
if quote.expiry and quote.expiry > int(time.time()):
|
||||||
raise TransactionError("quote expired")
|
raise TransactionError("quote expired")
|
||||||
|
|
||||||
logger.trace(f"crud: setting quote {quote_id} as issued")
|
|
||||||
quote.issued = True
|
|
||||||
quote.state = MintQuoteState.issued
|
|
||||||
await self.crud.update_mint_quote(quote=quote, db=self.db)
|
|
||||||
|
|
||||||
promises = await self._generate_promises(outputs)
|
promises = await self._generate_promises(outputs)
|
||||||
logger.trace("generated promises")
|
except Exception as e:
|
||||||
|
await self.db_write._unset_mint_quote_pending(
|
||||||
|
quote_id=quote_id, state=previous_state
|
||||||
|
)
|
||||||
|
raise e
|
||||||
|
|
||||||
# submit the quote update to the event manager
|
await self.db_write._unset_mint_quote_pending(
|
||||||
await self.events.submit(quote)
|
quote_id=quote_id, state=MintQuoteState.issued
|
||||||
|
)
|
||||||
|
|
||||||
del self.locks[quote_id]
|
|
||||||
return promises
|
return promises
|
||||||
|
|
||||||
def create_internal_melt_quote(
|
def create_internal_melt_quote(
|
||||||
@@ -567,7 +580,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
if mint_quote.state == MintQuoteState.issued:
|
if mint_quote.state == MintQuoteState.issued:
|
||||||
raise TransactionError("mint quote already issued")
|
raise TransactionError("mint quote already issued")
|
||||||
if mint_quote.state != MintQuoteState.unpaid:
|
if mint_quote.state != MintQuoteState.unpaid:
|
||||||
raise TransactionError("mint quote already paid")
|
raise TransactionError("mint quote is not unpaid")
|
||||||
|
|
||||||
if not mint_quote.checking_id:
|
if not mint_quote.checking_id:
|
||||||
raise TransactionError("mint quote has no checking id")
|
raise TransactionError("mint quote has no checking id")
|
||||||
@@ -800,7 +813,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
raise TransactionError("mint quote already issued")
|
raise TransactionError("mint quote already issued")
|
||||||
|
|
||||||
if mint_quote.state != MintQuoteState.unpaid:
|
if mint_quote.state != MintQuoteState.unpaid:
|
||||||
raise TransactionError("mint quote already paid")
|
raise TransactionError("mint quote is not unpaid")
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Settling bolt11 payment internally: {melt_quote.quote} ->"
|
f"Settling bolt11 payment internally: {melt_quote.quote} ->"
|
||||||
@@ -816,7 +829,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
mint_quote.state = MintQuoteState.paid
|
mint_quote.state = MintQuoteState.paid
|
||||||
mint_quote.paid_time = melt_quote.paid_time
|
mint_quote.paid_time = melt_quote.paid_time
|
||||||
|
|
||||||
async with get_db_connection(self.db) as conn:
|
async with self.db.get_connection() as conn:
|
||||||
await self.crud.update_melt_quote(quote=melt_quote, db=self.db, conn=conn)
|
await self.crud.update_melt_quote(quote=melt_quote, db=self.db, conn=conn)
|
||||||
await self.crud.update_mint_quote(quote=mint_quote, db=self.db, conn=conn)
|
await self.crud.update_mint_quote(quote=mint_quote, db=self.db, conn=conn)
|
||||||
|
|
||||||
@@ -970,22 +983,13 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
Tuple[List[BlindSignature],List[BlindSignature]]: Promises on both sides of the split.
|
Tuple[List[BlindSignature],List[BlindSignature]]: Promises on both sides of the split.
|
||||||
"""
|
"""
|
||||||
logger.trace("split called")
|
logger.trace("split called")
|
||||||
# explicitly check that amount of inputs is equal to amount of outputs
|
|
||||||
# note: we check this again in verify_inputs_and_outputs but only if any
|
|
||||||
# outputs are provided at all. To make sure of that before calling
|
|
||||||
# verify_inputs_and_outputs, we check it here.
|
|
||||||
self._verify_equation_balanced(proofs, outputs)
|
|
||||||
# verify spending inputs, outputs, and spending conditions
|
# verify spending inputs, outputs, and spending conditions
|
||||||
await self.verify_inputs_and_outputs(proofs=proofs, outputs=outputs)
|
await self.verify_inputs_and_outputs(proofs=proofs, outputs=outputs)
|
||||||
|
|
||||||
await self.db_write._set_proofs_pending(proofs)
|
await self.db_write._set_proofs_pending(proofs)
|
||||||
try:
|
try:
|
||||||
# Mark proofs as used and prepare new promises
|
async with self.db.get_connection(lock_table="proofs_pending") as conn:
|
||||||
async with get_db_connection(self.db) as conn:
|
|
||||||
# we do this in a single db transaction
|
|
||||||
await self._invalidate_proofs(proofs=proofs, conn=conn)
|
await self._invalidate_proofs(proofs=proofs, conn=conn)
|
||||||
promises = await self._generate_promises(outputs, keyset, conn)
|
promises = await self._generate_promises(outputs, keyset, conn)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.trace(f"split failed: {e}")
|
logger.trace(f"split failed: {e}")
|
||||||
raise e
|
raise e
|
||||||
@@ -1001,7 +1005,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
) -> Tuple[List[BlindedMessage], List[BlindedSignature]]:
|
) -> Tuple[List[BlindedMessage], List[BlindedSignature]]:
|
||||||
signatures: List[BlindedSignature] = []
|
signatures: List[BlindedSignature] = []
|
||||||
return_outputs: List[BlindedMessage] = []
|
return_outputs: List[BlindedMessage] = []
|
||||||
async with get_db_connection(self.db) as conn:
|
async with self.db.get_connection() as conn:
|
||||||
for output in outputs:
|
for output in outputs:
|
||||||
logger.trace(f"looking for promise: {output}")
|
logger.trace(f"looking for promise: {output}")
|
||||||
promise = await self.crud.get_promise(
|
promise = await self.crud.get_promise(
|
||||||
@@ -1057,7 +1061,7 @@ class Ledger(LedgerVerification, LedgerSpendingConditions, LedgerTasks, LedgerFe
|
|||||||
keyset = keyset or self.keyset
|
keyset = keyset or self.keyset
|
||||||
|
|
||||||
signatures = []
|
signatures = []
|
||||||
async with get_db_connection(self.db, conn) as conn:
|
async with self.db.get_connection(conn) as conn:
|
||||||
for promise in promises:
|
for promise in promises:
|
||||||
keyset_id, B_, amount, C_, e, s = promise
|
keyset_id, B_, amount, C_, e, s = promise
|
||||||
logger.trace(f"crud: _generate_promise storing promise for {amount}")
|
logger.trace(f"crud: _generate_promise storing promise for {amount}")
|
||||||
|
|||||||
@@ -2,14 +2,14 @@ import copy
|
|||||||
|
|
||||||
from ..core.base import MintKeyset, Proof
|
from ..core.base import MintKeyset, Proof
|
||||||
from ..core.crypto.keys import derive_keyset_id, derive_keyset_id_deprecated
|
from ..core.crypto.keys import derive_keyset_id, derive_keyset_id_deprecated
|
||||||
from ..core.db import Connection, Database, table_with_schema, timestamp_now
|
from ..core.db import Connection, Database
|
||||||
from ..core.settings import settings
|
from ..core.settings import settings
|
||||||
|
|
||||||
|
|
||||||
async def m000_create_migrations_table(conn: Connection):
|
async def m000_create_migrations_table(conn: Connection):
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE TABLE IF NOT EXISTS {table_with_schema(conn, 'dbversions')} (
|
CREATE TABLE IF NOT EXISTS {conn.table_with_schema('dbversions')} (
|
||||||
db TEXT PRIMARY KEY,
|
db TEXT PRIMARY KEY,
|
||||||
version INT NOT NULL
|
version INT NOT NULL
|
||||||
)
|
)
|
||||||
@@ -21,7 +21,7 @@ async def m001_initial(db: Database):
|
|||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'promises')} (
|
CREATE TABLE IF NOT EXISTS {db.table_with_schema('promises')} (
|
||||||
amount {db.big_int} NOT NULL,
|
amount {db.big_int} NOT NULL,
|
||||||
b_b TEXT NOT NULL,
|
b_b TEXT NOT NULL,
|
||||||
c_b TEXT NOT NULL,
|
c_b TEXT NOT NULL,
|
||||||
@@ -34,7 +34,7 @@ async def m001_initial(db: Database):
|
|||||||
|
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_used')} (
|
CREATE TABLE IF NOT EXISTS {db.table_with_schema('proofs_used')} (
|
||||||
amount {db.big_int} NOT NULL,
|
amount {db.big_int} NOT NULL,
|
||||||
c TEXT NOT NULL,
|
c TEXT NOT NULL,
|
||||||
secret TEXT NOT NULL,
|
secret TEXT NOT NULL,
|
||||||
@@ -47,7 +47,7 @@ async def m001_initial(db: Database):
|
|||||||
|
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'invoices')} (
|
CREATE TABLE IF NOT EXISTS {db.table_with_schema('invoices')} (
|
||||||
amount {db.big_int} NOT NULL,
|
amount {db.big_int} NOT NULL,
|
||||||
pr TEXT NOT NULL,
|
pr TEXT NOT NULL,
|
||||||
hash TEXT NOT NULL,
|
hash TEXT NOT NULL,
|
||||||
@@ -61,20 +61,20 @@ async def m001_initial(db: Database):
|
|||||||
|
|
||||||
|
|
||||||
async def drop_balance_views(db: Database, conn: Connection):
|
async def drop_balance_views(db: Database, conn: Connection):
|
||||||
await conn.execute(f"DROP VIEW IF EXISTS {table_with_schema(db, 'balance')}")
|
await conn.execute(f"DROP VIEW IF EXISTS {db.table_with_schema('balance')}")
|
||||||
await conn.execute(f"DROP VIEW IF EXISTS {table_with_schema(db, 'balance_issued')}")
|
await conn.execute(f"DROP VIEW IF EXISTS {db.table_with_schema('balance_issued')}")
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"DROP VIEW IF EXISTS {table_with_schema(db, 'balance_redeemed')}"
|
f"DROP VIEW IF EXISTS {db.table_with_schema('balance_redeemed')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def create_balance_views(db: Database, conn: Connection):
|
async def create_balance_views(db: Database, conn: Connection):
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE VIEW {table_with_schema(db, 'balance_issued')} AS
|
CREATE VIEW {db.table_with_schema('balance_issued')} AS
|
||||||
SELECT COALESCE(SUM(s), 0) AS balance FROM (
|
SELECT COALESCE(SUM(s), 0) AS balance FROM (
|
||||||
SELECT SUM(amount) AS s
|
SELECT SUM(amount) AS s
|
||||||
FROM {table_with_schema(db, 'promises')}
|
FROM {db.table_with_schema('promises')}
|
||||||
WHERE amount > 0
|
WHERE amount > 0
|
||||||
) AS balance_issued;
|
) AS balance_issued;
|
||||||
"""
|
"""
|
||||||
@@ -82,10 +82,10 @@ async def create_balance_views(db: Database, conn: Connection):
|
|||||||
|
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE VIEW {table_with_schema(db, 'balance_redeemed')} AS
|
CREATE VIEW {db.table_with_schema('balance_redeemed')} AS
|
||||||
SELECT COALESCE(SUM(s), 0) AS balance FROM (
|
SELECT COALESCE(SUM(s), 0) AS balance FROM (
|
||||||
SELECT SUM(amount) AS s
|
SELECT SUM(amount) AS s
|
||||||
FROM {table_with_schema(db, 'proofs_used')}
|
FROM {db.table_with_schema('proofs_used')}
|
||||||
WHERE amount > 0
|
WHERE amount > 0
|
||||||
) AS balance_redeemed;
|
) AS balance_redeemed;
|
||||||
"""
|
"""
|
||||||
@@ -93,11 +93,11 @@ async def create_balance_views(db: Database, conn: Connection):
|
|||||||
|
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE VIEW {table_with_schema(db, 'balance')} AS
|
CREATE VIEW {db.table_with_schema('balance')} AS
|
||||||
SELECT s_issued - s_used FROM (
|
SELECT s_issued - s_used FROM (
|
||||||
SELECT bi.balance AS s_issued, bu.balance AS s_used
|
SELECT bi.balance AS s_issued, bu.balance AS s_used
|
||||||
FROM {table_with_schema(db, 'balance_issued')} bi
|
FROM {db.table_with_schema('balance_issued')} bi
|
||||||
CROSS JOIN {table_with_schema(db, 'balance_redeemed')} bu
|
CROSS JOIN {db.table_with_schema('balance_redeemed')} bu
|
||||||
) AS balance;
|
) AS balance;
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
@@ -115,7 +115,7 @@ async def m003_mint_keysets(db: Database):
|
|||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'keysets')} (
|
CREATE TABLE IF NOT EXISTS {db.table_with_schema('keysets')} (
|
||||||
id TEXT NOT NULL,
|
id TEXT NOT NULL,
|
||||||
derivation_path TEXT,
|
derivation_path TEXT,
|
||||||
valid_from TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
valid_from TIMESTAMP NOT NULL DEFAULT {db.timestamp_now},
|
||||||
@@ -130,7 +130,7 @@ async def m003_mint_keysets(db: Database):
|
|||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'mint_pubkeys')} (
|
CREATE TABLE IF NOT EXISTS {db.table_with_schema('mint_pubkeys')} (
|
||||||
id TEXT NOT NULL,
|
id TEXT NOT NULL,
|
||||||
amount {db.big_int} NOT NULL,
|
amount {db.big_int} NOT NULL,
|
||||||
pubkey TEXT NOT NULL,
|
pubkey TEXT NOT NULL,
|
||||||
@@ -148,7 +148,7 @@ async def m004_keysets_add_version(db: Database):
|
|||||||
"""
|
"""
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'keysets')} ADD COLUMN version TEXT"
|
f"ALTER TABLE {db.table_with_schema('keysets')} ADD COLUMN version TEXT"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -159,7 +159,7 @@ async def m005_pending_proofs_table(db: Database) -> None:
|
|||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_pending')} (
|
CREATE TABLE IF NOT EXISTS {db.table_with_schema('proofs_pending')} (
|
||||||
amount {db.big_int} NOT NULL,
|
amount {db.big_int} NOT NULL,
|
||||||
c TEXT NOT NULL,
|
c TEXT NOT NULL,
|
||||||
secret TEXT NOT NULL,
|
secret TEXT NOT NULL,
|
||||||
@@ -179,11 +179,11 @@ async def m006_invoices_add_payment_hash(db: Database):
|
|||||||
"""
|
"""
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'invoices')} ADD COLUMN payment_hash"
|
f"ALTER TABLE {db.table_with_schema('invoices')} ADD COLUMN payment_hash"
|
||||||
" TEXT"
|
" TEXT"
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"UPDATE {table_with_schema(db, 'invoices')} SET payment_hash = hash"
|
f"UPDATE {db.table_with_schema('invoices')} SET payment_hash = hash"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -193,13 +193,13 @@ async def m007_proofs_and_promises_store_id(db: Database):
|
|||||||
"""
|
"""
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'proofs_used')} ADD COLUMN id TEXT"
|
f"ALTER TABLE {db.table_with_schema('proofs_used')} ADD COLUMN id TEXT"
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'proofs_pending')} ADD COLUMN id TEXT"
|
f"ALTER TABLE {db.table_with_schema('proofs_pending')} ADD COLUMN id TEXT"
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'promises')} ADD COLUMN id TEXT"
|
f"ALTER TABLE {db.table_with_schema('promises')} ADD COLUMN id TEXT"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -209,10 +209,10 @@ async def m008_promises_dleq(db: Database):
|
|||||||
"""
|
"""
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'promises')} ADD COLUMN e TEXT"
|
f"ALTER TABLE {db.table_with_schema('promises')} ADD COLUMN e TEXT"
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'promises')} ADD COLUMN s TEXT"
|
f"ALTER TABLE {db.table_with_schema('promises')} ADD COLUMN s TEXT"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -221,16 +221,16 @@ async def m009_add_out_to_invoices(db: Database):
|
|||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
# rename column pr to bolt11
|
# rename column pr to bolt11
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'invoices')} RENAME COLUMN pr TO"
|
f"ALTER TABLE {db.table_with_schema('invoices')} RENAME COLUMN pr TO"
|
||||||
" bolt11"
|
" bolt11"
|
||||||
)
|
)
|
||||||
# rename column hash to payment_hash
|
# rename column hash to payment_hash
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'invoices')} RENAME COLUMN hash TO id"
|
f"ALTER TABLE {db.table_with_schema('invoices')} RENAME COLUMN hash TO id"
|
||||||
)
|
)
|
||||||
|
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'invoices')} ADD COLUMN out BOOL"
|
f"ALTER TABLE {db.table_with_schema('invoices')} ADD COLUMN out BOOL"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ async def m010_add_index_to_proofs_used(db: Database):
|
|||||||
await conn.execute(
|
await conn.execute(
|
||||||
"CREATE INDEX IF NOT EXISTS"
|
"CREATE INDEX IF NOT EXISTS"
|
||||||
" proofs_used_secret_idx ON"
|
" proofs_used_secret_idx ON"
|
||||||
f" {table_with_schema(db, 'proofs_used')} (secret)"
|
f" {db.table_with_schema('proofs_used')} (secret)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -250,37 +250,37 @@ async def m011_add_quote_tables(db: Database):
|
|||||||
tables = ["invoices", "promises", "proofs_used", "proofs_pending"]
|
tables = ["invoices", "promises", "proofs_used", "proofs_pending"]
|
||||||
for table in tables:
|
for table in tables:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, table)} ADD COLUMN created"
|
f"ALTER TABLE {db.table_with_schema(table)} ADD COLUMN created"
|
||||||
" TIMESTAMP"
|
" TIMESTAMP"
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"UPDATE {table_with_schema(db, table)} SET created ="
|
f"UPDATE {db.table_with_schema(table)} SET created ="
|
||||||
f" '{timestamp_now(db)}'"
|
f" '{db.timestamp_now_str()}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
# add column "witness" to table proofs_used
|
# add column "witness" to table proofs_used
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'proofs_used')} ADD COLUMN witness"
|
f"ALTER TABLE {db.table_with_schema('proofs_used')} ADD COLUMN witness"
|
||||||
" TEXT"
|
" TEXT"
|
||||||
)
|
)
|
||||||
|
|
||||||
# add columns "seed" and "unit" to table keysets
|
# add columns "seed" and "unit" to table keysets
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'keysets')} ADD COLUMN seed TEXT"
|
f"ALTER TABLE {db.table_with_schema('keysets')} ADD COLUMN seed TEXT"
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'keysets')} ADD COLUMN unit TEXT"
|
f"ALTER TABLE {db.table_with_schema('keysets')} ADD COLUMN unit TEXT"
|
||||||
)
|
)
|
||||||
|
|
||||||
# fill columns "seed" and "unit" in table keysets
|
# fill columns "seed" and "unit" in table keysets
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"UPDATE {table_with_schema(db, 'keysets')} SET seed ="
|
f"UPDATE {db.table_with_schema('keysets')} SET seed ="
|
||||||
f" '{settings.mint_private_key}', unit = 'sat'"
|
f" '{settings.mint_private_key}', unit = 'sat'"
|
||||||
)
|
)
|
||||||
|
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'mint_quotes')} (
|
CREATE TABLE IF NOT EXISTS {db.table_with_schema('mint_quotes')} (
|
||||||
quote TEXT NOT NULL,
|
quote TEXT NOT NULL,
|
||||||
method TEXT NOT NULL,
|
method TEXT NOT NULL,
|
||||||
request TEXT NOT NULL,
|
request TEXT NOT NULL,
|
||||||
@@ -300,7 +300,7 @@ async def m011_add_quote_tables(db: Database):
|
|||||||
|
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'melt_quotes')} (
|
CREATE TABLE IF NOT EXISTS {db.table_with_schema('melt_quotes')} (
|
||||||
quote TEXT NOT NULL,
|
quote TEXT NOT NULL,
|
||||||
method TEXT NOT NULL,
|
method TEXT NOT NULL,
|
||||||
request TEXT NOT NULL,
|
request TEXT NOT NULL,
|
||||||
@@ -321,15 +321,15 @@ async def m011_add_quote_tables(db: Database):
|
|||||||
)
|
)
|
||||||
|
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"INSERT INTO {table_with_schema(db, 'mint_quotes')} (quote, method,"
|
f"INSERT INTO {db.table_with_schema('mint_quotes')} (quote, method,"
|
||||||
" request, checking_id, unit, amount, paid, issued, created_time,"
|
" request, checking_id, unit, amount, paid, issued, created_time,"
|
||||||
" paid_time) SELECT id, 'bolt11', bolt11, COALESCE(payment_hash, 'None'),"
|
" paid_time) SELECT id, 'bolt11', bolt11, COALESCE(payment_hash, 'None'),"
|
||||||
f" 'sat', amount, False, issued, COALESCE(created, '{timestamp_now(db)}'),"
|
f" 'sat', amount, False, issued, COALESCE(created, '{db.timestamp_now_str()}'),"
|
||||||
f" NULL FROM {table_with_schema(db, 'invoices')} "
|
f" NULL FROM {db.table_with_schema('invoices')} "
|
||||||
)
|
)
|
||||||
|
|
||||||
# drop table invoices
|
# drop table invoices
|
||||||
await conn.execute(f"DROP TABLE {table_with_schema(db, 'invoices')}")
|
await conn.execute(f"DROP TABLE {db.table_with_schema('invoices')}")
|
||||||
|
|
||||||
|
|
||||||
async def m012_keysets_uniqueness_with_seed(db: Database):
|
async def m012_keysets_uniqueness_with_seed(db: Database):
|
||||||
@@ -338,16 +338,16 @@ async def m012_keysets_uniqueness_with_seed(db: Database):
|
|||||||
# and copy the data from keysets_old to keysets, then drop keysets_old
|
# and copy the data from keysets_old to keysets, then drop keysets_old
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"DROP TABLE IF EXISTS {table_with_schema(db, 'keysets_old')}"
|
f"DROP TABLE IF EXISTS {db.table_with_schema('keysets_old')}"
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"CREATE TABLE {table_with_schema(db, 'keysets_old')} AS"
|
f"CREATE TABLE {db.table_with_schema('keysets_old')} AS"
|
||||||
f" SELECT * FROM {table_with_schema(db, 'keysets')}"
|
f" SELECT * FROM {db.table_with_schema('keysets')}"
|
||||||
)
|
)
|
||||||
await conn.execute(f"DROP TABLE {table_with_schema(db, 'keysets')}")
|
await conn.execute(f"DROP TABLE {db.table_with_schema('keysets')}")
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'keysets')} (
|
CREATE TABLE IF NOT EXISTS {db.table_with_schema('keysets')} (
|
||||||
id TEXT NOT NULL,
|
id TEXT NOT NULL,
|
||||||
derivation_path TEXT,
|
derivation_path TEXT,
|
||||||
seed TEXT,
|
seed TEXT,
|
||||||
@@ -364,13 +364,13 @@ async def m012_keysets_uniqueness_with_seed(db: Database):
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"INSERT INTO {table_with_schema(db, 'keysets')} (id,"
|
f"INSERT INTO {db.table_with_schema('keysets')} (id,"
|
||||||
" derivation_path, valid_from, valid_to, first_seen,"
|
" derivation_path, valid_from, valid_to, first_seen,"
|
||||||
" active, version, seed, unit) SELECT id, derivation_path,"
|
" active, version, seed, unit) SELECT id, derivation_path,"
|
||||||
" valid_from, valid_to, first_seen, active, version, seed,"
|
" valid_from, valid_to, first_seen, active, version, seed,"
|
||||||
f" unit FROM {table_with_schema(db, 'keysets_old')}"
|
f" unit FROM {db.table_with_schema('keysets_old')}"
|
||||||
)
|
)
|
||||||
await conn.execute(f"DROP TABLE {table_with_schema(db, 'keysets_old')}")
|
await conn.execute(f"DROP TABLE {db.table_with_schema('keysets_old')}")
|
||||||
|
|
||||||
|
|
||||||
async def m013_keysets_add_encrypted_seed(db: Database):
|
async def m013_keysets_add_encrypted_seed(db: Database):
|
||||||
@@ -380,16 +380,16 @@ async def m013_keysets_add_encrypted_seed(db: Database):
|
|||||||
# with the same columns but with a unique constraint on id
|
# with the same columns but with a unique constraint on id
|
||||||
# and copy the data from keysets_old to keysets, then drop keysets_old
|
# and copy the data from keysets_old to keysets, then drop keysets_old
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"DROP TABLE IF EXISTS {table_with_schema(db, 'keysets_old')}"
|
f"DROP TABLE IF EXISTS {db.table_with_schema('keysets_old')}"
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"CREATE TABLE {table_with_schema(db, 'keysets_old')} AS"
|
f"CREATE TABLE {db.table_with_schema('keysets_old')} AS"
|
||||||
f" SELECT * FROM {table_with_schema(db, 'keysets')}"
|
f" SELECT * FROM {db.table_with_schema('keysets')}"
|
||||||
)
|
)
|
||||||
await conn.execute(f"DROP TABLE {table_with_schema(db, 'keysets')}")
|
await conn.execute(f"DROP TABLE {db.table_with_schema('keysets')}")
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'keysets')} (
|
CREATE TABLE IF NOT EXISTS {db.table_with_schema('keysets')} (
|
||||||
id TEXT NOT NULL,
|
id TEXT NOT NULL,
|
||||||
derivation_path TEXT,
|
derivation_path TEXT,
|
||||||
seed TEXT,
|
seed TEXT,
|
||||||
@@ -406,21 +406,21 @@ async def m013_keysets_add_encrypted_seed(db: Database):
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"INSERT INTO {table_with_schema(db, 'keysets')} (id,"
|
f"INSERT INTO {db.table_with_schema('keysets')} (id,"
|
||||||
" derivation_path, valid_from, valid_to, first_seen,"
|
" derivation_path, valid_from, valid_to, first_seen,"
|
||||||
" active, version, seed, unit) SELECT id, derivation_path,"
|
" active, version, seed, unit) SELECT id, derivation_path,"
|
||||||
" valid_from, valid_to, first_seen, active, version, seed,"
|
" valid_from, valid_to, first_seen, active, version, seed,"
|
||||||
f" unit FROM {table_with_schema(db, 'keysets_old')}"
|
f" unit FROM {db.table_with_schema('keysets_old')}"
|
||||||
)
|
)
|
||||||
await conn.execute(f"DROP TABLE {table_with_schema(db, 'keysets_old')}")
|
await conn.execute(f"DROP TABLE {db.table_with_schema('keysets_old')}")
|
||||||
|
|
||||||
# add columns encrypted_seed and seed_encryption_method to keysets
|
# add columns encrypted_seed and seed_encryption_method to keysets
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'keysets')} ADD COLUMN encrypted_seed"
|
f"ALTER TABLE {db.table_with_schema('keysets')} ADD COLUMN encrypted_seed"
|
||||||
" TEXT"
|
" TEXT"
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'keysets')} ADD COLUMN"
|
f"ALTER TABLE {db.table_with_schema('keysets')} ADD COLUMN"
|
||||||
" seed_encryption_method TEXT"
|
" seed_encryption_method TEXT"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -429,13 +429,13 @@ async def m014_proofs_add_Y_column(db: Database):
|
|||||||
# get all proofs_used and proofs_pending from the database and compute Y for each of them
|
# get all proofs_used and proofs_pending from the database and compute Y for each of them
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
rows = await conn.fetchall(
|
rows = await conn.fetchall(
|
||||||
f"SELECT * FROM {table_with_schema(db, 'proofs_used')}"
|
f"SELECT * FROM {db.table_with_schema('proofs_used')}"
|
||||||
)
|
)
|
||||||
# Proof() will compute Y from secret upon initialization
|
# Proof() will compute Y from secret upon initialization
|
||||||
proofs_used = [Proof(**r) for r in rows]
|
proofs_used = [Proof(**r) for r in rows]
|
||||||
|
|
||||||
rows = await conn.fetchall(
|
rows = await conn.fetchall(
|
||||||
f"SELECT * FROM {table_with_schema(db, 'proofs_pending')}"
|
f"SELECT * FROM {db.table_with_schema('proofs_pending')}"
|
||||||
)
|
)
|
||||||
proofs_pending = [Proof(**r) for r in rows]
|
proofs_pending = [Proof(**r) for r in rows]
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
@@ -443,27 +443,27 @@ async def m014_proofs_add_Y_column(db: Database):
|
|||||||
await drop_balance_views(db, conn)
|
await drop_balance_views(db, conn)
|
||||||
|
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'proofs_used')} ADD COLUMN y TEXT"
|
f"ALTER TABLE {db.table_with_schema('proofs_used')} ADD COLUMN y TEXT"
|
||||||
)
|
)
|
||||||
for proof in proofs_used:
|
for proof in proofs_used:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"UPDATE {table_with_schema(db, 'proofs_used')} SET y = '{proof.Y}'"
|
f"UPDATE {db.table_with_schema('proofs_used')} SET y = '{proof.Y}'"
|
||||||
f" WHERE secret = '{proof.secret}'"
|
f" WHERE secret = '{proof.secret}'"
|
||||||
)
|
)
|
||||||
# Copy proofs_used to proofs_used_old and create a new table proofs_used
|
# Copy proofs_used to proofs_used_old and create a new table proofs_used
|
||||||
# with the same columns but with a unique constraint on (Y)
|
# with the same columns but with a unique constraint on (Y)
|
||||||
# and copy the data from proofs_used_old to proofs_used, then drop proofs_used_old
|
# and copy the data from proofs_used_old to proofs_used, then drop proofs_used_old
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"DROP TABLE IF EXISTS {table_with_schema(db, 'proofs_used_old')}"
|
f"DROP TABLE IF EXISTS {db.table_with_schema('proofs_used_old')}"
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"CREATE TABLE {table_with_schema(db, 'proofs_used_old')} AS"
|
f"CREATE TABLE {db.table_with_schema('proofs_used_old')} AS"
|
||||||
f" SELECT * FROM {table_with_schema(db, 'proofs_used')}"
|
f" SELECT * FROM {db.table_with_schema('proofs_used')}"
|
||||||
)
|
)
|
||||||
await conn.execute(f"DROP TABLE {table_with_schema(db, 'proofs_used')}")
|
await conn.execute(f"DROP TABLE {db.table_with_schema('proofs_used')}")
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_used')} (
|
CREATE TABLE IF NOT EXISTS {db.table_with_schema('proofs_used')} (
|
||||||
amount {db.big_int} NOT NULL,
|
amount {db.big_int} NOT NULL,
|
||||||
c TEXT NOT NULL,
|
c TEXT NOT NULL,
|
||||||
secret TEXT NOT NULL,
|
secret TEXT NOT NULL,
|
||||||
@@ -478,19 +478,19 @@ async def m014_proofs_add_Y_column(db: Database):
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"INSERT INTO {table_with_schema(db, 'proofs_used')} (amount, c, "
|
f"INSERT INTO {db.table_with_schema('proofs_used')} (amount, c, "
|
||||||
"secret, id, y, created, witness) SELECT amount, c, secret, id, y,"
|
"secret, id, y, created, witness) SELECT amount, c, secret, id, y,"
|
||||||
f" created, witness FROM {table_with_schema(db, 'proofs_used_old')}"
|
f" created, witness FROM {db.table_with_schema('proofs_used_old')}"
|
||||||
)
|
)
|
||||||
await conn.execute(f"DROP TABLE {table_with_schema(db, 'proofs_used_old')}")
|
await conn.execute(f"DROP TABLE {db.table_with_schema('proofs_used_old')}")
|
||||||
|
|
||||||
# add column y to proofs_pending
|
# add column y to proofs_pending
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'proofs_pending')} ADD COLUMN y TEXT"
|
f"ALTER TABLE {db.table_with_schema('proofs_pending')} ADD COLUMN y TEXT"
|
||||||
)
|
)
|
||||||
for proof in proofs_pending:
|
for proof in proofs_pending:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"UPDATE {table_with_schema(db, 'proofs_pending')} SET y = '{proof.Y}'"
|
f"UPDATE {db.table_with_schema('proofs_pending')} SET y = '{proof.Y}'"
|
||||||
f" WHERE secret = '{proof.secret}'"
|
f" WHERE secret = '{proof.secret}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -498,24 +498,24 @@ async def m014_proofs_add_Y_column(db: Database):
|
|||||||
# with the same columns but with a unique constraint on (Y)
|
# with the same columns but with a unique constraint on (Y)
|
||||||
# and copy the data from proofs_pending_old to proofs_pending, then drop proofs_pending_old
|
# and copy the data from proofs_pending_old to proofs_pending, then drop proofs_pending_old
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"DROP TABLE IF EXISTS {table_with_schema(db, 'proofs_pending_old')}"
|
f"DROP TABLE IF EXISTS {db.table_with_schema('proofs_pending_old')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"CREATE TABLE {table_with_schema(db, 'proofs_pending_old')} AS"
|
f"CREATE TABLE {db.table_with_schema('proofs_pending_old')} AS"
|
||||||
f" SELECT * FROM {table_with_schema(db, 'proofs_pending')}"
|
f" SELECT * FROM {db.table_with_schema('proofs_pending')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
await conn.execute(f"DROP TABLE {table_with_schema(db, 'proofs_pending')}")
|
await conn.execute(f"DROP TABLE {db.table_with_schema('proofs_pending')}")
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_pending')} (
|
CREATE TABLE IF NOT EXISTS {db.table_with_schema('proofs_pending')} (
|
||||||
amount {db.big_int} NOT NULL,
|
amount {db.big_int} NOT NULL,
|
||||||
c TEXT NOT NULL,
|
c TEXT NOT NULL,
|
||||||
secret TEXT NOT NULL,
|
secret TEXT NOT NULL,
|
||||||
y TEXT,
|
y TEXT,
|
||||||
id TEXT,
|
id TEXT,
|
||||||
created TIMESTAMP,
|
created TIMESTAMP DEFAULT {db.timestamp_now},
|
||||||
|
|
||||||
UNIQUE (Y)
|
UNIQUE (Y)
|
||||||
|
|
||||||
@@ -523,12 +523,12 @@ async def m014_proofs_add_Y_column(db: Database):
|
|||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"INSERT INTO {table_with_schema(db, 'proofs_pending')} (amount, c, "
|
f"INSERT INTO {db.table_with_schema('proofs_pending')} (amount, c, "
|
||||||
"secret, y, id, created) SELECT amount, c, secret, y, id, created"
|
"secret, y, id, created) SELECT amount, c, secret, y, id, created"
|
||||||
f" FROM {table_with_schema(db, 'proofs_pending_old')}"
|
f" FROM {db.table_with_schema('proofs_pending_old')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
await conn.execute(f"DROP TABLE {table_with_schema(db, 'proofs_pending_old')}")
|
await conn.execute(f"DROP TABLE {db.table_with_schema('proofs_pending_old')}")
|
||||||
|
|
||||||
# recreate the balance views
|
# recreate the balance views
|
||||||
await create_balance_views(db, conn)
|
await create_balance_views(db, conn)
|
||||||
@@ -540,13 +540,13 @@ async def m015_add_index_Y_to_proofs_used_and_pending(db: Database):
|
|||||||
await conn.execute(
|
await conn.execute(
|
||||||
"CREATE INDEX IF NOT EXISTS"
|
"CREATE INDEX IF NOT EXISTS"
|
||||||
" proofs_used_Y_idx ON"
|
" proofs_used_Y_idx ON"
|
||||||
f" {table_with_schema(db, 'proofs_used')} (Y)"
|
f" {db.table_with_schema('proofs_used')} (Y)"
|
||||||
)
|
)
|
||||||
|
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
"CREATE INDEX IF NOT EXISTS"
|
"CREATE INDEX IF NOT EXISTS"
|
||||||
" proofs_pending_Y_idx ON"
|
" proofs_pending_Y_idx ON"
|
||||||
f" {table_with_schema(db, 'proofs_pending')} (Y)"
|
f" {db.table_with_schema('proofs_pending')} (Y)"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -554,13 +554,13 @@ async def m016_recompute_Y_with_new_h2c(db: Database):
|
|||||||
# get all proofs_used and proofs_pending from the database and compute Y for each of them
|
# get all proofs_used and proofs_pending from the database and compute Y for each of them
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
rows = await conn.fetchall(
|
rows = await conn.fetchall(
|
||||||
f"SELECT * FROM {table_with_schema(db, 'proofs_used')}"
|
f"SELECT * FROM {db.table_with_schema('proofs_used')}"
|
||||||
)
|
)
|
||||||
# Proof() will compute Y from secret upon initialization
|
# Proof() will compute Y from secret upon initialization
|
||||||
proofs_used = [Proof(**r) for r in rows]
|
proofs_used = [Proof(**r) for r in rows]
|
||||||
|
async with db.connect() as conn:
|
||||||
rows = await conn.fetchall(
|
rows = await conn.fetchall(
|
||||||
f"SELECT * FROM {table_with_schema(db, 'proofs_pending')}"
|
f"SELECT * FROM {db.table_with_schema('proofs_pending')}"
|
||||||
)
|
)
|
||||||
proofs_pending = [Proof(**r) for r in rows]
|
proofs_pending = [Proof(**r) for r in rows]
|
||||||
|
|
||||||
@@ -583,10 +583,10 @@ async def m016_recompute_Y_with_new_h2c(db: Database):
|
|||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
UPDATE {table_with_schema(db, 'proofs_used')}
|
UPDATE {db.table_with_schema('proofs_used')}
|
||||||
SET y = tmp_proofs_used.y
|
SET y = tmp_proofs_used.y
|
||||||
FROM tmp_proofs_used
|
FROM tmp_proofs_used
|
||||||
WHERE {table_with_schema(db, 'proofs_used')}.secret = tmp_proofs_used.secret
|
WHERE {db.table_with_schema('proofs_used')}.secret = tmp_proofs_used.secret
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -603,10 +603,10 @@ async def m016_recompute_Y_with_new_h2c(db: Database):
|
|||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
UPDATE {table_with_schema(db, 'proofs_pending')}
|
UPDATE {db.table_with_schema('proofs_pending')}
|
||||||
SET y = tmp_proofs_pending.y
|
SET y = tmp_proofs_pending.y
|
||||||
FROM tmp_proofs_pending
|
FROM tmp_proofs_pending
|
||||||
WHERE {table_with_schema(db, 'proofs_pending')}.secret = tmp_proofs_pending.secret
|
WHERE {db.table_with_schema('proofs_pending')}.secret = tmp_proofs_pending.secret
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -636,7 +636,7 @@ async def m017_foreign_keys_proof_tables(db: Database):
|
|||||||
# add foreign key constraints to proofs_used table
|
# add foreign key constraints to proofs_used table
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_used_new')} (
|
CREATE TABLE IF NOT EXISTS {db.table_with_schema('proofs_used_new')} (
|
||||||
amount {db.big_int} NOT NULL,
|
amount {db.big_int} NOT NULL,
|
||||||
id TEXT,
|
id TEXT,
|
||||||
c TEXT NOT NULL,
|
c TEXT NOT NULL,
|
||||||
@@ -646,24 +646,24 @@ async def m017_foreign_keys_proof_tables(db: Database):
|
|||||||
created TIMESTAMP,
|
created TIMESTAMP,
|
||||||
melt_quote TEXT,
|
melt_quote TEXT,
|
||||||
|
|
||||||
FOREIGN KEY (melt_quote) REFERENCES {table_with_schema(db, 'melt_quotes')}(quote),
|
FOREIGN KEY (melt_quote) REFERENCES {db.table_with_schema('melt_quotes')}(quote),
|
||||||
|
|
||||||
UNIQUE (y)
|
UNIQUE (y)
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"INSERT INTO {table_with_schema(db, 'proofs_used_new')} (amount, id, c, secret, y, witness, created) SELECT amount, id, c, secret, y, witness, created FROM {table_with_schema(db, 'proofs_used')}"
|
f"INSERT INTO {db.table_with_schema('proofs_used_new')} (amount, id, c, secret, y, witness, created) SELECT amount, id, c, secret, y, witness, created FROM {db.table_with_schema('proofs_used')}"
|
||||||
)
|
)
|
||||||
await conn.execute(f"DROP TABLE {table_with_schema(db, 'proofs_used')}")
|
await conn.execute(f"DROP TABLE {db.table_with_schema('proofs_used')}")
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'proofs_used_new')} RENAME TO {table_with_schema(db, 'proofs_used')}"
|
f"ALTER TABLE {db.table_with_schema('proofs_used_new')} RENAME TO {db.table_with_schema('proofs_used')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# add foreign key constraints to proofs_pending table
|
# add foreign key constraints to proofs_pending table
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'proofs_pending_new')} (
|
CREATE TABLE IF NOT EXISTS {db.table_with_schema('proofs_pending_new')} (
|
||||||
amount {db.big_int} NOT NULL,
|
amount {db.big_int} NOT NULL,
|
||||||
id TEXT,
|
id TEXT,
|
||||||
c TEXT NOT NULL,
|
c TEXT NOT NULL,
|
||||||
@@ -673,24 +673,24 @@ async def m017_foreign_keys_proof_tables(db: Database):
|
|||||||
created TIMESTAMP,
|
created TIMESTAMP,
|
||||||
melt_quote TEXT,
|
melt_quote TEXT,
|
||||||
|
|
||||||
FOREIGN KEY (melt_quote) REFERENCES {table_with_schema(db, 'melt_quotes')}(quote),
|
FOREIGN KEY (melt_quote) REFERENCES {db.table_with_schema('melt_quotes')}(quote),
|
||||||
|
|
||||||
UNIQUE (y)
|
UNIQUE (y)
|
||||||
);
|
);
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"INSERT INTO {table_with_schema(db, 'proofs_pending_new')} (amount, id, c, secret, y, created) SELECT amount, id, c, secret, y, created FROM {table_with_schema(db, 'proofs_pending')}"
|
f"INSERT INTO {db.table_with_schema('proofs_pending_new')} (amount, id, c, secret, y, created) SELECT amount, id, c, secret, y, created FROM {db.table_with_schema('proofs_pending')}"
|
||||||
)
|
)
|
||||||
await conn.execute(f"DROP TABLE {table_with_schema(db, 'proofs_pending')}")
|
await conn.execute(f"DROP TABLE {db.table_with_schema('proofs_pending')}")
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'proofs_pending_new')} RENAME TO {table_with_schema(db, 'proofs_pending')}"
|
f"ALTER TABLE {db.table_with_schema('proofs_pending_new')} RENAME TO {db.table_with_schema('proofs_pending')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# add foreign key constraints to promises table
|
# add foreign key constraints to promises table
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
CREATE TABLE IF NOT EXISTS {table_with_schema(db, 'promises_new')} (
|
CREATE TABLE IF NOT EXISTS {db.table_with_schema('promises_new')} (
|
||||||
amount {db.big_int} NOT NULL,
|
amount {db.big_int} NOT NULL,
|
||||||
id TEXT,
|
id TEXT,
|
||||||
b_ TEXT NOT NULL,
|
b_ TEXT NOT NULL,
|
||||||
@@ -701,7 +701,7 @@ async def m017_foreign_keys_proof_tables(db: Database):
|
|||||||
mint_quote TEXT,
|
mint_quote TEXT,
|
||||||
swap_id TEXT,
|
swap_id TEXT,
|
||||||
|
|
||||||
FOREIGN KEY (mint_quote) REFERENCES {table_with_schema(db, 'mint_quotes')}(quote),
|
FOREIGN KEY (mint_quote) REFERENCES {db.table_with_schema('mint_quotes')}(quote),
|
||||||
|
|
||||||
UNIQUE (b_)
|
UNIQUE (b_)
|
||||||
);
|
);
|
||||||
@@ -709,11 +709,11 @@ async def m017_foreign_keys_proof_tables(db: Database):
|
|||||||
)
|
)
|
||||||
|
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"INSERT INTO {table_with_schema(db, 'promises_new')} (amount, id, b_, c_, dleq_e, dleq_s, created) SELECT amount, id, b_b, c_b, e, s, created FROM {table_with_schema(db, 'promises')}"
|
f"INSERT INTO {db.table_with_schema('promises_new')} (amount, id, b_, c_, dleq_e, dleq_s, created) SELECT amount, id, b_b, c_b, e, s, created FROM {db.table_with_schema('promises')}"
|
||||||
)
|
)
|
||||||
await conn.execute(f"DROP TABLE {table_with_schema(db, 'promises')}")
|
await conn.execute(f"DROP TABLE {db.table_with_schema('promises')}")
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'promises_new')} RENAME TO {table_with_schema(db, 'promises')}"
|
f"ALTER TABLE {db.table_with_schema('promises_new')} RENAME TO {db.table_with_schema('promises')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# recreate the balance views
|
# recreate the balance views
|
||||||
@@ -727,7 +727,7 @@ async def m018_duplicate_deprecated_keyset_ids(db: Database):
|
|||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
rows = await conn.fetchall( # type: ignore
|
rows = await conn.fetchall( # type: ignore
|
||||||
f"""
|
f"""
|
||||||
SELECT * from {table_with_schema(db, 'keysets')}
|
SELECT * from {db.table_with_schema('keysets')}
|
||||||
""",
|
""",
|
||||||
)
|
)
|
||||||
keysets = [MintKeyset(**row) for row in rows]
|
keysets = [MintKeyset(**row) for row in rows]
|
||||||
@@ -745,43 +745,43 @@ async def m018_duplicate_deprecated_keyset_ids(db: Database):
|
|||||||
for keyset in duplicated_keysets:
|
for keyset in duplicated_keysets:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"""
|
f"""
|
||||||
INSERT INTO {table_with_schema(db, 'keysets')}
|
INSERT INTO {db.table_with_schema('keysets')}
|
||||||
(id, derivation_path, valid_from, valid_to, first_seen, active, version, seed, unit, encrypted_seed, seed_encryption_method)
|
(id, derivation_path, valid_from, valid_to, first_seen, active, version, seed, unit, encrypted_seed, seed_encryption_method)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (:id, :derivation_path, :valid_from, :valid_to, :first_seen, :active, :version, :seed, :unit, :encrypted_seed, :seed_encryption_method)
|
||||||
""",
|
""",
|
||||||
(
|
{
|
||||||
keyset.id,
|
"id": keyset.id,
|
||||||
keyset.derivation_path,
|
"derivation_path": keyset.derivation_path,
|
||||||
keyset.valid_from,
|
"valid_from": keyset.valid_from,
|
||||||
keyset.valid_to,
|
"valid_to": keyset.valid_to,
|
||||||
keyset.first_seen,
|
"first_seen": keyset.first_seen,
|
||||||
keyset.active,
|
"active": keyset.active,
|
||||||
keyset.version,
|
"version": keyset.version,
|
||||||
keyset.seed,
|
"seed": keyset.seed,
|
||||||
keyset.unit.name,
|
"unit": keyset.unit.name,
|
||||||
keyset.encrypted_seed,
|
"encrypted_seed": keyset.encrypted_seed,
|
||||||
keyset.seed_encryption_method,
|
"seed_encryption_method": keyset.seed_encryption_method,
|
||||||
),
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def m019_add_fee_to_keysets(db: Database):
|
async def m019_add_fee_to_keysets(db: Database):
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'keysets')} ADD COLUMN input_fee_ppk INTEGER"
|
f"ALTER TABLE {db.table_with_schema('keysets')} ADD COLUMN input_fee_ppk INTEGER"
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"UPDATE {table_with_schema(db, 'keysets')} SET input_fee_ppk = 0"
|
f"UPDATE {db.table_with_schema('keysets')} SET input_fee_ppk = 0"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def m020_add_state_to_mint_and_melt_quotes(db: Database):
|
async def m020_add_state_to_mint_and_melt_quotes(db: Database):
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'mint_quotes')} ADD COLUMN state TEXT"
|
f"ALTER TABLE {db.table_with_schema('mint_quotes')} ADD COLUMN state TEXT"
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'melt_quotes')} ADD COLUMN state TEXT"
|
f"ALTER TABLE {db.table_with_schema('melt_quotes')} ADD COLUMN state TEXT"
|
||||||
)
|
)
|
||||||
|
|
||||||
# get all melt and mint quotes and figure out the state to set using the `paid` column
|
# get all melt and mint quotes and figure out the state to set using the `paid` column
|
||||||
@@ -789,7 +789,7 @@ async def m020_add_state_to_mint_and_melt_quotes(db: Database):
|
|||||||
# mint quotes:
|
# mint quotes:
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
rows = await conn.fetchall(
|
rows = await conn.fetchall(
|
||||||
f"SELECT * FROM {table_with_schema(db, 'mint_quotes')}"
|
f"SELECT * FROM {db.table_with_schema('mint_quotes')}"
|
||||||
)
|
)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if row["issued"]:
|
if row["issued"]:
|
||||||
@@ -799,13 +799,13 @@ async def m020_add_state_to_mint_and_melt_quotes(db: Database):
|
|||||||
else:
|
else:
|
||||||
state = "unpaid"
|
state = "unpaid"
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"UPDATE {table_with_schema(db, 'mint_quotes')} SET state = '{state}' WHERE quote = '{row['quote']}'"
|
f"UPDATE {db.table_with_schema('mint_quotes')} SET state = '{state}' WHERE quote = '{row['quote']}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
# melt quotes:
|
# melt quotes:
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
rows = await conn.fetchall(
|
rows = await conn.fetchall(
|
||||||
f"SELECT * FROM {table_with_schema(db, 'melt_quotes')}"
|
f"SELECT * FROM {db.table_with_schema('melt_quotes')}"
|
||||||
)
|
)
|
||||||
for row in rows:
|
for row in rows:
|
||||||
if row["paid"]:
|
if row["paid"]:
|
||||||
@@ -813,15 +813,15 @@ async def m020_add_state_to_mint_and_melt_quotes(db: Database):
|
|||||||
else:
|
else:
|
||||||
state = "unpaid"
|
state = "unpaid"
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"UPDATE {table_with_schema(db, 'melt_quotes')} SET state = '{state}' WHERE quote = '{row['quote']}'"
|
f"UPDATE {db.table_with_schema('melt_quotes')} SET state = '{state}' WHERE quote = '{row['quote']}'"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def m021_add_change_and_expiry_to_melt_quotes(db: Database):
|
async def m021_add_change_and_expiry_to_melt_quotes(db: Database):
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'melt_quotes')} ADD COLUMN change TEXT"
|
f"ALTER TABLE {db.table_with_schema('melt_quotes')} ADD COLUMN change TEXT"
|
||||||
)
|
)
|
||||||
await conn.execute(
|
await conn.execute(
|
||||||
f"ALTER TABLE {table_with_schema(db, 'melt_quotes')} ADD COLUMN expiry TIMESTAMP"
|
f"ALTER TABLE {db.table_with_schema('melt_quotes')} ADD COLUMN expiry TIMESTAMP"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -98,3 +98,9 @@ async def start_mint_init():
|
|||||||
await ledger.startup_ledger()
|
await ledger.startup_ledger()
|
||||||
logger.info("Mint started.")
|
logger.info("Mint started.")
|
||||||
# asyncio.create_task(rotate_keys())
|
# asyncio.create_task(rotate_keys())
|
||||||
|
|
||||||
|
|
||||||
|
async def shutdown_mint():
|
||||||
|
await ledger.shutdown_ledger()
|
||||||
|
logger.info("Mint shutdown.")
|
||||||
|
logger.remove()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from typing import Mapping
|
from typing import List, Mapping
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
@@ -17,13 +17,15 @@ class LedgerTasks(SupportsDb, SupportsBackends, SupportsEvents):
|
|||||||
crud: LedgerCrud
|
crud: LedgerCrud
|
||||||
events: LedgerEventManager
|
events: LedgerEventManager
|
||||||
|
|
||||||
async def dispatch_listeners(self) -> None:
|
async def dispatch_listeners(self) -> List[asyncio.Task]:
|
||||||
|
tasks = []
|
||||||
for method, unitbackends in self.backends.items():
|
for method, unitbackends in self.backends.items():
|
||||||
for unit, backend in unitbackends.items():
|
for unit, backend in unitbackends.items():
|
||||||
logger.debug(
|
logger.debug(
|
||||||
f"Dispatching backend invoice listener for {method} {unit} {backend.__class__.__name__}"
|
f"Dispatching backend invoice listener for {method} {unit} {backend.__class__.__name__}"
|
||||||
)
|
)
|
||||||
asyncio.create_task(self.invoice_listener(backend))
|
tasks.append(asyncio.create_task(self.invoice_listener(backend)))
|
||||||
|
return tasks
|
||||||
|
|
||||||
async def invoice_listener(self, backend: LightningBackend) -> None:
|
async def invoice_listener(self, backend: LightningBackend) -> None:
|
||||||
if backend.supports_incoming_payment_stream:
|
if backend.supports_incoming_payment_stream:
|
||||||
@@ -38,15 +40,28 @@ class LedgerTasks(SupportsDb, SupportsBackends, SupportsEvents):
|
|||||||
|
|
||||||
async def invoice_callback_dispatcher(self, checking_id: str) -> None:
|
async def invoice_callback_dispatcher(self, checking_id: str) -> None:
|
||||||
logger.debug(f"Invoice callback dispatcher: {checking_id}")
|
logger.debug(f"Invoice callback dispatcher: {checking_id}")
|
||||||
# TODO: db read, quote.paid = True, db write should be refactored and moved to ledger.py
|
async with self.db.get_connection(
|
||||||
quote = await self.crud.get_mint_quote(checking_id=checking_id, db=self.db)
|
lock_table="mint_quotes",
|
||||||
if not quote:
|
lock_select_statement=f"checking_id='{checking_id}'",
|
||||||
logger.error(f"Quote not found for {checking_id}")
|
lock_timeout=5,
|
||||||
return
|
) as conn:
|
||||||
# set the quote as paid
|
quote = await self.crud.get_mint_quote(
|
||||||
if not quote.paid:
|
checking_id=checking_id, db=self.db, conn=conn
|
||||||
quote.paid = True
|
)
|
||||||
quote.state = MintQuoteState.paid
|
if not quote:
|
||||||
await self.crud.update_mint_quote(quote=quote, db=self.db)
|
logger.error(f"Quote not found for {checking_id}")
|
||||||
logger.trace(f"Quote {quote} set as paid and ")
|
return
|
||||||
|
|
||||||
|
logger.trace(
|
||||||
|
f"Invoice callback dispatcher: quote {quote} trying to set as {MintQuoteState.paid}"
|
||||||
|
)
|
||||||
|
# set the quote as paid
|
||||||
|
if quote.state == MintQuoteState.unpaid:
|
||||||
|
quote.paid = True
|
||||||
|
quote.state = MintQuoteState.paid
|
||||||
|
await self.crud.update_mint_quote(quote=quote, db=self.db, conn=conn)
|
||||||
|
logger.trace(
|
||||||
|
f"Quote {quote.quote} with {MintQuoteState.unpaid} set as {quote.state.value}"
|
||||||
|
)
|
||||||
|
|
||||||
await self.events.submit(quote)
|
await self.events.submit(quote)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from ..core.base import (
|
|||||||
)
|
)
|
||||||
from ..core.crypto import b_dhke
|
from ..core.crypto import b_dhke
|
||||||
from ..core.crypto.secp import PublicKey
|
from ..core.crypto.secp import PublicKey
|
||||||
from ..core.db import Database
|
from ..core.db import Connection, Database
|
||||||
from ..core.errors import (
|
from ..core.errors import (
|
||||||
NoSecretInProofsError,
|
NoSecretInProofsError,
|
||||||
NotAllowedError,
|
NotAllowedError,
|
||||||
@@ -40,7 +40,11 @@ class LedgerVerification(
|
|||||||
lightning: Dict[Unit, LightningBackend]
|
lightning: Dict[Unit, LightningBackend]
|
||||||
|
|
||||||
async def verify_inputs_and_outputs(
|
async def verify_inputs_and_outputs(
|
||||||
self, *, proofs: List[Proof], outputs: Optional[List[BlindedMessage]] = None
|
self,
|
||||||
|
*,
|
||||||
|
proofs: List[Proof],
|
||||||
|
outputs: Optional[List[BlindedMessage]] = None,
|
||||||
|
conn: Optional[Connection] = None,
|
||||||
):
|
):
|
||||||
"""Checks all proofs and outputs for validity.
|
"""Checks all proofs and outputs for validity.
|
||||||
|
|
||||||
@@ -48,6 +52,7 @@ class LedgerVerification(
|
|||||||
proofs (List[Proof]): List of proofs to check.
|
proofs (List[Proof]): List of proofs to check.
|
||||||
outputs (Optional[List[BlindedMessage]], optional): List of outputs to check.
|
outputs (Optional[List[BlindedMessage]], optional): List of outputs to check.
|
||||||
Must be provided for a swap but not for a melt. Defaults to None.
|
Must be provided for a swap but not for a melt. Defaults to None.
|
||||||
|
conn (Optional[Connection], optional): Database connection. Defaults to None.
|
||||||
|
|
||||||
Raises:
|
Raises:
|
||||||
Exception: Scripts did not validate.
|
Exception: Scripts did not validate.
|
||||||
@@ -56,8 +61,10 @@ class LedgerVerification(
|
|||||||
Exception: BDHKE verification failed.
|
Exception: BDHKE verification failed.
|
||||||
"""
|
"""
|
||||||
# Verify inputs
|
# Verify inputs
|
||||||
|
if not proofs:
|
||||||
|
raise TransactionError("no proofs provided.")
|
||||||
# Verify proofs are spendable
|
# Verify proofs are spendable
|
||||||
if not len(await self._get_proofs_spent([p.Y for p in proofs])) == 0:
|
if not len(await self._get_proofs_spent([p.Y for p in proofs], conn)) == 0:
|
||||||
raise TokenAlreadySpentError()
|
raise TokenAlreadySpentError()
|
||||||
# Verify amounts of inputs
|
# Verify amounts of inputs
|
||||||
if not all([self._verify_amount(p.amount) for p in proofs]):
|
if not all([self._verify_amount(p.amount) for p in proofs]):
|
||||||
@@ -83,7 +90,7 @@ class LedgerVerification(
|
|||||||
self._verify_equation_balanced(proofs, outputs)
|
self._verify_equation_balanced(proofs, outputs)
|
||||||
|
|
||||||
# Verify outputs
|
# Verify outputs
|
||||||
await self._verify_outputs(outputs)
|
await self._verify_outputs(outputs, conn=conn)
|
||||||
|
|
||||||
# Verify inputs and outputs together
|
# Verify inputs and outputs together
|
||||||
if not self._verify_input_output_amounts(proofs, outputs):
|
if not self._verify_input_output_amounts(proofs, outputs):
|
||||||
@@ -104,7 +111,10 @@ class LedgerVerification(
|
|||||||
raise TransactionError("validation of output spending conditions failed.")
|
raise TransactionError("validation of output spending conditions failed.")
|
||||||
|
|
||||||
async def _verify_outputs(
|
async def _verify_outputs(
|
||||||
self, outputs: List[BlindedMessage], skip_amount_check=False
|
self,
|
||||||
|
outputs: List[BlindedMessage],
|
||||||
|
skip_amount_check=False,
|
||||||
|
conn: Optional[Connection] = None,
|
||||||
):
|
):
|
||||||
"""Verify that the outputs are valid."""
|
"""Verify that the outputs are valid."""
|
||||||
logger.trace(f"Verifying {len(outputs)} outputs.")
|
logger.trace(f"Verifying {len(outputs)} outputs.")
|
||||||
@@ -127,13 +137,15 @@ class LedgerVerification(
|
|||||||
if not self._verify_no_duplicate_outputs(outputs):
|
if not self._verify_no_duplicate_outputs(outputs):
|
||||||
raise TransactionError("duplicate outputs.")
|
raise TransactionError("duplicate outputs.")
|
||||||
# verify that outputs have not been signed previously
|
# verify that outputs have not been signed previously
|
||||||
signed_before = await self._check_outputs_issued_before(outputs)
|
signed_before = await self._check_outputs_issued_before(outputs, conn)
|
||||||
if any(signed_before):
|
if any(signed_before):
|
||||||
raise TransactionError("outputs have already been signed before.")
|
raise TransactionError("outputs have already been signed before.")
|
||||||
logger.trace(f"Verified {len(outputs)} outputs.")
|
logger.trace(f"Verified {len(outputs)} outputs.")
|
||||||
|
|
||||||
async def _check_outputs_issued_before(
|
async def _check_outputs_issued_before(
|
||||||
self, outputs: List[BlindedMessage]
|
self,
|
||||||
|
outputs: List[BlindedMessage],
|
||||||
|
conn: Optional[Connection] = None,
|
||||||
) -> List[bool]:
|
) -> List[bool]:
|
||||||
"""Checks whether the provided outputs have previously been signed by the mint
|
"""Checks whether the provided outputs have previously been signed by the mint
|
||||||
(which would lead to a duplication error later when trying to store these outputs again).
|
(which would lead to a duplication error later when trying to store these outputs again).
|
||||||
@@ -145,7 +157,7 @@ class LedgerVerification(
|
|||||||
result (List[bool]): Whether outputs are already present in the database.
|
result (List[bool]): Whether outputs are already present in the database.
|
||||||
"""
|
"""
|
||||||
result = []
|
result = []
|
||||||
async with self.db.connect() as conn:
|
async with self.db.get_connection(conn) as conn:
|
||||||
for output in outputs:
|
for output in outputs:
|
||||||
promise = await self.crud.get_promise(
|
promise = await self.crud.get_promise(
|
||||||
b_=output.B_, db=self.db, conn=conn
|
b_=output.B_, db=self.db, conn=conn
|
||||||
@@ -153,21 +165,15 @@ class LedgerVerification(
|
|||||||
result.append(False if promise is None else True)
|
result.append(False if promise is None else True)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
async def _get_proofs_pending(self, Ys: List[str]) -> Dict[str, Proof]:
|
async def _get_proofs_spent(
|
||||||
"""Returns a dictionary of only those proofs that are pending.
|
self, Ys: List[str], conn: Optional[Connection] = None
|
||||||
The key is the Y=h2c(secret) and the value is the proof.
|
) -> Dict[str, Proof]:
|
||||||
"""
|
|
||||||
proofs_pending = await self.crud.get_proofs_pending(Ys=Ys, db=self.db)
|
|
||||||
proofs_pending_dict = {p.Y: p for p in proofs_pending}
|
|
||||||
return proofs_pending_dict
|
|
||||||
|
|
||||||
async def _get_proofs_spent(self, Ys: List[str]) -> Dict[str, Proof]:
|
|
||||||
"""Returns a dictionary of all proofs that are spent.
|
"""Returns a dictionary of all proofs that are spent.
|
||||||
The key is the Y=h2c(secret) and the value is the proof.
|
The key is the Y=h2c(secret) and the value is the proof.
|
||||||
"""
|
"""
|
||||||
proofs_spent_dict: Dict[str, Proof] = {}
|
proofs_spent_dict: Dict[str, Proof] = {}
|
||||||
# check used secrets in database
|
# check used secrets in database
|
||||||
async with self.db.connect() as conn:
|
async with self.db.get_connection(conn=conn) as conn:
|
||||||
for Y in Ys:
|
for Y in Ys:
|
||||||
spent_proof = await self.crud.get_proof_used(db=self.db, Y=Y, conn=conn)
|
spent_proof = await self.crud.get_proof_used(db=self.db, Y=Y, conn=conn)
|
||||||
if spent_proof:
|
if spent_proof:
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
from typing import Any, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from ..core.base import Invoice, Proof, WalletKeyset
|
from ..core.base import Invoice, Proof, WalletKeyset
|
||||||
from ..core.db import Connection, Database
|
from ..core.db import Connection, Database
|
||||||
@@ -15,19 +15,19 @@ async def store_proof(
|
|||||||
"""
|
"""
|
||||||
INSERT INTO proofs
|
INSERT INTO proofs
|
||||||
(id, amount, C, secret, time_created, derivation_path, dleq, mint_id, melt_id)
|
(id, amount, C, secret, time_created, derivation_path, dleq, mint_id, melt_id)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (:id, :amount, :C, :secret, :time_created, :derivation_path, :dleq, :mint_id, :melt_id)
|
||||||
""",
|
""",
|
||||||
(
|
{
|
||||||
proof.id,
|
"id": proof.id,
|
||||||
proof.amount,
|
"amount": proof.amount,
|
||||||
str(proof.C),
|
"C": str(proof.C),
|
||||||
str(proof.secret),
|
"secret": str(proof.secret),
|
||||||
int(time.time()),
|
"time_created": int(time.time()),
|
||||||
proof.derivation_path,
|
"derivation_path": proof.derivation_path,
|
||||||
json.dumps(proof.dleq.dict()) if proof.dleq else "",
|
"dleq": json.dumps(proof.dleq.dict()) if proof.dleq else "",
|
||||||
proof.mint_id,
|
"mint_id": proof.mint_id,
|
||||||
proof.melt_id,
|
"melt_id": proof.melt_id,
|
||||||
),
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -41,30 +41,28 @@ async def get_proofs(
|
|||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
):
|
):
|
||||||
clauses = []
|
clauses = []
|
||||||
values: List[Any] = []
|
values: Dict[str, Any] = {}
|
||||||
|
|
||||||
if id:
|
if id:
|
||||||
clauses.append("id = ?")
|
clauses.append("id = :id")
|
||||||
values.append(id)
|
values["id"] = id
|
||||||
if melt_id:
|
if melt_id:
|
||||||
clauses.append("melt_id = ?")
|
clauses.append("melt_id = :melt_id")
|
||||||
values.append(melt_id)
|
values["melt_id"] = melt_id
|
||||||
if mint_id:
|
if mint_id:
|
||||||
clauses.append("mint_id = ?")
|
clauses.append("mint_id = :mint_id")
|
||||||
values.append(mint_id)
|
values["mint_id"] = mint_id
|
||||||
where = ""
|
where = ""
|
||||||
if clauses:
|
if clauses:
|
||||||
where = f"WHERE {' AND '.join(clauses)}"
|
where = f"WHERE {' AND '.join(clauses)}"
|
||||||
rows = (
|
rows = await (conn or db).fetchall(
|
||||||
await (conn or db).fetchall(
|
f"""
|
||||||
f"""
|
SELECT * from {table}
|
||||||
SELECT * from {table}
|
{where}
|
||||||
{where}
|
""",
|
||||||
""",
|
values,
|
||||||
tuple(values),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
return [Proof.from_dict(dict(r)) for r in rows[0]] if rows else []
|
return [Proof.from_dict(dict(r)) for r in rows] if rows else []
|
||||||
|
|
||||||
|
|
||||||
async def get_reserved_proofs(
|
async def get_reserved_proofs(
|
||||||
@@ -88,27 +86,27 @@ async def invalidate_proof(
|
|||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
"""
|
||||||
DELETE FROM proofs
|
DELETE FROM proofs
|
||||||
WHERE secret = ?
|
WHERE secret = :secret
|
||||||
""",
|
""",
|
||||||
(str(proof["secret"]),),
|
{"secret": str(proof["secret"])},
|
||||||
)
|
)
|
||||||
|
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
"""
|
||||||
INSERT INTO proofs_used
|
INSERT INTO proofs_used
|
||||||
(amount, C, secret, time_used, id, derivation_path, mint_id, melt_id)
|
(amount, C, secret, time_used, id, derivation_path, mint_id, melt_id)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (:amount, :C, :secret, :time_used, :id, :derivation_path, :mint_id, :melt_id)
|
||||||
""",
|
""",
|
||||||
(
|
{
|
||||||
proof.amount,
|
"amount": proof.amount,
|
||||||
str(proof.C),
|
"C": str(proof.C),
|
||||||
str(proof.secret),
|
"secret": str(proof.secret),
|
||||||
int(time.time()),
|
"time_used": int(time.time()),
|
||||||
proof.id,
|
"id": proof.id,
|
||||||
proof.derivation_path,
|
"derivation_path": proof.derivation_path,
|
||||||
proof.mint_id,
|
"mint_id": proof.mint_id,
|
||||||
proof.melt_id,
|
"melt_id": proof.melt_id,
|
||||||
),
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -123,29 +121,29 @@ async def update_proof(
|
|||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
clauses = []
|
clauses = []
|
||||||
values: List[Any] = []
|
values: Dict[str, Any] = {}
|
||||||
clauses.append("reserved = ?")
|
|
||||||
values.append(reserved)
|
|
||||||
|
|
||||||
if send_id is not None:
|
|
||||||
clauses.append("send_id = ?")
|
|
||||||
values.append(send_id)
|
|
||||||
|
|
||||||
if reserved is not None:
|
if reserved is not None:
|
||||||
clauses.append("time_reserved = ?")
|
clauses.append("reserved = :reserved")
|
||||||
values.append(int(time.time()))
|
values["reserved"] = reserved
|
||||||
|
clauses.append("time_reserved = :time_reserved")
|
||||||
|
values["time_reserved"] = int(time.time())
|
||||||
|
|
||||||
|
if send_id is not None:
|
||||||
|
clauses.append("send_id = :send_id")
|
||||||
|
values["send_id"] = send_id
|
||||||
|
|
||||||
if mint_id is not None:
|
if mint_id is not None:
|
||||||
clauses.append("mint_id = ?")
|
clauses.append("mint_id = :mint_id")
|
||||||
values.append(mint_id)
|
values["mint_id"] = mint_id
|
||||||
|
|
||||||
if melt_id is not None:
|
if melt_id is not None:
|
||||||
clauses.append("melt_id = ?")
|
clauses.append("melt_id = :melt_id")
|
||||||
values.append(melt_id)
|
values["melt_id"] = melt_id
|
||||||
|
|
||||||
await (conn or db).execute( # type: ignore
|
await (conn or db).execute( # type: ignore
|
||||||
f"UPDATE proofs SET {', '.join(clauses)} WHERE secret = ?",
|
f"UPDATE proofs SET {', '.join(clauses)} WHERE secret = :secret",
|
||||||
(*values, str(proof.secret)),
|
{**values, "secret": str(proof.secret)},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -157,9 +155,9 @@ async def secret_used(
|
|||||||
rows = await (conn or db).fetchone(
|
rows = await (conn or db).fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT * from proofs
|
SELECT * from proofs
|
||||||
WHERE secret = ?
|
WHERE secret = :secret
|
||||||
""",
|
""",
|
||||||
(secret,),
|
{"secret": secret},
|
||||||
)
|
)
|
||||||
return rows is not None
|
return rows is not None
|
||||||
|
|
||||||
@@ -174,19 +172,19 @@ async def store_keyset(
|
|||||||
"""
|
"""
|
||||||
INSERT INTO keysets
|
INSERT INTO keysets
|
||||||
(id, mint_url, valid_from, valid_to, first_seen, active, public_keys, unit, input_fee_ppk)
|
(id, mint_url, valid_from, valid_to, first_seen, active, public_keys, unit, input_fee_ppk)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (:id, :mint_url, :valid_from, :valid_to, :first_seen, :active, :public_keys, :unit, :input_fee_ppk)
|
||||||
""",
|
""",
|
||||||
(
|
{
|
||||||
keyset.id,
|
"id": keyset.id,
|
||||||
mint_url or keyset.mint_url,
|
"mint_url": mint_url or keyset.mint_url,
|
||||||
keyset.valid_from or int(time.time()),
|
"valid_from": keyset.valid_from or int(time.time()),
|
||||||
keyset.valid_to or int(time.time()),
|
"valid_to": keyset.valid_to or int(time.time()),
|
||||||
keyset.first_seen or int(time.time()),
|
"first_seen": keyset.first_seen or int(time.time()),
|
||||||
keyset.active,
|
"active": keyset.active,
|
||||||
keyset.serialize(),
|
"public_keys": keyset.serialize(),
|
||||||
keyset.unit.name,
|
"unit": keyset.unit.name,
|
||||||
keyset.input_fee_ppk,
|
"input_fee_ppk": keyset.input_fee_ppk,
|
||||||
),
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -198,32 +196,28 @@ async def get_keysets(
|
|||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> List[WalletKeyset]:
|
) -> List[WalletKeyset]:
|
||||||
clauses = []
|
clauses = []
|
||||||
values: List[Any] = []
|
values: Dict[str, Any] = {}
|
||||||
if id:
|
if id:
|
||||||
clauses.append("id = ?")
|
clauses.append("id = :id")
|
||||||
values.append(id)
|
values["id"] = id
|
||||||
if mint_url:
|
if mint_url:
|
||||||
clauses.append("mint_url = ?")
|
clauses.append("mint_url = :mint_url")
|
||||||
values.append(mint_url)
|
values["mint_url"] = mint_url
|
||||||
if unit:
|
if unit:
|
||||||
clauses.append("unit = ?")
|
clauses.append("unit = :unit")
|
||||||
values.append(unit)
|
values["unit"] = unit
|
||||||
where = ""
|
where = ""
|
||||||
if clauses:
|
if clauses:
|
||||||
where = f"WHERE {' AND '.join(clauses)}"
|
where = f"WHERE {' AND '.join(clauses)}"
|
||||||
|
|
||||||
row = await (conn or db).fetchall( # type: ignore
|
rows = await (conn or db).fetchall( # type: ignore
|
||||||
f"""
|
f"""
|
||||||
SELECT * from keysets
|
SELECT * from keysets
|
||||||
{where}
|
{where}
|
||||||
""",
|
""",
|
||||||
tuple(values),
|
values,
|
||||||
)
|
)
|
||||||
ret = []
|
return [WalletKeyset.from_row(r) for r in rows]
|
||||||
for r in row:
|
|
||||||
keyset = WalletKeyset.from_row(r)
|
|
||||||
ret.append(keyset)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
|
|
||||||
async def update_keyset(
|
async def update_keyset(
|
||||||
@@ -234,13 +228,13 @@ async def update_keyset(
|
|||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"""
|
"""
|
||||||
UPDATE keysets
|
UPDATE keysets
|
||||||
SET active = ?
|
SET active = :active
|
||||||
WHERE id = ?
|
WHERE id = :id
|
||||||
""",
|
""",
|
||||||
(
|
{
|
||||||
keyset.active,
|
"active": keyset.active,
|
||||||
keyset.id,
|
"id": keyset.id,
|
||||||
),
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -253,19 +247,19 @@ async def store_lightning_invoice(
|
|||||||
"""
|
"""
|
||||||
INSERT INTO invoices
|
INSERT INTO invoices
|
||||||
(amount, bolt11, id, payment_hash, preimage, paid, time_created, time_paid, out)
|
(amount, bolt11, id, payment_hash, preimage, paid, time_created, time_paid, out)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
VALUES (:amount, :bolt11, :id, :payment_hash, :preimage, :paid, :time_created, :time_paid, :out)
|
||||||
""",
|
""",
|
||||||
(
|
{
|
||||||
invoice.amount,
|
"amount": invoice.amount,
|
||||||
invoice.bolt11,
|
"bolt11": invoice.bolt11,
|
||||||
invoice.id,
|
"id": invoice.id,
|
||||||
invoice.payment_hash,
|
"payment_hash": invoice.payment_hash,
|
||||||
invoice.preimage,
|
"preimage": invoice.preimage,
|
||||||
invoice.paid,
|
"paid": invoice.paid,
|
||||||
invoice.time_created,
|
"time_created": invoice.time_created,
|
||||||
invoice.time_paid,
|
"time_paid": invoice.time_paid,
|
||||||
invoice.out,
|
"out": invoice.out,
|
||||||
),
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -278,16 +272,16 @@ async def get_lightning_invoice(
|
|||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> Optional[Invoice]:
|
) -> Optional[Invoice]:
|
||||||
clauses = []
|
clauses = []
|
||||||
values: List[Any] = []
|
values: Dict[str, Any] = {}
|
||||||
if id:
|
if id:
|
||||||
clauses.append("id = ?")
|
clauses.append("id = :id")
|
||||||
values.append(id)
|
values["id"] = id
|
||||||
if payment_hash:
|
if payment_hash:
|
||||||
clauses.append("payment_hash = ?")
|
clauses.append("payment_hash = :payment_hash")
|
||||||
values.append(payment_hash)
|
values["payment_hash"] = payment_hash
|
||||||
if out is not None:
|
if out is not None:
|
||||||
clauses.append("out = ?")
|
clauses.append("out = :out")
|
||||||
values.append(out)
|
values["out"] = out
|
||||||
|
|
||||||
where = ""
|
where = ""
|
||||||
if clauses:
|
if clauses:
|
||||||
@@ -298,7 +292,7 @@ async def get_lightning_invoice(
|
|||||||
"""
|
"""
|
||||||
row = await (conn or db).fetchone(
|
row = await (conn or db).fetchone(
|
||||||
query,
|
query,
|
||||||
tuple(values),
|
values,
|
||||||
)
|
)
|
||||||
return Invoice(**row) if row else None
|
return Invoice(**row) if row else None
|
||||||
|
|
||||||
@@ -309,18 +303,18 @@ async def get_lightning_invoices(
|
|||||||
pending: Optional[bool] = None,
|
pending: Optional[bool] = None,
|
||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> List[Invoice]:
|
) -> List[Invoice]:
|
||||||
clauses: List[Any] = []
|
clauses = []
|
||||||
values: List[Any] = []
|
values: Dict[str, Any] = {}
|
||||||
|
|
||||||
if paid is not None and not pending:
|
if paid is not None and not pending:
|
||||||
clauses.append("paid = ?")
|
clauses.append("paid = :paid")
|
||||||
values.append(paid)
|
values["paid"] = paid
|
||||||
|
|
||||||
if pending:
|
if pending:
|
||||||
clauses.append("paid = ?")
|
clauses.append("paid = :paid")
|
||||||
values.append(False)
|
values["paid"] = False
|
||||||
clauses.append("out = ?")
|
clauses.append("out = :out")
|
||||||
values.append(False)
|
values["out"] = False
|
||||||
|
|
||||||
where = ""
|
where = ""
|
||||||
if clauses:
|
if clauses:
|
||||||
@@ -331,7 +325,7 @@ async def get_lightning_invoices(
|
|||||||
SELECT * from invoices
|
SELECT * from invoices
|
||||||
{where}
|
{where}
|
||||||
""",
|
""",
|
||||||
tuple(values),
|
values,
|
||||||
)
|
)
|
||||||
return [Invoice(**r) for r in rows]
|
return [Invoice(**r) for r in rows]
|
||||||
|
|
||||||
@@ -345,23 +339,20 @@ async def update_lightning_invoice(
|
|||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
clauses = []
|
clauses = []
|
||||||
values: List[Any] = []
|
values: Dict[str, Any] = {}
|
||||||
clauses.append("paid = ?")
|
clauses.append("paid = :paid")
|
||||||
values.append(paid)
|
values["paid"] = paid
|
||||||
|
|
||||||
if time_paid:
|
if time_paid:
|
||||||
clauses.append("time_paid = ?")
|
clauses.append("time_paid = :time_paid")
|
||||||
values.append(time_paid)
|
values["time_paid"] = time_paid
|
||||||
if preimage:
|
if preimage:
|
||||||
clauses.append("preimage = ?")
|
clauses.append("preimage = :preimage")
|
||||||
values.append(preimage)
|
values["preimage"] = preimage
|
||||||
|
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
f"UPDATE invoices SET {', '.join(clauses)} WHERE id = ?",
|
f"UPDATE invoices SET {', '.join(clauses)} WHERE id = :id",
|
||||||
(
|
{**values, "id": id},
|
||||||
*values,
|
|
||||||
id,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -373,16 +364,16 @@ async def bump_secret_derivation(
|
|||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> int:
|
) -> int:
|
||||||
rows = await (conn or db).fetchone(
|
rows = await (conn or db).fetchone(
|
||||||
"SELECT counter from keysets WHERE id = ?", (keyset_id,)
|
"SELECT counter from keysets WHERE id = :keyset_id", {"keyset_id": keyset_id}
|
||||||
)
|
)
|
||||||
# if no counter for this keyset, create one
|
# if no counter for this keyset, create one
|
||||||
if not rows:
|
if not rows:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"UPDATE keysets SET counter = ? WHERE id = ?",
|
"UPDATE keysets SET counter = :counter WHERE id = :keyset_id",
|
||||||
(
|
{
|
||||||
0,
|
"counter": 0,
|
||||||
keyset_id,
|
"keyset_id": keyset_id,
|
||||||
),
|
},
|
||||||
)
|
)
|
||||||
counter = 0
|
counter = 0
|
||||||
else:
|
else:
|
||||||
@@ -390,8 +381,8 @@ async def bump_secret_derivation(
|
|||||||
|
|
||||||
if not skip:
|
if not skip:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
f"UPDATE keysets SET counter = counter + {by} WHERE id = ?",
|
"UPDATE keysets SET counter = counter + :by WHERE id = :keyset_id",
|
||||||
(keyset_id,),
|
{"by": by, "keyset_id": keyset_id},
|
||||||
)
|
)
|
||||||
return counter
|
return counter
|
||||||
|
|
||||||
@@ -403,11 +394,11 @@ async def set_secret_derivation(
|
|||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"UPDATE keysets SET counter = ? WHERE id = ?",
|
"UPDATE keysets SET counter = :counter WHERE id = :keyset_id",
|
||||||
(
|
{
|
||||||
counter,
|
"counter": counter,
|
||||||
keyset_id,
|
"keyset_id": keyset_id,
|
||||||
),
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -417,8 +408,8 @@ async def set_nostr_last_check_timestamp(
|
|||||||
conn: Optional[Connection] = None,
|
conn: Optional[Connection] = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
await (conn or db).execute(
|
await (conn or db).execute(
|
||||||
"UPDATE nostr SET last = ? WHERE type = ?",
|
"UPDATE nostr SET last = :last WHERE type = :type",
|
||||||
(timestamp, "dm"),
|
{"last": timestamp, "type": "dm"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -428,9 +419,9 @@ async def get_nostr_last_check_timestamp(
|
|||||||
) -> Optional[int]:
|
) -> Optional[int]:
|
||||||
row = await (conn or db).fetchone(
|
row = await (conn or db).fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT last from nostr WHERE type = ?
|
SELECT last from nostr WHERE type = :type
|
||||||
""",
|
""",
|
||||||
("dm",),
|
{"type": "dm"},
|
||||||
)
|
)
|
||||||
return row[0] if row else None
|
return row[0] if row else None
|
||||||
|
|
||||||
@@ -442,7 +433,7 @@ async def get_seed_and_mnemonic(
|
|||||||
row = await (conn or db).fetchone(
|
row = await (conn or db).fetchone(
|
||||||
"""
|
"""
|
||||||
SELECT seed, mnemonic from seed
|
SELECT seed, mnemonic from seed
|
||||||
""",
|
"""
|
||||||
)
|
)
|
||||||
return (
|
return (
|
||||||
(
|
(
|
||||||
@@ -464,10 +455,10 @@ async def store_seed_and_mnemonic(
|
|||||||
"""
|
"""
|
||||||
INSERT INTO seed
|
INSERT INTO seed
|
||||||
(seed, mnemonic)
|
(seed, mnemonic)
|
||||||
VALUES (?, ?)
|
VALUES (:seed, :mnemonic)
|
||||||
""",
|
""",
|
||||||
(
|
{
|
||||||
seed,
|
"seed": seed,
|
||||||
mnemonic,
|
"mnemonic": mnemonic,
|
||||||
),
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -166,12 +166,12 @@ async def m007_nostr(db: Database):
|
|||||||
"""
|
"""
|
||||||
INSERT INTO nostr
|
INSERT INTO nostr
|
||||||
(type, last)
|
(type, last)
|
||||||
VALUES (?, ?)
|
VALUES (:type, :last)
|
||||||
""",
|
""",
|
||||||
(
|
{
|
||||||
"dm",
|
"type": "db",
|
||||||
None,
|
"last": None,
|
||||||
),
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -248,10 +248,10 @@ async def m012_add_fee_to_keysets(db: Database):
|
|||||||
# # async def m020_add_state_to_mint_and_melt_quotes(db: Database):
|
# # async def m020_add_state_to_mint_and_melt_quotes(db: Database):
|
||||||
# # async with db.connect() as conn:
|
# # async with db.connect() as conn:
|
||||||
# # await conn.execute(
|
# # await conn.execute(
|
||||||
# # f"ALTER TABLE {table_with_schema(db, 'mint_quotes')} ADD COLUMN state TEXT"
|
# # f"ALTER TABLE {db.table_with_schema('mint_quotes')} ADD COLUMN state TEXT"
|
||||||
# # )
|
# # )
|
||||||
# # await conn.execute(
|
# # await conn.execute(
|
||||||
# # f"ALTER TABLE {table_with_schema(db, 'melt_quotes')} ADD COLUMN state TEXT"
|
# # f"ALTER TABLE {db.table_with_schema('melt_quotes')} ADD COLUMN state TEXT"
|
||||||
# # )
|
# # )
|
||||||
|
|
||||||
# # # get all melt and mint quotes and figure out the state to set using the `paid` column
|
# # # get all melt and mint quotes and figure out the state to set using the `paid` column
|
||||||
@@ -259,7 +259,7 @@ async def m012_add_fee_to_keysets(db: Database):
|
|||||||
# # # mint quotes:
|
# # # mint quotes:
|
||||||
# # async with db.connect() as conn:
|
# # async with db.connect() as conn:
|
||||||
# # rows = await conn.fetchall(
|
# # rows = await conn.fetchall(
|
||||||
# # f"SELECT * FROM {table_with_schema(db, 'mint_quotes')}"
|
# # f"SELECT * FROM {db.table_with_schema('mint_quotes')}"
|
||||||
# # )
|
# # )
|
||||||
# # for row in rows:
|
# # for row in rows:
|
||||||
# # if row["issued"]:
|
# # if row["issued"]:
|
||||||
@@ -269,13 +269,13 @@ async def m012_add_fee_to_keysets(db: Database):
|
|||||||
# # else:
|
# # else:
|
||||||
# # state = "unpaid"
|
# # state = "unpaid"
|
||||||
# # await conn.execute(
|
# # await conn.execute(
|
||||||
# # f"UPDATE {table_with_schema(db, 'mint_quotes')} SET state = '{state}' WHERE quote = '{row['quote']}'"
|
# # f"UPDATE {db.table_with_schema('mint_quotes')} SET state = '{state}' WHERE quote = '{row['quote']}'"
|
||||||
# # )
|
# # )
|
||||||
|
|
||||||
# # # melt quotes:
|
# # # melt quotes:
|
||||||
# # async with db.connect() as conn:
|
# # async with db.connect() as conn:
|
||||||
# # rows = await conn.fetchall(
|
# # rows = await conn.fetchall(
|
||||||
# # f"SELECT * FROM {table_with_schema(db, 'melt_quotes')}"
|
# # f"SELECT * FROM {db.table_with_schema('melt_quotes')}"
|
||||||
# # )
|
# # )
|
||||||
# # for row in rows:
|
# # for row in rows:
|
||||||
# # if row["paid"]:
|
# # if row["paid"]:
|
||||||
@@ -283,7 +283,7 @@ async def m012_add_fee_to_keysets(db: Database):
|
|||||||
# # else:
|
# # else:
|
||||||
# # state = "unpaid"
|
# # state = "unpaid"
|
||||||
# # await conn.execute(
|
# # await conn.execute(
|
||||||
# # f"UPDATE {table_with_schema(db, 'melt_quotes')} SET state = '{state}' WHERE quote = '{row['quote']}'"
|
# # f"UPDATE {db.table_with_schema('melt_quotes')} SET state = '{state}' WHERE quote = '{row['quote']}'"
|
||||||
# # )
|
# # )
|
||||||
# # add the equivalent of the above migration for the wallet here. do not use table_with_schema. use the tables and columns
|
# # add the equivalent of the above migration for the wallet here. do not use table_with_schema. use the tables and columns
|
||||||
# # as they are defined in the wallet db
|
# # as they are defined in the wallet db
|
||||||
|
|||||||
@@ -310,9 +310,10 @@ class Wallet(
|
|||||||
async def _check_used_secrets(self, secrets):
|
async def _check_used_secrets(self, secrets):
|
||||||
"""Checks if any of the secrets have already been used"""
|
"""Checks if any of the secrets have already been used"""
|
||||||
logger.trace("Checking secrets.")
|
logger.trace("Checking secrets.")
|
||||||
for s in secrets:
|
async with self.db.get_connection() as conn:
|
||||||
if await secret_used(s, db=self.db):
|
for s in secrets:
|
||||||
raise Exception(f"secret already used: {s}")
|
if await secret_used(s, db=self.db, conn=conn):
|
||||||
|
raise Exception(f"secret already used: {s}")
|
||||||
logger.trace("Secret check complete.")
|
logger.trace("Secret check complete.")
|
||||||
|
|
||||||
async def request_mint_with_callback(
|
async def request_mint_with_callback(
|
||||||
|
|||||||
417
poetry.lock
generated
417
poetry.lock
generated
@@ -1,5 +1,23 @@
|
|||||||
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aiosqlite"
|
||||||
|
version = "0.20.0"
|
||||||
|
description = "asyncio bridge to the standard sqlite3 module"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "aiosqlite-0.20.0-py3-none-any.whl", hash = "sha256:36a1deaca0cac40ebe32aac9977a6e2bbc7f5189f23f4a54d5908986729e5bd6"},
|
||||||
|
{file = "aiosqlite-0.20.0.tar.gz", hash = "sha256:6d35c8c256637f4672f843c31021464090805bf925385ac39473fb16eaaca3d7"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
typing_extensions = ">=4.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
dev = ["attribution (==1.7.0)", "black (==24.2.0)", "coverage[toml] (==7.4.1)", "flake8 (==7.0.0)", "flake8-bugbear (==24.2.6)", "flit (==3.9.0)", "mypy (==1.8.0)", "ufmt (==2.3.0)", "usort (==1.0.8.post1)"]
|
||||||
|
docs = ["sphinx (==7.2.6)", "sphinx-mdinclude (==0.5.3)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anyio"
|
name = "anyio"
|
||||||
version = "3.7.1"
|
version = "3.7.1"
|
||||||
@@ -33,23 +51,72 @@ files = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "attrs"
|
name = "async-timeout"
|
||||||
version = "23.2.0"
|
version = "4.0.3"
|
||||||
description = "Classes Without Boilerplate"
|
description = "Timeout context manager for asyncio programs"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.7"
|
python-versions = ">=3.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"},
|
{file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"},
|
||||||
{file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"},
|
{file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "asyncpg"
|
||||||
|
version = "0.29.0"
|
||||||
|
description = "An asyncio PostgreSQL driver"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8.0"
|
||||||
|
files = [
|
||||||
|
{file = "asyncpg-0.29.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72fd0ef9f00aeed37179c62282a3d14262dbbafb74ec0ba16e1b1864d8a12169"},
|
||||||
|
{file = "asyncpg-0.29.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:52e8f8f9ff6e21f9b39ca9f8e3e33a5fcdceaf5667a8c5c32bee158e313be385"},
|
||||||
|
{file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a9e6823a7012be8b68301342ba33b4740e5a166f6bbda0aee32bc01638491a22"},
|
||||||
|
{file = "asyncpg-0.29.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:746e80d83ad5d5464cfbf94315eb6744222ab00aa4e522b704322fb182b83610"},
|
||||||
|
{file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:ff8e8109cd6a46ff852a5e6bab8b0a047d7ea42fcb7ca5ae6eaae97d8eacf397"},
|
||||||
|
{file = "asyncpg-0.29.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:97eb024685b1d7e72b1972863de527c11ff87960837919dac6e34754768098eb"},
|
||||||
|
{file = "asyncpg-0.29.0-cp310-cp310-win32.whl", hash = "sha256:5bbb7f2cafd8d1fa3e65431833de2642f4b2124be61a449fa064e1a08d27e449"},
|
||||||
|
{file = "asyncpg-0.29.0-cp310-cp310-win_amd64.whl", hash = "sha256:76c3ac6530904838a4b650b2880f8e7af938ee049e769ec2fba7cd66469d7772"},
|
||||||
|
{file = "asyncpg-0.29.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4900ee08e85af01adb207519bb4e14b1cae8fd21e0ccf80fac6aa60b6da37b4"},
|
||||||
|
{file = "asyncpg-0.29.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a65c1dcd820d5aea7c7d82a3fdcb70e096f8f70d1a8bf93eb458e49bfad036ac"},
|
||||||
|
{file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b52e46f165585fd6af4863f268566668407c76b2c72d366bb8b522fa66f1870"},
|
||||||
|
{file = "asyncpg-0.29.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc600ee8ef3dd38b8d67421359779f8ccec30b463e7aec7ed481c8346decf99f"},
|
||||||
|
{file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:039a261af4f38f949095e1e780bae84a25ffe3e370175193174eb08d3cecab23"},
|
||||||
|
{file = "asyncpg-0.29.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:6feaf2d8f9138d190e5ec4390c1715c3e87b37715cd69b2c3dfca616134efd2b"},
|
||||||
|
{file = "asyncpg-0.29.0-cp311-cp311-win32.whl", hash = "sha256:1e186427c88225ef730555f5fdda6c1812daa884064bfe6bc462fd3a71c4b675"},
|
||||||
|
{file = "asyncpg-0.29.0-cp311-cp311-win_amd64.whl", hash = "sha256:cfe73ffae35f518cfd6e4e5f5abb2618ceb5ef02a2365ce64f132601000587d3"},
|
||||||
|
{file = "asyncpg-0.29.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6011b0dc29886ab424dc042bf9eeb507670a3b40aece3439944006aafe023178"},
|
||||||
|
{file = "asyncpg-0.29.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:b544ffc66b039d5ec5a7454667f855f7fec08e0dfaf5a5490dfafbb7abbd2cfb"},
|
||||||
|
{file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d84156d5fb530b06c493f9e7635aa18f518fa1d1395ef240d211cb563c4e2364"},
|
||||||
|
{file = "asyncpg-0.29.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:54858bc25b49d1114178d65a88e48ad50cb2b6f3e475caa0f0c092d5f527c106"},
|
||||||
|
{file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bde17a1861cf10d5afce80a36fca736a86769ab3579532c03e45f83ba8a09c59"},
|
||||||
|
{file = "asyncpg-0.29.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:37a2ec1b9ff88d8773d3eb6d3784dc7e3fee7756a5317b67f923172a4748a175"},
|
||||||
|
{file = "asyncpg-0.29.0-cp312-cp312-win32.whl", hash = "sha256:bb1292d9fad43112a85e98ecdc2e051602bce97c199920586be83254d9dafc02"},
|
||||||
|
{file = "asyncpg-0.29.0-cp312-cp312-win_amd64.whl", hash = "sha256:2245be8ec5047a605e0b454c894e54bf2ec787ac04b1cb7e0d3c67aa1e32f0fe"},
|
||||||
|
{file = "asyncpg-0.29.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0009a300cae37b8c525e5b449233d59cd9868fd35431abc470a3e364d2b85cb9"},
|
||||||
|
{file = "asyncpg-0.29.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5cad1324dbb33f3ca0cd2074d5114354ed3be2b94d48ddfd88af75ebda7c43cc"},
|
||||||
|
{file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:012d01df61e009015944ac7543d6ee30c2dc1eb2f6b10b62a3f598beb6531548"},
|
||||||
|
{file = "asyncpg-0.29.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000c996c53c04770798053e1730d34e30cb645ad95a63265aec82da9093d88e7"},
|
||||||
|
{file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:e0bfe9c4d3429706cf70d3249089de14d6a01192d617e9093a8e941fea8ee775"},
|
||||||
|
{file = "asyncpg-0.29.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:642a36eb41b6313ffa328e8a5c5c2b5bea6ee138546c9c3cf1bffaad8ee36dd9"},
|
||||||
|
{file = "asyncpg-0.29.0-cp38-cp38-win32.whl", hash = "sha256:a921372bbd0aa3a5822dd0409da61b4cd50df89ae85150149f8c119f23e8c408"},
|
||||||
|
{file = "asyncpg-0.29.0-cp38-cp38-win_amd64.whl", hash = "sha256:103aad2b92d1506700cbf51cd8bb5441e7e72e87a7b3a2ca4e32c840f051a6a3"},
|
||||||
|
{file = "asyncpg-0.29.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5340dd515d7e52f4c11ada32171d87c05570479dc01dc66d03ee3e150fb695da"},
|
||||||
|
{file = "asyncpg-0.29.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e17b52c6cf83e170d3d865571ba574577ab8e533e7361a2b8ce6157d02c665d3"},
|
||||||
|
{file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f100d23f273555f4b19b74a96840aa27b85e99ba4b1f18d4ebff0734e78dc090"},
|
||||||
|
{file = "asyncpg-0.29.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:48e7c58b516057126b363cec8ca02b804644fd012ef8e6c7e23386b7d5e6ce83"},
|
||||||
|
{file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:f9ea3f24eb4c49a615573724d88a48bd1b7821c890c2effe04f05382ed9e8810"},
|
||||||
|
{file = "asyncpg-0.29.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8d36c7f14a22ec9e928f15f92a48207546ffe68bc412f3be718eedccdf10dc5c"},
|
||||||
|
{file = "asyncpg-0.29.0-cp39-cp39-win32.whl", hash = "sha256:797ab8123ebaed304a1fad4d7576d5376c3a006a4100380fb9d517f0b59c1ab2"},
|
||||||
|
{file = "asyncpg-0.29.0-cp39-cp39-win_amd64.whl", hash = "sha256:cce08a178858b426ae1aa8409b5cc171def45d4293626e7aa6510696d46decd8"},
|
||||||
|
{file = "asyncpg-0.29.0.tar.gz", hash = "sha256:d1c49e1f44fffafd9a55e1a9b101590859d881d639ea2922516f5d9c512d354e"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
async-timeout = {version = ">=4.0.3", markers = "python_version < \"3.12.0\""}
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
cov = ["attrs[tests]", "coverage[toml] (>=5.3)"]
|
docs = ["Sphinx (>=5.3.0,<5.4.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-asyncio (>=0.3.0,<0.4.0)"]
|
||||||
dev = ["attrs[tests]", "pre-commit"]
|
test = ["flake8 (>=6.1,<7.0)", "uvloop (>=0.15.3)"]
|
||||||
docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"]
|
|
||||||
tests = ["attrs[tests-no-zope]", "zope-interface"]
|
|
||||||
tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"]
|
|
||||||
tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base58"
|
name = "base58"
|
||||||
@@ -175,13 +242,13 @@ test = ["coverage (>=7)", "hypothesis", "pytest"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "certifi"
|
name = "certifi"
|
||||||
version = "2024.6.2"
|
version = "2024.7.4"
|
||||||
description = "Python package for providing Mozilla's CA Bundle."
|
description = "Python package for providing Mozilla's CA Bundle."
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.6"
|
python-versions = ">=3.6"
|
||||||
files = [
|
files = [
|
||||||
{file = "certifi-2024.6.2-py3-none-any.whl", hash = "sha256:ddc6c8ce995e6987e7faf5e3f1b02b302836a0e5d98ece18392cb1a36c72ad56"},
|
{file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"},
|
||||||
{file = "certifi-2024.6.2.tar.gz", hash = "sha256:3cd43f1c6fa7dedc5899d69d3ad0398fd018ad1a17fba83ddaf78aa46c747516"},
|
{file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -582,6 +649,77 @@ docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1
|
|||||||
testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"]
|
testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)", "virtualenv (>=20.26.2)"]
|
||||||
typing = ["typing-extensions (>=4.8)"]
|
typing = ["typing-extensions (>=4.8)"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "greenlet"
|
||||||
|
version = "3.0.3"
|
||||||
|
description = "Lightweight in-process concurrent programming"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
files = [
|
||||||
|
{file = "greenlet-3.0.3-cp310-cp310-macosx_11_0_universal2.whl", hash = "sha256:9da2bd29ed9e4f15955dd1595ad7bc9320308a3b766ef7f837e23ad4b4aac31a"},
|
||||||
|
{file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d353cadd6083fdb056bb46ed07e4340b0869c305c8ca54ef9da3421acbdf6881"},
|
||||||
|
{file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dca1e2f3ca00b84a396bc1bce13dd21f680f035314d2379c4160c98153b2059b"},
|
||||||
|
{file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3ed7fb269f15dc662787f4119ec300ad0702fa1b19d2135a37c2c4de6fadfd4a"},
|
||||||
|
{file = "greenlet-3.0.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd4f49ae60e10adbc94b45c0b5e6a179acc1736cf7a90160b404076ee283cf83"},
|
||||||
|
{file = "greenlet-3.0.3-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73a411ef564e0e097dbe7e866bb2dda0f027e072b04da387282b02c308807405"},
|
||||||
|
{file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7f362975f2d179f9e26928c5b517524e89dd48530a0202570d55ad6ca5d8a56f"},
|
||||||
|
{file = "greenlet-3.0.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:649dde7de1a5eceb258f9cb00bdf50e978c9db1b996964cd80703614c86495eb"},
|
||||||
|
{file = "greenlet-3.0.3-cp310-cp310-win_amd64.whl", hash = "sha256:68834da854554926fbedd38c76e60c4a2e3198c6fbed520b106a8986445caaf9"},
|
||||||
|
{file = "greenlet-3.0.3-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:b1b5667cced97081bf57b8fa1d6bfca67814b0afd38208d52538316e9422fc61"},
|
||||||
|
{file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52f59dd9c96ad2fc0d5724107444f76eb20aaccb675bf825df6435acb7703559"},
|
||||||
|
{file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:afaff6cf5200befd5cec055b07d1c0a5a06c040fe5ad148abcd11ba6ab9b114e"},
|
||||||
|
{file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fe754d231288e1e64323cfad462fcee8f0288654c10bdf4f603a39ed923bef33"},
|
||||||
|
{file = "greenlet-3.0.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2797aa5aedac23af156bbb5a6aa2cd3427ada2972c828244eb7d1b9255846379"},
|
||||||
|
{file = "greenlet-3.0.3-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b7f009caad047246ed379e1c4dbcb8b020f0a390667ea74d2387be2998f58a22"},
|
||||||
|
{file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:c5e1536de2aad7bf62e27baf79225d0d64360d4168cf2e6becb91baf1ed074f3"},
|
||||||
|
{file = "greenlet-3.0.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:894393ce10ceac937e56ec00bb71c4c2f8209ad516e96033e4b3b1de270e200d"},
|
||||||
|
{file = "greenlet-3.0.3-cp311-cp311-win_amd64.whl", hash = "sha256:1ea188d4f49089fc6fb283845ab18a2518d279c7cd9da1065d7a84e991748728"},
|
||||||
|
{file = "greenlet-3.0.3-cp312-cp312-macosx_11_0_universal2.whl", hash = "sha256:70fb482fdf2c707765ab5f0b6655e9cfcf3780d8d87355a063547b41177599be"},
|
||||||
|
{file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4d1ac74f5c0c0524e4a24335350edad7e5f03b9532da7ea4d3c54d527784f2e"},
|
||||||
|
{file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:149e94a2dd82d19838fe4b2259f1b6b9957d5ba1b25640d2380bea9c5df37676"},
|
||||||
|
{file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:15d79dd26056573940fcb8c7413d84118086f2ec1a8acdfa854631084393efcc"},
|
||||||
|
{file = "greenlet-3.0.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:881b7db1ebff4ba09aaaeae6aa491daeb226c8150fc20e836ad00041bcb11230"},
|
||||||
|
{file = "greenlet-3.0.3-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fcd2469d6a2cf298f198f0487e0a5b1a47a42ca0fa4dfd1b6862c999f018ebbf"},
|
||||||
|
{file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:1f672519db1796ca0d8753f9e78ec02355e862d0998193038c7073045899f305"},
|
||||||
|
{file = "greenlet-3.0.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2516a9957eed41dd8f1ec0c604f1cdc86758b587d964668b5b196a9db5bfcde6"},
|
||||||
|
{file = "greenlet-3.0.3-cp312-cp312-win_amd64.whl", hash = "sha256:bba5387a6975598857d86de9eac14210a49d554a77eb8261cc68b7d082f78ce2"},
|
||||||
|
{file = "greenlet-3.0.3-cp37-cp37m-macosx_11_0_universal2.whl", hash = "sha256:5b51e85cb5ceda94e79d019ed36b35386e8c37d22f07d6a751cb659b180d5274"},
|
||||||
|
{file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:daf3cb43b7cf2ba96d614252ce1684c1bccee6b2183a01328c98d36fcd7d5cb0"},
|
||||||
|
{file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:99bf650dc5d69546e076f413a87481ee1d2d09aaaaaca058c9251b6d8c14783f"},
|
||||||
|
{file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2dd6e660effd852586b6a8478a1d244b8dc90ab5b1321751d2ea15deb49ed414"},
|
||||||
|
{file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e3391d1e16e2a5a1507d83e4a8b100f4ee626e8eca43cf2cadb543de69827c4c"},
|
||||||
|
{file = "greenlet-3.0.3-cp37-cp37m-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e1f145462f1fa6e4a4ae3c0f782e580ce44d57c8f2c7aae1b6fa88c0b2efdb41"},
|
||||||
|
{file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:1a7191e42732df52cb5f39d3527217e7ab73cae2cb3694d241e18f53d84ea9a7"},
|
||||||
|
{file = "greenlet-3.0.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:0448abc479fab28b00cb472d278828b3ccca164531daab4e970a0458786055d6"},
|
||||||
|
{file = "greenlet-3.0.3-cp37-cp37m-win32.whl", hash = "sha256:b542be2440edc2d48547b5923c408cbe0fc94afb9f18741faa6ae970dbcb9b6d"},
|
||||||
|
{file = "greenlet-3.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:01bc7ea167cf943b4c802068e178bbf70ae2e8c080467070d01bfa02f337ee67"},
|
||||||
|
{file = "greenlet-3.0.3-cp38-cp38-macosx_11_0_universal2.whl", hash = "sha256:1996cb9306c8595335bb157d133daf5cf9f693ef413e7673cb07e3e5871379ca"},
|
||||||
|
{file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ddc0f794e6ad661e321caa8d2f0a55ce01213c74722587256fb6566049a8b04"},
|
||||||
|
{file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c9db1c18f0eaad2f804728c67d6c610778456e3e1cc4ab4bbd5eeb8e6053c6fc"},
|
||||||
|
{file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7170375bcc99f1a2fbd9c306f5be8764eaf3ac6b5cb968862cad4c7057756506"},
|
||||||
|
{file = "greenlet-3.0.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6b66c9c1e7ccabad3a7d037b2bcb740122a7b17a53734b7d72a344ce39882a1b"},
|
||||||
|
{file = "greenlet-3.0.3-cp38-cp38-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:098d86f528c855ead3479afe84b49242e174ed262456c342d70fc7f972bc13c4"},
|
||||||
|
{file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:81bb9c6d52e8321f09c3d165b2a78c680506d9af285bfccbad9fb7ad5a5da3e5"},
|
||||||
|
{file = "greenlet-3.0.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fd096eb7ffef17c456cfa587523c5f92321ae02427ff955bebe9e3c63bc9f0da"},
|
||||||
|
{file = "greenlet-3.0.3-cp38-cp38-win32.whl", hash = "sha256:d46677c85c5ba00a9cb6f7a00b2bfa6f812192d2c9f7d9c4f6a55b60216712f3"},
|
||||||
|
{file = "greenlet-3.0.3-cp38-cp38-win_amd64.whl", hash = "sha256:419b386f84949bf0e7c73e6032e3457b82a787c1ab4a0e43732898a761cc9dbf"},
|
||||||
|
{file = "greenlet-3.0.3-cp39-cp39-macosx_11_0_universal2.whl", hash = "sha256:da70d4d51c8b306bb7a031d5cff6cc25ad253affe89b70352af5f1cb68e74b53"},
|
||||||
|
{file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:086152f8fbc5955df88382e8a75984e2bb1c892ad2e3c80a2508954e52295257"},
|
||||||
|
{file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d73a9fe764d77f87f8ec26a0c85144d6a951a6c438dfe50487df5595c6373eac"},
|
||||||
|
{file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7dcbe92cc99f08c8dd11f930de4d99ef756c3591a5377d1d9cd7dd5e896da71"},
|
||||||
|
{file = "greenlet-3.0.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1551a8195c0d4a68fac7a4325efac0d541b48def35feb49d803674ac32582f61"},
|
||||||
|
{file = "greenlet-3.0.3-cp39-cp39-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:64d7675ad83578e3fc149b617a444fab8efdafc9385471f868eb5ff83e446b8b"},
|
||||||
|
{file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b37eef18ea55f2ffd8f00ff8fe7c8d3818abd3e25fb73fae2ca3b672e333a7a6"},
|
||||||
|
{file = "greenlet-3.0.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:77457465d89b8263bca14759d7c1684df840b6811b2499838cc5b040a8b5b113"},
|
||||||
|
{file = "greenlet-3.0.3-cp39-cp39-win32.whl", hash = "sha256:57e8974f23e47dac22b83436bdcf23080ade568ce77df33159e019d161ce1d1e"},
|
||||||
|
{file = "greenlet-3.0.3-cp39-cp39-win_amd64.whl", hash = "sha256:c5ee858cfe08f34712f548c3c363e807e7186f03ad7a5039ebadb29e8c6be067"},
|
||||||
|
{file = "greenlet-3.0.3.tar.gz", hash = "sha256:43374442353259554ce33599da8b692d5aa96f8976d567d4badf263371fbe491"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
docs = ["Sphinx", "furo"]
|
||||||
|
test = ["objgraph", "psutil"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h11"
|
name = "h11"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
@@ -641,13 +779,13 @@ socks = ["socksio (==1.*)"]
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "identify"
|
name = "identify"
|
||||||
version = "2.5.36"
|
version = "2.6.0"
|
||||||
description = "File identification library for Python"
|
description = "File identification library for Python"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=3.8"
|
python-versions = ">=3.8"
|
||||||
files = [
|
files = [
|
||||||
{file = "identify-2.5.36-py2.py3-none-any.whl", hash = "sha256:37d93f380f4de590500d9dba7db359d0d3da95ffe7f9de1753faa159e71e7dfa"},
|
{file = "identify-2.6.0-py2.py3-none-any.whl", hash = "sha256:e79ae4406387a9d300332b5fd366d8994f1525e8414984e1a59e058b2eda2dd0"},
|
||||||
{file = "identify-2.5.36.tar.gz", hash = "sha256:e5e00f54165f9047fbebeb4a560f9acfb8af4c88232be60a488e9b68d122745d"},
|
{file = "identify-2.6.0.tar.gz", hash = "sha256:cb171c685bdc31bcc4c1734698736a7d5b6c8bf2e0c15117f4d469c8640ae5cf"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
@@ -858,20 +996,6 @@ files = [
|
|||||||
{file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
|
{file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "outcome"
|
|
||||||
version = "1.3.0.post0"
|
|
||||||
description = "Capture the outcome of Python function calls."
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
files = [
|
|
||||||
{file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"},
|
|
||||||
{file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.dependencies]
|
|
||||||
attrs = ">=19.2.0"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "packaging"
|
name = "packaging"
|
||||||
version = "24.1"
|
version = "24.1"
|
||||||
@@ -932,84 +1056,6 @@ nodeenv = ">=0.11.1"
|
|||||||
pyyaml = ">=5.1"
|
pyyaml = ">=5.1"
|
||||||
virtualenv = ">=20.10.0"
|
virtualenv = ">=20.10.0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "psycopg2-binary"
|
|
||||||
version = "2.9.9"
|
|
||||||
description = "psycopg2 - Python-PostgreSQL Database Adapter"
|
|
||||||
optional = true
|
|
||||||
python-versions = ">=3.7"
|
|
||||||
files = [
|
|
||||||
{file = "psycopg2-binary-2.9.9.tar.gz", hash = "sha256:7f01846810177d829c7692f1f5ada8096762d9172af1b1a28d4ab5b77c923c1c"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c2470da5418b76232f02a2fcd2229537bb2d5a7096674ce61859c3229f2eb202"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c6af2a6d4b7ee9615cbb162b0738f6e1fd1f5c3eda7e5da17861eacf4c717ea7"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75723c3c0fbbf34350b46a3199eb50638ab22a0228f93fb472ef4d9becc2382b"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:83791a65b51ad6ee6cf0845634859d69a038ea9b03d7b26e703f94c7e93dbcf9"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0ef4854e82c09e84cc63084a9e4ccd6d9b154f1dbdd283efb92ecd0b5e2b8c84"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed1184ab8f113e8d660ce49a56390ca181f2981066acc27cf637d5c1e10ce46e"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d2997c458c690ec2bc6b0b7ecbafd02b029b7b4283078d3b32a852a7ce3ddd98"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:b58b4710c7f4161b5e9dcbe73bb7c62d65670a87df7bcce9e1faaad43e715245"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:0c009475ee389757e6e34611d75f6e4f05f0cf5ebb76c6037508318e1a1e0d7e"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:8dbf6d1bc73f1d04ec1734bae3b4fb0ee3cb2a493d35ede9badbeb901fb40f6f"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp310-cp310-win32.whl", hash = "sha256:3f78fd71c4f43a13d342be74ebbc0666fe1f555b8837eb113cb7416856c79682"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:876801744b0dee379e4e3c38b76fc89f88834bb15bf92ee07d94acd06ec890a0"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ee825e70b1a209475622f7f7b776785bd68f34af6e7a46e2e42f27b659b5bc26"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1ea665f8ce695bcc37a90ee52de7a7980be5161375d42a0b6c6abedbf0d81f0f"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:143072318f793f53819048fdfe30c321890af0c3ec7cb1dfc9cc87aa88241de2"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c332c8d69fb64979ebf76613c66b985414927a40f8defa16cf1bc028b7b0a7b0"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7fc5a5acafb7d6ccca13bfa8c90f8c51f13d8fb87d95656d3950f0158d3ce53"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:977646e05232579d2e7b9c59e21dbe5261f403a88417f6a6512e70d3f8a046be"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6356793b84728d9d50ead16ab43c187673831e9d4019013f1402c41b1db9b27"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bc7bb56d04601d443f24094e9e31ae6deec9ccb23581f75343feebaf30423359"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:77853062a2c45be16fd6b8d6de2a99278ee1d985a7bd8b103e97e41c034006d2"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:78151aa3ec21dccd5cdef6c74c3e73386dcdfaf19bced944169697d7ac7482fc"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp311-cp311-win32.whl", hash = "sha256:dc4926288b2a3e9fd7b50dc6a1909a13bbdadfc67d93f3374d984e56f885579d"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:b76bedd166805480ab069612119ea636f5ab8f8771e640ae103e05a4aae3e417"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:8532fd6e6e2dc57bcb3bc90b079c60de896d2128c5d9d6f24a63875a95a088cf"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f8544b092a29a6ddd72f3556a9fcf249ec412e10ad28be6a0c0d948924f2212"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2d423c8d8a3c82d08fe8af900ad5b613ce3632a1249fd6a223941d0735fce493"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e5afae772c00980525f6d6ecf7cbca55676296b580c0e6abb407f15f3706996"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6e6f98446430fdf41bd36d4faa6cb409f5140c1c2cf58ce0bbdaf16af7d3f119"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c77e3d1862452565875eb31bdb45ac62502feabbd53429fdc39a1cc341d681ba"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:cb16c65dcb648d0a43a2521f2f0a2300f40639f6f8c1ecbc662141e4e3e1ee07"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:911dda9c487075abd54e644ccdf5e5c16773470a6a5d3826fda76699410066fb"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:57fede879f08d23c85140a360c6a77709113efd1c993923c59fde17aa27599fe"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:2293b001e319ab0d869d660a704942c9e2cce19745262a8aba2115ef41a0a42a"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03ef7df18daf2c4c07e2695e8cfd5ee7f748a1d54d802330985a78d2a5a6dca9"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a602ea5aff39bb9fac6308e9c9d82b9a35c2bf288e184a816002c9fae930b77"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8359bf4791968c5a78c56103702000105501adb557f3cf772b2c207284273984"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:275ff571376626195ab95a746e6a04c7df8ea34638b99fc11160de91f2fef503"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f9b5571d33660d5009a8b3c25dc1db560206e2d2f89d3df1cb32d72c0d117d52"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:420f9bbf47a02616e8554e825208cb947969451978dceb77f95ad09c37791dae"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:4154ad09dac630a0f13f37b583eae260c6aa885d67dfbccb5b02c33f31a6d420"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:a148c5d507bb9b4f2030a2025c545fccb0e1ef317393eaba42e7eabd28eb6041"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:68fc1f1ba168724771e38bee37d940d2865cb0f562380a1fb1ffb428b75cb692"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:281309265596e388ef483250db3640e5f414168c5a67e9c665cafce9492eda2f"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:60989127da422b74a04345096c10d416c2b41bd7bf2a380eb541059e4e999980"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:246b123cc54bb5361588acc54218c8c9fb73068bf227a4a531d8ed56fa3ca7d6"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34eccd14566f8fe14b2b95bb13b11572f7c7d5c36da61caf414d23b91fcc5d94"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:18d0ef97766055fec15b5de2c06dd8e7654705ce3e5e5eed3b6651a1d2a9a152"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d3f82c171b4ccd83bbaf35aa05e44e690113bd4f3b7b6cc54d2219b132f3ae55"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ead20f7913a9c1e894aebe47cccf9dc834e1618b7aa96155d2091a626e59c972"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ca49a8119c6cbd77375ae303b0cfd8c11f011abbbd64601167ecca18a87e7cdd"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:323ba25b92454adb36fa425dc5cf6f8f19f78948cbad2e7bc6cdf7b0d7982e59"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:1236ed0952fbd919c100bc839eaa4a39ebc397ed1c08a97fc45fee2a595aa1b3"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:729177eaf0aefca0994ce4cffe96ad3c75e377c7b6f4efa59ebf003b6d398716"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp38-cp38-win32.whl", hash = "sha256:804d99b24ad523a1fe18cc707bf741670332f7c7412e9d49cb5eab67e886b9b5"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp38-cp38-win_amd64.whl", hash = "sha256:a6cdcc3ede532f4a4b96000b6362099591ab4a3e913d70bcbac2b56c872446f7"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:72dffbd8b4194858d0941062a9766f8297e8868e1dd07a7b36212aaa90f49472"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:30dcc86377618a4c8f3b72418df92e77be4254d8f89f14b8e8f57d6d43603c0f"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:31a34c508c003a4347d389a9e6fcc2307cc2150eb516462a7a17512130de109e"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:15208be1c50b99203fe88d15695f22a5bed95ab3f84354c494bcb1d08557df67"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1873aade94b74715be2246321c8650cabf5a0d098a95bab81145ffffa4c13876"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a58c98a7e9c021f357348867f537017057c2ed7f77337fd914d0bedb35dace7"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:4686818798f9194d03c9129a4d9a702d9e113a89cb03bffe08c6cf799e053291"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:ebdc36bea43063116f0486869652cb2ed7032dbc59fbcb4445c4862b5c1ecf7f"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:ca08decd2697fdea0aea364b370b1249d47336aec935f87b8bbfd7da5b2ee9c1"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ac05fb791acf5e1a3e39402641827780fe44d27e72567a000412c648a85ba860"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp39-cp39-win32.whl", hash = "sha256:9dba73be7305b399924709b91682299794887cbbd88e38226ed9f6712eabee90"},
|
|
||||||
{file = "psycopg2_binary-2.9.9-cp39-cp39-win_amd64.whl", hash = "sha256:f7ae5d65ccfbebdfa761585228eb4d0df3a8b15cfb53bd953e713e09fbb12957"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pycparser"
|
name = "pycparser"
|
||||||
version = "2.22"
|
version = "2.22"
|
||||||
@@ -1318,21 +1364,6 @@ files = [
|
|||||||
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
|
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "represent"
|
|
||||||
version = "2.1"
|
|
||||||
description = "Create __repr__ automatically or declaratively."
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.8"
|
|
||||||
files = [
|
|
||||||
{file = "Represent-2.1-py3-none-any.whl", hash = "sha256:94fd22d7fec378240c598b20b233f80545ec7eb1131076e2d3d759cee9be2588"},
|
|
||||||
{file = "Represent-2.1.tar.gz", hash = "sha256:0b2d015c14e7ba6b3b5e6a7ba131a952013fe944339ac538764ce728a75dbcac"},
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.extras]
|
|
||||||
docstest = ["furo", "parver", "sphinx"]
|
|
||||||
test = ["ipython", "pytest", "rich"]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "respx"
|
name = "respx"
|
||||||
version = "0.20.2"
|
version = "0.20.2"
|
||||||
@@ -1476,79 +1507,82 @@ files = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sqlalchemy"
|
name = "sqlalchemy"
|
||||||
version = "1.3.24"
|
version = "1.4.52"
|
||||||
description = "Database Abstraction Library"
|
description = "Database Abstraction Library"
|
||||||
optional = false
|
optional = false
|
||||||
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
|
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7"
|
||||||
files = [
|
files = [
|
||||||
{file = "SQLAlchemy-1.3.24-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:87a2725ad7d41cd7376373c15fd8bf674e9c33ca56d0b8036add2d634dba372e"},
|
{file = "SQLAlchemy-1.4.52-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:f68016f9a5713684c1507cc37133c28035f29925c75c0df2f9d0f7571e23720a"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp27-cp27m-win32.whl", hash = "sha256:f597a243b8550a3a0b15122b14e49d8a7e622ba1c9d29776af741f1845478d79"},
|
{file = "SQLAlchemy-1.4.52-cp310-cp310-manylinux1_x86_64.manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24bb0f81fbbb13d737b7f76d1821ec0b117ce8cbb8ee5e8641ad2de41aa916d3"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp27-cp27m-win_amd64.whl", hash = "sha256:fc4cddb0b474b12ed7bdce6be1b9edc65352e8ce66bc10ff8cbbfb3d4047dbf4"},
|
{file = "SQLAlchemy-1.4.52-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e93983cc0d2edae253b3f2141b0a3fb07e41c76cd79c2ad743fc27eb79c3f6db"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp35-cp35m-macosx_10_14_x86_64.whl", hash = "sha256:f1149d6e5c49d069163e58a3196865e4321bad1803d7886e07d8710de392c548"},
|
{file = "SQLAlchemy-1.4.52-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:84e10772cfc333eb08d0b7ef808cd76e4a9a30a725fb62a0495877a57ee41d81"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:14f0eb5db872c231b20c18b1e5806352723a3a89fb4254af3b3e14f22eaaec75"},
|
{file = "SQLAlchemy-1.4.52-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:427988398d2902de042093d17f2b9619a5ebc605bf6372f7d70e29bde6736842"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2010_x86_64.whl", hash = "sha256:e98d09f487267f1e8d1179bf3b9d7709b30a916491997137dd24d6ae44d18d79"},
|
{file = "SQLAlchemy-1.4.52-cp310-cp310-win32.whl", hash = "sha256:1296f2cdd6db09b98ceb3c93025f0da4835303b8ac46c15c2136e27ee4d18d94"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:fc1f2a5a5963e2e73bac4926bdaf7790c4d7d77e8fc0590817880e22dd9d0b8b"},
|
{file = "SQLAlchemy-1.4.52-cp310-cp310-win_amd64.whl", hash = "sha256:80e7f697bccc56ac6eac9e2df5c98b47de57e7006d2e46e1a3c17c546254f6ef"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp35-cp35m-win32.whl", hash = "sha256:f3c5c52f7cb8b84bfaaf22d82cb9e6e9a8297f7c2ed14d806a0f5e4d22e83fb7"},
|
{file = "SQLAlchemy-1.4.52-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:2f251af4c75a675ea42766880ff430ac33291c8d0057acca79710f9e5a77383d"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp35-cp35m-win_amd64.whl", hash = "sha256:0352db1befcbed2f9282e72843f1963860bf0e0472a4fa5cf8ee084318e0e6ab"},
|
{file = "SQLAlchemy-1.4.52-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb8f9e4c4718f111d7b530c4e6fb4d28f9f110eb82e7961412955b3875b66de0"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:2ed6343b625b16bcb63c5b10523fd15ed8934e1ed0f772c534985e9f5e73d894"},
|
{file = "SQLAlchemy-1.4.52-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afb1672b57f58c0318ad2cff80b384e816735ffc7e848d8aa51e0b0fc2f4b7bb"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:34fcec18f6e4b24b4a5f6185205a04f1eab1e56f8f1d028a2a03694ebcc2ddd4"},
|
{file = "SQLAlchemy-1.4.52-cp311-cp311-win32.whl", hash = "sha256:6e41cb5cda641f3754568d2ed8962f772a7f2b59403b95c60c89f3e0bd25f15e"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:e47e257ba5934550d7235665eee6c911dc7178419b614ba9e1fbb1ce6325b14f"},
|
{file = "SQLAlchemy-1.4.52-cp311-cp311-win_amd64.whl", hash = "sha256:5bed4f8c3b69779de9d99eb03fd9ab67a850d74ab0243d1be9d4080e77b6af12"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:816de75418ea0953b5eb7b8a74933ee5a46719491cd2b16f718afc4b291a9658"},
|
{file = "SQLAlchemy-1.4.52-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:49e3772eb3380ac88d35495843daf3c03f094b713e66c7d017e322144a5c6b7c"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp36-cp36m-win32.whl", hash = "sha256:26155ea7a243cbf23287f390dba13d7927ffa1586d3208e0e8d615d0c506f996"},
|
{file = "SQLAlchemy-1.4.52-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:618827c1a1c243d2540314c6e100aee7af09a709bd005bae971686fab6723554"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp36-cp36m-win_amd64.whl", hash = "sha256:f03bd97650d2e42710fbe4cf8a59fae657f191df851fc9fc683ecef10746a375"},
|
{file = "SQLAlchemy-1.4.52-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:de9acf369aaadb71a725b7e83a5ef40ca3de1cf4cdc93fa847df6b12d3cd924b"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:a006d05d9aa052657ee3e4dc92544faae5fcbaafc6128217310945610d862d39"},
|
{file = "SQLAlchemy-1.4.52-cp312-cp312-win32.whl", hash = "sha256:763bd97c4ebc74136ecf3526b34808c58945023a59927b416acebcd68d1fc126"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1e2f89d2e5e3c7a88e25a3b0e43626dba8db2aa700253023b82e630d12b37109"},
|
{file = "SQLAlchemy-1.4.52-cp312-cp312-win_amd64.whl", hash = "sha256:f12aaf94f4d9679ca475975578739e12cc5b461172e04d66f7a3c39dd14ffc64"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:0d5d862b1cfbec5028ce1ecac06a3b42bc7703eb80e4b53fceb2738724311443"},
|
{file = "SQLAlchemy-1.4.52-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:853fcfd1f54224ea7aabcf34b227d2b64a08cbac116ecf376907968b29b8e763"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:0172423a27fbcae3751ef016663b72e1a516777de324a76e30efa170dbd3dd2d"},
|
{file = "SQLAlchemy-1.4.52-cp36-cp36m-manylinux1_x86_64.manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f98dbb8fcc6d1c03ae8ec735d3c62110949a3b8bc6e215053aa27096857afb45"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp37-cp37m-win32.whl", hash = "sha256:d37843fb8df90376e9e91336724d78a32b988d3d20ab6656da4eb8ee3a45b63c"},
|
{file = "SQLAlchemy-1.4.52-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1e135fff2e84103bc15c07edd8569612ce317d64bdb391f49ce57124a73f45c5"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp37-cp37m-win_amd64.whl", hash = "sha256:c10ff6112d119f82b1618b6dc28126798481b9355d8748b64b9b55051eb4f01b"},
|
{file = "SQLAlchemy-1.4.52-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b5de6af8852500d01398f5047d62ca3431d1e29a331d0b56c3e14cb03f8094c"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:861e459b0e97673af6cc5e7f597035c2e3acdfb2608132665406cded25ba64c7"},
|
{file = "SQLAlchemy-1.4.52-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3491c85df263a5c2157c594f54a1a9c72265b75d3777e61ee13c556d9e43ffc9"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:5de2464c254380d8a6c20a2746614d5a436260be1507491442cf1088e59430d2"},
|
{file = "SQLAlchemy-1.4.52-cp36-cp36m-win32.whl", hash = "sha256:427c282dd0deba1f07bcbf499cbcc9fe9a626743f5d4989bfdfd3ed3513003dd"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:d375d8ccd3cebae8d90270f7aa8532fe05908f79e78ae489068f3b4eee5994e8"},
|
{file = "SQLAlchemy-1.4.52-cp36-cp36m-win_amd64.whl", hash = "sha256:ca5ce82b11731492204cff8845c5e8ca1a4bd1ade85e3b8fcf86e7601bfc6a39"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:014ea143572fee1c18322b7908140ad23b3994036ef4c0d630110faf942652f8"},
|
{file = "SQLAlchemy-1.4.52-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:29d4247313abb2015f8979137fe65f4eaceead5247d39603cc4b4a610936cd2b"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp38-cp38-win32.whl", hash = "sha256:6607ae6cd3a07f8a4c3198ffbf256c261661965742e2b5265a77cd5c679c9bba"},
|
{file = "SQLAlchemy-1.4.52-cp37-cp37m-manylinux1_x86_64.manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a752bff4796bf22803d052d4841ebc3c55c26fb65551f2c96e90ac7c62be763a"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp38-cp38-win_amd64.whl", hash = "sha256:fcb251305fa24a490b6a9ee2180e5f8252915fb778d3dafc70f9cc3f863827b9"},
|
{file = "SQLAlchemy-1.4.52-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f7ea11727feb2861deaa293c7971a4df57ef1c90e42cb53f0da40c3468388000"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:01aa5f803db724447c1d423ed583e42bf5264c597fd55e4add4301f163b0be48"},
|
{file = "SQLAlchemy-1.4.52-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d913f8953e098ca931ad7f58797f91deed26b435ec3756478b75c608aa80d139"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4d0e3515ef98aa4f0dc289ff2eebb0ece6260bbf37c2ea2022aad63797eacf60"},
|
{file = "SQLAlchemy-1.4.52-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a251146b921725547ea1735b060a11e1be705017b568c9f8067ca61e6ef85f20"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2010_x86_64.whl", hash = "sha256:bce28277f308db43a6b4965734366f533b3ff009571ec7ffa583cb77539b84d6"},
|
{file = "SQLAlchemy-1.4.52-cp37-cp37m-win32.whl", hash = "sha256:1f8e1c6a6b7f8e9407ad9afc0ea41c1f65225ce505b79bc0342159de9c890782"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:8110e6c414d3efc574543109ee618fe2c1f96fa31833a1ff36cc34e968c4f233"},
|
{file = "SQLAlchemy-1.4.52-cp37-cp37m-win_amd64.whl", hash = "sha256:346ed50cb2c30f5d7a03d888e25744154ceac6f0e6e1ab3bc7b5b77138d37710"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp39-cp39-win32.whl", hash = "sha256:ee5f5188edb20a29c1cc4a039b074fdc5575337c9a68f3063449ab47757bb064"},
|
{file = "SQLAlchemy-1.4.52-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:4dae6001457d4497736e3bc422165f107ecdd70b0d651fab7f731276e8b9e12d"},
|
||||||
{file = "SQLAlchemy-1.3.24-cp39-cp39-win_amd64.whl", hash = "sha256:09083c2487ca3c0865dc588e07aeaa25416da3d95f7482c07e92f47e080aa17b"},
|
{file = "SQLAlchemy-1.4.52-cp38-cp38-manylinux1_x86_64.manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5d2e08d79f5bf250afb4a61426b41026e448da446b55e4770c2afdc1e200fce"},
|
||||||
{file = "SQLAlchemy-1.3.24.tar.gz", hash = "sha256:ebbb777cbf9312359b897bf81ba00dae0f5cb69fba2a18265dcc18a6f5ef7519"},
|
{file = "SQLAlchemy-1.4.52-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bbce5dd7c7735e01d24f5a60177f3e589078f83c8a29e124a6521b76d825b85"},
|
||||||
]
|
{file = "SQLAlchemy-1.4.52-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bdb7b4d889631a3b2a81a3347c4c3f031812eb4adeaa3ee4e6b0d028ad1852b5"},
|
||||||
|
{file = "SQLAlchemy-1.4.52-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c294ae4e6bbd060dd79e2bd5bba8b6274d08ffd65b58d106394cb6abbf35cf45"},
|
||||||
[package.extras]
|
{file = "SQLAlchemy-1.4.52-cp38-cp38-win32.whl", hash = "sha256:bcdfb4b47fe04967669874fb1ce782a006756fdbebe7263f6a000e1db969120e"},
|
||||||
mssql = ["pyodbc"]
|
{file = "SQLAlchemy-1.4.52-cp38-cp38-win_amd64.whl", hash = "sha256:7d0dbc56cb6af5088f3658982d3d8c1d6a82691f31f7b0da682c7b98fa914e91"},
|
||||||
mssql-pymssql = ["pymssql"]
|
{file = "SQLAlchemy-1.4.52-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:a551d5f3dc63f096ed41775ceec72fdf91462bb95abdc179010dc95a93957800"},
|
||||||
mssql-pyodbc = ["pyodbc"]
|
{file = "SQLAlchemy-1.4.52-cp39-cp39-manylinux1_x86_64.manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_5_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ab773f9ad848118df7a9bbabca53e3f1002387cdbb6ee81693db808b82aaab0"},
|
||||||
mysql = ["mysqlclient"]
|
{file = "SQLAlchemy-1.4.52-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d2de46f5d5396d5331127cfa71f837cca945f9a2b04f7cb5a01949cf676db7d1"},
|
||||||
oracle = ["cx-oracle"]
|
{file = "SQLAlchemy-1.4.52-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7027be7930a90d18a386b25ee8af30514c61f3852c7268899f23fdfbd3107181"},
|
||||||
postgresql = ["psycopg2"]
|
{file = "SQLAlchemy-1.4.52-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:99224d621affbb3c1a4f72b631f8393045f4ce647dd3262f12fe3576918f8bf3"},
|
||||||
postgresql-pg8000 = ["pg8000 (<1.16.6)"]
|
{file = "SQLAlchemy-1.4.52-cp39-cp39-win32.whl", hash = "sha256:c124912fd4e1bb9d1e7dc193ed482a9f812769cb1e69363ab68e01801e859821"},
|
||||||
postgresql-psycopg2binary = ["psycopg2-binary"]
|
{file = "SQLAlchemy-1.4.52-cp39-cp39-win_amd64.whl", hash = "sha256:2c286fab42e49db23c46ab02479f328b8bdb837d3e281cae546cc4085c83b680"},
|
||||||
postgresql-psycopg2cffi = ["psycopg2cffi"]
|
{file = "SQLAlchemy-1.4.52.tar.gz", hash = "sha256:80e63bbdc5217dad3485059bdf6f65a7d43f33c8bde619df5c220edf03d87296"},
|
||||||
pymysql = ["pymysql", "pymysql (<1)"]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sqlalchemy-aio"
|
|
||||||
version = "0.17.0"
|
|
||||||
description = "Async support for SQLAlchemy."
|
|
||||||
optional = false
|
|
||||||
python-versions = ">=3.6"
|
|
||||||
files = [
|
|
||||||
{file = "sqlalchemy_aio-0.17.0-py3-none-any.whl", hash = "sha256:3f4aa392c38f032d6734826a4138a0f02ed3122d442ed142be1e5964f2a33b60"},
|
|
||||||
{file = "sqlalchemy_aio-0.17.0.tar.gz", hash = "sha256:f531c7982662d71dfc0b117e77bb2ed544e25cd5361e76cf9f5208edcfb71f7b"},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
outcome = "*"
|
greenlet = {version = "!=0.4.17", optional = true, markers = "python_version >= \"3\" and (platform_machine == \"aarch64\" or platform_machine == \"ppc64le\" or platform_machine == \"x86_64\" or platform_machine == \"amd64\" or platform_machine == \"AMD64\" or platform_machine == \"win32\" or platform_machine == \"WIN32\" or extra == \"asyncio\")"}
|
||||||
represent = ">=1.4"
|
|
||||||
sqlalchemy = "<1.4"
|
|
||||||
|
|
||||||
[package.extras]
|
[package.extras]
|
||||||
test = ["pytest (>=5.4)", "pytest-asyncio (>=0.14)", "pytest-trio (>=0.6)"]
|
aiomysql = ["aiomysql (>=0.2.0)", "greenlet (!=0.4.17)"]
|
||||||
test-noextras = ["pytest (>=5.4)", "pytest-asyncio (>=0.14)"]
|
aiosqlite = ["aiosqlite", "greenlet (!=0.4.17)", "typing_extensions (!=3.10.0.1)"]
|
||||||
trio = ["trio (>=0.15)"]
|
asyncio = ["greenlet (!=0.4.17)"]
|
||||||
|
asyncmy = ["asyncmy (>=0.2.3,!=0.2.4)", "greenlet (!=0.4.17)"]
|
||||||
|
mariadb-connector = ["mariadb (>=1.0.1,!=1.1.2)"]
|
||||||
|
mssql = ["pyodbc"]
|
||||||
|
mssql-pymssql = ["pymssql"]
|
||||||
|
mssql-pyodbc = ["pyodbc"]
|
||||||
|
mypy = ["mypy (>=0.910)", "sqlalchemy2-stubs"]
|
||||||
|
mysql = ["mysqlclient (>=1.4.0)", "mysqlclient (>=1.4.0,<2)"]
|
||||||
|
mysql-connector = ["mysql-connector-python"]
|
||||||
|
oracle = ["cx_oracle (>=7)", "cx_oracle (>=7,<8)"]
|
||||||
|
postgresql = ["psycopg2 (>=2.7)"]
|
||||||
|
postgresql-asyncpg = ["asyncpg", "greenlet (!=0.4.17)"]
|
||||||
|
postgresql-pg8000 = ["pg8000 (>=1.16.6,!=1.29.0)"]
|
||||||
|
postgresql-psycopg2binary = ["psycopg2-binary"]
|
||||||
|
postgresql-psycopg2cffi = ["psycopg2cffi"]
|
||||||
|
pymysql = ["pymysql", "pymysql (<1)"]
|
||||||
|
sqlcipher = ["sqlcipher3_binary"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "starlette"
|
name = "starlette"
|
||||||
@@ -1848,10 +1882,7 @@ files = [
|
|||||||
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"]
|
||||||
test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
|
test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"]
|
||||||
|
|
||||||
[extras]
|
|
||||||
pgsql = ["psycopg2-binary"]
|
|
||||||
|
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.8.1"
|
python-versions = "^3.8.1"
|
||||||
content-hash = "82dc8679e17cd4fb9f897d60ac55e90250886b9b95bfa1def11694d26a027b65"
|
content-hash = "d312a7a7c367a8c7f3664f4691e8e39aae6bc9aaf2e116abaf9e7fa1f28fe479"
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ license = "MIT"
|
|||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.8.1"
|
python = "^3.8.1"
|
||||||
SQLAlchemy = "^1.3.24"
|
SQLAlchemy = {version = "1.4.52", extras = ["asyncio"]}
|
||||||
click = "^8.1.7"
|
click = "^8.1.7"
|
||||||
pydantic = "^1.10.2"
|
pydantic = "^1.10.2"
|
||||||
bech32 = "^1.2.0"
|
bech32 = "^1.2.0"
|
||||||
@@ -18,7 +18,6 @@ loguru = "^0.7.0"
|
|||||||
ecdsa = "^0.18.0"
|
ecdsa = "^0.18.0"
|
||||||
bitstring = "^3.1.9"
|
bitstring = "^3.1.9"
|
||||||
secp256k1 = "^0.14.0"
|
secp256k1 = "^0.14.0"
|
||||||
sqlalchemy-aio = "^0.17.0"
|
|
||||||
h11 = "^0.14.0"
|
h11 = "^0.14.0"
|
||||||
cryptography = "^41.0.3"
|
cryptography = "^41.0.3"
|
||||||
websocket-client = "^1.3.3"
|
websocket-client = "^1.3.3"
|
||||||
@@ -26,7 +25,6 @@ pycryptodomex = "^3.16.0"
|
|||||||
setuptools = "^68.1.2"
|
setuptools = "^68.1.2"
|
||||||
wheel = "^0.41.1"
|
wheel = "^0.41.1"
|
||||||
importlib-metadata = "^6.8.0"
|
importlib-metadata = "^6.8.0"
|
||||||
psycopg2-binary = { version = "^2.9.7", optional = true }
|
|
||||||
httpx = {extras = ["socks"], version = "^0.25.1"}
|
httpx = {extras = ["socks"], version = "^0.25.1"}
|
||||||
bip32 = "^3.4"
|
bip32 = "^3.4"
|
||||||
mnemonic = "^0.20"
|
mnemonic = "^0.20"
|
||||||
@@ -35,9 +33,8 @@ pre-commit = "^3.5.0"
|
|||||||
websockets = "^12.0"
|
websockets = "^12.0"
|
||||||
slowapi = "^0.1.9"
|
slowapi = "^0.1.9"
|
||||||
cbor2 = "^5.6.2"
|
cbor2 = "^5.6.2"
|
||||||
|
asyncpg = "^0.29.0"
|
||||||
[tool.poetry.extras]
|
aiosqlite = "^0.20.0"
|
||||||
pgsql = ["psycopg2-binary"]
|
|
||||||
|
|
||||||
[tool.poetry.group.dev.dependencies]
|
[tool.poetry.group.dev.dependencies]
|
||||||
pytest-asyncio = "^0.21.1"
|
pytest-asyncio = "^0.21.1"
|
||||||
|
|||||||
@@ -22,7 +22,8 @@ from cashu.mint.ledger import Ledger
|
|||||||
SERVER_PORT = 3337
|
SERVER_PORT = 3337
|
||||||
SERVER_ENDPOINT = f"http://localhost:{SERVER_PORT}"
|
SERVER_ENDPOINT = f"http://localhost:{SERVER_PORT}"
|
||||||
|
|
||||||
settings.debug = False
|
settings.debug = True
|
||||||
|
settings.log_level = "TRACE"
|
||||||
settings.cashu_dir = "./test_data/"
|
settings.cashu_dir = "./test_data/"
|
||||||
settings.mint_host = "localhost"
|
settings.mint_host = "localhost"
|
||||||
settings.mint_port = SERVER_PORT
|
settings.mint_port = SERVER_PORT
|
||||||
@@ -49,6 +50,7 @@ settings.mint_transaction_rate_limit_per_minute = 60
|
|||||||
settings.mint_lnd_enable_mpp = True
|
settings.mint_lnd_enable_mpp = True
|
||||||
settings.mint_clnrest_enable_mpp = False
|
settings.mint_clnrest_enable_mpp = False
|
||||||
settings.mint_input_fee_ppk = 0
|
settings.mint_input_fee_ppk = 0
|
||||||
|
settings.db_connection_pool = True
|
||||||
|
|
||||||
assert "test" in settings.cashu_dir
|
assert "test" in settings.cashu_dir
|
||||||
shutil.rmtree(settings.cashu_dir, ignore_errors=True)
|
shutil.rmtree(settings.cashu_dir, ignore_errors=True)
|
||||||
@@ -83,11 +85,6 @@ class UvicornServer(multiprocessing.Process):
|
|||||||
async def ledger():
|
async def ledger():
|
||||||
async def start_mint_init(ledger: Ledger) -> Ledger:
|
async def start_mint_init(ledger: Ledger) -> Ledger:
|
||||||
await migrate_databases(ledger.db, migrations_mint)
|
await migrate_databases(ledger.db, migrations_mint)
|
||||||
# add a new keyset (with a new ID) which will be duplicated with a keyset with an
|
|
||||||
# old ID by mint migration m018_duplicate_deprecated_keyset_ids
|
|
||||||
# await ledger.activate_keyset(derivation_path=settings.mint_derivation_path, version="0.15.0")
|
|
||||||
# await migrations_mint.m018_duplicate_deprecated_keyset_ids(ledger.db)
|
|
||||||
|
|
||||||
ledger = Ledger(
|
ledger = Ledger(
|
||||||
db=Database("mint", settings.mint_database),
|
db=Database("mint", settings.mint_database),
|
||||||
seed=settings.mint_private_key,
|
seed=settings.mint_private_key,
|
||||||
@@ -107,8 +104,10 @@ async def ledger():
|
|||||||
# clear postgres database
|
# clear postgres database
|
||||||
db = Database("mint", settings.mint_database)
|
db = Database("mint", settings.mint_database)
|
||||||
async with db.connect() as conn:
|
async with db.connect() as conn:
|
||||||
|
# drop all tables
|
||||||
await conn.execute("DROP SCHEMA public CASCADE;")
|
await conn.execute("DROP SCHEMA public CASCADE;")
|
||||||
await conn.execute("CREATE SCHEMA public;")
|
await conn.execute("CREATE SCHEMA public;")
|
||||||
|
await db.engine.dispose()
|
||||||
|
|
||||||
wallets_module = importlib.import_module("cashu.lightning")
|
wallets_module = importlib.import_module("cashu.lightning")
|
||||||
lightning_backend = getattr(wallets_module, settings.mint_backend_bolt11_sat)()
|
lightning_backend = getattr(wallets_module, settings.mint_backend_bolt11_sat)()
|
||||||
@@ -125,6 +124,7 @@ async def ledger():
|
|||||||
ledger = await start_mint_init(ledger)
|
ledger = await start_mint_init(ledger)
|
||||||
yield ledger
|
yield ledger
|
||||||
print("teardown")
|
print("teardown")
|
||||||
|
await ledger.shutdown_ledger()
|
||||||
|
|
||||||
|
|
||||||
# # This fixture is used for tests that require API access to the mint
|
# # This fixture is used for tests that require API access to the mint
|
||||||
@@ -134,6 +134,7 @@ def mint():
|
|||||||
"cashu.mint.app:app",
|
"cashu.mint.app:app",
|
||||||
port=settings.mint_listen_port,
|
port=settings.mint_listen_port,
|
||||||
host=settings.mint_listen_host,
|
host=settings.mint_listen_host,
|
||||||
|
log_level="trace",
|
||||||
)
|
)
|
||||||
|
|
||||||
server = UvicornServer(config=config)
|
server = UvicornServer(config=config)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import asyncio
|
||||||
import hashlib
|
import hashlib
|
||||||
import importlib
|
import importlib
|
||||||
import json
|
import json
|
||||||
@@ -6,13 +7,44 @@ import random
|
|||||||
import string
|
import string
|
||||||
import time
|
import time
|
||||||
from subprocess import PIPE, Popen, TimeoutExpired
|
from subprocess import PIPE, Popen, TimeoutExpired
|
||||||
from typing import Tuple
|
from typing import List, Tuple, Union
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
from cashu.core.errors import CashuError
|
||||||
from cashu.core.settings import settings
|
from cashu.core.settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
async def assert_err(f, msg: Union[str, CashuError]):
|
||||||
|
"""Compute f() and expect an error message 'msg'."""
|
||||||
|
try:
|
||||||
|
await f
|
||||||
|
except Exception as exc:
|
||||||
|
error_message: str = str(exc.args[0])
|
||||||
|
if isinstance(msg, CashuError):
|
||||||
|
if msg.detail not in error_message:
|
||||||
|
raise Exception(
|
||||||
|
f"CashuError. Expected error: {msg.detail}, got: {error_message}"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
if msg not in error_message:
|
||||||
|
raise Exception(f"Expected error: {msg}, got: {error_message}")
|
||||||
|
return
|
||||||
|
raise Exception(f"Expected error: {msg}, got no error")
|
||||||
|
|
||||||
|
|
||||||
|
async def assert_err_multiple(f, msgs: List[str]):
|
||||||
|
"""Compute f() and expect an error message 'msg'."""
|
||||||
|
try:
|
||||||
|
await f
|
||||||
|
except Exception as exc:
|
||||||
|
for msg in msgs:
|
||||||
|
if msg in str(exc.args[0]):
|
||||||
|
return
|
||||||
|
raise Exception(f"Expected error: {msgs}, got: {exc.args[0]}")
|
||||||
|
raise Exception(f"Expected error: {msgs}, got no error")
|
||||||
|
|
||||||
|
|
||||||
def get_random_string(N: int = 10):
|
def get_random_string(N: int = 10):
|
||||||
return "".join(
|
return "".join(
|
||||||
random.SystemRandom().choice(string.ascii_uppercase + string.digits)
|
random.SystemRandom().choice(string.ascii_uppercase + string.digits)
|
||||||
@@ -157,6 +189,9 @@ def pay_onchain(address: str, sats: int) -> str:
|
|||||||
return run_cmd(cmd)
|
return run_cmd(cmd)
|
||||||
|
|
||||||
|
|
||||||
def pay_if_regtest(bolt11: str):
|
async def pay_if_regtest(bolt11: str):
|
||||||
if is_regtest:
|
if is_regtest:
|
||||||
pay_real_invoice(bolt11)
|
pay_real_invoice(bolt11)
|
||||||
|
if is_fake:
|
||||||
|
await asyncio.sleep(settings.fakewallet_delay_incoming_payment or 0)
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
|||||||
292
tests/test_db.py
292
tests/test_db.py
@@ -1,15 +1,81 @@
|
|||||||
|
import asyncio
|
||||||
import datetime
|
import datetime
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
|
from typing import List
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
import pytest_asyncio
|
||||||
|
|
||||||
from cashu.core import db
|
from cashu.core import db
|
||||||
from cashu.core.db import Connection, timestamp_now
|
from cashu.core.db import Connection
|
||||||
from cashu.core.migrations import backup_database
|
from cashu.core.migrations import backup_database
|
||||||
from cashu.core.settings import settings
|
from cashu.core.settings import settings
|
||||||
from cashu.mint.ledger import Ledger
|
from cashu.mint.ledger import Ledger
|
||||||
from tests.helpers import is_github_actions, is_postgres
|
from cashu.wallet.wallet import Wallet
|
||||||
|
from tests.conftest import SERVER_ENDPOINT
|
||||||
|
from tests.helpers import is_github_actions, is_postgres, pay_if_regtest
|
||||||
|
|
||||||
|
|
||||||
|
async def assert_err(f, msg):
|
||||||
|
"""Compute f() and expect an error message 'msg'."""
|
||||||
|
try:
|
||||||
|
await f
|
||||||
|
except Exception as exc:
|
||||||
|
if msg not in str(exc.args[0]):
|
||||||
|
raise Exception(f"Expected error: {msg}, got: {exc.args[0]}")
|
||||||
|
return
|
||||||
|
raise Exception(f"Expected error: {msg}, got no error")
|
||||||
|
|
||||||
|
|
||||||
|
async def assert_err_multiple(f, msgs: List[str]):
|
||||||
|
"""Compute f() and expect an error message 'msg'."""
|
||||||
|
try:
|
||||||
|
await f
|
||||||
|
except Exception as exc:
|
||||||
|
for msg in msgs:
|
||||||
|
if msg in str(exc.args[0]):
|
||||||
|
return
|
||||||
|
raise Exception(f"Expected error: {msgs}, got: {exc.args[0]}")
|
||||||
|
raise Exception(f"Expected error: {msgs}, got no error")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest_asyncio.fixture(scope="function")
|
||||||
|
async def wallet():
|
||||||
|
wallet = await Wallet.with_db(
|
||||||
|
url=SERVER_ENDPOINT,
|
||||||
|
db="test_data/wallet",
|
||||||
|
name="wallet",
|
||||||
|
)
|
||||||
|
await wallet.load_mint()
|
||||||
|
yield wallet
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_db_tables(ledger: Ledger):
|
||||||
|
async with ledger.db.connect() as conn:
|
||||||
|
if ledger.db.type == db.SQLITE:
|
||||||
|
tables_res = await conn.execute(
|
||||||
|
"SELECT name FROM sqlite_master WHERE type='table';"
|
||||||
|
)
|
||||||
|
elif ledger.db.type in {db.POSTGRES, db.COCKROACH}:
|
||||||
|
tables_res = await conn.execute(
|
||||||
|
"SELECT table_name FROM information_schema.tables WHERE table_schema ="
|
||||||
|
" 'public';"
|
||||||
|
)
|
||||||
|
tables = [t[0] for t in tables_res.all()]
|
||||||
|
tables_expected = [
|
||||||
|
"dbversions",
|
||||||
|
"keysets",
|
||||||
|
"proofs_used",
|
||||||
|
"proofs_pending",
|
||||||
|
"melt_quotes",
|
||||||
|
"mint_quotes",
|
||||||
|
"mint_pubkeys",
|
||||||
|
"promises",
|
||||||
|
]
|
||||||
|
for table in tables_expected:
|
||||||
|
assert table in tables
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -27,7 +93,7 @@ async def test_backup_db_migration(ledger: Ledger):
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_timestamp_now(ledger: Ledger):
|
async def test_timestamp_now(ledger: Ledger):
|
||||||
ts = timestamp_now(ledger.db)
|
ts = ledger.db.timestamp_now_str()
|
||||||
if ledger.db.type == db.SQLITE:
|
if ledger.db.type == db.SQLITE:
|
||||||
assert isinstance(ts, str)
|
assert isinstance(ts, str)
|
||||||
assert int(ts) <= time.time()
|
assert int(ts) <= time.time()
|
||||||
@@ -37,33 +103,203 @@ async def test_timestamp_now(ledger: Ledger):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_get_connection(ledger: Ledger):
|
async def test_db_connect(ledger: Ledger):
|
||||||
async with ledger.db.connect() as conn:
|
async with ledger.db.connect() as conn:
|
||||||
assert isinstance(conn, Connection)
|
assert isinstance(conn, Connection)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_db_tables(ledger: Ledger):
|
async def test_db_get_connection(ledger: Ledger):
|
||||||
async with ledger.db.connect() as conn:
|
async with ledger.db.get_connection() as conn:
|
||||||
if ledger.db.type == db.SQLITE:
|
assert isinstance(conn, Connection)
|
||||||
tables_res = await conn.execute(
|
|
||||||
"SELECT name FROM sqlite_master WHERE type='table';"
|
|
||||||
)
|
# @pytest.mark.asyncio
|
||||||
elif ledger.db.type in {db.POSTGRES, db.COCKROACH}:
|
# async def test_db_get_connection_locked(wallet: Wallet, ledger: Ledger):
|
||||||
tables_res = await conn.execute(
|
# invoice = await wallet.request_mint(64)
|
||||||
"SELECT table_name FROM information_schema.tables WHERE table_schema ="
|
|
||||||
" 'public';"
|
# async def get_connection():
|
||||||
)
|
# """This code makes sure that only the error of the second connection is raised (which we check in the assert_err)"""
|
||||||
tables = [t[0] for t in await tables_res.fetchall()]
|
# try:
|
||||||
tables_expected = [
|
# async with ledger.db.get_connection(lock_table="mint_quotes"):
|
||||||
"dbversions",
|
# try:
|
||||||
"keysets",
|
# async with ledger.db.get_connection(
|
||||||
"proofs_used",
|
# lock_table="mint_quotes", lock_timeout=0.1
|
||||||
"proofs_pending",
|
# ) as conn2:
|
||||||
"melt_quotes",
|
# # write something with conn1, we never reach this point if the lock works
|
||||||
"mint_quotes",
|
# await conn2.execute(
|
||||||
"mint_pubkeys",
|
# f"INSERT INTO mint_quotes (quote, amount) VALUES ('{invoice.id}', 100);"
|
||||||
"promises",
|
# )
|
||||||
]
|
# except Exception as exc:
|
||||||
for table in tables_expected:
|
# # this is expected to raise
|
||||||
assert table in tables
|
# raise Exception(f"conn2: {str(exc)}")
|
||||||
|
|
||||||
|
# except Exception as exc:
|
||||||
|
# if str(exc).startswith("conn2"):
|
||||||
|
# raise exc
|
||||||
|
# else:
|
||||||
|
# raise Exception("not expected to happen")
|
||||||
|
|
||||||
|
# await assert_err(get_connection(), "failed to acquire database lock")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_db_get_connection_lock_row(wallet: Wallet, ledger: Ledger):
|
||||||
|
if ledger.db.type == db.SQLITE:
|
||||||
|
pytest.skip("SQLite does not support row locking")
|
||||||
|
|
||||||
|
invoice = await wallet.request_mint(64)
|
||||||
|
|
||||||
|
async def get_connection():
|
||||||
|
"""This code makes sure that only the error of the second connection is raised (which we check in the assert_err)"""
|
||||||
|
try:
|
||||||
|
async with ledger.db.get_connection(
|
||||||
|
lock_table="mint_quotes",
|
||||||
|
lock_select_statement=f"quote='{invoice.id}'",
|
||||||
|
lock_timeout=0.1,
|
||||||
|
) as conn1:
|
||||||
|
await conn1.execute(
|
||||||
|
f"UPDATE mint_quotes SET amount=100 WHERE quote='{invoice.id}';"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
async with ledger.db.get_connection(
|
||||||
|
lock_table="mint_quotes",
|
||||||
|
lock_select_statement=f"quote='{invoice.id}'",
|
||||||
|
lock_timeout=0.1,
|
||||||
|
) as conn2:
|
||||||
|
# write something with conn1, we never reach this point if the lock works
|
||||||
|
await conn2.execute(
|
||||||
|
f"UPDATE mint_quotes SET amount=101 WHERE quote='{invoice.id}';"
|
||||||
|
)
|
||||||
|
except Exception as exc:
|
||||||
|
# this is expected to raise
|
||||||
|
raise Exception(f"conn2: {str(exc)}")
|
||||||
|
except Exception as exc:
|
||||||
|
if "conn2" in str(exc):
|
||||||
|
raise exc
|
||||||
|
else:
|
||||||
|
raise Exception(f"not expected to happen: {str(exc)}")
|
||||||
|
|
||||||
|
await assert_err(get_connection(), "failed to acquire database lock")
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_db_set_proofs_pending_race_condition(wallet: Wallet, ledger: Ledger):
|
||||||
|
# fill wallet
|
||||||
|
invoice = await wallet.request_mint(64)
|
||||||
|
await pay_if_regtest(invoice.bolt11)
|
||||||
|
await wallet.mint(64, id=invoice.id)
|
||||||
|
assert wallet.balance == 64
|
||||||
|
|
||||||
|
await assert_err_multiple(
|
||||||
|
asyncio.gather(
|
||||||
|
ledger.db_write._set_proofs_pending(wallet.proofs),
|
||||||
|
ledger.db_write._set_proofs_pending(wallet.proofs),
|
||||||
|
),
|
||||||
|
[
|
||||||
|
"failed to acquire database lock",
|
||||||
|
"proofs are pending",
|
||||||
|
], # depending on how fast the database is, it can be either
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_db_set_proofs_pending_delayed_no_race_condition(
|
||||||
|
wallet: Wallet, ledger: Ledger
|
||||||
|
):
|
||||||
|
# fill wallet
|
||||||
|
invoice = await wallet.request_mint(64)
|
||||||
|
await pay_if_regtest(invoice.bolt11)
|
||||||
|
await wallet.mint(64, id=invoice.id)
|
||||||
|
assert wallet.balance == 64
|
||||||
|
|
||||||
|
async def delayed_set_proofs_pending():
|
||||||
|
await asyncio.sleep(0.1)
|
||||||
|
await ledger.db_write._set_proofs_pending(wallet.proofs)
|
||||||
|
|
||||||
|
await assert_err(
|
||||||
|
asyncio.gather(
|
||||||
|
ledger.db_write._set_proofs_pending(wallet.proofs),
|
||||||
|
delayed_set_proofs_pending(),
|
||||||
|
),
|
||||||
|
"proofs are pending",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_db_set_proofs_pending_no_race_condition_different_proofs(
|
||||||
|
wallet: Wallet, ledger: Ledger
|
||||||
|
):
|
||||||
|
# fill wallet
|
||||||
|
invoice = await wallet.request_mint(64)
|
||||||
|
await pay_if_regtest(invoice.bolt11)
|
||||||
|
await wallet.mint(64, id=invoice.id, split=[32, 32])
|
||||||
|
assert wallet.balance == 64
|
||||||
|
assert len(wallet.proofs) == 2
|
||||||
|
|
||||||
|
asyncio.gather(
|
||||||
|
ledger.db_write._set_proofs_pending(wallet.proofs[:1]),
|
||||||
|
ledger.db_write._set_proofs_pending(wallet.proofs[1:]),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_db_get_connection_lock_different_row(wallet: Wallet, ledger: Ledger):
|
||||||
|
if ledger.db.type == db.SQLITE:
|
||||||
|
pytest.skip("SQLite does not support row locking")
|
||||||
|
# this should work since we lock two different rows
|
||||||
|
invoice = await wallet.request_mint(64)
|
||||||
|
invoice2 = await wallet.request_mint(64)
|
||||||
|
|
||||||
|
async def get_connection2():
|
||||||
|
"""This code makes sure that only the error of the second connection is raised (which we check in the assert_err)"""
|
||||||
|
try:
|
||||||
|
async with ledger.db.get_connection(
|
||||||
|
lock_table="mint_quotes",
|
||||||
|
lock_select_statement=f"quote='{invoice.id}'",
|
||||||
|
lock_timeout=0.1,
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
async with ledger.db.get_connection(
|
||||||
|
lock_table="mint_quotes",
|
||||||
|
lock_select_statement=f"quote='{invoice2.id}'",
|
||||||
|
lock_timeout=0.1,
|
||||||
|
) as conn2:
|
||||||
|
# write something with conn1, this time we should reach this block with postgres
|
||||||
|
quote = await ledger.crud.get_mint_quote(
|
||||||
|
quote_id=invoice2.id, db=ledger.db, conn=conn2
|
||||||
|
)
|
||||||
|
assert quote is not None
|
||||||
|
quote.amount = 100
|
||||||
|
await ledger.crud.update_mint_quote(
|
||||||
|
quote=quote, db=ledger.db, conn=conn2
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
# this is expected to raise
|
||||||
|
raise Exception(f"conn2: {str(exc)}")
|
||||||
|
|
||||||
|
except Exception as exc:
|
||||||
|
if "conn2" in str(exc):
|
||||||
|
raise exc
|
||||||
|
else:
|
||||||
|
raise Exception(f"not expected to happen: {str(exc)}")
|
||||||
|
|
||||||
|
await get_connection2()
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_db_lock_table(wallet: Wallet, ledger: Ledger):
|
||||||
|
# fill wallet
|
||||||
|
invoice = await wallet.request_mint(64)
|
||||||
|
await pay_if_regtest(invoice.bolt11)
|
||||||
|
|
||||||
|
await wallet.mint(64, id=invoice.id)
|
||||||
|
assert wallet.balance == 64
|
||||||
|
|
||||||
|
async with ledger.db.connect(lock_table="proofs_pending", lock_timeout=0.1) as conn:
|
||||||
|
assert isinstance(conn, Connection)
|
||||||
|
await assert_err(
|
||||||
|
ledger.db_write._set_proofs_pending(wallet.proofs),
|
||||||
|
"failed to acquire database lock",
|
||||||
|
)
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ async def test_get_keyset(ledger: Ledger):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_mint(ledger: Ledger):
|
async def test_mint(ledger: Ledger):
|
||||||
quote = await ledger.mint_quote(PostMintQuoteRequest(amount=8, unit="sat"))
|
quote = await ledger.mint_quote(PostMintQuoteRequest(amount=8, unit="sat"))
|
||||||
pay_if_regtest(quote.request)
|
await pay_if_regtest(quote.request)
|
||||||
blinded_messages_mock = [
|
blinded_messages_mock = [
|
||||||
BlindedMessage(
|
BlindedMessage(
|
||||||
amount=8,
|
amount=8,
|
||||||
@@ -89,7 +89,7 @@ async def test_mint(ledger: Ledger):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_mint_invalid_blinded_message(ledger: Ledger):
|
async def test_mint_invalid_blinded_message(ledger: Ledger):
|
||||||
quote = await ledger.mint_quote(PostMintQuoteRequest(amount=8, unit="sat"))
|
quote = await ledger.mint_quote(PostMintQuoteRequest(amount=8, unit="sat"))
|
||||||
pay_if_regtest(quote.request)
|
await pay_if_regtest(quote.request)
|
||||||
blinded_messages_mock_invalid_key = [
|
blinded_messages_mock_invalid_key = [
|
||||||
BlindedMessage(
|
BlindedMessage(
|
||||||
amount=8,
|
amount=8,
|
||||||
|
|||||||
@@ -153,7 +153,7 @@ async def test_api_keyset_keys_old_keyset_id(ledger: Ledger):
|
|||||||
)
|
)
|
||||||
async def test_split(ledger: Ledger, wallet: Wallet):
|
async def test_split(ledger: Ledger, wallet: Wallet):
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet.mint(64, id=invoice.id)
|
await wallet.mint(64, id=invoice.id)
|
||||||
assert wallet.balance == 64
|
assert wallet.balance == 64
|
||||||
secrets, rs, derivation_paths = await wallet.generate_n_secrets(2)
|
secrets, rs, derivation_paths = await wallet.generate_n_secrets(2)
|
||||||
@@ -162,7 +162,7 @@ async def test_split(ledger: Ledger, wallet: Wallet):
|
|||||||
inputs_payload = [p.to_dict() for p in wallet.proofs]
|
inputs_payload = [p.to_dict() for p in wallet.proofs]
|
||||||
outputs_payload = [o.dict() for o in outputs]
|
outputs_payload = [o.dict() for o in outputs]
|
||||||
payload = {"inputs": inputs_payload, "outputs": outputs_payload}
|
payload = {"inputs": inputs_payload, "outputs": outputs_payload}
|
||||||
response = httpx.post(f"{BASE_URL}/v1/swap", json=payload)
|
response = httpx.post(f"{BASE_URL}/v1/swap", json=payload, timeout=None)
|
||||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||||
result = response.json()
|
result = response.json()
|
||||||
assert len(result["signatures"]) == 2
|
assert len(result["signatures"]) == 2
|
||||||
@@ -208,7 +208,7 @@ async def test_mint_quote(ledger: Ledger):
|
|||||||
assert result["expiry"] == expiry
|
assert result["expiry"] == expiry
|
||||||
|
|
||||||
# pay the invoice
|
# pay the invoice
|
||||||
pay_if_regtest(result["request"])
|
await pay_if_regtest(result["request"])
|
||||||
|
|
||||||
# get mint quote again from api
|
# get mint quote again from api
|
||||||
response = httpx.get(
|
response = httpx.get(
|
||||||
@@ -234,7 +234,7 @@ async def test_mint_quote(ledger: Ledger):
|
|||||||
)
|
)
|
||||||
async def test_mint(ledger: Ledger, wallet: Wallet):
|
async def test_mint(ledger: Ledger, wallet: Wallet):
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
quote_id = invoice.id
|
quote_id = invoice.id
|
||||||
secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(10000, 10001)
|
secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(10000, 10001)
|
||||||
outputs, rs = wallet._construct_outputs([32, 32], secrets, rs)
|
outputs, rs = wallet._construct_outputs([32, 32], secrets, rs)
|
||||||
@@ -352,7 +352,7 @@ async def test_melt_quote_external(ledger: Ledger, wallet: Wallet):
|
|||||||
async def test_melt_internal(ledger: Ledger, wallet: Wallet):
|
async def test_melt_internal(ledger: Ledger, wallet: Wallet):
|
||||||
# internal invoice
|
# internal invoice
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet.mint(64, id=invoice.id)
|
await wallet.mint(64, id=invoice.id)
|
||||||
assert wallet.balance == 64
|
assert wallet.balance == 64
|
||||||
|
|
||||||
@@ -411,7 +411,7 @@ async def test_melt_internal(ledger: Ledger, wallet: Wallet):
|
|||||||
async def test_melt_external(ledger: Ledger, wallet: Wallet):
|
async def test_melt_external(ledger: Ledger, wallet: Wallet):
|
||||||
# internal invoice
|
# internal invoice
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet.mint(64, id=invoice.id)
|
await wallet.mint(64, id=invoice.id)
|
||||||
assert wallet.balance == 64
|
assert wallet.balance == 64
|
||||||
|
|
||||||
@@ -439,6 +439,7 @@ async def test_melt_external(ledger: Ledger, wallet: Wallet):
|
|||||||
},
|
},
|
||||||
timeout=None,
|
timeout=None,
|
||||||
)
|
)
|
||||||
|
response.raise_for_status()
|
||||||
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
assert response.status_code == 200, f"{response.url} {response.status_code}"
|
||||||
result = response.json()
|
result = response.json()
|
||||||
assert result.get("payment_preimage") is not None
|
assert result.get("payment_preimage") is not None
|
||||||
@@ -486,7 +487,7 @@ async def test_api_check_state(ledger: Ledger):
|
|||||||
)
|
)
|
||||||
async def test_api_restore(ledger: Ledger, wallet: Wallet):
|
async def test_api_restore(ledger: Ledger, wallet: Wallet):
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet.mint(64, id=invoice.id)
|
await wallet.mint(64, id=invoice.id)
|
||||||
assert wallet.balance == 64
|
assert wallet.balance == 64
|
||||||
secret_counter = await bump_secret_derivation(
|
secret_counter = await bump_secret_derivation(
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ async def test_api_keyset_keys(ledger: Ledger):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_split(ledger: Ledger, wallet: Wallet):
|
async def test_split(ledger: Ledger, wallet: Wallet):
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet.mint(64, id=invoice.id)
|
await wallet.mint(64, id=invoice.id)
|
||||||
assert wallet.balance == 64
|
assert wallet.balance == 64
|
||||||
secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(20000, 20001)
|
secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(20000, 20001)
|
||||||
@@ -88,7 +88,7 @@ async def test_split(ledger: Ledger, wallet: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_split_deprecated_with_amount(ledger: Ledger, wallet: Wallet):
|
async def test_split_deprecated_with_amount(ledger: Ledger, wallet: Wallet):
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet.mint(64, id=invoice.id)
|
await wallet.mint(64, id=invoice.id)
|
||||||
assert wallet.balance == 64
|
assert wallet.balance == 64
|
||||||
secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(80000, 80001)
|
secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(80000, 80001)
|
||||||
@@ -124,7 +124,7 @@ async def test_api_mint_validation(ledger):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_mint(ledger: Ledger, wallet: Wallet):
|
async def test_mint(ledger: Ledger, wallet: Wallet):
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
quote_id = invoice.id
|
quote_id = invoice.id
|
||||||
secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(10000, 10001)
|
secrets, rs, derivation_paths = await wallet.generate_secrets_from_to(10000, 10001)
|
||||||
outputs, rs = wallet._construct_outputs([32, 32], secrets, rs)
|
outputs, rs = wallet._construct_outputs([32, 32], secrets, rs)
|
||||||
@@ -148,9 +148,9 @@ async def test_mint(ledger: Ledger, wallet: Wallet):
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_melt_internal(ledger: Ledger, wallet: Wallet):
|
async def test_melt_internal(ledger: Ledger, wallet: Wallet):
|
||||||
# internal invoice
|
# fill wallet
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet.mint(64, id=invoice.id)
|
await wallet.mint(64, id=invoice.id)
|
||||||
assert wallet.balance == 64
|
assert wallet.balance == 64
|
||||||
|
|
||||||
@@ -190,15 +190,13 @@ async def test_melt_internal_no_change_outputs(ledger: Ledger, wallet: Wallet):
|
|||||||
# Clients without NUT-08 will not send change outputs
|
# Clients without NUT-08 will not send change outputs
|
||||||
# internal invoice
|
# internal invoice
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet.mint(64, id=invoice.id)
|
await wallet.mint(64, id=invoice.id)
|
||||||
assert wallet.balance == 64
|
assert wallet.balance == 64
|
||||||
|
|
||||||
# create invoice to melt to
|
# create invoice to melt to
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
|
|
||||||
invoice_payment_request = invoice.bolt11
|
invoice_payment_request = invoice.bolt11
|
||||||
|
|
||||||
quote = await wallet.melt_quote(invoice_payment_request)
|
quote = await wallet.melt_quote(invoice_payment_request)
|
||||||
assert quote.amount == 64
|
assert quote.amount == 64
|
||||||
assert quote.fee_reserve == 0
|
assert quote.fee_reserve == 0
|
||||||
@@ -231,7 +229,7 @@ async def test_melt_internal_no_change_outputs(ledger: Ledger, wallet: Wallet):
|
|||||||
async def test_melt_external(ledger: Ledger, wallet: Wallet):
|
async def test_melt_external(ledger: Ledger, wallet: Wallet):
|
||||||
# internal invoice
|
# internal invoice
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet.mint(64, id=invoice.id)
|
await wallet.mint(64, id=invoice.id)
|
||||||
assert wallet.balance == 64
|
assert wallet.balance == 64
|
||||||
|
|
||||||
@@ -325,7 +323,7 @@ async def test_api_check_state(ledger: Ledger):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_api_restore(ledger: Ledger, wallet: Wallet):
|
async def test_api_restore(ledger: Ledger, wallet: Wallet):
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet.mint(64, id=invoice.id)
|
await wallet.mint(64, id=invoice.id)
|
||||||
assert wallet.balance == 64
|
assert wallet.balance == 64
|
||||||
secret_counter = await bump_secret_derivation(
|
secret_counter = await bump_secret_derivation(
|
||||||
|
|||||||
@@ -1,23 +1,18 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
|
|
||||||
|
from cashu.core.base import MeltQuoteState, MintQuoteState, ProofSpentState
|
||||||
from cashu.core.models import PostMeltQuoteRequest
|
from cashu.core.models import PostMeltQuoteRequest
|
||||||
from cashu.mint.ledger import Ledger
|
from cashu.mint.ledger import Ledger
|
||||||
from cashu.wallet.wallet import Wallet
|
from cashu.wallet.wallet import Wallet
|
||||||
from cashu.wallet.wallet import Wallet as Wallet1
|
from cashu.wallet.wallet import Wallet as Wallet1
|
||||||
from tests.conftest import SERVER_ENDPOINT
|
from tests.conftest import SERVER_ENDPOINT
|
||||||
from tests.helpers import is_postgres
|
from tests.helpers import (
|
||||||
|
assert_err,
|
||||||
|
is_github_actions,
|
||||||
async def assert_err(f, msg):
|
is_postgres,
|
||||||
"""Compute f() and expect an error message 'msg'."""
|
pay_if_regtest,
|
||||||
try:
|
)
|
||||||
await f
|
|
||||||
except Exception as exc:
|
|
||||||
if msg not in str(exc.args[0]):
|
|
||||||
raise Exception(f"Expected error: {msg}, got: {exc.args[0]}")
|
|
||||||
return
|
|
||||||
raise Exception(f"Expected error: {msg}, got no error")
|
|
||||||
|
|
||||||
|
|
||||||
@pytest_asyncio.fixture(scope="function")
|
@pytest_asyncio.fixture(scope="function")
|
||||||
@@ -31,6 +26,35 @@ async def wallet1(ledger: Ledger):
|
|||||||
yield wallet1
|
yield wallet1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
@pytest.mark.skipif(is_github_actions, reason="GITHUB_ACTIONS")
|
||||||
|
async def test_mint_proofs_pending(wallet1: Wallet, ledger: Ledger):
|
||||||
|
invoice = await wallet1.request_mint(64)
|
||||||
|
await pay_if_regtest(invoice.bolt11)
|
||||||
|
await wallet1.mint(64, id=invoice.id)
|
||||||
|
proofs = wallet1.proofs.copy()
|
||||||
|
|
||||||
|
proofs_states_before_split = await wallet1.check_proof_state(proofs)
|
||||||
|
assert all(
|
||||||
|
[s.state == ProofSpentState.unspent for s in proofs_states_before_split.states]
|
||||||
|
)
|
||||||
|
|
||||||
|
await ledger.db_write._set_proofs_pending(proofs)
|
||||||
|
|
||||||
|
proof_states = await wallet1.check_proof_state(proofs)
|
||||||
|
assert all([s.state == ProofSpentState.pending for s in proof_states.states])
|
||||||
|
await assert_err(wallet1.split(wallet1.proofs, 20), "proofs are pending.")
|
||||||
|
|
||||||
|
await ledger.db_write._unset_proofs_pending(proofs)
|
||||||
|
|
||||||
|
await wallet1.split(proofs, 20)
|
||||||
|
|
||||||
|
proofs_states_after_split = await wallet1.check_proof_state(proofs)
|
||||||
|
assert all(
|
||||||
|
[s.state == ProofSpentState.spent for s in proofs_states_after_split.states]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_mint_quote(wallet1: Wallet, ledger: Ledger):
|
async def test_mint_quote(wallet1: Wallet, ledger: Ledger):
|
||||||
invoice = await wallet1.request_mint(128)
|
invoice = await wallet1.request_mint(128)
|
||||||
@@ -42,10 +66,63 @@ async def test_mint_quote(wallet1: Wallet, ledger: Ledger):
|
|||||||
assert quote.unit == "sat"
|
assert quote.unit == "sat"
|
||||||
assert not quote.paid
|
assert not quote.paid
|
||||||
assert quote.checking_id == invoice.payment_hash
|
assert quote.checking_id == invoice.payment_hash
|
||||||
assert quote.paid_time is None
|
# assert quote.paid_time is None
|
||||||
assert quote.created_time
|
assert quote.created_time
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_mint_quote_state_transitions(wallet1: Wallet, ledger: Ledger):
|
||||||
|
invoice = await wallet1.request_mint(128)
|
||||||
|
assert invoice is not None
|
||||||
|
quote = await ledger.crud.get_mint_quote(quote_id=invoice.id, db=ledger.db)
|
||||||
|
assert quote is not None
|
||||||
|
assert quote.quote == invoice.id
|
||||||
|
assert quote.state == MintQuoteState.unpaid
|
||||||
|
|
||||||
|
# set pending again
|
||||||
|
async def set_state(quote, state):
|
||||||
|
quote.state = state
|
||||||
|
|
||||||
|
# set pending
|
||||||
|
await assert_err(
|
||||||
|
set_state(quote, MintQuoteState.pending),
|
||||||
|
"Cannot change state of an unpaid mint quote",
|
||||||
|
)
|
||||||
|
|
||||||
|
# set unpaid
|
||||||
|
await assert_err(
|
||||||
|
set_state(quote, MintQuoteState.unpaid),
|
||||||
|
"Cannot change state of an unpaid mint quote",
|
||||||
|
)
|
||||||
|
|
||||||
|
# set paid
|
||||||
|
quote.state = MintQuoteState.paid
|
||||||
|
|
||||||
|
# set unpaid
|
||||||
|
await assert_err(
|
||||||
|
set_state(quote, MintQuoteState.unpaid),
|
||||||
|
"Cannot change state of a paid mint quote to unpaid.",
|
||||||
|
)
|
||||||
|
|
||||||
|
# set pending
|
||||||
|
quote.state = MintQuoteState.pending
|
||||||
|
|
||||||
|
# set paid again
|
||||||
|
quote.state = MintQuoteState.paid
|
||||||
|
|
||||||
|
# set pending again
|
||||||
|
quote.state = MintQuoteState.pending
|
||||||
|
|
||||||
|
# set issued
|
||||||
|
quote.state = MintQuoteState.issued
|
||||||
|
|
||||||
|
# set pending again
|
||||||
|
await assert_err(
|
||||||
|
set_state(quote, MintQuoteState.pending),
|
||||||
|
"Cannot change state of an issued mint quote.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_get_mint_quote_by_request(wallet1: Wallet, ledger: Ledger):
|
async def test_get_mint_quote_by_request(wallet1: Wallet, ledger: Ledger):
|
||||||
invoice = await wallet1.request_mint(128)
|
invoice = await wallet1.request_mint(128)
|
||||||
@@ -56,7 +133,7 @@ async def test_get_mint_quote_by_request(wallet1: Wallet, ledger: Ledger):
|
|||||||
assert quote.amount == 128
|
assert quote.amount == 128
|
||||||
assert quote.unit == "sat"
|
assert quote.unit == "sat"
|
||||||
assert not quote.paid
|
assert not quote.paid
|
||||||
assert quote.paid_time is None
|
# assert quote.paid_time is None
|
||||||
assert quote.created_time
|
assert quote.created_time
|
||||||
|
|
||||||
|
|
||||||
@@ -74,10 +151,112 @@ async def test_melt_quote(wallet1: Wallet, ledger: Ledger):
|
|||||||
assert quote.unit == "sat"
|
assert quote.unit == "sat"
|
||||||
assert not quote.paid
|
assert not quote.paid
|
||||||
assert quote.checking_id == invoice.payment_hash
|
assert quote.checking_id == invoice.payment_hash
|
||||||
assert quote.paid_time is None
|
# assert quote.paid_time is None
|
||||||
assert quote.created_time
|
assert quote.created_time
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_melt_quote_set_pending(wallet1: Wallet, ledger: Ledger):
|
||||||
|
invoice = await wallet1.request_mint(128)
|
||||||
|
assert invoice is not None
|
||||||
|
melt_quote = await ledger.melt_quote(
|
||||||
|
PostMeltQuoteRequest(request=invoice.bolt11, unit="sat")
|
||||||
|
)
|
||||||
|
assert melt_quote is not None
|
||||||
|
assert melt_quote.state == MeltQuoteState.unpaid.value
|
||||||
|
quote = await ledger.crud.get_melt_quote(quote_id=melt_quote.quote, db=ledger.db)
|
||||||
|
assert quote is not None
|
||||||
|
assert quote.quote == melt_quote.quote
|
||||||
|
assert quote.state == MeltQuoteState.unpaid
|
||||||
|
previous_state = quote.state
|
||||||
|
await ledger.db_write._set_melt_quote_pending(quote)
|
||||||
|
quote = await ledger.crud.get_melt_quote(quote_id=melt_quote.quote, db=ledger.db)
|
||||||
|
assert quote is not None
|
||||||
|
assert quote.state == MeltQuoteState.pending
|
||||||
|
|
||||||
|
# set unpending
|
||||||
|
await ledger.db_write._unset_melt_quote_pending(quote, previous_state)
|
||||||
|
quote = await ledger.crud.get_melt_quote(quote_id=melt_quote.quote, db=ledger.db)
|
||||||
|
assert quote is not None
|
||||||
|
assert quote.state == previous_state
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_melt_quote_state_transitions(wallet1: Wallet, ledger: Ledger):
|
||||||
|
invoice = await wallet1.request_mint(128)
|
||||||
|
assert invoice is not None
|
||||||
|
melt_quote = await ledger.melt_quote(
|
||||||
|
PostMeltQuoteRequest(request=invoice.bolt11, unit="sat")
|
||||||
|
)
|
||||||
|
quote = await ledger.crud.get_melt_quote(quote_id=melt_quote.quote, db=ledger.db)
|
||||||
|
assert quote is not None
|
||||||
|
assert quote.quote == melt_quote.quote
|
||||||
|
assert quote.state == MeltQuoteState.unpaid
|
||||||
|
|
||||||
|
# set pending
|
||||||
|
quote.state = MeltQuoteState.pending
|
||||||
|
|
||||||
|
# set unpaid
|
||||||
|
quote.state = MeltQuoteState.unpaid
|
||||||
|
|
||||||
|
# set paid
|
||||||
|
quote.state = MeltQuoteState.paid
|
||||||
|
|
||||||
|
# set pending again
|
||||||
|
async def set_state(quote, state):
|
||||||
|
quote.state = state
|
||||||
|
|
||||||
|
await assert_err(
|
||||||
|
set_state(quote, MeltQuoteState.pending),
|
||||||
|
"Cannot change state of a paid melt quote.",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_mint_quote_set_pending(wallet1: Wallet, ledger: Ledger):
|
||||||
|
invoice = await wallet1.request_mint(128)
|
||||||
|
assert invoice is not None
|
||||||
|
quote = await ledger.crud.get_mint_quote(quote_id=invoice.id, db=ledger.db)
|
||||||
|
assert quote is not None
|
||||||
|
assert quote.state == MintQuoteState.unpaid
|
||||||
|
|
||||||
|
# pay_if_regtest pays on regtest, get_mint_quote pays on FakeWallet
|
||||||
|
await pay_if_regtest(invoice.bolt11)
|
||||||
|
_ = await ledger.get_mint_quote(invoice.id)
|
||||||
|
|
||||||
|
quote = await ledger.crud.get_mint_quote(quote_id=invoice.id, db=ledger.db)
|
||||||
|
assert quote is not None
|
||||||
|
assert quote.state == MintQuoteState.paid
|
||||||
|
|
||||||
|
previous_state = MintQuoteState.paid
|
||||||
|
await ledger.db_write._set_mint_quote_pending(quote.quote)
|
||||||
|
quote = await ledger.crud.get_mint_quote(quote_id=invoice.id, db=ledger.db)
|
||||||
|
assert quote is not None
|
||||||
|
assert quote.state == MintQuoteState.pending
|
||||||
|
|
||||||
|
# try to mint while pending
|
||||||
|
await assert_err(wallet1.mint(128, id=invoice.id), "Mint quote already pending.")
|
||||||
|
|
||||||
|
# set unpending
|
||||||
|
await ledger.db_write._unset_mint_quote_pending(quote.quote, previous_state)
|
||||||
|
|
||||||
|
quote = await ledger.crud.get_mint_quote(quote_id=invoice.id, db=ledger.db)
|
||||||
|
assert quote is not None
|
||||||
|
assert quote.state == previous_state
|
||||||
|
assert quote.state == MintQuoteState.paid
|
||||||
|
|
||||||
|
# # set paid and mint again
|
||||||
|
# quote.state = MintQuoteState.paid
|
||||||
|
# await ledger.crud.update_mint_quote(quote=quote, db=ledger.db)
|
||||||
|
|
||||||
|
await wallet1.mint(quote.amount, id=quote.quote)
|
||||||
|
|
||||||
|
# check if quote is issued
|
||||||
|
quote = await ledger.crud.get_mint_quote(quote_id=invoice.id, db=ledger.db)
|
||||||
|
assert quote is not None
|
||||||
|
assert quote.state == MintQuoteState.issued
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.skipif(not is_postgres, reason="only works with Postgres")
|
@pytest.mark.skipif(not is_postgres, reason="only works with Postgres")
|
||||||
async def test_postgres_working():
|
async def test_postgres_working():
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ def set_ledger_keyset_fees(
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_get_fees_for_proofs(wallet1: Wallet, ledger: Ledger):
|
async def test_get_fees_for_proofs(wallet1: Wallet, ledger: Ledger):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, split=[1] * 64, id=invoice.id)
|
await wallet1.mint(64, split=[1] * 64, id=invoice.id)
|
||||||
|
|
||||||
# two proofs
|
# two proofs
|
||||||
@@ -115,7 +115,7 @@ async def test_split_with_fees(wallet1: Wallet, ledger: Ledger):
|
|||||||
# set fees to 100 ppk
|
# set fees to 100 ppk
|
||||||
set_ledger_keyset_fees(100, ledger)
|
set_ledger_keyset_fees(100, ledger)
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
|
|
||||||
send_proofs, _ = await wallet1.select_to_send(wallet1.proofs, 10)
|
send_proofs, _ = await wallet1.select_to_send(wallet1.proofs, 10)
|
||||||
@@ -133,7 +133,7 @@ async def test_split_with_high_fees(wallet1: Wallet, ledger: Ledger):
|
|||||||
# set fees to 100 ppk
|
# set fees to 100 ppk
|
||||||
set_ledger_keyset_fees(1234, ledger)
|
set_ledger_keyset_fees(1234, ledger)
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
|
|
||||||
send_proofs, _ = await wallet1.select_to_send(wallet1.proofs, 10)
|
send_proofs, _ = await wallet1.select_to_send(wallet1.proofs, 10)
|
||||||
@@ -151,7 +151,7 @@ async def test_split_not_enough_fees(wallet1: Wallet, ledger: Ledger):
|
|||||||
# set fees to 100 ppk
|
# set fees to 100 ppk
|
||||||
set_ledger_keyset_fees(100, ledger)
|
set_ledger_keyset_fees(100, ledger)
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
|
|
||||||
send_proofs, _ = await wallet1.select_to_send(wallet1.proofs, 10)
|
send_proofs, _ = await wallet1.select_to_send(wallet1.proofs, 10)
|
||||||
@@ -173,6 +173,7 @@ async def test_melt_internal(wallet1: Wallet, ledger: Ledger):
|
|||||||
|
|
||||||
# mint twice so we have enough to pay the second invoice back
|
# mint twice so we have enough to pay the second invoice back
|
||||||
invoice = await wallet1.request_mint(128)
|
invoice = await wallet1.request_mint(128)
|
||||||
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(128, id=invoice.id)
|
await wallet1.mint(128, id=invoice.id)
|
||||||
assert wallet1.balance == 128
|
assert wallet1.balance == 128
|
||||||
|
|
||||||
@@ -217,7 +218,7 @@ async def test_melt_external_with_fees(wallet1: Wallet, ledger: Ledger):
|
|||||||
|
|
||||||
# mint twice so we have enough to pay the second invoice back
|
# mint twice so we have enough to pay the second invoice back
|
||||||
invoice = await wallet1.request_mint(128)
|
invoice = await wallet1.request_mint(128)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(128, id=invoice.id)
|
await wallet1.mint(128, id=invoice.id)
|
||||||
assert wallet1.balance == 128
|
assert wallet1.balance == 128
|
||||||
|
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ async def test_startup_fakewallet_pending_quote_pending(ledger: Ledger):
|
|||||||
async def test_startup_regtest_pending_quote_pending(wallet: Wallet, ledger: Ledger):
|
async def test_startup_regtest_pending_quote_pending(wallet: Wallet, ledger: Ledger):
|
||||||
# fill wallet
|
# fill wallet
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet.mint(64, id=invoice.id)
|
await wallet.mint(64, id=invoice.id)
|
||||||
assert wallet.balance == 64
|
assert wallet.balance == 64
|
||||||
|
|
||||||
@@ -286,7 +286,7 @@ async def test_startup_regtest_pending_quote_pending(wallet: Wallet, ledger: Led
|
|||||||
async def test_startup_regtest_pending_quote_success(wallet: Wallet, ledger: Ledger):
|
async def test_startup_regtest_pending_quote_success(wallet: Wallet, ledger: Ledger):
|
||||||
# fill wallet
|
# fill wallet
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet.mint(64, id=invoice.id)
|
await wallet.mint(64, id=invoice.id)
|
||||||
assert wallet.balance == 64
|
assert wallet.balance == 64
|
||||||
|
|
||||||
@@ -334,7 +334,7 @@ async def test_startup_regtest_pending_quote_failure(wallet: Wallet, ledger: Led
|
|||||||
"""Simulate a failure to pay the hodl invoice by canceling it."""
|
"""Simulate a failure to pay the hodl invoice by canceling it."""
|
||||||
# fill wallet
|
# fill wallet
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet.mint(64, id=invoice.id)
|
await wallet.mint(64, id=invoice.id)
|
||||||
assert wallet.balance == 64
|
assert wallet.balance == 64
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import pytest_asyncio
|
import pytest_asyncio
|
||||||
|
|
||||||
|
from cashu.core.base import MeltQuoteState, MintQuoteState
|
||||||
from cashu.core.helpers import sum_proofs
|
from cashu.core.helpers import sum_proofs
|
||||||
from cashu.core.models import PostMeltQuoteRequest, PostMintQuoteRequest
|
from cashu.core.models import PostMeltQuoteRequest, PostMintQuoteRequest
|
||||||
from cashu.mint.ledger import Ledger
|
from cashu.mint.ledger import Ledger
|
||||||
@@ -37,7 +38,9 @@ async def wallet1(ledger: Ledger):
|
|||||||
async def test_melt_internal(wallet1: Wallet, ledger: Ledger):
|
async def test_melt_internal(wallet1: Wallet, ledger: Ledger):
|
||||||
# mint twice so we have enough to pay the second invoice back
|
# mint twice so we have enough to pay the second invoice back
|
||||||
invoice = await wallet1.request_mint(128)
|
invoice = await wallet1.request_mint(128)
|
||||||
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(128, id=invoice.id)
|
await wallet1.mint(128, id=invoice.id)
|
||||||
|
await pay_if_regtest(invoice.bolt11)
|
||||||
assert wallet1.balance == 128
|
assert wallet1.balance == 128
|
||||||
|
|
||||||
# create a mint quote so that we can melt to it internally
|
# create a mint quote so that we can melt to it internally
|
||||||
@@ -48,17 +51,21 @@ async def test_melt_internal(wallet1: Wallet, ledger: Ledger):
|
|||||||
PostMeltQuoteRequest(request=invoice_payment_request, unit="sat")
|
PostMeltQuoteRequest(request=invoice_payment_request, unit="sat")
|
||||||
)
|
)
|
||||||
assert not melt_quote.paid
|
assert not melt_quote.paid
|
||||||
|
assert melt_quote.state == MeltQuoteState.unpaid.value
|
||||||
|
|
||||||
assert melt_quote.amount == 64
|
assert melt_quote.amount == 64
|
||||||
assert melt_quote.fee_reserve == 0
|
assert melt_quote.fee_reserve == 0
|
||||||
|
|
||||||
melt_quote_pre_payment = await ledger.get_melt_quote(melt_quote.quote)
|
melt_quote_pre_payment = await ledger.get_melt_quote(melt_quote.quote)
|
||||||
assert not melt_quote_pre_payment.paid, "melt quote should not be paid"
|
assert not melt_quote_pre_payment.paid, "melt quote should not be paid"
|
||||||
|
assert melt_quote_pre_payment.state == MeltQuoteState.unpaid
|
||||||
|
|
||||||
keep_proofs, send_proofs = await wallet1.split_to_send(wallet1.proofs, 64)
|
keep_proofs, send_proofs = await wallet1.split_to_send(wallet1.proofs, 64)
|
||||||
await ledger.melt(proofs=send_proofs, quote=melt_quote.quote)
|
await ledger.melt(proofs=send_proofs, quote=melt_quote.quote)
|
||||||
|
|
||||||
melt_quote_post_payment = await ledger.get_melt_quote(melt_quote.quote)
|
melt_quote_post_payment = await ledger.get_melt_quote(melt_quote.quote)
|
||||||
assert melt_quote_post_payment.paid, "melt quote should be paid"
|
assert melt_quote_post_payment.paid, "melt quote should be paid"
|
||||||
|
assert melt_quote_post_payment.state == MeltQuoteState.paid
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@@ -66,7 +73,7 @@ async def test_melt_internal(wallet1: Wallet, ledger: Ledger):
|
|||||||
async def test_melt_external(wallet1: Wallet, ledger: Ledger):
|
async def test_melt_external(wallet1: Wallet, ledger: Ledger):
|
||||||
# mint twice so we have enough to pay the second invoice back
|
# mint twice so we have enough to pay the second invoice back
|
||||||
invoice = await wallet1.request_mint(128)
|
invoice = await wallet1.request_mint(128)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(128, id=invoice.id)
|
await wallet1.mint(128, id=invoice.id)
|
||||||
assert wallet1.balance == 128
|
assert wallet1.balance == 128
|
||||||
|
|
||||||
@@ -74,6 +81,9 @@ async def test_melt_external(wallet1: Wallet, ledger: Ledger):
|
|||||||
invoice_payment_request = invoice_dict["payment_request"]
|
invoice_payment_request = invoice_dict["payment_request"]
|
||||||
|
|
||||||
mint_quote = await wallet1.melt_quote(invoice_payment_request)
|
mint_quote = await wallet1.melt_quote(invoice_payment_request)
|
||||||
|
assert not mint_quote.paid, "mint quote should not be paid"
|
||||||
|
assert mint_quote.state == MeltQuoteState.unpaid.value
|
||||||
|
|
||||||
total_amount = mint_quote.amount + mint_quote.fee_reserve
|
total_amount = mint_quote.amount + mint_quote.fee_reserve
|
||||||
keep_proofs, send_proofs = await wallet1.split_to_send(wallet1.proofs, total_amount)
|
keep_proofs, send_proofs = await wallet1.split_to_send(wallet1.proofs, total_amount)
|
||||||
melt_quote = await ledger.melt_quote(
|
melt_quote = await ledger.melt_quote(
|
||||||
@@ -82,22 +92,25 @@ async def test_melt_external(wallet1: Wallet, ledger: Ledger):
|
|||||||
|
|
||||||
melt_quote_pre_payment = await ledger.get_melt_quote(melt_quote.quote)
|
melt_quote_pre_payment = await ledger.get_melt_quote(melt_quote.quote)
|
||||||
assert not melt_quote_pre_payment.paid, "melt quote should not be paid"
|
assert not melt_quote_pre_payment.paid, "melt quote should not be paid"
|
||||||
|
assert melt_quote_pre_payment.state == MeltQuoteState.unpaid
|
||||||
|
|
||||||
assert not melt_quote.paid, "melt quote should not be paid"
|
assert not melt_quote.paid, "melt quote should not be paid"
|
||||||
await ledger.melt(proofs=send_proofs, quote=melt_quote.quote)
|
await ledger.melt(proofs=send_proofs, quote=melt_quote.quote)
|
||||||
|
|
||||||
melt_quote_post_payment = await ledger.get_melt_quote(melt_quote.quote)
|
melt_quote_post_payment = await ledger.get_melt_quote(melt_quote.quote)
|
||||||
assert melt_quote_post_payment.paid, "melt quote should be paid"
|
assert melt_quote_post_payment.paid, "melt quote should be paid"
|
||||||
|
assert melt_quote_post_payment.state == MeltQuoteState.paid
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.skipif(is_regtest, reason="only works with FakeWallet")
|
@pytest.mark.skipif(is_regtest, reason="only works with FakeWallet")
|
||||||
async def test_mint_internal(wallet1: Wallet, ledger: Ledger):
|
async def test_mint_internal(wallet1: Wallet, ledger: Ledger):
|
||||||
invoice = await wallet1.request_mint(128)
|
invoice = await wallet1.request_mint(128)
|
||||||
|
await pay_if_regtest(invoice.bolt11)
|
||||||
mint_quote = await ledger.get_mint_quote(invoice.id)
|
mint_quote = await ledger.get_mint_quote(invoice.id)
|
||||||
|
|
||||||
assert mint_quote.paid, "mint quote should be paid"
|
assert mint_quote.paid, "mint quote should be paid"
|
||||||
|
assert mint_quote.state == MintQuoteState.paid
|
||||||
|
|
||||||
output_amounts = [128]
|
output_amounts = [128]
|
||||||
secrets, rs, derivation_paths = await wallet1.generate_n_secrets(
|
secrets, rs, derivation_paths = await wallet1.generate_n_secrets(
|
||||||
@@ -111,24 +124,32 @@ async def test_mint_internal(wallet1: Wallet, ledger: Ledger):
|
|||||||
"outputs have already been signed before.",
|
"outputs have already been signed before.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
mint_quote_after_payment = await ledger.get_mint_quote(invoice.id)
|
||||||
|
assert mint_quote_after_payment.paid, "mint quote should be paid"
|
||||||
|
assert mint_quote_after_payment.state == MintQuoteState.issued
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
@pytest.mark.skipif(is_fake, reason="only works with Regtest")
|
@pytest.mark.skipif(is_fake, reason="only works with Regtest")
|
||||||
async def test_mint_external(wallet1: Wallet, ledger: Ledger):
|
async def test_mint_external(wallet1: Wallet, ledger: Ledger):
|
||||||
quote = await ledger.mint_quote(PostMintQuoteRequest(amount=128, unit="sat"))
|
quote = await ledger.mint_quote(PostMintQuoteRequest(amount=128, unit="sat"))
|
||||||
|
assert not quote.paid, "mint quote should not be paid"
|
||||||
|
assert quote.state == MintQuoteState.unpaid
|
||||||
|
|
||||||
mint_quote = await ledger.get_mint_quote(quote.quote)
|
mint_quote = await ledger.get_mint_quote(quote.quote)
|
||||||
assert not mint_quote.paid, "mint quote already paid"
|
assert not mint_quote.paid, "mint quote already paid"
|
||||||
|
assert mint_quote.state == MintQuoteState.unpaid
|
||||||
|
|
||||||
await assert_err(
|
await assert_err(
|
||||||
wallet1.mint(128, id=quote.quote),
|
wallet1.mint(128, id=quote.quote),
|
||||||
"quote not paid",
|
"quote not paid",
|
||||||
)
|
)
|
||||||
|
|
||||||
pay_if_regtest(quote.request)
|
await pay_if_regtest(quote.request)
|
||||||
|
|
||||||
mint_quote = await ledger.get_mint_quote(quote.quote)
|
mint_quote = await ledger.get_mint_quote(quote.quote)
|
||||||
assert mint_quote.paid, "mint quote should be paid"
|
assert mint_quote.paid, "mint quote should be paid"
|
||||||
|
assert mint_quote.state == MintQuoteState.paid
|
||||||
|
|
||||||
output_amounts = [128]
|
output_amounts = [128]
|
||||||
secrets, rs, derivation_paths = await wallet1.generate_n_secrets(
|
secrets, rs, derivation_paths = await wallet1.generate_n_secrets(
|
||||||
@@ -137,11 +158,15 @@ async def test_mint_external(wallet1: Wallet, ledger: Ledger):
|
|||||||
outputs, rs = wallet1._construct_outputs(output_amounts, secrets, rs)
|
outputs, rs = wallet1._construct_outputs(output_amounts, secrets, rs)
|
||||||
await ledger.mint(outputs=outputs, quote_id=quote.quote)
|
await ledger.mint(outputs=outputs, quote_id=quote.quote)
|
||||||
|
|
||||||
|
mint_quote_after_payment = await ledger.get_mint_quote(quote.quote)
|
||||||
|
assert mint_quote_after_payment.paid, "mint quote should be paid"
|
||||||
|
assert mint_quote_after_payment.state == MintQuoteState.issued
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_split(wallet1: Wallet, ledger: Ledger):
|
async def test_split(wallet1: Wallet, ledger: Ledger):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
|
|
||||||
keep_proofs, send_proofs = await wallet1.split_to_send(wallet1.proofs, 10)
|
keep_proofs, send_proofs = await wallet1.split_to_send(wallet1.proofs, 10)
|
||||||
@@ -158,7 +183,7 @@ async def test_split(wallet1: Wallet, ledger: Ledger):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_split_with_no_outputs(wallet1: Wallet, ledger: Ledger):
|
async def test_split_with_no_outputs(wallet1: Wallet, ledger: Ledger):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
_, send_proofs = await wallet1.split_to_send(wallet1.proofs, 10, set_reserved=False)
|
_, send_proofs = await wallet1.split_to_send(wallet1.proofs, 10, set_reserved=False)
|
||||||
await assert_err(
|
await assert_err(
|
||||||
@@ -170,7 +195,7 @@ async def test_split_with_no_outputs(wallet1: Wallet, ledger: Ledger):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_split_with_input_less_than_outputs(wallet1: Wallet, ledger: Ledger):
|
async def test_split_with_input_less_than_outputs(wallet1: Wallet, ledger: Ledger):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
|
|
||||||
keep_proofs, send_proofs = await wallet1.split_to_send(
|
keep_proofs, send_proofs = await wallet1.split_to_send(
|
||||||
@@ -199,7 +224,7 @@ async def test_split_with_input_less_than_outputs(wallet1: Wallet, ledger: Ledge
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_split_with_input_more_than_outputs(wallet1: Wallet, ledger: Ledger):
|
async def test_split_with_input_more_than_outputs(wallet1: Wallet, ledger: Ledger):
|
||||||
invoice = await wallet1.request_mint(128)
|
invoice = await wallet1.request_mint(128)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(128, id=invoice.id)
|
await wallet1.mint(128, id=invoice.id)
|
||||||
|
|
||||||
inputs = wallet1.proofs
|
inputs = wallet1.proofs
|
||||||
@@ -223,7 +248,7 @@ async def test_split_with_input_more_than_outputs(wallet1: Wallet, ledger: Ledge
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_split_twice_with_same_outputs(wallet1: Wallet, ledger: Ledger):
|
async def test_split_twice_with_same_outputs(wallet1: Wallet, ledger: Ledger):
|
||||||
invoice = await wallet1.request_mint(128)
|
invoice = await wallet1.request_mint(128)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(128, split=[64, 64], id=invoice.id)
|
await wallet1.mint(128, split=[64, 64], id=invoice.id)
|
||||||
inputs1 = wallet1.proofs[:1]
|
inputs1 = wallet1.proofs[:1]
|
||||||
inputs2 = wallet1.proofs[1:]
|
inputs2 = wallet1.proofs[1:]
|
||||||
@@ -258,7 +283,7 @@ async def test_split_twice_with_same_outputs(wallet1: Wallet, ledger: Ledger):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_mint_with_same_outputs_twice(wallet1: Wallet, ledger: Ledger):
|
async def test_mint_with_same_outputs_twice(wallet1: Wallet, ledger: Ledger):
|
||||||
invoice = await wallet1.request_mint(128)
|
invoice = await wallet1.request_mint(128)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
output_amounts = [128]
|
output_amounts = [128]
|
||||||
secrets, rs, derivation_paths = await wallet1.generate_n_secrets(
|
secrets, rs, derivation_paths = await wallet1.generate_n_secrets(
|
||||||
len(output_amounts)
|
len(output_amounts)
|
||||||
@@ -268,7 +293,7 @@ async def test_mint_with_same_outputs_twice(wallet1: Wallet, ledger: Ledger):
|
|||||||
|
|
||||||
# now try to mint with the same outputs again
|
# now try to mint with the same outputs again
|
||||||
invoice2 = await wallet1.request_mint(128)
|
invoice2 = await wallet1.request_mint(128)
|
||||||
pay_if_regtest(invoice2.bolt11)
|
await pay_if_regtest(invoice2.bolt11)
|
||||||
|
|
||||||
await assert_err(
|
await assert_err(
|
||||||
ledger.mint(outputs=outputs, quote_id=invoice2.id),
|
ledger.mint(outputs=outputs, quote_id=invoice2.id),
|
||||||
@@ -279,7 +304,7 @@ async def test_mint_with_same_outputs_twice(wallet1: Wallet, ledger: Ledger):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_melt_with_same_outputs_twice(wallet1: Wallet, ledger: Ledger):
|
async def test_melt_with_same_outputs_twice(wallet1: Wallet, ledger: Ledger):
|
||||||
invoice = await wallet1.request_mint(130)
|
invoice = await wallet1.request_mint(130)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(130, id=invoice.id)
|
await wallet1.mint(130, id=invoice.id)
|
||||||
|
|
||||||
output_amounts = [128]
|
output_amounts = [128]
|
||||||
@@ -290,7 +315,7 @@ async def test_melt_with_same_outputs_twice(wallet1: Wallet, ledger: Ledger):
|
|||||||
|
|
||||||
# we use the outputs once for minting
|
# we use the outputs once for minting
|
||||||
invoice2 = await wallet1.request_mint(128)
|
invoice2 = await wallet1.request_mint(128)
|
||||||
pay_if_regtest(invoice2.bolt11)
|
await pay_if_regtest(invoice2.bolt11)
|
||||||
await ledger.mint(outputs=outputs, quote_id=invoice2.id)
|
await ledger.mint(outputs=outputs, quote_id=invoice2.id)
|
||||||
|
|
||||||
# use the same outputs for melting
|
# use the same outputs for melting
|
||||||
@@ -307,7 +332,7 @@ async def test_melt_with_same_outputs_twice(wallet1: Wallet, ledger: Ledger):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_melt_with_less_inputs_than_invoice(wallet1: Wallet, ledger: Ledger):
|
async def test_melt_with_less_inputs_than_invoice(wallet1: Wallet, ledger: Ledger):
|
||||||
invoice = await wallet1.request_mint(32)
|
invoice = await wallet1.request_mint(32)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(32, id=invoice.id)
|
await wallet1.mint(32, id=invoice.id)
|
||||||
|
|
||||||
# outputs for fee return
|
# outputs for fee return
|
||||||
@@ -336,7 +361,7 @@ async def test_melt_with_less_inputs_than_invoice(wallet1: Wallet, ledger: Ledge
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_melt_with_more_inputs_than_invoice(wallet1: Wallet, ledger: Ledger):
|
async def test_melt_with_more_inputs_than_invoice(wallet1: Wallet, ledger: Ledger):
|
||||||
invoice = await wallet1.request_mint(130)
|
invoice = await wallet1.request_mint(130)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(130, split=[64, 64, 2], id=invoice.id)
|
await wallet1.mint(130, split=[64, 64, 2], id=invoice.id)
|
||||||
|
|
||||||
# outputs for fee return
|
# outputs for fee return
|
||||||
@@ -368,7 +393,7 @@ async def test_melt_with_more_inputs_than_invoice(wallet1: Wallet, ledger: Ledge
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_check_proof_state(wallet1: Wallet, ledger: Ledger):
|
async def test_check_proof_state(wallet1: Wallet, ledger: Ledger):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
|
|
||||||
keep_proofs, send_proofs = await wallet1.split_to_send(wallet1.proofs, 10)
|
keep_proofs, send_proofs = await wallet1.split_to_send(wallet1.proofs, 10)
|
||||||
@@ -385,7 +410,7 @@ async def test_check_proof_state(wallet1: Wallet, ledger: Ledger):
|
|||||||
# f"ws://localhost:{SERVER_PORT}/v1/quote/{invoice.id}"
|
# f"ws://localhost:{SERVER_PORT}/v1/quote/{invoice.id}"
|
||||||
# )
|
# )
|
||||||
# await asyncio.sleep(0.1)
|
# await asyncio.sleep(0.1)
|
||||||
# pay_if_regtest(invoice.bolt11)
|
# await pay_if_regtest(invoice.bolt11)
|
||||||
# await wallet1.mint(64, id=invoice.id)
|
# await wallet1.mint(64, id=invoice.id)
|
||||||
# await asyncio.sleep(0.1)
|
# await asyncio.sleep(0.1)
|
||||||
# data = str(ws.recv())
|
# data = str(ws.recv())
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ async def wallet():
|
|||||||
async def test_regtest_pending_quote(wallet: Wallet, ledger: Ledger):
|
async def test_regtest_pending_quote(wallet: Wallet, ledger: Ledger):
|
||||||
# fill wallet
|
# fill wallet
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet.mint(64, id=invoice.id)
|
await wallet.mint(64, id=invoice.id)
|
||||||
assert wallet.balance == 64
|
assert wallet.balance == 64
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,18 @@ async def assert_err(f, msg: Union[str, CashuError]):
|
|||||||
raise Exception(f"Expected error: {msg}, got no error")
|
raise Exception(f"Expected error: {msg}, got no error")
|
||||||
|
|
||||||
|
|
||||||
|
async def assert_err_multiple(f, msgs: List[str]):
|
||||||
|
"""Compute f() and expect an error message 'msg'."""
|
||||||
|
try:
|
||||||
|
await f
|
||||||
|
except Exception as exc:
|
||||||
|
for msg in msgs:
|
||||||
|
if msg in str(exc.args[0]):
|
||||||
|
return
|
||||||
|
raise Exception(f"Expected error: {msgs}, got: {exc.args[0]}")
|
||||||
|
raise Exception(f"Expected error: {msgs}, got no error")
|
||||||
|
|
||||||
|
|
||||||
def assert_amt(proofs: List[Proof], expected: int):
|
def assert_amt(proofs: List[Proof], expected: int):
|
||||||
"""Assert amounts the proofs contain."""
|
"""Assert amounts the proofs contain."""
|
||||||
assert sum([p.amount for p in proofs]) == expected
|
assert sum([p.amount for p in proofs]) == expected
|
||||||
@@ -155,7 +167,7 @@ async def test_request_mint(wallet1: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_mint(wallet1: Wallet):
|
async def test_mint(wallet1: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
expected_proof_amounts = wallet1.split_wallet_state(64)
|
expected_proof_amounts = wallet1.split_wallet_state(64)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
assert wallet1.balance == 64
|
assert wallet1.balance == 64
|
||||||
@@ -179,7 +191,7 @@ async def test_mint_amounts(wallet1: Wallet):
|
|||||||
"""Mint predefined amounts"""
|
"""Mint predefined amounts"""
|
||||||
amts = [1, 1, 1, 2, 2, 4, 16]
|
amts = [1, 1, 1, 2, 2, 4, 16]
|
||||||
invoice = await wallet1.request_mint(sum(amts))
|
invoice = await wallet1.request_mint(sum(amts))
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(amount=sum(amts), split=amts, id=invoice.id)
|
await wallet1.mint(amount=sum(amts), split=amts, id=invoice.id)
|
||||||
assert wallet1.balance == 27
|
assert wallet1.balance == 27
|
||||||
assert wallet1.proof_amounts == amts
|
assert wallet1.proof_amounts == amts
|
||||||
@@ -211,7 +223,7 @@ async def test_mint_amounts_wrong_order(wallet1: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_split(wallet1: Wallet):
|
async def test_split(wallet1: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
assert wallet1.balance == 64
|
assert wallet1.balance == 64
|
||||||
# the outputs we keep that we expect after the split
|
# the outputs we keep that we expect after the split
|
||||||
@@ -231,7 +243,7 @@ async def test_split(wallet1: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_split_to_send(wallet1: Wallet):
|
async def test_split_to_send(wallet1: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
assert wallet1.balance == 64
|
assert wallet1.balance == 64
|
||||||
|
|
||||||
@@ -253,7 +265,7 @@ async def test_split_to_send(wallet1: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_split_more_than_balance(wallet1: Wallet):
|
async def test_split_more_than_balance(wallet1: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
await assert_err(
|
await assert_err(
|
||||||
wallet1.split(wallet1.proofs, 128),
|
wallet1.split(wallet1.proofs, 128),
|
||||||
@@ -267,7 +279,7 @@ async def test_split_more_than_balance(wallet1: Wallet):
|
|||||||
async def test_melt(wallet1: Wallet):
|
async def test_melt(wallet1: Wallet):
|
||||||
# mint twice so we have enough to pay the second invoice back
|
# mint twice so we have enough to pay the second invoice back
|
||||||
topup_invoice = await wallet1.request_mint(128)
|
topup_invoice = await wallet1.request_mint(128)
|
||||||
pay_if_regtest(topup_invoice.bolt11)
|
await pay_if_regtest(topup_invoice.bolt11)
|
||||||
await wallet1.mint(128, id=topup_invoice.id)
|
await wallet1.mint(128, id=topup_invoice.id)
|
||||||
assert wallet1.balance == 128
|
assert wallet1.balance == 128
|
||||||
|
|
||||||
@@ -333,7 +345,7 @@ async def test_melt(wallet1: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_split_to_send_more_than_balance(wallet1: Wallet):
|
async def test_split_to_send_more_than_balance(wallet1: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
await assert_err(
|
await assert_err(
|
||||||
wallet1.split_to_send(wallet1.proofs, 128, set_reserved=True),
|
wallet1.split_to_send(wallet1.proofs, 128, set_reserved=True),
|
||||||
@@ -346,7 +358,7 @@ async def test_split_to_send_more_than_balance(wallet1: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_double_spend(wallet1: Wallet):
|
async def test_double_spend(wallet1: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
doublespend = await wallet1.mint(64, id=invoice.id)
|
doublespend = await wallet1.mint(64, id=invoice.id)
|
||||||
await wallet1.split(wallet1.proofs, 20)
|
await wallet1.split(wallet1.proofs, 20)
|
||||||
await assert_err(
|
await assert_err(
|
||||||
@@ -360,7 +372,7 @@ async def test_double_spend(wallet1: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_duplicate_proofs_double_spent(wallet1: Wallet):
|
async def test_duplicate_proofs_double_spent(wallet1: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
doublespend = await wallet1.mint(64, id=invoice.id)
|
doublespend = await wallet1.mint(64, id=invoice.id)
|
||||||
await assert_err(
|
await assert_err(
|
||||||
wallet1.split(wallet1.proofs + doublespend, 20),
|
wallet1.split(wallet1.proofs + doublespend, 20),
|
||||||
@@ -374,24 +386,24 @@ async def test_duplicate_proofs_double_spent(wallet1: Wallet):
|
|||||||
@pytest.mark.skipif(is_github_actions, reason="GITHUB_ACTIONS")
|
@pytest.mark.skipif(is_github_actions, reason="GITHUB_ACTIONS")
|
||||||
async def test_split_race_condition(wallet1: Wallet):
|
async def test_split_race_condition(wallet1: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
# run two splits in parallel
|
# run two splits in parallel
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
await assert_err(
|
await assert_err_multiple(
|
||||||
asyncio.gather(
|
asyncio.gather(
|
||||||
wallet1.split(wallet1.proofs, 20),
|
wallet1.split(wallet1.proofs, 20),
|
||||||
wallet1.split(wallet1.proofs, 20),
|
wallet1.split(wallet1.proofs, 20),
|
||||||
),
|
),
|
||||||
"proofs are pending.",
|
["proofs are pending.", "already spent."],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_send_and_redeem(wallet1: Wallet, wallet2: Wallet):
|
async def test_send_and_redeem(wallet1: Wallet, wallet2: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
_, spendable_proofs = await wallet1.split_to_send(
|
_, spendable_proofs = await wallet1.split_to_send(
|
||||||
wallet1.proofs, 32, set_reserved=True
|
wallet1.proofs, 32, set_reserved=True
|
||||||
@@ -410,7 +422,7 @@ async def test_send_and_redeem(wallet1: Wallet, wallet2: Wallet):
|
|||||||
async def test_invalidate_all_proofs(wallet1: Wallet):
|
async def test_invalidate_all_proofs(wallet1: Wallet):
|
||||||
"""Try to invalidate proofs that have not been spent yet. Should not work!"""
|
"""Try to invalidate proofs that have not been spent yet. Should not work!"""
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
await wallet1.invalidate(wallet1.proofs)
|
await wallet1.invalidate(wallet1.proofs)
|
||||||
assert wallet1.balance == 0
|
assert wallet1.balance == 0
|
||||||
@@ -420,7 +432,7 @@ async def test_invalidate_all_proofs(wallet1: Wallet):
|
|||||||
async def test_invalidate_unspent_proofs_with_checking(wallet1: Wallet):
|
async def test_invalidate_unspent_proofs_with_checking(wallet1: Wallet):
|
||||||
"""Try to invalidate proofs that have not been spent yet but force no check."""
|
"""Try to invalidate proofs that have not been spent yet but force no check."""
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
await wallet1.invalidate(wallet1.proofs, check_spendable=True)
|
await wallet1.invalidate(wallet1.proofs, check_spendable=True)
|
||||||
assert wallet1.balance == 64
|
assert wallet1.balance == 64
|
||||||
@@ -429,7 +441,7 @@ async def test_invalidate_unspent_proofs_with_checking(wallet1: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_split_invalid_amount(wallet1: Wallet):
|
async def test_split_invalid_amount(wallet1: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
await assert_err(
|
await assert_err(
|
||||||
wallet1.split(wallet1.proofs, -1),
|
wallet1.split(wallet1.proofs, -1),
|
||||||
@@ -440,7 +452,7 @@ async def test_split_invalid_amount(wallet1: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_token_state(wallet1: Wallet):
|
async def test_token_state(wallet1: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
assert wallet1.balance == 64
|
assert wallet1.balance == 64
|
||||||
resp = await wallet1.check_proof_state(wallet1.proofs)
|
resp = await wallet1.check_proof_state(wallet1.proofs)
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ def test_invoice_return_immediately(mint, cli_prefix):
|
|||||||
assert result.exception is None
|
assert result.exception is None
|
||||||
|
|
||||||
invoice, invoice_id = get_bolt11_and_invoice_id_from_invoice_command(result.output)
|
invoice, invoice_id = get_bolt11_and_invoice_id_from_invoice_command(result.output)
|
||||||
pay_if_regtest(invoice)
|
asyncio.run(pay_if_regtest(invoice))
|
||||||
|
|
||||||
result = runner.invoke(
|
result = runner.invoke(
|
||||||
cli,
|
cli,
|
||||||
@@ -146,7 +146,7 @@ def test_invoice_with_split(mint, cli_prefix):
|
|||||||
assert result.exception is None
|
assert result.exception is None
|
||||||
|
|
||||||
invoice, invoice_id = get_bolt11_and_invoice_id_from_invoice_command(result.output)
|
invoice, invoice_id = get_bolt11_and_invoice_id_from_invoice_command(result.output)
|
||||||
pay_if_regtest(invoice)
|
asyncio.run(pay_if_regtest(invoice))
|
||||||
result = runner.invoke(
|
result = runner.invoke(
|
||||||
cli,
|
cli,
|
||||||
[*cli_prefix, "invoice", "10", "-s", "1", "--id", invoice_id],
|
[*cli_prefix, "invoice", "10", "-s", "1", "--id", invoice_id],
|
||||||
@@ -164,7 +164,7 @@ def test_invoices_with_minting(cli_prefix):
|
|||||||
wallet1 = asyncio.run(init_wallet())
|
wallet1 = asyncio.run(init_wallet())
|
||||||
asyncio.run(reset_invoices(wallet=wallet1))
|
asyncio.run(reset_invoices(wallet=wallet1))
|
||||||
invoice = asyncio.run(wallet1.request_mint(64))
|
invoice = asyncio.run(wallet1.request_mint(64))
|
||||||
|
asyncio.run(pay_if_regtest(invoice.bolt11))
|
||||||
# act
|
# act
|
||||||
runner = CliRunner()
|
runner = CliRunner()
|
||||||
result = runner.invoke(
|
result = runner.invoke(
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ async def wallet2():
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_create_htlc_secret(wallet1: Wallet):
|
async def test_create_htlc_secret(wallet1: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
preimage = "00000000000000000000000000000000"
|
preimage = "00000000000000000000000000000000"
|
||||||
preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||||
@@ -69,7 +69,7 @@ async def test_create_htlc_secret(wallet1: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_htlc_split(wallet1: Wallet, wallet2: Wallet):
|
async def test_htlc_split(wallet1: Wallet, wallet2: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
preimage = "00000000000000000000000000000000"
|
preimage = "00000000000000000000000000000000"
|
||||||
preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||||
@@ -82,7 +82,7 @@ async def test_htlc_split(wallet1: Wallet, wallet2: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_htlc_redeem_with_preimage(wallet1: Wallet, wallet2: Wallet):
|
async def test_htlc_redeem_with_preimage(wallet1: Wallet, wallet2: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
preimage = "00000000000000000000000000000000"
|
preimage = "00000000000000000000000000000000"
|
||||||
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||||
@@ -96,7 +96,7 @@ async def test_htlc_redeem_with_preimage(wallet1: Wallet, wallet2: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_htlc_redeem_with_wrong_preimage(wallet1: Wallet, wallet2: Wallet):
|
async def test_htlc_redeem_with_wrong_preimage(wallet1: Wallet, wallet2: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
preimage = "00000000000000000000000000000000"
|
preimage = "00000000000000000000000000000000"
|
||||||
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
# preimage_hash = hashlib.sha256(bytes.fromhex(preimage)).hexdigest()
|
||||||
@@ -114,7 +114,7 @@ async def test_htlc_redeem_with_wrong_preimage(wallet1: Wallet, wallet2: Wallet)
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_htlc_redeem_with_no_signature(wallet1: Wallet, wallet2: Wallet):
|
async def test_htlc_redeem_with_no_signature(wallet1: Wallet, wallet2: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
preimage = "00000000000000000000000000000000"
|
preimage = "00000000000000000000000000000000"
|
||||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||||
@@ -134,7 +134,7 @@ async def test_htlc_redeem_with_no_signature(wallet1: Wallet, wallet2: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_htlc_redeem_with_wrong_signature(wallet1: Wallet, wallet2: Wallet):
|
async def test_htlc_redeem_with_wrong_signature(wallet1: Wallet, wallet2: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
preimage = "00000000000000000000000000000000"
|
preimage = "00000000000000000000000000000000"
|
||||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||||
@@ -158,7 +158,7 @@ async def test_htlc_redeem_with_wrong_signature(wallet1: Wallet, wallet2: Wallet
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_htlc_redeem_with_correct_signature(wallet1: Wallet, wallet2: Wallet):
|
async def test_htlc_redeem_with_correct_signature(wallet1: Wallet, wallet2: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
preimage = "00000000000000000000000000000000"
|
preimage = "00000000000000000000000000000000"
|
||||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||||
@@ -180,7 +180,7 @@ async def test_htlc_redeem_hashlock_wrong_signature_timelock_correct_signature(
|
|||||||
wallet1: Wallet, wallet2: Wallet
|
wallet1: Wallet, wallet2: Wallet
|
||||||
):
|
):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
preimage = "00000000000000000000000000000000"
|
preimage = "00000000000000000000000000000000"
|
||||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||||
@@ -214,7 +214,7 @@ async def test_htlc_redeem_hashlock_wrong_signature_timelock_wrong_signature(
|
|||||||
wallet1: Wallet, wallet2: Wallet
|
wallet1: Wallet, wallet2: Wallet
|
||||||
):
|
):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
preimage = "00000000000000000000000000000000"
|
preimage = "00000000000000000000000000000000"
|
||||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ async def test_check_invoice_external(wallet: LightningWallet):
|
|||||||
assert invoice.checking_id
|
assert invoice.checking_id
|
||||||
status = await wallet.get_invoice_status(invoice.checking_id)
|
status = await wallet.get_invoice_status(invoice.checking_id)
|
||||||
assert not status.paid
|
assert not status.paid
|
||||||
pay_if_regtest(invoice.payment_request)
|
await pay_if_regtest(invoice.payment_request)
|
||||||
status = await wallet.get_invoice_status(invoice.checking_id)
|
status = await wallet.get_invoice_status(invoice.checking_id)
|
||||||
assert status.paid
|
assert status.paid
|
||||||
|
|
||||||
@@ -113,7 +113,7 @@ async def test_pay_invoice_external(wallet: LightningWallet):
|
|||||||
invoice = await wallet.create_invoice(64)
|
invoice = await wallet.create_invoice(64)
|
||||||
assert invoice.payment_request
|
assert invoice.payment_request
|
||||||
assert invoice.checking_id
|
assert invoice.checking_id
|
||||||
pay_if_regtest(invoice.payment_request)
|
await pay_if_regtest(invoice.payment_request)
|
||||||
status = await wallet.get_invoice_status(invoice.checking_id)
|
status = await wallet.get_invoice_status(invoice.checking_id)
|
||||||
assert status.paid
|
assert status.paid
|
||||||
assert wallet.available_balance >= 64
|
assert wallet.available_balance >= 64
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ async def wallet2():
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_create_p2pk_pubkey(wallet1: Wallet):
|
async def test_create_p2pk_pubkey(wallet1: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
pubkey = await wallet1.create_p2pk_pubkey()
|
pubkey = await wallet1.create_p2pk_pubkey()
|
||||||
PublicKey(bytes.fromhex(pubkey), raw=True)
|
PublicKey(bytes.fromhex(pubkey), raw=True)
|
||||||
@@ -69,7 +69,7 @@ async def test_create_p2pk_pubkey(wallet1: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_p2pk(wallet1: Wallet, wallet2: Wallet):
|
async def test_p2pk(wallet1: Wallet, wallet2: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||||
# p2pk test
|
# p2pk test
|
||||||
@@ -93,7 +93,7 @@ async def test_p2pk(wallet1: Wallet, wallet2: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_p2pk_sig_all(wallet1: Wallet, wallet2: Wallet):
|
async def test_p2pk_sig_all(wallet1: Wallet, wallet2: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||||
# p2pk test
|
# p2pk test
|
||||||
@@ -109,7 +109,7 @@ async def test_p2pk_sig_all(wallet1: Wallet, wallet2: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_p2pk_receive_with_wrong_private_key(wallet1: Wallet, wallet2: Wallet):
|
async def test_p2pk_receive_with_wrong_private_key(wallet1: Wallet, wallet2: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # receiver side
|
pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # receiver side
|
||||||
# sender side
|
# sender side
|
||||||
@@ -130,7 +130,7 @@ async def test_p2pk_short_locktime_receive_with_wrong_private_key(
|
|||||||
wallet1: Wallet, wallet2: Wallet
|
wallet1: Wallet, wallet2: Wallet
|
||||||
):
|
):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # receiver side
|
pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # receiver side
|
||||||
# sender side
|
# sender side
|
||||||
@@ -156,7 +156,7 @@ async def test_p2pk_short_locktime_receive_with_wrong_private_key(
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_p2pk_locktime_with_refund_pubkey(wallet1: Wallet, wallet2: Wallet):
|
async def test_p2pk_locktime_with_refund_pubkey(wallet1: Wallet, wallet2: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # receiver side
|
pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # receiver side
|
||||||
# sender side
|
# sender side
|
||||||
@@ -185,7 +185,7 @@ async def test_p2pk_locktime_with_refund_pubkey(wallet1: Wallet, wallet2: Wallet
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_p2pk_locktime_with_wrong_refund_pubkey(wallet1: Wallet, wallet2: Wallet):
|
async def test_p2pk_locktime_with_wrong_refund_pubkey(wallet1: Wallet, wallet2: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
await wallet2.create_p2pk_pubkey() # receiver side
|
await wallet2.create_p2pk_pubkey() # receiver side
|
||||||
# sender side
|
# sender side
|
||||||
@@ -221,7 +221,7 @@ async def test_p2pk_locktime_with_second_refund_pubkey(
|
|||||||
wallet1: Wallet, wallet2: Wallet
|
wallet1: Wallet, wallet2: Wallet
|
||||||
):
|
):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey() # receiver side
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey() # receiver side
|
||||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # receiver side
|
pubkey_wallet2 = await wallet2.create_p2pk_pubkey() # receiver side
|
||||||
@@ -253,7 +253,7 @@ async def test_p2pk_locktime_with_second_refund_pubkey(
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_p2pk_multisig_2_of_2(wallet1: Wallet, wallet2: Wallet):
|
async def test_p2pk_multisig_2_of_2(wallet1: Wallet, wallet2: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||||
@@ -275,7 +275,7 @@ async def test_p2pk_multisig_2_of_2(wallet1: Wallet, wallet2: Wallet):
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_p2pk_multisig_duplicate_signature(wallet1: Wallet, wallet2: Wallet):
|
async def test_p2pk_multisig_duplicate_signature(wallet1: Wallet, wallet2: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||||
@@ -299,7 +299,7 @@ async def test_p2pk_multisig_duplicate_signature(wallet1: Wallet, wallet2: Walle
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_p2pk_multisig_quorum_not_met_1_of_2(wallet1: Wallet, wallet2: Wallet):
|
async def test_p2pk_multisig_quorum_not_met_1_of_2(wallet1: Wallet, wallet2: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||||
@@ -320,7 +320,7 @@ async def test_p2pk_multisig_quorum_not_met_1_of_2(wallet1: Wallet, wallet2: Wal
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_p2pk_multisig_quorum_not_met_2_of_3(wallet1: Wallet, wallet2: Wallet):
|
async def test_p2pk_multisig_quorum_not_met_2_of_3(wallet1: Wallet, wallet2: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
pubkey_wallet1 = await wallet1.create_p2pk_pubkey()
|
||||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||||
@@ -345,7 +345,7 @@ async def test_p2pk_multisig_quorum_not_met_2_of_3(wallet1: Wallet, wallet2: Wal
|
|||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_p2pk_multisig_with_duplicate_publickey(wallet1: Wallet, wallet2: Wallet):
|
async def test_p2pk_multisig_with_duplicate_publickey(wallet1: Wallet, wallet2: Wallet):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||||
# p2pk test
|
# p2pk test
|
||||||
@@ -363,7 +363,7 @@ async def test_p2pk_multisig_with_wrong_first_private_key(
|
|||||||
wallet1: Wallet, wallet2: Wallet
|
wallet1: Wallet, wallet2: Wallet
|
||||||
):
|
):
|
||||||
invoice = await wallet1.request_mint(64)
|
invoice = await wallet1.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet1.mint(64, id=invoice.id)
|
await wallet1.mint(64, id=invoice.id)
|
||||||
await wallet1.create_p2pk_pubkey()
|
await wallet1.create_p2pk_pubkey()
|
||||||
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
pubkey_wallet2 = await wallet2.create_p2pk_pubkey()
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ async def wallet():
|
|||||||
async def test_regtest_pending_quote(wallet: Wallet, ledger: Ledger):
|
async def test_regtest_pending_quote(wallet: Wallet, ledger: Ledger):
|
||||||
# fill wallet
|
# fill wallet
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet.mint(64, id=invoice.id)
|
await wallet.mint(64, id=invoice.id)
|
||||||
assert wallet.balance == 64
|
assert wallet.balance == 64
|
||||||
|
|
||||||
@@ -72,7 +72,7 @@ async def test_regtest_pending_quote(wallet: Wallet, ledger: Ledger):
|
|||||||
async def test_regtest_failed_quote(wallet: Wallet, ledger: Ledger):
|
async def test_regtest_failed_quote(wallet: Wallet, ledger: Ledger):
|
||||||
# fill wallet
|
# fill wallet
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet.mint(64, id=invoice.id)
|
await wallet.mint(64, id=invoice.id)
|
||||||
assert wallet.balance == 64
|
assert wallet.balance == 64
|
||||||
|
|
||||||
|
|||||||
@@ -38,12 +38,12 @@ async def test_regtest_pay_mpp(wallet: Wallet, ledger: Ledger):
|
|||||||
|
|
||||||
# top up wallet twice so we have enough for two payments
|
# top up wallet twice so we have enough for two payments
|
||||||
topup_invoice = await wallet.request_mint(128)
|
topup_invoice = await wallet.request_mint(128)
|
||||||
pay_if_regtest(topup_invoice.bolt11)
|
await pay_if_regtest(topup_invoice.bolt11)
|
||||||
proofs1 = await wallet.mint(128, id=topup_invoice.id)
|
proofs1 = await wallet.mint(128, id=topup_invoice.id)
|
||||||
assert wallet.balance == 128
|
assert wallet.balance == 128
|
||||||
|
|
||||||
topup_invoice = await wallet.request_mint(128)
|
topup_invoice = await wallet.request_mint(128)
|
||||||
pay_if_regtest(topup_invoice.bolt11)
|
await pay_if_regtest(topup_invoice.bolt11)
|
||||||
proofs2 = await wallet.mint(128, id=topup_invoice.id)
|
proofs2 = await wallet.mint(128, id=topup_invoice.id)
|
||||||
assert wallet.balance == 256
|
assert wallet.balance == 256
|
||||||
|
|
||||||
@@ -82,17 +82,17 @@ async def test_regtest_pay_mpp_incomplete_payment(wallet: Wallet, ledger: Ledger
|
|||||||
|
|
||||||
# top up wallet twice so we have enough for three payments
|
# top up wallet twice so we have enough for three payments
|
||||||
topup_invoice = await wallet.request_mint(128)
|
topup_invoice = await wallet.request_mint(128)
|
||||||
pay_if_regtest(topup_invoice.bolt11)
|
await pay_if_regtest(topup_invoice.bolt11)
|
||||||
proofs1 = await wallet.mint(128, id=topup_invoice.id)
|
proofs1 = await wallet.mint(128, id=topup_invoice.id)
|
||||||
assert wallet.balance == 128
|
assert wallet.balance == 128
|
||||||
|
|
||||||
topup_invoice = await wallet.request_mint(128)
|
topup_invoice = await wallet.request_mint(128)
|
||||||
pay_if_regtest(topup_invoice.bolt11)
|
await pay_if_regtest(topup_invoice.bolt11)
|
||||||
proofs2 = await wallet.mint(128, id=topup_invoice.id)
|
proofs2 = await wallet.mint(128, id=topup_invoice.id)
|
||||||
assert wallet.balance == 256
|
assert wallet.balance == 256
|
||||||
|
|
||||||
topup_invoice = await wallet.request_mint(128)
|
topup_invoice = await wallet.request_mint(128)
|
||||||
pay_if_regtest(topup_invoice.bolt11)
|
await pay_if_regtest(topup_invoice.bolt11)
|
||||||
proofs3 = await wallet.mint(128, id=topup_invoice.id)
|
proofs3 = await wallet.mint(128, id=topup_invoice.id)
|
||||||
assert wallet.balance == 384
|
assert wallet.balance == 384
|
||||||
|
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ async def test_generate_secrets_from_to(wallet3: Wallet):
|
|||||||
async def test_restore_wallet_after_mint(wallet3: Wallet):
|
async def test_restore_wallet_after_mint(wallet3: Wallet):
|
||||||
await reset_wallet_db(wallet3)
|
await reset_wallet_db(wallet3)
|
||||||
invoice = await wallet3.request_mint(64)
|
invoice = await wallet3.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet3.mint(64, id=invoice.id)
|
await wallet3.mint(64, id=invoice.id)
|
||||||
assert wallet3.balance == 64
|
assert wallet3.balance == 64
|
||||||
await reset_wallet_db(wallet3)
|
await reset_wallet_db(wallet3)
|
||||||
@@ -193,7 +193,7 @@ async def test_restore_wallet_after_split_to_send(wallet3: Wallet):
|
|||||||
await reset_wallet_db(wallet3)
|
await reset_wallet_db(wallet3)
|
||||||
|
|
||||||
invoice = await wallet3.request_mint(64)
|
invoice = await wallet3.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet3.mint(64, id=invoice.id)
|
await wallet3.mint(64, id=invoice.id)
|
||||||
assert wallet3.balance == 64
|
assert wallet3.balance == 64
|
||||||
|
|
||||||
@@ -218,7 +218,7 @@ async def test_restore_wallet_after_send_and_receive(wallet3: Wallet, wallet2: W
|
|||||||
)
|
)
|
||||||
await reset_wallet_db(wallet3)
|
await reset_wallet_db(wallet3)
|
||||||
invoice = await wallet3.request_mint(64)
|
invoice = await wallet3.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet3.mint(64, id=invoice.id)
|
await wallet3.mint(64, id=invoice.id)
|
||||||
assert wallet3.balance == 64
|
assert wallet3.balance == 64
|
||||||
|
|
||||||
@@ -261,7 +261,7 @@ async def test_restore_wallet_after_send_and_self_receive(wallet3: Wallet):
|
|||||||
await reset_wallet_db(wallet3)
|
await reset_wallet_db(wallet3)
|
||||||
|
|
||||||
invoice = await wallet3.request_mint(64)
|
invoice = await wallet3.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet3.mint(64, id=invoice.id)
|
await wallet3.mint(64, id=invoice.id)
|
||||||
assert wallet3.balance == 64
|
assert wallet3.balance == 64
|
||||||
|
|
||||||
@@ -290,7 +290,7 @@ async def test_restore_wallet_after_send_twice(
|
|||||||
await reset_wallet_db(wallet3)
|
await reset_wallet_db(wallet3)
|
||||||
|
|
||||||
invoice = await wallet3.request_mint(2)
|
invoice = await wallet3.request_mint(2)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet3.mint(2, id=invoice.id)
|
await wallet3.mint(2, id=invoice.id)
|
||||||
box.add(wallet3.proofs)
|
box.add(wallet3.proofs)
|
||||||
assert wallet3.balance == 2
|
assert wallet3.balance == 2
|
||||||
@@ -349,7 +349,7 @@ async def test_restore_wallet_after_send_and_self_receive_nonquadratic_value(
|
|||||||
await reset_wallet_db(wallet3)
|
await reset_wallet_db(wallet3)
|
||||||
|
|
||||||
invoice = await wallet3.request_mint(64)
|
invoice = await wallet3.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet3.mint(64, id=invoice.id)
|
await wallet3.mint(64, id=invoice.id)
|
||||||
box.add(wallet3.proofs)
|
box.add(wallet3.proofs)
|
||||||
assert wallet3.balance == 64
|
assert wallet3.balance == 64
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ async def test_wallet_subscription_mint(wallet: Wallet):
|
|||||||
asyncio.run(wallet.mint(int(invoice.amount), id=invoice.id))
|
asyncio.run(wallet.mint(int(invoice.amount), id=invoice.id))
|
||||||
|
|
||||||
invoice, sub = await wallet.request_mint_with_callback(128, callback=callback)
|
invoice, sub = await wallet.request_mint_with_callback(128, callback=callback)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
wait = settings.fakewallet_delay_incoming_payment or 2
|
wait = settings.fakewallet_delay_incoming_payment or 2
|
||||||
await asyncio.sleep(wait + 2)
|
await asyncio.sleep(wait + 2)
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ async def test_wallet_subscription_swap(wallet: Wallet):
|
|||||||
pytest.skip("No websocket support")
|
pytest.skip("No websocket support")
|
||||||
|
|
||||||
invoice = await wallet.request_mint(64)
|
invoice = await wallet.request_mint(64)
|
||||||
pay_if_regtest(invoice.bolt11)
|
await pay_if_regtest(invoice.bolt11)
|
||||||
await wallet.mint(64, id=invoice.id)
|
await wallet.mint(64, id=invoice.id)
|
||||||
|
|
||||||
triggered = False
|
triggered = False
|
||||||
|
|||||||
Reference in New Issue
Block a user