mirror of
https://github.com/fusion44/blitz_api.git
synced 2026-08-15 12:10:12 +02:00
refactor: lightning system decoupling
This commit is contained in:
parent
799eb8deef
commit
df93cbdcee
5 changed files with 1858 additions and 1789 deletions
|
|
@ -28,11 +28,13 @@ PLATFORM = config("platform", cast=str)
|
|||
|
||||
ln_node = config("ln_node")
|
||||
if ln_node == "lnd_grpc":
|
||||
import app.repositories.ln_impl.lnd_grpc as ln
|
||||
from app.repositories.ln_impl.lnd_grpc import LnNodeLNDgRPC as LnNode
|
||||
elif ln_node == "cln_grpc" and PLATFORM != APIPlatform.RASPIBLITZ:
|
||||
import app.repositories.ln_impl.cln_grpc as ln
|
||||
from app.repositories.ln_impl.cln_grpc import LnNodeCLNgRPC as LnNode
|
||||
elif ln_node == "cln_grpc" and PLATFORM == APIPlatform.RASPIBLITZ:
|
||||
import app.repositories.ln_impl.specializations.cln_grpc_blitz as ln
|
||||
from app.repositories.ln_impl.specializations.cln_grpc_blitz import (
|
||||
LnNodeCLNgRPCBlitz as LnNode,
|
||||
)
|
||||
elif ln_node == "none":
|
||||
logging.info(f"lightning was explicitly turned off")
|
||||
elif ln_node == "":
|
||||
|
|
@ -54,31 +56,33 @@ FWD_GATHER_INTERVAL = config("forwards_gather_interval", default=2.0, cast=float
|
|||
if FWD_GATHER_INTERVAL < 0.3:
|
||||
raise RuntimeError("forwards_gather_interval cannot be less than 0.3 seconds")
|
||||
|
||||
ln = LnNode()
|
||||
|
||||
|
||||
async def initialize_ln_repo() -> AsyncGenerator[InitLnRepoUpdate, None]:
|
||||
async for u in ln.initialize_impl():
|
||||
async for u in ln.initialize():
|
||||
yield u
|
||||
|
||||
|
||||
async def get_ln_info_lite() -> LightningInfoLite:
|
||||
ln_info = await ln.get_ln_info_impl()
|
||||
ln_info = await ln.get_ln_info()
|
||||
return LightningInfoLite.from_lninfo(ln_info)
|
||||
|
||||
|
||||
async def get_wallet_balance():
|
||||
return await ln.get_wallet_balance_impl()
|
||||
return await ln.get_wallet_balance()
|
||||
|
||||
|
||||
async def list_all_tx(
|
||||
successful_only: bool, index_offset: int, max_tx: int, reversed: bool
|
||||
) -> List[GenericTx]:
|
||||
return await ln.list_all_tx_impl(successful_only, index_offset, max_tx, reversed)
|
||||
return await ln.list_all_tx(successful_only, index_offset, max_tx, reversed)
|
||||
|
||||
|
||||
async def list_invoices(
|
||||
pending_only: bool, index_offset: int, num_max_invoices: int, reversed: bool
|
||||
) -> List[Invoice]:
|
||||
return await ln.list_invoices_impl(
|
||||
return await ln.list_invoices(
|
||||
pending_only,
|
||||
index_offset,
|
||||
num_max_invoices,
|
||||
|
|
@ -87,13 +91,13 @@ async def list_invoices(
|
|||
|
||||
|
||||
async def list_on_chain_tx() -> List[OnChainTransaction]:
|
||||
return await ln.list_on_chain_tx_impl()
|
||||
return await ln.list_on_chain_tx()
|
||||
|
||||
|
||||
async def list_payments(
|
||||
include_incomplete: bool, index_offset: int, max_payments: int, reversed: bool
|
||||
) -> List[Payment]:
|
||||
return await ln.list_payments_impl(
|
||||
return await ln.list_payments(
|
||||
include_incomplete, index_offset, max_payments, reversed
|
||||
)
|
||||
|
||||
|
|
@ -101,19 +105,19 @@ async def list_payments(
|
|||
async def add_invoice(
|
||||
value_msat: int, memo: str = "", expiry: int = 3600, is_keysend: bool = False
|
||||
) -> Invoice:
|
||||
return await ln.add_invoice_impl(memo, value_msat, expiry, is_keysend)
|
||||
return await ln.add_invoice(memo, value_msat, expiry, is_keysend)
|
||||
|
||||
|
||||
async def decode_pay_request(pay_req: str) -> PaymentRequest:
|
||||
return await ln.decode_pay_request_impl(pay_req)
|
||||
return await ln.decode_pay_request(pay_req)
|
||||
|
||||
|
||||
async def new_address(input: NewAddressInput) -> str:
|
||||
return await ln.new_address_impl(input)
|
||||
return await ln.new_address(input)
|
||||
|
||||
|
||||
async def send_coins(input: SendCoinsInput) -> SendCoinsResponse:
|
||||
res = await ln.send_coins_impl(input)
|
||||
res = await ln.send_coins(input)
|
||||
_schedule_wallet_balance_update()
|
||||
return res
|
||||
|
||||
|
|
@ -124,9 +128,7 @@ async def send_payment(
|
|||
fee_limit_msat: int,
|
||||
amount_msat: Optional[int] = None,
|
||||
) -> Payment:
|
||||
res = await ln.send_payment_impl(
|
||||
pay_req, timeout_seconds, fee_limit_msat, amount_msat
|
||||
)
|
||||
res = await ln.send_payment(pay_req, timeout_seconds, fee_limit_msat, amount_msat)
|
||||
_schedule_wallet_balance_update()
|
||||
return res
|
||||
|
||||
|
|
@ -147,41 +149,41 @@ async def channel_open(
|
|||
if not "@" in node_URI:
|
||||
raise ValueError("node_URI must contain @ with node physical address")
|
||||
|
||||
res = await ln.channel_open_impl(local_funding_amount, node_URI, target_confs)
|
||||
res = await ln.channel_open(local_funding_amount, node_URI, target_confs)
|
||||
return res
|
||||
|
||||
|
||||
async def channel_list() -> List[Channel]:
|
||||
res = await ln.channel_list_impl()
|
||||
res = await ln.channel_list()
|
||||
return res
|
||||
|
||||
|
||||
async def channel_close(channel_id: int, force_close: bool) -> str:
|
||||
res = await ln.channel_close_impl(channel_id, force_close)
|
||||
res = await ln.channel_close(channel_id, force_close)
|
||||
return res
|
||||
|
||||
|
||||
async def get_ln_info() -> LnInfo:
|
||||
ln_info = await ln.get_ln_info_impl()
|
||||
ln_info = await ln.get_ln_info()
|
||||
if PLATFORM == APIPlatform.RASPIBLITZ:
|
||||
ln_info.identity_uri = await redis_get("ln_default_address")
|
||||
return ln_info
|
||||
|
||||
|
||||
async def unlock_wallet(password: str) -> bool:
|
||||
res = await ln.unlock_wallet_impl(password)
|
||||
res = await ln.unlock_wallet(password)
|
||||
return res
|
||||
|
||||
|
||||
async def get_fee_revenue() -> FeeRevenue:
|
||||
return await ln.get_fee_revenue_impl()
|
||||
return await ln.get_fee_revenue()
|
||||
|
||||
|
||||
async def register_lightning_listener():
|
||||
"""
|
||||
Registers all lightning listeners
|
||||
|
||||
By calling get_ln_info_impl() once, we ensure that wallet is unlocked.
|
||||
By calling get_ln_info() once, we ensure that wallet is unlocked.
|
||||
Implementation will throw HTTPException with status_code 423_LOCKED if otherwise.
|
||||
It is the task of the caller to call register_lightning_listener() again
|
||||
"""
|
||||
|
|
@ -194,7 +196,7 @@ async def register_lightning_listener():
|
|||
)
|
||||
return
|
||||
|
||||
await ln.get_ln_info_impl()
|
||||
await ln.get_ln_info()
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.create_task(_handle_info_listener())
|
||||
|
|
@ -208,7 +210,7 @@ async def _handle_info_listener():
|
|||
last_info = None
|
||||
last_info_lite = None
|
||||
while True:
|
||||
info = await ln.get_ln_info_impl()
|
||||
info = await ln.get_ln_info()
|
||||
|
||||
if last_info != info:
|
||||
await broadcast_sse_msg(SSE.LN_INFO, info.dict())
|
||||
|
|
@ -270,7 +272,7 @@ def _schedule_wallet_balance_update():
|
|||
global _wallet_balance_update_scheduled
|
||||
_wallet_balance_update_scheduled = True
|
||||
await asyncio.sleep(1.1)
|
||||
wb = await ln.get_wallet_balance_impl()
|
||||
wb = await ln.get_wallet_balance()
|
||||
if _CACHE["wallet_balance"] != wb:
|
||||
await broadcast_sse_msg(SSE.WALLET_BALANCE, wb.dict())
|
||||
_CACHE["wallet_balance"] = wb
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
133
app/repositories/ln_impl/ln_base.py
Normal file
133
app/repositories/ln_impl/ln_base.py
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
from abc import abstractmethod
|
||||
from typing import AsyncGenerator, List, Optional
|
||||
|
||||
from app.models.lightning import (
|
||||
Channel,
|
||||
FeeRevenue,
|
||||
ForwardSuccessEvent,
|
||||
GenericTx,
|
||||
InitLnRepoUpdate,
|
||||
Invoice,
|
||||
LnInfo,
|
||||
NewAddressInput,
|
||||
OnChainTransaction,
|
||||
Payment,
|
||||
PaymentRequest,
|
||||
SendCoinsInput,
|
||||
SendCoinsResponse,
|
||||
WalletBalance,
|
||||
)
|
||||
|
||||
|
||||
class LightningNodeBase:
|
||||
@abstractmethod
|
||||
def get_implementation_name(self) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def initialize(self) -> AsyncGenerator[InitLnRepoUpdate, None]:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def get_wallet_balance(self) -> WalletBalance:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def list_all_tx(
|
||||
self, successful_only: bool, index_offset: int, max_tx: int, reversed: bool
|
||||
) -> List[GenericTx]:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def list_invoices(
|
||||
self,
|
||||
pending_only: bool,
|
||||
index_offset: int,
|
||||
num_max_invoices: int,
|
||||
reversed: bool,
|
||||
):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def list_on_chain_tx(self) -> List[OnChainTransaction]:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def list_payments(
|
||||
self,
|
||||
include_incomplete: bool,
|
||||
index_offset: int,
|
||||
max_payments: int,
|
||||
reversed: bool,
|
||||
):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def add_invoice(
|
||||
self,
|
||||
value_msat: int,
|
||||
memo: str = "",
|
||||
expiry: int = 3600,
|
||||
is_keysend: bool = False,
|
||||
) -> Invoice:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def decode_pay_request(self, pay_req: str) -> PaymentRequest:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def get_fee_revenue(self) -> FeeRevenue:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def new_address(self, input: NewAddressInput) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def send_coins(self, input: SendCoinsInput) -> SendCoinsResponse:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def send_payment(
|
||||
self,
|
||||
pay_req: str,
|
||||
timeout_seconds: int,
|
||||
fee_limit_msat: int,
|
||||
amount_msat: Optional[int] = None,
|
||||
) -> Payment:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def get_ln_info(self) -> LnInfo:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def unlock_wallet(self, password: str) -> bool:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def listen_invoices(self) -> AsyncGenerator[Invoice, None]:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def listen_forward_events(self) -> ForwardSuccessEvent:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def channel_open(
|
||||
self, local_funding_amount: int, node_URI: str, target_confs: int
|
||||
) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def peer_resolve_alias(self, node_pub: str) -> str:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def channel_list(self) -> List[Channel]:
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
async def channel_close(self, channel_id: int, force_close: bool) -> str:
|
||||
raise NotImplementedError()
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -6,7 +6,6 @@ from decouple import config
|
|||
from fastapi.exceptions import HTTPException
|
||||
from starlette import status
|
||||
|
||||
import app.repositories.ln_impl.cln_grpc as cln_main
|
||||
from app.core_utils import call_script2, redis_get
|
||||
from app.models.lightning import (
|
||||
Channel,
|
||||
|
|
@ -25,307 +24,240 @@ from app.models.lightning import (
|
|||
SendCoinsResponse,
|
||||
WalletBalance,
|
||||
)
|
||||
|
||||
# RaspiBlitz implements a lock function on top of CLN, so we need to implement this on Blitz only.
|
||||
from app.repositories.ln_impl.cln_grpc import LnNodeCLNgRPC
|
||||
|
||||
|
||||
_unlocked = False
|
||||
class LnNodeCLNgRPCBlitz(LnNodeCLNgRPC):
|
||||
# RaspiBlitz implements a lock function on top of CLN, so we need to implement this on Blitz only.
|
||||
|
||||
_NETWORK = config("network", default="mainnet")
|
||||
_unlocked = False
|
||||
|
||||
_NETWORK = config("network", default="mainnet")
|
||||
|
||||
def get_implementation_name() -> str:
|
||||
return "CLN_GRPC_BLITZ"
|
||||
def get_implementation_name(self) -> str:
|
||||
return "CLN_GRPC_BLITZ"
|
||||
|
||||
async def initialize(self) -> AsyncGenerator[InitLnRepoUpdate, None]:
|
||||
logging.debug("CLN_GRPC_BLITZ: RaspiBlitz is locked, waiting for unlock...")
|
||||
|
||||
async def initialize_impl() -> AsyncGenerator[InitLnRepoUpdate, None]:
|
||||
logging.debug("CLN_GRPC_BLITZ: RaspiBlitz is locked, waiting for unlock...")
|
||||
|
||||
global _unlocked
|
||||
|
||||
while not _unlocked:
|
||||
key = f"ln_cl_{_NETWORK}_locked"
|
||||
res = await redis_get(key)
|
||||
if res == "0":
|
||||
logging.debug(
|
||||
f"CLN_GRPC_BLITZ: Redis key {key} indicates that RaspiBlitz has been unlocked"
|
||||
)
|
||||
|
||||
_unlocked = True
|
||||
yield InitLnRepoUpdate(state=LnInitState.BOOTSTRAPPING_AFTER_UNLOCK)
|
||||
break
|
||||
elif res == "1":
|
||||
logging.debug(
|
||||
f"CLN_GRPC_BLITZ: Redis key {key} indicates that RaspiBlitz is still locked"
|
||||
)
|
||||
|
||||
yield InitLnRepoUpdate(
|
||||
state=LnInitState.LOCKED,
|
||||
msg="Wallet locked, unlock it to enable full RPC access",
|
||||
)
|
||||
else:
|
||||
logging.error(
|
||||
f"CLN_GRPC_BLITZ: Redis key {key} returns an unexpected value: {res}"
|
||||
)
|
||||
raise HTTPException(
|
||||
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Unknown lock status: {res}",
|
||||
)
|
||||
|
||||
await asyncio.sleep(2)
|
||||
|
||||
async for u in cln_main.initialize_impl():
|
||||
yield u
|
||||
if u.state == LnInitState.DONE:
|
||||
break
|
||||
|
||||
logging.info("CLN_GRPC_BLITZ: Initialization complete.")
|
||||
|
||||
|
||||
async def get_wallet_balance_impl() -> WalletBalance:
|
||||
try:
|
||||
return await cln_main.get_wallet_balance_impl()
|
||||
except:
|
||||
_check_if_locked()
|
||||
raise
|
||||
|
||||
|
||||
async def list_all_tx_impl(
|
||||
successful_only: bool, index_offset: int, max_tx: int, reversed: bool
|
||||
) -> List[GenericTx]:
|
||||
try:
|
||||
return await cln_main.list_all_tx_impl(
|
||||
successful_only, index_offset, max_tx, reversed
|
||||
)
|
||||
except:
|
||||
_check_if_locked()
|
||||
raise
|
||||
|
||||
|
||||
async def list_invoices_impl(
|
||||
pending_only: bool, index_offset: int, num_max_invoices: int, reversed: bool
|
||||
) -> List[Invoice]:
|
||||
try:
|
||||
return await cln_main.list_invoices_impl(
|
||||
pending_only, index_offset, num_max_invoices, reversed
|
||||
)
|
||||
except:
|
||||
_check_if_locked()
|
||||
raise
|
||||
|
||||
|
||||
async def list_on_chain_tx_impl() -> List[OnChainTransaction]:
|
||||
try:
|
||||
return await cln_main.list_on_chain_tx_impl()
|
||||
except:
|
||||
_check_if_locked()
|
||||
raise
|
||||
|
||||
|
||||
async def list_payments_impl(
|
||||
include_incomplete: bool, index_offset: int, max_payments: int, reversed: bool
|
||||
):
|
||||
try:
|
||||
return await cln_main.list_payments_impl(
|
||||
include_incomplete, index_offset, max_payments, reversed
|
||||
)
|
||||
except:
|
||||
_check_if_locked()
|
||||
raise
|
||||
|
||||
|
||||
async def add_invoice_impl(
|
||||
value_msat: int, memo: str = "", expiry: int = 3600, is_keysend: bool = False
|
||||
) -> Invoice:
|
||||
try:
|
||||
return await cln_main.add_invoice_impl(value_msat, memo, expiry, is_keysend)
|
||||
except:
|
||||
_check_if_locked()
|
||||
raise
|
||||
|
||||
|
||||
async def decode_pay_request_impl(pay_req: str) -> PaymentRequest:
|
||||
try:
|
||||
return await cln_main.decode_pay_request_impl(pay_req)
|
||||
except:
|
||||
_check_if_locked()
|
||||
raise
|
||||
|
||||
|
||||
async def get_fee_revenue_impl() -> FeeRevenue:
|
||||
try:
|
||||
return await cln_main.get_fee_revenue_impl()
|
||||
except:
|
||||
_check_if_locked()
|
||||
raise
|
||||
|
||||
|
||||
async def new_address_impl(input: NewAddressInput) -> str:
|
||||
try:
|
||||
return await cln_main.new_address_impl(input)
|
||||
except:
|
||||
_check_if_locked()
|
||||
raise
|
||||
|
||||
|
||||
async def send_coins_impl(input: SendCoinsInput) -> SendCoinsResponse:
|
||||
try:
|
||||
return await cln_main.send_coins_impl(input)
|
||||
except:
|
||||
_check_if_locked()
|
||||
raise
|
||||
|
||||
|
||||
async def send_payment_impl(
|
||||
pay_req: str,
|
||||
timeout_seconds: int,
|
||||
fee_limit_msat: int,
|
||||
amount_msat: Optional[int] = None,
|
||||
) -> Payment:
|
||||
try:
|
||||
return await cln_main.send_payment_impl(
|
||||
pay_req, timeout_seconds, fee_limit_msat, amount_msat
|
||||
)
|
||||
except:
|
||||
_check_if_locked()
|
||||
raise
|
||||
|
||||
|
||||
async def get_ln_info_impl() -> LnInfo:
|
||||
try:
|
||||
# This will return "CLN_GRPC" and not "CLN_GRPC_BLITZ" to
|
||||
# not to complicate things further.
|
||||
# res.implementation = get_implementation_name()
|
||||
return await cln_main.get_ln_info_impl()
|
||||
except:
|
||||
_check_if_locked()
|
||||
raise
|
||||
|
||||
|
||||
async def unlock_wallet_impl(password: str) -> bool:
|
||||
# RaspiBlitz implements a wallet lock functionality on top of CLN,
|
||||
# so we need to implement this on Blitz only
|
||||
|
||||
# /home/admin/config.scripts/cl.hsmtool.sh unlock mainnet PASSWORD_C
|
||||
# cl.hsmtool.sh [unlock] <mainnet|testnet|signet> <password>
|
||||
|
||||
global _unlocked
|
||||
key = f"ln_cl_{_NETWORK}_locked"
|
||||
res = await redis_get(key)
|
||||
if res == "0":
|
||||
raise HTTPException(
|
||||
status.HTTP_412_PRECONDITION_FAILED, detail="wallet already unlocked"
|
||||
)
|
||||
|
||||
res = await call_script2(
|
||||
f"/home/admin/config.scripts/cl.hsmtool.sh unlock {_NETWORK} {password}"
|
||||
)
|
||||
|
||||
if res.return_code == 0:
|
||||
logging.debug(
|
||||
f"CLN_GRPC_BLITZ: Unlock script successfully called via API. Waiting for Redis {key} to be set."
|
||||
)
|
||||
|
||||
# success: exit 0
|
||||
INTERVAL = 1
|
||||
total_wait_time = 0
|
||||
while total_wait_time < 60:
|
||||
while not self._unlocked:
|
||||
key = f"ln_cl_{self._NETWORK}_locked"
|
||||
res = await redis_get(key)
|
||||
if res == "0":
|
||||
_unlocked = True
|
||||
return True
|
||||
logging.debug(
|
||||
f"CLN_GRPC_BLITZ: Redis key {key} indicates that RaspiBlitz has been unlocked"
|
||||
)
|
||||
|
||||
await asyncio.sleep(INTERVAL)
|
||||
total_wait_time += INTERVAL
|
||||
self._unlocked = True
|
||||
yield InitLnRepoUpdate(state=LnInitState.BOOTSTRAPPING_AFTER_UNLOCK)
|
||||
break
|
||||
elif res == "1":
|
||||
logging.debug(
|
||||
f"CLN_GRPC_BLITZ: Redis key {key} indicates that RaspiBlitz is still locked"
|
||||
)
|
||||
|
||||
logging.debug(
|
||||
f"CLN_GRPC_BLITZ: Unlock script called successfully but redis key {key} indicates that RaspiBlitz is still locked. Stopped watching after polling for 60s for an unlock signal."
|
||||
yield InitLnRepoUpdate(
|
||||
state=LnInitState.LOCKED,
|
||||
msg="Wallet locked, unlock it to enable full RPC access",
|
||||
)
|
||||
else:
|
||||
logging.error(
|
||||
f"CLN_GRPC_BLITZ: Redis key {key} returns an unexpected value: {res}"
|
||||
)
|
||||
raise HTTPException(
|
||||
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Unknown lock status: {res}",
|
||||
)
|
||||
|
||||
await asyncio.sleep(2)
|
||||
|
||||
async for u in super().initialize_impl():
|
||||
yield u
|
||||
if u.state == LnInitState.DONE:
|
||||
break
|
||||
|
||||
logging.info("CLN_GRPC_BLITZ: Initialization complete.")
|
||||
|
||||
async def get_wallet_balance(self) -> WalletBalance:
|
||||
self._check_if_locked()
|
||||
return await super().get_wallet_balance()
|
||||
|
||||
async def list_all_tx(
|
||||
self, successful_only: bool, index_offset: int, max_tx: int, reversed: bool
|
||||
) -> List[GenericTx]:
|
||||
self._check_if_locked()
|
||||
return await super().list_all_tx(
|
||||
successful_only, index_offset, max_tx, reversed
|
||||
)
|
||||
|
||||
async def list_invoices(
|
||||
self,
|
||||
pending_only: bool,
|
||||
index_offset: int,
|
||||
num_max_invoices: int,
|
||||
reversed: bool,
|
||||
):
|
||||
self._check_if_locked()
|
||||
return await super().list_invoices(
|
||||
pending_only, index_offset, num_max_invoices, reversed
|
||||
)
|
||||
|
||||
async def list_on_chain_tx(self) -> List[OnChainTransaction]:
|
||||
self._check_if_locked()
|
||||
return await super().list_on_chain_tx()
|
||||
|
||||
async def list_payments(
|
||||
self,
|
||||
include_incomplete: bool,
|
||||
index_offset: int,
|
||||
max_payments: int,
|
||||
reversed: bool,
|
||||
):
|
||||
self._check_if_locked()
|
||||
return await super().list_payments(
|
||||
include_incomplete, index_offset, max_payments, reversed
|
||||
)
|
||||
|
||||
async def add_invoice(
|
||||
self,
|
||||
value_msat: int,
|
||||
memo: str = "",
|
||||
expiry: int = 3600,
|
||||
is_keysend: bool = False,
|
||||
) -> Invoice:
|
||||
self._check_if_locked()
|
||||
return await super().add_invoice(value_msat, memo, expiry, is_keysend)
|
||||
|
||||
async def decode_pay_request(self, pay_req: str) -> PaymentRequest:
|
||||
self._check_if_locked()
|
||||
return await super().decode_pay_request(pay_req)
|
||||
|
||||
async def get_fee_revenue(self) -> FeeRevenue:
|
||||
self._check_if_locked()
|
||||
return await super().get_fee_revenue()
|
||||
|
||||
async def new_address(self, input: NewAddressInput) -> str:
|
||||
self._check_if_locked()
|
||||
return await super().new_address(input)
|
||||
|
||||
async def send_coins(self, input: SendCoinsInput) -> SendCoinsResponse:
|
||||
self._check_if_locked()
|
||||
return await super().send_coins(input)
|
||||
|
||||
async def send_payment(
|
||||
self,
|
||||
pay_req: str,
|
||||
timeout_seconds: int,
|
||||
fee_limit_msat: int,
|
||||
amount_msat: Optional[int] = None,
|
||||
) -> Payment:
|
||||
self._check_if_locked()
|
||||
return await super().send_payment(
|
||||
pay_req, timeout_seconds, fee_limit_msat, amount_msat
|
||||
)
|
||||
|
||||
async def get_ln_info(self) -> LnInfo:
|
||||
self._check_if_locked()
|
||||
return await super().get_ln_info()
|
||||
|
||||
async def unlock_wallet(self, password: str) -> bool:
|
||||
# RaspiBlitz implements a wallet lock functionality on top of CLN,
|
||||
# so we need to implement this on Blitz only
|
||||
|
||||
# /home/admin/config.scripts/cl.hsmtool.sh unlock mainnet PASSWORD_C
|
||||
# cl.hsmtool.sh [unlock] <mainnet|testnet|signet> <password>
|
||||
|
||||
key = f"ln_cl_{self._NETWORK}_locked"
|
||||
res = await redis_get(key)
|
||||
if res == "0":
|
||||
raise HTTPException(
|
||||
status.HTTP_412_PRECONDITION_FAILED, detail="wallet already unlocked"
|
||||
)
|
||||
|
||||
res = await call_script2(
|
||||
f"/home/admin/config.scripts/cl.hsmtool.sh unlock {self._NETWORK} {password}"
|
||||
)
|
||||
|
||||
if res.return_code == 0:
|
||||
logging.debug(
|
||||
f"CLN_GRPC_BLITZ: Unlock script successfully called via API. Waiting for Redis {key} to be set."
|
||||
)
|
||||
|
||||
# success: exit 0
|
||||
INTERVAL = 1
|
||||
total_wait_time = 0
|
||||
while total_wait_time < 60:
|
||||
res = await redis_get(key)
|
||||
if res == "0":
|
||||
_unlocked = True
|
||||
return True
|
||||
|
||||
await asyncio.sleep(INTERVAL)
|
||||
total_wait_time += INTERVAL
|
||||
|
||||
logging.debug(
|
||||
f"CLN_GRPC_BLITZ: Unlock script called successfully but redis key {key} indicates that RaspiBlitz is still locked. Stopped watching after polling for 60s for an unlock signal."
|
||||
)
|
||||
|
||||
raise HTTPException(
|
||||
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Unknown error while trying to unlock.",
|
||||
)
|
||||
elif res.return_code == 1:
|
||||
logging.error("CLN_GRPC_BLITZ: Unknown error while trying to unlock.")
|
||||
logging.error(f"CLN_GRPC_BLITZ: {res.__str__()}")
|
||||
|
||||
raise HTTPException(
|
||||
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Unknown error while trying to unlock. See the API logs for more info.",
|
||||
)
|
||||
elif res.return_code == 2:
|
||||
# wrong password: exit 2
|
||||
raise HTTPException(
|
||||
status.HTTP_401_UNAUTHORIZED, detail="invalid passphrase"
|
||||
)
|
||||
elif res.return_code == 3:
|
||||
# fail to unlock after 1 minute + show logs: exit 3
|
||||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, detail=res)
|
||||
|
||||
raise HTTPException(
|
||||
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="Unknown error while trying to unlock.",
|
||||
detail=f"Unknown error while trying to unlock.\n{res}",
|
||||
)
|
||||
elif res.return_code == 1:
|
||||
logging.error("CLN_GRPC_BLITZ: Unknown error while trying to unlock.")
|
||||
logging.error(f"CLN_GRPC_BLITZ: {res.__str__()}")
|
||||
|
||||
raise HTTPException(
|
||||
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Unknown error while trying to unlock. See the API logs for more info.",
|
||||
)
|
||||
elif res.return_code == 2:
|
||||
# wrong password: exit 2
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail="invalid passphrase")
|
||||
elif res.return_code == 3:
|
||||
# fail to unlock after 1 minute + show logs: exit 3
|
||||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, detail=res)
|
||||
|
||||
raise HTTPException(
|
||||
status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail=f"Unknown error while trying to unlock.\n{res}",
|
||||
)
|
||||
|
||||
|
||||
async def listen_invoices() -> AsyncGenerator[Invoice, None]:
|
||||
try:
|
||||
async for i in cln_main.listen_invoices():
|
||||
async def listen_invoices(self) -> AsyncGenerator[Invoice, None]:
|
||||
self._check_if_locked()
|
||||
async for i in super().listen_invoices():
|
||||
yield i
|
||||
except:
|
||||
_check_if_locked()
|
||||
raise
|
||||
|
||||
async def listen_forward_events(self) -> ForwardSuccessEvent:
|
||||
self._check_if_locked()
|
||||
async for i in super().listen_forward_events():
|
||||
yield i
|
||||
|
||||
async def listen_forward_events() -> ForwardSuccessEvent:
|
||||
try:
|
||||
async for e in cln_main.listen_forward_events():
|
||||
yield e
|
||||
except:
|
||||
_check_if_locked()
|
||||
raise
|
||||
async def channel_open(
|
||||
self, local_funding_amount: int, node_URI: str, target_confs: int
|
||||
) -> str:
|
||||
self._check_if_locked()
|
||||
return await super().channel_open(local_funding_amount, node_URI, target_confs)
|
||||
|
||||
async def peer_resolve_alias(self, node_pub: str) -> str:
|
||||
self._check_if_locked()
|
||||
return await super().peer_resolve_alias(node_pub)
|
||||
|
||||
async def connect_peer_impl(node_URI: str) -> bool:
|
||||
try:
|
||||
return await cln_main.connect_peer_impl(node_URI)
|
||||
except:
|
||||
_check_if_locked()
|
||||
raise
|
||||
async def channel_list(self) -> List[Channel]:
|
||||
self._check_if_locked()
|
||||
return await super().channel_list()
|
||||
|
||||
async def channel_close(self, channel_id: int, force_close: bool) -> str:
|
||||
self._check_if_locked()
|
||||
return await super().channel_close(channel_id, force_close)
|
||||
|
||||
async def channel_open_impl(
|
||||
local_funding_amount: int, node_URI: str, target_confs: int
|
||||
) -> str:
|
||||
try:
|
||||
return await cln_main.channel_open_impl(
|
||||
local_funding_amount, node_URI, target_confs
|
||||
)
|
||||
except:
|
||||
_check_if_locked()
|
||||
raise
|
||||
def _check_if_locked(self):
|
||||
logging.debug(f"CLN_GRPC_BLITZ: _check_if_locked()")
|
||||
|
||||
|
||||
async def channel_list_impl() -> List[Channel]:
|
||||
try:
|
||||
return await cln_main.channel_list_impl()
|
||||
except:
|
||||
_check_if_locked()
|
||||
raise
|
||||
|
||||
|
||||
async def channel_close_impl(channel_id: int, force_close: bool) -> str:
|
||||
try:
|
||||
return await cln_main.channel_close_impl(channel_id, force_close)
|
||||
except:
|
||||
_check_if_locked()
|
||||
raise
|
||||
|
||||
|
||||
def _check_if_locked():
|
||||
logging.debug(f"CLN_GRPC_BLITZ: _check_if_locked()")
|
||||
|
||||
if not _unlocked:
|
||||
raise HTTPException(
|
||||
status.HTTP_423_LOCKED,
|
||||
detail="Wallet is locked. Unlock via /lightning/unlock-wallet",
|
||||
)
|
||||
if not self._unlocked:
|
||||
raise HTTPException(
|
||||
status.HTTP_423_LOCKED,
|
||||
detail="Wallet is locked. Unlock via /lightning/unlock-wallet",
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue