mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-23 09:37:31 +00:00
Integrate http_server (previously in electrum-merchant)
Use submodule to fetch HTML and CSS files
This commit is contained in:
parent
bd57880799
commit
747ab7a0a2
10 changed files with 124 additions and 238 deletions
3
.gitmodules
vendored
3
.gitmodules
vendored
|
@ -4,3 +4,6 @@
|
|||
[submodule "contrib/CalinsQRReader"]
|
||||
path = contrib/osx/CalinsQRReader
|
||||
url = https://github.com/spesmilo/CalinsQRReader
|
||||
[submodule "electrum/www"]
|
||||
path = electrum/www
|
||||
url = git@github.com:spesmilo/electrum-http.git
|
||||
|
|
|
@ -1039,7 +1039,6 @@ arg_types = {
|
|||
config_variables = {
|
||||
|
||||
'addrequest': {
|
||||
'requests_dir': 'directory where a bip70 file will be written.',
|
||||
'ssl_privkey': 'Path to your SSL private key, needed to sign the request.',
|
||||
'ssl_chain': 'Chain of SSL certificates, needed for signed requests. Put your certificate at the top and the root CA at the end',
|
||||
'url_rewrite': 'Parameters passed to str.replace(), in order to create the r= part of bitcoin: URIs. Example: \"(\'file:///var/www/\',\'https://electrum.org/\')\"',
|
||||
|
|
|
@ -33,6 +33,7 @@ from typing import Dict, Optional, Tuple
|
|||
import aiohttp
|
||||
from aiohttp import web
|
||||
from base64 import b64decode
|
||||
from collections import defaultdict
|
||||
|
||||
import jsonrpcclient
|
||||
import jsonrpcserver
|
||||
|
@ -41,6 +42,7 @@ from jsonrpcclient.clients.aiohttp_client import AiohttpClient
|
|||
|
||||
from .network import Network
|
||||
from .util import (json_decode, to_bytes, to_string, profiler, standardize_path, constant_time_compare)
|
||||
from .util import PR_PAID, PR_EXPIRED, get_request_status
|
||||
from .wallet import Wallet, Abstract_Wallet
|
||||
from .storage import WalletStorage
|
||||
from .commands import known_commands, Commands
|
||||
|
@ -168,6 +170,79 @@ class WatchTowerServer(Logger):
|
|||
async def add_sweep_tx(self, *args):
|
||||
return await self.lnwatcher.sweepstore.add_sweep_tx(*args)
|
||||
|
||||
class HttpServer(Logger):
|
||||
|
||||
def __init__(self, daemon):
|
||||
Logger.__init__(self)
|
||||
self.daemon = daemon
|
||||
self.config = daemon.config
|
||||
self.pending = defaultdict(asyncio.Event)
|
||||
self.daemon.network.register_callback(self.on_payment, ['payment_received'])
|
||||
|
||||
async def on_payment(self, evt, *args):
|
||||
print(evt, args)
|
||||
#await self.pending[key].set()
|
||||
|
||||
async def run(self):
|
||||
from aiohttp import helpers
|
||||
app = web.Application()
|
||||
#app.on_response_prepare.append(http_server.on_response_prepare)
|
||||
app.add_routes([web.post('/api/create_invoice', self.create_request)])
|
||||
app.add_routes([web.get('/api/get_invoice', self.get_request)])
|
||||
app.add_routes([web.get('/api/get_status', self.get_status)])
|
||||
app.add_routes([web.static('/electrum', 'electrum/www')])
|
||||
runner = web.AppRunner(app)
|
||||
await runner.setup()
|
||||
host = self.config.get('http_host', 'localhost')
|
||||
port = self.config.get('http_port', 8000)
|
||||
site = web.TCPSite(runner, port=port, host=host)
|
||||
await site.start()
|
||||
|
||||
async def create_request(self, request):
|
||||
params = await request.post()
|
||||
wallet = self.daemon.wallet
|
||||
if 'amount_sat' not in params or not params['amount_sat'].isdigit():
|
||||
raise web.HTTPUnsupportedMediaType()
|
||||
amount = int(params['amount_sat'])
|
||||
message = params['message'] or "donation"
|
||||
payment_hash = await wallet.lnworker._add_invoice_coro(amount, message, 3600)
|
||||
key = payment_hash.hex()
|
||||
raise web.HTTPFound('/electrum/index.html?id=' + key)
|
||||
|
||||
async def get_request(self, r):
|
||||
key = r.query_string
|
||||
request = self.daemon.wallet.get_request(key)
|
||||
return web.json_response(request)
|
||||
|
||||
async def get_status(self, request):
|
||||
ws = web.WebSocketResponse()
|
||||
await ws.prepare(request)
|
||||
key = request.query_string
|
||||
info = self.daemon.wallet.get_request(key)
|
||||
if not info:
|
||||
await ws.send_str('unknown invoice')
|
||||
await ws.close()
|
||||
return ws
|
||||
if info.get('status') == PR_PAID:
|
||||
await ws.send_str(f'already paid')
|
||||
await ws.close()
|
||||
return ws
|
||||
if info.get('status') == PR_EXPIRED:
|
||||
await ws.send_str(f'invoice expired')
|
||||
await ws.close()
|
||||
return ws
|
||||
while True:
|
||||
try:
|
||||
await asyncio.wait_for(self.pending[key].wait(), 1)
|
||||
break
|
||||
except asyncio.TimeoutError:
|
||||
# send data on the websocket, to keep it alive
|
||||
await ws.send_str('waiting')
|
||||
await ws.send_str('paid')
|
||||
await ws.close()
|
||||
return ws
|
||||
|
||||
|
||||
class AuthenticationError(Exception):
|
||||
pass
|
||||
|
||||
|
@ -197,6 +272,9 @@ class Daemon(Logger):
|
|||
if listen_jsonrpc:
|
||||
jobs.append(self.start_jsonrpc(config, fd))
|
||||
# server-side watchtower
|
||||
self.http_server = HttpServer(self)
|
||||
if self.http_server:
|
||||
jobs.append(self.http_server.run())
|
||||
self.watchtower = WatchTowerServer(self.network) if self.config.get('watchtower_host') else None
|
||||
if self.watchtower:
|
||||
jobs.append(self.watchtower.run)
|
||||
|
@ -296,6 +374,7 @@ class Daemon(Logger):
|
|||
wallet = Wallet(storage)
|
||||
wallet.start_network(self.network)
|
||||
self.wallets[path] = wallet
|
||||
self.wallet = wallet
|
||||
return wallet
|
||||
|
||||
def add_wallet(self, wallet: Abstract_Wallet):
|
||||
|
|
|
@ -151,4 +151,4 @@ class InvoiceList(MyTreeView):
|
|||
def create_menu_ln_payreq(self, menu, payreq_key):
|
||||
req = self.parent.wallet.lnworker.invoices[payreq_key][0]
|
||||
menu.addAction(_("Copy Lightning invoice"), lambda: self.parent.do_copy('Lightning invoice', req))
|
||||
menu.addAction(_("Delete"), lambda: self.parent.delete_lightning_payreq(payreq_key))
|
||||
menu.addAction(_("Delete"), lambda: self.parent.delete_invoice(payreq_key))
|
||||
|
|
|
@ -1028,9 +1028,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||
|
||||
return w
|
||||
|
||||
|
||||
def delete_payment_request(self, addr):
|
||||
self.wallet.remove_payment_request(addr, self.config)
|
||||
def delete_request(self, key):
|
||||
self.wallet.delete_request(key)
|
||||
self.request_list.update()
|
||||
self.clear_receive_tab()
|
||||
|
||||
|
|
|
@ -40,13 +40,11 @@ from electrum.bitcoin import COIN
|
|||
from electrum.lnaddr import lndecode
|
||||
import electrum.constants as constants
|
||||
|
||||
from .util import MyTreeView, pr_icons, read_QIcon
|
||||
from .util import MyTreeView, pr_icons, read_QIcon, webopen
|
||||
|
||||
REQUEST_TYPE_BITCOIN = 0
|
||||
REQUEST_TYPE_LN = 1
|
||||
|
||||
ROLE_REQUEST_TYPE = Qt.UserRole
|
||||
ROLE_RHASH_OR_ADDR = Qt.UserRole + 1
|
||||
ROLE_KEY = Qt.UserRole + 1
|
||||
|
||||
class RequestList(MyTreeView):
|
||||
|
||||
|
@ -76,7 +74,7 @@ class RequestList(MyTreeView):
|
|||
def select_key(self, key):
|
||||
for i in range(self.model().rowCount()):
|
||||
item = self.model().index(i, self.Columns.DATE)
|
||||
row_key = item.data(ROLE_RHASH_OR_ADDR)
|
||||
row_key = item.data(ROLE_KEY)
|
||||
if key == row_key:
|
||||
self.selectionModel().setCurrentIndex(item, QItemSelectionModel.SelectCurrent | QItemSelectionModel.Rows)
|
||||
break
|
||||
|
@ -85,12 +83,12 @@ class RequestList(MyTreeView):
|
|||
# TODO use siblingAtColumn when min Qt version is >=5.11
|
||||
item = self.model().itemFromIndex(idx.sibling(idx.row(), self.Columns.DATE))
|
||||
request_type = item.data(ROLE_REQUEST_TYPE)
|
||||
key = item.data(ROLE_RHASH_OR_ADDR)
|
||||
is_lightning = request_type == REQUEST_TYPE_LN
|
||||
req = self.wallet.get_request(key, is_lightning)
|
||||
key = item.data(ROLE_KEY)
|
||||
req = self.wallet.get_request(key)
|
||||
if req is None:
|
||||
self.update()
|
||||
return
|
||||
is_lightning = request_type == PR_TYPE_LN
|
||||
text = req.get('invoice') if is_lightning else req.get('URI')
|
||||
self.parent.receive_address_e.setText(text)
|
||||
|
||||
|
@ -101,9 +99,9 @@ class RequestList(MyTreeView):
|
|||
date_idx = idx.sibling(idx.row(), self.Columns.DATE)
|
||||
date_item = m.itemFromIndex(date_idx)
|
||||
status_item = m.itemFromIndex(idx)
|
||||
key = date_item.data(ROLE_RHASH_OR_ADDR)
|
||||
is_lightning = date_item.data(ROLE_REQUEST_TYPE) == REQUEST_TYPE_LN
|
||||
req = self.wallet.get_request(key, is_lightning)
|
||||
key = date_item.data(ROLE_KEY)
|
||||
is_lightning = date_item.data(ROLE_REQUEST_TYPE) == PR_TYPE_LN
|
||||
req = self.wallet.get_request(key)
|
||||
if req:
|
||||
status = req['status']
|
||||
status_str = get_request_status(req)
|
||||
|
@ -121,7 +119,7 @@ class RequestList(MyTreeView):
|
|||
if status == PR_PAID:
|
||||
continue
|
||||
is_lightning = req['type'] == PR_TYPE_LN
|
||||
request_type = REQUEST_TYPE_LN if is_lightning else REQUEST_TYPE_BITCOIN
|
||||
request_type = req['type']
|
||||
timestamp = req.get('time', 0)
|
||||
amount = req.get('amount')
|
||||
message = req['message'] if is_lightning else req['memo']
|
||||
|
@ -133,18 +131,17 @@ class RequestList(MyTreeView):
|
|||
self.set_editability(items)
|
||||
items[self.Columns.DATE].setData(request_type, ROLE_REQUEST_TYPE)
|
||||
items[self.Columns.STATUS].setIcon(read_QIcon(pr_icons.get(status)))
|
||||
if request_type == REQUEST_TYPE_LN:
|
||||
items[self.Columns.DATE].setData(req['rhash'], ROLE_RHASH_OR_ADDR)
|
||||
if request_type == PR_TYPE_LN:
|
||||
items[self.Columns.DATE].setData(req['rhash'], ROLE_KEY)
|
||||
items[self.Columns.DATE].setIcon(read_QIcon("lightning.png"))
|
||||
items[self.Columns.DATE].setData(REQUEST_TYPE_LN, ROLE_REQUEST_TYPE)
|
||||
else:
|
||||
elif request_type == PR_TYPE_ADDRESS:
|
||||
address = req['address']
|
||||
if address not in domain:
|
||||
continue
|
||||
expiration = req.get('exp', None)
|
||||
signature = req.get('sig')
|
||||
requestor = req.get('name', '')
|
||||
items[self.Columns.DATE].setData(address, ROLE_RHASH_OR_ADDR)
|
||||
items[self.Columns.DATE].setData(address, ROLE_KEY)
|
||||
if signature is not None:
|
||||
items[self.Columns.DATE].setIcon(read_QIcon("seal.png"))
|
||||
items[self.Columns.DATE].setToolTip(f'signed by {requestor}')
|
||||
|
@ -167,13 +164,9 @@ class RequestList(MyTreeView):
|
|||
item = self.model().itemFromIndex(idx.sibling(idx.row(), self.Columns.DATE))
|
||||
if not item:
|
||||
return
|
||||
addr = item.data(ROLE_RHASH_OR_ADDR)
|
||||
key = item.data(ROLE_KEY)
|
||||
request_type = item.data(ROLE_REQUEST_TYPE)
|
||||
assert request_type in [REQUEST_TYPE_BITCOIN, REQUEST_TYPE_LN]
|
||||
if request_type == REQUEST_TYPE_BITCOIN:
|
||||
req = self.wallet.receive_requests.get(addr)
|
||||
elif request_type == REQUEST_TYPE_LN:
|
||||
req = self.wallet.lnworker.invoices[addr][0]
|
||||
req = self.wallet.get_request(key)
|
||||
if req is None:
|
||||
self.update()
|
||||
return
|
||||
|
@ -184,19 +177,15 @@ class RequestList(MyTreeView):
|
|||
if column == self.Columns.AMOUNT:
|
||||
column_data = column_data.strip()
|
||||
menu.addAction(_("Copy {}").format(column_title), lambda: self.parent.do_copy(column_title, column_data))
|
||||
if request_type == REQUEST_TYPE_BITCOIN:
|
||||
self.create_menu_bitcoin_payreq(menu, addr)
|
||||
elif request_type == REQUEST_TYPE_LN:
|
||||
self.create_menu_ln_payreq(menu, addr, req)
|
||||
|
||||
#menu.addAction(_("Copy Address"), lambda: self.parent.do_copy('Address', addr))
|
||||
menu.addAction(_("Copy Request"), lambda: self.parent.do_copy('URI', self.wallet.get_request_URI(addr)))
|
||||
if 'http_url' in req:
|
||||
menu.addAction(_("View in web browser"), lambda: webopen(req['http_url']))
|
||||
|
||||
# do bip70 only for browser access
|
||||
# so, each request should have an ID, regardless
|
||||
#menu.addAction(_("Save as BIP70 file"), lambda: self.parent.export_payment_request(addr))
|
||||
menu.addAction(_("Delete"), lambda: self.parent.delete_request(key))
|
||||
run_hook('receive_list_menu', menu, key)
|
||||
menu.exec_(self.viewport().mapToGlobal(position))
|
||||
|
||||
def create_menu_bitcoin_payreq(self, menu, addr):
|
||||
menu.addAction(_("Copy Address"), lambda: self.parent.do_copy('Address', addr))
|
||||
menu.addAction(_("Copy URI"), lambda: self.parent.do_copy('URI', self.wallet.get_request_URI(addr)))
|
||||
menu.addAction(_("Save as BIP70 file"), lambda: self.parent.export_payment_request(addr))
|
||||
menu.addAction(_("Delete"), lambda: self.parent.delete_payment_request(addr))
|
||||
run_hook('receive_list_menu', menu, addr)
|
||||
|
||||
def create_menu_ln_payreq(self, menu, payreq_key, req):
|
||||
menu.addAction(_("Copy Lightning invoice"), lambda: self.parent.do_copy('Lightning invoice', req))
|
||||
menu.addAction(_("Delete"), lambda: self.parent.delete_lightning_payreq(payreq_key))
|
||||
|
|
|
@ -1279,32 +1279,6 @@ class Abstract_Wallet(AddressSynchronizer):
|
|||
out['status'] = status
|
||||
if conf is not None:
|
||||
out['confirmations'] = conf
|
||||
# check if bip70 file exists
|
||||
rdir = config.get('requests_dir')
|
||||
if rdir:
|
||||
key = out.get('id', addr)
|
||||
path = os.path.join(rdir, 'req', key[0], key[1], key)
|
||||
if os.path.exists(path):
|
||||
baseurl = 'file://' + rdir
|
||||
rewrite = config.get('url_rewrite')
|
||||
if rewrite:
|
||||
try:
|
||||
baseurl = baseurl.replace(*rewrite)
|
||||
except BaseException as e:
|
||||
self.logger.info(f'Invalid config setting for "url_rewrite". err: {e}')
|
||||
out['request_url'] = os.path.join(baseurl, 'req', key[0], key[1], key, key)
|
||||
out['URI'] += '&r=' + out['request_url']
|
||||
out['index_url'] = os.path.join(baseurl, 'index.html') + '?id=' + key
|
||||
websocket_server_announce = config.get('websocket_server_announce')
|
||||
if websocket_server_announce:
|
||||
out['websocket_server'] = websocket_server_announce
|
||||
else:
|
||||
out['websocket_server'] = config.get('websocket_server', 'localhost')
|
||||
websocket_port_announce = config.get('websocket_port_announce')
|
||||
if websocket_port_announce:
|
||||
out['websocket_port'] = websocket_port_announce
|
||||
else:
|
||||
out['websocket_port'] = config.get('websocket_port', 9999)
|
||||
return out
|
||||
|
||||
def get_request_URI(self, addr):
|
||||
|
@ -1346,11 +1320,19 @@ class Abstract_Wallet(AddressSynchronizer):
|
|||
status = PR_INFLIGHT if conf <= 0 else PR_PAID
|
||||
return status, conf
|
||||
|
||||
def get_request(self, key, is_lightning):
|
||||
if not is_lightning:
|
||||
def get_request(self, key):
|
||||
from .simple_config import get_config
|
||||
config = get_config()
|
||||
if key in self.receive_requests:
|
||||
req = self.get_payment_request(key, {})
|
||||
else:
|
||||
req = self.lnworker.get_request(key)
|
||||
if not req:
|
||||
return
|
||||
if config.get('http_port', 8000):
|
||||
host = config.get('http_host', 'localhost')
|
||||
port = config.get('http_port', 8000)
|
||||
req['http_url'] = 'http://%s:%d/electrum/index.html?id=%s'%(host, port, key)
|
||||
return req
|
||||
|
||||
def receive_tx_callback(self, tx_hash, tx, tx_height):
|
||||
|
@ -1389,24 +1371,6 @@ class Abstract_Wallet(AddressSynchronizer):
|
|||
self.receive_requests[addr] = req
|
||||
self.storage.put('payment_requests', self.receive_requests)
|
||||
self.set_label(addr, message) # should be a default label
|
||||
|
||||
rdir = config.get('requests_dir')
|
||||
if rdir and amount is not None:
|
||||
key = req.get('id', addr)
|
||||
pr = paymentrequest.make_request(config, req)
|
||||
path = os.path.join(rdir, 'req', key[0], key[1], key)
|
||||
if not os.path.exists(path):
|
||||
try:
|
||||
os.makedirs(path)
|
||||
except OSError as exc:
|
||||
if exc.errno != errno.EEXIST:
|
||||
raise
|
||||
with open(os.path.join(path, key), 'wb') as f:
|
||||
f.write(pr.SerializeToString())
|
||||
# reload
|
||||
req = self.get_payment_request(addr, config)
|
||||
with open(os.path.join(path, key + '.json'), 'w', encoding='utf-8') as f:
|
||||
f.write(json.dumps(req))
|
||||
return req
|
||||
|
||||
def delete_request(self, key):
|
||||
|
@ -1427,14 +1391,7 @@ class Abstract_Wallet(AddressSynchronizer):
|
|||
def remove_payment_request(self, addr, config):
|
||||
if addr not in self.receive_requests:
|
||||
return False
|
||||
r = self.receive_requests.pop(addr)
|
||||
rdir = config.get('requests_dir')
|
||||
if rdir:
|
||||
key = r.get('id', addr)
|
||||
for s in ['.json', '']:
|
||||
n = os.path.join(rdir, 'req', key[0], key[1], key, key + s)
|
||||
if os.path.exists(n):
|
||||
os.unlink(n)
|
||||
self.receive_requests.pop(addr)
|
||||
self.storage.put('payment_requests', self.receive_requests)
|
||||
return True
|
||||
|
||||
|
|
|
@ -1,132 +0,0 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# Electrum - lightweight Bitcoin client
|
||||
# Copyright (C) 2015 Thomas Voegtlin
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person
|
||||
# obtaining a copy of this software and associated documentation files
|
||||
# (the "Software"), to deal in the Software without restriction,
|
||||
# including without limitation the rights to use, copy, modify, merge,
|
||||
# publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
# and to permit persons to whom the Software is furnished to do so,
|
||||
# subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be
|
||||
# included in all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
|
||||
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
# SOFTWARE.
|
||||
import threading
|
||||
import os
|
||||
import json
|
||||
from collections import defaultdict
|
||||
import asyncio
|
||||
from typing import Dict, List, Tuple, TYPE_CHECKING
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
try:
|
||||
from SimpleWebSocketServer import WebSocket, SimpleSSLWebSocketServer
|
||||
except ImportError:
|
||||
sys.exit("install SimpleWebSocketServer")
|
||||
|
||||
from . import bitcoin
|
||||
from .synchronizer import SynchronizerBase
|
||||
from .logging import Logger
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .network import Network
|
||||
from .simple_config import SimpleConfig
|
||||
|
||||
|
||||
request_queue = asyncio.Queue()
|
||||
|
||||
|
||||
class ElectrumWebSocket(WebSocket, Logger):
|
||||
|
||||
def __init__(self):
|
||||
WebSocket.__init__(self)
|
||||
Logger.__init__(self)
|
||||
|
||||
def handleMessage(self):
|
||||
assert self.data[0:3] == 'id:'
|
||||
self.logger.info(f"message received {self.data}")
|
||||
request_id = self.data[3:]
|
||||
asyncio.run_coroutine_threadsafe(
|
||||
request_queue.put((self, request_id)), asyncio.get_event_loop())
|
||||
|
||||
def handleConnected(self):
|
||||
self.logger.info(f"connected {self.address}")
|
||||
|
||||
def handleClose(self):
|
||||
self.logger.info(f"closed {self.address}")
|
||||
|
||||
|
||||
class BalanceMonitor(SynchronizerBase):
|
||||
|
||||
def __init__(self, config: 'SimpleConfig', network: 'Network'):
|
||||
SynchronizerBase.__init__(self, network)
|
||||
self.config = config
|
||||
self.expected_payments = defaultdict(list) # type: Dict[str, List[Tuple[WebSocket, int]]]
|
||||
|
||||
def make_request(self, request_id):
|
||||
# read json file
|
||||
rdir = self.config.get('requests_dir')
|
||||
n = os.path.join(rdir, 'req', request_id[0], request_id[1], request_id, request_id + '.json')
|
||||
with open(n, encoding='utf-8') as f:
|
||||
s = f.read()
|
||||
d = json.loads(s)
|
||||
addr = d.get('address')
|
||||
amount = d.get('amount')
|
||||
return addr, amount
|
||||
|
||||
async def main(self):
|
||||
# resend existing subscriptions if we were restarted
|
||||
for addr in self.expected_payments:
|
||||
await self._add_address(addr)
|
||||
# main loop
|
||||
while True:
|
||||
ws, request_id = await request_queue.get()
|
||||
try:
|
||||
addr, amount = self.make_request(request_id)
|
||||
except Exception:
|
||||
self.logger.exception('')
|
||||
continue
|
||||
self.expected_payments[addr].append((ws, amount))
|
||||
await self._add_address(addr)
|
||||
|
||||
async def _on_address_status(self, addr, status):
|
||||
self.logger.info(f'new status for addr {addr}')
|
||||
sh = bitcoin.address_to_scripthash(addr)
|
||||
balance = await self.network.get_balance_for_scripthash(sh)
|
||||
for ws, amount in self.expected_payments[addr]:
|
||||
if not ws.closed:
|
||||
if sum(balance.values()) >= amount:
|
||||
ws.sendMessage('paid')
|
||||
|
||||
|
||||
class WebSocketServer(threading.Thread):
|
||||
|
||||
def __init__(self, config: 'SimpleConfig', network: 'Network'):
|
||||
threading.Thread.__init__(self)
|
||||
self.config = config
|
||||
self.network = network
|
||||
asyncio.set_event_loop(network.asyncio_loop)
|
||||
self.daemon = True
|
||||
self.balance_monitor = BalanceMonitor(self.config, self.network)
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
asyncio.set_event_loop(self.network.asyncio_loop)
|
||||
host = self.config.get('websocket_server')
|
||||
port = self.config.get('websocket_port', 9999)
|
||||
certfile = self.config.get('ssl_chain')
|
||||
keyfile = self.config.get('ssl_privkey')
|
||||
self.server = SimpleSSLWebSocketServer(host, port, ElectrumWebSocket, certfile, keyfile)
|
||||
self.server.serveforever()
|
1
electrum/www
Submodule
1
electrum/www
Submodule
|
@ -0,0 +1 @@
|
|||
Subproject commit 538fa508d41512e670fb84970f821a5db71836d9
|
|
@ -375,15 +375,6 @@ if __name__ == '__main__':
|
|||
# run daemon
|
||||
init_plugins(config, 'cmdline')
|
||||
d = daemon.Daemon(config, fd)
|
||||
if config.get('websocket_server'):
|
||||
from electrum import websockets
|
||||
websockets.WebSocketServer(config, d.network)
|
||||
if config.get('requests_dir'):
|
||||
path = os.path.join(config.get('requests_dir'), 'index.html')
|
||||
if not os.path.exists(path):
|
||||
print("Requests directory not configured.")
|
||||
print("You can configure it using https://github.com/spesmilo/electrum-merchant")
|
||||
sys_exit(1)
|
||||
d.run_daemon()
|
||||
sys_exit(0)
|
||||
else:
|
||||
|
|
Loading…
Add table
Reference in a new issue