2018-11-03 23:50:34 +01:00
|
|
|
import asyncio
|
|
|
|
import logging
|
|
|
|
from aiohttp.web import Application, WebSocketResponse, json_response
|
|
|
|
from aiohttp.http_websocket import WSMsgType, WSCloseCode
|
2019-01-13 08:59:54 +01:00
|
|
|
|
|
|
|
from torba.client.util import satoshis_to_coins
|
|
|
|
from .node import Conductor, set_logging
|
2018-11-03 23:50:34 +01:00
|
|
|
|
|
|
|
|
|
|
|
PORT = 7954
|
|
|
|
|
|
|
|
|
|
|
|
class WebSocketLogHandler(logging.Handler):
|
|
|
|
|
|
|
|
def __init__(self, send_message):
|
|
|
|
super().__init__()
|
|
|
|
self.send_message = send_message
|
|
|
|
|
|
|
|
def emit(self, record):
|
|
|
|
try:
|
|
|
|
self.send_message({
|
|
|
|
'type': 'log',
|
|
|
|
'name': record.name,
|
|
|
|
'message': self.format(record)
|
|
|
|
})
|
|
|
|
except Exception:
|
|
|
|
self.handleError(record)
|
|
|
|
|
|
|
|
|
2018-11-04 06:55:50 +01:00
|
|
|
class ConductorService:
|
2018-11-03 23:50:34 +01:00
|
|
|
|
2018-11-04 00:45:28 +01:00
|
|
|
def __init__(self, stack: Conductor, loop: asyncio.AbstractEventLoop) -> None:
|
2018-11-03 23:50:34 +01:00
|
|
|
self.stack = stack
|
|
|
|
self.loop = loop
|
|
|
|
self.app = Application()
|
|
|
|
self.app.router.add_post('/start', self.start_stack)
|
|
|
|
self.app.router.add_post('/generate', self.generate)
|
|
|
|
self.app.router.add_post('/transfer', self.transfer)
|
|
|
|
self.app.router.add_post('/balance', self.balance)
|
|
|
|
self.app.router.add_get('/log', self.log)
|
|
|
|
self.app['websockets'] = set()
|
|
|
|
self.app.on_shutdown.append(self.on_shutdown)
|
|
|
|
self.handler = self.app.make_handler()
|
|
|
|
self.server = None
|
|
|
|
|
|
|
|
async def start(self):
|
|
|
|
self.server = await self.loop.create_server(
|
|
|
|
self.handler, '0.0.0.0', PORT
|
|
|
|
)
|
|
|
|
print('serving on', self.server.sockets[0].getsockname())
|
|
|
|
|
|
|
|
async def stop(self):
|
|
|
|
await self.stack.stop()
|
|
|
|
self.server.close()
|
|
|
|
await self.server.wait_closed()
|
|
|
|
await self.app.shutdown()
|
|
|
|
await self.handler.shutdown(60.0)
|
|
|
|
await self.app.cleanup()
|
|
|
|
|
|
|
|
async def start_stack(self, _):
|
2019-01-13 08:59:54 +01:00
|
|
|
set_logging(
|
|
|
|
self.stack.ledger_module, logging.DEBUG, WebSocketLogHandler(self.send_message)
|
|
|
|
)
|
2018-11-03 23:50:34 +01:00
|
|
|
self.stack.blockchain_started or await self.stack.start_blockchain()
|
2019-01-13 08:59:54 +01:00
|
|
|
self.send_message({'type': 'service', 'name': 'blockchain', 'port': self.stack.blockchain_node.port})
|
2018-11-03 23:50:34 +01:00
|
|
|
self.stack.spv_started or await self.stack.start_spv()
|
2019-01-13 08:59:54 +01:00
|
|
|
self.send_message({'type': 'service', 'name': 'spv', 'port': self.stack.spv_node.port})
|
2018-11-03 23:50:34 +01:00
|
|
|
self.stack.wallet_started or await self.stack.start_wallet()
|
2019-01-14 04:38:36 +01:00
|
|
|
self.send_message({'type': 'service', 'name': 'wallet', 'port': self.stack.wallet_node.port})
|
2018-11-03 23:50:34 +01:00
|
|
|
self.stack.wallet_node.ledger.on_header.listen(self.on_status)
|
|
|
|
self.stack.wallet_node.ledger.on_transaction.listen(self.on_status)
|
|
|
|
return json_response({'started': True})
|
|
|
|
|
|
|
|
async def generate(self, request):
|
|
|
|
data = await request.post()
|
|
|
|
blocks = data.get('blocks', 1)
|
|
|
|
await self.stack.blockchain_node.generate(int(blocks))
|
|
|
|
return json_response({'blocks': blocks})
|
|
|
|
|
|
|
|
async def transfer(self, request):
|
|
|
|
data = await request.post()
|
|
|
|
address = data.get('address')
|
|
|
|
if not address:
|
|
|
|
address = await self.stack.wallet_node.account.receiving.get_or_create_usable_address()
|
|
|
|
amount = data.get('amount', 1)
|
|
|
|
txid = await self.stack.blockchain_node.send_to_address(address, amount)
|
|
|
|
await self.stack.wallet_node.ledger.on_transaction.where(
|
|
|
|
lambda e: e.tx.id == txid and e.address == address
|
|
|
|
)
|
|
|
|
return json_response({
|
|
|
|
'address': address,
|
|
|
|
'amount': amount,
|
|
|
|
'txid': txid
|
|
|
|
})
|
|
|
|
|
|
|
|
async def balance(self, _):
|
|
|
|
return json_response({
|
|
|
|
'balance': await self.stack.blockchain_node.get_balance()
|
|
|
|
})
|
|
|
|
|
|
|
|
async def log(self, request):
|
2018-11-04 00:45:28 +01:00
|
|
|
web_socket = WebSocketResponse()
|
|
|
|
await web_socket.prepare(request)
|
|
|
|
self.app['websockets'].add(web_socket)
|
2018-11-03 23:50:34 +01:00
|
|
|
try:
|
2018-11-04 00:45:28 +01:00
|
|
|
async for msg in web_socket:
|
2018-11-03 23:50:34 +01:00
|
|
|
if msg.type == WSMsgType.TEXT:
|
|
|
|
if msg.data == 'close':
|
2018-11-04 00:45:28 +01:00
|
|
|
await web_socket.close()
|
2018-11-03 23:50:34 +01:00
|
|
|
elif msg.type == WSMsgType.ERROR:
|
2018-11-04 00:45:28 +01:00
|
|
|
print('web socket connection closed with exception %s' %
|
|
|
|
web_socket.exception())
|
2018-11-03 23:50:34 +01:00
|
|
|
finally:
|
2018-11-04 00:45:28 +01:00
|
|
|
self.app['websockets'].remove(web_socket)
|
|
|
|
return web_socket
|
2018-11-03 23:50:34 +01:00
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
async def on_shutdown(app):
|
2018-11-04 00:45:28 +01:00
|
|
|
for web_socket in app['websockets']:
|
|
|
|
await web_socket.close(code=WSCloseCode.GOING_AWAY, message='Server shutdown')
|
2018-11-03 23:50:34 +01:00
|
|
|
|
|
|
|
async def on_status(self, _):
|
|
|
|
if not self.app['websockets']:
|
|
|
|
return
|
|
|
|
self.send_message({
|
|
|
|
'type': 'status',
|
|
|
|
'height': self.stack.wallet_node.ledger.headers.height,
|
2019-01-13 08:59:54 +01:00
|
|
|
'balance': satoshis_to_coins(await self.stack.wallet_node.account.get_balance()),
|
2018-11-03 23:50:34 +01:00
|
|
|
'miner': await self.stack.blockchain_node.get_balance()
|
|
|
|
})
|
|
|
|
|
|
|
|
def send_message(self, msg):
|
2018-11-04 00:45:28 +01:00
|
|
|
for web_socket in self.app['websockets']:
|
2019-01-13 08:59:54 +01:00
|
|
|
self.loop.create_task(web_socket.send_json(msg))
|