fix sqlite database locked issues by using an async lock on the database and requiring explicit transaction control (or each command will be its own transaction).

This commit is contained in:
fiatjaf
2021-03-26 19:10:30 -03:00
parent 9cc7052920
commit 85011d23c3
10 changed files with 276 additions and 276 deletions
+74 -36
View File
@@ -4,6 +4,7 @@ from uuid import uuid4
from typing import List, Optional, Dict, Any
from lnbits import bolt11
from lnbits.db import Connection
from lnbits.settings import DEFAULT_WALLET_NAME
from . import db
@@ -14,32 +15,36 @@ from .models import User, Wallet, Payment
# --------
async def create_account() -> User:
async def create_account(conn: Optional[Connection] = None) -> User:
user_id = uuid4().hex
await db.execute("INSERT INTO accounts (id) VALUES (?)", (user_id,))
await (conn or db).execute("INSERT INTO accounts (id) VALUES (?)", (user_id,))
new_account = await get_account(user_id=user_id)
new_account = await get_account(user_id=user_id, conn=conn)
assert new_account, "Newly created account couldn't be retrieved"
return new_account
async def get_account(user_id: str) -> Optional[User]:
row = await db.fetchone(
async def get_account(
user_id: str, conn: Optional[Connection] = None
) -> Optional[User]:
row = await (conn or db).fetchone(
"SELECT id, email, pass as password FROM accounts WHERE id = ?", (user_id,)
)
return User(**row) if row else None
async def get_user(user_id: str) -> Optional[User]:
user = await db.fetchone("SELECT id, email FROM accounts WHERE id = ?", (user_id,))
async def get_user(user_id: str, conn: Optional[Connection] = None) -> Optional[User]:
user = await (conn or db).fetchone(
"SELECT id, email FROM accounts WHERE id = ?", (user_id,)
)
if user:
extensions = await db.fetchall(
extensions = await (conn or db).fetchall(
"SELECT extension FROM extensions WHERE user = ? AND active = 1", (user_id,)
)
wallets = await db.fetchall(
wallets = await (conn or db).fetchall(
"""
SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) AS balance_msat
FROM wallets
@@ -63,8 +68,10 @@ async def get_user(user_id: str) -> Optional[User]:
)
async def update_user_extension(*, user_id: str, extension: str, active: int) -> None:
await db.execute(
async def update_user_extension(
*, user_id: str, extension: str, active: int, conn: Optional[Connection] = None
) -> None:
await (conn or db).execute(
"""
INSERT OR REPLACE INTO extensions (user, extension, active)
VALUES (?, ?, ?)
@@ -77,9 +84,14 @@ async def update_user_extension(*, user_id: str, extension: str, active: int) ->
# -------
async def create_wallet(*, user_id: str, wallet_name: Optional[str] = None) -> Wallet:
async def create_wallet(
*,
user_id: str,
wallet_name: Optional[str] = None,
conn: Optional[Connection] = None,
) -> Wallet:
wallet_id = uuid4().hex
await db.execute(
await (conn or db).execute(
"""
INSERT INTO wallets (id, name, user, adminkey, inkey)
VALUES (?, ?, ?, ?, ?)
@@ -93,14 +105,16 @@ async def create_wallet(*, user_id: str, wallet_name: Optional[str] = None) -> W
),
)
new_wallet = await get_wallet(wallet_id=wallet_id)
new_wallet = await get_wallet(wallet_id=wallet_id, conn=conn)
assert new_wallet, "Newly created wallet couldn't be retrieved"
return new_wallet
async def delete_wallet(*, user_id: str, wallet_id: str) -> None:
await db.execute(
async def delete_wallet(
*, user_id: str, wallet_id: str, conn: Optional[Connection] = None
) -> None:
await (conn or db).execute(
"""
UPDATE wallets AS w
SET
@@ -113,8 +127,10 @@ async def delete_wallet(*, user_id: str, wallet_id: str) -> None:
)
async def get_wallet(wallet_id: str) -> Optional[Wallet]:
row = await db.fetchone(
async def get_wallet(
wallet_id: str, conn: Optional[Connection] = None
) -> Optional[Wallet]:
row = await (conn or db).fetchone(
"""
SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) AS balance_msat
FROM wallets
@@ -126,8 +142,10 @@ async def get_wallet(wallet_id: str) -> Optional[Wallet]:
return Wallet(**row) if row else None
async def get_wallet_for_key(key: str, key_type: str = "invoice") -> Optional[Wallet]:
row = await db.fetchone(
async def get_wallet_for_key(
key: str, key_type: str = "invoice", conn: Optional[Connection] = None
) -> Optional[Wallet]:
row = await (conn or db).fetchone(
"""
SELECT *, COALESCE((SELECT balance FROM balances WHERE wallet = wallets.id), 0) AS balance_msat
FROM wallets
@@ -149,8 +167,10 @@ async def get_wallet_for_key(key: str, key_type: str = "invoice") -> Optional[Wa
# ---------------
async def get_standalone_payment(checking_id_or_hash: str) -> Optional[Payment]:
row = await db.fetchone(
async def get_standalone_payment(
checking_id_or_hash: str, conn: Optional[Connection] = None
) -> Optional[Payment]:
row = await (conn or db).fetchone(
"""
SELECT *
FROM apipayments
@@ -163,8 +183,10 @@ async def get_standalone_payment(checking_id_or_hash: str) -> Optional[Payment]:
return Payment.from_row(row) if row else None
async def get_wallet_payment(wallet_id: str, payment_hash: str) -> Optional[Payment]:
row = await db.fetchone(
async def get_wallet_payment(
wallet_id: str, payment_hash: str, conn: Optional[Connection] = None
) -> Optional[Payment]:
row = await (conn or db).fetchone(
"""
SELECT *
FROM apipayments
@@ -185,6 +207,7 @@ async def get_payments(
incoming: bool = False,
since: Optional[int] = None,
exclude_uncheckable: bool = False,
conn: Optional[Connection] = None,
) -> List[Payment]:
"""
Filters payments to be returned by complete | pending | outgoing | incoming.
@@ -227,7 +250,7 @@ async def get_payments(
if clause:
where = f"WHERE {' AND '.join(clause)}"
rows = await db.fetchall(
rows = await (conn or db).fetchall(
f"""
SELECT *
FROM apipayments
@@ -240,8 +263,10 @@ async def get_payments(
return [Payment.from_row(row) for row in rows]
async def delete_expired_invoices() -> None:
rows = await db.fetchall(
async def delete_expired_invoices(
conn: Optional[Connection] = None,
) -> None:
rows = await (conn or db).fetchall(
"""
SELECT bolt11
FROM apipayments
@@ -258,7 +283,7 @@ async def delete_expired_invoices() -> None:
if expiration_date > datetime.datetime.utcnow():
continue
await db.execute(
await (conn or db).execute(
"""
DELETE FROM apipayments
WHERE pending = 1 AND hash = ?
@@ -284,8 +309,9 @@ async def create_payment(
pending: bool = True,
extra: Optional[Dict] = None,
webhook: Optional[str] = None,
conn: Optional[Connection] = None,
) -> Payment:
await db.execute(
await (conn or db).execute(
"""
INSERT INTO apipayments
(wallet, checking_id, bolt11, hash, preimage,
@@ -309,14 +335,18 @@ async def create_payment(
),
)
new_payment = await get_wallet_payment(wallet_id, payment_hash)
new_payment = await get_wallet_payment(wallet_id, payment_hash, conn=conn)
assert new_payment, "Newly created payment couldn't be retrieved"
return new_payment
async def update_payment_status(checking_id: str, pending: bool) -> None:
await db.execute(
async def update_payment_status(
checking_id: str,
pending: bool,
conn: Optional[Connection] = None,
) -> None:
await (conn or db).execute(
"UPDATE apipayments SET pending = ? WHERE checking_id = ?",
(
int(pending),
@@ -325,12 +355,20 @@ async def update_payment_status(checking_id: str, pending: bool) -> None:
)
async def delete_payment(checking_id: str) -> None:
await db.execute("DELETE FROM apipayments WHERE checking_id = ?", (checking_id,))
async def delete_payment(
checking_id: str,
conn: Optional[Connection] = None,
) -> None:
await (conn or db).execute(
"DELETE FROM apipayments WHERE checking_id = ?", (checking_id,)
)
async def check_internal(payment_hash: str) -> Optional[str]:
row = await db.fetchone(
async def check_internal(
payment_hash: str,
conn: Optional[Connection] = None,
) -> Optional[str]:
row = await (conn or db).fetchone(
"""
SELECT checking_id FROM apipayments
WHERE hash = ? AND pending AND amount > 0