blitz_api/app/utils.py
Stefan Stammberger 20e0457a73
feat: send a wallet_locked_status SSE message
When someone connects to the SSE endpoint and the wallet is
locked a message with event name `wallet_lock_status` type is sent:
```js
// only sent when a new SSE connection is established and the wallet is
//  locked at the same time
event: wallet_lock_status
data: {"locked": true}
```
When the wallet is unlocked either through SSH or the API, another
event is sent:
```js
// sent always when a new connection is established when the wallet
// is unlocked, or it is sent when the wallet was unlocked after
// connection was established
event: wallet_lock_status
data: {"locked": false}
```

It is always the first thing the client receives. After the unlock
event, the usual warmup data is sent.

refs #34
2021-11-30 19:57:17 +01:00

161 lines
5.2 KiB
Python

import json
import os
from types import coroutine
from typing import Dict
import aiohttp
import grpc
import requests
from decouple import config
from fastapi.encoders import jsonable_encoder
from fastapi_plugins import redis_plugin
import app.repositories.ln_impl.protos.lightning_pb2_grpc as lnrpc
import app.repositories.ln_impl.protos.router_pb2_grpc as routerrpc
import app.repositories.ln_impl.protos.walletunlocker_pb2_grpc as unlockerrpc
class BitcoinConfig:
def __init__(self) -> None:
self.network = config("network")
if self.network == "testnet":
self.ip = config("bitcoind_ip_testnet")
self.rpc_port = config("bitcoind_port_rpc_testnet")
self.zmq_port = config("bitcoind_port_zmq_hashblock_testnet")
else:
self.ip = config("bitcoind_ip_mainnet")
self.rpc_port = config("bitcoind_port_rpc_mainnet")
self.zmq_port = config("bitcoind_port_zmq_hashblock_mainnet")
self.rpc_url = f"http://{self.ip}:{self.rpc_port}"
self.zmq_url = f"tcp://{self.ip}:{self.zmq_port}"
self.username = config("bitcoind_user")
self.pw = config("bitcoind_pw")
bitcoin_config = BitcoinConfig()
class LightningConfig:
def __init__(self) -> None:
self.network = config("network")
self.ln_node = config("ln_node")
if self.ln_node == "lnd":
# Due to updated ECDSA generated tls.cert we need to let gprc know that
# we need to use that cipher suite otherwise there will be a handshake
# error when we communicate with the lnd rpc server.
os.environ["GRPC_SSL_CIPHER_SUITES"] = "HIGH+ECDSA"
# Uncomment to see full gRPC logs
# os.environ["GRPC_TRACE"] = 'all'
# os.environ["GRPC_VERBOSITY"] = 'DEBUG'
self.lnd_macaroon = config("lnd_macaroon")
self._lnd_cert = bytes.fromhex(config("lnd_cert"))
self._lnd_grpc_ip = config("lnd_grpc_ip")
self._lnd_grpc_port = config("lnd_grpc_port")
self._lnd_rest_port = config("lnd_rest_port")
self._lnd_grpc_url = self._lnd_grpc_ip + ":" + self._lnd_grpc_port
auth_creds = grpc.metadata_call_credentials(self.metadata_callback)
ssl_creds = grpc.ssl_channel_credentials(self._lnd_cert)
combined_creds = grpc.composite_channel_credentials(ssl_creds, auth_creds)
self._channel = grpc.aio.secure_channel(self._lnd_grpc_url, combined_creds)
self.lnd_stub = lnrpc.LightningStub(self._channel)
self.router_stub = routerrpc.RouterStub(self._channel)
self.wallet_unlocker = unlockerrpc.WalletUnlockerStub(self._channel)
elif self.ln_node == "clightning":
# TODO: implement c-lightning
pass
else:
raise NameError(
f'Node type "{self.ln_node}" is unknown. Use "lnd" or "clightning"'
)
def metadata_callback(self, context, callback):
# for more info see grpc docs
callback([("macaroon", self.lnd_macaroon)], None)
lightning_config = LightningConfig()
def bitcoin_rpc(method: str, params: list = []) -> requests.Response:
"""Make an RPC request to the Bitcoin daemon
Connection parameters are read from the .env file.
Parameters
----------
method : str
The method to call.
params : list, optional
Any parameters to include with the call
"""
auth = (bitcoin_config.username, bitcoin_config.pw)
headers = {"Content-type": "text/plain"}
data = (
'{"jsonrpc": "2.0", "method": "'
+ method
+ '", "id":"0", "params":'
+ json.dumps(params)
+ "}"
)
return requests.post(bitcoin_config.rpc_url, auth=auth, headers=headers, data=data)
async def bitcoin_rpc_async(method: str, params: list = []) -> coroutine:
auth = aiohttp.BasicAuth(bitcoin_config.username, bitcoin_config.pw)
headers = {"Content-type": "text/plain"}
data = (
'{"jsonrpc": "2.0", "method": "'
+ method
+ '", "id":"0", "params":'
+ json.dumps(params)
+ "}"
)
async with aiohttp.ClientSession(auth=auth, headers=headers) as session:
async with session.post(bitcoin_config.rpc_url, data=data) as resp:
return await resp.json()
async def send_sse_message(id: str, json_data: Dict):
"""Send a message to any SSE connections
Parameters
----------
id : str
ID String von SSE class
data : list, optional
The data to include
"""
await redis_plugin.redis.publish_json(
"default", {"event": id, "data": json.dumps(jsonable_encoder(json_data))}
)
class SSE:
SYSTEM_INFO = "system_info"
HARDWARE_INFO = "hardware_info"
INSTALLED_APP_STATUS = "installed_app_status"
BTC_NETWORK_STATUS = "btc_network_status"
BTC_MEMPOOL_STATUS = "btc_mempool_status"
BTC_NEW_BLOC = "btc_new_bloc"
BTC_INFO = "btc_info"
LN_INFO = "ln_info"
LN_INFO_LITE = "ln_info_lite"
LN_INVOICE_STATUS = "ln_invoice_status"
LN_PAYMENT_STATUS = "ln_payment_status"
LN_ONCHAIN_PAYMENT_STATUS = "ln_onchain_payment_status"
WALLET_BALANCE = "wallet_balance"
WALLET_LOCK_STATUS = "wallet_lock_status"